Graphics: Implement point-light shadow-mapping
This commit is contained in:
committed by
Jérôme Leclercq
parent
6731e07b54
commit
f8238a6e6c
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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*/)
|
||||
|
||||
150
src/Nazara/Graphics/PointLightShadowData.cpp
Normal file
150
src/Nazara/Graphics/PointLightShadowData.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user