Changed shadow task to do a single octree query as well as pipeline/depth sort for all cascades. Still issue with disapearing objects from shadow map with viewpoint

This commit is contained in:
Olivier Prat 2018-01-31 17:13:06 +01:00
parent 3fa2babec2
commit d422545c78
6 changed files with 273 additions and 107 deletions

View file

@ -213,28 +213,34 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
initZPassPipelines(*shapePlumber, state);
}
const auto coarseFrustum = task.addJob<RenderShadowSetup>("ShadowSetup");
const auto setupOutput = task.addJob<RenderShadowSetup>("ShadowSetup");
// Fetch and cull the items from the scene
static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered();
const auto fetchInput = render::Varying(shadowCasterFilter);
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowTree", fetchInput);
const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying();
const auto shadowItems = task.addJob<FetchSpatialSelection>("FetchShadowSelection", selectionInputs);
// Sort
const auto sortedPipelines = task.addJob<PipelineSortShapes>("PipelineSortShadow", shadowItems);
const auto sortedShapes = task.addJob<DepthSortShapes>("DepthSortShadow", sortedPipelines, true);
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
char jobName[64];
sprintf(jobName, "ShadowCascadeSetup%d", i);
const auto setupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, i);
const auto shadowFilter = setupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
const auto shadowFilter = task.addJob<RenderShadowCascadeSetup>(jobName, i);
// 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);
// Sort
const auto sortedPipelines = task.addJob<PipelineSortShapes>("PipelineSortShadowSort", culledShadowSelection);
const auto sortedShapesAndBounds = task.addJob<DepthSortShapesAndComputeBounds>("DepthSortShadowMap", sortedPipelines, true);
// CPU jobs: finer grained culling
const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter).asVarying();
const auto culledShadowItemsAndBounds = task.addJob<CullShapeBounds>("CullShadowCascade", cullInputs, cullFunctor, RenderDetails::SHADOW);
// GPU jobs: Render to shadow map
task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapesAndBounds, shapePlumber, i);
task.addJob<RenderShadowCascadeTeardown>("ShadowCascadeTeardown", setupOutput);
sprintf(jobName, "RenderShadowMap%d", i);
task.addJob<RenderShadowMap>(jobName, culledShadowItemsAndBounds, shapePlumber, i);
task.addJob<RenderShadowCascadeTeardown>("ShadowCascadeTeardown", shadowFilter);
}
task.addJob<RenderShadowTeardown>("ShadowTeardown", setupOutput);
}
void RenderShadowTask::configure(const Config& configuration) {
@ -267,16 +273,19 @@ void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) {
_bias[cascadeIndex]._slope = value * value * value * 0.01f;
}
void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Output& output) {
void RenderShadowSetup::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.edit1() = args->_sizeScale;
const auto globalShadow = lightStage->getCurrentKeyShadow();
if (globalShadow) {
globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
auto firstCascadeFrustum = globalShadow->getCascade(0).getFrustum();
auto& firstCascadeFrustum = globalShadow->getCascade(0).getFrustum();
unsigned int cascadeIndex;
_coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition());
_coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation());
@ -296,12 +305,13 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O
auto near = firstCascadeFrustum->getNearClip();
auto far = firstCascadeFrustum->getFarClip();
for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) {
auto cascadeLeft = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight());
auto cascadeRight = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight());
auto cascadeTop = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp());
auto cascadeBottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp());
auto cascadeNear = firstCascadeFrustum->getNearClip();
auto cascadeFar = firstCascadeFrustum->getFarClip();
auto& cascadeFrustum = globalShadow->getCascade(cascadeIndex).getFrustum();
auto cascadeLeft = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getRight());
auto cascadeRight = glm::dot(cascadeFrustum->getFarTopRight(), cascadeFrustum->getRight());
auto cascadeTop = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getUp());
auto cascadeBottom = glm::dot(cascadeFrustum->getFarBottomRight(), cascadeFrustum->getUp());
auto cascadeNear = cascadeFrustum->getNearClip();
auto cascadeFar = cascadeFrustum->getFarClip();
left = glm::min(left, cascadeLeft);
right = glm::max(right, cascadeRight);
bottom = glm::min(bottom, cascadeBottom);
@ -312,9 +322,14 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O
_coarseShadowFrustum->setProjection(glm::ortho<float>(left, right, bottom, top, near, far));
_coarseShadowFrustum->calculate();
output = _coarseShadowFrustum;
} else {
output = nullptr;
// Push frustum for further culling and selection
args->pushViewFrustum(*_coarseShadowFrustum);
args->_renderMode = RenderArgs::SHADOW_RENDER_MODE;
if (lightStage->getCurrentKeyLight()->getType() == graphics::Light::SUN) {
// Set to ridiculously high amount to prevent solid angle culling in octree selection
args->_sizeScale = 1e16f;
}
}
}
@ -324,37 +339,41 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon
// 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();
output = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered();
// Set the keylight render args
args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum()));
args->_renderMode = RenderArgs::SHADOW_RENDER_MODE;
if (lightStage->getCurrentKeyLight()->getType() == graphics::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;
}
auto& cascade = globalShadow->getCascade(_cascadeIndex);
auto& cascadeFrustum = cascade.getFrustum();
args->pushViewFrustum(*cascadeFrustum);
// Set the cull threshold to 2 shadow texels.
auto texelSize = glm::max(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x;
texelSize *= 2.0f;
// SizeScale is used in the shadow cull function defined ine RenderViewTask
args->_sizeScale = texelSize * texelSize;
} else {
output.edit1() = ItemFilter::Builder::nothing();
output = 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()) {
if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE && !input.selectsNothing()) {
args->popViewFrustum();
}
assert(args->hasViewFrustum());
}
void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) {
RenderArgs* args = renderContext->args;
if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE) {
args->popViewFrustum();
}
assert(args->hasViewFrustum());
// Reset the render args
args->_renderMode = input.get0();
args->_sizeScale = input.get2();
};
args->_sizeScale = input.get1();
}

View file

@ -82,13 +82,13 @@ signals:
class RenderShadowSetup {
public:
using Output = ViewFrustumPointer;
using Outputs = render::VaryingSet2<RenderArgs::RenderMode, float>;
using Config = RenderShadowSetupConfig;
using JobModel = render::Job::ModelO<RenderShadowSetup, Output, Config>;
using JobModel = render::Job::ModelO<RenderShadowSetup, Outputs, Config>;
RenderShadowSetup();
void configure(const Config& configuration);
void run(const render::RenderContextPointer& renderContext, Output& output);
void run(const render::RenderContextPointer& renderContext, Outputs& output);
private:
@ -104,7 +104,7 @@ private:
class RenderShadowCascadeSetup {
public:
using Outputs = render::VaryingSet3<RenderArgs::RenderMode, render::ItemFilter, float>;
using Outputs = render::ItemFilter;
using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>;
RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {}
@ -117,9 +117,16 @@ private:
class RenderShadowCascadeTeardown {
public:
using Input = RenderShadowCascadeSetup::Outputs;
using Input = render::ItemFilter;
using JobModel = render::Job::ModelI<RenderShadowCascadeTeardown, Input>;
void run(const render::RenderContextPointer& renderContext, const Input& input);
};
class RenderShadowTeardown {
public:
using Input = RenderShadowSetup::Outputs;
using JobModel = render::Job::ModelI<RenderShadowTeardown, Input>;
void run(const render::RenderContextPointer& renderContext, const Input& input);
};
#endif // hifi_RenderShadowTask_h

View file

@ -21,13 +21,8 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render:
// but the cullFunctor passed is probably tailored for perspective projection and culls too much.
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 boundsSquareRadius = glm::dot(bounds.getDimensions(), bounds.getDimensions());
return boundsSquareRadius > args->_sizeScale;
});
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);

View file

@ -19,6 +19,50 @@
using namespace render;
// Culling Frustum / solidAngle test helper class
struct Test {
CullFunctor _functor;
RenderArgs* _args;
RenderDetails::Item& _renderDetails;
glm::vec3 _eyePos;
float _squareTanAlpha;
Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) :
_functor(functor),
_args(pargs),
_renderDetails(renderDetails) {
// FIXME: Keep this code here even though we don't use it yet
/*_eyePos = _args->getViewFrustum().getPosition();
float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust));
auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees
angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree
auto tanAlpha = tan(angle);
_squareTanAlpha = (float)(tanAlpha * tanAlpha);
*/
}
bool frustumTest(const AABox& bound) {
if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) {
_renderDetails._outOfView++;
return false;
}
return true;
}
bool solidAngleTest(const AABox& bound) {
// FIXME: Keep this code here even though we don't use it yet
//auto eyeToPoint = bound.calcCenter() - _eyePos;
//auto boundSize = bound.getDimensions();
//float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
//if (test < 0.0f) {
if (!_functor(_args, bound)) {
_renderDetails._tooSmall++;
return false;
}
return true;
}
};
void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
const ItemBounds& inItems, ItemBounds& outItems) {
assert(renderContext->args);
@ -82,18 +126,20 @@ void FetchSpatialTree::configure(const Config& config) {
_lodAngle = config.lodAngle;
}
void FetchSpatialTree::run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection) {
void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemSpatialTree::ItemSelection& outSelection) {
// start fresh
outSelection.clear();
auto& filter = inputs;
if (!filter.selectsNothing()) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
auto& scene = renderContext->_scene;
// Eventually use a frozen frustum
auto queryFrustum = args->getViewFrustum();
// Eventually use a frozen frustum
if (_freezeFrustum) {
if (_justFrozeFrustum) {
_justFrozeFrustum = false;
@ -134,50 +180,6 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
args->pushViewFrustum(_frozenFrustum); // replace the true view frustum by the frozen one
}
// Culling Frustum / solidAngle test helper class
struct Test {
CullFunctor _functor;
RenderArgs* _args;
RenderDetails::Item& _renderDetails;
glm::vec3 _eyePos;
float _squareTanAlpha;
Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) :
_functor(functor),
_args(pargs),
_renderDetails(renderDetails)
{
// FIXME: Keep this code here even though we don't use it yet
/*_eyePos = _args->getViewFrustum().getPosition();
float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust));
auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees
angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree
auto tanAlpha = tan(angle);
_squareTanAlpha = (float)(tanAlpha * tanAlpha);
*/
}
bool frustumTest(const AABox& bound) {
if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) {
_renderDetails._outOfView++;
return false;
}
return true;
}
bool solidAngleTest(const AABox& bound) {
// FIXME: Keep this code here even though we don't use it yet
//auto eyeToPoint = bound.calcCenter() - _eyePos;
//auto boundSize = bound.getDimensions();
//float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
//if (test < 0.0f) {
if (!_functor(_args, bound)) {
_renderDetails._tooSmall++;
return false;
}
return true;
}
};
Test test(_cullFunctor, args, details);
// Now we have a selection of items to render
@ -309,3 +311,112 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
std::static_pointer_cast<Config>(renderContext->jobConfig)->numItems = (int)outItems.size();
}
void CullShapeBounds::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
const auto& inShapes = inputs.get0();
const auto& filter = inputs.get1();
auto& outShapes = outputs.edit0();
auto& outBounds = outputs.edit1();
outShapes.clear();
outBounds = AABox();
if (!filter.selectsNothing()) {
auto& details = args->_details.edit(_detailType);
Test test(_cullFunctor, args, details);
for (auto& inItems : inShapes) {
auto key = inItems.first;
auto outItems = outShapes.find(key);
if (outItems == outShapes.end()) {
outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first;
outItems->second.reserve(inItems.second.size());
}
details._considered += (int)inItems.second.size();
for (auto& item : inItems.second) {
if (test.frustumTest(item.bound) && test.solidAngleTest(item.bound)) {
outItems->second.emplace_back(item);
outBounds += item.bound;
}
}
details._rendered += (int)outItems->second.size();
}
for (auto& items : outShapes) {
items.second.shrink_to_fit();
}
}
}
void FetchSpatialSelection::run(const RenderContextPointer& renderContext,
const Inputs& inputs, ItemBounds& outItems) {
assert(renderContext->args);
RenderArgs* args = renderContext->args;
auto& scene = renderContext->_scene;
auto& inSelection = inputs.get0();
// Now we have a selection of items to render
outItems.clear();
outItems.reserve(inSelection.numItems());
const auto filter = inputs.get1();
if (!filter.selectsNothing()) {
// Now get the bound, and
// filter individually against the _filter
// 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);
}
}
}
}
}

View file

@ -55,12 +55,13 @@ namespace render {
float _lodAngle;
public:
using Config = FetchSpatialTreeConfig;
using JobModel = Job::ModelIO<FetchSpatialTree, ItemFilter, ItemSpatialTree::ItemSelection, Config>;
using Inputs = ItemFilter;
using JobModel = Job::ModelIO<FetchSpatialTree, Inputs, ItemSpatialTree::ItemSelection, Config>;
FetchSpatialTree() {}
void configure(const Config& config);
void run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection);
void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemSpatialTree::ItemSelection& outSelection);
};
class CullSpatialSelectionConfig : public Job::Config {
@ -96,7 +97,8 @@ namespace render {
_detailType(type) {}
CullSpatialSelection(CullFunctor cullFunctor) :
_cullFunctor{ cullFunctor } {}
_cullFunctor{ cullFunctor } {
}
CullFunctor _cullFunctor;
RenderDetails::Type _detailType{ RenderDetails::OTHER };
@ -105,6 +107,38 @@ namespace render {
void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems);
};
class CullShapeBounds {
public:
using Inputs = render::VaryingSet2<ShapeBounds, ItemFilter>;
using Outputs = render::VaryingSet2<ShapeBounds, AABox>;
using JobModel = Job::ModelIO<CullShapeBounds, Inputs, Outputs>;
CullShapeBounds(CullFunctor cullFunctor, RenderDetails::Type type) :
_cullFunctor{ cullFunctor },
_detailType(type) {}
CullShapeBounds(CullFunctor cullFunctor) :
_cullFunctor{ cullFunctor } {
}
void run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
private:
CullFunctor _cullFunctor;
RenderDetails::Type _detailType{ RenderDetails::OTHER };
};
class FetchSpatialSelection {
public:
using Inputs = render::VaryingSet2<ItemSpatialTree::ItemSelection, ItemFilter>;
using JobModel = Job::ModelIO<FetchSpatialSelection, Inputs, ItemBounds>;
FetchSpatialSelection() {}
void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems);
};
}
#endif // hifi_render_CullTask_h;

View file

@ -23,9 +23,9 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin
// CPU jobs:
// Fetch and cull the items from the scene
const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
const auto spatialFilter = render::Varying(filter);
const auto spatialSelection = task.addJob<FetchSpatialTree>("FetchSceneSelection", spatialFilter);
const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, spatialFilter).asVarying();
const auto fetchInput = render::Varying(filter);
const auto spatialSelection = task.addJob<FetchSpatialTree>("FetchSceneSelection", fetchInput);
const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, render::Varying(filter)).asVarying();
const auto culledSpatialSelection = task.addJob<CullSpatialSelection>("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM);
// Overlays are not culled