WebGL,一項允許開發人員在瀏覽器裡操縱GPU來顯示圖形的技術。讓我們一起走進WebGL的世界。
讀者對象
本系列適合具有基礎JavaScript知識的開發人員。
準備工作
我們應該在本地搭建好web伺服器,或者安裝了具有預覽功能的IDE。如果你安裝了Visual Studio,Nivk童鞋為我們開發了WebGL代碼提示功能,你可以通過以下步驟使Visual Studio支援WebGL代碼提示:開啟Visual Studio——點擊工具——點擊選項——展開文字編輯器——展開JavaScript——展開IntelliSense——點擊引用——切換引用組為Implicit(Web)——將http://www.teajs.net/WebGL-vs-doc.js引用添加到當前組。
此外我們還應該安裝了支援WebGL的瀏覽器,本系列將全部以Chrome為例。
本文的完整樣本可以在這裡下載(訪問密碼f6b0)。
本文目標
我們將一邊學習WebGL基礎知識一邊利用所學知識繪製一個六面有著不同顏色的立方體。最後我們將得到一個類似的立方體:
理想實驗
我們應該如何繪製立方體呢?我們可以在三維空間定義六個頂點,定義頂點間的串連順序,然後命令GPU按我們給定的點和順序把圖形畫出來,並在不同的面塗上不同的顏色。
那我們應該如何定義頂點呢?使用座標系來定義點已經是常識了。接下來我們來看看如何建立標準的笛卡爾三維座標系。
WebGL座標系統
習慣上,我們建立右手座標系,座標原點位於畫布中央,X軸單位長度為原點到畫布右邊緣的長度,Y軸單位長度類似。我們會發現X軸的單位長度和Y軸的不一致,這個問題將有之後介紹的投影矩陣來解決。先假設座標各軸的單位長度相同,我們來定義一下立方體的六個頂點。
頂點座標
如果立方體中心位於座標原點並假設邊長為2,我們可以得到V0=(1.0,1.0,1.0),V1=(-1.0,1.0,1.0),V2=(-1.0,-1.0,1.0),V3=(1.0,-1.0,1.0),V4=(1.0,1.0,-1.0),V5=(-1.0,1.0,-1.0),V6=(-1.0,-1.0,-1.0),V7=(1.0,-1.0,-1.0)。
如何繪製面
因為WebGL只能繪製三角形(還有點和線),所以為了繪製面V0-V1-V2-V3,我們可以通過繪製三角形V0-V1-V2和三角形V0-V2-V3來拼成面V0-V1-V2-V3。其他面類似。
纏繞順序
對於三角形V0-V1-V2,我們通過串連V0和V1然後串連V1和V2最後串連V2和V0這種逆時針的纏繞順序來繪製,也可以通過串連V0和V2然後串連V2和V1最後串連V1和V0這種順時針的纏繞順序來繪製。習慣上,我們採用逆時針的纏繞順序。
如果一個三角形正面各頂點的纏繞順序為逆時針,那麼如果我們站在這個三角形的背面就會感覺各頂點的纏繞順序為順時針。
WebGL通過三角形的纏繞順序來判斷正反面。如果我們採用逆時針的纏繞順序,那麼一個面各頂點的纏繞順序為逆時針為正面,反之為背面。背面是看不到的面,我們可以通過啟用WebGL的面剔除功能來剔除背面三角形。
繪製順序
下面我們來看看這個立方體的各個頂點的繪製順序,需要注意的是繪製的所有三角形都是逆時針的纏繞順序。
(從現在開始,三角形V0-V1-V2表示是通過串連V0和V1然後串連V1和V2最後串連V2和V0這種逆時針的纏繞順序繪製的。)
面V0-V1-V2-V3可通過三角形V0-V1-V2和三角形V0-V2-V3拼成。
面V4-V5-V6-V7可通過三角形V4-V6-V5和三角形V4-V7-V6拼成。(這裡要注意一下,面V4-V5-V6-V7的正面是朝向Z軸負方向的,這時候逆時針到底是怎麼繞的不易判斷。但是這個面的背面是朝向Z軸正方向,也就是說我們站在立方體前望向Z軸負方向,剛好看到的是這個面的背面,所以我們按順時針的順序來纏繞,就保證這個面的正面的纏繞順序為逆時針)
面V4-V5-V1-V0可通過三角形V4-V5-V1和三角形V4-V1-V0拼成。
面V7-V6-V2-V3可通過三角形V7-V2-V6和三角形V7-V3-V2拼成。
面V4-V0-V3-V7可通過三角形V4-V0-V3和三角形V4-V3-V7拼成。
面V5-V1-V2-V6可通過三角形V5-V2-V1和三角形V5-V6-V2拼成。
GPU處理方式
CPU 由專為順序串列處理而最佳化的幾個核心組成。基於CPU遍曆一個數組,一般我們處理完第一個元素才能處理第二個。而GPU 由數以千計的更小、更高效的核心組成,可以高效地處理並行任務。基於GPU遍曆一個數組,我們可以同時處理所有元素。我們將所有頂點資訊存入一個數組,然後傳遞給GPU處理。
WebGL渲染管線
那GPU接收到數組後是怎麼處理的呢?
來看個簡化的模型:
頂點數組:包含我們要提交給GPU的頂點資訊。
頂點著色器:處理頂點的程式。GPU將並行地在每個頂點上運行頂點著色器。頂點著色器的作用之一是得到頂點的位置資訊。
圖元裝配:經過頂點著色器我們得到了頂點的位置,圖元裝配階段將頂點串連成三角形(或串連成線段,或描述為點),然後考察新圖形是否位於畫布可見的地區內。可見地區內的圖形進入下個步驟,其他的刪除。
光柵化:經過圖元裝配我們得到了三角形的外形,光柵化階段將用像素來填充三角形。經過光柵化,我們得到了由像素描述的三角形,而非由頂點描述的三角形。
片段著色器:處理像素的程式。GPU將並行地在光柵化得到的每個像素上運行片段著色器。片段著色器的作用之一是指定每個像素的顏色。
深度測試:測試像素的前後關係。被其他像素遮擋的像素是無法被看到的,將在測試裡被丟棄。
框架緩衝區:到達框架緩衝區的像素將被顯示到螢幕上。
其中圖元裝配,光柵化,深度測試都是自動完成的,我們真正要關心的是頂點著色器和片段著色器。
頂點著色器
我們先來看一段完整的頂點著色器代碼:
attribute vec3 aVertexPosition;attribute vec3 aVertexColor;uniform mat4 uModelViewMatrix;uniform mat4 uProjectionMatrix;varying vec4 vColor;void main(){ gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); vColor = vec4(aVertexColor, 1.0);}
首先我們會明顯發現的是這不是JavaScript代碼。這是專門用於編寫OpenGL著色器的GLSL語言。不同於JavaScript,GLSL是一門強型別和編譯型的語言。
從第一行開始考察,attribute是儲存限定符,vec3是資料類型,aVertexPosition是變數名。接下來四行也是一樣的道理。
那麼attribute,uniform,varying是什麼意思呢?
attribute表示當頂點著色器在每個頂點運行時它修飾的變數每次都不一樣。
uniform表示當頂點著色器在每個頂點運行時它修飾的變數每次都一樣。
varying表示當頂點著色器在每個頂點運行時它修飾的變數的值最終都需要傳遞給片段著色器。因為三個頂點可以定義一個三角形,而很多像素才能裝配一個三角形,所以varying修飾的變數的值將經過插值後分配給片段著色器。
那vec3,mat4代表什麼呢?
vec3表示所修飾的變數的資料類型為三維向量。
mat4表示所修飾的變數的資料類型為4*4矩陣。
那麼這些變數將用來做什麼呢?
aVertexPosition將用來存放各個頂點的位置座標。因為頂點座標只需要三個分量XYZ,所以我們把aVertexPosition定義為vec3類型。因為在頂點著色器啟動並執行時候頂點的位置一般都不一樣,所以我們把它定義為attribute類型。
aVertexColor將用來存放各個頂點的顏色資訊。因為顏色資訊只需要三個分量RGB(假設物體完全不透明),所以和aVertexPosition的情況基本一樣。
uModelViewMatrix將用來存放模型視圖矩陣,uProjectionMatrix將用來存放投影矩陣。因為對圖形進行3D變換需要用到4*4矩陣,所以我們把它們的類型定義為mat4。因為我們一般是對整個3D物體進行變換的,所以對於每個頂點這些矩陣為不變數,所以我們把它們定義為uniform變數。
vColor將用於存放顏色資訊。顏色資訊從頂點數組途經頂點著色器和插值運算最終傳遞到片段著色器中。我們要給立方體的每個面定義不同的顏色,這些顏色資訊需要傳遞給片段著色器,要給片段著色器傳遞這種會變化的值,只能使用varying變數。
接下來我們會看到main函數,使用過C或之類語言的童鞋會很熟悉這是程式的入口。void表示這個函數沒有返回值。
下一個映入眼簾的是gl_Position。我們並沒有定義過這個變數,它是頂點著色器內建的變數,用於向GPU傳遞頂點的位置座標。我們通過vec4(aVertexPosition, 1.0)來得到一個四維向量,1.0表示它的第四維為1.0。為什麼需要一個四維向量呢?因為4*4矩陣只能和四維向量點乘。uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0)對原始的頂點座標進行矩陣變換,然後存入gl_Position。為什麼要進行矩陣變換呢?因為我們用三維座標來定義圖形,使用矩陣變換將方便我們對圖形進行平移旋轉縮放等操作,並最終能把三維的座標投影到二維的螢幕上去。
接下來的是vColor = vec4(aVertexColor, 1.0),我們通過vec4(aVertexColor, 1.0)來得到一個四維向量,第四維表示透明度,1.0為完全不透明,0.0為完全透明。我們把vec4(aVertexColor, 1.0)的值賦給vColor,值經過插值最終將傳遞給片段著色器。
片段著色器
precision highp float;varying vec4 vColor;void main(){ gl_FragColor = vColor;}
第一行表示片段著色器要採用高精度的浮點值。我們可以把highp換成mediump(中等精度)或lowp(低精度)來改變浮點值的精度。選擇什麼精度取決於你的需求,精度越高計算越慢越占記憶體越耗電。
第二行和頂點著色器的第五行一致,vColor的值來自於頂點著色器,但是經過了插值(自動完成)。
gl_FragColor是片段著色器內建的變數,用於向GPU傳遞每個像素的顏色值。這裡我們簡單的把vColor的值賦給gl_FragColor。
需要注意的是在片段著色器裡是不能定義attribute變數的。
編譯連結
GPU不能直接理解GLSL語言,我們需要編譯著色器原始碼並連結到著色器程式才能供GPU使用。方便的是我們可以在瀏覽器裡調用JavaScript API完成編譯連結工作。
身體力行
現在到了實戰的時候了。我們邊讀代碼邊講解細節問題。
先看看我們的HTML結構:
<!DOCTYPE html><html><head> <meta charset="utf-8" /> <title>六色立方</title> <!--<script src="promise-0.1.1.js"></script>--> <script src="gl-matrix.js"></script> <script> window.onload = function () { }; </script></head><body> <canvas id="webgl" width="960" height="540"></canvas></body></html>
因為JavaScript並不能直接支援矩陣和向量的運算,所以我們引用了gl-matrix庫,來協助我們簡化這類運算。gl-matrix的github地址為https://github.com/toji/gl-matrix。
如果你使用Chrome瀏覽器,注釋掉promise-0.1.1.js,這個檔案只是為了讓其他瀏覽器完整支援Promise API。(現在是學習Promise API的大好時機,Chrome完整支援,Firefox接近完整支援,微軟很早之前就為WinJS引入Promise,相信IE也即將支援。一個不錯的Promise API教程在這裡)
擷取3D繪圖上下文
var gl = webgl.getContext("webgl");
這個和擷取2D繪圖內容相關的方法差不多,只是參數從2d變成了webgl。
初始化著色器
著色器原始碼只是字串而已,我們定義一個JS變數vertexSource用來儲存頂點著色器的原始碼,定義一個JS變數fragmentSource用來儲存片段著色器的原始碼。最簡單的方法是我們直接把著色器原始碼的字串賦值給這倆變數。這種方法很好,除了不易讀。這裡我們採用另外一種方法,通過AJAX載入著色器原始碼的文字檔,然後提取出字串。
function get(url) { return new Promise(function (resolve) { var xhr = new XMLHttpRequest(); xhr.onload = function () { resolve(this.responseText); }; xhr.open("get", url); xhr.send(); });}Promise.all([get("source.vert"), get("source.frag")]).then(function (sources) { var vertexSource = sources[0]; var fragmentSource = sources[1];});
source.vert和source.frag是我們的兩個文字檔,如果你使用Visual Studio,啟動調試時會發現這倆檔案載入失敗了,我們需要讓web伺服器允許載入vert和frag這兩種格式,所以可以把Web.config改成這樣:
<?xml version="1.0"?><configuration> <system.web> <compilation debug="true" targetFramework="4.5.1"/> <httpRuntime targetFramework="4.5.1"/> </system.web> <system.webServer> <staticContent> <mimeMap fileExtension=".vert" mimeType="text/plain" /> <mimeMap fileExtension=".frag" mimeType="text/plain" /> </staticContent > </system.webServer></configuration>
得到原始碼的字串後,現在我們要來編譯它了,對於頂點著色器:
var vertexShader = gl.createShader(gl.VERTEX_SHADER);gl.shaderSource(vertexShader, vertexSource);gl.compileShader(vertexShader);
我們通過建立了一個頂點著色器對象,然後指定它的原始碼,最後進行了編譯。
片段著色器也是一樣的流程:
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);gl.shaderSource(fragmentShader, fragmentSource);gl.compileShader(fragmentShader);
接下來我們要把它們連結到著色器程式:
var program = gl.createProgram();gl.attachShader(program, vertexShader);gl.attachShader(program, fragmentShader);gl.linkProgram(program);gl.useProgram(program);
我們建立了著色器程式,然後將頂點著色器和片段著色器包含進程式,接著連結,最後通知WebGL我們要使用的著色器程式就是它了。
為頂點著色器傳入矩陣
我們的螢幕是二維的,卻要它能展示出圖形的立體感。就像繪畫裡的透視法,投影矩陣就是我們的透視法,協助完成三維到二維的變換。我們真正要關心的是怎麼搭建和搭建怎樣的一個3D情境,而不用去在意要怎麼把情境變換到平面裡。gl-matrix為我們完成了所有髒活。
我們知道觀察事物的角度不同得出的結論也往往不同。要描述某個3D情境,我們還需要指定觀察者的角度位置。
var modelViewMatrix = mat4.create();mat4.lookAt(modelViewMatrix, [4, 4, 8], [0, 0, 0], [0, 1, 0]);
我們建立了一個4*4單位矩陣,它將作為我們的模型視圖矩陣,用於描述物體的變換和觀察者的觀察方式。因為我們不打算對立方體進行變換操作,所以這裡我們只需調用lookAt函數來指定我們的觀察者站在(4,4,8)座標位置,眼睛望向(0,0,0)座標位置,頭頂朝向(0,1,0)座標位置。需要指出的是這裡JS上下文中的mat4是gl-matrix引入的全域變數,不同於著色器原始碼裡的mat4。
還記得我們在頂點著色器裡定義的uModelViewMatrix嗎?我們已經得到了模型變換矩陣modelViewMatrix,現在需要把它傳遞給頂點著色器的uModelViewMatrix。WebGL API並沒有提供直接給著色器裡的變數賦值的方法,所以我們需要先獲得變數在記憶體裡的地址,再在相應的記憶體裡寫入值。
var uModelViewMatrix = gl.getUniformLocation(program, "uModelViewMatrix");gl.uniformMatrix4fv(uModelViewMatrix, false, modelViewMatrix);
我們先調用了getUniformLocation擷取uniform變數uModelViewMatrix的地址,然後賦值給JS變數uModelViewMatrix。uniformMatrix4fv用於將模型視圖矩陣modelViewMatrix寫入相應的記憶體。false參數表示不需要轉置(行變列,列變行)這個矩陣,WebGL要求這個參數必須設定為false。
接下來我們來設定投影矩陣。
var projectionMatrix = mat4.create();mat4.perspective(projectionMatrix, Math.PI / 6, webgl.width / webgl.height, 0.1, 100);
我們建立了一個4*4單位矩陣,它將作為我們的投影矩陣。Math.PI / 6表示視角為30°,webgl.width / webgl.height表示視口的寬高比,0.1表示視錐近截面到觀察點的距離,100表示視錐遠截面到觀察點的距離。在近截面和遠截面所截的視錐外的圖形在圖元裝配階段將被捨棄。
圖中兩紅線的夾角為視角,藍點為觀察點,橙色截面為近截面,紫色截面為遠截面。
設定完投影矩陣,我們需要把它傳遞給頂點著色器的uniform變數uProjectionMatrix。
var uProjectionMatrix = gl.getUniformLocation(program, "uProjectionMatrix");gl.uniformMatrix4fv(uProjectionMatrix, false, projectionMatrix);
這部分和之前傳遞模型視圖矩陣的做法是相同的。
初始化緩衝區
現在開始傳遞立方體的頂點座標和顏色值了,還記得頂點著色器裡的兩個attribute變數aVertexPosition和aVertexColor嗎?aVertexPosition用來接收頂點座標,aVertexColor用來接收頂點顏色。
我們將通過繪製兩個三角形來合成一個立方體的面,六個面我們需要繪製十二個三角形。
先定義一個數組vertices,用來存放所有的頂點資訊。
var vertices = [ //前 1.0, 1.0, 1.0, 0.0, 0.8, 0.0, -1.0, 1.0, 1.0, 0.0, 0.8, 0.0, -1.0, -1.0, 1.0, 0.0, 0.8, 0.0, 1.0, -1.0, 1.0, 0.0, 0.8, 0.0, //後 1.0, 1.0, -1.0, 0.6, 0.9, 0.0, -1.0, 1.0, -1.0, 0.6, 0.9, 0.0, -1.0, -1.0, -1.0, 0.6, 0.9, 0.0, 1.0, -1.0, -1.0, 0.6, 0.9, 0.0, //上 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, -1.0, 1.0, -1.0, 1.0, 1.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, //下 1.0, -1.0, -1.0, 1.0, 0.5, 0.0, -1.0, -1.0, -1.0, 1.0, 0.5, 0.0, -1.0, -1.0, 1.0, 1.0, 0.5, 0.0, 1.0, -1.0, 1.0, 1.0, 0.5, 0.0, //右 1.0, 1.0, -1.0, 0.9, 0.0, 0.2, 1.0, 1.0, 1.0, 0.9, 0.0, 0.2, 1.0, -1.0, 1.0, 0.9, 0.0, 0.2, 1.0, -1.0, -1.0, 0.9, 0.0, 0.2, //左 -1.0, 1.0, -1.0, 0.6, 0.0, 0.6, -1.0, 1.0, 1.0, 0.6, 0.0, 0.6, -1.0, -1.0, 1.0, 0.6, 0.0, 0.6, -1.0, -1.0, -1.0, 0.6, 0.0, 0.6];
每行表示一個頂點,前三個元素表示該頂點的座標XYZ,後三個元素表示該頂點的顏色RGB。我們會發現WebGL中很多數值都是介於0.0~1.0(或-1.0~1.0),統一的取值範圍便於運算最佳化。對於立方體的一個面,四個頂點都是相同的顏色,但是四個頂點的位置座標卻各不相同。對於立方體的一個頂點,與它接壤的三個面裡這個頂點的位置座標都相同,但是因為三個面的顏色都不同導致這個頂點的顏色值在三個面裡都不相同。所以雖然理想中只需要6個頂點6種顏色,但是實際上我們不得不為每個面定義四個不同的頂點。
圖中括弧裡表示的是三個顏色分量。與其把一個頂點當做一個三態疊加的頂點,不如就把它當做是三個重疊的不同頂點。
vertices只是一個數組,WebGL並不能直接操作JS數組,我們需要把它轉換成類型化數組然後載入緩衝區。
var vertexBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
我們建立了一個緩衝區,然後綁定為gl.ARRAY_BUFFER,之後的緩衝區操作都將基於當前綁定的緩衝區。這和2D繪圖上下文中的fillStyle是相似的,給fillStyle綁定某種顏色後,之後的填充操作都將使用這種顏色,直到fillStyle再次被改變為止。
bufferData方法用於向當前緩衝區傳遞資料,這裡我們把vertices封裝成Float32Array三十二位浮點類型化數組然後傳入。gl.STATIC_DRAW表示我們的資料只載入一次,然後在之後繪圖中多次使用。
在我們把頂點資訊載入緩衝區後,WebGL已可以直接操作它。是時候把頂點資訊傳入頂點著色器了。
var aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 24, 0);gl.enableVertexAttribArray(aVertexPosition);
我們通過getAttribLocation擷取了aVertexPosition的地址,這一步和我們擷取uModelViewMatrix的地址的方法是相似的。aVertexPosition需要接收頂點的位置資訊,但是我們的頂點數組包含了位置和顏色兩種資訊,所以需要調用vertexAttribPointer把位置資訊提取出來。實參3表示頂點數組中我們是用3個浮點值表示一個頂點,gl.FLOAT表示我們的資料為浮點類型的值,false表示我們的資料已經是介於-1.0~1.0的gl.FLOAT類型,不需要正常化。24表示的是從同一種類型的元素到下一個同類型元素的跨度,頂點數組的第一行第一個元素是位置座標,第二行第一個元素也是位置座標,它們間跨了6個元素,因為我們的頂點數組是用Float32Array類型儲存的,所以一個元素佔了4位元組,6個元素需要跨24位元組。0表示每個跨度裡我們的位置座標類型的第一個元素的位移量,因為我們先排位置資訊再排顏色資訊,所以它的位移量為0。
enableVertexAttribArray告訴WebGL我們要使用頂點數組。預設情況下是使用常量頂點資料,但是因為我們的每個頂點都有各自不同的位置資訊,所以不能使用常量頂點資料。
傳入頂點位置資訊後我們再傳入頂點顏色資訊:
var aVertexColor = gl.getAttribLocation(program, "aVertexColor");gl.vertexAttribPointer(aVertexColor, 3, gl.FLOAT, false, 24, 12);gl.enableVertexAttribArray(aVertexColor);
這和之前傳入頂點位置資訊的操作是很相似的。我們的顏色資訊是用0.0~1.0範圍的值表示的,如果我們用0~255範圍的值來表示顏色資訊的話,那麼就需要vertexAttribPointer的gl.FLOAT實參改為gl.UNSIGNED_BYTE,false實參修改為true,WebGL內部將自動為我們把顏色資訊的值映射到0.0~1.0的範圍。因為在一個跨度中前三個是位置資訊,所以位移量設定為3*4位元組=12位元組。
頂點資訊傳遞完後,現在該傳遞繪製順序的資訊了。
var indices = [ 0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 8, 9, 10, 8, 10, 11, 12, 14, 13, 12, 15, 14, 16, 17, 18, 16, 18, 19, 20, 22, 21, 20, 23, 22];
我們定義了一個數組indices,元素的值i表示頂點數組vertices所表示的第i個頂點(從零開始計數)。數組indices的每三個元素表示一個三角形的繪製順序。所以0,1,2表示繪製三角形V0-V1-V2的順序,0,2,3表示繪製三角形V0-V2-V3的順序。數組indices的每行表示使用兩個三角形合成的一個面。
指定繪製順序的數組我們稱之為索引數組。
接著,我們把索引數組載入緩衝區:
var indexBuffer = gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array(indices), gl.STATIC_DRAW);
這和把頂點資訊載入緩衝區的步驟是相似的。頂點數組我們綁定為gl.ARRAY_BUFFER,而索引數組則需要綁定為gl.ELEMENT_ARRAY_BUFFER。因為我們的索引數組的元素都是很小的正整數,所以這裡把數組indices封裝成8位無符號類型化數組。
繪製
到現在,我們所需各種資訊都齊全了,只差把它繪製出來。
gl.enable(gl.DEPTH_TEST);gl.enable(gl.CULL_FACE);gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_BYTE, 0);
我們啟用了深度測試和面剔除功能。然後我們把清除顏色設定為黑色,接著我們清除了前一次繪圖遺留的顏色緩衝和深度緩衝。雖然這裡我們只繪製一幀,不清除也一樣,但是每次繪圖前清除緩衝是個好習慣。
最後我們調用drawElements來繪製圖形。drawElements基於WebGL當前綁定的索引數組來繪製圖形。第一個參數表示要用什麼圖元渲染,這裡指定為三角形;第二個參數表示要使用多少個索引數組元素來渲染圖形,這裡我們指定為indices.length表示我們需要所有的索引數組元素來渲染;第三個參數表示索引數組元素的類型,因為我們把數組indices封裝成Uint8Array,所以元素的類型指定為gl.UNSIGNED_BYTE,如果我們是用Uint16Array來封裝,則這個參數指定為gl.UNSIGNED_SHORT;最後一個參數我們要從索引數組的第幾個元素開始渲染,所有元素都需要用來渲染,所以這個參數指定為0。
結束語
經過這些步驟,終於我們得到了一個六色立方體。你可以把樣本載入到瀏覽器中查看,然後試試修改觀察者的位置角度或頂點數組的位置顏色,最後重新整理瀏覽器看看圖形發生了什麼變化。
WebGL更多細節本文並沒有詳細展開,比如光照紋理等,將在WebGL系列的後續文章繼續介紹。
歡迎留言交流。