鍍金池/ 教程/ HTML/ WebGL 是如何工作的
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 - 更少的代碼,更多的樂(lè)趣
WebGL 著色器和 GLSL

WebGL 是如何工作的

這部分是上一節(jié)WebGL 基本原理的延續(xù)。在繼續(xù)之前,我們需要討論 WebGL 和 GPU 是如何運(yùn)作的。GPU 有兩個(gè)基礎(chǔ)任務(wù),第一個(gè)就是將點(diǎn)處理為投影矩陣。第二部分就是基于第一部分將相應(yīng)的像素點(diǎn)描繪出來(lái)。

當(dāng)用戶調(diào)用

gl.drawArrays(gl.TRIANGLE, 0, 9);   

這里的 9 就意味著“處理 9 個(gè)頂點(diǎn)”,所以就有 9 個(gè)頂點(diǎn)需要被處理。

http://wiki.jikexueyuan.com/project/webgl/images/vertex-shader-anim.gif" alt="" />

上圖左側(cè)的是用戶自己提供的數(shù)據(jù)。定點(diǎn)著色器就是用戶在 GLSL 中寫(xiě)的函數(shù)。處理每個(gè)定點(diǎn)時(shí),均會(huì)被調(diào)用一次。用戶可以將投影矩陣的值存儲(chǔ)在特定的變量 gl_Position 中。GPU 會(huì)處理這些值,并將他們存儲(chǔ)在其內(nèi)部。

假設(shè)用戶希望繪制三角形 TRIANGLES, 那么每次繪制時(shí),上述的第一部分就會(huì)產(chǎn)生三個(gè)頂點(diǎn),然后 GPU 會(huì)使用他們來(lái)繪制三角形。首先 GPU 會(huì)將三個(gè)頂點(diǎn)對(duì)應(yīng)的像素繪制出來(lái),然后將三角形光柵化,或者說(shuō)是使用像素點(diǎn)繪制出來(lái)。對(duì)每一個(gè)像素點(diǎn),GPU 都會(huì)調(diào)用用戶定義的片段著色器來(lái)確定該像素點(diǎn)該涂成什么顏色。當(dāng)然,用戶定義的片段著色器必須在 gl_FragColor 變量中設(shè)置對(duì)應(yīng)的值。

我們例子中的片段著色器中并沒(méi)有存儲(chǔ)每一個(gè)像素的信息。我們可以在其中存儲(chǔ)更豐富的信息。我們可以為每一個(gè)值定義不同的意義從定點(diǎn)著色器傳遞到片段著色器。

作為一個(gè)簡(jiǎn)單的例子,我們將直接計(jì)算出來(lái)的投影矩陣坐標(biāo)從定點(diǎn)著色器傳遞給片段著色器。

我們將繪制一個(gè)簡(jiǎn)單的三角形。我們?cè)?a href="two-matrix.html">上個(gè)例子的基礎(chǔ)上更改一下。

// Fill the buffer with the values that define a triangle.
function setGeometry(gl) {
  gl.bufferData(
  gl.ARRAY_BUFFER,
  new Float32Array([
 0, -100,
   150,  125,
  -175,  100]),
  gl.STATIC_DRAW);
}   

然后,我們繪制三個(gè)頂點(diǎn)。

// Draw the scene.
function drawScene() {
  ...
  // Draw the geometry.
  gl.drawArrays(gl.TRIANGLES, 0, 3);
}   

然后,我們可以在頂點(diǎn)著色器中定義變量來(lái)將數(shù)據(jù)傳遞給片段著色器。

varying vec4 v_color;
...
void main() {
  // Multiply the position by the matrix.
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);

  // Convert from clipspace to colorspace.
  // Clipspace goes -1.0 to +1.0
  // Colorspace goes from 0.0 to 1.0
  v_color = gl_Position * 0.5 + 0.5;
}

然后,我們?cè)谄沃髦新暶飨嗤淖兞俊?

precision mediump float;

varying vec4 v_color;

void main() {
  gl_FragColor = v_color;
}   

WebGL 將會(huì)連接頂點(diǎn)著色器中的變量和片段著色器中的相同名字和類(lèi)型的變量。

下面是可以交互的版本。

移動(dòng)、縮放或旋轉(zhuǎn)這個(gè)三角形。注意由于顏色是從投影矩陣計(jì)算而來(lái),所以,顏色并不會(huì)隨著三角形的移動(dòng)而一直一樣。他們完全是根據(jù)背景色設(shè)定的。

現(xiàn)在我們考慮下面的內(nèi)容。我們僅僅計(jì)算三個(gè)頂點(diǎn)。我們的頂點(diǎn)著色器被調(diào)用了三次,因此,僅僅計(jì)算了三個(gè)顏色。而我們的三角形可以有好多顏色,這就是為何被稱(chēng)為 varying。

WebGL 使用了我們?yōu)槊總€(gè)定點(diǎn)計(jì)算的三個(gè)值,然后將三角形光柵化。對(duì)于每一個(gè)像素,都會(huì)使用被修改過(guò)的值來(lái)調(diào)用片段著色器。

基于上述例子,我們以三個(gè)頂點(diǎn)開(kāi)始.

http://wiki.jikexueyuan.com/project/webgl/images/3XcNPQC.png" alt="" />

我們的頂點(diǎn)著色器會(huì)引用矩陣來(lái)轉(zhuǎn)換、旋轉(zhuǎn)、縮放和轉(zhuǎn)化為投影矩陣。轉(zhuǎn)換、旋轉(zhuǎn)和縮放的默認(rèn)值是轉(zhuǎn)換為200,150,旋轉(zhuǎn)為 0,縮放為 1,1,所以實(shí)際上只進(jìn)行轉(zhuǎn)換。我們的后臺(tái)緩存是 400x300。我們的頂點(diǎn)矩陣應(yīng)用矩陣然后計(jì)算下面的三個(gè)投影矩陣頂點(diǎn)。

http://wiki.jikexueyuan.com/project/webgl/images/tjAxxoe.png" alt="" />

同樣也會(huì)將這些轉(zhuǎn)換到顏色空間上,然后將他們寫(xiě)到我們聲明的多變變量 v_color。

http://wiki.jikexueyuan.com/project/webgl/images/OFgAThJ.png" alt="" />

這三個(gè)值會(huì)寫(xiě)回到 v_color,然后它會(huì)被傳遞到片段著色器用于每一個(gè)像素進(jìn)行著色。

http://wiki.jikexueyuan.com/project/webgl/images/10.png" alt="" />

v_color 被修改為 v0,v1 和 v2 三個(gè)值中的一個(gè)。

我們也可以在頂點(diǎn)著色器中存儲(chǔ)更多的數(shù)據(jù)以便往片段著色器中傳遞。所以,對(duì)于以兩種顏色繪制包含兩個(gè)三角色的矩形的例子。為了實(shí)現(xiàn)這個(gè)例子,我們需要往頂點(diǎn)著色器中附加更多的屬性,以便傳遞更多的數(shù)據(jù),這些數(shù)據(jù)會(huì)直接傳遞到片段著色器中。

attribute vec2 a_position;
attribute vec4 a_color;
...
varying vec4 v_color;
?
void main() {
   ...
  // Copy the color from the attribute to the varying.
  v_color = a_color;
}    

我們現(xiàn)在需要使用 WebGL 顏色相關(guān)的功能。

`// look up where the vertex data needs to go.
     var positionLocation = gl.getAttribLocation    (program, "a_position");
 var colorLocation = gl.getAttribLocation(program, "a_color");
 ...
 // Create a buffer for the colors.
 var buffer = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 gl.enableVertexAttribArray(colorLocation);
 gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 0, 0);

// Set the colors.
    setColors(gl);

// Fill the buffer with colors for the 2 triangles
// that make the rectangle.
function setColors(gl) {
 // Pick 2 random colors.
 var r1 = Math.random();
 var b1 = Math.random();
 var g1 = Math.random();
  var r2 = Math.random();
  var b2 = Math.random();
  var g2 = Math.random();
 gl.bufferData(
 gl.ARRAY_BUFFER,
 new Float32Array(
[ r1, b1, g1, 1,
r1, b1, g1, 1,
 r1, b1, g1, 1,
 r2, b2, g2, 1,
 r2, b2, g2, 1,
 r2, b2, g2, 1]),
 gl.STATIC_DRAW);
}  `   

下面是結(jié)果。

注意,在上面的例子中,有兩個(gè)苦點(diǎn)顏色的三角形。我們?nèi)詫⒁獋鬟f的值存儲(chǔ)在多變變量中,所以,該變量會(huì)相關(guān)三角形區(qū)域內(nèi)改變。我們只是對(duì)于每個(gè)三角形的三個(gè)頂點(diǎn)使用了相同的顏色。如果我們使用了不同的顏色,我們可以看到整個(gè)渲染過(guò)程。

/ Fill the buffer with colors for the 2 triangles
// that make the rectangle.
function setColors(gl) {
  // Make every vertex a different color.
  gl.bufferData(
  gl.ARRAY_BUFFER,
  new Float32Array(
[ Math.random(), Math.random(), Math.random(), 1,
  Math.random(), Math.random(), Math.random(), 1,
  Math.random(), Math.random(), Math.random(), 1,
  Math.random(), Math.random(), Math.random(), 1,
  Math.random(), Math.random(), Math.random(), 1,
  Math.random(), Math.random(), Math.random(), 1]),
  gl.STATIC_DRAW);
}    

現(xiàn)在我們看一下被渲染后的多變變量。

從定點(diǎn)著色器往片段著色器可以傳遞更多更豐富的數(shù)據(jù)。如果我們來(lái)檢驗(yàn)圖像處理示例,就會(huì)發(fā)現(xiàn)在紋理坐標(biāo)中會(huì)傳遞更多的屬性.

緩存和屬性指令究竟做了什么?

緩存是獲取定點(diǎn)和頂點(diǎn)相關(guān)數(shù)據(jù)到 GPU 中的方法。gl.createBuffer 用于創(chuàng)建緩存。 gl.bindBuffer 方法用于將緩存激活來(lái)處于準(zhǔn)備工作的狀態(tài)。 gl.bufferData 方法可以將數(shù)據(jù)拷貝到緩存中。

一旦,數(shù)據(jù)到了緩存中,就需要告訴 WebGL 如何從里面錯(cuò)去數(shù)據(jù),并將它提供給頂點(diǎn)著色器以給相應(yīng)的屬性賦值。

為了實(shí)現(xiàn)這個(gè)功能,首先我們需要求 WebGL 提供一個(gè)屬性存儲(chǔ)位置。下面是示例代碼。

// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var colorLocation = gl.getAttribLocation(program, "a_color");    

一旦我們知道了對(duì)應(yīng)的屬性,我們可以觸發(fā)兩個(gè)指令。

gl.enableVertexAttribArray(location);    

這個(gè)指令會(huì)告訴 WebGL 我們希望將緩存中的數(shù)據(jù)賦值給一個(gè)變量。

gl.vertexAttribPointer(
location,
numComponents,
typeOfData,
normalizeFlag,
strideToNextPieceOfData,
offsetIntoBuffer);    

這個(gè)指令會(huì)告訴 WebGL 會(huì)從緩存中獲取數(shù)據(jù),這個(gè)緩存會(huì)與 gl.bindBuffer 綁定。每個(gè)頂點(diǎn)可以有 1 到 4 個(gè)部件,數(shù)據(jù)的類(lèi)型可以是 BYTE,FLOAT,INT,UNSIGNED_SHORT 等。跳躍意味著從數(shù)據(jù)的這片到那片會(huì)跨越多少個(gè)字節(jié)。跨越多遠(yuǎn)會(huì)以偏移量的方式存儲(chǔ)在緩存中。

部件的數(shù)目一般會(huì)是 1 到 4。

如果每個(gè)數(shù)據(jù)類(lèi)型僅使用一個(gè)緩存,那么跨越和偏移量都會(huì)是 0??缭綖?0 意味著“使用一個(gè)跨越連匹配類(lèi)型和尺寸”。偏移量為 0 意味著是在緩存的開(kāi)頭部分。將這個(gè)值賦值為除 O 之外其他的值會(huì)實(shí)現(xiàn)更為靈活的功能。雖然在性能方面它有些優(yōu)勢(shì),但是并不值得搞得很復(fù)雜,除非程序員希望將 WebGL 運(yùn)用到極致。

我希望到此就可以將緩沖區(qū)和屬性相關(guān)內(nèi)容已經(jīng)介紹清楚了。

下面我們學(xué)習(xí)著色器和 GLSL。

什么是頂點(diǎn)屬性指針 vertexAttribPointer 的規(guī)范化標(biāo)志 normalizeFlag ?

規(guī)范化標(biāo)志應(yīng)用于非浮點(diǎn)指針類(lèi)型。如果該值置為 false, 就意味著該值就會(huì)被翻譯為類(lèi)型。BYTE 的標(biāo)示范圍是-128 到 127。UNSIGNED_BYTE 范圍是 0 到 255,SHORT 是從-32768 到 32767。

如果將規(guī)范化標(biāo)志置為 true,那么BYTE的標(biāo)示范圍將為變?yōu)?1.0 到 +1.0,UNSIGNED_BYTE 將會(huì)變?yōu)?0.0 到 +1.0,規(guī)范化后的 SHORT 將會(huì)變?yōu)?-1.0 到 +1.0,它將有比 BYTE 更高的精確度.

標(biāo)準(zhǔn)化數(shù)據(jù)最通用的地方就是用于顏色。大部分時(shí)候,顏色范圍為 0.0 到 1.0 紅色、綠色和藍(lán)色需要個(gè)浮點(diǎn)型的值來(lái)表示,alpha 需要 16 字節(jié)來(lái)表示頂點(diǎn)的每個(gè)顏色。如果要實(shí)現(xiàn)更為復(fù)雜的圖形,可以增加更多的字節(jié)。相反的,程序可以將顏色轉(zhuǎn)為 UNSIGNED_BYTE 類(lèi)型,這個(gè)類(lèi)型使用 0 表示 0.0,使用 255 表示 1.0。那么僅需要 4 個(gè)字節(jié)來(lái)表示頂點(diǎn)的每個(gè)顏色,這將節(jié)省 75% 的存儲(chǔ)空間。

我們按照下面的方式來(lái)更改我們的代碼。當(dāng)我們告訴 WebGL 如何獲取顏色。

 gl.vertexAttribPointer(colorLocation, 4, gl.UNSIGNED_BYTE, true, 0, 0);   

我們可以使用下面的代碼來(lái)填充我們的緩沖區(qū)。

// Fill the buffer with colors for the 2 triangles
// that make the rectangle.
function setColors(gl) {
  // Pick 2 random colors.
  var r1 = Math.random() * 256; // 0 to 255.99999
  var b1 = Math.random() * 256; // these values
  var g1 = Math.random() * 256; // will be truncated
  var r2 = Math.random() * 256; // when stored in the
  var b2 = Math.random() * 256; // Uint8Array
  var g2 = Math.random() * 256;

  gl.bufferData(
  gl.ARRAY_BUFFER,
  new Uint8Array(   // Uint8Array
[ r1, b1, g1, 255,
  r1, b1, g1, 255,
  r1, b1, g1, 255,
  r2, b2, g2, 255,
  r2, b2, g2, 255,
  r2, b2, g2, 255]),
  gl.STATIC_DRAW);
}

下面是個(gè)例子。