{T}

着色器编程

着色器(Shader)是运行在 GPU 上的程序,可实现高度自定义的视觉效果。Three.js 提供了 ShaderMaterialRawShaderMaterial 两种材质用于自定义着色器开发。

系统架构

plaintext
┌─────────────────────────────────────────────────────────────┐
│                    着色器渲染管线                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐  │
│  │   JavaScript │    │  顶点着色器   │    │  片元着色器   │  │
│  │    应用层    │───▶│  Vertex      │───▶│  Fragment    │  │
│  │              │    │  Shader      │    │  Shader      │  │
│  └──────────────┘    └──────────────┘    └──────────────┘  │
│         │                   │                    │          │
│         ▼                   ▼                    ▼          │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐  │
│  │   Uniforms   │    │  Attributes  │    │   Varyings   │  │
│  │  全局变量    │    │  顶点属性    │    │  插值变量    │  │
│  └──────────────┘    └──────────────┘    └──────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
 
数据流向:
JavaScript → Uniforms/Attributes → 顶点着色器 → Varyings → 片元着色器 → 像素输出

核心组件说明

组件说明更新频率使用场景
Attributes顶点级别数据静态/偶尔更新位置、法线、UV、顶点颜色
Uniforms全局共享数据每帧更新时间、矩阵、颜色、纹理
Varyings着色器间传递自动插值传递计算结果到片元着色器

概述

着色器编程是 Three.js 高级开发的核心技能。顶点着色器(Vertex Shader)处理几何变换,片元着色器(Fragment Shader)处理像素颜色,通过组合两者可以实现各种复杂的视觉效果。

着色器类型

类型执行位置主要职责执行频率
顶点着色器GPU 顶点处理单元坐标变换、顶点动画每顶点一次
片元着色器GPU 片元处理单元颜色计算、纹理采样每像素一次
几何着色器GPU 几何处理单元图元生成(WebGL 2)每图元一次

GLSL 基础

GLSL(OpenGL Shading Language)是着色器的编程语言。

数据类型

glsl
// ==================== 基本类型 ====================
float f = 1.0;          // 浮点数(必须带小数点)
int i = 1;              // 整数
bool b = true;          // 布尔值
 
// ==================== 向量类型 ====================
vec2 v2 = vec2(1.0, 1.0);           // 二维向量
vec3 v3 = vec3(1.0, 1.0, 1.0);      // 三维向量(位置、颜色)
vec4 v4 = vec4(1.0, 1.0, 1.0, 1.0); // 四维向量(齐次坐标、RGBA)
 
// 特定向量类型
ivec2 iv2 = ivec2(1, 2);    // 整数向量
bvec2 bv2 = bvec2(true, false); // 布尔向量
 
// ==================== 矩阵类型 ====================
mat2 m2 = mat2(1.0, 0.0, 0.0, 1.0);  // 2x2 矩阵
mat3 m3 = mat3(1.0);                  // 3x3 单位矩阵
mat4 m4 = mat4(1.0);                  // 4x4 单位矩阵
 
// ==================== 访问向量分量 ====================
float x = v3.x;    // 单分量访问
float y = v3.y;
float z = v3.z;
 
// 多种访问方式(等效)
float r = v4.r;    // 颜色语义 (rgba)
float s = v2.s;    // 纹理语义 (stpq)
 
// Swizzling(重组)
vec3 rgb = v4.rgb;     // 提取 RGB
vec2 xy = v3.xy;       // 提取 XY
vec3 yzx = v3.yzx;     // 重排顺序
vec4 rgba = v4.abgr;   // 反转顺序
 
// ==================== 采样器类型 ====================
sampler2D tex;         // 2D 纹理采样器
samplerCube cube;      // 立方体贴图采样器
sampler3D vol;         // 3D 纹理采样器(WebGL 2)

内置函数

glsl
// ==================== 数学函数 ====================
abs(x)              // 绝对值
sign(x)             // 符号函数
floor(x)            // 向下取整
ceil(x)             // 向上取整
round(x)            // 四舍五入
fract(x)            // 小数部分
mod(x, y)           // 取模
min(x, y)           // 最小值
max(x, y)           // 最大值
clamp(x, minVal, maxVal)  // 限制范围
mix(x, y, a)        // 线性插值 (x * (1-a) + y * a)
step(edge, x)       // 阶跃函数(x < edge ? 0 : 1)
smoothstep(edge0, edge1, x)  // 平滑阶跃
 
// ==================== 三角函数 ====================
sin(x), cos(x), tan(x)           // 三角函数
asin(x), acos(x), atan(y, x)     // 反三角函数
radians(degrees)                 // 角度转弧度
degrees(radians)                 // 弧度转角度
 
// ==================== 指数函数 ====================
pow(x, y)          // 幂函数
exp(x)             // e^x
log(x)             // 自然对数
exp2(x)            // 2^x
log2(x)            // 以2为底的对数
sqrt(x)            // 平方根
inversesqrt(x)     // 平方根倒数
 
// ==================== 向量函数 ====================
length(x)              // 向量长度
distance(x, y)         // 两点距离
dot(x, y)              // 点积
cross(x, y)            // 叉积(仅 vec3)
normalize(x)           // 归一化
faceforward(N, I, Nref)  // 正面朝向
reflect(I, N)          // 反射向量
refract(I, N, eta)     // 折射向量
 
// ==================== 纹理函数 ====================
texture2D(sampler, coord)           // 2D 纹理采样
textureCube(sampler, coord)         // 立方体贴图采样
texture2DLod(sampler, coord, lod)   // 指定 LOD 采样(WebGL 2)
texture2DProj(sampler, coord)       // 投影纹理采样
 
// ==================== 导数函数(WebGL 2)====================
dFdx(p)             // X 方向导数
dFdy(p)             // Y 方向导数
fwidth(p)           // abs(dFdx(p)) + abs(dFdy(p))

精度修饰符

glsl
// 精度声明(通常在着色器顶部)
precision highp float;    // 高精度(32位)
precision mediump float;  // 中精度(16位)
precision lowp float;     // 低精度(8位)
 
// 单变量精度
highp vec3 position;
mediump vec2 uv;
lowp vec4 color;          // 适合颜色
 
// 精度选择建议
// highp:   位置、深度、复杂计算
// mediump: UV 坐标、法线
// lowp:    颜色、简单布尔值

ShaderMaterial 基础

基本结构

javascript
import * as THREE from 'three';
 
// 创建着色器材质
const material = new THREE.ShaderMaterial({
  // ==================== Uniform 变量 ====================
  uniforms: {
    time: { value: 0 },
    color: { value: new THREE.Color(0xff0000) },
    texture: { value: null }
  },
  
  // ==================== 顶点着色器 ====================
  vertexShader: `
    // 声明传递给片元着色器的变量
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    
    void main() {
      // 传递 UV 坐标
      vUv = uv;
      vNormal = normalize(normalMatrix * normal);
      
      // 计算世界坐标位置
      vec4 worldPosition = modelMatrix * vec4(position, 1.0);
      vPosition = worldPosition.xyz;
      
      // 计算裁剪空间坐标(必需)
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  
  // ==================== 片元着色器 ====================
  fragmentShader: `
    // 接收来自顶点着色器的变量
    uniform float time;
    uniform vec3 color;
    
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    
    void main() {
      // 计算动态颜色
      float intensity = sin(time + vUv.x * 10.0) * 0.5 + 0.5;
      
      // 输出最终颜色(必需)
      gl_FragColor = vec4(color * intensity, 1.0);
    }
  `,
  
  // ==================== 材质选项 ====================
  transparent: true,        // 启用透明
  side: THREE.DoubleSide,   // 双面渲染
  depthWrite: true,         // 写入深度缓冲
  blending: THREE.NormalBlending  // 混合模式
});
 
// 创建网格
const geometry = new THREE.PlaneGeometry(2, 2);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
 
// 更新 uniform
function animate() {
  requestAnimationFrame(animate);
  
  // 更新时间 uniform
  material.uniforms.time.value = performance.now() * 0.001;
  
  renderer.render(scene, camera);
}

Three.js 内置属性

ShaderMaterial 会自动注入以下属性和 uniform:

顶点着色器内置属性(Attributes)

glsl
// 顶点属性
attribute vec3 position;    // 顶点位置(局部坐标)
attribute vec3 normal;      // 顶点法线
attribute vec2 uv;          // 第一组 UV 坐标
attribute vec2 uv2;         // 第二组 UV 坐标
attribute vec3 color;       // 顶点颜色
attribute vec4 skinWeight;  // 骨骼权重
attribute vec4 skinIndex;   // 骨骼索引

内置矩阵(Uniforms)

glsl
// 变换矩阵
uniform mat4 modelMatrix;          // 模型矩阵(局部→世界)
uniform mat4 viewMatrix;           // 视图矩阵(世界→相机)
uniform mat4 projectionMatrix;     // 投影矩阵(相机→裁剪)
uniform mat4 modelViewMatrix;      // 模型视图矩阵(局部→相机)
uniform mat3 normalMatrix;         // 法线矩阵(用于正确变换法线)
uniform mat4 viewMatrixInverse;    // 视图矩阵逆矩阵

其他内置 Uniform

glsl
// 相机
uniform vec3 cameraPosition;       // 相机世界坐标
uniform float cameraNear;          // 近裁剪面
uniform float cameraFar;           // 远裁剪面
 
// 时间(需启用)
uniform float time;                // 动画时间
 
// 雾效
uniform vec3 fogColor;             // 雾颜色
uniform float fogNear;             // 雾近距
uniform float fogFar;              // 雾远距
uniform float fogDensity;          // 雾密度
 
// 光照(需启用)
uniform vec3 ambientLightColor;    // 环境光颜色
uniform vec3 directionalLightColor[MAX_LIGHTS];
uniform vec3 directionalLightDirection[MAX_LIGHTS];

RawShaderMaterial

RawShaderMaterial 不会自动注入任何内置属性,需要完全手动管理:

javascript
const rawMaterial = new THREE.RawShaderMaterial({
  uniforms: {
    // 必须手动定义所有 uniform
    projectionMatrix: { value: camera.projectionMatrix },
    modelViewMatrix: { value: new THREE.Matrix4() },
    time: { value: 0 }
  },
  
  vertexShader: `
    // 必须手动声明所有 attribute
    attribute vec3 position;
    attribute vec2 uv;
    
    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;
    
    varying vec2 vUv;
    
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  
  fragmentShader: `
    precision mediump float;
    
    varying vec2 vUv;
    uniform float time;
    
    void main() {
      gl_FragColor = vec4(vUv, 0.5 + 0.5 * sin(time), 1.0);
    }
  `
});

使用场景对比:

特性ShaderMaterialRawShaderMaterial
自动注入属性✅ 是❌ 否
代码简洁性✅ 高⚠️ 低
完全控制⚠️ 部分✅ 完全
兼容性✅ 自动处理⚠️ 需手动处理
适用场景大多数情况需要精细控制时

常用着色器效果

渐变效果

javascript
const gradientMaterial = new THREE.ShaderMaterial({
  uniforms: {
    color1: { value: new THREE.Color(0xff0000) },
    color2: { value: new THREE.Color(0x0000ff) },
    direction: { value: new THREE.Vector2(0, 1) }  // 渐变方向
  },
  vertexShader: `
    varying vec2 vUv;
    varying vec3 vPosition;
    
    void main() {
      vUv = uv;
      vPosition = position;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform vec3 color1;
    uniform vec3 color2;
    uniform vec2 direction;
    
    varying vec2 vUv;
    varying vec3 vPosition;
    
    void main() {
      // 基于方向的渐变
      float mixFactor = dot(vUv - 0.5, direction) + 0.5;
      mixFactor = clamp(mixFactor, 0.0, 1.0);
      
      vec3 color = mix(color1, color2, mixFactor);
      gl_FragColor = vec4(color, 1.0);
    }
  `
});

噪声效果

javascript
const noiseMaterial = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 },
    scale: { value: 5.0 },
    speed: { value: 1.0 }
  },
  vertexShader: `
    varying vec2 vUv;
    
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform float time;
    uniform float scale;
    uniform float speed;
    
    varying vec2 vUv;
    
    // ==================== 随机函数 ====================
    float random(vec2 st) {
      return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
    }
    
    // ==================== 值噪声 ====================
    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;
    }
    
    // ==================== 分形布朗运动(FBM)====================
    float fbm(vec2 st) {
      float value = 0.0;
      float amplitude = 0.5;
      float frequency = 1.0;
      
      for (int i = 0; i < 6; i++) {
        value += amplitude * noise(st * frequency);
        frequency *= 2.0;
        amplitude *= 0.5;
      }
      
      return value;
    }
    
    void main() {
      vec2 st = vUv * scale;
      float t = time * speed;
      
      // 动态噪声
      float n = fbm(st + t);
      
      // 添加时间扭曲
      n += 0.5 * fbm(st * 2.0 + vec2(t * 0.5, t * 0.3));
      
      gl_FragColor = vec4(vec3(n), 1.0);
    }
  `
});

波浪效果

javascript
const waveMaterial = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 },
    amplitude: { value: 0.5 },
    frequency: { value: 2.0 },
    speed: { value: 1.0 }
  },
  vertexShader: `
    uniform float time;
    uniform float amplitude;
    uniform float frequency;
    uniform float speed;
    
    varying vec2 vUv;
    varying float vElevation;
    varying vec3 vNormal;
    
    void main() {
      vUv = uv;
      
      vec3 pos = position;
      
      // 多层波浪叠加
      float wave1 = sin(pos.x * frequency + time * speed) * amplitude;
      float wave2 = sin(pos.y * frequency * 0.8 + time * speed * 1.2) * amplitude * 0.5;
      float wave3 = cos((pos.x + pos.y) * frequency * 0.5 + time * speed * 0.7) * amplitude * 0.3;
      
      float elevation = wave1 + wave2 + wave3;
      
      pos.z += elevation;
      vElevation = elevation;
      
      // 计算法线(近似)
      float dx = cos(pos.x * frequency + time * speed) * amplitude * frequency;
      float dy = cos(pos.y * frequency * 0.8 + time * speed * 1.2) * amplitude * frequency * 0.4;
      vNormal = normalize(vec3(-dx, -dy, 1.0));
      
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
  `,
  fragmentShader: `
    varying vec2 vUv;
    varying float vElevation;
    varying vec3 vNormal;
    
    void main() {
      // 基于高度的颜色映射
      float intensity = (vElevation + 1.0) * 0.5;
      
      // 海洋颜色
      vec3 deepColor = vec3(0.0, 0.1, 0.3);
      vec3 shallowColor = vec3(0.0, 0.5, 0.7);
      vec3 foamColor = vec3(0.9, 0.95, 1.0);
      
      vec3 color;
      if (intensity < 0.4) {
        color = mix(deepColor, shallowColor, intensity / 0.4);
      } else {
        color = mix(shallowColor, foamColor, (intensity - 0.4) / 0.6);
      }
      
      // 简单光照
      vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
      float diff = max(dot(vNormal, lightDir), 0.0);
      color *= 0.5 + 0.5 * diff;
      
      gl_FragColor = vec4(color, 1.0);
    }
  `,
  side: THREE.DoubleSide
});

发光效果(Fresnel)

javascript
const glowMaterial = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 },
    glowColor: { value: new THREE.Color(0x00ffff) },
    intensity: { value: 1.0 },
    power: { value: 3.0 }
  },
  vertexShader: `
    varying vec3 vNormal;
    varying vec3 vViewPosition;
    
    void main() {
      vNormal = normalize(normalMatrix * normal);
      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
      vViewPosition = -mvPosition.xyz;
      gl_Position = projectionMatrix * mvPosition;
    }
  `,
  fragmentShader: `
    uniform vec3 glowColor;
    uniform float intensity;
    uniform float time;
    uniform float power;
    
    varying vec3 vNormal;
    varying vec3 vViewPosition;
    
    void main() {
      // 计算视线方向
      vec3 viewDir = normalize(vViewPosition);
      
      // Fresnel 效果
      float fresnel = pow(1.0 - abs(dot(viewDir, vNormal)), power);
      
      // 脉冲效果
      float pulse = sin(time * 2.0) * 0.3 + 0.7;
      
      vec3 color = glowColor * fresnel * intensity * pulse;
      
      gl_FragColor = vec4(color, fresnel * 0.8);
    }
  `,
  transparent: true,
  side: THREE.BackSide,  // 背面渲染实现外发光
  depthWrite: false,
  blending: THREE.AdditiveBlending
});

全息效果

javascript
const hologramMaterial = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 },
    color: { value: new THREE.Color(0x00ffcc) },
    scanlineSpeed: { value: 2.0 }
  },
  vertexShader: `
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    
    void main() {
      vUv = uv;
      vNormal = normalize(normalMatrix * normal);
      vPosition = position;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform float time;
    uniform vec3 color;
    uniform float scanlineSpeed;
    
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    
    void main() {
      // 扫描线效果
      float scanline = sin(vUv.y * 100.0 + time * scanlineSpeed * 10.0) * 0.1;
      
      // Fresnel 边缘发光
      vec3 viewDir = normalize(cameraPosition - vPosition);
      float fresnel = pow(1.0 - abs(dot(viewDir, vNormal)), 2.0);
      
      // 闪烁效果
      float flicker = sin(time * 20.0) * 0.05 + 0.95;
      
      // 组合效果
      float alpha = (0.5 + scanline + fresnel * 0.5) * flicker;
      
      gl_FragColor = vec4(color, alpha);
    }
  `,
  transparent: true,
  side: THREE.DoubleSide,
  depthWrite: false,
  blending: THREE.AdditiveBlending
});

使用纹理

纹理采样

javascript
const textureMaterial = new THREE.ShaderMaterial({
  uniforms: {
    map: { value: textureLoader.load('texture.jpg') },
    normalMap: { value: textureLoader.load('normal.jpg') },
    time: { value: 0 },
    distortionStrength: { value: 0.05 }
  },
  vertexShader: `
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    
    void main() {
      vUv = uv;
      vNormal = normalize(normalMatrix * normal);
      vPosition = (modelMatrix * vec4(position, 1.0)).xyz;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D map;
    uniform sampler2D normalMap;
    uniform float time;
    uniform float distortionStrength;
    
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    
    void main() {
      // UV 扭曲效果
      vec2 uv = vUv;
      uv.x += sin(uv.y * 10.0 + time) * distortionStrength;
      uv.y += cos(uv.x * 10.0 + time) * distortionStrength;
      
      // 采样基础纹理
      vec4 texColor = texture2D(map, uv);
      
      // 法线贴图
      vec3 normal = texture2D(normalMap, uv).rgb;
      normal = normalize(normal * 2.0 - 1.0);
      
      // 简单光照
      vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
      float diff = max(dot(normal, lightDir), 0.0);
      
      vec3 color = texColor.rgb * (0.3 + 0.7 * diff);
      
      gl_FragColor = vec4(color, texColor.a);
    }
  `
});

多纹理混合

javascript
const blendMaterial = new THREE.ShaderMaterial({
  uniforms: {
    texture1: { value: textureLoader.load('grass.jpg') },
    texture2: { value: textureLoader.load('rock.jpg') },
    blendMap: { value: textureLoader.load('blend.png') }
  },
  vertexShader: `
    varying vec2 vUv;
    
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D texture1;
    uniform sampler2D texture2;
    uniform sampler2D blendMap;
    
    varying vec2 vUv;
    
    void main() {
      // 采样混合贴图
      float blend = texture2D(blendMap, vUv).r;
      
      // 采样两个纹理
      vec4 color1 = texture2D(texture1, vUv);
      vec4 color2 = texture2D(texture2, vUv);
      
      // 混合
      gl_FragColor = mix(color1, color2, blend);
    }
  `
});

光照着色器

Blinn-Phong 光照模型

javascript
const litMaterial = new THREE.ShaderMaterial({
  uniforms: {
    lightPosition: { value: new THREE.Vector3(5, 5, 5) },
    lightColor: { value: new THREE.Color(0xffffff) },
    ambientColor: { value: new THREE.Color(0x404040) },
    diffuseColor: { value: new THREE.Color(0x00ff00) },
    specularColor: { value: new THREE.Color(0xffffff) },
    shininess: { value: 30.0 }
  },
  vertexShader: `
    varying vec3 vNormal;
    varying vec3 vPosition;
    varying vec2 vUv;
    
    void main() {
      vNormal = normalize(normalMatrix * normal);
      vPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform vec3 lightPosition;
    uniform vec3 lightColor;
    uniform vec3 ambientColor;
    uniform vec3 diffuseColor;
    uniform vec3 specularColor;
    uniform float shininess;
    
    varying vec3 vNormal;
    varying vec3 vPosition;
    varying vec2 vUv;
    
    void main() {
      // 归一化向量
      vec3 normal = normalize(vNormal);
      vec3 lightDir = normalize(lightPosition - vPosition);
      vec3 viewDir = normalize(-vPosition);
      vec3 halfDir = normalize(lightDir + viewDir);  // Blinn-Phong
      
      // 环境光
      vec3 ambient = ambientColor * diffuseColor;
      
      // 漫反射
      float diff = max(dot(normal, lightDir), 0.0);
      vec3 diffuse = diff * lightColor * diffuseColor;
      
      // 镜面反射(Blinn-Phong)
      float spec = pow(max(dot(normal, halfDir), 0.0), shininess);
      vec3 specular = spec * lightColor * specularColor;
      
      // 最终颜色
      vec3 color = ambient + diffuse + specular;
      
      gl_FragColor = vec4(color, 1.0);
    }
  `
});

PBR 光照模型

javascript
const pbrMaterial = new THREE.ShaderMaterial({
  uniforms: {
    albedo: { value: new THREE.Color(0.8, 0.2, 0.2) },
    metallic: { value: 0.5 },
    roughness: { value: 0.5 },
    lightPosition: { value: new THREE.Vector3(5, 5, 5) },
    lightColor: { value: new THREE.Color(1, 1, 1) },
    cameraPosition: { value: new THREE.Vector3() }
  },
  vertexShader: `
    varying vec3 vNormal;
    varying vec3 vWorldPosition;
    
    void main() {
      vNormal = normalize(normalMatrix * normal);
      vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform vec3 albedo;
    uniform float metallic;
    uniform float roughness;
    uniform vec3 lightPosition;
    uniform vec3 lightColor;
    uniform vec3 cameraPosition;
    
    varying vec3 vNormal;
    varying vec3 vWorldPosition;
    
    const float PI = 3.14159265359;
    
    // 正态分布函数(GGX/Trowbridge-Reitz)
    float distributionGGX(vec3 N, vec3 H, float roughness) {
      float a = roughness * roughness;
      float a2 = a * a;
      float NdotH = max(dot(N, H), 0.0);
      float NdotH2 = NdotH * NdotH;
      
      float nom = a2;
      float denom = (NdotH2 * (a2 - 1.0) + 1.0);
      denom = PI * denom * denom;
      
      return nom / denom;
    }
    
    // 几何遮蔽函数
    float geometrySchlickGGX(float NdotV, float roughness) {
      float r = (roughness + 1.0);
      float k = (r * r) / 8.0;
      
      float nom = NdotV;
      float denom = NdotV * (1.0 - k) + k;
      
      return nom / denom;
    }
    
    float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
      float NdotV = max(dot(N, V), 0.0);
      float NdotL = max(dot(N, L), 0.0);
      float ggx2 = geometrySchlickGGX(NdotV, roughness);
      float ggx1 = geometrySchlickGGX(NdotL, roughness);
      
      return ggx1 * ggx2;
    }
    
    // Fresnel 方程
    vec3 fresnelSchlick(float cosTheta, vec3 F0) {
      return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
    }
    
    void main() {
      vec3 N = normalize(vNormal);
      vec3 V = normalize(cameraPosition - vWorldPosition);
      vec3 L = normalize(lightPosition - vWorldPosition);
      vec3 H = normalize(V + L);
      
      float distance = length(lightPosition - vWorldPosition);
      float attenuation = 1.0 / (distance * distance);
      vec3 radiance = lightColor * attenuation;
      
      // 基础反射率
      vec3 F0 = vec3(0.04);
      F0 = mix(F0, albedo, metallic);
      
      // Cook-Torrance BRDF
      float NDF = distributionGGX(N, H, roughness);
      float G = geometrySmith(N, V, L, roughness);
      vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
      
      vec3 numerator = NDF * G * F;
      float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
      vec3 specular = numerator / denominator;
      
      // 能量守恒
      vec3 kS = F;
      vec3 kD = vec3(1.0) - kS;
      kD *= 1.0 - metallic;
      
      // 最终颜色
      float NdotL = max(dot(N, L), 0.0);
      vec3 Lo = (kD * albedo / PI + specular) * radiance * NdotL;
      
      vec3 ambient = vec3(0.03) * albedo;
      vec3 color = ambient + Lo;
      
      // HDR tonemapping
      color = color / (color + vec3(1.0));
      // Gamma 校正
      color = pow(color, vec3(1.0 / 2.2));
      
      gl_FragColor = vec4(color, 1.0);
    }
  `
});

高级技术

顶点位移

javascript
const displacementMaterial = new THREE.ShaderMaterial({
  uniforms: {
    heightMap: { value: textureLoader.load('height.jpg') },
    displacementScale: { value: 2.0 },
    time: { value: 0 }
  },
  vertexShader: `
    uniform sampler2D heightMap;
    uniform float displacementScale;
    uniform float time;
    
    varying vec2 vUv;
    varying float vHeight;
    varying vec3 vNormal;
    
    void main() {
      vUv = uv;
      
      // 从高度图读取位移值
      float height = texture2D(heightMap, uv).r;
      vHeight = height;
      
      // 应用位移
      vec3 pos = position;
      pos += normal * height * displacementScale;
      
      // 动态波浪
      pos.y += sin(pos.x * 2.0 + time) * 0.1;
      pos.y += cos(pos.z * 2.0 + time) * 0.1;
      
      // 计算新的法线(近似)
      float delta = 0.01;
      float hL = texture2D(heightMap, uv - vec2(delta, 0.0)).r;
      float hR = texture2D(heightMap, uv + vec2(delta, 0.0)).r;
      float hD = texture2D(heightMap, uv - vec2(0.0, delta)).r;
      float hU = texture2D(heightMap, uv + vec2(0.0, delta)).r;
      
      vNormal = normalize(vec3(hL - hR, 2.0 * delta, hD - hU));
      
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
  `,
  fragmentShader: `
    varying vec2 vUv;
    varying float vHeight;
    varying vec3 vNormal;
    
    void main() {
      // 基于高度的颜色映射
      vec3 lowColor = vec3(0.1, 0.5, 0.1);   // 绿色(低地)
      vec3 midColor = vec3(0.5, 0.4, 0.2);   // 棕色(山地)
      vec3 highColor = vec3(1.0, 1.0, 1.0);  // 白色(雪顶)
      
      vec3 color;
      if (vHeight < 0.5) {
        color = mix(lowColor, midColor, vHeight * 2.0);
      } else {
        color = mix(midColor, highColor, (vHeight - 0.5) * 2.0);
      }
      
      // 光照
      vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
      float diff = max(dot(vNormal, lightDir), 0.0);
      color *= 0.5 + 0.5 * diff;
      
      gl_FragColor = vec4(color, 1.0);
    }
  `
});

菲涅尔效果

javascript
const fresnelMaterial = new THREE.ShaderMaterial({
  uniforms: {
    fresnelColor: { value: new THREE.Color(0x00ffff) },
    bias: { value: 0.1 },
    scale: { value: 1.0 },
    power: { value: 2.0 }
  },
  vertexShader: `
    varying vec3 vNormal;
    varying vec3 vViewPosition;
    
    void main() {
      vNormal = normalize(normalMatrix * normal);
      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
      vViewPosition = -mvPosition.xyz;
      gl_Position = projectionMatrix * mvPosition;
    }
  `,
  fragmentShader: `
    uniform vec3 fresnelColor;
    uniform float bias;
    uniform float scale;
    uniform float power;
    
    varying vec3 vNormal;
    varying vec3 vViewPosition;
    
    void main() {
      vec3 viewDir = normalize(vViewPosition);
      float fresnel = bias + scale * pow(1.0 - abs(dot(viewDir, vNormal)), power);
      
      gl_FragColor = vec4(fresnelColor * fresnel, 1.0);
    }
  `
});

API 参考

ShaderMaterial 属性

属性类型默认值说明
uniformsObject{}Uniform 变量对象
vertexShaderString''顶点着色器代码
fragmentShaderString''片元着色器代码
transparentBooleanfalse是否透明
sideIntegerFrontSide渲染面(FrontSide/BackSide/DoubleSide)
depthWriteBooleantrue是否写入深度缓冲
depthTestBooleantrue是否进行深度测试
blendingIntegerNormalBlending混合模式
wireframeBooleanfalse是否线框模式
lightsBooleanfalse是否包含光照计算
fogBooleantrue是否受雾影响

Uniform 类型映射

JavaScript 类型GLSL 类型说明
Numberfloat浮点数
THREE.Vector2vec2二维向量
THREE.Vector3vec3三维向量
THREE.Vector4vec4四维向量
THREE.Colorvec3颜色(RGB)
THREE.Matrix3mat33x3 矩阵
THREE.Matrix4mat44x4 矩阵
THREE.Texturesampler2D2D 纹理
THREE.CubeTexturesamplerCube立方体贴图
Arrayarray数组类型

常用方法

javascript
// 设置 uniform 值
material.uniforms.time.value = 1.0;
 
// 动态更新着色器
material.vertexShader = newVertexShader;
material.fragmentShader = newFragmentShader;
material.needsUpdate = true;  // 触发重新编译
 
// 克隆材质
const clonedMaterial = material.clone();
 
// 释放资源
material.dispose();

配置参数详解

透明度配置

javascript
const material = new THREE.ShaderMaterial({
  transparent: true,
  
  // Alpha 测试(透明度阈值)
  alphaTest: 0.5,  // Alpha < 0.5 的像素被丢弃
  
  // 深度写入
  depthWrite: false,  // 透明物体通常禁用深度写入
  
  // 混合模式
  blending: THREE.AdditiveBlending,  // 叠加混合
  // 其他混合模式:
  // THREE.NormalBlending - 正常混合
  // THREE.SubtractiveBlending - 减法混合
  // THREE.MultiplyBlending - 乘法混合
  // THREE.CustomBlending - 自定义混合
});

渲染面配置

javascript
// 正面渲染(默认)
material.side = THREE.FrontSide;
 
// 背面渲染(用于内表面)
material.side = THREE.BackSide;
 
// 双面渲染
material.side = THREE.DoubleSide;

深度测试配置

javascript
// 完全禁用深度测试(始终渲染)
material.depthTest = false;
 
// 禁用深度写入(用于透明物体)
material.depthWrite = false;

调试与错误处理

错误检查

javascript
// 方法一:使用 renderer 的错误回调
const material = new THREE.ShaderMaterial({
  vertexShader: `...`,
  fragmentShader: `...`,
  uniforms: { ... },
  onBeforeCompile: (shader) => {
    console.log('着色器编译前:', shader);
  }
});
 
// 方法二:手动检查编译状态
function checkShaderErrors(material, renderer) {
  const gl = renderer.getContext();
  const program = renderer.properties.get(material).currentProgram;
  
  if (program) {
    const vertexShader = program.vertexShader;
    const fragmentShader = program.fragmentShader;
    
    // 检查顶点着色器
    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
      const error = gl.getShaderInfoLog(vertexShader);
      console.error('顶点着色器编译错误:', error);
      console.error('着色器代码:', material.vertexShader);
    }
    
    // 检查片元着色器
    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
      const error = gl.getShaderInfoLog(fragmentShader);
      console.error('片元着色器编译错误:', error);
      console.error('着色器代码:', material.fragmentShader);
    }
    
    // 检查链接状态
    if (!gl.getProgramParameter(program.program, gl.LINK_STATUS)) {
      const error = gl.getProgramInfoLog(program.program);
      console.error('程序链接错误:', error);
    }
  }
}
 
// 首次渲染后检查
renderer.render(scene, camera);
checkShaderErrors(material, renderer);

性能监控

javascript
// 使用 WebGL 扩展进行性能监控
const gl = renderer.getContext();
 
// WebGL 1 扩展
const timerExt = gl.getExtension('EXT_disjoint_timer_query');
 
// WebGL 2 内置支持
if (gl instanceof WebGL2RenderingContext) {
  console.log('支持 GPU 时间查询');
}
 
// 使用 Stats.js 进行帧率监控
import Stats from 'stats.js';
const stats = new Stats();
document.body.appendChild(stats.dom);
 
function animate() {
  stats.begin();
  renderer.render(scene, camera);
  stats.end();
  requestAnimationFrame(animate);
}

调试技巧

javascript
// 1. 使用纯色输出验证着色器是否工作
fragmentShader: `
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);  // 纯红色
  }
`
 
// 2. 输出变量值进行调试
fragmentShader: `
  varying vec2 vUv;
  varying vec3 vNormal;
  
  void main() {
    // 输出 UV 坐标
    gl_FragColor = vec4(vUv, 0.0, 1.0);
    
    // 或输出法线
    // gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
  }
`
 
// 3. 使用 console.log 在 JavaScript 端验证数据
console.log('Uniform 值:', material.uniforms.time.value);
console.log('几何体属性:', geometry.attributes.position.array);

性能优化

优化策略

  1. 减少条件分支
glsl
// 不推荐:使用 if-else
if (condition) {
  color = color1;
} else {
  color = color2;
}
 
// 推荐:使用 mix 函数
color = mix(color2, color1, float(condition));
  1. 在顶点着色器计算
glsl
// 不推荐:在片元着色器计算
// (每个像素执行一次)
varying vec3 vNormal;
void main() {
  vec3 normal = normalize(vNormal);  // 每像素归一化
}
 
// 推荐:在顶点着色器计算
// (每个顶点执行一次)
varying vec3 vNormal;
void main() {
  vNormal = normalize(normalMatrix * normal);  // 每顶点归一化
}
  1. 使用内置函数
glsl
// 不推荐:手动实现
float length = sqrt(x * x + y * y + z * z);
 
// 推荐:使用内置函数
float length = length(vec3(x, y, z));
  1. 避免重复计算
glsl
// 不推荐:重复计算
float value = sin(time) * 0.5;
float result1 = value + 0.5;
float result2 = value * 2.0;
 
// 推荐:复用计算结果
float sineValue = sin(time);
float value = sineValue * 0.5;
float result1 = value + 0.5;
float result2 = value * 2.0;
  1. 精度优化
glsl
// 在着色器顶部设置默认精度
precision highp float;   // 需要高精度
precision mediump int;   // 整数可用中精度
 
// 对特定变量使用低精度
lowp vec4 color;         // 颜色值不需要高精度

性能分析工具

javascript
// 1. 使用 Chrome DevTools 的 WebGL Inspector
// 2. 使用 Spector.js
import * as SPECTOR from 'spectorjs';
const spector = new SPECTOR.Spector();
spector.spyCanvas(canvas);
spector.onCapture.add((capture) => {
  console.log('捕获的帧:', capture);
});
 
// 3. 手动测量渲染时间
console.time('render');
renderer.render(scene, camera);
console.timeEnd('render');

最佳实践

代码组织

javascript
// 推荐:将着色器代码分离到常量
const VERTEX_SHADER = `
  varying vec2 vUv;
  
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`;
 
const FRAGMENT_SHADER = `
  uniform float time;
  varying vec2 vUv;
  
  void main() {
    gl_FragColor = vec4(vUv, sin(time) * 0.5 + 0.5, 1.0);
  }
`;
 
const material = new THREE.ShaderMaterial({
  vertexShader: VERTEX_SHADER,
  fragmentShader: FRAGMENT_SHADER,
  uniforms: {
    time: { value: 0 }
  }
});

Uniform 管理

javascript
// 推荐:使用函数更新 uniform
function updateUniforms(time, deltaTime) {
  material.uniforms.time.value = time;
  material.uniforms.deltaTime.value = deltaTime;
}
 
// 推荐:使用对象组织 uniform
const uniforms = {
  time: { value: 0 },
  color: { value: new THREE.Color(0xff0000) },
  texture: { value: null }
};
 
const material = new THREE.ShaderMaterial({ uniforms });
 
// 更新
uniforms.time.value = elapsedTime;
uniforms.texture.value = loadedTexture;

复用着色器代码

javascript
// 使用 chunk 复用代码
const commonChunk = `
  float random(vec2 st) {
    return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
  }
  
  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;
  }
`;
 
const fragmentShader = `
  ${commonChunk}
  
  void main() {
    float n = noise(vUv * 10.0);
    gl_FragColor = vec4(vec3(n), 1.0);
  }
`;

常见问题

Q1: 着色器编译错误如何排查?

A: 按以下步骤排查:

javascript
// 1. 检查语法错误
// 确保每个语句以分号结尾
// 确保所有变量都已声明
 
// 2. 检查变量类型匹配
// float 不能直接赋值 int
float f = 1.0;  // 正确
float f = 1;    // 错误
 
// 3. 检查内置变量名冲突
// 避免使用 color、normal 等作为 uniform 名
 
// 4. 使用浏览器控制台查看错误
// WebGL 错误会输出到控制台

Q2: 着色器显示全黑或全白?

A: 可能原因及解决方案:

javascript
// 1. 未正确计算 gl_Position
void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
 
// 2. 输出颜色超出范围
// 颜色值应在 [0, 1] 范围内
gl_FragColor = vec4(color, 1.0);  // color 应为 vec3,值在 0-1
 
// 3. 法线未归一化
vNormal = normalize(normalMatrix * normal);
 
// 4. 深度问题
// 检查相机近远裁剪面设置
camera.near = 0.1;
camera.far = 1000;

Q3: 如何在着色器中使用纹理?

A:

javascript
// 1. 声明 uniform 采样器
uniform sampler2D map;
 
// 2. 使用 texture2D 采样
vec4 color = texture2D(map, vUv);
 
// 3. 确保 UV 坐标正确传递
varying vec2 vUv;  // 片元着色器中声明
vUv = uv;          // 顶点着色器中传递

Q4: 透明度渲染不正确?

A:

javascript
// 1. 启用透明
transparent: true
 
// 2. 设置正确的混合模式
blending: THREE.NormalBlending
 
// 3. 禁用深度写入(对于透明物体)
depthWrite: false
 
// 4. 在片元着色器中正确设置 alpha
gl_FragColor = vec4(color, alpha);  // alpha 应在 [0, 1] 范围

Q5: 性能太差怎么办?

A: 参考优化策略:

javascript
// 1. 减少片元着色器复杂度
// 将计算移到顶点着色器
 
// 2. 使用 LOD(细节层次)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);    // 近距离
lod.addLevel(mediumDetailMesh, 20);  // 中距离
lod.addLevel(lowDetailMesh, 50);     // 远距离
 
// 3. 降低渲染分辨率
renderer.setPixelRatio(0.5);
 
// 4. 减少后处理通道数量
 
// 5. 使用 instancing 减少绘制调用
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);

Q6: 如何实现自定义光照?

A: 参考光照着色器章节,或使用 Three.js 的 onBeforeCompile

javascript
const material = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  onBeforeCompile: (shader) => {
    // 修改着色器代码
    shader.vertexShader = shader.vertexShader.replace(
      '#include <common>',
      `
        #include <common>
        varying vec3 vWorldPosition;
      `
    );
    
    shader.fragmentShader = shader.fragmentShader.replace(
      '#include <dithering_fragment>',
      `
        #include <dithering_fragment>
        // 自定义后处理
        gl_FragColor.rgb *= 1.2;
      `
    );
  }
});

Q7: RawShaderMaterial 和 ShaderMaterial 有什么区别?

A:

特性ShaderMaterialRawShaderMaterial
自动注入 attributes
自动注入 uniforms
自动添加精度声明
适用场景快速开发精细控制
javascript
// ShaderMaterial - 自动注入
// position, normal, uv, projectionMatrix 等自动可用
 
// RawShaderMaterial - 需要手动声明
// 必须手动声明所有变量和精度

Q8: 如何调试着色器?

A:

javascript
// 1. 输出中间值
fragmentShader: `
  varying vec2 vUv;
  
  void main() {
    // 输出 UV 坐标可视化
    gl_FragColor = vec4(vUv, 0.0, 1.0);
  }
`
 
// 2. 分步验证
// 先确保基础颜色正确,再逐步添加复杂计算
 
// 3. 使用 ShaderToy 测试
// https://www.shadertoy.com/
 
// 4. 使用浏览器开发工具
// 检查 WebGL 错误信息

相关链接