Graphics: Implement point-light shadow-mapping

This commit is contained in:
SirLynix
2022-12-03 17:16:30 +01:00
committed by Jérôme Leclercq
parent 6731e07b54
commit f8238a6e6c
17 changed files with 333 additions and 62 deletions

View File

@@ -712,9 +712,13 @@ namespace Nz
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, input.attachmentId);
TextureLayout& textureLayout = textureLayouts[textureId];
assert(textureLayouts[textureId] != TextureLayout::Undefined);
textureLayout = TextureLayout::ColorInput;
if (!input.assumedLayout)
{
assert(textureLayouts[textureId] != TextureLayout::Undefined);
textureLayout = TextureLayout::ColorInput;
}
else
textureLayout = *input.assumedLayout;
};
auto RegisterColorOutput = [&](const FramePass::Output& output, bool shouldLoad)
@@ -949,7 +953,21 @@ namespace Nz
return false;
}
void FrameGraph::RegisterPassInput(std::size_t passIndex, std::size_t attachmentIndex)
{
auto it = m_pending.attachmentWriteList.find(attachmentIndex);
if (it != m_pending.attachmentWriteList.end())
{
const PassList& dependencyPassList = it->second;
for (std::size_t dependencyPass : dependencyPassList)
{
if (dependencyPass != passIndex)
TraverseGraph(dependencyPass);
}
}
}
std::size_t FrameGraph::RegisterTexture(std::size_t attachmentIndex)
{
if (auto it = m_pending.attachmentToTextures.find(attachmentIndex); it != m_pending.attachmentToTextures.end())
@@ -1126,31 +1144,9 @@ namespace Nz
const FramePass& framePass = m_framePasses[passIndex];
for (const auto& input : framePass.GetInputs())
{
auto it = m_pending.attachmentWriteList.find(input.attachmentId);
if (it != m_pending.attachmentWriteList.end())
{
const PassList& dependencyPassList = it->second;
for (std::size_t dependencyPass : dependencyPassList)
{
if (dependencyPass != passIndex)
TraverseGraph(dependencyPass);
}
}
}
RegisterPassInput(passIndex, input.attachmentId);
if (std::size_t dsInput = framePass.GetDepthStencilInput(); dsInput != FramePass::InvalidAttachmentId)
{
auto it = m_pending.attachmentWriteList.find(dsInput);
if (it != m_pending.attachmentWriteList.end())
{
const PassList& dependencyPassList = it->second;
for (std::size_t dependencyPass : dependencyPassList)
{
if (dependencyPass != passIndex)
TraverseGraph(dependencyPass);
}
}
}
RegisterPassInput(passIndex, dsInput);
}
}

View File

@@ -5,6 +5,7 @@
#include <Nazara/Graphics/PointLight.hpp>
#include <Nazara/Graphics/Enums.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/PointLightShadowData.hpp>
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
#include <Nazara/Math/Vector2.hpp>
#include <Nazara/Math/Vector3.hpp>
@@ -26,13 +27,14 @@ namespace Nz
AccessByOffset<UInt32&>(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Point);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r, m_color.g, m_color.b, m_color.a);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_position.x, m_position.y, m_position.z, m_invRadius);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.shadowMapSize) = Vector2f(-1.f, -1.f);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_position.x, m_position.y, m_position.z, 0.f);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter2) = Vector4f(m_radius, m_invRadius, 0.f, 0.f);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.shadowMapSize) = (IsShadowCaster()) ? Vector2f(1.f / GetShadowMapSize()) : Vector2f(-1.f, -1.f);
}
std::unique_ptr<LightShadowData> PointLight::InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const
{
return nullptr; //< TODO
return std::make_unique<PointLightShadowData>(pipeline, elementRegistry, *this);
}
void PointLight::UpdateTransform(const Vector3f& position, const Quaternionf& /*rotation*/, const Vector3f& /*scale*/)

View File

@@ -0,0 +1,150 @@
// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/PointLightShadowData.hpp>
#include <Nazara/Graphics/BakedFrameGraph.hpp>
#include <Nazara/Graphics/FrameGraph.hpp>
#include <Nazara/Graphics/FramePipeline.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/PointLight.hpp>
#include <Nazara/Math/Quaternion.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
namespace
{
// TODO: Make it constexpr
std::array s_dirRotations = {
Quaternionf::RotationBetween(Vector3f::Forward(), Vector3f::UnitX()),
Quaternionf::RotationBetween(Vector3f::Forward(), -Vector3f::UnitX()),
Quaternionf::RotationBetween(Vector3f::Forward(), Vector3f::UnitY()),
Quaternionf::RotationBetween(Vector3f::Forward(), -Vector3f::UnitY()),
Quaternionf::Identity(),
Quaternionf(0.f, 0.f, 1.f, 0.f)
};
constexpr std::array<std::string_view, 6> s_dirNames = {
"Point-light shadow mapping +X",
"Point-light shadow mapping -X",
"Point-light shadow mapping +Y",
"Point-light shadow mapping -Y",
"Point-light shadow mapping +Z",
"Point-light shadow mapping -Z"
};
}
PointLightShadowData::PointLightShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry, const PointLight& light) :
m_pipeline(pipeline),
m_light(light)
{
m_onLightShadowMapSettingChange.Connect(m_light.OnLightShadowMapSettingChange, [this](Light* /*light*/, PixelFormat /*newPixelFormat*/, UInt32 newSize)
{
for (DirectionData& direction : m_directions)
direction.viewer.UpdateViewport(Recti(0, 0, SafeCast<int>(newSize), SafeCast<int>(newSize)));
});
m_onLightTransformInvalidated.Connect(m_light.OnLightTransformInvalided, [this]([[maybe_unused]] Light* light)
{
assert(&m_light == light);
for (std::size_t i = 0; i < m_directions.size(); ++i)
{
DirectionData& direction = m_directions[i];
ViewerInstance& viewerInstance = direction.viewer.GetViewerInstance();
viewerInstance.UpdateEyePosition(m_light.GetPosition());
viewerInstance.UpdateViewMatrix(Matrix4f::TransformInverse(m_light.GetPosition(), s_dirRotations[i]));
m_pipeline.QueueTransfer(&viewerInstance);
}
});
std::size_t shadowPassIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex("ShadowPass");
UInt32 shadowMapSize = light.GetShadowMapSize();
for (std::size_t i = 0; i < m_directions.size(); ++i)
{
ShadowViewer& viewer = m_directions[i].viewer;
viewer.UpdateRenderMask(0xFFFFFFFF);
viewer.UpdateViewport(Recti(0, 0, SafeCast<int>(shadowMapSize), SafeCast<int>(shadowMapSize)));
ViewerInstance& viewerInstance = viewer.GetViewerInstance();
viewerInstance.UpdateProjectionMatrix(Matrix4f::Perspective(RadianAnglef(HalfPi<float>), 1.f, 0.01f, m_light.GetRadius()));
viewerInstance.UpdateEyePosition(m_light.GetPosition());
viewerInstance.UpdateViewMatrix(Matrix4f::TransformInverse(m_light.GetPosition(), s_dirRotations[i]));
m_pipeline.QueueTransfer(&viewerInstance);
m_directions[i].depthPass.emplace(m_pipeline, elementRegistry, &viewer, shadowPassIndex, std::string(s_dirNames[i]));
}
m_pipeline.ForEachRegisteredMaterialInstance([this](const MaterialInstance& matInstance)
{
for (DirectionData& direction : m_directions)
direction.depthPass->RegisterMaterialInstance(matInstance);
});
}
void PointLightShadowData::PrepareRendering(RenderFrame& renderFrame)
{
for (DirectionData& direction : m_directions)
{
const Matrix4f& viewProjMatrix = direction.viewer.GetViewerInstance().GetViewProjMatrix();
Frustumf frustum = Frustumf::Extract(viewProjMatrix);
std::size_t visibilityHash = 5U;
const auto& visibleRenderables = m_pipeline.FrustumCull(frustum, 0xFFFFFFFF, visibilityHash);
direction.depthPass->Prepare(renderFrame, frustum, visibleRenderables, visibilityHash);
}
}
void PointLightShadowData::RegisterMaterialInstance(const MaterialInstance& matInstance)
{
for (DirectionData& direction : m_directions)
direction.depthPass->RegisterMaterialInstance(matInstance);
}
void PointLightShadowData::RegisterPassInputs(FramePass& pass)
{
std::size_t cubeInputIndex = pass.AddInput(m_cubeAttachmentIndex);
pass.SetInputLayout(cubeInputIndex, TextureLayout::ColorInput);
for (DirectionData& direction : m_directions)
pass.AddInput(direction.attachmentIndex);
}
void PointLightShadowData::RegisterToFrameGraph(FrameGraph& frameGraph)
{
UInt32 shadowMapSize = m_light.GetShadowMapSize();
m_cubeAttachmentIndex = frameGraph.AddAttachmentCube({
"Point-light shadowmap",
m_light.GetShadowMapFormat(),
FramePassAttachmentSize::Fixed,
shadowMapSize, shadowMapSize,
});
for (std::size_t i = 0; i < m_directions.size(); ++i)
{
DirectionData& direction = m_directions[i];
direction.attachmentIndex = frameGraph.AddAttachmentCubeFace(m_cubeAttachmentIndex, static_cast<CubemapFace>(i));
direction.depthPass->RegisterToFrameGraph(frameGraph, direction.attachmentIndex);
}
}
const Texture* PointLightShadowData::RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph) const
{
return bakedGraph.GetAttachmentTexture(m_cubeAttachmentIndex).get();
}
void PointLightShadowData::UnregisterMaterialInstance(const MaterialInstance& matInstance)
{
for (DirectionData& direction : m_directions)
direction.depthPass->UnregisterMaterialInstance(matInstance);
}
}

View File

@@ -98,7 +98,7 @@ external
[tag("SkeletalData")] skeletalData: uniform[SkeletalData],
[tag("LightData")] lightData: uniform[LightData],
[tag("ShadowMaps2D")] shadowMaps2D: array[depth_sampler2D[f32], MaxLightCount],
[tag("ShadowMapsCube")] shadowMapsCube: array[depth_sampler_cube[f32], MaxLightCount]
[tag("ShadowMapsCube")] shadowMapsCube: array[sampler_cube[f32], MaxLightCount]
}
struct VertToFrag
@@ -118,6 +118,11 @@ struct FragOut
[location(0)] RenderTarget0: vec4[f32]
}
fn LinearizeDepth(depth: f32, zNear: f32, zFar: f32) -> f32
{
return zNear * zFar / (zFar + depth * (zNear - zFar));
}
[entry(frag), cond(!DepthPass || AlphaTest)]
fn main(input: VertToFrag) -> FragOut
{
@@ -189,25 +194,60 @@ fn main(input: VertToFrag) -> FragOut
else if (light.type == PointLight)
{
let lightPos = light.parameter1.xyz;
let lightInvRadius = light.parameter1.w;
let lightRadius = light.parameter2.x;
let lightInvRadius = light.parameter2.y;
let lightToPos = input.worldPos - lightPos;
let dist = length(lightToPos);
let lightToPosNorm = lightToPos / max(dist, 0.0001);
let attenuationFactor = max(1.0 - dist * lightInvRadius, 0.0);
lightAmbient += attenuationFactor * light.color.rgb * lightAmbientFactor * settings.AmbientColor.rgb;
let lambert = max(dot(normal, -lightToPosNorm), 0.0);
lightDiffuse += attenuationFactor * lambert * light.color.rgb * lightDiffuseFactor;
let reflection = reflect(lightToPosNorm, normal);
let specFactor = max(dot(reflection, eyeVec), 0.0);
specFactor = pow(specFactor, settings.Shininess);
lightSpecular += attenuationFactor * specFactor * light.color.rgb;
let shadowFactor = 1.0;
if (light.invShadowMapSize.x > 0.0)
{
shadowFactor = 0.0;
let sampleDir = vec3[f32](lightToPosNorm.x, lightToPosNorm.y, -lightToPosNorm.z);
const sampleCount = 4;
const offset = 0.005;
const bias = 0.05;
const invSampleCount = 1.0 / f32(sampleCount);
const start = vec3[f32](offset * 0.5, offset * 0.5, offset * 0.5);
const shadowContribution = 1.0 / f32(sampleCount * sampleCount * sampleCount);
[unroll]
for x in 0 -> sampleCount
{
[unroll]
for y in 0 -> sampleCount
{
[unroll]
for z in 0 -> sampleCount
{
let dirOffset = vec3[f32](f32(x), f32(y), f32(z)) * invSampleCount * offset - start;
let sampleDir = sampleDir + dirOffset;
let depth = shadowMapsCube[i].Sample(sampleDir).r;
depth = LinearizeDepth(depth, 0.01, lightRadius);
if (depth > dist - bias)
shadowFactor += shadowContribution;
}
}
}
}
lightAmbient += attenuationFactor * light.color.rgb * lightAmbientFactor * settings.AmbientColor.rgb;
lightDiffuse += shadowFactor * attenuationFactor * lambert * light.color.rgb * lightDiffuseFactor;
lightSpecular += shadowFactor * attenuationFactor * specFactor * light.color.rgb;
}
else if (light.type == SpotLight)
{
@@ -233,10 +273,11 @@ fn main(input: VertToFrag) -> FragOut
let specFactor = max(dot(reflection, eyeVec), 0.0);
specFactor = pow(specFactor, settings.Shininess);
let shadowFactor = 0.0;
let shadowFactor = 1.0;
if (light.invShadowMapSize.x > 0.0)
{
let shadowCoords = input.lightProjPos[i].xyz / input.lightProjPos[i].w;
shadowFactor = 0.0;
[unroll]
for x in -1 -> 2
{

View File

@@ -14,10 +14,10 @@ namespace Nz
{
SpotLightShadowData::SpotLightShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry, const SpotLight& light) :
m_pipeline(pipeline),
m_light(light),
m_viewer(Recti(0, 0, 1, 1), 0xFFFFFFFF)
m_light(light)
{
UInt32 shadowMapSize = light.GetShadowMapSize();
m_viewer.UpdateRenderMask(0xFFFFFFFF);
m_viewer.UpdateViewport(Recti(0, 0, SafeCast<int>(shadowMapSize), SafeCast<int>(shadowMapSize)));
ViewerInstance& viewerInstance = m_viewer.GetViewerInstance();

View File

@@ -198,7 +198,7 @@ namespace Nz
auto& bindingEntry = m_bindingCache.emplace_back();
bindingEntry.bindingIndex = bindingIndex;
bindingEntry.content = ShaderBinding::TextureBindings {
SafeCast<UInt32>(renderState.shadowMaps2D.size()), &m_textureBindingCache[textureBindingBaseIndex]
SafeCast<UInt32>(renderState.shadowMapsCube.size()), &m_textureBindingCache[textureBindingBaseIndex]
};
}

View File

@@ -128,7 +128,7 @@ namespace Nz
}
}
void OpenGLShaderBinding::UpdateDebugName(std::string_view name)
void OpenGLShaderBinding::UpdateDebugName(std::string_view /*name*/)
{
// No OpenGL object to name
}