diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 7a5c50df50..04da70d733 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -27,6 +27,8 @@ // Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; +static const unsigned int SUN_SHADOW_CASCADE_COUNT{ 4 }; +static const float SUN_SHADOW_MAX_DISTANCE{ 40.0f }; using namespace render; using namespace render::entities; @@ -116,7 +118,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { // Do we need to allocate the light in the stage ? if (LightStage::isIndexInvalid(_sunIndex)) { _sunIndex = _stage->addLight(_sunLight); - _shadowIndex = _stage->addShadow(_sunIndex); + _shadowIndex = _stage->addShadow(_sunIndex, SUN_SHADOW_MAX_DISTANCE, SUN_SHADOW_CASCADE_COUNT); } else { _stage->updateLightArrayBuffer(_sunIndex); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp index 0c1b6880cb..9adfd550ef 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp @@ -318,7 +318,10 @@ int GLBackend::makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slot if (requestedBinding != slotBindings.end()) { if (binding != (*requestedBinding)._location) { binding = (*requestedBinding)._location; - glProgramUniform1i(glprogram, location, binding); + for (auto i = 0; i < size; i++) { + // If we are working with an array of textures, reserve for each elemet + glProgramUniform1i(glprogram, location+i, binding+i); + } } } diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 02a5496151..fe03ead4e1 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -39,7 +39,7 @@ void DebugDeferredBufferConfig::setMode(int newMode) { emit dirty(); } -enum Slot { +enum TextureSlot { Albedo = 0, Normal, Specular, @@ -56,7 +56,11 @@ enum Slot { AmbientOcclusionBlurred }; - +enum ParamSlot { + CameraCorrection = 0, + DeferredFrameTransform, + ShadowTransform +}; static const std::string DEFAULT_ALBEDO_SHADER { "vec4 getFragmentColor() {" @@ -127,12 +131,14 @@ static const std::string DEFAULT_DEPTH_SHADER { " return vec4(vec3(texture(depthMap, uv).x), 1.0);" " }" }; + static const std::string DEFAULT_LIGHTING_SHADER { "vec4 getFragmentColor() {" " return vec4(pow(texture(lightingMap, uv).xyz, vec3(1.0 / 2.2)), 1.0);" " }" }; -static const std::string DEFAULT_SHADOW_SHADER { + +static const std::string DEFAULT_SHADOW_SHADER{ "uniform sampler2DShadow shadowMap;" "vec4 getFragmentColor() {" " for (int i = 255; i >= 0; --i) {" @@ -145,10 +151,31 @@ static const std::string DEFAULT_SHADOW_SHADER { " }" }; +static const std::string DEFAULT_SHADOW_CASCADE_SHADER{ + "vec3 cascadeColors[4] = vec3[4]( vec3(0,1,0), vec3(0,0,1), vec3(1,0,0), vec3(1) );" + "vec4 getFragmentColor() {" + " DeferredFrameTransform deferredTransform = getDeferredFrameTransform();" + " DeferredFragment frag = unpackDeferredFragment(deferredTransform, uv);" + " vec4 viewPosition = vec4(frag.position.xyz, 1.0);" + " float viewDepth = -viewPosition.z;" + " vec4 worldPosition = getViewInverse() * viewPosition;" + " vec4 cascadeShadowCoords[2];" + " ivec2 cascadeIndices;" + " float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);" + " vec3 firstCascadeColor = cascadeColors[cascadeIndices.x];" + " vec3 secondCascadeColor = cascadeColors[cascadeIndices.x];" + " if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) {" + " secondCascadeColor = cascadeColors[cascadeIndices.y];" + " }" + " vec3 color = mix(firstCascadeColor, secondCascadeColor, cascadeMix);" + " return vec4(mix(vec3(0.0), color, evalShadowFalloff(viewDepth)), 1.0);" + "}" +}; + static const std::string DEFAULT_LINEAR_DEPTH_SHADER { "vec4 getFragmentColor() {" " return vec4(vec3(1.0 - texture(linearDepthMap, uv).x * 0.01), 1.0);" - " }" + "}" }; static const std::string DEFAULT_HALF_LINEAR_DEPTH_SHADER{ @@ -285,8 +312,13 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string cust return DEFAULT_SCATTERING_SHADER; case LightingMode: return DEFAULT_LIGHTING_SHADER; - case ShadowMode: + case ShadowCascade0Mode: + case ShadowCascade1Mode: + case ShadowCascade2Mode: + case ShadowCascade3Mode: return DEFAULT_SHADOW_SHADER; + case ShadowCascadeIndicesMode: + return DEFAULT_SHADOW_CASCADE_SHADER; case LinearDepthMode: return DEFAULT_LINEAR_DEPTH_SHADER; case HalfLinearDepthMode: @@ -353,6 +385,10 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str const auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("cameraCorrectionBuffer", CameraCorrection)); + slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", DeferredFrameTransform)); + slotBindings.insert(gpu::Shader::Binding("shadowTransformBuffer", ShadowTransform)); + slotBindings.insert(gpu::Shader::Binding("albedoMap", Albedo)); slotBindings.insert(gpu::Shader::Binding("normalMap", Normal)); slotBindings.insert(gpu::Shader::Binding("specularMap", Specular)); @@ -404,6 +440,7 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I auto& linearDepthTarget = inputs.get1(); auto& surfaceGeometryFramebuffer = inputs.get2(); auto& ambientOcclusionFramebuffer = inputs.get3(); + auto& frameTransform = inputs.get4(); gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); @@ -422,8 +459,8 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I // TODO REMOVE: Temporary until UI auto first = _customPipelines.begin()->first; - - batch.setPipeline(getPipeline(_mode, first)); + auto pipeline = getPipeline(_mode, first); + batch.setPipeline(pipeline); if (deferredFramebuffer) { batch.setResourceTexture(Albedo, deferredFramebuffer->getDeferredColorTexture()); @@ -439,7 +476,10 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow(); const auto& globalShadow = lightAndShadow.second; if (globalShadow) { - batch.setResourceTexture(Shadow, globalShadow->map); + const auto cascadeIndex = glm::clamp(_mode - Mode::ShadowCascade0Mode, 0, (int)globalShadow->getCascadeCount() - 1); + batch.setResourceTexture(Shadow, globalShadow->getCascade(cascadeIndex).map); + batch.setUniformBuffer(ShadowTransform, globalShadow->getBuffer()); + batch.setUniformBuffer(DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); } if (linearDepthTarget) { @@ -460,7 +500,6 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I const glm::vec2 topRight(_size.z, _size.w); geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId); - batch.setResourceTexture(Albedo, nullptr); batch.setResourceTexture(Normal, nullptr); batch.setResourceTexture(Specular, nullptr); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index bd5618f5be..8227c4f7a3 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -15,6 +15,7 @@ #include #include +#include "DeferredFrameTransform.h" #include "DeferredFramebuffer.h" #include "SurfaceGeometryPass.h" #include "AmbientOcclusionEffect.h" @@ -37,7 +38,7 @@ signals: class DebugDeferredBuffer { public: - using Inputs = render::VaryingSet4; + using Inputs = render::VaryingSet5; using Config = DebugDeferredBufferConfig; using JobModel = render::Job::ModelI; @@ -64,7 +65,11 @@ protected: LightmapMode, ScatteringMode, LightingMode, - ShadowMode, + ShadowCascade0Mode, + ShadowCascade1Mode, + ShadowCascade2Mode, + ShadowCascade3Mode, + ShadowCascadeIndicesMode, LinearDepthMode, HalfLinearDepthMode, HalfNormalMode, diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 646b19198b..3286531643 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -58,7 +58,7 @@ enum DeferredShader_MapSlot { DEFERRED_BUFFER_DEPTH_UNIT = 3, DEFERRED_BUFFER_OBSCURANCE_UNIT = 4, SHADOW_MAP_UNIT = 5, - SKYBOX_MAP_UNIT = 6, + SKYBOX_MAP_UNIT = SHADOW_MAP_UNIT + SHADOW_CASCADE_MAX_COUNT, DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, DEFERRED_BUFFER_CURVATURE_UNIT, DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, @@ -156,7 +156,7 @@ static gpu::ShaderPointer makeLightProgram(const char* vertSource, const char* f slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), DEFERRED_BUFFER_EMISSIVE_UNIT)); slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DEFERRED_BUFFER_DEPTH_UNIT)); slotBindings.insert(gpu::Shader::Binding(std::string("obscuranceMap"), DEFERRED_BUFFER_OBSCURANCE_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("shadowMap"), SHADOW_MAP_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("shadowMaps"), SHADOW_MAP_UNIT)); slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), SKYBOX_MAP_UNIT)); slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_UNIT)); @@ -501,9 +501,11 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow(); const auto& globalShadow = lightAndShadow.second; - // Bind the shadow buffer + // Bind the shadow buffers if (globalShadow) { - batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow->map); + for (unsigned int i = 0; i < globalShadow->getCascadeCount(); i++) { + batch.setResourceTexture(SHADOW_MAP_UNIT+i, globalShadow->getCascade(i).map); + } } auto& program = deferredLightingEffect->_directionalSkyboxLight; @@ -567,8 +569,9 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, deferredLightingEffect->unsetKeyLightBatch(batch, locations->lightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT); - - batch.setResourceTexture(SHADOW_MAP_UNIT, nullptr); + for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { + batch.setResourceTexture(SHADOW_MAP_UNIT+i, nullptr); + } } } diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index bf2dce85e8..e568554452 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -13,65 +13,202 @@ #include "LightStage.h" +#include + std::string LightStage::_stageName { "LIGHT_STAGE"}; +const glm::mat4 LightStage::Shadow::_biasMatrix{ + 0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0 }; +const int LightStage::Shadow::MAP_SIZE = 1024; + +static const auto MAX_BIAS = 0.006f; + const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; LightStage::LightStage() { } -LightStage::Shadow::Schema::Schema() : - bias{ 0.005f }, - scale{ 1.0f / MAP_SIZE } { - +LightStage::Shadow::Schema::Schema() { + ShadowTransform defaultTransform; + defaultTransform.bias = MAX_BIAS; + std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform); + invMapSize = 1.0f / MAP_SIZE; + cascadeCount = 1; + invCascadeBlendWidth = 1.0f / 0.2f; + invFalloffDistance = 1.0f / 2.0f; + maxDistance = 20.0f; } -gpu::FramebufferPointer LightStage::Shadow::framebuffer; -gpu::TexturePointer LightStage::Shadow::map; +LightStage::Shadow::Cascade::Cascade() : + _frustum{ std::make_shared() }, + _minDistance{ 0.0f }, + _maxDistance{ 20.0f } { + framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::createShadowmap(MAP_SIZE)); + map = framebuffer->getDepthStencilBuffer(); +} -LightStage::Shadow::Shadow(model::LightPointer light) : _light{ light}, _frustum{ std::make_shared() } { - Schema schema; - _schemaBuffer = std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema); +const glm::mat4& LightStage::Shadow::Cascade::getView() const { + return _frustum->getView(); +} - if (!framebuffer) { - framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::createShadowmap(MAP_SIZE)); - map = framebuffer->getDepthStencilBuffer(); +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; } -void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, - float viewMinShadowDistance, float viewMaxShadowDistance, - float nearDepth, float farDepth) { - assert(viewMinShadowDistance < viewMaxShadowDistance); - assert(nearDepth < farDepth); +LightStage::Shadow::Shadow(model::LightPointer light, float maxDistance, unsigned int cascadeCount) : + _light{ light } { + cascadeCount = std::min(cascadeCount, (unsigned int)SHADOW_CASCADE_MAX_COUNT); + Schema schema; + schema.cascadeCount = cascadeCount; + _schemaBuffer = std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema); + _cascades.resize(cascadeCount); + setMaxDistance(maxDistance); +} + +void LightStage::Shadow::setMaxDistance(float value) { + // This overlaping factor isn't really used directly for blending of shadow cascades. It + // just there to be sure the cascades do overlap. The blending width used is relative + // to the UV space and is set in the Schema with invCascadeBlendWidth. + static const auto OVERLAP_FACTOR = 1.0f / 5.0f; + + _maxDistance = std::max(0.0f, value); + + if (_cascades.size() == 1) { + _cascades.front().setMinDistance(0.0f); + _cascades.front().setMaxDistance(_maxDistance); + } else { + // Distribute the cascades along that distance + // TODO : these parameters should be exposed to the user as part of the light entity parameters, no? + static const auto LOW_MAX_DISTANCE = 2.0f; + static const auto MAX_RESOLUTION_LOSS = 0.6f; // Between 0 and 1, 0 giving tighter distributions + + // The max cascade distance is computed by multiplying the previous cascade's max distance by a certain + // factor. There is a "user" factor that is computed from a desired max resolution loss in the shadow + // and an optimal one based on the global min and max shadow distance, all cascades considered. The final + // distance is a gradual blend between the two + const auto userDistanceScale = 1.0f / (1.0f - MAX_RESOLUTION_LOSS); + const auto optimalDistanceScale = powf(_maxDistance / LOW_MAX_DISTANCE, 1.0f / (_cascades.size() - 1)); + + float maxCascadeUserDistance = LOW_MAX_DISTANCE; + float maxCascadeOptimalDistance = LOW_MAX_DISTANCE; + float minCascadeDistance = 0.0f; + + for (size_t cascadeIndex = 0; cascadeIndex < _cascades.size(); ++cascadeIndex) { + float blendFactor = cascadeIndex / float(_cascades.size() - 1); + float maxCascadeDistance; + + if (cascadeIndex == size_t(_cascades.size() - 1)) { + maxCascadeDistance = _maxDistance; + } else { + maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor*blendFactor; + } + + float shadowOverlapDistance = maxCascadeDistance * OVERLAP_FACTOR; + + _cascades[cascadeIndex].setMinDistance(minCascadeDistance); + _cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance); + + // Compute distances for next cascade + minCascadeDistance = maxCascadeDistance; + maxCascadeUserDistance = maxCascadeUserDistance * userDistanceScale; + maxCascadeOptimalDistance = maxCascadeOptimalDistance * optimalDistanceScale; + maxCascadeUserDistance = std::min(maxCascadeUserDistance, _maxDistance); + } + } + + // Update the buffer + const auto& lastCascade = _cascades.back(); + auto& schema = _schemaBuffer.edit(); + schema.maxDistance = _maxDistance; + schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*lastCascade.getMaxDistance()); +} + +void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, + float nearDepth, float farDepth) { + assert(nearDepth < farDepth); // Orient the keylight frustum - const auto& direction = glm::normalize(_light->getDirection()); + auto lightDirection = glm::normalize(_light->getDirection()); glm::quat orientation; - if (direction == IDENTITY_UP) { + if (lightDirection == IDENTITY_UP) { orientation = glm::quat(glm::mat3(-IDENTITY_RIGHT, IDENTITY_FORWARD, -IDENTITY_UP)); - } else if (direction == -IDENTITY_UP) { + } else if (lightDirection == -IDENTITY_UP) { orientation = glm::quat(glm::mat3(IDENTITY_RIGHT, IDENTITY_FORWARD, IDENTITY_UP)); } else { - auto side = glm::normalize(glm::cross(direction, IDENTITY_UP)); - auto up = glm::normalize(glm::cross(side, direction)); - orientation = glm::quat_cast(glm::mat3(side, up, -direction)); + auto side = glm::normalize(glm::cross(lightDirection, IDENTITY_UP)); + auto up = glm::normalize(glm::cross(side, lightDirection)); + orientation = glm::quat_cast(glm::mat3(side, up, -lightDirection)); } - _frustum->setOrientation(orientation); // Position the keylight frustum - _frustum->setPosition(viewFrustum.getPosition() - (nearDepth + farDepth)*direction); + auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; + for (auto& cascade : _cascades) { + cascade._frustum->setOrientation(orientation); + cascade._frustum->setPosition(position); + } + // Update the buffer + auto& schema = _schemaBuffer.edit(); + schema.lightDirInViewSpace = glm::inverse(viewFrustum.getView()) * glm::vec4(lightDirection, 0.f); +} - const Transform view{ _frustum->getView()}; - const Transform viewInverse{ view.getInverseMatrix() }; +void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, + float nearDepth, float farDepth) { + assert(nearDepth < farDepth); + assert(cascadeIndex < _cascades.size()); - auto nearCorners = viewFrustum.getCorners(viewMinShadowDistance); - auto farCorners = viewFrustum.getCorners(viewMaxShadowDistance); + auto& cascade = _cascades[cascadeIndex]; + const auto viewMinCascadeShadowDistance = std::max(viewFrustum.getNearClip(), cascade.getMinDistance()); + const auto viewMaxCascadeShadowDistance = std::min(viewFrustum.getFarClip(), cascade.getMaxDistance()); + const auto viewMaxShadowDistance = _cascades.back().getMaxDistance(); - vec3 min{ viewInverse.transform(nearCorners.bottomLeft) }; + const Transform shadowView{ cascade._frustum->getView()}; + const Transform shadowViewInverse{ shadowView.getInverseMatrix() }; + + auto nearCorners = viewFrustum.getCorners(viewMinCascadeShadowDistance); + auto farCorners = viewFrustum.getCorners(viewMaxCascadeShadowDistance); + + 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); @@ -89,36 +226,35 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, fitFrustum(farCorners.topLeft); fitFrustum(farCorners.topRight); - // Re-adjust near shadow distance - auto near = glm::max(max.z, -nearDepth); - auto far = -min.z; + // Re-adjust near and far shadow distance + auto near = glm::min(-max.z, nearDepth); + 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); - _frustum->setProjection(ortho); + cascade._frustum->setProjection(ortho); // Calculate the frustum's internal state - _frustum->calculate(); + cascade._frustum->calculate(); // Update the buffer - _schemaBuffer.edit().projection = ortho; - _schemaBuffer.edit().viewInverse = viewInverse.getMatrix(); + auto& schema = _schemaBuffer.edit(); + schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); + // Adapt shadow bias to shadow resolution with a totally empirical formula + const auto maxShadowFrustumDim = std::max(fabsf(min.x - max.x), fabsf(min.y - max.y)); + const auto REFERENCE_TEXEL_DENSITY = 7.5f; + const auto cascadeTexelDensity = MAP_SIZE / maxShadowFrustumDim; + schema.cascades[cascadeIndex].bias = MAX_BIAS * std::min(1.0f, REFERENCE_TEXEL_DENSITY / cascadeTexelDensity); } -void LightStage::Shadow::setFrustum(const ViewFrustum& shadowFrustum) { +void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { + assert(cascadeIndex < _cascades.size()); const Transform view{ shadowFrustum.getView() }; const Transform viewInverse{ view.getInverseMatrix() }; + auto& cascade = _cascades[cascadeIndex]; - *_frustum = shadowFrustum; + *cascade._frustum = shadowFrustum; // Update the buffer - _schemaBuffer.edit().projection = shadowFrustum.getProjection(); - _schemaBuffer.edit().viewInverse = viewInverse.getMatrix(); -} - -const glm::mat4& LightStage::Shadow::getView() const { - return _frustum->getView(); -} - -const glm::mat4& LightStage::Shadow::getProjection() const { - return _frustum->getProjection(); + _schemaBuffer.edit().cascades[cascadeIndex].reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); } LightStage::Index LightStage::findLight(const LightPointer& light) const { @@ -142,7 +278,7 @@ LightStage::Index LightStage::addLight(const LightPointer& light) { _descs.emplace_back(Desc()); } else { assert(_descs[lightId].shadowId == INVALID_INDEX); - _descs.emplace(_descs.begin() + lightId, Desc()); + _descs[lightId] = Desc(); } // INsert the light and its index in the reverese map @@ -156,12 +292,12 @@ LightStage::Index LightStage::addLight(const LightPointer& light) { } } -LightStage::Index LightStage::addShadow(Index lightIndex) { +LightStage::Index LightStage::addShadow(Index lightIndex, float maxDistance, unsigned int cascadeCount) { auto light = getLight(lightIndex); Index shadowId = INVALID_INDEX; if (light) { assert(_descs[lightIndex].shadowId == INVALID_INDEX); - shadowId = _shadows.newElement(std::make_shared(light)); + shadowId = _shadows.newElement(std::make_shared(light, maxDistance, cascadeCount)); _descs[lightIndex].shadowId = shadowId; } return shadowId; diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index fa581c8315..508e67ec17 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -44,45 +44,74 @@ public: class Shadow { public: using UniformBufferView = gpu::BufferView; - static const int MAP_SIZE = 1024; + static const int MAP_SIZE; - Shadow(model::LightPointer light); + class Cascade { + friend Shadow; + public: - void setKeylightFrustum(const ViewFrustum& viewFrustum, float viewMinShadowDistance, float viewMaxShadowDistance, float nearDepth = 1.0f, float farDepth = 1000.0f); + Cascade(); - void setFrustum(const ViewFrustum& shadowFrustum); - const std::shared_ptr getFrustum() const { return _frustum; } + gpu::FramebufferPointer framebuffer; + gpu::TexturePointer map; - const glm::mat4& getView() const; - const glm::mat4& getProjection() const; + const std::shared_ptr& getFrustum() const { return _frustum; } + + const glm::mat4& getView() const; + const glm::mat4& getProjection() const; + + void setMinDistance(float value) { _minDistance = value; } + void setMaxDistance(float value) { _maxDistance = value; } + float getMinDistance() const { return _minDistance; } + float getMaxDistance() const { return _maxDistance; } + + private: + + std::shared_ptr _frustum; + float _minDistance; + float _maxDistance; + + float computeFarDistance(const ViewFrustum& viewFrustum, const Transform& shadowViewInverse, + float left, float right, float bottom, float top, float viewMaxShadowDistance) const; + }; + + Shadow(model::LightPointer light, float maxDistance, unsigned int cascadeCount = 1); + + void setKeylightFrustum(const ViewFrustum& viewFrustum, + float nearDepth = 1.0f, float farDepth = 1000.0f); + void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, + float nearDepth = 1.0f, float farDepth = 1000.0f); + void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum); const UniformBufferView& getBuffer() const { return _schemaBuffer; } - // Shadow maps are shared among all lights for the moment as only one key light - // is used. - static gpu::FramebufferPointer framebuffer; - static gpu::TexturePointer map; + unsigned int getCascadeCount() const { return (unsigned int)_cascades.size(); } + const Cascade& getCascade(unsigned int index) const { return _cascades[index]; } + + float getMaxDistance() const { return _maxDistance; } + void setMaxDistance(float value); const model::LightPointer& getLight() const { return _light; } protected: - model::LightPointer _light; - std::shared_ptr _frustum; +#include "Shadows_shared.slh" - class Schema { + using Cascades = std::vector; + + static const glm::mat4 _biasMatrix; + + model::LightPointer _light; + float _maxDistance; + Cascades _cascades; + + class Schema : public ShadowParameters { public: Schema(); - glm::mat4 projection; - glm::mat4 viewInverse; - - glm::float32 bias; - glm::float32 scale; }; UniformBufferView _schemaBuffer = nullptr; - }; using ShadowPointer = std::shared_ptr; @@ -91,7 +120,7 @@ public: Index findLight(const LightPointer& light) const; Index addLight(const LightPointer& light); - Index addShadow(Index lightIndex); + Index addShadow(Index lightIndex, float maxDistance = 20.0f, unsigned int cascadeCount = 1U); LightPointer removeLight(Index index); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index a395136978..5c9abbabed 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -205,34 +205,23 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZones", zones); const auto frustums = task.addJob("ExtractFrustums"); const auto viewFrustum = frustums.getN(ExtractFrustums::VIEW_FRUSTUM); - const auto shadowFrustum = frustums.getN(ExtractFrustums::SHADOW_FRUSTUM); task.addJob("DrawViewFrustum", viewFrustum, glm::vec3(1.0f, 1.0f, 0.0f)); - task.addJob("DrawShadowFrustum", shadowFrustum, glm::vec3(0.0f, 0.0f, 1.0f)); + for (auto i = 0; i < ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT; i++) { + const auto shadowFrustum = frustums.getN(ExtractFrustums::SHADOW_CASCADE0_FRUSTUM+i); + float tint = 1.0f - i / float(ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT - 1); + char jobName[64]; + sprintf(jobName, "DrawShadowFrustum%d", i); + task.addJob(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f)); + } // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true task.addJob("DrawSelectionBounds", selectedItems); } - // Layered Overlays - const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); - const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); - const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); - const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); - - const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); - const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); - task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); - task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); - - { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer - task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); - task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); - } - // Debugging stages { // Debugging Deferred buffer job - const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer)); + const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, deferredFrameTransform)); task.addJob("DebugDeferredBuffer", debugFramebuffers); const auto debugSubsurfaceScatteringInputs = DebugSubsurfaceScattering::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, @@ -259,6 +248,22 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZoneStack", deferredFrameTransform); } + // Layered Overlays + const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); + const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); + const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); + const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); + + const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); + const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); + task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); + task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); + + { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer + task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); + task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); + } + // AA job to be revisited task.addJob("Antialiasing", primaryFramebuffer); @@ -555,17 +560,20 @@ void ExtractFrustums::run(const render::RenderContextPointer& renderContext, Out } // Return shadow frustum - auto& shadowFrustum = output[SHADOW_FRUSTUM].edit(); auto lightStage = args->_scene->getStage(LightStage::getName()); - if (lightStage) { - auto globalShadow = lightStage->getCurrentKeyShadow(); + for (auto i = 0; i < SHADOW_CASCADE_FRUSTUM_COUNT; i++) { + auto& shadowFrustum = output[SHADOW_CASCADE0_FRUSTUM+i].edit(); + if (lightStage) { + auto globalShadow = lightStage->getCurrentKeyShadow(); - if (globalShadow) { - shadowFrustum = globalShadow->getFrustum(); + if (globalShadow && i<(int)globalShadow->getCascadeCount()) { + auto& cascade = globalShadow->getCascade(i); + shadowFrustum = cascade.getFrustum(); + } else { + shadowFrustum.reset(); + } } else { shadowFrustum.reset(); } - } else { - shadowFrustum.reset(); } } diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 40ae503fb7..f51201d77d 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -174,8 +174,14 @@ class ExtractFrustums { public: enum Frustum { - VIEW_FRUSTUM, - SHADOW_FRUSTUM, + SHADOW_CASCADE0_FRUSTUM = 0, + SHADOW_CASCADE1_FRUSTUM, + SHADOW_CASCADE2_FRUSTUM, + SHADOW_CASCADE3_FRUSTUM, + + SHADOW_CASCADE_FRUSTUM_COUNT, + + VIEW_FRUSTUM = SHADOW_CASCADE_FRUSTUM_COUNT, FRUSTUM_COUNT }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 7a6e3dc74f..2e5b7132e4 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -22,6 +22,8 @@ #include "DeferredLightingEffect.h" #include "FramebufferCache.h" +#include "RenderUtilsLogging.h" + // These values are used for culling the objects rendered in the shadow map // but are readjusted afterwards #define SHADOW_FRUSTUM_NEAR 1.0f @@ -89,31 +91,13 @@ 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; computeNearFar(sceneBoundVertices, shadowClipPlanes, near, far); - // Limit the far range to the one used originally. There's no point in rendering objects - // that are not in the view frustum. + // Limit the far range to the one used originally. far = glm::min(far, shadowFrustum.getFarClip()); const auto depthEpsilon = 0.1f; @@ -137,9 +121,12 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con assert(lightStage); auto shadow = lightStage->getCurrentKeyShadow(); - if (!shadow) return; + if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) { + return; + } - const auto& fbo = shadow->framebuffer; + auto& cascade = shadow->getCascade(_cascadeIndex); + auto& fbo = cascade.framebuffer; RenderArgs* args = renderContext->args; ShapeKey::Builder defaultKeyBuilder; @@ -149,7 +136,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con // the minimal Z range. adjustNearFar(inShapeBounds, adjustedShadowFrustum); // Reapply the frustum as it has been adjusted - shadow->setFrustum(adjustedShadowFrustum); + shadow->setCascadeFrustum(_cascadeIndex, adjustedShadowFrustum); args->popViewFrustum(); args->pushViewFrustum(adjustedShadowFrustum); @@ -178,6 +165,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); std::vector skinnedShapeKeys{}; + std::vector ownPipelineShapeKeys{}; // Iterate through all inShapes and render the unskinned args->_shapePipeline = shadowPipeline; @@ -185,8 +173,10 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con for (auto items : inShapes) { if (items.first.isSkinned()) { skinnedShapeKeys.push_back(items.first); - } else { + } else if (!items.first.hasOwnPipeline()) { renderItems(renderContext, items.second); + } else { + ownPipelineShapeKeys.push_back(items.first); } } @@ -197,7 +187,15 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con renderItems(renderContext, inShapes.at(key)); } + // Finally render the items with their own pipeline last to prevent them from breaking the + // render state. This is probably a temporary code as there is probably something better + // to do in the render call of objects that have their own pipeline. args->_shapePipeline = nullptr; + for (const auto& key : ownPipelineShapeKeys) { + args->_itemShapeKey = key._flags.to_ulong(); + renderItems(renderContext, inShapes.at(key)); + } + args->_batch = nullptr; }); } @@ -215,22 +213,26 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende initZPassPipelines(*shapePlumber, state); } - const auto cachedMode = task.addJob("ShadowSetup"); + task.addJob("ShadowSetup"); - // CPU jobs: - // Fetch and cull the items from the scene - auto shadowFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - const auto shadowSelection = task.addJob("FetchShadowSelection", shadowFilter); - const auto culledShadowSelection = task.addJob("CullShadowSelection", shadowSelection, cullFunctor, RenderDetails::SHADOW, shadowFilter); + for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { + const auto setupOutput = task.addJob("ShadowCascadeSetup", i); + const auto shadowFilter = setupOutput.getN(1); - // Sort - const auto sortedPipelines = task.addJob("PipelineSortShadowSort", culledShadowSelection); - const auto sortedShapesAndBounds = task.addJob("DepthSortShadowMap", sortedPipelines, true); + // CPU jobs: + // Fetch and cull the items from the scene + const auto shadowSelection = task.addJob("FetchShadowSelection", shadowFilter); + const auto cullInputs = CullSpatialSelection::Inputs(shadowSelection, shadowFilter).asVarying(); + const auto culledShadowSelection = task.addJob("CullShadowSelection", cullInputs, cullFunctor, RenderDetails::SHADOW); - // GPU jobs: Render to shadow map - task.addJob("RenderShadowMap", sortedShapesAndBounds, shapePlumber); + // Sort + const auto sortedPipelines = task.addJob("PipelineSortShadowSort", culledShadowSelection); + const auto sortedShapesAndBounds = task.addJob("DepthSortShadowMap", sortedPipelines, true); - task.addJob("ShadowTeardown", cachedMode); + // GPU jobs: Render to shadow map + task.addJob("RenderShadowMap", sortedShapesAndBounds, shapePlumber, i); + task.addJob("ShadowCascadeTeardown", setupOutput); + } } void RenderShadowTask::configure(const Config& configuration) { @@ -239,31 +241,57 @@ void RenderShadowTask::configure(const Config& configuration) { // Task::configure(configuration); } -void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Output& output) { +void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); + // Cache old render args + RenderArgs* args = renderContext->args; const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { - // Cache old render args - RenderArgs* args = renderContext->args; - output = args->_renderMode; - - auto nearClip = args->getViewFrustum().getNearClip(); - float nearDepth = -args->_boomOffset.z; - const float SHADOW_MAX_DISTANCE = 20.0f; - globalShadow->setKeylightFrustum(args->getViewFrustum(), nearDepth, nearClip + SHADOW_MAX_DISTANCE, SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - - // Set the keylight render args - args->pushViewFrustum(*(globalShadow->getFrustum())); - args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; + globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); } } -void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) { +void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { + auto lightStage = renderContext->_scene->getStage(); + assert(lightStage); + // Cache old render args RenderArgs* args = renderContext->args; + output.edit0() = args->_renderMode; + output.edit2() = args->_sizeScale; + + const auto globalShadow = lightStage->getCurrentKeyShadow(); + if (globalShadow && _cascadeIndexgetCascadeCount()) { + output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); + + globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); + + // Set the keylight render args + args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); + args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; + if (lightStage->getCurrentKeyLight()->getType() == model::Light::SUN) { + const float shadowSizeScale = 1e16f; + // Set the size scale to a ridiculously high value to prevent small object culling which assumes + // the view frustum is a perspective projection. But this isn't the case for the sun which + // is an orthographic projection. + args->_sizeScale = shadowSizeScale; + } + + } else { + output.edit1() = ItemFilter::Builder::nothing(); + } +} + +void RenderShadowCascadeTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) { + RenderArgs* args = renderContext->args; + + if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE && !input.get1().selectsNothing()) { + args->popViewFrustum(); + } + assert(args->hasViewFrustum()); // Reset the render args - args->popViewFrustum(); - args->_renderMode = input; + args->_renderMode = input.get0(); + args->_sizeScale = input.get2(); }; diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 7b2bbeb306..d8d4c624e7 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -24,11 +24,12 @@ public: using Inputs = render::VaryingSet2; using JobModel = render::Job::ModelI; - RenderShadowMap(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} + RenderShadowMap(render::ShapePlumberPointer shapePlumber, unsigned int cascadeIndex) : _shapePlumber{ shapePlumber }, _cascadeIndex{ cascadeIndex } {} void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: render::ShapePlumberPointer _shapePlumber; + unsigned int _cascadeIndex; }; class RenderShadowTaskConfig : public render::Task::Config::Persistent { @@ -54,15 +55,30 @@ public: class RenderShadowSetup { public: - using Output = RenderArgs::RenderMode; - using JobModel = render::Job::ModelO; - void run(const render::RenderContextPointer& renderContext, Output& output); + using JobModel = render::Job::Model; + + RenderShadowSetup() {} + void run(const render::RenderContextPointer& renderContext); + }; -class RenderShadowTeardown { +class RenderShadowCascadeSetup { public: - using Input = RenderArgs::RenderMode; - using JobModel = render::Job::ModelI; + using Outputs = render::VaryingSet3; + using JobModel = render::Job::ModelO; + + RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} + void run(const render::RenderContextPointer& renderContext, Outputs& output); + +private: + + unsigned int _cascadeIndex; +}; + +class RenderShadowCascadeTeardown { +public: + using Input = RenderShadowCascadeSetup::Outputs; + using JobModel = render::Job::ModelI; void run(const render::RenderContextPointer& renderContext, const Input& input); }; diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 1085a1148c..dc6c66e058 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -14,15 +14,21 @@ #include "RenderDeferredTask.h" #include "RenderForwardTask.h" - - void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) { // auto items = input.get(); // Shadows use an orthographic projection because they are linked to sunlights // but the cullFunctor passed is probably tailored for perspective projection and culls too much. - // TODO : create a special cull functor for this. - task.addJob("RenderShadowTask", nullptr); + task.addJob("RenderShadowTask", [](const RenderArgs* args, const AABox& bounds) { + // Cull only objects that are too small relatively to shadow frustum + auto& frustum = args->getViewFrustum(); + auto frustumSize = std::max(frustum.getHeight(), frustum.getWidth()); + const auto boundsRadius = bounds.getDimensions().length(); + const auto relativeBoundRadius = boundsRadius / frustumSize; + const auto threshold = 1e-3f; + return relativeBoundRadius > threshold; + return true; + }); const auto items = task.addJob("FetchCullSort", cullFunctor); assert(items.canCast()); diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index e844db43dd..a12dd0f4a4 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -11,53 +11,17 @@ <@if not SHADOW_SLH@> <@def SHADOW_SLH@> +<@include ShadowCore.slh@> + +#define SHADOW_NOISE_ENABLED 0 +#define SHADOW_SCREEN_SPACE_DITHER 1 + // the shadow texture -uniform sampler2DShadow shadowMap; - -struct ShadowTransform { - mat4 projection; - mat4 viewInverse; - - float bias; - float scale; -}; - -uniform shadowTransformBuffer { - ShadowTransform _shadowTransform; -}; - -mat4 getShadowViewInverse() { - return _shadowTransform.viewInverse; -} - -mat4 getShadowProjection() { - return _shadowTransform.projection; -} - -float getShadowScale() { - return _shadowTransform.scale; -} - -float getShadowBias() { - return _shadowTransform.bias; -} - -// Compute the texture coordinates from world coordinates -vec4 evalShadowTexcoord(vec4 position) { - mat4 biasMatrix = mat4( - 0.5, 0.0, 0.0, 0.0, - 0.0, 0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); - float bias = -getShadowBias(); - - vec4 shadowCoord = biasMatrix * getShadowProjection() * getShadowViewInverse() * position; - return vec4(shadowCoord.xy, shadowCoord.z + bias, 1.0); -} +uniform sampler2DShadow shadowMaps[SHADOW_CASCADE_MAX_COUNT]; // Sample the shadowMap with PCF (built-in) -float fetchShadow(vec3 shadowTexcoord) { - return texture(shadowMap, shadowTexcoord); +float fetchShadow(int cascadeIndex, vec3 shadowTexcoord) { + return texture(shadowMaps[cascadeIndex], shadowTexcoord); } vec2 PCFkernel[4] = vec2[4]( @@ -67,38 +31,83 @@ vec2 PCFkernel[4] = vec2[4]( vec2(0.5, -1.5) ); -float evalShadowAttenuationPCF(vec4 position, vec4 shadowTexcoord) { - // PCF is buggy so disable it for the time being -#if 0 - float pcfRadius = 3.0; +float evalShadowNoise(vec4 seed) { + float dot_product = dot(seed, vec4(12.9898,78.233,45.164,94.673)); + return fract(sin(dot_product) * 43758.5453); +} + +struct ShadowSampleOffsets { + vec3 points[4]; +}; + +ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { float shadowScale = getShadowScale(); + ShadowSampleOffsets offsets; + +#if SHADOW_SCREEN_SPACE_DITHER + // Pattern dithering in screen space + ivec2 coords = ivec2(gl_FragCoord.xy); +#else + // Pattern dithering in world space (mm resolution) + ivec2 coords = ivec2(position.x, position.y+position.z); +#endif + +#if SHADOW_NOISE_ENABLED + // Add some noise to break dithering + int index = int(4.0*evalShadowNoise(gl_FragCoord.xyyx))%4; + coords.x += index & 1; + coords.y += (index & 2) >> 1; +#endif // Offset for efficient PCF, see http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html - vec2 offset = pcfRadius * step(fract(position.xy), vec2(0.5, 0.5)); + ivec2 offset = coords & ivec2(1,1); + offset.y = (offset.x+offset.y) & 1; - float shadowAttenuation = (0.25 * ( - fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[0], 0.0)) + - fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[1], 0.0)) + - fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[2], 0.0)) + - fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[3], 0.0)) - )); -#else - float shadowAttenuation = fetchShadow(shadowTexcoord.xyz); -#endif + offsets.points[0] = shadowScale * vec3(offset + PCFkernel[0], 0.0); + offsets.points[1] = shadowScale * vec3(offset + PCFkernel[1], 0.0); + offsets.points[2] = shadowScale * vec3(offset + PCFkernel[2], 0.0); + offsets.points[3] = shadowScale * vec3(offset + PCFkernel[3], 0.0); + + return offsets; +} + +float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { + shadowTexcoord.z -= bias; + float shadowAttenuation = 0.25 * ( + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) + + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) + + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) + + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3]) + ); return shadowAttenuation; } -float evalShadowAttenuation(vec4 position) { - vec4 shadowTexcoord = evalShadowTexcoord(position); - if (shadowTexcoord.x < 0.0 || shadowTexcoord.x > 1.0 || - shadowTexcoord.y < 0.0 || shadowTexcoord.y > 1.0 || - shadowTexcoord.z < 0.0 || shadowTexcoord.z > 1.0) { +float evalShadowCascadeAttenuation(int cascadeIndex, vec3 viewNormal, ShadowSampleOffsets offsets, vec4 shadowTexcoord) { + if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { // If a point is not in the map, do not attenuate return 1.0; } + // Multiply bias if we are at a grazing angle with light + float tangentFactor = abs(dot(getShadowDirInViewSpace(), viewNormal)); + float bias = getShadowBias(cascadeIndex) * (5.0-4.0*tangentFactor); + return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); +} - return evalShadowAttenuationPCF(position, shadowTexcoord); +float evalShadowAttenuation(vec4 worldPosition, float viewDepth, vec3 viewNormal) { + ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); + vec4 cascadeShadowCoords[2]; + ivec2 cascadeIndices; + float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); + + vec2 cascadeAttenuations = vec2(1.0, 1.0); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, viewNormal, offsets, cascadeShadowCoords[0]); + if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, viewNormal, offsets, cascadeShadowCoords[1]); + } + float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); + // Falloff to max distance + return mix(1.0, attenuation, evalShadowFalloff(viewDepth)); } <@endif@> diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh new file mode 100644 index 0000000000..9b3b086598 --- /dev/null +++ b/libraries/render-utils/src/ShadowCore.slh @@ -0,0 +1,96 @@ + +<@if not SHADOW_CORE_SLH@> +<@def SHADOW_CORE_SLH@> + +<@include Shadows_shared.slh@> + +layout(std140) uniform shadowTransformBuffer { + ShadowParameters shadow; +}; + +int getShadowCascadeCount() { + return shadow.cascadeCount; +} + +float getShadowCascadeInvBlendWidth() { + return shadow.invCascadeBlendWidth; +} + +float evalShadowFalloff(float depth) { + return clamp((shadow.maxDistance-depth) * shadow.invFalloffDistance, 0.0, 1.0); +} + +mat4 getShadowReprojection(int cascadeIndex) { + return shadow.cascades[cascadeIndex].reprojection; +} + +float getShadowScale() { + return shadow.invMapSize; +} + +float getShadowBias(int cascadeIndex) { + return shadow.cascades[cascadeIndex].bias; +} + +vec3 getShadowDirInViewSpace() { + return shadow.lightDirInViewSpace; +} + +// Compute the texture coordinates from world coordinates +vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { + vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position; + return vec4(shadowCoord.xyz, 1.0); +} + +bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) { + bvec2 greaterThanZero = greaterThanEqual(cascadeTexCoords.xy, vec2(0)); + bvec2 lessThanOne = lessThanEqual(cascadeTexCoords.xy, vec2(1)); + return all(greaterThanZero) && all(lessThanOne); +} + +int getFirstShadowCascadeOnPixel(int startCascadeIndex, vec4 worldPosition, out vec4 cascadeShadowCoords) { + int cascadeIndex; + startCascadeIndex = min(startCascadeIndex, getShadowCascadeCount()-1); + for (cascadeIndex=startCascadeIndex ; cascadeIndex diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh new file mode 100644 index 0000000000..abb226421c --- /dev/null +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -0,0 +1,34 @@ +// glsl / C++ compatible source as interface for Shadows +#ifdef __cplusplus +# define MAT4 glm::mat4 +# define VEC3 glm::vec3 +#else +# define MAT4 mat4 +# define VEC3 vec3 +#endif + +#define SHADOW_CASCADE_MAX_COUNT 4 + +struct ShadowTransform { + MAT4 reprojection; + + float bias; + float _padding1; + float _padding2; + float _padding3; +}; + +struct ShadowParameters { + ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT]; + VEC3 lightDirInViewSpace; + int cascadeCount; + float invMapSize; + float invCascadeBlendWidth; + float maxDistance; + float invFalloffDistance; +}; + +// <@if 1@> +// Trigger Scribe include +// <@endif@> +// diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index e9750f0054..426de623a1 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -16,7 +16,6 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> - uniform sampler2D linearDepthMap; uniform sampler2D halfLinearDepthMap; uniform sampler2D halfNormalMap; @@ -24,6 +23,8 @@ uniform sampler2D occlusionMap; uniform sampler2D occlusionBlurredMap; uniform sampler2D scatteringMap; +<@include ShadowCore.slh@> + <$declareDeferredCurvature()$> float curvatureAO(float k) { diff --git a/libraries/render-utils/src/directional_ambient_light_shadow.slf b/libraries/render-utils/src/directional_ambient_light_shadow.slf index d3778c9228..f7ea8c5966 100644 --- a/libraries/render-utils/src/directional_ambient_light_shadow.slf +++ b/libraries/render-utils/src/directional_ambient_light_shadow.slf @@ -26,8 +26,9 @@ void main(void) { DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0); - float shadowAttenuation = evalShadowAttenuation(worldPos); + vec4 viewPos = vec4(frag.position.xyz, 1.0); + vec4 worldPos = getViewInverse() * viewPos; + float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal); if (frag.mode == FRAG_MODE_UNLIT) { discard; diff --git a/libraries/render-utils/src/directional_skybox_light_shadow.slf b/libraries/render-utils/src/directional_skybox_light_shadow.slf index 3ca0f71df5..37c9ae7fba 100644 --- a/libraries/render-utils/src/directional_skybox_light_shadow.slf +++ b/libraries/render-utils/src/directional_skybox_light_shadow.slf @@ -26,8 +26,9 @@ void main(void) { DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0); - float shadowAttenuation = evalShadowAttenuation(worldPos); + vec4 viewPos = vec4(frag.position.xyz, 1.0); + vec4 worldPos = getViewInverse() * viewPos; + float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal); // Light mapped or not ? if (frag.mode == FRAG_MODE_UNLIT) { diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 4fc53d99f9..70331cdb47 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -82,28 +82,30 @@ void FetchSpatialTree::configure(const Config& config) { _lodAngle = config.lodAngle; } -void FetchSpatialTree::run(const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - RenderArgs* args = renderContext->args; - auto& scene = renderContext->_scene; - +void FetchSpatialTree::run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection) { // start fresh outSelection.clear(); - // Eventually use a frozen frustum - auto queryFrustum = args->getViewFrustum(); - if (_freezeFrustum) { - if (_justFrozeFrustum) { - _justFrozeFrustum = false; - _frozenFrutstum = args->getViewFrustum(); - } - queryFrustum = _frozenFrutstum; - } + if (!filter.selectsNothing()) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + auto& scene = renderContext->_scene; - // Octree selection! - float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); - scene->getSpatialTree().selectCellItems(outSelection, _filter, queryFrustum, angle); + // Eventually use a frozen frustum + auto queryFrustum = args->getViewFrustum(); + if (_freezeFrustum) { + if (_justFrozeFrustum) { + _justFrozeFrustum = false; + _frozenFrustum = args->getViewFrustum(); + } + queryFrustum = _frozenFrustum; + } + + // Octree selection! + float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); + scene->getSpatialTree().selectCellItems(outSelection, filter, queryFrustum, angle); + } } void CullSpatialSelection::configure(const Config& config) { @@ -113,11 +115,12 @@ void CullSpatialSelection::configure(const Config& config) { } void CullSpatialSelection::run(const RenderContextPointer& renderContext, - const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems) { + const Inputs& inputs, ItemBounds& outItems) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& scene = renderContext->_scene; + auto& inSelection = inputs.get0(); auto& details = args->_details.edit(_detailType); details._considered += (int)inSelection.numItems(); @@ -126,9 +129,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (_freezeFrustum) { if (_justFrozeFrustum) { _justFrozeFrustum = false; - _frozenFrutstum = args->getViewFrustum(); + _frozenFrustum = args->getViewFrustum(); } - args->pushViewFrustum(_frozenFrutstum); // replace the true view frustum by the frozen one + args->pushViewFrustum(_frozenFrustum); // replace the true view frustum by the frozen one } // Culling Frustum / solidAngle test helper class @@ -181,122 +184,124 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, outItems.clear(); outItems.reserve(inSelection.numItems()); - // Now get the bound, and - // filter individually against the _filter - // visibility cull if partially selected ( octree cell contianing it was partial) - // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item) + const auto filter = inputs.get1(); + if (!filter.selectsNothing()) { + // Now get the bound, and + // filter individually against the _filter + // visibility cull if partially selected ( octree cell contianing it was partial) + // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item) - if (_skipCulling) { - // inside & fit items: filter only, culling is disabled - { - PerformanceTimer perfTimer("insideFitItems"); - for (auto id : inSelection.insideItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - outItems.emplace_back(itemBound); - } - } - } - - // inside & subcell items: filter only, culling is disabled - { - PerformanceTimer perfTimer("insideSmallItems"); - for (auto id : inSelection.insideSubcellItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - outItems.emplace_back(itemBound); - } - } - } - - // partial & fit items: filter only, culling is disabled - { - PerformanceTimer perfTimer("partialFitItems"); - for (auto id : inSelection.partialItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - outItems.emplace_back(itemBound); - } - } - } - - // partial & subcell items: filter only, culling is disabled - { - PerformanceTimer perfTimer("partialSmallItems"); - for (auto id : inSelection.partialSubcellItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - outItems.emplace_back(itemBound); - } - } - } - - } else { - - // inside & fit items: easy, just filter - { - PerformanceTimer perfTimer("insideFitItems"); - for (auto id : inSelection.insideItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - outItems.emplace_back(itemBound); - } - } - } - - // inside & subcell items: filter & distance cull - { - PerformanceTimer perfTimer("insideSmallItems"); - for (auto id : inSelection.insideSubcellItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.solidAngleTest(itemBound.bound)) { + if (_skipCulling) { + // inside & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideFitItems"); + for (auto id : inSelection.insideItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); } } } - } - // partial & fit items: filter & frustum cull - { - PerformanceTimer perfTimer("partialFitItems"); - for (auto id : inSelection.partialItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.frustumTest(itemBound.bound)) { + // inside & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideSmallItems"); + for (auto id : inSelection.insideSubcellItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); } } } - } - // partial & subcell items:: filter & frutum cull & solidangle cull - { - PerformanceTimer perfTimer("partialSmallItems"); - for (auto id : inSelection.partialSubcellItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.frustumTest(itemBound.bound)) { + // partial & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialFitItems"); + for (auto id : inSelection.partialItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // partial & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialSmallItems"); + for (auto id : inSelection.partialSubcellItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + } else { + + // inside & fit items: easy, just filter + { + PerformanceTimer perfTimer("insideFitItems"); + for (auto id : inSelection.insideItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // inside & subcell items: filter & distance cull + { + PerformanceTimer perfTimer("insideSmallItems"); + for (auto id : inSelection.insideSubcellItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); } } } } + + // partial & fit items: filter & frustum cull + { + PerformanceTimer perfTimer("partialFitItems"); + for (auto id : inSelection.partialItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + if (test.frustumTest(itemBound.bound)) { + outItems.emplace_back(itemBound); + } + } + } + } + + // partial & subcell items:: filter & frutum cull & solidangle cull + { + PerformanceTimer perfTimer("partialSmallItems"); + for (auto id : inSelection.partialSubcellItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + if (test.frustumTest(itemBound.bound)) { + if (test.solidAngleTest(itemBound.bound)) { + outItems.emplace_back(itemBound); + } + } + } + } + } } } details._rendered += (int)outItems.size(); - // Restore frustum if using the frozen one: if (_freezeFrustum) { args->popViewFrustum(); diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index fae2a342a1..486c4f4cdf 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -51,19 +51,16 @@ namespace render { class FetchSpatialTree { bool _freezeFrustum{ false }; // initialized by Config bool _justFrozeFrustum{ false }; - ViewFrustum _frozenFrutstum; + ViewFrustum _frozenFrustum; float _lodAngle; public: using Config = FetchSpatialTreeConfig; - using JobModel = Job::ModelO; + using JobModel = Job::ModelIO; FetchSpatialTree() {} - FetchSpatialTree(const ItemFilter& filter) : _filter(filter) {} - - ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() }; void configure(const Config& config); - void run(const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection); + void run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection); }; class CullSpatialSelectionConfig : public Job::Config { @@ -88,25 +85,24 @@ namespace render { bool _freezeFrustum{ false }; // initialized by Config bool _justFrozeFrustum{ false }; bool _skipCulling{ false }; - ViewFrustum _frozenFrutstum; + ViewFrustum _frozenFrustum; public: using Config = CullSpatialSelectionConfig; - using JobModel = Job::ModelIO; + using Inputs = render::VaryingSet2; + using JobModel = Job::ModelIO; - CullSpatialSelection(CullFunctor cullFunctor, RenderDetails::Type type, const ItemFilter& filter) : + CullSpatialSelection(CullFunctor cullFunctor, RenderDetails::Type type) : _cullFunctor{ cullFunctor }, - _detailType(type), - _filter(filter) {} + _detailType(type) {} CullSpatialSelection(CullFunctor cullFunctor) : _cullFunctor{ cullFunctor } {} CullFunctor _cullFunctor; RenderDetails::Type _detailType{ RenderDetails::OTHER }; - ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() }; void configure(const Config& config); - void run(const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems); + void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 0f4137e38d..629cc55ccb 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -215,79 +215,158 @@ void DrawBounds::run(const RenderContextPointer& renderContext, }); } -gpu::PipelinePointer DrawFrustum::_pipeline; +DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) : + _color{ color } { + _meshVertices = gpu::BufferView(std::make_shared(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ); + _meshStream.addBuffer(_meshVertices._buffer, _meshVertices._offset, _meshVertices._stride); +} + +void DrawQuadVolume::configure(const Config& configuration) { + _isUpdateEnabled = !configuration.isFrozen; +} + +void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, const glm::vec3 vertices[8], + const gpu::BufferView& indices, int indexCount) { + assert(renderContext->args); + assert(renderContext->args->_context); + if (_isUpdateEnabled) { + auto& streamVertices = _meshVertices.edit >(); + std::copy(vertices, vertices + 8, streamVertices.begin()); + } + + RenderArgs* args = renderContext->args; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + batch.setPipeline(getPipeline()); + batch.setIndexBuffer(indices); + + batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f); + batch.setInputStream(0, _meshStream); + batch.drawIndexed(gpu::LINES, indexCount, 0U); + + args->_batch = nullptr; + }); +} + +gpu::PipelinePointer DrawQuadVolume::getPipeline() { + static gpu::PipelinePointer pipeline; + + if (!pipeline) { + auto vs = gpu::StandardShaderLib::getDrawTransformVertexPositionVS(); + auto ps = gpu::StandardShaderLib::getDrawColorPS(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("color", 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(true, false)); + pipeline = gpu::Pipeline::create(program, state); + } + return pipeline; +} + +gpu::BufferView DrawAABox::_cubeMeshIndices; + +DrawAABox::DrawAABox(const glm::vec3& color) : + DrawQuadVolume{ color } { +} + +void DrawAABox::run(const render::RenderContextPointer& renderContext, const Inputs& box) { + if (!box.isNull()) { + static const uint8_t indexData[] = { + 0, 1, + 1, 2, + 2, 3, + 3, 0, + 4, 5, + 5, 6, + 6, 7, + 7, 4, + 0, 4, + 1, 5, + 3, 7, + 2, 6 + }; + + if (!_cubeMeshIndices._buffer) { + auto indices = std::make_shared(sizeof(indexData), indexData); + _cubeMeshIndices = gpu::BufferView(indices, gpu::Element(gpu::SCALAR, gpu::UINT8, gpu::INDEX)); + } + + glm::vec3 vertices[8]; + + getVertices(box, vertices); + + DrawQuadVolume::run(renderContext, vertices, _cubeMeshIndices, sizeof(indexData) / sizeof(indexData[0])); + } +} + +void DrawAABox::getVertices(const AABox& box, glm::vec3 vertices[8]) { + vertices[0] = box.getVertex(TOP_LEFT_NEAR); + vertices[1] = box.getVertex(TOP_RIGHT_NEAR); + vertices[2] = box.getVertex(BOTTOM_RIGHT_NEAR); + vertices[3] = box.getVertex(BOTTOM_LEFT_NEAR); + vertices[4] = box.getVertex(TOP_LEFT_FAR); + vertices[5] = box.getVertex(TOP_RIGHT_FAR); + vertices[6] = box.getVertex(BOTTOM_RIGHT_FAR); + vertices[7] = box.getVertex(BOTTOM_LEFT_FAR); +} + gpu::BufferView DrawFrustum::_frustumMeshIndices; DrawFrustum::DrawFrustum(const glm::vec3& color) : - _color{ color } { - _frustumMeshVertices = gpu::BufferView(std::make_shared(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ); - _frustumMeshStream.addBuffer(_frustumMeshVertices._buffer, _frustumMeshVertices._offset, _frustumMeshVertices._stride); -} - -void DrawFrustum::configure(const Config& configuration) { - _updateFrustum = !configuration.isFrozen; + DrawQuadVolume{ color } { } void DrawFrustum::run(const render::RenderContextPointer& renderContext, const Input& input) { - assert(renderContext->args); - assert(renderContext->args->_context); - - RenderArgs* args = renderContext->args; if (input) { const auto& frustum = *input; - static uint8_t indexData[] = { 0, 1, 2, 3, 0, 4, 5, 6, 7, 4, 5, 1, 2, 6, 7, 3 }; + static const uint8_t indexData[] = { + 0, 1, + 1, 2, + 2, 3, + 3, 0, + 0, 2, + 3, 1, + 4, 5, + 5, 6, + 6, 7, + 7, 4, + 4, 6, + 7, 5, + 0, 4, + 1, 5, + 3, 7, + 2, 6 + }; if (!_frustumMeshIndices._buffer) { auto indices = std::make_shared(sizeof(indexData), indexData); _frustumMeshIndices = gpu::BufferView(indices, gpu::Element(gpu::SCALAR, gpu::UINT8, gpu::INDEX)); } - if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformVertexPositionVS(); - auto ps = gpu::StandardShaderLib::getDrawColorPS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + glm::vec3 vertices[8]; - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("color", 0)); - gpu::Shader::makeProgram(*program, slotBindings); + getVertices(frustum, vertices); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(true, false)); - _pipeline = gpu::Pipeline::create(program, state); - } - - if (_updateFrustum) { - updateFrustum(frustum); - } - - // Render the frustums in wireframe - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - batch.setPipeline(_pipeline); - batch.setIndexBuffer(_frustumMeshIndices); - - batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f); - batch.setInputStream(0, _frustumMeshStream); - batch.drawIndexed(gpu::LINE_STRIP, sizeof(indexData) / sizeof(indexData[0]), 0U); - - args->_batch = nullptr; - }); + DrawQuadVolume::run(renderContext, vertices, _frustumMeshIndices, sizeof(indexData) / sizeof(indexData[0])); } } -void DrawFrustum::updateFrustum(const ViewFrustum& frustum) { - auto& vertices = _frustumMeshVertices.edit >(); +void DrawFrustum::getVertices(const ViewFrustum& frustum, glm::vec3 vertices[8]) { vertices[0] = frustum.getNearTopLeft(); vertices[1] = frustum.getNearTopRight(); vertices[2] = frustum.getNearBottomRight(); diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index 5d98c37c21..6f98e3bef1 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -70,12 +70,12 @@ private: int _colorLocation { -1 }; }; -class DrawFrustumConfig : public render::JobConfig { +class DrawQuadVolumeConfig : public render::JobConfig { Q_OBJECT Q_PROPERTY(bool isFrozen MEMBER isFrozen NOTIFY dirty) public: - DrawFrustumConfig(bool enabled = false) : JobConfig(enabled) {} + DrawQuadVolumeConfig(bool enabled = false) : JobConfig(enabled) {} bool isFrozen{ false }; signals: @@ -83,30 +83,58 @@ signals: }; -class DrawFrustum { +class DrawQuadVolume { +public: + + using Config = DrawQuadVolumeConfig; + + void configure(const Config& configuration); + +protected: + DrawQuadVolume(const glm::vec3& color); + + void run(const render::RenderContextPointer& renderContext, const glm::vec3 vertices[8], + const gpu::BufferView& indices, int indexCount); + + gpu::BufferView _meshVertices; + gpu::BufferStream _meshStream; + glm::vec3 _color; + bool _isUpdateEnabled{ true }; + + static gpu::PipelinePointer getPipeline(); +}; + +class DrawAABox : public DrawQuadVolume { +public: + using Inputs = AABox; + using JobModel = render::Job::ModelI; + + DrawAABox(const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f)); + + void run(const render::RenderContextPointer& renderContext, const Inputs& box); + +protected: + + static gpu::BufferView _cubeMeshIndices; + + static void getVertices(const AABox& box, glm::vec3 vertices[8]); +}; + +class DrawFrustum : public DrawQuadVolume { public: - using Config = DrawFrustumConfig; using Input = ViewFrustumPointer; using JobModel = render::Job::ModelI; DrawFrustum(const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f)); - void configure(const Config& configuration); void run(const render::RenderContextPointer& renderContext, const Input& input); private: - static gpu::PipelinePointer _pipeline; static gpu::BufferView _frustumMeshIndices; - bool _updateFrustum{ true }; - gpu::BufferView _frustumMeshVertices; - gpu::BufferStream _frustumMeshStream; - glm::vec3 _color; - - void updateFrustum(const ViewFrustum& frustum); + static void getVertices(const ViewFrustum& frustum, glm::vec3 vertices[8]); }; - } #endif // hifi_render_DrawTask_h diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 2b02db81f9..77f5910b9e 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -182,6 +182,8 @@ public: Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } + Builder& withNothing() { _value.reset(); _mask.reset(); return (*this); } + // Convenient standard keys that we will keep on using all over the place static Builder visibleWorldItems() { return Builder().withVisible().withWorldSpace(); } static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); } @@ -191,12 +193,14 @@ public: static Builder background() { return Builder().withViewSpace().withLayered(); } static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); } static Builder transparentShapeLayered() { return Builder().withTypeShape().withTransparent().withWorldSpace().withLayered(); } + static Builder nothing() { return Builder().withNothing(); } }; ItemFilter(const Builder& builder) : ItemFilter(builder._value, builder._mask) {} // Item Filter operator testing if a key pass the filter bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); } + bool selectsNothing() const { return !_mask.any(); } class Less { public: diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index b9f65f48a0..d7294fa2bd 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -22,9 +22,11 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin // CPU jobs: // Fetch and cull the items from the scene - auto spatialFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered(); + const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered(); + const auto spatialFilter = render::Varying(filter); const auto spatialSelection = task.addJob("FetchSceneSelection", spatialFilter); - const auto culledSpatialSelection = task.addJob("CullSceneSelection", spatialSelection, cullFunctor, RenderDetails::ITEM, spatialFilter); + const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, spatialFilter).asVarying(); + const auto culledSpatialSelection = task.addJob("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM); // Overlays are not culled const auto nonspatialSelection = task.addJob("FetchOverlaySelection"); diff --git a/libraries/render/src/render/SortTask.cpp b/libraries/render/src/render/SortTask.cpp index 63673a71a5..5b53b5d403 100644 --- a/libraries/render/src/render/SortTask.cpp +++ b/libraries/render/src/render/SortTask.cpp @@ -75,7 +75,6 @@ void render::depthSortItems(const RenderContextPointer& renderContext, bool fron std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), backToFrontSort); } - // Finally once sorted result to a list of itemID // Finally once sorted result to a list of itemID and keep uniques render::ItemID previousID = Item::INVALID_ITEM_ID; if (!bounds) { diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index e502d44a08..956c61deaf 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -338,8 +338,6 @@ int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle int clippedTriangleCount = 0; int i; - assert(clippedTriangleCount > 0); - for (i = 0; i < 3; i++) { pointDistanceToPlane[i] = plane.distance(triangleVertices[i]); arePointsClipped.set(i, pointDistanceToPlane[i] < 0.0f); @@ -424,7 +422,7 @@ int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int pl *clippedTriangles = triangle; - while (planes < planesEnd) { + while (planes < planesEnd && triangleCount) { int clippedSubTriangleCount; trianglesToTest.clear(); diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index dcbfd83ec7..5b016d4e91 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -71,6 +71,8 @@ void ViewFrustum::setProjection(const glm::mat4& projection) { glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f); top /= top.w; _fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top)))))); + _height = _corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_RIGHT_NEAR].y; + _width = _corners[TOP_RIGHT_NEAR].x - _corners[TOP_LEFT_NEAR].x; } // ViewFrustum::calculate() @@ -691,7 +693,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 +752,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 44d09ad456..b55fe8b327 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -71,7 +71,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]; } @@ -87,6 +87,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; @@ -131,6 +135,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 @@ -172,6 +182,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; diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 86a16d9a25..1da7871172 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -184,7 +184,11 @@ Rectangle { ListElement { text: "Lightmap"; color: "White" } ListElement { text: "Scattering"; color: "White" } ListElement { text: "Lighting"; color: "White" } - ListElement { text: "Shadow"; color: "White" } + ListElement { text: "Shadow Cascade 0"; color: "White" } + ListElement { text: "Shadow Cascade 1"; color: "White" } + ListElement { text: "Shadow Cascade 2"; color: "White" } + ListElement { text: "Shadow Cascade 3"; color: "White" } + ListElement { text: "Shadow Cascade Indices"; color: "White" } ListElement { text: "Linear Depth"; color: "White" } ListElement { text: "Half Linear Depth"; color: "White" } ListElement { text: "Half Normal"; color: "White" } diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index 8548ba4119..a8fcf1aec7 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -15,15 +15,24 @@ Column { id: root spacing: 8 property var viewConfig: Render.getConfig("RenderMainView.DrawViewFrustum"); - property var shadowConfig: Render.getConfig("RenderMainView.DrawShadowFrustum"); + property var shadow0Config: Render.getConfig("RenderMainView.DrawShadowFrustum0"); + property var shadow1Config: Render.getConfig("RenderMainView.DrawShadowFrustum1"); + property var shadow2Config: Render.getConfig("RenderMainView.DrawShadowFrustum2"); + property var shadow3Config: Render.getConfig("RenderMainView.DrawShadowFrustum3"); Component.onCompleted: { viewConfig.enabled = true; - shadowConfig.enabled = true; + shadow0Config.enabled = true; + shadow1Config.enabled = true; + shadow2Config.enabled = true; + shadow3Config.enabled = true; } Component.onDestruction: { viewConfig.enabled = false; - shadowConfig.enabled = false; + shadow0Config.enabled = false; + shadow1Config.enabled = false; + shadow2Config.enabled = false; + shadow3Config.enabled = false; } CheckBox { @@ -31,7 +40,14 @@ Column { checked: false onCheckedChanged: { viewConfig.isFrozen = checked; - shadowConfig.isFrozen = checked; + shadow0Config.isFrozen = checked; + shadow1Config.isFrozen = checked; + shadow2Config.isFrozen = checked; + shadow3Config.isFrozen = checked; + shadow0BoundConfig.isFrozen = checked; + shadow1BoundConfig.isFrozen = checked; + shadow2BoundConfig.isFrozen = checked; + shadow3BoundConfig.isFrozen = checked; } } Row { @@ -46,5 +62,10 @@ Column { color: "blue" font.italic: true } + Label { + text: "Items" + color: "magenta" + font.italic: true + } } }