WebGL 著色器和 GLSL

2023-09-19 09:46 更新

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是圖像庫著色器語言的簡稱。語言著色器就是被寫在這里。它具有一些 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è)著色器程序。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)