一、我們講什麼?
我們講兩個東西:
1、WebGL背後的工作原理是什麼?
2、以Three.js為例,講述框架在背後扮演什麼樣的角色?
二、我們為什麼要了解原理?
我們假定你對WebGL已經有一定了解,或者用Three.js做過了一些東西,這個時候,你可能碰到了這樣一些問題:
1、很多東西還是做不出來,甚至沒有任何思路;
2、碰到bug無法解決,甚至沒有方向;
3、效能出現問題,完全不知道如何去優化。
這個時候,我們需要了解更多。
三、先了解一個基礎概念
1、什麼是矩陣?
簡單說來,矩陣用於座標變換,如下圖:
2、那它具體是怎麼變換的呢,如下圖:
3、舉個例項,將座標平移2,如下圖:
如果這時候,你還是沒有理解,沒有關係,你只需要知道,矩陣用於座標變換。
四、WebGL的工作原理
4.1、WebGL API
在瞭解一門新技術前,我們都會先看看它的開發文件或者API。
檢視Canvas的繪圖API,我們會發現它能畫直線、矩形、圓、弧線、貝塞爾曲線。
於是,我們看了看WebGL繪圖API,發現:
它只能會點、線、三角形?一定是我看錯了。
沒有,你沒看錯。
就算是這樣一個複雜的模型,也是一個個三角形畫出來的。
4.2、WebGL繪製流程
簡單說來,WebGL繪製過程包括以下三步:
1、獲取頂點座標
2、圖元裝配(即畫出一個個三角形)
3、光柵化(生成片元,即一個個畫素點)
接下來,我們分步講解每個步驟。
4.2.1、獲取頂點座標
頂點座標從何而來呢?一個立方體還好說,如果是一個機器人呢?
沒錯,我們不會一個一個寫這些座標。
往往它來自三維軟體匯出,或者是框架生成,如下圖:
寫入快取區是啥?
沒錯,為了簡化流程,之前我沒有介紹。
由於頂點資料往往成千上萬,在獲取到頂點座標後,我們通常會將它儲存在視訊記憶體,即快取區內,方便GPU更快讀取。
4.2.2、圖元裝配
我們已經知道,圖元裝配就是由頂點生成一個個圖元(即三角形)。那這個過程是自動完成的嗎?答案是並非完全如此。
為了使我們有更高的可控性,即自由控制頂點位置,WebGL把這個權力交給了我們,這就是可程式設計渲染管線(不用理解)。
WebGL需要我們先處理頂點,那怎麼處理呢?我們先看下圖:
我們引入了一個新的名詞,叫“頂點著色器”,它由opengl es編寫,由javascript以字串的形式定義並傳遞給GPU生成。
比如如下就是一段頂點著色器程式碼:
1
2
3
4
|
attribute vec4 position; void main() { gl_Position = position; } |
attribute修飾符用於宣告由瀏覽器(javascript)傳輸給頂點著色器的變數值;
position即我們定義的頂點座標;
gl_Position是一個內建的傳出變數。
這段程式碼什麼也沒做,如果是繪製2d圖形,沒問題,但如果是繪製3d圖形,即傳入的頂點座標是一個三維座標,我們則需要轉換成螢幕座標。
比如:v(-0.5, 0.0, 1.0)轉換為p(0.2, -0.4),這個過程類似我們用相機拍照。
4.2.2.1、頂點著色器處理流程
回到剛才的話題,頂點著色器是如何處理頂點座標的呢?
如上圖,頂點著色器會先將座標轉換完畢,然後由GPU進行圖元裝配,有多少頂點,這段頂點著色器程式就執行了多少次。
你可能留意到,這時候頂點著色器變為:
1
2
3
4
5
|
attribute vec4 position; uniform mat4 matrix; void main() { gl_Position = position * matrix; } |
這就是應用了矩陣matrix,將三維世界座標轉換成螢幕座標,這個矩陣叫投影矩陣,由javascript傳入,至於這個matrix怎麼生成,我們暫且不討論。
4.2.3、光柵化
和圖元裝配類似,光柵化也是可控的。
在圖元生成完畢之後,我們需要給模型“上色”,而完成這部分工作的,則是執行在GPU的“片元著色器”來完成。
它同樣是一段opengl es程式,模型看起來是什麼質地(顏色、漫反射貼圖等)、燈光等由片元著色器來計算。
如下是一段簡單的片元著色器程式碼:
1
2
3
4
|
precision mediump float ; void main( void ) { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); } |
gl_FragColor即輸出的顏色值。
4.2.3.1、片元著色器處理流程
片元著色器具體是如何控制顏色生成的呢?
如上圖,頂點著色器是有多少頂點,執行了多少次,而片元著色器則是,生成多少片元(畫素),執行多少次。
4.3、WebGL的完整工作流程
至此,實質上,WebGL經歷瞭如下處理流程:
1、準備資料階段
在這個階段,我們需要提供頂點座標、索引(三角形繪製順序)、uv(決定貼圖座標)、法線(決定光照效果),以及各種矩陣(比如投影矩陣)。
其中頂點資料儲存在快取區(因為數量巨大),以修飾符attribute傳遞給頂點著色器;
矩陣則以修飾符uniform傳遞給頂點著色器。
2、生成頂點著色器
根據我們需要,由Javascript定義一段頂點著色器(opengl es)程式的字串,生成並且編譯成一段著色器程式傳遞給GPU。
3、圖元裝配
GPU根據頂點數量,挨個執行頂點著色器程式,生成頂點最終的座標,完成座標轉換。
4、生成片元著色器
模型是什麼顏色,看起來是什麼質地,光照效果,陰影(流程較複雜,需要先渲染到紋理,可以先不關注),都在這個階段處理。
5、光柵化
能過片元著色器,我們確定好了每個片元的顏色,以及根據深度快取區判斷哪些片元被擋住了,不需要渲染,最終將片元資訊儲存到顏色快取區,最終完成整個渲染。
五、Three.js究竟做了什麼?
我們知道,three.js幫我們完成了很多事情,但是它具體做了什麼呢,他在整個流程中,扮演了什麼角色呢?
我們先簡單看一下,three.js參與的流程:
黃色和綠色部分,都是three.js參與的部分,其中黃色是javascript部分,綠色是opengl es部分。
我們發現,能做的,three.js基本上都幫我們做了。
- 輔助我們匯出了模型資料;
- 自動生成了各種矩陣;
- 生成了頂點著色器;
- 輔助我們生成材質,配置燈光;
- 根據我們設定的材質生成了片元著色器。
而且將webGL基於光柵化的2D API,封裝成了我們人類能看懂的 3D API。
5.1、Three.js頂點處理流程
從WebGL工作原理的章節中,我們已經知道了頂點著色器會將三維世界座標轉換成螢幕座標,但實際上,座標轉換不限於投影矩陣。
如下圖:
之前WebGL在圖元裝配之後的結果,由於我們認為模型是固定在座標原點,並且相機在x軸和y軸座標都是0,其實正常的結果是這樣的:
5.1.1、模型矩陣
現在,我們將模型順時針旋轉Math.PI/6,所有頂點位置肯定都變化了。
1
|
box.rotation.y = Math.PI/6; |
但是,如果我們直接將頂點位置用javascript計算出來,那效能會很低(頂點通常成千上萬),而且,這些資料也非常不利於維護。
所以,我們用矩陣modelMatrix將這個旋轉資訊記錄下來。
5.1.2、檢視矩陣
然後,我們將相機往上偏移30。
1
|
camera.position.y = 30; |
同理,我們用矩陣viewMatrix將移動資訊記錄下來。
5.1.3、投影矩陣
這是我們之前介紹過的了,我們用projectMatrix記錄。
5.1.4、應用矩陣
然後,我們編寫頂點著色器:
1
|
gl_Position = position * modelMatrix * viewMatrix * projectionMatrix; |
這樣,我們就在GPU中,將最終頂點位置計算出來了。
實際上,上面所有步驟,three.js都幫我們完成了。
5.2、片元著色器處理流程
我們已經知道片元著色器負責處理材質、燈光等資訊,但具體是怎麼處理呢?
如下圖:
5.3、three.js完整執行流程:
當我們選擇材質後,three.js會根據我們所選的材質,選擇對應的頂點著色器和片元著色器。
three.js中已經內建了我們常用著色器。