Chapter 09

GLSL 着色器入门

打开 GPU 编程的大门——用 GLSL 编写自定义顶点变形和片段着色,实现任何材质无法达到的独特视觉效果

1. 着色器管线概述

WebGL 的渲染管线中,有两个可编程阶段——顶点着色器和片段着色器,这两个程序用 GLSL(OpenGL Shading Language)编写并运行在 GPU 上:

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

5. Three.js ShaderMaterial 内置变量

使用 ShaderMaterial(非 Raw)时,Three.js 自动注入以下变量:

变量类型含义
positionattribute vec3顶点位置(模型空间)
normalattribute vec3顶点法线(模型空间)
uvattribute vec2第一套 UV 坐标
modelMatrixuniform mat4模型变换矩阵
viewMatrixuniform mat4视图矩阵
projectionMatrixuniform mat4投影矩阵
modelViewMatrixuniform mat4modelMatrix * viewMatrix
normalMatrixuniform mat3用于变换法线的矩阵
cameraPositionuniform 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 连接两个着色器阶段。波浪/噪声/渐变是最常用的入门效果。