Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cull shadow commands #3819

Merged
merged 54 commits into from
Apr 15, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f58ded2
Cull shadow commands
lilleyse Apr 7, 2016
7b943e8
Merge branch 'shadows' into shadows-commands
lilleyse Apr 7, 2016
9dbb5c3
Use numberOfPasses
lilleyse Apr 7, 2016
ae48196
Fix precision issues with spot lights
lilleyse Apr 7, 2016
cc09976
getTerrainShadowCommands temp
lilleyse Apr 8, 2016
b5c45ea
Added a test version of getTerrainShadowCommands
lilleyse Apr 8, 2016
def5187
Added radiusSquared
lilleyse Apr 11, 2016
40a68bc
Added grid button
lilleyse Apr 11, 2016
0582b1f
Check when shadow map is out of view and needs update
lilleyse Apr 8, 2016
6babfbd
Merge branch 'shadows-commands' into shadows-multiple
bagnell Apr 11, 2016
c30e606
Add TODO for 3d-tiles
lilleyse Apr 12, 2016
916050d
Merge branch 'shadows-derived' into shadows-merged
bagnell Apr 12, 2016
249322a
Merge branch 'shadows-commands' into shadows-merged
bagnell Apr 12, 2016
21f79df
Temporarily disable globe update when getting terrain commands.
bagnell Apr 12, 2016
8648679
Move around shadow map and globe updates. Make an array of shadow maps.
bagnell Apr 12, 2016
aba1981
Add ability to render to multiple shadow maps.
bagnell Apr 12, 2016
465d88a
Rename pass to shadowPass
lilleyse Apr 12, 2016
32afa8e
Change angle check from 0.1 to 0.05
lilleyse Apr 12, 2016
481b239
Merge branch 'shadows' into shadows-commands
lilleyse Apr 12, 2016
bd98b6b
Add Sandcastle example to for multiple shadow maps and demonstrates t…
bagnell Apr 12, 2016
b3c5fcf
Merge branch 'shadows' into shadows-multiple
bagnell Apr 12, 2016
16da6a8
Add temporary fix for shadow casters
lilleyse Apr 13, 2016
6471382
Prevent crash when shadow map is not enabled
lilleyse Apr 13, 2016
8409ee6
Fix positionCartographic
lilleyse Apr 12, 2016
de6192e
Reduce terrain aliasing
lilleyse Apr 12, 2016
7436308
Improve shadow quality for the closest cascade
lilleyse Apr 12, 2016
0d1b1f1
Model can toggle casting or receiving shadows at runtime
lilleyse Apr 12, 2016
fee0316
Remove renderState getters
lilleyse Apr 13, 2016
282e963
Don't render terrain skirts for shadow casters
lilleyse Apr 13, 2016
b756123
Change Camera back to inverseTransformation
lilleyse Apr 13, 2016
833f158
Remove unneeded normalize in GlobeFS
lilleyse Apr 13, 2016
9cb906f
Turn off normal shading if cast shadows is false
lilleyse Apr 13, 2016
4f1b65e
Added button for terrain receive shadows
lilleyse Apr 13, 2016
6407d78
Fix visibility check after 0582b1f
lilleyse Apr 13, 2016
49cb616
Fix broken 2d view
lilleyse Apr 13, 2016
2cffb31
Add more model position options, including space
lilleyse Apr 13, 2016
1bd1c37
Slight bias tweaks
lilleyse Apr 13, 2016
f1ede3c
Merge branch 'shadows-commands' into shadows-multiple
bagnell Apr 14, 2016
5677298
Revert changes to shadows Sandcastle example.
bagnell Apr 14, 2016
e59ef50
Updates after merge.
bagnell Apr 14, 2016
9ef9d74
Merge pull request #3848 from AnalyticalGraphicsInc/shadows-misc
bagnell Apr 14, 2016
ad94eb8
Merge branch 'shadows-commands' into shadows-multiple
bagnell Apr 14, 2016
1b89577
Update after merge.
bagnell Apr 14, 2016
524e431
Formatting
lilleyse Apr 14, 2016
6198cfb
Remove FrameState.shadowMap
lilleyse Apr 14, 2016
9c6f37f
Remove TODO
lilleyse Apr 14, 2016
5538b42
Fix camera clone bug
lilleyse Apr 14, 2016
ba3a661
Tweak point light bias
lilleyse Apr 14, 2016
df04f64
Added shadow darkness uniform
lilleyse Apr 14, 2016
5626da7
Increase default maximum shadow distance
lilleyse Apr 14, 2016
21faca4
Added model with transparent texture
lilleyse Apr 15, 2016
a28d349
Only set maximum cascade sizes when the scene camera is close
lilleyse Apr 15, 2016
1abfe29
Merge pull request #3852 from AnalyticalGraphicsInc/shadows-multiple
lilleyse Apr 15, 2016
cf89f82
Merge pull request #3855 from AnalyticalGraphicsInc/shadows-misc
bagnell Apr 15, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Source/Scene/Scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,7 @@ define([
// When moving the camera low LOD terrain tiles begin to load, whose bounding volumes
// throw off the near/far fitting for the shadow map. Only update shadowNear and shadowFar
// for reasonably sized bounding volumes.
if (!(distances.start < 0.0 && distances.stop > frameState.shadowFar)) {
if (!((distances.start < -100.0) && (distances.stop > 100.0) && (pass === Pass.GLOBE))){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to add a TODO that 3D Tiles are not part of Pass.GLOBE. However, we can introduce Pass.ENVIRONMENT, etc., but will need to think through opaque vs. translucent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add the TODO. I'll have to try it out, but it may not actually be a problem.

shadowNear = Math.min(shadowNear, distances.start);
shadowFar = Math.max(shadowFar, distances.stop);
}
Expand Down Expand Up @@ -1791,6 +1791,10 @@ define([
var isPointLight = shadowMap.isPointLight;
var renderState = isPointLight ? shadowMap.pointRenderState : shadowMap.primitiveRenderState;

if (shadowMap.outOfView) {
return;
}

var shadowPassCommands = shadowMap.passCommands;
resetShadowCommands(shadowPassCommands);

Expand Down
191 changes: 144 additions & 47 deletions Source/Scene/ShadowMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ define([
'../Core/GeometryAttribute',
'../Core/GeometryAttributes',
'../Core/GeometryInstance',
'../Core/Intersect',
'../Core/Math',
'../Core/Matrix4',
'../Core/PixelFormat',
Expand Down Expand Up @@ -70,6 +71,7 @@ define([
GeometryAttribute,
GeometryAttributes,
GeometryInstance,
Intersect,
CesiumMath,
Matrix4,
PixelFormat,
Expand Down Expand Up @@ -145,6 +147,10 @@ define([
this.softShadows = defaultValue(options.softShadows, false);
this._exponentialShadows = false;

this._outOfView = false;
this._outOfViewPrevious = false;
this._needsUpdate = true;

this._terrainBias = {
polygonOffset : true,
polygonOffsetFactor : 1.1,
Expand Down Expand Up @@ -196,6 +202,7 @@ define([
this._shadowMapCamera = new ShadowMapCamera();
this._shadowMapCullingVolume = undefined;
this._sceneCamera = undefined;
this._boundingSphere = new BoundingSphere();

this._isPointLight = defaultValue(options.isPointLight, false);
this._radius = defaultValue(options.radius, 100.0);
Expand All @@ -210,6 +217,7 @@ define([
if (this._cascadesEnabled) {
// Cascaded shadows are always orthographic. The frustum dimensions are calculated on the fly.
this._lightCamera.frustum = new OrthographicFrustum();
this._shadowMapCamera.frustum = new OrthographicFrustum();
} else if (defined(this._lightCamera.frustum.fov)) {
// If the light camera uses a perspective frustum, then the light source is a spot light
this._isSpotLight = true;
Expand Down Expand Up @@ -248,7 +256,6 @@ define([
this._debugFreezeFrame = false;
this.debugVisualizeCascades = false;
this._debugLightFrustum = undefined;
this._debugLightFrustumMatrix = new Matrix4();
this._debugCameraFrustum = undefined;
this._debugCascadeFrustums = new Array(this._numberOfCascades);
this._debugShadowViewCommand = undefined;
Expand Down Expand Up @@ -314,6 +321,15 @@ define([
};

defineProperties(ShadowMap.prototype, {
/**
* Whether the shadow map is out of view of the scene camera.
*/
outOfView : {
get : function() {
return this._outOfView;
}
},

/**
* The camera representing the shadow volume.
*
Expand Down Expand Up @@ -609,6 +625,8 @@ define([
} else {
createFramebufferColor(shadowMap, context);
}

clearFramebuffer(shadowMap, context);
}

function checkFramebuffer(shadowMap, context) {
Expand All @@ -629,6 +647,14 @@ define([
}
}

function clearFramebuffer(shadowMap, context, pass) {
pass = defaultValue(pass, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is pass? The enum?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, is this the cascade pass? If so, let's name it that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could also be the point light faces.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe shadowPass or shadowPhase.

if ((shadowMap._isPointLight && shadowMap._usesCubeMap) || (pass === 0)) {
shadowMap._clearCommand.framebuffer = shadowMap._passFramebuffers[pass];
shadowMap._clearCommand.execute(context, shadowMap._clearPassState);
}
}

ShadowMap.prototype.setSize = function(size) {
this._shadowMapSize = size;
var numberOfPasses = this._numberOfPasses;
Expand Down Expand Up @@ -932,24 +958,20 @@ define([
}
}
} else if (shadowMap._isPointLight) {
var translation = shadowMap._shadowMapCamera.positionWC;
var rotation = Quaternion.IDENTITY;
var uniformScale = shadowMap._radius * 2.0;
var scale = Cartesian3.fromElements(uniformScale, uniformScale, uniformScale, scratchScale);
var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(translation, rotation, scale, scratchMatrix);
if (!defined(shadowMap._debugLightFrustum) || !Matrix4.equals(modelMatrix, shadowMap._debugLightFrustumMatrix)) {
// Recreate debug point light if the radius or position changes
if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
var translation = shadowMap._shadowMapCamera.positionWC;
var rotation = Quaternion.IDENTITY;
var uniformScale = shadowMap._radius * 2.0;
var scale = Cartesian3.fromElements(uniformScale, uniformScale, uniformScale, scratchScale);
var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(translation, rotation, scale, scratchMatrix);

shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
shadowMap._debugLightFrustum = createDebugPointLight(modelMatrix, Color.YELLOW);
Matrix4.clone(modelMatrix, shadowMap._debugLightFrustumMatrix);
}
shadowMap._debugLightFrustum.update(frameState);
} else {
var matrix = shadowMap._shadowMapCamera.getViewProjection();
if (!defined(shadowMap._debugLightFrustum) || !Matrix4.equals(matrix, shadowMap._debugLightFrustumMatrix)) {
// Recreate debug frustum if the light camera changes
if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
shadowMap._debugLightFrustum = createDebugFrustum(shadowMap._shadowMapCamera, Color.YELLOW);
Matrix4.clone(matrix, shadowMap._debugLightFrustumMatrix);
}
shadowMap._debugLightFrustum.update(frameState);
}
Expand Down Expand Up @@ -1129,7 +1151,7 @@ define([

// TODO : This is just an estimate, should take scene geometry into account
// 2. Set bounding box back to include objects in the light's view
max.z += 100.0; // Note: in light space, a positive number is behind the camera
max.z += 1000.0; // Note: in light space, a positive number is behind the camera

// 3. Adjust light view matrix so that it is centered on the bounding volume
var translation = scratchTranslation;
Expand Down Expand Up @@ -1211,16 +1233,90 @@ define([
}
}

var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchBoundingSphere = new BoundingSphere();
var scratchCenter = scratchBoundingSphere.center;

function checkVisibility(shadowMap, frameState) {
var camera = frameState.camera;
var lightCamera = shadowMap._lightCamera;
var sceneCamera = shadowMap._sceneCamera;
var shadowMapCamera = shadowMap._shadowMapCamera;

var boundingSphere = scratchBoundingSphere;

// Check whether the shadow map is in view and needs to be updated
if (shadowMap._cascadesEnabled) {
// If the nearest shadow receiver is further than the shadow map's maximum distance then the shadow map is out of view.
if (sceneCamera.frustum.near > shadowMap._maximumDistance) {
shadowMap._outOfView = true;
shadowMap._needsUpdate = false;
return;
}

// If the light source is below the horizon then the shadow map is out of view
var surfaceNormal = frameState.mapProjection.ellipsoid.geodeticSurfaceNormal(sceneCamera.positionWC, scratchCartesian1);
var lightDirection = Cartesian3.negate(shadowMapCamera.directionWC, scratchCartesian2);
var dot = Cartesian3.dot(surfaceNormal, lightDirection);
if (dot < 0.1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an epsilon in Cesium.Math. 0.1 seems huge, how did you come up with it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the Sandcastle I just cut if off at a light horizon above 85 degrees. Maybe that's too much.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever you think based on your "empirical evidence."

shadowMap._outOfView = true;
shadowMap._needsUpdate = false;
return;
}

// If the scene camera or light direction changes, then the shadow map needs to update
var sceneCameraChanged = !Matrix4.equals(sceneCamera.viewMatrix, camera.viewMatrix);
var directionChanged = !Cartesian3.equals(shadowMapCamera.directionWC, lightCamera.directionWC);
shadowMap._needsUpdate = sceneCameraChanged || directionChanged;

// By default cascaded shadows are always in view
shadowMap._outOfView = false;
} else if (shadowMap._isPointLight) {
// Sphere-frustum intersection test
boundingSphere.center = shadowMapCamera.positionWC;
boundingSphere.radius = shadowMap._radius;
shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
} else {
// Simplify frustum-frustum intersection test as a sphere-frustum test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth adding a more precise test on the roadmap? Or perhaps the CPU overhead will be too high to justify it?

BTW have you seen http://www.realtimerendering.com/intersections.html?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did check out that link, do we have GPU Gems 4 lying around? That was my thought, not worth the hassle since sphere-frustum seems good enough.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put it on the roadmap. There is not a GPU Gems 4 AFAIK. Perhaps GPU Pro 4?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's actually Graphics Gems, anyways I'll add to the road map.

var frustumRadius = shadowMapCamera.frustum.far / 2.0;
var frustumCenter = Cartesian3.add(shadowMapCamera.positionWC, Cartesian3.multiplyByScalar(shadowMapCamera.directionWC, frustumRadius, scratchCenter), scratchCenter);
boundingSphere.center = frustumCenter;
boundingSphere.radius = frustumRadius;
shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
}
}

function updateCameras(shadowMap, frameState) {
if (shadowMap.debugFreezeFrame) {
return;
var camera = frameState.camera; // The actual camera in the scene
var lightCamera = shadowMap._lightCamera; // The external camera representing the light source
var sceneCamera = shadowMap._sceneCamera; // Clone of camera, with clamped near and far planes
var shadowMapCamera = shadowMap._shadowMapCamera; // Camera representing the shadow volume, initially cloned from lightCamera

if (defined(sceneCamera)) {
// Skip check on the first frame
checkVisibility(shadowMap, frameState);
}

var camera = frameState.camera;
// Clear the shadow texture when a cascaded shadow map goes out of view (e.g. when the sun dips below the horizon).
// This prevents objects that still read from the shadow map from reading old values.
if (shadowMap._cascadesEnabled && !shadowMap._outOfViewPrevious && shadowMap._outOfView) {
clearFramebuffer(shadowMap, frameState.context);
}
shadowMap._outOfViewPrevious = shadowMap._outOfView;

// Clone light camera into the shadow map camera
var shadowMapCamera = shadowMap._shadowMapCamera;
shadowMapCamera.clone(shadowMap._lightCamera);
if (shadowMap._cascadesEnabled) {
Cartesian3.clone(lightCamera.directionWC, shadowMapCamera.directionWC);
} else if (shadowMap._isPointLight) {
Cartesian3.clone(lightCamera.positionWC, shadowMapCamera.positionWC);
} else {
shadowMapCamera.clone(lightCamera);
}

// Get the light direction in eye coordinates
var lightDirection = shadowMap._lightDirectionEC;
Expand All @@ -1237,50 +1333,54 @@ define([
var far;
if (shadowMap._fitNearFar) {
// shadowFar can be very large, so limit to shadowMap._maximumDistance
near = frameState.shadowNear;
far = Math.max(Math.min(frameState.shadowFar, shadowMap._maximumDistance), near + 0.01);
near = Math.min(frameState.shadowNear, shadowMap._maximumDistance);
far = Math.min(frameState.shadowFar, shadowMap._maximumDistance);
} else {
near = camera.frustum.near;
far = shadowMap._maximumDistance;
}

shadowMap._sceneCamera = Camera.clone(camera, shadowMap._sceneCamera);
shadowMap._sceneCamera = Camera.clone(camera, sceneCamera);
shadowMap._sceneCamera.frustum.near = near;
shadowMap._sceneCamera.frustum.far = far;
shadowMap._distance = far - near;
}

ShadowMap.prototype.update = function(frameState) {
var lightMoved = !Cartesian3.equals(this._lightCamera.positionWC, this._shadowMapCamera.positionWC);

updateFramebuffer(this, frameState.context);
updateCameras(this, frameState);

if (this._isPointLight && lightMoved) {
computeOmnidirectional(this);
}
if (this._needsUpdate) {
updateFramebuffer(this, frameState.context);

if (this._cascadesEnabled) {
fitShadowMapToScene(this, frameState);
if (this._isPointLight) {
computeOmnidirectional(this);
}

if (this._cascadesEnabled) {
fitShadowMapToScene(this, frameState);

if (this._numberOfCascades > 1) {
computeCascades(this);
if (this._numberOfCascades > 1) {
computeCascades(this);
}
}
}

if (!this._isPointLight) {
// Compute the culling volume
var position = this._shadowMapCamera.positionWC;
var direction = this._shadowMapCamera.directionWC;
var up = this._shadowMapCamera.upWC;
this._shadowMapCullingVolume = this._shadowMapCamera.frustum.computeCullingVolume(position, direction, up);
if (!this._isPointLight) {
// Compute the culling volume
var position = this._shadowMapCamera.positionWC;
var direction = this._shadowMapCamera.directionWC;
var up = this._shadowMapCamera.upWC;
this._shadowMapCullingVolume = this._shadowMapCamera.frustum.computeCullingVolume(position, direction, up);

if (this._numberOfPasses === 1) {
// Since there is only one pass, use the shadow map camera as the pass camera.
this._passCameras[0].clone(this._shadowMapCamera);
}
}
}

if (this._numberOfPasses === 1) {
// Since there is only one pass, use the shadow map camera as the pass camera.
this._passCameras[0].clone(this._shadowMapCamera);

// Transforms from eye space to shadow texture space
// Transforms from eye space to shadow texture space.
// Always requires an update since the scene camera constantly changes.
var inverseView = this._sceneCamera.inverseViewMatrix;
Matrix4.multiply(this._shadowMapCamera.getViewProjection(), inverseView, this._shadowMapMatrix);
}
Expand All @@ -1291,10 +1391,7 @@ define([
};

ShadowMap.prototype.updatePass = function(context, pass) {
if ((this._isPointLight && this._usesCubeMap) || (pass === 0)) {
this._clearCommand.framebuffer = this._passFramebuffers[pass];
this._clearCommand.execute(context, this._clearPassState);
}
clearFramebuffer(this, context, pass);
};

var scratchTexelStepSize = new Cartesian2();
Expand Down