You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

THREE.js技术问询:如何通过Bloom后期处理调整场景背景及悬停物体发光时背景异常的解决方法

解决Three.js Bloom后期处理导致白色背景异常的问题

Hey there! 我看你遇到了Three.js里Bloom后期处理和白色背景冲突的问题——这是因为当前渲染流程会把白色背景也纳入Bloom的计算范围,叠加后就会让背景出现过曝或者异常的情况。别担心,咱们改两处地方就能完美解决:

问题根源

当渲染Bloom通道时,整个场景(包括白色背景)都会被渲染到bloomComposer中,之后和原场景叠加时,背景的白色被Bloom处理后就会出现视觉异常。

核心解决步骤

  1. 渲染Bloom时临时隐藏背景:在renderBloom函数里,先保存原背景颜色,渲染Bloom前临时将背景设为黑色,渲染完成后再恢复白色背景,避免Bloom处理背景。
  2. 优化鼠标悬停的图层控制:原来直接操作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

火山引擎 最新活动