EffectComposer + Layers 实现分区特效
一、Layers
网格模型对象Mesh、相机对象Camera、组对象Group等都具有从基类Object3D继承的图层属性.layers,图层属性.layers的属性值是图层对象THREE.Layers。
图层对象Layers可以分配32个编号,编号为0-31,通过图层对象.set()方法可以设置图层对象编号。.mask属性表示当前图层对象与0-31中的某个图层相对应,所属图层所对应的比特位为1,其他位为0.
执行图层对象的.set()方法,改变的是图层对象的.mask属性,例如:设置.set(0),.mask属性值是1;设置.set(1),.mask属性值是2;设置.set(2),.mask属性值是4;设置.set(3),.mask属性值是8......
.set()方法的参数可以理解为二进制中右侧1向左平移的位数,得到的值赋给图层对象的.mask属性。
除了.set()方法外,还可以使用.enable()方法给图层对象的.mask属性赋值。二者的区别是,.set()会删除图层对象已有的所有对应关系,增加与参数指定的图层的对应关系。而.enable()只是增加图层对象与参数指定图层的对应关系。
二、EffectComposer
EffectComposer(效果组合器)用于在three.js中实现后期处理效果。该类管理了产生最终视觉效果的后期处理过程链。后处理过程根据它们添加的顺序来执行,最后一个过程会被自动渲染到屏幕上。
设置后期处理的步骤:
1、创建THREE.EffectComposer对象
2、配置EffectComposer对象,在该对象上添加后期处理通道
3、在render循环中,使用EffectComposer来渲染场景、应用通道,并输出结果。
三、EffectComposer + Layers
以辉光效果为例,使用EffectComposer + Layers实现分区辉光效果。步骤如下:
1、创建EffectComposer,首先在效果处理器中添加RenderPass,这个通道会渲染场景,但不会将渲染结果输出到屏幕上。然后添加UnrealBloomPass渲染通道,实现辉光效果。
const initComposer = () => { composer = new EffectComposer(renderer); const renderScene = new RenderPass(scene, camera); // 辉光效果 const bloomPass = new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85 ); bloomPass.threshold = params.bloomThreshold; bloomPass.strength = params.bloomStrength; bloomPass.radius = params.bloomRadius; composer.addPass(renderScene); composer.addPass(bloomPass); };
2、创建正常方块和发光方块,并为它们设置不同的渲染图层。
const main = () => { const geometry = new THREE.BoxGeometry(20, 20, 10); // 正常方块 const normalMtl = new THREE.MeshLambertMaterial({ color: 0x00ffff }); const normalBox = new THREE.Mesh(geometry, normalMtl); normalBox.position.z = -5; normalBox.layers.set(0); scene.add(normalBox); // 发光方块 const bloomMtl = new THREE.MeshLambertMaterial({ color: 0xff5500 }); const bloomBox = new THREE.Mesh(geometry, bloomMtl); bloomBox.position.z = 5; bloomBox.layers.set(1); scene.add(bloomBox); };
3、先用EffectComposer渲染出辉光效果,再使用renderer渲染正常方块。
const render = () => { renderer.autoClear = false; renderer.clear(); camera.layers.set(1); composer.render(); renderer.clearDepth(); // 清除深度缓存 camera.layers.set(0); renderer.render(scene, camera); requestAnimationFrame(render); };
四、注意:
1、使用layers分层渲染时,只有当渲染目标与camera处于同一层时才会被渲染,所以需要用camera.layers.set()来改变相机的图层,从而渲染不同图层对象上的物体。
2、renderer.autoClear属性定义渲染器是否在渲染每一帧之前自动清除其输出,默认为true。需要将.autoClear属性设置为false,否则renderer.render(scene, camera);会清除composer.render();创建的辉光效果。
3、与threejs提供的案例不同,案例中是将不同图层上的物体渲染到scene上,将所有物体放到同一图层进行渲染的效果跟分层渲染的效果是一样的,具体体现在物体的渲染深度上。而EffectComposer更像是渲染出一张有辉光特效的“背景图”,renderer在这张“背景图”上渲染正常方块,以至于无论相机在哪个角度,都看到正常方块绘制在发光方块之上。解决方案参考EffectComposer +ShaderPass 实现分区特效。
五、完整代码
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> * { margin: 0; padding: 0; } </style> </head> <body> <script src="./src/index.js"></script> </body> </html>
index.js
import * as THREE from "../node_modules/three/build/three.module.js"; import { OrbitControls } from "../node_modules/three/examples/jsm/controls/OrbitControls.js"; import { EffectComposer } from "../node_modules/three/examples/jsm/postprocessing/EffectComposer.js"; import { UnrealBloomPass } from "../node_modules/three/examples/jsm/postprocessing/UnrealBloomPass.js"; import { RenderPass } from "../node_modules/three/examples/jsm/postprocessing/RenderPass.js"; let scene, camera, renderer, composer; const params = { exposure: 0, bloomStrength: 1.5, bloomThreshold: 0, bloomRadius: 0, }; const init = () => { // 场景 scene = new THREE.Scene(); // 相机 camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 100000 ); camera.position.set(50, 50, 50); camera.position.y = 50; // 渲染器 renderer = new THREE.WebGLRenderer({ antialias: true, }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 环境光 const light = new THREE.AmbientLight(0xffffff, 0.6); light.layers.enable(0); light.layers.enable(1); scene.add(light); // 控制器 const controls = new OrbitControls(camera, renderer.domElement); scene.add(new THREE.AxesHelper(100)); window.onresize = () => { renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); }; }; const initComposer = () => { composer = new EffectComposer(renderer); const renderScene = new RenderPass(scene, camera); // 光晕 const bloomPass = new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85 ); bloomPass.threshold = params.bloomThreshold; bloomPass.strength = params.bloomStrength; bloomPass.radius = params.bloomRadius; composer.addPass(renderScene); composer.addPass(bloomPass); }; const main = () => { const geometry = new THREE.BoxGeometry(20, 20, 10); // 正常方块 const normalMtl = new THREE.MeshLambertMaterial({ color: 0x00ffff }); const normalBox = new THREE.Mesh(geometry, normalMtl); normalBox.position.z = -5; normalBox.layers.set(0); scene.add(normalBox); // 发光方块 const bloomMtl = new THREE.MeshLambertMaterial({ color: 0xff5500 }); const bloomBox = new THREE.Mesh(geometry, bloomMtl); bloomBox.position.z = 5; bloomBox.layers.set(1); scene.add(bloomBox); }; const render = () => { renderer.autoClear = false; renderer.clear(); camera.layers.set(1); composer.render(); renderer.clearDepth(); // 清除深度缓存 camera.layers.set(0); renderer.render(scene, camera); requestAnimationFrame(render); }; init(); initComposer(); main(); render();