本文整理自 div 俠於 凹凸 2022 年技術分享,簡單介紹了 WebGL 畫一個基礎圖形的流程,希望你瞭解之後,在使用 3d 渲染庫的時候可以少點迷糊。 四種常用的頁面繪圖工具 關於h5頁面的圖形繪製,我們大多談及的是這四種工具:html+css,svg、canvas2d、webgl。 html ...
本文整理自 div 俠於 凹凸 2022 年技術分享,簡單介紹了 WebGL 畫一個基礎圖形的流程,希望你瞭解之後,在使用 3d 渲染庫的時候可以少點迷糊。
四種常用的頁面繪圖工具
關於h5頁面的圖形繪製,我們大多談及的是這四種工具:html+css,svg、canvas2d、webgl。
html+css 是最常見的繪圖工具了,使用 css 繪圖跟平時寫頁面佈局一樣,在製作圖表的時候,我們可以用 css 把圖表的樣式定義好,其他的,就是根據數據的不同 ,給元素添加上不同的屬性。這樣的開發對於圖表元素簡單、數據結點少的場景非常友好。不僅可以減少開發的工具量,而且不用引入多餘的代碼庫。但是,隨時需要繪製的圖形越來越多, css 代碼做變得越來越複雜,加上 css 本來沒有邏輯語義,代碼會變得不易閱讀和維護。
svg 是可縮放矢量圖形,他跟 html , css 的結合很緊密,可以把 svg 當做 img 的 src ,也可以用 css 操控 svg 的屬性, svg 和 html 都是文本標記語言, svg 較 html 增加了對非線性圖形的支持,包括圓弧,貝塞爾曲線等。同時, svg 支持
canvas2D 是 canvas 的 2d 繪圖上下文,他提供了一系列方法,用於對 canvas 區域的圖像進行修改和繪製,相比於前兩者的開箱即用, canvas2d 很多圖形和顏色都需要自己實現和封裝使得這個工具上手的難度大了不少,但是,如果把這些基礎的事情做好,你將擁有一個功能完全覆蓋前面兩個工具,而且便於擴展的繪圖工具。
webGL 也是 canvas 的繪圖上下文,是 opengl es 的 web 實現。最大的特點,就是更低層,可以直接使用 gpu 的並行能力。在處理圖形數量非常多,像素級處理和 3d 物體的場景下,擁有很高的性能優勢。
四種工具的選擇思路
當我們拿到一個繪圖需求的時候,應該先看看這個需求用到的圖形是不是比較少,而且簡單。如果是的話,可以直接選擇 css 進行快速開發。如果圖形雖然簡單但比較多,或者圖形有一些曲線需求,這個時候 svg 還可以快速應付。如果圖形之間的結構複雜,數量比較多的時候選擇 canvas2d 。而當圖形的數量級大到一定的量,或者需要對每一個像素進行處理,或者需要大量的 3d 展示的時候,我們得使用 webgl 了
webgl的hello world
webgl 的 hello world 不像其他工具一樣可以一兩行代碼就搞定,而是足足有四十多行代碼。雖然這串代碼在各個 3d 渲染庫里都有對應封裝的方法,基本不用我們自己徒手去寫,但是學習這串代碼可以讓我們對 webgl 繪圖過程有一個最基礎的瞭解。
webgl 繪圖一共有五個步驟:
- 創建 webgl 繪圖上下文
- 創建著色器編程,關聯到 gl 上下文中 (跟第3步並行)
- 創建數據,放入緩衝區並把緩衝區關聯到 gl 止下文中(跟第2步並行)
- gpu載入緩存中的數據
- 繪製圖形
創建Webgl上下文
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
創建著色器程式
const program = gl.createProgram();
gl.attachShader(program, /*某個著色器(下文的vertexShader)*/);
gl.linkProgram(program);
gl.useProgram(program);
著色器是一段給 gpu 運行的程式,我們用 glCreateProgram 創建一個空的程式對象,然後使用 glAttachShader 給這個程式對象填充編譯後的著色器代碼。著色器是什麼,怎麼編譯後面再說,這裡可以把他當成某一個函數編譯後的代碼。把幾個這種編譯後的函數放入程式對象後, gpu 執行這個程式對象,就會把像素信息當做入參,依次執行程式對象中的函數。
填充完著色器代碼後,調用 glLinkProgram 把程式關聯到 gl 上下文中,並用 glUseProgram 來啟用這個程式。
接下來,來看一下著色器代碼怎麼搞出來。
const vertex = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 1.0, 1.0);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);
首先我們定義了一個變數 vertex 並給他賦值一串其他語言格式的代碼字元串,這個串代碼是 glsl 代碼,是一個跟 c 語言很相似的代碼。代碼接收一個傳入的二維向量 position ,然後把他執行環境中的全局變數 gl_Position 設置成一個四維向量,這個四維向量前兩個維度的分量是傳入的二維向量。
接下來用 glCreateShader 創建一個著色器, VERTEX_SHADER 常量說明這個著色器是一個頂點著色器,跟頂點著色器對應的是片元著色器,頂點著色器處理做為確定點的位置。片元著色器則對頂點構成的圖形中的所有位置進行逐個處理,比如兩點畫一個直線,兩點是頂點著色器確定的,直線是片元著色器在確定了兩個點的位置之後畫的。
在我們創建了一個空的頂點著色器對象 vertexShader 之後,就可以用 glShaderSource 把前面的字元串代碼放入頂點著色器對象中,然後用 glCompileShader 把這段代碼編譯成可執行文件。這個過程跟c語言的編譯過程是相似的。
gl.attachShader(program, /*某個著色器(下文的vertexShader)*/);
gl.attachShader(program, vertexShader);
完成這一步之後,就要回到上面寫註釋那裡,把著色器對象關聯到程式對象里。當然,你還得去寫一個片元著色器,用同樣的步驟把一個片元著色器也關聯到程式對象里。
將數據存入緩衝區
const points = new Float32Array([-1, -1, 0, 1, 1, -1]);
const bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
經過上文的操作之後,我們已經有了一個裝載著著色器代碼的程式對象,這個對象放到 gl 繪圖上下文中被啟用了。接下來,我們要定義的就是給這個程式用的數據。
在頂點著色器那一塊,代碼裡面接受一個傳入的二維向量,就是我們現在要定義的。首先定義一個類型化數組,初始化的時候放入6個數,這個6個數後面會被繪圖程式分成三組放到三次頂點著色器調用中。另外,使用類型化數組是為了優化性能,讓大量數據的情況下,數據占用的空間更小。
有了數據之後,調用 glCreateBuffer 創建一個緩衝區對象,用 glBindBuffer 把這個對象跟 gl 繪圖上下文關聯起來,最後調用 glBufferData 把 points 的數據放入緩衝區中。
gpu載入緩存中的數據
const vPosition = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);
在這一步中,我們先調用 glGetAttribLocation 拿到程式對象中 position 這個變數的位置,調用 glVertexAttribPointer 把這個變數的長度設置為 2 ,類型設置成 glFLOAT ,並用 glEnableVertexAttribArray 啟用這個變數
繪製圖形
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);
到了最後一步,只要用 glClear 把顏色緩衝區清空,然後用 glDrawArrays 進行繪圖就行了。其中 gl.TRIANGLES 確定了片元著色器的繪圖範圍,當這個值是 gl.POINTS ,著色器會把點兩兩連接,而 gl.TRIANGLES 讓第三個點成一組繪製三角形
這樣, webgl 的一個hello world就完成了,上面的三角形就是這40行代碼輸出的圖像。
總結
這段程式在 three.js 和其他的 3d 框架和工具庫里都有一定的封裝,通過那些庫進行 webgl 的繪圖相對來說會方便很多,但如果不知道這些庫最根本的操作,就很容易在遇到問題的時候繞進去。所以希望本文能增加大家對 web 3d 底層方面的理解,給大家在學習這些3d工具庫的時候提供一些幫助。
參考資料
GPU與渲染管線:如何用WebGL繪製最簡單的幾何圖形?
歡迎關註凹凸實驗室博客:aotu.io
或者關註凹凸實驗室公眾號(AOTULabs),不定時推送文章: