banner
cos

cos

愿热情永存,愿热爱不灭,愿生活无憾
github
tg_channel
bilibili

青訓營 |「WebGL基礎」

本節課重點內容#

Why WebGL / Why GPU?#

  • WebGL 是什麼?
    • GPU ≠ WebGL ≠ 3D
  • WebGL 為什麼不像其他前端技術那麼簡單?

現代的圖像系統#

  • 光柵 (Raster):幾乎所有的現代圖形系統都是基於光柵來繪製圖形的,光柵就是指構成圖像的像素陣列
  • 像素 (Pixel)一個像素對應圖像上的一個點,它通常保存圖像上的某個具體位置的顏色等信息。
  • 幀緩衝 (Frame Buffer):在繪圖過程中,像素信息被存放於幀緩衝中,幀緩衝是一塊內存地址。
  • CPU (Central Processing Unit):中央處理單元,負責邏輯計算
  • GPU (Graphics Processing Unit):圖形處理單元,負責圖形計算

image.png

  • 如上圖,現代圖像的渲染如圖過程
  1. 輪廓提取 /meshing
  2. 光柵化
  3. 幀緩衝
  4. 渲染

The Pipeline#

image.png

GPU#

  • GPU 由大量的小運算單元構成
  • 每個運算單元只負責處理很簡單的計算
  • 每個運算單元彼此獨立
  • 因此所有計算可以並行處理

WebGL & OpenGL 關係#

OpenGL, OpenGL ES, WebGL, GLSL, GLSL ES API Tables (umich.edu)

image.png

WebGL 繪圖步驟#

步驟

  1. 創建 WebGL 上下文
  2. 創建 WebGL Program
  3. 將數據存入緩衝區
  4. 將緩衝區數據讀取到 GPU
  5. GPU 執行 WebGL 程序,輸出結果

image.png

如圖,針對幾個單詞進行解釋:

  • Raw Vertices & Primitives 原始頂點 & 原語
  • Vertex Processor 頂點著色器
  • 運算後送到 片元著色器 進行處理:Fragment Processor

創建 WebGL 上下文#

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
// 創建上下文, 注意兼容
function create3DContext(canvas, options) {
    const names = ['webgl', 'experimental-webgL','webkit-3d','moz-webgl'];  // 特性判斷
    if(options.webgl2) names.unshift(webgl2);
    let context = null;
    for(let ii = 0; ii < names.length; ++ii) {
        try {
            context = canvas.getContext(names[ii], options);
        } catch(e) {
            // no-empty
        }
        if(context) {
            break;
        }
    }
    return context;
}

創建 WebGL Program(The Shaders)#

  1. Vertex Shader(頂點著色器)

    通過類型數組 position,並行處理每個頂點的位置

    attribute vec2 position;// vec2 二維向量
    void main() {
        gl_PointSize = 1.0;
        gl_Position = vec4(position, 1.0, 1.0);
    }
    
  2. Fragment Shader(片元著色器)

    為頂點輪廓包圍的區域內所有像素進行著色

    precision mediump float;
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);//對應rgba(255,0,0,1.0),紅色
    }
    

其具體步驟如下:

  1. 創建頂點著色器和片元著色器代碼:

    // 頂點著色器程序代碼
    const vertexShaderCode = `
    attribute vec2 position;
    void main() {
        gl_PointSize = 1.0;
        gl_Position = vec4(position, 1.0, 1.0);
    }
    `;
    // 片元著色器程序代碼
    const fragmentShaderCode = `
    precision mediump float;
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
    `;
    
  2. 使用 createShader() 創建著色器對象

  3. 使用 shaderSource() 設置著色器的程序代碼

  4. 使用 compileShader() 編譯一個著色器

    // 頂點著色器
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertex);
    gl.compileShader(vertexShader);
    // 片元著色器
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragment);
    gl.compileShader(fragmentShader);
    
  5. 使用 **createProgram()** 創建 WebGLProgram 對象

  6. 使用 attachShader() WebGLProgram 添加一個片段或者頂點著色器。

  7. 使用 **linkProgram() ** 鏈接給定的WebGLProgram,從而完成為程序的片元和頂點著色器準備 GPU 代碼的過程。

  8. 使用 useProgram() 將定義好的WebGLProgram 對象添加到當前的渲染狀態

    // 創建著色器程序並鏈接
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    
    gl.useProgram(program);
    

將數據存到緩衝區中(Data to Frame Buffer)#

  • 坐標軸:webGL 的坐標系統是歸一化的,瀏覽器和 canvas2D的坐標系統是以左上角為坐標原點,y 軸向下,x 軸向右,坐標值相對於原點。而webGL的坐標系是以繪製畫布的中心點為原點正常的笛卡爾坐標系

通過一個頂點數組表示其頂點,使用 createBuffer() 創建並初始化一個用於儲存頂點數據或著色數據的WebGLBuffer對象並返回bufferId,然後使用 bindBuffer() 將給定的 bufferId 綁定到目標並返回,最後使用 **bufferData()**,將數據綁定至 buffer 中。

// 頂點數據
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);

讀取緩衝區數據到 GPU(Frame Buffer to GPU)#

const vPosition = gl.getAttribLocation(program, 'position'); // 獲取頂點著色器中的position變量的地址
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); // 給變量設置長度和類型
gl.enableVertexAttribArray(vPosition); // 激活這個變量

輸出結果(Output)#

Output

drawArrays() 從向量數組中繪製圖元

// output
gl.clear(gl.COLOR_BUFFER_BIT);  //清除緩衝的數據
gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);

image.png

WebGL 太複雜?其他方式#

canvas 2D#

看看人家 canvas2D,繪製同樣的三角形:

// canvas 簡單粗暴,都封裝好了
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(250, 0);
ctx.lineTo(500, 500);
ctx.lineTo(0, 500);
ctx.fillStyle = 'red';
ctx.fill();

Mesh.js#

mesh-js/mesh.js: A graphics system born for visualization 😘. (github.com)

const {Renderer, Figure2D, Mesh2D} = meshjs;
const canvas = document.querySelector ('canvas');
const renderer = new Renderer(canvas);

const figure = new Figure2D();
figurie.beginPath();
figure.moveTo(250, 0);
figure.lineTo(500500);
figure.lineTo(0, 500);
const mesh = new Mesh2D(figure, canvas);
mesh.setFill({
    color: [1, 0, 0, 1],
});
renderer.drawMeshes([mesh]);

Earcut#

使用Earcut進行三角剖分

const vertices = [
    [-0.7, 0.5],
    [-0.4, 0.3],
    [-0.25, 0.71],
    [-0.1, 0.56],
    [-0.1, 0.13],
    [0.4, 0.21],
    [0, -0.6],
    [-0.3, -0.3],
    [-0.6, -0.3],
    [-0.45, 0.0],
];
const points = vertices.flat();
const triangles = earcut(points)

3D Meshing#

由設計師導出給我們,再提取

SpriteJS/next - The next generation of spritejs.

圖形變換(Transforms)#

這就是數字圖像處理相關的知識了(學過的都還回來了.jpg)

平移#

image.png

旋轉#

image.png

縮放#

image.png

線性變換(旋轉 + 縮放)#

image.png

image.png

從線性變換到齊次矩陣

image.png

老師的又一栗子:Apply Transforms

3D Matrix#

3D 標準模型的四個齊次矩陣(mat4)

  1. 投影矩陣 Projection Matrix(正交投影和透視投影)
  2. 模型矩陣 Model Matrix (對頂點進行變換 Transform)
  3. 視圖矩陣 View Matrix(3D 的視角,想像成一個相機,在相機的視口下)
  4. 法向量矩陣 Normal Matrix(垂直於物體表面的法向量,通常用於計算物體光照)

Read more#

  1. The Book of Shaders (介紹片元著色器,非常好玩的)
  2. Mesh.js (底層庫,欸嘿)
  3. Glsl Doodle (片元著色器的一個輕量庫,有很多小 demo)
  4. SpriteJS (月影老師寫的開源庫 orz)
  5. Three.js(很多有意思的遊戲項目)
  6. Shadertoy BETA(很多有意思的項目)

總結感想#

這節課老師非常詳盡的講解了 WebGL 的繪圖及其相關庫,展示了很多有意思的 WebGL 小項目~

本文引用的大部分內容來自月影老師的課和 MDN!月影老師,tql!

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。