EffectComposer + ShaderPass 实现分区特效
一、实现步骤
在EffectComposer + Layers 实现分区特效中,EffectComposer更像是渲染出一张有辉光特效的“背景图”,renderer在这张“背景图”上渲染正常方块,以至于无论相机在哪个角度,都看到正常方块绘制在发光方块之上。针对这个问题,参考threejs提供的案例,使用ShaderPass实现分区辉光效果。步骤如下:
1、创建两个效果组合器bloomComposer和finalComposer,其中bloomComposer用来生成辉光效果,并将其渲染的结果作为着色器材质的输入,传递给finalComposer。finalComposer则用来渲染整个场景,在材质通道ShaderPass中将辉光效果整合到scene内。
const initComposer = () => { bloomComposer = new EffectComposer(renderer); bloomComposer.renderToScreen = false; 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; bloomComposer.addPass(renderScene); bloomComposer.addPass(bloomPass); finalComposer = new EffectComposer(renderer); const finalShader = new THREE.ShaderMaterial({ uniforms: { baseTexture: { value: null }, bloomTexture: { value: bloomComposer.renderTarget2.texture } }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } `, fragmentShader: ` uniform sampler2D baseTexture; uniform sampler2D bloomTexture; varying vec2 vUv; void main() { gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) ); } `, defines: {} }); const finalPass = new ShaderPass(finalShader, "baseTexture"); finalPass.needsSwap = true; finalComposer.addPass(renderScene); finalComposer.addPass(finalPass); };
2、创建正常方块和发光方块,并为它们设置不同的渲染图层,在渲染的过程钟将相机设置到对应图层进行渲染。
也可以像threejs案例中那样,在渲染发光物体时将非发光物体设置为黑色,这种方式对辉光效果有作用,因为黑色的物体不会产生辉光效果。对其他后处理效果就不一定适用了。
3、先用bloomComposer渲染出辉光效果,再使用finalComposer渲染场景。
const render = () => { renderer.autoClear = false; renderer.clear(); camera.layers.set(1); bloomComposer.render(); renderer.clearDepth(); // 清除深度缓存 camera.layers.set(0); finalComposer.render(scene, camera); requestAnimationFrame(render); };
二、效果
三、完整代码
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"; import { ShaderPass } from "../node_modules/three/examples/jsm/postprocessing/ShaderPass.js"; let scene, camera, renderer, bloomComposer, finalComposer; 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 = () => { bloomComposer = new EffectComposer(renderer); bloomComposer.renderToScreen = false; 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; bloomComposer.addPass(renderScene); bloomComposer.addPass(bloomPass); finalComposer = new EffectComposer(renderer); const finalShader = new THREE.ShaderMaterial({ uniforms: { baseTexture: { value: null }, bloomTexture: { value: bloomComposer.renderTarget2.texture } }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } `, fragmentShader: ` uniform sampler2D baseTexture; uniform sampler2D bloomTexture; varying vec2 vUv; void main() { gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) ); } `, defines: {} }); const finalPass = new ShaderPass(finalShader, "baseTexture"); finalPass.needsSwap = true; finalComposer.addPass(renderScene); finalComposer.addPass(finalPass); }; 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); bloomComposer.render(); renderer.clearDepth(); // 清除深度缓存 camera.layers.set(0); finalComposer.render(scene, camera); requestAnimationFrame(render); }; init(); initComposer(); main(); render();