1. Three.js 后期处理性能考量与最佳实践

1.1. 一、性能考量要点

1. 渲染目标(RenderTarget)管理

javascript

// 避免频繁创建/销毁RenderTarget
const rt = new THREE.WebGLRenderTarget(width, height, {
  minFilter: THREE.LinearFilter,
  magFilter: THREE.LinearFilter,
  format: THREE.RGBAFormat
});

// 复用RenderTarget
function updateRenderTargetSize(rt, width, height) {
  if (rt.width !== width || rt.height !== height) {
    rt.setSize(width, height);
  }
}

2. 分辨率优化

javascript

// 降低后处理分辨率(质量vs性能权衡)
const pixelRatio = renderer.getPixelRatio();
const downSampleRatio = 0.5; // 50%分辨率

const postProcessingTarget = new THREE.WebGLRenderTarget(
  Math.floor(width * downSampleRatio * pixelRatio),
  Math.floor(height * downSampleRatio * pixelRatio)
);

3. Pass管理策略

javascript

class OptimizedEffectComposer extends THREE.EffectComposer {
  constructor(renderer, renderTarget) {
    super(renderer, renderTarget);
    this.enabledPasses = new Set();
  }

  addPass(pass) {
    super.addPass(pass);
    this.enabledPasses.add(pass);
    pass.enabled = true;
  }

  setPassEnabled(pass, enabled) {
    if (this.enabledPasses.has(pass)) {
      pass.enabled = enabled;
    }
  }
}

1.2. 二、最佳实践

1. Pass合并与优化

javascript

// 合并多个效果到单个ShaderPass
const customShader = {
  uniforms: {
    tDiffuse: { value: null },
    uBloomStrength: { value: 0.5 },
    uVignetteStrength: { value: 0.8 },
    uChromaticAberration: { value: 0.005 }
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float uBloomStrength;
    uniform float uVignetteStrength;
    uniform float uChromaticAberration;
    varying vec2 vUv;

    void main() {
      // 合并多个效果
      vec2 uv = vUv;
      vec2 center = vec2(0.5);

      // 色差效果
      float r = texture2D(tDiffuse, uv + (center - uv) * uChromaticAberration).r;
      float g = texture2D(tDiffuse, uv).g;
      float b = texture2D(tDiffuse, uv - (center - uv) * uChromaticAberration).b;

      vec3 color = vec3(r, g, b);

      // 渐晕效果
      float vignette = 1.0 - distance(uv, center) * uVignetteStrength;
      color *= vignette;

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

2. 动态质量调节

javascript

class AdaptivePostProcessing {
  constructor(composer) {
    this.composer = composer;
    this.qualityMode = 'high'; // high, medium, low
    this.frameTimeHistory = [];
    this.targetFPS = 60;
  }

  updateQuality() {
    const avgFrameTime = this.getAverageFrameTime();
    const currentFPS = 1000 / avgFrameTime;

    if (currentFPS < this.targetFPS * 0.8) {
      this.lowerQuality();
    } else if (currentFPS > this.targetFPS * 1.2) {
      this.raiseQuality();
    }
  }

  lowerQuality() {
    switch(this.qualityMode) {
      case 'high':
        this.setBloomStrength(0.5);
        this.setAAEnabled(false);
        this.qualityMode = 'medium';
        break;
      case 'medium':
        this.setDownSampleRatio(0.75);
        this.qualityMode = 'low';
        break;
    }
  }

  raiseQuality() {
    switch(this.qualityMode) {
      case 'low':
        this.setDownSampleRatio(1.0);
        this.qualityMode = 'medium';
        break;
      case 'medium':
        this.setBloomStrength(1.0);
        this.setAAEnabled(true);
        this.qualityMode = 'high';
        break;
    }
  }
}

3. 选择性渲染

javascript

// 只对需要后处理的物体进行渲染
const postProcessScene = new THREE.Scene();
const postProcessCamera = new THREE.Camera();

// 在主渲染时标记需要后处理的物体
function renderSelectively() {
  // 第一次渲染:不需要后处理的物体
  renderer.setRenderTarget(null);
  scene.traverse(obj => {
    if (obj.userData.skipPostProcessing) {
      obj.visible = true;
    } else {
      obj.visible = false;
    }
  });
  renderer.render(scene, camera);

  // 第二次渲染:需要后处理的物体到RenderTarget
  renderer.setRenderTarget(postProcessTarget);
  scene.traverse(obj => {
    if (!obj.userData.skipPostProcessing) {
      obj.visible = true;
    } else {
      obj.visible = false;
    }
  });
  renderer.render(scene, camera);

  // 应用后处理
  composer.setRenderTarget(postProcessTarget);
  composer.render();
}

4. GPU Instancing与后处理结合

javascript

// 对于大量重复物体,使用Instancing减少draw calls
const geometry = new THREE.InstancedBufferGeometry();
// ... 设置geometry

const material = new THREE.ShaderMaterial({
  uniforms: {
    uPostProcessingData: { value: new THREE.Vector4() }
  },
  vertexShader: `
    attribute vec3 instanceOffset;
    attribute float instanceScale;

    void main() {
      vec3 pos = position * instanceScale + instanceOffset;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
  `,
  fragmentShader: `
    uniform vec4 uPostProcessingData;

    void main() {
      // 为后处理输出额外数据
      gl_FragColor = vec4(color, uPostProcessingData.x);
    }
  `
});

1.3. 优化技巧

1. Texture复用

javascript

const texturePool = new Map();

function getTexture(key, width, height) {
  if (!texturePool.has(key)) {
    texturePool.set(key, new THREE.WebGLRenderTarget(width, height));
  }
  return texturePool.get(key);
}

2. Ping-pong渲染

javascript

class PingPongRender {
  constructor(renderer, width, height) {
    this.renderTargetA = new THREE.WebGLRenderTarget(width, height);
    this.renderTargetB = new THREE.WebGLRenderTarget(width, height);
    this.currentTarget = this.renderTargetA;
    this.previousTarget = this.renderTargetB;
  }

  swap() {
    [this.currentTarget, this.previousTarget] = 
      [this.previousTarget, this.currentTarget];
  }

  getCurrent() { return this.currentTarget; }
  getPrevious() { return this.previousTarget; }
}

3. 延迟渲染与后处理

javascript

// 使用G-Buffer进行延迟渲染
const gBuffer = {
  position: new THREE.WebGLRenderTarget(width, height, {
    format: THREE.RGBAFormat,
    type: THREE.FloatType
  }),
  normal: new THREE.WebGLRenderTarget(width, height, {
    format: THREE.RGBAFormat
  }),
  albedo: new THREE.WebGLRenderTarget(width, height, {
    format: THREE.RGBAFormat
  })
};

// 延迟后处理Shader
const deferredPostShader = {
  uniforms: {
    tPosition: { value: null },
    tNormal: { value: null },
    tAlbedo: { value: null },
    // ... 其他uniforms
  },
  // ... shader代码
};

1.4. 性能监控

javascript

class PostProcessingProfiler {
  constructor(composer) {
    this.composer = composer;
    this.passTimings = new Map();
    this.frameStart = 0;
  }

  startFrame() {
    this.frameStart = performance.now();
  }

  measurePass(pass, callback) {
    const start = performance.now();
    callback();
    const duration = performance.now() - start;

    if (!this.passTimings.has(pass)) {
      this.passTimings.set(pass, []);
    }

    const timings = this.passTimings.get(pass);
    timings.push(duration);

    // 保持最近100帧的数据
    if (timings.length > 100) {
      timings.shift();
    }

    return duration;
  }

  getPassAverage(pass) {
    const timings = this.passTimings.get(pass);
    if (!timings || timings.length === 0) return 0;

    return timings.reduce((a, b) => a + b) / timings.length;
  }

  logPerformance() {
    console.group('Post Processing Performance');
    this.composer.passes.forEach(pass => {
      const avg = this.getPassAverage(pass);
      console.log(`${pass.constructor.name}: ${avg.toFixed(2)}ms`);
    });
    console.groupEnd();
  }
}

1.5. 推荐的工作流程

  1. 开发阶段:使用全质量后处理

  2. 测试阶段:启用性能监控,识别瓶颈

  3. 优化阶段

  4. 发布阶段

1.6. 设备能力检测

javascript

const capabilities = {
  isWebGL2: typeof WebGL2RenderingContext !== 'undefined',
  maxTextureSize: renderer.capabilities.maxTextureSize,
  maxPrecision: renderer.capabilities.precision,
  floatTextures: renderer.extensions.has('OES_texture_float'),
  drawBuffers: renderer.extensions.has('WEBGL_draw_buffers')
};

function getRecommendedQuality(capabilities) {
  if (capabilities.isWebGL2 && 
      capabilities.maxTextureSize >= 4096 &&
      capabilities.floatTextures) {
    return 'high';
  } else if (capabilities.maxTextureSize >= 2048) {
    return 'medium';
  }
  return 'low';
}

这些实践可以帮助你在保持视觉效果的同时,确保Three.js后处理的性能优化。关键是根据具体应用场景平衡视觉效果和性能消耗。