Skip to content

Instantly share code, notes, and snippets.

@Reputeless
Last active November 1, 2023 15:27
Show Gist options
  • Save Reputeless/869e39bf6061bbda89bbe47e58489518 to your computer and use it in GitHub Desktop.
Save Reputeless/869e39bf6061bbda89bbe47e58489518 to your computer and use it in GitHub Desktop.
Shadow

既存の Siv3D の 3D シーンに Variance Shadow Maps によるシャドウ(影)を導入できる実験的機能 ShadowMap + ShadowParameter クラスの説明です。

1. 導入方法

  1. App フォルダに depth.hlslshadow.hlsl を配置します。
  2. 次のような手順で 3D シーンを描画します。
  3. 適切なパラメータを手作業で探るときは、本ページ Main.cpp の UI が参考になります。
# include <Siv3D.hpp>

class ShadowMap
{
  // ...
};

struct ShadowParameter
{
  // 適切なパラメータを設定
};

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 };

	while (System::Update())
	{
		camera.update(2.0);
		Graphics3D::SetCameraTransform(camera);

		// Shadow map
		{
			// 太陽の位置(影を作りたいシーンの位置に合わせる)
			const Vec3 sunPos = (shadowParameter.shadowCenter + Graphics3D::GetSunDirection() * 64);

			// viewSize は影を作りたいシーンの広さに合わせる
			shadowMap.beginDepth(sunPos, shadowParameter.viewSize, shadowParameter.near, shadowParameter.far);
			{
				// 専用のシェーダーを使って深度を描く
				const ScopedCustomShader3D shader{ shadowMap.getDepthPixelShader() };

				// 影を作りたいものの形状のみを描く(色やテクスチャ等は無意味)
				Box{ Vec3{ -8, 2, 0 }, 4 }.draw();
				Sphere{ Vec3{ 0, 2, 0 }, 2 }.draw();
				Cylinder{ Vec3{ 8, 2, 0 }, 2, 4 }.draw();
			}
			shadowMap.endDepth();
		}

		// 3D scene
		{
			const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
			shadowMap.beginShadow();
			{
				// 専用のシェーダーを使って影付きのシーンを描く
				const ScopedCustomShader3D shader{ shadowMap.getShadowPixelShader() };

				Plane{ 64 }.draw(uvChecker);
				Box{ Vec3{ -8, 2, 0 }, 4 }.draw(ColorF{ 0.8, 0.6, 0.4 }.removeSRGBCurve());
				Sphere{ Vec3{ 0, 2, 0 }, 2 }.draw(ColorF{ 0.4, 0.8, 0.6 }.removeSRGBCurve());
				Cylinder{ Vec3{ 8, 2, 0 }, 2, 4 }.draw(ColorF{ 0.6, 0.4, 0.8 }.removeSRGBCurve());
			}
			shadowMap.endShadow();
		}

		{
			Graphics3D::Flush();
			renderTexture.resolve();
			Shader::LinearToScreen(renderTexture);
		}

		// シャドウマップの中身を確認する。
		// シーンにフィットしているのが望ましい。
		shadowMap.getDepthTexture().resized(512).draw();
	}
}

2. カスタムシェーダをシャドウ対応させる方法

2.1 深度書き込み

depth.hlsl を参考に、深度と深度の二乗だけを float2 で返すピクセルシェーダを実行します。

ほとんどのケースでは depth.hlsl で対応可能ですが、アルファテストを行いたい場合などはカスタムシェーダが必要になるでしょう。

2.2 シャドウ付きシーン描画

shadow.hlsl を参考に、

  • テクスチャ slot 3 に深度テクスチャを追加します。
//
//	Shadow map
//
Texture2D g_texture3 : register(t3);
SamplerState g_sampler3 : register(s3);
  • 定数バッファ slot 4 にシャドウマップアクセスのための行列を追加します。
cbuffer PSShadow : register(b4)
{
	row_major float4x4 g_worldToProjectedShadow;
}
  • シャドウ込みで明るさレベルを返す関数を追加します。この戻り値を g_sunColor に乗算すると影の領域が暗くなります。
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 = projectedPosition.z;
	const float2 moments = g_texture3.Sample(g_sampler3, uv).rg;

	if (moments.x <= depth)
	{
		return 1.0;
	}
	
	const float variance = max((moments.y - (moments.x * moments.x)), 0.00002);
	const float d = (depth - moments.x);
	const float c = (variance / (variance + d * d));
	return pow(abs(c), 4);
}
//-----------------------------------------------
//
// 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.
//
//-----------------------------------------------
namespace s3d
{
//
// VS Output / PS Input
//
struct PSInput
{
float4 position : SV_POSITION;
float3 worldPosition : TEXCOORD0;
float2 uv : TEXCOORD1;
float3 normal : TEXCOORD2;
};
}
//
// Functions
//
float2 PS(s3d::PSInput input) : SV_TARGET
{
const float depth = input.position.z;
const float moment1 = depth;
float moment2 = depth * depth;
float dx = ddx(depth);
float dy = ddy(depth);
moment2 += 0.25 * (dx * dx + dy * dy);
return float2(moment1, moment2);
}
# 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);
}
}
}
//-----------------------------------------------
//
// 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);
//
// Shadow map
//
Texture2D g_texture3 : register(t3);
SamplerState g_sampler3 : register(s3);
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;
}
cbuffer PSShadow : register(b4)
{
row_major float4x4 g_worldToProjectedShadow;
}
//
// Functions
//
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);
}
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 = projectedPosition.z;
const float2 moments = g_texture3.Sample(g_sampler3, uv).rg;
if (moments.x <= depth)
{
return 1.0;
}
const float variance = max((moments.y - (moments.x * moments.x)), 0.00002);
const float d = (depth - moments.x);
const float c = (variance / (variance + d * d));
return pow(abs(c), 4);
}
float4 PS(s3d::PSInput input) : SV_TARGET
{
// Shadow
const float shadow = CalculateShadow(input.worldPosition);
const float3 lightColor = (g_sunColor * shadow);
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