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);

// 清空画布
gl.clear(gl.COLOR_BUFFER_BIT);

在这段代码中,我们做了几件事情:

  1. 我们获取了 HTML canvas 元素的引用。
  2. 我们获取了 WebGL 渲染上下文。
  3. 我们设置了清空颜色(在这个例子中,是黑色)。
  4. 我们用指定的颜色清空了画布。

这可能看起来不多,但恭喜你!你刚刚创建了你的第一个 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