Skip to content

Commit

Permalink
Allow directional lights to automatically follow shadows
Browse files Browse the repository at this point in the history
  • Loading branch information
AdaRoseCannon committed Mar 21, 2022
1 parent cafb63d commit 59d8a5f
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 2 deletions.
8 changes: 7 additions & 1 deletion docs/components/light.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ creating a child entity it targets. For example, pointing down its -Z axis:
</a-light>
```

Directional lights are the most efficient type for adding realtime shadows to a scene.
Directional lights are the most efficient type for adding realtime shadows to a scene. You can use shadows like so:

```html
<a-light type="directional" light="castShadow:true;" position="1 1 1" intensity="0.5" auto-shadow-cam="#objects"></a-light>
```

The `auto-shadow-cam` configuration maps to `light.shadowCameraAutoTarget` which tells the light to automatically update the shadow camera to be the minimum size and position to encompass the target elements.

### Hemisphere

Expand Down
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ <h2>Examples</h2>
<li><a href="showcase/spheres-and-fog/">Spheres and Fog</a></li>
<li><a href="showcase/wikipedia/">Wikipedia</a></li>
<li><a href="boilerplate/hello-world/">Hello World</a></li>
<li><a href="boilerplate/ar-hello-world/">AR Hello World</a></li>
<li><a href="boilerplate/panorama/">360&deg; Image</a></li>
<li><a href="boilerplate/360-video/">360&deg; Video</a></li>
<li><a href="boilerplate/3d-model/">3D Model (glTF)</a></li>
Expand Down
66 changes: 66 additions & 0 deletions src/components/light.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ var CubeLoader = new THREE.CubeTextureLoader();

var probeCache = {};

function distanceOfPointFromPlane (positionOnPlane, planeNormal, p1) {
// the d value in the plane equation a*x + b*y + c*z=d
var d = planeNormal.dot(positionOnPlane);

// distance of point from plane
return (d - planeNormal.dot(p1)) / planeNormal.length();
}

function nearestPointInPlane (positionOnPlane, planeNormal, p1, out) {
var t = distanceOfPointFromPlane(positionOnPlane, planeNormal, p1);
// closest point on the plane
out.copy(planeNormal);
out.multiplyScalar(t);
out.add(p1);
return out;
}

/**
* Light component.
*/
Expand Down Expand Up @@ -42,6 +59,7 @@ module.exports.Component = registerComponent('light', {
shadowCameraBottom: {default: -5, if: {castShadow: true}},
shadowCameraLeft: {default: -5, if: {castShadow: true}},
shadowCameraVisible: {default: false, if: {castShadow: true}},
shadowCameraAutoTarget: {default: '', if: {type: ['spot', 'directional']}},
shadowMapHeight: {default: 512, if: {castShadow: true}},
shadowMapWidth: {default: 512, if: {castShadow: true}},
shadowRadius: {default: 1, if: {castShadow: true}}
Expand Down Expand Up @@ -141,11 +159,59 @@ module.exports.Component = registerComponent('light', {
return;
}

if (data.shadowCameraAutoTarget) {
this.shadowCameraAutoTargetEls = Array.from(document.querySelectorAll(data.shadowCameraAutoTarget));
}

// No light yet or light type has changed. Create and add light.
this.setLight(this.data);
this.updateShadow();
},

tick: (function tickSetup () {
var bbox = new THREE.Box3();
var normal = new THREE.Vector3();
var cameraWorldPosition = new THREE.Vector3();
var tempMat = new THREE.Matrix4();
var sphere = new THREE.Sphere();
var tempVector = new THREE.Vector3();

return function tick () {
if (
this.data.type === 'directional' &&
this.light.shadow &&
this.light.shadow.camera instanceof THREE.OrthographicCamera &&
this.shadowCameraAutoTargetEls.length
) {
var camera = this.light.shadow.camera;
camera.getWorldDirection(normal);
camera.getWorldPosition(cameraWorldPosition);
tempMat.copy(camera.matrixWorld);
tempMat.invert();

camera.near = 1;
camera.left = 100000;
camera.right = -100000;
camera.top = -100000;
camera.bottom = 100000;
this.shadowCameraAutoTargetEls.forEach(function (el) {
bbox.setFromObject(el.object3D);
bbox.getBoundingSphere(sphere);
var distanceToPlane = distanceOfPointFromPlane(cameraWorldPosition, normal, sphere.center);
var pointOnCameraPlane = nearestPointInPlane(cameraWorldPosition, normal, sphere.center, tempVector);

var pointInXYPlane = pointOnCameraPlane.applyMatrix4(tempMat);
camera.near = Math.min(-distanceToPlane - sphere.radius - 1, camera.near);
camera.left = Math.min(-sphere.radius + pointInXYPlane.x, camera.left);
camera.right = Math.max(sphere.radius + pointInXYPlane.x, camera.right);
camera.top = Math.max(sphere.radius + pointInXYPlane.y, camera.top);
camera.bottom = Math.min(-sphere.radius + pointInXYPlane.y, camera.bottom);
});
camera.updateProjectionMatrix();
}
};
}()),

setLight: function (data) {
var el = this.el;
var newLight = this.getLight(data);
Expand Down
3 changes: 2 additions & 1 deletion src/extras/primitives/primitives/a-light.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ registerPrimitive('a-light', {
penumbra: 'light.penumbra',
type: 'light.type',
target: 'light.target',
envmap: 'light.envMap'
envmap: 'light.envMap',
'auto-shadow-cam': 'light.shadowCameraAutoTarget'
}
});

0 comments on commit 59d8a5f

Please sign in to comment.