WebGL 是如何工作的

2021-03-06 11:15 更新

WebGL 是如何工作的

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

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

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

這里的 9 就意味著“處理 9 個頂點”,所以就有 9 個頂點需要被處理。

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

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

我們例子中的片段著色器中并沒有存儲每一個像素的信息。我們可以在其中存儲更豐富的信息。我們可以為每一個值定義不同的意義從頂點著色器傳遞到片段著色器。

作為一個簡單的例子,我們將直接計算出來的投影矩陣坐標(biāo)從頂點著色器傳遞給片段著色器。

我們將繪制一個簡單的三角形。我們在上個例子的基礎(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);
}   

然后,我們繪制三個頂點。

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

然后,我們可以在頂點著色器中定義變量來將數(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;
}

然后,我們在片段著色器中聲明相同的變量。

precision mediump float;

varying vec4 v_color;

void main() {
  gl_FragColor = v_color;
}   

WebGL 將會連接頂點著色器中的變量和片段著色器中的相同名字和類型的變量。

下面是可以交互的版本。

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

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

WebGL 使用了我們?yōu)槊總€頂點計算的三個值,然后將三角形光柵化。對于每一個像素,都會使用被修改過的值來調(diào)用片段著色器。

基于上述例子,我們以三個頂點開始.

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

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

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

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

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

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é)果。

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

/ 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)在我們看一下被渲染后的多變變量。

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

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

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

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

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

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

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

gl.enableVertexAttribArray(location);    

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

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

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

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

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

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

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

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

規(guī)范化標(biāo)志應(yīng)用于非浮點指針類型。如果該值置為 false, 就意味著該值就會被翻譯為類型。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 將會變?yōu)?0.0 到 +1.0,規(guī)范化后的 SHORT 將會變?yōu)?-1.0 到 +1.0,它將有比 BYTE 更高的精確度.

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

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

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

我們可以使用下面的代碼來填充我們的緩沖區(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);
}

下一篇是個例子。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號