Can't get Outer glow to work with UnrealBloomPass

I can’t seem to figure out how to get my outer glow to work. I’m trying to use UnrealBloomPass.
I want all the spheres / stars to have a white outer glow.
The scene renders and everything works with no errors but the outer glow is not present.

Any help would be greatly appreciated!

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';

// Setup

// Define your custom vertex shader code
const vertexShader = `
  // Your vertex shader code here
`;

// Define your custom fragment shader code
const fragmentShader = `
  uniform vec3 glowColor;
  
  void main() {
    gl_FragColor = vec4(glowColor, 1.0);
  }
`;

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector('#bg'),
  antialias: true
});

renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
camera.position.setZ(30);
camera.position.setX(-3);

renderer.render(scene, camera);


window.addEventListener('resize', onWindowResize, false);
// Function to handle window resize
function onWindowResize() {
  // Update camera aspect ratio
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  // Update renderer size
  renderer.setSize(window.innerWidth, window.innerHeight);

  // Call any additional resize-related logic or updates here
  // ...
}

// Lights

const pointLight = new THREE.PointLight(0xffffff);
pointLight.position.set(5, 5, 5);

const ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(pointLight, ambientLight);

// Helpers

// const lightHelper = new THREE.PointLightHelper(pointLight)
// const gridHelper = new THREE.GridHelper(200, 50);
// scene.add(lightHelper, gridHelper)

// const controls = new OrbitControls(camera, renderer.domElement);

// Add the UnrealBloomPass effect
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(bloomPass);
bloomPass.threshold = 0;
bloomPass.strength = 5;
bloomPass.radius = 0.5;
bloomPass.renderToScreen = true;

function addStar() {
  const geometry = new THREE.SphereGeometry(0.02, 15, 15);
  const material = new THREE.MeshStandardMaterial({ color: 0xffffff });
  const star = new THREE.Mesh(geometry, material);

  const [x, y, z] = Array(3).fill().map(() => THREE.MathUtils.randFloatSpread(100));

  star.position.set(x, y, z);
  scene.add(star);

  // Create the outer glow effect using a ShaderMaterial
  const outerGlowGeometry = new THREE.SphereGeometry(0.04, 15, 15);
  const outerGlowMaterial = new THREE.ShaderMaterial({
    vertexShader,
    fragmentShader,
    uniforms: {
      glowColor: { value: new THREE.Color(0xffffff) }, // Set the outer glow color to white
    },
  });
  const outerGlow = new THREE.Mesh(outerGlowGeometry, outerGlowMaterial);
  outerGlow.position.copy(star.position);
  scene.add(outerGlow);
}

// Add the star and outer glow to the scene
Array(10000).fill().forEach(addStar);

// Background

const spaceTexture = new THREE.TextureLoader().load('space.jpg');
scene.background = spaceTexture;


// Moon

const moonTexture = new THREE.TextureLoader().load('moon.jpg');
const normalTexture = new THREE.TextureLoader().load('normal.jpg');

const moon = new THREE.Mesh(
  new THREE.SphereGeometry(3, 32, 32),
  new THREE.MeshStandardMaterial({
    map: moonTexture,
    normalMap: normalTexture,
  })
);

scene.add(moon);

moon.position.z = 1;
moon.position.setX(-10);

function moveCamera() {
  const t = document.body.getBoundingClientRect().top;
  
  camera.position.z = t * -0.01;
  camera.position.x = t * -0.0002;
  camera.rotation.y = t * -0.0002;
}

document.body.onscroll = moveCamera;
moveCamera();

// Animation Loop
function animate() {
  requestAnimationFrame(animate);

  moon.rotation.x += 0.005;

  // Move the camera automatically
  const autoMoveSpeed = -0.007; // Adjust this value to control the automatic movement speed
  camera.position.z -= autoMoveSpeed;
  camera.position.x -= -0.00014;
  camera.rotation.y -= -0.00014;


  renderer.render(scene, camera);
}

animate();


The bloom pass does not work correctly without OutputPass at the end of your pass chain. So please setup EffectComposer like in the official example:

https://threejs.org/examples/webgl_postprocessing_unreal_bloom

OutputPass performs tone mapping (the key for good looking bloom) and the output color space conversion to sRGB.

Also make sure that all color textures (moonTexture and spaceTexture) are configured as sRGB textures. Meaning:

spaceTexture.colorSpace = THREE.SRGBColorSpace;

This is not required for normal maps (since these are no color textures).

1 Like

Thanks so much! Was stuck on this for ages.
Got it sorted by following that example.
Does UnrealBloom just add a glow to everything? I removed the Outerglow object around the spheres and the spheres still glow which is great, seems like it would be better for performance. But just curious to know why that is?
Can I somehow crank up the UnrealBloom glow effect on a specific object? eg. the moon.
Does this look right to you? I notice if the Bloom strength is any higher it starts to look a bit pixelated and also gets cropped to a square around the sphere, is that just to do with the threshold and radius?
dxp.zencreative.com.au
Thanks in advance!

Yes, the bloom pass affects the entire scene.

You normally don’t need additional glow mesh structures around your actual meshes when using bloom via post-processing. You get more glow if objects are bright (e.g. by using a bright color or emissive value).

A bloom strength of 5 is quite high. I suggest you use a value around 1 or lower. Normally, that should be sufficient.

There is an example for this however it needs some extra work: three.js webgl - postprocessing - unreal bloom selective

Yes, if that happens, radius and strength are set to too high values. It also happens if the exposure of your tone mapping is set to a too high value.

the official example linked above is imo super confusing. selective bloom is inbuilt and there is nothing that needs to be done, you select bloom for specific meshes with the materials. you don’t need copy passes, scene traverse, masking, extra renders and all that.

here’s an example using jsm/effect composer

const target = new THREE.WebGLRenderTarget(width, height, {
  type: THREE.HalfFloatType,
  format: THREE.RGBAFormat,
})
target.samples = 8
const composer = new EffectComposer(renderer, target)
composer.addPass(new RenderPass(scene, camera))

// Setting threshold to 1 will make sure nothing glows
composer.addPass(new UnrealBloomPass(undefined, 1, 1, 1))

// This mesh will glow because emissive leaves 0-1 range, everything else won't
const mesh = new THREE.Mesh(
  geometry,
  new THREE.MeshStandardMaterial({
    toneMapped: false,
    emissive: "red",
    emissiveIntensity: 10

and a sandbox demonstrating it glass flower - CodeSandbox btw that one uses pmndrs/postprocessing which imo you should use anyway. it is a better vanilla threejs effect composer (although the example above is react, that library is not).

selecting bloom for specific meshes is the same there. you just crank a color out of 0-1 range.

color.set(“red”) // :x: won’t glow
color.setRBG(10, 0, 0) // :white_check_mark: will glow
etc