談起WebGL可能有一些人比較陌生,實(shí)際上WebGL是一種3D繪圖標(biāo)準(zhǔn)剧董,這種繪圖技術(shù)標(biāo)準(zhǔn)允許把JavaScript和OpenGL ES 2.0結(jié)合在一起曾雕,通過增加OpenGL ES 2.0的一個(gè)JavaScript綁定跃惫,WebGL可以為HTML5 Canvas提供硬件3D加速渲染,這樣Web開發(fā)人員就可以借助系統(tǒng)顯卡來在瀏覽器里更流暢地展示3D場景和模型了宙项,還能創(chuàng)建復(fù)雜的導(dǎo)航和數(shù)據(jù)視覺化。顯然株扛,WebGL技術(shù)標(biāo)準(zhǔn)免去了開發(fā)網(wǎng)頁專用渲染插件的麻煩尤筐,可被用于創(chuàng)建具有復(fù)雜3D結(jié)構(gòu)的網(wǎng)站頁面,甚至可以用來設(shè)計(jì)3D網(wǎng)頁游戲等等洞就。
此鏈接可以查看你的游覽器是否支持WebGL以及支持的版本盆繁。
檢測瀏覽器是否支持WebGL
看WebGL的背景實(shí)際上是JavaScript操作一些OpenGL接口,也就意味著旬蟋,可能會編寫一部分GLSL ES 2.0的代碼油昂,沒錯(cuò),你猜對了,WebGL只是綁定了一層冕碟,內(nèi)部的一些核心內(nèi)容拦惋,如著色器,材質(zhì)安寺,燈光等都是需要借助GLSL ES語法來操作的.
基于WebGL周邊也衍生了眾多的第三方庫厕妖,如開發(fā)應(yīng)用類的Three.js,開發(fā)游戲類的Egert.js等挑庶,都大大的降低了學(xué)習(xí)WebGL的成本叹放,但是本著有問題解決問題,沒問題制造問題在解決問題的程序猿態(tài)度挠羔,還是覺得應(yīng)該稍微了解一下WebGL一些基本的概念井仰,以便能更好的去理解不同框架帶來的便捷以及優(yōu)勢。一些簡單的效果其實(shí)無需多引入一個(gè)體積可觀的三方庫來實(shí)現(xiàn)破加。如下圖的效果:
接下來先簡單介紹一下使用到的知識要點(diǎn)俱恶。
創(chuàng)建webGL對象
不同瀏覽器生命WebGL對象方式有所區(qū)別,雖然大部分瀏覽器都支持experimental-webgl范舀,而且以后會變成webgl合是,所以創(chuàng)建時(shí)做一下兼容處理
var canvas = document.getElementById("glcanvas");
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
著色器
WebGL依賴一種新的稱為著色器(shader)的繪圖機(jī)制。著色器提供了靈活且強(qiáng)大的繪制二維或三維圖形的方法锭环,所有WebGL必須使用它聪全。著色器不僅強(qiáng)大,而且更復(fù)雜辅辩,僅僅通過一條簡單的繪圖指令是不能操作它的难礼。
WebGL需要兩種著色器
- 頂點(diǎn)著色器(Vertex shader):頂點(diǎn)著色器是用來描述頂點(diǎn)特性(如位置、顏色等)的程序玫锋。頂點(diǎn)(Vertex)是指二維或三維空間的一個(gè)點(diǎn)蛾茉,比如二維或三維空間線與線之間的交叉點(diǎn)或者端點(diǎn)。
- 片元著色器(Fragment shader):進(jìn)行逐片元處理過程(如光照等)的程序撩鹿。片元(fragment)是一個(gè)WebGL的術(shù)語谦炬,你可以將其理解成像素。
著色器語言使用的是GLSL ES語言节沦,所以在javascript需要將之存放在字符串中键思,等待調(diào)用編譯
創(chuàng)建頂點(diǎn)著色器:
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' +
' gl_PointSize = 10.0;\n' +
'}\n';
創(chuàng)建片元著色器:
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
瀏覽器的整個(gè)過程如下:
著色器中包含幾個(gè)內(nèi)置變量:gl_Position, gl_PointSize, gl_FragColor。
著色器語言中涉及到vec4的數(shù)據(jù)類型甫贯,此數(shù)據(jù)類型是一個(gè)思維浮點(diǎn)數(shù)組吼鳞,所以其值不可以是整形如(1,1,1,1),正確應(yīng)為:(1.0,1.0,1.0,1.0)
gl_Position: 為一種vec4類型的變量获搏,且必須被賦值赖条。四維坐標(biāo)矢量失乾,我們稱之為齊次坐標(biāo),即(x,y,z,w)等價(jià)于三維左邊(x/w,y/w,z/w)纬乍,w相當(dāng)于深度碱茁,沒有特殊要求設(shè)置為1.0即可。
gl_PointSize:表示頂點(diǎn)的尺寸仿贬,也是浮點(diǎn)數(shù)纽竣,為非必填項(xiàng),如果不填則默認(rèn)顯示為1.0茧泪。
gl_FragColor:該變量為片元著色器唯一的內(nèi)置變量蜓氨,表示其顏色,也是一個(gè)vec4類型變量队伟,分別代表(R,G,B,A)穴吹,不過顏色范圍是從0.0-1.0對應(yīng)Javascript中的#00-#FF。
有了著色器我們就可以著手去繪制圖像了嗜侮,既然繪制3D圖形港令,必然會有對應(yīng)的三維坐標(biāo)系,WebGL采用右手坐標(biāo)系锈颗,如圖所示:
使用著色器
讓我們來看看如何把著色器代碼編譯并且使用起來
著色器代碼需要載入到一個(gè)程序中顷霹,webgl使用此程序才能調(diào)用著色器。
var program = gl.createProgram();
// 創(chuàng)建頂點(diǎn)著色器
var vShader = gl.createShader(gl.VERTEX_SHADER);
//創(chuàng)建片元著色器
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
//shader容器與著色器綁定
gl.shaderSource(vShader, VSHADER_SOURCE);
gl.shaderSource(fShader, FSHADER_SOURCE);
//將GLSE語言編譯成瀏覽器可用代碼
gl.compileShader(vShader);
gl.compileShader(fShader);
//將著色器添加到程序上
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
//鏈接程序击吱,在鏈接操作執(zhí)行以后淋淀,可以任意修改shader的源代碼,
對shader重新編譯不會影響整個(gè)程序覆醇,除非重新鏈接程序
gl.linkProgram(program);
//加載并使用鏈接好的程序
gl.useProgram(program);
讓我們嘗試?yán)L制一個(gè)點(diǎn)
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0 ,1);
我們來看一看最終結(jié)果,果然出來了一個(gè)點(diǎn)
程序創(chuàng)建完之后朵纷,我們需要需要對著色器進(jìn)行動態(tài)控制才能達(dá)到我們所需要的功能。
首先讓我來介紹2個(gè)變量叫乌,我們需要借助這2個(gè)變量搭建的橋梁才能使JavaScript與GLSL ES之間進(jìn)行溝通柴罐。
- attribute: 用于頂點(diǎn)點(diǎn)著色器(Vertex Shader)傳值時(shí)使用徽缚。
- uniform:可用于頂點(diǎn)著色器(Vertex Shader)與片元著色器(Fragment Shader)使用憨奸。
將頂點(diǎn)動態(tài)化
先在頂點(diǎn)著色器代碼中,將對應(yīng)的vec4的固定值變成變量
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = 10.0;\n' +
'}\n';
位置參數(shù)使用了attribute變量來承載凿试。這樣WebGL對象就可以獲取到對應(yīng)的存儲位置排宰,就可以去動態(tài)改變GLSL變量了。
使用WebGL來獲取對應(yīng)參數(shù)的存儲地址地址
//返回對應(yīng)的地址信息
var aPosition = gl.getAttribLocation(gl.program, 'a_Position');
//判斷地址是否獲取成功
if(aPosition < 0) {
console.log('沒有獲取到對應(yīng)position');
}
然后給變量賦值
gl.vertexAttrib3f(aPosition, 1.0, 1.0, 0.0);
//或者使用Float32Array來傳參
var p = new Float32Array([1.0, 1.0, 1.0]);
gl.vertexAttrib3fv(aPosition, p);
注意:vertexAttrib3fv這個(gè)函數(shù)是典型的GLSL語法命名規(guī)范那婉,
vertexAttrib函數(shù)功能板甘,
3:對應(yīng)需要傳3個(gè)參數(shù),或者是幾維向量详炬,
f:表示參數(shù)是float類型盐类,
v:表示傳如的為一個(gè)vector變量。
也就是說對應(yīng)設(shè)置頂點(diǎn)著色器的函數(shù)有一下幾種功能,參考文檔:
void gl.vertexAttrib1f(index, v0);
void gl.vertexAttrib2f(index, v0, v1);
void gl.vertexAttrib3f(index, v0, v1, v2);
void gl.vertexAttrib4f(index, v0, v1, v2, v3);
void gl.vertexAttrib1fv(index, value);
void gl.vertexAttrib2fv(index, value);
void gl.vertexAttrib3fv(index, value);
void gl.vertexAttrib4fv(index, value);
同樣操作可以如下修改PointSize:
//著色器中添加變量
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute float a_PointSize;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = a_PointSize;\n' +
'}\n';
var aPointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
gl.vertexAttrib1f(aPointSize, 10.0);
片元著色器編程
對片元著色器變成需要使用uniform變量來承載在跳。
var FSHADER_SOURCE =
'precision mediump float;\n'+
'uniform vec4 vColor;\n'+
'void main() {\n' +
' gl_FragColor = vColor;\n' + // Set the point color
'}\n';
獲取片元著色器變量地址
var vColor = gl.getUniformLocation(gl.program, 'vColor');
給變量賦值
gl.uniform4f(vColor, 1.0, 0.0, 0.0, 1.0);
//或使用Float32Array來傳參
var color = new Float32Array([1.0, 0.0, 0.0, 1.0]);
gl.uniform4fv(vColor,color)
注意:uniform3fv這個(gè)函數(shù)是典型的GLSL語法命名規(guī)范枪萄,
uniform3fv函數(shù)功能,
3:對應(yīng)需要傳3個(gè)參數(shù)猫妙,或者是幾維向量瓷翻,
f:表示參數(shù)是float類型,
u:表示參數(shù)是Uint32Array類型割坠,
i:表示參數(shù)是integer類型齐帚,
ui:表示參數(shù)是unsigned integer類型,
v:表示傳如的為一個(gè)vector變量彼哼。
頂點(diǎn)著色器對應(yīng)函數(shù)对妄,參考文檔
void gl.uniform1ui(location, v0);
void gl.uniform2ui(location, v0, v1);
void gl.uniform3ui(location, v0, v1, v2);
void gl.uniform4ui(location, v0, v1, v2, v3);
void gl.uniform1fv(location, data, optional srcOffset, optional srcLength);
void gl.uniform2fv(location, data, optional srcOffset, optional srcLength);
void gl.uniform3fv(location, data, optional srcOffset, optional srcLength);
void gl.uniform4fv(location, data, optional srcOffset, optional srcLength);
void gl.uniform1iv(location, data, optional srcOffset, optional srcLength);
void gl.uniform2iv(location, data, optional srcOffset, optional srcLength);
void gl.uniform3iv(location, data, optional srcOffset, optional srcLength);
void gl.uniform4iv(location, data, optional srcOffset, optional srcLength);
void gl.uniform1uiv(location, data, optional srcOffset, optional srcLength);
void gl.uniform2uiv(location, data, optional srcOffset, optional srcLength);
void gl.uniform3uiv(location, data, optional srcOffset, optional srcLength);
void gl.uniform4uiv(location, data, optional srcOffset, optional srcLength);
著色器中的代碼precision mediump float;表示的意思是著色器中配置的float對象會占用中等尺寸內(nèi)存。
具體包含的尺寸:
- highp for vertex positions,
- mediump for texture coordinates,
- lowp for colors.
如果不設(shè)置此參數(shù)會報(bào)錯(cuò):
我們可以繪制自定義的點(diǎn)了敢朱,接下來我們就可以嘗試?yán)L制大批量點(diǎn)來達(dá)到波浪的基礎(chǔ)效果饥伊,但是之前的操作都是針對一個(gè)點(diǎn)的,如何可以同時(shí)繪制多個(gè)訂點(diǎn)呢蔫饰,如果你的回答是循環(huán)數(shù)據(jù)琅豆,BINGGO,沒錯(cuò)這樣你的確是可以達(dá)到這個(gè)目的篓吁,但是不是我們接下來要講的茫因,因?yàn)樵?D繪制的時(shí)候是會經(jīng)常出現(xiàn)大批量點(diǎn)、線杖剪、面的繪制的冻押,所以WebGL提供了一種承載機(jī)制來達(dá)到傳遞多點(diǎn)的能力,說了這么多盛嘿,也讓我們來看看它到底是什么吧
緩存區(qū)對象
之前的方式可以通過循環(huán)來繪制多個(gè)點(diǎn)洛巢,一次需要繪制多個(gè)點(diǎn),需要同時(shí)傳遞進(jìn)去多個(gè)點(diǎn)的數(shù)據(jù)次兆。剛好稿茉,在WebGL中提供了一種機(jī)制:緩存區(qū)對象(buffer data),緩存區(qū)對象可以同時(shí)向著色器傳遞多個(gè)頂點(diǎn)坐標(biāo)。緩存區(qū)是WebGL中的一塊內(nèi)存區(qū)域芥炭,我們可以向里面存放大量頂點(diǎn)坐標(biāo)數(shù)據(jù)漓库,可隨時(shí)供著色器使用。
使用緩存區(qū)步驟
- 創(chuàng)建緩存區(qū)對象(gl.createBuffer())
- 綁定緩存區(qū)對象(gl.bindBuffer())
- 將數(shù)據(jù)寫入緩存區(qū)對象(gl.bufferData())
- 將緩存區(qū)對象分配給一個(gè)attribute變量(gl.vertexAttribPointer())
- 開啟attribute變量(gl.enableVertexAttribArray())
首先园蝠,我們?nèi)匀恍枰獎(jiǎng)?chuàng)建WebGL對象渺蒿、片元著色器以及頂點(diǎn)著色器,具體創(chuàng)建的步驟以及原理彪薛,可參考之前的教程茂装。具體代碼實(shí)現(xiàn)如下:
當(dāng)創(chuàng)建好WebGL之后怠蹂,可以通過著色器中的attrbute或者uniform對象來傳遞需要?jiǎng)討B(tài)修改或設(shè)置的的變量。
接下來我們需要進(jìn)行緩沖區(qū)的操作:
首先少态,需要?jiǎng)?chuàng)建一個(gè)緩沖區(qū)來承載大量頂點(diǎn)的坐標(biāo)
//創(chuàng)建緩存區(qū)
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer) {
log('創(chuàng)建緩存區(qū)失敗褥蚯。');
return -1;
}
//將創(chuàng)建的緩存區(qū)對象綁定到target表示的目標(biāo)上
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
//開辟存儲空間,向綁定在target上的緩存區(qū)對象中寫入數(shù)據(jù)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
//獲取著色器中的變量值
var a_position = gl.getAttribLocation(gl.program, 'a_p');
//將緩存區(qū)對象綁定到著色器變量中
gl.vertexAttribPointer(a_position, 3, gl.FLOAT, false, 0, 0);
// 啟用緩存區(qū)
gl.enableVertexAttribArray(a_position);
// 繪制緩存區(qū)中畫的多個(gè)頂點(diǎn)
gl.drawArrays(gl.POINTS, 0 , array);
看完了繪制過程况增,讓我們來拆解一下具體內(nèi)容:
首先赞庶,我們要在茫茫內(nèi)存中申請一個(gè)區(qū)域來放置緩存區(qū)對象的內(nèi)容,但是我們無法直接放置緩存對象進(jìn)入內(nèi)存中澳骤,否則會無法識別對應(yīng)的數(shù)據(jù)類型歧强,從而無法達(dá)到存取自如的境界,那我們就需要將數(shù)據(jù)的類型告知內(nèi)存为肮,bingBuffer就是為解決此問題誕生的摊册,函數(shù)會在內(nèi)存中申請一部分區(qū)域,并且通過target來制定數(shù)據(jù)類型颊艳,也就是說茅特,緩存區(qū)是需要放置在target表示的類型部分去存儲。
gl.bindBuffer(target, buffer)
target: 指定存儲緩存區(qū)的目標(biāo)類型
- gl.ARRAY_BUFFER : 指緩存區(qū)中包含了頂點(diǎn)的數(shù)據(jù)
- gl.ELEMENT_ARRAY_BUFFER : 指緩存區(qū)中包含了頂點(diǎn)數(shù)據(jù)的索引值
buffer: 自己創(chuàng)建的緩存區(qū)對象
接下來棋枕,我們需要做的是填充剛剛申請的緩存區(qū)白修,我們需要使用一個(gè)符合GLSL語法的數(shù)據(jù)格式,Javascript中可用Float32Array類型來創(chuàng)建支持GLSL的數(shù)據(jù)重斑。使用bufferData函數(shù)將數(shù)據(jù)放入緩存區(qū)內(nèi)兵睛。
gl.bufferData(target, size, usage)
target: 同上
size: 為多個(gè)頂點(diǎn)坐標(biāo)的集合數(shù)組
usage: 表示程序?qū)⑷绾问褂镁彺鎱^(qū)中的數(shù)據(jù)
- gl.STATIC_DRAW : 只會向緩存區(qū)對象中寫入一次數(shù)據(jù),但需要繪制很多次
- gl.STREAM_DRAW : 只會向緩存區(qū)對象中寫入一次數(shù)據(jù)窥浪,然后繪制若干次
- gl.DYNAMIC_DRAW : 會想緩存區(qū)對象中多次寫入數(shù)據(jù)祖很,并繪制很多次
緩存區(qū)中已經(jīng)存儲了多個(gè)頂點(diǎn)坐標(biāo),接下來我們需要將此數(shù)據(jù)運(yùn)用到對應(yīng)的著色器上漾脂,才能真正的繪制出來可視化圖像假颇,如何傳遞呢?首先我們需要在著色器中建立一個(gè)attribute類型的變量以方便我們操作骨稿,著色器中的對象笨鸡,著色器中存在對象之后,我們可以使用Javascript中getAttribLocation函數(shù)獲取著色器中的attribute類型變量啊终,并且通過vertexAttribPointer將其賦值改變镜豹,從而達(dá)到改變圖像呈現(xiàn)。
gl.getAttribLocation(program,name)
param: webgl之前創(chuàng)建的進(jìn)程
name: 變量名稱
gl.vertexAttribPointer(name, size, type, normalized, stride, offset)
name: 指定要賦值的attribute變量位置
size: 指定每個(gè)頂點(diǎn)數(shù)據(jù)的分量個(gè)數(shù)(1或4)
type: 指定傳入的數(shù)據(jù)格式
- gl.BYTE: 字節(jié)型, 取值范圍[-128, 127]
- gl.SHORT: 短整型,取值范圍[-32768, 32767]
- gl.UNSIGNED_BYTE: 無符號字節(jié)型,取值范圍[0, 255]
- gl.UNSIGNED_SHORT: 無符號短整型, 取值范圍[0, 65535]
- gl.FLOAT: 浮點(diǎn)型
normalized: 表明是否將非浮點(diǎn)數(shù)的數(shù)據(jù)歸入到[0, 1]或[-1, 1]區(qū)間
stride: 指定相鄰2個(gè)頂點(diǎn)間的字節(jié)數(shù)蓝牲,默認(rèn)為0
offset: 指定緩存區(qū)對象中的偏移量,設(shè)置為0即可
如為2泰讽,則
new Float32Array([
1.0, 1.0,
1.0,1.0
])
代表2個(gè)頂點(diǎn)
如為4例衍,則
new Float32Array([
1.0, 1.0, 1.0,1.0
])
代表1個(gè)頂點(diǎn)
現(xiàn)在緩存區(qū)已經(jīng)存在多個(gè)頂點(diǎn)數(shù)據(jù)昔期,接下來我們來啟用攜帶緩存區(qū)數(shù)據(jù)的attribute變量,使用enableVertexAttribArray來啟用對應(yīng)變量佛玄。
gl.enableVertexAttribArray(name)
name: 待啟動的變量指針硼一,也就是名稱
所有的緩存區(qū)操作步驟我們都已經(jīng)完成,那么接下來我們可以繪制出緩存區(qū)中的多個(gè)頂點(diǎn)
gl.drawArrays(mode, first, count)
mode: 需要繪制的圖像形狀
- gl.POINTS: 繪制一個(gè)點(diǎn)梦抢。
- gl.LINE_STRIP: 繪制一條直線到下一個(gè)頂點(diǎn)般贼。
- gl.LINE_LOOP: 繪制一條首尾相連的線。
- gl.LINES: 繪制一條線奥吩。
- gl.TRIANGLES: 繪制一個(gè)三角形哼蛆。
first: 繪制的開始點(diǎn)
count: 需要繪制的圖形個(gè)數(shù)
看看屏幕吧,是不是出來了好多點(diǎn)霞赫,接下來我們就可以來繪制對應(yīng)的波浪圖了腮介。
接下來會講解如何繪制波浪圖。