WebGL
WebGL 是針對 Canvas 的 3D 上下文桥胞。與其他 Web 技術(shù)不同,WebGL 并不是 W3C 制定的標(biāo)準(zhǔn),而
是由 Khronos Group 制定的。其官方網(wǎng)站是這樣介紹的:“Khronos Group 是一個(gè)非盈利的由會(huì)員資助的
協(xié)會(huì)昆码,專注于為并行計(jì)算以及各種平臺(tái)和設(shè)備上的圖形及動(dòng)態(tài)媒體制定無版稅的開放標(biāo)準(zhǔn)×诖ⅲ” Khronos
Group 也設(shè)計(jì)了其他圖形處理 API赋咽,比如 OpenGL ES 2.0。瀏覽器中使用的 WebGL 就是基于 OpenGL ES
2.0 制定的吨娜。
類型化數(shù)組
WebGL 涉及的復(fù)雜計(jì)算需要提前知道數(shù)值的精度脓匿,而標(biāo)準(zhǔn)的 JavaScript 數(shù)值無法滿足需要。為此宦赠,WebGL 引入了一個(gè)概念陪毡,叫類型化數(shù)組(typed arrays)米母。類型化數(shù)組也是數(shù)組,只不過其元素被設(shè)置為特定類型的值毡琉。
類型化數(shù)組的核心就是一個(gè)名為 ArrayBuffer 的類型爱咬。每個(gè) ArrayBuffer 對象表示的只是內(nèi)存中指定的字節(jié)數(shù),但不會(huì)指定這些字節(jié)用于保存什么類型的數(shù)據(jù)绊起。通過 ArrayBuffer 所能做的,就是為了將來使用而分配一定數(shù)量的字節(jié)燎斩。
視圖
使用 ArrayBuffer(數(shù)組緩沖器類型)的一種特別的方式就是用它來創(chuàng)建數(shù)組緩沖器視圖虱歪。其中,最常見的視圖是 DataView栅表,通過它可以選擇 ArrayBuffer中一小段字節(jié)笋鄙。為此,可以在創(chuàng)建 DataView實(shí)例的時(shí)候傳入一個(gè) ArrayBuffer怪瓶、一個(gè)可選的字節(jié)偏移量(從該字節(jié)開始選擇)和一個(gè)可選的要選
擇的字節(jié)數(shù)萧落。
示例:
//基于整個(gè)緩沖器創(chuàng)建一個(gè)新視圖
var view = new DataView(buffer);
//創(chuàng)建一個(gè)開始于字節(jié) 9 的新視圖
var view = new DataView(buffer, 9);
//創(chuàng)建一個(gè)從字節(jié) 9 開始到字節(jié) 18 的新視圖
var view = new DataView(buffer, 9, 10);
類型化視圖
類型化視圖一般也被稱為類型化數(shù)組,因?yàn)樗鼈兂嗽乇仨毷悄撤N特定的數(shù)據(jù)類型外洗贰,與常規(guī)的
數(shù)組無異找岖。類型化視圖也分幾種,而且它們都繼承了 DataView敛滋。
- Int8Array:表示 8 位二補(bǔ)整數(shù)许布。
- Uint8Array:表示 8 位無符號(hào)整數(shù)。
- Int16Array:表示 16 位二補(bǔ)整數(shù)绎晃。
- Uint16Array:表示 16 位無符號(hào)整數(shù)蜜唾。
- Int32Array:表示 32 位二補(bǔ)整數(shù)。
- Uint32Array:表示 32 位無符號(hào)整數(shù)庶艾。
- Float32Array:表示 32 位 IEEE 浮點(diǎn)值袁余。
- Float64Array:表示 64 位 IEEE 浮點(diǎn)值。
WebGL上下文
目前咱揍,在支持的瀏覽器中颖榜,WebGL 的名字叫"experimental-webgl",這是因?yàn)?WebGL 規(guī)范仍然未制定完成述召。制定完成后朱转,這個(gè)上下文的名字就會(huì)變成簡單的"webgl"。如果瀏覽器不支持 WebGL积暖,那么取得該上下文時(shí)會(huì)返回 null藤为。
示例:
var drawing = document.getElementById("drawing");
//確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var gl = drawing.getContext("experimental-webgl");
if (gl){
//使用 WebGL
}
}
通過給 getContext()傳遞第二個(gè)參數(shù),可以為 WebGL 上下文設(shè)置一些選項(xiàng)夺刑。
- alpha:值為 true缅疟,表示為上下文創(chuàng)建一個(gè) Alpha 通道緩沖區(qū)分别;默認(rèn)值為 true。
- depth:值為 true存淫,表示可以使用 16 位深緩沖區(qū)耘斩;默認(rèn)值為 true。
- stencil:值為 true桅咆,表示可以使用 8 位模板緩沖區(qū)括授;默認(rèn)值為 false。
- antialias:值為 true岩饼,表示將使用默認(rèn)機(jī)制執(zhí)行抗鋸齒操作荚虚;默認(rèn)值為 true。
- premultipliedAlpha:值為 true籍茧,表示繪圖緩沖區(qū)有預(yù)乘 Alpha 值版述;默認(rèn)值為 true。
- preserveDrawingBuffer:值為 true寞冯,表示在繪圖完成后保留繪圖緩沖區(qū)渴析;默認(rèn)值為 false。
示例:
var drawing = document.getElementById("drawing");
//確定瀏覽器支持<canvas>元素
if (drawing.getContext){
try {
gl = drawing.getContext("experimental-webgl");
} catch (ex) {
//什么也不做
}
if (gl){
//使用 WebGL
} else {
alert("WebGL context could not be created.");
}
}
常量
如果你熟悉 OpenGL吮龄,那肯定會(huì)對各種操作中使用非常多的常量印象深刻俭茧。這些常量在 OpenGL 中
都帶前綴 GL_。 在 WebGL 中漓帚,保存在上下文對象中的這些常量都沒有 GL_前綴恢恼。比如說,
GL_COLOR_BUFFER_BIT 常量在 WebGL 上下文中就是 gl.COLOR_BUFFER_BIT胰默。WebGL 以這種方式支
持大多數(shù) OpenGL 常量(有一部分常量是不支持的)场斑。
方法命名
OpenGL(以及 WebGL)中的很多方法都試圖通過名字傳達(dá)有關(guān)數(shù)據(jù)類型的信息。如果某方法可以
接收不同類型及不同數(shù)量的參數(shù)牵署,看方法名的后綴就可以知道漏隐。方法名的后綴會(huì)包含參數(shù)個(gè)數(shù)(1 到 4)
和接收的數(shù)據(jù)類型(f 表示浮點(diǎn)數(shù),i 表示整數(shù))奴迅。例如青责,gl.uniform4f()意味著要接收 4 個(gè)浮點(diǎn)數(shù),
而 gl.uniform3i()則表示要接收 3 個(gè)整數(shù)取具。
也有很多方法接收數(shù)組參數(shù)而非一個(gè)個(gè)單獨(dú)的參數(shù)脖隶。這樣的方法其名字中會(huì)包含字母 v(即 vector,
矢量)暇检。因此产阱,gl.uniform3iv()可以接收一個(gè)包含 3 個(gè)值的整數(shù)數(shù)組。請
準(zhǔn)備繪圖
在實(shí)際操作 WebGL 上下文之前块仆,一般都要使用某種實(shí)色清除<canvas>构蹬,為繪圖做好準(zhǔn)備王暗。為此,
首先必須使用 clearColor()方法來指定要使用的顏色值庄敛,該方法接收 4 個(gè)參數(shù):紅俗壹、綠、藍(lán)和透明度藻烤。
每個(gè)參數(shù)必須是一個(gè) 0 到 1 之間的數(shù)值绷雏,表示每種分量在最終顏色中的強(qiáng)度。
gl.clearColor(0,0,0,1); //black
gl.clear(gl.COLOR_BUFFER_BIT);
以上代碼把清理顏色緩沖區(qū)的值設(shè)置為黑色怖亭,然后調(diào)用了 clear()方法之众,這個(gè)方法與 OpenGL 中的
glClear()等價(jià)。傳入的參數(shù) gl.COLOR_BUFFER_BIT 告訴 WebGL 使用之前定義的顏色來填充相應(yīng)區(qū)
域依许。一般來說,都要先清理緩沖區(qū)缀蹄,然后再執(zhí)行其他繪圖操作峭跳。
視口與坐標(biāo)
開始繪圖之前,通常要先定義 WebGL 的視口(viewport)缺前。默認(rèn)情況下蛀醉,視口可以使用整個(gè)<canvas>
區(qū)域。要改變視口大小衅码,可以調(diào)用 viewport()方法并傳入 4 個(gè)參數(shù):(視口相對于<canvas>元素的)
x 坐標(biāo)拯刁、y 坐標(biāo)、寬度和高度逝段。
緩沖區(qū)
頂點(diǎn)信息保存在 JavaScript 的類型化數(shù)組中垛玻,使用之前必須轉(zhuǎn)換到 WebGL 的緩沖區(qū)。要?jiǎng)?chuàng)建緩沖區(qū)奶躯,可以調(diào)用 gl.createBuffer()帚桩,然后使用 gl.bindBuffer()綁定到 WebGL 上下文。這兩步做完之
后嘹黔,就可以用數(shù)據(jù)來填充緩沖區(qū)了账嚎。
調(diào)用 gl.bindBuffer()可以將 buffer 設(shè)置為上下文的當(dāng)前緩沖區(qū)。此后儡蔓,所有緩沖區(qū)操作都
直接在 buffer 中執(zhí)行郭蕉。因此,調(diào)用 gl.bufferData()時(shí)不需要明確傳入 buffer 也沒有問題喂江。最后
一行代碼使用 Float32Array 中的數(shù)據(jù)初始化了 buffer(一般都是用 Float32Array 來保存頂點(diǎn)信
息)召锈。如果想使用 drawElements()輸出緩沖區(qū)的內(nèi)容,也可以傳入 gl.ELEMENT_ARRAY_BUFFER获询。
gl.bufferData()的最后一個(gè)參數(shù)用于指定使用緩沖區(qū)的方式烟勋,取值范圍是如下幾個(gè)常量规求。
- gl.STATIC_DRAW:數(shù)據(jù)只加載一次,在多次繪圖中使用卵惦。
- gl.STREAM_DRAW:數(shù)據(jù)只加載一次阻肿,在幾次繪圖中使用。
- gl.DYNAMIC_DRAW:數(shù)據(jù)動(dòng)態(tài)改變沮尿,在多次繪圖中使用丛塌。
- gl.deleteBuffer(buffer):刪除緩存,釋放內(nèi)存
錯(cuò)誤
JavaScript 與 WebGL 之間的一個(gè)最大的區(qū)別在于畜疾,WebGL 操作一般不會(huì)拋出錯(cuò)誤赴邻。為了知道是否
有錯(cuò)誤發(fā)生,必須在調(diào)用某個(gè)可能出錯(cuò)的方法后啡捶,手工調(diào)用 gl.getError()方法姥敛。這個(gè)方法返回一個(gè)
表示錯(cuò)誤類型的常量∠故睿可能的錯(cuò)誤常量如下彤敛。
- gl.NO_ERROR:上一次操作沒有發(fā)生錯(cuò)誤(值為 0)。
- gl.INVALID_ENUM:應(yīng)該給方法傳入 WebGL 常量了赌,但卻傳錯(cuò)了參數(shù)墨榄。
- gl.INVALID_VALUE:在需要無符號(hào)數(shù)的地方傳入了負(fù)值。
- gl.INVALID_OPERATION:在當(dāng)前狀態(tài)下不能完成操作勿她。
- gl.OUT_OF_MEMORY:沒有足夠的內(nèi)存完成操作袄秩。
- gl.CONTEXT_LOST_WEBGL:由于外部事件(如設(shè)備斷電)干擾丟失了當(dāng)前 WebGL 上下文。
著色器
著色器(shader)是 OpenGL 中的另一個(gè)概念逢并。WebGL 中有兩種著色器:頂點(diǎn)著色器和片段(或像
素)著色器之剧。頂點(diǎn)著色器用于將 3D 頂點(diǎn)轉(zhuǎn)換為需要渲染的 2D 點(diǎn)。片段著色器用于準(zhǔn)確計(jì)算要繪制的每個(gè)像素的顏色砍聊。WebGL 著色器的獨(dú)特之處也是其難點(diǎn)在于猪狈,它們并不是用 JavaScript 寫的。這些著色
器是使用 GLSL(OpenGL Shading Language辩恼,OpenGL 著色語言)寫的雇庙,GLSL 是一種與 C 和 JavaScript
完全不同的語言。
編寫著色器
GLSL 是一種類 C 語言灶伊,專門用于編寫 OpenGL 著色器疆前。因?yàn)?WebGL 是 OpenGL ES 2.0 的實(shí)現(xiàn),所
以 OpenGL 中使用的著色器可以直接在 WebGL 中使用聘萨。這樣就方便了將桌面圖形應(yīng)用移植到瀏覽器中竹椒。
每個(gè)著色器都有一個(gè) main()方法,該方法在繪圖期間會(huì)重復(fù)執(zhí)行米辐。為著色器傳遞數(shù)據(jù)的方式有兩
種:Attribute 和 Uniform胸完。通過 Attribute 可以向頂點(diǎn)著色器中傳入頂點(diǎn)信息书释,通過 Uniform 可以向任何
著色器傳入常量值。Attribute 和 Uniform 在 main()方法外部定義赊窥,分別使用關(guān)鍵字 attribute 和
uniform爆惧。
編寫著色器程序
瀏覽器不能理解 GLSL 程序,因此必須準(zhǔn)備好字符串形式的 GLSL 程序锨能,以便編譯并鏈接到著色器
程序扯再。為便于使用,通常是把著色器包含在頁面的<script>標(biāo)簽內(nèi)址遇,并為該標(biāo)簽指定一個(gè)自定義的 type
屬性熄阻。由于無法識(shí)別 type 屬性值,瀏覽器不會(huì)解析<script>標(biāo)簽中的內(nèi)容倔约,但這不影響你讀寫其中
的代碼秃殉。
示例:
<script type="x-webgl/x-vertex-shader" id="vertexShader">
attribute vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}
</script>
<script type="x-webgl/x-fragment-shader" id="fragmentShader">
uniform vec4 uColor;
void main() {
gl_FragColor = uColor;
}
</script>
// 然后,可以通過 text 屬性提取出<script>元素的內(nèi)容:
var vertexGlsl = document.getElementById("vertexShader").text,
fragmentGlsl = document.getElementById("fragmentShader").text;
// 創(chuàng)建著色器
// 要?jiǎng)?chuàng)建著色器對象浸剩,可以調(diào)用gl.createShader()方法并傳入要?jiǎng)?chuàng)建的著色器類型(gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER)钾军。編譯著色器使用的是 gl.compileShader()。
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexGlsl);
gl.compileShader(vertexShader);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentGlsl);
gl.compileShader(fragmentShader);
// 鏈接到著色器程序中乒省。
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
//通知webGL使用這個(gè)程序
gl.useProgram(program);
為著色器傳入值
前面定義的著色器都必須接收一個(gè)值才能工作。為了給著色器傳入這個(gè)值畦木,必須先找到要接收這個(gè)
值的變量袖扛。對于 Uniform 變量,可以使用 gl.getUniformLocation()十籍,這個(gè)方法返回一個(gè)對象蛆封,表示
Uniform 變量在內(nèi)存中的位置。然后可以基于變量的位置來賦值勾栗。
示例:
var uColor = gl.getUniformLocation(program, "uColor");
gl.uniform4fv(uColor, [0, 0, 0, 1]);
//第一行代碼從 program 中找到 Uniform 變量 uColor惨篱,返回了它在內(nèi)存中的位置。第二行代碼使用gl.uniform4fv()給 uColor 賦值围俘。
調(diào)試著色器和程序
WebGL 中的其他操作一樣砸讳,著色器操作也可能會(huì)失敗,而且也是靜默失敗界牡。如果你想知道著色
器或程序執(zhí)行中是否發(fā)生了錯(cuò)誤簿寂,必須親自詢問 WebGL 上下文。
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
alert(gl.getShaderInfoLog(vertexShader));
}
繪圖
WebGL 只能繪制三種形狀:點(diǎn)宿亡、線和三角常遂。其他所有形狀都是由這三種基本形狀合成之后,再繪
制到三維空間中的挽荠。執(zhí)行繪圖操作要調(diào)用 gl.drawArrays()或 gl.drawElements()方法克胳,前者用于
數(shù)組緩沖區(qū)平绩,后者用于元素?cái)?shù)組緩沖區(qū)。
gl.drawArrays()或 gl.drawElements()的第一個(gè)參數(shù)都是一個(gè)常量漠另,表示要繪制的形狀捏雌。可
取值的常量范圍包括以下這些酗钞。
- gl.POINTS:將每個(gè)頂點(diǎn)當(dāng)成一個(gè)點(diǎn)來繪制腹忽。
- gl.LINES:將數(shù)組當(dāng)成一系列頂點(diǎn),在這些頂點(diǎn)間畫線砚作。每個(gè)頂點(diǎn)既是起點(diǎn)也是終點(diǎn)窘奏,因此數(shù)
組中必須包含偶數(shù)個(gè)頂點(diǎn)才能完成繪制。 - gl.LINE_LOOP:將數(shù)組當(dāng)成一系列頂點(diǎn)葫录,在這些頂點(diǎn)間畫線着裹。線條從第一個(gè)頂點(diǎn)到第二個(gè)頂點(diǎn),
再從第二個(gè)頂點(diǎn)到第三個(gè)頂點(diǎn)米同,依此類推骇扇,直至最后一個(gè)頂點(diǎn)。然后再從最后一個(gè)頂點(diǎn)到第一
個(gè)頂點(diǎn)畫一條線面粮。結(jié)果就是一個(gè)形狀的輪廓颜武。 - gl.LINE_STRIP:除了不畫最后一個(gè)頂點(diǎn)與第一個(gè)頂點(diǎn)之間的線之外,其他與 gl.LINE_LOOP
相同勉失。 - gl.TRIANGLES:將數(shù)組當(dāng)成一系列頂點(diǎn)地技,在這些頂點(diǎn)間繪制三角形。除非明確指定柴底,每個(gè)三角
形都單獨(dú)繪制婿脸,不與其他三角形共享頂點(diǎn)。 - gl.TRIANGLES_STRIP:除了將前三個(gè)頂點(diǎn)之后的頂點(diǎn)當(dāng)作第三個(gè)頂點(diǎn)與前兩個(gè)頂點(diǎn)共同構(gòu)成
一個(gè)新三角形外柄驻,其他都與 gl.TRIANGLES 相同狐树。例如,如果數(shù)組中包含 A鸿脓、B抑钟、C、D 四個(gè)頂
點(diǎn)野哭,則第一個(gè)三角形連接 ABC味赃,而第二個(gè)三角形連接 BCD。 - gl. TRIANGLES_FAN:除了將前三個(gè)頂點(diǎn)之后的頂點(diǎn)當(dāng)作第三個(gè)頂點(diǎn)與前一個(gè)頂點(diǎn)及第一個(gè)頂
點(diǎn)共同構(gòu)成一個(gè)新三角形外虐拓,其他都與 gl.TRIANGLES 相同心俗。例如,如果數(shù)組中包含 A、B城榛、C揪利、
D 四個(gè)頂點(diǎn),則第一個(gè)三角形連接 ABC狠持,而第二個(gè)三角形連接 ACD疟位。
紋理
WebGL 的紋理可以使用 DOM 中的圖像。要?jiǎng)?chuàng)建一個(gè)新紋理喘垂,可以調(diào)用 gl.createTexture()甜刻,
然后再將一幅圖像綁定到該紋理。如果圖像尚未加載到內(nèi)存中正勒,可能需要?jiǎng)?chuàng)建一個(gè) Image 對象的實(shí)例得院,
以便動(dòng)態(tài)加載圖像。圖像加載完成之前章贞,紋理不會(huì)初始化祥绞,因此,必須在 load 事件觸發(fā)后才能設(shè)置紋
理鸭限。
示例:
var image = new Image(),
texture;
image.src = "smile.gif";
image.onload = function(){
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
//清除當(dāng)前紋理
gl.bindTexture(gl.TEXTURE_2D, null);
}
讀取像素
與 2D 上下文 類似蜕径,通過 WebGL 上下文也能讀取像素值。讀取像素值的方法 readPixels()與
OpenGL 中的同名方法只有一點(diǎn)不同败京,即最后一個(gè)參數(shù)必須是類型化數(shù)組兜喻。像素信息是從幀緩沖區(qū)讀取的,然后保存在類型化數(shù)組中赡麦。readPixels()方法的參數(shù)有:x朴皆、y、寬度隧甚、高度车荔、圖像格式渡冻、數(shù)據(jù)類
型和類型化數(shù)組戚扳。前 4 個(gè)參數(shù)指定讀取哪個(gè)區(qū)域中的像素。圖像格式參數(shù)幾乎總是 gl.RGBA族吻。數(shù)據(jù)類型
參數(shù)用于指定保存在類型化數(shù)組中的數(shù)據(jù)的類型帽借,但有以下限制。
- 如果類型是 gl.UNSIGNED_BYTE超歌,則類型化數(shù)組必須是 Uint8Array砍艾。
- 如果類型是 gl.UNSIGNED_SHORT_5_6_5、gl.UNSIGNED_SHORT_4_4_4_4 或 gl.UNSIGNED_
SHORT_5_5_5_1巍举,則類型化數(shù)組必須是 Uint16Array脆荷。