WebGL基礎(chǔ)介紹

談起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接口,也就意味著,可能會(huì)編寫一部分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)。如下圖的效果:

波浪預(yù)覽

接下來先簡單介紹一下使用到的知識要點(diǎn)。

創(chuàng)建webGL對象

不同瀏覽器生命WebGL對象方式有所區(qū)別,雖然大部分瀏覽器都支持experimental-webgl,而且以后會(huì)變成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è)過程如下:

WebGL渲染過程

著色器中包含幾個(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圖形,必然會(huì)有對應(yīng)的三維坐標(biāo)系,WebGL采用右手坐標(biāo)系,如圖所示:

坐標(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重新編譯不會(huì)影響整個(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)

繪制單點(diǎn)的結(jié)果

程序創(chuàng)建完之后,我們需要需要對著色器進(jìn)行動(dòng)態(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)動(dòng)態(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)的存儲(chǔ)位置,就可以去動(dòng)態(tài)改變GLSL變量了。

使用WebGL來獲取對應(yīng)參數(shù)的存儲(chǔ)地址地址
//返回對應(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對象會(huì)占用中等尺寸內(nèi)存。
具體包含的尺寸:

  • highp for vertex positions,
  • mediump for texture coordinates,
  • lowp for colors.

如果不設(shè)置此參數(shù)會(huì)報(bào)錯(cuò):


yanglei8.jpg

我們可以繪制自定義的點(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í)候是會(huì)經(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);
//開辟存儲(chǔ)空間,向綁定在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)存中,否則會(huì)無法識別對應(yīng)的數(shù)據(jù)類型,從而無法達(dá)到存取自如的境界,那我們就需要將數(shù)據(jù)的類型告知內(nèi)存,bingBuffer就是為解決此問題誕生的,函數(shù)會(huì)在內(nèi)存中申請一部分區(qū)域,并且通過target來制定數(shù)據(jù)類型,也就是說,緩存區(qū)是需要放置在target表示的類型部分去存儲(chǔ)。

gl.bindBuffer(target, buffer)

target: 指定存儲(chǔ)緩存區(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 : 只會(huì)向緩存區(qū)對象中寫入一次數(shù)據(jù),但需要繪制很多次
  • gl.STREAM_DRAW : 只會(huì)向緩存區(qū)對象中寫入一次數(shù)據(jù),然后繪制若干次
  • gl.DYNAMIC_DRAW : 會(huì)想緩存區(qū)對象中多次寫入數(shù)據(jù),并繪制很多次

緩存區(qū)中已經(jīng)存儲(chǔ)了多個(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: 待啟動(dòng)的變量指針,也就是名稱
所有的緩存區(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)的波浪圖了。
    接下來會(huì)講解如何繪制波浪圖。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容