摘要:通過上述概念可以看出,WebGL 將 JavaScript 和 OpenGL ES 2.0 結合在一起,因此也會使用 GLSL(OpenGL Shader Language) 作爲 Shading Language(一種頂點計算和着色的語言,緩存編譯到 GPU,由 GPU 來執行)。通過這個例子可以先思考一下,想要渲染出一個圖形,就需要告訴 GPU 圖形的頂點(即座標向量),如果需要變化(如:平移、旋轉、縮放等),就需要告之對應的矩陣,這也就是文章後面要說的 GLSL 語言核心需要做的事情~。

作者:竇金蘭  — 字節跳動IES前端工程師  

希望通過這篇文章,大家能夠對 OpenGL/WebGL 有一個基礎的認識~~

OpenGL

定義

OpenGL 是一套規範,不是接口,學習這套規範,就可以在支持 OpenGL 的機器上正常使用這些規範,在顯示器上看到繪製的結果。

這套接口是 Khronos 這個組織在維護。怎麼維護呢?就是寫一個說明書,指導各個 GPU 廠家,如果他們要支持 OpenGL 的話,要怎麼實現一個具體的 OpenGL 庫。比如 Khronos 說要實現 glDrawArray這 個接口,那麼硬件廠家就得在它的庫裏實現這個接口。如果不實現,那麼就不算支持 OpenGL。當然也有一些接口不一定要實現。

廠家實現的 OpenGL 庫的內容,其實就是廠家自己的團隊整合自己的圖形知識以及 GPU 硬件的指令,這些 OpenGL 的實現通常被稱爲“驅動”,它們負責將 OpenGL 定義的 API 命令翻譯爲 GPU 指令。因此使用時只需要安裝顯卡的驅動。

既然是在 GPU 上運行的 OpenGL,那麼接下來我們來了解一下 GPU ~

GPU

概念

顯卡處理器稱爲圖形處理器(即 GPU ),它是顯卡的“心臟”,與 CPU 類似,只不過 GPU 是專爲執行復雜的數學和幾何計算而設計的,這些計算是圖形渲染所必需的。一些最快速的 GPU 集成的晶體管數甚至超過了普通 CPU。

GPU 的工作

現代的 GPU 功能涵蓋了圖形顯示的方方面面,這裏只取一個簡單的方向作爲例子。這個立方體渲染的例子,會有助於理解接下來會講到的 GLSL(OpenGL着色器) 語言。

大家可能都見過上面這張圖,這是老版本 Direct X (是由微軟公司創建的一系列專爲多媒體以及遊戲開發的應用程序接口)的一項測試,就是一個旋轉的立方體。顯示出一個這樣的立方體要經過很多步驟,我們先考慮簡單一點的,想象一下他只是一個線框,沒有側面的“X”圖像。再簡化一點,連線都沒有,就是八個點(立方體有八個頂點的)。那麼問題就簡化成如何讓這八個點轉起來。

首先,在創造這個立方體的時候,肯定有八個頂點的座標,座標都是用向量表示的,因而至少也是個三維向量。然後“旋轉”這個變換,在線性代數里面是用一個矩陣來表示的。向量旋轉,是用向量乘以這個矩陣。把這八個點轉一下,就是進行八次向量與矩陣的乘法而已。

這種計算並不複雜,拆開來看無非就是幾次乘積加一起,就是計算量比較大。八個點就要算八次,2000個點就要算2000次。這就是 GPU 工作的一部分,頂點變換,這也是最簡單的一部分。

通過這個例子可以先思考一下,想要渲染出一個圖形,就需要告訴 GPU 圖形的頂點(即座標向量),如果需要變化(如:平移、旋轉、縮放等),就需要告之對應的矩陣,這也就是文章後面要說的 GLSL 語言核心需要做的事情~

CPU 與 GPU 區別大揭祕

CPU 和 GPU 因爲最初用來處理的任務就不同,所以設計上有很大的區別。它們分別針對了兩種不同的應用場景。

CPU 需要很強的通用性來處理各種不同的數據類型,同時又要邏輯判斷又會引入大量的分支跳轉和中斷的處理。這些都使得 CPU 的內部結構異常複雜。而 GPU 面對的則是類型高度統一的、相互無依賴的大規模數據和不需要被打斷的純淨的計算環境。

於是 CPU 和 GPU 就呈現出非常不同的架構(示意圖):

其中綠色的是計算單元,橙紅色的是存儲單元,橙黃色的是控制單元。

GPU 採用了數量衆多的計算單元和超長的流水線,但只有非常簡單的控制邏輯並省去了 Cache。而 CPU 不僅被 Cache 佔據了大量空間,而且還有有複雜的控制邏輯和諸多優化電路,相比之下計算能力只是 CPU 很小的一部分。

而 GPU 的工作大部分就是這樣,計算量大,但沒什麼技術含量,而且需要重複很多次。就像你有個工作需要算幾億次一百以內加減乘除一樣,最好的辦法就是僱上幾十個小學生一起算,一人算一部分,反正這些計算也沒什麼技術含量,純粹體力活,人海戰術而已。而 CPU 則像老教授,積分微分都會算,一個老教授資頂二十個小學生。GPU 就是這樣,用很多簡單的計算單元去完成大量的計算任務。不過這種策略基於一個前提,就是每個小學生工作沒有什麼依賴性,是互相獨立的,即 GPU 的計算單元所做的事情是互相獨立的。

還有一些任務涉及到步驟的問題,不能把執行順序顛倒了。這種比較複雜的問題都是 CPU 來做的。

GPU 的運算速度取決於僱了多少小學生,CPU 的運算速度取決於請了多厲害的教授。教授處理複雜任務的能力是碾壓小學生的,但是對於沒那麼複雜的任務,還是人多力量大。不過現在的 GPU 也能做一些稍微複雜的工作,但還是需要 CPU 把數據給 GPU 才能開始幹活,因此還是靠 CPU 來管的。

至此爲止,GPU 的內容先了解到這裏,接下來我們繼續回到 OpenGL。

OpenGL ES

OpenGL ES 與 WebGL 有關,WebGL 是基於 OpenGL ES 2.0 的 Javascript API,因此我們在這裏先來了解一下OpenGL ES。

OpenGL ES 是 OpenGL 的子集,專門針對手機/PDA(掌上電腦,如: 條形掃碼器,POS機等)/遊戲主機等嵌入式設備設計的。OpenGL ES 主要直接提供 C api,各自平臺根據習慣提供一層包裝(比如Android提供了Java的包裝,iOS提供了obj-c的包裝)。

雖然 OpenGL ES 是 OpenGL 的子集,但是 OpenGL 與 OpenGL ES 還是有一點區別,比如他們的數據類型會存在一些不一樣:

  1. OpenGL ES 沒有 double 型(浮點)數據類型,而是加入了高性能的定點小數數據類型;

  2. OpenGL ES 沒有 glBegin/glEnd/glVertex,只能用 glDrawArrays/glDraw......;

  3. 沒有實時將非壓縮圖片數據轉成壓縮貼圖的功能,程序必須直接提供壓縮好的貼圖;

  4. ...

可實現濾鏡效果:chestnut:

這裏可以簡單看一些直接使用 OpenGL 實現的濾鏡效果

  1. 縮放、出竅、抖動、閃白、毛刺

  2. 灰度、旋渦、馬賽克

  3. 分屏

注意:這些直接使用 OpenGL 實現濾鏡效果的例子可以瞭解一下,但是團隊項目中使用到的濾鏡效果是通過 effect sdk 統一支持(Windows/Mac/Android/iOS),因此如果需要實現 effect creator for web,則需要在瀏覽器支持 effect sdk 的使用,以保證多端統一,且支持現有功能效果。

至此,除了 GLSL 語言以及具體API,OpenGL 的基礎知識就這麼多了。OpenGL 是在移動端/桌面端使用,那麼在 Web 端呢?就是大家熟悉的 WebGL 了,我們一起來看一下~

WebGL

一些 WebGL 應用場景:chestnut:

  • 3D 的數據可視化

  • kaspersky

  • 3D遊戲開發

  • WebCam Mesh

  • webgl games

  • Cube Slam

  • 打造炫酷的交互

  • WebGL bookcase

  • H5宣傳頁面 & 廣告

  • 淘寶雙11VR邀請函 H5頁面

  • 進行 3D 產品 / 物體展示

  • WebGL reflection

  • ...

概念

WebGL 是一種 3D 繪圖標準,這種繪圖技術標準把 JavaScript 和 OpenGL ES 2.0 結合在一起,通過 HTML5 的 Canvas 來和 DOM 打交道,爲HTML5 Canvas 提供硬件 3D 加速渲染。WebGL 技術標準免去了開發網頁專用渲染插件的麻煩,可被用於創建具有複雜 3D 結構的網站頁面,甚至可以用來設計 3D 網頁遊戲等。

與 OpenGL 的關係

通過上述概念可以看出,WebGL 將 JavaScript 和 OpenGL ES 2.0 結合在一起,因此也會使用 GLSL(OpenGL Shader Language) 作爲 Shading Language(一種頂點計算和着色的語言,緩存編譯到 GPU,由 GPU 來執行)。

說白了,就是通過瀏覽器提供的接口,我們能夠直接和底層的 OpenGL 庫打交道。由於能直接調用底層接口,並且有硬件加速,因此 WebGL 要比普通的 Canvas 2D Api 性能要高出不少。這裏有一個WebGL 和 Canvas 2D Api 性能的對比實驗結果,橫座標是繪製任務數量,縱座標是頁面的 FPS(畫面每秒傳輸幀數)。

從結果中可見,當需要執行大量繪製任務時,WebGL 的性能遠遠超越了 Canvas 2D Api,達到了後者的3~5倍。

Three.js

爲什麼會介紹一下這個庫,是因爲在學習 WebGL知識時 總會看到一個庫:Three.js,那我們這裏也來簡單的瞭解一下。Three.js 是一個用於在瀏覽器中繪製3D圖形的JS庫,其底層實際是對瀏覽器提供的 WebGL Api 進行了封裝,類似於 JS 與 JQuery 的關係,甚至不需要 WebGL 基礎就能夠上手使用,但是由於是以 WebGL 爲基礎,所以遇到問題還得回來查看 WebGL,而 WebGL 的基礎又是 OpenGL ES,因此 OpenGL 就顯得至關重要了。

OpenGL 很重要,而 OpenGL 還有一個重要部分就是前面多次提到的 GLSL(OpenGL 着色器語言),接下來我們就來看看這個着色器語言究竟是什麼吧~~

GLSL着色器語言

首先要明白,着色器(Shader)是運行在 GPU 上的小程序。這些小程序爲圖形渲染管線的某個特定部分而運行。從基本意義上來說,着色器只是一種把輸入轉化爲輸出的程序,比如我們要畫一個三角形,着色器只是通過讀取我們傳給它的頂點,顏色,變化等輸入,然後經過一系列計算,最終輸出圖形。

着色器主要分爲頂點着色器和片段(像素)着色器,這也是主要的兩種着色器,還有一種是幾何着色器。每個着色器是非常獨立的程序,它們之間不能相互通信,唯一的溝通只能通過輸入和輸出。通常一個WebGL 應用會有多個着色程序。我們可以根據着色起的名字來思考一下他們的作用。頂點着色器,顧名思義就是爲了渲染圖形的頂點所使用的,回想一下我們剛纔講的 GPU 的工作,一個立方體的渲染,肯定是先要找到立方體的頂點,這個就是頂點着色器的作用了。頂點找到後,就會連接成線,以及形成平面,那麼線段/平面的顏色等就是片段着色器的工作了。

着色器是使用一種叫GLSL的類C語言寫成的。GLSL是爲圖形計算量身定製的,它包含一些針對向量和矩陣操作的有用特性。數據類型:

修飾符:

這裏只是簡單介紹了一下常用概念,關於 GLSL 概念的詳解,可以看一下這裏

我們在 GPU 的工作一節提到過,座標都是向量表示,變化(比如:旋轉/平移/縮放等)都是通過矩陣表示,回到大學線性代數知識,向量(a1=[x1, y1, z1])與矩陣相乘,就會得到另一個向量(a2=[x2, y2, z2]),a1通過矩陣運算,得到了a2,這樣就改變了座標的位置。看到這裏就明白了,如何通過計算得出我們想要的結果,就需要線性代數的知識了。(PS:矩陣真的很神奇,幾乎一切變化都從這裏來,在最後的例子中帶大家來看看矩陣帶來的魔法吧)

看完着色器的基本知識後,我們就可以看一下渲染的過程了。

WebGL 渲染過程

WebGL API 在瞭解一門新技術前,我們都會先看看它的開發文檔或者API。於是,我們查看WebGL繪圖API,發現:

是的,它只能畫點、線、三角形。就算是像下面這樣的複雜模型,也是一個個三角形畫出來的。

簡單繪製流程

簡單說來,WebGL繪製過程包括以下三步:

  1. 獲取頂點座標(使用頂點着色器)

  2. 圖元裝配(這裏畫出一個個三角形,gl.TRIANGLES)

  3. 光柵化(生成片元/片段,即一個個像素點,使用片段/像素着色器)

接下來,我們分步講解每個步驟。

頂點座標

頂點座標從何而來呢?一個立方體還好說,但如果是像上邊複雜的茶壺呢?想一下,每個三角形都有三個頂點,而一個茶壺就會有成千上萬個頂點,而且還需要精密的計算,顯然人的肉眼以及精力是不允許一個一個寫這些座標的。往往它使用三維軟件(C4D、MAYA、3DS max等)導出,或者是框架生成。

獲取頂點座標過程圖:

前面兩個步驟都很好理解,但是第三部寫入緩存區是什麼意思呢?由於頂點數據往往成千上萬,在獲取到頂點座標後,我們通常會將它存儲在緩存區內,方便 GPU 更快的讀取。

圖元裝配

我們已經知道,圖元裝配就是由頂點生成一個個圖元(即點/線/三角形)。那這個過程是自動完成的嗎?答案是並非完全如此。WebGL 需要我們先處理頂點,那怎麼處理呢?我們先看下圖:

第一步就是將上面緩存中的頂點座標傳入了頂點着色器,頂點着色器根據傳入的gl.POINTS/gl.LINES/gl.TRIANGLES參數,進行圖元裝配(通俗一點講,就是要畫點,還是線,還是三角形)

下面是一段頂點着色器代碼:

這裏具體解釋一下:attribute 修飾符,是由瀏覽器(javascript)傳輸給頂點着色器的變量值修飾符;vec4 就是包含4個元素的浮點型向量(座標);position 即我們定義的頂點座標,傳入到着色器的;gl_Position 是一個內建的傳出變量。

這段代碼其實就是 GPU 通過傳入的數據找頂點的過程。

光柵化

和圖元裝配類似,光柵化也是可控的。

在圖元生成完畢之後,我們需要給模型“上色”,模型看起來是什麼質地(顏色、漫反射、貼圖等)、燈光等,而完成這部分工作的,則是運行在 GPU 的“片元着色器”來完成。如下是一段簡單的片元着色器代碼:

precision 是指向一個整數的指針,返回的該整數是對應格式的精度的位數,用 log2 取對數的值,暫不做深究。gl_FragColor 是一個內建的傳出變量,即輸出的顏色值,這段代碼就是紫粉色。

片元着色器處理流程

片元着色器具體是如何控制顏色生成的呢?

如上圖,頂點着色器是有多少頂點,運行了多少次,而片元着色器則是,有多少片元(像素),運行多少次。

整體詳細繪製流程

至此,實質上,WebGL經歷瞭如下處理流程(這裏我們涉及到的前面沒講到的名詞稍微多一點,但是大概涵蓋了所有涉及到的內容):

  1. 準備數據階段 在這個階段,我們需要提供頂點座標、索引(三角形繪製順序)、uv(決定貼圖座標)、法線(決定光照效果),以及各種矩陣(比如投影矩陣)。

如何傳入?

  • 頂點數據存儲在緩存區(因爲數量巨大),以修飾符attribute傳遞給頂點着色器;

  • 矩陣則以修飾符uniform傳遞給頂點着色器。

  1. 生成頂點着色器 根據我們需要,由Javascript定義一段頂點着色器程序的字符串,生成並且編譯成一段着色器程序傳遞給 GPU。傳入的頂點着色器程序,是一個字符串,這是 WebGL API 所要求的,會進行編譯成着色器語言。我們來大致看一下看一下。

  1. 圖元裝配 GPU根據頂點數量,挨個執行頂點着色器程序,生成頂點最終的座標,完成座標轉換。

  2. 生成片元着色器 這一步則是解決我們最終繪製出來的效果,它的模型是什麼顏色,看起來是什麼質地,光照效果,陰影(流程較複雜,需要先渲染到紋理,可以先不關注),都在這個階段處理。

  3. 光柵化 通過第4步生成了片元着色器,因此 GPU 內部已經確定好了每個片元的顏色,然後根據深度緩存區判斷哪些片元被擋住了,不需要渲染,最終將片元信息存儲到顏色緩存區,最終完成整個渲染。

WebGL 入門實例

通過一些小例子,學會使用 WebGL 基礎知識

例1:簡單的畫一個三角形,學會從 WebGL 到着色器的全過程 [可參看這裏] 步驟:

  1. 獲取canvas,以及 webgl context

  2. 編寫着色器(字符串形式)

  3. 創建頂點/片段着色器

  4. 將頂點/片段着色器鏈接在一起

  5. 將位置的座標放入buffer 中,因爲着色器從 buffer 讀取數據

  6. 傳入繪製需要的數據(比如2D/3D 緩衝位置等)

  7. 開始繪製

例2:通過例1進行簡單的修改,一個變色三角形 [可參看這裏https://webglfundamentals.org/webgl/lessons/zh cn/webgl-how-it-works.html] 例3:只能畫 點/線/三角形 的 WebGL 如何畫一個矩形呢?[可參看這裏https://webglfundamentals.org/webgl/lessons/zh cn/webgl-fundamentals.html] 例4:繪製一張圖片呀,再加一點魔法 [可參看這裏https://webglfundamentals.org/webgl/lessons/zh cn/webgl-image-processing.html] 例5:給圖片施更多的魔法(矩陣的神奇力量) [可參看這裏https://webglfundamentals.org/webgl/lessons/zh cn/webgl-image-processing-continued.html] 例6:二維平移/旋轉/縮放(二維矩陣的由來看這裏https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-2d-matrices.html) 其實最重要的就是頂點座標,因爲片段着色器只是將頂點按照所需圖元連線,因此 平移/旋轉/縮放 只需計算出變化後的頂點座標即可

WebGL 入門篇大概就講到這裏,相信大家對基礎已經有了一定的瞭解,但是 WebGL 還有很多知識,比如 投影/光源/相機/三維呈現 等,有興趣的可以接下來了解。

相關資料

OpenGL中文教程OpenGL ESOpenGL書籍:

  1. 紅寶書 OpenGL Programming Guide,出到第八版

  2. 藍寶書 OpenGL SuperBible,出到第六版

  3. 橙寶書 OpenGL Shading Language,出到第三版

  4. 更多

一個易懂的WebGL教程:WebGLFundamentalsWebGL MDN

相關文章圖解WebGL與Threejs工作原理與CPU的區別是什麼,渲染是否是並行計算的?

相關文章