WebGL - 圖形管線

你好,有志青年程序員們!今天,我們將踏上一段令人振奮的旅程,探索 WebGL 圖形管線。別擔心你對編程還是新手——我會成為你友好的導遊,我們會一步步前進。在這個教程結束時,你將對 WebGL 如何將你的代碼轉化為屏幕上令人驚艷的視覺效果有堅實的理解。

WebGL - Graphics Pipeline

JavaScript:起點

在我們深入 WebGL 之前,讓我們從熟悉的東西開始——JavaScript。WebGL 是通過 JavaScript 來訪問的,這讓它成為我們冒險的完美起點。

你的第一個 WebGL 程式

讓我們從一個簡單的例子開始:

// 獲取 canvas 元素
const canvas = document.getElementById('myCanvas');

// 獲取 WebGL 組上下文
const gl = canvas.getContext('webgl');

// 設置清空顏色(背景顏色)
gl.clearColor(0.0, 0.0, 0.0, 1.0);

// 清空 canvas
gl.clear(gl.COLOR_BUFFER_BIT);

在這段代碼中,我們做了幾件事情:

  1. 我們獲取 HTML canvas 元素的引用。
  2. 我們獲取 WebGL 渲染上下文。
  3. 我們設置清空顏色(在這個例子中是黑色)。
  4. 我們使用我們指定的顏色清空 canvas。

這可能看起來沒有什麼,但恭喜你!你剛剛創建了你的第一個 WebGL 程式。這就像為一幅傑作準備一個空白的畫布。

頂點著色器:塑造你的世界

現在我們有了準備好的畫布,讓我們來谈谈頂點著色器。可以把頂點著色器看作是你 3D 世界中的雕塑家。它們處理由你的物件提供的原始數據——頂點。

一個簡單的頂點著色器

這裡是一個基本的頂點著色器的例子:

attribute vec4 a_position;

void main() {
gl_Position = a_position;
}

這個著色器做了一件簡單但關鍵的事情——它將每個頂点的位置賦值給 gl_Position。這就像告訴你 3D 物件的每個點,“你到這裡來!”

基元組裝:連接點

頂點著色器完成其工作後,WebGL 進行基元組裝。這個階段就像連接點遊戲——它將個別的頂點組合起來,弄清楚它們應該如何連接形成形狀。

舉例來說,如果你正在繪製一個三角形,基元組裝會取三個頂點並理解它們形成一個單一的三角形。

光栅化:到處都是像素

接下來是光栅化的魔法。這個階段將我們的 3D 形狀轉換成你屏幕上看到的 2D 像素。這就像拍攝一個 3D 雕塑的詳細照片。

片段著色器:為你的世界上色

片段著色器是真正發揮藝術性的地方。當頂點著色器處理物件的結構時,片段著色器則為它們上色。

一個簡單的片段著色器

這裡是一個將一切染成紅色的基本片段著色器:

precision mediump float;

void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

這個著色器將 gl_FragColor 設置為代表紅色的向量(紅色滿,綠色無,藍色無,不透明滿)。這就像將你的整個 3D 世界浸泡在紅色油漆中!

片段操作:最後的觸碰

片段著色器之後,WebGL 對片段進行各種操作。這包括深度測試(確定哪些物件在前),混合(透明物件如何相互作用的)等等。

帧緩存:終曲

最後,我們來到帧緩存。這裡是你的渲染圖像在显示在屏幕之前存储的地方。這就像後台區域,在最後的觸碰之後才揭開序幕。

準備一切

現在我們已經走過了每一個階段,讓我們看看它們如何在完整的 WebGL 程式中一起工作:

// 頂點著色器源代碼
const vsSource = `
attribute vec4 aVertexPosition;

void main() {
gl_Position = aVertexPosition;
}
`;

// 片段著色器源代碼
const fsSource = `
precision mediump float;

void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;

// 初始化著色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vsSource);
gl.compileShader(vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fsSource);
gl.compileShader(fragmentShader);

// 創建著色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

// 使用程序
gl.useProgram(shaderProgram);

// 創建緩存並傳送數據
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
1.0,  1.0,
-1.0,  1.0,
1.0, -1.0,
-1.0, -1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

// 告訴 WebGL 如何從位置緩存中提取頂點位置
const numComponents = 2;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(
gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(gl.getAttribLocation(shaderProgram, 'aVertexPosition'));

// 繪製場景
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

這個程式在黑色背景上創建了一個簡單的紅色正方形。讓我們分解一下:

  1. 我們定義了我們的頂點和片段著色器。
  2. 我們編譯這些著色器並將它們鏈接到一個程序中。
  3. 我們創建了一個緩存並將我們正方形的頂點位置數據傳送到其中。
  4. 我們告訴 WebGL 如何解釋這個緩存數據。
  5. 最後,我們清空屏幕並繪製我們的正方形。

每一步都對應到我們討論的图形管线的每一個階段。這就像觀察一條生產線,從原材料(頂點數據)轉化為成品(屏幕上的像素)。

方法表

這裡是我們使用過的關鍵 WebGL 方法的表格:

方法 描述
gl.createShader() 創建一個著色器對象
gl.shaderSource() 設置著色器的源代碼
gl.compileShader() 編譯一個著色器
gl.createProgram() 創建一個程序對象
gl.attachShader() 將一個著色器附加到一個程序
gl.linkProgram() 鏈接一個程序對象
gl.useProgram() 將指定的程序設置為當前渲染狀態的一部分
gl.createBuffer() 創建一個緩存對象
gl.bindBuffer() 將一個緩存對象绑定到一個目標
gl.bufferData() 創建並初始化緩存對象的數據存儲
gl.vertexAttribPointer() 指定如何從頂點數據中提取頂點
gl.enableVertexAttribArray() 启用一個頂點屬性數組
gl.clearColor() 指定清空顏色緩存時使用的顏色
gl.clear() 清空緩存到預設值
gl.drawArrays() 根據數組數據渲染基元

這就是全部了!我們已經穿越了 WebGL 圖形管線,從 JavaScript 到最後的帧緩存。記住,像任何技能一樣,精通 WebGL 需要練習。但是,你寫的每一行代碼都讓你更接近在網頁瀏覽器中創建令人驚艷的 3D 圖形。繼續實驗,繼續學習,最重要的是,玩得開心!

Credits: Image by storyset