鍍金池/ 教程/ HTML/ WebGL 著色器和 GLSL
WebGL 文本 HTML
WebGL 文本 Canvas 2D
WebGL 2D 圖像旋轉(zhuǎn)
WebGL 圖像處理(續(xù))
WebGL 2D 矩陣
WebGL 繪制多個(gè)東西
WebGL 圖像處理
WebGL 2D 圖像轉(zhuǎn)換
WebGL 3D 透視
WebGL 是如何工作的
WebGL 文本 紋理
WebGL 2D 圖像伸縮
WebGL 場(chǎng)景圖
WebGL 3D 攝像機(jī)
WebGL 文本 使用字符紋理
WebGL 正交 3D
WebGL 基本原理
WebGL - 更少的代碼,更多的樂趣
WebGL 著色器和 GLSL

WebGL 著色器和 GLSL

我們已經(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)著色器

頂點(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ū)中獲取數(shù)據(jù))
  • 一致變量(每次繪畫調(diào)用時(shí)都保持一致的值)
  • 紋理(從像素中得到的數(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ù),通常有下面這三種方式。

  • 一致變量(每次繪制像素點(diǎn)時(shí)都會(huì)調(diào)用且一直保持一致)
  • 紋理(從像素中獲取數(shù)據(jù))
  • 多變變量(從定點(diǎn)著色器中傳遞出來且被柵格化的值)

片段著色器中的一致變量

參考頂點(diǎn)著色器中的一致變量。

片段著色器中的紋理

我們可以從紋理中獲取值來創(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

GLSL是圖像庫著色器語言的簡(jiǎn)稱。語言著色器就是被寫在這里。它具有一些 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;
  • v.x 等價(jià)于 v.s,v.r,v[0]
  • v.y 等價(jià)于 v.t,v.g,v[1]
  • v.z 等價(jià)于 v.p,v.b,v[2]
  • v.w 等價(jià)于 v.q,v.a,v[3]

可以調(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.drawArraysgl.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è)著色器程序。