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