mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Fixed issue with far distance of cascades being underestimated, especially first cascade
This commit is contained in:
parent
0b6dcb2717
commit
c0ca7a129d
5 changed files with 168 additions and 39 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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]];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
Loading…
Reference in a new issue