Fixed issue with far distance of cascades being underestimated, especially first cascade

This commit is contained in:
Olivier Prat 2017-12-05 17:33:11 +01:00
parent 0b6dcb2717
commit c0ca7a129d
5 changed files with 168 additions and 39 deletions

View file

@ -53,6 +53,41 @@ const glm::mat4& LightStage::Shadow::Cascade::getProjection() const {
return _frustum->getProjection();
}
float LightStage::Shadow::Cascade::computeFarDistance(const ViewFrustum& viewFrustum, const Transform& shadowViewInverse,
float left, float right, float bottom, float top, float viewMaxShadowDistance) const {
// Far distance should be extended to the intersection of the infinitely extruded shadow frustum
// with the view frustum side planes. To do so, we generate 10 triangles in shadow space which are the result of
// tesselating the side and far faces of the view frustum and clip them with the 4 side planes of the
// shadow frustum. The resulting clipped triangle vertices with the farthest Z gives the desired
// shadow frustum far distance.
std::array<Triangle, 10> viewFrustumTriangles;
Plane shadowClipPlanes[4] = {
Plane(glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, top, 0.0f)),
Plane(glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, bottom, 0.0f)),
Plane(glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(left, 0.0f, 0.0f)),
Plane(glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(right, 0.0f, 0.0f))
};
viewFrustum.tesselateSidesAndFar(shadowViewInverse, viewFrustumTriangles.data(), viewMaxShadowDistance);
static const int MAX_TRIANGLE_COUNT = 16;
auto far = 0.0f;
for (auto& triangle : viewFrustumTriangles) {
Triangle clippedTriangles[MAX_TRIANGLE_COUNT];
auto clippedTriangleCount = clipTriangleWithPlanes(triangle, shadowClipPlanes, 4, clippedTriangles, MAX_TRIANGLE_COUNT);
for (auto i = 0; i < clippedTriangleCount; i++) {
const auto& clippedTriangle = clippedTriangles[i];
far = glm::max(far, -clippedTriangle.v0.z);
far = glm::max(far, -clippedTriangle.v1.z);
far = glm::max(far, -clippedTriangle.v2.z);
}
}
return far;
}
LightStage::Shadow::Shadow(model::LightPointer light, unsigned int cascadeCount) : _light{ light } {
cascadeCount = std::min(cascadeCount, (unsigned int)SHADOW_CASCADE_MAX_COUNT);
Schema schema;
@ -62,11 +97,12 @@ LightStage::Shadow::Shadow(model::LightPointer light, unsigned int cascadeCount)
}
void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,
float viewMinShadowDistance, float viewMaxShadowDistance, float viewOverlapDistance,
float viewMinCascadeShadowDistance, float viewMaxCascadeShadowDistance,
float viewCascadeOverlapDistance, float viewMaxShadowDistance,
float nearDepth, float farDepth) {
assert(viewMinShadowDistance < viewMaxShadowDistance);
assert(viewMinCascadeShadowDistance < viewMaxCascadeShadowDistance);
assert(nearDepth < farDepth);
assert(viewOverlapDistance > 0.0f);
assert(viewCascadeOverlapDistance > 0.0f);
assert(cascadeIndex < _cascades.size());
// Orient the keylight frustum
@ -89,17 +125,17 @@ void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const Vie
// Position the keylight frustum
cascade._frustum->setPosition(viewFrustum.getPosition() - (nearDepth + farDepth)*direction);
const Transform view{ cascade._frustum->getView()};
const Transform viewInverse{ view.getInverseMatrix() };
const Transform shadowView{ cascade._frustum->getView()};
const Transform shadowViewInverse{ shadowView.getInverseMatrix() };
auto nearCorners = viewFrustum.getCorners(viewMinShadowDistance);
auto farCorners = viewFrustum.getCorners(viewMaxShadowDistance);
auto nearCorners = viewFrustum.getCorners(viewMinCascadeShadowDistance);
auto farCorners = viewFrustum.getCorners(viewMaxCascadeShadowDistance);
vec3 min{ viewInverse.transform(nearCorners.bottomLeft) };
vec3 min{ shadowViewInverse.transform(nearCorners.bottomLeft) };
vec3 max{ min };
// Expand keylight frustum to fit view frustum
auto fitFrustum = [&min, &max, &viewInverse](const vec3& viewCorner) {
const auto corner = viewInverse.transform(viewCorner);
auto fitFrustum = [&min, &max, &shadowViewInverse](const vec3& viewCorner) {
const auto corner = shadowViewInverse.transform(viewCorner);
min.x = glm::min(min.x, corner.x);
min.y = glm::min(min.y, corner.y);
@ -116,13 +152,11 @@ void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const Vie
fitFrustum(farCorners.bottomRight);
fitFrustum(farCorners.topLeft);
fitFrustum(farCorners.topRight);
// TODO: Far distance should be extended to the intersection of the exteruded shadow frustum far plane
// with the view frustum.
// Re-adjust near shadow distance
auto near = glm::min(-max.z, nearDepth);
auto far = -min.z;
auto far = cascade.computeFarDistance(viewFrustum, shadowViewInverse, min.x, max.x, min.y, max.y, viewMaxShadowDistance);
glm::mat4 ortho = glm::ortho<float>(min.x, max.x, min.y, max.y, near, far);
cascade._frustum->setProjection(ortho);
@ -132,10 +166,10 @@ void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const Vie
// Update the buffer
auto& schema = _schemaBuffer.edit<Schema>();
if (cascadeIndex == getCascadeCount() - 1) {
schema.maxDistance = viewMaxShadowDistance;
schema.invFalloffDistance = 1.0f / viewOverlapDistance;
schema.maxDistance = viewMaxCascadeShadowDistance;
schema.invFalloffDistance = 1.0f / viewCascadeOverlapDistance;
}
schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * viewInverse.getMatrix();
schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix();
}
void LightStage::Shadow::setFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) {

View file

@ -65,12 +65,16 @@ public:
private:
std::shared_ptr<ViewFrustum> _frustum;
float computeFarDistance(const ViewFrustum& viewFrustum, const Transform& shadowViewInverse,
float left, float right, float bottom, float top, float viewMaxShadowDistance) const;
};
Shadow(model::LightPointer light, unsigned int cascadeCount = 1);
void setKeylightFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,
float viewMinShadowDistance, float viewMaxShadowDistance, float viewOverlapDistance,
float viewMinCascadeShadowDistance, float viewMaxCascadeShadowDistance,
float viewCascadeOverlapDistance, float viewMaxShadowDistance,
float nearDepth = 1.0f, float farDepth = 1000.0f);
void setFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum);

View file

@ -89,24 +89,7 @@ static void adjustNearFar(const AABox& inShapeBounds, ViewFrustum& shadowFrustum
for (i = 0; i < 8; i++) {
sceneBoundVertices[i] = shadowViewInverse.transform(inShapeBounds.getVertex(static_cast<BoxVertex>(i)));
}
// This indirection array is just a protection in case the ViewFrustum::PlaneIndex enum
// changes order especially as we don't need to test the NEAR and FAR planes.
static const ViewFrustum::PlaneIndex planeIndices[4] = {
ViewFrustum::TOP_PLANE,
ViewFrustum::BOTTOM_PLANE,
ViewFrustum::LEFT_PLANE,
ViewFrustum::RIGHT_PLANE
};
// Same goes for the shadow frustum planes.
for (i = 0; i < 4; i++) {
const auto& worldPlane = shadowFrustum.getPlanes()[planeIndices[i]];
// We assume the transform doesn't have a non uniform scale component to apply the
// transform to the normal without using the correct transpose of inverse, which should be the
// case for a view matrix.
auto planeNormal = shadowViewInverse.transformDirection(worldPlane.getNormal());
auto planePoint = shadowViewInverse.transform(worldPlane.getPoint());
shadowClipPlanes[i].setNormalAndPoint(planeNormal, planePoint);
}
shadowFrustum.getUniformlyTransformedSidePlanes(shadowViewInverse, shadowClipPlanes);
float near = std::numeric_limits<float>::max();
float far = 0.0f;
@ -278,7 +261,8 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O
minCascadeDistance = std::max(minCascadeDistance, nearClip);
}
maxCascadeDistance = std::min(maxCascadeDistance, farClip);
globalShadow->setKeylightFrustum(_cascadeIndex, args->getViewFrustum(), minCascadeDistance, maxCascadeDistance, shadowOverlapDistance, SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
globalShadow->setKeylightFrustum(_cascadeIndex, args->getViewFrustum(), minCascadeDistance, maxCascadeDistance,
shadowOverlapDistance, HIGH_CASCADE_MAX_DISTANCE, SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
// Set the keylight render args
args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum()));

View file

@ -691,7 +691,7 @@ void ViewFrustum::getFurthestPointFromCamera(const AACube& box, glm::vec3& furth
}
}
const ViewFrustum::Corners ViewFrustum::getCorners(const float& depth) const {
const ViewFrustum::Corners ViewFrustum::getCorners(const float depth) const {
glm::vec3 normal = glm::normalize(_direction);
auto getCorner = [&](enum::BoxVertex nearCorner, enum::BoxVertex farCorner) {
@ -750,3 +750,98 @@ void ViewFrustum::invalidate() {
}
_centerSphereRadius = -1.0e6f; // -10^6 should be negative enough
}
void ViewFrustum::getSidePlanes(::Plane planes[4]) const {
planes[0] = _planes[TOP_PLANE];
planes[1] = _planes[BOTTOM_PLANE];
planes[2] = _planes[LEFT_PLANE];
planes[3] = _planes[RIGHT_PLANE];
}
void ViewFrustum::getTransformedSidePlanes(const Transform& transform, ::Plane planes[4]) const {
glm::mat4 normalTransform;
transform.getInverseTransposeMatrix(normalTransform);
getSidePlanes(planes);
for (auto i = 0; i < 4; i++) {
// We assume the transform doesn't have a non uniform scale component to apply the
// transform to the normal without using the correct transpose of inverse.
auto transformedNormal = normalTransform * Transform::Vec4(planes[i].getNormal(), 0.0f);
auto planePoint = transform.transform(planes[i].getPoint());
glm::vec3 planeNormal(transformedNormal.x, transformedNormal.y, transformedNormal.z);
planes[i].setNormalAndPoint(planeNormal, planePoint);
}
}
void ViewFrustum::getUniformlyTransformedSidePlanes(const Transform& transform, ::Plane planes[4]) const {
getSidePlanes(planes);
for (auto i = 0; i < 4; i++) {
// We assume the transform doesn't have a non uniform scale component to apply the
// transform to the normal without using the correct transpose of inverse.
auto planeNormal = transform.transformDirection(planes[i].getNormal());
auto planePoint = transform.transform(planes[i].getPoint());
planes[i].setNormalAndPoint(planeNormal, planePoint);
}
}
void ViewFrustum::tesselateSides(Triangle triangles[8]) const {
tesselateSides(_cornersWorld, triangles);
}
void ViewFrustum::tesselateSides(const Transform& transform, Triangle triangles[8]) const {
glm::vec3 points[8];
for (auto i = 0; i < 8; i++) {
points[i] = transform.transform(_cornersWorld[i]);
}
tesselateSides(points, triangles);
}
void ViewFrustum::tesselateSidesAndFar(const Transform& transform, Triangle triangles[10], float farDistance) const {
glm::vec3 points[8];
// First 4 points are at near
for (auto i = 0; i < 4; i++) {
points[i] = transform.transform(_cornersWorld[i]);
}
auto farCorners = getCorners(farDistance);
points[BOTTOM_LEFT_FAR] = transform.transform(farCorners.bottomLeft);
points[BOTTOM_RIGHT_FAR] = transform.transform(farCorners.bottomRight);
points[TOP_LEFT_FAR] = transform.transform(farCorners.topLeft);
points[TOP_RIGHT_FAR] = transform.transform(farCorners.topRight);
tesselateSides(points, triangles);
// Add far side
triangles[8].v0 = points[BOTTOM_LEFT_FAR];
triangles[8].v1 = points[BOTTOM_RIGHT_FAR];
triangles[8].v2 = points[TOP_RIGHT_FAR];
triangles[9].v0 = points[BOTTOM_LEFT_FAR];
triangles[9].v1 = points[TOP_LEFT_FAR];
triangles[9].v2 = points[TOP_RIGHT_FAR];
}
void ViewFrustum::tesselateSides(const glm::vec3 points[8], Triangle triangles[8]) {
static_assert(BOTTOM_RIGHT_NEAR == (BOTTOM_LEFT_NEAR + 1), "Assuming a certain sequence in corners");
static_assert(TOP_RIGHT_NEAR == (BOTTOM_RIGHT_NEAR + 1), "Assuming a certain sequence in corners");
static_assert(TOP_LEFT_NEAR == (TOP_RIGHT_NEAR + 1), "Assuming a certain sequence in corners");
static_assert(BOTTOM_RIGHT_FAR == (BOTTOM_LEFT_FAR + 1), "Assuming a certain sequence in corners");
static_assert(TOP_RIGHT_FAR == (BOTTOM_RIGHT_FAR + 1), "Assuming a certain sequence in corners");
static_assert(TOP_LEFT_FAR == (TOP_RIGHT_FAR + 1), "Assuming a certain sequence in corners");
static const int triangleVertexIndices[8][3] = {
{ BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR },{ BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR },
{ BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR },{ BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR },
{ TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_RIGHT_FAR },{ TOP_LEFT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR },
{ BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR },{ BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR }
};
for (auto i = 0; i < 8; i++) {
auto& triangle = triangles[i];
auto vertexIndices = triangleVertexIndices[i];
triangle.v0 = points[vertexIndices[0]];
triangle.v1 = points[vertexIndices[1]];
triangle.v2 = points[vertexIndices[2]];
}
}

View file

@ -74,7 +74,7 @@ public:
glm::vec3 bottomRight;
// Get the corners depth units from frustum position, along frustum orientation
};
const Corners getCorners(const float& depth) const;
const Corners getCorners(const float depth) const;
// getters for corners
const glm::vec3& getFarTopLeft() const { return _cornersWorld[TOP_LEFT_FAR]; }
@ -90,6 +90,10 @@ public:
void setCenterRadius(float radius) { _centerSphereRadius = radius; }
float getCenterRadius() const { return _centerSphereRadius; }
void tesselateSides(Triangle triangles[8]) const;
void tesselateSides(const Transform& transform, Triangle triangles[8]) const;
void tesselateSidesAndFar(const Transform& transform, Triangle triangles[10], float farDistance) const;
void calculate();
typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection;
@ -134,6 +138,12 @@ public:
enum PlaneIndex { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE, NUM_PLANES };
const ::Plane* getPlanes() const { return _planes; }
void getSidePlanes(::Plane planes[4]) const;
// Transform can have a different scale value in X,Y,Z components
void getTransformedSidePlanes(const Transform& transform, ::Plane planes[4]) const;
// Transform is assumed to have the same scale value in all three X,Y,Z components, which
// allows for a faster computation.
void getUniformlyTransformedSidePlanes(const Transform& transform, ::Plane planes[4]) const;
void invalidate(); // causes all reasonable intersection tests to fail
@ -175,6 +185,8 @@ private:
template <typename TBOX>
CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const;
static void tesselateSides(const glm::vec3 points[8], Triangle triangles[8]);
};
using ViewFrustumPointer = std::shared_ptr<ViewFrustum>;