最終開始WebGL的演示樣例了,......javascript
開始html
使用WebGL的步驟,很是easy:java
1. 得到WebGL的渲染環境(也叫渲染上下文)。node
2. 發揮你的想象力,利用《WebGL參考手冊》中的函數,參考《OpenGL ES 2.0編程指南》和各類已有的WebGL演示,針對得到的WebGL渲染環境進行操做,表達出你的意境。web
爲了得到WebGL的渲染環境,或者說,爲了給WebGL一個渲染環境,咱們需要在Web頁面中定義一個名稱爲「canvas 」的HTML5元素。每個canvas元素都可以相應一個WebGL渲染環境。注意,我說的是「可以相應」,而不是「相應」或「必須相應」;這是因爲canvas元素還可以用做Web2D的渲染環境。——假設在一個頁面中定義了多個canvas元素,咱們就可以同一時候運行多個WebGL渲染。編程
定義canvas元素不難,只是要記得給它指定一個id,以方便咱們在js中對它進行訪問。另外,由於要在js中使用向量和矩陣等相關的操做,還要引入一個js文件「glMatrix-0.9.5.js」。該文件可以從網絡下載,後面的版本可能會有所不一樣。作完這兩項工做後,個人HTML文件的完整代碼例如如下:canvas
<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設置爲一、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)。