javascripttypescriptthree.jsblender

Problem importing GLTF model in Three.js: Lights not working properly


I have been trying to import a GLTF model into Three.js, but I'm encountering several issues. When I import my scene with all the elements, the lights do not work as expected. The directional lights I configured in Blender (using the Eevee engine) do not appear correctly in Three.js.

Context:

Code:

Here is the code I am using to load the model and set up the lights:

import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { ModelViewerProps } from "./types";

export class ModelViewer {
  private _container: HTMLDivElement | null;
  private _scene: THREE.Scene = new THREE.Scene();
  private _camera: THREE.PerspectiveCamera;
  private _renderer: THREE.WebGLRenderer;
  private _controls?: OrbitControls;

  constructor(props: ModelViewerProps) {
    this._container = props.container;

    this._renderer = new THREE.WebGLRenderer({ antialias: true });
    this._renderer.setPixelRatio(window.devicePixelRatio);
    this._renderer.setSize(window.innerWidth, window.innerHeight);
    this._renderer.shadowMap.enabled = true;
    this._renderer.toneMapping = THREE.ACESFilmicToneMapping;
    this._renderer.toneMappingExposure = 1.25;
    this._renderer.setClearColor(0x000000, 1);

    this._camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    );
    this._camera.position.set(0, 2, 5);

    if (props.useOrbitControls) {
      this._controls = new OrbitControls(
        this._camera,
        this._renderer.domElement
      );
    }

    this.loadModel(props.modelPath);

    if (this._container) {
      this._container.appendChild(this._renderer.domElement);
    }

    window.addEventListener("resize", this.onWindowResize.bind(this));
    this.animate();
  }

  private loadModel(url: string) {
    const loader = new GLTFLoader();
    loader.load(
      url,
      (gltf) => {
        this._scene.add(gltf.scene);
        if (gltf.cameras.length > 0) {
          if (gltf.cameras[0] instanceof THREE.PerspectiveCamera) {
            this._camera = gltf.cameras[0];
          }
        }
      },
      (xhr) => {
        console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
      },
      (error) => {
        console.error(error);
      }
    );
  }

  private onWindowResize() {
    const width = window.innerWidth;
    const height = window.innerHeight;

    this._camera.aspect = width / height;
    this._camera.updateProjectionMatrix();

    this._renderer.setSize(width, height);
  }

  private animate() {
    requestAnimationFrame(() => this.animate());
    if (this._controls) {
      this._controls.update();
    }
    this._renderer.render(this._scene, this._camera);
  }
}

This is how the scene looks in Blender: enter image description here


Solution

  • A few things to keep in mind:

    1. Blender does not export lights to glTF files by default. Under export options, enable Include > Data > Punctual Lights.
    2. Conversions between different lighting systems (Blender to glTF to three.js) are not always straightforward. In Blender there are three options for lighting unit export: Standard, Unitless, and Raw. Standard applies the most technically-correct conversions, but in practice — if you aren't being pretty careful with lighting units and scene sizes and exposure values in three.js — you might find unitless easier to work with.
    3. The image produced in a render is not a predetermined output of particular lighting data. The image formation process (including exposure, tone mapping, color grading, ...) are subjective choices by software and humans. Blender and three.js have some commonalities here (both support AgX tone mapping, for example) but some differences are to be expected.
    4. The image you've rendered in Blender appears to include a "bloom" effect, creating the glow-like haze around the numbers. Effects like bloom are not included in exports, and would need to be re-created manually in three.js, based on its post-processing documentation.

    You may find it easier to create the lighting and post-processing you want directly in three.js, bringing in only the model and materials from Blender. Transferring lighting, effects, and image formation between realtime applications is usually a challenge.

    Updated as of Blender 4.2, three.js r174.