着色器 (Shader) 与底层渲染
在高级 Web3D 面试中,能否手写着色器(Shader)是区分“API 调用工程师”与“真正的图形开发者”的分水岭。
WebGL 渲染管线
理解 Shader 必须先理解 GPU 的渲染管线。GPU 处理数据的流水线大致如下:
- 顶点数据准备:将模型的顶点坐标、法线、UV 坐标等送入显存(Buffer)。
- 顶点着色器 (Vertex Shader):对每一个顶点执行一次计算。主要任务是坐标变换,把顶点从“模型局部坐标系”转换为“屏幕裁剪空间坐标系”。
- 图元装配与光栅化 (Rasterization):将顶点连成三角形,并将三角形内部转化为屏幕上的一个个像素片段(Fragment)。
- 片元着色器 (Fragment Shader):对每一个像素片段执行一次计算。主要任务是计算颜色,根据光照、材质、纹理贴图等计算出该像素最终输出到屏幕上的 RGBA 颜色值。
为什么需要自定义 Shader?
Three.js 提供了如 MeshStandardMaterial、MeshPhysicalMaterial 等极其完善的物理材质,但有些特效是内置材质无法做到的:
- 特效材质:如流动的岩浆、全息扫描光效、水波纹、溶解消失效果。
- GPGPU (通用 GPU 计算):利用 GPU 大规模并行计算的能力,把数据伪装成像素存进纹理,在片元着色器里进行物理模拟计算(如几百万个粒子的流体碰撞模拟、鸟群/鱼群的群体行为模拟)。
- 性能优化:将原本需要在 CPU 中进行的骨骼动画计算、海量实例状态更新,转移到顶点着色器中执行。
GLSL 基础
GLSL(OpenGL Shading Language)是一门类 C 的语言,运行在 GPU 上。面试要点:
数据类型
- 标量:
float、int、bool - 向量:
vec2/vec3/vec4(浮点)、ivec*、bvec*。可用.xyzw/.rgba/.stpq取分量,支持「混合(swizzle)」如color.bgr。 - 矩阵:
mat2/mat3/mat4 - 采样器:
sampler2D(纹理)、samplerCube
变量限定符(高频考点)
面试题:attribute、uniform、varying 的区别?
| 限定符 | 作用 | 所在着色器 |
|---|---|---|
attribute | 逐顶点输入(顶点坐标、法线、UV),每个顶点不同 | 仅顶点着色器 |
uniform | 全局常量,一次 draw call 内所有顶点/片元相同(如 MVP 矩阵、时间、颜色) | 顶点 + 片元 |
varying | 顶点着色器输出 → 片元着色器输入,会在三角形内部自动插值 | 顶点写、片元读 |
WebGL2 / GLSL ES 3.0 中,
attribute→in,varying→out(顶点)/in(片元)。
内置变量
- 顶点着色器输出:
gl_Position(裁剪空间坐标,必须赋值)、gl_PointSize(点的大小) - 片元着色器输出:
gl_FragColor(最终像素颜色,GLSL ES 1.0)
ShaderMaterial 实战
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uColor: { value: new THREE.Color(0xff0000) },
},
// 顶点着色器:计算位置,把 uv 传给片元
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
// projectionMatrix / modelViewMatrix / position 由 three 自动注入
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
// 片元着色器:根据 uv 和时间算颜色,做条纹流动效果
fragmentShader: `
uniform float uTime;
uniform vec3 uColor;
varying vec2 vUv;
void main() {
float strength = sin(vUv.x * 10.0 + uTime) * 0.5 + 0.5;
gl_FragColor = vec4(uColor * strength, 1.0);
}
`,
});
// 动画循环里更新时间 uniform
const clock = new THREE.Clock();
function animate() {
material.uniforms.uTime.value = clock.getElapsedTime();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
RawShaderMaterial 与 ShaderMaterial
在 Three.js 中写自定义 Shader,通常使用这两个类:
ShaderMaterial:Three.js 会自动帮你注入常用的 Uniforms 和 Attributes(如投影矩阵projectionMatrix、模型视图矩阵modelViewMatrix、顶点位置position、法线normal、uv),降低编写难度。RawShaderMaterial:完全不注入任何额外变量,需要自己声明所有 attribute/uniform 和precision,提供最干净、最原生的 WebGL 体验,适合将从 ShaderToy 抄来的原生代码直接迁移。
原生 WebGL 的着色器编译、链接与数据绑定流程见 WebGL 基础。