1. 程式人生 > 實用技巧 >WebGL-工作原理

WebGL-工作原理

此文上接WebGL 基礎概念。 在繼續學習之前,我們需要探討一下WebGL在GPU上究竟做了什麼。 WebGL在GPU上的工作基本上分為兩部分,第一部分是將頂點(或資料流)轉換到裁剪空間座標, 第二部分是基於第一部分的結果繪製畫素點。

  當你呼叫:

var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 9;
gl.drawArrays(primitiveType, offset, count);

這裡的9表示“處理9個頂點”,所以將會有9個頂點被轉換。

  左側是你提供的資料。頂點著色器(Vertex Shader)是你寫進GLSL 中的一個方法,每個頂點呼叫一次,在這個方法中做一些數學運算後設置了一個特殊的gl_Position變數, 這個變數就是該頂點轉換到裁剪空間中的座標值,GPU接收該值並將其儲存起來。

  假設你正在畫三角形,頂點著色器每完成三次頂點處理,WebGL就會用這三個頂點畫一個三角形。 它計算出這三個頂點對應的畫素後,就會光柵化這個三角形,“光柵化”其實就是“用畫素畫出來” 的花哨叫法。對於每一個畫素,它會呼叫你的片斷著色器詢問你使用什麼顏色。 你通過給片斷著色器的一個特殊變數gl_FragColor設定一個顏色值,實現自定義畫素顏色。

  使用它們可以做出非常有趣的東西,但如你所見,到目前為止的例子中, 處理每個畫素時片斷著色器可用資訊很少,幸運的是我們可以給它傳遞更多資訊。 想要從頂點著色器傳值到片斷著色器,我們可以定義“可變數(varyings)”。

  一個簡單的例子,將頂點著色器計算出的裁剪空間座標從頂點著色器傳遞到片斷著色器。

  我們來畫一個簡單的三角形,從之前的例子繼續,讓我們把矩形改成三角形。

// 定義一個三角形填充到緩衝裡
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
             0, -100,
           150,  125,
          -175,  100]),
      gl.STATIC_DRAW);
}

我們只需要畫三個頂點:

// 繪製場景
function drawScene() {
  ...
  
// 繪製幾何體 var primitiveType = gl.TRIANGLES; var offset = 0; var count = 3; gl.drawArrays(primitiveType, offset, count); }

然後在我們的頂點著色器中定義一個varying(可變數)用來給片斷著色器傳值。

varying vec4 v_color;
...
void main() {
  // 將位置和矩陣相乘
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);

  // 從裁減空間轉換到顏色空間
  // 裁減空間範圍 -1.0 到 +1.0
  // 顏色空間範圍 0.0 到 1.0
  v_color = gl_Position * 0.5 + 0.5;
}

在片斷著色器中定義同名varying變數:

precision mediump float;

varying vec4 v_color;

void main() {
  gl_FragColor = v_color;
}

WebGL會將同名的可變數從頂點著色器輸入到片斷著色器中。

執行下面的程式碼:

"use strict";

function main() {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }

  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

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

  // lookup uniforms
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");

  // Create a buffer.
  var positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Set Geometry.
  setGeometry(gl);

  var translation = [200, 150];
  var angleInRadians = 0;
  var scale = [1, 1];

  drawScene();

  // Setup a ui.
  webglLessonsUI.setupSlider("#x", {value: translation[0], slide: updatePosition(0), max: gl.canvas.width });
  webglLessonsUI.setupSlider("#y", {value: translation[1], slide: updatePosition(1), max: gl.canvas.height});
  webglLessonsUI.setupSlider("#angle", {slide: updateAngle, max: 360});
  webglLessonsUI.setupSlider("#scaleX", {value: scale[0], slide: updateScale(0), min: -5, max: 5, step: 0.01, precision: 2});
  webglLessonsUI.setupSlider("#scaleY", {value: scale[1], slide: updateScale(1), min: -5, max: 5, step: 0.01, precision: 2});

  function updatePosition(index) {
    return function(event, ui) {
      translation[index] = ui.value;
      drawScene();
    }
  }

  function updateAngle(event, ui) {
    var angleInDegrees = 360 - ui.value;
    angleInRadians = angleInDegrees * Math.PI / 180;
    drawScene();
  }

  function updateScale(index) {
    return function(event, ui) {
      scale[index] = ui.value;
      drawScene();
    }
  }

  // Draw the scene.
  function drawScene() {
    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

    // Turn on the attribute
    gl.enableVertexAttribArray(positionAttributeLocation);

    // Bind the position buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
    var size = 2;          // 2 components per iteration
    var type = gl.FLOAT;   // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0;        // start at the beginning of the buffer
    gl.vertexAttribPointer(
        positionAttributeLocation, size, type, normalize, stride, offset)

    // Compute the matrix
    var matrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);
    matrix = m3.translate(matrix, translation[0], translation[1]);
    matrix = m3.rotate(matrix, angleInRadians);
    matrix = m3.scale(matrix, scale[0], scale[1]);

    // Set the matrix.
    gl.uniformMatrix3fv(matrixLocation, false, matrix);

    // Draw the geometry.
    var primitiveType = gl.TRIANGLES;
    var offset = 0;
    var count = 3;
    gl.drawArrays(primitiveType, offset, count);
  }
}

// Fill the buffer with the values that define a triangle.
// Note, will put the values in whatever buffer is currently
// bound to the ARRAY_BUFFER bind point
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
             0, -100,
           150,  125,
          -175,  100]),
      gl.STATIC_DRAW);
}

main();

當你移動,縮放,旋轉三角形時,發現顏色隨位置變化,不跟著三角形移動。

回想一下,我們只計算了三個頂點,呼叫了三次頂點著色器,所以也只計算出了三個顏色值, 但是我們的三角形卻有很多顏色,這就是稱之為可變數的varying的原因啦!

WebGL先獲得頂點著色器中計算的三個顏色值,在光柵化三角形時將會根據這三個值進行插值。 每一個畫素在呼叫片斷著色器時,可變數的值是與之對應的插值。

讓我們從上例的三個頂點開始分析

我們的給頂點著色器施加了一個包含平移,旋轉和縮放的的矩陣,並將結果轉換到裁剪空間。 預設平移,旋轉和縮放值為:平移 = 200, 150,旋轉 = 0,縮放 = 1,所以這裡只進行了平移。 畫布大小(背景緩衝)為 400×300,所以三個頂點在裁剪空間中為以下座標值。

同時將這些值轉換到顏色空間中賦給我們定義的可變數v_color。

利用這三個值進行插值後傳進每個畫素執行的片斷著色器中。

想要給片斷著色器傳值,我們可以先把值傳遞給頂點著色器然後再傳給片斷著色器。 讓我們來畫一個由兩個不同顏色三角形組成的矩形。我們需要給頂點著色器新增一個屬性值, 把值通過屬性傳遞給它後它再直接傳遞給片斷著色器。

attribute vec2 a_position;
attribute vec4 a_color;
...
varying vec4 v_color;

void main() {
   ...
  // 直接把屬性值中的資料賦給可變數
  v_color = a_color;
}

現在要給WebGL提供要用的顏色。

// 尋找頂點著色器中需要的資料
  var positionLocation = gl.getAttribLocation(program, "a_position");
  var colorLocation = gl.getAttribLocation(program, "a_color");
  ...
  // 給顏色資料建立一個緩衝
  var colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  // 設定顏色
  setColors(gl);
  ...

// 給矩形的兩個三角形
// 設定顏色值併發到緩衝
function setColors(gl) {
  // 生成兩個隨機顏色
  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);
}

在渲染的時候設定顏色屬性:

gl.enableVertexAttribArray(colorLocation);

// 繫結顏色緩衝
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);

// 告訴顏色屬性怎麼從 colorBuffer (ARRAY_BUFFER) 中讀取顏色值
var size = 4;          // 每次迭代使用4個單位的資料
var type = gl.FLOAT;   // 單位資料型別是32位的浮點型
var normalize = false; // 不需要歸一化資料
var stride = 0;        // 0 = 移動距離 * 單位距離長度sizeof(type) 
                       // 每次迭代跳多少距離到下一個資料
var offset = 0;        // 從繫結緩衝的起始處開始
gl.vertexAttribPointer(
    colorLocation, size, type, normalize, stride, offset)

調整頂點的數量為6用來畫兩個三角形:

// 畫幾何體
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);

執行下面的程式碼:

"use strict";

function main() {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }

  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

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

  // lookup uniforms
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");

  // Create a buffer for the positons.
  var positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  // Set Geometry.
  setGeometry(gl);

  // Create a buffer for the colors.
  var colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  // Set the colors.
  setColors(gl);

  var translation = [200, 150];
  var angleInRadians = 0;
  var scale = [1, 1];

  drawScene();

  // Setup a ui.
  webglLessonsUI.setupSlider("#x", {value: translation[0], slide: updatePosition(0), max: gl.canvas.width });
  webglLessonsUI.setupSlider("#y", {value: translation[1], slide: updatePosition(1), max: gl.canvas.height});
  webglLessonsUI.setupSlider("#angle", {slide: updateAngle, max: 360});
  webglLessonsUI.setupSlider("#scaleX", {value: scale[0], slide: updateScale(0), min: -5, max: 5, step: 0.01, precision: 2});
  webglLessonsUI.setupSlider("#scaleY", {value: scale[1], slide: updateScale(1), min: -5, max: 5, step: 0.01, precision: 2});

  function updatePosition(index) {
    return function(event, ui) {
      translation[index] = ui.value;
      drawScene();
    }
  }

  function updateAngle(event, ui) {
    var angleInDegrees = 360 - ui.value;
    angleInRadians = angleInDegrees * Math.PI / 180;
    drawScene();
  }

  function updateScale(index) {
    return function(event, ui) {
      scale[index] = ui.value;
      drawScene();
    }
  }

  // Draw the scene.
  function drawScene() {
    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

    // Turn on the position attribute
    gl.enableVertexAttribArray(positionLocation);

    // Bind the position buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
    var size = 2;          // 2 components per iteration
    var type = gl.FLOAT;   // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0;        // start at the beginning of the buffer
    gl.vertexAttribPointer(
        positionLocation, size, type, normalize, stride, offset)

    // Turn on the color attribute
    gl.enableVertexAttribArray(colorLocation);

    // Bind the color buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);

    // Tell the color attribute how to get data out of colorBuffer (ARRAY_BUFFER)
    var size = 4;          // 4 components per iteration
    var type = gl.FLOAT;   // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0;        // start at the beginning of the buffer
    gl.vertexAttribPointer(
        colorLocation, size, type, normalize, stride, offset)

    // Compute the matrix
    var matrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);
    matrix = m3.translate(matrix, translation[0], translation[1]);
    matrix = m3.rotate(matrix, angleInRadians);
    matrix = m3.scale(matrix, scale[0], scale[1]);

    // Set the matrix.
    gl.uniformMatrix3fv(matrixLocation, false, matrix);

    // Draw the geometry.
    var primitiveType = gl.TRIANGLES;
    var offset = 0;
    var count = 6;
    gl.drawArrays(primitiveType, offset, count);
  }
}


// Fill the buffer with the values that define a rectangle.
// Note, will put the values in whatever buffer is currently
// bound to the ARRAY_BUFFER bind point
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          -150, -100,
           150, -100,
          -150,  100,
           150, -100,
          -150,  100,
           150,  100]),
      gl.STATIC_DRAW);
}

// Fill the buffer with colors for the 2 triangles
// that make the rectangle.
// Note, will put the values in whatever buffer is currently
// bound to the ARRAY_BUFFER bind point
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);
}

main();

你可能注意到這兩個三角形是純色的。我們傳遞給每個三角形的頂點的顏色值是相同的, 所以我們傳遞的varying會被插值成相同的顏色,如果我們傳遞不同的顏色,就會看到插值的顏色。

// 給矩形的兩個三角形
// 設定顏色值併發到緩衝
function setColors(gl) {
  // 給每個頂點定義不同的顏色
  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);
}

執行下面的程式碼:

"use strict";

function main() {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }

  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

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

  // lookup uniforms
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");

  // Create a buffer for the positons.
  var positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  // Set Geometry.
  setGeometry(gl);

  // Create a buffer for the colors.
  var colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  // Set the colors.
  setColors(gl);

  var translation = [200, 150];
  var angleInRadians = 0;
  var scale = [1, 1];

  drawScene();

  // Setup a ui.
  webglLessonsUI.setupSlider("#x", {value: translation[0], slide: updatePosition(0), max: gl.canvas.width });
  webglLessonsUI.setupSlider("#y", {value: translation[1], slide: updatePosition(1), max: gl.canvas.height});
  webglLessonsUI.setupSlider("#angle", {slide: updateAngle, max: 360});
  webglLessonsUI.setupSlider("#scaleX", {value: scale[0], slide: updateScale(0), min: -5, max: 5, step: 0.01, precision: 2});
  webglLessonsUI.setupSlider("#scaleY", {value: scale[1], slide: updateScale(1), min: -5, max: 5, step: 0.01, precision: 2});

  function updatePosition(index) {
    return function(event, ui) {
      translation[index] = ui.value;
      drawScene();
    }
  }

  function updateAngle(event, ui) {
    var angleInDegrees = 360 - ui.value;
    angleInRadians = angleInDegrees * Math.PI / 180;
    drawScene();
  }

  function updateScale(index) {
    return function(event, ui) {
      scale[index] = ui.value;
      drawScene();
    }
  }

  // Draw the scene.
  function drawScene() {
    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

    // Turn on the position attribute
    gl.enableVertexAttribArray(positionLocation);

    // Bind the position buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
    var size = 2;          // 2 components per iteration
    var type = gl.FLOAT;   // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0;        // start at the beginning of the buffer
    gl.vertexAttribPointer(
        positionLocation, size, type, normalize, stride, offset)

    // Turn on the color attribute
    gl.enableVertexAttribArray(colorLocation);

    // Bind the color buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);

    // Tell the color attribute how to get data out of colorBuffer (ARRAY_BUFFER)
    var size = 4;          // 4 components per iteration
    var type = gl.FLOAT;   // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0;        // start at the beginning of the buffer
    gl.vertexAttribPointer(
        colorLocation, size, type, normalize, stride, offset)

    // Compute the matrix
    var matrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);
    matrix = m3.translate(matrix, translation[0], translation[1]);
    matrix = m3.rotate(matrix, angleInRadians);
    matrix = m3.scale(matrix, scale[0], scale[1]);

    // Set the matrix.
    gl.uniformMatrix3fv(matrixLocation, false, matrix);

    // Draw the geometry.
    var primitiveType = gl.TRIANGLES;
    var offset = 0;
    var count = 6;
    gl.drawArrays(primitiveType, offset, count);
  }
}


// Fill the buffer with the values that define a rectangle.
// Note, will put the values in whatever buffer is currently
// bound to the ARRAY_BUFFER bind point
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          -150, -100,
           150, -100,
          -150,  100,
           150, -100,
          -150,  100,
           150,  100]),
      gl.STATIC_DRAW);
}

// Fill the buffer with colors for the 2 triangles
// that make the rectangle.
// Note, will put the values in whatever buffer is currently
// bound to the ARRAY_BUFFER bind point
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);
}

main();

可能不值一提的是上例還演示了使用多個屬性並且通過頂點著色器向片斷著色器傳值。 如果你看了處理圖片的例子, 那裡面還用了另外一個屬性傳遞紋理座標。

關於buffer和attribute的程式碼

緩衝操作是在GPU上獲取頂點和其他頂點資料的一種方式。 gl.createBuffer建立一個緩衝;gl.bindBuffer是設定緩衝為當前使用緩衝; gl.bufferData將資料拷貝到緩衝,這個操作一般在初始化完成。

一旦資料存到緩衝中,還需要告訴WebGL怎麼從緩衝中提取資料傳給頂點著色器的屬性。

要做這些,首先需要獲取WebGL給屬性分配的地址,如下方程式碼所示:

// 詢問頂點資料應該放在哪裡
var positionLocation = gl.getAttribLocation(program, "a_position");
var colorLocation = gl.getAttribLocation(program, "a_color");

這一步一般也是在初始化部分完成。

一旦知道了屬性的地址,在繪製前還需要發出三個命令。

gl.enableVertexAttribArray(location);

這個命令是告訴WebGL我們想從緩衝中提供資料。

gl.bindBuffer(gl.ARRAY_BUFFER, someBuffer);

這個命令是將緩衝繫結到 ARRAY_BUFFER 繫結點,它是WebGL內部的一個全域性變數。

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

這個命令告訴WebGL從 ARRAY_BUFFER 繫結點當前繫結的緩衝獲取資料。 每個頂點有幾個單位的資料(1 – 4),單位資料型別是什麼(BYTE, FLOAT, INT, UNSIGNED_SHORT, 等等…), stride 是從一個數據到下一個資料要跳過多少位,最後是資料在緩衝的什麼位置。

單位個數永遠是 1 到 4 之間。

如果每個型別的資料都用一個緩衝儲存,stride 和 offset 都是 0 。 對 stride 來說 0 表示 “用符合單位型別和單位個數的大小”。 對 offset 來說 0 表示從緩衝起始位置開始讀取。 它們使用 0 以外的值時會複雜得多,雖然這樣會取得一些效能能上的優勢, 但是一般情況下並不值得,除非你想充分壓榨WebGL的效能。

希望這些關於緩衝和屬性的內容對你來說講的足夠清楚。


vertexAttribPointer 中的 normalizeFlag 引數是什麼意思?

標準化標記(normalizeFlag)適用於所有非浮點型資料。如果傳遞false就解讀原資料型別。 BYTE 型別的範圍是從 -128 到 127,UNSIGNED_BYTE 型別的範圍是從 0 到 255, SHORT 型別的範圍是從 -32768 到 32767,等等…

如果標準化標記設為true,BYTE 資料的值(-128 to 127)將會轉換到 -1.0 到 +1.0 之間, UNSIGNED_BYTE (0 to 255) 變為 0.0 到 +1.0 之間,SHORT 也是轉換到 -1.0 到 +1.0 之間, 但比 BYTE 精確度高。

最常用的是標準化顏色資料。大多數情況顏色值範圍為 0.0 到 +1.0。 使用4個浮點型資料儲存紅,綠,藍和阿爾法通道資料時,每個頂點的顏色將會佔用16位元組空間, 如果你有複雜的幾何體將會佔用很多記憶體。代替的做法是將顏色資料轉換為四個 UNSIGNED_BYTE , 其中 0 表示 0.0,255 表示 1.0。現在每個頂點只需要四個位元組儲存顏色值,省了 75% 空間。

我們來修改之前程式碼實現。當我們告訴WebGL如何獲取顏色資料時將這樣

 // 告訴顏色屬性如何從colorBuffer中提取資料 (ARRAY_BUFFER)
  var size = 4;                 // 每次迭代使用四個單位資料
  var type = gl.UNSIGNED_BYTE;  // 資料型別是8位的 UNSIGNED_BYTE 型別。
  var normalize = true;         // 標準化資料
  var stride = 0;               // 0 = 移動距離 * 單位距離長度sizeof(type) 
                                // 每次迭代跳多少距離到下一個資料
  var offset = 0;               // 從緩衝的起始處開始
  gl.vertexAttribPointer(
      colorLocation, size, type, normalize, stride, offset)

如下向緩衝新增資料:

// 給矩形的兩個三角形
// 設定顏色值併發到緩衝
function setColors(gl) {
  // 設定兩個隨機顏色
  var r1 = Math.random() * 256; // 0 到 255.99999 之間
  var b1 = Math.random() * 256; // 這些資料
  var g1 = Math.random() * 256; // 在存入緩衝時
  var r2 = Math.random() * 256; // 將被擷取成
  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);
}

執行結果: