2

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.

enter image description here

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>
Miger
  • 1,175
  • 1
  • 12
  • 33
  • There is another example, with selective bloom: https://threejs.org/examples/webgl_postprocessing_unreal_bloom_selective.html – prisoner849 Apr 09 '21 at 06:37
  • This does not help me as it uses multiple separate objects: `for ( let i = 0; i < 50; i ++ ) { const color = new THREE.Color(); color.setHSL( Math.random(), 0.7, Math.random() * 0.2 + 0.05 ); const material = new THREE.MeshBasicMaterial( { color: color } ); const sphere = new THREE.Mesh( geometry, material ); scene.add( sphere ); if ( Math.random() < 0.25 ) sphere.layers.enable( BLOOM_SCENE );}` This is not what I need! I'm only loading one object and use an emission texture map to tell the model where it should glow and where it shouldn't.. how do I do that? – Miger Apr 09 '21 at 09:23

2 Answers2

3

That official example is overcomplicated, from my point of view. But the concept of selective bloom itself is simple enough:

  1. Make all non-bloomed objects totally black
  2. Render the scene with bloomComposer
  3. Restore materials/colors to previous
  4. Render the scene with 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>
prisoner849
  • 16,894
  • 4
  • 34
  • 68
  • I guess I understand it somewhat.. thanks I will see what I can do. – Miger Apr 12 '21 at 09:52
  • It would be better though if you could give me a more direct example, which shows how to do that with one single object and its alpha texture map.. because even the "not complex" example you provided is hard to understand for me. But thanks for your help so far! – Miger Apr 12 '21 at 10:40
  • 1
    @Miger if you could provide an editable working example with your model and scene setup, I could try to solve it directly. – prisoner849 Apr 12 '21 at 14:04
  • Thank you very much. I will provide you with the full code and all necessary resources. You can download the model and the textures using the links provided in the code. I really hope you can help. – Miger Apr 12 '21 at 16:27
  • If you wanted a jsFiddle or codePen, I'm sorry but I didn't manage to create one of those and I do not have time to deal with that too.. I hope you can understand. – Miger Apr 12 '21 at 16:31
  • @Miger I upload resouces on github and use github pages for links to the resources, that I need to use in jsFiddle or codepen. Works perfectly well. – prisoner849 Apr 12 '21 at 16:54
  • Ok I will see what I can do.. thanks for you help. I'll get back to you once I've created the jsFiddle.. won't be long. – Miger Apr 12 '21 at 21:20
  • I've continued working on everything your explanation and example were helpful nonetheless. You can still try to implement it in my JSFiddle but for me the general question has been answered. Thanks! – Miger Apr 15 '21 at 00:56
0

there is a way, way easier method than going through all these complicated steps and passes. just push colors out of their normal 0-1 range and that is literally it: https://twitter.com/0xca0a/status/1525083552672632833

  1. set up bloom with a threshold of 1 (nothing gets bloomed)
  2. push material colors into hd range
  3. disable tonemapping
hpalu
  • 1,357
  • 7
  • 12
  • Please what do you mean by push material colors into hd range. Please I am also new to Three.js thank you –  May 24 '22 at 21:01
  • 1
    Is there a way to achieve this with Vanilla ThreeJS? This method doesn't appear to work without react-three-fibre and drei and I wonder if they are doing anything unique to rendering that allows for this HD range to work? – dmcb Nov 16 '22 at 05:01