最終開始WebGL的演示樣例了,......
開始
使用WebGL的步驟,非常easy:
1. 獲得WebGL的渲染環境(也叫渲染上下文)。
2. 發揮你的想象力,利用《WebGL參考手冊》中的函數,參考《OpenGL ES 2.0編程指南》和各種已有的WebGL演示,針對獲得的WebGL渲染環境進行操作,表達出你的意境。
為了獲得WebGL的渲染環境,或者說,為了給WebGL一個渲染環境,我們須要在Web頁面中定義一個名稱為“canvas ”的HTML5元素。每一個canvas元素都能夠相應一個WebGL渲染環境。注意,我說的是“能夠相應”,而不是“相應”或“必須相應”;這是由於canvas元素還能夠用作Web2D的渲染環境。——假設在一個頁面中定義了多個canvas元素,我們就能夠同一時候運行多個WebGL渲染。
定義canvas元素不難,只是要記得給它指定一個id,以方便我們在js中對它進行訪問。另外,因為要在js中使用向量和矩陣等相關的操作,還要引入一個js文件“glMatrix-0.9.5.js”。該文件能夠從網絡下載,后面的版本可能會有所不同。做完這兩項工作后,我的HTML文件的完整代碼例如以下:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>
</head>
<body>
<canvas id="myCanvas" style="border:1px solid red;" width='600px' height='450px'></canvas>
</body>
</html>
細致看,我還給canvas元素指定了一個像素的紅色邊框,這在沒有WebGL渲染或渲染失效的情況下,易於觀察我們所要操作的區域。我還指定了該元素的寬度為600像素,高度為450像素。由這個寬度和高度所形成的元素的區域,就是我們能夠操作的WebGL渲染區域。只是,相同請注意,我說的還是“能夠”,由於真正可實際操作的WebGL渲染區域是須要我們使用WebGL API進行設置的。這個元素區域是我們能夠設置的最大有效區域。
獲得WebGL的渲染環境,直接調用canvas元素的getContext("webgl")方法就可以。只是要注意的是,因為眼下WebGL還處於實驗室階段,傳入的參數可能是“experimental-webgl”。詳細的代碼例如以下:
var myCanvasObject = document.getElementById('myCanvas');
var webgl = myCanvasObject.getContext("experimental-webgl");
注意,為了方便演示,如無必要,我不會寫容錯性的代碼。
假設沒有發生錯誤的話,webgl就是WebGL的渲染環境。必須說明且你必須記住,不論什么對WebGL函數、常量等的調用,都須要通過渲染環境進行,如webgl.viewport(...)、webgl.VERTEX_SHADER(此處的webgl就是myCanvasObject.getContext("experimental-webgl")的返回值)。通常,WebGL應用不是幾個函數就搞定的;也就是說,webgl這個渲染環境須要在N多個地方使用,為便於訪問,我把它存儲在全局變量中。在本章的演示樣例中,WebGL相關的初始化無需用戶交互,為方便起見,我放在了body的onload事件中(如無必要,以后的演示樣例也會如此),於是,我的HTML文件的內容變成了以下這個樣子:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>
<script>
var webgl = null;
function Init()
{
var myCanvasObject = document.getElementById('myCanvas');
webgl = myCanvasObject.getContext("experimental-webgl");
}
</script>
</head>
<body onload='Init()'>
<canvas id="myCanvas" style="border:1px solid red;" width='>600px' height='450px'></canvas>
</body>
</html>
以下進行一些實際的事情。
首先,設定WebGL的渲染區域(視見區):webgl.viewport(0, 0, width, height)。該渲染區域,並非你要直接操作(也不能、不應當操作;除非你的是WebD3D(如果有的話)之類的玩意)的區域(即使看起來非常像)。這點和2D渲染不同。該區域事實上是一個終於結果的顯示區域。當你在WebGL內部畫好圖像之后,WebGL會自己主動通過某種方式,將其映射到這個區域中。比如,如果我們指定視見區的寬和高都是10px;在WebGL中,我們畫了一個5px*20px的圖像(如果能夠的話);那么,終於你看到的可能並非原圖中5px*10px的某部分,而可能是5px*20px這整幅圖像被“拉伸”到10px*10px之后的效果。“拉伸”僅僅是對前面句子“通過某種方式”中的“方式”一詞的一種形象的說法,並不准確;它究竟怎樣進行,這個是着色器的事情。
WebGL中有兩種着色器:頂點着色器和片段着色器。當中,頂點着色器用來處理頂點的位置;片段着色器用來處理頂點的顏色。什么是頂點?簡單說,頂點就是定義了你要繪畫的那些圖像上的點。比方,兩個點A和B能夠確定一條線段,那么,在3D中,我們就說,A和B是線段AB的頂點。
着色器用WebGL函數createShader()創建。該函數接收一個參數,用來指定要創建的着色器的類型。該類型是一個枚舉量,頂點着色器用WebGL枚舉FRAGMENT_SHADER表示, 片段着色器用WebGL枚舉VERTEX_SHADER表示。盡管你能夠直接指定一個和枚舉量相等的整數作為傳入參數,只是在不同的瀏覽器上,這些整數值可能不同,關鍵是不好記,easy出錯,所以,還是建議使用枚舉量。
創建好着色器后,你還須要指定它們將3D內容轉換到視見區的“方式”。該“方式”是一個字符串,因為它由符合WebGL着色語言語法的語句組成的,因此我們稱之為源代碼。指定着色器的源代碼調用WebGL函數shaderSource(shaderObject, sourceCode)就可以。此處有個算不上是技巧的技巧。在不使用技巧之前,設置源代碼的js語句應當是這個樣子:
var source = "attribute vec3 v3Position;void main(void){gl_Position = vec4(v3Position, 1.0);}";
webgl.shaderSource(shaderObject, source);
假設你略微思考一下,就可能發現不爽的地方。一般,頂點着色器要運行的動作是復雜的,須要編寫的着色語言語句也是不少的。直接用字符串提供的話,不方便改動和閱讀。因此,我們有必要利用一下HTML,讓那些着色語句以直觀的、格式化的形式顯示在HTML的源代碼之中,但不顯示在終於的頁面中。比方,我們能夠將着色語句放置在一個樣式指定為不可見不顯示的塊元素中。但另一種更好的做法,就是將着色語句寫在一個單獨的script標記中,如以下這般(script標記中的type屬性無關緊要;以下演示樣例中的設置僅僅是為了好看。當然,假設你聰明點的,能夠用它來決定該script所相應的着色器的類型;只是在這樣的情況下,將typ設置為1、2或a、b,或者把type替換為其它不論什么屬性,比方xyz,效果都是一樣的。唯一要注意的就是,你在進行推斷時使用的屬性和值必須和此處所指定的同樣):
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 v3Position;
void main(void)
{
gl_Position = vec4(v3Position, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
void main(void)
{
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
源代碼的設置語句也要作出對應改動。首先,獲取script中的內容,這兒有一個現成的js函數,例如以下:
function ShaderSourceFromScript(scriptID)
{
var shaderScript = document.getElementById(scriptID);
if (shaderScript == null) return "";
var sourceCode = "";
var child = shaderScript.firstChild;
while (child)
{
if (child.nodeType == child.TEXT_NODE ) sourceCode += child.textContent;
child = child.nextSibling;
}
return sourceCode;
}
然后,將獲取到的內容(已經是一個字符串)設置為着色器的源代碼:
webgl.shaderSource(vertexShaderObject, ShaderSourceFromScript("shader-vs"));
webgl.shaderSource(fragmentShaderObject, ShaderSourceFromScript("shader-fs"));
OK,着色器有了,源代碼也有了,你應當也想到了,既然是源代碼,是須要編譯的吧?確實,着色語言看起來有點象C,某些行為也象C,須要編譯和鏈接。
着色器源代碼的編譯使用WebGL函數compileShader(shaderObject)。編譯過程中,相同可能碰到一些諸如語法錯誤之類的問題。因此,在編譯之后,須要檢查一下編譯的結果狀態。假設編譯沒有成功,則WebGL渲染就不能正常進行。檢查編譯狀態使用WebGL函數getShaderParameter(shaderObject, WebGL.COMPILE_STATUS)。該函數返回一個布爾值,true表示編譯成功,false表示有錯。要注意,有錯,不一定就是語法錯誤,還可能是着色器中使用的資源超出了瀏覽器所能支持的范圍,就象C程序中無法為某個數組分配4G大小的內存那樣。
接下來是鏈接。鏈接須要用到一個新的對象:程序對象。程序對象的創建使用WebGL函數createProgram()。在鏈接之前,須要確保着色器對象已附加到程序對象;附加着色器通過WebGL函數attachShader(programObject, shaderObject)進行。此處要說明的是,附加操作能夠在鏈接之前的不論什么時刻進行,不用管着色器對象是否已經通過編譯、是否已經指定了源代碼,僅僅要它滿足一個條件,就是一個程序對象僅僅能且必須附加一個頂點着色器和一個片段着色器。鏈接通過WebGL函數linkProgram(programObject)進行。它會作很多事情,詳細信息可參考《OpenGL ES 2.0 編程指南》的《第四章 着色器和程序/程序的創建和鏈接》和其它相關章節,在此不作多述。相同,鏈接也會有成功和失敗,這可通過調用WebGL函數getProgramParameter(programObject, WebGL.LINK_STATUS)獲得。
最后,還要使用WebGL函數useProgram(programObject)來指定WebGL使用哪個程序對象進行渲染。
但要讓WebGL渲染出東西來,我們還要向WebGL提供數據。在我進行簡單地復述解說之前,你最好首先看一遍《OpenGL ES 2.0 編程指南》的《第六章 頂點屬性、頂點數組和緩沖對象》。
WebGL中,有兩種頂點數據:一種是常量數據,一種是數組數據。所謂常量數據,就是指頂點的某個屬性(如,顏色)值恆定不變。而數組數據是指,頂點的某個屬性(如,位置)並不是恆定不變,而是由一系列不全然同樣的值組成;我們將這些值組合成一個數組。換言之,數組中每一個元素都相應着該屬性的一個可能值。注意,此處說的是可能值,由於在一些時候,我們會跳過數組中的一些值。當進行渲染的時候,WebGL會依照我們指定的規則枚舉數組中的元素,將枚舉到的元素的值傳遞到着色器中的相應變量中。
本章演示樣例,僅僅是簡單地顯示一個三角形,所需的數據是三個頂點的位置。因為這三個位置各不同樣(否則也不會組成一個三角形),所以我們須要使用數組數據。
首先,我們用js中的數組定義好這個三個頂點位置。要注意的是,WebGL中的坐標系的范圍是[-1, +1]。在沒有使用坐標轉換之前,我們定義的坐標范圍不能超出這個范圍,否則就會顯示不對。另外,我們是在3D中畫三角形,因此,使用的是三維坐標(當然,你也能夠使用四維坐標)。
var jsArrayData = [
0.0, 1.0, 0.0,//上頂點
-1.0, -1.0, 0.0,//左頂點
1.0, 0.0, 0.0];//右頂點
你發現,jsArrayData僅僅是非常普通的一維數組,共同擁有9個元素,每三個為一組,每組和一個頂點位置相應。WebGL無法直接訪問js中的數據,我們要通過一定的方法將這些數據弄到WebGL能夠訪問的地方。這須要借助WebGL提供的API函數,過程例如以下:
1. 使用WebGL函數createBuffer()創建一塊WebGL能夠訪問的存儲區(我們稱之為緩沖)。
2. 將創建的存儲區設置為對應存儲區類型的當前操作對象,這通過WebGL的緩沖綁定函數bindBuffer(WebGL.ARRAY_BUFFER, buffer)完畢。該函數的第一個參數表示要設置的存儲區類型,能夠為WebGL.ARRAY_BUFFER和WebGL.ELEMENT_ARRAY_BUFFER;前者表示綁定的緩沖為頂點數組數據,后者表示綁定的緩沖為頂點元素數組數據(對於它是啥,能夠參考《OpenGL ES 2.0 編程指南》,或者耐心等待我在后面章節的復述解說)。
3. 將js中的數據“拷貝”到WebGL緩沖中:WebGL函數bufferData(WebGL.ARRAY_BUFFER, new Float32Array(jsArrayData), WebGL.STATIC_DRAW)。該函數的第一個參數和bindBuffer意義同樣。第二個參數就是我們要拷貝的數據了。僅僅只是,我們須要將js中的數組略微處理一下,轉換為WebGL能夠識別的數據格式:ArrayBuffer或ArrayBufferView。當中,ArrayBufferView又有下面幾種子類型:
. Int8Array
. Uint8Array
. Int16Array
. Uint16Array
. Int32Array
. Uint32Array
. Float32Array
. Float64Array
要注意,這些類型是為了WebGL而產生的瀏覽器內建類型;我們能夠在js中直接使用它們。它們的一個主要作用,就是將js中的數組轉換為WebGL能夠識別的數據格式。轉換方式就和上面見到的那般簡單,僅僅要將js數組作為參數傳遞給構造函數就可以。
bufferData的第三個參數指定緩沖的使用方法,對於它的詳細解說,你能夠參考《OpenGL ES 2.0 編程指南》,或者耐心等待以后的章節。
數據到此就設置完了,接下來就應當讓WebGL運行畫圖了。
畫圖有兩種方式,一個是依據實際傳入的頂點數組數據畫圖,另一種是依據傳入的頂點元素數組數據畫圖。此處我們用的是前者,后者在以后的章節復述解說,或者你也能夠參考《OpenGL ES 2.0 編程指南》。
無論是頂點數組數據還是頂點元素數組數據,在一個WebGL應用中,一般會同一時候有N個。那么在畫圖的時候,我們就首先須要告訴WebGL,我們要用的是哪個。此操作和上面設置js數據到WebGL中同樣,都是通過WebGL函數bindBuffer來完畢。可是,光這樣還不行,由於我們還須要讓WebGL把頂點數組數據和着色器中的變量聯系起來;僅僅有這樣,着色器才干訪問到我們設置給它的頂點數據(位置,顏色等)。這須要兩個步驟:
1. 將着色器中的變量(必須是attribute變量)關聯到一個屬性索引,使用WebGL函數bindAttribLocation(programObject, positionIndex, "shaderAttributeName")。在本章演示樣例中,第二個參數為0;第三個參數屬性名稱為“v3Position”。注意,該操作必須在程序對象鏈接前進行(這點,你將在終於的演示樣例中看到),否則無效。
2. 使用WebGL函數enableVertexAttribArray(positionIndex)啟用對應關聯索引上的數組數據或元素數組數據。
3. 通過以下的WebGL函數,指定關聯索引上的數組數據或元素數組數據的正確信息:
void vertexAttribPointer(GLuint positionIndex, GLint size, GLenum type,
GLboolean normalized, GLsizei stride, GLintptr offset);
當中,size之單個數據的大小。比方,頂點的位置我們一般用(x,y,z)表示,則,此值為3;頂點的紋理坐標用(s,t)表示,則此值為2。type指定數據的類型,能夠為WebGL的BYTE、UNSIGNED_BYTE、SHORT、UNSIGNED_SHORT、FLOAT、FIXED等。normalized指定數據轉換為浮點型時,是否須要規范化。stride指定相鄰的兩個數據之間的間隔(詳解參考《OpenGL ES 2.0 編程指南》的《第六章 頂點屬性、頂點數組和緩沖對象/頂點數組》)。offset指定起始數據的偏移,以字節為單位。
畫圖的最后一步工作,是調用WebGL函數drawArrays(mode, first, count)或drawElements(mode, count, type, offset)運行圖形繪畫。這兩個函數的第一個參數mode,指定繪畫的模式,有點、線、三角形、三角扇等(詳細有哪些及其效果,請參考《OpenGL ES 2.0 編程指南》的《第七章 基元集和光柵化》)。函數drawElements相同在以后復述介紹,或自行參考《OpenGL ES 2.0 編程指南》的相關章節。函數drawArrays的第二個參數指定起始頂點的索引。第三個參數指定要繪畫的頂點的個數。在本節演示樣例中,我們的繪畫模式為三角形,起始點是0,繪畫個數是3(由於三角形僅僅能有三個頂點)。注意,為了確保正確,你需要在每幀渲染開始之前進行必需要的擦除:首先使用WebGL函數clearColor(red, green, blue, alpha)等設置擦除信息,然后調用WebGL函數clear(WebGL.COLOR_BUFFER_BIT)運行擦除。
如今,把上面的一切結合到一起,整個HTML源代碼例如以下:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 v3Position;
void main(void)
{
gl_Position = vec4(v3Position, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
void main(void)
{
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
<script>
function ShaderSourceFromScript(scriptID)
{
var shaderScript = document.getElementById(scriptID);
if (shaderScript == null) return "";
var sourceCode = "";
var child = shaderScript.firstChild;
while (child)
{
if (child.nodeType == child.TEXT_NODE ) sourceCode += child.textContent;
child = child.nextSibling;
}
return sourceCode;
}
var webgl = null;
var vertexShaderObject = null;
var fragmentShaderObject = null;
var programObject = null;
var triangleBuffer = null;
var v3PositionIndex = 0;
function Init()
{
var myCanvasObject = document.getElementById('myCanvas');
webgl = myCanvasObject.getContext("experimental-webgl");
webgl.viewport(0, 0, myCanvasObject.clientWidth, myCanvasObject.clientHeight);
vertexShaderObject = webgl.createShader(webgl.VERTEX_SHADER);
fragmentShaderObject = webgl.createShader(webgl.FRAGMENT_SHADER);
webgl.shaderSource(vertexShaderObject, ShaderSourceFromScript("shader-vs"));
webgl.shaderSource(fragmentShaderObject, ShaderSourceFromScript("shader-fs"));
webgl.compileShader(vertexShaderObject);
webgl.compileShader(fragmentShaderObject);
if(!webgl.getShaderParameter(vertexShaderObject, webgl.COMPILE_STATUS)){alert("error:vertexShaderObject");return;}
if(!webgl.getShaderParameter(fragmentShaderObject, webgl.COMPILE_STATUS)){alert("error:fragmentShaderObject");return;}
programObject = webgl.createProgram();
webgl.attachShader(programObject, vertexShaderObject);
webgl.attachShader(programObject, fragmentShaderObject);
webgl.bindAttribLocation(programObject, v3PositionIndex, "v3Position");
webgl.linkProgram(programObject);
if(!webgl.getProgramParameter(programObject, webgl.LINK_STATUS)){alert("error:programObject");return;}
webgl.useProgram(programObject);
var jsArrayData = [
0.0, 1.0, 0.0,//上頂點
-1.0, -1.0, 0.0,//左頂點
1.0, 0.0, 0.0];//右頂點
triangleBuffer = webgl.createBuffer();
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsArrayData), webgl.STATIC_DRAW);
webgl.clearColor(0.0, 0.0, 0.0, 1.0);
webgl.clear(webgl.COLOR_BUFFER_BIT);
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
webgl.enableVertexAttribArray(v3PositionIndex);
webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 0, 0);
webgl.drawArrays(webgl.TRIANGLES, 0, 3);
}
</script>
</head>
<body onload='Init()'>
<canvas id="myCanvas" style="border:1px solid red;" width='600px' height='450px'></canvas>
</body>
</html>
你能夠將上面的源代碼拷貝到一個HTML文件里,然后在支持WebGL的瀏覽器中執行。在我使用的FF瀏覽器上,執行結果例如以下:

從圖中,你能夠判斷出WebGL x和y軸的坐標系統,左下角為(-1, -1), 右上角為(1, 1)。
