Merge pull request #11938 from Zvork/csm

Cascaded Shadow Maps and bug fix
This commit is contained in:
Sam Gateau 2017-12-14 00:32:43 +07:00 committed by GitHub
commit a75010fb94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1129 additions and 461 deletions

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -15,6 +15,7 @@
#include <QFileInfo>
#include <render/DrawTask.h>
#include "DeferredFrameTransform.h"
#include "DeferredFramebuffer.h"
#include "SurfaceGeometryPass.h"
#include "AmbientOcclusionEffect.h"
@ -37,7 +38,7 @@ signals:
class DebugDeferredBuffer {
public:
using Inputs = render::VaryingSet4<DeferredFramebufferPointer, LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer>;
using Inputs = render::VaryingSet5<DeferredFramebufferPointer, LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer, DeferredFrameTransformPointer>;
using Config = DebugDeferredBufferConfig;
using JobModel = render::Job::ModelI<DebugDeferredBuffer, Inputs, Config>;
@ -64,7 +65,11 @@ protected:
LightmapMode,
ScatteringMode,
LightingMode,
ShadowMode,
ShadowCascade0Mode,
ShadowCascade1Mode,
ShadowCascade2Mode,
ShadowCascade3Mode,
ShadowCascadeIndicesMode,
LinearDepthMode,
HalfLinearDepthMode,
HalfNormalMode,

View file

@ -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);
}
}
}

View file

@ -13,65 +13,202 @@
#include "LightStage.h"
#include <cmath>
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<ViewFrustum>() },
_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<ViewFrustum>() } {
Schema schema;
_schemaBuffer = std::make_shared<gpu::Buffer>(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<Triangle, 10> viewFrustumTriangles;
Plane shadowClipPlanes[4] = {
Plane(glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, top, 0.0f)),
Plane(glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, bottom, 0.0f)),
Plane(glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(left, 0.0f, 0.0f)),
Plane(glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(right, 0.0f, 0.0f))
};
viewFrustum.tesselateSidesAndFar(shadowViewInverse, viewFrustumTriangles.data(), viewMaxShadowDistance);
static const int MAX_TRIANGLE_COUNT = 16;
auto far = 0.0f;
for (auto& triangle : viewFrustumTriangles) {
Triangle clippedTriangles[MAX_TRIANGLE_COUNT];
auto clippedTriangleCount = clipTriangleWithPlanes(triangle, shadowClipPlanes, 4, clippedTriangles, MAX_TRIANGLE_COUNT);
for (auto i = 0; i < clippedTriangleCount; i++) {
const auto& clippedTriangle = clippedTriangles[i];
far = glm::max(far, -clippedTriangle.v0.z);
far = glm::max(far, -clippedTriangle.v1.z);
far = glm::max(far, -clippedTriangle.v2.z);
}
}
return far;
}
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<gpu::Buffer>(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>();
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>();
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<float>(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<Schema>().projection = ortho;
_schemaBuffer.edit<Schema>().viewInverse = viewInverse.getMatrix();
auto& schema = _schemaBuffer.edit<Schema>();
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<Schema>().projection = shadowFrustum.getProjection();
_schemaBuffer.edit<Schema>().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<Schema>().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<Shadow>(light));
shadowId = _shadows.newElement(std::make_shared<Shadow>(light, maxDistance, cascadeCount));
_descs[lightIndex].shadowId = shadowId;
}
return shadowId;

View file

@ -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<ViewFrustum> getFrustum() const { return _frustum; }
gpu::FramebufferPointer framebuffer;
gpu::TexturePointer map;
const glm::mat4& getView() const;
const glm::mat4& getProjection() const;
const std::shared_ptr<ViewFrustum>& 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<ViewFrustum> _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<ViewFrustum> _frustum;
#include "Shadows_shared.slh"
class Schema {
using Cascades = std::vector<Cascade>;
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<Shadow>;
@ -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);

View file

@ -205,34 +205,23 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
task.addJob<DrawBounds>("DrawZones", zones);
const auto frustums = task.addJob<ExtractFrustums>("ExtractFrustums");
const auto viewFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::VIEW_FRUSTUM);
const auto shadowFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::SHADOW_FRUSTUM);
task.addJob<DrawFrustum>("DrawViewFrustum", viewFrustum, glm::vec3(1.0f, 1.0f, 0.0f));
task.addJob<DrawFrustum>("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::Output>(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<DrawFrustum>(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f));
}
// Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true
task.addJob<DrawBounds>("DrawSelectionBounds", selectedItems);
}
// Layered Overlays
const auto filteredOverlaysOpaque = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT);
const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT);
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying();
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying();
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
task.addJob<DrawOverlay3D>("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false);
{ // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer
task.addJob<DrawBounds>("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque);
task.addJob<DrawBounds>("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>("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<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform);
}
// Layered Overlays
const auto filteredOverlaysOpaque = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT);
const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT);
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying();
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying();
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
task.addJob<DrawOverlay3D>("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false);
{ // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer
task.addJob<DrawBounds>("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque);
task.addJob<DrawBounds>("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent);
}
// AA job to be revisited
task.addJob<Antialiasing>("Antialiasing", primaryFramebuffer);
@ -555,17 +560,20 @@ void ExtractFrustums::run(const render::RenderContextPointer& renderContext, Out
}
// Return shadow frustum
auto& shadowFrustum = output[SHADOW_FRUSTUM].edit<ViewFrustumPointer>();
auto lightStage = args->_scene->getStage<LightStage>(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<ViewFrustumPointer>();
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();
}
}

View file

@ -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
};

View file

@ -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<BoxVertex>(i)));
}
// This indirection array is just a protection in case the ViewFrustum::PlaneIndex enum
// changes order especially as we don't need to test the NEAR and FAR planes.
static const ViewFrustum::PlaneIndex planeIndices[4] = {
ViewFrustum::TOP_PLANE,
ViewFrustum::BOTTOM_PLANE,
ViewFrustum::LEFT_PLANE,
ViewFrustum::RIGHT_PLANE
};
// Same goes for the shadow frustum planes.
for (i = 0; i < 4; i++) {
const auto& worldPlane = shadowFrustum.getPlanes()[planeIndices[i]];
// We assume the transform doesn't have a non uniform scale component to apply the
// transform to the normal without using the correct transpose of inverse, which should be the
// case for a view matrix.
auto planeNormal = shadowViewInverse.transformDirection(worldPlane.getNormal());
auto planePoint = shadowViewInverse.transform(worldPlane.getPoint());
shadowClipPlanes[i].setNormalAndPoint(planeNormal, planePoint);
}
shadowFrustum.getUniformlyTransformedSidePlanes(shadowViewInverse, shadowClipPlanes);
float near = std::numeric_limits<float>::max();
float far = 0.0f;
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<ShapeKey> skinnedShapeKeys{};
std::vector<ShapeKey> 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<RenderShadowSetup>("ShadowSetup");
task.addJob<RenderShadowSetup>("ShadowSetup");
// CPU jobs:
// Fetch and cull the items from the scene
auto shadowFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered();
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowSelection", shadowFilter);
const auto culledShadowSelection = task.addJob<CullSpatialSelection>("CullShadowSelection", shadowSelection, cullFunctor, RenderDetails::SHADOW, shadowFilter);
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
const auto setupOutput = task.addJob<RenderShadowCascadeSetup>("ShadowCascadeSetup", i);
const auto shadowFilter = setupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
// Sort
const auto sortedPipelines = task.addJob<PipelineSortShapes>("PipelineSortShadowSort", culledShadowSelection);
const auto sortedShapesAndBounds = task.addJob<DepthSortShapesAndComputeBounds>("DepthSortShadowMap", sortedPipelines, true);
// CPU jobs:
// Fetch and cull the items from the scene
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowSelection", shadowFilter);
const auto cullInputs = CullSpatialSelection::Inputs(shadowSelection, shadowFilter).asVarying();
const auto culledShadowSelection = task.addJob<CullSpatialSelection>("CullShadowSelection", cullInputs, cullFunctor, RenderDetails::SHADOW);
// GPU jobs: Render to shadow map
task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapesAndBounds, shapePlumber);
// Sort
const auto sortedPipelines = task.addJob<PipelineSortShapes>("PipelineSortShadowSort", culledShadowSelection);
const auto sortedShapesAndBounds = task.addJob<DepthSortShapesAndComputeBounds>("DepthSortShadowMap", sortedPipelines, true);
task.addJob<RenderShadowTeardown>("ShadowTeardown", cachedMode);
// GPU jobs: Render to shadow map
task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapesAndBounds, shapePlumber, i);
task.addJob<RenderShadowCascadeTeardown>("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<LightStage>();
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<LightStage>();
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 && _cascadeIndex<globalShadow->getCascadeCount()) {
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();
};

View file

@ -24,11 +24,12 @@ public:
using Inputs = render::VaryingSet2<render::ShapeBounds, AABox>;
using JobModel = render::Job::ModelI<RenderShadowMap, Inputs>;
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<RenderShadowSetup, Output>;
void run(const render::RenderContextPointer& renderContext, Output& output);
using JobModel = render::Job::Model<RenderShadowSetup>;
RenderShadowSetup() {}
void run(const render::RenderContextPointer& renderContext);
};
class RenderShadowTeardown {
class RenderShadowCascadeSetup {
public:
using Input = RenderArgs::RenderMode;
using JobModel = render::Job::ModelI<RenderShadowTeardown, Input>;
using Outputs = render::VaryingSet3<RenderArgs::RenderMode, render::ItemFilter, float>;
using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>;
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<RenderShadowCascadeTeardown, Input>;
void run(const render::RenderContextPointer& renderContext, const Input& input);
};

View file

@ -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<Input>();
// 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>("RenderShadowTask", nullptr);
task.addJob<RenderShadowTask>("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<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
assert(items.canCast<RenderFetchCullSortTask::Output>());

View file

@ -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@>

View file

@ -0,0 +1,96 @@
<!
// ShadowCore.slh
// libraries/render-utils/src
//
// Created by Olivier Prat on 11/13/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
!>
<@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<getShadowCascadeCount() ; cascadeIndex++) {
cascadeShadowCoords = evalShadowTexcoord(cascadeIndex, worldPosition);
if (isShadowCascadeProjectedOnPixel(cascadeShadowCoords)) {
return cascadeIndex;
}
}
return cascadeIndex;
}
float evalShadowCascadeWeight(vec4 cascadeTexCoords) {
// Inspired by DirectX CascadeShadowMaps11 example
vec2 distanceToOne = vec2(1.0) - cascadeTexCoords.xy;
float blend1 = min( cascadeTexCoords.x, cascadeTexCoords.y );
float blend2 = min( distanceToOne.x, distanceToOne.y );
float blend = min( blend1, blend2 );
return clamp(blend * getShadowCascadeInvBlendWidth(), 0.0, 1.0);
}
float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out vec4 cascadeShadowCoords[2], out ivec2 cascadeIndices) {
cascadeIndices.x = getFirstShadowCascadeOnPixel(0, worldPosition, cascadeShadowCoords[0]);
cascadeIndices.y = cascadeIndices.x+1;
if (cascadeIndices.x < (getShadowCascadeCount()-1)) {
cascadeIndices.y = getFirstShadowCascadeOnPixel(cascadeIndices.y, worldPosition, cascadeShadowCoords[1]);
float firstCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[0]);
float secondCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[1]);
// Returns the mix amount between first and second cascade.
return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight);
} else {
return 0.0;
}
}
<@endif@>

View file

@ -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@> <!def that !>
//

View file

@ -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) {

View file

@ -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;

View file

@ -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) {

View file

@ -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();

View file

@ -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<FetchSpatialTree, ItemSpatialTree::ItemSelection, Config>;
using JobModel = Job::ModelIO<FetchSpatialTree, ItemFilter, ItemSpatialTree::ItemSelection, Config>;
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<CullSpatialSelection, ItemSpatialTree::ItemSelection, ItemBounds, Config>;
using Inputs = render::VaryingSet2<ItemSpatialTree::ItemSelection, ItemFilter>;
using JobModel = Job::ModelIO<CullSpatialSelection, Inputs, ItemBounds, Config>;
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);
};
}

View file

@ -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<gpu::Buffer>(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::array<glm::vec3, 8U> >();
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<gpu::Buffer>(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<gpu::Buffer>(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<gpu::Buffer>(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<std::array<glm::vec3, 8U> >();
void DrawFrustum::getVertices(const ViewFrustum& frustum, glm::vec3 vertices[8]) {
vertices[0] = frustum.getNearTopLeft();
vertices[1] = frustum.getNearTopRight();
vertices[2] = frustum.getNearBottomRight();

View file

@ -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, Inputs, Config>;
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, Input, Config>;
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

View file

@ -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:

View file

@ -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<FetchSpatialTree>("FetchSceneSelection", spatialFilter);
const auto culledSpatialSelection = task.addJob<CullSpatialSelection>("CullSceneSelection", spatialSelection, cullFunctor, RenderDetails::ITEM, spatialFilter);
const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, spatialFilter).asVarying();
const auto culledSpatialSelection = task.addJob<CullSpatialSelection>("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM);
// Overlays are not culled
const auto nonspatialSelection = task.addJob<FetchNonspatialItems>("FetchOverlaySelection");

View file

@ -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) {

View file

@ -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();

View file

@ -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]];
}
}

View file

@ -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 <typename TBOX>
CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const;
static void tesselateSides(const glm::vec3 points[8], Triangle triangles[8]);
};
using ViewFrustumPointer = std::shared_ptr<ViewFrustum>;

View file

@ -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" }

View file

@ -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
}
}
}