Skip to content

Commit 81ec901

Browse files
committed
Add exposure and no tone mapping and allow directional lights to automatically follow shadows
1 parent cafb63d commit 81ec901

File tree

7 files changed

+126
-3
lines changed

7 files changed

+126
-3
lines changed

docs/components/light.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,13 @@ creating a child entity it targets. For example, pointing down its -Z axis:
9191
</a-light>
9292
```
9393

94-
Directional lights are the most efficient type for adding realtime shadows to a scene.
94+
Directional lights are the most efficient type for adding realtime shadows to a scene. You can use shadows like so:
95+
96+
```html
97+
<a-light type="directional" light="castShadow:true;" position="1 1 1" intensity="0.5" auto-shadow-cam="#objects"></a-light>
98+
```
99+
100+
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.
95101

96102
### Hemisphere
97103

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Hello, World! • A-Frame</title>
6+
<meta name="description" content="Hello, World! • A-Frame">
7+
<script src="../../../dist/aframe-master.js"></script>
8+
<script>
9+
AFRAME.registerComponent('follow-shadow', {
10+
schema: {type: 'selector'},
11+
init() {this.el.object3D.renderOrder = -1;},
12+
tick() {
13+
if (this.data) {
14+
this.el.object3D.position.copy(this.data.object3D.position);
15+
this.el.object3D.position.y-=0.001; // stop z-fighting
16+
}
17+
}
18+
});
19+
</script>
20+
</head>
21+
<body>
22+
<a-scene
23+
reflection="directionalLight:a-light[type=directional]"
24+
ar-hit-test="target:#objects;"
25+
>
26+
<a-light type="directional" light="castShadow:true;" position="1 1 1" intensity="0.5" auto-shadow-cam="#objects"></a-light>
27+
<a-camera position="0 0.4 0" wasd-controls="acceleration:10;"></a-camera>
28+
<a-entity id="objects" scale="0.2 0.2 0.2" position="0 0 -1" shadow>
29+
<a-box position="-1 0.5 1" rotation="0 45 0" color="#4CC3D9"></a-box>
30+
<a-sphere position="0 1.25 -1" radius="1.25" color="#EF2D5E"></a-sphere>
31+
<a-cylinder position="1 0.75 1" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
32+
</a-entity>
33+
<a-plane follow-shadow="#objects" material="shader:shadow" shadow="cast:false;" rotation="-90 0 0" width="2" height="2"></a-plane>
34+
<a-sky color="#ECECEC" hide-on-enter-ar></a-sky>
35+
</a-scene>
36+
</body>
37+
</html>

examples/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ <h2>Examples</h2>
148148
<li><a href="showcase/spheres-and-fog/">Spheres and Fog</a></li>
149149
<li><a href="showcase/wikipedia/">Wikipedia</a></li>
150150
<li><a href="boilerplate/hello-world/">Hello World</a></li>
151+
<li><a href="boilerplate/ar-hello-world/">AR Hello World</a></li>
151152
<li><a href="boilerplate/panorama/">360&deg; Image</a></li>
152153
<li><a href="boilerplate/360-video/">360&deg; Video</a></li>
153154
<li><a href="boilerplate/3d-model/">3D Model (glTF)</a></li>

src/components/light.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,23 @@ var CubeLoader = new THREE.CubeTextureLoader();
1111

1212
var probeCache = {};
1313

14+
function distanceOfPointFromPlane (positionOnPlane, planeNormal, p1) {
15+
// the d value in the plane equation a*x + b*y + c*z=d
16+
var d = planeNormal.dot(positionOnPlane);
17+
18+
// distance of point from plane
19+
return (d - planeNormal.dot(p1)) / planeNormal.length();
20+
}
21+
22+
function nearestPointInPlane (positionOnPlane, planeNormal, p1, out) {
23+
var t = distanceOfPointFromPlane(positionOnPlane, planeNormal, p1);
24+
// closest point on the plane
25+
out.copy(planeNormal);
26+
out.multiplyScalar(t);
27+
out.add(p1);
28+
return out;
29+
}
30+
1431
/**
1532
* Light component.
1633
*/
@@ -42,6 +59,7 @@ module.exports.Component = registerComponent('light', {
4259
shadowCameraBottom: {default: -5, if: {castShadow: true}},
4360
shadowCameraLeft: {default: -5, if: {castShadow: true}},
4461
shadowCameraVisible: {default: false, if: {castShadow: true}},
62+
shadowCameraAutoTarget: {default: '', if: {type: ['spot', 'directional']}},
4563
shadowMapHeight: {default: 512, if: {castShadow: true}},
4664
shadowMapWidth: {default: 512, if: {castShadow: true}},
4765
shadowRadius: {default: 1, if: {castShadow: true}}
@@ -141,11 +159,59 @@ module.exports.Component = registerComponent('light', {
141159
return;
142160
}
143161

162+
if (data.shadowCameraAutoTarget) {
163+
this.shadowCameraAutoTargetEls = Array.from(document.querySelectorAll(data.shadowCameraAutoTarget));
164+
}
165+
144166
// No light yet or light type has changed. Create and add light.
145167
this.setLight(this.data);
146168
this.updateShadow();
147169
},
148170

171+
tick: (function tickSetup () {
172+
var bbox = new THREE.Box3();
173+
var normal = new THREE.Vector3();
174+
var cameraWorldPosition = new THREE.Vector3();
175+
var tempMat = new THREE.Matrix4();
176+
var sphere = new THREE.Sphere();
177+
var tempVector = new THREE.Vector3();
178+
179+
return function tick () {
180+
if (
181+
this.data.type === 'directional' &&
182+
this.light.shadow &&
183+
this.light.shadow.camera instanceof THREE.OrthographicCamera &&
184+
this.shadowCameraAutoTargetEls.length
185+
) {
186+
var camera = this.light.shadow.camera;
187+
camera.getWorldDirection(normal);
188+
camera.getWorldPosition(cameraWorldPosition);
189+
tempMat.copy(camera.matrixWorld);
190+
tempMat.invert();
191+
192+
camera.near = 1;
193+
camera.left = 100000;
194+
camera.right = -100000;
195+
camera.top = -100000;
196+
camera.bottom = 100000;
197+
this.shadowCameraAutoTargetEls.forEach(function (el) {
198+
bbox.setFromObject(el.object3D);
199+
bbox.getBoundingSphere(sphere);
200+
var distanceToPlane = distanceOfPointFromPlane(cameraWorldPosition, normal, sphere.center);
201+
var pointOnCameraPlane = nearestPointInPlane(cameraWorldPosition, normal, sphere.center, tempVector);
202+
203+
var pointInXYPlane = pointOnCameraPlane.applyMatrix4(tempMat);
204+
camera.near = Math.min(-distanceToPlane - sphere.radius - 1, camera.near);
205+
camera.left = Math.min(-sphere.radius + pointInXYPlane.x, camera.left);
206+
camera.right = Math.max(sphere.radius + pointInXYPlane.x, camera.right);
207+
camera.top = Math.max(sphere.radius + pointInXYPlane.y, camera.top);
208+
camera.bottom = Math.min(-sphere.radius + pointInXYPlane.y, camera.bottom);
209+
});
210+
camera.updateProjectionMatrix();
211+
}
212+
};
213+
}()),
214+
149215
setLight: function (data) {
150216
var el = this.el;
151217
var newLight = this.getLight(data);

src/extras/primitives/primitives/a-light.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ registerPrimitive('a-light', {
1515
penumbra: 'light.penumbra',
1616
type: 'light.type',
1717
target: 'light.target',
18-
envmap: 'light.envMap'
18+
envmap: 'light.envMap',
19+
'auto-shadow-cam': 'light.shadowCameraAutoTarget'
1920
}
2021
});

src/shaders/flat.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ module.exports.Shader = registerShader('flat', {
1515
src: {type: 'map'},
1616
width: {default: 512},
1717
wireframe: {default: false},
18-
wireframeLinewidth: {default: 2}
18+
wireframeLinewidth: {default: 2},
19+
toneMapped: {default: true}
1920
},
2021

2122
/**
@@ -63,6 +64,7 @@ function getMaterialData (data, materialData) {
6364
materialData.color.set(data.color);
6465
materialData.fog = data.fog;
6566
materialData.wireframe = data.wireframe;
67+
materialData.toneMapped = data.toneMapped;
6668
materialData.wireframeLinewidth = data.wireframeLinewidth;
6769
return materialData;
6870
}

src/systems/renderer.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ module.exports.System = registerSystem('renderer', {
1616
maxCanvasWidth: {default: 1920},
1717
maxCanvasHeight: {default: 1920},
1818
physicallyCorrectLights: {default: false},
19+
exposure: {default: 1, if: {type: ['ACESFilmic', 'Linear', 'Reinhard', 'Cineon']}},
20+
toneMapping: {default: 'No', oneOf: ['No', 'ACESFilmic', 'Linear', 'Reinhard', 'Cineon']},
1921
precision: {default: 'high', oneOf: ['high', 'medium', 'low']},
2022
sortObjects: {default: false},
2123
colorManagement: {default: false},
@@ -31,6 +33,7 @@ module.exports.System = registerSystem('renderer', {
3133
var renderer = sceneEl.renderer;
3234
renderer.sortObjects = data.sortObjects;
3335
renderer.physicallyCorrectLights = data.physicallyCorrectLights;
36+
renderer.toneMapping = THREE[this.data.toneMapping + 'ToneMapping'];
3437

3538
if (data.colorManagement || data.gammaOutput) {
3639
renderer.outputEncoding = THREE.sRGBEncoding;
@@ -48,6 +51,13 @@ module.exports.System = registerSystem('renderer', {
4851
}
4952
},
5053

54+
update: function () {
55+
var data = this.data;
56+
var sceneEl = this.el;
57+
var renderer = sceneEl.renderer;
58+
renderer.toneMappingExposure = data.exposure;
59+
},
60+
5161
applyColorCorrection: function (colorOrTexture) {
5262
if (!this.data.colorManagement || !colorOrTexture) {
5363
return;

0 commit comments

Comments
 (0)