Skip to main content

着色器 (Shader) 与底层渲染

在高级 Web3D 面试中,能否手写着色器(Shader)是区分“API 调用工程师”与“真正的图形开发者”的分水岭。

WebGL 渲染管线

理解 Shader 必须先理解 GPU 的渲染管线。GPU 处理数据的流水线大致如下:

  1. 顶点数据准备:将模型的顶点坐标、法线、UV 坐标等送入显存(Buffer)。
  2. 顶点着色器 (Vertex Shader):对每一个顶点执行一次计算。主要任务是坐标变换,把顶点从“模型局部坐标系”转换为“屏幕裁剪空间坐标系”。
  3. 图元装配与光栅化 (Rasterization):将顶点连成三角形,并将三角形内部转化为屏幕上的一个个像素片段(Fragment)。
  4. 片元着色器 (Fragment Shader):对每一个像素片段执行一次计算。主要任务是计算颜色,根据光照、材质、纹理贴图等计算出该像素最终输出到屏幕上的 RGBA 颜色值。

为什么需要自定义 Shader?

Three.js 提供了如 MeshStandardMaterialMeshPhysicalMaterial 等极其完善的物理材质,但有些特效是内置材质无法做到的:

  • 特效材质:如流动的岩浆、全息扫描光效、水波纹、溶解消失效果。
  • GPGPU (通用 GPU 计算):利用 GPU 大规模并行计算的能力,把数据伪装成像素存进纹理,在片元着色器里进行物理模拟计算(如几百万个粒子的流体碰撞模拟、鸟群/鱼群的群体行为模拟)。
  • 性能优化:将原本需要在 CPU 中进行的骨骼动画计算、海量实例状态更新,转移到顶点着色器中执行。

GLSL 基础

GLSL(OpenGL Shading Language)是一门类 C 的语言,运行在 GPU 上。面试要点:

数据类型

  • 标量:floatintbool
  • 向量: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 中,attributeinvaryingout(顶点)/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、法线 normaluv),降低编写难度。
  • RawShaderMaterial:完全不注入任何额外变量,需要自己声明所有 attribute/uniform 和 precision,提供最干净、最原生的 WebGL 体验,适合将从 ShaderToy 抄来的原生代码直接迁移。

原生 WebGL 的着色器编译、链接与数据绑定流程见 WebGL 基础