From 7569fb727672622466e2d8d59b7711264d250017 Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Mon, 8 Jun 2026 12:26:52 -0700 Subject: [PATCH 1/3] NPREffect to support cel shading, gooch, etc. --- CMakeLists.txt | 2 + DirectXTK_Desktop_2022.vcxproj | 4 + DirectXTK_Desktop_2022.vcxproj.filters | 6 + DirectXTK_Desktop_2022_Win10.vcxproj | 4 + DirectXTK_Desktop_2022_Win10.vcxproj.filters | 6 + DirectXTK_Desktop_2026.vcxproj | 4 + DirectXTK_Desktop_2026.vcxproj.filters | 6 + DirectXTK_GDKW_2022.vcxproj | 2 + DirectXTK_GDKW_2022.vcxproj.filters | 6 + DirectXTK_GDK_2022.vcxproj | 2 + DirectXTK_GDK_2022.vcxproj.filters | 6 + DirectXTK_Windows10_2022.vcxproj | 4 + DirectXTK_Windows10_2022.vcxproj.filters | 6 + Inc/Effects.h | 83 +++ Src/NPREffect.cpp | 500 +++++++++++++++++++ Src/Shaders/CompileShaders.cmd | 13 + Src/Shaders/NPREffect.fx | 221 ++++++++ 17 files changed, 875 insertions(+) create mode 100644 Src/NPREffect.cpp create mode 100644 Src/Shaders/NPREffect.fx diff --git a/CMakeLists.txt b/CMakeLists.txt index d97b3ebe0..fbae3fe70 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 d03abf3d5..d66aefc47 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 6f61bad47..055345812 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 8ff0f6c1d..0f6540d6d 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 3eead7e89..cfea2c700 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 bf6e87bae..5418c4d3e 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 3eead7e89..cfea2c700 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 4accaa96e..f309eeb1a 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 f3b9fda7f..511fb4177 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 0e9b32c8a..d4f512a99 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 f3b9fda7f..511fb4177 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 6b97f4836..25ee0c87e 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 e875551d9..354afcf73 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 695f7bb48..5e47edb3c 100644 --- a/Inc/Effects.h +++ b/Inc/Effects.h @@ -878,6 +878,89 @@ 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 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 000000000..3abd48158 --- /dev/null +++ b/Src/NPREffect.cpp @@ -0,0 +1,500 @@ +//-------------------------------------------------------------------------------------- +// 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::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 2d7bb9fb9..fff8d52f2 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 000000000..abc635909 --- /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); +} From 8c8af3ef5e0a91bf201acc31c5f1a8bc6a8ce8e7 Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Mon, 8 Jun 2026 12:32:11 -0700 Subject: [PATCH 2/3] Pick lint --- Inc/Effects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Inc/Effects.h b/Inc/Effects.h index 5e47edb3c..1fead3cf5 100644 --- a/Inc/Effects.h +++ b/Inc/Effects.h @@ -920,7 +920,7 @@ namespace DirectX DIRECTX_TOOLKIT_API void __cdecl SetAlpha(float value); DIRECTX_TOOLKIT_API void XM_CALLCONV SetColorAndAlpha(FXMVECTOR value); - // Light settings. + // Light settings. void XM_CALLCONV SetLightDirection(int whichLight, FXMVECTOR value) override; DIRECTX_TOOLKIT_API void __cdecl EnableDefaultLighting() override; From 18b5deece197a0196cb4234a199b49a48cc2347d Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Tue, 9 Jun 2026 23:49:54 -0700 Subject: [PATCH 3/3] Added missing interface methods --- Inc/Effects.h | 2 ++ Src/NPREffect.cpp | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Inc/Effects.h b/Inc/Effects.h index 1fead3cf5..d30c2f5a9 100644 --- a/Inc/Effects.h +++ b/Inc/Effects.h @@ -955,6 +955,8 @@ namespace DirectX 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; diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp index 3abd48158..25a188312 100644 --- a/Src/NPREffect.cpp +++ b/Src/NPREffect.cpp @@ -334,6 +334,18 @@ void XM_CALLCONV NPREffect::SetMatrices(FXMMATRIX world, CXMMATRIX view, CXMMATR // Light settings. +void NPREffect::SetLightingEnabled(bool) +{ + // Unsupported interface. +} + + +void NPREffect::SetPerPixelLighting(bool) +{ + // Unsupported interface. +} + + void NPREffect::SetAmbientLightColor(FXMVECTOR) { // Unsupported interface.