今天博客的主要有以下內(nèi)容:
- shader是什么
- shader 編程
- shader 內(nèi)建變量
- shader 如何編譯shader
- shader 與app直接的傳值交互
shader是什么
shader
著色器
筒饰,是一種較為簡短的程序片段撬码,用于告訴圖形軟件如何計算和輸出圖像稚字。shader主要分兩類:Vertex Shader(頂點著色器)
和Fragment Shader(片段著色器)
渲染管線
又叫渲染流水線
,是顯示芯片內(nèi)部處理圖形信號
相互獨立
的并行
處理單元。一個流水線是一序列可以并行
和按照固定順序
進(jìn)行的階段,遵循著前入后出
的流程。類比來說,就像是一個工廠怪嫌,同時生產(chǎn)寶馬和賓利兩種汽車,這兩款汽車的每個零件又是同時制作的柳沙,就可以說這個車間內(nèi)有兩個不同的渲染管線
我們根據(jù)上圖進(jìn)行說明岩灭。3D軟件或是游戲在運行的時候,會調(diào)用圖形API赂鲤,OpenGL或者是DirectX噪径。而頂點著色器和片段著色器就在圖中GPU運算框選部產(chǎn)生作用
上圖就是針對到unity引擎中。Geometry部分可以理解為建模数初,這個過程就是把Mesh數(shù)據(jù)導(dǎo)入到unity之中找爱,unity引擎再調(diào)用圖形API,而調(diào)用圖形API的過程就是在驅(qū)動GPU進(jìn)行處理運算泡孩。
進(jìn)入到GPU后车摄,首先進(jìn)行的是頂點處理程序(Vertex Processor)對應(yīng)頂點shader,頂點運算的結(jié)果會傳遞給像素處理器(Pixel Processor相當(dāng)于片段處理器)仑鸥,其對應(yīng)的便是像素shader(也就是片段shader)吮播,最后輸出可以在屏幕上顯示的像素信息,即Frame Buffer(幀緩沖)眼俊,F(xiàn)rame Buffer不僅可以儲存顏色信息還可以儲存深度值意狠。
shader 材質(zhì) 貼圖
著色器實際就是一小段程序,他負(fù)責(zé)將輸入的頂點數(shù)據(jù)以指定的方式和輸入的貼圖或者顏色等組合起來泵琳,然后進(jìn)行輸出摄职。繪圖單元將圖像繪制到屏幕上誊役。
輸入的貼圖或者顏色等获列,加上對應(yīng)的shader,以及對shader的特定的參數(shù)設(shè)置蛔垢,將這些內(nèi)容(shader及輸入?yún)?shù))打包儲存在一起击孩,得到的就是一個Material(材質(zhì))
,這些包里其實還有其他東西比如向量鹏漆、矩陣巩梢。之后创泄,我們便將材質(zhì)賦予到三維模型上進(jìn)行渲染(輸出)了
材質(zhì),就像是游戲引擎最終使用的商品括蝠,shader就好比是生產(chǎn)這種商品的加工方法鞠抑,而貼圖則是這商品的原材料。
如果我們不用Unity或是其他引擎的話忌警,實現(xiàn)材質(zhì)就需要利用OpenGL或是DirectX的API的調(diào)用搁拙,手動組織出個shader,這樣就很麻煩法绵,所以引擎提供了便捷箕速。
我們在ios上開發(fā),沒有unity 那樣的引擎.這里我們需要自己封裝材質(zhì).
shader 編程
OpenGL著色語言(OpenGL Shading Language)又叫GLSL,是用來在OpenGL中著色編程的語言朋譬,是一種面向過程的語言盐茎,基本的語法和C/C++基本相同,他們是在圖形卡的GPU (Graphic Processor Unit圖形處理單元)上執(zhí)行的徙赢,代替了固定的渲染管線的一部分字柠,使渲染管線中不同層次具有可編程性。比如:視圖轉(zhuǎn)換狡赐、投影轉(zhuǎn)換等募谎。GLSL(GL Shading Language)的著色器代碼分成2個部分:Vertex Shader(頂點著色器)和Fragment(片斷著色器)。
數(shù)據(jù)類型
數(shù)據(jù)類型主要分為標(biāo)量
阴汇、向量
数冬、矩陣
、采樣器
搀庶、結(jié)構(gòu)體
拐纱、數(shù)組
、空類型
七種類型.
標(biāo)量
標(biāo)量
表示的是只有大小沒有方向的量
,在GLSL中標(biāo)量只有bool
、int
和float
三種户辞。對于int墩邀,和Java一樣,可以寫為十進(jìn)制(16)帕识、八進(jìn)制(020)或者十六進(jìn)制(0x10)。對于標(biāo)量的運算,我們最需要注意的是精度缭黔,防止溢出問題。
向量
向量我們可以看做是數(shù)組蒂破,在GLSL通常用于儲存顏色
馏谨、坐標(biāo)
等數(shù)據(jù),針對維數(shù)附迷,可分為二維惧互、三維和四維向量
哎媚。針對存儲的標(biāo)量類型,可以分為bool喊儡、int和float
拨与。共有vec2、vec3艾猜、vec4截珍,ivec2、ivec3箩朴、ivec4岗喉、bvec2、bvec3和bvec4
九種類型炸庞,數(shù)組代表維數(shù)钱床、i表示int類型、b表示bool類型
埠居。需要注意的是查牌,GLSL中的向量表示豎向量,所以與矩陣相乘進(jìn)行變換時滥壕,矩陣在前纸颜,向量在后(與DirectX正好相反)
。向量在GPU中由硬件支持運算绎橘,比CPU快的多胁孙。作為顏色向量時,用rgba表示分量
称鳞,就如同取數(shù)組的中具體數(shù)據(jù)的索引值涮较。三維顏色向量就用rgb表示分量
。比如對于顏色向量vec4 color冈止,color[0]和color.r都表示color向量的第一個值狂票,也就是紅色的分量。其他相同熙暴。作為位置向量時闺属,用xyzw表示分量
,xyz分別表示xyz坐標(biāo)周霉,w表示向量的模掂器。三維坐標(biāo)向量為xyz表示分量,二維向量為xy表示分量诗眨。作為紋理向量時唉匾,用stpq表示分量
,三維用stp表示分量匠楚,二維用st表示分量巍膘。
矩陣
在GLSL中矩陣擁有22、33芋簿、4*4三種類型的矩陣
峡懈,分別用mat2、mat3与斤、mat4
表示肪康。我們可以把矩陣看做是一個二維數(shù)組,也可以用二維數(shù)組下表的方式取里面具體位置的值撩穿。
采樣器
采樣器是專門用來對紋理進(jìn)行采樣工作的
磷支,一個采樣器變量表示一副或者一套紋理貼圖
。所謂的紋理貼圖可以理解為我們看到的物體上的皮膚食寡。
結(jié)構(gòu)體
和C語言中的結(jié)構(gòu)體相同雾狈,用struct來定義結(jié)構(gòu)體,關(guān)于結(jié)構(gòu)體參考C語言中的結(jié)構(gòu)體抵皱。
數(shù)組
數(shù)組知識也和Java中類似善榛。
空類型
空類型用void表示,僅用來聲明不返回任何值得函數(shù)呻畸。
類型 | 含義 |
---|---|
float&bool&int | 基本數(shù)據(jù)類型 |
vec2 | 包含了2個浮點數(shù)的向量 |
vec3 | 包含了3個浮點數(shù)的向量 |
vec4 | 包含了4個浮點數(shù)的向量 |
ivec2 | 包含了2個整數(shù)的向量 |
ivec3 | 包含了3個整數(shù)的向量 |
ivec4 | 包含了4個整數(shù)的向量 |
bvec2 | 包含了2個布爾數(shù)的向量 |
bvec3 | 包含了3個布爾數(shù)的向量 |
bvec4 | 包含了4個布爾數(shù)的向量 |
mat2 | 2*2維矩陣 |
mat3 | 3*3維矩陣 |
mat4 | 4*4維矩陣 |
sampler1(2,3)D | 1(2,3)D紋理采樣器 |
sampler1(2,3)DShadow | 1(2,3)D深度紋理句柄 |
sampler1(2,3)DRect | |
sampler1(2,3)DRectShadow | |
samplerCube |
數(shù)據(jù)聲明
VS FS代表是否在vertex shader和fragment shader可用
名稱 | 含義 | VS | FS |
---|---|---|---|
const | 只讀常量 | True | True |
attribute | 每個頂點數(shù)據(jù)的鏈接的數(shù)據(jù) | True | False |
varying | 頂點著色器和片段著色器之間的鏈接數(shù)據(jù) | True | True |
uniform | 著色器統(tǒng)一值 | True | True |
基本語法
運算符
優(yōu)先級(越小越高) | 運算符 | 說明 | 結(jié)合性 |
---|---|---|---|
1 | () | 聚組:a*(b+c) | N/A |
2 | [] () . ++ -- | 數(shù)組下標(biāo)__[],方法參數(shù)__fun(arg1,arg2,arg3),屬性訪問a.b,自增/減后綴a++ a-- | L - R |
3 | ++ -- + - ! | 自增/減前綴++a --a,正負(fù)號(一般正號不寫)a ,-a,取反!false | R - L |
4 | * / | 乘除數(shù)學(xué)運算 | L - R |
5 | + - | 加減數(shù)學(xué)運算 | L - R |
7 | < > <= >= | 關(guān)系運算符 | L - R |
8 | == != | 相等性運算符 | L - R |
9 | && | 邏輯與 | L - R |
10 | ^^ | 邏輯排他或(用處基本等于!=) | L - R |
11 | || | 邏輯或 | L - R |
12 | ? : | 三目運算符 | L - R |
13 | = += -= *= /= | 賦值與復(fù)合賦值 | L - R |
14 | , | 順序分配運算 | L - R |
ps 左值與右值:
左值:表示一個儲存位置,可以是變量,也可以是表達(dá)式,但表達(dá)式最后的結(jié)果必須是一個儲存位置.
右值:表示一個值, 可以是一個變量或者表達(dá)式再或者純粹的值.
操作符的優(yōu)先級:決定含有多個操作符的表達(dá)式的求值順序移盆,每個操作的優(yōu)先級不同.
操作符的結(jié)合性:決定相同優(yōu)先級的操作符是從左到右計算,還是從右到左計算伤为。
和我們平時接觸的運算符大多數(shù)是一樣的
類型轉(zhuǎn)換
比如float a=1;就是一種錯誤的寫法咒循,必須嚴(yán)格的寫成float a=1.0,也不可以強制轉(zhuǎn)換绞愚,即float a=(float)1;也是錯誤的寫法剑鞍,但是可以用內(nèi)置函數(shù)來進(jìn)行轉(zhuǎn)換,如float a=float(1);還有float a=float(true);(true為1.0爽醋,false為0.0)等蚁署,值得注意的是,低精度的int不能轉(zhuǎn)換為低精度的float蚂四。
shader 對值要求嚴(yán)格
流程控制
GLSL中的流程控制與大多數(shù)語言中基本相同光戈,主要有:
if(){}、if(){}else{}遂赠、if(){}else if(){}else{}
while(){}和do{}while()
for(;;){}
break和continue
這里需要注意的是片段著色器中有一種特殊的控制流discard. 使用discard會退出片段著色器久妆,不執(zhí)行后面的片段著色操作。片段也不會寫入幀緩沖區(qū)跷睦。
for (l = 0; l < numLights; l++)
{
if (!lightExists[l]);
continue;
color += light[l];
}
...
while (i < num)
{
sum += color[i];
i++;
}
...
do{
color += light[lightNum];
lightNum--;
}while (lightNum > 0)
...
if (true)
discard;
變量運算舉例
glsl中,沒有隱式類型轉(zhuǎn)換,原則上glsl要求任何表達(dá)式左右兩側(cè)(l-value),(r-value)的類型必須一致 也就是說以下表達(dá)式都是錯誤的:
int a =2.0; //error,r-value為float 而 lvalue 為int. error
int a =1.0+2; //error
float a =2;//error
float a =2.0+1; //error
bool a = 0; //error
vec3 a = vec3(1.0, 2.0, 3.0) * 2; //error
float與float , int與int之間是可以直接運算的,但float與int不行.它們需要進(jìn)行一次顯示轉(zhuǎn)換.即要么把float轉(zhuǎn)成int: int(1.0) ,要么把int轉(zhuǎn)成float: float(1) ,以下表達(dá)式都是正確的:
int a=int(2.0);
float a= float(2);
int a=int(2.0)*2 + 1;
float a= float(2)*6.0+2.3;
float 與 vec(向量) mat(矩陣):
vec,mat這些類型其實是由float復(fù)合而成的,當(dāng)它們與float運算時,其實就是在每一個分量上分別與float進(jìn)行運算,這就是所謂的逐分量運算.glsl里 大部分涉及vec,mat的運算都是逐分量運算,但也并不全是.
逐分量運算是線性的,這就是說 vec 與 float 的運算結(jié)果是還是 vec.
vec3 a = vec3(1.0, 2.0, 3.0);
mat3 m = mat3(1.0);
float s = 10.0;
vec3 b = s * a; // vec3(10.0, 20.0, 30.0)
vec3 c = a * s; // vec3(10.0, 20.0, 30.0)
mat3 m2 = s * m; // = mat3(10.0)
mat3 m3 = m * s; // = mat3(10.0)
vec(向量) 與 vec(向量):
兩向量間的運算首先要保證操作數(shù)的階數(shù)都相同
.否則不能計算.例如: vec3*vec2 vec4+vec3 等等都是不行的.
它們的計算方式是兩操作數(shù)在同位置上的分量分別進(jìn)行運算,其本質(zhì)還是逐分量進(jìn)行的,這和上面所說的float類型的 逐分量運算可能有一點點差異,相同的是 vec 與 vec 運算結(jié)果還是 vec, 且階數(shù)不變.
vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(0.1, 0.2, 0.3);
vec3 c = a + b; // = vec3(1.1, 2.2, 3.3)
vec3 d = a * b; // = vec3(0.1, 0.4, 0.9)
vec(向量) 與 mat(矩陣):
要保證操作數(shù)的階數(shù)相同
,且vec與mat間只存在乘法運算
.
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = m * v; // = vec2(1. * 10. + 3. * 20., 2. * 10. + 4. * 20.)
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = v * m; // = vec2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)
其實就是線性代數(shù)的計算啦
mat(矩陣) 與 mat(矩陣):
要保證操作數(shù)的階數(shù)相同
.
mat2 a = mat2(1., 2., 3., 4.);
mat2 b = mat2(10., 20., 30., 40.);
mat2 c = a * b; //mat2(1.*10.+3.*20.,2.*10.+4.*20.,1.* 30.+3.*40.,2.* 30.+4.*40.);
mat2 d = a+b;//mat2(1.+10.,2.+20.,3.+30.,4.+40);
變量限定符
修飾符 | 說明 |
---|---|
none | (默認(rèn)的可省略)本地變量,可讀可寫,函數(shù)的輸入?yún)?shù)既是這種類型 |
const | 聲明變量或函數(shù)的參數(shù)為只讀類型 |
attribute | 只能存在于vertex shader中,一般用于保存頂點或法線數(shù)據(jù),它可以在數(shù)據(jù)緩沖區(qū)中讀取數(shù)據(jù) |
uniform | 在運行時shader無法改變uniform變量, 一般用來放置程序傳遞給shader的變換矩陣筷弦,材質(zhì),光照參數(shù)等等. |
varying | 主要負(fù)責(zé)在vertex 和 fragment 之間傳遞變量 |
const:
和C語言類似,被const限定符修飾的變量初始化后不可變,除了局部變量,函數(shù)參數(shù)也可以使用const修飾符.但要注意的是結(jié)構(gòu)變量可以用const修飾, 但結(jié)構(gòu)中的字段不行.
const變量必須在聲明時就初始化
const vec3 v3 = vec3(0.,0.,0.)
局部變量只能使用const限定符.
函數(shù)參數(shù)只能使用const限定符.
struct light {
vec4 color;
vec3 pos;
//const vec3 pos1; //結(jié)構(gòu)中的字段不可用const修飾會報錯.
};
const light lgt = light(vec4(1.0), vec3(0.0)); //結(jié)構(gòu)變量可以用const修飾
attribute:
attribute變量是全局且只讀的,它只能在vertex shader中使用,只能與浮點數(shù),向量或矩陣變量組合, 一般attribute變量用來放置程序傳遞來的模型頂點,法線,顏色,紋理等數(shù)據(jù)它可以訪問數(shù)據(jù)緩沖區(qū)
attribute vec4 a_Position;
uniform
uniform變量是全局且只讀的,在整個shader執(zhí)行完畢前其值不會改變,他可以和任意基本類型變量組合, 一般我們使用uniform變量來放置外部程序傳遞來的環(huán)境數(shù)據(jù)(如點光源位置,模型的變換矩陣等等) 這些數(shù)據(jù)在運行中顯然是不需要被改變的.
uniform vec4 lightPosition;
varying
varying類型變量是 vertex shader 與 fragment shader 之間的信使,一般我們在 vertex shader 中修改它然后在fragment shader使用它,但不能在 fragment shader中修改它.
//頂點著色器
varying vec4 v_Color;
void main(){
...
v_Color = vec4(1.,1.,1.,1);
}
//片元著色器
...
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
...
要注意全局變量限制符只能為 const、attribute烂琴、uniform和varying中的一個.不可復(fù)合.
函數(shù)參數(shù)限定符
函數(shù)的參數(shù)默認(rèn)是以拷貝的形式傳遞的,也就是值傳遞,任何傳遞給函數(shù)參數(shù)的變量,其值都會被復(fù)制一份,然后再交給函數(shù)內(nèi)部進(jìn)行處理. 我們可以為參數(shù)添加限定符來達(dá)到傳遞引用的目的,glsl中提供的參數(shù)限定符如下:
限定符 | 說明 |
---|---|
< none: default > | 默認(rèn)使用 in 限定符 |
in | 復(fù)制到函數(shù)中在函數(shù)中可讀寫 |
out | 返回時從函數(shù)中復(fù)制出來 |
inout | 復(fù)制到函數(shù)中并在返回時復(fù)制出來 |
in 是函數(shù)參數(shù)的默認(rèn)限定符,最終真正傳入函數(shù)形參的其實是實參的一份拷貝.在函數(shù)中,修改in修飾的形參不會影響到實參變量本身.
out 它的作用是向函數(shù)外部傳遞新值,out模式下傳遞進(jìn)來的參數(shù)是write-only的(可寫不可讀).就像是一個"坑位",坑位中的值需要函數(shù)給他賦予. 在函數(shù)中,修改out修飾的形參會影響到實參本身.
inout inout下,形參可以被理解為是一個帶值的"坑位",及可讀也可寫,在函數(shù)中,修改inout修飾的形參會影響到實參本身.
函數(shù)
glsl允許在程序的最外部聲明函數(shù).函數(shù)不能嵌套,不能遞歸調(diào)用,且必須聲明返回值類型(無返回值時聲明為void)
在其他方面glsl函數(shù)與c函數(shù)非常類似.
vec4 getPosition(){
vec4 v4 = vec4(0.,0.,0.,1.);
return v4;
}
void doubleSize(inout float size){
size= size*2.0 ;
}
void main() {
float psize= 10.0;
doubleSize(psize);
gl_Position = getPosition();
gl_PointSize = psize;
}
構(gòu)造函數(shù)
glsl中變量可以在聲明的時候初始化,float pSize = 10.0 也可以先聲明然后等需要的時候在進(jìn)行賦值.
聚合類型對象如(向量,矩陣,數(shù)組,結(jié)構(gòu)) 需要使用其構(gòu)造函數(shù)來進(jìn)行初始化. vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
//一般類型
float pSize = 10.0;
float pSize1;
pSize1=10.0;
...
//復(fù)合類型
vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
vec4 color1;
color1 =vec4(0.0, 1.0, 0.0, 1.0);
...
//結(jié)構(gòu)
struct light {
float intensity;
vec3 position;
};
light lightVar = light(3.0, vec3(1.0, 2.0, 3.0));
//數(shù)組
const float c[3] = float[3](5.0, 7.2, 1.1);
類型轉(zhuǎn)換
glsl可以使用構(gòu)造函數(shù)進(jìn)行顯式類型轉(zhuǎn)換,各值如下
bool t= true;
bool f = false;
int a = int(t); //true轉(zhuǎn)換為1或1.0
int a1 = int(f);//false轉(zhuǎn)換為0或0.0
float b = float(t);
float b1 = float(f);
bool c = bool(0);//0或0.0轉(zhuǎn)換為false
bool c1 = bool(1);//非0轉(zhuǎn)換為true
///只有0 和1 可以轉(zhuǎn)換成布爾值
bool d = bool(0.0);
bool d1 = bool(1.0);
精度限定
glsl在進(jìn)行光柵化著色的時候,會產(chǎn)生大量的浮點數(shù)運算,這些運算可能是當(dāng)前設(shè)備所不能承受的
,所以glsl提供了3種浮點數(shù)精度,我們可以根據(jù)不同的設(shè)備來使用合適的精度.
在變量前面加上 highp mediump lowp 即可完成對該變量的精度聲明.
lowp float color;
varying mediump vec2 Coord;
lowp ivec2 foo(lowp mat3);
highp mat4 m;
我們一般在片元著色器(fragment shader)最開始的地方加上 precision mediump float; 便設(shè)定了默認(rèn)的精度.這樣所有沒有顯式表明精度的變量 都會按照設(shè)定好的默認(rèn)精度來處理.
如何確定精度:
變量的精度首先是由精度限定符決定的,如果沒有精度限定符,則要尋找其右側(cè)表達(dá)式中,已經(jīng)確定精度的變量,一旦找到,那么整個表達(dá)式都將在該精度下運行.如果找到多個, 則選擇精度較高的那種,如果一個都找不到,則使用默認(rèn)或更大的精度類型.
uniform highp float h1;
highp float h2 = 2.3 * 4.7; //運算過程和結(jié)果都 是高精度
mediump float m;
m = 3.7 * h1 * h2; //運算過程 是高精度
h2 = m * h1; //運算過程 是高精度
m = h2 – h1; //運算過程 是高精度
h2 = m + m; //運算過程和結(jié)果都 是中等精度
void f(highp float p); // 形參 p 是高精度
f(3.3); //傳入的 3.3是高精度
invariant關(guān)鍵字
由于shader在編譯時會進(jìn)行一些內(nèi)部優(yōu)化,可能會導(dǎo)致同樣的運算在不同shader里結(jié)果不一定精確相等.這會引起一些問題,尤其是vertx shader向fragmeng shader傳值的時候. 所以我們需要使用invariant 關(guān)鍵字來顯式要求計算結(jié)果必須精確一致. 當(dāng)然我們也可使用 #pragma STDGL invariant(all)來命令所有輸出變量必須精確一致, 但這樣會限制編譯器優(yōu)化程度,降低性能.
#pragma STDGL invariant(all) //所有輸出變量為 invariant
invariant varying texCoord; //varying在傳遞數(shù)據(jù)的時候聲明為invariant
限定符的順序
當(dāng)需要用到多個限定符的時候要遵循以下順序:
1.在一般變量中: invariant > storage > precision
2.在參數(shù)中: storage > parameter > precision
我們來舉例說明:
invariant varying lowp float color; // invariant > storage > precision
void doubleSize(const in lowp float s){ //storage > parameter > precision
float s1=s;
}
預(yù)編譯指令
以 # 開頭的是預(yù)編譯指令,常用的有:
#define #undef #if #ifdef #ifndef #else
#elif #endif #error #pragma #extension #version #line
比如 #version 100 他的意思是規(guī)定當(dāng)前shader使用 GLSL ES 1.00標(biāo)準(zhǔn)進(jìn)行編譯,如果使用這條預(yù)編譯指令,則他必須出現(xiàn)在程序的最開始位置.
內(nèi)置的宏
LINE : 當(dāng)前源碼中的行號.
VERSION : 一個整數(shù),指示當(dāng)前的glsl版本 比如 100 ps: 100 = v1.00
GL_ES : 如果當(dāng)前是在 OPGL ES 環(huán)境中運行則 GL_ES 被設(shè)置成1,一般用來檢查當(dāng)前環(huán)境是不是 OPENGL ES.
GL_FRAGMENT_PRECISION_HIGH : 如果當(dāng)前系統(tǒng)glsl的片元著色器支持高浮點精度,則設(shè)置為1.一般用于檢查著色器精度.
實例:
1.如何通過判斷系統(tǒng)環(huán)境,來選擇合適的精度:
#ifdef GL_ES //
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
2.自定義宏:
#define NUM 100
#if NUM==100
#endif
shader 內(nèi)建變量
glsl程序使用一些特殊的內(nèi)置變量與硬件進(jìn)行溝通.他們大致分成兩種 一種是 input類型,他負(fù)責(zé)向硬件(渲染管線)發(fā)送數(shù)據(jù). 另一種是output類型,負(fù)責(zé)向程序回傳數(shù)據(jù),以便編程時需要.
在 vertex Shader 中:
output 類型的內(nèi)置變量:
變量 | 說明 | 單位 |
---|---|---|
highp vec4 gl_Position; | gl_Position 放置頂點坐標(biāo)信息 | vec4 |
mediump float gl_PointSize; | gl_PointSize 需要繪制點的大小,(只在gl.POINTS模式下有效) | float |
在 fragment Shader 中:
input 類型的內(nèi)置變量:
變量 | 說明 | 單位 |
---|---|---|
mediump vec4 gl_FragCoord; | 片元在framebuffer畫面的相對位置 | vec4 |
bool gl_FrontFacing; | 標(biāo)志當(dāng)前圖元是不是正面圖元的一部分 | bool |
mediump vec2 gl_PointCoord; | 經(jīng)過插值計算后的紋理坐標(biāo),點的范圍是0.0到1.0 | vec2 |
output 類型的內(nèi)置變量:
變量 | 說明 | 單位 |
---|---|---|
mediump vec4 gl_FragColor; | 設(shè)置當(dāng)前片點的顏色 | vec4 RGBA color |
mediump vec4 gl_FragData[n] | 設(shè)置當(dāng)前片點的顏色,使用glDrawBuffers數(shù)據(jù)數(shù)組 | vec4 RGBA color |
內(nèi)置的常量
lsl提供了一些內(nèi)置的常量,用來說明當(dāng)前系統(tǒng)的一些特性. 有時我們需要針對這些特性,對shader程序進(jìn)行優(yōu)化,讓程序兼容度更好.
在 vertex Shader 中:
- const mediump int gl_MaxVertexAttribs>=8
gl_MaxVertexAttribs 表示在vertex shader(頂點著色器)中可用的最大attributes數(shù).這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 8 個.
- const mediump int gl_MaxVertexUniformVectors >= 128
gl_MaxVertexUniformVectors 表示在vertex shader(頂點著色器)中可用的最大uniform vectors數(shù). 這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 128 個.
- const mediump int gl_MaxVaryingVectors >= 8
gl_MaxVaryingVectors 表示在vertex shader(頂點著色器)中可用的最大varying vectors數(shù). 這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 8 個.
- const mediump int gl_MaxVertexTextureImageUnits >= 0
gl_MaxVaryingVectors 表示在vertex shader(頂點著色器)中可用的最大紋理單元數(shù)(貼圖). 這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 甚至可以一個都沒有(無法獲取頂點紋理)
- const mediump int gl_MaxCombinedTextureImageUnits >= 8
gl_MaxVaryingVectors 表示在 vertex Shader和fragment Shader總共最多支持多少個紋理單元. 這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 8 個.
在 fragment Shader 中:
- const mediump int gl_MaxTextureImageUnits >= 8
gl_MaxVaryingVectors 表示在 fragment Shader(片元著色器)中能訪問的最大紋理單元數(shù),這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 8 個.
- const mediump int gl_MaxFragmentUniformVectors >= 16
gl_MaxFragmentUniformVectors 表示在 fragment Shader(片元著色器)中可用的最大uniform vectors數(shù),這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 16 個.
- const mediump int gl_MaxDrawBuffers = 1
gl_MaxDrawBuffers 表示可用的drawBuffers數(shù),在OpenGL ES 2.0中這個值為1, 在將來的版本可能會有所變化.
glsl中還有一種內(nèi)置的uniform狀態(tài)變量, gl_DepthRange 它用來表明全局深度范圍.
結(jié)構(gòu)如下:
struct gl_DepthRangeParameters {
highp float near; // n
highp float far; // f
highp float diff; // f - n
};
uniform gl_DepthRangeParameters gl_DepthRange;
除了 gl_DepthRange 外的所有uniform狀態(tài)常量都已在glsl 1.30 中廢棄.
內(nèi)置函數(shù)
glsl提供了非常豐富的函數(shù)庫,供我們使用,這些功能都是非常有用且會經(jīng)常用到的. 這些函數(shù)按功能區(qū)分大改可以分成7類:
通用函數(shù):
下文中的 類型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
方法 | 說明 |
---|---|
T abs(T x) | 返回x的絕對值 |
T sign(T x) | 比較x與0的值,大于,等于,小于 分別返回 1.0 ,0.0,-1.0 |
T floor(T x) | 返回<=x的最大整數(shù) |
T ceil(T x) | 返回>=等于x的最小整數(shù) |
T fract(T x) | 獲取x的小數(shù)部分 |
T mod(T x, T y) T mod(T x, float y) |
取x,y的余數(shù) |
T min(T x, T y) T min(T x, float y) |
取x,y的最小值 |
T max(T x, T y) T max(T x, float y) |
取x,y的最大值 |
T clamp(T x, T minVal, T maxVal) T clamp(T x, float minVal,float maxVal) |
min(max(x, minVal), maxVal),返回值被限定在 minVal,maxVal之間 |
T mix(T x, T y, T a) T mix(T x, T y, float a) |
取x,y的線性混合,x(1-a)+ya |
T step(T edge, T x) T step(float edge, T x) |
如果 x<edge 返回 0.0 否則返回1.0 |
T smoothstep(T edge0, T edge1, T x) T smoothstep(float edge0,float edge1, T x) |
如果x<edge0 返回 0.0 如果x>edge1返回1.0, 否則返回Hermite插值 |
角度&三角函數(shù):
下文中的 類型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
方法 | 說明 |
---|---|
T radians(T degrees) | 角度轉(zhuǎn)弧度 |
T degrees(T radians) | 弧度轉(zhuǎn)角度 |
T sin(T angle) | 正弦函數(shù),角度是弧度 |
T cos(T angle) | 余弦函數(shù),角度是弧度 |
T tan(T angle) | 正切函數(shù),角度是弧度 |
T asin(T x) | 反正弦函數(shù),返回值是弧度 |
T acos(T x) | 反余弦函數(shù),返回值是弧度 |
T atan(T y, T x) T atan(T y_over_x) |
反正切函數(shù),返回值是弧度 |
?##### 指數(shù)函數(shù):
下文中的 類型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
方法 | 說明 |
---|---|
T pow(T x, T y) | 返回x的y次冪 xy |
T exp(T x) | 返回x的自然指數(shù)冪 ex |
T log(T x) | 返回x的自然對數(shù) ln |
T exp2(T x) | 返回2的x次冪 2x |
T log2(T x) | 返回2為底的對數(shù) log2 |
T sqrt(T x) | 開根號 √x |
T inversesqrt(T x) | 先開根號,在取倒數(shù),就是 1/√x |
幾何函數(shù):
下文中的 類型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
方法 | 說明 |
---|---|
float length(T x) | 返回矢量x的長度 |
float distance(T p0, T p1) | 返回p0 p1兩點的距離 |
float dot(T x, T y) | 返回x y的點積 |
vec3 cross(vec3 x, vec3 y) | 返回x y的叉積 |
T normalize(T x) | 對x進(jìn)行歸一化,保持向量方向不變但長度變?yōu)? |
T faceforward(T N, T I, T Nref) | 根據(jù) 矢量 N 與Nref 調(diào)整法向量 |
T reflect(T I, T N) 返回 I - 2 * dot(N,I) * N, 結(jié)果是入射矢量 I 關(guān)于法向量N的 鏡面反射矢量
T refract(T I, T N, float eta) |返回入射矢量I關(guān)于法向量N的折射矢量,折射率為eta
矩陣函數(shù)
mat可以為任意類型矩陣.
方法 | 說明 |
---|---|
mat matrixCompMult(mat x, mat y) | 將矩陣 x 和 y的元素逐分量相乘 |
向量函數(shù)
下文中的 類型 T可以是 vec2, vec3, vec4, 且可以逐分量操作.
bvec指的是由bool類型組成的一個向量:
vec3 v3= vec3(0.,0.,0.);
vec3 v3_1= vec3(1.,1.,1.);
bvec3 aa= lessThan(v3,v3_1); //bvec3(true,true,true)
方法 | 說明 |
---|---|
bvec lessThan(T x, T y) | 逐分量比較x < y,將結(jié)果寫入bvec對應(yīng)位置 |
bvec lessThanEqual(T x, T y) | 逐分量比較 x <= y,將結(jié)果寫入bvec對應(yīng)位置 |
bvec greaterThan(T x, T y) | 逐分量比較 x > y,將結(jié)果寫入bvec對應(yīng)位置 |
bvec greaterThanEqual(T x, T y) | 逐分量比較 x >= y,將結(jié)果寫入bvec對應(yīng)位置 |
bvec equal(T x, T y) bvec equal(bvec x, bvec y) |
逐分量比較 x == y,將結(jié)果寫入bvec對應(yīng)位置 |
bvec notEqual(T x, T y) bvec notEqual(bvec x, bvec y) |
逐分量比較 x!= y,將結(jié)果寫入bvec對應(yīng)位置 |
bool any(bvec x) | 如果x的任意一個分量是true,則結(jié)果為true |
bool all(bvec x) | 如果x的所有分量是true,則結(jié)果為true |
bvec not(bvec x) | bool矢量的逐分量取反 |
紋理查詢函數(shù)
圖像紋理有兩種 一種是平面2d紋理,另一種是盒紋理,針對不同的紋理類型有不同訪問方法.
紋理查詢的最終目的是從sampler中提取指定坐標(biāo)的顏色信息. 函數(shù)中帶有Cube字樣的是指 需要傳入盒狀紋理. 帶有Proj字樣的是指帶投影的版本.
以下函數(shù)只在vertex shader中可用:
vec4 texture2DLod(sampler2D sampler, vec2 coord, float lod);
vec4 texture2DProjLod(sampler2D sampler, vec3 coord, float lod);
vec4 texture2DProjLod(sampler2D sampler, vec4 coord, float lod);
vec4 textureCubeLod(samplerCube sampler, vec3 coord, float lod)
以下函數(shù)只在fragment shader中可用:
vec4 texture2D(sampler2D sampler, vec2 coord, float bias);
vec4 texture2DProj(sampler2D sampler, vec3 coord, float bias);
vec4 texture2DProj(sampler2D sampler, vec4 coord, float bias);
vec4 textureCube(samplerCube sampler, vec3 coord, float bias);
在 vertex shader 與 fragment shader 中都可用:
vec4 texture2D(sampler2D sampler, vec2 coord);
vec4 texture2DProj(sampler2D sampler, vec3 coord);
vec4 texture2DProj(sampler2D sampler, vec4 coord);
vec4 textureCube(samplerCube sampler, vec3 coord);
官方的shader范例:
下面的shader如果你可以一眼看懂,說明你已經(jīng)對glsl語言基本掌握了.
Vertex Shader:
uniform mat4 mvp_matrix; //透視矩陣 * 視圖矩陣 * 模型變換矩陣
uniform mat3 normal_matrix; //法線變換矩陣(用于物體變換后法線跟著變換)
uniform vec3 ec_light_dir; //光照方向
attribute vec4 a_vertex; // 頂點坐標(biāo)
attribute vec3 a_normal; //頂點法線
attribute vec2 a_texcoord; //紋理坐標(biāo)
varying float v_diffuse; //法線與入射光的夾角
varying vec2 v_texcoord; //2d紋理坐標(biāo)
void main(void)
{
//歸一化法線
vec3 ec_normal = normalize(normal_matrix * a_normal);
//v_diffuse 是法線與光照的夾角.根據(jù)向量點乘法則,當(dāng)兩向量長度為1是 乘積即cosθ值
v_diffuse = max(dot(ec_light_dir, ec_normal), 0.0);
v_texcoord = a_texcoord;
gl_Position = mvp_matrix * a_vertex;
}
Fragment Shader:
precision mediump float;
uniform sampler2D t_reflectance;
uniform vec4 i_ambient;
varying float v_diffuse;
varying vec2 v_texcoord;
void main (void)
{
vec4 color = texture2D(t_reflectance, v_texcoord);
//這里分解開來是 color*vec3(1,1,1)*v_diffuse + color*i_ambient
//色*光*夾角cos + 色*環(huán)境光
gl_FragColor = color*(vec4(v_diffuse) + i_ambient);
}
shader 如何編譯shader
shader 需要編譯和連接兩個過程
為啥這樣不講解了爹殊。具體代碼如下
@interface Shader ()
@property (nonatomic ,readwrite) GLuint program;
@end
@implementation Shader
- (instancetype)init
{
self = [super init];
if (self) {
self.program = glCreateProgram();
}
return self;
}
-(BOOL)compileLinkSuccessShaderName:(NSString *)shader completeBlock:(void(^)(GLuint program))completeBlock
{
NSURL *vertShaderURL, *fragShaderURL;
GLuint vertShader, fragShader;
GLuint luint =self.program;
vertShaderURL = [[NSBundle mainBundle] URLForResource:shader withExtension:@"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER URL:vertShaderURL]) {
NSLog(@"Failed to compile vertex shader");
return NO;
}
// Create and compile fragment shader.
fragShaderURL = [[NSBundle mainBundle] URLForResource:shader withExtension:@"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER URL:fragShaderURL]) {
NSLog(@"Failed to compile fragment shader");
return NO;
}
// Attach vertex shader to program.
glAttachShader(luint, vertShader);
// Attach fragment shader to program.
glAttachShader(luint, fragShader);
completeBlock(luint);
if (![self linkProgram:luint]) {
NSLog(@"Failed to link program: %d", luint);
if (vertShader) {
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader) {
glDeleteShader(fragShader);
fragShader = 0;
}
if (luint) {
glDeleteProgram(luint);
luint = 0;
}
return NO;
}
glUseProgram(luint);
return YES;
}
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type URL:(NSURL *)URL
{
NSError *error;
NSString *sourceString = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:&error];
if (sourceString == nil) {
NSLog(@"Failed to load vertex shader: %@", [error localizedDescription]);
return NO;
}
GLint status;
const GLchar *source;
source = (GLchar *)[sourceString UTF8String];
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
#if defined(DEBUG)
GLint logLength;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
NSLog(@"Shader compile log:\n%s", log);
free(log);
}
#endif
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status == 0) {
glDeleteShader(*shader);
return NO;
}
return YES;
}
-(BOOL)linkProgram:(GLuint)prog
{
GLint status;
glLinkProgram(prog);
#if defined(DEBUG)
GLint logLength;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
NSLog(@"Program link log:\n%s", log);
free(log);
}
#endif
glGetProgramiv(prog, GL_LINK_STATUS, &status);
if (status == 0) {
return NO;
}
return YES;
}
- (BOOL)validateProgram:(GLuint)prog
{
GLint logLength, status;
glValidateProgram(prog);
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
NSLog(@"Program validate log:\n%s", log);
free(log);
}
glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
if (status == 0) {
return NO;
}
return YES;
}
@end
這里需要注意,我們在調(diào)用
glBindAttribLocation
的時候綁定頂點位置的時候必須在shader 的 link之前
奸绷。要不不生效
glGetUniformLocation
函數(shù)的調(diào)用必須在shader link 之后
梗夸,要不也不生效
shader 與app直接的傳值交互
shader 與app的交互主要是通過 attribute 和 uniform 修飾符來進(jìn)行的
shader 中通過attribute 和 uniform 聲明變量
。 在app 中通過glBindAttribLocation 綁定 attribute 變量,通過頂點進(jìn)行傳值 , glGetUniformLocation 獲取shader中 uniform 中的標(biāo)志符號,通過標(biāo)志符號調(diào)用
glUniform4f
glUniform4i
glUniform4iv
glUniform3i
等給shader中通過uniform 修改變量的類型傳值