From 0d24af1094be4cb873166bcdc8314666d2ebd9aa Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Mon, 8 Jun 2026 12:26:52 -0700 Subject: [PATCH 01/13] 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 141347a4a..1da832dde 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 25090838741144894e08795c4e91ab6de8d0ff0b Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Mon, 8 Jun 2026 12:32:11 -0700 Subject: [PATCH 02/13] 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 1366139f541d6e0e277a43b4802a4b86d34e36ee Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Tue, 9 Jun 2026 23:49:54 -0700 Subject: [PATCH 03/13] 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. From 9d59af43469bd29241a6befbf5cbeb7b82f9fddd Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Tue, 14 Jul 2026 11:01:16 -0700 Subject: [PATCH 04/13] Fix eye position update --- Src/NPREffect.cpp | 20 ++++++++++++++++---- Src/Shaders/NPREffect.fx | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp index 25a188312..79bd4b7a3 100644 --- a/Src/NPREffect.cpp +++ b/Src/NPREffect.cpp @@ -269,6 +269,16 @@ void NPREffect::Impl::Apply(_In_ ID3D11DeviceContext* deviceContext) 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; + } + // Set shaders and constant buffers. ApplyShaders(deviceContext, GetCurrentShaderPermutation()); } @@ -311,7 +321,7 @@ void XM_CALLCONV NPREffect::SetView(FXMMATRIX value) { pImpl->matrices.view = value; - pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj; + pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj | EffectDirtyFlags::EyePosition; } @@ -329,7 +339,7 @@ void XM_CALLCONV NPREffect::SetMatrices(FXMMATRIX world, CXMMATRIX view, CXMMATR pImpl->matrices.view = view; pImpl->matrices.projection = projection; - pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj | EffectDirtyFlags::WorldInverseTranspose; + pImpl->dirtyFlags |= EffectDirtyFlags::WorldViewProj | EffectDirtyFlags::WorldInverseTranspose | EffectDirtyFlags::EyePosition; } @@ -424,8 +434,10 @@ void NPREffect::SetSpecularPower(float value) void NPREffect::DisableSpecular() { - // Set w of specularColorAndSpecularPower to 0. - pImpl->constants.specularColorAndSpecularPower = XMVectorSetW(pImpl->constants.specularColorAndSpecularPower, 0.0f); + // Set specular color to black, power to 1 + // Note: Don't use a power of 0 or the shader will generate strange highlights on non-specular materials + + pImpl->constants.specularColorAndSpecularPower = g_XMIdentityR3; pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; } diff --git a/Src/Shaders/NPREffect.fx b/Src/Shaders/NPREffect.fx index abc635909..0daeefd30 100644 --- a/Src/Shaders/NPREffect.fx +++ b/Src/Shaders/NPREffect.fx @@ -213,9 +213,9 @@ float4 PSGoochShading(PSInputPixelLightingTx pin) : SV_Target0 // Specular highlight float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); float3 reflectDir = reflect(LightDirection, normal); - float spec = pow(max(0, dot(viewDir, reflectDir)), SpecularPower); + float specular = pow(max(0, dot(viewDir, reflectDir)), SpecularPower); - color += SpecularColor * spec; + color += SpecularColor * specular; return float4(color, pin.Diffuse.a); } From 628d3acad0ef4de1a284be75762c641a32a4fb6a Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Tue, 14 Jul 2026 16:03:28 -0700 Subject: [PATCH 05/13] Add texture support to NPREffect --- Inc/Effects.h | 5 +- Src/NPREffect.cpp | 141 ++++++++++++++++++-- Src/Shaders/CompileShaders.cmd | 13 ++ Src/Shaders/NPREffect.fx | 226 ++++++++++++++++++++++++++++++--- Src/Shaders/Structures.fxh | 15 +++ 5 files changed, 368 insertions(+), 32 deletions(-) diff --git a/Inc/Effects.h b/Inc/Effects.h index d30c2f5a9..c906694a1 100644 --- a/Inc/Effects.h +++ b/Inc/Effects.h @@ -225,7 +225,7 @@ namespace DirectX // Vertex color setting. DIRECTX_TOOLKIT_API void __cdecl SetVertexColorEnabled(bool value); - // Texture setting. + // Texture settings. DIRECTX_TOOLKIT_API void __cdecl SetTextureEnabled(bool value); DIRECTX_TOOLKIT_API void __cdecl SetTexture(_In_opt_ ID3D11ShaderResourceView* value); @@ -927,7 +927,8 @@ namespace DirectX static constexpr int MaxDirectionalLights = 1; // Texture settings. - // TODO: Implement texture settings. + DIRECTX_TOOLKIT_API void __cdecl SetTextureEnabled(bool value); + DIRECTX_TOOLKIT_API void __cdecl SetTexture(_In_opt_ ID3D11ShaderResourceView* value); // Shader mode setting. DIRECTX_TOOLKIT_API void __cdecl SetMode(Mode mode); diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp index 79bd4b7a3..94868c565 100644 --- a/Src/NPREffect.cpp +++ b/Src/NPREffect.cpp @@ -36,9 +36,9 @@ namespace { using ConstantBufferType = NPREffectConstants; - static constexpr int VertexShaderCount = 8; - static constexpr int PixelShaderCount = 2; - static constexpr int ShaderPermutationCount = 16; + static constexpr int VertexShaderCount = 16; + static constexpr int PixelShaderCount = 4; + static constexpr int ShaderPermutationCount = 32; static constexpr int ModeCount = 2; }; @@ -67,7 +67,9 @@ class NPREffect::Impl : public EffectBase bool vertexColorEnabled; bool biasedVertexNormals; bool instancing; + bool textureEnabled; NPREffect::Mode nprMode; + Microsoft::WRL::ComPtr texture; int GetCurrentShaderPermutation() const noexcept; @@ -92,8 +94,22 @@ namespace #include "XboxOneNPREffect_VSNPREffectVcBn.inc" #include "XboxOneNPREffect_VSNPREffectVcBnInst.inc" +#include "XboxOneNPREffect_VSNPREffectTx.inc" +#include "XboxOneNPREffect_VSNPREffectInstTx.inc" + +#include "XboxOneNPREffect_VSNPREffectVcTx.inc" +#include "XboxOneNPREffect_VSNPREffectVcInstTx.inc" + +#include "XboxOneNPREffect_VSNPREffectBnTx.inc" +#include "XboxOneNPREffect_VSNPREffectBnInstTx.inc" + +#include "XboxOneNPREffect_VSNPREffectVcBnTx.inc" +#include "XboxOneNPREffect_VSNPREffectVcBnInstTx.inc" + #include "XboxOneNPREffect_PSCelShading.inc" +#include "XboxOneNPREffect_PSCelShadingTx.inc" #include "XboxOneNPREffect_PSGoochShading.inc" +#include "XboxOneNPREffect_PSGoochShadingTx.inc" #else #include "NPREffect_VSNPREffect.inc" #include "NPREffect_VSNPREffectInst.inc" @@ -107,8 +123,23 @@ namespace #include "NPREffect_VSNPREffectVcBn.inc" #include "NPREffect_VSNPREffectVcBnInst.inc" +#include "NPREffect_VSNPREffectTx.inc" +#include "NPREffect_VSNPREffectInstTx.inc" + +#include "NPREffect_VSNPREffectVcTx.inc" +#include "NPREffect_VSNPREffectVcInstTx.inc" + +#include "NPREffect_VSNPREffectBnTx.inc" +#include "NPREffect_VSNPREffectBnInstTx.inc" + +#include "NPREffect_VSNPREffectVcBnTx.inc" +#include "NPREffect_VSNPREffectVcBnInstTx.inc" + #include "NPREffect_PSCelShading.inc" +#include "NPREffect_PSCelShadingTx.inc" + #include "NPREffect_PSGoochShading.inc" +#include "NPREffect_PSGoochShadingTx.inc" #endif } @@ -116,14 +147,22 @@ namespace 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) }, + { 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) }, + { NPREffect_VSNPREffectTx, sizeof(NPREffect_VSNPREffectTx) }, + { NPREffect_VSNPREffectVcTx, sizeof(NPREffect_VSNPREffectVcTx) }, + { NPREffect_VSNPREffectBnTx, sizeof(NPREffect_VSNPREffectBnTx) }, + { NPREffect_VSNPREffectVcBnTx, sizeof(NPREffect_VSNPREffectVcBnTx) }, + { NPREffect_VSNPREffectInstTx, sizeof(NPREffect_VSNPREffectInstTx) }, + { NPREffect_VSNPREffectVcInstTx, sizeof(NPREffect_VSNPREffectVcInstTx) }, + { NPREffect_VSNPREffectBnInstTx, sizeof(NPREffect_VSNPREffectBnInstTx) }, + { NPREffect_VSNPREffectVcBnInstTx, sizeof(NPREffect_VSNPREffectVcBnInstTx) }, }; @@ -153,14 +192,40 @@ const int EffectBase::VertexShaderIndices[] = 7, // instancing + vertex color (biased vertex normal) + cel shading 7, // instancing + vertex color (biased vertex normal) + gooch shading + + 8, // cel shading + texture + 8, // gooch shading + texture + + 9, // vertex color + cel shading + texture + 9, // vertex color + gooch shading + texture + + 10, // cel shading (biased vertex normal) + texture + 10, // gooch shading (biased vertex normal) + texture + + 11, // vertex color (biased vertex normal) + cel shading + texture + 11, // vertex color (biased vertex normal) + gooch shading + texture + + 12, // instancing + cel shading + texture + 12, // instancing + gooch shading + texture + + 13, // instancing + vertex color + cel shading + texture + 13, // instancing + vertex color + gooch shading + texture + + 14, // instancing (biased vertex normal) + cel shading + texture + 14, // instancing (biased vertex normal) + gooch shading + texture + + 15, // instancing + vertex color (biased vertex normal) + cel shading + texture + 15, // instancing + vertex color (biased vertex normal) + gooch shading + texture }; template<> const ShaderBytecode EffectBase::PixelShaderBytecode[] = { - { NPREffect_PSCelShading, sizeof(NPREffect_PSCelShading) }, - { NPREffect_PSGoochShading, sizeof(NPREffect_PSGoochShading) }, + { NPREffect_PSCelShading, sizeof(NPREffect_PSCelShading) }, + { NPREffect_PSGoochShading, sizeof(NPREffect_PSGoochShading) }, + { NPREffect_PSCelShadingTx, sizeof(NPREffect_PSCelShadingTx) }, + { NPREffect_PSGoochShadingTx, sizeof(NPREffect_PSGoochShadingTx) }, }; @@ -190,6 +255,30 @@ const int EffectBase::PixelShaderIndices[] = 0, // instancing + vertex color (biased vertex normal) + cel shading 1, // instancing + vertex color (biased vertex normal) + gooch shading + + 2, // cel shading + texture + 3, // gooch shading + texture + + 2, // vertex color + cel shading + texture + 3, // vertex color + gooch shading + texture + + 2, // cel shading (biased vertex normal) + texture + 3, // gooch shading (biased vertex normal) + texture + + 2, // vertex color (biased vertex normal) + cel shading + texture + 3, // vertex color (biased vertex normal) + gooch shading + texture + + 2, // instancing + cel shading + texture + 3, // instancing + gooch shading + texture + + 2, // instancing + vertex color + cel shading + texture + 3, // instancing + vertex color + gooch shading + texture + + 2, // instancing (biased vertex normal) + cel shading + texture + 3, // instancing (biased vertex normal) + gooch shading + texture + + 2, // instancing + vertex color (biased vertex normal) + cel shading + texture + 3, // instancing + vertex color (biased vertex normal) + gooch shading + texture }; #pragma endregion @@ -204,6 +293,7 @@ NPREffect::Impl::Impl(_In_ ID3D11Device* device) vertexColorEnabled(false), biasedVertexNormals(false), instancing(false), + textureEnabled(false), nprMode(NPREffect::Mode_Cel) { static_assert(static_cast(std::size(EffectBase::VertexShaderIndices)) == NPREffectTraits::ShaderPermutationCount, "array/max mismatch"); @@ -242,6 +332,12 @@ int NPREffect::Impl::GetCurrentShaderPermutation() const noexcept permutation += 8; } + // Use shaders without texture coordinates? + if (textureEnabled) + { + permutation += 16; + } + return permutation; } @@ -281,6 +377,13 @@ void NPREffect::Impl::Apply(_In_ ID3D11DeviceContext* deviceContext) // Set shaders and constant buffers. ApplyShaders(deviceContext, GetCurrentShaderPermutation()); + + // Set texture. + if (textureEnabled) + { + ID3D11ShaderResourceView* srv = texture.Get(); + deviceContext->PSSetShaderResources(0, 1, &srv); + } } @@ -425,6 +528,7 @@ void NPREffect::SetSpecularColor(FXMVECTOR value) void NPREffect::SetSpecularPower(float value) { + // TODO: specular power needs to be specular threshold! // Set w of specularColorAndSpecularPower. pImpl->constants.specularColorAndSpecularPower = XMVectorSetW(pImpl->constants.specularColorAndSpecularPower, value); @@ -461,7 +565,16 @@ void NPREffect::SetColorAndAlpha(FXMVECTOR value) // Texture settings. -// TODO: Implement texture settings. +void NPREffect::SetTextureEnabled(bool value) +{ + pImpl->textureEnabled = value; +} + + +void NPREffect::SetTexture(_In_opt_ ID3D11ShaderResourceView* value) +{ + pImpl->texture = value; +} // Shader mode setting. diff --git a/Src/Shaders/CompileShaders.cmd b/Src/Shaders/CompileShaders.cmd index fff8d52f2..5780aa77e 100644 --- a/Src/Shaders/CompileShaders.cmd +++ b/Src/Shaders/CompileShaders.cmd @@ -236,8 +236,21 @@ call :CompileShaderSM4%1 NPREffect vs VSNPREffectBnInst call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcInst call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcBnInst +call :CompileShaderSM4%1 NPREffect vs VSNPREffectTx +call :CompileShaderSM4%1 NPREffect vs VSNPREffectBnTx +call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcTx +call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcBnTx + +call :CompileShaderSM4%1 NPREffect vs VSNPREffectInstTx +call :CompileShaderSM4%1 NPREffect vs VSNPREffectBnInstTx +call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcInstTx +call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcBnInstTx + call :CompileShaderSM4%1 NPREffect ps PSCelShading +call :CompileShaderSM4%1 NPREffect ps PSCelShadingTx + call :CompileShaderSM4%1 NPREffect ps PSGoochShading +call :CompileShaderSM4%1 NPREffect ps PSGoochShadingTx call :CompileShader%1 SpriteEffect vs SpriteVertexShader call :CompileShader%1 SpriteEffect ps SpritePixelShader diff --git a/Src/Shaders/NPREffect.fx b/Src/Shaders/NPREffect.fx index 0daeefd30..58a8abcfe 100644 --- a/Src/Shaders/NPREffect.fx +++ b/Src/Shaders/NPREffect.fx @@ -10,6 +10,10 @@ //-------------------------------------------------------------------------------------- +Texture2D Texture : register(t0); +SamplerState Sampler : register(s0); + + cbuffer Parameters : register(b0) { float3 LightDirection : packoffset(c0); @@ -38,8 +42,133 @@ cbuffer Parameters : register(b0) #include "Structures.fxh" #include "Utilities.fxh" -// Vertex shader: basic -VSOutputPixelLightingTx VSNPREffect(VSInputNmTx vin) + +// Vertex shader: basic. +VSOutputPixelLighting VSNPREffect(VSInputNm vin) +{ + VSOutputPixelLighting 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); + + return vout; +} + +VSOutputPixelLighting VSNPREffectBn(VSInputNm vin) +{ + VSOutputPixelLighting 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); + + return vout; +} + + +// Vertex shader: vertex color. +VSOutputPixelLighting VSNPREffectVc(VSInputNmVc vin) +{ + VSOutputPixelLighting 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; + + return vout; +} + +VSOutputPixelLighting VSNPREffectVcBn(VSInputNmVc vin) +{ + VSOutputPixelLighting 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; + + return vout; +} + + +// Vertex shader: instancing. +VSOutputPixelLighting VSNPREffectInst(VSInputNmInst vin) +{ + VSOutputPixelLighting 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); + + return vout; +} + +VSOutputPixelLighting VSNPREffectBnInst(VSInputNmInst vin) +{ + VSOutputPixelLighting 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); + + return vout; +} + + +// Vertex shader: vertex color + instancing. +VSOutputPixelLighting VSNPREffectVcInst(VSInputNmVcInst vin) +{ + VSOutputPixelLighting 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; + + return vout; +} + +VSOutputPixelLighting VSNPREffectVcBnInst(VSInputNmVcInst vin) +{ + VSOutputPixelLighting 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; + + return vout; +} + + +// Vertex shader: texture. +VSOutputPixelLightingTx VSNPREffectTx(VSInputNmTx vin) { VSOutputPixelLightingTx vout; @@ -52,7 +181,7 @@ VSOutputPixelLightingTx VSNPREffect(VSInputNmTx vin) return vout; } -VSOutputPixelLightingTx VSNPREffectBn(VSInputNmTx vin) +VSOutputPixelLightingTx VSNPREffectBnTx(VSInputNmTx vin) { VSOutputPixelLightingTx vout; @@ -68,8 +197,8 @@ VSOutputPixelLightingTx VSNPREffectBn(VSInputNmTx vin) } -// Vertex shader: vertex color -VSOutputPixelLightingTx VSNPREffectVc(VSInputNmTxVc vin) +// Vertex shader: texture + vertex color. +VSOutputPixelLightingTx VSNPREffectVcTx(VSInputNmTxVc vin) { VSOutputPixelLightingTx vout; @@ -83,7 +212,7 @@ VSOutputPixelLightingTx VSNPREffectVc(VSInputNmTxVc vin) return vout; } -VSOutputPixelLightingTx VSNPREffectVcBn(VSInputNmTxVc vin) +VSOutputPixelLightingTx VSNPREffectVcBnTx(VSInputNmTxVc vin) { VSOutputPixelLightingTx vout; @@ -100,8 +229,8 @@ VSOutputPixelLightingTx VSNPREffectVcBn(VSInputNmTxVc vin) } -// Vertex shader: instancing -VSOutputPixelLightingTx VSNPREffectInst(VSInputNmTxInst vin) +// Vertex shader: texture + instancing. +VSOutputPixelLightingTx VSNPREffectInstTx(VSInputNmTxInst vin) { VSOutputPixelLightingTx vout; @@ -116,7 +245,7 @@ VSOutputPixelLightingTx VSNPREffectInst(VSInputNmTxInst vin) return vout; } -VSOutputPixelLightingTx VSNPREffectBnInst(VSInputNmTxInst vin) +VSOutputPixelLightingTx VSNPREffectBnInstTx(VSInputNmTxInst vin) { VSOutputPixelLightingTx vout; @@ -134,8 +263,8 @@ VSOutputPixelLightingTx VSNPREffectBnInst(VSInputNmTxInst vin) } -// Vertex shader: vertex color + instancing -VSOutputPixelLightingTx VSNPREffectVcInst(VSInputNmTxVcInst vin) +// Vertex shader: texture + vertex color + instancing. +VSOutputPixelLightingTx VSNPREffectVcInstTx(VSInputNmTxVcInst vin) { VSOutputPixelLightingTx vout; @@ -151,7 +280,7 @@ VSOutputPixelLightingTx VSNPREffectVcInst(VSInputNmTxVcInst vin) return vout; } -VSOutputPixelLightingTx VSNPREffectVcBnInst(VSInputNmTxVcInst vin) +VSOutputPixelLightingTx VSNPREffectVcBnInstTx(VSInputNmTxVcInst vin) { VSOutputPixelLightingTx vout; @@ -170,8 +299,8 @@ VSOutputPixelLightingTx VSNPREffectVcBnInst(VSInputNmTxVcInst vin) } -// Pixel shader: cel shading -float4 PSCelShading(PSInputPixelLightingTx pin) : SV_Target0 +// Pixel shader: cel shading. +float4 PSCelShading(PSInputPixelLighting pin) : SV_Target0 { float3 normal = normalize(pin.NormalWS); float3 lightDir = normalize(-LightDirection); @@ -187,6 +316,8 @@ float4 PSCelShading(PSInputPixelLightingTx pin) : SV_Target0 float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); float3 halfVec = normalize(lightDir + viewDir); float NdotH = max(0, dot(normal, halfVec)); + + //TODO: Need to revisit for cel float specular = step(0.95, pow(NdotH, SpecularPower)); color += SpecularColor * specular; @@ -195,8 +326,38 @@ float4 PSCelShading(PSInputPixelLightingTx pin) : SV_Target0 } -// Pixel shader: Gooch shading -float4 PSGoochShading(PSInputPixelLightingTx pin) : SV_Target0 +// Pixel shader: cel shading + texture. +float4 PSCelShadingTx(PSInputPixelLightingTx pin) : SV_Target0 +{ + float3 normal = normalize(pin.NormalWS); + float3 lightDir = normalize(-LightDirection); + + // Sample base texture + float4 texColor = Texture.Sample(Sampler, pin.TexCoord); + + // 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 * texColor.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)); + + //TODO: Need to revisit for cel + float specular = step(0.95, pow(NdotH, SpecularPower)); + + color += SpecularColor * specular; + + return float4(color, pin.Diffuse.a * texColor.a); +} + + +// Pixel shader: Gooch shading. +float4 PSGoochShading(PSInputPixelLighting pin) : SV_Target0 { float3 normal = normalize(pin.NormalWS); float3 lightDir = normalize(-LightDirection); @@ -213,9 +374,42 @@ float4 PSGoochShading(PSInputPixelLightingTx pin) : SV_Target0 // Specular highlight float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); float3 reflectDir = reflect(LightDirection, normal); + + //TODO: Need to revisit for Gooch float specular = pow(max(0, dot(viewDir, reflectDir)), SpecularPower); color += SpecularColor * specular; return float4(color, pin.Diffuse.a); } + + +// Pixel shader: Gooch shading + texture. +float4 PSGoochShadingTx(PSInputPixelLightingTx pin) : SV_Target0 +{ + float3 normal = normalize(pin.NormalWS); + float3 lightDir = normalize(-LightDirection); + + // Sample base texture + float4 texColor = Texture.Sample(Sampler, pin.TexCoord); + + // 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 * texColor.rgb; + float3 warmContrib = GoochWarmColor + GoochBeta * pin.Diffuse.rgb * texColor.rgb; + + float3 color = lerp(coolContrib, warmContrib, t); + + // Specular highlight + float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); + float3 reflectDir = reflect(LightDirection, normal); + + //TODO: Need to revisit for Gooch + float specular = pow(max(0, dot(viewDir, reflectDir)), SpecularPower); + + color += SpecularColor * specular; + + return float4(color, pin.Diffuse.a * texColor.a); +} diff --git a/Src/Shaders/Structures.fxh b/Src/Shaders/Structures.fxh index 0b49c2c38..cf0cc0673 100644 --- a/Src/Shaders/Structures.fxh +++ b/Src/Shaders/Structures.fxh @@ -60,6 +60,21 @@ struct VSInputNmTxVc float4 Color : COLOR; }; +struct VSInputNmInst +{ + float4 Position : SV_Position; + float3 Normal : NORMAL; + float4x3 Transform : InstMatrix; +}; + +struct VSInputNmVcInst +{ + float4 Position : SV_Position; + float3 Normal : NORMAL; + float4 Color : COLOR; + float4x3 Transform : InstMatrix; +}; + struct VSInputNmTxInst { float4 Position : SV_Position; From c1acf1f98da79ee2862026231a1c837acf93330c Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Thu, 16 Jul 2026 15:31:22 -0700 Subject: [PATCH 06/13] Specular smoothing and rimlighting --- Inc/Effects.h | 7 +- Src/NPREffect.cpp | 93 ++++++++++-- Src/Shaders/NPREffect.fx | 300 +++++++++++++++++---------------------- 3 files changed, 223 insertions(+), 177 deletions(-) diff --git a/Inc/Effects.h b/Inc/Effects.h index c906694a1..f56aa88d2 100644 --- a/Inc/Effects.h +++ b/Inc/Effects.h @@ -915,8 +915,13 @@ namespace DirectX // 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 SetSpecularThreshold(float threshold, float smoothing); DIRECTX_TOOLKIT_API void __cdecl DisableSpecular(); + DIRECTX_TOOLKIT_API void XM_CALLCONV SetRimLightingColor(FXMVECTOR value); + DIRECTX_TOOLKIT_API void __cdecl SetRimLightingPower(float power); + DIRECTX_TOOLKIT_API void __cdecl SetRimLightingIntensity(float strength); + DIRECTX_TOOLKIT_API void __cdecl SetRimLightingRange(float start, float end); + DIRECTX_TOOLKIT_API void __cdecl DisableRimLighting(); DIRECTX_TOOLKIT_API void __cdecl SetAlpha(float value); DIRECTX_TOOLKIT_API void XM_CALLCONV SetColorAndAlpha(FXMVECTOR value); diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp index 94868c565..39766f83c 100644 --- a/Src/NPREffect.cpp +++ b/Src/NPREffect.cpp @@ -19,7 +19,9 @@ namespace { XMVECTOR lightDirectionAndCelBands; XMVECTOR diffuseColorAndAlpha; - XMVECTOR specularColorAndSpecularPower; + XMVECTOR specularColorAndSpecularThreshold; + XMVECTOR rimColorAndPower; + XMVECTOR extraSettings; // .x = SpecularSmoothing, .y = RimStrength, .z = RimStart, .w = RimEnd XMVECTOR goochCoolColorAndAlpha; XMVECTOR goochWarmColorAndBeta; XMVECTOR eyePosition; @@ -47,7 +49,9 @@ namespace // 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_defaultSpecular = { { { 1.f, 1.f, 1.f, 0.95f } } }; + constexpr XMVECTORF32 s_defaultRim = { { { 0.f, 0.f, 0.f, 4.f } } }; + constexpr XMVECTORF32 s_defaultExtraSettings = { { { 0.004f, 0.75f, 0.2f, 0.6f } } }; constexpr XMVECTORF32 s_defaultCool = { { { 0.f, 0.f, 0.55f, 0.25f } } }; constexpr XMVECTORF32 s_defaultWarm = { { { 0.3f, 0.3f, 0.f, 0.25f } } }; } @@ -303,7 +307,9 @@ NPREffect::Impl::Impl(_In_ ID3D11Device* device) constants.lightDirectionAndCelBands = s_defaultLightDir; constants.diffuseColorAndAlpha = s_defaultDiffuse; - constants.specularColorAndSpecularPower = s_defaultSpecular; + constants.specularColorAndSpecularThreshold = s_defaultSpecular; + constants.rimColorAndPower = s_defaultRim; + constants.extraSettings = s_defaultExtraSettings; constants.goochCoolColorAndAlpha = s_defaultCool; constants.goochWarmColorAndBeta = s_defaultWarm; constants.eyePosition = g_XMZero; @@ -519,18 +525,30 @@ void NPREffect::SetDiffuseColor(FXMVECTOR value) void NPREffect::SetSpecularColor(FXMVECTOR value) { - // Set xyz, preserve w (specular power). - pImpl->constants.specularColorAndSpecularPower = XMVectorSelect(pImpl->constants.specularColorAndSpecularPower, value, g_XMSelect1110); + // Set xyz, preserve w (specular threshold). + pImpl->constants.specularColorAndSpecularThreshold = XMVectorSelect(pImpl->constants.specularColorAndSpecularThreshold, value, g_XMSelect1110); pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; } -void NPREffect::SetSpecularPower(float value) +void NPREffect::SetSpecularThreshold(float threshold, float smoothing) { - // TODO: specular power needs to be specular threshold! - // Set w of specularColorAndSpecularPower. - pImpl->constants.specularColorAndSpecularPower = XMVectorSetW(pImpl->constants.specularColorAndSpecularPower, value); + if (threshold < 0.f || threshold > 1.f) + { + throw std::invalid_argument("Specular threshold must be between 0 and 1"); + } + + if (smoothing < 0.f || smoothing > 1.f) + { + throw std::invalid_argument("Specular smoothing must be between 0 and 1"); + } + + // Set w of specularColorAndSpecularThreshold. + pImpl->constants.specularColorAndSpecularThreshold = XMVectorSetW(pImpl->constants.specularColorAndSpecularThreshold, threshold); + + // Set x of extraSettings. + pImpl->constants.extraSettings = XMVectorSetX(pImpl->constants.extraSettings, smoothing); pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; } @@ -541,7 +559,62 @@ void NPREffect::DisableSpecular() // Set specular color to black, power to 1 // Note: Don't use a power of 0 or the shader will generate strange highlights on non-specular materials - pImpl->constants.specularColorAndSpecularPower = g_XMIdentityR3; + pImpl->constants.specularColorAndSpecularThreshold = g_XMIdentityR3; + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::SetRimLightingColor(FXMVECTOR value) +{ + // Set xyz, preserve w (rim power). + pImpl->constants.rimColorAndPower = XMVectorSelect(pImpl->constants.rimColorAndPower, value, g_XMSelect1110); + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::SetRimLightingPower(float power) +{ + // Set w of rimColorAndPower. + pImpl->constants.rimColorAndPower = XMVectorSetW(pImpl->constants.rimColorAndPower, power); + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::SetRimLightingIntensity(float strength) +{ + if (strength < 0.f || strength > 1.f) + { + throw std::invalid_argument("Rim lighting strength must be between 0 and 1"); + } + + // Set y of extraSettings. + pImpl->constants.extraSettings = XMVectorSetY(pImpl->constants.extraSettings, strength); + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::SetRimLightingRange(float start, float end) +{ + if (start < 0.f || end > 1.f || start > end) + { + throw std::invalid_argument("Rim lighting start/end must be between 0 and 1"); + } + + // Set zw of extraSettings. + XMVECTORF32 range = { { { 0.f, 0.f, start, end } } }; + pImpl->constants.extraSettings = XMVectorSelect(range, pImpl->constants.extraSettings, g_XMSelect1100); + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::DisableRimLighting() +{ + pImpl->constants.rimColorAndPower = g_XMIdentityR3; pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; } diff --git a/Src/Shaders/NPREffect.fx b/Src/Shaders/NPREffect.fx index 58a8abcfe..87ed60826 100644 --- a/Src/Shaders/NPREffect.fx +++ b/Src/Shaders/NPREffect.fx @@ -23,50 +23,66 @@ cbuffer Parameters : register(b0) float Alpha : packoffset(c1.w); float3 SpecularColor : packoffset(c2); - float SpecularPower : packoffset(c2.w); + float SpecularThreshold : packoffset(c2.w); - float3 GoochCoolColor : packoffset(c3); - float GoochAlpha : packoffset(c3.w); + float3 RimColor : packoffset(c3); + float RimPower : packoffset(c3.w); - float3 GoochWarmColor : packoffset(c4); - float GoochBeta : packoffset(c4.w); + float SpecularSmoothing : packoffset(c4.x); + float RimStrength : packoffset(c4.y); + float RimStart : packoffset(c4.z); + float RimEnd : packoffset(c4.w); - float3 EyePosition : packoffset(c5); + float3 GoochCoolColor : packoffset(c5); + float GoochAlpha : packoffset(c5.w); - float4x4 World : packoffset(c6); - float3x3 WorldInverseTranspose : packoffset(c10); - float4x4 WorldViewProj : packoffset(c13); + float3 GoochWarmColor : packoffset(c6); + float GoochBeta : packoffset(c6.w); + + float3 EyePosition : packoffset(c7); + + float4x4 World : packoffset(c8); + float3x3 WorldInverseTranspose : packoffset(c12); + float4x4 WorldViewProj : packoffset(c15); }; #include "Structures.fxh" #include "Utilities.fxh" +VSOutputPixelLighting ComputeCommonVS(float4 position, float3 normal) +{ + VSOutputPixelLighting vout; + vout.PositionPS = mul(position, WorldViewProj); + vout.PositionWS = float4(mul(position, World).xyz, 1); + vout.NormalWS = normalize(mul(normal, WorldInverseTranspose)); + return vout; +} + +VSOutputPixelLightingTx ComputeCommonVSTx(float4 position, float3 normal, float2 texcoord) +{ + VSOutputPixelLightingTx vout; + vout.TexCoord = texcoord; + vout.PositionPS = mul(position, WorldViewProj); + vout.PositionWS = float4(mul(position, World).xyz, 1); + vout.NormalWS = normalize(mul(normal, WorldInverseTranspose)); + return vout; +} // Vertex shader: basic. VSOutputPixelLighting VSNPREffect(VSInputNm vin) { - VSOutputPixelLighting vout; - - vout.PositionPS = mul(vin.Position, WorldViewProj); - vout.PositionWS = float4(mul(vin.Position, World).xyz, 1); - vout.NormalWS = normalize(mul(vin.Normal, WorldInverseTranspose)); + VSOutputPixelLighting vout = ComputeCommonVS(vin.Position, vin.Normal); vout.Diffuse = float4(DiffuseColor, Alpha); - return vout; } VSOutputPixelLighting VSNPREffectBn(VSInputNm vin) { - VSOutputPixelLighting 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)); + VSOutputPixelLighting vout = ComputeCommonVS(vin.Position, normal); vout.Diffuse = float4(DiffuseColor, Alpha); - return vout; } @@ -74,29 +90,19 @@ VSOutputPixelLighting VSNPREffectBn(VSInputNm vin) // Vertex shader: vertex color. VSOutputPixelLighting VSNPREffectVc(VSInputNmVc vin) { - VSOutputPixelLighting vout; - - vout.PositionPS = mul(vin.Position, WorldViewProj); - vout.PositionWS = float4(mul(vin.Position, World).xyz, 1); - vout.NormalWS = normalize(mul(vin.Normal, WorldInverseTranspose)); + VSOutputPixelLighting vout = ComputeCommonVS(vin.Position, vin.Normal); vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor; vout.Diffuse.a = vin.Color.a * Alpha; - return vout; } VSOutputPixelLighting VSNPREffectVcBn(VSInputNmVc vin) { - VSOutputPixelLighting 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)); + VSOutputPixelLighting vout = ComputeCommonVS(vin.Position, normal); vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor; vout.Diffuse.a = vin.Color.a * Alpha; - return vout; } @@ -104,31 +110,19 @@ VSOutputPixelLighting VSNPREffectVcBn(VSInputNmVc vin) // Vertex shader: instancing. VSOutputPixelLighting VSNPREffectInst(VSInputNmInst vin) { - VSOutputPixelLighting 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)); + VSOutputPixelLighting vout = ComputeCommonVS(inst.Position, inst.Normal); vout.Diffuse = float4(DiffuseColor, Alpha); - return vout; } VSOutputPixelLighting VSNPREffectBnInst(VSInputNmInst vin) { - VSOutputPixelLighting 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)); + VSOutputPixelLighting vout = ComputeCommonVS(inst.Position, inst.Normal); vout.Diffuse = float4(DiffuseColor, Alpha); - return vout; } @@ -136,33 +130,21 @@ VSOutputPixelLighting VSNPREffectBnInst(VSInputNmInst vin) // Vertex shader: vertex color + instancing. VSOutputPixelLighting VSNPREffectVcInst(VSInputNmVcInst vin) { - VSOutputPixelLighting 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)); + VSOutputPixelLighting vout = ComputeCommonVS(inst.Position, inst.Normal); vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor; vout.Diffuse.a = vin.Color.a * Alpha; - return vout; } VSOutputPixelLighting VSNPREffectVcBnInst(VSInputNmVcInst vin) { - VSOutputPixelLighting 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)); + VSOutputPixelLighting vout = ComputeCommonVS(inst.Position, inst.Normal); vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor; vout.Diffuse.a = vin.Color.a * Alpha; - return vout; } @@ -170,29 +152,17 @@ VSOutputPixelLighting VSNPREffectVcBnInst(VSInputNmVcInst vin) // Vertex shader: texture. VSOutputPixelLightingTx VSNPREffectTx(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)); + VSOutputPixelLightingTx vout = ComputeCommonVSTx(vin.Position, vin.Normal, vin.TexCoord); vout.Diffuse = float4(DiffuseColor, Alpha); - vout.TexCoord = vin.TexCoord; - return vout; } VSOutputPixelLightingTx VSNPREffectBnTx(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)); + VSOutputPixelLightingTx vout = ComputeCommonVSTx(vin.Position, normal, vin.TexCoord); vout.Diffuse = float4(DiffuseColor, Alpha); - vout.TexCoord = vin.TexCoord; - return vout; } @@ -200,31 +170,19 @@ VSOutputPixelLightingTx VSNPREffectBnTx(VSInputNmTx vin) // Vertex shader: texture + vertex color. VSOutputPixelLightingTx VSNPREffectVcTx(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)); + VSOutputPixelLightingTx vout = ComputeCommonVSTx(vin.Position, vin.Normal, vin.TexCoord); vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor; vout.Diffuse.a = vin.Color.a * Alpha; - vout.TexCoord = vin.TexCoord; - return vout; } VSOutputPixelLightingTx VSNPREffectVcBnTx(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)); + VSOutputPixelLightingTx vout = ComputeCommonVSTx(vin.Position, normal, vin.TexCoord); vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor; vout.Diffuse.a = vin.Color.a * Alpha; - vout.TexCoord = vin.TexCoord; - return vout; } @@ -232,33 +190,19 @@ VSOutputPixelLightingTx VSNPREffectVcBnTx(VSInputNmTxVc vin) // Vertex shader: texture + instancing. VSOutputPixelLightingTx VSNPREffectInstTx(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)); + VSOutputPixelLightingTx vout = ComputeCommonVSTx(inst.Position, inst.Normal, vin.TexCoord); vout.Diffuse = float4(DiffuseColor, Alpha); - vout.TexCoord = vin.TexCoord; - return vout; } VSOutputPixelLightingTx VSNPREffectBnInstTx(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)); + VSOutputPixelLightingTx vout = ComputeCommonVSTx(inst.Position, inst.Normal, vin.TexCoord); vout.Diffuse = float4(DiffuseColor, Alpha); - vout.TexCoord = vin.TexCoord; - return vout; } @@ -266,93 +210,117 @@ VSOutputPixelLightingTx VSNPREffectBnInstTx(VSInputNmTxInst vin) // Vertex shader: texture + vertex color + instancing. VSOutputPixelLightingTx VSNPREffectVcInstTx(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)); + VSOutputPixelLightingTx vout = ComputeCommonVSTx(inst.Position, inst.Normal, vin.TexCoord); vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor; vout.Diffuse.a = vin.Color.a * Alpha; - vout.TexCoord = vin.TexCoord; - return vout; } VSOutputPixelLightingTx VSNPREffectVcBnInstTx(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)); + VSOutputPixelLightingTx vout = ComputeCommonVSTx(inst.Position, inst.Normal, vin.TexCoord); vout.Diffuse.rgb = vin.Color.rgb * DiffuseColor; vout.Diffuse.a = vin.Color.a * Alpha; - vout.TexCoord = vin.TexCoord; - return vout; } +//--- Cel shading --- +float Quantize(float intensity, float bands) +{ + intensity = max(0, intensity); + return floor(intensity * bands) / bands; +} + +float HardEdgeSpecular(float dot1) +{ + return smoothstep(SpecularThreshold - SpecularSmoothing, SpecularThreshold + SpecularSmoothing, dot1); +} + +float RimLighting(float dot1) +{ + float fresnel = pow(1.0 - saturate(dot1), RimPower); + return smoothstep(RimStart, RimEnd, fresnel) * RimStrength; +} + + // Pixel shader: cel shading. float4 PSCelShading(PSInputPixelLighting pin) : SV_Target0 { float3 normal = normalize(pin.NormalWS); - float3 lightDir = normalize(-LightDirection); - // Quantize the diffuse lighting into discrete bands + float3 lightDir = normalize(-LightDirection); 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); + float NdotV = max(0, dot(normal, viewDir)); + float3 halfVec = normalize(lightDir + viewDir); float NdotH = max(0, dot(normal, halfVec)); - //TODO: Need to revisit for cel - float specular = step(0.95, pow(NdotH, SpecularPower)); + // Quantize the diffuse lighting into discrete bands + float quantized = Quantize(NdotL, CelBands); + float3 color = pin.Diffuse.rgb * quantized; + + // Rim lighting + float outline = RimLighting(NdotV); + color = lerp(color, RimColor, outline); + // Specular highlight (hard edge) + float specular = HardEdgeSpecular(NdotH); color += SpecularColor * specular; return float4(color, pin.Diffuse.a); -} + } // Pixel shader: cel shading + texture. float4 PSCelShadingTx(PSInputPixelLightingTx pin) : SV_Target0 { float3 normal = normalize(pin.NormalWS); + float3 lightDir = normalize(-LightDirection); + float NdotL = dot(normal, lightDir); + + float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); + float NdotV = max(0, dot(normal, viewDir)); + float VdotL = max(0, dot(viewDir, lightDir)); + + float3 halfVec = normalize(lightDir + viewDir); + float NdotH = max(0, dot(normal, halfVec)); // Sample base texture float4 texColor = Texture.Sample(Sampler, pin.TexCoord); // Quantize the diffuse lighting into discrete bands - float NdotL = dot(normal, lightDir); - float intensity = max(0, NdotL); - float quantized = floor(intensity * CelBands) / CelBands; - + float quantized = Quantize(NdotL, CelBands); float3 color = pin.Diffuse.rgb * texColor.rgb * quantized; + // Rim lighting + float outline = RimLighting(NdotV); + color = lerp(color, RimColor, outline); + // 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 = HardEdgeSpecular(NdotH); + color += SpecularColor * specular; - //TODO: Need to revisit for cel - float specular = step(0.95, pow(NdotH, SpecularPower)); + return float4(color, pin.Diffuse.a); +} - color += SpecularColor * specular; - return float4(color, pin.Diffuse.a * texColor.a); +//--- Gooch shading --- +float3 GoochShading(float dot1, float3 color) +{ + float t = (1.0 + dot1) * 0.5; + + float3 coolContrib = GoochCoolColor + GoochAlpha * color; + float3 warmContrib = GoochWarmColor + GoochBeta * color; + + return lerp(coolContrib, warmContrib, t); } @@ -361,23 +329,23 @@ float4 PSGoochShading(PSInputPixelLighting 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); + float NdotV = max(0, dot(normal, viewDir)); + float3 reflectDir = reflect(LightDirection, normal); + float RdotV = max(0, dot(viewDir, reflectDir)); + + // Gooch diffuse term: blend between cool and warm based + float3 color = GoochShading(NdotL, pin.Diffuse.rgb); - //TODO: Need to revisit for Gooch - float specular = pow(max(0, dot(viewDir, reflectDir)), SpecularPower); + // Rim lighting + float outline = RimLighting(NdotV); + color += lerp(color, RimColor, outline); + // Specular highlight (hard edge) + float specular = HardEdgeSpecular(RdotV); color += SpecularColor * specular; return float4(color, pin.Diffuse.a); @@ -389,26 +357,26 @@ float4 PSGoochShadingTx(PSInputPixelLightingTx pin) : SV_Target0 { float3 normal = normalize(pin.NormalWS); float3 lightDir = normalize(-LightDirection); + float NdotL = dot(normal, lightDir); + + float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); + float NdotV = max(0, dot(normal, viewDir)); + + float3 reflectDir = reflect(LightDirection, normal); + float RdotV = max(0, dot(viewDir, reflectDir)); // Sample base texture float4 texColor = Texture.Sample(Sampler, pin.TexCoord); - // 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 * texColor.rgb; - float3 warmContrib = GoochWarmColor + GoochBeta * pin.Diffuse.rgb * texColor.rgb; + // Gooch diffuse term: blend between cool and warm based + float3 color = GoochShading(NdotL, pin.Diffuse.rgb * texColor.rgb); - float3 color = lerp(coolContrib, warmContrib, t); + // Rim lighting + float outline = RimLighting(NdotV); + color += lerp(color, RimColor, outline); // Specular highlight - float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); - float3 reflectDir = reflect(LightDirection, normal); - - //TODO: Need to revisit for Gooch - float specular = pow(max(0, dot(viewDir, reflectDir)), SpecularPower); - + float specular = HardEdgeSpecular(RdotV); color += SpecularColor * specular; return float4(color, pin.Diffuse.a * texColor.a); From 1160ef65aa53a36e957c3f05e987d34b659d4f7f Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Thu, 16 Jul 2026 15:56:43 -0700 Subject: [PATCH 07/13] Code review --- Src/NPREffect.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp index 39766f83c..378ff08ad 100644 --- a/Src/NPREffect.cpp +++ b/Src/NPREffect.cpp @@ -665,6 +665,11 @@ void NPREffect::SetMode(Mode mode) // Cel shading settings. void NPREffect::SetCelShaderBands(int bands) { + if (bands < 1) + { + throw std::invalid_argument("Cel shading bands must be greater than 0"); + } + // Set w of lightDirectionAndCelBands. pImpl->constants.lightDirectionAndCelBands = XMVectorSetW(pImpl->constants.lightDirectionAndCelBands, static_cast(bands)); From bb5ed6f357a0149388edd42eba3bbaaf0c947b64 Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Thu, 16 Jul 2026 16:11:12 -0700 Subject: [PATCH 08/13] Fix alpha --- Src/Shaders/NPREffect.fx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Shaders/NPREffect.fx b/Src/Shaders/NPREffect.fx index 87ed60826..45401e063 100644 --- a/Src/Shaders/NPREffect.fx +++ b/Src/Shaders/NPREffect.fx @@ -275,7 +275,7 @@ float4 PSCelShading(PSInputPixelLighting pin) : SV_Target0 color += SpecularColor * specular; return float4(color, pin.Diffuse.a); - } +} // Pixel shader: cel shading + texture. @@ -308,7 +308,7 @@ float4 PSCelShadingTx(PSInputPixelLightingTx pin) : SV_Target0 float specular = HardEdgeSpecular(NdotH); color += SpecularColor * specular; - return float4(color, pin.Diffuse.a); + return float4(color, pin.Diffuse.a * texColor.a); } From a4ad94a5346145ff0f18a945f32dcb51f5b7aaa8 Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Thu, 16 Jul 2026 16:24:05 -0700 Subject: [PATCH 09/13] Code review --- Inc/Effects.h | 12 ++--- Src/NPREffect.cpp | 110 +++++++++++++++++++++++----------------------- 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/Inc/Effects.h b/Inc/Effects.h index f56aa88d2..95d8c869d 100644 --- a/Inc/Effects.h +++ b/Inc/Effects.h @@ -917,11 +917,6 @@ namespace DirectX DIRECTX_TOOLKIT_API void XM_CALLCONV SetSpecularColor(FXMVECTOR value); DIRECTX_TOOLKIT_API void __cdecl SetSpecularThreshold(float threshold, float smoothing); DIRECTX_TOOLKIT_API void __cdecl DisableSpecular(); - DIRECTX_TOOLKIT_API void XM_CALLCONV SetRimLightingColor(FXMVECTOR value); - DIRECTX_TOOLKIT_API void __cdecl SetRimLightingPower(float power); - DIRECTX_TOOLKIT_API void __cdecl SetRimLightingIntensity(float strength); - DIRECTX_TOOLKIT_API void __cdecl SetRimLightingRange(float start, float end); - DIRECTX_TOOLKIT_API void __cdecl DisableRimLighting(); DIRECTX_TOOLKIT_API void __cdecl SetAlpha(float value); DIRECTX_TOOLKIT_API void XM_CALLCONV SetColorAndAlpha(FXMVECTOR value); @@ -945,6 +940,13 @@ namespace DirectX 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); + // Rim lighting settings. + DIRECTX_TOOLKIT_API void XM_CALLCONV SetRimLightingColor(FXMVECTOR value); + DIRECTX_TOOLKIT_API void __cdecl SetRimLightingPower(float power); + DIRECTX_TOOLKIT_API void __cdecl SetRimLightingIntensity(float strength); + DIRECTX_TOOLKIT_API void __cdecl SetRimLightingRange(float start, float end); + DIRECTX_TOOLKIT_API void __cdecl DisableRimLighting(); + // Vertex color setting. DIRECTX_TOOLKIT_API void __cdecl SetVertexColorEnabled(bool value); diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp index 378ff08ad..87f2e6f60 100644 --- a/Src/NPREffect.cpp +++ b/Src/NPREffect.cpp @@ -565,61 +565,6 @@ void NPREffect::DisableSpecular() } -void NPREffect::SetRimLightingColor(FXMVECTOR value) -{ - // Set xyz, preserve w (rim power). - pImpl->constants.rimColorAndPower = XMVectorSelect(pImpl->constants.rimColorAndPower, value, g_XMSelect1110); - - pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; -} - - -void NPREffect::SetRimLightingPower(float power) -{ - // Set w of rimColorAndPower. - pImpl->constants.rimColorAndPower = XMVectorSetW(pImpl->constants.rimColorAndPower, power); - - pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; -} - - -void NPREffect::SetRimLightingIntensity(float strength) -{ - if (strength < 0.f || strength > 1.f) - { - throw std::invalid_argument("Rim lighting strength must be between 0 and 1"); - } - - // Set y of extraSettings. - pImpl->constants.extraSettings = XMVectorSetY(pImpl->constants.extraSettings, strength); - - pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; -} - - -void NPREffect::SetRimLightingRange(float start, float end) -{ - if (start < 0.f || end > 1.f || start > end) - { - throw std::invalid_argument("Rim lighting start/end must be between 0 and 1"); - } - - // Set zw of extraSettings. - XMVECTORF32 range = { { { 0.f, 0.f, start, end } } }; - pImpl->constants.extraSettings = XMVectorSelect(range, pImpl->constants.extraSettings, g_XMSelect1100); - - pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; -} - - -void NPREffect::DisableRimLighting() -{ - pImpl->constants.rimColorAndPower = g_XMIdentityR3; - - pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; -} - - void NPREffect::SetAlpha(float value) { // Set w of diffuseColorAndAlpha. @@ -694,6 +639,61 @@ void NPREffect::SetGoochWarmColor(FXMVECTOR value, float beta) } +// Rim lighting settings. +void NPREffect::SetRimLightingColor(FXMVECTOR value) +{ + // Set xyz, preserve w (rim power). + pImpl->constants.rimColorAndPower = XMVectorSelect(pImpl->constants.rimColorAndPower, value, g_XMSelect1110); + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::SetRimLightingPower(float power) +{ + // Set w of rimColorAndPower. + pImpl->constants.rimColorAndPower = XMVectorSetW(pImpl->constants.rimColorAndPower, power); + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::SetRimLightingIntensity(float strength) +{ + if (strength < 0.f || strength > 1.f) + { + throw std::invalid_argument("Rim lighting strength must be between 0 and 1"); + } + + // Set y of extraSettings. + pImpl->constants.extraSettings = XMVectorSetY(pImpl->constants.extraSettings, strength); + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::SetRimLightingRange(float start, float end) +{ + if (start < 0.f || end > 1.f || start > end) + { + throw std::invalid_argument("Rim lighting start/end must be between 0 and 1"); + } + + // Set zw of extraSettings. + XMVECTORF32 range = { { { 0.f, 0.f, start, end } } }; + pImpl->constants.extraSettings = XMVectorSelect(range, pImpl->constants.extraSettings, g_XMSelect1100); + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + + +void NPREffect::DisableRimLighting() +{ + pImpl->constants.rimColorAndPower = g_XMIdentityR3; + + pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; +} + // Vertex color setting. void NPREffect::SetVertexColorEnabled(bool value) { From 2885accf6824022a68b42b898922f9fb705c2226 Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Thu, 16 Jul 2026 16:38:00 -0700 Subject: [PATCH 10/13] More code review --- Inc/Effects.h | 2 +- Src/NPREffect.cpp | 6 ++---- Src/Shaders/NPREffect.fx | 6 ------ 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Inc/Effects.h b/Inc/Effects.h index 95d8c869d..e24f5a60b 100644 --- a/Inc/Effects.h +++ b/Inc/Effects.h @@ -933,7 +933,7 @@ namespace DirectX // Shader mode setting. DIRECTX_TOOLKIT_API void __cdecl SetMode(Mode mode); - // Cel shading settings. + // Cel shading setting. DIRECTX_TOOLKIT_API void __cdecl SetCelShaderBands(int bands); // Gooch shading settings. diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp index 87f2e6f60..ee0ed8bb1 100644 --- a/Src/NPREffect.cpp +++ b/Src/NPREffect.cpp @@ -556,9 +556,7 @@ void NPREffect::SetSpecularThreshold(float threshold, float smoothing) void NPREffect::DisableSpecular() { - // Set specular color to black, power to 1 - // Note: Don't use a power of 0 or the shader will generate strange highlights on non-specular materials - + // Set specular color to black, threshold to 1 pImpl->constants.specularColorAndSpecularThreshold = g_XMIdentityR3; pImpl->dirtyFlags |= EffectDirtyFlags::ConstantBuffer; @@ -607,7 +605,7 @@ void NPREffect::SetMode(Mode mode) } -// Cel shading settings. +// Cel shading setting. void NPREffect::SetCelShaderBands(int bands) { if (bands < 1) diff --git a/Src/Shaders/NPREffect.fx b/Src/Shaders/NPREffect.fx index 45401e063..02e66ae6a 100644 --- a/Src/Shaders/NPREffect.fx +++ b/Src/Shaders/NPREffect.fx @@ -1,13 +1,7 @@ -//-------------------------------------------------------------------------------------- -// 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 -//-------------------------------------------------------------------------------------- Texture2D Texture : register(t0); From 3d04b98d6bcf6244f8130051c066df983e559940 Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Thu, 16 Jul 2026 17:47:29 -0700 Subject: [PATCH 11/13] Update copilot instr --- .github/copilot-instructions.md | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 77eb78d6c..eb2c710a9 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -319,6 +319,43 @@ When reviewing documentation, do the following: - Read the documentation on the wiki located in [this git repository](https://github.com/microsoft/DirectXTK.wiki.git). - Report any specific gaps in the documentation compared to the public interface. +## Code Change Rules + +- Make precise, surgical changes that **fully** address the request. Don't modify unrelated code, but ensure changes are complete and correct. +- Don't fix pre-existing issues unrelated to your task. However, if you discover bugs directly caused by or tightly coupled to the code you're changing, fix those too. +- Update documentation if it is directly related to the changes you are making. +- Always validate that your changes don't break existing behavior. + +### Linting, Building, Testing + +- Only run linters, builds and tests that already exist. Do not add new linting, building or testing tools unless necessary for the task. +- Use the smallest targeted test, build, or lint command that covers the changed behavior. When related targeted selectors use the same runner, include them in one invocation; escalate to full-suite or baseline runs only when targeted validation shows they are needed. +- Documentation changes do not need to be linted, built or tested unless there are specific tests for documentation. + +### Using Ecosystem Tools + +- Prefer ecosystem tools (package managers, scaffolding, refactoring tools, linters) over manual changes. +- Install packages only when changing dependencies or after a missing-dependency failure. + +### Code Commenting Style + +- Only comment code that needs a bit of clarification. Do not comment otherwise. + ## Release Process The release process is documented in the [release skill](.github/skills/release/SKILL.md). Invoke the `release` skill for step-by-step guidance when performing a release. + +## CoPilot Skills + +The project includes published CoPilot skills for developers: + +- **Release-Process**: Guide for performing the DirectX Tool Kit release process. Invoked when asked to help with releasing a new version, publishing packages, or updating ports. See [Release Process skill](.github/skills/release/SKILL.md) for details. +- **directxtk-usage**: Provides usage guidance for the DirectX Tool Kit for DX11 library. Located in the `skills/directxtk-usage/` directory. +- **directxtk-tutorial**: Provides an interactive tutorial which can create a project, add the toolkit, and implement features in a step-by-step fashion. Located in the `skills/directxtk-tutorial` directory. + +To use these skills in the Copilot CLI: + +```bash +/skills list +/skills search directxtk +``` From 4c73b9c1ebaa168b13099c89675de2d50b8407ac Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Thu, 16 Jul 2026 23:10:07 -0700 Subject: [PATCH 12/13] NPREffect does not require SM4 --- Src/Shaders/CompileShaders.cmd | 50 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Src/Shaders/CompileShaders.cmd b/Src/Shaders/CompileShaders.cmd index 5780aa77e..911d5390b 100644 --- a/Src/Shaders/CompileShaders.cmd +++ b/Src/Shaders/CompileShaders.cmd @@ -226,31 +226,31 @@ 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 vs VSNPREffectTx -call :CompileShaderSM4%1 NPREffect vs VSNPREffectBnTx -call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcTx -call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcBnTx - -call :CompileShaderSM4%1 NPREffect vs VSNPREffectInstTx -call :CompileShaderSM4%1 NPREffect vs VSNPREffectBnInstTx -call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcInstTx -call :CompileShaderSM4%1 NPREffect vs VSNPREffectVcBnInstTx - -call :CompileShaderSM4%1 NPREffect ps PSCelShading -call :CompileShaderSM4%1 NPREffect ps PSCelShadingTx - -call :CompileShaderSM4%1 NPREffect ps PSGoochShading -call :CompileShaderSM4%1 NPREffect ps PSGoochShadingTx +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 vs VSNPREffectTx +call :CompileShader%1 NPREffect vs VSNPREffectBnTx +call :CompileShader%1 NPREffect vs VSNPREffectVcTx +call :CompileShader%1 NPREffect vs VSNPREffectVcBnTx + +call :CompileShader%1 NPREffect vs VSNPREffectInstTx +call :CompileShader%1 NPREffect vs VSNPREffectBnInstTx +call :CompileShader%1 NPREffect vs VSNPREffectVcInstTx +call :CompileShader%1 NPREffect vs VSNPREffectVcBnInstTx + +call :CompileShader%1 NPREffect ps PSCelShading +call :CompileShader%1 NPREffect ps PSCelShadingTx + +call :CompileShader%1 NPREffect ps PSGoochShading +call :CompileShader%1 NPREffect ps PSGoochShadingTx call :CompileShader%1 SpriteEffect vs SpriteVertexShader call :CompileShader%1 SpriteEffect ps SpritePixelShader From caa4dfd9b73d719cce105b7984d25338834f3278 Mon Sep 17 00:00:00 2001 From: Chuck Walbourn Date: Fri, 17 Jul 2026 01:45:46 -0700 Subject: [PATCH 13/13] Add MatCap shading --- Inc/Effects.h | 6 +- Src/NPREffect.cpp | 127 +++++++++++++++++++++++++-------- Src/Shaders/CompileShaders.cmd | 3 + Src/Shaders/NPREffect.fx | 42 +++++++++++ 4 files changed, 147 insertions(+), 31 deletions(-) diff --git a/Inc/Effects.h b/Inc/Effects.h index e24f5a60b..be5eb5484 100644 --- a/Inc/Effects.h +++ b/Inc/Effects.h @@ -879,7 +879,7 @@ namespace DirectX }; //------------------------------------------------------------------------------ - // Built-in shader for non-photorealistic rendering (cel shading, Gooch shading). + // Built-in shader for non-photorealistic rendering class NPREffect : public IEffect, public IEffectMatrices, public IEffectLights { public: @@ -887,6 +887,7 @@ namespace DirectX { Mode_Cel = 0, // Cel (toon) shading Mode_Gooch, // Gooch shading + Mode_MatCap, // Material Capture shading }; DIRECTX_TOOLKIT_API explicit NPREffect(_In_ ID3D11Device* device); @@ -940,6 +941,9 @@ namespace DirectX 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); + // MatCap shading setting. + DIRECTX_TOOLKIT_API void __cdecl SetMatCap(_In_opt_ ID3D11ShaderResourceView* value); + // Rim lighting settings. DIRECTX_TOOLKIT_API void XM_CALLCONV SetRimLightingColor(FXMVECTOR value); DIRECTX_TOOLKIT_API void __cdecl SetRimLightingPower(float power); diff --git a/Src/NPREffect.cpp b/Src/NPREffect.cpp index ee0ed8bb1..37cbe7998 100644 --- a/Src/NPREffect.cpp +++ b/Src/NPREffect.cpp @@ -39,10 +39,10 @@ namespace using ConstantBufferType = NPREffectConstants; static constexpr int VertexShaderCount = 16; - static constexpr int PixelShaderCount = 4; - static constexpr int ShaderPermutationCount = 32; + static constexpr int PixelShaderCount = 6; + static constexpr int ShaderPermutationCount = 48; - static constexpr int ModeCount = 2; + static constexpr int ModeCount = 3; }; @@ -74,6 +74,7 @@ class NPREffect::Impl : public EffectBase bool textureEnabled; NPREffect::Mode nprMode; Microsoft::WRL::ComPtr texture; + Microsoft::WRL::ComPtr matcap; int GetCurrentShaderPermutation() const noexcept; @@ -112,8 +113,12 @@ namespace #include "XboxOneNPREffect_PSCelShading.inc" #include "XboxOneNPREffect_PSCelShadingTx.inc" + #include "XboxOneNPREffect_PSGoochShading.inc" #include "XboxOneNPREffect_PSGoochShadingTx.inc" + +#include "XboxOneNPREffect_PSMatCapShading.inc" +#include "XboxOneNPREffect_PSMatCapShadingTx.inc" #else #include "NPREffect_VSNPREffect.inc" #include "NPREffect_VSNPREffectInst.inc" @@ -144,6 +149,9 @@ namespace #include "NPREffect_PSGoochShading.inc" #include "NPREffect_PSGoochShadingTx.inc" + +#include "NPREffect_PSMatCapShading.inc" +#include "NPREffect_PSMatCapShadingTx.inc" #endif } @@ -175,61 +183,79 @@ const int EffectBase::VertexShaderIndices[] = { 0, // cel shading 0, // gooch shading + 0, // matcap shading 1, // vertex color + cel shading 1, // vertex color + gooch shading + 1, // vertex color + matcap shading 2, // cel shading (biased vertex normal) 2, // gooch shading (biased vertex normal) + 2, // matcap shading (biased vertex normal) 3, // vertex color (biased vertex normal) + cel shading 3, // vertex color (biased vertex normal) + gooch shading + 3, // vertex color (biased vertex normal) + matcap shading 4, // instancing + cel shading 4, // instancing + gooch shading + 4, // instancing + matcap shading 5, // instancing + vertex color + cel shading 5, // instancing + vertex color + gooch shading + 5, // instancing + vertex color + matcap shading 6, // instancing (biased vertex normal) + cel shading 6, // instancing (biased vertex normal) + gooch shading + 6, // instancing (biased vertex normal) + matcap shading 7, // instancing + vertex color (biased vertex normal) + cel shading 7, // instancing + vertex color (biased vertex normal) + gooch shading + 7, // instancing + vertex color (biased vertex normal) + matcap shading 8, // cel shading + texture 8, // gooch shading + texture + 8, // matcap shading + texture 9, // vertex color + cel shading + texture 9, // vertex color + gooch shading + texture + 9, // vertex color + matcap shading + texture 10, // cel shading (biased vertex normal) + texture 10, // gooch shading (biased vertex normal) + texture + 10, // matcap shading (biased vertex normal) + texture 11, // vertex color (biased vertex normal) + cel shading + texture 11, // vertex color (biased vertex normal) + gooch shading + texture + 11, // vertex color (biased vertex normal) + matcap shading + texture 12, // instancing + cel shading + texture 12, // instancing + gooch shading + texture + 12, // instancing + matcap shading + texture 13, // instancing + vertex color + cel shading + texture 13, // instancing + vertex color + gooch shading + texture + 13, // instancing + vertex color + matcap shading + texture 14, // instancing (biased vertex normal) + cel shading + texture 14, // instancing (biased vertex normal) + gooch shading + texture + 14, // instancing (biased vertex normal) + matcap shading + texture 15, // instancing + vertex color (biased vertex normal) + cel shading + texture 15, // instancing + vertex color (biased vertex normal) + gooch shading + texture + 15, // instancing + vertex color (biased vertex normal) + matcap shading + texture }; template<> const ShaderBytecode EffectBase::PixelShaderBytecode[] = { - { NPREffect_PSCelShading, sizeof(NPREffect_PSCelShading) }, - { NPREffect_PSGoochShading, sizeof(NPREffect_PSGoochShading) }, - { NPREffect_PSCelShadingTx, sizeof(NPREffect_PSCelShadingTx) }, - { NPREffect_PSGoochShadingTx, sizeof(NPREffect_PSGoochShadingTx) }, + { NPREffect_PSCelShading, sizeof(NPREffect_PSCelShading) }, + { NPREffect_PSGoochShading, sizeof(NPREffect_PSGoochShading) }, + { NPREffect_PSMatCapShading, sizeof(NPREffect_PSMatCapShading) }, + { NPREffect_PSCelShadingTx, sizeof(NPREffect_PSCelShadingTx) }, + { NPREffect_PSGoochShadingTx, sizeof(NPREffect_PSGoochShadingTx) }, + { NPREffect_PSMatCapShadingTx, sizeof(NPREffect_PSMatCapShadingTx) }, }; @@ -238,51 +264,67 @@ const int EffectBase::PixelShaderIndices[] = { 0, // cel shading 1, // gooch shading + 2, // matcap shading 0, // vertex color + cel shading 1, // vertex color + gooch shading + 2, // vertex color + matcap shading 0, // cel shading (biased vertex normal) 1, // gooch shading (biased vertex normal) + 2, // matcap shading (biased vertex normal) 0, // vertex color (biased vertex normal) + cel shading 1, // vertex color (biased vertex normal) + gooch shading + 2, // vertex color (biased vertex normal) + matcap shading 0, // instancing + cel shading 1, // instancing + gooch shading + 2, // instancing + matcap shading 0, // instancing + vertex color + cel shading 1, // instancing + vertex color + gooch shading + 2, // instancing + vertex color + matcap shading 0, // instancing (biased vertex normal) + cel shading 1, // instancing (biased vertex normal) + gooch shading + 2, // instancing (biased vertex normal) + matcap shading 0, // instancing + vertex color (biased vertex normal) + cel shading 1, // instancing + vertex color (biased vertex normal) + gooch shading + 2, // instancing + vertex color (biased vertex normal) + matcap shading - 2, // cel shading + texture - 3, // gooch shading + texture + 3, // cel shading + texture + 4, // gooch shading + texture + 5, // matcap shading + texture - 2, // vertex color + cel shading + texture - 3, // vertex color + gooch shading + texture + 3, // vertex color + cel shading + texture + 4, // vertex color + gooch shading + texture + 5, // vertex color + matcap shading + texture - 2, // cel shading (biased vertex normal) + texture - 3, // gooch shading (biased vertex normal) + texture + 3, // cel shading (biased vertex normal) + texture + 4, // gooch shading (biased vertex normal) + texture + 5, // matcap shading (biased vertex normal) + texture - 2, // vertex color (biased vertex normal) + cel shading + texture - 3, // vertex color (biased vertex normal) + gooch shading + texture + 3, // vertex color (biased vertex normal) + cel shading + texture + 4, // vertex color (biased vertex normal) + gooch shading + texture + 5, // vertex color (biased vertex normal) + matcap shading + texture - 2, // instancing + cel shading + texture - 3, // instancing + gooch shading + texture + 3, // instancing + cel shading + texture + 4, // instancing + gooch shading + texture + 5, // instancing + matcap shading + texture - 2, // instancing + vertex color + cel shading + texture - 3, // instancing + vertex color + gooch shading + texture + 3, // instancing + vertex color + cel shading + texture + 4, // instancing + vertex color + gooch shading + texture + 5, // instancing + vertex color + matcap shading + texture - 2, // instancing (biased vertex normal) + cel shading + texture - 3, // instancing (biased vertex normal) + gooch shading + texture + 3, // instancing (biased vertex normal) + cel shading + texture + 4, // instancing (biased vertex normal) + gooch shading + texture + 5, // instancing (biased vertex normal) + matcap shading + texture - 2, // instancing + vertex color (biased vertex normal) + cel shading + texture - 3, // instancing + vertex color (biased vertex normal) + gooch shading + texture + 3, // instancing + vertex color (biased vertex normal) + cel shading + texture + 4, // instancing + vertex color (biased vertex normal) + gooch shading + texture + 5, // instancing + vertex color (biased vertex normal) + matcap shading + texture }; #pragma endregion @@ -323,25 +365,25 @@ int NPREffect::Impl::GetCurrentShaderPermutation() const noexcept // Support vertex coloring? if (vertexColorEnabled) { - permutation += 2; + permutation += 3; } if (biasedVertexNormals) { // Compressed normals need to be scaled and biased in the vertex shader. - permutation += 4; + permutation += 6; } if (instancing) { // Vertex shader needs to use vertex matrix transform. - permutation += 8; + permutation += 12; } // Use shaders without texture coordinates? if (textureEnabled) { - permutation += 16; + permutation += 24; } return permutation; @@ -385,10 +427,28 @@ void NPREffect::Impl::Apply(_In_ ID3D11DeviceContext* deviceContext) ApplyShaders(deviceContext, GetCurrentShaderPermutation()); // Set texture. - if (textureEnabled) + switch (nprMode) { - ID3D11ShaderResourceView* srv = texture.Get(); - deviceContext->PSSetShaderResources(0, 1, &srv); + case Mode_MatCap: + if (textureEnabled) + { + ID3D11ShaderResourceView* srvs[] = { texture.Get(), matcap.Get() }; + deviceContext->PSSetShaderResources(0, 2, srvs); + } + else + { + ID3D11ShaderResourceView* srv = matcap.Get(); + deviceContext->PSSetShaderResources(0, 1, &srv); + } + break; + + default: + if (textureEnabled) + { + ID3D11ShaderResourceView* srv = texture.Get(); + deviceContext->PSSetShaderResources(0, 1, &srv); + } + break; } } @@ -637,6 +697,13 @@ void NPREffect::SetGoochWarmColor(FXMVECTOR value, float beta) } +// MatCap shading setting. +void NPREffect::SetMatCap(_In_opt_ ID3D11ShaderResourceView* value) +{ + pImpl->matcap = value; +} + + // Rim lighting settings. void NPREffect::SetRimLightingColor(FXMVECTOR value) { diff --git a/Src/Shaders/CompileShaders.cmd b/Src/Shaders/CompileShaders.cmd index 911d5390b..4ecfff9f0 100644 --- a/Src/Shaders/CompileShaders.cmd +++ b/Src/Shaders/CompileShaders.cmd @@ -252,6 +252,9 @@ call :CompileShader%1 NPREffect ps PSCelShadingTx call :CompileShader%1 NPREffect ps PSGoochShading call :CompileShader%1 NPREffect ps PSGoochShadingTx +call :CompileShader%1 NPREffect ps PSMatCapShading +call :CompileShader%1 NPREffect ps PSMatCapShadingTx + call :CompileShader%1 SpriteEffect vs SpriteVertexShader call :CompileShader%1 SpriteEffect ps SpritePixelShader diff --git a/Src/Shaders/NPREffect.fx b/Src/Shaders/NPREffect.fx index 02e66ae6a..3f668f7de 100644 --- a/Src/Shaders/NPREffect.fx +++ b/Src/Shaders/NPREffect.fx @@ -5,6 +5,7 @@ Texture2D Texture : register(t0); +Texture2D Texture2 : register(t1); SamplerState Sampler : register(s0); @@ -375,3 +376,44 @@ float4 PSGoochShadingTx(PSInputPixelLightingTx pin) : SV_Target0 return float4(color, pin.Diffuse.a * texColor.a); } + + +//--- MatCap shading --- + +// Pixel shader: MatCap shading. +float4 PSMatCapShading(PSInputPixelLighting pin) : SV_Target0 +{ + float3 normal = normalize(pin.NormalWS); + float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); + float3 reflectDir = reflect(viewDir, normal); + + float2 uv = reflectDir.xy * 0.5 + 0.5; + + // Sample matcap texture + float4 matcap = Texture.Sample(Sampler, uv); + + float3 color = pin.Diffuse.rgb * matcap.rgb; + + return float4(color, pin.Diffuse.a); +} + + +// Pixel shader: MatCap shading + texture. +float4 PSMatCapShadingTx(PSInputPixelLightingTx pin) : SV_Target0 +{ + float3 normal = normalize(pin.NormalWS); + float3 viewDir = normalize(EyePosition - pin.PositionWS.xyz); + float3 reflectDir = reflect(viewDir, normal); + + float2 uv = reflectDir.xy * 0.5 + 0.5; + + // Sample base texture + float4 texColor = Texture.Sample(Sampler, pin.TexCoord); + + // Sample matcap texture + float4 matcap = Texture2.Sample(Sampler, uv); + + float3 color = pin.Diffuse.rgb * texColor.rgb * matcap.rgb; + + return float4(color, pin.Diffuse.a * texColor.a); +}