glTF: Assign an HDR environement map to glTF asset

I have my asset exported and looking alright, but it’s missing an environment map. I know glTF has all the data built in and got some crazy results when I tried to add my own MeshStandardMaterial.

This is what my scene looks like (ambient light is being used to visualize the model, otherwise it’s totally black). Code is below

var renderer;
var control;
var mixer;

var mesh; // use this to get a reference to to the mesh outside the scope of loader.load

var container = document.createElement('div');

var clock = new THREE.Clock();

var scene = new THREE.Scene();

var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 10000);
camera.position.set(50,50,50); = new THREE.Vector3( 0, 0, 0 );

renderer = new THREE.WebGLRenderer( { alpha: false } );
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

var controls = new THREE.OrbitControls( camera, renderer.domElement );

var light = new THREE.AmbientLight( 0xffffff, 1 );
light.position.set( 0.0, 0.0, 0.0 ).normalize();
scene.add( light );

// HDR
var floorMaterial = new THREE.MeshBasicMaterial( {
    color: 0xffffff,
    // needsUpdate: true
} );

var planeGeometry = new THREE.PlaneBufferGeometry( 200, 200 );
var planeMesh1 = new THREE.Mesh( planeGeometry, floorMaterial );
planeMesh1.position.y = - 50;
planeMesh1.rotation.x = - Math.PI * 0.5;
scene.add( planeMesh1 );

var ibl_texture = new THREE.EXRLoader().load( './assets/hdr/piz_compressed.exr', function ( texture ) {
    texture.minFilter = THREE.NearestFilter;
    texture.magFilter = THREE.NearestFilter;
    texture.encoding = THREE.LinearEncoding;

    var cubemapGenerator = new THREE.EquirectangularToCubeGenerator( texture, 512 );
    var cubeMapTexture = cubemapGenerator.update( renderer );

    var pmremGenerator = new THREE.PMREMGenerator( cubeMapTexture );
    pmremGenerator.update( renderer );

    var pmremCubeUVPacker = new THREE.PMREMCubeUVPacker( pmremGenerator.cubeLods );
    pmremCubeUVPacker.update( renderer );

    exrCubeRenderTarget = pmremCubeUVPacker.CubeUVRenderTarget;

}); = ibl_texture;

var manager = new THREE.LoadingManager();

// Material
var textureLoader = new THREE.TextureLoader();
var aoTexture = textureLoader.load('./assets/textures/ao.png');
var baseTexture = textureLoader.load('./assets/textures/baseColor.png');
var heightTexture = textureLoader.load('./assets/textures/height.png');
var metalTexture = textureLoader.load('./assets/textures/metallic.png');
var normalTexture = textureLoader.load('./assets/textures/normal.png');
var alphaTexture = textureLoader.load('./assets/textures/opacity.png');
var roughTexture = textureLoader.load('./assets/textures/roughness.png');
var scattTexture = textureLoader.load('./assets/textures/scattering.png');

var material = new THREE.MeshStandardMaterial({
    alphaMap: alphaTexture,
    map: baseTexture,
    aoMap: aoTexture,
    normalMap: normalTexture,
    roughness: 1,
    roughnessMap: roughTexture,
    metalness: 1,
    metalnessMap: metalTexture,
    bumpMap: heightTexture,
    lights: true,

var loader = new THREE.GLTFLoader();
loader.load( './assets/models/from_sketchfab/scene.gltf', function ( gltf ) {

	var content = gltf.scene;

    clips = gltf.animations;
    content.position.set(0, 0, 0);
	content.scale.set(15, 15, 15);


    clips.forEach((clip) => {
        if (clip.validate()) clip.optimize();

    mixer = new THREE.AnimationMixer(scene);

    clips.forEach((clip) => {

	// Apply envMap to model.
	content.traverse((node) => {
		if (node.isMesh) {
		    node.material.envMap = ibl_texture;



function animate() {
    requestAnimationFrame( animate );





function render() {
    renderer.render( scene, camera );

Not sure about the HDR environment but for that material variable you create, I assume you want to attach it to the mesh in your glTF model? If so, see the notes in — you’ll want .flipY = false on each texture, at least.

I have the material working, but the hands are freaking out. Everything looks fine if I don’t move the camera. Once I do (mostly when I zoom in and out) the hands start to freak out. Any thoughts? This doesn’t happen when the mesh isn’t assigned.


Hi Mike,

Try swappping this out:
// Apply envMap to model.
content.traverse((node) => {
if (node.isMesh) {
node.material.envMap = ibl_texture;

For this:

content.traverse( function ( child ) {

    if ( child.isMesh ) {
      child.material.envMap = ibl_texture;
      //shadows are optional:
      //child.castShadow = true;
      //child.receiveShadow = true;

1 Like

Hm, yeah I’m confused by the hands/skinning issue — do you see the same on Or could you share a demo and/or the model? It sounds a bit like

It doesn’t occur in the viewer or within my own code if I keep the material off the mesh. It only happens when I assign the material. I figured out that I have to assign the render target to the envmap in my render method (based on the three.js example file for EXR that I’m using). I can send my repo privately if you’d like to inspect it.

@mikebourbeauart, can you please, show that part of code which you described here:

I figured out that I have to assign the render target to the envmap in my render method

I took several approaches but failed. All this a bit confusing for me.
Thanks in advance!