2

I am making a dungeon crawler type game using three.js. I was using MeshBasicMaterial to make everything "truebright" to make the dungeon consistently visible throughout. However, I wanted to add "bonus" lights coming through under doorways or slits in the wall to give atmosphere. But light doesn't display on BasicMaterial, so I switched over to Phong to test out the light on my floor. Now my floor is black! Most likely because there is no global light.

Is there any way to simulate the properties of MeshBasicMaterial while allowing different colored lights? The dungeon is closed on all four sides, so I imagine that putting an extremely large global light would cast shadows everywhere or override the colors on the ground.

Not the main focus of my question but additionally: how do I make it so the light is stopped by walls instead of just clipping through them? The walls are just 1x1x1 3d mesh cubes spawned by a mapping system.

With MeshBasicMaterial

With MeshPhongMaterial

With MeshPhongMaterial

Edric
  • 24,639
  • 13
  • 81
  • 91
Kerma
  • 69
  • 1
  • 10

1 Answers1

3

Once you switch to MeshPhongMaterial, the material becomes shaded. You can set some parameters to being it closer to MeshBasicMaterial, but you're still going to get gradient lighting, which is really what you're looking for for your "bonus" lighting anyway. In the code below, I set the shininess property to 0, which eliminates the hard light effect of the Phong shading.

To get the light to not bleed through your walls, you'll need to implement shadow casting. This is actually really simple in THREE.js, and there are an abundance of articles online describing how to do it, so I won't duplicate any of that here. But as you can see in my simple example, you need to set your meshes to cast and receive shadows (castShadows/receiveShadows, respectively), and set your light to cast them as well (castShadows).

var renderer, scene, camera, controls, stats;

var WIDTH = window.innerWidth,
  HEIGHT = window.innerHeight,
  FOV = 70,
  NEAR = 1,
  FAR = 1000;

function populateScene() {
  var cfgeo = new THREE.PlaneBufferGeometry(100, 100),
    lwallgeo = new THREE.PlaneBufferGeometry(20, 20),
    rwallgeo = new THREE.PlaneBufferGeometry(50, 20),
    farwallgeo = new THREE.PlaneBufferGeometry(50, 20),
    bumpgeo = new THREE.PlaneBufferGeometry(10, 10);

  var mat = new THREE.MeshPhongMaterial({
    color: 0xcccccc,
    emissive: new THREE.Color(0x0c0c0c),
    shininess: 0,
    side: THREE.DoubleSide
  });

  var ceiling = new THREE.Mesh(cfgeo, mat),
    floor = new THREE.Mesh(cfgeo, mat),
    lwall = new THREE.Mesh(lwallgeo, mat),
    rwall = new THREE.Mesh(rwallgeo, mat),
    farwall = new THREE.Mesh(farwallgeo, mat),
    bump1 = new THREE.Mesh(bumpgeo, mat),
    bump2 = new THREE.Mesh(bumpgeo, mat);
  ceiling.castShadow = true;
  ceiling.receiveShadow = true;
  floor.castShadow = true;
  floor.receiveShadow = true;
  lwall.castShadow = true;
  lwall.receiveShadow = true;
  rwall.castShadow = true;
  rwall.receiveShadow = true;
  farwall.castShadow = true;
  farwall.receiveShadow = true;
  bump1.castShadow = true;
  bump1.receiveShadow = true;
  bump2.castShadow = true;
  bump2.receiveShadow = true;

  ceiling.position.y = 10;
  ceiling.rotation.x = Math.PI / 2;
  floor.position.y = -10;
  floor.rotation.x = Math.PI / -2;
  lwall.rotation.y = Math.PI / 2;
  lwall.position.x = -10;
  rwall.rotation.y = Math.PI / -2;
  rwall.position.x = 10;
  rwall.position.y = 2;
  farwall.position.z = -20;
  bump1.rotation.y = Math.PI / -2;
  bump2.rotation.y = Math.PI / -2;
  bump1.position.set(10, -10, -15);
  bump2.position.set(10, -10, 5);

  scene.add(ceiling);
  scene.add(floor);
  scene.add(lwall);
  scene.add(rwall);
  scene.add(farwall);
  scene.add(bump1);
  scene.add(bump2);

  var bonus = new THREE.SpotLight(0xcccc00, 0.5);
  bonus.position.set(15, -7, -5);
  bonus.castShadow = true;
  bonus.distance = 20;
  var tgt = new THREE.Object3D();
  tgt.position.set(0, -10, -10);
  bonus.target = tgt;
  scene.add(bonus);
  scene.add(tgt);
}

function init() {
  document.body.style.backgroundColor = "slateGray";

  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
  });
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;

  document.body.appendChild(renderer.domElement);
  document.body.style.overflow = "hidden";
  document.body.style.margin = "0";
  document.body.style.padding = "0";

  scene = new THREE.Scene();

  camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
  camera.position.z = 15;
  scene.add(camera);

  controls = new THREE.TrackballControls(camera, renderer.domElement);
  controls.dynamicDampingFactor = 0.5;
  controls.rotateSpeed = 3;

  var light = new THREE.PointLight(0xffffff, 1, Infinity);
  camera.add(light);

  stats = new Stats();
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.top = '0';
  document.body.appendChild(stats.domElement);

  resize();
  window.onresize = resize;

  populateScene();

  animate();
}

function resize() {
  WIDTH = window.innerWidth;
  HEIGHT = window.innerHeight;
  if (renderer && camera && controls) {
    renderer.setSize(WIDTH, HEIGHT);
    camera.aspect = WIDTH / HEIGHT;
    camera.updateProjectionMatrix();
    controls.handleResize();
  }
}

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

function animate() {
  requestAnimationFrame(animate);
  render();
  controls.update();
  stats.update();
}

function threeReady() {
  init();
}

(function() {
  function addScript(url, callback) {
    callback = callback || function() {};
    var script = document.createElement("script");
    script.addEventListener("load", callback);
    script.setAttribute("src", url);
    document.head.appendChild(script);
  }

  addScript("https://threejs.org/build/three.js", function() {
    addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function() {
      addScript("https://threejs.org/examples/js/libs/stats.min.js", function() {
        threeReady();
      })
    })
  })
})();
TheJim01
  • 8,411
  • 1
  • 30
  • 54
  • 1
    I wanted to try `MeshLambertMaterial`, but it causes strange artifacts that I wasn't able to correct. I was able to use `MeshLambertMaterial` for basic shadow casting (`SpotLight` shining on a spinning box over a floor), but the scene above becomes totally messed up if not using `MeshPhongMaterial`. – TheJim01 Jun 15 '17 at 15:50
  • 1
    It worked perfectly, both the shadows tip and the floor colour. But I have an issue with the walls. I don't want them to be solid white, they actually have minor texturing to help distinguish them from the floor and ceiling. But the code you have just appears to bleach them white with artificial light. Is there any way to make them receive light effects without removing the texture like this? http://imgur.com/a/kL7qz – Kerma Jun 30 '17 at 12:57
  • 1
    To which image are you referring? I don't see the light "bleaching" your textures. – TheJim01 Jun 30 '17 at 13:59
  • 1
    Oh sorry, totally my mistake. For some reason I forgot to add what it looks like with `MeshPhongMaterial` working correctly, I linked the wrong first image. https://i.imgur.com/5TVJFX2.png Here is what it looks like with the emission and color set to white, rather than just `MeshBasicMaterial`, like it is in the second picture of the first link. The slit block is still basic for side by side comparison. – Kerma Jul 02 '17 at 10:18