问题描述
在我的项目中,我想显示有时带有小 LED 灯的 3d 对象。这个想法是,这些小灯需要发出某种绽放,使其看起来像是在发光。
我已尝试应用 UnrealBloom,但它被考虑用于整个场景,而不仅仅是具有实际发射值的部分(使用发射纹理贴图)。场景也变得非常模糊。
>这显然不是我想要的。我只需要红色的小 LED 灯泡就可以发光而不是整个物体。但是,我还没有找到一种方法来告诉引擎只将泛光应用到排放贴图所指向的位置。
我使用了一个非常简单的代码设置,它几乎与 UnrealBloom Example 相同:
如何正确设置发射纹理并仅使对象的发射部分发光并防止出现不切实际的闪亮表面和非常模糊的视觉效果?
UPDATE: Editable example of my setup is now available on JSFiddle!
<div id="bloom-solution" style="margin:0px; overflow:hidden;">
<div id="body">
<h1 id="info" style="
color: rgb(255,255,255);
position: fixed;
top: 45%;
left: 50%;
transform: translate(-50%,-50%);
">loading scene,this might take a few seconds..</h1>
<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="module">
import * as THREE from 'https://threejs.org/build/three.module.js'
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'https://threejs.org/examples/jsm/loaders/GLTFLoader.js'
import { RGBELoader } from 'https://threejs.org/examples/jsm/loaders/RGBELoader.js'
import { EffectComposer } from 'https://threejs.org/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'https://threejs.org/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'https://threejs.org/examples/jsm/postprocessing/UnrealBloomPass.js';
// RESOURCES ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
const COLOR_TEXTURE = "https://cdn.jsdelivr.net/gh/MJegerlehnerBio/bloom-solution/Recordplayer_Color.jpeg"
const MetaLnesS_TEXTURE = "https://cdn.jsdelivr.net/gh/MJegerlehnerBio/bloom-solution/Recordplayer_Metalness.jpeg"
const EMISSION_TEXTURE = "https://cdn.jsdelivr.net/gh/MJegerlehnerBio/bloom-solution/Recordplayer_Emission.jpeg"
const ALPHA_TEXTURE = "https://cdn.jsdelivr.net/gh/MJegerlehnerBio/bloom-solution/Recordplayer_Alpha.jpeg"
const TURNTABLE_MODEL = "https://cdn.jsdelivr.net/gh/MJegerlehnerBio/bloom-solution/turntable_a111.glb"
const HDRI_MAP = "https://cdn.jsdelivr.net/gh/MJegerlehnerBio/bloom-solution/forest.hdr"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function $(e){return document.getElementById(e)}
const container = document.createElement( 'div' )
document.body.appendChild( container )
const scene = new THREE.Scene()
scene.background = new THREE.Color( new THREE.Color("rgb(250,244,227)") )
scene.fog = new THREE.Fog( new THREE.Color("rgb(100,100,100)"),10,50 )
const camera = new THREE.PerspectiveCamera( 45,window.innerWidth / window.innerHeight,0.1,1000 )
camera.position.set( 7,3,7 )
const renderer = new THREE.Webglrenderer( { antialias: true } )
renderer.setPixelRatio( window.devicePixelRatio )
renderer.setSize( window.innerWidth,window.innerHeight )
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.outputEncoding = THREE.sRGBEncoding
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
container.appendChild( renderer.domElement )
const controls = new OrbitControls( camera,renderer.domElement )
controls.mindistance = 1
controls.enablePan = true
controls.enableZoom = true;
controls.enabledamping = true
controls.dampingFactor = 0.1
controls.rotateSpeed = 0.5
const directionalLight = new THREE.DirectionalLight( new THREE.Color("rgb(255,255)"),1 )
directionalLight.castShadow = true
directionalLight.shadow.camera.top = 4
directionalLight.shadow.camera.bottom = - 4
directionalLight.shadow.camera.left = - 4
directionalLight.shadow.camera.right = 4
directionalLight.shadow.camera.near = 0.1
directionalLight.shadow.camera.far = 40
directionalLight.shadow.camera.far = 40
directionalLight.shadow.bias = - 0.002
directionalLight.position.set( 0,20,20 )
directionalLight.shadow.mapSize.width = 1024*4
directionalLight.shadow.mapSize.height = 1024*4
scene.add( directionalLight )
scene.add( new THREE.CameraHelper( directionalLight.shadow.camera ) )
var gltfLoader
var model
var mesh
const pmremGenerator = new THREE.PMREMGenerator( renderer )
pmremGenerator.compileEquirectangularshader()
new RGBELoader().setDataType( THREE.UnsignedByteType ).load( HDRI_MAP,function ( texture ) {
const envMap = pmremGenerator.fromEquirectangular( texture ).texture
scene.environment = envMap
texture.dispose()
pmremGenerator.dispose()
gltfLoader = new GLTFLoader()
gltfLoader.load( TURNTABLE_MODEL,function ( gltf ) {
model = gltf.scene
model.position.y = 1
model.traverse( function ( child ) {
if ( child.isMesh ) {
mesh = child
child.castShadow = true
child.receiveShadow = true
child.material.transparent = true
child.material.envMapIntensity = 1
$("info").style.display = "none";
}
} );
model.scale.set(15,15,15)
scene.add( model )
animate()
} )
});
const animate = function () {
requestAnimationFrame( animate )
controls.update()
renderer.render( scene,camera )
};
window.addEventListener( 'resize',function () {
const width = window.innerWidth
const height = window.innerHeight
renderer.setSize( width,height )
camera.aspect = width / height
camera.updateProjectionMatrix()
} )
</script>
</div>
</div>
解决方法
在我看来,那个官方示例过于复杂。但是选择性绽放本身的概念很简单:
- 使所有未开花的物体全黑
- 使用
bloomComposer
渲染场景 - 将材料/颜色恢复到以前的状态
- 使用
finalComposer
渲染场景
就是这样。如何管理变暗/变黑的非开花对象并恢复它们的材料,这取决于您。
这是一个例子(看起来很复杂,但实际上并没有那么多):
body{
overflow: hidden;
margin: 0;
}
<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="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.127.0/build/three.module.js';
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/controls/OrbitControls.js';
import { EffectComposer } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/ShaderPass.js';
import { UnrealBloomPass } from 'https://cdn.jsdelivr.net/npm/three@0.127.0/examples/jsm/postprocessing/UnrealBloomPass.js';
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60,innerWidth / innerHeight,1,100);
camera.position.set(0,3,5);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth,innerHeight);
//renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera,renderer.domElement);
let light = new THREE.DirectionalLight(0xffffff,0.5);
light.position.setScalar(1);
scene.add(light,new THREE.AmbientLight(0xffffff,0.5));
let uniforms = {
globalBloom: {value: 1}
}
// texture
new THREE.TextureLoader().load("https://threejs.org/examples/textures/hardwood2_diffuse.jpg",tex => {
//console.log(tex);
let img = tex.image;
let c = document.createElement("canvas");
let min = Math.min(img.width,img.height);
c.width = c.height = min;
let ctx = c.getContext("2d");
ctx.drawImage(img,0);
let c2 = document.createElement("canvas");
c2.width = c2.height = min;
let ctx2 = c2.getContext("2d");
ctx2.clearRect(0,min,min);
["#f00","#0f0","#ff0","#f0f","#0ff"].forEach( (col,i,a) => {
let id = i - ((a.length - 1) / 2);
let dist = id * 150;
//console.log(dist,col,c.width,c.height);
ctx.beginPath();
ctx.arc(min * 0.5 + dist,min * 0.5,25,2 * Math.PI);
ctx.fillStyle = col;
ctx.fill();
}
);
let cTex = new THREE.CanvasTexture(c);
let c2Tex = new THREE.CanvasTexture(c2);
setInterval(() => {
ctx2.clearRect(0,min);
let id = THREE.MathUtils.randInt(0,4) - 2;
let dist = id * 150;
ctx2.beginPath();
ctx2.arc(min * 0.5 + dist,2 * Math.PI);
ctx2.fillStyle = "#fff";
ctx2.fill();
c2Tex.needsUpdate = true;
},125);
let g = new THREE.PlaneGeometry(5,5);
g.rotateX(Math.PI * -0.5);
let m = new THREE.MeshStandardMaterial(
{
roughness: 0.6,metalness: 0.5,map: cTex,emissiveMap: c2Tex,onBeforeCompile: shader => {
shader.uniforms.globalBloom = uniforms.globalBloom;
shader.fragmentShader = `
uniform float globalBloom;
${shader.fragmentShader}
`.replace(
`#include <dithering_fragment>`,`#include <dithering_fragment>
vec3 col = texture2D( map,vUv).rgb;
float em = texture2D( emissiveMap,vUv ).g;
col *= em;
gl_FragColor.rgb = mix(gl_FragColor.rgb,globalBloom);
`
);
console.log(shader.fragmentShader);
}
}
);
let o = new THREE.Mesh(g,m);
scene.add(o);
})
window.onresize = function () {
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width,height );
bloomComposer.setSize( width,height );
finalComposer.setSize( width,height );
};
// bloom
const renderScene = new RenderPass( scene,camera );
const bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth,window.innerHeight ),1.5,0.1 );
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 );
renderer.setAnimationLoop( _ => {
renderer.setClearColor(0x000000);
uniforms.globalBloom.value = 1;
bloomComposer.render();
renderer.setClearColor(0x202020);
uniforms.globalBloom.value = 0;
finalComposer.render();
//renderer.render(scene,camera);
})
</script>