How to Apply Bloom to Specific Materials in Three.js

In Three.js, the UnrealBloomPass normally applies a glowing effect to the entire canvas based on color intensity. To restrict this glow to specific materials, you must implement a technique called selective bloom. This process involves using camera layers, rendering the glowing objects separately to a dedicated bloom buffer, and then blending that buffer back over the non-glowing scene using a custom combination shader.


Step 1: Define Camera Layers

Three.js cameras can filter which objects they render using layers. Assign a specific layer number to represent the glowing objects.

import * as THREE from 'three';

const ENTIRE_SCENE = 0;
const BLOOM_SCENE = 1;

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

To make an object glow, enable this layer on the mesh:

const glowingMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const glowingMesh = new THREE.Mesh(geometry, glowingMaterial);

// Enable the bloom layer on this mesh
glowingMesh.layers.enable(BLOOM_SCENE);
scene.add(glowingMesh);

Step 2: Set Up the Composers

You need two separate EffectComposer instances: one to generate the bloom texture and another to render the final combined image.

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. Bloom Composer (renders black background + glowing objects)
const renderPass = new RenderPass(scene, camera);
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);

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

Step 3: Create the Blend Shader

Next, create a final composer that merges the standard scene render with the bloom texture generated by the bloom composer.

// Custom shader to combine the original scene with the bloom texture
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;

// 2. Final Composer
const finalComposer = new EffectComposer(renderer);
finalComposer.addPass(renderPass);
finalComposer.addPass(mixPass);

Step 4: Implement the Render Loop

During the render loop, you must temporarily hide non-glowing objects to prevent them from blocking or receiving the bloom effect. This is achieved by swapping all non-glowing materials with a basic black material, rendering the bloom composer, restoring the materials, and then rendering the final composer.

Define a black material and a utility to swap materials:

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

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

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

Implement this logic inside your main animation loop:

function animate() {
  requestAnimationFrame(animate);

  // 1. Prepare scene for bloom pass (darken non-glow objects)
  scene.traverse(darkenNonBloomed);
  
  // 2. Render bloom texture
  bloomComposer.render();
  
  // 3. Restore original materials
  scene.traverse(restoreMaterial);
  
  // 4. Render the final composite image
  finalComposer.render();
}

animate();

By isolating the glowing materials to a separate layer, temporarily rendering non-glowing elements as solid black, and combining the passes via a custom shader, only the specified materials will display the UnrealBloomPass glow.