根据代码内容和截图中的步骤,以下是完整的三.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. 样式优化
- 深色背景以突出描边效果
- 控制面板样式与描边颜色呼应
重要注意事项
- 通道顺序很重要:RenderPass → OutlinePass → BloomPass → SMAA
- 关闭渲染器内置抗锯齿:
antialias: false - SMAA必须最后添加,否则会影响其他通道效果
- 描边物体必须可选中:确保物体被添加到
outlinePass.selectedObjects - 性能考虑:后处理通道越多,性能消耗越大
截图中的补充步骤说明
- gltf后处理颜色异常(伽马校正) - 如需处理颜色偏差,可在渲染器中启用伽马校正:
renderer.outputEncoding = THREE.sRGBEncoding; renderer.toneMapping = THREE.ACESFilmicToneMapping; - 抗锯齿后处理 - 使用
SMAAPass而非FXAAPass以获得更好效果 - 多通道组合 - 可灵活组合不同通道(如 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>
1525

被折叠的 条评论
为什么被折叠?



