Hello World

就像它在備註部分提到的那樣,我們需要提供兩個功能。頂點著色器和片段著色器

讓我們從頂點著色器開始

// an attribute will receive data from a buffer
attribute vec4 position;

// all shaders have a main function
void main() {

  // gl_Position is a special variable a vertex shader 
  // is responsible for setting
  gl_Position = position;
}

如果整個事情是用 JavaScript 而不是 GLSL 編寫的,你可以想象它會像這樣使用

// *** PSUEDO CODE!! ***

var positionBuffer = [
  0, 0, 0, 0,
  0, 0.5, 0, 0,
  0.7, 0, 0, 0,
];
var attributes = {};
var gl_Position;

drawArrays(..., offset, count) {
  for (var i = 0; i < count; ++i) {
     // copy the next 4 values from positionBuffer to the position attribute
     attributes.position = positionBuffer.slice((offset + i) * 4, 4); 
     runVertexShader();
     ...
     doSomethingWith_gl_Position();
}

接下來我們需要一個片段著色器

// fragment shaders don't have a default precision so we need
// to pick one. mediump, short for medium precision, is a good default.
precision mediump float;

void main() {
  // gl_FragColor is a special variable a fragment shader
  // is responsible for setting
  gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple 
}

上面我們將 gl_FragColor 設定為 1, 0, 0.5, 1,紅色為 1,綠色為 0,藍色為 0.5,alpha 為 1。WebGL 中的顏色從 0 到 1。

現在我們已經編寫了 2 個函式,讓我們開始使用 WebGL

首先,我們需要一個 HTML canvas 元素

 <canvas id="c"></canvas>

然後在 JavaScript 中我們可以看一下

 var canvas = document.getElementById("c");

現在我們可以建立一個 WebGLRenderingContext

 var gl = canvas.getContext("webgl");
 if (!gl) {
    // no webgl for you!
    ...

現在我們需要編譯這些著色器以將它們放在 GPU 上,所以首先我們需要將它們變成字串。你可以以正常的方式獲取字串。通過使用 AJAX 連線,將它們放在非 JavaScript 型別的指令碼標記中,或者在這種情況下使用多行模板文字

var vertexShaderSource = `
// an attribute will receive data from a buffer
attribute vec4 position;

// all shaders have a main function
void main() {

  // gl_Position is a special variable a vertex shader 
  // is responsible for setting
  gl_Position = position;
}
`;

var fragmentShaderSource = `
// fragment shaders don't have a default precision so we need
// to pick one. mediump is a good default
precision mediump float;

void main() {
  // gl_FragColor is a special variable a fragment shader
  // is responsible for setting
  gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple 
}
`;

然後我們需要一個函式來建立著色器,上傳源並編譯著色器

function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);  
  gl.compileShader(shader);
  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }

  console.log(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}

我們現在可以呼叫該函式來建立 2 個著色器

var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

然後,我們需要將這兩個著色器連結到一個程式中

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var sucesss = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.log(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
}

並稱之為

var program = createProgram(gl, vertexShader, fragmentShader);

現在我們已經在 GPU 上建立了一個 GLSL 程式,我們需要為它提供資料。大多數 WebGL API 都是關於設定狀態以向我們的 GLSL 程式提供資料。在這種情況下,我們對 GLSL 程式的唯一輸入是 position,這是一個屬性。我們應該做的第一件事是查詢我們剛剛建立的程式的屬性位置

var positionAttributeLocation = gl.getAttribLocation(program, "position");

屬性從緩衝區獲取資料,因此我們需要建立緩衝區

var positionBuffer = gl.createBuffer();

WebGL 允許我們在全域性繫結點上操作許多 WebGL 資源。你可以將繫結點視為 WebGL 內部的全域性變數。首先,將繫結點設定為資源。然後,所有其他函式通過繫結點引用資源。所以,讓我們繫結位置緩衝區。

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

現在我們可以通過繫結點引用資料將資料放入該緩衝區

// three 2d points
var positions = [
  0, 0, 
  0, 0.5,
  0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

這裡有很多事情要做。第一件事是我們有 positions 這是一個 JavaScript 陣列。另一方面,WebGL 需要強型別資料,因此 new Float32Array(positions) 行建立一個 32 位浮點數的新陣列,並複製 positions 中的值。gl.bufferData 然後將該資料複製到 GPU 上的 positionBuffer。它使用位置緩衝區,因為我們將它繫結到上面的 ARRAY_BUFFER 繫結點。

最後一個引數 gl.STATIC_DRAW 是 WebGL 關於我們如何使用資料的提示。它可以嘗試使用該資訊來優化某些事物。gl.STATIC_DRAW 告訴 WebGL 我們不太可能更改這些資料。

既然我們已將資料放入緩衝區,我們需要告訴屬性如何從中獲取資料。首先,我們需要開啟屬性

gl.enableVertexAttribArray(positionAttributeLocation);

然後我們需要指定如何拉出資料

var size = 2;          // 2 components per iteration
var type = gl.FLOAT;   // the data is 32bit floats
var normalize = false; // use the data as is
var stride = 0;        // 0 = move size * sizeof(type) each iteration
var offset = 0;        // start at the beginning of the buffer
gl.vertexAttribPointer(
   positionAttributeLocation, size, type, normalize, stride, offset)

gl.vertexAttribPointer 的一個隱藏部分是它將當前的 ARRAY_BUFFER 繫結到屬性。換句話說,現在這個屬性繫結到 positionBuffer,我們可以自由地將其他東西繫結到 ARRAY_BUFFER 繫結點。

請注意,從我們的 GLSL 頂點著色器的角度來看,position 屬性是一個 vec4

attribute vec4 position;

vec4 是 4 浮點值。在 JavaScript 中你可以想到它像 position = {x: 0, y: 0, z: 0, w: 0}。上面我們設定了 size = 2。屬性預設為 0, 0, 0, 1,因此該屬性將從緩衝區中獲取其前 2 個值(x 和 y)。z 和 w 分別是預設值 0 和 1。

畢竟我們終於可以要求 WebGL 執行 GLSL 程式了。

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

這將執行我們的頂點著色器 3 次。我們的頂點著色器中的第一次 position.xposition.y 將被設定為 positionBuffer 的前 2 個值。第二次 position.xy 將被設定為第二個 2 值。最後一次將其設定為最後 2 個值。

因為我們將 primitiveType 設定為 gl.TRIANGLES,所以每次我們的頂點著色器執行 3 次時,WebGL 將根據我們設定 gl_Position 的 3 個值繪製一個三角形。無論我們的畫布大小是多少,這些值都在剪輯空間座標中,每個方向從 -1 到 1。

因為我們的頂點著色器只是將我們的 positionBuffer 值複製到 gl_Position,所以三角形將在剪輯空間座標處繪製

  0, 0, 
  0, 0.5,
  0.7, 0,

這些值如何轉換為畫素取決於 gl.viewport 設定。gl.viewport 預設為畫布的初始大小。由於我們沒有為畫布設定大小,因此預設大小為 300x150。從剪輯空間轉換為畫素(在 WebGL 和 OpenGL 文獻中通常稱為螢幕空間)WebGL 將繪製一個三角形

 clip space      screen space
   0, 0       ->   150, 75
   0, 0.5     ->   150, 112.5
 0.7, 0       ->   255, 75

WebGL 現在將渲染該三角形。對於要繪製它的每個畫素,它將呼叫我們的片段著色器。我們的片段著色器只是將 gl_FragColor 設定為 1, 0, 0.5, 1。由於 Canvas 是每通道 8 位畫布,這意味著 WebGL 將把值 [255, 0, 127, 255] 寫入畫布。

我們仍然沒有從評論中提到三件大事。紋理,變化和制服。每個都需要它自己的主題。