|
# include <Siv3D.hpp> |
|
|
|
class ShadowMap |
|
{ |
|
public: |
|
|
|
ShadowMap() = default; |
|
|
|
explicit ShadowMap(const Size& resolution, size_t blurLevel = 1, bool linearFilter = true) |
|
: m_resolution{ resolution } |
|
, m_blurLevel{ blurLevel } |
|
, m_linearFilter{ linearFilter } |
|
, m_camera{ resolution, 30_deg, Vec3{ 10, 16, -32 } } |
|
, m_psDepth{ HLSL{ U"depth.hlsl", U"PS" } } |
|
, m_psShadow{ HLSL{ U"shadow.hlsl", U"PS" } } |
|
, m_cb{ { m_camera.getMat4x4() } } |
|
, m_depthTexture{ m_resolution, DepthFormat, HasDepth::Yes } |
|
, m_internalTexture{ m_resolution, DepthFormat } |
|
, m_nternalTextureHalf0{ (m_resolution / 2), DepthFormat } |
|
, m_nternalTextureHalf1{ (m_resolution / 2), DepthFormat } {} |
|
|
|
void beginDepth(const Vec3& sunPos, double viewSize, double nearZ, double farZ) |
|
{ |
|
if (m_depthPass) |
|
{ |
|
throw Error{ U"ShadowMap::beginDepth() called twice" }; |
|
} |
|
|
|
if (m_shadowPass) |
|
{ |
|
throw Error{ U"ShadowMap::beginDepth() called while shadow pass is active" }; |
|
} |
|
|
|
auto p = std::make_unique<DepthPass>(); |
|
p->pRenderTarget = std::make_unique<ScopedRenderTarget3D>(m_depthTexture.clear(ColorF{ 0.0 })); |
|
p->pRenderStates = std::make_unique<ScopedRenderStates3D>(BlendState::Opaque); |
|
p->oldCameraMat = Graphics3D::GetCameraTransform(); |
|
p->oldEyePosition = Graphics3D::GetEyePosition(); |
|
m_depthPass = std::move(p); |
|
|
|
m_camera.update(sunPos, viewSize, nearZ, farZ); |
|
Graphics3D::SetCameraTransform(m_camera); |
|
} |
|
|
|
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(); |
|
blur(); |
|
} |
|
|
|
void beginShadow() |
|
{ |
|
if (m_shadowPass) |
|
{ |
|
throw Error{ U"ShadowMap::beginShadow() called twice" }; |
|
} |
|
|
|
if (m_depthPass) |
|
{ |
|
throw Error{ U"ShadowMap::beginShadow() called while depth pass is active" }; |
|
} |
|
|
|
auto p = std::make_unique<ShadowPass>(); |
|
|
|
if (m_linearFilter) |
|
{ |
|
p->pRenderStates = std::make_unique<ScopedRenderStates3D>(ScopedRenderStates3D::SamplerStateInfo{ ShaderStage::Pixel, 3, SamplerState::ClampLinear }); |
|
} |
|
else |
|
{ |
|
p->pRenderStates = std::make_unique<ScopedRenderStates3D>(ScopedRenderStates3D::SamplerStateInfo{ ShaderStage::Pixel, 3, SamplerState::ClampNearest }); |
|
} |
|
|
|
m_shadowPass = std::move(p); |
|
|
|
Graphics3D::SetPSTexture(3, m_depthTexture); |
|
m_cb->worldToProjectedShadow = (m_camera.getMat4x4() * Mat4x4::Scale(Float3{ 0.5, 0.5, 1.0 }) * Mat4x4::Translate(Float3{ 0.5, 0.5, 0.0 })); |
|
Graphics3D::SetPSConstantBuffer(4, m_cb); |
|
} |
|
|
|
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(); |
|
} |
|
|
|
[[nodiscard]] |
|
const Size& getResolution() const noexcept |
|
{ |
|
return m_resolution; |
|
} |
|
|
|
void setResolution(const Size& resolution) |
|
{ |
|
if (m_depthPass) |
|
{ |
|
throw Error{ U"ShadowMap::setResolution() called while depth pass is active" }; |
|
} |
|
|
|
if (m_shadowPass) |
|
{ |
|
throw Error{ U"ShadowMap::setResolution() called while shadow pass is active" }; |
|
} |
|
|
|
if (m_resolution == resolution) |
|
{ |
|
return; |
|
} |
|
|
|
m_resolution = resolution; |
|
|
|
m_depthTexture = RenderTexture{ m_resolution, DepthFormat, HasDepth::Yes }; |
|
m_internalTexture = RenderTexture{ m_resolution, DepthFormat }; |
|
m_nternalTextureHalf0 = RenderTexture{ (m_resolution / 2), DepthFormat }; |
|
m_nternalTextureHalf1 = RenderTexture{ (m_resolution / 2), DepthFormat }; |
|
} |
|
|
|
[[nodiscard]] |
|
size_t getBlurLevel() const noexcept |
|
{ |
|
return m_blurLevel; |
|
} |
|
|
|
void setBlurLevel(size_t level) |
|
{ |
|
m_blurLevel = level; |
|
} |
|
|
|
[[nodiscard]] |
|
bool getLinearFilter() const noexcept |
|
{ |
|
return m_linearFilter; |
|
} |
|
|
|
void setLinearFilter(bool linearFilter) |
|
{ |
|
m_linearFilter = linearFilter; |
|
} |
|
|
|
[[nodiscard]] |
|
const PixelShader& getDepthPixelShader() const noexcept |
|
{ |
|
return m_psDepth; |
|
} |
|
|
|
[[nodiscard]] |
|
const PixelShader& getShadowPixelShader() const noexcept |
|
{ |
|
return m_psShadow; |
|
} |
|
|
|
[[nodiscard]] |
|
const Texture& getDepthTexture() const noexcept |
|
{ |
|
return m_depthTexture; |
|
} |
|
|
|
[[nodiscard]] |
|
Mat4x4 SIV3D_VECTOR_CALL getMat4x4() const noexcept |
|
{ |
|
return m_camera.getMat4x4(); |
|
} |
|
|
|
private: |
|
|
|
static constexpr TextureFormat DepthFormat = TextureFormat::R32G32_Float; |
|
|
|
class DepthCamera3D : public BasicCamera3D |
|
{ |
|
public: |
|
|
|
using BasicCamera3D::BasicCamera3D; |
|
|
|
void update(const Vec3& sunPos, double viewSize, double nearZ, double farZ) |
|
{ |
|
const float viewF = static_cast<float>(viewSize); |
|
m_proj = DirectX::XMMatrixOrthographicLH(viewF, viewF, static_cast<float>(farZ), static_cast<float>(nearZ)); |
|
{ |
|
const Vec3 sunDirection = Graphics3D::GetSunDirection(); |
|
const SIMD_Float4 eyePosition{ sunPos, 0.0f }; |
|
const SIMD_Float4 focusPosition{ (sunPos - sunDirection), 0.0f }; |
|
const SIMD_Float4 upDirection{ Vec3{ 0, 1, 0 }, 0.0f }; |
|
m_view = DirectX::XMMatrixLookAtLH(eyePosition, focusPosition, upDirection); |
|
m_invView = m_view.inverse(); |
|
} |
|
|
|
{ |
|
m_viewProj = (m_view * m_proj); |
|
m_invViewProj = m_viewProj.inverse(); |
|
} |
|
} |
|
}; |
|
|
|
struct alignas(16) DepthPass |
|
{ |
|
std::unique_ptr<ScopedRenderTarget3D> pRenderTarget; |
|
std::unique_ptr<ScopedRenderStates3D> pRenderStates; |
|
Mat4x4 oldCameraMat = Mat4x4::Identity(); |
|
Float3 oldEyePosition{ 0, 0, 0 }; |
|
}; |
|
|
|
struct ShadowPass |
|
{ |
|
std::unique_ptr<ScopedRenderStates3D> pRenderStates; |
|
}; |
|
|
|
struct alignas(16) PSShadow |
|
{ |
|
Mat4x4 worldToProjectedShadow; |
|
}; |
|
|
|
Size m_resolution{ 0, 0 }; |
|
|
|
size_t m_blurLevel = 1; |
|
|
|
bool m_linearFilter = true; |
|
|
|
DepthCamera3D m_camera; |
|
|
|
PixelShader m_psDepth; |
|
|
|
PixelShader m_psShadow; |
|
|
|
ConstantBuffer<PSShadow> m_cb; |
|
|
|
RenderTexture m_depthTexture; |
|
|
|
RenderTexture m_internalTexture; |
|
|
|
RenderTexture m_nternalTextureHalf0; |
|
|
|
RenderTexture m_nternalTextureHalf1; |
|
|
|
std::unique_ptr<DepthPass> m_depthPass; |
|
|
|
std::unique_ptr<ShadowPass> m_shadowPass; |
|
|
|
void blur() |
|
{ |
|
if (m_blurLevel == 0) |
|
{ |
|
return; |
|
} |
|
|
|
if (m_blurLevel == 1) |
|
{ |
|
Shader::GaussianBlur(m_depthTexture, m_internalTexture, m_depthTexture); |
|
} |
|
else if (m_blurLevel == 2) |
|
{ |
|
Shader::GaussianBlur(m_depthTexture, m_internalTexture, m_depthTexture); |
|
Shader::GaussianBlur(m_depthTexture, m_internalTexture, m_depthTexture); |
|
} |
|
else |
|
{ |
|
Shader::Downsample(m_depthTexture, m_nternalTextureHalf0); |
|
Shader::GaussianBlur(m_nternalTextureHalf0, m_nternalTextureHalf1, m_nternalTextureHalf0); |
|
Shader::Downsample(m_nternalTextureHalf0, m_depthTexture); |
|
} |
|
|
|
Graphics2D::Flush(); |
|
} |
|
}; |
|
|
|
struct ShadowParameter |
|
{ |
|
bool linearFilter = true; |
|
|
|
size_t blurLevel = 1; |
|
|
|
size_t resolutionOption = 3; |
|
|
|
Vec3 shadowCenter{ 0, 2, 0 }; |
|
|
|
double viewSize = 22.0; |
|
|
|
double near = 32.0, far = 100.0; |
|
|
|
Size getResolution() const noexcept |
|
{ |
|
static constexpr std::array<Size, 5> Resolutions = { Size{ 256, 256 }, Size{ 512, 512 }, Size{ 1024, 1024 }, Size{ 2048, 2048 }, Size{ 4096, 4096 } }; |
|
return Resolutions[resolutionOption]; |
|
} |
|
}; |
|
|
|
void DrawObjects(double time) |
|
{ |
|
Box::FromPoints({ -8, 0, 5 }, { 0, 0.5, 7 }).draw(); |
|
Box::FromPoints({ -0.5, 0, 5 }, { 0, 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()); |
|
} |
|
|
|
Box::FromPoints({ -8, 0, -2 }, { -7, 8, 2 }).draw(); |
|
Box::FromPoints({ -6, 0, -8 }, { -5.5, 6, -4 }).oriented(Quaternion::RotateY(-45_deg)).draw(); |
|
Box::FromPoints({ 6, 0, -6 }, { 9, 1, -3 }).oriented(Quaternion::RotateY(-45_deg)).draw(); |
|
|
|
const double sphereY = (2 + Periodic::Jump0_1(2s, time) * 4); |
|
Sphere{ Vec3{ 0, sphereY, 0 }, 2.0 }.draw(); |
|
|
|
OrientedBox{ Arg::bottomCenter(6, 0, 0), { 4, 1.5, 0.2 }, Quaternion::RotateY(time * 20_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); |
|
|
|
Cylinder{ Vec3{ 2, 0, -4 }, Vec3{ 2, 8, -4 }, 0.2 }.draw(); |
|
} |
|
|
|
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 } }; |
|
|
|
ShadowParameter shadowParameter; |
|
ShadowMap shadowMap{ shadowParameter.getResolution(), shadowParameter.blurLevel, shadowParameter.linearFilter }; |
|
|
|
bool showUI = true; |
|
bool showDepth = true; |
|
double time = 89.0; |
|
bool pause = true; |
|
|
|
while (System::Update()) |
|
{ |
|
ClearPrint(); |
|
|
|
if (KeySpace.down()) |
|
{ |
|
showUI = (not showUI); |
|
} |
|
|
|
if (not pause) |
|
{ |
|
time += Scene::DeltaTime(); |
|
} |
|
|
|
camera.update(2.0); |
|
|
|
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::SetCameraTransform(camera); |
|
Graphics3D::SetGlobalAmbientColor(ColorF{ 0.25 }); |
|
|
|
// Shadow map |
|
{ |
|
const Vec3 sunPos = (shadowParameter.shadowCenter + Graphics3D::GetSunDirection() * 64); |
|
shadowMap.beginDepth(sunPos, shadowParameter.viewSize, shadowParameter.near, shadowParameter.far); |
|
{ |
|
// 専用のシェーダーを使って深度を描く |
|
const ScopedCustomShader3D shader{ shadowMap.getDepthPixelShader() }; |
|
|
|
// 影を作りたいものの形状のみを描く(色やテクスチャ等は無意味) |
|
DrawObjects(time); |
|
} |
|
shadowMap.endDepth(); |
|
} |
|
|
|
// 3D scene |
|
{ |
|
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) }; |
|
shadowMap.beginShadow(); |
|
{ |
|
// 専用のシェーダーを使って影付きのシーンを描く |
|
const ScopedCustomShader3D shader{ shadowMap.getShadowPixelShader() }; |
|
Plane{ 64 }.draw(uvChecker); |
|
DrawObjects(time); |
|
} |
|
shadowMap.endShadow(); |
|
} |
|
|
|
{ |
|
Graphics3D::Flush(); |
|
renderTexture.resolve(); |
|
Shader::LinearToScreen(renderTexture); |
|
} |
|
|
|
// UI |
|
if (showUI) |
|
{ |
|
Print << U"shadowCenter: {:.1f}"_fmt(shadowParameter.shadowCenter); |
|
|
|
if (showDepth) |
|
{ |
|
shadowMap.getDepthTexture().resized(512).draw(); |
|
} |
|
|
|
SimpleGUI::CheckBox(pause, U"Pause", Vec2{ 1100, 40 }, 160); |
|
|
|
SimpleGUI::CheckBox(showDepth, U"Show depth", Vec2{ 1100, 80 }, 160); |
|
|
|
if (SimpleGUI::CheckBox(shadowParameter.linearFilter, U"Linear filter", Vec2{ 1100, 120 }, 160)) |
|
{ |
|
shadowMap.setLinearFilter(shadowParameter.linearFilter); |
|
} |
|
|
|
if (SimpleGUI::RadioButtons(shadowParameter.blurLevel, { U"0", U"1", U"2", U"3" }, Vec2{ 1100, 160 }, 100)) |
|
{ |
|
shadowMap.setBlurLevel(shadowParameter.blurLevel); |
|
} |
|
|
|
if (SimpleGUI::RadioButtons(shadowParameter.resolutionOption, { U"256", U"512", U"1024", U"2048", U"4096" }, Vec2{ 1100, 320 }, 100)) |
|
{ |
|
shadowMap.setResolution(shadowParameter.getResolution()); |
|
} |
|
|
|
if (SimpleGUI::Button(U"←", Vec2{ 40, 600 }, 40)) |
|
{ |
|
shadowParameter.shadowCenter.x -= 1.0; |
|
} |
|
|
|
if (SimpleGUI::Button(U"→", Vec2{ 120, 600 }, 40)) |
|
{ |
|
shadowParameter.shadowCenter.x += 1.0; |
|
} |
|
|
|
if (SimpleGUI::Button(U"↑", Vec2{ 80, 560 }, 40)) |
|
{ |
|
shadowParameter.shadowCenter.z += 1.0; |
|
} |
|
|
|
if (SimpleGUI::Button(U"↓", Vec2{ 80, 640 }, 40)) |
|
{ |
|
shadowParameter.shadowCenter.z -= 1.0; |
|
} |
|
|
|
SimpleGUI::Slider(U"viewSize: {:.1f}"_fmt(shadowParameter.viewSize), shadowParameter.viewSize, 4.0, 100.0, Vec2{ 180, 560 }, 150.0, 240); |
|
SimpleGUI::Slider(U"near: {:.0f}"_fmt(shadowParameter.near), shadowParameter.near, 1.0, 200.0, Vec2{ 180, 600 }, 150.0, 240); |
|
SimpleGUI::Slider(U"far: {:.0f}"_fmt(shadowParameter.far), shadowParameter.far, 1.0, 200.0, Vec2{ 180, 640 }, 150.0, 240); |
|
} |
|
} |
|
} |