diff --git a/CMakeLists.txt b/CMakeLists.txt
index 122a66f8..4ed762df 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -151,6 +151,7 @@ set(LIBRARY_SOURCES
Src/ModelLoadSDKMESH.cpp
Src/ModelLoadVBO.cpp
Src/NormalMapEffect.cpp
+ Src/NPREffect.cpp
Src/PBREffect.cpp
Src/PBREffectFactory.cpp
Src/pch.h
@@ -172,6 +173,7 @@ set(SHADER_SOURCES
Src/Shaders/EnvironmentMapEffect.fx
Src/Shaders/GenerateMips.hlsl
Src/Shaders/NormalMapEffect.fx
+ Src/Shaders/NPREffect.fx
Src/Shaders/PBREffect.fx
Src/Shaders/PostProcess.fx
Src/Shaders/RootSig.fxh
diff --git a/DirectXTK_Desktop_2022_Win10.vcxproj b/DirectXTK_Desktop_2022_Win10.vcxproj
index 89db95de..4965e855 100644
--- a/DirectXTK_Desktop_2022_Win10.vcxproj
+++ b/DirectXTK_Desktop_2022_Win10.vcxproj
@@ -111,6 +111,7 @@
+
@@ -193,6 +194,11 @@
Document
+
+
+ Document
+
+
{3E0E8608-CD9B-4C76-AF33-29CA38F2C9F0}
Win32Proj
diff --git a/DirectXTK_Desktop_2022_Win10.vcxproj.filters b/DirectXTK_Desktop_2022_Win10.vcxproj.filters
index 1ec33539..5116af65 100644
--- a/DirectXTK_Desktop_2022_Win10.vcxproj.filters
+++ b/DirectXTK_Desktop_2022_Win10.vcxproj.filters
@@ -281,6 +281,9 @@
Src
+
+ Src
+
Src
@@ -376,6 +379,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
diff --git a/DirectXTK_Desktop_2026.vcxproj b/DirectXTK_Desktop_2026.vcxproj
index fd498f0d..5704fd65 100644
--- a/DirectXTK_Desktop_2026.vcxproj
+++ b/DirectXTK_Desktop_2026.vcxproj
@@ -111,6 +111,7 @@
+
@@ -193,6 +194,11 @@
Document
+
+
+ Document
+
+
{3E0E8608-CD9B-4C76-AF33-29CA38F2C9F0}
Win32Proj
diff --git a/DirectXTK_Desktop_2026.vcxproj.filters b/DirectXTK_Desktop_2026.vcxproj.filters
index 1ec33539..5116af65 100644
--- a/DirectXTK_Desktop_2026.vcxproj.filters
+++ b/DirectXTK_Desktop_2026.vcxproj.filters
@@ -281,6 +281,9 @@
Src
+
+ Src
+
Src
@@ -376,6 +379,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
diff --git a/DirectXTK_GDKW_2022.vcxproj b/DirectXTK_GDKW_2022.vcxproj
index bf83e1a1..d83bf563 100644
--- a/DirectXTK_GDKW_2022.vcxproj
+++ b/DirectXTK_GDKW_2022.vcxproj
@@ -438,6 +438,7 @@
+
@@ -511,6 +512,11 @@
Document
+
+
+ Document
+
+
diff --git a/DirectXTK_GDKW_2022.vcxproj.filters b/DirectXTK_GDKW_2022.vcxproj.filters
index cb5d5384..8a878fbe 100644
--- a/DirectXTK_GDKW_2022.vcxproj.filters
+++ b/DirectXTK_GDKW_2022.vcxproj.filters
@@ -260,6 +260,9 @@
Src
+
+ Src
+
Src
@@ -346,6 +349,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders
diff --git a/DirectXTK_GDKX_2022.vcxproj b/DirectXTK_GDKX_2022.vcxproj
index fef34f47..60aec220 100644
--- a/DirectXTK_GDKX_2022.vcxproj
+++ b/DirectXTK_GDKX_2022.vcxproj
@@ -612,6 +612,7 @@
+
@@ -693,6 +694,11 @@
Document
+
+
+ Document
+
+
diff --git a/DirectXTK_GDKX_2022.vcxproj.filters b/DirectXTK_GDKX_2022.vcxproj.filters
index b3e20206..efb30072 100644
--- a/DirectXTK_GDKX_2022.vcxproj.filters
+++ b/DirectXTK_GDKX_2022.vcxproj.filters
@@ -266,6 +266,9 @@
Src
+
+ Src
+
Src
@@ -355,6 +358,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders
diff --git a/DirectXTK_GDKX_2026.vcxproj b/DirectXTK_GDKX_2026.vcxproj
index 60e1b528..9f85832f 100644
--- a/DirectXTK_GDKX_2026.vcxproj
+++ b/DirectXTK_GDKX_2026.vcxproj
@@ -612,6 +612,7 @@
+
@@ -693,6 +694,11 @@
Document
+
+
+ Document
+
+
diff --git a/DirectXTK_GDKX_2026.vcxproj.filters b/DirectXTK_GDKX_2026.vcxproj.filters
index b3e20206..efb30072 100644
--- a/DirectXTK_GDKX_2026.vcxproj.filters
+++ b/DirectXTK_GDKX_2026.vcxproj.filters
@@ -266,6 +266,9 @@
Src
+
+ Src
+
Src
@@ -355,6 +358,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders
diff --git a/DirectXTK_GDK_2022.vcxproj b/DirectXTK_GDK_2022.vcxproj
index 1542f005..fdbd09bf 100644
--- a/DirectXTK_GDK_2022.vcxproj
+++ b/DirectXTK_GDK_2022.vcxproj
@@ -597,6 +597,7 @@
+
@@ -678,6 +679,11 @@
Document
+
+
+ Document
+
+
diff --git a/DirectXTK_GDK_2022.vcxproj.filters b/DirectXTK_GDK_2022.vcxproj.filters
index b3e20206..efb30072 100644
--- a/DirectXTK_GDK_2022.vcxproj.filters
+++ b/DirectXTK_GDK_2022.vcxproj.filters
@@ -266,6 +266,9 @@
Src
+
+ Src
+
Src
@@ -355,6 +358,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders
diff --git a/DirectXTK_Windows10_2022.vcxproj b/DirectXTK_Windows10_2022.vcxproj
index 473be188..6a19fdd2 100644
--- a/DirectXTK_Windows10_2022.vcxproj
+++ b/DirectXTK_Windows10_2022.vcxproj
@@ -124,6 +124,7 @@
+
@@ -195,6 +196,11 @@
Document
+
+
+ Document
+
+
{945B8F0E-AE5F-447C-933A-9D069532D3E4}
StaticLibrary
diff --git a/DirectXTK_Windows10_2022.vcxproj.filters b/DirectXTK_Windows10_2022.vcxproj.filters
index 8bfddbfc..9f01fe06 100644
--- a/DirectXTK_Windows10_2022.vcxproj.filters
+++ b/DirectXTK_Windows10_2022.vcxproj.filters
@@ -221,6 +221,9 @@
Src\Shaders
+
+ Src\Shaders
+
Src\Shaders\Shared
@@ -350,6 +353,9 @@
Src
+
+ Src
+
Src
diff --git a/Inc/Effects.h b/Inc/Effects.h
index aa0343d5..1493f870 100644
--- a/Inc/Effects.h
+++ b/Inc/Effects.h
@@ -751,6 +751,78 @@ namespace DirectX
};
+ //------------------------------------------------------------------------------
+ // 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 NPREffect(
+ _In_ ID3D12Device* device,
+ uint32_t effectFlags,
+ const EffectPipelineStateDescription& pipelineDescription,
+ Mode nprMode = Mode_Cel);
+
+ 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_ ID3D12GraphicsCommandList* commandList) 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.
+
+ // 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);
+
+ private:
+ // Private implementation.
+ class Impl;
+
+ std::unique_ptr pImpl;
+
+ // Unsupported interface methods.
+ 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 texture resources
class DIRECTX_TOOLKIT_API IEffectTextureFactory
diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp
new file mode 100644
index 00000000..104b79ed
--- /dev/null
+++ b/Src/NPREffect.cpp
@@ -0,0 +1,567 @@
+//--------------------------------------------------------------------------------------
+// File: NPREffect.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// https://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#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 RootSignatureCount = 1;
+ };
+
+
+ // 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:
+ Impl(_In_ ID3D12Device* device, uint32_t effectFlags, const EffectPipelineStateDescription& pipelineDescription,
+ NPREffect::Mode nprMode);
+
+ Impl(const Impl&) = delete;
+ Impl& operator=(const Impl&) = delete;
+
+ Impl(Impl&&) = default;
+ Impl& operator=(Impl&&) = default;
+
+ enum RootParameterIndex
+ {
+ ConstantBuffer,
+ RootParameterCount
+ };
+
+ int GetPipelineStatePermutation(NPREffect::Mode nprMode, uint32_t effectFlags) const noexcept;
+
+ void Apply(_In_ ID3D12GraphicsCommandList* commandList);
+};
+
+
+#pragma region Shaders
+// Include the precompiled shader code.
+namespace
+{
+#ifdef _GAMING_XBOX_SCARLETT
+#include "XboxGamingScarlettNPREffect_VSNPREffect.inc"
+#include "XboxGamingScarlettNPREffect_VSNPREffectInst.inc"
+
+#include "XboxGamingScarlettNPREffect_VSNPREffectVc.inc"
+#include "XboxGamingScarlettNPREffect_VSNPREffectVcInst.inc"
+
+#include "XboxGamingScarlettNPREffect_VSNPREffectBn.inc"
+#include "XboxGamingScarlettNPREffect_VSNPREffectBnInst.inc"
+
+#include "XboxGamingScarlettNPREffect_VSNPREffectVcBn.inc"
+#include "XboxGamingScarlettNPREffect_VSNPREffectVcBnInst.inc"
+
+#include "XboxGamingScarlettNPREffect_PSCelShading.inc"
+#include "XboxGamingScarlettNPREffect_PSGoochShading.inc"
+#elif defined(_GAMING_XBOX)
+#include "XboxGamingXboxOneNPREffect_VSNPREffect.inc"
+#include "XboxGamingXboxOneNPREffect_VSNPREffectInst.inc"
+
+#include "XboxGamingXboxOneNPREffect_VSNPREffectVc.inc"
+#include "XboxGamingXboxOneNPREffect_VSNPREffectVcInst.inc"
+
+#include "XboxGamingXboxOneNPREffect_VSNPREffectBn.inc"
+#include "XboxGamingXboxOneNPREffect_VSNPREffectBnInst.inc"
+
+#include "XboxGamingXboxOneNPREffect_VSNPREffectVcBn.inc"
+#include "XboxGamingXboxOneNPREffect_VSNPREffectVcBnInst.inc"
+
+#include "XboxGamingXboxOneNPREffect_PSCelShading.inc"
+#include "XboxGamingXboxOneNPREffect_PSGoochShading.inc"
+#elif 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 D3D12_SHADER_BYTECODE 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, // biased normals + cel shading
+ 2, // biased normals + gooch shading
+
+ 3, // vertex color + biased normals + cel shading
+ 3, // vertex color + biased normals + 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 normals + cel shading
+ 6, // instancing + biased normals + gooch shading
+
+ 7, // instancing + vertex color + biased normals + cel shading
+ 7, // instancing + vertex color + biased normals + gooch shading
+};
+
+template<>
+const D3D12_SHADER_BYTECODE 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, // biased normals + cel shading
+ 1, // biased normals + gooch shading
+
+ 0, // vertex color + biased normals + cel shading
+ 1, // vertex color + biased normals + 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 normals + cel shading
+ 1, // instancing + biased normals + gooch shading
+
+ 0, // instancing + vertex color + biased normals + cel shading
+ 1, // instancing + vertex color + biased normals + gooch shading
+};
+#pragma endregion
+
+// Global pool of per-device NPREffect resources.
+template<>
+SharedResourcePool::DeviceResources> EffectBase::deviceResourcesPool = {};
+
+
+// Constructor.
+NPREffect::Impl::Impl(
+ _In_ ID3D12Device* device,
+ uint32_t effectFlags,
+ const EffectPipelineStateDescription& pipelineDescription,
+ NPREffect::Mode nprMode)
+ : EffectBase(device)
+{
+ 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;
+
+ switch (nprMode)
+ {
+ case Mode_Cel:
+ case Mode_Gooch:
+ break;
+
+ default:
+ throw std::invalid_argument("Invalid nprMode");
+ }
+
+ // Create root signature.
+ {
+ ENUM_FLAGS_CONSTEXPR D3D12_ROOT_SIGNATURE_FLAGS rootSignatureFlags =
+ D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
+ | D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS
+ | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS
+ | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS
+ #ifdef _GAMING_XBOX_SCARLETT
+ | D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS
+ | D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS
+ #endif
+ ;
+
+ CD3DX12_ROOT_PARAMETER rootParameters[RootParameterIndex::RootParameterCount] = {};
+ rootParameters[RootParameterIndex::ConstantBuffer].InitAsConstantBufferView(0, 0, D3D12_SHADER_VISIBILITY_ALL);
+
+ CD3DX12_ROOT_SIGNATURE_DESC rsigDesc = {};
+ rsigDesc.Init(1, rootParameters, 0, nullptr, rootSignatureFlags);
+
+ mRootSignature = GetRootSignature(0, rsigDesc);
+ }
+
+ assert(mRootSignature != nullptr);
+
+ // Create pipeline state.
+ const int sp = GetPipelineStatePermutation(nprMode, effectFlags);
+ assert(sp >= 0 && sp < NPREffectTraits::ShaderPermutationCount);
+ _Analysis_assume_(sp >= 0 && sp < NPREffectTraits::ShaderPermutationCount);
+
+ const int vi = EffectBase::VertexShaderIndices[sp];
+ assert(vi >= 0 && vi < NPREffectTraits::VertexShaderCount);
+ _Analysis_assume_(vi >= 0 && vi < NPREffectTraits::VertexShaderCount);
+ const int pi = EffectBase::PixelShaderIndices[sp];
+ assert(pi >= 0 && pi < NPREffectTraits::PixelShaderCount);
+ _Analysis_assume_(pi >= 0 && pi < NPREffectTraits::PixelShaderCount);
+
+ pipelineDescription.CreatePipelineState(
+ device,
+ mRootSignature,
+ EffectBase::VertexShaderBytecode[vi],
+ EffectBase::PixelShaderBytecode[pi],
+ mPipelineState.GetAddressOf());
+
+ SetDebugObjectName(mPipelineState.Get(), L"NPREffect");
+}
+
+
+int NPREffect::Impl::GetPipelineStatePermutation(NPREffect::Mode nprMode, uint32_t effectFlags) const noexcept
+{
+ int permutation = static_cast(nprMode);
+
+ // Support vertex coloring?
+ if (effectFlags & EffectFlags::VertexColor)
+ {
+ permutation += 2;
+ }
+
+ if (effectFlags & EffectFlags::BiasedVertexNormals)
+ {
+ // Compressed normals need to be scaled and biased in the vertex shader.
+ permutation += 4;
+ }
+
+ if (effectFlags & EffectFlags::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_ ID3D12GraphicsCommandList* commandList)
+{
+ // 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;
+ }
+
+ // Eye position.
+ if (dirtyFlags & EffectDirtyFlags::EyePosition)
+ {
+ const XMMATRIX viewInverse = XMMatrixInverse(nullptr, matrices.view);
+ constants.eyePosition = viewInverse.r[3];
+
+ dirtyFlags &= ~EffectDirtyFlags::EyePosition;
+ dirtyFlags |= EffectDirtyFlags::ConstantBuffer;
+ }
+
+ UpdateConstants();
+
+ // Set the root signature
+ commandList->SetGraphicsRootSignature(mRootSignature);
+
+ // Set constants
+ commandList->SetGraphicsRootConstantBufferView(RootParameterIndex::ConstantBuffer, GetConstantBufferGpuAddress());
+
+ // Set the pipeline state
+ commandList->SetPipelineState(EffectBase::mPipelineState.Get());
+}
+
+
+// Public constructor.
+NPREffect::NPREffect(
+ _In_ ID3D12Device* device,
+ uint32_t effectFlags,
+ const EffectPipelineStateDescription& pipelineDescription,
+ Mode nprMode)
+ : pImpl(std::make_unique(device, effectFlags, pipelineDescription, nprMode))
+{}
+
+
+NPREffect::NPREffect(NPREffect&&) noexcept = default;
+NPREffect& NPREffect::operator= (NPREffect&&) noexcept = default;
+NPREffect::~NPREffect() = default;
+
+
+// IEffect methods.
+void NPREffect::Apply(_In_ ID3D12GraphicsCommandList* commandList)
+{
+ pImpl->Apply(commandList);
+}
+
+
+// Camera settings.
+void NPREffect::SetWorld(FXMMATRIX value)
+{
+ pImpl->matrices.world = value;
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj | EffectDirtyFlags::WorldInverseTranspose;
+}
+
+
+void NPREffect::SetView(FXMMATRIX value)
+{
+ pImpl->matrices.view = value;
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj | EffectDirtyFlags::EyePosition;
+}
+
+
+void NPREffect::SetProjection(FXMMATRIX value)
+{
+ pImpl->matrices.projection = value;
+
+ pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj;
+}
+
+
+void 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 | EffectDirtyFlags::EyePosition;
+}
+
+
+// Light settings.
+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.
+
+
+// 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;
+}
diff --git a/Src/Shaders/CompileShaders.cmd b/Src/Shaders/CompileShaders.cmd
index f1f2317a..edaa0f78 100644
--- a/Src/Shaders/CompileShaders.cmd
+++ b/Src/Shaders/CompileShaders.cmd
@@ -258,6 +258,19 @@ call :CompileShader%1 DebugEffect ps PSRGBNormals
call :CompileShader%1 DebugEffect ps PSRGBTangents
call :CompileShader%1 DebugEffect ps PSRGBBiTangents
+call :CompileShader%1 NPREffect vs VSNPREffect
+call :CompileShader%1 NPREffect vs VSNPREffectBn
+call :CompileShader%1 NPREffect vs VSNPREffectVc
+call :CompileShader%1 NPREffect vs VSNPREffectVcBn
+
+call :CompileShader%1 NPREffect vs VSNPREffectInst
+call :CompileShader%1 NPREffect vs VSNPREffectBnInst
+call :CompileShader%1 NPREffect vs VSNPREffectVcInst
+call :CompileShader%1 NPREffect vs VSNPREffectVcBnInst
+
+call :CompileShader%1 NPREffect ps PSCelShading
+call :CompileShader%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..548aec3e
--- /dev/null
+++ b/Src/Shaders/NPREffect.fx
@@ -0,0 +1,232 @@
+//--------------------------------------------------------------------------------------
+// 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=615561
+//--------------------------------------------------------------------------------------
+
+
+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 "RootSig.fxh"
+#include "Structures.fxh"
+#include "Utilities.fxh"
+
+// Vertex shader: basic
+[RootSignature(NPREffectRS)]
+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;
+}
+
+[RootSignature(NPREffectRS)]
+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
+[RootSignature(NPREffectRS)]
+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;
+}
+
+[RootSignature(NPREffectRS)]
+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
+[RootSignature(NPREffectRS)]
+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;
+}
+
+[RootSignature(NPREffectRS)]
+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
+[RootSignature(NPREffectRS)]
+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;
+}
+
+[RootSignature(NPREffectRS)]
+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
+[RootSignature(NPREffectRS)]
+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
+[RootSignature(NPREffectRS)]
+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);
+}
diff --git a/Src/Shaders/RootSig.fxh b/Src/Shaders/RootSig.fxh
index 45e3198a..83d891cb 100644
--- a/Src/Shaders/RootSig.fxh
+++ b/Src/Shaders/RootSig.fxh
@@ -202,6 +202,15 @@
" DENY_MESH_SHADER_ROOT_ACCESS )," \
"CBV(b0)"
+#define NPREffectRS \
+"RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \
+" DENY_AMPLIFICATION_SHADER_ROOT_ACCESS |" \
+" DENY_DOMAIN_SHADER_ROOT_ACCESS |" \
+" DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \
+" DENY_HULL_SHADER_ROOT_ACCESS |" \
+" DENY_MESH_SHADER_ROOT_ACCESS )," \
+"CBV(b0)"
+
#else // !__XBOX_SCARLETT
#define NoTextureRS \
@@ -371,4 +380,11 @@
" DENY_HULL_SHADER_ROOT_ACCESS )," \
"CBV(b0)"
+#define NPREffectRS \
+"RootFlags ( ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |" \
+" DENY_DOMAIN_SHADER_ROOT_ACCESS |" \
+" DENY_GEOMETRY_SHADER_ROOT_ACCESS |" \
+" DENY_HULL_SHADER_ROOT_ACCESS )," \
+"CBV(b0)"
+
#endif