重新自學(xué)學(xué)習(xí)openGL 之shader學(xué)習(xí)

今天博客的主要有以下內(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)量只有boolintfloat三種户辞。對于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.)

向量與矩陣的乘法規(guī)則如下:


其實就是線性代數(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 中:

  1. const mediump int gl_MaxVertexAttribs>=8

gl_MaxVertexAttribs 表示在vertex shader(頂點著色器)中可用的最大attributes數(shù).這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 8 個.

  1. const mediump int gl_MaxVertexUniformVectors >= 128

gl_MaxVertexUniformVectors 表示在vertex shader(頂點著色器)中可用的最大uniform vectors數(shù). 這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 128 個.

  1. const mediump int gl_MaxVaryingVectors >= 8

gl_MaxVaryingVectors 表示在vertex shader(頂點著色器)中可用的最大varying vectors數(shù). 這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 8 個.

  1. const mediump int gl_MaxVertexTextureImageUnits >= 0

gl_MaxVaryingVectors 表示在vertex shader(頂點著色器)中可用的最大紋理單元數(shù)(貼圖). 這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 甚至可以一個都沒有(無法獲取頂點紋理)

  1. const mediump int gl_MaxCombinedTextureImageUnits >= 8

gl_MaxVaryingVectors 表示在 vertex Shader和fragment Shader總共最多支持多少個紋理單元. 這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 8 個.

在 fragment Shader 中:

  1. const mediump int gl_MaxTextureImageUnits >= 8

gl_MaxVaryingVectors 表示在 fragment Shader(片元著色器)中能訪問的最大紋理單元數(shù),這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 8 個.

  1. const mediump int gl_MaxFragmentUniformVectors >= 16

gl_MaxFragmentUniformVectors 表示在 fragment Shader(片元著色器)中可用的最大uniform vectors數(shù),這個值的大小取決于 OpenGL ES 在某設(shè)備上的具體實現(xiàn), 不過最低不能小于 16 個.

  1. 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 修改變量的類型傳值


OpenGL shader GLSL 中文手冊
OpenGL著色器基本語法收集整理
參考博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末号醉,一起剝皮案震驚了整個濱河市反症,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌畔派,老刑警劉巖铅碍,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異线椰,居然都是意外死亡胞谈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進(jìn)店門士嚎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呜魄,“玉大人,你說我怎么就攤上這事莱衩【粜幔” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵笨蚁,是天一觀的道長睹晒。 經(jīng)常有香客問我,道長括细,這世上最難降的妖魔是什么伪很? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮奋单,結(jié)果婚禮上锉试,老公的妹妹穿的比我還像新娘。我一直安慰自己览濒,他們只是感情好呆盖,可當(dāng)我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贷笛,像睡著了一般应又。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乏苦,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天株扛,我揣著相機與錄音,去河邊找鬼。 笑死洞就,一個胖子當(dāng)著我的面吹牛盆繁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奖磁,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼改基,長吁一口氣:“原來是場噩夢啊……” “哼繁疤!你這毒婦竟也來了咖为?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤稠腊,失蹤者是張志新(化名)和其女友劉穎躁染,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體架忌,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡吞彤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叹放。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饰恕。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖井仰,靈堂內(nèi)的尸體忽然破棺而出埋嵌,到底是詐尸還是另有隱情,我是刑警寧澤俱恶,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布雹嗦,位于F島的核電站,受9級特大地震影響合是,放射性物質(zhì)發(fā)生泄漏了罪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一聪全、第九天 我趴在偏房一處隱蔽的房頂上張望泊藕。 院中可真熱鬧,春花似錦难礼、人聲如沸娃圆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踊餐。三九已至,卻和暖如春臀稚,著一層夾襖步出監(jiān)牢的瞬間吝岭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窜管,地道東北人散劫。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像幕帆,于是被迫代替她去往敵國和親获搏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,982評論 2 361

推薦閱讀更多精彩內(nèi)容