THREE.js:模拟 MeshBasicMaterial,同时允许彩色光

THREE.js: Simulating MeshBasicMaterial while allowing colored lights

我正在使用 three.js 制作地牢探索类游戏。我使用 MeshBasicMaterial 制作所有内容 "truebright" 以使地牢始终可见。但是,我想添加 "bonus" 从门口或墙上的缝隙中射进来的灯光来营造气氛。但是 BasicMaterial 上没有显示灯光,所以我切换到 Phong 来测试地板上的灯光。现在我的地板是黑色的!很可能是因为没有全局光。

有没有办法模拟MeshBasicMaterial的属性同时允许不同颜色的光?地牢的四个侧面都是封闭的,所以我想放一个非常大的全局光会到处投射阴影或覆盖地面上的颜色。

这不是我的问题的主要焦点,但另外:我如何做到让光线被墙壁阻挡而不是仅仅穿过它们?墙壁只是由映射系统生成的 1x1x1 3d 网格立方体。

一旦切换到 MeshPhongMaterial,material 就会变暗。您可以将一些参数设置为更接近 MeshBasicMaterial,但您仍然会获得渐变照明,这确实是您正在寻找的 "bonus" 照明。在下面的代码中,我将 shininess 属性 设置为 0,这消除了 Phong 着色的硬光效果。

要使光线不渗入墙壁,您需要实施阴影投射。这在 THREE.js 中实际上非常简单,并且网上有大量文章描述了如何做,所以我不会在这里重复任何内容。但正如您在我的简单示例中看到的那样,您需要将网格设置为投射和接收阴影(分别为 castShadows/receiveShadows),并将灯光也设置为投射它们(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();
      })
    })
  })
})();