FakeGlow.js: Integration Bloom effects without using post processing in threeJS

In the world of 3D graphics, creating realistic lighting and material effects is essential for immersing viewers in interactive visual experiences. One such effect is the glow, which adds a sense of radiance to objects within a scene. A new custom shader material, FakeGlowMaterial, allows developers to create highly customizable and visually striking glowing effects in a Three.js-based environment. In this article, we’ll dive into the code and explore how FakeGlowMaterial works, how to use it, and how it integrates with Three.js for an engaging visual experience.

Introduction to FakeGlowMaterial

The FakeGlowMaterial class is a custom shader material built on top of Three.js’s ShaderMaterial. It allows the user to apply a glowing effect to objects within a 3D scene. Unlike traditional glow effects, this material simulates the glow through shader manipulation, making it more efficient in rendering and providing a high degree of control over the appearance of the glow.

The material is defined by two main parts: the vertex shader and the fragment shader.

Key Features of FakeGlowMaterial

  • Customization: The material offers several parameters for customization, including:
  1. falloff: The intensity at which the glow fades.
  2. glowInternalRadius: The internal radius that influences how the glow sharpness behaves.
  3. glowColor: The color of the glow, which can be customized to any desired shade.
  4. glowSharpness: Controls the sharpness of the glow effect.
  5. opacity: The transparency level of the glow.
  6. side: Defines which side of the object the glow will be applied to (front, back, or both).
  7. depthTest: Whether or not to enable depth testing for proper rendering.
  • Shader Effects: The custom shaders handle the glow effect by adjusting how the glow is applied to the object’s surface. The vertex shader calculates the position and normal of each vertex, while the fragment shader applies the glow color and falloff effect based on the view direction and the object’s normal.

Code Breakdown

Here is a detailed look at the important sections of the FakeGlowMaterial code.

Vertex Shader

varying vec3 vPosition;
varying vec3 vNormal;

void main() {
  vec4 modelPosition = modelMatrix * vec4(position, 1.0);
  gl_Position = projectionMatrix * viewMatrix * modelPosition;
  vec4 modelNormal = modelMatrix * vec4(normal, 0.0);
  vPosition = modelPosition.xyz;
  vNormal = modelNormal.xyz;
}

The vertex shader calculates the position of each vertex in the scene’s world space and passes it along with the normal vector to the fragment shader. This data is critical for computing the glow effect based on the angle between the object’s surface and the camera view.

Fragment Shader

uniform vec3 glowColor;
uniform float falloff;
uniform float glowSharpness;
uniform float glowInternalRadius;
uniform float opacity;

varying vec3 vPosition;
varying vec3 vNormal;

void main() {
  vec3 normal = normalize(vNormal);
  if(!gl_FrontFacing)
    normal *= -1.0;
  vec3 viewDirection = normalize(cameraPosition - vPosition);
  float fresnel = dot(viewDirection, normal);
  fresnel = pow(fresnel, glowInternalRadius + 0.1);
  float falloff = smoothstep(0., falloff, fresnel);
  float fakeGlow = fresnel;
  fakeGlow += fresnel * glowSharpness;
  fakeGlow *= falloff;
  gl_FragColor = vec4(clamp(glowColor * fresnel, 0., 1.0), clamp(fakeGlow, 0., opacity));
}

The fragment shader computes the final color of each fragment based on the fresnel effect, which is influenced by the angle between the camera and the surface. The glow effect becomes more intense as the camera moves closer to the object. The falloff factor smooths the transition of the glow, while the glowSharpness and glowInternalRadius fine-tune the appearance of the glow.

Main.js: Setting Up the Scene

Now that we have an understanding of the FakeGlowMaterial, let’s see how it is utilized within a Three.js scene.

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import * as dat from 'lil-gui';
import FakeGlowMaterial from './FakeGlowMaterial.js';

const canvas = document.querySelector('canvas.webgl');
const scene = new THREE.Scene();

// Screen resolution setup
const screenRes = { width: window.innerWidth, height: window.innerHeight };
window.addEventListener('resize', () => {
  screenRes.width = window.innerWidth;
  screenRes.height = window.innerHeight;
  camera.aspect = screenRes.width / screenRes.height;
  camera.updateProjectionMatrix();
  renderer.setSize(screenRes.width, screenRes.height);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1));
});

// Camera setup
const camera = new THREE.PerspectiveCamera(35, screenRes.width / screenRes.height, 1, 1000);
camera.position.set(0, 0, 6);
scene.add(camera);

// Controls setup
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
controls.autoRotate = true;
controls.target.set(0, -0.3, 0);

// Lights setup
const light = new THREE.DirectionalLight();
light.intensity = 1;
light.position.set(-20, 20, 50);
scene.add(light);
const ambientLight = new THREE.AmbientLight();
ambientLight.intensity = 0.1;
scene.add(ambientLight);

// Renderer setup
const renderer = new THREE.WebGLRenderer({ canvas: canvas });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
renderer.setSize(screenRes.width, screenRes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1));

// Skybox setup
const geometry = new THREE.SphereGeometry(6, 40, 40);
const texture = new THREE.TextureLoader().load('background2.jpg');
texture.flipY = true;
const material = new THREE.MeshStandardMaterial({ map: texture, side: THREE.BackSide });
const skyBox = new THREE.Mesh(geometry, material);
scene.add(skyBox);
skyBox.rotation.y = -1;

// FakeGlow Material and Mesh setup
const fakeGlowMaterial = new FakeGlowMaterial({ glowColor: '#8039ea' });
const torusMesh = new THREE.TorusKnotGeometry(0.8, 0.5, 128, 128);
const Torus = new THREE.Mesh(torusMesh, fakeGlowMaterial);
scene.add(Torus);

// Torus with MeshPhysicalMaterial for comparison
const torusMaterial = new THREE.MeshPhysicalMaterial({ color: '#2babab', roughness: 0.2, clearcoat: 1 });
const torusMesh2 = new THREE.TorusKnotGeometry(0.8, 0.1, 128, 128);
const Torus2 = new THREE.Mesh(torusMesh2, torusMaterial);
scene.add(Torus2);

// GUI setup
const gui = new dat.GUI();
gui.add(fakeGlowMaterial.uniforms.falloff, 'value').min(0).max(1).step(0.01).name('Falloff');
gui.add(fakeGlowMaterial.uniforms.glowInternalRadius, 'value').min(-10).max(10).step(0.01).name('Glow Internal Radius');
gui.addColor({ GlowColor: fakeGlowMaterial.uniforms.glowColor.value.getStyle() }, 'GlowColor')
  .onChange((color) => { fakeGlowMaterial.uniforms.glowColor.value.setStyle(color); fakeGlowMaterial.needsUpdate = true; }).name('Glow Color');
gui.add(fakeGlowMaterial.uniforms.glowSharpness, 'value').min(0).max(1).step(0.01).name('Glow Sharpness');
gui.add(fakeGlowMaterial.uniforms.opacity, 'value').min(0).max(1).step(0.01).name('Opacity');

// Animation loop
const tick = () => {
  controls.update();
  renderer.render(scene, camera);
  window.requestAnimationFrame(tick);
};

tick();

Conclusion

FakeGlowMaterial offers an efficient and customizable way to create glowing effects in Three.js. By leveraging shader programming, it allows for real-time adjustment of glow parameters and gives developers full control over how objects appear in a 3D scene. Whether you’re building an interactive 3D experience or a game, this material can significantly enhance the visual appeal of your objects with minimal performance overhead.

If you’re looking to experiment with this effect, FakeGlowMaterial provides an excellent starting point. With the included parameters and GUI for real-time adjustments, creating stunning glow effects has never been easier!

Leave a comment

Your email address will not be published. Required fields are marked *