THREE.js技术问询:如何通过Bloom后期处理调整场景背景及悬停物体发光时背景异常的解决方法
解决Three.js Bloom后期处理导致白色背景异常的问题
Hey there! 我看你遇到了Three.js里Bloom后期处理和白色背景冲突的问题——这是因为当前渲染流程会把白色背景也纳入Bloom的计算范围,叠加后就会让背景出现过曝或者异常的情况。别担心,咱们改两处地方就能完美解决:
问题根源
当渲染Bloom通道时,整个场景(包括白色背景)都会被渲染到bloomComposer中,之后和原场景叠加时,背景的白色被Bloom处理后就会出现视觉异常。
核心解决步骤
- 渲染Bloom时临时隐藏背景:在
renderBloom函数里,先保存原背景颜色,渲染Bloom前临时将背景设为黑色,渲染完成后再恢复白色背景,避免Bloom处理背景。 - 优化鼠标悬停的图层控制:原来直接操作
scene.children[2]的方式依赖固定索引,容易出错,改成遍历所有物体重置图层,再给选中的物体单独设置Bloom图层。
修改后的完整代码
<script type="x-shader/x-vertex" id="vertexshader"> varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } </script> <script type="x-shader/x-fragment" id="fragmentshader"> uniform sampler2D baseTexture; uniform sampler2D bloomTexture; varying vec2 vUv; void main() { gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) ); } </script> <script type="importmap"> { "imports": { "three": "./build/three.module.js" } } </script> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from './jsm/controls/OrbitControls.js'; import Stats from './jsm/libs/stats.module.js'; import { GUI } from './jsm/libs/lil-gui.module.min.js'; import { EffectComposer } from './jsm/postprocessing/EffectComposer.js'; import { RenderPass } from './jsm/postprocessing/RenderPass.js'; import { UnrealBloomPass } from './jsm/postprocessing/UnrealBloomPass.js'; import { FBXLoader } from './Loader/FBXLoader/FBXLoader.js'; import { OBJLoader } from './Loader/OBJLoader.js'; import { MTLLoader } from './Loader/MTLLoader.js' import { GLTFLoader } from "./Loader/GLTFLoader.js"; import { ShaderPass } from './jsm/postprocessing/ShaderPass.js'; const ENTIRE_SCENE = 0, BLOOM_SCENE = 1; const bloomLayer = new THREE.Layers(); bloomLayer.set(BLOOM_SCENE); let params = { exposure: 1, bloomThreshold: 0.41, bloomStrength: 0.66, bloomRadius: 0.05, scene: 'Scene with Glow' }; //set MeshBasicMaterial const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' }); const materials = {}; /*--------renderer--------*/ const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); renderer.shadowMap.type = THREE.PCFSoftShadowMap; //add scene const scene = new THREE.Scene(); /*--------camera--------*/ const camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 100000000); camera.position.set(0, 10, 0); camera.lookAt(0, 0, 0); /*--------OrbitControls--------*/ let controls = new OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.enablePan = false; controls.maxPolarAngle = Math.PI / 2; controls.enableDamping = true; //Render 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; const bloomComposer = new EffectComposer(renderer); bloomComposer.renderToScreen = false; bloomComposer.addPass(renderScene); bloomComposer.addPass(bloomPass); const finalPass = new ShaderPass( new THREE.ShaderMaterial({ uniforms: { baseTexture: { value: null }, bloomTexture: { value: bloomComposer.renderTarget2.texture } }, vertexShader: document.getElementById('vertexshader').textContent, fragmentShader: document.getElementById('fragmentshader').textContent, defines: {} }), 'baseTexture' ); finalPass.needsSwap = true; const finalComposer = new EffectComposer(renderer); finalComposer.addPass(renderScene); finalComposer.addPass(finalPass); /*--------Stats--------*/ const stats = Stats(); stats.showPanel(0); document.body.appendChild(stats.dom); /*--------AmbientLight-1--------*/ scene.add(new THREE.AmbientLight(0xffffff, 1)); //light1 const light1 = new THREE.PointLight(0xddffdd, 1); light1.position.z = 0; light1.position.y = 300; light1.position.x = 200; scene.add(light1); //GUI const gui = new GUI(); gui.add(params, 'scene', ['Scene with Glow', 'Scene only']).onChange(function (value) { switch (value) { case 'Scene with Glow': bloomComposer.renderToScreen = false; break; case 'Scene only': // nothing to do break; } }); const folder = gui.addFolder('Bloom Parameters'); folder.add(params, 'exposure', 0.1, 2).onChange(function (value) { renderer.toneMappingExposure = Math.pow(value, 4.0); }); folder.add(params, 'bloomThreshold', 0.0, 1.0).onChange(function (value) { bloomPass.threshold = Number(value); }); folder.add(params, 'bloomStrength', 0.0, 10.0).onChange(function (value) { bloomPass.strength = Number(value); }); folder.add(params, 'bloomRadius', 0.0, 1.0).step(0.01).onChange(function (value) { bloomPass.radius = Number(value); }); /*--------scene.background--------*/ scene.background = new THREE.Color(0xffffff); /*--------cube--------*/ const geometry = new THREE.BoxGeometry(4, 4, 4); const material = new THREE.MeshBasicMaterial({ color: 0x000050 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); /*--------raycaster--------*/ const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); window.addEventListener('mousemove', onMouseMove); function onMouseMove(event) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(scene.children); // 重置所有Mesh到ENTIRE_SCENE图层 scene.traverse(obj => { if (obj.isMesh) { obj.layers.set(ENTIRE_SCENE); } }); if (intersects.length > 0) { // 给选中的物体设置BLOOM_SCENE图层 intersects[0].object.layers.set(BLOOM_SCENE); } } //window.onresize window.onresize = function () { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }; function disposeMaterial(obj) { if (obj.material) { obj.material.dispose(); } } //render function render() { switch (params.scene) { case 'Scene only': renderer.render(scene, camera); break; case 'Scene with Glow': default: // render scene with bloom renderBloom(true); // render the entire scene, then render bloom scene on top finalComposer.render(); break; } } function renderBloom(mask) { // 保存原背景颜色 const originalBackground = scene.background.clone(); if (mask === true) { // 临时将背景设为黑色,避免Bloom处理白色背景 scene.background = new THREE.Color(0x000000); scene.traverse(darkenNonBloomed); bloomComposer.render(); scene.traverse(restoreMaterial); // 恢复原背景颜色 scene.background = originalBackground; } else { camera.layers.set(BLOOM_SCENE); bloomComposer.render(); camera.layers.set(ENTIRE_SCENE); } } function darkenNonBloomed(obj) { if (obj.isMesh && bloomLayer.test(obj.layers) === false) { materials[obj.uuid] = obj.material; obj.material = darkMaterial; } } function restoreMaterial(obj) { if (materials[obj.uuid]) { obj.material = materials[obj.uuid]; delete materials[obj.uuid]; } } function animate() { stats.update(); render(); requestAnimationFrame(animate); }; animate(); </script>
这样修改后,Bloom效果只会作用在鼠标悬停的物体上,白色背景不会被Bloom处理,就能正常显示啦!
内容的提问来源于stack exchange,提问作者jImmy




