1. 着色器管线概述
WebGL 的渲染管线中,有两个可编程阶段——顶点着色器和片段着色器,这两个程序用 GLSL(OpenGL Shading Language)编写并运行在 GPU 上:
- 顶点着色器(Vertex Shader) 对几何体的每个顶点运行一次,负责将 3D 顶点位置变换为裁剪空间坐标(设置 gl_Position)。可以在这里做顶点偏移、波浪变形等几何效果。
- 片段着色器(Fragment Shader) 对每个被三角形覆盖的像素(片段)运行一次,负责计算该像素的最终颜色(设置 gl_FragColor)。纹理采样、光照计算、特效都在这里实现。
- 光栅化(Rasterization) 顶点着色器和片段着色器之间的固定管线阶段,将三角形转换为屏幕上的像素片段,并插值传递 varying 变量。
2. GLSL 基础语法
// GLSL 是强类型语言,类似 C
// 基础类型
float x = 1.0; // 必须有小数点!
int n = 5;
bool b = true;
// 向量类型(GPU 的核心)
vec2 uv = vec2(0.5, 0.5); // 2D UV 坐标
vec3 pos = vec3(1.0, 2.0, 3.0); // xyz 位置
vec4 col = vec4(1.0, 0.0, 0.0, 1.0); // rgba 颜色
// 向量分量访问(Swizzling)
vec3 rgb = col.rgb; // 取前3分量
float r = col.r; // 也可以用 .x .y .z .w
vec3 zzz = pos.zzz; // 可重复
// 矩阵
mat4 mvpMatrix;
mat3 normalMatrix;
// 内置函数
sin(x); cos(x); abs(x); floor(x); ceil(x);
min(a, b); max(a, b); clamp(x, 0.0, 1.0);
mix(a, b, t); // 线性插值:a*(1-t) + b*t
smoothstep(edge0, edge1, x); // 平滑阶跃函数
length(v); normalize(v); dot(a, b); cross(a, b);
3. ShaderMaterial / RawShaderMaterial
Three.js 提供两种自定义着色器材质:ShaderMaterial 会自动注入 Three.js 的内置 uniform 变量和 attribute;RawShaderMaterial 完全由你控制,更底层。
const material = new THREE.ShaderMaterial({
// uniforms — JS 传递给 GLSL 的变量
uniforms: {
uTime: { value: 0 },
uColor: { value: new THREE.Color(0x049EF4) },
uTex: { value: someTexture }
},
// 顶点着色器(GLSL 字符串)
vertexShader: `
uniform float uTime;
varying vec2 vUv; // 传递给片段着色器的变量
void main() {
vUv = uv; // Three.js 内置 attribute
// 顶点位移(波浪效果)
vec3 pos = position;
pos.z += sin(pos.x * 3.0 + uTime) * 0.1;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
// 片段着色器
fragmentShader: `
uniform vec3 uColor;
varying vec2 vUv;
void main() {
// 用 UV 坐标生成渐变颜色
vec3 color = mix(uColor, vec3(1.0), vUv.y);
gl_FragColor = vec4(color, 1.0);
}
`
});
// 每帧更新 uniform
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
material.uniforms.uTime.value = clock.getElapsedTime();
renderer.render(scene, camera);
}
4. uniforms 与 varying
- uniform 从 JavaScript 传入 GLSL 的只读变量,对同一帧的所有顶点/片段值相同("统一"的)。用于传递时间、颜色、纹理、矩阵等。
- attribute 顶点着色器专用,每个顶点有独立值(如位置、法线、UV)。BufferGeometry 的 setAttribute 就是在设置 attribute。只能在顶点着色器中访问。
- varying 顶点着色器到片段着色器的数据传递桥梁。顶点着色器设置值,片段着色器读取值,光栅化时会在三角形内自动插值。
5. Three.js ShaderMaterial 内置变量
使用 ShaderMaterial(非 Raw)时,Three.js 自动注入以下变量:
| 变量 | 类型 | 含义 |
|---|---|---|
position | attribute vec3 | 顶点位置(模型空间) |
normal | attribute vec3 | 顶点法线(模型空间) |
uv | attribute vec2 | 第一套 UV 坐标 |
modelMatrix | uniform mat4 | 模型变换矩阵 |
viewMatrix | uniform mat4 | 视图矩阵 |
projectionMatrix | uniform mat4 | 投影矩阵 |
modelViewMatrix | uniform mat4 | modelMatrix * viewMatrix |
normalMatrix | uniform mat3 | 用于变换法线的矩阵 |
cameraPosition | uniform vec3 | 相机世界坐标 |
6. 实战:水波着色器
const waterMat = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uWaveHeight: { value: 0.15 },
uDeepColor: { value: new THREE.Color(0x006994) },
uShallowColor: { value: new THREE.Color(0x00d4ff) }
},
vertexShader: `
uniform float uTime;
uniform float uWaveHeight;
varying vec2 vUv;
varying float vElevation;
void main() {
vUv = uv;
// 叠加多个不同频率/方向的波
float wave1 = sin(position.x * 3.0 + uTime * 1.5) *
cos(position.z * 2.0 + uTime * 0.8);
float wave2 = sin(position.x * 5.0 - uTime * 2.0) *
sin(position.z * 4.0 + uTime * 1.2) * 0.5;
float elevation = (wave1 + wave2) * uWaveHeight;
vElevation = elevation; // 传给片段着色器
vec3 pos = position;
pos.y += elevation;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
uniform vec3 uDeepColor;
uniform vec3 uShallowColor;
varying vec2 vUv;
varying float vElevation;
void main() {
// 根据高度混合深浅水颜色
float mixFactor = (vElevation + 0.15) / 0.3;
mixFactor = clamp(mixFactor, 0.0, 1.0);
vec3 color = mix(uDeepColor, uShallowColor, mixFactor);
// 边缘半透明
float alpha = 0.85;
gl_FragColor = vec4(color, alpha);
}
`,
transparent: true,
side: THREE.DoubleSide
});
const water = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10, 128, 128), // 高分段才能显示波形细节
waterMat
);
water.rotation.x = -Math.PI / 2;
7. 实战:噪声渐变着色器
// 实现噪声渐变效果(无需外部库)
const noiseMat = new THREE.ShaderMaterial({
uniforms: { uTime: { value: 0 } },
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float uTime;
varying vec2 vUv;
// 伪随机函数
float random(vec2 st) {
return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
}
// 值噪声
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f); // 平滑插值
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
void main() {
vec2 uv = vUv * 4.0;
// 多层噪声叠加(FBM)
float n = noise(uv + uTime * 0.2) * 0.5
+ noise(uv * 2.0 - uTime * 0.3) * 0.25
+ noise(uv * 4.0 + uTime * 0.1) * 0.125;
// 映射到蓝紫渐变色
vec3 colorA = vec3(0.02, 0.04, 0.3); // 深蓝
vec3 colorB = vec3(0.4, 0.1, 0.8); // 紫
vec3 colorC = vec3(0.02, 0.6, 0.95); // 青蓝
vec3 color = mix(mix(colorA, colorB, n), colorC, n * n);
gl_FragColor = vec4(color, 1.0);
}
`
});
GLSL 学习资源:
• The Book of Shaders(thebookofshaders.com)— 最好的 GLSL 交互式教程
• Shadertoy(shadertoy.com)— 在线 GLSL 着色器社区,大量可参考的效果
• GLSL Sandbox(glslsandbox.com)— 快速原型测试着色器
本章小结:GLSL 着色器是突破 Three.js 内置材质限制的关键——任何独特视觉效果都能通过着色器实现。核心要点:顶点着色器处理几何变形,片段着色器控制颜色;uniforms 传递 JS 数据;varying 连接两个着色器阶段。波浪/噪声/渐变是最常用的入门效果。