2
votes

Three.js renders a black scene because of texture loading.

I am using three.js inside an angular component (angular version 8).

I was trying to follow an old three.js tutorial on rendering Earth in WebGL which used now deprecated THREE.ImageUtils.loadTexture() to load a texture for mesh material map.

It didn't work for me, so I tried using modern THREE.TextureLoader().load(). However, for some reason, it never acted upon its callback.

So I tried using THREE.TextureLoader().load() paired with THREE.LoadingManager(). While THREE.LoadingManager()'s callback seems to work, it still produces only a black scene with some light.

While I'm new at using three.js, to me it seems that my code:

  1. Does include light
  2. Does seem to render not only once
  3. Doesn't throw any errors to console

Code:

In my component.html:

...
<div #rendererContainer></div>
...

In my component.ts:

import {Component, OnInit, ViewChild, ElementRef, AfterViewInit} from '@angular/core';
import * as THREE from 'three';

export class MyComponent implements OnInit, AfterViewInit {

  @ViewChild('rendererContainer', {static: false}) rendererContainer: ElementRef;

  renderer = new THREE.WebGLRenderer();
  scene = null;
  camera = null;
  mesh = null;
  earthSurface = new THREE.MeshStandardMaterial({color: 0xaaaaaa});

  constructor() {
    // scene and camera
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000);
    this.camera.position.z = 1.5;

    // light
    this.scene.add(new THREE.AmbientLight(0x333333));
    const light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(5, 3, 5);
    this.scene.add(light);

    const manager = new THREE.LoadingManager();
    const textureLoader = new THREE.TextureLoader(manager);
    this.earthSurface.map = textureLoader.load('planet.jpg');
    manager.onLoad = () => {
      // call back
      const geometry = new THREE.SphereGeometry(0.5, 32, 32);
      this.mesh = new THREE.Mesh(
        geometry,
        this.earthSurface
      );
      this.scene.add(this.mesh);
      this.rerender();
      console.log(this.earthSurface);
    };
  }

  ngOnInit() {}

  ngAfterViewInit() {
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.rendererContainer.nativeElement.appendChild(this.renderer.domElement);
      this.animate();
      // setInterval(() => {this.rerender();}, 1.5 * 1000);
  }

  rerender() {
    this.renderer.render(this.scene, this.camera);
  }

  animate() {
    this.renderer.render(this.scene, this.camera);
    window.requestAnimationFrame(() => this.animate());
  }

}
1

1 Answers

2
votes

I tested out and your code works with some suggestions

You can totally drop rerender function and just make sure to load the color of your scene like this:

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

Make sure, that image that you load has a correct path, you can try out:

this.earthSurface.map = textureLoader.load('https://upload.wikimedia.org/wikipedia/commons/b/b8/Exploding_planet.jpg');