How to create a halo effect similar to a light on an object?

I use textures to illuminate the entire scene and model.

Now there is a question, how to make a part of the model (with white text) produce a halo effect similar to a light?

If possible, how many methods can we achieve it?
I really hope for your help, thank you!

This is the code.

<template>
    <div ref="container"></div>
</template>

<script setup>
    import {ref, onMounted} from "vue";

    import * as THREE from "three";
    // GLTF加载器
    import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";
    // 解压器
    import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader";
    import {OrbitControls} from "three/examples/jsm/controls/OrbitControls.js";
    import {HDRJPGLoader} from "@monogrid/gainmap-js";
    import {RoomEnvironment} from 'three/examples/jsm/environments/RoomEnvironment.js';
    import {GUI} from "three/examples/jsm/libs/lil-gui.module.min.js";
    import Stats from "three/examples/jsm/libs/stats.module.js";

    const container = ref(null);

    let // 场景   相机  渲染  轨道控制器   glb文件内容
        scene, camera, renderer, control, content, cube;

    let gui, stats;

    let hdrJpgEquirectangularMap;

    const params = {
        exposure: 2.0,

        cameraX: 7.4,
        cameraY: 12.5,
        cameraZ: 60,

        targetX: 6.45,
        targetY: 12.8,
        targetZ: 2.56,

    };

    // 挂载
    onMounted(() => {
        // 初始化
        init();
        // 渲染
        render();

        // 监听窗口变化
        window.addEventListener("resize", () => {
            // 重置渲染器宽高比
            renderer.setSize(window.innerWidth, window.innerHeight);
            // 重置相机宽高比
            camera.aspect = window.innerWidth / window.innerHeight;
            // 更新相机投影矩阵
            camera.updateProjectionMatrix();
        });
    });

    function initScene() {
        scene = new THREE.Scene();
    }

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

        camera.position.set(params.cameraX, params.cameraY, params.cameraZ)
        camera.updateProjectionMatrix();
    }

    function initRenderer() {
        // 创建渲染器 antialias 抗锯齿
        renderer = new THREE.WebGLRenderer({antialias: true});
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        // 尺寸
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 设置像素比
        renderer.setPixelRatio(window.devicePixelRatio);
        container.value.appendChild(renderer.domElement);
    }

    function initControl() {
        control = new OrbitControls(camera, renderer.domElement);
        control.target.set(params.targetX, params.targetY, params.targetZ);
        control.update();
    }

    // 加载模型
    function loadModel() {

        // 解压器(用于解压经过压缩过的glb文件)
        const dracoLoader = new DRACOLoader();
        dracoLoader.setDecoderPath('./draco/');

        // 加载器
        new GLTFLoader().setDRACOLoader(dracoLoader).load("characters.glb", (glb) => {

                // glb文件场景
                const _scene = glb.scene || glb.scenes[0];
                if (!_scene) {
                    // Valid, but not supported by this viewer.
                    throw new Error(
                        'This model contains no scene, and cannot be viewed here. However, it may contain individual 3D resources.',
                    );
                }

                // glb文件内容
                content = _scene;

                // 文件场景添加到当前场景中
                scene.add(content);
            },
            function (xhr) {
                // 加载进度
                // console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
            },
            function (error) {
                console.log("An error happened:", error);
            }
        );
    };

    // 加载贴图
    function loadTexture() {
        new HDRJPGLoader(renderer).load("textures/sun_gain_map.jpg", (texture) => {
                hdrJpgEquirectangularMap = texture.renderTarget.texture;
                hdrJpgEquirectangularMap.mapping = THREE.EquirectangularReflectionMapping;
                hdrJpgEquirectangularMap.needsUpdate = true;

                // 环境背景
                scene.environment = hdrJpgEquirectangularMap;
                scene.background = hdrJpgEquirectangularMap;

                // 曝光度
                renderer.toneMappingExposure = params.exposure;
            },
            function (progress) {
                // console.log("Progress", ((progress.loaded / progress.total) * 100).toFixed(2));
            },
        );
    }

    function initStats() {
        stats = new Stats();
        container.value.appendChild(stats.dom);
    }

    function initGui() {
        gui = new GUI();
        gui.add(params, "exposure", 0, 15, 0.01).name("曝光度").onFinishChange((value) => {
            // 曝光度
            renderer.toneMappingExposure = value;
        });

        // region camera
        const cameraPosition = gui.addFolder('camera position');
        cameraPosition.add(params, 'cameraX', -300, 300, 2).onFinishChange((value) => {
            camera.position.x = value;
            camera.updateProjectionMatrix();
        });
        cameraPosition.add(params, 'cameraY', -300, 300, 2).onFinishChange((value) => {
            camera.position.y = value;
            camera.updateProjectionMatrix();
        });
        cameraPosition.add(params, 'cameraZ', -300, 300, 2).onFinishChange((value) => {
            camera.position.z = value;
            camera.updateProjectionMatrix();
        });
        // endregion camera

        gui.open();
    }

    function render() {
        requestAnimationFrame(render);

        stats.begin();
        renderer.render(scene, camera);
        stats.end();
    }

    function init() {
        initScene();
        initRenderer();
        initCamera();
        initControl();
        loadTexture();
        loadModel();
        initStats();
        initGui();
    }
</script> 


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



        let composer = new EffectComposer( renderer );
    
function onWindowResize() {
	const width = window.innerWidth;
	const height = window.innerHeight;
	
	composer.setSize( width, height );
}
	
	
	const params = {
		threshold: 0,
		strength: 1,
		radius: 0,
	};

const bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 );
bloomPass.threshold = params.threshold;
bloomPass.strength = params.strength;
bloomPass.radius = params.radius;
composer.addPass( bloomPass );

  
const outputPass = new OutputPass();
  
composer.addPass( outputPass );

..
 in your render function:
instead of renderer.render(


      //renderer.render(scene, camera);
      composer.render(scene, camera)



Then adjust threshold up until only your white text blooms.

Thank you for your reply
I will render.render (scene, camera); After modifying to composer.render(scene, camera), the model did not render (the entire image is black)
In addition, I hope that the red part in the model is normal, while the white part can emit light like a light bulb.
However, in the above code, I did not see any distinction made between these two parts.

Something related: Glowing effect on emissive map and not on color map - #9 by prisoner849

1 Like

Here’s a longer explanation of the bloom effect, how to choose the emissive brightness and bloom threshold:

3 Likes