上一篇我們通過定義的頂點(diǎn)坐標(biāo)繪制了一個(gè)三角形山害,通過設(shè)置顏色值秘蛔,改變了三角形的渲染顏色偎窘。在實(shí)例化GLKBaseEffect對(duì)象的時(shí)候蝇闭,我們說到GLKit提供GLKBaseEffect是為了在需要的時(shí)候自動(dòng)地構(gòu)建GPU程序零如,而無需我們編寫著色器的代碼躏将。對(duì)于一些簡(jiǎn)單的圖形,我們使用GLKit提供GLKBaseEffect完全可以達(dá)到渲染效果考蕾。如果要實(shí)現(xiàn)比較復(fù)雜的動(dòng)畫效果祸憋,比如后面會(huì)學(xué)習(xí)的矩陣變換、燈光肖卧、物理引擎和粒子效果...都會(huì)自定義著色器來實(shí)現(xiàn)比較方便蚯窥。
我們先看一下OpenGL的渲染流程:可以看出圖形管線包含很多部分:先接受一組3D坐標(biāo),然后經(jīng)過圖元裝配 -- 幾何著色器 -- 光柵化 -- 片段著色器 -- 最后測(cè)試混合才轉(zhuǎn)變?yōu)槟闫聊簧系挠猩?D像素輸出塞帐。圖形渲染管線的每個(gè)階段將會(huì)把前一個(gè)階段的輸出作為這個(gè)階段的輸入拦赠。所有這些階段都是高度專門化的(它們都有一個(gè)特定的函數(shù)),并且很容易并行執(zhí)行葵姥。正是由于它們具有并行執(zhí)行的特性荷鼠,當(dāng)今大多數(shù)顯卡都有成千上萬的小處理核心,它們?cè)贕PU上為每一個(gè)(渲染管線)階段運(yùn)行各自的小程序榔幸,從而在圖形渲染管線中快速處理你的數(shù)據(jù)允乐。這些小程序叫做著色器(Shader)。
注意上圖中的藍(lán)色階段代表我們可以注入自定義的著色器削咆,這就允許我們用自己寫的著色器來替換默認(rèn)的牍疏。這樣我們就可以更細(xì)致地控制圖形渲染管線中的特定部分了,而且因?yàn)樗鼈冞\(yùn)行在GPU上拨齐,所以它們可以給我們節(jié)約寶貴的CPU時(shí)間鳞陨。
圖形管線非常復(fù)雜,它包含很多可配置的部分瞻惋。然而厦滤,對(duì)于大多數(shù)場(chǎng)合,我們只需要配置頂點(diǎn)和片段著色器就行了歼狼。幾何著色器是可選的馁害,通常使用它默認(rèn)的著色器就行了。
所以這里主要說的是兩個(gè)可以自定義的著色器蹂匹,也就是頂點(diǎn)著色器和片段著色器。
先編寫一個(gè)頂點(diǎn)著色器:
attribute vec4 position;
attribute vec4 color;
varying vec4 fColor;
void main(void) {
fColor = color;
gl_Position = position;
}
簡(jiǎn)要說明:
前面三行聲明變量,前面兩個(gè)為attribute關(guān)鍵字類型變量履植,后面一個(gè)是varying類型计雌。
attribute:聲明的是頂點(diǎn)數(shù)據(jù)屬性,其中position是接受位置坐標(biāo)玫霎、color是接受顏色值凿滤。一般情況下應(yīng)用中頂點(diǎn)數(shù)組傳遞數(shù)據(jù)到頂點(diǎn)著色器內(nèi)都需要定義attribute關(guān)鍵字的屬性來接受,然后在著色器內(nèi)部可以對(duì)這些數(shù)據(jù)進(jìn)行處理庶近。
varying:與attribute一樣都是定義屬性的關(guān)鍵字翁脆。不同的是varying關(guān)鍵字定義的屬性是要傳遞給片段著色器的變量。因?yàn)槠沃魇菬o法直接接受CPU傳遞過來的數(shù)據(jù)鼻种。
所以上面的attribute vec4 position和attribute vec4 color是定義為接受頂點(diǎn)數(shù)組傳遞過來的坐標(biāo)和顏色值的反番。
而varying vec4 fColor是定義為傳遞到片段著色器的變量。
再看一下數(shù)據(jù)類型:
vec4:其實(shí)還有vec2叉钥、vec3罢缸。分別代表二維、三維投队、四維向量枫疆。
這里定義的position、color和fColor是四維向量敷鸦。
總結(jié)一下:
頂點(diǎn)著色器中屬性的定義格式為:變量類型 變量數(shù)據(jù)類型 變量名(attribute vec4 position)
其中變量類型有三種(除了上面介紹的兩種息楔,還有兩外一種):
attribute:接受頂點(diǎn)數(shù)據(jù)的變量,相當(dāng)于輸入變量
varying:傳遞到片段著色器的變量轧膘,相當(dāng)于輸出變量
uniform:相當(dāng)于全局變量
變量數(shù)據(jù)類型:
vec開頭的:vec2钞螟、vec3、vec4代表二維谎碍、三維鳞滨、四維向量
float:浮點(diǎn)數(shù)。在著色器中沒有數(shù)據(jù)類型轉(zhuǎn)換蟆淀,所以定義的flozt必須寫成浮點(diǎn)數(shù)格式拯啦。比如0需要寫成0.0、1寫成1.0
int:整形
mat:mat開頭的有mat2熔任、mat3褒链、mat4分別代表二維、三維疑苔、四維矩陣甫匹。主要用來傳遞變換矩陣。
再來看一下片段著色器:
precision mediump float;
varying lowp vec4 fColor;
void main(void) {
gl_FragColor = fColor;
}
與頂點(diǎn)著色器不同,在片段著色器中的變量只有varying和uniform類型的兵迅,其中varying是從頂點(diǎn)著色器傳遞過來的抢韭。看一下頂點(diǎn)著色器恍箭,里面定義的varying vec4 fColor就是需要傳遞到片段著色器的刻恭,而在這里就需要定義varying lowp vec4 fColor來接受,變量名必須相同扯夭。另外還有一點(diǎn)鳍贾,對(duì)比一下varying vec4 fColor和varying lowp vec4 fColor就可以看出,在片段著色器中定義的接受屬性多了lowp交洗。這是修飾變量精度骑科,在片段著色器中定義的所有變量都需要聲明精度。
精度包含三種:lowp藕筋、highp纵散、mediump分別是低精度、中等精度隐圾、高精度伍掀。
還有一點(diǎn)不同的是precision mediump float,這是統(tǒng)一精度聲明暇藏。如果在第一行做了統(tǒng)一精度聲明的話蜜笤,后面就不需要每個(gè)變量都聲明了。當(dāng)然這里這是為float指定了mediump精度盐碱,如果要為其他類型指定精度的話加上就可以了把兔,比如: precision lowp vec4。
通過上面幾步已經(jīng)寫好了頂點(diǎn)著色器和片段著色器瓮顽,但這些知識(shí)源碼县好,并沒有應(yīng)用到程序中進(jìn)行數(shù)據(jù)處理的能力。所以需要?jiǎng)討B(tài)編譯生成著色器對(duì)象然后鏈接到著色器程序上暖混。我們一步步來缕贡。
1.加載文件內(nèi)容
NSString *vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader" ofType:@"glsl"];
NSString *fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader" ofType:@"glsl"];
NSString *vertexContext = [NSString stringWithContentsOfFile:vertexShaderPath encoding:NSUTF8StringEncoding error:nil];
NSString *fragmentContext = [NSString stringWithContentsOfFile:fragmentShaderPath encoding:NSUTF8StringEncoding error:nil];
2.創(chuàng)建指定類型著色器對(duì)象
參數(shù):
GL_VERTEX_SHADER:頂點(diǎn)著色器枚舉
GL_FRAGMENT_SHADER:片段著色器枚舉
GLuint shader = glCreateShader(shaderType);
3.著色器對(duì)象加載著色器源碼
// 將著色器源碼加載到著色器對(duì)象上
glShaderSource(shader, 1, &vertexContext.UTF8String, NULL);
4.運(yùn)行時(shí)編譯shader
glCompileShader(shader);
5.檢查編譯狀態(tài)
GLint logLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
printf("Shader compile log:\n%s", log);
printf("Shader: \n %s\n", source);
free(log);
}
6.創(chuàng)建一個(gè)著色器程序?qū)ο?GLuint program = glCreateProgram();
7.鏈接兩個(gè)著色器對(duì)象
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
8.鏈接程序
glLinkProgram(programHandle);
9.刪除著色器對(duì)象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
最后生成的program就是具備數(shù)據(jù)處理能力的著色器程序了,可以理解為在GPU上運(yùn)行的程序拣播。
關(guān)于著色器語法和著色器的編譯連接在這里先告一段落晾咪,接下來我們使用自定義的著色器程序來繪制圖形。
先定義頂點(diǎn)坐標(biāo)贮配,上篇文章畫的三角形谍倦,這里我們就畫一個(gè)矩陣。
OpenGL 2.0以后的基本圖元包含點(diǎn)泪勒、線昼蛀、線帶宴猾、三角形...并沒有矩形,所以這里繪制矩形需要多個(gè)三角形來合成曹洽。
/*
^
|
(-0.5,0.5) | (0.5,0.5)
o | o
|
|
------------------------------>
|
(-0.5,-0.5) | (0.5,-0.5)
o | o
|
|
*/
四個(gè)頂點(diǎn)鳍置,繪制兩個(gè)三角形就可以合成一個(gè)矩形。
注意:
頂點(diǎn)數(shù)組我們就需要定義兩個(gè)三角形的坐標(biāo)送淆,而不是4個(gè)頂點(diǎn)坐標(biāo):
static GLfloat vertices[] = {
// 第一個(gè)三角形
0.5, 0.5f, 0.0, 1.0 , 0.0, 0.0,
0.5f, -0.5f, 0.0, 1.0 , 0.0, 0.0,
-0.5f, -0.5f, 0.0, 1.0 , 0.0, 0.0,
// 第二個(gè)三角形
-0.5, -0.5f, 0.0, 1.0 , 0.0, 0.0,
-0.5f, 0.5f, 0.0, 1.0 , 0.0, 0.0,
0.5f, 0.5f, 0.0, 1.0 , 0.0, 0.0,
};
給頂點(diǎn)著色器屬性賦值:
還是利用glEnableVertexAttribArray來激活著色器中的兩個(gè)屬性position和color,然后利用glGetAttribLocation函數(shù)獲取各自的位置信息:
在這之前有一步需要做:使用program
// 調(diào)用glUseProgram函數(shù)使用這個(gè)shader
glUseProgram(program);
// position
GLuint positionAttribLocation = glGetAttribLocation(program, "position");
glEnableVertexAttribArray(positionAttribLocation);
// color
GLuint colorAttribLocation = glGetAttribLocation(program, "color");
glEnableVertexAttribArray(colorAttribLocation);
獲取到position和color的location之后使用glVertexAttribPointer函數(shù)進(jìn)行數(shù)據(jù)傳遞:
// 參數(shù)就不做解釋了怕轿,前面都有說過偷崩。取顏色值需要偏離三個(gè)位置
glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)vertices);
glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)vertices + 3 * sizeof(GLfloat));
// 然后進(jìn)行繪制
glDrawArrays(GL_TRIANGLES, 0, 6);
最后效果:附上本例源碼:LearningOpenGL ES GitHub