1. 着色器材质

我来详细介绍一下 Three.js 中的着色器材质(ShaderMaterial)。

1.1. 着色器材质基础

着色器材质允许你使用自定义的 GLSL 代码来编写顶点和片元着色器,实现高度自定义的渲染效果。

javascript

// 创建基础的着色器材质
const material = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 }
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    varying vec2 vUv;
    uniform float time;
    void main() {
      gl_FragColor = vec4(vUv, sin(time), 1.0);
    }
  `
});

1.2. 核心组成部分

1.2.1. Uniforms(统一变量)

从 JavaScript 传递到着色器的变量。

javascript

uniforms: {
  time: { value: 0 },
  texture: { value: textureLoader.load('image.jpg') },
  color: { value: new THREE.Color(0xff0000) },
  resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
}

1.2.2. Attributes(属性变量)

每个顶点特有的数据。

javascript

// 在顶点着色器中
attribute float size;
attribute vec3 customColor;

// 在 JavaScript 中设置
geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));

1.2.3. Varyings(可变变量)

从顶点着色器传递到片元着色器的插值变量。

glsl

// 顶点着色器
varying vec2 vUv;
varying vec3 vNormal;

void main() {
  vUv = uv;
  vNormal = normal;
}

// 片元着色器
varying vec2 vUv;
varying vec3 vNormal;

1.3. 常用内置 Uniforms

javascript

// 自动传入的内置 uniforms
uniforms: {
  modelMatrix: { value: mesh.matrixWorld },
  viewMatrix: { value: camera.matrixWorldInverse },
  projectionMatrix: { value: camera.projectionMatrix },
  modelViewMatrix: { value: mesh.modelViewMatrix },
  normalMatrix: { value: mesh.normalMatrix },
  cameraPosition: { value: camera.position }
}

1.4. 完整示例

1.4.1. 波动平面效果

javascript

const vertexShader = `
  uniform float time;
  varying vec2 vUv;

  void main() {
    vUv = uv;

    // 创建波动效果
    float wave = sin(position.x * 5.0 + time) * 0.1;
    vec3 pos = position;
    pos.z = wave;

    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
  }
`;

const fragmentShader = `
  uniform float time;
  uniform vec3 color;
  varying vec2 vUv;

  void main() {
    // 创建动态颜色
    vec3 finalColor = color * (0.5 + 0.5 * sin(time + vUv.x * 10.0));

    // 添加一些图案
    float pattern = sin(vUv.x * 20.0 + time) * sin(vUv.y * 20.0);

    gl_FragColor = vec4(finalColor + pattern * 0.2, 1.0);
  }
`;

const material = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 },
    color: { value: new THREE.Color(0x00ffff) }
  },
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  side: THREE.DoubleSide
});

// 动画循环中更新 uniform
function animate() {
  material.uniforms.time.value += 0.01;
}

1.4.2. 噪声纹理效果

javascript

// 噪声函数(通常在片元着色器中)
const noiseShader = `
  // 简单的伪随机函数
  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;
  }
`;

1.5. 性能优化技巧

1.5.1. 预编译着色器

javascript

const shader = THREE.ShaderLib.standard;
const material = new THREE.ShaderMaterial({
  uniforms: THREE.UniformsUtils.clone(shader.uniforms),
  vertexShader: shader.vertexShader,
  fragmentShader: shader.fragmentShader
});

1.5.2. 重用 Uniforms

javascript

const sharedUniforms = {
  time: { value: 0 },
  resolution: { value: new THREE.Vector2() }
};

const material1 = new THREE.ShaderMaterial({
  uniforms: THREE.UniformsUtils.merge([sharedUniforms, { color: { value: new THREE.Color(0xff0000) } }]),
  // ...
});

const material2 = new THREE.ShaderMaterial({
  uniforms: THREE.UniformsUtils.merge([sharedUniforms, { color: { value: new THREE.Color(0x00ff00) } }]),
  // ...
});

1.6. 调试技巧

1.6.1. 使用着色器编辑器

javascript

// 显示编译错误
material.onCompile = (shader) => {
  console.log('Shader compiled:', shader);
};

// 检查编译状态
console.log(material.program);

1.6.2. 逐步调试

glsl

// 使用颜色可视化调试
gl_FragColor = vec4(vUv, 0.0, 1.0); // 检查UV
gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0); // 检查法线

1.7. 常用资源库

javascript

// 引入着色器库
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass.js';

// 常用库:
// - three/examples/jsm/shaders/*  - Three.js内置着色器
// - https://github.com/mrdoob/three.js/tree/dev/examples/jsm/shaders
// - https://thebookofshaders.com/ - 学习GLSL

1.8. 最佳实践

  1. 最小化 uniform 更新:只在必要时更新 uniforms

  2. 使用 defines:编译时常量使用 defines 而不是 uniforms

  3. 避免分支:尽量在着色器中避免 if 语句

  4. 纹理优化:使用适当大小的纹理,考虑使用纹理图集

  5. 精度选择:根据需求选择 highp/mediump/lowp

javascript

const material = new THREE.ShaderMaterial({
  defines: {
    USE_TEXTURE: true,
    MAX_STEPS: 100
  },
  precision: 'mediump', // 或 'highp''lowp'
  // ...
});

着色器材质是 Three.js 中最强大的功能之一,虽然学习曲线较陡,但能实现几乎所有你能想象的视觉效果。建议从简单的示例开始,逐步学习 GLSL 语法和 GPU 编程概念。