原文鏈接:WebGL漫游之旅(一)
一荆针、WebGL基本概念
WebGL (Web Graphics Library) is a JavaScript API for rendering interactive 3D and 2D graphics within any compatible web browser without the use of plug-ins. WebGL does so by introducing an API that closely conforms to OpenGL ES 2.0 that can be used in HTML5 canvas elements. --MDN
以上是MDN對于WebGL的描述寒屯,簡單來說干发,WebGL 就是一組基于 JavaScript 語言的圖形規(guī)范唯卖,瀏覽器廠商按照這組規(guī)范進(jìn)行實(shí)現(xiàn),為 Web 開發(fā)者提供一套3D圖形相關(guān)的 API。
我們可以通過這些API直接使用JavaScript直接和GPU進(jìn)行通信,從而實(shí)現(xiàn)一些非常炫酷的圖形虑润。而webGL是在GPU上運(yùn)行的,因此我們需要使用能夠在GPU上運(yùn)行的代碼加酵,首先我們需要一種叫做GLSL的語言拳喻,它是一種和C or CPP類似的強(qiáng)類型的語言,所以寫起來很麻煩(這也是很多人吐槽WebGL的一個(gè)方面)猪腕,其次這樣的代碼需要提供成對的方法冗澈,每對方法中,一個(gè)叫做頂點(diǎn)著色器陋葡,一個(gè)叫做片段著色器亚亲,這樣的每一對組合起來就稱作一個(gè)program(著色程序)。其中頂點(diǎn)著色器的作用是計(jì)算頂點(diǎn)的位置腐缤,根據(jù)計(jì)算出來的一系列的頂點(diǎn)的位置捌归,WebGL就可以對點(diǎn)、線以及三角形在內(nèi)的一些圖元進(jìn)行光柵化處理岭粤。當(dāng)對這些圖元進(jìn)行光柵化處理的時(shí)候就需要使用片段著色器方法了惜索,它的作用是計(jì)算出當(dāng)前繪制圖元中的每個(gè)像素的顏色值。
1.1 什么是GLSL
上面我們提到了GLSL剃浇,其中文的意思是OpenGL著色語言巾兆,它是用來在 OpenGL 編寫著色器程序的語言,全稱為 OpenGL Shading Language虎囚。而著色器程序則是在GPU上運(yùn)行的簡短的程序角塑,代替了GPU固定渲染管線的一部分,使GPU渲染過程中的某些部分允許開發(fā)者通過編程進(jìn)行控制淘讥。
而GPU渲染過程中具體允許我們對其進(jìn)行控制的部分有以下幾個(gè)方面:
- JavaScript程序吉拳,處理著色器所需要的頂點(diǎn)坐標(biāo)、法向量适揉、顏色留攒、紋理等。
- 頂點(diǎn)著色器嫉嘀,接受JavaScript傳遞過來的頂點(diǎn)信息炼邀,將頂點(diǎn)繪制到對應(yīng)的坐標(biāo)。
- 圖元裝配階段剪侮,將三個(gè)頂點(diǎn)裝配成指定的圖元類型拭宁。
- 光柵化階段洛退,將三角形內(nèi)部區(qū)域用空像素進(jìn)行填充。
- 片元著色器杰标,為三角形內(nèi)部的像素填充顏色信息兵怯。
1.2 WebGL工作流程
上面對WebGL的基本情況進(jìn)行了一個(gè)簡單的概述,但好像也沒有解答webGL將3D模型顯示到屏幕上的工作原理及流程腔剂。其實(shí)這個(gè)過程就好比富士康工作流水一樣媒区,按照既定的工作流程來對原材料進(jìn)行加工,從而生產(chǎn)出完整的產(chǎn)品掸犬。WebGL大致也是如此袜漩,按照工作流水線的方式,將3D的模型數(shù)據(jù)渲染到2D屏幕上的湾碎,這個(gè)渲染方式的過程一般被稱之為圖形管線或者渲染管線宙攻。
上面我們又說到過點(diǎn)、線介褥、三角形這些基本圖元座掘,但我們經(jīng)常看見很多通過WebGL所繪制出來的諸如球體柔滔、圓柱溢陪、各式的立方體等模型,也看見了很多炫酷廊遍、復(fù)雜的模型,很顯然這些并不屬于這些基本圖元里面贩挣,但其實(shí)這些模型本質(zhì)上都是有一個(gè)個(gè)頂點(diǎn)組成的喉前,GPU將這些點(diǎn)用三角線圖元繪制成一個(gè)個(gè)微小的小平面,然后通過這些小平面的互相連接王财,來組成各種各樣的的立體模型卵迂。因此通常來說,我們首先要做的就是創(chuàng)建組成模型的頂點(diǎn)數(shù)據(jù)绒净。
一般情況下见咒,最初的頂點(diǎn)坐標(biāo)是相對于模型中心的,我們需要對頂點(diǎn)坐標(biāo)按照一系列步驟執(zhí)行模型轉(zhuǎn)換挂疆、視圖轉(zhuǎn)換改览、投影轉(zhuǎn)換,在通過這一系列的轉(zhuǎn)換后的坐標(biāo)叫做裁剪空間坐標(biāo)缤言,這個(gè)坐標(biāo)才是WebGL可以接受的坐標(biāo)宝当。我們把最后的變換矩陣和原始頂點(diǎn)坐標(biāo)傳遞給GPU,GPU的渲染管線然后對他們執(zhí)行流水工作胆萧,主要過程如下:
- 進(jìn)入頂點(diǎn)著色器庆揩,利用GPU的并行計(jì)算優(yōu)勢對頂點(diǎn)逐個(gè)進(jìn)行坐標(biāo)變換。
- 進(jìn)入圖元裝配階段,將會(huì)頂點(diǎn)按照圖元類型組裝成圖形
- 進(jìn)入光柵化階段订晌,光柵化階段對圖像用不包含顏色信息的像素進(jìn)行填充
- 進(jìn)入著色器階段虏辫,為像素著色,并最終顯示在屏幕上
二锈拨、WebGL初體驗(yàn)
上面將WebGL的大致情況進(jìn)行了描述砌庄,下面就是真刀實(shí)槍的來搞事情了,和Three.js一樣(這是廢話推励。)鹤耍,我們在使用WebGL進(jìn)行開發(fā)的時(shí)候首先需要使用canvas,我們可以再HTML文件里的這樣聲明一個(gè)canvas验辞。順便對瀏覽器對canvas的支持情況進(jìn)行一個(gè)檢查:
<body onload="main()">
<canvas id="glcanvas" width="640" height="480">
Your browser doesn't appear to support the HTML5 <code><canvas></code> element.
</canvas>
</body>
webGL應(yīng)用主要包含兩個(gè)要素:JavaScript程序和著色器程序稿黄。首先讓我們來準(zhǔn)備著色器程序,使用GLSL編寫頂點(diǎn)著色器和片元著色器跌造。
頂點(diǎn)著色器的任務(wù)我們在上面已經(jīng)說了杆怕,它主要是告訴GPU我們所要形成的圖形在裁剪坐標(biāo)系的位置,下面這個(gè)代碼就是告訴GPU我們需要在裁剪坐標(biāo)系的原點(diǎn)壳贪,即屏幕中心畫一個(gè)大小為20的點(diǎn):
void main(){
//聲明頂點(diǎn)位置
gl_Position = vec4(0.0, 0.0, 0.0, 1.0)
//聲明所需繪制的點(diǎn)的大小
gl_PointSize = 20.0
}
當(dāng)頂點(diǎn)著色器中的數(shù)據(jù)經(jīng)過圖元裝配和光柵化之后陵珍,來到了片元著色器,從而通過片元著色器將像素渲染成我們所需要的顏色:
void main(){
//設(shè)置像素的填充顏色為紅色违施。
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0)
}
在這里互纯,gl_Position、gl_PointSize磕蒲、gl_FragColor 是 GLSL 的內(nèi)置屬性:
- gl_Position:頂點(diǎn)的裁剪坐標(biāo)系坐標(biāo)留潦,包含X、Y辣往、Z兔院、W四個(gè)坐標(biāo)分量。頂點(diǎn)著色器接收坐標(biāo)后站削,會(huì)對它進(jìn)行透視除法坊萝,即將各個(gè)分量同時(shí)除以 W,從而轉(zhuǎn)換成 NDC 坐標(biāo)许起,NDC 坐標(biāo)每個(gè)分量的取值范圍都在(-1, 1)之間十偶,GPU 獲取這個(gè)屬性值作為頂點(diǎn)的最終位置進(jìn)行繪制。
- gl_FragColor:片元(像素)顏色园细,包含 R, G, B, A 四個(gè)顏色分量扯键,且每個(gè)分量的取值范圍在(0,1)之間,GPU 會(huì)獲取這個(gè)值珊肃,作為像素的最終顏色進(jìn)行著色荣刑。
- gl_PointSize:繪制到屏幕的點(diǎn)的大小馅笙,gl_PointSize只有在繪制圖元是點(diǎn)的時(shí)候才會(huì)生效。
然后我們就可以著手寫我們的JavaScript部分的代碼了厉亏,首先我們需要獲取webGL的繪圖環(huán)境:
const canvas = document.querySelector('#canvas')
const gl = canvas.getContext('webgl')
然后創(chuàng)建頂點(diǎn)著色器:
// 獲取頂點(diǎn)著色器源碼
const vertexShaderSource = document.querySelector('#vertexShader').innerHTML
// 創(chuàng)建頂點(diǎn)著色器對象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 將源碼分配給頂點(diǎn)著色器對象
gl.shaderSource(vertexShader, vertexShaderSource)
// 編譯頂點(diǎn)著色器程序
gl.compileShader(vertexShader)
再就是創(chuàng)建片元著色器:
// 獲取片元著色器源碼
const fragmentShaderSource = document.querySelector('#fragmentShader').innerHTML
// 創(chuàng)建片元著色器程序
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 將源碼分配給片元著色器對象
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 編譯片元著色器
gl.compileShader(fragmentShader)
以上就將我們的著色器對象創(chuàng)建完成了董习,接下來我們就可以創(chuàng)建著色器程序了:
//創(chuàng)建著色器程序
const program = gl.createProgram()
//將頂點(diǎn)著色器掛載在著色器程序上。
gl.attachShader(program, vertexShader)
//將片元著色器掛載在著色器程序上爱只。
gl.attachShader(program, fragmentShader)
//鏈接著色器程序
gl.linkProgram(program)
我們在進(jìn)行webgl開發(fā)的時(shí)候皿淋,可能會(huì)在一個(gè)WebGL應(yīng)用里包含多個(gè)program,因此我們在使用某狗program繪制前恬试,要先啟用它窝趣,才能進(jìn)行繪制:
gl.useProgram(program)
// 繪制
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.POINTS, 0, 1)
如此我們完成了我們的第一個(gè)webGL代碼了,效果如下:
完整代碼如下:
<body onload="main()">
<!-- 頂點(diǎn)著色器源碼 -->
<script type="shader-source" id="vertexShader">
void main(){
//聲明頂點(diǎn)位置
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
//聲明要繪制的點(diǎn)的大小训柴。
gl_PointSize = 10.0;
}
</script>
<!-- 片元著色器源碼 -->
<script type="shader-source" id="fragmentShader">
void main(){
//設(shè)置像素顏色為紅色
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</script>
<canvas id="canvas" width="640" height="480">
Your browser doesn't appear to support the HTML5 <code><canvas></code> element.
</canvas>
<script type="text/javascript">
function main() {
// 獲取webGL的繪圖環(huán)境
const canvas = document.querySelector("#canvas")
const gl = canvas.getContext("webgl")
// 創(chuàng)建頂點(diǎn)著色器
const vertexShaderSource = document.querySelector('#vertexShader').innerHTML
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vertexShader, vertexShaderSource)
gl.compileShader(vertexShader)
// 創(chuàng)建片元著色器
const fragmentShaderSource = document.querySelector('#fragmentShader').innerHTML
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fragmentShader, fragmentShaderSource)
gl.compileShader(fragmentShader)
// 創(chuàng)建著色器程序
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
gl.useProgram(program)
// 繪制
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.POINTS, 0, 1)
}
</script>
</body>
參考資料: