我們已經(jīng)討論了一些關(guān)于著色器和 GLSL 的相關(guān)內(nèi)容,但是仍然介紹的不是很仔細(xì)??梢酝ㄟ^一個(gè)例子來更清楚的了解。
正如 WebGL 是如何工作的中講到的,每次繪制,都需要兩個(gè)著色器,分別是頂點(diǎn)著色器和片段著色器。每個(gè)著色器都是一個(gè)函數(shù)。頂點(diǎn)著色器和片段著色器都是鏈接在程序中的。一個(gè)典型的 WebGL 程序都會(huì)包含很多這樣的著色器程序。
頂點(diǎn)著色器的任務(wù)就是產(chǎn)生投影矩陣的坐標(biāo)。其形式如下:
void main() {
gl_Position = doMathToMakeClipspaceCoordinates
}
每一個(gè)頂點(diǎn)都會(huì)調(diào)用你的著色器。每次調(diào)用程序都需要設(shè)置特定的全局變量 gl_Position
來表示投影矩陣的坐標(biāo)。
頂點(diǎn)著色器需要數(shù)據(jù),它以下面三種方式來獲取這些數(shù)據(jù)。
屬性
最常用的方式就是使用緩存區(qū)和屬性。WebGL 如何工作的中已經(jīng)涵蓋了緩沖區(qū)和屬性。
程序可以以下面的方式創(chuàng)建緩存區(qū)。
var buf = gl.createBuffer();
在這些緩存中存儲(chǔ)數(shù)據(jù)。
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, someData, gl.STATIC_DRAW);
于是,給定一個(gè)著色器程序,程序可以去查找屬性的位置。
var positionLoc = gl.getAttribLocation(someShaderProgram, "a_position");
下面告訴 WebGL 如何從緩存區(qū)中獲取數(shù)據(jù)并存儲(chǔ)到屬性中。
// turn on getting data out of a buffer for this attribute
gl.enableVertexAttribArray(positionLoc);
var numComponents = 3; // (x, y, z)
var type = gl.FLOAT;
var normalize = false; // leave the values as they are
var offset = 0; // start at the beginning of the buffer
var stride = 0; // how many bytes to move to the next vertex
// 0 = use the correct stride for type and numComponents
gl.vertexAttribPointer(positionLoc, numComponents, type, false, stride, offset);
在 WebGL 基本原理中我們展示了我們可以在著色器中附加一些邏輯,然后將值直接傳遞。
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
如果我們可以將投影矩陣放入我們的緩存區(qū)中,它就會(huì)開始運(yùn)作。
屬性可以使用 float,vec2,vec3,vec4,mat2,mat3 和 mat4 作為類型。
一致性變量
對(duì)于頂點(diǎn)著色器,一致性變量就是在繪畫每次調(diào)用時(shí),在著色器中一直保持不變的值。下面是一個(gè)往頂點(diǎn)中添加偏移量著色器的例子。
attribute vec4 a_position;
uniform vec4 u_offset;
void main() {
gl_Position = a_position + u_offset;
}
下面,我們需要對(duì)每一個(gè)頂點(diǎn)都偏移一定量。首先,我們需要先找到一致變量的位置。
var offsetLoc = gl.getUniformLocation(someProgram, "u_offset");
然后,我們?cè)诶L制前需要設(shè)置一致性變量。
gl.uniform4fv(offsetLoc, [1, 0, 0, 0]); // offset it to the right half the screen
一致性變量可以有很多種類型。對(duì)每一種類型都可以調(diào)用相應(yīng)的函數(shù)來設(shè)置。
gl.uniform1f (floatUniformLoc, v); // for float
gl.uniform1fv(floatUniformLoc, [v]); // for float or float array
gl.uniform2f (vec2UniformLoc, v0, v1);// for vec2
gl.uniform2fv(vec2UniformLoc, [v0, v1]); // for vec2 or vec2 array
gl.uniform3f (vec3UniformLoc, v0, v1, v2);// for vec3
gl.uniform3fv(vec3UniformLoc, [v0, v1, v2]); // for vec3 or vec3 array
gl.uniform4f (vec4UniformLoc, v0, v1, v2, v4);// for vec4
gl.uniform4fv(vec4UniformLoc, [v0, v1, v2, v4]); // for vec4 or vec4 array
gl.uniformMatrix2fv(mat2UniformLoc, false, [ 4x element array ]) // for mat2 or mat2 array
gl.uniformMatrix3fv(mat3UniformLoc, false, [ 9x element array ]) // for mat3 or mat3 array
gl.uniformMatrix4fv(mat4UniformLoc, false, [ 17x element array ]) // for mat4 or mat4 array
gl.uniform1i (intUniformLoc, v); // for int
gl.uniform1iv(intUniformLoc, [v]); // for int or int array
gl.uniform2i (ivec2UniformLoc, v0, v1);// for ivec2
gl.uniform2iv(ivec2UniformLoc, [v0, v1]); // for ivec2 or ivec2 array
gl.uniform3i (ivec3UniformLoc, v0, v1, v2);// for ivec3
gl.uniform3iv(ivec3UniformLoc, [v0, v1, v2]); // for ivec3 or ivec3 array
gl.uniform4i (ivec4UniformLoc, v0, v1, v2, v4);// for ivec4
gl.uniform4iv(ivec4UniformLoc, [v0, v1, v2, v4]); // for ivec4 or ivec4 array
gl.uniform1i (sampler2DUniformLoc, v); // for sampler2D (textures)
gl.uniform1iv(sampler2DUniformLoc, [v]); // for sampler2D or sampler2D array
gl.uniform1i (samplerCubeUniformLoc, v); // for samplerCube (textures)
gl.uniform1iv(samplerCubeUniformLoc, [v]); // for samplerCube or samplerCube array
一般類型都有 bool,bvec2,bvec3 和 bvec4。他們相應(yīng)的調(diào)用函數(shù)形式為 gl.uniform?f?
或 gl.uniform?i?
。
可以一次性設(shè)置數(shù)組中的所有一致性變量。比如:
// in shader
uniform vec2 u_someVec2[3];
// in JavaScript at init time
var someVec2Loc = gl.getUniformLocation(someProgram, "u_someVec2");
// at render time
gl.uniform2fv(someVec2Loc, [1, 2, 3, 4, 5, 6]); // set the entire array of u_someVec3
但是,如果程序希望單獨(dú)設(shè)置數(shù)組中的成員,那么必須單個(gè)的查詢每個(gè)成員的位置。
// in JavaScript at init time
var someVec2Element0Loc = gl.getUniformLocation(someProgram, "u_someVec2[0]");
var someVec2Element1Loc = gl.getUniformLocation(someProgram, "u_someVec2[1]");
var someVec2Element2Loc = gl.getUniformLocation(someProgram, "u_someVec2[2]");
// at render time
gl.uniform2fv(someVec2Element0Loc, [1, 2]); // set element 0
gl.uniform2fv(someVec2Element1Loc, [3, 4]); // set element 1
gl.uniform2fv(someVec2Element2Loc, [5, 6]); // set element 2
類似的,可以創(chuàng)建一個(gè)結(jié)構(gòu)體。
struct SomeStruct {
bool active;
vec2 someVec2;
};
uniform SomeStruct u_someThing;
程序可以單獨(dú)的查詢每一個(gè)成員。
var someThingActiveLoc = gl.getUniformLocation(someProgram, "u_someThing.active");
var someThingSomeVec2Loc = gl.getUniformLocation(someProgram, "u_someThing.someVec2");
頂點(diǎn)著色器中的紋理
片段著色器的任務(wù)就是為當(dāng)前被柵格化的像素提供顏色。它通常以下面的方式呈現(xiàn)出來。
precision mediump float;
void main() {
gl_FragColor = doMathToMakeAColor;
}
片段著色器對(duì)每一個(gè)像素都會(huì)調(diào)用一次。每次調(diào)用都會(huì)設(shè)置全局變量 gl_FragColor
來設(shè)置一些顏色。
片段著色器需要存儲(chǔ)獲取數(shù)據(jù),通常有下面這三種方式。
片段著色器中的一致變量
片段著色器中的紋理
我們可以從紋理中獲取值來創(chuàng)建 sampler2D
一致變量, 然后使用 GLSL 函數(shù) texture2D
來從中獲取值。
precision mediump float;
uniform sampler2D u_texture;
void main() {
vec2 texcoord = vec2(0.5, 0.5) // get a value from the middle of the texture
gl_FragColor = texture2D(u_texture, texcoord);
}
從紋理中提取的值是要依據(jù)很多設(shè)置的。最基本的,我們需要?jiǎng)?chuàng)建并在紋理中存儲(chǔ)值。比如,
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
var level = 0;
var width = 2;
var height = 1;
var data = new Uint8Array([255, 0, 0, 255, 0, 255, 0, 255]);
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
然后,在著色器程序中查詢一致變量的位置。
var someSamplerLoc = gl.getUniformLocation(someProgram, "u_texture");
WebGL 需要將它綁定到紋理單元中。
var unit = 5; // Pick some texture unit
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, tex);
然后告知著色器哪個(gè)單元會(huì)被綁定到紋理中。
gl.uniform1i(someSamplerLoc, unit);
多變變量
多變變量是從頂點(diǎn)著色器往片段著色器中傳遞的值,這些在WebGL 如何工作的已經(jīng)涵蓋了。
為了使用多變變量,需要在頂點(diǎn)著色器和片段著色器中均設(shè)置匹配的多變變量。我們?cè)陧旤c(diǎn)著色器中設(shè)置多變變量。當(dāng) WebGL 繪制像素時(shí),它會(huì)柵格化該值,然后傳遞到片段著色器中相對(duì)應(yīng)的片段著色器。
頂點(diǎn)著色器
attribute vec4 a_position;
uniform vec4 u_offset;
varying vec4 v_positionWithOffset;
void main() {
gl_Position = a_position + u_offset;
v_positionWithOffset = a_position + u_offset;
}
片段著色器
precision mediump float;
varying vec4 v_positionWithOffset;
void main() {
// convert from clipsapce (-1 <-> +1) to color space (0 -> 1).
vec4 color = v_positionWithOffset * 0.5 + 0.5
gl_FragColor = color;
}
上面的例子是無關(guān)緊要的例子。它一般不會(huì)直接復(fù)制投影矩陣的值到片段著色器中。
GLSL是圖像庫著色器語言的簡稱。語言著色器就是被寫在這里。它具有一些 JavaScript 中不存在的獨(dú)特的特性。 它用于實(shí)現(xiàn)一些邏輯來渲染圖像。比如,它可以創(chuàng)建類似于 vec2,vec3 和 vec4 分別表示2、3、4個(gè)值。類似的,mat2,mat3 和 mat4 來表示 2x2,3x3,4x4 的矩陣??梢詫?shí)現(xiàn) vec 來乘以一個(gè)標(biāo)量。
vec4 a = vec4(1, 2, 3, 4);
vec4 b = a * 2.0;
// b is now vec4(2, 4, 6, 8);
類似的,可以實(shí)現(xiàn)矩陣的乘法和矩陣的向量乘法。
mat4 a = ???
mat4 b = ???
mat4 c = a * b;
vec4 v = ???
vec4 y = c * v;
也可以選擇 vec 的部分,比如,vec4
vec4 v;
可以調(diào)整 vec 組件意味著可以交換或重復(fù)組件。
v.yyyy
這等價(jià)于
vec4(v.y, v.y, v.y, v.y)
類似的
v.bgra
等價(jià)于
vec4(v.b, v.g, v.r, v.a)
當(dāng)創(chuàng)建一個(gè) vec 或 一個(gè) mat時(shí),程序可以一次操作多個(gè)部分。比如,
vec4(v.rgb, 1)
這等價(jià)于
vec4(v.r, v.g, v.b, 1)
你可能意識(shí)到 GLSL 是一種很嚴(yán)格類型的語言。
float f = 1; // ERROR 1 is an int. You can't assign an int to a float
正確的方式如下:
float f = 1.0; // use float
float f = float(1) // cast the integer to a float
上面例子的 vec4(v.rgb, 1)
并不會(huì)對(duì) 1 進(jìn)行混淆,這是因?yàn)?vec4 是類似于 float(1)。
GLSL 是內(nèi)置函數(shù)的分支.可以同時(shí)操作多個(gè)組件。比如,
T sin(T angle)
這意味著 T 可以是 float,vec2,vec3 或 vec4。如果用戶在 vec4 中傳遞數(shù)據(jù)。也就是說 v 是 vec4,
vec4 s = sin(v);
折等價(jià)于
vec4 s = vec4(sin(v.x), sin(v.y), sin(v.z), sin(v.w));
有時(shí)候,一個(gè)參數(shù)是 float,另一個(gè)是 T。這意味著 float 將應(yīng)用到所有的部件。比如,如果 v1,v2 是 vec4,f 是 flat,然后
vec4 m = mix(v1, v2, f);
這等價(jià)于
vec4 m = vec4(
mix(v1.x, v2.x, f),
mix(v1.y, v2.y, f),
mix(v1.z, v2.z, f),
mix(v1.w, v2.w, f));
可以在 WebGL 參考目錄的最后一頁查看 GLSL 函數(shù)。如果你希望查看一些仔細(xì)的可以查看 GLSL 規(guī)范.
WebGL 是關(guān)于創(chuàng)建各種著色器的,將數(shù)據(jù)存儲(chǔ)在這些著色器上,然后調(diào)用 gl.drawArrays
或 gl.drawElements
來使 WebGL 處理頂點(diǎn)通過為每個(gè)頂點(diǎn)調(diào)用當(dāng)前的頂點(diǎn)著色器,然后為每一個(gè)像素調(diào)用當(dāng)前的片段著色器。
實(shí)際上,著色器的創(chuàng)建需要寫幾行代碼。因?yàn)?,這些代碼在大部分 WebGL 程序中的是一樣的,又因?yàn)橐坏懥?,可以幾乎可以忽略他們?nèi)绾尉幾g GLSL 著色器和鏈接成一個(gè)著色器程序。
更多建議: