THREE.js:模拟MeshBasicMaterial同时允许彩灯

Posted

技术标签:

【中文标题】THREE.js:模拟MeshBasicMaterial同时允许彩灯【英文标题】:THREE.js: Simulating MeshBasicMaterial while allowing colored lights 【发布时间】:2017-06-11 04:56:59 【问题描述】:

我正在使用three.js 制作一个地牢爬行类游戏。我正在使用 MeshBasicMaterial 使所有东西都“真正明亮”,以使地牢始终可见。但是,我想在门口或墙上的缝隙下添加“奖励”灯以营造氛围。但是 BasicMaterial 上不显示灯光,所以我切换到 Phong 来测试我地板上的灯光。现在我的地板是黑色的!很可能是因为没有全局光源。

有什么方法可以模拟MeshBasicMaterial的属性,同时允许不同颜色的灯光?地牢的四个面都是封闭的,所以我想放置一个非常大的全局光会在各处投射阴影或覆盖地面的颜色。

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

【问题讨论】:

为什么不使用THREE.MeshBasicMaterial()maplightMap属性来制作你想要的效果? 【参考方案1】:

切换到MeshPhongMaterial 后,材质将变为阴影。您可以将一些参数设置为更接近MeshBasicMaterial,但您仍将获得渐变照明,这确实是您正在寻找的“奖励”照明。在下面的代码中,我将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();
      )
    )
  )
)();

【讨论】:

我想尝试MeshLambertMaterial,但它会导致我无法纠正的奇怪伪影。我可以使用MeshLambertMaterial 进行基本的阴影投射(SpotLight 照在地板上的旋转盒子上),但是如果不使用MeshPhongMaterial,上面的场景就会完全混乱。 效果非常好,无论是阴影尖端还是地板颜色。但是我有墙壁的问题。我不希望它们是纯白色的,它们实际上具有较小的纹理以帮助将它们与地板和天花板区分开来。但是您刚刚使用的代码似乎用人造光将它们漂白了。有什么方法可以让它们在不去除这样的纹理的情况下获得光效? imgur.com/a/kL7qz 您指的是哪张图片?我没有看到光线“漂白”你的纹理。 哦,对不起,完全是我的错。出于某种原因,我忘记添加 MeshPhongMaterial 正常工作的样子,我链接了错误的第一张图片。 i.imgur.com/5TVJFX2.png 这是发射和颜色设置为白色的样子,而不仅仅是MeshBasicMaterial,就像在第一个链接的第二张图片中一样。狭缝块仍然是并排比较的基础。

以上是关于THREE.js:模拟MeshBasicMaterial同时允许彩灯的主要内容,如果未能解决你的问题,请参考以下文章

使用键盘箭头使用three.js 进行第一人称模拟

模拟楼宇扫光效果(three.js实战12)

使用 bufferGeometry 获取 THREE.js Points 对象中顶点的屏幕坐标

Three.js重建真实地形教程

SphereBufferGeometry 上的 THREE.js ShaderMaterial UV 包裹问题

Three.js 之灯光