1. Three.js 自定义后处理着色器

1.1. 基础后处理着色器结构

1.1.1. 创建自定义着色器通道

javascript

import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';

// 1. 定义着色器材质
const CustomShader = {
  uniforms: {
    "tDiffuse": { value: null },      // 主纹理
    "uTime": { value: 0.0 },          // 时间
    "uResolution": { value: new THREE.Vector2(1, 1) }, // 分辨率
    "uStrength": { value: 1.0 },      // 效果强度
    "uColor": { value: new THREE.Color(1.0, 1.0, 1.0) } // 颜色
  },

  vertexShader: `
    varying vec2 vUv;

    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,

  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float uTime;
    uniform vec2 uResolution;
    uniform float uStrength;
    uniform vec3 uColor;

    varying vec2 vUv;

    void main() {
      // 获取原始颜色
      vec4 color = texture2D(tDiffuse, vUv);

      // 应用效果
      // 这里可以添加各种图像处理算法

      gl_FragColor = color;
    }
  `
};

// 2. 创建着色器通道
const customPass = new ShaderPass(CustomShader);

// 3. 创建后处理合成器
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(customPass);

// 4. 在动画循环中更新
function animate() {
  customPass.uniforms.uTime.value += 0.016; // 更新时间

  composer.render();
  requestAnimationFrame(animate);
}

1.2. 常用后处理效果实现

1.2.1. 模糊效果 (Blur)

javascript

const BlurShader = {
  uniforms: {
    "tDiffuse": { value: null },
    "uStrength": { value: 2.0 },
    "uDirection": { value: new THREE.Vector2(1.0, 0.0) }
  },

  vertexShader: `/* 同上 */`,

  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float uStrength;
    uniform vec2 uDirection;
    uniform vec2 uResolution;

    varying vec2 vUv;

    // 高斯核权重
    float gaussian(float x, float sigma) {
      return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * 3.14159) * sigma);
    }

    void main() {
      vec2 texelSize = 1.0 / uResolution;
      vec4 color = vec4(0.0);
      float total = 0.0;

      // 7x7 高斯模糊
      for (float i = -3.0; i <= 3.0; i++) {
        float weight = gaussian(i, uStrength);
        vec2 offset = uDirection * i * texelSize;
        color += texture2D(tDiffuse, vUv + offset) * weight;
        total += weight;
      }

      gl_FragColor = color / total;
    }
  `
};

// 双通道模糊(水平和垂直)
const blurPass1 = new ShaderPass(BlurShader);
blurPass1.uniforms.uDirection.value.set(1.0, 0.0); // 水平模糊

const blurPass2 = new ShaderPass(BlurShader);
blurPass2.uniforms.uDirection.value.set(0.0, 1.0); // 垂直模糊

1.2.2. 边缘检测 (Edge Detection)

javascript

const EdgeDetectionShader = {
  uniforms: {
    "tDiffuse": { value: null },
    "uThreshold": { value: 0.1 },
    "uEdgeColor": { value: new THREE.Color(0.0, 0.0, 0.0) },
    "uBackgroundColor": { value: new THREE.Color(1.0, 1.0, 1.0) }
  },

  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float uThreshold;
    uniform vec3 uEdgeColor;
    uniform vec3 uBackgroundColor;
    uniform vec2 uResolution;

    varying vec2 vUv;

    // Sobel 算子
    float sobel(vec2 uv) {
      vec2 texelSize = 1.0 / uResolution;

      // 3x3 像素采样
      float topLeft = texture2D(tDiffuse, uv + vec2(-1, 1) * texelSize).r;
      float top = texture2D(tDiffuse, uv + vec2(0, 1) * texelSize).r;
      float topRight = texture2D(tDiffuse, uv + vec2(1, 1) * texelSize).r;
      float left = texture2D(tDiffuse, uv + vec2(-1, 0) * texelSize).r;
      float right = texture2D(tDiffuse, uv + vec2(1, 0) * texelSize).r;
      float bottomLeft = texture2D(tDiffuse, uv + vec2(-1, -1) * texelSize).r;
      float bottom = texture2D(tDiffuse, uv + vec2(0, -1) * texelSize).r;
      float bottomRight = texture2D(tDiffuse, uv + vec2(1, -1) * texelSize).r;

      // Sobel 卷积核
      float gx = -topLeft - 2.0 * left - bottomLeft + topRight + 2.0 * right + bottomRight;
      float gy = -topLeft - 2.0 * top - topRight + bottomLeft + 2.0 * bottom + bottomRight;

      return sqrt(gx * gx + gy * gy);
    }

    void main() {
      float edge = sobel(vUv);

      if (edge > uThreshold) {
        gl_FragColor = vec4(uEdgeColor, 1.0);
      } else {
        vec4 original = texture2D(tDiffuse, vUv);
        gl_FragColor = vec4(mix(original.rgb, uBackgroundColor, 0.2), original.a);
      }
    }
  `
};

1.2.3. 像素化效果 (Pixelation)

javascript

const PixelationShader = {
  uniforms: {
    "tDiffuse": { value: null },
    "uPixelSize": { value: 8.0 },
    "uResolution": { value: new THREE.Vector2(1, 1) }
  },

  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float uPixelSize;
    uniform vec2 uResolution;

    varying vec2 vUv;

    void main() {
      // 计算像素化的UV
      vec2 pixelSize = vec2(uPixelSize) / uResolution;
      vec2 pixelatedUV = floor(vUv / pixelSize) * pixelSize + pixelSize * 0.5;

      // 采样像素中心颜色
      vec4 color = texture2D(tDiffuse, pixelatedUV);

      // 添加网格效果
      vec2 grid = mod(vUv / pixelSize, 1.0);
      float gridLine = step(0.95, grid.x) + step(0.95, grid.y);
      color.rgb = mix(color.rgb, vec3(0.0), gridLine * 0.3);

      gl_FragColor = color;
    }
  `
};

1.2.4. 色彩分级 (Color Grading)

javascript

const ColorGradingShader = {
  uniforms: {
    "tDiffuse": { value: null },
    "uBrightness": { value: 0.0 },
    "uContrast": { value: 1.0 },
    "uSaturation": { value: 1.0 },
    "uExposure": { value: 1.0 },
    "uGamma": { value: 2.2 },
    "uTemperature": { value: 0.0 },  // -1冷色到1暖色
    "uTint": { value: 0.0 },        // -1绿到1紫
    "uVignette": { value: 0.5 }     // 暗角强度
  },

  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float uBrightness;
    uniform float uContrast;
    uniform float uSaturation;
    uniform float uExposure;
    uniform float uGamma;
    uniform float uTemperature;
    uniform float uTint;
    uniform float uVignette;

    varying vec2 vUv;

    // 计算亮度
    float luminance(vec3 rgb) {
      return dot(rgb, vec3(0.2126, 0.7152, 0.0722));
    }

    // 色温调整
    vec3 adjustTemperature(vec3 color, float temp) {
      vec3 warm = vec3(1.0, 0.7, 0.4);
      vec3 cool = vec3(0.4, 0.7, 1.0);

      if (temp > 0.0) {
        return mix(color, color * warm, temp);
      } else {
        return mix(color, color * cool, -temp);
      }
    }

    // 色调调整
    vec3 adjustTint(vec3 color, float tint) {
      vec3 green = vec3(0.4, 1.0, 0.4);
      vec3 purple = vec3(1.0, 0.4, 1.0);

      if (tint > 0.0) {
        return mix(color, color * purple, tint);
      } else {
        return mix(color, color * green, -tint);
      }
    }

    // 暗角效果
    float vignette(vec2 uv) {
      uv = uv * 2.0 - 1.0;
      float dist = length(uv);
      return smoothstep(0.8, 0.2, dist * (1.0 + uVignette));
    }

    void main() {
      vec4 texel = texture2D(tDiffuse, vUv);
      vec3 color = texel.rgb;

      // 亮度调整
      color += uBrightness;

      // 对比度调整
      color = ((color - 0.5) * max(uContrast, 0.0)) + 0.5;

      // 饱和度调整
      float lum = luminance(color);
      color = mix(vec3(lum), color, uSaturation);

      // 曝光调整
      color *= uExposure;

      // 色温调整
      color = adjustTemperature(color, uTemperature);

      // 色调调整
      color = adjustTint(color, uTint);

      // Gamma校正
      color = pow(color, vec3(1.0 / uGamma));

      // 应用暗角
      color *= vignette(vUv);

      gl_FragColor = vec4(color, texel.a);
    }
  `
};

1.3. 高级后处理效果

1.3.1. 景深 (Depth of Field)

javascript

const DepthOfFieldShader = {
  uniforms: {
    "tDiffuse": { value: null },
    "tDepth": { value: null },  // 深度纹理
    "focus": { value: 10.0 },
    "aperture": { value: 0.025 },
    "maxBlur": { value: 1.0 },
    "near": { value: 0.1 },
    "far": { value: 1000.0 }
  },

  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform sampler2D tDepth;
    uniform float focus;
    uniform float aperture;
    uniform float maxBlur;
    uniform float near;
    uniform float far;
    uniform vec2 uResolution;

    varying vec2 vUv;

    // 深度值转换
    float getLinearDepth(vec2 uv) {
      float depth = texture2D(tDepth, uv).r;
      return (2.0 * near) / (far + near - depth * (far - near));
    }

    // 计算模糊半径
    float getBlurRadius(float depth) {
      float coc = abs(depth - focus) * aperture;
      return clamp(coc, 0.0, maxBlur);
    }

    void main() {
      float centerDepth = getLinearDepth(vUv);
      float radius = getBlurRadius(centerDepth);

      vec2 texelSize = 1.0 / uResolution;
      vec4 color = vec4(0.0);
      float totalWeight = 0.0;

      // 圆形采样
      int samples = 16;
      float angleStep = 2.0 * 3.14159 / float(samples);

      for (int i = 0; i < samples; i++) {
        float angle = float(i) * angleStep;
        vec2 offset = vec2(cos(angle), sin(angle)) * radius * texelSize;

        for (float r = 0.5; r <= 2.5; r += 1.0) {
          vec2 sampleUV = vUv + offset * r;
          float weight = 1.0 / r;

          color += texture2D(tDiffuse, sampleUV) * weight;
          totalWeight += weight;
        }
      }

      color /= totalWeight;
      gl_FragColor = color;
    }
  `
};

1.3.2. 屏幕空间环境光遮蔽 (SSAO)

javascript

const SSAOShader = {
  uniforms: {
    "tDiffuse": { value: null },
    "tDepth": { value: null },
    "tNormal": { value: null },  // 法线纹理
    "uKernel": { value: [] },
    "uNoise": { value: null },
    "uRadius": { value: 0.5 },
    "uBias": { value: 0.025 },
    "uPower": { value: 2.0 },
    "near": { value: 0.1 },
    "far": { value: 1000.0 }
  },

  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform sampler2D tDepth;
    uniform sampler2D tNormal;
    uniform sampler2D uNoise;
    uniform float uRadius;
    uniform float uBias;
    uniform float uPower;
    uniform float near;
    uniform float far;
    uniform vec2 uResolution;
    uniform vec3 uKernel[64];

    varying vec2 vUv;

    // 重建世界位置
    vec3 getViewPosition(vec2 uv, float depth) {
      vec2 ndc = uv * 2.0 - 1.0;
      vec4 clipPos = vec4(ndc, depth * 2.0 - 1.0, 1.0);
      vec4 viewPos = inverse(projectionMatrix) * clipPos;
      return viewPos.xyz / viewPos.w;
    }

    void main() {
      float depth = texture2D(tDepth, vUv).r;
      vec3 viewPos = getViewPosition(vUv, depth);
      vec3 normal = normalize(texture2D(tNormal, vUv).xyz * 2.0 - 1.0);

      // 随机旋转
      vec2 noiseScale = uResolution / 4.0;
      vec3 randomVec = texture2D(uNoise, vUv * noiseScale).xyz;

      // 创建切线空间矩阵
      vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
      vec3 bitangent = cross(normal, tangent);
      mat3 tbn = mat3(tangent, bitangent, normal);

      float occlusion = 0.0;
      int kernelSize = 64;

      for (int i = 0; i < kernelSize; i++) {
        // 获取采样位置
        vec3 samplePos = tbn * uKernel[i];
        samplePos = viewPos + samplePos * uRadius;

        // 投影到屏幕空间
        vec4 offset = projectionMatrix * vec4(samplePos, 1.0);
        offset.xyz /= offset.w;
        offset.xyz = offset.xyz * 0.5 + 0.5;

        // 获取采样深度
        float sampleDepth = getViewPosition(offset.xy, texture2D(tDepth, offset.xy).r).z;

        // 范围检查
        float rangeCheck = smoothstep(0.0, 1.0, uRadius / abs(viewPos.z - sampleDepth));

        // 累加遮蔽
        occlusion += (sampleDepth >= samplePos.z + uBias ? 1.0 : 0.0) * rangeCheck;
      }

      occlusion = 1.0 - (occlusion / float(kernelSize));
      occlusion = pow(occlusion, uPower);

      vec4 color = texture2D(tDiffuse, vUv);
      gl_FragColor = vec4(color.rgb * occlusion, color.a);
    }
  `
};

1.3.3. 动态模糊 (Motion Blur)

javascript

const MotionBlurShader = {
  uniforms: {
    "tDiffuse": { value: null },
    "tDepth": { value: null },
    "uVelocity": { value: null },  // 速度纹理
    "uBlurSize": { value: 10.0 },
    "uDeltaTime": { value: 0.016 }
  },

  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform sampler2D tDepth;
    uniform sampler2D uVelocity;
    uniform float uBlurSize;
    uniform float uDeltaTime;
    uniform vec2 uResolution;

    varying vec2 vUv;

    void main() {
      // 获取速度
      vec2 velocity = texture2D(uVelocity, vUv).rg;
      velocity *= uBlurSize * uDeltaTime;

      // 计算模糊
      vec4 color = texture2D(tDiffuse, vUv);

      if (length(velocity) > 0.0) {
        int samples = 8;

        for (int i = 1; i <= samples; i++) {
          float weight = 1.0 / float(samples);
          vec2 offset = velocity * (float(i) / float(samples));

          color += texture2D(tDiffuse, vUv + offset) * weight;
          color += texture2D(tDiffuse, vUv - offset) * weight;
        }

        color /= float(samples * 2 + 1);
      }

      gl_FragColor = color;
    }
  `
};

1.4. 着色器优化技巧

1.4.1. LOD (Level of Detail) 优化

javascript

class AdaptiveShader {
  constructor() {
    this.shaders = {
      high: HighQualityShader,
      medium: MediumQualityShader,
      low: LowQualityShader
    };

    this.currentQuality = 'high';
  }

  updateQuality(fps) {
    if (fps < 30) {
      this.currentQuality = 'low';
    } else if (fps < 45) {
      this.currentQuality = 'medium';
    } else {
      this.currentQuality = 'high';
    }
  }

  getShader() {
    return this.shaders[this.currentQuality];
  }
}

1.4.2. 使用 RenderTarget 缓存

javascript

class MultiPassEffect {
  constructor(renderer) {
    this.renderer = renderer;

    // 创建多个渲染目标
    this.renderTargets = [
      new THREE.WebGLRenderTarget(width, height, {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        format: THREE.RGBAFormat
      }),
      new THREE.WebGLRenderTarget(width, height, {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        format: THREE.RGBAFormat
      })
    ];

    this.currentTarget = 0;
  }

  pingPong() {
    this.currentTarget = 1 - this.currentTarget;
    return this.renderTargets[this.currentTarget];
  }

  render(pass) {
    const readTarget = this.renderTargets[this.currentTarget];
    const writeTarget = this.pingPong();

    pass.uniforms.tDiffuse.value = readTarget.texture;
    this.renderer.setRenderTarget(writeTarget);
    pass.render(this.renderer, writeTarget, 0);

    return writeTarget.texture;
  }
}

1.5. 调试和性能分析

1.5.1. 着色器调试工具

javascript

class ShaderDebugger {
  static addDebugUI(shaderPass, gui) {
    const folder = gui.addFolder('Shader调试');

    // 添加所有uniforms到GUI
    for (const [key, uniform] of Object.entries(shaderPass.material.uniforms)) {
      if (uniform.value instanceof THREE.Vector2) {
        folder.add(uniform.value, 'x', -10, 10).name(`${key}.x`);
        folder.add(uniform.value, 'y', -10, 10).name(`${key}.y`);
      } else if (uniform.value instanceof THREE.Vector3) {
        folder.add(uniform.value, 'x', -10, 10).name(`${key}.x`);
        folder.add(uniform.value, 'y', -10, 10).name(`${key}.y`);
        folder.add(uniform.value, 'z', -10, 10).name(`${key}.z`);
      } else if (uniform.value instanceof THREE.Color) {
        folder.addColor(uniform.value, 'value').name(key);
      } else if (typeof uniform.value === 'number') {
        folder.add(uniform.value, 'value', -10, 10).name(key);
      }
    }

    // 添加性能监视
    const perf = {
      enabled: true,
      lastTime: performance.now(),
      frames: 0,
      fps: 60
    };

    folder.add(perf, 'enabled').name('启用效果');
    folder.add(perf, 'fps').listen().name('FPS');

    shaderPass.enabled = perf.enabled;

    return { folder, perf };
  }
}

这些自定义后处理着色器可以组合使用,创建复杂的视觉效果。记得根据具体需求优化性能,特别是采样次数和纹理大小。