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