In my project I want to display 3d objects which sometimes have small LED lights. The idea is, that these small lights need to be emitting some kind of bloom to make it look like they are glowing.
I've tried to apply the UnrealBloom however it is considered for the entire scene and not just for the parts that have the actual emission value (using an emission texture map).. the scene gets very blurry as well.
This is obviously not what I wanted. I only need the little red LED light bulp to glow not the entire object. However I have not yet found a way to tell the engine to only apply the bloom to where the emission map is pointing at.
I'm using a very simple code setup which is almost the same as the UnrealBloom Example:
How can I setup the emission texture correctly and make only the emissive parts of the object glow and prevent the unrealistically shiny surfaces and very blurry visuals?
UPDATE: Editable example of my setup is now available on JSFiddle!
<body style="margin:0px; overflow:hidden;">
<div id="bloom-solution">
<div id="body">
<h2 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..</h2>
<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/MigerRepo/bloom-solution/RecordPlayer_Color.jpeg"
const METALNESS_TEXTURE = "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/RecordPlayer_Metalness.jpeg"
const EMISSION_TEXTURE = "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/RecordPlayer_Emission.jpeg"
const ALPHA_TEXTURE = "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/RecordPlayer_Alpha.jpeg"
const TURNTABLE_MODEL = "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/turntable_a111.glb"
const HDRI_MAP = "https://cdn.jsdelivr.net/gh/MigerRepo/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, 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>
</body>
That official example is overcomplicated, from my point of view. But the concept of selective bloom itself is simple enough:
bloomComposer
finalComposer
That's it. How to manage the darkening/blackening non-bloomed object and restore their materials, it's up to you.
Here is an example (which seems complex, but actually it's not that much):
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, 0);
let c2 = document.createElement("canvas");
c2.width = c2.height = min;
let ctx2 = c2.getContext("2d");
ctx2.clearRect(0, 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, i, c.width, c.height);
ctx.beginPath();
ctx.arc(min * 0.5 + dist, min * 0.5, 25, 0, 2 * Math.PI);
ctx.fillStyle = col;
ctx.fill();
}
);
let cTex = new THREE.CanvasTexture(c);
let c2Tex = new THREE.CanvasTexture(c2);
setInterval(() => {
ctx2.clearRect(0, 0, min, min);
let id = THREE.MathUtils.randInt(0, 4) - 2;
let dist = id * 150;
ctx2.beginPath();
ctx2.arc(min * 0.5 + dist, min * 0.5, 25, 0, 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, col, 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, 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>