從上一篇文章中我們了解到片元著色器是如何編寫的:
片元著色器
片元著色器shaderf.fsh
//傳遞過來的紋理坐標
varying lowp vec2 varyTextCoord;
// 紋理采樣器 (獲取對應的紋理ID)
uniform sampler2D colorMap;
void main() {
//將紋理顏色添加到對應的像素點上
gl_FragColor = texture2D(colorMap, varyTextCoord);
//返回值應該是一個vec4 即是RGBA--顏色值。
}
gl_FragColor GLSL內建變量 (賦值像素點顏色值)GLSL語言已經(jīng)提前定義好的變量,有相應的特殊含義。
內建函數(shù) GLSL提前封裝好的函數(shù)
texture2D(紋理采樣器,紋理坐標),獲取對應坐標紋素(讀取紋素,讀取每一個像素點的顏色值)。
我們知道sampler(采樣器)是GLSL提供的可供紋理對象使用的內建數(shù)據(jù),而且sampler通常實在片元著色器中內定義,被uniform修飾符修飾,表示這個變量是不會被修改的。
通過上面的代碼可以看到聲明sampler的類型還有一個sampler2D。這個只是代表一個二維的紋理類型。sampler1D,sampler2D,sampler3D 表示不同維度的紋理類型.
在上面的代碼中我們簡單聲明了一個紋理對象. uniform sampler2D,將一個紋理添加片元著色器中.
uniform sampler2D colorMap;
同時我們使用GLSL內建的texture函數(shù)來采樣紋理的顏色值.
gl_FragColor = texture2D(colorMap, varyTextCoord);
紋理單元
這里聲明的sampler2D變量是個uniform,我們卻沒有用glUniform給它賦值,一般來講我們需要用glUniform1i()函數(shù)進行將紋理對象(數(shù)據(jù))從CPU中傳入顯存中的著色器。之所以使用glUniform1i()函數(shù),是因為只需要給紋理采樣器傳入一個索引值(位置)即可,這樣我們就能夠在一個片元著色器中設置多個紋理。
那么這個索引值就是我們接下來要介紹的‘紋理單元’:
一個紋理的位置值通常稱為一個紋理單元(Texture Unit)。一個紋理的默認紋理單元是0,它是默認的激活紋理單元。紋理單元的主要目的是讓我們在著色器中可以使用多于一個的紋理。
如果我們只傳入一個紋理對象,那么倒是不用考慮紋理單元的問題。但是當有多個紋理對象要傳入的時候,我們必須指定紋理對象,然后在主函數(shù)用glUniform1i()函數(shù)將紋理對象一個一個綁定到著色器內部。
通過把紋理單元賦值給采樣器,我們可以一次綁定多個紋理,只要我們首先激活對應的紋理單元。就像glBindTexture一樣,我們可以使用管理ActiveTexture激活紋理單元,傳入我們需要使用的紋理單元。
//在綁定紋理之前先激活紋理單元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
激活紋理單元之后,接下來的glBindTexture函數(shù)調用會綁定這個紋理到當前激活的紋理單元,紋理單元GL_TEXTURE0默認總是被激活.
OpenGL提供有16個紋理單元供我們使用,也就是說我們可以激活從GL_TEXTURE0到GL_TEXTRUE15。它們都是按順序定義的,所以我們也可以通過GL_TEXTURE0 + 8的方式獲得GL_TEXTURE8,這在當我們需要循環(huán)一些紋理單元的時候會很有用。
紋理混合
GLSL內建的mix函數(shù)會將兩個紋理進行結合并輸出最終顏色值。
varying vec2 TexCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
void main()
{
gl_FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}
genType mix (genType x, genType y, float a)
返回線性混合的x和y,如:x?(1?a)+y?a
mix函數(shù)需要接受兩個值作為參數(shù),并對它們根據(jù)第三個參數(shù)進行線性插值(線性插值是一種針對一維數(shù)據(jù)的插值方法,它根據(jù)一維數(shù)據(jù)序列中需要插值的點的左右鄰近兩個數(shù)據(jù)點來進行數(shù)值的估計。當然了它不是求這兩個點數(shù)據(jù)大小的平均值(當然也有求平均值的情況),而是根據(jù)到這兩個點的距離來分配它們的比重的)。
例如:如果第三個值是0.0,它會返回第一個輸入;如果是1.0,會返回第二個輸入值。輸入0.2則會返回80%的第一個輸入顏色和20%的第二個輸入顏色,即返回兩個紋理的混合色。
設置多個紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0); // 手動設置
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);
其實,使用glUniform1i()函數(shù)作為著色器內部和程序來進行傳入值,需要知道兩個參數(shù),一個是在著色器內部接受信息的對象為位置。一個是外界的數(shù)據(jù)對象,嚴格來講傳入數(shù)據(jù)本身也不是這個函數(shù)做的,這個函數(shù)只是告訴著色器那個紋理對象對應哪個采樣器對象。注意,我們使用glUniform1i設置uniform采樣器的位置值,或者說紋理單元。通過glUniform1i的設置,我們保證每個uniform采樣器對應著正確的紋理單元

重點介紹一下glTexImage2D函數(shù):
glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels)
參數(shù)1:紋理模式(綁定紋理對象的種類),GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
參數(shù)2:加載的層次,一般設置為0, 0表示沒有進行縮小的原始圖片等級。
參數(shù)3:紋理的顏色值GL_RGBA, 表示了紋理所采用的內部格式,內部格式是我們的像素數(shù)據(jù)在顯卡中存儲的格式,這里的GL_RGB顯然就表示紋理中像素的顏色值是以RGB的格式存儲的。
參數(shù)4:紋理的寬
參數(shù)5:紋理的高
參數(shù)6:border,邊界寬度,通常為0.
參數(shù)7:format(描述了像素在內存中的存儲格式)
參數(shù)8:type(描述了像素在內存中的數(shù)據(jù)類型)
參數(shù)9:紋理數(shù)據(jù)
紋理翻轉
在使用OpenGL函數(shù)加載紋理到圖形時,經(jīng)常遇到紋理上下顛倒的問題。原因是因為OpenGL要求紋理坐標原點(0,0)在左下角。

- 利用旋轉矩陣翻轉圖形,不翻轉紋理
即讓圖形的頂點坐標旋轉180度,而紋理坐標保持不變。
//rotate等于shaderv.vsh中的uniform屬性,rotateMatrix
GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
//獲取渲旋轉的弧度
float radians = 180 * 3.14159f / 180.0f;
//求得弧度對于的sin\cos值
float s = sin(radians);
float c = cos(radians);
GLfloat zRotation[16] = {
c, -s, 0, 0,
s, c, 0, 0,
0, 0, 1.0, 0,
0.0, 0, 0, 1.0
};
//設置旋轉矩陣
glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
/*
glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
location : 對于shader 中的ID
count : 個數(shù)
transpose : 轉置
value : 指針
*/
2.解壓圖片時,將圖片源文件翻轉
//將 UIImage 轉換為 CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
//讀取圖片的大小,寬和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
//獲取圖片字節(jié)數(shù) 寬*高*4(RGBA)
GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
//創(chuàng)建上下文
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
//在CGContextRef上,將圖片繪制出來
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
CGContextDrawImage(spriteContext, rect, spriteImage);
//釋放上下文
CGContextRelease(spriteContext);
//綁定紋理到默認的紋理ID
glBindTexture(GL_TEXTURE_2D, 0);
CGContextDrawImage 使用的是Core Graphics框架,坐標系與UIKit 不一樣。UIKit框架的原點在屏幕的左上角,Core Graphics框架的原點在屏幕的左下角。
CGContextDrawImage
參數(shù)1:繪圖上下文
參數(shù)2:rect坐標
參數(shù)3:繪制的圖片
3.修改片元著色器的紋理坐標
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
//gl_FragColor = texture2D(colorMap, varyTextCoord);
gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0-varyTextCoord.y));
//因為紋理坐標的范圍是0-1,所以翻轉的話都統(tǒng)一用1去減
}
4.修改頂點著色器紋理坐標
attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main()
{
//varyTextCoord = textCoordinate;
varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
gl_Position = position;
//因為紋理坐標的范圍是0-1,所以翻轉的話都統(tǒng)一用1去減
}
同時也可以在頂點著色器中直接翻轉頂點坐標:
attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main()
{
varyTextCoord = textCoordinate;
//varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
gl_Position = vec4(position.x,-position.y,position.z,1.0f);
}
//在翻轉頂點時,就不是直接對Y值用1去減,因為頂點的取值范圍是-1 - 1 ,所以我們直接加上負號做翻轉即可
5.直接從源紋理坐標數(shù)據(jù)修改
//原始坐標
GLfloat attrArr[] =
{
0.5f, -0.5f, 0.0f, 1.0f, 1.0f, //右下
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // 左下
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // 右上
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, // 左上
0.5f, -0.5f, 0.0f, 1.0f, 1.0f, // 右下
};
//更改后的坐標
GLfloat attrArr[] =
{
0.5f, -0.5f, -1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -1.0f, 1.0f, 0.0f,
};
翻轉前效果圖:

文中部分內容參考:CC老師 http://www.itdecent.cn/p/848d982db9f2