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);
// 清空画布
gl.clear(gl.COLOR_BUFFER_BIT);
在这段代码中,我们做了几件事情:
- 我们获取了 HTML canvas 元素的引用。
- 我们获取了 WebGL 渲染上下文。
- 我们设置了清空颜色(在这个例子中,是黑色)。
- 我们用指定的颜色清空了画布。
这可能看起来不多,但恭喜你!你刚刚创建了你的第一个 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