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.x
和 position.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]
寫入畫布。
我們仍然沒有從評論中提到三件大事。紋理,變化和制服。每個都需要它自己的主題。