mirror of
https://github.com/lubosz/overte.git
synced 2025-04-29 19:03:10 +02: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
|
// 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.
|
// 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 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;
|
||||||
using namespace render::entities;
|
using namespace render::entities;
|
||||||
|
@ -116,7 +118,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
||||||
// Do we need to allocate the light in the stage ?
|
// Do we need to allocate the light in the stage ?
|
||||||
if (LightStage::isIndexInvalid(_sunIndex)) {
|
if (LightStage::isIndexInvalid(_sunIndex)) {
|
||||||
_sunIndex = _stage->addLight(_sunLight);
|
_sunIndex = _stage->addLight(_sunLight);
|
||||||
_shadowIndex = _stage->addShadow(_sunIndex);
|
_shadowIndex = _stage->addShadow(_sunIndex, SUN_SHADOW_MAX_DISTANCE, SUN_SHADOW_CASCADE_COUNT);
|
||||||
} else {
|
} else {
|
||||||
_stage->updateLightArrayBuffer(_sunIndex);
|
_stage->updateLightArrayBuffer(_sunIndex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,7 +318,10 @@ int GLBackend::makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slot
|
||||||
if (requestedBinding != slotBindings.end()) {
|
if (requestedBinding != slotBindings.end()) {
|
||||||
if (binding != (*requestedBinding)._location) {
|
if (binding != (*requestedBinding)._location) {
|
||||||
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();
|
emit dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Slot {
|
enum TextureSlot {
|
||||||
Albedo = 0,
|
Albedo = 0,
|
||||||
Normal,
|
Normal,
|
||||||
Specular,
|
Specular,
|
||||||
|
@ -56,7 +56,11 @@ enum Slot {
|
||||||
AmbientOcclusionBlurred
|
AmbientOcclusionBlurred
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ParamSlot {
|
||||||
|
CameraCorrection = 0,
|
||||||
|
DeferredFrameTransform,
|
||||||
|
ShadowTransform
|
||||||
|
};
|
||||||
|
|
||||||
static const std::string DEFAULT_ALBEDO_SHADER {
|
static const std::string DEFAULT_ALBEDO_SHADER {
|
||||||
"vec4 getFragmentColor() {"
|
"vec4 getFragmentColor() {"
|
||||||
|
@ -127,12 +131,14 @@ static const std::string DEFAULT_DEPTH_SHADER {
|
||||||
" return vec4(vec3(texture(depthMap, uv).x), 1.0);"
|
" return vec4(vec3(texture(depthMap, uv).x), 1.0);"
|
||||||
" }"
|
" }"
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::string DEFAULT_LIGHTING_SHADER {
|
static const std::string DEFAULT_LIGHTING_SHADER {
|
||||||
"vec4 getFragmentColor() {"
|
"vec4 getFragmentColor() {"
|
||||||
" return vec4(pow(texture(lightingMap, uv).xyz, vec3(1.0 / 2.2)), 1.0);"
|
" 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;"
|
"uniform sampler2DShadow shadowMap;"
|
||||||
"vec4 getFragmentColor() {"
|
"vec4 getFragmentColor() {"
|
||||||
" for (int i = 255; i >= 0; --i) {"
|
" 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 {
|
static const std::string DEFAULT_LINEAR_DEPTH_SHADER {
|
||||||
"vec4 getFragmentColor() {"
|
"vec4 getFragmentColor() {"
|
||||||
" return vec4(vec3(1.0 - texture(linearDepthMap, uv).x * 0.01), 1.0);"
|
" return vec4(vec3(1.0 - texture(linearDepthMap, uv).x * 0.01), 1.0);"
|
||||||
" }"
|
"}"
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::string DEFAULT_HALF_LINEAR_DEPTH_SHADER{
|
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;
|
return DEFAULT_SCATTERING_SHADER;
|
||||||
case LightingMode:
|
case LightingMode:
|
||||||
return DEFAULT_LIGHTING_SHADER;
|
return DEFAULT_LIGHTING_SHADER;
|
||||||
case ShadowMode:
|
case ShadowCascade0Mode:
|
||||||
|
case ShadowCascade1Mode:
|
||||||
|
case ShadowCascade2Mode:
|
||||||
|
case ShadowCascade3Mode:
|
||||||
return DEFAULT_SHADOW_SHADER;
|
return DEFAULT_SHADOW_SHADER;
|
||||||
|
case ShadowCascadeIndicesMode:
|
||||||
|
return DEFAULT_SHADOW_CASCADE_SHADER;
|
||||||
case LinearDepthMode:
|
case LinearDepthMode:
|
||||||
return DEFAULT_LINEAR_DEPTH_SHADER;
|
return DEFAULT_LINEAR_DEPTH_SHADER;
|
||||||
case HalfLinearDepthMode:
|
case HalfLinearDepthMode:
|
||||||
|
@ -353,6 +385,10 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str
|
||||||
const auto program = gpu::Shader::createProgram(vs, ps);
|
const auto program = gpu::Shader::createProgram(vs, ps);
|
||||||
|
|
||||||
gpu::Shader::BindingSet slotBindings;
|
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("albedoMap", Albedo));
|
||||||
slotBindings.insert(gpu::Shader::Binding("normalMap", Normal));
|
slotBindings.insert(gpu::Shader::Binding("normalMap", Normal));
|
||||||
slotBindings.insert(gpu::Shader::Binding("specularMap", Specular));
|
slotBindings.insert(gpu::Shader::Binding("specularMap", Specular));
|
||||||
|
@ -404,6 +440,7 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
|
||||||
auto& linearDepthTarget = inputs.get1();
|
auto& linearDepthTarget = inputs.get1();
|
||||||
auto& surfaceGeometryFramebuffer = inputs.get2();
|
auto& surfaceGeometryFramebuffer = inputs.get2();
|
||||||
auto& ambientOcclusionFramebuffer = inputs.get3();
|
auto& ambientOcclusionFramebuffer = inputs.get3();
|
||||||
|
auto& frameTransform = inputs.get4();
|
||||||
|
|
||||||
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
|
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||||
batch.enableStereo(false);
|
batch.enableStereo(false);
|
||||||
|
@ -422,8 +459,8 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
|
||||||
|
|
||||||
// TODO REMOVE: Temporary until UI
|
// TODO REMOVE: Temporary until UI
|
||||||
auto first = _customPipelines.begin()->first;
|
auto first = _customPipelines.begin()->first;
|
||||||
|
auto pipeline = getPipeline(_mode, first);
|
||||||
batch.setPipeline(getPipeline(_mode, first));
|
batch.setPipeline(pipeline);
|
||||||
|
|
||||||
if (deferredFramebuffer) {
|
if (deferredFramebuffer) {
|
||||||
batch.setResourceTexture(Albedo, deferredFramebuffer->getDeferredColorTexture());
|
batch.setResourceTexture(Albedo, deferredFramebuffer->getDeferredColorTexture());
|
||||||
|
@ -439,7 +476,10 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
|
||||||
auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow();
|
auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow();
|
||||||
const auto& globalShadow = lightAndShadow.second;
|
const auto& globalShadow = lightAndShadow.second;
|
||||||
if (globalShadow) {
|
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) {
|
if (linearDepthTarget) {
|
||||||
|
@ -460,7 +500,6 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
|
||||||
const glm::vec2 topRight(_size.z, _size.w);
|
const glm::vec2 topRight(_size.z, _size.w);
|
||||||
geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId);
|
geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId);
|
||||||
|
|
||||||
|
|
||||||
batch.setResourceTexture(Albedo, nullptr);
|
batch.setResourceTexture(Albedo, nullptr);
|
||||||
batch.setResourceTexture(Normal, nullptr);
|
batch.setResourceTexture(Normal, nullptr);
|
||||||
batch.setResourceTexture(Specular, nullptr);
|
batch.setResourceTexture(Specular, nullptr);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
||||||
#include <render/DrawTask.h>
|
#include <render/DrawTask.h>
|
||||||
|
#include "DeferredFrameTransform.h"
|
||||||
#include "DeferredFramebuffer.h"
|
#include "DeferredFramebuffer.h"
|
||||||
#include "SurfaceGeometryPass.h"
|
#include "SurfaceGeometryPass.h"
|
||||||
#include "AmbientOcclusionEffect.h"
|
#include "AmbientOcclusionEffect.h"
|
||||||
|
@ -37,7 +38,7 @@ signals:
|
||||||
|
|
||||||
class DebugDeferredBuffer {
|
class DebugDeferredBuffer {
|
||||||
public:
|
public:
|
||||||
using Inputs = render::VaryingSet4<DeferredFramebufferPointer, LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer>;
|
using Inputs = render::VaryingSet5<DeferredFramebufferPointer, LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer, DeferredFrameTransformPointer>;
|
||||||
using Config = DebugDeferredBufferConfig;
|
using Config = DebugDeferredBufferConfig;
|
||||||
using JobModel = render::Job::ModelI<DebugDeferredBuffer, Inputs, Config>;
|
using JobModel = render::Job::ModelI<DebugDeferredBuffer, Inputs, Config>;
|
||||||
|
|
||||||
|
@ -64,7 +65,11 @@ protected:
|
||||||
LightmapMode,
|
LightmapMode,
|
||||||
ScatteringMode,
|
ScatteringMode,
|
||||||
LightingMode,
|
LightingMode,
|
||||||
ShadowMode,
|
ShadowCascade0Mode,
|
||||||
|
ShadowCascade1Mode,
|
||||||
|
ShadowCascade2Mode,
|
||||||
|
ShadowCascade3Mode,
|
||||||
|
ShadowCascadeIndicesMode,
|
||||||
LinearDepthMode,
|
LinearDepthMode,
|
||||||
HalfLinearDepthMode,
|
HalfLinearDepthMode,
|
||||||
HalfNormalMode,
|
HalfNormalMode,
|
||||||
|
|
|
@ -58,7 +58,7 @@ enum DeferredShader_MapSlot {
|
||||||
DEFERRED_BUFFER_DEPTH_UNIT = 3,
|
DEFERRED_BUFFER_DEPTH_UNIT = 3,
|
||||||
DEFERRED_BUFFER_OBSCURANCE_UNIT = 4,
|
DEFERRED_BUFFER_OBSCURANCE_UNIT = 4,
|
||||||
SHADOW_MAP_UNIT = 5,
|
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_LINEAR_DEPTH_UNIT,
|
||||||
DEFERRED_BUFFER_CURVATURE_UNIT,
|
DEFERRED_BUFFER_CURVATURE_UNIT,
|
||||||
DEFERRED_BUFFER_DIFFUSED_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("specularMap"), DEFERRED_BUFFER_EMISSIVE_UNIT));
|
||||||
slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DEFERRED_BUFFER_DEPTH_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("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("skyboxMap"), SKYBOX_MAP_UNIT));
|
||||||
|
|
||||||
slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_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();
|
auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow();
|
||||||
const auto& globalShadow = lightAndShadow.second;
|
const auto& globalShadow = lightAndShadow.second;
|
||||||
|
|
||||||
// Bind the shadow buffer
|
// Bind the shadow buffers
|
||||||
if (globalShadow) {
|
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;
|
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);
|
deferredLightingEffect->unsetKeyLightBatch(batch, locations->lightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT);
|
||||||
|
|
||||||
|
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
|
||||||
batch.setResourceTexture(SHADOW_MAP_UNIT, nullptr);
|
batch.setResourceTexture(SHADOW_MAP_UNIT+i, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,65 +13,202 @@
|
||||||
|
|
||||||
#include "LightStage.h"
|
#include "LightStage.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
std::string LightStage::_stageName { "LIGHT_STAGE"};
|
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 };
|
const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX };
|
||||||
|
|
||||||
LightStage::LightStage() {
|
LightStage::LightStage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
LightStage::Shadow::Schema::Schema() :
|
LightStage::Shadow::Schema::Schema() {
|
||||||
bias{ 0.005f },
|
ShadowTransform defaultTransform;
|
||||||
scale{ 1.0f / MAP_SIZE } {
|
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;
|
LightStage::Shadow::Cascade::Cascade() :
|
||||||
gpu::TexturePointer LightStage::Shadow::map;
|
_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>() } {
|
const glm::mat4& LightStage::Shadow::Cascade::getView() const {
|
||||||
Schema schema;
|
return _frustum->getView();
|
||||||
_schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
|
}
|
||||||
|
|
||||||
if (!framebuffer) {
|
const glm::mat4& LightStage::Shadow::Cascade::getProjection() const {
|
||||||
framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::createShadowmap(MAP_SIZE));
|
return _frustum->getProjection();
|
||||||
map = framebuffer->getDepthStencilBuffer();
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
|
||||||
float viewMinShadowDistance, float viewMaxShadowDistance,
|
|
||||||
float nearDepth, float farDepth) {
|
float nearDepth, float farDepth) {
|
||||||
assert(viewMinShadowDistance < viewMaxShadowDistance);
|
|
||||||
assert(nearDepth < farDepth);
|
assert(nearDepth < farDepth);
|
||||||
|
|
||||||
// Orient the keylight frustum
|
// Orient the keylight frustum
|
||||||
const auto& direction = glm::normalize(_light->getDirection());
|
auto lightDirection = glm::normalize(_light->getDirection());
|
||||||
glm::quat orientation;
|
glm::quat orientation;
|
||||||
if (direction == IDENTITY_UP) {
|
if (lightDirection == IDENTITY_UP) {
|
||||||
orientation = glm::quat(glm::mat3(-IDENTITY_RIGHT, IDENTITY_FORWARD, -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));
|
orientation = glm::quat(glm::mat3(IDENTITY_RIGHT, IDENTITY_FORWARD, IDENTITY_UP));
|
||||||
} else {
|
} else {
|
||||||
auto side = glm::normalize(glm::cross(direction, IDENTITY_UP));
|
auto side = glm::normalize(glm::cross(lightDirection, IDENTITY_UP));
|
||||||
auto up = glm::normalize(glm::cross(side, direction));
|
auto up = glm::normalize(glm::cross(side, lightDirection));
|
||||||
orientation = glm::quat_cast(glm::mat3(side, up, -direction));
|
orientation = glm::quat_cast(glm::mat3(side, up, -lightDirection));
|
||||||
}
|
}
|
||||||
_frustum->setOrientation(orientation);
|
|
||||||
|
|
||||||
// Position the keylight frustum
|
// 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()};
|
void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,
|
||||||
const Transform viewInverse{ view.getInverseMatrix() };
|
float nearDepth, float farDepth) {
|
||||||
|
assert(nearDepth < farDepth);
|
||||||
|
assert(cascadeIndex < _cascades.size());
|
||||||
|
|
||||||
auto nearCorners = viewFrustum.getCorners(viewMinShadowDistance);
|
auto& cascade = _cascades[cascadeIndex];
|
||||||
auto farCorners = viewFrustum.getCorners(viewMaxShadowDistance);
|
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 };
|
vec3 max{ min };
|
||||||
// Expand keylight frustum to fit view frustum
|
// Expand keylight frustum to fit view frustum
|
||||||
auto fitFrustum = [&min, &max, &viewInverse](const vec3& viewCorner) {
|
auto fitFrustum = [&min, &max, &shadowViewInverse](const vec3& viewCorner) {
|
||||||
const auto corner = viewInverse.transform(viewCorner);
|
const auto corner = shadowViewInverse.transform(viewCorner);
|
||||||
|
|
||||||
min.x = glm::min(min.x, corner.x);
|
min.x = glm::min(min.x, corner.x);
|
||||||
min.y = glm::min(min.y, corner.y);
|
min.y = glm::min(min.y, corner.y);
|
||||||
|
@ -89,36 +226,35 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
|
||||||
fitFrustum(farCorners.topLeft);
|
fitFrustum(farCorners.topLeft);
|
||||||
fitFrustum(farCorners.topRight);
|
fitFrustum(farCorners.topRight);
|
||||||
|
|
||||||
// Re-adjust near shadow distance
|
// Re-adjust near and far shadow distance
|
||||||
auto near = glm::max(max.z, -nearDepth);
|
auto near = glm::min(-max.z, nearDepth);
|
||||||
auto far = -min.z;
|
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);
|
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
|
// Calculate the frustum's internal state
|
||||||
_frustum->calculate();
|
cascade._frustum->calculate();
|
||||||
|
|
||||||
// Update the buffer
|
// Update the buffer
|
||||||
_schemaBuffer.edit<Schema>().projection = ortho;
|
auto& schema = _schemaBuffer.edit<Schema>();
|
||||||
_schemaBuffer.edit<Schema>().viewInverse = viewInverse.getMatrix();
|
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 view{ shadowFrustum.getView() };
|
||||||
const Transform viewInverse{ view.getInverseMatrix() };
|
const Transform viewInverse{ view.getInverseMatrix() };
|
||||||
|
auto& cascade = _cascades[cascadeIndex];
|
||||||
|
|
||||||
*_frustum = shadowFrustum;
|
*cascade._frustum = shadowFrustum;
|
||||||
// Update the buffer
|
// Update the buffer
|
||||||
_schemaBuffer.edit<Schema>().projection = shadowFrustum.getProjection();
|
_schemaBuffer.edit<Schema>().cascades[cascadeIndex].reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix();
|
||||||
_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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LightStage::Index LightStage::findLight(const LightPointer& light) const {
|
LightStage::Index LightStage::findLight(const LightPointer& light) const {
|
||||||
|
@ -142,7 +278,7 @@ LightStage::Index LightStage::addLight(const LightPointer& light) {
|
||||||
_descs.emplace_back(Desc());
|
_descs.emplace_back(Desc());
|
||||||
} else {
|
} else {
|
||||||
assert(_descs[lightId].shadowId == INVALID_INDEX);
|
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
|
// 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);
|
auto light = getLight(lightIndex);
|
||||||
Index shadowId = INVALID_INDEX;
|
Index shadowId = INVALID_INDEX;
|
||||||
if (light) {
|
if (light) {
|
||||||
assert(_descs[lightIndex].shadowId == INVALID_INDEX);
|
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;
|
_descs[lightIndex].shadowId = shadowId;
|
||||||
}
|
}
|
||||||
return shadowId;
|
return shadowId;
|
||||||
|
|
|
@ -44,45 +44,74 @@ public:
|
||||||
class Shadow {
|
class Shadow {
|
||||||
public:
|
public:
|
||||||
using UniformBufferView = gpu::BufferView;
|
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);
|
gpu::FramebufferPointer framebuffer;
|
||||||
const std::shared_ptr<ViewFrustum> getFrustum() const { return _frustum; }
|
gpu::TexturePointer map;
|
||||||
|
|
||||||
const glm::mat4& getView() const;
|
const std::shared_ptr<ViewFrustum>& getFrustum() const { return _frustum; }
|
||||||
const glm::mat4& getProjection() const;
|
|
||||||
|
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; }
|
const UniformBufferView& getBuffer() const { return _schemaBuffer; }
|
||||||
|
|
||||||
// Shadow maps are shared among all lights for the moment as only one key light
|
unsigned int getCascadeCount() const { return (unsigned int)_cascades.size(); }
|
||||||
// is used.
|
const Cascade& getCascade(unsigned int index) const { return _cascades[index]; }
|
||||||
static gpu::FramebufferPointer framebuffer;
|
|
||||||
static gpu::TexturePointer map;
|
float getMaxDistance() const { return _maxDistance; }
|
||||||
|
void setMaxDistance(float value);
|
||||||
|
|
||||||
const model::LightPointer& getLight() const { return _light; }
|
const model::LightPointer& getLight() const { return _light; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
model::LightPointer _light;
|
#include "Shadows_shared.slh"
|
||||||
std::shared_ptr<ViewFrustum> _frustum;
|
|
||||||
|
|
||||||
class Schema {
|
using Cascades = std::vector<Cascade>;
|
||||||
|
|
||||||
|
static const glm::mat4 _biasMatrix;
|
||||||
|
|
||||||
|
model::LightPointer _light;
|
||||||
|
float _maxDistance;
|
||||||
|
Cascades _cascades;
|
||||||
|
|
||||||
|
class Schema : public ShadowParameters {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Schema();
|
Schema();
|
||||||
|
|
||||||
glm::mat4 projection;
|
|
||||||
glm::mat4 viewInverse;
|
|
||||||
|
|
||||||
glm::float32 bias;
|
|
||||||
glm::float32 scale;
|
|
||||||
};
|
};
|
||||||
UniformBufferView _schemaBuffer = nullptr;
|
UniformBufferView _schemaBuffer = nullptr;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using ShadowPointer = std::shared_ptr<Shadow>;
|
using ShadowPointer = std::shared_ptr<Shadow>;
|
||||||
|
@ -91,7 +120,7 @@ public:
|
||||||
Index findLight(const LightPointer& light) const;
|
Index findLight(const LightPointer& light) const;
|
||||||
Index addLight(const LightPointer& light);
|
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);
|
LightPointer removeLight(Index index);
|
||||||
|
|
||||||
|
|
|
@ -205,34 +205,23 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
|
||||||
task.addJob<DrawBounds>("DrawZones", zones);
|
task.addJob<DrawBounds>("DrawZones", zones);
|
||||||
const auto frustums = task.addJob<ExtractFrustums>("ExtractFrustums");
|
const auto frustums = task.addJob<ExtractFrustums>("ExtractFrustums");
|
||||||
const auto viewFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::VIEW_FRUSTUM);
|
const auto viewFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::VIEW_FRUSTUM);
|
||||||
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>("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
|
// Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true
|
||||||
task.addJob<DrawBounds>("DrawSelectionBounds", selectedItems);
|
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 stages
|
||||||
{
|
{
|
||||||
// Debugging Deferred buffer job
|
// 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);
|
task.addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers);
|
||||||
|
|
||||||
const auto debugSubsurfaceScatteringInputs = DebugSubsurfaceScattering::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel,
|
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);
|
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
|
// AA job to be revisited
|
||||||
task.addJob<Antialiasing>("Antialiasing", primaryFramebuffer);
|
task.addJob<Antialiasing>("Antialiasing", primaryFramebuffer);
|
||||||
|
|
||||||
|
@ -555,17 +560,20 @@ void ExtractFrustums::run(const render::RenderContextPointer& renderContext, Out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return shadow frustum
|
// Return shadow frustum
|
||||||
auto& shadowFrustum = output[SHADOW_FRUSTUM].edit<ViewFrustumPointer>();
|
|
||||||
auto lightStage = args->_scene->getStage<LightStage>(LightStage::getName());
|
auto lightStage = args->_scene->getStage<LightStage>(LightStage::getName());
|
||||||
if (lightStage) {
|
for (auto i = 0; i < SHADOW_CASCADE_FRUSTUM_COUNT; i++) {
|
||||||
auto globalShadow = lightStage->getCurrentKeyShadow();
|
auto& shadowFrustum = output[SHADOW_CASCADE0_FRUSTUM+i].edit<ViewFrustumPointer>();
|
||||||
|
if (lightStage) {
|
||||||
|
auto globalShadow = lightStage->getCurrentKeyShadow();
|
||||||
|
|
||||||
if (globalShadow) {
|
if (globalShadow && i<(int)globalShadow->getCascadeCount()) {
|
||||||
shadowFrustum = globalShadow->getFrustum();
|
auto& cascade = globalShadow->getCascade(i);
|
||||||
|
shadowFrustum = cascade.getFrustum();
|
||||||
|
} else {
|
||||||
|
shadowFrustum.reset();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
shadowFrustum.reset();
|
shadowFrustum.reset();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
shadowFrustum.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,8 +174,14 @@ class ExtractFrustums {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
enum Frustum {
|
enum Frustum {
|
||||||
VIEW_FRUSTUM,
|
SHADOW_CASCADE0_FRUSTUM = 0,
|
||||||
SHADOW_FRUSTUM,
|
SHADOW_CASCADE1_FRUSTUM,
|
||||||
|
SHADOW_CASCADE2_FRUSTUM,
|
||||||
|
SHADOW_CASCADE3_FRUSTUM,
|
||||||
|
|
||||||
|
SHADOW_CASCADE_FRUSTUM_COUNT,
|
||||||
|
|
||||||
|
VIEW_FRUSTUM = SHADOW_CASCADE_FRUSTUM_COUNT,
|
||||||
|
|
||||||
FRUSTUM_COUNT
|
FRUSTUM_COUNT
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
#include "DeferredLightingEffect.h"
|
#include "DeferredLightingEffect.h"
|
||||||
#include "FramebufferCache.h"
|
#include "FramebufferCache.h"
|
||||||
|
|
||||||
|
#include "RenderUtilsLogging.h"
|
||||||
|
|
||||||
// These values are used for culling the objects rendered in the shadow map
|
// These values are used for culling the objects rendered in the shadow map
|
||||||
// but are readjusted afterwards
|
// but are readjusted afterwards
|
||||||
#define SHADOW_FRUSTUM_NEAR 1.0f
|
#define SHADOW_FRUSTUM_NEAR 1.0f
|
||||||
|
@ -89,31 +91,13 @@ static void adjustNearFar(const AABox& inShapeBounds, ViewFrustum& shadowFrustum
|
||||||
for (i = 0; i < 8; i++) {
|
for (i = 0; i < 8; i++) {
|
||||||
sceneBoundVertices[i] = shadowViewInverse.transform(inShapeBounds.getVertex(static_cast<BoxVertex>(i)));
|
sceneBoundVertices[i] = shadowViewInverse.transform(inShapeBounds.getVertex(static_cast<BoxVertex>(i)));
|
||||||
}
|
}
|
||||||
// This indirection array is just a protection in case the ViewFrustum::PlaneIndex enum
|
shadowFrustum.getUniformlyTransformedSidePlanes(shadowViewInverse, shadowClipPlanes);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
float near = std::numeric_limits<float>::max();
|
float near = std::numeric_limits<float>::max();
|
||||||
float far = 0.0f;
|
float far = 0.0f;
|
||||||
|
|
||||||
computeNearFar(sceneBoundVertices, shadowClipPlanes, near, far);
|
computeNearFar(sceneBoundVertices, shadowClipPlanes, near, far);
|
||||||
// Limit the far range to the one used originally. There's no point in rendering objects
|
// Limit the far range to the one used originally.
|
||||||
// that are not in the view frustum.
|
|
||||||
far = glm::min(far, shadowFrustum.getFarClip());
|
far = glm::min(far, shadowFrustum.getFarClip());
|
||||||
|
|
||||||
const auto depthEpsilon = 0.1f;
|
const auto depthEpsilon = 0.1f;
|
||||||
|
@ -137,9 +121,12 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
|
||||||
assert(lightStage);
|
assert(lightStage);
|
||||||
|
|
||||||
auto shadow = lightStage->getCurrentKeyShadow();
|
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;
|
RenderArgs* args = renderContext->args;
|
||||||
ShapeKey::Builder defaultKeyBuilder;
|
ShapeKey::Builder defaultKeyBuilder;
|
||||||
|
@ -149,7 +136,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
|
||||||
// the minimal Z range.
|
// the minimal Z range.
|
||||||
adjustNearFar(inShapeBounds, adjustedShadowFrustum);
|
adjustNearFar(inShapeBounds, adjustedShadowFrustum);
|
||||||
// Reapply the frustum as it has been adjusted
|
// Reapply the frustum as it has been adjusted
|
||||||
shadow->setFrustum(adjustedShadowFrustum);
|
shadow->setCascadeFrustum(_cascadeIndex, adjustedShadowFrustum);
|
||||||
args->popViewFrustum();
|
args->popViewFrustum();
|
||||||
args->pushViewFrustum(adjustedShadowFrustum);
|
args->pushViewFrustum(adjustedShadowFrustum);
|
||||||
|
|
||||||
|
@ -178,6 +165,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
|
||||||
auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
|
auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
|
||||||
|
|
||||||
std::vector<ShapeKey> skinnedShapeKeys{};
|
std::vector<ShapeKey> skinnedShapeKeys{};
|
||||||
|
std::vector<ShapeKey> ownPipelineShapeKeys{};
|
||||||
|
|
||||||
// Iterate through all inShapes and render the unskinned
|
// Iterate through all inShapes and render the unskinned
|
||||||
args->_shapePipeline = shadowPipeline;
|
args->_shapePipeline = shadowPipeline;
|
||||||
|
@ -185,8 +173,10 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
|
||||||
for (auto items : inShapes) {
|
for (auto items : inShapes) {
|
||||||
if (items.first.isSkinned()) {
|
if (items.first.isSkinned()) {
|
||||||
skinnedShapeKeys.push_back(items.first);
|
skinnedShapeKeys.push_back(items.first);
|
||||||
} else {
|
} else if (!items.first.hasOwnPipeline()) {
|
||||||
renderItems(renderContext, items.second);
|
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));
|
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;
|
args->_shapePipeline = nullptr;
|
||||||
|
for (const auto& key : ownPipelineShapeKeys) {
|
||||||
|
args->_itemShapeKey = key._flags.to_ulong();
|
||||||
|
renderItems(renderContext, inShapes.at(key));
|
||||||
|
}
|
||||||
|
|
||||||
args->_batch = nullptr;
|
args->_batch = nullptr;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -215,22 +213,26 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
|
||||||
initZPassPipelines(*shapePlumber, state);
|
initZPassPipelines(*shapePlumber, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto cachedMode = task.addJob<RenderShadowSetup>("ShadowSetup");
|
task.addJob<RenderShadowSetup>("ShadowSetup");
|
||||||
|
|
||||||
// CPU jobs:
|
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
|
||||||
// Fetch and cull the items from the scene
|
const auto setupOutput = task.addJob<RenderShadowCascadeSetup>("ShadowCascadeSetup", i);
|
||||||
auto shadowFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered();
|
const auto shadowFilter = setupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
|
||||||
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowSelection", shadowFilter);
|
|
||||||
const auto culledShadowSelection = task.addJob<CullSpatialSelection>("CullShadowSelection", shadowSelection, cullFunctor, RenderDetails::SHADOW, shadowFilter);
|
|
||||||
|
|
||||||
// Sort
|
// CPU jobs:
|
||||||
const auto sortedPipelines = task.addJob<PipelineSortShapes>("PipelineSortShadowSort", culledShadowSelection);
|
// Fetch and cull the items from the scene
|
||||||
const auto sortedShapesAndBounds = task.addJob<DepthSortShapesAndComputeBounds>("DepthSortShadowMap", sortedPipelines, true);
|
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
|
// Sort
|
||||||
task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapesAndBounds, shapePlumber);
|
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) {
|
void RenderShadowTask::configure(const Config& configuration) {
|
||||||
|
@ -239,31 +241,57 @@ void RenderShadowTask::configure(const Config& configuration) {
|
||||||
// Task::configure(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>();
|
auto lightStage = renderContext->_scene->getStage<LightStage>();
|
||||||
assert(lightStage);
|
assert(lightStage);
|
||||||
|
// Cache old render args
|
||||||
|
RenderArgs* args = renderContext->args;
|
||||||
|
|
||||||
const auto globalShadow = lightStage->getCurrentKeyShadow();
|
const auto globalShadow = lightStage->getCurrentKeyShadow();
|
||||||
if (globalShadow) {
|
if (globalShadow) {
|
||||||
// Cache old render args
|
globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
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
|
// Reset the render args
|
||||||
args->popViewFrustum();
|
args->_renderMode = input.get0();
|
||||||
args->_renderMode = input;
|
args->_sizeScale = input.get2();
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,11 +24,12 @@ public:
|
||||||
using Inputs = render::VaryingSet2<render::ShapeBounds, AABox>;
|
using Inputs = render::VaryingSet2<render::ShapeBounds, AABox>;
|
||||||
using JobModel = render::Job::ModelI<RenderShadowMap, Inputs>;
|
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);
|
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
render::ShapePlumberPointer _shapePlumber;
|
render::ShapePlumberPointer _shapePlumber;
|
||||||
|
unsigned int _cascadeIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RenderShadowTaskConfig : public render::Task::Config::Persistent {
|
class RenderShadowTaskConfig : public render::Task::Config::Persistent {
|
||||||
|
@ -54,15 +55,30 @@ public:
|
||||||
|
|
||||||
class RenderShadowSetup {
|
class RenderShadowSetup {
|
||||||
public:
|
public:
|
||||||
using Output = RenderArgs::RenderMode;
|
using JobModel = render::Job::Model<RenderShadowSetup>;
|
||||||
using JobModel = render::Job::ModelO<RenderShadowSetup, Output>;
|
|
||||||
void run(const render::RenderContextPointer& renderContext, Output& output);
|
RenderShadowSetup() {}
|
||||||
|
void run(const render::RenderContextPointer& renderContext);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class RenderShadowTeardown {
|
class RenderShadowCascadeSetup {
|
||||||
public:
|
public:
|
||||||
using Input = RenderArgs::RenderMode;
|
using Outputs = render::VaryingSet3<RenderArgs::RenderMode, render::ItemFilter, float>;
|
||||||
using JobModel = render::Job::ModelI<RenderShadowTeardown, Input>;
|
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);
|
void run(const render::RenderContextPointer& renderContext, const Input& input);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,15 +14,21 @@
|
||||||
#include "RenderDeferredTask.h"
|
#include "RenderDeferredTask.h"
|
||||||
#include "RenderForwardTask.h"
|
#include "RenderForwardTask.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) {
|
void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) {
|
||||||
// auto items = input.get<Input>();
|
// auto items = input.get<Input>();
|
||||||
|
|
||||||
// Shadows use an orthographic projection because they are linked to sunlights
|
// 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.
|
// 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", [](const RenderArgs* args, const AABox& bounds) {
|
||||||
task.addJob<RenderShadowTask>("RenderShadowTask", nullptr);
|
// 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);
|
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
|
||||||
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||||
|
|
|
@ -11,53 +11,17 @@
|
||||||
<@if not SHADOW_SLH@>
|
<@if not SHADOW_SLH@>
|
||||||
<@def SHADOW_SLH@>
|
<@def SHADOW_SLH@>
|
||||||
|
|
||||||
|
<@include ShadowCore.slh@>
|
||||||
|
|
||||||
|
#define SHADOW_NOISE_ENABLED 0
|
||||||
|
#define SHADOW_SCREEN_SPACE_DITHER 1
|
||||||
|
|
||||||
// the shadow texture
|
// the shadow texture
|
||||||
uniform sampler2DShadow shadowMap;
|
uniform sampler2DShadow shadowMaps[SHADOW_CASCADE_MAX_COUNT];
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sample the shadowMap with PCF (built-in)
|
// Sample the shadowMap with PCF (built-in)
|
||||||
float fetchShadow(vec3 shadowTexcoord) {
|
float fetchShadow(int cascadeIndex, vec3 shadowTexcoord) {
|
||||||
return texture(shadowMap, shadowTexcoord);
|
return texture(shadowMaps[cascadeIndex], shadowTexcoord);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 PCFkernel[4] = vec2[4](
|
vec2 PCFkernel[4] = vec2[4](
|
||||||
|
@ -67,38 +31,83 @@ vec2 PCFkernel[4] = vec2[4](
|
||||||
vec2(0.5, -1.5)
|
vec2(0.5, -1.5)
|
||||||
);
|
);
|
||||||
|
|
||||||
float evalShadowAttenuationPCF(vec4 position, vec4 shadowTexcoord) {
|
float evalShadowNoise(vec4 seed) {
|
||||||
// PCF is buggy so disable it for the time being
|
float dot_product = dot(seed, vec4(12.9898,78.233,45.164,94.673));
|
||||||
#if 0
|
return fract(sin(dot_product) * 43758.5453);
|
||||||
float pcfRadius = 3.0;
|
}
|
||||||
|
|
||||||
|
struct ShadowSampleOffsets {
|
||||||
|
vec3 points[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) {
|
||||||
float shadowScale = getShadowScale();
|
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
|
// 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 * (
|
offsets.points[0] = shadowScale * vec3(offset + PCFkernel[0], 0.0);
|
||||||
fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[0], 0.0)) +
|
offsets.points[1] = shadowScale * vec3(offset + PCFkernel[1], 0.0);
|
||||||
fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[1], 0.0)) +
|
offsets.points[2] = shadowScale * vec3(offset + PCFkernel[2], 0.0);
|
||||||
fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[2], 0.0)) +
|
offsets.points[3] = shadowScale * vec3(offset + PCFkernel[3], 0.0);
|
||||||
fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[3], 0.0))
|
|
||||||
));
|
return offsets;
|
||||||
#else
|
}
|
||||||
float shadowAttenuation = fetchShadow(shadowTexcoord.xyz);
|
|
||||||
#endif
|
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;
|
return shadowAttenuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
float evalShadowAttenuation(vec4 position) {
|
float evalShadowCascadeAttenuation(int cascadeIndex, vec3 viewNormal, ShadowSampleOffsets offsets, vec4 shadowTexcoord) {
|
||||||
vec4 shadowTexcoord = evalShadowTexcoord(position);
|
if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) {
|
||||||
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) {
|
|
||||||
// If a point is not in the map, do not attenuate
|
// If a point is not in the map, do not attenuate
|
||||||
return 1.0;
|
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@>
|
<@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@>
|
<@include gpu/Color.slh@>
|
||||||
<$declareColorWheel()$>
|
<$declareColorWheel()$>
|
||||||
|
|
||||||
|
|
||||||
uniform sampler2D linearDepthMap;
|
uniform sampler2D linearDepthMap;
|
||||||
uniform sampler2D halfLinearDepthMap;
|
uniform sampler2D halfLinearDepthMap;
|
||||||
uniform sampler2D halfNormalMap;
|
uniform sampler2D halfNormalMap;
|
||||||
|
@ -24,6 +23,8 @@ uniform sampler2D occlusionMap;
|
||||||
uniform sampler2D occlusionBlurredMap;
|
uniform sampler2D occlusionBlurredMap;
|
||||||
uniform sampler2D scatteringMap;
|
uniform sampler2D scatteringMap;
|
||||||
|
|
||||||
|
<@include ShadowCore.slh@>
|
||||||
|
|
||||||
<$declareDeferredCurvature()$>
|
<$declareDeferredCurvature()$>
|
||||||
|
|
||||||
float curvatureAO(float k) {
|
float curvatureAO(float k) {
|
||||||
|
|
|
@ -26,8 +26,9 @@ void main(void) {
|
||||||
DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
|
DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
|
||||||
DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0);
|
DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0);
|
||||||
|
|
||||||
vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0);
|
vec4 viewPos = vec4(frag.position.xyz, 1.0);
|
||||||
float shadowAttenuation = evalShadowAttenuation(worldPos);
|
vec4 worldPos = getViewInverse() * viewPos;
|
||||||
|
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal);
|
||||||
|
|
||||||
if (frag.mode == FRAG_MODE_UNLIT) {
|
if (frag.mode == FRAG_MODE_UNLIT) {
|
||||||
discard;
|
discard;
|
||||||
|
|
|
@ -26,8 +26,9 @@ void main(void) {
|
||||||
DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
|
DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
|
||||||
DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0);
|
DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0);
|
||||||
|
|
||||||
vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0);
|
vec4 viewPos = vec4(frag.position.xyz, 1.0);
|
||||||
float shadowAttenuation = evalShadowAttenuation(worldPos);
|
vec4 worldPos = getViewInverse() * viewPos;
|
||||||
|
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal);
|
||||||
|
|
||||||
// Light mapped or not ?
|
// Light mapped or not ?
|
||||||
if (frag.mode == FRAG_MODE_UNLIT) {
|
if (frag.mode == FRAG_MODE_UNLIT) {
|
||||||
|
|
|
@ -82,28 +82,30 @@ void FetchSpatialTree::configure(const Config& config) {
|
||||||
_lodAngle = config.lodAngle;
|
_lodAngle = config.lodAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FetchSpatialTree::run(const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection) {
|
void FetchSpatialTree::run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection) {
|
||||||
assert(renderContext->args);
|
|
||||||
assert(renderContext->args->hasViewFrustum());
|
|
||||||
RenderArgs* args = renderContext->args;
|
|
||||||
auto& scene = renderContext->_scene;
|
|
||||||
|
|
||||||
// start fresh
|
// start fresh
|
||||||
outSelection.clear();
|
outSelection.clear();
|
||||||
|
|
||||||
// Eventually use a frozen frustum
|
if (!filter.selectsNothing()) {
|
||||||
auto queryFrustum = args->getViewFrustum();
|
assert(renderContext->args);
|
||||||
if (_freezeFrustum) {
|
assert(renderContext->args->hasViewFrustum());
|
||||||
if (_justFrozeFrustum) {
|
RenderArgs* args = renderContext->args;
|
||||||
_justFrozeFrustum = false;
|
auto& scene = renderContext->_scene;
|
||||||
_frozenFrutstum = args->getViewFrustum();
|
|
||||||
}
|
|
||||||
queryFrustum = _frozenFrutstum;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Octree selection!
|
// Eventually use a frozen frustum
|
||||||
float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
|
auto queryFrustum = args->getViewFrustum();
|
||||||
scene->getSpatialTree().selectCellItems(outSelection, _filter, queryFrustum, angle);
|
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) {
|
void CullSpatialSelection::configure(const Config& config) {
|
||||||
|
@ -113,11 +115,12 @@ void CullSpatialSelection::configure(const Config& config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems) {
|
const Inputs& inputs, ItemBounds& outItems) {
|
||||||
assert(renderContext->args);
|
assert(renderContext->args);
|
||||||
assert(renderContext->args->hasViewFrustum());
|
assert(renderContext->args->hasViewFrustum());
|
||||||
RenderArgs* args = renderContext->args;
|
RenderArgs* args = renderContext->args;
|
||||||
auto& scene = renderContext->_scene;
|
auto& scene = renderContext->_scene;
|
||||||
|
auto& inSelection = inputs.get0();
|
||||||
|
|
||||||
auto& details = args->_details.edit(_detailType);
|
auto& details = args->_details.edit(_detailType);
|
||||||
details._considered += (int)inSelection.numItems();
|
details._considered += (int)inSelection.numItems();
|
||||||
|
@ -126,9 +129,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
if (_freezeFrustum) {
|
if (_freezeFrustum) {
|
||||||
if (_justFrozeFrustum) {
|
if (_justFrozeFrustum) {
|
||||||
_justFrozeFrustum = false;
|
_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
|
// Culling Frustum / solidAngle test helper class
|
||||||
|
@ -181,122 +184,124 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
|
||||||
outItems.clear();
|
outItems.clear();
|
||||||
outItems.reserve(inSelection.numItems());
|
outItems.reserve(inSelection.numItems());
|
||||||
|
|
||||||
// Now get the bound, and
|
const auto filter = inputs.get1();
|
||||||
// filter individually against the _filter
|
if (!filter.selectsNothing()) {
|
||||||
// visibility cull if partially selected ( octree cell contianing it was partial)
|
// Now get the bound, and
|
||||||
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
|
// 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) {
|
if (_skipCulling) {
|
||||||
// inside & fit items: filter only, culling is disabled
|
// inside & fit items: filter only, culling is disabled
|
||||||
{
|
{
|
||||||
PerformanceTimer perfTimer("insideFitItems");
|
PerformanceTimer perfTimer("insideFitItems");
|
||||||
for (auto id : inSelection.insideItems) {
|
for (auto id : inSelection.insideItems) {
|
||||||
auto& item = scene->getItem(id);
|
auto& item = scene->getItem(id);
|
||||||
if (_filter.test(item.getKey())) {
|
if (filter.test(item.getKey())) {
|
||||||
ItemBound itemBound(id, item.getBound());
|
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)) {
|
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// partial & fit items: filter & frustum cull
|
// inside & subcell items: filter only, culling is disabled
|
||||||
{
|
{
|
||||||
PerformanceTimer perfTimer("partialFitItems");
|
PerformanceTimer perfTimer("insideSmallItems");
|
||||||
for (auto id : inSelection.partialItems) {
|
for (auto id : inSelection.insideSubcellItems) {
|
||||||
auto& item = scene->getItem(id);
|
auto& item = scene->getItem(id);
|
||||||
if (_filter.test(item.getKey())) {
|
if (filter.test(item.getKey())) {
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
if (test.frustumTest(itemBound.bound)) {
|
|
||||||
outItems.emplace_back(itemBound);
|
outItems.emplace_back(itemBound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// partial & subcell items:: filter & frutum cull & solidangle cull
|
// partial & fit items: filter only, culling is disabled
|
||||||
{
|
{
|
||||||
PerformanceTimer perfTimer("partialSmallItems");
|
PerformanceTimer perfTimer("partialFitItems");
|
||||||
for (auto id : inSelection.partialSubcellItems) {
|
for (auto id : inSelection.partialItems) {
|
||||||
auto& item = scene->getItem(id);
|
auto& item = scene->getItem(id);
|
||||||
if (_filter.test(item.getKey())) {
|
if (filter.test(item.getKey())) {
|
||||||
ItemBound itemBound(id, item.getBound());
|
ItemBound itemBound(id, item.getBound());
|
||||||
if (test.frustumTest(itemBound.bound)) {
|
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 (test.solidAngleTest(itemBound.bound)) {
|
||||||
outItems.emplace_back(itemBound);
|
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();
|
details._rendered += (int)outItems.size();
|
||||||
|
|
||||||
|
|
||||||
// Restore frustum if using the frozen one:
|
// Restore frustum if using the frozen one:
|
||||||
if (_freezeFrustum) {
|
if (_freezeFrustum) {
|
||||||
args->popViewFrustum();
|
args->popViewFrustum();
|
||||||
|
|
|
@ -51,19 +51,16 @@ namespace render {
|
||||||
class FetchSpatialTree {
|
class FetchSpatialTree {
|
||||||
bool _freezeFrustum{ false }; // initialized by Config
|
bool _freezeFrustum{ false }; // initialized by Config
|
||||||
bool _justFrozeFrustum{ false };
|
bool _justFrozeFrustum{ false };
|
||||||
ViewFrustum _frozenFrutstum;
|
ViewFrustum _frozenFrustum;
|
||||||
float _lodAngle;
|
float _lodAngle;
|
||||||
public:
|
public:
|
||||||
using Config = FetchSpatialTreeConfig;
|
using Config = FetchSpatialTreeConfig;
|
||||||
using JobModel = Job::ModelO<FetchSpatialTree, ItemSpatialTree::ItemSelection, Config>;
|
using JobModel = Job::ModelIO<FetchSpatialTree, ItemFilter, ItemSpatialTree::ItemSelection, Config>;
|
||||||
|
|
||||||
FetchSpatialTree() {}
|
FetchSpatialTree() {}
|
||||||
FetchSpatialTree(const ItemFilter& filter) : _filter(filter) {}
|
|
||||||
|
|
||||||
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
|
|
||||||
|
|
||||||
void configure(const Config& config);
|
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 {
|
class CullSpatialSelectionConfig : public Job::Config {
|
||||||
|
@ -88,25 +85,24 @@ namespace render {
|
||||||
bool _freezeFrustum{ false }; // initialized by Config
|
bool _freezeFrustum{ false }; // initialized by Config
|
||||||
bool _justFrozeFrustum{ false };
|
bool _justFrozeFrustum{ false };
|
||||||
bool _skipCulling{ false };
|
bool _skipCulling{ false };
|
||||||
ViewFrustum _frozenFrutstum;
|
ViewFrustum _frozenFrustum;
|
||||||
public:
|
public:
|
||||||
using Config = CullSpatialSelectionConfig;
|
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 },
|
_cullFunctor{ cullFunctor },
|
||||||
_detailType(type),
|
_detailType(type) {}
|
||||||
_filter(filter) {}
|
|
||||||
|
|
||||||
CullSpatialSelection(CullFunctor cullFunctor) :
|
CullSpatialSelection(CullFunctor cullFunctor) :
|
||||||
_cullFunctor{ cullFunctor } {}
|
_cullFunctor{ cullFunctor } {}
|
||||||
|
|
||||||
CullFunctor _cullFunctor;
|
CullFunctor _cullFunctor;
|
||||||
RenderDetails::Type _detailType{ RenderDetails::OTHER };
|
RenderDetails::Type _detailType{ RenderDetails::OTHER };
|
||||||
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
|
|
||||||
|
|
||||||
void configure(const Config& config);
|
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;
|
gpu::BufferView DrawFrustum::_frustumMeshIndices;
|
||||||
|
|
||||||
DrawFrustum::DrawFrustum(const glm::vec3& color) :
|
DrawFrustum::DrawFrustum(const glm::vec3& color) :
|
||||||
_color{ color } {
|
DrawQuadVolume{ 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawFrustum::run(const render::RenderContextPointer& renderContext, const Input& input) {
|
void DrawFrustum::run(const render::RenderContextPointer& renderContext, const Input& input) {
|
||||||
assert(renderContext->args);
|
|
||||||
assert(renderContext->args->_context);
|
|
||||||
|
|
||||||
RenderArgs* args = renderContext->args;
|
|
||||||
if (input) {
|
if (input) {
|
||||||
const auto& frustum = *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) {
|
if (!_frustumMeshIndices._buffer) {
|
||||||
auto indices = std::make_shared<gpu::Buffer>(sizeof(indexData), indexData);
|
auto indices = std::make_shared<gpu::Buffer>(sizeof(indexData), indexData);
|
||||||
_frustumMeshIndices = gpu::BufferView(indices, gpu::Element(gpu::SCALAR, gpu::UINT8, gpu::INDEX));
|
_frustumMeshIndices = gpu::BufferView(indices, gpu::Element(gpu::SCALAR, gpu::UINT8, gpu::INDEX));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_pipeline) {
|
glm::vec3 vertices[8];
|
||||||
auto vs = gpu::StandardShaderLib::getDrawTransformVertexPositionVS();
|
|
||||||
auto ps = gpu::StandardShaderLib::getDrawColorPS();
|
|
||||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
|
||||||
|
|
||||||
gpu::Shader::BindingSet slotBindings;
|
getVertices(frustum, vertices);
|
||||||
slotBindings.insert(gpu::Shader::Binding("color", 0));
|
|
||||||
gpu::Shader::makeProgram(*program, slotBindings);
|
|
||||||
|
|
||||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
DrawQuadVolume::run(renderContext, vertices, _frustumMeshIndices, sizeof(indexData) / sizeof(indexData[0]));
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawFrustum::updateFrustum(const ViewFrustum& frustum) {
|
void DrawFrustum::getVertices(const ViewFrustum& frustum, glm::vec3 vertices[8]) {
|
||||||
auto& vertices = _frustumMeshVertices.edit<std::array<glm::vec3, 8U> >();
|
|
||||||
vertices[0] = frustum.getNearTopLeft();
|
vertices[0] = frustum.getNearTopLeft();
|
||||||
vertices[1] = frustum.getNearTopRight();
|
vertices[1] = frustum.getNearTopRight();
|
||||||
vertices[2] = frustum.getNearBottomRight();
|
vertices[2] = frustum.getNearBottomRight();
|
||||||
|
|
|
@ -70,12 +70,12 @@ private:
|
||||||
int _colorLocation { -1 };
|
int _colorLocation { -1 };
|
||||||
};
|
};
|
||||||
|
|
||||||
class DrawFrustumConfig : public render::JobConfig {
|
class DrawQuadVolumeConfig : public render::JobConfig {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool isFrozen MEMBER isFrozen NOTIFY dirty)
|
Q_PROPERTY(bool isFrozen MEMBER isFrozen NOTIFY dirty)
|
||||||
public:
|
public:
|
||||||
|
|
||||||
DrawFrustumConfig(bool enabled = false) : JobConfig(enabled) {}
|
DrawQuadVolumeConfig(bool enabled = false) : JobConfig(enabled) {}
|
||||||
|
|
||||||
bool isFrozen{ false };
|
bool isFrozen{ false };
|
||||||
signals:
|
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:
|
public:
|
||||||
using Config = DrawFrustumConfig;
|
|
||||||
using Input = ViewFrustumPointer;
|
using Input = ViewFrustumPointer;
|
||||||
using JobModel = render::Job::ModelI<DrawFrustum, Input, Config>;
|
using JobModel = render::Job::ModelI<DrawFrustum, Input, Config>;
|
||||||
|
|
||||||
DrawFrustum(const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f));
|
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);
|
void run(const render::RenderContextPointer& renderContext, const Input& input);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static gpu::PipelinePointer _pipeline;
|
|
||||||
static gpu::BufferView _frustumMeshIndices;
|
static gpu::BufferView _frustumMeshIndices;
|
||||||
|
|
||||||
bool _updateFrustum{ true };
|
static void getVertices(const ViewFrustum& frustum, glm::vec3 vertices[8]);
|
||||||
gpu::BufferView _frustumMeshVertices;
|
|
||||||
gpu::BufferStream _frustumMeshStream;
|
|
||||||
glm::vec3 _color;
|
|
||||||
|
|
||||||
void updateFrustum(const ViewFrustum& frustum);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // hifi_render_DrawTask_h
|
#endif // hifi_render_DrawTask_h
|
||||||
|
|
|
@ -182,6 +182,8 @@ public:
|
||||||
Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
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& 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
|
// Convenient standard keys that we will keep on using all over the place
|
||||||
static Builder visibleWorldItems() { return Builder().withVisible().withWorldSpace(); }
|
static Builder visibleWorldItems() { return Builder().withVisible().withWorldSpace(); }
|
||||||
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
|
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
|
||||||
|
@ -191,12 +193,14 @@ public:
|
||||||
static Builder background() { return Builder().withViewSpace().withLayered(); }
|
static Builder background() { return Builder().withViewSpace().withLayered(); }
|
||||||
static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); }
|
static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); }
|
||||||
static Builder transparentShapeLayered() { return Builder().withTypeShape().withTransparent().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) {}
|
ItemFilter(const Builder& builder) : ItemFilter(builder._value, builder._mask) {}
|
||||||
|
|
||||||
// Item Filter operator testing if a key pass the filter
|
// Item Filter operator testing if a key pass the filter
|
||||||
bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); }
|
bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); }
|
||||||
|
bool selectsNothing() const { return !_mask.any(); }
|
||||||
|
|
||||||
class Less {
|
class Less {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -22,9 +22,11 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin
|
||||||
|
|
||||||
// CPU jobs:
|
// CPU jobs:
|
||||||
// Fetch and cull the items from the scene
|
// 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 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
|
// Overlays are not culled
|
||||||
const auto nonspatialSelection = task.addJob<FetchNonspatialItems>("FetchOverlaySelection");
|
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);
|
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
|
// Finally once sorted result to a list of itemID and keep uniques
|
||||||
render::ItemID previousID = Item::INVALID_ITEM_ID;
|
render::ItemID previousID = Item::INVALID_ITEM_ID;
|
||||||
if (!bounds) {
|
if (!bounds) {
|
||||||
|
|
|
@ -338,8 +338,6 @@ int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle
|
||||||
int clippedTriangleCount = 0;
|
int clippedTriangleCount = 0;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
assert(clippedTriangleCount > 0);
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++) {
|
for (i = 0; i < 3; i++) {
|
||||||
pointDistanceToPlane[i] = plane.distance(triangleVertices[i]);
|
pointDistanceToPlane[i] = plane.distance(triangleVertices[i]);
|
||||||
arePointsClipped.set(i, pointDistanceToPlane[i] < 0.0f);
|
arePointsClipped.set(i, pointDistanceToPlane[i] < 0.0f);
|
||||||
|
@ -424,7 +422,7 @@ int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int pl
|
||||||
|
|
||||||
*clippedTriangles = triangle;
|
*clippedTriangles = triangle;
|
||||||
|
|
||||||
while (planes < planesEnd) {
|
while (planes < planesEnd && triangleCount) {
|
||||||
int clippedSubTriangleCount;
|
int clippedSubTriangleCount;
|
||||||
|
|
||||||
trianglesToTest.clear();
|
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);
|
glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f);
|
||||||
top /= top.w;
|
top /= top.w;
|
||||||
_fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top))))));
|
_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()
|
// 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);
|
glm::vec3 normal = glm::normalize(_direction);
|
||||||
|
|
||||||
auto getCorner = [&](enum::BoxVertex nearCorner, enum::BoxVertex farCorner) {
|
auto getCorner = [&](enum::BoxVertex nearCorner, enum::BoxVertex farCorner) {
|
||||||
|
@ -750,3 +752,98 @@ void ViewFrustum::invalidate() {
|
||||||
}
|
}
|
||||||
_centerSphereRadius = -1.0e6f; // -10^6 should be negative enough
|
_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;
|
glm::vec3 bottomRight;
|
||||||
// Get the corners depth units from frustum position, along frustum orientation
|
// 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
|
// getters for corners
|
||||||
const glm::vec3& getFarTopLeft() const { return _cornersWorld[TOP_LEFT_FAR]; }
|
const glm::vec3& getFarTopLeft() const { return _cornersWorld[TOP_LEFT_FAR]; }
|
||||||
|
@ -87,6 +87,10 @@ public:
|
||||||
void setCenterRadius(float radius) { _centerSphereRadius = radius; }
|
void setCenterRadius(float radius) { _centerSphereRadius = radius; }
|
||||||
float getCenterRadius() const { return _centerSphereRadius; }
|
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();
|
void calculate();
|
||||||
|
|
||||||
typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection;
|
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 };
|
enum PlaneIndex { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE, NUM_PLANES };
|
||||||
|
|
||||||
const ::Plane* getPlanes() const { return _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
|
void invalidate(); // causes all reasonable intersection tests to fail
|
||||||
|
|
||||||
|
@ -172,6 +182,8 @@ private:
|
||||||
template <typename TBOX>
|
template <typename TBOX>
|
||||||
CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const;
|
CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const;
|
||||||
|
|
||||||
|
static void tesselateSides(const glm::vec3 points[8], Triangle triangles[8]);
|
||||||
|
|
||||||
};
|
};
|
||||||
using ViewFrustumPointer = std::shared_ptr<ViewFrustum>;
|
using ViewFrustumPointer = std::shared_ptr<ViewFrustum>;
|
||||||
|
|
||||||
|
|
|
@ -184,7 +184,11 @@ Rectangle {
|
||||||
ListElement { text: "Lightmap"; color: "White" }
|
ListElement { text: "Lightmap"; color: "White" }
|
||||||
ListElement { text: "Scattering"; color: "White" }
|
ListElement { text: "Scattering"; color: "White" }
|
||||||
ListElement { text: "Lighting"; 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: "Linear Depth"; color: "White" }
|
||||||
ListElement { text: "Half Linear Depth"; color: "White" }
|
ListElement { text: "Half Linear Depth"; color: "White" }
|
||||||
ListElement { text: "Half Normal"; color: "White" }
|
ListElement { text: "Half Normal"; color: "White" }
|
||||||
|
|
|
@ -15,15 +15,24 @@ Column {
|
||||||
id: root
|
id: root
|
||||||
spacing: 8
|
spacing: 8
|
||||||
property var viewConfig: Render.getConfig("RenderMainView.DrawViewFrustum");
|
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: {
|
Component.onCompleted: {
|
||||||
viewConfig.enabled = true;
|
viewConfig.enabled = true;
|
||||||
shadowConfig.enabled = true;
|
shadow0Config.enabled = true;
|
||||||
|
shadow1Config.enabled = true;
|
||||||
|
shadow2Config.enabled = true;
|
||||||
|
shadow3Config.enabled = true;
|
||||||
}
|
}
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
viewConfig.enabled = false;
|
viewConfig.enabled = false;
|
||||||
shadowConfig.enabled = false;
|
shadow0Config.enabled = false;
|
||||||
|
shadow1Config.enabled = false;
|
||||||
|
shadow2Config.enabled = false;
|
||||||
|
shadow3Config.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
|
@ -31,7 +40,14 @@ Column {
|
||||||
checked: false
|
checked: false
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
viewConfig.isFrozen = checked;
|
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 {
|
Row {
|
||||||
|
@ -46,5 +62,10 @@ Column {
|
||||||
color: "blue"
|
color: "blue"
|
||||||
font.italic: true
|
font.italic: true
|
||||||
}
|
}
|
||||||
|
Label {
|
||||||
|
text: "Items"
|
||||||
|
color: "magenta"
|
||||||
|
font.italic: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue