Three Scene. Environment Texture Not Updating on 3D Model

I am importing a glTF model into a WebGL ThreeJS scene. In the gltf-viewer.donmccurdy.com preview I can see that the environment setting makes a big difference to how my model renders:

This is how it looks without the environment map:

So I am loading a jpg texture to use as the environment map and then loading my glTF and adding the texture as the loaded models scene.environment property. Here's the main chunk of the code:

import * as THREE from "three";
import { GLTFLoader } from "GLTFLoader";
// import { EXRLoader } from "EXRLoader";

export class AppController {

    constructor(app) {
        this.app = app;
    }
    
    animate = () => {
        requestAnimationFrame( this.animate );
        this.renderer.render( this.scene, this.camera );
    }
    
    init = () => {
        console.log('AppController.init()');
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

        this.scene.background = new THREE.Color( 0x00ffff );

        const light = new THREE.AmbientLight( 0x404040 ); // soft white light
        this.scene.add( light );

        this.renderer = new THREE.WebGLRenderer();
        this.renderer.setSize( window.innerWidth, window.innerHeight );
        this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
        this.renderer.outputEncoding = THREE.sRGBEncoding;
        document.body.appendChild( this.renderer.domElement );
        this.camera.position.z = 100;

        const fthis = this;
        new THREE.TextureLoader().load("./assets/images/env.jpg",
        function ( texture ) {
            fthis.scene.environment = texture;
            fthis.loadModel(texture);
        },
        undefined,
        function ( err ) {
            console.error( err );
        });
        this.animate();
    }
    
    loadModel = (texture) => {

        const loader = new GLTFLoader();
        const modelPath = 'assets/models/Pebble_Logo_Extrude.gltf';
        const fthis = this;
        loader.load( modelPath, function ( gltf ) {
            console.log('gltf', gltf);
            fthis.pebbleLogo = gltf;
            fthis.pebbleLogo.scene.environment = texture;
            fthis.pebbleLogo.scene.traverse(function (child) {
                if (child instanceof THREE.Mesh) {
                    if(child.material !== null){
                        console.log("setting envmap");
                        child.material.envMap = texture;
                        child.material.metalness = 0.0;
                        child.material.envMapIntensity = 2.0;
                        child.material.needsUpdate = true;
                    }
                }
            });
            fthis.scene.add( fthis.pebbleLogo.scene );
        }, undefined, function ( error ) {
            console.error( error );
        } );
    }
}

The result is this:

As you can see, the environment map has had no effect at all. I did read something about possibly needing to use the EXRLoader to make the map, but this was an old article and I assumed this may no longer be the case seeing as Three doesn't include in their documentation.

Any help most appreciated.

2 Answers

Before assigning the environment map to Scene.environment, you need the following line of code:

texture.mapping = THREE.EquirectangularReflectionMapping;

This defines that your loaded texture is in the equirectangular format.

Besides, if you load a LDR environment map, you want to add the following line, too:

texture.encoding = THREE.sRGBEncoding;

BTW: If you use no HDR environment map, there is no need to configure tone mapping. So you can remove:

this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
1

In the examples source, you'll see they are using RGBELoader to get the texture for the environment mapping. This is all you need. My updated code:

import * as THREE from "three";
import { GLTFLoader } from "GLTFLoader";
import { RGBELoader } from "RGBELoader";

export class AppController {

    constructor(app) {
        this.app = app;
    }
    
    animate = () => {
        requestAnimationFrame( this.animate );
        this.renderer.render( this.scene, this.camera );
    }
    
    init = () => {
        console.log('AppController.init()');
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

        this.scene.background = new THREE.Color( 0x00ffff );
        const light = new THREE.AmbientLight( 0x404040 ); // soft white light
        this.scene.add( light );

        this.renderer = new THREE.WebGLRenderer();
        this.renderer.setSize( window.innerWidth, window.innerHeight );
        this.renderer.shadowMap.enabled = true;
        this.renderer.outputEncoding = THREE.sRGBEncoding;
        document.body.appendChild( this.renderer.domElement );
        this.camera.position.z = 50;

        const fthis = this;
        new RGBELoader()
            .setPath( 'assets/images/' )
            .load( 'venice_sunset_1k.hdr', function ( texture ) {
                texture.mapping = THREE.EquirectangularReflectionMapping;
                fthis.scene.environment = texture;
                fthis.loadModel(texture);
            } );
        this.animate();

        // get the mouse position
        this.raycaster = new THREE.Raycaster();
        this.mouse = new THREE.Vector2();

        // setup listeners
        document.addEventListener('mousemove', this.onDocumentMouseMove, false);
        window.addEventListener('resize', this.onWindowResize, false);
    }
    
    onDocumentMouseMove = (event) => {
        event.preventDefault();
        this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    }
    
    onWindowResize = () => {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }

    loadModel = (texture) => {

        const loader = new GLTFLoader();
        const modelPath = 'assets/models/Pebble_Logo_Extrude.gltf';
        const fthis = this;
        loader.load( modelPath, function ( gltf ) {
            console.log('gltf', gltf);
            fthis.pebbleLogo = gltf;
            fthis.scene.add( fthis.pebbleLogo.scene );
        }, undefined, function ( error ) {
            console.error( error );
        } );
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Sarah Jenkins

Sarah Jenkins

Senior Technology Editor & AI Specialist

Sarah Jenkins is a veteran tech journalist with over 12 years of experience covering artificial intelligence, mobile innovations, and digital ethics. Her insights have appeared in leading technology publications worldwide.

Share this article
Twitter Facebook Pinterest