WebGL簡易教程(四):顏色

1. 概述

在上一篇教程《WebGL簡易教程(三):繪製一個三角形(緩衝區對象)》中,經過使用緩衝區對象(buffer object)來向頂點着色器傳送數據。那麼,若是這些數據(與頂點相關的數據,如法向量、顏色等)須要繼續傳送到片元着色器該怎麼辦呢?web

例如這裏給三角形的每一個頂點賦予不一樣的顏色,繪製一個彩色的三角形。這個時候就須要用到以前(《WebGL簡易教程(二):向着色器傳輸數據》)介紹過的varying變量了。編程

2. 示例:繪製三角形

改進上一篇中繪製三角形(HelloTriangle.js)的代碼:canvas

// 頂點着色器程序
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' + // attribute variable
  'attribute vec4 a_Color;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_Position = a_Position;\n' + // Set the vertex coordinates of the point
  '  v_Color = a_Color;\n' +
  '}\n';

// 片元着色器程序
var FSHADER_SOURCE = 
  'precision mediump float;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' +
  '}\n';

function main() {
  // 獲取 <canvas> 元素
  var canvas = document.getElementById('webgl');

  // 獲取WebGL渲染上下文
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  // 設置頂點位置
  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log('Failed to set the positions of the vertices');
    return;
  }

  // 指定清空<canvas>的顏色
  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  // 清空<canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);

  // 繪製三角形
  gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
  // 頂點座標和顏色
  var verticesColors = new Float32Array([    
     0.0,  0.5,  1.0,  0.0,  0.0, 
    -0.5, -0.5,  0.0,  1.0,  0.0, 
     0.5, -0.5,  0.0,  0.0,  1.0, 
  ]);

  //
  var n = 3; // 點的個數
  var FSIZE = verticesColors.BYTES_PER_ELEMENT;   //數組中每一個元素的字節數

  // 建立緩衝區對象
  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
  }

  // 將緩衝區對象綁定到目標
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  // 向緩衝區對象寫入數據
  gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

  //獲取着色器中attribute變量a_Position的地址 
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  // 將緩衝區對象分配給a_Position變量
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 5*FSIZE, 0);

  // 鏈接a_Position變量與分配給它的緩衝區對象
  gl.enableVertexAttribArray(a_Position);

  //獲取着色器中attribute變量a_Color的地址 
  var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  if(a_Color < 0) {
    console.log('Failed to get the storage location of a_Color');
    return -1;
  }
  // 將緩衝區對象分配給a_Color變量
  gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
  // 鏈接a_Color變量與分配給它的緩衝區對象
  gl.enableVertexAttribArray(a_Color);  

  // 解除綁定
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  return n;
}

1) 數據的組織

與以前的例子類似,數據仍然經過緩衝區傳遞到頂點着色器。在頂點着色器中,定義了兩個attribute變量,分別表明位置和顏色信息:數組

// 頂點着色器程序
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' + // attribute variable
  'attribute vec4 a_Color;\n' +
…
  '}\n';

這意味着須要向頂點着色器傳遞兩次數據。這裏採起的作法仍然是一次性向緩衝區寫入位置和顏色等全部的數據,而後分批次傳入頂點着色器:ide

// 建立緩衝區對象
  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
  }

// 將緩衝區對象綁定到目標
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  // 向緩衝區對象寫入數據
  gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

  //獲取着色器中attribute變量a_Position的地址 
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  // 將緩衝區對象分配給a_Position變量
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 5*FSIZE, 0);

  // 鏈接a_Position變量與分配給它的緩衝區對象
  gl.enableVertexAttribArray(a_Position);

  //獲取着色器中attribute變量a_Color的地址 
  var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  if(a_Color < 0) {
    console.log('Failed to get the storage location of a_Color');
    return -1;
  }
  // 將緩衝區對象分配給a_Color變量
  gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
  // 鏈接a_Color變量與分配給它的緩衝區對象
  gl.enableVertexAttribArray(a_Color);

能夠看到建立緩衝區對象、將緩衝區對象綁定到目標、向緩衝區對象寫入數據這三個步驟都是一致的。但分配attribute變量和鏈接attribute變量這兩個步驟分別進行了兩次。其中的關鍵點就在於gl.vertexAttribPointer()這個函數。以前使用這個函數都是使用的默認值,這裏經過設置步進和偏移值,分別訪問了緩衝區中不一樣的數據。函數

經過gl.vertexAttribPointer()函數定義能夠知道,傳送到緩衝區的數據是2(size)的位置數據和3(size)的顏色數據,因此步進參數stride都是5(size)。第一次傳送位置數據的時候是從初始位置開始的,因此offset是0;而第二次傳送顏色數據的時候須要偏移第一個位置數據,因此offfset是2(size)。webgl

2) varying變量

在以前的教程(《WebGL簡易教程(二):向着色器傳輸數據》)中提到,能夠傳送數據給片元着色器,來給繪製場景賦予顏色。可是這裏卻經過緩衝區把數據傳遞給了頂點着色器。所以,在這裏給頂點着色器和片元着色器,分別定義了一個相同的varying變量:3d

// 頂點着色器程序
var VSHADER_SOURCE =
  …
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  …
  '  v_Color = a_Color;\n' +
  '}\n';

// 片元着色器程序
var FSHADER_SOURCE = 
  …
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' +
  '}\n';

varying變量表達的正是一種可變的變量,它的做用就是從頂點着色器向片元着色器傳輸數據。在頂點着色器的main函數中,將從緩衝區對象中獲取的attribute變量a_Color賦值給預先定義的varying變量v_Color;同時在片元着色器中又定義了一個同類型同名的varying變量v_Color,那麼頂點着色器中該變量的值就會自動傳入到片元着色器中。最後在片元着色器的main函數中將該值傳入到gl_FragColor中,就獲得最終的結果了。其示意圖以下:code

3. 結果

最後的運行結果以下,最後會發現獲得了一個顏色平滑過渡的,三個角各是紅、綠、藍顏色的三角形:

4. 理解

1) 圖形裝配和光柵化

更進一步思考下,這裏雖然給每一個頂點賦予的顏色值,可是爲何三角形的表面都賦予了顏色,而且是平滑過渡的效果呢?其實這裏省略了頂點着色器與片元着色器之間數據傳輸細節——圖形裝配和光柵化。

點組成線,線組成面,將孤立的點組成基本圖形(圖元)的過程就是圖形裝配。圖形裝配的輸入數據就是頂點着色器中gl_Position獲得的值,由gl.drawArrays()中第一個參數值來肯定裝配成什麼樣的圖元。在這個例子中,頂點着色器告訴WebGL系統,準備了三個點,WebGL經過圖像裝配,將其裝配成三角形。

知道裝配的圖形仍是不夠的,理論上的三角形是接二連三的圖形,而通常的圖形顯示設備都是離散的片元(像素)。圖像轉換成片元,就是光柵化的過程。

圖形裝配和光柵化的示意圖以下:

2) 內插過程

在第二節詳解示例中的代碼時,提到了頂點着色器和片元着色都定義了相同的varying變量v_Color,數據就會從頂點着色器傳入到片元着色器。但其實二者雖然同名,但並非一回事。在頂點着色器中,這個varying變量是與頂點相關的值,而通過圖形裝配和光柵化後,片元着色器的varying變量就是與片元相關的值了。而且,這個值是通過內插過程獲得的。

在這個例子中,給三個頂點賦予了三個不一樣的顏色值。WebGL就根據三個頂點的顏色值內插了三角形中每一個片元(像素)的顏色值,並傳遞給片元着色器。所謂內插過程,能夠想象成一條漸變色帶,知道肯定了起止顏色,就能獲取中間任意位置的顏色。

5. 參考

原本部分代碼和插圖來自《WebGL編程指南》,源代碼連接:https://share.weiyun.com/5VjlUKo ,密碼:sw0x2x。會在此共享目錄中持續更新後續的內容。

相關文章
相關標籤/搜索