THREE.JS后处理 EffectComposer 实现步骤

根据代码内容和截图中的步骤,以下是完整的三.js 后处理 EffectComposer 实现步骤总结:


13. 后处理 EffectComposer 实现步骤

1. 安装和引入必要的依赖

  • 确保已安装 three 及相关扩展
  • 引入必要的后处理类:
    import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
    import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
    import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass";
    import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass";
    import { SMAAPass } from "three/examples/jsm/postprocessing/SMAAPass";
    

2. 初始化渲染器与场景

  • 创建 WebGLRenderer关闭内置抗锯齿(留待后处理实现):
    const renderer = new THREE.WebGLRenderer({ 
      antialias: false,
      alpha: true 
    });
    
  • 设置渲染器尺寸和像素比
  • 创建场景、相机、控制器和基本几何体

3. 创建 EffectComposer 实例

const composer = new EffectComposer(renderer);
composer.setSize(window.innerWidth, window.innerHeight);

4. 添加 RenderPass(基础渲染通道)

  • 必须是第一个通道,负责将场景渲染到后处理缓冲区
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);

5. 添加 OutlinePass(发光描边通道)

5.1 初始化 OutlinePass
const outlinePass = new OutlinePass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  scene,
  camera
);
5.2 配置描边样式(关键调整)
outlinePass.edgeStrength = 5.0;       // 边缘强度
outlinePass.edgeGlow = 1.0;          // 边缘发光强度
outlinePass.edgeThickness = 2.0;     // 边缘厚度
outlinePass.pulsePeriod = 1.5;       // 脉动周期(秒)
outlinePass.visibleEdgeColor.set(0x00ff00); // 可见边缘颜色(绿色)
outlinePass.hiddenEdgeColor.set(0xff00ff);  // 隐藏边缘颜色(洋红色)
5.3 添加描边通道到组合器
composer.addPass(outlinePass);

6. 添加 BloomPass(发光通道)

const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  1.5, 0.4, 0.85
);
bloomPass.threshold = 0.1;
bloomPass.strength = 0.3;  // 减小强度,避免覆盖描边
bloomPass.radius = 0.1;
composer.addPass(bloomPass);

7. 添加抗锯齿通道(SMAA)

  • 必须在最后添加,用于后处理阶段的抗锯齿
const smaaPass = new SMAAPass(
  window.innerWidth * renderer.getPixelRatio(),
  window.innerHeight * renderer.getPixelRatio()
);
smaaPass.setSize(window.innerWidth, window.innerHeight);
composer.addPass(smaaPass); // 注意:SMAA必须在最后

8. 实现物体选中与描边控制

8.1 存储选中物体
const selectedObjects = ref([]);
8.2 切换描边函数
function toggleOutline(object) {
  const index = selectedObjects.value.indexOf(object);
  if (index === -1) {
    selectedObjects.value.push(object);
  } else {
    selectedObjects.value.splice(index, 1);
  }
  outlinePass.selectedObjects = selectedObjects.value;
}
8.3 清除描边
function clearOutline() {
  selectedObjects.value = [];
  outlinePass.selectedObjects = [];
}

9. 修改动画循环

  • 使用 composer.render() 替代 renderer.render()
function animate() {
  requestAnimationFrame(animate);
  // ... 更新动画
  composer.render(); // 替换原来的 renderer.render(scene, camera)
}

10. 添加响应式处理

const handleResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  composer.setSize(window.innerWidth, window.innerHeight);
  outlinePass.setSize(window.innerWidth, window.innerHeight);
  smaaPass.setSize(window.innerWidth, window.innerHeight);
};

11. 添加鼠标交互(可选)

  • 使用 Raycaster 实现点击物体切换描边
function onMouseClick(event) {
  const mouse = new THREE.Vector2();
  const raycaster = new THREE.Raycaster();
  
  // 计算标准化设备坐标
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects([cube, sphere]);
  
  if (intersects.length > 0) {
    const clickedObject = intersects[0].object;
    toggleOutline(clickedObject);
  }
}

12. 样式优化

  • 深色背景以突出描边效果
  • 控制面板样式与描边颜色呼应

重要注意事项

  1. 通道顺序很重要:RenderPass → OutlinePass → BloomPass → SMAA
  2. 关闭渲染器内置抗锯齿antialias: false
  3. SMAA必须最后添加,否则会影响其他通道效果
  4. 描边物体必须可选中:确保物体被添加到 outlinePass.selectedObjects
  5. 性能考虑:后处理通道越多,性能消耗越大

截图中的补充步骤说明

  1. gltf后处理颜色异常(伽马校正) - 如需处理颜色偏差,可在渲染器中启用伽马校正:
    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    
  2. 抗锯齿后处理 - 使用 SMAAPass 而非 FXAAPass 以获得更好效果
  3. 多通道组合 - 可灵活组合不同通道(如 GlitchPass + OutlinePass)

代码如下:

<template>
  <div class="container" ref="containerRef"></div>
  <div class="controls">
    <button @click="toggleOutline(cube)">高亮方块</button>
    <button @click="toggleOutline(sphere)">高亮球体</button>
    <button @click="clearOutline">清除高亮</button>
  </div>
</template>

<script setup>
import { onMounted, ref, onUnmounted } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass";
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass";
import { SMAAPass } from "three/examples/jsm/postprocessing/SMAAPass";

const containerRef = ref(null);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ 
  antialias: false,
  alpha: true 
});

renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

// 创建物体
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(0.5, 0.5, 0.5),
  new THREE.MeshLambertMaterial({ 
    color: 0x0000ff,
    transparent: true,
    opacity: 0.8  // 增加透明度,更容易看到描边
  })
);
cube.position.set(-1, 0, 0);
cube.name = "立方体";
scene.add(cube);

const sphere = new THREE.Mesh(
  new THREE.SphereGeometry(0.4, 32, 32),
  new THREE.MeshLambertMaterial({ 
    color: 0xff0000,
    transparent: true,
    opacity: 0.8
  })
);
sphere.position.set(1, 0, 0);
sphere.name = "球体";
scene.add(sphere);

// 添加背景平面,更容易看到效果
const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeMaterial = new THREE.MeshLambertMaterial({ 
  color: 0x333333,
  side: THREE.DoubleSide 
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.position.y = -1;
scene.add(plane);

const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 7);
scene.add(directionalLight);

camera.position.set(0, 2, 5);
camera.lookAt(0, 0, 0);

// ============ 创建后期处理组合器 ============
const composer = new EffectComposer(renderer);
composer.setSize(window.innerWidth, window.innerHeight);

// 1. 渲染通道(必须第一个)
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);

// 2. OutlinePass描边通道
const outlinePass = new OutlinePass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  scene,
  camera
);

// ============ 关键调整:增强描边效果 ============
outlinePass.edgeStrength = 5.0;       // 增加到5,更明显的边缘强度
outlinePass.edgeGlow = 1.0;          // 增加到1,更强的边缘发光
outlinePass.edgeThickness = 2.0;     // 增加到2,更粗的描边
outlinePass.pulsePeriod = 1.5;       // 脉动周期1.5秒
outlinePass.visibleEdgeColor.set(0x00ff00); // 绿色
outlinePass.hiddenEdgeColor.set(0xff00ff);  // 改为洋红色,更容易区分

// 可选:使用不同的颜色模式
// outlinePass.usePatternTexture = true; // 使用纹理模式
// if (outlinePass.usePatternTexture) {
//   const textureLoader = new THREE.TextureLoader();
//   outlinePass.patternTexture = textureLoader.load('textures/tripattern.jpg');
// }

composer.addPass(outlinePass);

// 3. Bloom发光通道
const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  1.5, 0.4, 0.85
);
bloomPass.threshold = 0.1;
bloomPass.strength = 0.3;  // 减小强度,避免覆盖描边
bloomPass.radius = 0.1;
composer.addPass(bloomPass);

// 4. SMAA抗锯齿通道(必须最后添加!)
const smaaPass = new SMAAPass(
  window.innerWidth * renderer.getPixelRatio(),
  window.innerHeight * renderer.getPixelRatio()
);
smaaPass.setSize(window.innerWidth, window.innerHeight);
composer.addPass(smaaPass); // 注意:SMAA必须在最后

// 存储选中的物体
const selectedObjects = ref([]);

// 高亮控制函数
function toggleOutline(object) {
  const index = selectedObjects.value.indexOf(object);
  if (index === -1) {
    selectedObjects.value.push(object);
  } else {
    selectedObjects.value.splice(index, 1);
  }
  outlinePass.selectedObjects = selectedObjects.value;
  console.log('当前选中物体:', selectedObjects.value.map(obj => obj.name));
}

function clearOutline() {
  selectedObjects.value = [];
  outlinePass.selectedObjects = [];
  console.log('清除所有描边');
}

// 动画循环
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  sphere.rotation.y += 0.01;
  composer.render();
}

// 响应式调整大小
const handleResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  composer.setSize(window.innerWidth, window.innerHeight);
  outlinePass.setSize(window.innerWidth, window.innerHeight);
  smaaPass.setSize(window.innerWidth, window.innerHeight);
};

let controls;

onMounted(() => {
  controls = new OrbitControls(camera, containerRef.value);
  controls.enableDamping = true;
  containerRef.value.appendChild(renderer.domElement);
  window.addEventListener('resize', handleResize);
  
  // 测试:默认高亮球体
  setTimeout(() => {
    toggleOutline(sphere);
    console.log('默认高亮球体');
  }, 1000);
  
  // 添加点击事件,点击物体切换高亮
  window.addEventListener('click', onMouseClick);
});

// 鼠标点击检测
function onMouseClick(event) {
  const mouse = new THREE.Vector2();
  const raycaster = new THREE.Raycaster();
  
  // 计算标准化设备坐标
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  
  // 更新射线
  raycaster.setFromCamera(mouse, camera);
  
  // 检测物体
  const objects = [cube, sphere];
  const intersects = raycaster.intersectObjects(objects);
  
  if (intersects.length > 0) {
    const clickedObject = intersects[0].object;
    toggleOutline(clickedObject);
  }
}

onUnmounted(() => {
  window.removeEventListener('resize', handleResize);
  window.removeEventListener('click', onMouseClick);
  if (controls) controls.dispose();
  renderer.dispose();
});

animate();
</script>

<style scoped>
.container {
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  overflow: hidden;
  position: relative;
  background: #1a1a1a; /* 深色背景,更容易看到描边 */
}

.controls {
  position: absolute;
  top: 20px;
  left: 20px;
  z-index: 100;
  display: flex;
  gap: 10px;
}

.controls button {
  padding: 8px 16px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  border: 1px solid #00ff00; /* 绿色边框,与描边呼应 */
  border-radius: 4px;
  cursor: pointer;
  font-family: sans-serif;
  font-weight: bold;
}

.controls button:hover {
  background: rgba(0, 255, 0, 0.2);
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值