準備 WebGL Canvas


最近玩 3D 建模發現了 SculptGL,一個簡單、基於瀏覽器的雕塑應用程式,執行之流暢令人驚奇。

SculptGL 的底層是基於 WebGL,WebGL 則是基於OpenGL ES(OpenGL for Embedded Systems),這是 OpenGL(Open Graphics Library) 的嵌入式系統特化版本,至於 OpenGL 應該不用多做解釋了,玩 2D、3D 渲染的開發者,都知道的高效程式庫。

透過 WebGL,瀏覽器可以將大部份的繪圖渲染運算,送進專司影像運算的圖形處理器(Graphics Processing Unit,GPU),從而增加瀏覽器上的繪圖效率,若對 HTML5 Canvas API、SVG 繪圖等在效率上不滿意的話,可以試著使用基於 WebGL 的程式庫,像是 PixiJSThree.js 等,或者直接玩 WebGL。

WebGL 常被誤解為一套 JavaScript API,確實地,開發者可以透過 JavaScript 來處理 HTML 頁面、與使用者的互動等,並呼叫 WebGL 制訂的 JavaScript API 與著色器程式進行溝通,然而,撰寫著色器程式需要 GLSL(OpenGL Shading Language),這是一個類似 C 語言的程式語言。

開發者需要使用 GLSL 撰寫著色器(Shader),透過 JavaScript API 編譯、為 Canvas 繫結、建立著色器並送入 GPU,之後透過 JavaScript 將繪圖需要的資料透過 API 送給著色器程式來渲染,也就是說,想要認識 WebGL,對 GLSL 的認識也是不可少的。

圖像最後是在 Canvas 上渲染的,因此起手式就是準備一個 Canvas,就 WebGL 第一個範例來說,目標就設定在令 Canvas 使用整個瀏覽器視埠(viewport),背景為黑色,暫時不會涉及 GLSL 與著色器!

無論如何,先有個 Canvas 再說:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <title>Hello Canvas</title>      
    </head>
    <body>
        <canvas id="glCanvas" width="640" height="480">
    </body>
</html>

在這邊,Canvas 的大小是 640 x 480 對吧!?你指的是哪個大小?DOM 元素本身的尺寸還是顯示尺寸呢?因為這個 HTML 頁面沒有任何 CSS 設置,因此 DOM 元素本身尺寸是 640 x 480,顯示尺寸也會是 640 x 480,不過,若是使用了 CSS 來調整其寬高:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <title>Hello Canvas</title>      
        <style>
            #glCanvas {
                width: 1024px;
                height: 768px;
            }
        </style>
    </head>
    <body>
        <canvas id="glCanvas" width="640" height="480">
    </body>
</html>

這個 HTML 頁面中的 Canvas,像素是 640 x 480,然而顯示尺寸也會是 1024 x 768,這會如何?若在上頭繪圖,顯示的結果就會像是將一張 640 x 480 的點陣圖拉大兩倍,結果可能就會出現鋸齒狀的圖像。

也就是說,HTML 標籤的 widthheight 調整的 DOM 元素的尺寸,也可以透過 DOM 元素的 widthheight 特性取得或設定值;CSS 的 widthheight 調整的是顯示尺寸,可以透過 DOM 元素的 clientWidthclientHeight 特性取得或設定值,或者是透過 window.getComputedStyle 取得運算樣式(Computed style)。

如果會使用 CSS 來放大 Canvas 顯示尺寸,並希望不希望有鋸齒狀的顯示結果,可以在取得 Canvas 顯示尺寸之後,設定 DOM 元素的 widthheight 特性,例如,想要使用整個瀏覽器視埠的話,基本上可以這麼做:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <title>Hello Canvas</title>      
        <style>
            body {
                margin: 0;
                overflow: hidden;
            }        
            #glCanvas {
                width: 100vw;
                height: 100vh;
                display: block;
            }    
        </style>
    </head>
    <body>
        <canvas id="glCanvas">
        <script>
            const glCanvas = document.getElementById('glCanvas');
            glCanvas.width = glCanvas.clientWidth;
            glCanvas.height = glCanvas.clientHeight;
        </script>
    </body>
</html>

margin 設為 0,overflow 設為 hidden,而 Canvas 使用 CSS 設定整個視埠尺寸,這樣顯示上 Canvas 就會是整個視埠,接著用顯示大小來設定 DOM 元素大小。

接著,可以從 Canvas 取得支援 WebGL 的 Context 物件,這是透過 Canvas DOM 物件的 getContext 方法指定 Context 類型來取得,對於支援 WebGL 第一版的瀏覽器要傳入字串 'webgl',這系列的文件,若沒有特別提醒,討論的對象就是指 WebGL 第一版,對於 WebGL 第二版要傳入 'webgl2',如果不支援的話,getContext 傳回 null,如果支援的話,傳回 WebGLRenderingContext 實例。

第一次建立 WebGL 的 Context 物件時,視埠的大小與 Canvas 大小是一樣的,如果需要自行設定視埠大小,或者在 Canvas 改變大小時,重新設定視埠大小,可以使用 viewport 方法:

gl.viewport(0, 0, canvas.width, canvas.height);

想要設定背景為黑色,方式是設定清除色為黑,並用清除色來清除色彩緩衝區。

設定清除色可以使用 WebGLRenderingContextclearColor 方法,其前三個參數為 RGB 設定,最後一個參數為不透明度,都是接受 0.0 到 1.0 的值;清除色彩緩衝區的話,可使用 clear 方法傳入 WebGLRenderingContext 上的 COLOR_BUFFER_BIT 常數。

因此整個範例可以寫為:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <title>Hello Canvas</title>      
        <style>
            body {
                margin: 0;
                overflow: hidden;
            }        
            #glCanvas {
                width: 100vw;
                height: 100vh;
                display: block;
            }    
        </style>
    </head>
    <body>
        <canvas id="glCanvas">
        <script>
            const glCanvas = document.getElementById('glCanvas');
            glCanvas.width = glCanvas.clientWidth;
            glCanvas.height = glCanvas.clientHeight;

            const gl = glCanvas.getContext('webgl');
            if (!gl) {
                throw '無法初始化 WebGL,您的瀏覽器不支援';
            }


            gl.clearColor(0.0, 0.0, 0.0, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT);
        </script>
    </body>
</html>

按我觀看結果