WebGL初探
目前,我們有很多方案可以快速的接觸到 WebGL 并繪制復雜的圖形,但***發現我們忽視了很多細節性的東西。當然,這對初學 WebGL 是有必要的,它能迅速提起我們對 WebGL 的學習興趣。當學習到更加深入的階段時,我們更想了解 WebGL 的工作機制,這也將對我們編程有極大的幫助。以上也是我想寫這樣一個系列的原因。
簡介
用更專業的描述講,WebGL (Web Graphics Library) 是一個用以渲染交互式 3D 和 2D 圖形的無需插件且兼容下一代瀏覽器的 JavaScript API,通過 HTML5 中 <canvas> 元素實現功能。WebGL 是由 Khronos Group 集團制定,而非 W3C 組織。目前,我們可以使用的是 WebGL ***個版本,它繼承自 OpenGL ES 2.0 。而 OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對手機、PDA 和游戲主機等嵌入式設備而設計。以下是各版本之間的關系圖:
Hello World
首先,我們將通過實現一個簡單的 WebGL 程序(清空繪圖區)叩開 WebGL 的大門。下面將實現一個最簡單的 WebGL 功能:
創建 canvas 元素
WebGL 采用 HTML5 中的 <canvas> 元素。為了使用 WebGL 進行 3D 渲染,你首先需要一個 canvas 元素。這里創建了一個 canvas 元素,并使用 onload 事件創建來初始化 WebGL 上下文。
- <body onload="start()">
- <canvas id="glcanvas" width="640" height="480">
- Your browser doesn't appear to support the HTML5 <code><canvas></code> element.
- </canvas>
- </body>
獲取 WebGL 上下文
目前,各瀏覽器基本都實現了對 WebGL 的支持,但 IE11 及 Edge 瀏覽器稍微有些不同。以下是對初始化 WebGL 的基本封裝:
- function initWebGL(canvas) {
- // 創建全局變量
- window.gl = null;
- try {
- // 嘗試獲取標準上下文,如果失敗,回退到試驗性上下文
- gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
- }
- catch(e) {
- throw '創建失敗。';
- }
- // 如果沒有GL上下文,馬上放棄
- if (!gl) {
- alert("WebGL初始化失敗,可能是因為您的瀏覽器不支持。");
- gl = null;
- }
- return gl;
- }
這里通過采用 canvas 的 getContext(contextType, contextAttributes) 方法判斷瀏覽器是否支持 WebGL,并創建其上下文。當返回值是 canvas 的上下文時,瀏覽器可支持 WebGL,為 null 時,則創建失敗。注意,在 IE11 及 Edge 瀏覽器下,需要使用 "experimental-webgl" 創建 WebGL,此處做了兼容處理。
清空繪圖區
下面將背景顏色設置為黑色,并清空緩存區。
- var gl; // WebGL的全局變量
- function start() {
- var canvas = document.getElementById("glcanvas");
- // 初始化 WebGL 上下文
- gl = initWebGL(canvas);
- // 只有在 WebGL 可用的時候才繼續
- if (gl) {
- // 設置清除顏色為黑色,不透明
- gl.clearColor(0.0, 0.0, 0.0, 1.0);
- // 清除顏色和深度緩存
- gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
- }
- }
這樣,我們可以在瀏覽器中看到一塊黑色區域。你可能已經注意到,WebGL 遵循的是傳統 OpenGL 顏色分量的取值范圍,從 0.0 到 1.0。RGB 的值越高,顏色越亮。注意,clear() 方法在這里清除顏色和深度緩存,而不是繪制區域的 <canvas>,該方法繼承自 OpenGL(基于多緩存模型)。實際還有模版緩存,但實際很少會被用到。
更進一步
上面我們完成了***個 WebGL 程序,但是我們還未接觸到 WebGL 的核心:可編程著色器。接下來,我們將使用可編程著色器在屏幕上繪制點??删幊讨魇且粋€較為復雜的概念,也有自己的編程語言 GLSL,后面將會又專門的文章具體講解可編程著色器。這里我們只需要簡單了解繪制的流程:
編寫著色器程序
- // 頂點著色器程序
- var VSHADER_SOURCE =
- 'void main() {\n' +
- ' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 設置頂點位置
- ' gl_PointSize = 10.0;\n' + // 設置點的大小
- '}\n';
- // 片元著色器程序
- var FSHADER_SOURCE =
- 'void main() {\n' +
- ' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n' + // 設置點的顏色,此處為白色
- '}\n';
上面程序是不是有中似曾相識的感覺?沒錯,GLSL 語言和 C 語言很類似。著色器程序中包含一個主函數,且返回值為空。其中vec4() 構造函數用于生成一個四維向量(x,y,z,w)。
編譯著色器
首先,需要用 createShader( type ) 方法生成相應類型的 WebGLShader。接著,使用 shaderSource( shader, sourceCode ) 作為 GLSL 源碼的鉤子函數。***使用 compileShader( shader ) 完成對著色器的編譯。程序中我們做了編譯后的校驗,當著色器編譯失敗時,會報出失敗并刪除著色器。
- function createShader (gl, type, sourceCode) {
- // 編譯著色器類型:頂點著色器及片元著色器。
- var shader = gl.createShader( type );
- gl.shaderSource( shader, sourceCode );
- gl.compileShader( shader );
- if ( !gl.getShaderParameter(shader, gl.COMPILE_STATUS) ) {
- var info = gl.getShaderInfoLog( shader );
- console.log( "無法編譯 WebGL 程序。 \n\n" + info);
- gl.deleteShader(shader);
- return null;
- }
- return shader;
- }
連接到可用程序
此時,著色器仍是不可用的,需要將其賦值到 WebGLProgram 上。這里主要進行了三步操作,首先,需要使用 createProgram() 方法創建和初始化一個 WebGLProgram 對象。接著,使用 gl.attachShader(program, shader) 將該對象結合兩個已經編譯的著色器。***,使用 linkProgram(program) 將 WebGLProgram 和著色器連接。
- function createProgram(gl, vshader, fshader) {
- // 創建著色器對象
- var vertexShader = createShader(gl, gl.VERTEX_SHADER, vshader);
- var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fshader);
- if (!vertexShader || !fragmentShader) {
- return null;
- }
- // 創建編程對象
- var program = gl.createProgram();
- if (!program) {
- return null;
- }
- // 賦值已創建的著色器對象
- gl.attachShader(program, vertexShader);
- gl.attachShader(program, fragmentShader);
- // 連接編程對象
- gl.linkProgram(program);
- // 檢查鏈接結果
- var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
- if (!linked) {
- var error = gl.getProgramInfoLog(program);
- console.log('鏈接程序失敗:' + error);
- gl.deleteProgram(program);
- gl.deleteShader(fragmentShader);
- gl.deleteShader(vertexShader);
- return null;
- }
- return program;
- }
使用可用著色器程序
這一步主要使用 useProgram(program) 方法告訴 GPU 使用程序。
- function initShaders(gl, vshader, fshader) {
- var program = createProgram(gl, vshader, fshader);
- if (!program) {
- console.log('創建程序失敗。');
- return false;
- }
- gl.useProgram(program);
- gl.program = program;
- return true;
- }
繪制一個點
***,使用 drawArrays(mode, first, count) 繪制一個點,該函數是一個非常強大的渲染函數,后續文章會有詳細介紹。此處只需要知道傳入 "POINTS" 繪制了一個點。
- // 繪制一個點
- gl.drawArrays(gl.POINTS, 0, 1);
至此,我們已經完成了繪制一個點的全部程序。當運行以上程序時,我們會在瀏覽器中看到一個白色的點。
結束語
到現在,我們雖然還沒有使用 WebGL 繪制三維圖形,但我們已經進入了 WebGL 世界。我們已經使用 WebGL 繪制了簡單的圖形。但是這只是 WebGL 的繪制的冰山一角,我們使用 WebGL 當然不是為了繪制這樣一個簡單的圖形。為了繪制更復雜的圖形,我們還有很多的細節需要去了解。但是無論如何,我們都已經開啟了 WebGL 的***步,其實問題也并沒有我們想象的那么難。