diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index 5562e0fa2d..28a54cefca 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -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 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(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(); 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) { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 0a0a67f591..7cf0961e3a 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -65,12 +65,16 @@ public: private: std::shared_ptr _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); diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 6b00a14d23..c5ed26823a 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -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(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::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())); diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index dcbfd83ec7..37c01510f3 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -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]]; + } +} diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index 98f666d666..d711e2a80a 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -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 CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const; + static void tesselateSides(const glm::vec3 points[8], Triangle triangles[8]); + }; using ViewFrustumPointer = std::shared_ptr;