1. 自定义着色器效果

我来详细讲解 Three.js 中自定义着色器效果的实现方法。

1.1. 着色器基础

1.1.1. 两种主要方法

1.1.1.1. 方法一:ShaderMaterial

javascript

// 创建自定义着色器材质
const customMaterial = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 },
    texture1: { value: texture },
  },
  vertexShader: vertexShaderCode,
  fragmentShader: fragmentShaderCode,
});
1.1.1.2. 方法二:RawShaderMaterial

javascript

// 需要自己声明所有变量(包括内置变量)
const rawMaterial = new THREE.RawShaderMaterial({
  uniforms: uniforms,
  vertexShader: vertexShaderCode,
  fragmentShader: fragmentShaderCode,
});

1.2. 完整的着色器示例

1.2.1. 波浪效果

javascript

// 顶点着色器
const vertexShader = `
  uniform float uTime;
  uniform float uWaveHeight;
  uniform float uWaveSpeed;

  varying vec2 vUv;
  varying float vElevation;

  void main() {
    vUv = uv;

    // 创建波浪效果
    float elevation = sin(position.x * 10.0 + uTime * uWaveSpeed) * 
                     sin(position.z * 10.0 + uTime * uWaveSpeed) * 
                     uWaveHeight;

    vec3 newPosition = position;
    newPosition.y += elevation;

    vElevation = elevation;

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

// 片元着色器
const fragmentShader = `
  uniform float uTime;
  uniform vec3 uColor1;
  uniform vec3 uColor2;

  varying vec2 vUv;
  varying float vElevation;

  void main() {
    // 根据高度混合颜色
    float mixStrength = (vElevation + 1.0) * 0.5;
    vec3 color = mix(uColor1, uColor2, mixStrength);

    // 添加一些动态效果
    float pulse = sin(uTime * 2.0) * 0.1 + 0.9;
    color *= pulse;

    gl_FragColor = vec4(color, 1.0);
  }
`;

// 创建材质
const waveMaterial = new THREE.ShaderMaterial({
  uniforms: {
    uTime: { value: 0 },
    uWaveHeight: { value: 0.2 },
    uWaveSpeed: { value: 1.0 },
    uColor1: { value: new THREE.Color(0x1a2a6c) },
    uColor2: { value: new THREE.Color(0xb21f1f) },
  },
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
});

1.3. 高级着色器效果

1.3.1. 噪声效果

javascript

// GLSL 噪声函数(放在着色器顶部)
const noiseShader = `
  // 经典噪声函数
  vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
  vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
  vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
  vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }

  float snoise(vec3 v) {
    const vec2 C = vec2(1.0/6.0, 1.0/3.0);
    const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);

    vec3 i = floor(v + dot(v, C.yyy));
    vec3 x0 = v - i + dot(i, C.xxx);

    vec3 g = step(x0.yzx, x0.xyz);
    vec3 l = 1.0 - g;
    vec3 i1 = min(g.xyz, l.zxy);
    vec3 i2 = max(g.xyz, l.zxy);

    vec3 x1 = x0 - i1 + C.xxx;
    vec3 x2 = x0 - i2 + C.yyy;
    vec3 x3 = x0 - D.yyy;

    i = mod289(i);
    vec4 p = permute(permute(permute(
              i.z + vec4(0.0, i1.z, i2.z, 1.0))
            + i.y + vec4(0.0, i1.y, i2.y, 1.0))
            + i.x + vec4(0.0, i1.x, i2.x, 1.0));

    float n_ = 0.142857142857;
    vec3 ns = n_ * D.wyz - D.xzx;

    vec4 j = p - 49.0 * floor(p * ns.z * ns.z);

    vec4 x_ = floor(j * ns.z);
    vec4 y_ = floor(j - 7.0 * x_);

    vec4 x = x_ * ns.x + ns.yyyy;
    vec4 y = y_ * ns.x + ns.yyyy;
    vec4 h = 1.0 - abs(x) - abs(y);

    vec4 b0 = vec4(x.xy, y.xy);
    vec4 b1 = vec4(x.zw, y.zw);

    vec4 s0 = floor(b0) * 2.0 + 1.0;
    vec4 s1 = floor(b1) * 2.0 + 1.0;
    vec4 sh = -step(h, vec4(0.0));

    vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
    vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;

    vec3 p0 = vec3(a0.xy, h.x);
    vec3 p1 = vec3(a0.zw, h.y);
    vec3 p2 = vec3(a1.xy, h.z);
    vec3 p3 = vec3(a1.zw, h.w);

    vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
    p0 *= norm.x;
    p1 *= norm.y;
    p2 *= norm.z;
    p3 *= norm.w;

    vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
    m = m * m;
    return 42.0 * dot(m*m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
  }
`;

// 使用噪声的着色器
const noiseVertexShader = `
  ${noiseShader}

  uniform float uTime;
  uniform float uNoiseScale;
  uniform float uNoiseStrength;

  varying vec2 vUv;
  varying float vNoise;

  void main() {
    vUv = uv;

    // 生成噪声
    vec3 pos = position;
    float noise = snoise(vec3(
      pos.x * uNoiseScale + uTime * 0.5,
      pos.y * uNoiseScale,
      pos.z * uNoiseScale + uTime * 0.3
    ));

    vNoise = noise;

    // 应用噪声变形
    pos += normal * noise * uNoiseStrength;

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

1.3.2. 后处理效果

javascript

class GlitchEffect {
  constructor(renderer, scene, camera) {
    this.renderer = renderer;
    this.scene = scene;
    this.camera = camera;

    this.init();
  }

  init() {
    // 创建渲染目标
    this.renderTarget = new THREE.WebGLRenderTarget(
      window.innerWidth,
      window.innerHeight,
      {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        format: THREE.RGBAFormat
      }
    );

    // 创建后处理材质
    this.postMaterial = new THREE.ShaderMaterial({
      uniforms: {
        tDiffuse: { value: null },
        uTime: { value: 0 },
        uIntensity: { value: 0.5 }
      },
      vertexShader: `
        varying vec2 vUv;
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        uniform sampler2D tDiffuse;
        uniform float uTime;
        uniform float uIntensity;
        varying vec2 vUv;

        float random(vec2 st) {
          return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
        }

        void main() {
          vec2 uv = vUv;

          // 创建扫描线效果
          float scanLine = sin(uv.y * 800.0 + uTime * 10.0) * 0.02;

          // 创建颜色偏移(RGB分离)
          float r = texture2D(tDiffuse, uv + vec2(0.02 * uIntensity, 0.0)).r;
          float g = texture2D(tDiffuse, uv + vec2(0.0, 0.01 * uIntensity)).g;
          float b = texture2D(tDiffuse, uv + vec2(-0.02 * uIntensity, 0.0)).b;

          // 随机干扰
          float noise = random(vec2(uTime, uv.y)) * uIntensity * 0.1;

          vec3 color = vec3(r, g, b) + scanLine + noise;

          gl_FragColor = vec4(color, 1.0);
        }
      `
    });

    // 创建后处理平面
    this.postPlane = new THREE.Mesh(
      new THREE.PlaneGeometry(2, 2),
      this.postMaterial
    );

    this.postScene = new THREE.Scene();
    this.postCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    this.postScene.add(this.postPlane);
  }

  render() {
    // 渲染到目标
    this.renderer.setRenderTarget(this.renderTarget);
    this.renderer.render(this.scene, this.camera);

    // 更新uniforms
    this.postMaterial.uniforms.uTime.value = performance.now() * 0.001;
    this.postMaterial.uniforms.tDiffuse.value = this.renderTarget.texture;

    // 渲染后处理效果
    this.renderer.setRenderTarget(null);
    this.renderer.render(this.postScene, this.postCamera);
  }
}

1.4. 优化技巧

1.4.1. 统一管理uniforms

javascript

class ShaderManager {
  constructor() {
    this.uniforms = {
      uTime: { value: 0 },
      uResolution: { value: new THREE.Vector2() },
      uMouse: { value: new THREE.Vector2() }
    };
  }

  update(time) {
    this.uniforms.uTime.value = time;
  }

  updateResolution(width, height) {
    this.uniforms.uResolution.value.set(width, height);
  }

  updateMouse(x, y) {
    this.uniforms.uMouse.value.set(x, y);
  }
}

1.4.2. 着色器复用

javascript

// 创建可复用的着色器模块
const ShaderLib = {
  noise: noiseShader,
  utils: `
    // 常用工具函数
    float map(float value, float min1, float max1, float min2, float max2) {
      return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
    }

    vec3 hueShift(vec3 color, float hue) {
      const vec3 k = vec3(0.57735, 0.57735, 0.57735);
      float cosAngle = cos(hue);
      return vec3(color * cosAngle + cross(k, color) * sin(hue) + k * dot(k, color) * (1.0 - cosAngle));
    }
  `
};

// 组合使用
const customShader = `
  ${ShaderLib.noise}
  ${ShaderLib.utils}

  // 主着色器代码...
`;

1.5. 调试技巧

1.5.1. 使用ShaderFrog等可视化工具

1.5.2. Three.js调试工具

javascript

// 使用dat.GUI调试uniforms
const gui = new dat.GUI();
const params = {
  waveHeight: 0.2,
  waveSpeed: 1.0,
  color1: '#1a2a6c',
  color2: '#b21f1f'
};

gui.add(params, 'waveHeight', 0, 1).onChange(val => {
  material.uniforms.uWaveHeight.value = val;
});

gui.add(params, 'waveSpeed', 0, 5).onChange(val => {
  material.uniforms.uWaveSpeed.value = val;
});

// 在动画循环中更新time
function animate() {
  requestAnimationFrame(animate);

  const time = performance.now() * 0.001;
  material.uniforms.uTime.value = time;

  renderer.render(scene, camera);
}

1.6. 性能考虑

  1. 减少uniform更新:只在必要时更新uniforms

  2. 使用低精度:在片元着色器中使用lowpmediump

  3. 减少分支语句:避免在着色器中使用复杂的if语句

  4. 纹理压缩:使用压缩纹理格式

  5. LOD优化:根据距离使用不同复杂度的着色器

这个指南覆盖了Three.js自定义着色器的主要方面。从基础到高级效果,你可以根据自己的需求组合使用这些技术。