WebGL - 圖形管線
你好,有志青年程序員們!今天,我們將踏上一段令人振奮的旅程,探索 WebGL 圖形管線。別擔心你對編程還是新手——我會成為你友好的導遊,我們會一步步前進。在這個教程結束時,你將對 WebGL 如何將你的代碼轉化為屏幕上令人驚艷的視覺效果有堅實的理解。
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);
在這段代碼中,我們做了幾件事情:
- 我們獲取 HTML canvas 元素的引用。
- 我們獲取 WebGL 渲染上下文。
- 我們設置清空顏色(在這個例子中是黑色)。
- 我們使用我們指定的顏色清空 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);
這個程式在黑色背景上創建了一個簡單的紅色正方形。讓我們分解一下:
- 我們定義了我們的頂點和片段著色器。
- 我們編譯這些著色器並將它們鏈接到一個程序中。
- 我們創建了一個緩存並將我們正方形的頂點位置數據傳送到其中。
- 我們告訴 WebGL 如何解釋這個緩存數據。
- 最後,我們清空屏幕並繪製我們的正方形。
每一步都對應到我們討論的图形管线的每一個階段。這就像觀察一條生產線,從原材料(頂點數據)轉化為成品(屏幕上的像素)。
方法表
這裡是我們使用過的關鍵 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