Achieve a customized glow effect by blending FragColor with UnrealBloom in ThreeJS using GLSL

I am interested in implementing selective bloom for a GLTF model imported into ThreeJS using an Emission map.

To accomplish this, the first step is to make objects that should not have bloom completely black. The plan includes utilizing UnrealBloomPass and ShaderPass to blend the bloomed and non-bloomed effects together somehow.

GLSL code needs to be used, which I have only limited familiarity with. Below is my basic setup:

View Example In JSFiddle

<!DOCTYPE html>
<html lang="en">
    (...)
</html>

Upon viewing the result in the JSFiddle, the noticeable glow effect can be observed while the rest of the object remains black.

My next challenge involves understanding how to merge the fragment shader with the UnrealBloomPass using GLSL. This merging process is essential to achieve the desired selective bloom effect. Despite my limited experience with GLSL and its usage in conjunction with ThreeJS, I aim to successfully implement this feature.

Any guidance on how to effectively enable selective bloom would be greatly appreciated.

Answer №1

Following the order for selective bloom remains consistent:

  1. Ensure all non-bloomed objects are completely black
  2. Render the scene using bloomComposer
  3. Restore materials/colors to their original state
  4. Render the scene with finalComposer

To patch the model's material and assign a common uniform indicating the render type, use the following CSS snippet:

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">
console.clear();
import * as THREE from 'https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1d69756f78785d2d332c2f2a332d">[email protected]</a>/build/three.module.js';
import {GLTFLoader} from 'https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c3b7abb1a6a683f3edf2f1f4edf3">[email protected]</a>/examples/jsm/loaders/GLTFLoader.js';

import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="77031f0512123747594645405947">[email protected]</a>/examples/jsm/controls/OrbitControls.js';

import { EffectComposer } from 'https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0e7a667c6b6b4e3e203f3c39203e">[email protected]</a>/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="22564a50474762120c1310150c12">[email protected]</a>/examples/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from 'https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="483c203a2d2d087866797a7f6678">[email protected]</a>/examples/jsm/postprocessing/ShaderPass.js';
import { UnrealBloomPass } from 'https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d8acb0aabdbd98e8f6e9eaeff6e8">[email protected]</a>/examples/jsm/postprocessing/UnrealBloomPass.js';

let model;

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100);
camera.position.set(7.7, 2.5, 7.2);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
//renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener("change", e => {console.log(camera.position)})

let light = new THREE.DirectionalLight(0xffffff, 1.5);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));

let uniforms = {
  globalBloom: {value: 1}
}

let loader = new GLTFLoader();
loader.load( "https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/turntable_121.glb", function ( gltf ) {
    model = gltf.scene;
  let emssvTex = new THREE.TextureLoader().load("https://cdn.jsdelivr.net/gh/MigerRepo/bloom-solution/RecordPlayer_Emission.jpeg", function( texture ) {
    texture.flipY = false
    texture.encoding = THREE.sRGBEncoding
  })    
  model.traverse( function ( child ) {
    if ( child.isMesh ) {
        child.material.emissiveMap = emssvTex;
      child.material.onBeforeCompile = shader => {
        shader.uniforms.globalBloom = uniforms.globalBloom;
        shader.fragmentShader = `
            uniform float globalBloom;
          ${shader.fragmentShader}
        `.replace(
            `#include <dithering_fragment>`,
          `#include <dithering_fragment>
            if (globalBloom > 0.5){
                gl_FragColor = texture2D( emissiveMap, vUv );
            }
          `
        );
        console.log(shader.fragmentShader);
      }
    }
  });
  model.scale.setScalar(40);
  scene.add(model);
});

// bloom
const renderScene = new RenderPass( scene, camera );

const bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 2, 0, 0 );

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 );

window.onresize = function () {

  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize( innerWidth, innerHeight );

  bloomComposer.setSize( innerWidth, innerHeight );
  finalComposer.setSize( innerWidth, innerHeight );

};

renderer.setAnimationLoop( _ => {
    
  renderer.setClearColor(0x000000);
  uniforms.globalBloom.value = 1;
  
  bloomComposer.render();
  
  renderer.setClearColor(0x404040);
  uniforms.globalBloom.value = 0;
  
    finalComposer.render();
  //renderer.render(scene, camera);
})

</script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Problem with Handlebars: When compiling, TypeError occurs because inverse is not recognized as a function

I've hit a roadblock with an error that I just can't seem to solve. My goal is to dynamically compile a template extracted from a div on the page, provide it with content, and then display it in a designated area of my webpage. Here's the s ...

Inspect the render function in the 'RestApi' class

I encountered the following error: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined ...

Is it possible to modify the appearance or behavior of a button in a React application based on its current state?

I'm looking to customize the color of a button based on different states. For example, if the state is active, the button should appear in red; otherwise it should be blue. Can anyone suggest how this can be achieved in React? Furthermore, I would als ...

Axios fails to input data into the table

Having trouble with my axios request to insert.php. The variable note_text is coming back as null. I suspect it's because I'm not properly specifying the 2nd argument. My assumption was that there would be a variable like $ _POST['note_text ...

The error "ReferenceError: process is not defined in vue 3" is being

<script setup> import puppeteer from 'puppeteer'; const onChange = async () => { // Initializing the puppeteer library const browser = await puppeteer.launch(); // Creating a new page const page = await browser.newPage(); / ...

Struggling with making react-hook-form correctly validate an email address

I've been struggling for a long time to make this validation work correctly, but it seems impossible. I even added some text at the bottom to display an error message related to the email, but it always shows no error, regardless of the situation. Ed ...

When using node.js with express, the req.on('end') event is triggered, but the req.on('data') event does not fire

When using body parser, you have the option of either: application/x-www-form-urlencoded body parser or json body parser Both options yield the same results. This is how the API is being called: $.ajax({ type:'post', url:'/ ...

Creating an Ionic 3 canvas using a provider

I recently followed a tutorial on integrating the canvas API into Ionic, which can be found at this link. However, I encountered an issue where all the canvas-related functions had to be placed within the pages class, making it quite cumbersome as these f ...

What is the best way to retrieve data and handle error messages using the fetch API in Node.js?

I am attempting to utilize Node's 18 fetch API to send requests to a REST server. Below is the code snippet: const get = () => { let response = await fetch("10.0.0.12/test", { method: "GET", }); console.log(&quo ...

Issues have been identified with React Native UI components RCTBubblingEventBlock and RCTDirectEventBlock not functioning as expected

In my custom native view within an Ignite project, I am facing a challenge with setting up communication from Objective-C to React Native. While the communication from React Native to iOS works using HTML injection, the reverse direction is not functioning ...

Wicked PDF - Pausing to Allow AJAX Request Completion

My challenge lies in creating a PDF using WickedPDF, where all my static HTML/CSS content loads perfectly. However, I am facing an issue with certain elements on the page which are populated through AJAX requests—they do not appear in the generated PDF. ...

Generate a dynamic file import loop within a React application

Suppose I have a directory containing several .md files: /static/blog/ example_title.md another_example.md three_examples.md and an array that includes all the titles: const blogEntries = ["example_title", "another_example", "three_examples"] In ...

What is the best way to convert template interpolation using different words into a correct expression syntax in JavaScript regex?

I have a string that contains template interpolation along with words that are not inside the interpolation. The string can be in one of these various forms: foo{{bar}} {{foo}}bar foo{{bar}}baz {{foo}}{{bar}} foo {{foo}} {{foo}}bar{{baz}} The text interpo ...

Exploring the srcObject feature in React NativeDiscovering the ins and

I am currently working with react native web technology. React-Native-Web: https://github.com/necolas/react-native-web One of the requirements is to incorporate a Video tag. I have developed a video component using CreateElement. Here is the video compo ...

Creating a Set of Buttons in HTML

Need some assistance with grouped buttons. Let's consider a scenario where there are 5 buttons on an HTML page. When the 3rd button is clicked, buttons from 0 to 3 should change color and the function should return 3. Similarly, when the 5th button is ...

jQuery sliding tabs

Hello everyone! I am currently on the search for a specific slider, and I'm hoping to find a good example of it similar to what is demonstrated HERE. Unfortunately, I do not know the official name of this particular type of slider, making it challengi ...

Link-defying button

I'm developing a website with a homepage that serves as an entry point to the rest of the site (it welcomes the user and allows them to click anywhere to access the main website). The following code accomplishes this: <template> <div onc ...

Implementing a loading animation effect using AJAX that requires a loop delay and sleep functionality

My jquery ui dialog is loaded via ajax with a partial view from the server. Initially, the dialog opens as a dialog-ajax-loader and then animates/grows to fit the content once the call returns. The issue arises when the dialog content gets cached or the a ...

Avoiding scrolling when a button (enveloped in <Link> from react-scroll) is pressed in React

Currently, I am implementing the smooth scroll effect on a component using react-scroll. However, adding a button inside the component to create a favorite list has caused an issue where the smooth scroll effect is triggered upon clicking the button. Is ...

Every time I switch views using the router in vue.js, my three.js canvas gets replicated

After creating a Vue.js integrated with three.js application, I encountered an issue with the canvas getting duplicated every time I opened the view containing the three.js application. The canvas remained visible below the new view, as shown in this image ...