Merge pull request #12323 from Zvork/shadow

Shadow performance / acnee improvements
This commit is contained in:
John Conklin II 2018-02-12 14:41:13 -08:00 committed by GitHub
commit 51bf3c1466
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 722 additions and 192 deletions

View file

@ -18,8 +18,7 @@
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) {
void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) {
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
assert(items.canCast<RenderFetchCullSortTask::Output>());

View file

@ -62,7 +62,7 @@ Framebuffer* Framebuffer::createShadowmap(uint16 width) {
samplerDesc._wrapModeU = Sampler::WRAP_BORDER;
samplerDesc._wrapModeV = Sampler::WRAP_BORDER;
samplerDesc._filter = Sampler::FILTER_MIN_MAG_LINEAR;
samplerDesc._comparisonFunc = LESS_EQUAL;
samplerDesc._comparisonFunc = LESS;
depthTexture->setSampler(Sampler(samplerDesc));
framebuffer->setDepthStencilBuffer(depthTexture, depthFormat);

View file

@ -64,8 +64,14 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc
return voxelSizeScale / powf(2.0f, renderLevel);
}
float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) {
float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) {
const float maxScale = (float)TREE_SCALE;
float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO;
return atan(maxScale / visibleDistanceAtMaxScale);
}
float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust) {
// Smallest visible element is 1cm
const float smallestSize = 0.01f;
return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale);
}

View file

@ -25,7 +25,8 @@ float calculateRenderAccuracy(const glm::vec3& position,
float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale);
float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust);
float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust);
float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust);
// MIN_ELEMENT_ANGULAR_DIAMETER = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301 radians ~= 0.25 degrees
const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians

View file

@ -23,8 +23,6 @@ const glm::mat4 LightStage::Shadow::_biasMatrix{
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() {
@ -63,7 +61,7 @@ LightStage::LightStage() {
LightStage::Shadow::Schema::Schema() {
ShadowTransform defaultTransform;
defaultTransform.bias = MAX_BIAS;
defaultTransform.fixedBias = 0.005f;
std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform);
invMapSize = 1.0f / MAP_SIZE;
cascadeCount = 1;
@ -214,13 +212,10 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
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);
}
void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,
float nearDepth, float farDepth) {
float nearDepth, float farDepth, float fixedBias, float slopeBias) {
assert(nearDepth < farDepth);
assert(cascadeIndex < _cascades.size());
@ -269,12 +264,10 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co
// Update the buffer
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);
auto& schemaCascade = schema.cascades[cascadeIndex];
schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix();
schemaCascade.fixedBias = fixedBias;
schemaCascade.slopeBias = slopeBias;
}
void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) {
@ -285,7 +278,9 @@ void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const View
*cascade._frustum = shadowFrustum;
// Update the buffer
_schemaBuffer.edit<Schema>().cascades[cascadeIndex].reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix();
auto& schema = _schemaBuffer.edit<Schema>();
auto& schemaCascade = schema.cascades[cascadeIndex];
schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix();
}
LightStage::Index LightStage::findLight(const LightPointer& light) const {

View file

@ -80,7 +80,7 @@ public:
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);
float nearDepth = 1.0f, float farDepth = 1000.0f, float fixedBias = 0.005f, float slopeBias = 0.005f);
void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum);
const UniformBufferView& getBuffer() const { return _schemaBuffer; }
@ -213,6 +213,7 @@ protected:
Index _sunOffLightId;
Index _defaultLightId;
};
using LightStagePointer = std::shared_ptr<LightStage>;

View file

@ -200,8 +200,10 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
});
}
void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask) {
cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&) { return true; };
void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cameraCullFunctor, uint8_t tagBits, uint8_t tagMask) {
::CullFunctor shadowCullFunctor = [this](const RenderArgs* args, const AABox& bounds) {
return _cullFunctor(args, bounds);
};
// Prepare the ShapePipeline
ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>();
@ -213,26 +215,54 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
initZPassPipelines(*shapePlumber, state);
}
task.addJob<RenderShadowSetup>("ShadowSetup");
const auto setupOutput = task.addJob<RenderShadowSetup>("ShadowSetup");
const auto queryResolution = setupOutput.getN<RenderShadowSetup::Outputs>(2);
// Fetch and cull the items from the scene
static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask);
const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying();
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowTree", fetchInput);
const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying();
const auto shadowItems = task.addJob<FetchSpatialSelection>("FetchShadowSelection", selectionInputs);
// Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not
// frustum culling or this will make shadow casters out of the camera frustum disappear.
const auto cameraFrustum = setupOutput.getN<RenderShadowSetup::Outputs>(2);
const auto applyFunctorInputs = ApplyCullFunctorOnItemBounds::Inputs(shadowItems, cameraFrustum).asVarying();
const auto culledShadowItems = task.addJob<ApplyCullFunctorOnItemBounds>("ShadowCullCamera", applyFunctorInputs, cameraCullFunctor);
// Sort
const auto sortedPipelines = task.addJob<PipelineSortShapes>("PipelineSortShadow", culledShadowItems);
const auto sortedShapes = task.addJob<DepthSortShapes>("DepthSortShadow", sortedPipelines, true);
render::Varying cascadeFrustums[SHADOW_CASCADE_MAX_COUNT] = {
ViewFrustumPointer(),
ViewFrustumPointer(),
ViewFrustumPointer(),
ViewFrustumPointer()
};
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
const auto setupOutput = task.addJob<RenderShadowCascadeSetup>("ShadowCascadeSetup", i, tagBits, tagMask);
const auto shadowFilter = setupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
char jobName[64];
sprintf(jobName, "ShadowCascadeSetup%d", i);
const auto cascadeSetupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, i, _cullFunctor, tagBits, tagMask);
const auto shadowFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(0);
auto antiFrustum = render::Varying(ViewFrustumPointer());
cascadeFrustums[i] = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
if (i > 1) {
antiFrustum = cascadeFrustums[i - 2];
}
// 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, antiFrustum).asVarying();
const auto culledShadowItemsAndBounds = task.addJob<CullShapeBounds>("CullShadowCascade", cullInputs, shadowCullFunctor, 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) {
@ -241,15 +271,107 @@ void RenderShadowTask::configure(const Config& configuration) {
// Task::configure(configuration);
}
void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) {
RenderShadowSetup::RenderShadowSetup() :
_cameraFrustum{ std::make_shared<ViewFrustum>() },
_coarseShadowFrustum{ std::make_shared<ViewFrustum>() } {
}
void RenderShadowSetup::configure(const Config& configuration) {
setConstantBias(0, configuration.constantBias0);
setConstantBias(1, configuration.constantBias1);
setConstantBias(2, configuration.constantBias2);
setConstantBias(3, configuration.constantBias3);
setSlopeBias(0, configuration.slopeBias0);
setSlopeBias(1, configuration.slopeBias1);
setSlopeBias(2, configuration.slopeBias2);
setSlopeBias(3, configuration.slopeBias3);
}
void RenderShadowSetup::setConstantBias(int cascadeIndex, float value) {
_bias[cascadeIndex]._constant = value * value * value * 0.004f;
}
void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) {
_bias[cascadeIndex]._slope = value * value * value * 0.01f;
}
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() = glm::ivec2(0, 0);
// Save main camera frustum
*_cameraFrustum = args->getViewFrustum();
output.edit2() = _cameraFrustum;
const auto globalShadow = lightStage->getCurrentKeyShadow();
if (globalShadow) {
globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
auto& firstCascade = globalShadow->getCascade(0);
auto& firstCascadeFrustum = firstCascade.getFrustum();
unsigned int cascadeIndex;
// Adjust each cascade frustum
for (cascadeIndex = 0; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) {
auto& bias = _bias[cascadeIndex];
globalShadow->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(),
SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR,
bias._constant, bias._slope);
}
// Now adjust coarse frustum bounds
auto frustumPosition = firstCascadeFrustum->getPosition();
auto farTopLeft = firstCascadeFrustum->getFarTopLeft() - frustumPosition;
auto farBottomRight = firstCascadeFrustum->getFarBottomRight() - frustumPosition;
auto left = glm::dot(farTopLeft, firstCascadeFrustum->getRight());
auto right = glm::dot(farBottomRight, firstCascadeFrustum->getRight());
auto top = glm::dot(farTopLeft, firstCascadeFrustum->getUp());
auto bottom = glm::dot(farBottomRight, firstCascadeFrustum->getUp());
auto near = firstCascadeFrustum->getNearClip();
auto far = firstCascadeFrustum->getFarClip();
for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) {
auto& cascadeFrustum = globalShadow->getCascade(cascadeIndex).getFrustum();
farTopLeft = cascadeFrustum->getFarTopLeft() - frustumPosition;
farBottomRight = cascadeFrustum->getFarBottomRight() - frustumPosition;
auto cascadeLeft = glm::dot(farTopLeft, cascadeFrustum->getRight());
auto cascadeRight = glm::dot(farBottomRight, cascadeFrustum->getRight());
auto cascadeTop = glm::dot(farTopLeft, cascadeFrustum->getUp());
auto cascadeBottom = glm::dot(farBottomRight, 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);
top = glm::max(top, cascadeTop);
near = glm::min(near, cascadeNear);
far = glm::max(far, cascadeFar);
}
_coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition());
_coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation());
_coarseShadowFrustum->setProjection(glm::ortho<float>(left, right, bottom, top, near, far));
_coarseShadowFrustum->calculate();
// Push frustum for further culling and selection
args->pushViewFrustum(*_coarseShadowFrustum);
args->_renderMode = RenderArgs::SHADOW_RENDER_MODE;
// We want for the octree query enough resolution to catch the details in the lowest cascade. So compute
// the desired resolution for the first cascade frustum and extrapolate it to the coarse frustum.
glm::ivec2 queryResolution = firstCascade.framebuffer->getSize();
queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth());
queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight());
output.edit1() = queryResolution;
}
}
@ -259,39 +381,44 @@ 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().withTagBits(_tagBits, _tagMask);
globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask);
// 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);
auto texelSize = glm::min(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x;
// Set the cull threshold to 24 shadow texels. This is totally arbitrary
const auto minTexelCount = 24.0f;
// TODO : maybe adapt that with LOD management system?
texelSize *= minTexelCount;
_cullFunctor._minSquareSize = texelSize * texelSize;
output.edit1() = cascadeFrustum;
} else {
output.edit1() = ItemFilter::Builder::nothing();
output.edit0() = ItemFilter::Builder::nothing();
output.edit1() = ViewFrustumPointer();
}
}
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();
};
}

View file

@ -17,6 +17,8 @@
#include <render/CullTask.h>
#include "Shadows_shared.slh"
class ViewFrustum;
class RenderShadowMap {
@ -48,40 +50,101 @@ public:
using JobModel = render::Task::Model<RenderShadowTask, Config>;
RenderShadowTask() {}
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor shouldRender, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cameraCullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00);
void configure(const Config& configuration);
struct CullFunctor {
float _minSquareSize{ 0.0f };
bool operator()(const RenderArgs* args, const AABox& bounds) const {
// Cull only objects that are too small relatively to shadow frustum
const auto boundsSquareRadius = glm::dot(bounds.getDimensions(), bounds.getDimensions());
return boundsSquareRadius > _minSquareSize;
}
};
CullFunctor _cullFunctor;
};
class RenderShadowSetupConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(float constantBias0 MEMBER constantBias0 NOTIFY dirty)
Q_PROPERTY(float constantBias1 MEMBER constantBias1 NOTIFY dirty)
Q_PROPERTY(float constantBias2 MEMBER constantBias2 NOTIFY dirty)
Q_PROPERTY(float constantBias3 MEMBER constantBias3 NOTIFY dirty)
Q_PROPERTY(float slopeBias0 MEMBER slopeBias0 NOTIFY dirty)
Q_PROPERTY(float slopeBias1 MEMBER slopeBias1 NOTIFY dirty)
Q_PROPERTY(float slopeBias2 MEMBER slopeBias2 NOTIFY dirty)
Q_PROPERTY(float slopeBias3 MEMBER slopeBias3 NOTIFY dirty)
public:
float constantBias0{ 0.15f };
float constantBias1{ 0.15f };
float constantBias2{ 0.175f };
float constantBias3{ 0.2f };
float slopeBias0{ 0.6f };
float slopeBias1{ 0.6f };
float slopeBias2{ 0.7f };
float slopeBias3{ 0.82f };
signals:
void dirty();
};
class RenderShadowSetup {
public:
using JobModel = render::Job::Model<RenderShadowSetup>;
using Outputs = render::VaryingSet3<RenderArgs::RenderMode, glm::ivec2, ViewFrustumPointer>;
using Config = RenderShadowSetupConfig;
using JobModel = render::Job::ModelO<RenderShadowSetup, Outputs, Config>;
RenderShadowSetup() {}
void run(const render::RenderContextPointer& renderContext);
RenderShadowSetup();
void configure(const Config& configuration);
void run(const render::RenderContextPointer& renderContext, Outputs& output);
private:
ViewFrustumPointer _cameraFrustum;
ViewFrustumPointer _coarseShadowFrustum;
struct {
float _constant;
float _slope;
} _bias[SHADOW_CASCADE_MAX_COUNT];
void setConstantBias(int cascadeIndex, float value);
void setSlopeBias(int cascadeIndex, float value);
};
class RenderShadowCascadeSetup {
public:
using Outputs = render::VaryingSet3<RenderArgs::RenderMode, render::ItemFilter, float>;
using Outputs = render::VaryingSet2<render::ItemFilter, ViewFrustumPointer>;
using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>;
RenderShadowCascadeSetup(unsigned int cascadeIndex, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) : _cascadeIndex{ cascadeIndex }, _tagBits(tagBits), _tagMask(tagMask) {}
RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) :
_cascadeIndex{ cascadeIndex }, _cullFunctor{ cullFunctor }, _tagBits(tagBits), _tagMask(tagMask) {}
void run(const render::RenderContextPointer& renderContext, Outputs& output);
private:
unsigned int _cascadeIndex;
RenderShadowTask::CullFunctor& _cullFunctor;
uint8_t _tagBits{ 0x00 };
uint8_t _tagMask{ 0x00 };
};
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

@ -17,18 +17,9 @@
void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred, uint8_t tagBits, uint8_t tagMask) {
// 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.
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;
}, tagBits, tagMask);
// Warning : the cull functor passed to the shadow pass should only be testing for LOD culling. If frustum culling
// is performed, then casters not in the view frustum will be removed, which is not what we wish.
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor, tagBits, tagMask);
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, tagBits, tagMask);
assert(items.canCast<RenderFetchCullSortTask::Output>());

View file

@ -79,31 +79,26 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) +
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3])
);
return shadowAttenuation;
}
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);
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) {
float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL;
return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias);
}
float evalShadowAttenuation(vec4 worldPosition, float viewDepth, vec3 viewNormal) {
float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) {
ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
vec4 cascadeShadowCoords[2];
vec4 cascadeShadowCoords[2] = { vec4(0), vec4(0) };
ivec2 cascadeIndices;
float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
// Adjust bias if we are at a grazing angle with light
float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1);
vec2 cascadeAttenuations = vec2(1.0, 1.0);
cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, viewNormal, offsets, cascadeShadowCoords[0]);
cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], oneMinusNdotL);
if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) {
cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, viewNormal, offsets, cascadeShadowCoords[1]);
cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], oneMinusNdotL);
}
float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix);
// Falloff to max distance

View file

@ -37,14 +37,15 @@ float getShadowScale() {
return shadow.invMapSize;
}
float getShadowBias(int cascadeIndex) {
return shadow.cascades[cascadeIndex].bias;
float getShadowFixedBias(int cascadeIndex) {
return shadow.cascades[cascadeIndex].fixedBias;
}
vec3 getShadowDirInViewSpace() {
return shadow.lightDirInViewSpace;
float getShadowSlopeBias(int cascadeIndex) {
return shadow.cascades[cascadeIndex].slopeBias;
}
// Compute the texture coordinates from world coordinates
vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) {
vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position;
@ -52,8 +53,8 @@ vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) {
}
bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) {
bvec2 greaterThanZero = greaterThanEqual(cascadeTexCoords.xy, vec2(0));
bvec2 lessThanOne = lessThanEqual(cascadeTexCoords.xy, vec2(1));
bvec2 greaterThanZero = greaterThan(cascadeTexCoords.xy, vec2(0));
bvec2 lessThanOne = lessThan(cascadeTexCoords.xy, vec2(1));
return all(greaterThanZero) && all(lessThanOne);
}
@ -81,10 +82,10 @@ float evalShadowCascadeWeight(vec4 cascadeTexCoords) {
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)) {
float firstCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[0]);
if (firstCascadeWeight<1.0 && 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);

View file

@ -11,16 +11,14 @@
struct ShadowTransform {
MAT4 reprojection;
float bias;
float fixedBias;
float slopeBias;
float _padding1;
float _padding2;
float _padding3;
};
struct ShadowParameters {
ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT];
VEC3 lightDirInViewSpace;
int cascadeCount;
float invMapSize;
float invCascadeBlendWidth;

View file

@ -28,7 +28,9 @@ void main(void) {
vec4 viewPos = vec4(frag.position.xyz, 1.0);
vec4 worldPos = getViewInverse() * viewPos;
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal);
Light shadowLight = getKeyLight();
vec3 worldLightDirection = getLightDirection(shadowLight);
float shadowAttenuation = evalShadowAttenuation(worldLightDirection, worldPos, -viewPos.z, frag.normal);
if (frag.mode == FRAG_MODE_UNLIT) {
discard;

View file

@ -28,7 +28,9 @@ void main(void) {
vec4 viewPos = vec4(frag.position.xyz, 1.0);
vec4 worldPos = getViewInverse() * viewPos;
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal);
Light shadowLight = getKeyLight();
vec3 worldLightDirection = getLightDirection(shadowLight);
float shadowAttenuation = evalShadowAttenuation(worldLightDirection, worldPos, -viewPos.z, frag.normal);
// Light mapped or not ?
if (frag.mode == FRAG_MODE_UNLIT) {

View file

@ -14,11 +14,66 @@
#include <algorithm>
#include <assert.h>
#include <OctreeUtils.h>
#include <PerfStat.h>
#include <OctreeUtils.h>
using namespace render;
// Culling Frustum / solidAngle test helper class
struct Test {
CullFunctor _functor;
RenderArgs* _args;
RenderDetails::Item& _renderDetails;
ViewFrustumPointer _antiFrustum;
glm::vec3 _eyePos;
float _squareTanAlpha;
Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum = nullptr) :
_functor(functor),
_args(pargs),
_renderDetails(renderDetails),
_antiFrustum(antiFrustum) {
// FIXME: Keep this code here even though we don't use it yet
/*_eyePos = _args->getViewFrustum().getPosition();
float a = glm::degrees(Octree::getPerspectiveAccuracyAngle(_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 antiFrustumTest(const AABox& bound) {
assert(_antiFrustum);
if (_antiFrustum->boxInsideFrustum(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);
@ -84,18 +139,21 @@ 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.get0();
auto frustumResolution = inputs.get1();
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;
@ -105,8 +163,19 @@ void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Item
}
// Octree selection!
float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
scene->getSpatialTree().selectCellItems(outSelection, filter, queryFrustum, angle);
float threshold = 0.0f;
if (queryFrustum.isPerspective()) {
threshold = getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust);
if (frustumResolution.y > 0) {
threshold = glm::max(queryFrustum.getFieldOfView() / frustumResolution.y, threshold);
}
} else {
threshold = getOrthographicAccuracySize(args->_sizeScale, args->_boundaryLevelAdjust);
glm::vec2 frustumSize = glm::vec2(queryFrustum.getWidth(), queryFrustum.getHeight());
const auto pixelResolution = frustumResolution.x > 0 ? frustumResolution : glm::ivec2(2048, 2048);
threshold = glm::max(threshold, glm::min(frustumSize.x / pixelResolution.x, frustumSize.y / pixelResolution.y));
}
scene->getSpatialTree().selectCellItems(outSelection, filter, queryFrustum, threshold);
}
}
@ -136,50 +205,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
@ -311,3 +336,146 @@ 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();
const auto& antiFrustum = inputs.get2();
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, antiFrustum);
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();
if (antiFrustum == nullptr) {
for (auto& item : inItems.second) {
if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) {
outItems->second.emplace_back(item);
outBounds += item.bound;
}
}
} else {
for (auto& item : inItems.second) {
if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(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 ApplyCullFunctorOnItemBounds::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
auto& inItems = inputs.get0();
auto& outItems = outputs;
auto inputFrustum = inputs.get1();
if (inputFrustum != nullptr) {
args->pushViewFrustum(*inputFrustum);
}
outItems.clear();
outItems.reserve(inItems.size());
for (auto& item : inItems) {
if (_cullFunctor(args, item.bound)) {
outItems.emplace_back(item);
}
}
if (inputFrustum != nullptr) {
args->popViewFrustum();
}
}
void FetchSpatialSelection::run(const RenderContextPointer& renderContext,
const Inputs& inputs, ItemBounds& outItems) {
assert(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
{
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
{
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
{
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
{
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

@ -53,14 +53,16 @@ namespace render {
bool _justFrozeFrustum{ false };
ViewFrustum _frozenFrustum;
float _lodAngle;
public:
using Config = FetchSpatialTreeConfig;
using JobModel = Job::ModelIO<FetchSpatialTree, ItemFilter, ItemSpatialTree::ItemSelection, Config>;
using Inputs = render::VaryingSet2<ItemFilter, glm::ivec2>;
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 +98,8 @@ namespace render {
_detailType(type) {}
CullSpatialSelection(CullFunctor cullFunctor) :
_cullFunctor{ cullFunctor } {}
_cullFunctor{ cullFunctor } {
}
CullFunctor _cullFunctor;
RenderDetails::Type _detailType{ RenderDetails::OTHER };
@ -105,6 +108,51 @@ namespace render {
void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems);
};
class CullShapeBounds {
public:
using Inputs = render::VaryingSet3<ShapeBounds, ItemFilter, ViewFrustumPointer>;
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);
};
class ApplyCullFunctorOnItemBounds {
public:
using Inputs = render::VaryingSet2<ItemBounds, ViewFrustumPointer>;
using Outputs = ItemBounds;
using JobModel = Job::ModelIO<ApplyCullFunctorOnItemBounds, Inputs, Outputs>;
ApplyCullFunctorOnItemBounds(render::CullFunctor cullFunctor) : _cullFunctor(cullFunctor) {}
void run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
private:
render::CullFunctor _cullFunctor;
};
}
#endif // hifi_render_CullTask_h;

View file

@ -148,7 +148,7 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS
}
// Draw the LOD Reticle
{
float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
float angle = glm::degrees(getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
Transform crosshairModel;
crosshairModel.setTranslation(glm::vec3(0.0, 0.0, -1000.0));
crosshairModel.setScale(1000.0f * tanf(glm::radians(angle))); // Scaling at the actual tan of the lod angle => Multiplied by TWO

View file

@ -24,7 +24,8 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin
// Fetch and cull the items from the scene
const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered().withTagBits(tagBits, tagMask);
const auto spatialFilter = render::Varying(filter);
const auto spatialSelection = task.addJob<FetchSpatialTree>("FetchSceneSelection", spatialFilter);
const auto fetchInput = FetchSpatialTree::Inputs(filter, glm::ivec2(0,0)).asVarying();
const auto spatialSelection = task.addJob<FetchSpatialTree>("FetchSceneSelection", fetchInput);
const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, spatialFilter).asVarying();
const auto culledSpatialSelection = task.addJob<CullSpatialSelection>("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM);

View file

@ -12,9 +12,26 @@
#include <ViewFrustum.h>
using namespace render;
void Octree::PerspectiveSelector::setAngle(float a) {
const float MAX_LOD_ANGLE = glm::radians(45.0f);
const float MIN_LOD_ANGLE = glm::radians(1.0f / 60.0f);
angle = std::max(MIN_LOD_ANGLE, std::min(MAX_LOD_ANGLE, a));
auto tanAlpha = tan(angle);
squareTanAlpha = (float)(tanAlpha * tanAlpha);
}
float Octree::PerspectiveSelector::testThreshold(const Coord3f& point, float size) const {
auto eyeToPoint = point - eyePos;
return (size * size / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
}
float Octree::OrthographicSelector::testThreshold(const Coord3f& point, float size) const {
return (size * size) - squareMinSize;
}
const float Octree::INV_DEPTH_DIM[] = {
1.0f,
@ -520,10 +537,9 @@ int Octree::selectTraverse(Index cellID, CellSelection& selection, const Frustum
// Test for lod
auto cellLocation = cell.getlocation();
float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth));
if (lod < 0.0f) {
float test = selector.testThreshold(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth));
if (test < 0.0f) {
return 0;
break;
}
// Select this cell partially in frustum
@ -543,13 +559,13 @@ int Octree::selectTraverse(Index cellID, CellSelection& selection, const Frustum
}
int Octree::selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const {
int Octree::selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const {
int numSelectedsIn = (int) selection.size();
auto cell = getConcreteCell(cellID);
auto cellLocation = cell.getlocation();
float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth));
if (lod < 0.0f) {
float test = selector.testThreshold(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth));
if (test < 0.0f) {
return 0;
}
@ -580,24 +596,40 @@ int Octree::selectCellBrick(Index cellID, CellSelection& selection, bool inside)
return (int) selection.size() - numSelectedsIn;
}
int ItemSpatialTree::selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const {
int ItemSpatialTree::selectCells(CellSelection& selection, const ViewFrustum& frustum, float threshold) const {
auto worldPlanes = frustum.getPlanes();
FrustumSelector selector;
for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) {
::Plane octPlane;
octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH));
selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient());
if (frustum.isPerspective()) {
PerspectiveSelector selector;
for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) {
::Plane octPlane;
octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH));
selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient());
}
selector.eyePos = evalCoordf(frustum.getPosition(), ROOT_DEPTH);
selector.setAngle(threshold);
return Octree::select(selection, selector);
} else {
OrthographicSelector selector;
for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) {
::Plane octPlane;
octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH));
selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient());
}
// Divide the threshold (which is in world distance units) by the dimension of the octree
// as all further computations will be done in normalized octree units
threshold *= getInvCellWidth(ROOT_DEPTH);
selector.setSize(threshold);
return Octree::select(selection, selector);
}
selector.eyePos = evalCoordf(frustum.getPosition(), ROOT_DEPTH);
selector.setAngle(glm::radians(lodAngle));
return Octree::select(selection, selector);
}
int ItemSpatialTree::selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const {
selectCells(selection.cellSelection, frustum, lodAngle);
int ItemSpatialTree::selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum,
float threshold) const {
selectCells(selection.cellSelection, frustum, threshold);
// Just grab the items in every selected bricks
for (auto brickId : selection.cellSelection.insideBricks) {

View file

@ -312,23 +312,27 @@ namespace render {
class FrustumSelector {
public:
Coord4f frustum[6];
virtual ~FrustumSelector() {}
virtual float testThreshold(const Coord3f& point, float size) const = 0;
};
class PerspectiveSelector : public FrustumSelector {
public:
Coord3f eyePos;
float angle;
float squareTanAlpha;
const float MAX_LOD_ANGLE = glm::radians(45.0f);
const float MIN_LOD_ANGLE = glm::radians(1.0f / 60.0f);
void setAngle(float a);
float testThreshold(const Coord3f& point, float size) const override;
};
void setAngle(float a) {
angle = std::max(MIN_LOD_ANGLE, std::min(MAX_LOD_ANGLE, a));
auto tanAlpha = tan(angle);
squareTanAlpha = (float)(tanAlpha * tanAlpha);
}
class OrthographicSelector : public FrustumSelector {
public:
float squareMinSize;
float testSolidAngle(const Coord3f& point, float size) const {
auto eyeToPoint = point - eyePos;
return (size * size / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
}
void setSize(float a) { squareMinSize = a * a; }
float testThreshold(const Coord3f& point, float size) const override;
};
int select(CellSelection& selection, const FrustumSelector& selector) const;
@ -443,7 +447,7 @@ namespace render {
Index resetItem(Index oldCell, const ItemKey& oldKey, const AABox& bound, const ItemID& item, ItemKey& newKey);
// Selection and traverse
int selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const;
int selectCells(CellSelection& selection, const ViewFrustum& frustum, float threshold) const;
class ItemSelection {
public:
@ -469,7 +473,8 @@ namespace render {
}
};
int selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const;
int selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum,
float threshold) const;
};
}

View file

@ -280,6 +280,18 @@ bool ViewFrustum::boxIntersectsFrustum(const AABox& box) const {
return true;
}
bool ViewFrustum::boxInsideFrustum(const AABox& box) const {
// only check against frustum
for (int i = 0; i < NUM_FRUSTUM_PLANES; i++) {
const glm::vec3& normal = _planes[i].getNormal();
// check distance to nearest box point
if (_planes[i].distance(box.getNearestVertex(normal)) < 0.0f) {
return false;
}
}
return true;
}
bool ViewFrustum::sphereIntersectsKeyhole(const glm::vec3& center, float radius) const {
// check positive touch against central sphere
if (glm::length(center - _position) <= (radius + _centerSphereRadius)) {
@ -847,3 +859,7 @@ void ViewFrustum::tesselateSides(const glm::vec3 points[8], Triangle triangles[8
triangle.v2 = points[vertexIndices[2]];
}
}
bool ViewFrustum::isPerspective() const {
return _projection[3][2] != 0.0f && _projection[2][3] != 0.0f && _projection[3][3] == 0.0f;
}

View file

@ -49,6 +49,7 @@ public:
// setters for lens attributes
void setProjection(const glm::mat4 & projection);
void setFocalLength(float focalLength) { _focalLength = focalLength; }
bool isPerspective() const;
// getters for lens attributes
const glm::mat4& getProjection() const { return _projection; }
@ -103,6 +104,7 @@ public:
bool sphereIntersectsFrustum(const glm::vec3& center, float radius) const;
bool cubeIntersectsFrustum(const AACube& box) const;
bool boxIntersectsFrustum(const AABox& box) const;
bool boxInsideFrustum(const AABox& box) const;
bool sphereIntersectsKeyhole(const glm::vec3& center, float radius) const;
bool cubeIntersectsKeyhole(const AACube& cube) const;

View file

@ -14,7 +14,7 @@ var qml = Script.resolvePath('shadow.qml');
var window = new OverlayWindow({
title: 'Shadow Debug',
source: qml,
width: 200,
height: 90
width: 250,
height: 300
});
window.closed.connect(function() { Script.stop(); });

View file

@ -10,11 +10,15 @@
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
import "configSlider"
Column {
id: root
spacing: 8
property var viewConfig: Render.getConfig("RenderMainView.DrawViewFrustum");
property var shadowConfig : Render.getConfig("RenderMainView.ShadowSetup");
property var shadow0Config: Render.getConfig("RenderMainView.DrawShadowFrustum0");
property var shadow1Config: Render.getConfig("RenderMainView.DrawShadowFrustum1");
property var shadow2Config: Render.getConfig("RenderMainView.DrawShadowFrustum2");
@ -33,6 +37,14 @@ Column {
shadow1Config.enabled = false;
shadow2Config.enabled = false;
shadow3Config.enabled = false;
shadow0Config.isFrozen = false;
shadow1Config.isFrozen = false;
shadow2Config.isFrozen = false;
shadow3Config.isFrozen = false;
shadow0BoundConfig.isFrozen = false;
shadow1BoundConfig.isFrozen = false;
shadow2BoundConfig.isFrozen = false;
shadow3BoundConfig.isFrozen = false;
}
CheckBox {
@ -68,4 +80,69 @@ Column {
font.italic: true
}
}
ConfigSlider {
label: qsTr("Cascade 0 constant bias")
integral: false
config: shadowConfig
property: "constantBias0"
max: 1.0
min: 0.0
}
ConfigSlider {
label: qsTr("Cascade 1 constant bias")
integral: false
config: shadowConfig
property: "constantBias1"
max: 1.0
min: 0.0
}
ConfigSlider {
label: qsTr("Cascade 2 constant bias")
integral: false
config: shadowConfig
property: "constantBias2"
max: 1.0
min: 0.0
}
ConfigSlider {
label: qsTr("Cascade 3 constant bias")
integral: false
config: shadowConfig
property: "constantBias3"
max: 1.0
min: 0.0
}
ConfigSlider {
label: qsTr("Cascade 0 slope bias")
integral: false
config: shadowConfig
property: "slopeBias0"
max: 1.0
min: 0.0
}
ConfigSlider {
label: qsTr("Cascade 1 slope bias")
integral: false
config: shadowConfig
property: "slopeBias1"
max: 1.0
min: 0.0
}
ConfigSlider {
label: qsTr("Cascade 2 slope bias")
integral: false
config: shadowConfig
property: "slopeBias2"
max: 1.0
min: 0.0
}
ConfigSlider {
label: qsTr("Cascade 3 slope bias")
integral: false
config: shadowConfig
property: "slopeBias3"
max: 1.0
min: 0.0
}
}