WebGL 的 Hello World

本文整理自 div 俠於 凹凸 2022 年技術分享,簡單介紹了 WebGL 畫一個基礎圖形的流程,希望你瞭解之後,在使用 3d 渲染庫的時候可以少點迷糊。

四種常用的頁面繪圖工具

關於h5頁面的圖形繪製,我們大多談及的是這四種工具:html+css,svg、canvas2d、webgl。

image-20220321180951763

html+css 是最常見的繪圖工具了,使用 css 繪圖跟平時寫頁面佈局一樣,在製作圖表的時候,我們可以用 css 把圖表的樣式定義好,其他的,就是根據數據的不同 ,給元素添加上不同的屬性。這樣的開發對於圖表元素簡單、數據結點少的場景非常友好。不僅可以減少開發的工具量,而且不用引入多餘的代碼庫。但是,隨時需要繪製的圖形越來越多, css 代碼做變得越來越複雜,加上 css 本來沒有邏輯語義,代碼會變得不易閱讀和維護。

svg 是可縮放矢量圖形,他跟 html , css 的結合很緊密,可以把 svg 當做 img 的 src ,也可以用 css 操控 svg 的屬性, svg 和 html 都是文本標記語言, svg 較 html 增加了對非線性圖形的支持,包括圓弧,貝塞爾曲線等。同時, svg 支持, 等複用類的語法,這讓他就算繪製很多圖形,代碼也保留一定可讀性。不過 svg 也有一些缺點。因爲一個圖形都是一個元素結點。在數據多的時候,頁面刷新引起的佈局、渲染計算的開銷就會非常大。而且,完整的 svg 把結構,樣式,複用邏輯都放在一起,跟 html + css + js 這種三者分開的模式比總少了一些整潔。

canvas2D 是 canvas 的 2d 繪圖上下文,他提供了一系列方法,用於對 canvas 區域的圖像進行修改和繪製,相比於前兩者的開箱即用, canvas2d 很多圖形和顏色都需要自己實現和封裝使得這個工具上手的難度大了不少,但是,如果把這些基礎的事情做好,你將擁有一個功能完全覆蓋前面兩個工具,而且便於擴展的繪圖工具。

webGL 也是 canvas 的繪圖上下文,是 opengl es 的 web 實現。最大的特點,就是更低層,可以直接使用 gpu 的並行能力。在處理圖形數量非常多,像素級處理和 3d 物體的場景下,擁有很高的性能優勢。

四種工具的選擇思路

image-20220321181126634

當我們拿到一個繪圖需求的時候,應該先看看這個需求用到的圖形是不是比較少,而且簡單。如果是的話,可以直接選擇 css 進行快速開發。如果圖形雖然簡單但比較多,或者圖形有一些曲線需求,這個時候 svg 還可以快速應付。如果圖形之間的結構複雜,數量比較多的時候選擇 canvas2d 。而當圖形的數量級大到一定的量,或者需要對每一個像素進行處理,或者需要大量的 3d 展示的時候,我們得使用 webgl 了

image-20220321181247016

webgl的hello world

webgl 的 hello world 不像其他工具一樣可以一兩行代碼就搞定,而是足足有四十多行代碼。雖然這串代碼在各個 3d 渲染庫裏都有對應封裝的方法,基本不用我們自己徒手去寫,但是學習這串代碼可以讓我們對 webgl 繪圖過程有一個最基礎的瞭解。

webgl 繪圖一共有五個步驟:

  1. 創建 webgl 繪圖上下文
  2. 創建着色器編程,關聯到 gl 上下文中 (跟第3步並行)
  3. 創建數據,放入緩衝區並把緩衝區關聯到 gl 止下文中(跟第2步並行)
  4. gpu加載緩存中的數據
  5. 繪製圖形

創建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);

完成這一步之後,就要回到上面寫註釋那裏,把着色器對象關聯到程序對象裏。當然,你還得去寫一個片元着色器,用同樣的步驟把一個片元着色器也關聯到程序對象裏。

image-20220321181429987

將數據存入緩衝區

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 讓第三個點成一組繪製三角形

image-20220321181511519

這樣, webgl 的一個hello world就完成了,上面的三角形就是這40行代碼輸出的圖像。

總結

這段程序在 three.js 和其他的 3d 框架和工具庫裏都有一定的封裝,通過那些庫進行 webgl 的繪圖相對來說會方便很多,但如果不知道這些庫最根本的操作,就很容易在遇到問題的時候繞進去。所以希望本文能增加大家對 web 3d 底層方面的理解,給大家在學習這些3d工具庫的時候提供一些幫助。

參考資料

GPU與渲染管線:如何用WebGL繪製最簡單的幾何圖形?

歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公衆號(AOTULabs),不定時推送文章:

歡迎關注凹凸實驗室公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章