mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 18:26:26 +02:00
Merge pull request #13800 from samcake/zvork-shadow
Fixing PR13731 shader error on Mac
This commit is contained in:
commit
61f0e9323a
19 changed files with 704 additions and 398 deletions
|
@ -205,7 +205,9 @@ void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inp
|
||||||
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", 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>());
|
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||||
if (isDeferred) {
|
if (isDeferred) {
|
||||||
task.addJob<RenderDeferredTask>("RenderDeferredTask", items, false);
|
const render::Varying cascadeSceneBBoxes;
|
||||||
|
const auto renderInput = RenderDeferredTask::Input(items, cascadeSceneBBoxes).asVarying();
|
||||||
|
task.addJob<RenderDeferredTask>("RenderDeferredTask", renderInput, false);
|
||||||
} else {
|
} else {
|
||||||
task.addJob<RenderForwardTask>("Forward", items);
|
task.addJob<RenderForwardTask>("Forward", items);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ namespace render {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GameWorkloadRenderItem::GameWorkloadRenderItem() : _key(render::ItemKey::Builder::opaqueShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1)) {
|
GameWorkloadRenderItem::GameWorkloadRenderItem() : _key(render::ItemKey::Builder::opaqueShape().withoutShadowCaster().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
render::ItemKey GameWorkloadRenderItem::getKey() const {
|
render::ItemKey GameWorkloadRenderItem::getKey() const {
|
||||||
|
|
|
@ -98,7 +98,9 @@ const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, cons
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, bool renderShadows) {
|
void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, bool renderShadows) {
|
||||||
const auto& items = input.get<Input>();
|
const auto& inputs = input.get<Input>();
|
||||||
|
const auto& items = inputs.get0();
|
||||||
|
|
||||||
auto fadeEffect = DependencyManager::get<FadeEffect>();
|
auto fadeEffect = DependencyManager::get<FadeEffect>();
|
||||||
|
|
||||||
// Prepare the ShapePipelines
|
// Prepare the ShapePipelines
|
||||||
|
@ -226,6 +228,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
|
||||||
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
|
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
|
||||||
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
|
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
|
||||||
|
|
||||||
|
// We don't want the overlay to clear the deferred frame buffer depth because we would like to keep it for debugging visualisation
|
||||||
|
// task.addJob<SetSeparateDeferredDepthBuffer>("SeparateDepthForOverlay", deferredFramebuffer);
|
||||||
|
|
||||||
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, jitter).asVarying();
|
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, jitter).asVarying();
|
||||||
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, jitter).asVarying();
|
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, jitter).asVarying();
|
||||||
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
|
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
|
||||||
|
@ -256,13 +261,19 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
|
||||||
task.addJob<DrawBounds>("DrawZones", zones);
|
task.addJob<DrawBounds>("DrawZones", zones);
|
||||||
const auto frustums = task.addJob<ExtractFrustums>("ExtractFrustums");
|
const auto frustums = task.addJob<ExtractFrustums>("ExtractFrustums");
|
||||||
const auto viewFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::VIEW_FRUSTUM);
|
const auto viewFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::VIEW_FRUSTUM);
|
||||||
task.addJob<DrawFrustum>("DrawViewFrustum", viewFrustum, glm::vec3(1.0f, 1.0f, 0.0f));
|
task.addJob<DrawFrustum>("DrawViewFrustum", viewFrustum, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
for (auto i = 0; i < ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT; i++) {
|
for (auto i = 0; i < ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT; i++) {
|
||||||
const auto shadowFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::SHADOW_CASCADE0_FRUSTUM+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);
|
float tint = 1.0f - i / float(ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT - 1);
|
||||||
char jobName[64];
|
char jobName[64];
|
||||||
sprintf(jobName, "DrawShadowFrustum%d", i);
|
sprintf(jobName, "DrawShadowFrustum%d", i);
|
||||||
task.addJob<DrawFrustum>(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f));
|
task.addJob<DrawFrustum>(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f));
|
||||||
|
if (!inputs[1].isNull()) {
|
||||||
|
const auto& shadowCascadeSceneBBoxes = inputs.get1();
|
||||||
|
const auto shadowBBox = shadowCascadeSceneBBoxes[ExtractFrustums::SHADOW_CASCADE0_FRUSTUM + i];
|
||||||
|
sprintf(jobName, "DrawShadowBBox%d", i);
|
||||||
|
task.addJob<DrawAABox>(jobName, shadowBBox, glm::vec3(1.0f, tint, 0.0f));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true
|
// Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true
|
||||||
|
@ -449,3 +460,26 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const
|
||||||
|
|
||||||
config->setNumDrawn((int)inItems.size());
|
config->setNumDrawn((int)inItems.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetSeparateDeferredDepthBuffer::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) {
|
||||||
|
assert(renderContext->args);
|
||||||
|
|
||||||
|
const auto deferredFramebuffer = inputs->getDeferredFramebuffer();
|
||||||
|
const auto frameSize = deferredFramebuffer->getSize();
|
||||||
|
const auto renderbufferCount = deferredFramebuffer->getNumRenderBuffers();
|
||||||
|
|
||||||
|
if (!_framebuffer || _framebuffer->getSize() != frameSize || _framebuffer->getNumRenderBuffers() != renderbufferCount) {
|
||||||
|
auto depthFormat = deferredFramebuffer->getDepthStencilBufferFormat();
|
||||||
|
auto depthStencilTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y));
|
||||||
|
_framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("deferredFramebufferSeparateDepth"));
|
||||||
|
_framebuffer->setDepthStencilBuffer(depthStencilTexture, depthFormat);
|
||||||
|
for (decltype(deferredFramebuffer->getNumRenderBuffers()) i = 0; i < renderbufferCount; i++) {
|
||||||
|
_framebuffer->setRenderBuffer(i, deferredFramebuffer->getRenderBuffer(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderArgs* args = renderContext->args;
|
||||||
|
gpu::doInBatch("SetSeparateDeferredDepthBuffer::run", args->_context, [this](gpu::Batch& batch) {
|
||||||
|
batch.setFramebuffer(_framebuffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <render/RenderFetchCullSortTask.h>
|
#include <render/RenderFetchCullSortTask.h>
|
||||||
#include "LightingModel.h"
|
#include "LightingModel.h"
|
||||||
#include "LightClusters.h"
|
#include "LightClusters.h"
|
||||||
|
#include "RenderShadowTask.h"
|
||||||
|
|
||||||
class DrawDeferredConfig : public render::Job::Config {
|
class DrawDeferredConfig : public render::Job::Config {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -87,7 +88,8 @@ public:
|
||||||
using JobModel = render::Job::ModelI<DrawStateSortDeferred, Inputs, Config>;
|
using JobModel = render::Job::ModelI<DrawStateSortDeferred, Inputs, Config>;
|
||||||
|
|
||||||
DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber)
|
DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber)
|
||||||
: _shapePlumber{ shapePlumber } {}
|
: _shapePlumber{ shapePlumber } {
|
||||||
|
}
|
||||||
|
|
||||||
void configure(const Config& config) {
|
void configure(const Config& config) {
|
||||||
_maxDrawn = config.maxDrawn;
|
_maxDrawn = config.maxDrawn;
|
||||||
|
@ -101,6 +103,19 @@ protected:
|
||||||
bool _stateSort;
|
bool _stateSort;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SetSeparateDeferredDepthBuffer {
|
||||||
|
public:
|
||||||
|
using Inputs = DeferredFramebufferPointer;
|
||||||
|
using JobModel = render::Job::ModelI<SetSeparateDeferredDepthBuffer, Inputs>;
|
||||||
|
|
||||||
|
SetSeparateDeferredDepthBuffer() = default;
|
||||||
|
|
||||||
|
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
gpu::FramebufferPointer _framebuffer;
|
||||||
|
};
|
||||||
|
|
||||||
class RenderDeferredTaskConfig : public render::Task::Config {
|
class RenderDeferredTaskConfig : public render::Task::Config {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty)
|
Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty)
|
||||||
|
@ -121,7 +136,7 @@ signals:
|
||||||
|
|
||||||
class RenderDeferredTask {
|
class RenderDeferredTask {
|
||||||
public:
|
public:
|
||||||
using Input = RenderFetchCullSortTask::Output;
|
using Input = render::VaryingSet2<RenderFetchCullSortTask::Output, RenderShadowTask::Output>;
|
||||||
using Config = RenderDeferredTaskConfig;
|
using Config = RenderDeferredTaskConfig;
|
||||||
using JobModel = render::Task::ModelI<RenderDeferredTask, Input, Config>;
|
using JobModel = render::Task::ModelI<RenderDeferredTask, Input, Config>;
|
||||||
|
|
||||||
|
|
|
@ -89,9 +89,10 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend
|
||||||
const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, render::hifi::LAYER_3D_FRONT);
|
const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, render::hifi::LAYER_3D_FRONT);
|
||||||
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
|
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
|
||||||
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
|
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
|
||||||
|
const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f));
|
||||||
|
|
||||||
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, nullptr).asVarying();
|
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, nullJitter).asVarying();
|
||||||
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, nullptr).asVarying();
|
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, nullJitter).asVarying();
|
||||||
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
|
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
|
||||||
task.addJob<DrawOverlay3D>("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false);
|
task.addJob<DrawOverlay3D>("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false);
|
||||||
|
|
||||||
|
|
|
@ -33,182 +33,10 @@ using namespace render;
|
||||||
|
|
||||||
extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state);
|
extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state);
|
||||||
|
|
||||||
static void computeNearFar(const Triangle& triangle, const Plane shadowClipPlanes[4], float& near, float& far) {
|
void RenderShadowTask::configure(const Config& configuration) {
|
||||||
static const int MAX_TRIANGLE_COUNT = 16;
|
DependencyManager::get<DeferredLightingEffect>()->setShadowMapEnabled(configuration.enabled);
|
||||||
Triangle clippedTriangles[MAX_TRIANGLE_COUNT];
|
// This is a task, so must still propogate configure() to its Jobs
|
||||||
auto clippedTriangleCount = clipTriangleWithPlanes(triangle, shadowClipPlanes, 4, clippedTriangles, MAX_TRIANGLE_COUNT);
|
// Task::configure(configuration);
|
||||||
|
|
||||||
for (auto i = 0; i < clippedTriangleCount; i++) {
|
|
||||||
const auto& clippedTriangle = clippedTriangles[i];
|
|
||||||
|
|
||||||
near = glm::min(near, -clippedTriangle.v0.z);
|
|
||||||
near = glm::min(near, -clippedTriangle.v1.z);
|
|
||||||
near = glm::min(near, -clippedTriangle.v2.z);
|
|
||||||
|
|
||||||
far = glm::max(far, -clippedTriangle.v0.z);
|
|
||||||
far = glm::max(far, -clippedTriangle.v1.z);
|
|
||||||
far = glm::max(far, -clippedTriangle.v2.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void computeNearFar(const glm::vec3 sceneBoundVertices[8], const Plane shadowClipPlanes[4], float& near, float& far) {
|
|
||||||
// This code is inspired from Microsoft's CascadedShadowMaps11 sample which is under MIT licence.
|
|
||||||
// See https://code.msdn.microsoft.com/windowsdesktop/Direct3D-Shadow-Win32-2d72a4f2/sourcecode?fileId=121915&pathId=1645833187
|
|
||||||
// Basically it decomposes the object bounding box in triangles and clips each triangle with the shadow
|
|
||||||
// frustum planes. Finally it computes the minimum and maximum depth of the clipped triangle vertices
|
|
||||||
// in shadow space to extract the near and far distances of the shadow frustum.
|
|
||||||
static const std::array<int[4], 6> boxQuadVertexIndices = { {
|
|
||||||
{ TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR },
|
|
||||||
{ TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR },
|
|
||||||
{ TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR },
|
|
||||||
{ TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR },
|
|
||||||
{ BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR },
|
|
||||||
{ TOP_LEFT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }
|
|
||||||
} };
|
|
||||||
Triangle triangle;
|
|
||||||
|
|
||||||
for (auto quadVertexIndices : boxQuadVertexIndices) {
|
|
||||||
triangle.v0 = sceneBoundVertices[quadVertexIndices[0]];
|
|
||||||
triangle.v1 = sceneBoundVertices[quadVertexIndices[1]];
|
|
||||||
triangle.v2 = sceneBoundVertices[quadVertexIndices[2]];
|
|
||||||
computeNearFar(triangle, shadowClipPlanes, near, far);
|
|
||||||
triangle.v1 = sceneBoundVertices[quadVertexIndices[3]];
|
|
||||||
computeNearFar(triangle, shadowClipPlanes, near, far);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void adjustNearFar(const AABox& inShapeBounds, ViewFrustum& shadowFrustum) {
|
|
||||||
const Transform shadowView{ shadowFrustum.getView() };
|
|
||||||
const Transform shadowViewInverse{ shadowView.getInverseMatrix() };
|
|
||||||
|
|
||||||
glm::vec3 sceneBoundVertices[8];
|
|
||||||
// Keep only the left, right, top and bottom shadow frustum planes as we wish to determine
|
|
||||||
// the near and far
|
|
||||||
Plane shadowClipPlanes[4];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// The vertices of the scene bounding box are expressed in the shadow frustum's local space
|
|
||||||
for (i = 0; i < 8; i++) {
|
|
||||||
sceneBoundVertices[i] = shadowViewInverse.transform(inShapeBounds.getVertex(static_cast<BoxVertex>(i)));
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
far = glm::min(far, shadowFrustum.getFarClip());
|
|
||||||
|
|
||||||
const auto depthEpsilon = 0.1f;
|
|
||||||
auto projMatrix = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, near - depthEpsilon, far + depthEpsilon);
|
|
||||||
auto shadowProjection = shadowFrustum.getProjection();
|
|
||||||
|
|
||||||
shadowProjection[2][2] = projMatrix[2][2];
|
|
||||||
shadowProjection[3][2] = projMatrix[3][2];
|
|
||||||
shadowFrustum.setProjection(shadowProjection);
|
|
||||||
shadowFrustum.calculate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderShadowMap::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) {
|
|
||||||
assert(renderContext->args);
|
|
||||||
assert(renderContext->args->hasViewFrustum());
|
|
||||||
|
|
||||||
const auto& inShapes = inputs.get0();
|
|
||||||
const auto& inShapeBounds = inputs.get1();
|
|
||||||
|
|
||||||
auto lightStage = renderContext->_scene->getStage<LightStage>();
|
|
||||||
assert(lightStage);
|
|
||||||
|
|
||||||
auto shadow = lightStage->getCurrentKeyShadow();
|
|
||||||
if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& cascade = shadow->getCascade(_cascadeIndex);
|
|
||||||
auto& fbo = cascade.framebuffer;
|
|
||||||
|
|
||||||
RenderArgs* args = renderContext->args;
|
|
||||||
ShapeKey::Builder defaultKeyBuilder;
|
|
||||||
auto adjustedShadowFrustum = args->getViewFrustum();
|
|
||||||
|
|
||||||
// Adjust the frustum near and far depths based on the rendered items bounding box to have
|
|
||||||
// the minimal Z range.
|
|
||||||
adjustNearFar(inShapeBounds, adjustedShadowFrustum);
|
|
||||||
// Reapply the frustum as it has been adjusted
|
|
||||||
shadow->setCascadeFrustum(_cascadeIndex, adjustedShadowFrustum);
|
|
||||||
args->popViewFrustum();
|
|
||||||
args->pushViewFrustum(adjustedShadowFrustum);
|
|
||||||
|
|
||||||
gpu::doInBatch("RenderShadowMap::run", args->_context, [&](gpu::Batch& batch) {
|
|
||||||
args->_batch = &batch;
|
|
||||||
batch.enableStereo(false);
|
|
||||||
|
|
||||||
glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()};
|
|
||||||
batch.setViewportTransform(viewport);
|
|
||||||
batch.setStateScissorRect(viewport);
|
|
||||||
|
|
||||||
batch.setFramebuffer(fbo);
|
|
||||||
batch.clearDepthFramebuffer(1.0, false);
|
|
||||||
|
|
||||||
glm::mat4 projMat;
|
|
||||||
Transform viewMat;
|
|
||||||
args->getViewFrustum().evalProjectionMatrix(projMat);
|
|
||||||
args->getViewFrustum().evalViewTransform(viewMat);
|
|
||||||
|
|
||||||
batch.setProjectionTransform(projMat);
|
|
||||||
batch.setViewTransform(viewMat, false);
|
|
||||||
|
|
||||||
auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder);
|
|
||||||
auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
|
|
||||||
auto shadowSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned());
|
|
||||||
|
|
||||||
std::vector<ShapeKey> skinnedShapeKeys{};
|
|
||||||
std::vector<ShapeKey> skinnedDQShapeKeys{};
|
|
||||||
std::vector<ShapeKey> ownPipelineShapeKeys{};
|
|
||||||
|
|
||||||
// Iterate through all inShapes and render the unskinned
|
|
||||||
args->_shapePipeline = shadowPipeline;
|
|
||||||
batch.setPipeline(shadowPipeline->pipeline);
|
|
||||||
for (auto items : inShapes) {
|
|
||||||
if (items.first.isSkinned()) {
|
|
||||||
if (items.first.isDualQuatSkinned()) {
|
|
||||||
skinnedDQShapeKeys.push_back(items.first);
|
|
||||||
} else {
|
|
||||||
skinnedShapeKeys.push_back(items.first);
|
|
||||||
}
|
|
||||||
} else if (!items.first.hasOwnPipeline()) {
|
|
||||||
renderItems(renderContext, items.second);
|
|
||||||
} else {
|
|
||||||
ownPipelineShapeKeys.push_back(items.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reiterate to render the skinned
|
|
||||||
args->_shapePipeline = shadowSkinnedPipeline;
|
|
||||||
batch.setPipeline(shadowSkinnedPipeline->pipeline);
|
|
||||||
for (const auto& key : skinnedShapeKeys) {
|
|
||||||
renderItems(renderContext, inShapes.at(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reiterate to render the DQ skinned
|
|
||||||
args->_shapePipeline = shadowSkinnedDQPipeline;
|
|
||||||
batch.setPipeline(shadowSkinnedDQPipeline->pipeline);
|
|
||||||
for (const auto& key : skinnedDQShapeKeys) {
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cameraCullFunctor, uint8_t tagBits, uint8_t tagMask) {
|
void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cameraCullFunctor, uint8_t tagBits, uint8_t tagMask) {
|
||||||
|
@ -256,35 +84,221 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Output cascadeSceneBBoxes;
|
||||||
|
|
||||||
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
|
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
|
||||||
char jobName[64];
|
char jobName[64];
|
||||||
sprintf(jobName, "ShadowCascadeSetup%d", i);
|
sprintf(jobName, "ShadowCascadeSetup%d", i);
|
||||||
const auto cascadeSetupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, i, _cullFunctor, tagBits, tagMask);
|
const auto cascadeSetupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, i, _cullFunctor, tagBits, tagMask);
|
||||||
const auto shadowRenderFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(0);
|
const auto shadowFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(0);
|
||||||
const auto shadowBoundsFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
|
|
||||||
auto antiFrustum = render::Varying(ViewFrustumPointer());
|
auto antiFrustum = render::Varying(ViewFrustumPointer());
|
||||||
cascadeFrustums[i] = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(2);
|
cascadeFrustums[i] = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
|
||||||
if (i > 1) {
|
if (i > 1) {
|
||||||
antiFrustum = cascadeFrustums[i - 2];
|
antiFrustum = cascadeFrustums[i - 2];
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPU jobs: finer grained culling
|
// CPU jobs: finer grained culling
|
||||||
const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowRenderFilter, shadowBoundsFilter, antiFrustum).asVarying();
|
const auto cullInputs = CullShadowBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying();
|
||||||
const auto culledShadowItemsAndBounds = task.addJob<CullShapeBounds>("CullShadowCascade", cullInputs, shadowCullFunctor, RenderDetails::SHADOW);
|
sprintf(jobName, "CullShadowCascade%d", i);
|
||||||
|
const auto culledShadowItemsAndBounds = task.addJob<CullShadowBounds>(jobName, cullInputs, shadowCullFunctor);
|
||||||
|
|
||||||
// GPU jobs: Render to shadow map
|
// GPU jobs: Render to shadow map
|
||||||
sprintf(jobName, "RenderShadowMap%d", i);
|
sprintf(jobName, "RenderShadowMap%d", i);
|
||||||
task.addJob<RenderShadowMap>(jobName, culledShadowItemsAndBounds, shapePlumber, i);
|
task.addJob<RenderShadowMap>(jobName, culledShadowItemsAndBounds, shapePlumber, i);
|
||||||
task.addJob<RenderShadowCascadeTeardown>("ShadowCascadeTeardown", shadowRenderFilter);
|
sprintf(jobName, "ShadowCascadeTeardown%d", i);
|
||||||
|
task.addJob<RenderShadowCascadeTeardown>(jobName, shadowFilter);
|
||||||
|
|
||||||
|
cascadeSceneBBoxes[i] = culledShadowItemsAndBounds.getN<CullShadowBounds::Outputs>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output = render::Varying(cascadeSceneBBoxes);
|
||||||
|
|
||||||
task.addJob<RenderShadowTeardown>("ShadowTeardown", setupOutput);
|
task.addJob<RenderShadowTeardown>("ShadowTeardown", setupOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderShadowTask::configure(const Config& configuration) {
|
static void computeNearFar(const Triangle& triangle, const Plane shadowClipPlanes[4], float& near, float& far) {
|
||||||
DependencyManager::get<DeferredLightingEffect>()->setShadowMapEnabled(configuration.enabled);
|
static const int MAX_TRIANGLE_COUNT = 16;
|
||||||
// This is a task, so must still propogate configure() to its Jobs
|
Triangle clippedTriangles[MAX_TRIANGLE_COUNT];
|
||||||
// Task::configure(configuration);
|
auto clippedTriangleCount = clipTriangleWithPlanes(triangle, shadowClipPlanes, 4, clippedTriangles, MAX_TRIANGLE_COUNT);
|
||||||
|
|
||||||
|
for (auto i = 0; i < clippedTriangleCount; i++) {
|
||||||
|
const auto& clippedTriangle = clippedTriangles[i];
|
||||||
|
|
||||||
|
near = glm::min(near, -clippedTriangle.v0.z);
|
||||||
|
near = glm::min(near, -clippedTriangle.v1.z);
|
||||||
|
near = glm::min(near, -clippedTriangle.v2.z);
|
||||||
|
|
||||||
|
far = glm::max(far, -clippedTriangle.v0.z);
|
||||||
|
far = glm::max(far, -clippedTriangle.v1.z);
|
||||||
|
far = glm::max(far, -clippedTriangle.v2.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void computeNearFar(const glm::vec3 sceneBoundVertices[8], const Plane shadowClipPlanes[4], float& near, float& far) {
|
||||||
|
// This code is inspired from Microsoft's CascadedShadowMaps11 sample which is under MIT licence.
|
||||||
|
// See https://code.msdn.microsoft.com/windowsdesktop/Direct3D-Shadow-Win32-2d72a4f2/sourcecode?fileId=121915&pathId=1645833187
|
||||||
|
// Basically it decomposes the object bounding box in triangles and clips each triangle with the shadow
|
||||||
|
// frustum planes. Finally it computes the minimum and maximum depth of the clipped triangle vertices
|
||||||
|
// in shadow space to extract the near and far distances of the shadow frustum.
|
||||||
|
static const std::array<int[4], 6> boxQuadVertexIndices = { {
|
||||||
|
{ TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR },
|
||||||
|
{ TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR },
|
||||||
|
{ TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR },
|
||||||
|
{ TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR },
|
||||||
|
{ BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR },
|
||||||
|
{ TOP_LEFT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }
|
||||||
|
} };
|
||||||
|
Triangle triangle;
|
||||||
|
|
||||||
|
for (auto quadVertexIndices : boxQuadVertexIndices) {
|
||||||
|
triangle.v0 = sceneBoundVertices[quadVertexIndices[0]];
|
||||||
|
triangle.v1 = sceneBoundVertices[quadVertexIndices[1]];
|
||||||
|
triangle.v2 = sceneBoundVertices[quadVertexIndices[2]];
|
||||||
|
computeNearFar(triangle, shadowClipPlanes, near, far);
|
||||||
|
triangle.v1 = sceneBoundVertices[quadVertexIndices[3]];
|
||||||
|
computeNearFar(triangle, shadowClipPlanes, near, far);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void adjustNearFar(const AABox& inShapeBounds, ViewFrustum& shadowFrustum) {
|
||||||
|
if (!inShapeBounds.isNull()) {
|
||||||
|
const Transform shadowView{ shadowFrustum.getView() };
|
||||||
|
const Transform shadowViewInverse{ shadowView.getInverseMatrix() };
|
||||||
|
|
||||||
|
glm::vec3 sceneBoundVertices[8];
|
||||||
|
// Keep only the left, right, top and bottom shadow frustum planes as we wish to determine
|
||||||
|
// the near and far
|
||||||
|
Plane shadowClipPlanes[4];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// The vertices of the scene bounding box are expressed in the shadow frustum's local space
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
sceneBoundVertices[i] = shadowViewInverse.transform(inShapeBounds.getVertex(static_cast<BoxVertex>(i)));
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
far = glm::min(far, shadowFrustum.getFarClip());
|
||||||
|
if (near > far) {
|
||||||
|
near = far;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto depthEpsilon = 0.1f;
|
||||||
|
auto projMatrix = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, near - depthEpsilon, far + depthEpsilon);
|
||||||
|
auto shadowProjection = shadowFrustum.getProjection();
|
||||||
|
|
||||||
|
shadowProjection[2][2] = projMatrix[2][2];
|
||||||
|
shadowProjection[3][2] = projMatrix[3][2];
|
||||||
|
shadowFrustum.setProjection(shadowProjection);
|
||||||
|
shadowFrustum.calculate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderShadowMap::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) {
|
||||||
|
assert(renderContext->args);
|
||||||
|
assert(renderContext->args->hasViewFrustum());
|
||||||
|
|
||||||
|
const auto& inShapes = inputs.get0();
|
||||||
|
const auto& inShapeBounds = inputs.get1();
|
||||||
|
|
||||||
|
auto lightStage = renderContext->_scene->getStage<LightStage>();
|
||||||
|
assert(lightStage);
|
||||||
|
|
||||||
|
auto shadow = lightStage->getCurrentKeyShadow();
|
||||||
|
if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& cascade = shadow->getCascade(_cascadeIndex);
|
||||||
|
auto& fbo = cascade.framebuffer;
|
||||||
|
|
||||||
|
RenderArgs* args = renderContext->args;
|
||||||
|
ShapeKey::Builder defaultKeyBuilder;
|
||||||
|
auto adjustedShadowFrustum = args->getViewFrustum();
|
||||||
|
|
||||||
|
// Adjust the frustum near and far depths based on the rendered items bounding box to have
|
||||||
|
// the minimal Z range.
|
||||||
|
adjustNearFar(inShapeBounds, adjustedShadowFrustum);
|
||||||
|
// Reapply the frustum as it has been adjusted
|
||||||
|
shadow->setCascadeFrustum(_cascadeIndex, adjustedShadowFrustum);
|
||||||
|
args->popViewFrustum();
|
||||||
|
args->pushViewFrustum(adjustedShadowFrustum);
|
||||||
|
|
||||||
|
gpu::doInBatch("RenderShadowMap::run", args->_context, [&](gpu::Batch& batch) {
|
||||||
|
args->_batch = &batch;
|
||||||
|
batch.enableStereo(false);
|
||||||
|
|
||||||
|
glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()};
|
||||||
|
batch.setViewportTransform(viewport);
|
||||||
|
batch.setStateScissorRect(viewport);
|
||||||
|
|
||||||
|
batch.setFramebuffer(fbo);
|
||||||
|
batch.clearDepthFramebuffer(1.0, false);
|
||||||
|
|
||||||
|
if (!inShapeBounds.isNull()) {
|
||||||
|
glm::mat4 projMat;
|
||||||
|
Transform viewMat;
|
||||||
|
args->getViewFrustum().evalProjectionMatrix(projMat);
|
||||||
|
args->getViewFrustum().evalViewTransform(viewMat);
|
||||||
|
|
||||||
|
batch.setProjectionTransform(projMat);
|
||||||
|
batch.setViewTransform(viewMat, false);
|
||||||
|
|
||||||
|
auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder);
|
||||||
|
auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
|
||||||
|
auto shadowSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned());
|
||||||
|
|
||||||
|
std::vector<ShapeKey> skinnedShapeKeys{};
|
||||||
|
std::vector<ShapeKey> skinnedDQShapeKeys{};
|
||||||
|
std::vector<ShapeKey> ownPipelineShapeKeys{};
|
||||||
|
|
||||||
|
// Iterate through all inShapes and render the unskinned
|
||||||
|
args->_shapePipeline = shadowPipeline;
|
||||||
|
batch.setPipeline(shadowPipeline->pipeline);
|
||||||
|
for (auto items : inShapes) {
|
||||||
|
if (items.first.isSkinned()) {
|
||||||
|
if (items.first.isDualQuatSkinned()) {
|
||||||
|
skinnedDQShapeKeys.push_back(items.first);
|
||||||
|
} else {
|
||||||
|
skinnedShapeKeys.push_back(items.first);
|
||||||
|
}
|
||||||
|
} else if (!items.first.hasOwnPipeline()) {
|
||||||
|
renderItems(renderContext, items.second);
|
||||||
|
} else {
|
||||||
|
ownPipelineShapeKeys.push_back(items.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reiterate to render the skinned
|
||||||
|
args->_shapePipeline = shadowSkinnedPipeline;
|
||||||
|
batch.setPipeline(shadowSkinnedPipeline->pipeline);
|
||||||
|
for (const auto& key : skinnedShapeKeys) {
|
||||||
|
renderItems(renderContext, inShapes.at(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reiterate to render the DQ skinned
|
||||||
|
args->_shapePipeline = shadowSkinnedDQPipeline;
|
||||||
|
batch.setPipeline(shadowSkinnedDQPipeline->pipeline);
|
||||||
|
for (const auto& key : skinnedDQShapeKeys) {
|
||||||
|
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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderShadowSetup::RenderShadowSetup() :
|
RenderShadowSetup::RenderShadowSetup() :
|
||||||
|
@ -408,11 +422,8 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon
|
||||||
|
|
||||||
const auto globalShadow = lightStage->getCurrentKeyShadow();
|
const auto globalShadow = lightStage->getCurrentKeyShadow();
|
||||||
if (globalShadow && _cascadeIndex<globalShadow->getCascadeCount()) {
|
if (globalShadow && _cascadeIndex<globalShadow->getCascadeCount()) {
|
||||||
auto baseFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask);
|
|
||||||
// Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers)
|
// Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers)
|
||||||
output.edit1() = baseFilter;
|
output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask);
|
||||||
// First item filter is to filter items to render in shadow map (so only keep casters)
|
|
||||||
output.edit0() = baseFilter.withShadowCaster();
|
|
||||||
|
|
||||||
// Set the keylight render args
|
// Set the keylight render args
|
||||||
auto& cascade = globalShadow->getCascade(_cascadeIndex);
|
auto& cascade = globalShadow->getCascade(_cascadeIndex);
|
||||||
|
@ -425,11 +436,10 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon
|
||||||
texelSize *= minTexelCount;
|
texelSize *= minTexelCount;
|
||||||
_cullFunctor._minSquareSize = texelSize * texelSize;
|
_cullFunctor._minSquareSize = texelSize * texelSize;
|
||||||
|
|
||||||
output.edit2() = cascadeFrustum;
|
output.edit1() = cascadeFrustum;
|
||||||
} else {
|
} else {
|
||||||
output.edit0() = ItemFilter::Builder::nothing();
|
output.edit0() = ItemFilter::Builder::nothing();
|
||||||
output.edit1() = ItemFilter::Builder::nothing();
|
output.edit1() = ViewFrustumPointer();
|
||||||
output.edit2() = ViewFrustumPointer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,3 +462,98 @@ void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext
|
||||||
// Reset the render args
|
// Reset the render args
|
||||||
args->_renderMode = input.get0();
|
args->_renderMode = input.get0();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static AABox& merge(AABox& box, const AABox& otherBox, const glm::vec3& dir) {
|
||||||
|
if (!otherBox.isInvalid()) {
|
||||||
|
int vertexIndex = 0;
|
||||||
|
vertexIndex |= ((dir.z > 0.0f) & 1) << 2;
|
||||||
|
vertexIndex |= ((dir.y > 0.0f) & 1) << 1;
|
||||||
|
vertexIndex |= ((dir.x < 0.0f) & 1);
|
||||||
|
auto vertex = otherBox.getVertex((BoxVertex)vertexIndex);
|
||||||
|
if (!box.isInvalid()) {
|
||||||
|
const auto boxCenter = box.calcCenter();
|
||||||
|
vertex -= boxCenter;
|
||||||
|
vertex = dir * glm::max(0.0f, glm::dot(vertex, dir));
|
||||||
|
vertex += boxCenter;
|
||||||
|
}
|
||||||
|
box += vertex;
|
||||||
|
}
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CullShadowBounds::run(const render::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();
|
||||||
|
ViewFrustumPointer antiFrustum;
|
||||||
|
auto& outShapes = outputs.edit0();
|
||||||
|
auto& outBounds = outputs.edit1();
|
||||||
|
|
||||||
|
if (!inputs[3].isNull()) {
|
||||||
|
antiFrustum = inputs.get2();
|
||||||
|
}
|
||||||
|
outShapes.clear();
|
||||||
|
outBounds = AABox();
|
||||||
|
|
||||||
|
if (!filter.selectsNothing()) {
|
||||||
|
auto& details = args->_details.edit(RenderDetails::SHADOW);
|
||||||
|
render::CullTest test(_cullFunctor, args, details, antiFrustum);
|
||||||
|
auto scene = args->_scene;
|
||||||
|
auto lightStage = renderContext->_scene->getStage<LightStage>();
|
||||||
|
assert(lightStage);
|
||||||
|
const auto globalLightDir = lightStage->getCurrentKeyLight()->getDirection();
|
||||||
|
auto castersFilter = render::ItemFilter::Builder(filter).withShadowCaster().build();
|
||||||
|
const auto& receiversFilter = filter;
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
const auto shapeKey = scene->getItem(item.id).getKey();
|
||||||
|
if (castersFilter.test(shapeKey)) {
|
||||||
|
outItems->second.emplace_back(item);
|
||||||
|
outBounds += item.bound;
|
||||||
|
} else if (receiversFilter.test(shapeKey)) {
|
||||||
|
// Receivers are not rendered but they still increase the bounds of the shadow scene
|
||||||
|
// although only in the direction of the light direction so as to have a correct far
|
||||||
|
// distance without decreasing the near distance.
|
||||||
|
merge(outBounds, item.bound, globalLightDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto& item : inItems.second) {
|
||||||
|
if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) {
|
||||||
|
const auto shapeKey = scene->getItem(item.id).getKey();
|
||||||
|
if (castersFilter.test(shapeKey)) {
|
||||||
|
outItems->second.emplace_back(item);
|
||||||
|
outBounds += item.bound;
|
||||||
|
} else if (receiversFilter.test(shapeKey)) {
|
||||||
|
// Receivers are not rendered but they still increase the bounds of the shadow scene
|
||||||
|
// although only in the direction of the light direction so as to have a correct far
|
||||||
|
// distance without decreasing the near distance.
|
||||||
|
merge(outBounds, item.bound, globalLightDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
details._rendered += (int)outItems->second.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& items : outShapes) {
|
||||||
|
items.second.shrink_to_fit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -46,8 +46,11 @@ signals:
|
||||||
|
|
||||||
class RenderShadowTask {
|
class RenderShadowTask {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
// There is one AABox per shadow cascade
|
||||||
|
using Output = render::VaryingArray<AABox, SHADOW_CASCADE_MAX_COUNT>;
|
||||||
using Config = RenderShadowTaskConfig;
|
using Config = RenderShadowTaskConfig;
|
||||||
using JobModel = render::Task::Model<RenderShadowTask, Config>;
|
using JobModel = render::Task::ModelO<RenderShadowTask, Output, Config>;
|
||||||
|
|
||||||
RenderShadowTask() {}
|
RenderShadowTask() {}
|
||||||
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cameraCullFunctor, 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);
|
||||||
|
@ -118,7 +121,7 @@ private:
|
||||||
|
|
||||||
class RenderShadowCascadeSetup {
|
class RenderShadowCascadeSetup {
|
||||||
public:
|
public:
|
||||||
using Outputs = render::VaryingSet3<render::ItemFilter, render::ItemFilter, ViewFrustumPointer>;
|
using Outputs = render::VaryingSet2<render::ItemFilter, ViewFrustumPointer>;
|
||||||
using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>;
|
using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>;
|
||||||
|
|
||||||
RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) :
|
RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) :
|
||||||
|
@ -147,4 +150,22 @@ public:
|
||||||
void run(const render::RenderContextPointer& renderContext, const Input& input);
|
void run(const render::RenderContextPointer& renderContext, const Input& input);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CullShadowBounds {
|
||||||
|
public:
|
||||||
|
using Inputs = render::VaryingSet3<render::ShapeBounds, render::ItemFilter, ViewFrustumPointer>;
|
||||||
|
using Outputs = render::VaryingSet2<render::ShapeBounds, AABox>;
|
||||||
|
using JobModel = render::Job::ModelIO<CullShadowBounds, Inputs, Outputs>;
|
||||||
|
|
||||||
|
CullShadowBounds(render::CullFunctor cullFunctor) :
|
||||||
|
_cullFunctor{ cullFunctor } {
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
render::CullFunctor _cullFunctor;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
#endif // hifi_RenderShadowTask_h
|
#endif // hifi_RenderShadowTask_h
|
||||||
|
|
|
@ -17,17 +17,15 @@
|
||||||
void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred, uint8_t tagBits, uint8_t tagMask) {
|
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>();
|
// auto items = input.get<Input>();
|
||||||
|
|
||||||
// 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.
|
|
||||||
if (isDeferred) {
|
|
||||||
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor, tagBits, tagMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, tagBits, tagMask);
|
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, tagBits, tagMask);
|
||||||
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||||
|
|
||||||
if (isDeferred) {
|
if (isDeferred) {
|
||||||
task.addJob<RenderDeferredTask>("RenderDeferredTask", items, true);
|
// 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.
|
||||||
|
const auto cascadeSceneBBoxes = task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor, tagBits, tagMask);
|
||||||
|
const auto renderInput = RenderDeferredTask::Input(items, cascadeSceneBBoxes).asVarying();
|
||||||
|
task.addJob<RenderDeferredTask>("RenderDeferredTask", renderInput, true);
|
||||||
} else {
|
} else {
|
||||||
task.addJob<RenderForwardTask>("Forward", items);
|
task.addJob<RenderForwardTask>("Forward", items);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
<@include render-utils/ShaderConstants.h@>
|
<@include render-utils/ShaderConstants.h@>
|
||||||
<@include ShadowCore.slh@>
|
<@include ShadowCore.slh@>
|
||||||
|
|
||||||
|
#define SHADOW_DITHER 1
|
||||||
#define SHADOW_NOISE_ENABLED 0
|
#define SHADOW_NOISE_ENABLED 0
|
||||||
#define SHADOW_SCREEN_SPACE_DITHER 1
|
#define SHADOW_SCREEN_SPACE_DITHER 1
|
||||||
|
|
||||||
|
@ -32,10 +33,12 @@ vec2 PCFkernel[4] = vec2[4](
|
||||||
vec2(0.5, -1.5)
|
vec2(0.5, -1.5)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#if SHADOW_NOISE_ENABLED
|
||||||
float evalShadowNoise(vec4 seed) {
|
float evalShadowNoise(vec4 seed) {
|
||||||
float dot_product = dot(seed, vec4(12.9898,78.233,45.164,94.673));
|
float dot_product = dot(seed, vec4(12.9898,78.233,45.164,94.673));
|
||||||
return fract(sin(dot_product) * 43758.5453);
|
return fract(sin(dot_product) * 43758.5453);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
struct ShadowSampleOffsets {
|
struct ShadowSampleOffsets {
|
||||||
vec3 points[4];
|
vec3 points[4];
|
||||||
|
@ -74,12 +77,16 @@ ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) {
|
||||||
|
|
||||||
float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) {
|
float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) {
|
||||||
shadowTexcoord.z -= bias;
|
shadowTexcoord.z -= bias;
|
||||||
|
#if SHADOW_DITHER
|
||||||
float shadowAttenuation = 0.25 * (
|
float shadowAttenuation = 0.25 * (
|
||||||
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) +
|
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) +
|
||||||
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) +
|
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) +
|
||||||
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) +
|
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) +
|
||||||
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3])
|
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3])
|
||||||
);
|
);
|
||||||
|
#else
|
||||||
|
float shadowAttenuation = fetchShadow(cascadeIndex, shadowTexcoord.xyz);
|
||||||
|
#endif
|
||||||
return shadowAttenuation;
|
return shadowAttenuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,20 +97,55 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets
|
||||||
|
|
||||||
float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) {
|
float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) {
|
||||||
ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
|
ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
|
||||||
vec4 cascadeShadowCoords[2];
|
|
||||||
cascadeShadowCoords[0] = vec4(0);
|
vec4 cascadeShadowCoords[4];
|
||||||
cascadeShadowCoords[1] = vec4(0);
|
vec4 cascadeWeights;
|
||||||
ivec2 cascadeIndices;
|
vec4 cascadeAttenuations = vec4(1.0);
|
||||||
float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
|
vec3 cascadeMix;
|
||||||
|
bvec4 isPixelOnCascade;
|
||||||
// Adjust bias if we are at a grazing angle with light
|
int cascadeIndex;
|
||||||
float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1);
|
float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1);
|
||||||
vec2 cascadeAttenuations = vec2(1.0, 1.0);
|
|
||||||
cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], oneMinusNdotL);
|
for (cascadeIndex=0 ; cascadeIndex<getShadowCascadeCount() ; cascadeIndex++) {
|
||||||
if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) {
|
cascadeShadowCoords[cascadeIndex] = evalShadowTexcoord(cascadeIndex, worldPosition);
|
||||||
cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], oneMinusNdotL);
|
|
||||||
}
|
}
|
||||||
float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix);
|
|
||||||
|
isPixelOnCascade.x = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[0]);
|
||||||
|
isPixelOnCascade.y = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[1]);
|
||||||
|
isPixelOnCascade.z = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[2]);
|
||||||
|
isPixelOnCascade.w = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[3]);
|
||||||
|
|
||||||
|
if (isPixelOnCascade.x) {
|
||||||
|
cascadeAttenuations.x = evalShadowCascadeAttenuation(0, offsets, cascadeShadowCoords[0], oneMinusNdotL);
|
||||||
|
}
|
||||||
|
if (isPixelOnCascade.y) {
|
||||||
|
cascadeAttenuations.y = evalShadowCascadeAttenuation(1, offsets, cascadeShadowCoords[1], oneMinusNdotL);
|
||||||
|
}
|
||||||
|
if (isPixelOnCascade.z) {
|
||||||
|
cascadeAttenuations.z = evalShadowCascadeAttenuation(2, offsets, cascadeShadowCoords[2], oneMinusNdotL);
|
||||||
|
}
|
||||||
|
if (isPixelOnCascade.w) {
|
||||||
|
cascadeAttenuations.w = evalShadowCascadeAttenuation(3, offsets, cascadeShadowCoords[3], oneMinusNdotL);
|
||||||
|
}
|
||||||
|
|
||||||
|
cascadeWeights.x = evalShadowCascadeWeight(cascadeShadowCoords[0]);
|
||||||
|
cascadeWeights.y = evalShadowCascadeWeight(cascadeShadowCoords[1]);
|
||||||
|
cascadeWeights.z = evalShadowCascadeWeight(cascadeShadowCoords[2]);
|
||||||
|
cascadeWeights.w = evalShadowCascadeWeight(cascadeShadowCoords[3]);
|
||||||
|
cascadeWeights = mix(vec4(0.0), cascadeWeights, isPixelOnCascade);
|
||||||
|
|
||||||
|
cascadeMix.x = evalCascadeMix(cascadeWeights.x, cascadeWeights.y);
|
||||||
|
cascadeMix.y = evalCascadeMix(cascadeWeights.y, cascadeWeights.z);
|
||||||
|
cascadeMix.z = evalCascadeMix(cascadeWeights.z, cascadeWeights.w);
|
||||||
|
|
||||||
|
vec3 attenuations = mix(cascadeAttenuations.xyz, cascadeAttenuations.yzw, cascadeMix.xyz);
|
||||||
|
|
||||||
|
attenuations.x = mix(1.0, attenuations.x, isPixelOnCascade.x);
|
||||||
|
attenuations.y = mix(1.0, attenuations.y, !isPixelOnCascade.x && isPixelOnCascade.y);
|
||||||
|
attenuations.z = mix(1.0, attenuations.z, !any(isPixelOnCascade.xy) && any(isPixelOnCascade.zw));
|
||||||
|
|
||||||
|
float attenuation = min(attenuations.x, min(attenuations.y, attenuations.z));
|
||||||
|
|
||||||
// Falloff to max distance
|
// Falloff to max distance
|
||||||
return mix(1.0, attenuation, evalShadowFalloff(viewDepth));
|
return mix(1.0, attenuation, evalShadowFalloff(viewDepth));
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,10 @@ float evalShadowCascadeWeight(vec4 cascadeTexCoords) {
|
||||||
return clamp(blend * getShadowCascadeInvBlendWidth(), 0.0, 1.0);
|
return clamp(blend * getShadowCascadeInvBlendWidth(), 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float evalCascadeMix(float firstCascadeWeight, float secondCascadeWeight) {
|
||||||
|
return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight);
|
||||||
|
}
|
||||||
|
|
||||||
float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out vec4 cascadeShadowCoords[2], out ivec2 cascadeIndices) {
|
float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out vec4 cascadeShadowCoords[2], out ivec2 cascadeIndices) {
|
||||||
cascadeIndices.x = getFirstShadowCascadeOnPixel(0, worldPosition, cascadeShadowCoords[0]);
|
cascadeIndices.x = getFirstShadowCascadeOnPixel(0, worldPosition, cascadeShadowCoords[0]);
|
||||||
cascadeIndices.y = cascadeIndices.x+1;
|
cascadeIndices.y = cascadeIndices.x+1;
|
||||||
|
@ -88,7 +92,7 @@ float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out ve
|
||||||
|
|
||||||
float secondCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[1]);
|
float secondCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[1]);
|
||||||
// Returns the mix amount between first and second cascade.
|
// Returns the mix amount between first and second cascade.
|
||||||
return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight);
|
return evalCascadeMix(firstCascadeWeight, secondCascadeWeight);
|
||||||
} else {
|
} else {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,60 +19,50 @@
|
||||||
|
|
||||||
using namespace render;
|
using namespace render;
|
||||||
|
|
||||||
// Culling Frustum / solidAngle test helper class
|
CullTest::CullTest(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum) :
|
||||||
struct Test {
|
_functor(functor),
|
||||||
CullFunctor _functor;
|
_args(pargs),
|
||||||
RenderArgs* _args;
|
_renderDetails(renderDetails),
|
||||||
RenderDetails::Item& _renderDetails;
|
_antiFrustum(antiFrustum) {
|
||||||
ViewFrustumPointer _antiFrustum;
|
// FIXME: Keep this code here even though we don't use it yet
|
||||||
glm::vec3 _eyePos;
|
/*_eyePos = _args->getViewFrustum().getPosition();
|
||||||
float _squareTanAlpha;
|
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);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum = nullptr) :
|
bool CullTest::frustumTest(const AABox& bound) {
|
||||||
_functor(functor),
|
if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) {
|
||||||
_args(pargs),
|
_renderDetails._outOfView++;
|
||||||
_renderDetails(renderDetails),
|
return false;
|
||||||
_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);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool frustumTest(const AABox& bound) {
|
bool CullTest::antiFrustumTest(const AABox& bound) {
|
||||||
if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) {
|
assert(_antiFrustum);
|
||||||
_renderDetails._outOfView++;
|
if (_antiFrustum->boxInsideFrustum(bound)) {
|
||||||
return false;
|
_renderDetails._outOfView++;
|
||||||
}
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool antiFrustumTest(const AABox& bound) {
|
bool CullTest::solidAngleTest(const AABox& bound) {
|
||||||
assert(_antiFrustum);
|
// FIXME: Keep this code here even though we don't use it yet
|
||||||
if (_antiFrustum->boxInsideFrustum(bound)) {
|
//auto eyeToPoint = bound.calcCenter() - _eyePos;
|
||||||
_renderDetails._outOfView++;
|
//auto boundSize = bound.getDimensions();
|
||||||
return false;
|
//float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
|
||||||
}
|
//if (test < 0.0f) {
|
||||||
return true;
|
if (!_functor(_args, bound)) {
|
||||||
|
_renderDetails._tooSmall++;
|
||||||
|
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,
|
void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
|
||||||
const ItemBounds& inItems, ItemBounds& outItems) {
|
const ItemBounds& inItems, ItemBounds& outItems) {
|
||||||
|
@ -205,7 +195,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
args->pushViewFrustum(_frozenFrustum); // replace the true view frustum by the frozen one
|
args->pushViewFrustum(_frozenFrustum); // replace the true view frustum by the frozen one
|
||||||
}
|
}
|
||||||
|
|
||||||
Test test(_cullFunctor, args, details);
|
CullTest test(_cullFunctor, args, details);
|
||||||
|
|
||||||
// Now we have a selection of items to render
|
// Now we have a selection of items to render
|
||||||
outItems.clear();
|
outItems.clear();
|
||||||
|
@ -382,7 +372,7 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input
|
||||||
|
|
||||||
if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) {
|
if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) {
|
||||||
auto& details = args->_details.edit(_detailType);
|
auto& details = args->_details.edit(_detailType);
|
||||||
Test test(_cullFunctor, args, details, antiFrustum);
|
CullTest test(_cullFunctor, args, details, antiFrustum);
|
||||||
auto scene = args->_scene;
|
auto scene = args->_scene;
|
||||||
|
|
||||||
for (auto& inItems : inShapes) {
|
for (auto& inItems : inShapes) {
|
||||||
|
|
|
@ -22,6 +22,22 @@ namespace render {
|
||||||
void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
|
void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
|
||||||
const ItemBounds& inItems, ItemBounds& outItems);
|
const ItemBounds& inItems, ItemBounds& outItems);
|
||||||
|
|
||||||
|
// Culling Frustum / solidAngle test helper class
|
||||||
|
struct CullTest {
|
||||||
|
CullFunctor _functor;
|
||||||
|
RenderArgs* _args;
|
||||||
|
RenderDetails::Item& _renderDetails;
|
||||||
|
ViewFrustumPointer _antiFrustum;
|
||||||
|
glm::vec3 _eyePos;
|
||||||
|
float _squareTanAlpha;
|
||||||
|
|
||||||
|
CullTest(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum = nullptr);
|
||||||
|
|
||||||
|
bool frustumTest(const AABox& bound);
|
||||||
|
bool antiFrustumTest(const AABox& bound);
|
||||||
|
bool solidAngleTest(const AABox& bound);
|
||||||
|
};
|
||||||
|
|
||||||
class FetchNonspatialItems {
|
class FetchNonspatialItems {
|
||||||
public:
|
public:
|
||||||
using JobModel = Job::ModelIO<FetchNonspatialItems, ItemFilter, ItemBounds>;
|
using JobModel = Job::ModelIO<FetchNonspatialItems, ItemFilter, ItemBounds>;
|
||||||
|
|
|
@ -210,10 +210,15 @@ void DrawBounds::run(const RenderContextPointer& renderContext,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gpu::Stream::FormatPointer DrawQuadVolume::_format;
|
||||||
|
|
||||||
DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) :
|
DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) :
|
||||||
_color{ color } {
|
_color{ color } {
|
||||||
_meshVertices = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ);
|
_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);
|
if (!_format) {
|
||||||
|
_format = std::make_shared<gpu::Stream::Format>();
|
||||||
|
_format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawQuadVolume::configure(const Config& configuration) {
|
void DrawQuadVolume::configure(const Config& configuration) {
|
||||||
|
@ -243,10 +248,11 @@ void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, cons
|
||||||
batch.setProjectionTransform(projMat);
|
batch.setProjectionTransform(projMat);
|
||||||
batch.setViewTransform(viewMat);
|
batch.setViewTransform(viewMat);
|
||||||
batch.setPipeline(getPipeline());
|
batch.setPipeline(getPipeline());
|
||||||
batch.setIndexBuffer(indices);
|
|
||||||
|
|
||||||
batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f);
|
batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f);
|
||||||
batch.setInputStream(0, _meshStream);
|
batch.setInputFormat(_format);
|
||||||
|
batch.setInputBuffer(gpu::Stream::POSITION, _meshVertices);
|
||||||
|
batch.setIndexBuffer(indices);
|
||||||
batch.drawIndexed(gpu::LINES, indexCount, 0U);
|
batch.drawIndexed(gpu::LINES, indexCount, 0U);
|
||||||
|
|
||||||
args->_batch = nullptr;
|
args->_batch = nullptr;
|
||||||
|
|
|
@ -95,10 +95,11 @@ protected:
|
||||||
const gpu::BufferView& indices, int indexCount);
|
const gpu::BufferView& indices, int indexCount);
|
||||||
|
|
||||||
gpu::BufferView _meshVertices;
|
gpu::BufferView _meshVertices;
|
||||||
gpu::BufferStream _meshStream;
|
|
||||||
glm::vec3 _color;
|
glm::vec3 _color;
|
||||||
bool _isUpdateEnabled{ true };
|
bool _isUpdateEnabled{ true };
|
||||||
|
|
||||||
|
static gpu::Stream::FormatPointer _format;
|
||||||
|
|
||||||
static gpu::PipelinePointer getPipeline();
|
static gpu::PipelinePointer getPipeline();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -61,9 +61,9 @@ void render::depthSortItems(const RenderContextPointer& renderContext, bool fron
|
||||||
for (auto itemDetails : inItems) {
|
for (auto itemDetails : inItems) {
|
||||||
auto item = scene->getItem(itemDetails.id);
|
auto item = scene->getItem(itemDetails.id);
|
||||||
auto bound = itemDetails.bound; // item.getBound();
|
auto bound = itemDetails.bound; // item.getBound();
|
||||||
float distance = args->getViewFrustum().distanceToCamera(bound.calcCenter());
|
float distanceSquared = args->getViewFrustum().distanceToCameraSquared(bound.calcCenter());
|
||||||
|
|
||||||
itemBoundSorts.emplace_back(ItemBoundSort(distance, distance, distance, itemDetails.id, bound));
|
itemBoundSorts.emplace_back(ItemBoundSort(distanceSquared, distanceSquared, distanceSquared, itemDetails.id, bound));
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort against Z
|
// sort against Z
|
||||||
|
|
|
@ -654,10 +654,10 @@ const ViewFrustum::Corners ViewFrustum::getCorners(const float depth) const {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
float ViewFrustum::distanceToCamera(const glm::vec3& point) const {
|
float ViewFrustum::distanceToCameraSquared(const glm::vec3& point) const {
|
||||||
glm::vec3 temp = getPosition() - point;
|
glm::vec3 temp = getPosition() - point;
|
||||||
float distanceToPoint = sqrtf(glm::dot(temp, temp));
|
float distanceToPointSquared = glm::dot(temp, temp);
|
||||||
return distanceToPoint;
|
return distanceToPointSquared;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewFrustum::evalProjectionMatrix(glm::mat4& proj) const {
|
void ViewFrustum::evalProjectionMatrix(glm::mat4& proj) const {
|
||||||
|
|
|
@ -127,7 +127,8 @@ public:
|
||||||
bool getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const;
|
bool getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const;
|
||||||
void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const;
|
void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const;
|
||||||
|
|
||||||
float distanceToCamera(const glm::vec3& point) const;
|
float distanceToCameraSquared(const glm::vec3& point) const;
|
||||||
|
float distanceToCamera(const glm::vec3& point) const { return sqrtf(distanceToCameraSquared(point)); }
|
||||||
|
|
||||||
void evalProjectionMatrix(glm::mat4& proj) const;
|
void evalProjectionMatrix(glm::mat4& proj) const;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
//
|
//
|
||||||
// debugShadow.js
|
// debugShadow.js
|
||||||
// developer/utilities/render
|
// developer/utilities/render
|
||||||
|
@ -9,12 +11,71 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
// Set up the qml ui
|
(function() {
|
||||||
var qml = Script.resolvePath('shadow.qml');
|
var TABLET_BUTTON_NAME = "Shadow";
|
||||||
var window = new OverlayWindow({
|
var QMLAPP_URL = Script.resolvePath("./shadow.qml");
|
||||||
title: 'Shadow Debug',
|
|
||||||
source: qml,
|
|
||||||
width: 250,
|
var onLuciScreen = false;
|
||||||
height: 300
|
|
||||||
});
|
function onClicked() {
|
||||||
window.closed.connect(function() { Script.stop(); });
|
if (onLuciScreen) {
|
||||||
|
tablet.gotoHomeScreen();
|
||||||
|
} else {
|
||||||
|
tablet.loadQMLSource(QMLAPP_URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||||
|
var button = tablet.addButton({
|
||||||
|
text: TABLET_BUTTON_NAME,
|
||||||
|
sortOrder: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
var hasEventBridge = false;
|
||||||
|
|
||||||
|
function wireEventBridge(on) {
|
||||||
|
if (!tablet) {
|
||||||
|
print("Warning in wireEventBridge(): 'tablet' undefined!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (on) {
|
||||||
|
if (!hasEventBridge) {
|
||||||
|
tablet.fromQml.connect(fromQml);
|
||||||
|
hasEventBridge = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hasEventBridge) {
|
||||||
|
tablet.fromQml.disconnect(fromQml);
|
||||||
|
hasEventBridge = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScreenChanged(type, url) {
|
||||||
|
if (url === QMLAPP_URL) {
|
||||||
|
onLuciScreen = true;
|
||||||
|
} else {
|
||||||
|
onLuciScreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.editProperties({isActive: onLuciScreen});
|
||||||
|
wireEventBridge(onLuciScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromQml(message) {
|
||||||
|
}
|
||||||
|
|
||||||
|
button.clicked.connect(onClicked);
|
||||||
|
tablet.screenChanged.connect(onScreenChanged);
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(function () {
|
||||||
|
if (onLuciScreen) {
|
||||||
|
tablet.gotoHomeScreen();
|
||||||
|
}
|
||||||
|
button.clicked.disconnect(onClicked);
|
||||||
|
tablet.screenChanged.disconnect(onScreenChanged);
|
||||||
|
tablet.removeButton(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
|
@ -8,21 +8,31 @@
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
import QtQuick 2.5
|
import QtQuick 2.7
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
import "qrc:///qml/styles-uit"
|
import "qrc:///qml/styles-uit"
|
||||||
import "qrc:///qml/controls-uit" as HifiControls
|
import "qrc:///qml/controls-uit" as HifiControls
|
||||||
import "configSlider"
|
|
||||||
|
import "configSlider"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root;
|
||||||
|
|
||||||
|
HifiConstants { id: hifi; }
|
||||||
|
color: hifi.colors.baseGray;
|
||||||
|
|
||||||
Column {
|
|
||||||
id: root
|
|
||||||
spacing: 8
|
|
||||||
property var viewConfig: Render.getConfig("RenderMainView.DrawViewFrustum");
|
property var viewConfig: Render.getConfig("RenderMainView.DrawViewFrustum");
|
||||||
property var shadowConfig : Render.getConfig("RenderMainView.ShadowSetup");
|
property var shadowConfig : Render.getConfig("RenderMainView.ShadowSetup");
|
||||||
property var shadow0Config: Render.getConfig("RenderMainView.DrawShadowFrustum0");
|
property var shadow0Config: Render.getConfig("RenderMainView.DrawShadowFrustum0");
|
||||||
property var shadow1Config: Render.getConfig("RenderMainView.DrawShadowFrustum1");
|
property var shadow1Config: Render.getConfig("RenderMainView.DrawShadowFrustum1");
|
||||||
property var shadow2Config: Render.getConfig("RenderMainView.DrawShadowFrustum2");
|
property var shadow2Config: Render.getConfig("RenderMainView.DrawShadowFrustum2");
|
||||||
property var shadow3Config: Render.getConfig("RenderMainView.DrawShadowFrustum3");
|
property var shadow3Config: Render.getConfig("RenderMainView.DrawShadowFrustum3");
|
||||||
|
property var shadowBBox0Config: Render.getConfig("RenderMainView.DrawShadowBBox0");
|
||||||
|
property var shadowBBox1Config: Render.getConfig("RenderMainView.DrawShadowBBox1");
|
||||||
|
property var shadowBBox2Config: Render.getConfig("RenderMainView.DrawShadowBBox2");
|
||||||
|
property var shadowBBox3Config: Render.getConfig("RenderMainView.DrawShadowBBox3");
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
viewConfig.enabled = true;
|
viewConfig.enabled = true;
|
||||||
|
@ -30,6 +40,10 @@ Column {
|
||||||
shadow1Config.enabled = true;
|
shadow1Config.enabled = true;
|
||||||
shadow2Config.enabled = true;
|
shadow2Config.enabled = true;
|
||||||
shadow3Config.enabled = true;
|
shadow3Config.enabled = true;
|
||||||
|
shadowBBox0Config.enabled = true;
|
||||||
|
shadowBBox1Config.enabled = true;
|
||||||
|
shadowBBox2Config.enabled = true;
|
||||||
|
shadowBBox3Config.enabled = true;
|
||||||
}
|
}
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
viewConfig.enabled = false;
|
viewConfig.enabled = false;
|
||||||
|
@ -41,108 +55,103 @@ Column {
|
||||||
shadow1Config.isFrozen = false;
|
shadow1Config.isFrozen = false;
|
||||||
shadow2Config.isFrozen = false;
|
shadow2Config.isFrozen = false;
|
||||||
shadow3Config.isFrozen = false;
|
shadow3Config.isFrozen = false;
|
||||||
shadow0BoundConfig.isFrozen = false;
|
|
||||||
shadow1BoundConfig.isFrozen = false;
|
shadowBBox0Config.enabled = false;
|
||||||
shadow2BoundConfig.isFrozen = false;
|
shadowBBox1Config.enabled = false;
|
||||||
shadow3BoundConfig.isFrozen = false;
|
shadowBBox2Config.enabled = false;
|
||||||
|
shadowBBox3Config.enabled = false;
|
||||||
|
shadowBBox0Config.isFrozen = false;
|
||||||
|
shadowBBox1Config.isFrozen = false;
|
||||||
|
shadowBBox2Config.isFrozen = false;
|
||||||
|
shadowBBox3Config.isFrozen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
ColumnLayout {
|
||||||
text: "Freeze Frustums"
|
spacing: 20
|
||||||
checked: false
|
anchors.left: parent.left
|
||||||
onCheckedChanged: {
|
anchors.right: parent.right
|
||||||
viewConfig.isFrozen = checked;
|
anchors.margins: hifi.dimensions.contentMargin.x
|
||||||
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 {
|
|
||||||
spacing: 8
|
|
||||||
Label {
|
|
||||||
text: "View"
|
|
||||||
color: "yellow"
|
|
||||||
font.italic: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "Shadow"
|
|
||||||
color: "blue"
|
|
||||||
font.italic: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "Items"
|
|
||||||
color: "magenta"
|
|
||||||
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 {
|
RowLayout {
|
||||||
label: qsTr("Cascade 0 slope bias")
|
spacing: 20
|
||||||
integral: false
|
Layout.fillWidth: true
|
||||||
config: shadowConfig
|
|
||||||
property: "slopeBias0"
|
HifiControls.CheckBox {
|
||||||
max: 1.0
|
boxSize: 20
|
||||||
min: 0.0
|
text: "Freeze"
|
||||||
}
|
checked: false
|
||||||
ConfigSlider {
|
onCheckedChanged: {
|
||||||
label: qsTr("Cascade 1 slope bias")
|
viewConfig.isFrozen = checked;
|
||||||
integral: false
|
shadow0Config.isFrozen = checked;
|
||||||
config: shadowConfig
|
shadow1Config.isFrozen = checked;
|
||||||
property: "slopeBias1"
|
shadow2Config.isFrozen = checked;
|
||||||
max: 1.0
|
shadow3Config.isFrozen = checked;
|
||||||
min: 0.0
|
|
||||||
}
|
shadowBBox0Config.isFrozen = checked;
|
||||||
ConfigSlider {
|
shadowBBox1Config.isFrozen = checked;
|
||||||
label: qsTr("Cascade 2 slope bias")
|
shadowBBox2Config.isFrozen = checked;
|
||||||
integral: false
|
shadowBBox3Config.isFrozen = checked;
|
||||||
config: shadowConfig
|
}
|
||||||
property: "slopeBias2"
|
}
|
||||||
max: 1.0
|
HifiControls.Label {
|
||||||
min: 0.0
|
text: "View"
|
||||||
}
|
color: "yellow"
|
||||||
ConfigSlider {
|
font.italic: true
|
||||||
label: qsTr("Cascade 3 slope bias")
|
}
|
||||||
integral: false
|
HifiControls.Label {
|
||||||
config: shadowConfig
|
text: "Shadow"
|
||||||
property: "slopeBias3"
|
color: "blue"
|
||||||
max: 1.0
|
font.italic: true
|
||||||
min: 0.0
|
}
|
||||||
|
HifiControls.Label {
|
||||||
|
text: "AABB"
|
||||||
|
color: "red"
|
||||||
|
font.italic: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
model: [
|
||||||
|
"0", "1", "2", "3"
|
||||||
|
]
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 8
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
HifiControls.Separator {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
}
|
||||||
|
HifiControls.CheckBox {
|
||||||
|
text: "Cascade "+modelData
|
||||||
|
boxSize: 20
|
||||||
|
checked: Render.getConfig("RenderMainView.DrawShadowFrustum"+modelData)
|
||||||
|
onCheckedChanged: {
|
||||||
|
Render.getConfig("RenderMainView.DrawShadowFrustum"+modelData).enabled = checked;
|
||||||
|
Render.getConfig("RenderMainView.DrawShadowBBox"+modelData).enabled = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConfigSlider {
|
||||||
|
label: qsTr("Constant bias")
|
||||||
|
integral: false
|
||||||
|
config: shadowConfig
|
||||||
|
property: "constantBias"+modelData
|
||||||
|
max: 1.0
|
||||||
|
min: 0.0
|
||||||
|
height: 38
|
||||||
|
width:250
|
||||||
|
}
|
||||||
|
ConfigSlider {
|
||||||
|
label: qsTr("Slope bias")
|
||||||
|
integral: false
|
||||||
|
config: shadowConfig
|
||||||
|
property: "slopeBias"+modelData
|
||||||
|
max: 1.0
|
||||||
|
min: 0.0
|
||||||
|
height: 38
|
||||||
|
width: 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue