diff --git a/CMakeLists.txt b/CMakeLists.txt
index d97b3ebe..fbae3fe7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -119,6 +119,7 @@ set(LIBRARY_SOURCES
Src/DDSTextureLoader.cpp
Src/DebugEffect.cpp
Src/DGSLEffect.cpp
+ Src/NPREffect.cpp
Src/DGSLEffectFactory.cpp
Src/DirectXHelpers.cpp
Src/DualPostProcess.cpp
@@ -150,6 +151,7 @@ set(SHADER_SOURCES
Src/Shaders/AlphaTestEffect.fx
Src/Shaders/BasicEffect.fx
Src/Shaders/DebugEffect.fx
+ Src/Shaders/NPREffect.fx
Src/Shaders/DGSLEffect.fx
Src/Shaders/DGSLLambert.hlsl
Src/Shaders/DGSLPhong.hlsl
diff --git a/DirectXTK_Desktop_2022.vcxproj b/DirectXTK_Desktop_2022.vcxproj
index d03abf3d..d66aefc4 100644
--- a/DirectXTK_Desktop_2022.vcxproj
+++ b/DirectXTK_Desktop_2022.vcxproj
@@ -75,6 +75,7 @@
+
@@ -179,6 +180,9 @@
Document
+
+ Document
+
{E0B52AE7-E160-4D32-BF3F-910B785E5A8E}
diff --git a/DirectXTK_Desktop_2022.vcxproj.filters b/DirectXTK_Desktop_2022.vcxproj.filters
index 6f61bad4..05534581 100644
--- a/DirectXTK_Desktop_2022.vcxproj.filters
+++ b/DirectXTK_Desktop_2022.vcxproj.filters
@@ -248,6 +248,9 @@
Src
+
+ Src
+
Src
@@ -352,6 +355,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
diff --git a/DirectXTK_Desktop_2022_Win10.vcxproj b/DirectXTK_Desktop_2022_Win10.vcxproj
index 8ff0f6c1..0f6540d6 100644
--- a/DirectXTK_Desktop_2022_Win10.vcxproj
+++ b/DirectXTK_Desktop_2022_Win10.vcxproj
@@ -83,6 +83,7 @@
+
@@ -189,6 +190,9 @@
Document
+
+ Document
+
{E0B52AE7-E160-4D32-BF3F-910B785E5A8E}
diff --git a/DirectXTK_Desktop_2022_Win10.vcxproj.filters b/DirectXTK_Desktop_2022_Win10.vcxproj.filters
index 3eead7e8..cfea2c70 100644
--- a/DirectXTK_Desktop_2022_Win10.vcxproj.filters
+++ b/DirectXTK_Desktop_2022_Win10.vcxproj.filters
@@ -272,6 +272,9 @@
Src
+
+ Src
+
Src
@@ -352,6 +355,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
diff --git a/DirectXTK_Desktop_2026.vcxproj b/DirectXTK_Desktop_2026.vcxproj
index bf6e87ba..5418c4d3 100644
--- a/DirectXTK_Desktop_2026.vcxproj
+++ b/DirectXTK_Desktop_2026.vcxproj
@@ -83,6 +83,7 @@
+
@@ -189,6 +190,9 @@
Document
+
+ Document
+
{E0B52AE7-E160-4D32-BF3F-910B785E5A8E}
diff --git a/DirectXTK_Desktop_2026.vcxproj.filters b/DirectXTK_Desktop_2026.vcxproj.filters
index 3eead7e8..cfea2c70 100644
--- a/DirectXTK_Desktop_2026.vcxproj.filters
+++ b/DirectXTK_Desktop_2026.vcxproj.filters
@@ -272,6 +272,9 @@
Src
+
+ Src
+
Src
@@ -352,6 +355,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
diff --git a/DirectXTK_GDKW_2022.vcxproj b/DirectXTK_GDKW_2022.vcxproj
index 4accaa96..f309eeb1 100644
--- a/DirectXTK_GDKW_2022.vcxproj
+++ b/DirectXTK_GDKW_2022.vcxproj
@@ -403,6 +403,7 @@
+
@@ -469,6 +470,7 @@
+
diff --git a/DirectXTK_GDKW_2022.vcxproj.filters b/DirectXTK_GDKW_2022.vcxproj.filters
index f3b9fda7..511fb417 100644
--- a/DirectXTK_GDKW_2022.vcxproj.filters
+++ b/DirectXTK_GDKW_2022.vcxproj.filters
@@ -274,6 +274,9 @@
Src
+
+ Src
+
Src
@@ -354,6 +357,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
diff --git a/DirectXTK_GDK_2022.vcxproj b/DirectXTK_GDK_2022.vcxproj
index 0e9b32c8..d4f512a9 100644
--- a/DirectXTK_GDK_2022.vcxproj
+++ b/DirectXTK_GDK_2022.vcxproj
@@ -228,6 +228,7 @@
+
@@ -291,6 +292,7 @@
+
diff --git a/DirectXTK_GDK_2022.vcxproj.filters b/DirectXTK_GDK_2022.vcxproj.filters
index f3b9fda7..511fb417 100644
--- a/DirectXTK_GDK_2022.vcxproj.filters
+++ b/DirectXTK_GDK_2022.vcxproj.filters
@@ -274,6 +274,9 @@
Src
+
+ Src
+
Src
@@ -354,6 +357,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
diff --git a/DirectXTK_Windows10_2022.vcxproj b/DirectXTK_Windows10_2022.vcxproj
index 6b97f483..25ee0c87 100644
--- a/DirectXTK_Windows10_2022.vcxproj
+++ b/DirectXTK_Windows10_2022.vcxproj
@@ -96,6 +96,7 @@
+
@@ -191,6 +192,9 @@
Document
+
+ Document
+
{f4776924-619c-42c7-88b2-82c947ccc9e7}
diff --git a/DirectXTK_Windows10_2022.vcxproj.filters b/DirectXTK_Windows10_2022.vcxproj.filters
index e875551d..354afcf7 100644
--- a/DirectXTK_Windows10_2022.vcxproj.filters
+++ b/DirectXTK_Windows10_2022.vcxproj.filters
@@ -206,6 +206,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
@@ -347,6 +350,9 @@
Src
+
+ Src
+
Src
diff --git a/Inc/Effects.h b/Inc/Effects.h
index 695f7bb4..d30c2f5a 100644
--- a/Inc/Effects.h
+++ b/Inc/Effects.h
@@ -878,6 +878,91 @@ namespace DirectX
std::unique_ptr pImpl;
};
+ //------------------------------------------------------------------------------
+ // Built-in shader for non-photorealistic rendering (cel shading, Gooch shading).
+ class NPREffect : public IEffect, public IEffectMatrices, public IEffectLights
+ {
+ public:
+ enum Mode : uint32_t
+ {
+ Mode_Cel = 0, // Cel (toon) shading
+ Mode_Gooch, // Gooch shading
+ };
+
+ DIRECTX_TOOLKIT_API explicit NPREffect(_In_ ID3D11Device* device);
+
+ DIRECTX_TOOLKIT_API NPREffect(NPREffect&&) noexcept;
+ DIRECTX_TOOLKIT_API NPREffect& operator= (NPREffect&&) noexcept;
+
+ NPREffect(NPREffect const&) = delete;
+ NPREffect& operator= (NPREffect const&) = delete;
+
+ DIRECTX_TOOLKIT_API ~NPREffect() override;
+
+ // IEffect methods.
+ DIRECTX_TOOLKIT_API void __cdecl Apply(_In_ ID3D11DeviceContext* deviceContext) override;
+
+ DIRECTX_TOOLKIT_API void __cdecl GetVertexShaderBytecode(
+ _Out_ void const** pShaderByteCode,
+ _Out_ size_t* pByteCodeLength) override;
+
+ // Camera settings.
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetWorld(FXMMATRIX value) override;
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetView(FXMMATRIX value) override;
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetProjection(FXMMATRIX value) override;
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetMatrices(FXMMATRIX world, CXMMATRIX view, CXMMATRIX projection) override;
+
+ // Material settings.
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetDiffuseColor(FXMVECTOR value);
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetSpecularColor(FXMVECTOR value);
+ DIRECTX_TOOLKIT_API void __cdecl SetSpecularPower(float value);
+ DIRECTX_TOOLKIT_API void __cdecl DisableSpecular();
+ DIRECTX_TOOLKIT_API void __cdecl SetAlpha(float value);
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetColorAndAlpha(FXMVECTOR value);
+
+ // Light settings.
+ void XM_CALLCONV SetLightDirection(int whichLight, FXMVECTOR value) override;
+ DIRECTX_TOOLKIT_API void __cdecl EnableDefaultLighting() override;
+
+ static constexpr int MaxDirectionalLights = 1;
+
+ // Texture settings.
+ // TODO: Implement texture settings.
+
+ // Shader mode setting.
+ DIRECTX_TOOLKIT_API void __cdecl SetMode(Mode mode);
+
+ // Cel shading settings.
+ DIRECTX_TOOLKIT_API void __cdecl SetCelShaderBands(int bands);
+
+ // Gooch shading settings.
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetGoochCoolColor(FXMVECTOR value, float alpha = 0.25f);
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetGoochWarmColor(FXMVECTOR value, float beta = 0.25f);
+
+ // Vertex color setting.
+ DIRECTX_TOOLKIT_API void __cdecl SetVertexColorEnabled(bool value);
+
+ // Normal compression settings.
+ DIRECTX_TOOLKIT_API void __cdecl SetBiasedVertexNormals(bool value);
+
+ // Instancing settings.
+ DIRECTX_TOOLKIT_API void __cdecl SetInstancingEnabled(bool value);
+
+ private:
+ // Private implementation.
+ class Impl;
+
+ std::unique_ptr pImpl;
+
+ // Unsupported interface methods.
+ DIRECTX_TOOLKIT_API void __cdecl SetLightingEnabled(bool value) override;
+ DIRECTX_TOOLKIT_API void __cdecl SetPerPixelLighting(bool value) override;
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetAmbientLightColor(FXMVECTOR value) override;
+ DIRECTX_TOOLKIT_API void __cdecl SetLightEnabled(int whichLight, bool value) override;
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetLightDiffuseColor(int whichLight, FXMVECTOR value) override;
+ DIRECTX_TOOLKIT_API void XM_CALLCONV SetLightSpecularColor(int whichLight, FXMVECTOR value) override;
+ };
+
//------------------------------------------------------------------------------
// Abstract interface to factory for sharing effects and texture resources
class DIRECTX_TOOLKIT_API IEffectFactory
diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp
new file mode 100644
index 00000000..25a18831
--- /dev/null
+++ b/Src/NPREffect.cpp
@@ -0,0 +1,512 @@
+//--------------------------------------------------------------------------------------
+// File: NPREffect.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// https://go.microsoft.com/fwlink/?LinkId=248929
+//--------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "EffectCommon.h"
+
+using namespace DirectX;
+
+namespace
+{
+ // Constant buffer layout. Must match the shader!
+ struct NPREffectConstants
+ {
+ XMVECTOR lightDirectionAndCelBands;
+ XMVECTOR diffuseColorAndAlpha;
+ XMVECTOR specularColorAndSpecularPower;
+ XMVECTOR goochCoolColorAndAlpha;
+ XMVECTOR goochWarmColorAndBeta;
+ XMVECTOR eyePosition;
+ XMMATRIX world;
+ XMVECTOR worldInverseTranspose[3];
+ XMMATRIX worldViewProj;
+ };
+
+ static_assert((sizeof(NPREffectConstants) % 16) == 0, "CB size not padded correctly");
+
+
+ // Traits type describes our characteristics to the EffectBase template.
+ struct NPREffectTraits
+ {
+ using ConstantBufferType = NPREffectConstants;
+
+ static constexpr int VertexShaderCount = 8;
+ static constexpr int PixelShaderCount = 2;
+ static constexpr int ShaderPermutationCount = 16;
+
+ static constexpr int ModeCount = 2;
+ };
+
+
+ // Default values
+ constexpr XMVECTORF32 s_defaultLightDir = { { { 0.f, -1.f, 0.f, 4.f } } };
+ constexpr XMVECTORF32 s_defaultDiffuse = { { { 1.f, 1.f, 1.f, 1.f } } };
+ constexpr XMVECTORF32 s_defaultSpecular = { { { 1.f, 1.f, 1.f, 32.f } } };
+ constexpr XMVECTORF32 s_defaultCool = { { { 0.f, 0.f, 0.55f, 0.25f } } };
+ constexpr XMVECTORF32 s_defaultWarm = { { { 0.3f, 0.3f, 0.f, 0.25f } } };
+}
+
+// Internal NPREffect implementation class.
+class NPREffect::Impl : public EffectBase
+{
+public:
+ explicit Impl(_In_ ID3D11Device* device);
+
+ Impl(const Impl&) = delete;
+ Impl& operator=(const Impl&) = delete;
+
+ Impl(Impl&&) = default;
+ Impl& operator=(Impl&&) = default;
+
+ bool vertexColorEnabled;
+ bool biasedVertexNormals;
+ bool instancing;
+ NPREffect::Mode nprMode;
+
+ int GetCurrentShaderPermutation() const noexcept;
+
+ void Apply(_In_ ID3D11DeviceContext* deviceContext);
+};
+
+
+#pragma region Shaders
+// Include the precompiled shader code.
+namespace
+{
+#if defined(_XBOX_ONE) && defined(_TITLE)
+#include "XboxOneNPREffect_VSNPREffect.inc"
+#include "XboxOneNPREffect_VSNPREffectInst.inc"
+
+#include "XboxOneNPREffect_VSNPREffectVc.inc"
+#include "XboxOneNPREffect_VSNPREffectVcInst.inc"
+
+#include "XboxOneNPREffect_VSNPREffectBn.inc"
+#include "XboxOneNPREffect_VSNPREffectBnInst.inc"
+
+#include "XboxOneNPREffect_VSNPREffectVcBn.inc"
+#include "XboxOneNPREffect_VSNPREffectVcBnInst.inc"
+
+#include "XboxOneNPREffect_PSCelShading.inc"
+#include "XboxOneNPREffect_PSGoochShading.inc"
+#else
+#include "NPREffect_VSNPREffect.inc"
+#include "NPREffect_VSNPREffectInst.inc"
+
+#include "NPREffect_VSNPREffectVc.inc"
+#include "NPREffect_VSNPREffectVcInst.inc"
+
+#include "NPREffect_VSNPREffectBn.inc"
+#include "NPREffect_VSNPREffectBnInst.inc"
+
+#include "NPREffect_VSNPREffectVcBn.inc"
+#include "NPREffect_VSNPREffectVcBnInst.inc"
+
+#include "NPREffect_PSCelShading.inc"
+#include "NPREffect_PSGoochShading.inc"
+#endif
+}
+
+
+template<>
+const ShaderBytecode EffectBase::VertexShaderBytecode[] =
+{
+ { NPREffect_VSNPREffect, sizeof(NPREffect_VSNPREffect) },
+ { NPREffect_VSNPREffectVc, sizeof(NPREffect_VSNPREffectVc) },
+ { NPREffect_VSNPREffectBn, sizeof(NPREffect_VSNPREffectBn) },
+ { NPREffect_VSNPREffectVcBn, sizeof(NPREffect_VSNPREffectVcBn) },
+ { NPREffect_VSNPREffectInst, sizeof(NPREffect_VSNPREffectInst) },
+ { NPREffect_VSNPREffectVcInst, sizeof(NPREffect_VSNPREffectVcInst) },
+ { NPREffect_VSNPREffectBnInst, sizeof(NPREffect_VSNPREffectBnInst) },
+ { NPREffect_VSNPREffectVcBnInst, sizeof(NPREffect_VSNPREffectVcBnInst) },
+};
+
+
+template<>
+const int EffectBase::VertexShaderIndices[] =
+{
+ 0, // cel shading
+ 0, // gooch shading
+
+ 1, // vertex color + cel shading
+ 1, // vertex color + gooch shading
+
+ 2, // cel shading (biased vertex normal)
+ 2, // gooch shading (biased vertex normal)
+
+ 3, // vertex color (biased vertex normal) + cel shading
+ 3, // vertex color (biased vertex normal) + gooch shading
+
+ 4, // instancing + cel shading
+ 4, // instancing + gooch shading
+
+ 5, // instancing + vertex color + cel shading
+ 5, // instancing + vertex color + gooch shading
+
+ 6, // instancing (biased vertex normal) + cel shading
+ 6, // instancing (biased vertex normal) + gooch shading
+
+ 7, // instancing + vertex color (biased vertex normal) + cel shading
+ 7, // instancing + vertex color (biased vertex normal) + gooch shading
+};
+
+
+template<>
+const ShaderBytecode EffectBase::PixelShaderBytecode[] =
+{
+ { NPREffect_PSCelShading, sizeof(NPREffect_PSCelShading) },
+ { NPREffect_PSGoochShading, sizeof(NPREffect_PSGoochShading) },
+};
+
+
+template<>
+const int EffectBase::PixelShaderIndices[] =
+{
+ 0, // cel shading
+ 1, // gooch shading
+
+ 0, // vertex color + cel shading
+ 1, // vertex color + gooch shading
+
+ 0, // cel shading (biased vertex normal)
+ 1, // gooch shading (biased vertex normal)
+
+ 0, // vertex color (biased vertex normal) + cel shading
+ 1, // vertex color (biased vertex normal) + gooch shading
+
+ 0, // instancing + cel shading
+ 1, // instancing + gooch shading
+
+ 0, // instancing + vertex color + cel shading
+ 1, // instancing + vertex color + gooch shading
+
+ 0, // instancing (biased vertex normal) + cel shading
+ 1, // instancing (biased vertex normal) + gooch shading
+
+ 0, // instancing + vertex color (biased vertex normal) + cel shading
+ 1, // instancing + vertex color (biased vertex normal) + gooch shading
+};
+#pragma endregion
+
+// Global pool of per-device NPREffect resources.
+template<>
+SharedResourcePool::DeviceResources> EffectBase::deviceResourcesPool = {};
+
+
+// Constructor.
+NPREffect::Impl::Impl(_In_ ID3D11Device* device)
+ : EffectBase(device),
+ vertexColorEnabled(false),
+ biasedVertexNormals(false),
+ instancing(false),
+ nprMode(NPREffect::Mode_Cel)
+{
+ static_assert(static_cast(std::size(EffectBase::VertexShaderIndices)) == NPREffectTraits::ShaderPermutationCount, "array/max mismatch");
+ static_assert(static_cast(std::size(EffectBase::VertexShaderBytecode)) == NPREffectTraits::VertexShaderCount, "array/max mismatch");
+ static_assert(static_cast(std::size(EffectBase::PixelShaderBytecode)) == NPREffectTraits::PixelShaderCount, "array/max mismatch");
+ static_assert(static_cast(std::size(EffectBase::PixelShaderIndices)) == NPREffectTraits::ShaderPermutationCount, "array/max mismatch");
+
+ constants.lightDirectionAndCelBands = s_defaultLightDir;
+ constants.diffuseColorAndAlpha = s_defaultDiffuse;
+ constants.specularColorAndSpecularPower = s_defaultSpecular;
+ constants.goochCoolColorAndAlpha = s_defaultCool;
+ constants.goochWarmColorAndBeta = s_defaultWarm;
+ constants.eyePosition = g_XMZero;
+}
+
+
+int NPREffect::Impl::GetCurrentShaderPermutation() const noexcept
+{
+ int permutation = static_cast(nprMode);
+
+ // Support vertex coloring?
+ if (vertexColorEnabled)
+ {
+ permutation += 2;
+ }
+
+ if (biasedVertexNormals)
+ {
+ // Compressed normals need to be scaled and biased in the vertex shader.
+ permutation += 4;
+ }
+
+ if (instancing)
+ {
+ // Vertex shader needs to use vertex matrix transform.
+ permutation += 8;
+ }
+
+ return permutation;
+}
+
+
+// Sets our state onto the D3D device.
+void NPREffect::Impl::Apply(_In_ ID3D11DeviceContext* deviceContext)
+{
+ assert(deviceContext != nullptr);
+
+ // Compute derived parameter values.
+ matrices.SetConstants(dirtyFlags, constants.worldViewProj);
+
+ // World inverse transpose matrix.
+ if (dirtyFlags & EffectDirtyFlags::WorldInverseTranspose)
+ {
+ constants.world = XMMatrixTranspose(matrices.world);
+
+ const XMMATRIX worldInverse = XMMatrixInverse(nullptr, matrices.world);
+
+ constants.worldInverseTranspose[0] = worldInverse.r[0];
+ constants.worldInverseTranspose[1] = worldInverse.r[1];
+ constants.worldInverseTranspose[2] = worldInverse.r[2];
+
+ dirtyFlags &= ~EffectDirtyFlags::WorldInverseTranspose;
+ dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+ }
+
+ // Set shaders and constant buffers.
+ ApplyShaders(deviceContext, GetCurrentShaderPermutation());
+}
+
+
+// Public constructor.
+NPREffect::NPREffect(_In_ ID3D11Device* device)
+ : pImpl(std::make_unique(device))
+{}
+
+
+NPREffect::NPREffect(NPREffect&&) noexcept = default;
+NPREffect& NPREffect::operator= (NPREffect&&) noexcept = default;
+NPREffect::~NPREffect() = default;
+
+
+// IEffect methods.
+void NPREffect::Apply(_In_ ID3D11DeviceContext* deviceContext)
+{
+ pImpl->Apply(deviceContext);
+}
+
+
+void NPREffect::GetVertexShaderBytecode(_Out_ void const** pShaderByteCode, _Out_ size_t* pByteCodeLength)
+{
+ pImpl->GetVertexShaderBytecode(pImpl->GetCurrentShaderPermutation(), pShaderByteCode, pByteCodeLength);
+}
+
+
+// Camera settings.
+void XM_CALLCONV NPREffect::SetWorld(FXMMATRIX value)
+{
+ pImpl->matrices.world = value;
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj | EffectDirtyFlags::WorldInverseTranspose;
+}
+
+
+void XM_CALLCONV NPREffect::SetView(FXMMATRIX value)
+{
+ pImpl->matrices.view = value;
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj;
+}
+
+
+void XM_CALLCONV NPREffect::SetProjection(FXMMATRIX value)
+{
+ pImpl->matrices.projection = value;
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj;
+}
+
+
+void XM_CALLCONV NPREffect::SetMatrices(FXMMATRIX world, CXMMATRIX view, CXMMATRIX projection)
+{
+ pImpl->matrices.world = world;
+ pImpl->matrices.view = view;
+ pImpl->matrices.projection = projection;
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj | EffectDirtyFlags::WorldInverseTranspose;
+}
+
+
+// Light settings.
+void NPREffect::SetLightingEnabled(bool)
+{
+ // Unsupported interface.
+}
+
+
+void NPREffect::SetPerPixelLighting(bool)
+{
+ // Unsupported interface.
+}
+
+
+void NPREffect::SetAmbientLightColor(FXMVECTOR)
+{
+ // Unsupported interface.
+}
+
+
+void NPREffect::SetLightEnabled(int, bool)
+{
+ // Unsupported interface.
+}
+
+
+void NPREffect::SetLightDirection(int whichLight, FXMVECTOR value)
+{
+ if (whichLight != 0)
+ {
+ // Only support one light
+ return;
+ }
+
+ // Set xyz to new value, but preserve existing w (cel bands).
+ pImpl->constants.lightDirectionAndCelBands = XMVectorSelect(pImpl->constants.lightDirectionAndCelBands, value, g_XMSelect1110);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+void NPREffect::SetLightDiffuseColor(int, FXMVECTOR)
+{
+ // Unsupported interface.
+}
+
+
+void NPREffect::SetLightSpecularColor(int, FXMVECTOR)
+{
+ // Unsupported interface.
+}
+
+
+void NPREffect::EnableDefaultLighting()
+{
+ // Set xyz to new value, but preserve existing w (cel bands).
+ pImpl->constants.lightDirectionAndCelBands = XMVectorSelect(pImpl->constants.lightDirectionAndCelBands, s_defaultLightDir, g_XMSelect1110);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+// Material settings.
+void NPREffect::SetDiffuseColor(FXMVECTOR value)
+{
+ // Set xyz, preserve w (alpha).
+ pImpl->constants.diffuseColorAndAlpha = XMVectorSelect(pImpl->constants.diffuseColorAndAlpha, value, g_XMSelect1110);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+void NPREffect::SetSpecularColor(FXMVECTOR value)
+{
+ // Set xyz, preserve w (specular power).
+ pImpl->constants.specularColorAndSpecularPower = XMVectorSelect(pImpl->constants.specularColorAndSpecularPower, value, g_XMSelect1110);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+void NPREffect::SetSpecularPower(float value)
+{
+ // Set w of specularColorAndSpecularPower.
+ pImpl->constants.specularColorAndSpecularPower = XMVectorSetW(pImpl->constants.specularColorAndSpecularPower, value);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+void NPREffect::DisableSpecular()
+{
+ // Set w of specularColorAndSpecularPower to 0.
+ pImpl->constants.specularColorAndSpecularPower = XMVectorSetW(pImpl->constants.specularColorAndSpecularPower, 0.0f);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+void NPREffect::SetAlpha(float value)
+{
+ // Set w of diffuseColorAndAlpha.
+ pImpl->constants.diffuseColorAndAlpha = XMVectorSetW(pImpl->constants.diffuseColorAndAlpha, value);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+void NPREffect::SetColorAndAlpha(FXMVECTOR value)
+{
+ pImpl->constants.diffuseColorAndAlpha = value;
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+// Texture settings.
+// TODO: Implement texture settings.
+
+
+// Shader mode setting.
+void NPREffect::SetMode(Mode mode)
+{
+ if (static_cast(mode) < 0 || static_cast(mode) >= NPREffectTraits::ModeCount)
+ {
+ throw std::invalid_argument("Unsupported mode");
+ }
+
+ pImpl->nprMode = mode;
+}
+
+
+// Cel shading settings.
+void NPREffect::SetCelShaderBands(int bands)
+{
+ // Set w of lightDirectionAndCelBands.
+ pImpl->constants.lightDirectionAndCelBands = XMVectorSetW(pImpl->constants.lightDirectionAndCelBands, static_cast(bands));
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+// Gooch shading settings.
+void NPREffect::SetGoochCoolColor(FXMVECTOR value, float alpha)
+{
+ pImpl->constants.goochCoolColorAndAlpha = XMVectorSetW(value, alpha);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+void NPREffect::SetGoochWarmColor(FXMVECTOR value, float beta)
+{
+ pImpl->constants.goochWarmColorAndBeta = XMVectorSetW(value, beta);
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+}
+
+
+// Vertex color setting.
+void NPREffect::SetVertexColorEnabled(bool value)
+{
+ pImpl->vertexColorEnabled = value;
+}
+
+
+// Normal compression settings.
+void NPREffect::SetBiasedVertexNormals(bool value)
+{
+ pImpl->biasedVertexNormals = value;
+}
+
+
+// Instancing settings.
+void NPREffect::SetInstancingEnabled(bool value)
+{
+ pImpl->instancing = value;
+}
diff --git a/Src/Shaders/CompileShaders.cmd b/Src/Shaders/CompileShaders.cmd
index 2d7bb9fb..fff8d52f 100644
--- a/Src/Shaders/CompileShaders.cmd
+++ b/Src/Shaders/CompileShaders.cmd
@@ -226,6 +226,19 @@ call :CompileShaderSM4%1 DebugEffect ps PSRGBNormals
call :CompileShaderSM4%1 DebugEffect ps PSRGBTangents
call :CompileShaderSM4%1 DebugEffect ps PSRGBBiTangents
+call :CompileShaderSM4%1 NPREffect vs VSNPREffect
+call :CompileShaderSM4%1 NPREffect vs VSNPREffectBn
+call :CompileShaderSM4%1 NPREffect vs VSNPREffectVc
+call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcBn
+
+call :CompileShaderSM4%1 NPREffect vs VSNPREffectInst
+call :CompileShaderSM4%1 NPREffect vs VSNPREffectBnInst
+call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcInst
+call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcBnInst
+
+call :CompileShaderSM4%1 NPREffect ps PSCelShading
+call :CompileShaderSM4%1 NPREffect ps PSGoochShading
+
call :CompileShader%1 SpriteEffect vs SpriteVertexShader
call :CompileShader%1 SpriteEffect ps SpritePixelShader
diff --git a/Src/Shaders/NPREffect.fx b/Src/Shaders/NPREffect.fx
new file mode 100644
index 00000000..abc63590
--- /dev/null
+++ b/Src/Shaders/NPREffect.fx
@@ -0,0 +1,221 @@
+//--------------------------------------------------------------------------------------
+// File: NPREffect.fx
+//
+// Non-photorealistic rendering effects (cel shading and Gooch shading)
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// https://go.microsoft.com/fwlink/?LinkId=248929
+//--------------------------------------------------------------------------------------
+
+
+cbuffer Parameters : register(b0)
+{
+ float3 LightDirection : packoffset(c0);
+ float CelBands : packoffset(c0.w);
+
+ float3 DiffuseColor : packoffset(c1);
+ float Alpha : packoffset(c1.w);
+
+ float3 SpecularColor : packoffset(c2);
+ float SpecularPower : packoffset(c2.w);
+
+ float3 GoochCoolColor : packoffset(c3);
+ float GoochAlpha : packoffset(c3.w);
+
+ float3 GoochWarmColor : packoffset(c4);
+ float GoochBeta : packoffset(c4.w);
+
+ float3 EyePosition : packoffset(c5);
+
+ float4x4 World : packoffset(c6);
+ float3x3 WorldInverseTranspose : packoffset(c10);
+ float4x4 WorldViewProj : packoffset(c13);
+};
+
+
+#include "Structures.fxh"
+#include "Utilities.fxh"
+
+// Vertex shader: basic
+VSOutputPixelLightingTx VSNPREffect(VSInputNmTx vin)
+{
+ VSOutputPixelLightingTx vout;
+
+ vout.PositionPS = mul(vin.Position, WorldViewProj);
+ vout.PositionWS = float4(mul(vin.Position, World).xyz, 1);
+ vout.NormalWS = normalize(mul(vin.Normal, WorldInverseTranspose));
+ vout.Diffuse = float4(DiffuseColor, Alpha);
+ vout.TexCoord = vin.TexCoord;
+
+ return vout;
+}
+
+VSOutputPixelLightingTx VSNPREffectBn(VSInputNmTx vin)
+{
+ VSOutputPixelLightingTx vout;
+
+ float3 normal = BiasX2(vin.Normal);
+
+ vout.PositionPS = mul(vin.Position, WorldViewProj);
+ vout.PositionWS = float4(mul(vin.Position, World).xyz, 1);
+ vout.NormalWS = normalize(mul(normal, WorldInverseTranspose));
+ vout.Diffuse = float4(DiffuseColor, Alpha);
+ vout.TexCoord = vin.TexCoord;
+
+ return vout;
+}
+
+
+// Vertex shader: vertex color
+VSOutputPixelLightingTx VSNPREffectVc(VSInputNmTxVc vin)
+{
+ VSOutputPixelLightingTx vout;
+
+ vout.PositionPS = mul(vin.Position, WorldViewProj);
+ vout.PositionWS = float4(mul(vin.Position, World).xyz, 1);
+ vout.NormalWS = normalize(mul(vin.Normal, WorldInverseTranspose));
+ vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor;
+ vout.Diffuse.a = vin.Color.a * Alpha;
+ vout.TexCoord = vin.TexCoord;
+
+ return vout;
+}
+
+VSOutputPixelLightingTx VSNPREffectVcBn(VSInputNmTxVc vin)
+{
+ VSOutputPixelLightingTx vout;
+
+ float3 normal = BiasX2(vin.Normal);
+
+ vout.PositionPS = mul(vin.Position, WorldViewProj);
+ vout.PositionWS = float4(mul(vin.Position, World).xyz, 1);
+ vout.NormalWS = normalize(mul(normal, WorldInverseTranspose));
+ vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor;
+ vout.Diffuse.a = vin.Color.a * Alpha;
+ vout.TexCoord = vin.TexCoord;
+
+ return vout;
+}
+
+
+// Vertex shader: instancing
+VSOutputPixelLightingTx VSNPREffectInst(VSInputNmTxInst vin)
+{
+ VSOutputPixelLightingTx vout;
+
+ CommonInstancing inst = ComputeCommonInstancing(vin.Position, vin.Normal, vin.Transform);
+
+ vout.PositionPS = mul(inst.Position, WorldViewProj);
+ vout.PositionWS = float4(mul(inst.Position, World).xyz, 1);
+ vout.NormalWS = normalize(mul(inst.Normal, WorldInverseTranspose));
+ vout.Diffuse = float4(DiffuseColor, Alpha);
+ vout.TexCoord = vin.TexCoord;
+
+ return vout;
+}
+
+VSOutputPixelLightingTx VSNPREffectBnInst(VSInputNmTxInst vin)
+{
+ VSOutputPixelLightingTx vout;
+
+ float3 normal = BiasX2(vin.Normal);
+
+ CommonInstancing inst = ComputeCommonInstancing(vin.Position, normal, vin.Transform);
+
+ vout.PositionPS = mul(inst.Position, WorldViewProj);
+ vout.PositionWS = float4(mul(inst.Position, World).xyz, 1);
+ vout.NormalWS = normalize(mul(inst.Normal, WorldInverseTranspose));
+ vout.Diffuse = float4(DiffuseColor, Alpha);
+ vout.TexCoord = vin.TexCoord;
+
+ return vout;
+}
+
+
+// Vertex shader: vertex color + instancing
+VSOutputPixelLightingTx VSNPREffectVcInst(VSInputNmTxVcInst vin)
+{
+ VSOutputPixelLightingTx vout;
+
+ CommonInstancing inst = ComputeCommonInstancing(vin.Position, vin.Normal, vin.Transform);
+
+ vout.PositionPS = mul(inst.Position, WorldViewProj);
+ vout.PositionWS = float4(mul(inst.Position, World).xyz, 1);
+ vout.NormalWS = normalize(mul(inst.Normal, WorldInverseTranspose));
+ vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor;
+ vout.Diffuse.a = vin.Color.a * Alpha;
+ vout.TexCoord = vin.TexCoord;
+
+ return vout;
+}
+
+VSOutputPixelLightingTx VSNPREffectVcBnInst(VSInputNmTxVcInst vin)
+{
+ VSOutputPixelLightingTx vout;
+
+ float3 normal = BiasX2(vin.Normal);
+
+ CommonInstancing inst = ComputeCommonInstancing(vin.Position, normal, vin.Transform);
+
+ vout.PositionPS = mul(inst.Position, WorldViewProj);
+ vout.PositionWS = float4(mul(inst.Position, World).xyz, 1);
+ vout.NormalWS = normalize(mul(inst.Normal, WorldInverseTranspose));
+ vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor;
+ vout.Diffuse.a = vin.Color.a * Alpha;
+ vout.TexCoord = vin.TexCoord;
+
+ return vout;
+}
+
+
+// Pixel shader: cel shading
+float4 PSCelShading(PSInputPixelLightingTx pin) : SV_Target0
+{
+ float3 normal = normalize(pin.NormalWS);
+ float3 lightDir = normalize(-LightDirection);
+
+ // Quantize the diffuse lighting into discrete bands
+ float NdotL = dot(normal, lightDir);
+ float intensity = max(0, NdotL);
+ float quantized = floor(intensity * CelBands) / CelBands;
+
+ float3 color = pin.Diffuse.rgb * quantized;
+
+ // Specular highlight (hard edge)
+ float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz);
+ float3 halfVec = normalize(lightDir + viewDir);
+ float NdotH = max(0, dot(normal, halfVec));
+ float specular = step(0.95, pow(NdotH, SpecularPower));
+
+ color += SpecularColor * specular;
+
+ return float4(color, pin.Diffuse.a);
+}
+
+
+// Pixel shader: Gooch shading
+float4 PSGoochShading(PSInputPixelLightingTx pin) : SV_Target0
+{
+ float3 normal = normalize(pin.NormalWS);
+ float3 lightDir = normalize(-LightDirection);
+
+ // Gooch diffuse term: blend between cool and warm based on NdotL
+ float NdotL = dot(normal, lightDir);
+ float t = (1.0 + NdotL) * 0.5;
+
+ float3 coolContrib = GoochCoolColor + GoochAlpha * pin.Diffuse.rgb;
+ float3 warmContrib = GoochWarmColor + GoochBeta * pin.Diffuse.rgb;
+
+ float3 color = lerp(coolContrib, warmContrib, t);
+
+ // Specular highlight
+ float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz);
+ float3 reflectDir = reflect(LightDirection, normal);
+ float spec = pow(max(0, dot(viewDir, reflectDir)), SpecularPower);
+
+ color += SpecularColor * spec;
+
+ return float4(color, pin.Diffuse.a);
+}