如何在不编辑底层网格的情况下操纵Three.js中的阴影?

时间:2016-05-18 22:25:29

标签: three.js glsl webgl

我正在开发一款应用,应该允许用户操作场景中的3D对象并观察他们的变化如何影响地面阴影:

enter image description here

在这个场景中,黄色圆柱体在白色平面上投下阴影,圆柱体的中间包含在绿色立方体中。我想要发生的是立方体去除阴影的中间,如下所示:

enter image description here

显而易见,我的第一个想法是从黄色圆柱体中减去绿色立方体体积,经过一些谷歌搜索后我找到了CSG.js.不幸的是,CSG.js对于我将要使用的实际模型来说太慢了,这将至少有15k个顶点。

我开始深入研究Three.js源码并阅读阴影贴​​图以了解阴影是如何产生的,但是我的shader-fu还不够强大,无法完全掌握如何调整阴影渲染。

我怎样才能实现这个"阴影减法"实现

var camera, scene, renderer;

init();
animate();

function init() {
  scene = new THREE.Scene();

  camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
  camera.position.z = 500;
  camera.position.y = 100;
  camera.lookAt(scene.position);

  var ambient = new THREE.AmbientLight(0x909090);
  scene.add(ambient);

  var directionalLight = new THREE.DirectionalLight( 0xffffff, 1.0 );
  directionalLight.position.set( -300, 300, 0 );
  directionalLight.castShadow = true;
  directionalLight.shadow.camera.near    =   10;
  directionalLight.shadow.camera.far     =   2000;
  directionalLight.shadow.camera.right   =   350;
  directionalLight.shadow.camera.left    =  -350;
  directionalLight.shadow.camera.top     =   350;
  directionalLight.shadow.camera.bottom  =  -350;
  directionalLight.shadow.mapSize.width  = 1024;
  directionalLight.shadow.mapSize.height = 1024;
  scene.add( directionalLight );

  //var lightHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
  //scene.add(lightHelper);

  var geometry = new THREE.CylinderGeometry( 50, 50, 400, 32 );
  var material = new THREE.MeshPhongMaterial( {color: 0xffff00} );
  var cylinder = new THREE.Mesh( geometry, material );
  cylinder.castShadow = true;
  scene.add( cylinder );

  var geometry = new THREE.BoxGeometry( 110, 110, 110 );
  var material = new THREE.MeshPhongMaterial( {color: 0x00ff00} );
  var cube = new THREE.Mesh( geometry, material );
  cube.castShadow = true;
  scene.add( cube );

  var geometry = new THREE.PlaneGeometry( 3000, 3000, 32 );
  var material = new THREE.MeshPhongMaterial( {color: 0xffffff, side: THREE.DoubleSide} );
  var plane = new THREE.Mesh( geometry, material );
  plane.lookAt(new THREE.Vector3(0, 1, 0));
  plane.position.y = -200;
  plane.receiveShadow = true;
  scene.add( plane );

  renderer = new THREE.WebGLRenderer();
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.BasicShadowMap;
  document.body.appendChild( renderer.domElement );

  window.addEventListener( 'resize', onWindowResize, false );
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate() {
  requestAnimationFrame( animate );

  renderer.render( scene, camera );
}

jsFiddle

更新

更复杂的场景怎么样?红色圆柱体的阴影是否可以不受影响(你可以看到它被cube.customDepthMaterial = new THREE.MeshBasicMaterial({ depthTest: false})切成两半)?

enter image description here

Updated jsFiddle

1 个答案:

答案 0 :(得分:4)

您可以通过设置对象的.customDepthMaterial属性来从场景的其余部分中减去对象的阴影:

var cube = new THREE.Mesh( geometry, material );
cube.castShadow = true;
cube.receiveShadow = false;

// The secret sauce
cube.customDepthMaterial =
  new THREE.MeshBasicMaterial({ depthTest: false});

scene.add( cube );

jsFiddle

不需要着色器。

为什么会这样做
渲染阴影贴图时,每个对象的深度材质(.customDepthMaterial或默认值)为used to render the scene from the light's perspective。深度材质的结果渲染表示物体的深度来自作为RGBA打包的相机。由于THREE.MeshBasicMaterial默认为{ color: 0xffffff, opacity: 1 },因此它将返回使对象比阴影相机far更远的最大深度。

我已停用depthTest,因为在您想要的结果屏幕截图中,您剪切了给定圆柱体的多维数据集所在的区域。禁用depthTest意味着被圆柱体阻挡的立方体部分仍将切除阴影,为您提供所需的结果。

<强>文档
很遗憾,.customDepthMaterial还没有关于color的文档,但我确实找到了使用它的an official example

更新后的答案:
允许对象的阴影始终显示:

  1. 您可以使用与上述相同的技巧,只需将素材的opacity0设置为depthTest
  2. 确保在“减法阴影”对象之后将其添加到场景中。这样,即使添加阴影都{{1}}已禁用,添加阴影也会胜出。
  3. updated jsFiddle

    如果你有更复杂的事情,那么你需要找到一种方法来管理阴影渲染的顺序。

    在r77中测试