WebGL是OpenGL ES 2.0的Web標準,它結合JavaScript在HTML5的<canvas></canvas>上渲染圖形。WebGL現在已經是HTML5 Canvas的繪製上下文之一。
因此在使用上的流程和OpenGL ES存在了諸多相似之處,函數名和參數也幾乎是相同的。
...
<canvas id="main-canvas"></canvas>
...
let gl = document.getElementById("main-canvas").getContext("webgl");
if (!gl) {
alert("無法使用WebGL!");
} else {
console.log(gl);
}
這樣,就獲取到了在canvas上渲染webgl的上下文。
接下來,在canvas上一切的渲染操作,最終都是要依靠這個gl變量來控制的。可以通過變量gl來使用WebGL API了。
着色器準備
首先要準備一下着色器的源碼,着色器(shader)是由專門的着色器語言(Shadering Language)編寫的,在OpenGL ES中,使用的是GLSL(OpenGL Shadering Language,一種類C++語言)。着色器是成對出現的,分別是頂點着色器:
<script type="notjs" id="vertex-shader-2d">
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
</script>
和片段着色器:
<script type="notjs" id="fragment-shader-2d">
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0.3, 0.5, 1);
}
</script>
頂點着色器接受一些輸入,輸出的是裁剪平面的頂點座標。
因爲最終在canvas上渲染圖形還是要使用JavaScript而不是GLSL,所以需要獲取這些GLSL代碼,然後編譯處理成JavaScript的對象。
// 獲取着色器源碼
const vertex_shader_source = document.querySelector("#vertex-shader-2d").text,
fragment_shader_source = document.querySelector("#fragment-shader-2d").text;
先編譯着色器:
// 編譯
function setupShader(gl, type, source) {
let shader = gl.createShader(type); // 先創建空着色器對象
gl.shaderSource(shader, source); // 載入着色器源碼
gl.compileShader(shader); // 編譯
let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
然後鏈接頂點着色器和片段着色器,生成一個着色器程序的js對象:
// 鏈接
function createProgram(gl, vertexShader, fragmentShader) {
let program = gl.createProgram(); // 先創建一個空的着色器程序
gl.attachShader(program, vertexShader); // 載入編譯好的頂點着色器
gl.attachShader(program, fragmentShader); // 載入編譯好的片段着色器
gl.linkProgram(program); // 鏈接
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
然後調用該函數,program變量就是包含了這一對着色器源碼的js對象:
let program = createProgram(gl, vertex_shader_source, fragment_shader_source);
因爲在着色器中有一個輸入值,即頂點着色器中的a_position,着色器接收輸入值必須從緩衝區中獲取,而獲取到的值需要在着色器中的正確位置放入,所以要找到頂點着色器中a_position的位置:
let position_attribute_location = gl.getAttribLocation(program, "a_position");
緩衝區準備
着色器設置好了之後,就要開始準備數據,作爲着色器的輸入了。
上文說過,作爲着色器的輸入數據,必須是在緩衝區中的數據。而現在沒有緩衝區,也沒有數據,所以接下來創建這兩個東西,然後把數據放入緩衝區中。
先創建緩衝區:
let position_buffer = gl.createBuffer();
然後使用ARRAY_BUFFER綁定點,綁定到這個position_buffer(使用ARRAY_BUFFER說明要用頂點數組方式繪圖):
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
緩衝區準備好了,接下來準備數據:
let positions = [
0, 0,
0, 0.2,
0.7, 0,
-0.5, -0.5,
-0.4, -0.2,
-0.7, 0,
];
然後通過ARRAY_BUFFER綁定點,將數據放到緩衝區中:
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
準備畫布
由於WebGL的裁剪空間是一個橫縱座標都是在[-1, 1]範圍內的矩形,而我們的畫布的尺寸一般不是這個,所以需要作一下尺寸上的映射:
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
然後清空一下畫布:
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
渲染
要開始渲染了。首先我們要指定着色器程序:
gl.useProgram(program);
然後啓用a_position這個輸入值(屬性):
gl.enableVertexAttribArray(position_attribute_location);
之後準備將緩衝區的數據輸送到着色器:
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(position_attribute_location, size, type, normalize, stride, offset);
對這裏參數的說明:
- size=2表示每次迭代讀取兩個數據,即x和y。由於頂點着色器中gl_Position的類型是vec4,包含x,y,z,w四個數據,而這裏只需要前兩個x和y。
- type=gl_FLOAT表示使用的是32爲浮點類型的數據。
- normalize=false表示不需要歸一化數據。
- offset=0表示從緩衝區的開始位置讀取數據。
最後設置圖元類型、通過drawArrays(),使用頂點數組中的頂點數據繪製:
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
結果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>GLSL-test</title>
</head>
<body>
<canvas id="main-canvas"></canvas>
<!-- 頂點着色器 -->
<script type="notjs" id="vertex-shader-2d">
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
</script>
<!-- 片段着色器 -->
<script type="notjs" id="fragment-shader-2d">
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0.3, 0.5, 1);
}
</script>
<script>
// 全局變量
let gl;
// 準備WebGL上下文
function prepareContext() {
// 使用Canvas
let main_canvas = document.querySelector("#main-canvas");
main_canvas.setAttribute("width", "640");
main_canvas.setAttribute("height", "640");
gl = main_canvas.getContext("webgl");
if (!gl) {
alert("無法使用WebGL!");
} else {
console.log(gl);
}
}
// 編譯
function setupShader(gl, type, source) {
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
// 鏈接
function linkShader(gl, vertexShader, fragmentShader) {
let program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
// 創建緩衝區,並填入頂點數據
function createBuffer(data) {
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
}
(function main() {
// 環境上下文準備
prepareContext();
// 着色器準備
const vertex_shader_source = document.querySelector("#vertex-shader-2d").text,
fragment_shader_source = document.querySelector("#fragment-shader-2d").text;
// 編譯
let vertex_shader = setupShader(gl, gl.VERTEX_SHADER, vertex_shader_source),
fragment_shader = setupShader(gl, gl.FRAGMENT_SHADER, fragment_shader_source);
// 鏈接
let shaderProgram = linkShader(gl, vertex_shader, fragment_shader);
let position_attribute_location = gl.getAttribLocation(shaderProgram, "a_position");
// 準備輸入數據
createBuffer([0, 0, -0.3, 0.2, 0.2, 0.5, -0.5, -0.5, -0.4, -0.2, -0.7, 0]);
// 準備畫布
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 加載着色器,準備讀取緩衝區的數據
gl.useProgram(shaderProgram);
gl.enableVertexAttribArray(position_attribute_location);
gl.vertexAttribPointer(position_attribute_location, 2, gl.FLOAT, false, 0, 0);
// 執行繪製
gl.drawArrays(gl.TRIANGLES, 0, 6);
})();
</script>
</body>
</html>
參考
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API/Tutorial