前端建模基礎
計算機圖形學(Computer Graphics)主要是研究物體在計算機中的圖形顯示與互動,各個領域都能利用計算機圖形顯示獲取相關的好處,作為一個前端工程師,瞭解相關圖形學的基礎有利於往更高的臺階前行。本文章借鑑了《現代計算機圖形學基礎》一書中的部分概念,希望整理出對前端工程師更為貼近的內容
認識計算機圖形學(CG)
計算機圖形在前端中最常見的應用就是資料統計了吧,echart,G2等等外掛提供了多樣的資料圖,這些外掛通過將大量的資料轉化成可視形式,其資料趨勢與線性關係得以更容易的展示出來,使得資料在前端的顯示更為直觀與優雅
另外,新興起的VR(virtual-reality)技術,也在不少的前端頁面中出現,VR互動中,使用者可與三維場景中的物件進行互動。此類展現方式可以讓使用者更好的與設計者溝通,使使用者體驗得到增強
CG是以建模,繪製,互動,動畫,影像為核心內容並服務於各個領域的一門科學。
對於前端工程師來說,建模就是指使用前端庫,通過資料來表達物體和場景並構建對應的資料化三維模型的過程。
繪製則是指將這些三維模型通過一些計算和變換,最終在螢幕上渲染出來的過程,也是從幾何模型到畫素的轉化。
所以,工程師使用計算機語言輸入的是三維模型,而通過計算機模擬人類的視覺功能,對三維模型進行加工處理,最終在螢幕上輸出的是二維影像。雖然輸出的是二維影像,但是通過計算機的處理,影像中的物體之間的關係符合人類對現實世界的認知,從而使大腦形成三維空間的感知。
基礎概念
圖形是由點,線,面等幾何元素組成而成的,這些元素統稱為圖元。簡單的圖元可以組成一些立體圖元,從而又組合成簡單的幾何模型,複雜的模型又通過這些簡單的模型組合而成,通過模型變換整合座標系最終形成複雜場景。在threejs中的場景圖就是這樣一層一層的構建的。
圖形只得是由資料組成的幾何資訊,而影像指的是由畫素組成的二維柵格,所以圖形不會失真,影像會失真
正如上述,建立的三維模型最終要在顯示器中顯示,而顯示器是基於畫素的二維影像顯示,所以需要將圖形經過一系列的幾何變換對映成螢幕座標中的二維影像,確定圖元對應的影像座標位置,其中涉及各種物理座標的轉換,最後還要經過光柵化處理(確定指定解析度下哪些畫素被覆蓋,以何種顏色進行色彩顯示)。最後才顯示在顯示器中。
這一系列的過程也稱做GPU的圖形流水線(也稱渲染流水線或渲染管線),實際的應用中,圖形流水線是十分複雜的,以OpenGL為例,按照流水線操作的物件大體可分為兩類操作:
處理影像元素的畫素操作 | 處理圖元元素的幾何操作 |
---|---|
區域填充 | 模型變換 |
片元組裝 | 頂點著色 |
紋理對映 | 紋理變換 |
顏色合成 | 視點變換 |
深度測試 | 視域剔除 |
遮罩測試 | 投影變換 |
透明融合 | 背面剔除 |
霧化處理 | 視窗變換 |
?座標的幾何變換
幾何變換包括了:
- 模型變換:將物體的區域性座標系變換到世界座標系,比如three中的場景圖將一個模型自身的座標系融合進sence中,否則模型的動畫計算量將無可估量
- 視點變換:將場景中的物體模型轉化到以人眼為中心的座標系下表示,方便人眼視網膜投影成像
- 投影變換:將眼睛座標系通過投影產生二維影像,這就像是three中的camera,使用正交投影或者透視投影減少一個維度,生成二維影像
- 視窗變換:最後將二維影像進一步畫素化,對映到螢幕視窗上,顯示出來
幾種變換的原理都是矩陣變換,幾何操作中使用矩陣變換與canvas的矩陣變換類似,因為使用齊次座標系,所以使用四階矩陣表示線性關係
1const point = vec(x, y, z, 1)2const matrix = [
3 a, d, g, j,
4 b, e, h, k,
5 c, f, i, l,
6 0, 0, 0, 1
7]
8// j k l 表示x y z的平移
9// a e i 表示x y z的縮放
10// ehfi, agci, adbe (cos⌀ - sin⌀ sin⌀ cos⌀)表示x y z的旋轉
?光柵化
光柵化處理包括了:
- 簡單圖元生成
複雜的圖形是由基本的圖元組合而成的,因此首先需要使用演算法將圖元變為畫素集合。例如:
圖片摘自《現代計算機圖形學基礎》 - 多邊形填充
確定螢幕上哪些畫素位於多邊形內部 - 剔除
將不會或不需要在螢幕上顯示的圖元排除,減少圖形繪製的計算量
包括視域剔除,小物體剔除,背面剔除,退化剔除等等 - 可見性判斷
深度測試,判斷模型是否被遮擋
深度快取演算法(z-buffer):按照越在後面的物體深度z值越大的原則,每個畫素只填充z值最小的圖後設資料
但是深度問題一直是3D場景中難以處理的問題,就像和平精英這樣的遊戲中也避免不了類似的bug,遠處觀察時可以穿透建築或是樹看到後面的人,這就是因為顯示器分辨不出哪個在前哪個在後的問題 - 其他(紋理貼圖,透明,反走樣,陰影,霧化,模糊)
?數字幾何
幾何模型是從數學形狀的角度描述物體的外觀。通常建模使用多邊形網格表示。
由頂點、邊和多邊形面組成的集合來表示的幾何形狀稱為網格(Mesh)。其中由三角形面片組成的三角形網格更加的穩定與靈活,成為前端建模的首選。
在WebGL中,與大多數實時3D圖形一樣,三角形是繪製模型的基本元素。因此,在WebGL中繪製的過程涉及使用JavaScript生成資訊,該資訊指定了這些三角形的建立位置和方式,以及它們的外觀(顏色,陰影,紋理等)。然後,此資訊將饋送到 GPU 進行處理,並返回場景檢視。
通過對網格的編輯,操縱和修改基礎幾何模型,可以實現對複雜模型的快速建模
?圖形硬體
顯示卡的功能是將計算機需要顯示的資料資訊轉換為顯示器資訊,並驅動其顯示的計算機硬體。在GPU出現之前,圖形流水線的操作大多由CPU來完成,顯示卡上只能處理一些簡單的演算法。而隨著圖形顯示功能的需求發展,CPU的串聯工作模式已經不能滿足數以億計的畫素計算,(一臺1920*1080的顯示器,一幀就有約200萬的畫素需要計算,60Hz的重新整理率,一秒就有上億的計算量)。所以以並聯工作方式為基礎的GPU得以迅速發展,GPU是由數以千計的更小更高效的核心組成的大規模平行計算構建,專門為同時處理多重任務而設計的。
支援可程式設計GPU(Graphic Processing Unit)圖形處理器的出現,取代了部分原本由CPU執行的工作(主要是幾何變換和光柵化),GPU更適合處理大量並行而相單一的邏輯,這樣就減輕了CPU的負擔,並提升了繪製效率。
GPU的並行處理包括基於任務的並行處理與基於資料的並行處理
圖形流水線也稱渲染流水線或渲染管線,GPU的並行處理機制使得渲染流水線更加高效的進行。由固定函式進行處理流水線上功能的也稱固定管線
隨著開發者需求的增長,上述固定渲染流水線已經滿足不了開發者的需求,所以一些用來代替固定渲染管線的可高度程式設計程式演算法出現了,這就是著色器。通過著色器,開發者可以自己編寫顯示卡渲染的相關演算法,可以實現各種各樣的影像效果而不用受顯示卡的固定渲染管線限制。
WebGL渲染管線
建立頂點陣列。這些陣列包含頂點屬性,以及有關頂點的紋理、顏色或光照(垂直頂點)如何影響頂點的資訊。
然後,通過將頂點陣列中的資料傳送到頂點緩衝區,以便GPU快速讀取。我們還提供指向頂點陣列元素的附加索引陣列。它們控制頂點稍後如何組合成三角形。
頂點緩衝區在視訊記憶體中開闢一塊區域,這樣繪製時直接讀取視訊記憶體中的資料就可以了,明顯提高渲染速度。
GPU 首先從頂點緩衝區中讀取每個選定的頂點,並通過頂點著色器執行它。頂點著色器將一組頂點屬性作為輸入並輸出一組新的屬性。
然後,GPU 連線投影的頂點以形成三角形。它通過按 indexs 陣列指定的順序獲取頂點。
光柵器獲取每個三角形,對其進行裁剪,丟棄螢幕外部的部分,並將剩餘的可見部分分解為畫素大小的碎片。頂點著色器其他頂點屬性的輸出也會在每個三角形的柵格化表面上進行插值,從而為每個片元分配一個平滑的值漸變。
然後,生成的畫素大小的片元通過片元著色器。片元著色器輸出每個畫素的顏色和深度值,然後將其繪製到幀緩衝區中。
幀緩衝器是渲染作業輸出的最終目標。幀快取包括顏色、scissor、alpha、stencil、depth這些快取,所以幀快取不是一片快取,而是所有這些快取的組合,幀緩衝器還可以具有深度緩衝區和/或模具緩衝區,這兩者都可以選擇在片元繪製到幀緩衝器之前對其進行過濾。
深度測試會丟棄位於已繪製物件後面的物件中的片元,而模具測試使用繪製到模具緩衝區中的形狀來約束幀緩衝器的可繪製部分,從而“模具化”渲染作業。在這兩個濾鏡中倖存下來的片元的顏色值 alpha 與它們覆蓋的顏色值混合在一起。最終的顏色、深度和模具值將繪製到相應的緩衝區中。緩衝區的輸出還可以用作其他渲染作業的紋理輸入。
GLSL ES
專門用來為著色器程式設計的程式語言就是著色器語言。區別於大多數執行在CPU中的語言,著色器語言執行在GPU中,與OpenGL相配合的著色器語言是GLSL,應用在客戶端,而與WebGL配合的著色器語言是GLSL ES,應用在瀏覽器平臺。
WebGL著色器程式碼分為頂點著色器程式碼和片元著色器程式碼兩部分,通過WebGL編譯處理後,最終在GPU的頂點著色器單元與片元著色器單元上執行。
頂點著色器定義了頂點的渲染位置和點的渲染畫素大小,對應幾何操作。頂點著色器至少會計算頂點在螢幕空間中的投影位置。但它也可以生成其他屬性,例如每個頂點的顏色或紋理座標。您可以對自己的頂點著色器進行編碼,也可以使用 WebGL 庫提供的頂點著色器。
片元著色器定義了點的渲染結果畫素的顏色值,對應畫素操作。常見的片元著色器操作包括紋理貼圖和光照。由於片元著色器為繪製的每個畫素獨立執行,因此它可以執行最複雜的特殊效果;但是,它也是圖形管道中對效能最敏感的部分。與頂點著色器一樣,您可以編寫自己的片元著色器,也可以使用 WebGL 庫提供的著色器。如果使用Three.js開發專案,其已經封裝好大部分功能,但若是需要自定義著色器或者使用原生WebGL的時候,便需要學習著色器語言。
首先,與OpenGL配合的GLSL使用C語言作為基礎高階著色語言,避免了使用匯編語言或硬體規格語言的複雜性。GLSL ES語言是則是在GLSL著色器語言的基礎上,刪除和簡化一部分功能後形成的,其語法與C語言的較為類似。
GLSL ES大小寫敏感,而且表示式後不能省略';'
?基本資料型別
著色器語言GLSL的基本資料型別和C語言一樣具有常見的整型數int、浮點數float和布林值bool型別資料。
關鍵字 | 資料型別 | 值 |
---|---|---|
bool | 布林值 | 布林變數值為true或false |
int | 整型數 | 值為整數,比如0,1,2,3… |
float | 單精度浮點數 | 浮點數用小數點表示,比如0.6,3.14,2.8 |
浮點數不能省略小數點
基本資料型別可以使用內建函式轉換:int()、float()、bool()
?宣告一個變數/常量
著色器語言ES GLSL和C語言一樣屬於強型別語言,宣告一個變數需要定義變數的資料型別
1// 著色器語言定義一個整形常量2int count = 10;
3// 定義一個浮點數變數num,並賦值10.0
4float num = 10.0;
5// 宣告一個資料型別是布林值的變數,並賦值為true
6bool lightBool = true;
7
8// 著色器語言定義一個整形常量
9const int count = 10;
變數名不能以數字開頭
不能以gl_、webgl_或webgl開頭,這些已經被OpenGL ES保留了
?向量
關鍵字 | 資料型別 |
---|---|
vec2 | 二維向量,具有xy兩個分量,分量是浮點數 |
vec3 | 三維向量 ,具有xyz三個分量,分量是浮點數 |
vec4 | 四維向量 ,具有xyzw四個分量,分量是浮點數 |
1vec3 dirction = vec3(1.0, 0.0, 0.0)ivec 向量的分量是整型數
ivec 向量的分量是布林值
2dirction.x = 1.0
3dirction.y = 0.0 + 2.0
4// dirction === vec3(1.0, 2.0, 0.0)
5
6vec2 v2 = v3.xy
7// v2 === vec2(1.0, 2.0)
向量與數字的運算和向量之間的運算都可以化簡為向量分量之間的運算
?矩陣
關鍵字 | 資料型別 |
---|---|
mat2 | 2×2矩陣,4個元素 |
mat3 | 3×3矩陣,9個元素 |
mat4 | 4×4矩陣,16個元素 |
2// 2.0 0.0 0.0 0.0
3// 0.0 2.0 0.0 0.0
4// 0.0 0.0 2.0 0.0
5// 0.0 0.0 0.0 2.0
6mat4 matrix = mat4(2.0)
7// 需要表示的矩陣
8// 1.1 1.2 1.3 1.4
9// 2.1 2.2 2.3 2.4
10// 3.1 3.2 3.3 3.4
11// 4.1 4.2 4.3 4.4
12// 注意行列對應關係,按照列的先後順序,從上到下依次傳入mat建構函式引數中
13mat4 matrix4 = mat4(
141.1,2.1,3.1,4.1,
151.2,2.2,3.2,4.2,
161.3,2.3,3.3,4.3,
171.4,2.4,3.4,4.4
18);
19
20// 訪問矩陣matrix4的第二列
21vec4 v4 = matrix4[1];//返回值vec4(1.2,2.2,3.2,4.2)
22// 訪問矩陣matrix4的第三列第四行對應的元素
23float f = matrix4[2][3];//返回4.3
矩陣與數字運算可以化簡為矩陣中的元素與數字的運算
矩陣與向量之間的運算和矩陣之間的運算則遵循線性代數的演算法
?陣列
和javascript語言、C語言一樣 可以宣告陣列型別變數,不過WebGL著色器的資料僅僅支援一維陣列,不支援多維陣列。
浮點數陣列使用Float32Array宣告
?條件語句/fors語句
關於if語句、for語句的使用,和javascript語言邏輯規則一致
?函式
函式有返回值時,函式計算後需要返回的值通過關鍵字return返回,注意宣告函式時候,函式名稱前需要宣告return返回值的資料型別。
1int add(int x,int y){2 return ...
3}
自定義函式無返回值的時候,和主函式main一樣,使用關鍵字void宣告函式。
自定義宣告一個函式,傳入一個引數,預設情況下,引數被修改了,但是並不影響傳入的引數對應變數的值,如果使用out關鍵字宣告引數變數,函式內部改變引數,函式外引數對應的變數會改變
?結構體
結構體主要功能就是利用WebGL著色器已經提供的常見資料型別,自定義一個新的資料型別。
1struct newVar {2 int a;
3 float b;
4};
5
6uniform newVar myVar;
7
8myVar.a = 8
9myVar.b = 8.0
?內建變數
內建變數就是不用宣告可以直接賦值,主要是為了實現特定的功能。
內建變數 | 含義 | 值資料型別 |
---|---|---|
gl_PointSize | 點渲染模式,方形點區域渲染畫素大小 | float |
gl_Position | 頂點位置座標 | vec4(x,y,z,1.0) |
gl_FragColor | 片元顏色值 | vec4(r,g,b,a) |
gl_FragCoord | 片元座標,單位畫素 | vec2 |
gl_PointCoord | 點渲染模式對應點畫素座標 | vec2 |
?attribute、uniform 和 varying
關鍵字(變數型別) | 資料傳遞 | 宣告變數 |
---|---|---|
attribute | javascript——>頂點著色器 | 宣告頂點資料變數 |
uniform | javascript——>頂點、片元著色器 | 宣告非頂點資料變數 |
varying | 頂點著色器——>片元著色器 | 宣告需要插值計算的頂點變數 |
頂點著色器和片元著色器程式碼都有一個唯一的主函式main(),attribute、varying和uniform型別的變數需要在main函式之外宣告,在main函式中通常編寫,逐片元或逐頂點處理的程式碼。
著色器中通過attribute和uniform關鍵字宣告的變數,需要通過javascript程式碼傳遞相關的資料。varying型別變數主要是為了完成頂點著色器和片元著色器之間的資料傳遞和插值計算。
?discard
捨棄片元,可用於光柵化中的剔除操作
WebGL
WebGL是一個JavaScript API,它允許我們直接在瀏覽器中實現互動式3D圖形。WebGL API多數與GPU硬體相關,GPU硬體(渲染管線)=>顯示卡驅動=>作業系統=>瀏覽器=>WebGL API為上一層提供介面,開發者呼叫WebGL API控制圖形處理單元
Canvas物件方法.getContext('2d')可以在二維畫布上繪製影像。執行canvas.getContext('webgl');返回物件具有一系列繪製渲染三維場景的方法,也就是WebGL API。
幾乎整個WebGL API都是關於如何設定著色器的狀態值以及執行它們。
對於想要繪製的每一個物件,都需要先設定一系列狀態值,然後通過呼叫 gl.drawArrays 或 gl.drawElements 執行一個著色方法對(一個著色程式),使得你的著色器對能夠在GPU上執行。
這邊不詳述WebGL的各個API,只通過下述例子簡單實踐管線渲染的流程,從而更好的理解前文內容,更多的請自行查閱官方文件
實際上學習WebGL是為了學習Web3D知識,而通過原生WebGL直接編寫程式,會比較麻煩,實際開發中一般會使用Three.js這樣或那樣的庫
2<!DOCTYPE html>
3<html lang="en">
4 <head>
5 <meta charset="UTF-8" />
6 <title>WebGL</title>
7 </head>
8 <body>
9 <!--canvas標籤建立一個寬高均為500畫素,背景為藍色的矩形畫布-->
10 <canvas id="c"></canvas>
11 <script>
12 // js (內建vue3(Vue),jQuery($))
13 var canvas = document.querySelector("#c");
14 var gl = canvas.getContext("webgl");
15
16 // 著色器程式碼
17 var vertexShaderSource = `
18 attribute vec4 a_position;
19 void main() {
20 gl_Position = a_position;
21 }
22 `;
23 var fragmentShaderSource = `
24 precision mediump float;
25 void main() {
26 gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple
27 }
28 `;
29
30 //建立頂點著色器物件
31 var vertexShader = gl.createShader(gl.VERTEX_SHADER);
32 //建立片元著色器物件
33 var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
34 //引入頂點、片元著色器原始碼
35 gl.shaderSource(vertexShader,vertexShaderSource);
36 gl.shaderSource(fragmentShader,fragmentShaderSource);
37 //編譯頂點、片元著色器
38 gl.compileShader(vertexShader);
39 gl.compileShader(fragmentShader);
40 //建立程式物件program
41 var program = gl.createProgram();
42 //附著頂點著色器和片元著色器到program
43 gl.attachShader(program,vertexShader);
44 gl.attachShader(program,fragmentShader);
45 //連結program
46 gl.linkProgram(program);
47 //使用program
48 gl.useProgram(program);
49
50 // 從建立的著色程式中找到屬性【a_position】值所在的位置
51 var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
52 // 屬性值從緩衝中獲取資料,所以我們建立一個緩衝
53 var positionBuffer = gl.createBuffer();
54 // 繫結一個資料來源到繫結點,然後可以引用繫結點指向該資料來源
55 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
56 // 建立頂點資料
57 var positions = [
58 0, 0,
59 0, 0.5,
60 0.7, 0,
61 ];
62 // 通過繫結點向緩衝中存放資料
63 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
64
65 // 設定背景色
66 gl.clearColor(0, 0, 0, 1);
67 gl.clear(gl.COLOR_BUFFER_BIT);
68
69 // 允許頂點著色器讀取GPU(伺服器端)資料
70 gl.enableVertexAttribArray(positionAttributeLocation);
71 // 需要告訴WebGL怎麼從我們之前準備的緩衝中獲取資料給著色器中的屬性
72 var size = 2;
73 var type = gl.FLOAT;
74 var normalize = false;
75 var stride = 0;
76 var offset = 0;
77 gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
78
79 // 設定primitiveType(圖元型別)為 gl.TRIANGLES(三角形)
80 var primitiveType = gl.TRIANGLES;
81 // 著色器將執行三次
82 var count = 3;
83 gl.drawArrays(primitiveType, 0, count);
84 </script>
85 </body>
86</html>