什么是着色器
着色器是用 GLSL 编写的程序,发送到 GPU。它们用于定位几何体的每个顶点并为几何体的每个可见像素着色。术语“像素”并不准确,因为渲染中的每个点并不一定与屏幕的每个像素相匹配,这就是为什么我们用“片段”这个词的原因。
顶点着色器
顶点着色器的目的是确定几何体的顶点位置。我们需要发送顶点位置、网格变换(如位置、旋转和缩放)、相机信息(如位置、旋转和视野)。然后,GPU 将遵循顶点着色器中的指令来处理所有这些信息,以便将顶点投影到一个 2D 空间,也就是我们的渲染器——换句话说,就是我们的屏幕。
在使用顶点着色器时,它的代码将应用于几何体的每个顶点。但是,像顶点位置这样的数据在每个顶点之间会发生变化。这种在顶点之间发生变化的数据被称为属性(attribute)。然而,有些数据在每个顶点之间并不需要改变,比如网格的位置。这种在顶点之间不发生变化的数据被称为统一变量(uniform)。
const material = new THREE.RawShaderMaterial({
vertexShader: `
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;
void main()
{
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
precision mediump float;
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
})
其中vertexShader就是使用到modelMatrix, viewMatrix, projectionMatrix来计算gl_Position,也就是投影位置;
- 模型矩阵(Model Matrix):模型矩阵表示物体在 3D 世界坐标系中的位置、旋转和缩放。模型矩阵将物体的本地坐标转换为世界坐标。
- 视图矩阵(View Matrix):视图矩阵定义了摄像机(或观察者)在 3D 世界中的位置和方向。视图矩阵将世界坐标转换为摄像机坐标,这个坐标系以摄像机为原点,以摄像机的方向为 Z 轴。
- 投影矩阵(Projection Matrix):投影矩阵定义了摄像机的投影类型(透视投影或正交投影)和视锥体的参数。投影矩阵将摄像机坐标系中的坐标投影到归一化设备坐标(NDC,Normalized Device Coordinates)空间,该空间的坐标范围为 [-1, 1],在 X、Y 和 Z 轴上。
在进行投影计算时,Three.js 首先将物体的顶点坐标乘以模型矩阵,将其从本地坐标系转换为世界坐标系。接着,乘以视图矩阵,将世界坐标转换为摄像机坐标。最后,乘以投影矩阵,将摄像机坐标投影到归一化设备坐标空间。
完成投影计算后,顶点坐标在归一化设备坐标空间中。接下来,Three.js 会将这些坐标映射到屏幕空间,这涉及到将归一化设备坐标转换为屏幕坐标。通常,这个过程包括将 X 和 Y 轴上的坐标乘以屏幕宽度和高度的一半,然后加上屏幕宽度和高度的一半,将坐标范围从 [-1, 1] 映射到屏幕像素范围。
gl_Position是一个在 OpenGL 和 WebGL 的顶点着色器(vertex shader)中使用的内置变量。它表示顶点在剪裁空间(clip space)中的位置。顶点着色器的主要任务是计算 3D 场景中每个顶点的最终位置,以便在屏幕上正确地显示 3D 图形。
在剪裁空间中,坐标值在 X、Y 和 Z 轴上都在范围 [-1, 1] 内。因此,gl_Position的范围是:
- X 轴:-1 到 1
- Y 轴:-1 到 1
- Z 轴:-1 到 1
- W 分量:通常为 1(用于齐次坐标)
这是一个四维向量,X、Y 和 Z 分量表示顶点在剪裁空间的位置,而 W 分量表示齐次坐标。在顶点着色器完成计算并输出 gl_Position 后,图形管线的下一阶段将执行剪裁和透视除法,将顶点从剪裁空间转换为归一化设备坐标(NDC)。
透视除法是通过将 gl_Position 的 X、Y 和 Z 分量分别除以 W 分量来完成的。在大多数情况下,W 分量等于 1,因此在透视除法之后,NDC 坐标的范围仍然是 [-1, 1]。不过,W 分量在某些情况下可能与 Z 分量有关,以实现透视投影的效果。在透视除法之后,顶点坐标会映射到屏幕空间,以生成最终的 2D 图像。
代码解释:
uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix; attribute vec3 position;
这里的position就是顶点的坐标,它是一个三维向量,然后代码中有三个四维矩阵,每个矩阵都将执行部分转换:
modelMatrix 将应用与 Mesh 相关的所有变换。如果我们缩放、旋转或移动 Mesh,这些变换将包含在 modelMatrix 中并应用于position。
viewMatrix 将应用与相机相关的变换。如果我们向左旋转相机,顶点应该在右边。如果我们朝着 Mesh 的方向移动相机,顶点应该变大等等。
projectionMatrix 最终将我们的坐标转换成最终的裁剪空间坐标。
要应用矩阵,我们将其相乘。如果我们想要将 mat4 应用于一个变量,这个变量必须是 vec4。我们还可以将矩阵与其他矩阵相乘:
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
实际上还可以简写,其中 viewMatrix 和 modelMatrix 被合并成一个 modelViewMatrix:
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
在这里的公式中,*
符号表示的是矩阵乘法,而不是叉积或点积。在 3D 图形渲染中,矩阵乘法用于将顶点从一个坐标系转换到另一个坐标系。例如,将顶点从模型空间转换到世界空间、从世界空间转换到摄像机空间,以及从摄像机空间投影到剪裁空间。
矩阵乘法在数学上有一些特殊的性质,例如不满足交换律。在 3D 图形中,矩阵乘法的顺序非常重要,因为它决定了变换的执行顺序。例如,要将顶点从模型空间投影到剪裁空间,需要按照以下顺序执行矩阵乘法:
顶点(剪裁空间) = 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点(模型空间)