How to Apply Selective Bloom in Three.js

This article explains how to isolate and apply a glowing bloom effect to specific emissive objects in a Three.js scene without affecting the entire environment. You will learn how to configure layers, set up post-processing composers, and use a shader material to merge a selective bloom pass with your main scene.


Step 1: Define Layers and Assign Objects

Three.js uses layers to control which objects are rendered by which camera or pass. Define a specific layer index for your glowing objects, and enable that layer on the mesh you want to glow.

import * as THREE from 'three';

const ENTIRE_SCENE = 0;
const BLOOM_SCENE = 1;

const bloomLayer = new THREE.Layers();
bloomLayer.set(BLOOM_SCENE);

// Create your glowing object
const geometry = new THREE.IcosahedronGeometry(1, 4);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const glowingMesh = new THREE.Mesh(geometry, material);

// Assign the mesh to the bloom layer
glowingMesh.layers.enable(BLOOM_SCENE);
scene.add(glowingMesh);

Step 2: Set Up the Post-Processing Composers

You need two EffectComposer instances: one to generate the bloom effect texture, and another to render the final combined scene.

You will also need to import the necessary post-processing passes from the Three.js library.

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';

// 1. Basic Render Pass
const renderPass = new RenderPass(scene, camera);

// 2. Bloom Composer (renders the glow)
const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  1.5, // Strength
  0.4, // Radius
  0.85 // Threshold
);

const bloomComposer = new EffectComposer(renderer);
bloomComposer.renderToScreen = false; // We only want the texture output
bloomComposer.addPass(renderPass);
bloomComposer.addPass(bloomPass);

Step 3: Create the Blend Shader

To combine the bloomed objects with the rest of the scene, create a custom ShaderPass. This shader takes the original scene render and adds the bloom texture on top of it.

const mixShader = {
  uniforms: {
    baseTexture: { value: null },
    bloomTexture: { value: bloomComposer.renderTarget2.texture }
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D baseTexture;
    uniform sampler2D bloomTexture;
    varying vec2 vUv;
    void main() {
      gl_FragColor = (texture2D(baseTexture, vUv) + vec4(1.0) * texture2D(bloomTexture, vUv));
    }
  `
};

const mixPass = new ShaderPass(
  new THREE.ShaderMaterial({
    uniforms: mixShader.uniforms,
    vertexShader: mixShader.vertexShader,
    fragmentShader: mixShader.fragmentShader,
    defines: {}
  }),
  'baseTexture'
);
mixPass.needsSwap = true;

// 3. Final Composer (renders everything together)
const finalComposer = new EffectComposer(renderer);
finalComposer.addPass(renderPass);
finalComposer.addPass(mixPass);

Step 4: Implement the Material Swap Logic

To prevent non-glowing objects from showing up in the bloom texture, you must temporarily hide them during the bloom pass render.

This is achieved by swapping non-bloomed materials with a completely black basic material before rendering the bloom composer, and then restoring them immediately afterward.

const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' });
const materialsStore = {};

function darkenNonBloomed(obj) {
  if (obj.isMesh && bloomLayer.test(obj.layers) === false) {
    materialsStore[obj.uuid] = obj.material;
    obj.material = darkMaterial;
  }
}

function restoreMaterial(obj) {
  if (materialsStore[obj.uuid]) {
    obj.material = materialsStore[obj.uuid];
    delete materialsStore[obj.uuid];
  }
}

Step 5: Update the Animation Loop

In your render/animation loop, execute the material swap, render the bloom composer, restore the original materials, and finally render the main composer.

function animate() {
  requestAnimationFrame(animate);

  // 1. Prepare scene for bloom (turn non-bloomed objects black)
  scene.traverse(darkenNonBloomed);

  // 2. Render the bloom texture
  bloomComposer.render();

  // 3. Restore original materials
  scene.traverse(restoreMaterial);

  // 4. Render the final blended scene
  finalComposer.render();
}

animate();