Skip to content

Instantly share code, notes, and snippets.

@Reputeless
Created November 18, 2023 13:32
Show Gist options
  • Select an option

  • Save Reputeless/61c4948459409f548a5622e7863b6a40 to your computer and use it in GitHub Desktop.

Select an option

Save Reputeless/61c4948459409f548a5622e7863b6a40 to your computer and use it in GitHub Desktop.
# include <Siv3D.hpp> // Siv3D v0.6.13
class ShadowMap
{
public:
struct Config
{
Size resolution = Size{ 1024, 1024 };
double lightBleedingReduction = 0.8;
bool useMSAA = false;
bool blur = false;
bool useMipMap = false;
};
struct Light
{
Vec3 focusPos{ 0, 0, 0 };
Vec3 sunPos = (focusPos + Graphics3D::DefaultSunDirection * 64.0);
Vec3 upDirection{ 0, 1, 0 };
double viewSize = 64.0;
double nearZ = 1.0;
double farZ = 1024.0;
};
ShadowMap() = default;
explicit ShadowMap(const Config& config)
: m_config{ config }
, m_initialized{ true }
{
updateTextures();
}
void beginDepth(const Light& light)
{
if (not m_initialized)
{
throw Error{ U"ShadowMap::beginDepth() called before initialization" };
}
if (m_depthPass)
{
throw Error{ U"ShadowMap::beginDepth() called twice" };
}
if (m_shadowPass)
{
throw Error{ U"ShadowMap::beginDepth() called while shadow pass is active" };
}
m_depthPass.emplace();
m_depthPass->oldCameraMat = Graphics3D::GetCameraTransform();
m_depthPass->oldEyePosition = Graphics3D::GetEyePosition();
m_depthPass->renderTarget = std::make_unique<ScopedRenderTarget3D>(m_textures[0]->clear(ColorF{ 0.0 }));
m_depthPass->renderStates = std::make_unique<ScopedRenderStates3D>(BlendState::Opaque);
m_camera.update(light);
Graphics3D::SetCameraTransform(m_camera.viewProj, light.sunPos);
m_cbp->worldToShadowMap = (m_camera.viewProj * Mat4x4::Scale(Float3{ 0.5, 0.5, 1.0 }) * Mat4x4::Translate(Float3{ 0.5, 0.5, 0.0 }));
m_cbp->sunPosition = light.sunPos;
m_cbp->lightBleedingReduction = static_cast<float>(m_config.lightBleedingReduction);
Graphics3D::SetPSConstantBuffer(4, m_cbp);
}
void endDepth()
{
if (not m_depthPass)
{
throw Error{ U"ShadowMap::endDepth() called without beginDepth()" };
}
if (m_shadowPass)
{
throw Error{ U"ShadowMap::endDepth() called while shadow pass is active" };
}
Graphics3D::SetCameraTransform(m_depthPass->oldCameraMat, m_depthPass->oldEyePosition);
m_depthPass.reset();
Graphics3D::Flush();
if (m_config.useMSAA)
{
dynamic_cast<MSRenderTexture*>(m_textures[0].get())->resolve();
}
if (m_config.blur)
{
if (m_config.useMSAA)
{
Shader::GaussianBlur(*m_textures[0], *m_textures[1], *m_textures[2]);
}
else
{
Shader::GaussianBlur(*m_textures[0], *m_textures[1], *m_textures[0]);
}
Graphics2D::Flush();
}
if (m_config.useMipMap)
{
getDepthTexture().generateMips();
}
}
void beginShadow(const Light& light)
{
if (not m_initialized)
{
throw Error{ U"ShadowMap::beginShadow() called before initialization" };
}
if (m_shadowPass)
{
throw Error{ U"ShadowMap::beginShadow() called twice" };
}
if (m_depthPass)
{
throw Error{ U"ShadowMap::beginShadow() called while depth pass is active" };
}
m_shadowPass.emplace();
m_shadowPass->pRenderStates = std::make_unique<ScopedRenderStates3D>(ScopedRenderStates3D::SamplerStateInfo{ ShaderStage::Pixel, 3, (m_config.useMipMap ? SamplerState::ClampAniso : SamplerState::ClampLinear) });
Graphics3D::SetPSTexture(3, getDepthTexture());
Graphics3D::SetPSConstantBuffer(4, m_cbp);
}
void endShadow()
{
if (not m_shadowPass)
{
throw Error{ U"ShadowMap::endShadow() called without beginShadow()" };
}
if (m_depthPass)
{
throw Error{ U"ShadowMap::endShadow() called while depth pass is active" };
}
Graphics3D::SetPSTexture(3, none);
m_shadowPass.reset();
}
void setConfig(const Config& config)
{
if (m_depthPass || m_shadowPass)
{
throw Error{ U"ShadowMap::setConfig() called while depth or shadow pass is active" };
}
m_config = config;
updateTextures();
}
[[nodiscard]]
const RenderTexture& getDepthTexture() const noexcept
{
return *m_textures[getDepthTextureIndex()];
}
private:
struct TextureState
{
Size resolution{ 0, 0 };
bool isRequired = false;
bool useMSAA = false;
bool useDepth = false;
bool useMipMap = false;
[[nodiscard]]
bool operator ==(const TextureState&) const = default;
};
struct DepthCamera
{
Mat4x4 viewProj = Mat4x4::Identity();
void update(const Light& light)
{
const SIMD_Float4 eyePosition{ light.sunPos, 0.0f };
const SIMD_Float4 focusPosition{ light.focusPos, 0.0f };
const SIMD_Float4 upDirection{ light.upDirection, 0.0f };
const Mat4x4 view = DirectX::XMMatrixLookAtLH(eyePosition, focusPosition, upDirection);
const float viewF = static_cast<float>(light.viewSize);
const Mat4x4 proj = DirectX::XMMatrixOrthographicLH(viewF, viewF, static_cast<float>(light.farZ), static_cast<float>(light.nearZ));
viewProj = (view * proj);
}
};
struct alignas(16) DepthPass
{
Mat4x4 oldCameraMat = Mat4x4::Identity();
Float3 oldEyePosition{ 0, 0, 0 };
std::unique_ptr<ScopedRenderTarget3D> renderTarget;
std::unique_ptr<ScopedRenderStates3D> renderStates;
};
struct ShadowPass
{
std::unique_ptr<ScopedRenderStates3D> pRenderStates;
};
struct PSShadow
{
Mat4x4 worldToShadowMap;
Float3 sunPosition{ 0,0,0 };
float lightBleedingReduction = 0.8f;
};
static constexpr TextureFormat DepthFormat = TextureFormat::R32G32_Float;
Config m_config;
std::array<TextureState, 3> m_currentTextureStates;
std::array<std::unique_ptr<RenderTexture>, 3> m_textures;
DepthCamera m_camera;
Optional<DepthPass> m_depthPass;
Optional<ShadowPass> m_shadowPass;
ConstantBuffer<PSShadow> m_cbp;
bool m_initialized = false;
static void CreateDepthTexture(std::unique_ptr<RenderTexture>& depthTexture, const Config& config)
{
if (depthTexture)
{
const bool currentMSAA = (dynamic_cast<const MSRenderTexture*>(depthTexture.get()) != nullptr);
const bool currentMipMap = depthTexture->hasMipMap();
const Size currentResolution = depthTexture->size();
if ((config.useMSAA == currentMSAA)
&& (config.useMipMap == currentMipMap)
&& (config.resolution == currentResolution))
{
return;
}
}
depthTexture.reset();
if (config.useMSAA)
{
depthTexture = std::make_unique<MSRenderTexture>(config.resolution, DepthFormat, HasDepth::Yes, HasMipMap{config.useMipMap});
}
else
{
depthTexture = std::make_unique<RenderTexture>(config.resolution, DepthFormat, HasDepth::Yes, HasMipMap{ config.useMipMap });
}
}
static void CreateInternalTexture(std::unique_ptr<RenderTexture>& internalTexture1, std::unique_ptr<RenderTexture>& internalTexture2, const Config& config)
{
{
if (internalTexture1)
{
const Size currentResolution = internalTexture1->size();
if (config.resolution == currentResolution)
{
return;
}
}
internalTexture1.reset();
internalTexture1 = std::make_unique<RenderTexture>(config.resolution, DepthFormat);
}
{
if (not config.useMSAA)
{
if (internalTexture2)
{
internalTexture2.reset();
return;
}
}
if (internalTexture2)
{
const Size currentResolution = internalTexture2->size();
if (config.resolution == currentResolution)
{
return;
}
}
internalTexture2.reset();
internalTexture2 = std::make_unique<RenderTexture>(config.resolution, DepthFormat);
}
}
[[nodiscard]]
size_t getDepthTextureIndex() const noexcept
{
if (m_config.blur)
{
return (m_config.useMSAA ? 2 : 1);
}
else
{
return 0;
}
}
[[nodiscard]]
static std::unique_ptr<RenderTexture> CreateRenderTexture(const TextureState& textureState)
{
if (not textureState.isRequired)
{
return nullptr;
}
if (textureState.useMSAA)
{
return std::make_unique<MSRenderTexture>(
textureState.resolution,
DepthFormat,
HasDepth{ textureState.useDepth },
HasMipMap{ textureState.useMipMap });
}
else
{
return std::make_unique<RenderTexture>(
textureState.resolution,
DepthFormat,
HasDepth{ textureState.useDepth },
HasMipMap{ textureState.useMipMap });
}
}
void updateTextures()
{
if (not m_initialized)
{
return;
}
const size_t depthTextureIndex = getDepthTextureIndex();
std::array<TextureState, 3> requiredTextureStates;
// [0]
{
requiredTextureStates[0] =
{
.resolution = m_config.resolution,
.isRequired = true,
.useMSAA = m_config.useMSAA,
.useDepth = true,
.useMipMap = (m_config.useMipMap && (depthTextureIndex == 0)),
};
}
// [1]
{
requiredTextureStates[1] =
{
.resolution = m_config.resolution,
.isRequired = m_config.blur,
.useMSAA = false,
.useDepth = false,
.useMipMap = (m_config.useMipMap && (depthTextureIndex == 1)),
};
}
// [2]
{
requiredTextureStates[2] =
{
.resolution = m_config.resolution,
.isRequired = (m_config.blur && m_config.useMSAA),
.useMSAA = false,
.useDepth = false,
.useMipMap = (m_config.useMipMap && (depthTextureIndex == 2)),
};
}
for (size_t i = 0; i < 3; ++i)
{
if (m_currentTextureStates[i] == requiredTextureStates[i])
{
continue;
}
m_textures[i] = CreateRenderTexture(requiredTextureStates[i]);
m_currentTextureStates[i] = requiredTextureStates[i];
}
}
};
void DrawObjects(double time)
{
Box::FromPoints({ -8, 0, 5 }, { 0, 0.5, 7 }).draw();
Box::FromPoints({ -0.5, 0, 5 }, { 0, 1.8, 7 }).draw();
Box::FromPoints({ -0.5, 1.8, 5 }, { 6, 2.0, 7 }).draw();
for (int32 x = -2; x <= 2; ++x)
{
Cylinder{ Vec3{ (x * 4), 0, 4 }, Vec3{ (x * 4), 4, 4 }, 0.3 }.draw(HSV{ x * 80, 0.3, 1.0 }.removeSRGBCurve());
}
for (int32 y = 2; y < 8; ++y)
{
Cylinder{ Vec3{ 0, y, -4 }, Vec3{ 4, y, -4 }, 0.1 }.draw(HSV{ 80, 0.3, 1.0 }.removeSRGBCurve());
}
Cylinder{ Vec3{ 0, 0, -4 }, Vec3{ 0, 8, -4 }, 0.1 }.draw(HSV{ 80, 0.3, 1.0 }.removeSRGBCurve());
Cylinder{ Vec3{ 0, 0, -4 }, Vec3{ 0, 0.2, -4 }, 0.5 }.draw(HSV{ 80, 0.3, 1.0 }.removeSRGBCurve());
Cylinder{ Vec3{ 4, 0, -4 }, Vec3{ 4, 8, -4 }, 0.1 }.draw(HSV{ 80, 0.3, 1.0 }.removeSRGBCurve());
Cylinder{ Vec3{ 4, 0, -4 }, Vec3{ 4, 0.2, -4 }, 0.5 }.draw(HSV{ 80, 0.3, 1.0 }.removeSRGBCurve());
Cylinder{ Vec3{ 8, 1.0, -5 }, Vec3{ 8, 9.0, -5 }, 0.1 }.draw(HSV{ 160, 0.3, 1.0 }.removeSRGBCurve());
Cylinder{ Vec3{ 8, 1.0, -5 }, Vec3{ 8, 1.2, -5 }, 0.5 }.draw(HSV{ 160, 0.3, 1.0 }.removeSRGBCurve());
Box::FromPoints({ -8, 0, -2 }, { -7, 8, 2 }).draw();
Box::FromPoints({ -6, 0, -8 }, { -5.5, 6, -4 }).oriented(Quaternion::RotateY(-45_deg)).draw(Linear::Palette::Limegreen);
Box::FromPoints({ 6, 0, -6 }, { 9, 1, -3 }).oriented(Quaternion::RotateY(-45_deg)).draw(Linear::Palette::Skyblue);
const double sphereY = (2 + Periodic::Jump0_1(1.6s, time) * 4);
Sphere{ Vec3{ 1, sphereY, 0 }, 2.0 }.draw();
OrientedBox{ Arg::bottomCenter(6, 0, 0), { 4, 1.5, 0.2 }, Quaternion::RotateY(time * 50_deg) }.draw();
Cylinder{ Vec3{ 6, 0, 0 }, Vec3{ 6, 2, 0 }, 0.2 }.draw(Linear::Palette::Purple);
OrientedBox{ Vec3{ -2, 6, -6 }, 1, 8, 1, Quaternion::RollPitchYaw(0, 45_deg, time * 60_deg) }.draw(Linear::Palette::Skyblue);
OrientedBox{ Vec3{ -2, 6, -6 }, 8, 1, 1, Quaternion::RollPitchYaw(0, 45_deg, time * 60_deg) }.draw(Linear::Palette::Skyblue);
}
void Main()
{
Window::Resize(1280, 720);
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
const Texture uvChecker{ U"example/texture/uv.png", TextureDesc::MippedSRGB };
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };
size_t resolutionOption = 4;
ShadowMap::Config config
{
.resolution = Size{ 1024, 1024 },
.lightBleedingReduction = 0.8,
.useMSAA = false,
.blur = false,
.useMipMap = false,
};
ShadowMap::Light light
{
.focusPos = Vec3{ 0, 0, 0 },
.viewSize = 22.0,
};
ShadowMap shadowMap{ config };
const PixelShader psDepth = HLSL{ U"shadow.hlsl", U"Depth_PS" };
const PixelShader psShadow = HLSL{ U"shadow.hlsl", U"Shading_PS" };
double time = 8.0;
bool paused = true;
bool showDepth = true;
bool showUI = true;
while (System::Update())
{
if (not paused)
{
time += Scene::DeltaTime();
}
camera.update(2.0);
Graphics3D::SetCameraTransform(camera);
double halfDay0_1 = Periodic::Sawtooth0_1(60s, time);
const Quaternion q = (Quaternion::RotateY(halfDay0_1 * 180_deg) * Quaternion::RotateX(50_deg));
const Vec3 sunDirection = (q * Vec3::Right());
Graphics3D::SetSunDirection(sunDirection);
Graphics3D::SetGlobalAmbientColor(ColorF{ 0.25 });
{
light.sunPos = (light.focusPos + sunDirection * 64.0);
shadowMap.beginDepth(light);
{
const ScopedCustomShader3D shader{ psDepth };
Plane{ 64 }.draw();
DrawObjects(time);
}
shadowMap.endDepth();
}
// 3D scene
{
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
shadowMap.beginShadow(light);
{
const ScopedCustomShader3D shader{ psShadow };
Plane{ 64 }.draw(uvChecker);
DrawObjects(time);
}
shadowMap.endShadow();
}
{
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
if (showUI)
{
//Print << U"shadowCenter: {:.1f}"_fmt(shadowParameter.shadowCenter);
if (SimpleGUI::Slider(U"lightBleedingReduction: {:.2f}"_fmt(config.lightBleedingReduction), config.lightBleedingReduction, 0.0, 1.0, Vec2{ 860, 40 }, 280, 120))
{
shadowMap.setConfig(config);
}
if (SimpleGUI::CheckBox(config.useMSAA, U"MSAA", Vec2{ 1100, 80 }, 160))
{
shadowMap.setConfig(config);
}
if (SimpleGUI::CheckBox(config.blur, U"blur", Vec2{ 1100, 120 }, 160))
{
shadowMap.setConfig(config);
}
if (SimpleGUI::CheckBox(config.useMipMap, U"MipMap", Vec2{ 1100, 160 }, 160))
{
shadowMap.setConfig(config);
}
if (SimpleGUI::RadioButtons(resolutionOption, { U"256x256", U"256x512", U"512x512", U"512x1024", U"1024x1024", U"1024x2048", U"2048x2048", U"2048x4096", U"4096x4096" }, Vec2{ 1100, 200 }, 160))
{
config.resolution =
(resolutionOption == 0) ? Size{ 256, 256 }
: (resolutionOption == 1) ? Size{ 256, 512 }
: (resolutionOption == 2) ? Size{ 512, 512 }
: (resolutionOption == 3) ? Size{ 512, 1024 }
: (resolutionOption == 4) ? Size{ 1024, 1024 }
: (resolutionOption == 5) ? Size{ 1024, 2048 }
: (resolutionOption == 6) ? Size{ 2048, 2048 }
: (resolutionOption == 7) ? Size{ 2048, 4096 }
: Size{ 4096, 4096 };
shadowMap.setConfig(config);
}
SimpleGUI::CheckBox(showDepth, U"Show depth", Vec2{ 1100, 560 }, 160);
SimpleGUI::CheckBox(paused, U"Pause", Vec2{ 1100, 600 }, 160);
if (showDepth)
{
shadowMap.getDepthTexture().resized(512, 512).draw(0, 0, ColorF{ 1 / 100.0, 1 / 10000.0, 0.0 });
PutText(U"{}x{}"_fmt(shadowMap.getDepthTexture().width(), shadowMap.getDepthTexture().height()), Arg::bottomRight(506, 510));
SimpleGUI::Slider(U"viewSize: {:.1f}"_fmt(light.viewSize), light.viewSize, 4.0, 100.0, Vec2{ 180, 640 }, 150.0, 240);
if (SimpleGUI::Button(U"←", Vec2{ 40, 600 }, 40))
{
light.focusPos.x -= 1.0;
}
if (SimpleGUI::Button(U"→", Vec2{ 120, 600 }, 40))
{
light.focusPos.x += 1.0;
}
if (SimpleGUI::Button(U"↑", Vec2{ 80, 560 }, 40))
{
light.focusPos.z += 1.0;
}
if (SimpleGUI::Button(U"↓", Vec2{ 80, 640 }, 40))
{
light.focusPos.z -= 1.0;
}
}
}
}
}
//-----------------------------------------------
//
// This file is part of the Siv3D Engine.
//
// Copyright (c) 2008-2023 Ryo Suzuki
// Copyright (c) 2016-2023 OpenSiv3D Project
//
// Licensed under the MIT License.
//
//-----------------------------------------------
//
// Textures
//
Texture2D g_texture0 : register(t0);
SamplerState g_sampler0 : register(s0);
namespace s3d
{
//
// VS Output / PS Input
//
struct PSInput
{
float4 position : SV_POSITION;
float3 worldPosition : TEXCOORD0;
float2 uv : TEXCOORD1;
float3 normal : TEXCOORD2;
};
}
//
// Constant Buffer
//
cbuffer PSPerFrame : register(b0)
{
float3 g_globalAmbientColor;
float3 g_sunColor;
float3 g_sunDirection;
}
cbuffer PSPerView : register(b1)
{
float3 g_eyePosition;
}
cbuffer PSPerMaterial : register(b3)
{
float3 g_ambientColor;
uint g_hasTexture;
float4 g_diffuseColor;
float3 g_specularColor;
float g_shininess;
float3 g_emissionColor;
}
float4 GetDiffuseColor(float2 uv)
{
float4 diffuseColor = g_diffuseColor;
if (g_hasTexture)
{
diffuseColor *= g_texture0.Sample(g_sampler0, uv);
}
return diffuseColor;
}
float3 CalculateDiffuseReflection(float3 n, float3 l, float3 lightColor, float3 diffuseColor, float3 ambientColor)
{
const float3 directColor = lightColor * saturate(dot(n, l));
return ((ambientColor + directColor) * diffuseColor);
}
float3 CalculateSpecularReflection(float3 n, float3 h, float shininess, float nl, float3 lightColor, float3 specularColor)
{
const float highlight = pow(saturate(dot(n, h)), shininess) * float(0.0 < nl);
return (lightColor * specularColor * highlight);
}
////////////////////////////////////////////////////////////
//
// Depth
//
cbuffer PSShadow : register(b4)
{
row_major float4x4 g_worldToProjectedShadow;
float3 g_sunPosition;
float g_lightBleedingReduction;
}
float2 Depth_PS(s3d::PSInput input) : SV_TARGET
{
const float depth = length(g_sunPosition - input.worldPosition);
return float2(depth, (depth * depth));
}
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//
// Shading
//
Texture2D g_texture3 : register(t3);
SamplerState g_sampler3 : register(s3);
float LineStep(float min, float max, float value)
{
return saturate((value - min) / (max - min));
}
float ReduceLightBleeding(float p_max, float amount)
{
return LineStep(amount, 1.0, p_max);
}
float CalculateShadow(float3 worldPosition)
{
const float4 projectedPosition = mul(float4(worldPosition, 1.0), g_worldToProjectedShadow);
if (any(saturate(projectedPosition.xyz) != projectedPosition.xyz))
{
return 1.0;
}
const float2 uv = float2(projectedPosition.x, (1.0 - projectedPosition.y));
const float depth = (length(g_sunPosition - worldPosition) - 0.03125);
const float2 moments = g_texture3.Sample(g_sampler3, uv).rg;
if (depth <= moments.x)
{
return 1.0;
}
const float variance = (moments.y - (moments.x * moments.x));
const float d = (moments.x - depth);
const float lit = (variance / (variance + (d * d)));
return ReduceLightBleeding(lit, g_lightBleedingReduction);
}
float4 Shading_PS(s3d::PSInput input) : SV_TARGET
{
// Shadow
const float lit = CalculateShadow(input.worldPosition);
const float3 lightColor = (g_sunColor * lit);
const float3 lightDirection = g_sunDirection;
const float3 n = normalize(input.normal);
const float3 l = lightDirection;
float4 diffuseColor = GetDiffuseColor(input.uv);
const float3 ambientColor = (g_ambientColor * g_globalAmbientColor);
// Diffuse
const float3 diffuseReflection = CalculateDiffuseReflection(n, l, lightColor, diffuseColor.rgb, ambientColor);
// Specular
const float3 v = normalize(g_eyePosition - input.worldPosition);
const float3 h = normalize(v + lightDirection);
const float3 specularReflection = CalculateSpecularReflection(n, h, g_shininess, dot(n, l), lightColor, g_specularColor);
return float4(diffuseReflection + specularReflection + g_emissionColor, diffuseColor.a);
}
//
////////////////////////////////////////////////////////////
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment