diff --git a/.gitignore b/.gitignore index 97068de85..444582a7f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ projects/ workdir/cache/ workdir/Assets/ +workdir/test_results/ workdir/pso workdir/assert.txt diff --git a/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp b/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp index 5e2549e29..3fd8776e9 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp @@ -414,7 +414,8 @@ namespace HAL void CommandList::update_texture(HAL::Resource* resource, ivec3 offset, ivec3 box, UINT sub_resource, ResourceAddress address, texture_layout layout) { auto buffer_desc = address.resource->get_desc().as_buffer(); - auto size = box.z * box.y * layout.row_stride; + // rows_count, not box.y: block-compressed formats have box.y / 4 rows + auto size = static_cast(box.z) * layout.rows_count * layout.row_stride; ASSERT(address.resource_offset + size <= buffer_desc.SizeInBytes); ASSERT(box.z > 0); diff --git a/sources/HAL/DXGI/HAL.DXGI.Swapchain.cpp b/sources/HAL/DXGI/HAL.DXGI.Swapchain.cpp index 522af374f..f2e4ae8c5 100644 --- a/sources/HAL/DXGI/HAL.DXGI.Swapchain.cpp +++ b/sources/HAL/DXGI/HAL.DXGI.Swapchain.cpp @@ -83,8 +83,8 @@ namespace HAL auto native = m_swapChain; device.get_queue(CommandListType::DIRECT)->run([native, this](){ - PROFILE(L"Present") - native->Present(1, 0); + PROFILE(L"Present"); + native->Present(1, 0); }); frames[m_frameIndex].fence_event = device.get_queue(CommandListType::DIRECT)->signal(); diff --git a/sources/HAL/HAL.ixx b/sources/HAL/HAL.ixx index 078c187d1..08fee042d 100644 --- a/sources/HAL/HAL.ixx +++ b/sources/HAL/HAL.ixx @@ -11,6 +11,7 @@ export import :Utils; export import :Device; export import :Queue; export import :Shader; +export import :ShaderCompiler; export import :HLSL; export import :Fence; diff --git a/sources/HAL/autogen/autogen.cpp b/sources/HAL/autogen/autogen.cpp index 2b94ec61c..9cfb08a61 100644 --- a/sources/HAL/autogen/autogen.cpp +++ b/sources/HAL/autogen/autogen.cpp @@ -112,6 +112,10 @@ std::optional get_slot(std::string_view slot_name) if(slot_name == "SMAA_Global") return SlotID::SMAA_Global; if(slot_name == "SMAA_Weights") return SlotID::SMAA_Weights; if(slot_name == "SMAA_Blend") return SlotID::SMAA_Blend; + if(slot_name == "SSSClassifyParams") return SlotID::SSSClassifyParams; + if(slot_name == "SSSTilesAppend") return SlotID::SSSTilesAppend; + if(slot_name == "SSSTilesRead") return SlotID::SSSTilesRead; + if(slot_name == "SSSBlurParams") return SlotID::SSSBlurParams; if(slot_name == "DispatchParameters") return SlotID::DispatchParameters; if(slot_name == "Countour") return SlotID::Countour; if(slot_name == "DrawStencil") return SlotID::DrawStencil; @@ -382,6 +386,14 @@ uint get_table_index(SlotID id) if(id == SlotID::SMAA_Blend) return Slots::Post::AA::SMAA_Blend::Slot::ID; + if(id == SlotID::SSSClassifyParams) return Slots::Subsurface::SSSClassifyParams::Slot::ID; + + if(id == SlotID::SSSTilesAppend) return Slots::Subsurface::SSSTilesAppend::Slot::ID; + + if(id == SlotID::SSSTilesRead) return Slots::Subsurface::SSSTilesRead::Slot::ID; + + if(id == SlotID::SSSBlurParams) return Slots::Subsurface::SSSBlurParams::Slot::ID; + if(id == SlotID::DispatchParameters) return Slots::Shadows::Screen::DispatchParameters::Slot::ID; if(id == SlotID::Countour) return Slots::Editor::Countour::Slot::ID; @@ -605,6 +617,10 @@ std::string get_slot_name(SlotID id) if(id == SlotID::SMAA_Global) return "SMAA_Global"; if(id == SlotID::SMAA_Weights) return "SMAA_Weights"; if(id == SlotID::SMAA_Blend) return "SMAA_Blend"; + if(id == SlotID::SSSClassifyParams) return "SSSClassifyParams"; + if(id == SlotID::SSSTilesAppend) return "SSSTilesAppend"; + if(id == SlotID::SSSTilesRead) return "SSSTilesRead"; + if(id == SlotID::SSSBlurParams) return "SSSBlurParams"; if(id == SlotID::DispatchParameters) return "DispatchParameters"; if(id == SlotID::Countour) return "Countour"; if(id == SlotID::DrawStencil) return "DrawStencil"; diff --git a/sources/HAL/autogen/autogen.ixx b/sources/HAL/autogen/autogen.ixx index 9794de8d8..350e41547 100644 --- a/sources/HAL/autogen/autogen.ixx +++ b/sources/HAL/autogen/autogen.ixx @@ -272,6 +272,14 @@ export import :Autogen.Slots.Post.AA.SMAA_Weights; export import :Autogen.Tables.Post.AA.SMAA_Weights; export import :Autogen.Slots.Post.AA.SMAA_Blend; export import :Autogen.Tables.Post.AA.SMAA_Blend; +export import :Autogen.Slots.Subsurface.SSSClassifyParams; +export import :Autogen.Tables.Subsurface.SSSClassifyParams; +export import :Autogen.Slots.Subsurface.SSSTilesAppend; +export import :Autogen.Tables.Subsurface.SSSTilesAppend; +export import :Autogen.Slots.Subsurface.SSSTilesRead; +export import :Autogen.Tables.Subsurface.SSSTilesRead; +export import :Autogen.Slots.Subsurface.SSSBlurParams; +export import :Autogen.Tables.Subsurface.SSSBlurParams; export import :Autogen.Slots.Shadows.Screen.DispatchParameters; export import :Autogen.Tables.Shadows.Screen.DispatchParameters; export import :Autogen.Slots.Editor.Countour; @@ -484,6 +492,9 @@ export import :Autogen.PSO.Environment.CubemapENVDiffuse; export import :Autogen.PSO.Post.AA.EdgeDetectCompute; export import :Autogen.PSO.Post.AA.BlendWeightCompute; export import :Autogen.PSO.Post.AA.BlendingCompute; +export import :Autogen.PSO.Subsurface.SSSClassify; +export import :Autogen.PSO.Subsurface.SSSBlurH; +export import :Autogen.PSO.Subsurface.SSSBlurV; export import :Autogen.PSO.Shadows.Screen.SS_Shadow; export import :Autogen.PSO.Post.TonemapHistogramClear; export import :Autogen.PSO.Post.TonemapHistogramBuild; diff --git a/sources/HAL/autogen/enums.ixx b/sources/HAL/autogen/enums.ixx index 664a67466..9320cb566 100644 --- a/sources/HAL/autogen/enums.ixx +++ b/sources/HAL/autogen/enums.ixx @@ -95,6 +95,9 @@ export EdgeDetectCompute, BlendWeightCompute, BlendingCompute, + SSSClassify, + SSSBlurH, + SSSBlurV, SS_Shadow, TonemapHistogramClear, TonemapHistogramBuild, @@ -322,6 +325,10 @@ export SMAA_Global = "SMAA_Global"_crc32, SMAA_Weights = "SMAA_Weights"_crc32, SMAA_Blend = "SMAA_Blend"_crc32, + SSSClassifyParams = "SSSClassifyParams"_crc32, + SSSTilesAppend = "SSSTilesAppend"_crc32, + SSSTilesRead = "SSSTilesRead"_crc32, + SSSBlurParams = "SSSBlurParams"_crc32, DispatchParameters = "DispatchParameters"_crc32, Countour = "Countour"_crc32, DrawStencil = "DrawStencil"_crc32, diff --git a/sources/HAL/autogen/layout/Frame/DefaultLayout.layout.ixx b/sources/HAL/autogen/layout/Frame/DefaultLayout.layout.ixx index 753c98d76..b5e34f38f 100644 --- a/sources/HAL/autogen/layout/Frame/DefaultLayout.layout.ixx +++ b/sources/HAL/autogen/layout/Frame/DefaultLayout.layout.ixx @@ -19,7 +19,7 @@ namespace Frame static const uint ID = 4; static const uint CB = 37; static const uint CB_ID = 11; - static const uint SRV = 9; + static const uint SRV = 11; static const uint SRV_ID = 13; static const uint UAV = 9; static const uint UAV_ID = 14; @@ -31,7 +31,7 @@ namespace Frame static const uint ID = 5; static const uint CB = 32; static const uint CB_ID = 15; - static const uint SRV = 11; + static const uint SRV = 12; static const uint SRV_ID = 17; static const uint UAV = 8; static const uint UAV_ID = 18; @@ -45,7 +45,7 @@ namespace Frame static const uint CB_ID = 19; static const uint SRV = 14; static const uint SRV_ID = 21; - static const uint UAV = 5; + static const uint UAV = 6; static const uint UAV_ID = 22; static inline const std::vector tables = { 19, 21, 22 }; }; diff --git a/sources/HAL/autogen/pso.cpp b/sources/HAL/autogen/pso.cpp index d4c55376d..48333845c 100644 --- a/sources/HAL/autogen/pso.cpp +++ b/sources/HAL/autogen/pso.cpp @@ -103,6 +103,9 @@ void init_pso(HAL::Device& device, enum_array& pso) tasks.emplace_back(PSOBase::create(device, pso[PSO::EdgeDetectCompute])); tasks.emplace_back(PSOBase::create(device, pso[PSO::BlendWeightCompute])); tasks.emplace_back(PSOBase::create(device, pso[PSO::BlendingCompute])); + tasks.emplace_back(PSOBase::create(device, pso[PSO::SSSClassify])); + tasks.emplace_back(PSOBase::create(device, pso[PSO::SSSBlurH])); + tasks.emplace_back(PSOBase::create(device, pso[PSO::SSSBlurV])); tasks.emplace_back(PSOBase::create(device, pso[PSO::SS_Shadow])); tasks.emplace_back(PSOBase::create(device, pso[PSO::TonemapHistogramClear])); tasks.emplace_back(PSOBase::create(device, pso[PSO::TonemapHistogramBuild])); diff --git a/sources/HAL/autogen/pso/Meshes/GBufferDraw.pso.ixx b/sources/HAL/autogen/pso/Meshes/GBufferDraw.pso.ixx index 53f4ab666..130bf264c 100644 --- a/sources/HAL/autogen/pso/Meshes/GBufferDraw.pso.ixx +++ b/sources/HAL/autogen/pso/Meshes/GBufferDraw.pso.ixx @@ -62,7 +62,7 @@ export namespace PSOS CaptureMeshlets.Apply(mpso, key); ClusterLod.Apply(mpso, key); - mpso.rtv_formats = { HAL::Format::R8G8B8A8_UNORM, HAL::Format::R8G8B8A8_UNORM, HAL::Format::R8G8B8A8_UNORM, HAL::Format::R16G16_FLOAT, HAL::Format::R32_UINT }; + mpso.rtv_formats = { HAL::Format::R8G8B8A8_UNORM, HAL::Format::R8G8B8A8_UNORM, HAL::Format::R8G8B8A8_UNORM, HAL::Format::R16G16_FLOAT, HAL::Format::R32_UINT, HAL::Format::R8G8B8A8_UNORM }; mpso.blend = { }; mpso.ds =HAL::Format::D32_FLOAT; diff --git a/sources/HAL/autogen/pso/Subsurface/SSSBlurH.pso.ixx b/sources/HAL/autogen/pso/Subsurface/SSSBlurH.pso.ixx new file mode 100644 index 000000000..bd8df2123 --- /dev/null +++ b/sources/HAL/autogen/pso/Subsurface/SSSBlurH.pso.ixx @@ -0,0 +1,55 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.PSO.Subsurface.SSSBlurH; + +import Core; +import :PSO; +import :Enums; +import :Types; + + +export namespace PSOS +{ + namespace Subsurface + { + struct SSSBlurH: public PSOBase + { + struct Keys { + GEN_DEF_COMP(Keys); + private: + SERIALIZE() + { + } + }; + + GEN_COMPUTE_PSO(SSSBlurH) + + + SimplePSO init_pso(Keys & key, std::function f) + { + + + SimplePSO mpso("SSSBlurH"); + if(f) f(mpso,key); + + mpso.root_signature = Layouts::DefaultLayout; + + mpso.compute.file_name = "shaders/postprocess/sss_blur.hlsl"; + mpso.compute.entry_point = "CS_H"; + mpso.compute.flags = HAL::ShaderOptions::None; + + return mpso; + } + + private: + SERIALIZE() + { + ar&NVP(wrap(psos)); + } + }; + } +} \ No newline at end of file diff --git a/sources/HAL/autogen/pso/Subsurface/SSSBlurV.pso.ixx b/sources/HAL/autogen/pso/Subsurface/SSSBlurV.pso.ixx new file mode 100644 index 000000000..a51a923d0 --- /dev/null +++ b/sources/HAL/autogen/pso/Subsurface/SSSBlurV.pso.ixx @@ -0,0 +1,55 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.PSO.Subsurface.SSSBlurV; + +import Core; +import :PSO; +import :Enums; +import :Types; + + +export namespace PSOS +{ + namespace Subsurface + { + struct SSSBlurV: public PSOBase + { + struct Keys { + GEN_DEF_COMP(Keys); + private: + SERIALIZE() + { + } + }; + + GEN_COMPUTE_PSO(SSSBlurV) + + + SimplePSO init_pso(Keys & key, std::function f) + { + + + SimplePSO mpso("SSSBlurV"); + if(f) f(mpso,key); + + mpso.root_signature = Layouts::DefaultLayout; + + mpso.compute.file_name = "shaders/postprocess/sss_blur.hlsl"; + mpso.compute.entry_point = "CS_V"; + mpso.compute.flags = HAL::ShaderOptions::None; + + return mpso; + } + + private: + SERIALIZE() + { + ar&NVP(wrap(psos)); + } + }; + } +} \ No newline at end of file diff --git a/sources/HAL/autogen/pso/Subsurface/SSSClassify.pso.ixx b/sources/HAL/autogen/pso/Subsurface/SSSClassify.pso.ixx new file mode 100644 index 000000000..9a187e189 --- /dev/null +++ b/sources/HAL/autogen/pso/Subsurface/SSSClassify.pso.ixx @@ -0,0 +1,55 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.PSO.Subsurface.SSSClassify; + +import Core; +import :PSO; +import :Enums; +import :Types; + + +export namespace PSOS +{ + namespace Subsurface + { + struct SSSClassify: public PSOBase + { + struct Keys { + GEN_DEF_COMP(Keys); + private: + SERIALIZE() + { + } + }; + + GEN_COMPUTE_PSO(SSSClassify) + + + SimplePSO init_pso(Keys & key, std::function f) + { + + + SimplePSO mpso("SSSClassify"); + if(f) f(mpso,key); + + mpso.root_signature = Layouts::DefaultLayout; + + mpso.compute.file_name = "shaders/postprocess/sss_classify.hlsl"; + mpso.compute.entry_point = "CS_CLASSIFY"; + mpso.compute.flags = HAL::ShaderOptions::None; + + return mpso; + } + + private: + SERIALIZE() + { + ar&NVP(wrap(psos)); + } + }; + } +} \ No newline at end of file diff --git a/sources/HAL/autogen/rt/Meshes/GBuffer.rt.ixx b/sources/HAL/autogen/rt/Meshes/GBuffer.rt.ixx index c876f3a45..c6cb32c25 100644 --- a/sources/HAL/autogen/rt/Meshes/GBuffer.rt.ixx +++ b/sources/HAL/autogen/rt/Meshes/GBuffer.rt.ixx @@ -24,12 +24,14 @@ export namespace RT HLSL::RenderTarget specular; HLSL::RenderTarget motion; HLSL::RenderTarget object_id; + HLSL::RenderTarget material; HLSL::DepthStencil depth; HLSL::RenderTarget& GetAlbedo() { return albedo; } HLSL::RenderTarget& GetNormals() { return normals; } HLSL::RenderTarget& GetSpecular() { return specular; } HLSL::RenderTarget& GetMotion() { return motion; } HLSL::RenderTarget& GetObject_Id() { return object_id; } + HLSL::RenderTarget& GetMaterial() { return material; } HLSL::DepthStencil& GetDepth() { return depth; } template void compile(Compiler& compiler) const @@ -39,6 +41,7 @@ export namespace RT compiler.compile(specular); compiler.compile(motion); compiler.compile(object_id); + compiler.compile(material); compiler.compile(depth); } }; diff --git a/sources/HAL/autogen/slots/Subsurface/SSSBlurParams.ixx b/sources/HAL/autogen/slots/Subsurface/SSSBlurParams.ixx new file mode 100644 index 000000000..e195496c1 --- /dev/null +++ b/sources/HAL/autogen/slots/Subsurface/SSSBlurParams.ixx @@ -0,0 +1,26 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.Slots.Subsurface.SSSBlurParams; +import Core; +import :Autogen.Tables.Subsurface.SSSBlurParams; +import :Autogen.Layouts.Frame.DefaultLayout; +import :SIG; +import :Types; +import :Enums; +import :Slots; + +export namespace Slots +{ + namespace Subsurface + { + struct SSSBlurParams :public DataHolder + { + static constexpr SIG_TYPE TYPE = SIG_TYPE::Slot; + SSSBlurParams() = default; + }; + } +} \ No newline at end of file diff --git a/sources/HAL/autogen/slots/Subsurface/SSSClassifyParams.ixx b/sources/HAL/autogen/slots/Subsurface/SSSClassifyParams.ixx new file mode 100644 index 000000000..580db19fd --- /dev/null +++ b/sources/HAL/autogen/slots/Subsurface/SSSClassifyParams.ixx @@ -0,0 +1,26 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.Slots.Subsurface.SSSClassifyParams; +import Core; +import :Autogen.Tables.Subsurface.SSSClassifyParams; +import :Autogen.Layouts.Frame.DefaultLayout; +import :SIG; +import :Types; +import :Enums; +import :Slots; + +export namespace Slots +{ + namespace Subsurface + { + struct SSSClassifyParams :public DataHolder + { + static constexpr SIG_TYPE TYPE = SIG_TYPE::Slot; + SSSClassifyParams() = default; + }; + } +} \ No newline at end of file diff --git a/sources/HAL/autogen/slots/Subsurface/SSSTilesAppend.ixx b/sources/HAL/autogen/slots/Subsurface/SSSTilesAppend.ixx new file mode 100644 index 000000000..f9093d016 --- /dev/null +++ b/sources/HAL/autogen/slots/Subsurface/SSSTilesAppend.ixx @@ -0,0 +1,26 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.Slots.Subsurface.SSSTilesAppend; +import Core; +import :Autogen.Tables.Subsurface.SSSTilesAppend; +import :Autogen.Layouts.Frame.DefaultLayout; +import :SIG; +import :Types; +import :Enums; +import :Slots; + +export namespace Slots +{ + namespace Subsurface + { + struct SSSTilesAppend :public DataHolder + { + static constexpr SIG_TYPE TYPE = SIG_TYPE::Slot; + SSSTilesAppend() = default; + }; + } +} \ No newline at end of file diff --git a/sources/HAL/autogen/slots/Subsurface/SSSTilesRead.ixx b/sources/HAL/autogen/slots/Subsurface/SSSTilesRead.ixx new file mode 100644 index 000000000..7ee3631ae --- /dev/null +++ b/sources/HAL/autogen/slots/Subsurface/SSSTilesRead.ixx @@ -0,0 +1,26 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.Slots.Subsurface.SSSTilesRead; +import Core; +import :Autogen.Tables.Subsurface.SSSTilesRead; +import :Autogen.Layouts.Frame.DefaultLayout; +import :SIG; +import :Types; +import :Enums; +import :Slots; + +export namespace Slots +{ + namespace Subsurface + { + struct SSSTilesRead :public DataHolder + { + static constexpr SIG_TYPE TYPE = SIG_TYPE::Slot; + SSSTilesRead() = default; + }; + } +} \ No newline at end of file diff --git a/sources/HAL/autogen/tables/GI/RTXCombine.table.ixx b/sources/HAL/autogen/tables/GI/RTXCombine.table.ixx index 3cf31cf07..bece57d89 100644 --- a/sources/HAL/autogen/tables/GI/RTXCombine.table.ixx +++ b/sources/HAL/autogen/tables/GI/RTXCombine.table.ixx @@ -21,11 +21,15 @@ export namespace Table { static constexpr SlotID ID = SlotID::RTXCombine; HLSL::Texture2D reflection; + HLSL::Texture2D coat; HLSL::Texture2D indirect; HLSL::Texture2D shadow; + HLSL::RWTexture2D specular_out; HLSL::RWTexture2D target; Table::Meshes::GBuffer gbuffer; HLSL::Texture2D& GetReflection() { return reflection; } + HLSL::Texture2D& GetCoat() { return coat; } + HLSL::RWTexture2D& GetSpecular_out() { return specular_out; } HLSL::Texture2D& GetIndirect() { return indirect; } HLSL::Texture2D& GetShadow() { return shadow; } HLSL::RWTexture2D& GetTarget() { return target; } @@ -35,16 +39,20 @@ export namespace Table void compile(Compiler& compiler) const { compiler.compile(reflection, "RTXCombine::reflection"); + compiler.compile(coat, "RTXCombine::coat"); compiler.compile(indirect, "RTXCombine::indirect"); compiler.compile(shadow, "RTXCombine::shadow"); + compiler.compile(specular_out, "RTXCombine::specular_out"); compiler.compile(target, "RTXCombine::target"); compiler.compile(gbuffer, "RTXCombine::gbuffer"); } struct Compiled { uint reflection; // Texture2D + uint coat; // Texture2D uint indirect; // Texture2D uint shadow; // Texture2D + uint specular_out; // RWTexture2D uint target; // RWTexture2D Table::Meshes::GBuffer::Compiled gbuffer; // GBuffer diff --git a/sources/HAL/autogen/tables/GI/Voxel/VoxelOutput.table.ixx b/sources/HAL/autogen/tables/GI/Voxel/VoxelOutput.table.ixx index fb212b281..64085be34 100644 --- a/sources/HAL/autogen/tables/GI/Voxel/VoxelOutput.table.ixx +++ b/sources/HAL/autogen/tables/GI/Voxel/VoxelOutput.table.ixx @@ -27,6 +27,7 @@ export namespace Table HLSL::Texture2DArray ddgi_visibility; HLSL::StructuredBuffer ddgi_residency; HLSL::RWTexture2D noise; + HLSL::RWTexture2D coat; HLSL::RWTexture2D DirAndPdf; HLSL::RWTexture2D noiseRaw; HLSL::RWTexture2D shadow_noise; @@ -37,6 +38,7 @@ export namespace Table Table::GI::DDGI::DDGIInfo ddgi_cascade3; Table::GI::DDGI::DDGIInfo ddgi_cascade4; HLSL::RWTexture2D& GetNoise() { return noise; } + HLSL::RWTexture2D& GetCoat() { return coat; } HLSL::RWTexture2D& GetDirAndPdf() { return DirAndPdf; } HLSL::Texture2D& GetBlueNoise() { return blueNoise; } HLSL::RWTexture2D& GetNoiseRaw() { return noiseRaw; } @@ -59,6 +61,7 @@ export namespace Table compiler.compile_auto(ddgi_visibility, "VoxelOutput::ddgi_visibility"); compiler.compile_auto(ddgi_residency, "VoxelOutput::ddgi_residency"); compiler.compile_auto(noise, "VoxelOutput::noise"); + compiler.compile_auto(coat, "VoxelOutput::coat"); compiler.compile_auto(DirAndPdf, "VoxelOutput::DirAndPdf"); compiler.compile_auto(noiseRaw, "VoxelOutput::noiseRaw"); compiler.compile_auto(shadow_noise, "VoxelOutput::shadow_noise"); @@ -76,6 +79,7 @@ export namespace Table uint ddgi_visibility; // Texture2DArray uint ddgi_residency; // StructuredBuffer uint noise; // RWTexture2D + uint coat; // RWTexture2D uint DirAndPdf; // RWTexture2D uint noiseRaw; // RWTexture2D uint shadow_noise; // RWTexture2D diff --git a/sources/HAL/autogen/tables/Meshes/GBuffer.table.ixx b/sources/HAL/autogen/tables/Meshes/GBuffer.table.ixx index d2e3e1b95..b46e4c19d 100644 --- a/sources/HAL/autogen/tables/Meshes/GBuffer.table.ixx +++ b/sources/HAL/autogen/tables/Meshes/GBuffer.table.ixx @@ -24,12 +24,14 @@ export namespace Table HLSL::Texture2D specular; HLSL::Texture2D motion; HLSL::Texture2D object_id; + HLSL::Texture2D material; HLSL::Texture2D depth; HLSL::Texture2D& GetAlbedo() { return albedo; } HLSL::Texture2D& GetNormals() { return normals; } HLSL::Texture2D& GetSpecular() { return specular; } HLSL::Texture2D& GetMotion() { return motion; } HLSL::Texture2D& GetObject_id() { return object_id; } + HLSL::Texture2D& GetMaterial() { return material; } HLSL::Texture2D& GetDepth() { return depth; } static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; template @@ -40,6 +42,7 @@ export namespace Table compiler.compile(specular, "GBuffer::specular"); compiler.compile(motion, "GBuffer::motion"); compiler.compile_auto(object_id, "GBuffer::object_id"); + compiler.compile_auto(material, "GBuffer::material"); compiler.compile(depth, "GBuffer::depth"); } struct Compiled @@ -49,6 +52,7 @@ export namespace Table uint specular; // RenderTarget uint motion; // RenderTarget uint object_id; // RenderTarget + uint material; // RenderTarget uint depth; // DepthStencil diff --git a/sources/HAL/autogen/tables/Raytrace/TranslucentPayload.table.ixx b/sources/HAL/autogen/tables/Raytrace/TranslucentPayload.table.ixx index 277e3fbcf..388a3f6ef 100644 --- a/sources/HAL/autogen/tables/Raytrace/TranslucentPayload.table.ixx +++ b/sources/HAL/autogen/tables/Raytrace/TranslucentPayload.table.ixx @@ -22,6 +22,7 @@ export namespace Table float dist; float3 normal; float3 albedo; + float3 transmission_color; float roughness; float ior; float thickness; @@ -31,6 +32,7 @@ export namespace Table float& GetDist() { return dist; } float3& GetNormal() { return normal; } float3& GetAlbedo() { return albedo; } + float3& GetTransmission_color() { return transmission_color; } float& GetRoughness() { return roughness; } float& GetIor() { return ior; } float& GetThickness() { return thickness; } @@ -44,6 +46,7 @@ export namespace Table compiler.compile(dist, "TranslucentPayload::dist"); compiler.compile(normal, "TranslucentPayload::normal"); compiler.compile(albedo, "TranslucentPayload::albedo"); + compiler.compile(transmission_color, "TranslucentPayload::transmission_color"); compiler.compile(roughness, "TranslucentPayload::roughness"); compiler.compile(ior, "TranslucentPayload::ior"); compiler.compile(thickness, "TranslucentPayload::thickness"); @@ -63,6 +66,7 @@ export namespace Table ar& NVP(dist); ar& NVP(normal); ar& NVP(albedo); + ar& NVP(transmission_color); ar& NVP(roughness); ar& NVP(ior); ar& NVP(thickness); diff --git a/sources/HAL/autogen/tables/Reflections/ReflectionCombine.table.ixx b/sources/HAL/autogen/tables/Reflections/ReflectionCombine.table.ixx index 919da634e..4c3282541 100644 --- a/sources/HAL/autogen/tables/Reflections/ReflectionCombine.table.ixx +++ b/sources/HAL/autogen/tables/Reflections/ReflectionCombine.table.ixx @@ -21,9 +21,11 @@ export namespace Table { static constexpr SlotID ID = SlotID::ReflectionCombine; HLSL::Texture2D reflection; + HLSL::Texture2D coat; HLSL::RWTexture2D target; Table::Meshes::GBuffer gbuffer; HLSL::Texture2D& GetReflection() { return reflection; } + HLSL::Texture2D& GetCoat() { return coat; } HLSL::RWTexture2D& GetTarget() { return target; } Table::Meshes::GBuffer& GetGbuffer() { return gbuffer; } static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; @@ -31,12 +33,14 @@ export namespace Table void compile(Compiler& compiler) const { compiler.compile(reflection, "ReflectionCombine::reflection"); + compiler.compile(coat, "ReflectionCombine::coat"); compiler.compile(target, "ReflectionCombine::target"); compiler.compile(gbuffer, "ReflectionCombine::gbuffer"); } struct Compiled { uint reflection; // Texture2D + uint coat; // Texture2D uint target; // RWTexture2D Table::Meshes::GBuffer::Compiled gbuffer; // GBuffer diff --git a/sources/HAL/autogen/tables/Subsurface/SSSBlurParams.table.ixx b/sources/HAL/autogen/tables/Subsurface/SSSBlurParams.table.ixx new file mode 100644 index 000000000..cb1d0aa18 --- /dev/null +++ b/sources/HAL/autogen/tables/Subsurface/SSSBlurParams.table.ixx @@ -0,0 +1,77 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.Tables.Subsurface.SSSBlurParams; + +import Core; +import :SIG; +import :Types; +import :HLSL; +import :Enums; +import :Autogen.Tables.Meshes.GBuffer; +export namespace Table +{ + #pragma pack(push, 1) + namespace Subsurface + { + struct SSSBlurParams + { + static constexpr SlotID ID = SlotID::SSSBlurParams; + int add_specular; + HLSL::Texture2D source; + HLSL::Texture2D specular; + HLSL::RWTexture2D target; + Table::Meshes::GBuffer gbuffer; + HLSL::Texture2D& GetSource() { return source; } + HLSL::Texture2D& GetSpecular() { return specular; } + HLSL::RWTexture2D& GetTarget() { return target; } + int& GetAdd_specular() { return add_specular; } + Table::Meshes::GBuffer& GetGbuffer() { return gbuffer; } + static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; + template + void compile(Compiler& compiler) const + { + compiler.compile(add_specular, "SSSBlurParams::add_specular"); + compiler.compile(source, "SSSBlurParams::source"); + compiler.compile_auto(specular, "SSSBlurParams::specular"); + compiler.compile(target, "SSSBlurParams::target"); + compiler.compile(gbuffer, "SSSBlurParams::gbuffer"); + } + struct Compiled + { + int add_specular; // int + uint source; // Texture2D + uint specular; // Texture2D + uint target; // RWTexture2D + Table::Meshes::GBuffer::Compiled gbuffer; // GBuffer + + + private: + SERIALIZE() + { + ar& NVP(gbuffer); + ar& NVP(add_specular); + } + + + }; + + static std::string get_typename() + { + return "Tables::SSSBlurParams"; + } + private: + SERIALIZE() + { + ar& NVP(gbuffer); + ar& NVP(add_specular); + } + + }; + } + #pragma pack(pop) +} + diff --git a/sources/HAL/autogen/tables/Subsurface/SSSClassifyParams.table.ixx b/sources/HAL/autogen/tables/Subsurface/SSSClassifyParams.table.ixx new file mode 100644 index 000000000..0c1cebd51 --- /dev/null +++ b/sources/HAL/autogen/tables/Subsurface/SSSClassifyParams.table.ixx @@ -0,0 +1,47 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.Tables.Subsurface.SSSClassifyParams; + +import Core; +import :SIG; +import :Types; +import :HLSL; +import :Enums; +import :Autogen.Tables.Meshes.GBuffer; +export namespace Table +{ + #pragma pack(push, 1) + namespace Subsurface + { + struct SSSClassifyParams + { + static constexpr SlotID ID = SlotID::SSSClassifyParams; + Table::Meshes::GBuffer gbuffer; + Table::Meshes::GBuffer& GetGbuffer() { return gbuffer; } + static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; + template + void compile(Compiler& compiler) const + { + compiler.compile(gbuffer, "SSSClassifyParams::gbuffer"); + } + using Compiled = SSSClassifyParams; + + static std::string get_typename() + { + return "Tables::SSSClassifyParams"; + } + private: + SERIALIZE() + { + ar& NVP(gbuffer); + } + + }; + } + #pragma pack(pop) +} + diff --git a/sources/HAL/autogen/tables/Subsurface/SSSTilesAppend.table.ixx b/sources/HAL/autogen/tables/Subsurface/SSSTilesAppend.table.ixx new file mode 100644 index 000000000..786594134 --- /dev/null +++ b/sources/HAL/autogen/tables/Subsurface/SSSTilesAppend.table.ixx @@ -0,0 +1,56 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.Tables.Subsurface.SSSTilesAppend; + +import Core; +import :SIG; +import :Types; +import :HLSL; +import :Enums; +export namespace Table +{ + #pragma pack(push, 1) + namespace Subsurface + { + struct SSSTilesAppend + { + static constexpr SlotID ID = SlotID::SSSTilesAppend; + HLSL::AppendStructuredBuffer tiles; + HLSL::AppendStructuredBuffer& GetTiles() { return tiles; } + static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; + template + void compile(Compiler& compiler) const + { + compiler.compile(tiles, "SSSTilesAppend::tiles"); + } + struct Compiled + { + uint tiles; // AppendStructuredBuffer + + + private: + SERIALIZE() + { + } + + + }; + + static std::string get_typename() + { + return "Tables::SSSTilesAppend"; + } + private: + SERIALIZE() + { + } + + }; + } + #pragma pack(pop) +} + diff --git a/sources/HAL/autogen/tables/Subsurface/SSSTilesRead.table.ixx b/sources/HAL/autogen/tables/Subsurface/SSSTilesRead.table.ixx new file mode 100644 index 000000000..ce7654554 --- /dev/null +++ b/sources/HAL/autogen/tables/Subsurface/SSSTilesRead.table.ixx @@ -0,0 +1,56 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +export module HAL:Autogen.Tables.Subsurface.SSSTilesRead; + +import Core; +import :SIG; +import :Types; +import :HLSL; +import :Enums; +export namespace Table +{ + #pragma pack(push, 1) + namespace Subsurface + { + struct SSSTilesRead + { + static constexpr SlotID ID = SlotID::SSSTilesRead; + HLSL::StructuredBuffer tiles; + HLSL::StructuredBuffer& GetTiles() { return tiles; } + static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; + template + void compile(Compiler& compiler) const + { + compiler.compile(tiles, "SSSTilesRead::tiles"); + } + struct Compiled + { + uint tiles; // StructuredBuffer + + + private: + SERIALIZE() + { + } + + + }; + + static std::string get_typename() + { + return "Tables::SSSTilesRead"; + } + private: + SERIALIZE() + { + } + + }; + } + #pragma pack(pop) +} + diff --git a/sources/Prism/defs/AssetRenderer.prism b/sources/Prism/defs/AssetRenderer.prism index a9fc89f16..7369c5a7a 100644 --- a/sources/Prism/defs/AssetRenderer.prism +++ b/sources/Prism/defs/AssetRenderer.prism @@ -32,6 +32,7 @@ namespace Editor [Always = RenderTarget] [Size = ViewportContext::frame_size] [Format = R8G8B8A8_UNORM] Texture GBuffer_Specular; [Always = RenderTarget] [Size = ViewportContext::frame_size] [Format = R16G16_FLOAT] Texture GBuffer_Speed; [Always = RenderTarget] [Size = ViewportContext::frame_size] [Format = R32_UINT] Texture GBuffer_ObjectID; + [Always = RenderTarget] [Size = ViewportContext::frame_size] [Format = R8G8B8A8_UNORM] Texture GBuffer_Material; [Always = UnorderedAccess | RenderTarget] [Size = ViewportContext::frame_size] [Format = R32_TYPELESS] Texture GBuffer_DepthMips; # Static: occlusion pass 1 tests against LAST frame's HiZ (see SceneSystem). # MipCount=0: full auto mip chain (a Hi-Z pyramid), matching the original diff --git a/sources/Prism/defs/framegraph_tests.prism b/sources/Prism/defs/framegraph_tests.prism index a3e45f366..4a0c4d741 100644 --- a/sources/Prism/defs/framegraph_tests.prism +++ b/sources/Prism/defs/framegraph_tests.prism @@ -333,11 +333,19 @@ namespace FrameGraphTests [Always = CopyDest] [Size = FGTestState::size] [Format = R8G8B8A8_UNORM] Texture GBuffer_Albedo; [Always = CopyDest] [Size = FGTestState::size] [Format = R8G8B8A8_UNORM] Texture GBuffer_Normals; [Always = CopyDest] [Size = FGTestState::size] [Format = R8G8B8A8_UNORM] Texture GBuffer_Specular; + [Always = CopyDest] [Size = FGTestState::size] [Format = R8G8B8A8_UNORM] Texture GBuffer_Material; [Always = CopyDest] [Size = FGTestState::size] [Format = R16G16_FLOAT] Texture GBuffer_Speed; [Always = CopyDest] [Size = FGTestState::size] [Format = R32_TYPELESS] Texture GBuffer_DepthMips; [Always = UnorderedAccess] [Size = FGTestState::size] [Format = R16G16B16A16_FLOAT] Texture ResultTexture; } + # SIGMA-packed shadow as NRD_ShadowCombine reads it (uploaded by the test). + [RunAlways] + PassNode FGT_ProduceShadow + { + [Always = CopyDest] [Size = FGTestState::size] [Format = R8_UNORM] Texture VSM_ShadowDenoised; + } + # ---- pipelines, one per scenario -------------------------------------------- Pipeline FGT_GraphicsPipeline @@ -479,6 +487,16 @@ namespace FrameGraphTests FGT_CopyResultToSink; } + # NRD_ShadowCombine over the same synthetic GBuffer, to compare against + # FGT_VSMUnshadowedPipeline: with a fully lit shadow both must light alike. + Pipeline FGT_NRDShadowCombinePipeline + { + FGT_ProduceGBuffer; + FGT_ProduceShadow; + NRD_ShadowCombine; + FGT_CopyResultToSink; + } + # VSM_DepthAnalysis is [Required]: nothing needs to consume its result. Pipeline FGT_VSMDepthAnalysisPipeline { diff --git a/sources/Prism/defs/nrd_sig_test.prism b/sources/Prism/defs/nrd_sig_test.prism index 928a36d61..f680608ed 100644 --- a/sources/Prism/defs/nrd_sig_test.prism +++ b/sources/Prism/defs/nrd_sig_test.prism @@ -1078,6 +1078,7 @@ namespace Denoise # comment. [Always = Read] Texture GBuffer_Albedo; [Always = Read] Texture GBuffer_Normals; + [Always = Read] Texture GBuffer_Material; [Always = Read] Texture GBuffer_Specular; [Always = Read] Texture GBuffer_Speed; [Always = None] Texture GBuffer_DepthMips; @@ -1120,6 +1121,7 @@ namespace Denoise [Always = Read] Texture GBuffer_Albedo; [Always = Read] Texture GBuffer_Normals; [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; [Always = Read] Texture GBuffer_Speed; [Always = None] Texture GBuffer_DepthMips; [Always = Read] Texture VSM_ShadowDenoised; diff --git a/sources/Prism/defs/pssm.prism b/sources/Prism/defs/pssm.prism index 038039a90..5bfd11552 100644 --- a/sources/Prism/defs/pssm.prism +++ b/sources/Prism/defs/pssm.prism @@ -234,6 +234,7 @@ namespace Shadows [Always = Read] Texture GBuffer_Albedo; [Always = Read] Texture GBuffer_Normals; [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; [Always = Read] Texture GBuffer_Speed; [Always = None] Texture GBuffer_DepthMips; [Always = Read] [Optional = !exists(ShadowMask)] Texture LightMask; diff --git a/sources/Prism/defs/raytracing.prism b/sources/Prism/defs/raytracing.prism index 46f78c69b..eac009d84 100644 --- a/sources/Prism/defs/raytracing.prism +++ b/sources/Prism/defs/raytracing.prism @@ -241,10 +241,13 @@ namespace Raytrace # uses front_face to know whether it is entering or leaving the volume. float3 normal; float3 albedo; + # Tint of transmitted light and colour the medium absorbs to over + # absorption_distance; the base colour when the graph leaves it unset. + float3 transmission_color; float roughness; - # Material graph outputs: refraction (IOR), thickness, transmission, - # absorption distance. + # Material graph outputs: ior, thickness (0 = thin-walled), transmission + # weight, transmission depth. float ior; float thickness; float transmission; diff --git a/sources/Prism/defs/scene.prism b/sources/Prism/defs/scene.prism index 7ddf06f24..44a2bdc80 100644 --- a/sources/Prism/defs/scene.prism +++ b/sources/Prism/defs/scene.prism @@ -26,6 +26,12 @@ namespace Meshes # read GBuffer_ObjectID directly. Only the render-target form uses it. [Auto = Texture_Null] RenderTarget object_id; + # Shading model (alpha) and its parameters (rgb), see common/shading_model.hlsl. + # [Auto] so passes that never sample it need not declare GBuffer_Material: + # SetTable() leaves it null for them. The lighting passes that do sample it + # (VSM_Combine/ShadowResolve/Unshadowed, PSSM_Combine) declare it [Read]. + [Auto = Texture_Null] + RenderTarget material; DepthStencil depth; } @@ -62,7 +68,7 @@ namespace Meshes [AS, nullable] define ClusterLod; - rtv ={ R8G8B8A8_UNORM, R8G8B8A8_UNORM, R8G8B8A8_UNORM, R16G16_FLOAT, R32_UINT }; + rtv ={ R8G8B8A8_UNORM, R8G8B8A8_UNORM, R8G8B8A8_UNORM, R16G16_FLOAT, R32_UINT, R8G8B8A8_UNORM }; ds = D32_FLOAT; cull = None; @@ -180,6 +186,7 @@ namespace Meshes [Always = RenderTarget] [Size = ViewportContext::frame_size] [Format = R8G8B8A8_UNORM] Texture GBuffer_Specular; [Always = RenderTarget] [Size = ViewportContext::frame_size] [Format = R16G16_FLOAT] Texture GBuffer_Speed; [Always = RenderTarget] [Size = ViewportContext::frame_size] [Format = R32_UINT] Texture GBuffer_ObjectID; + [Always = RenderTarget] [Size = ViewportContext::frame_size] [Format = R8G8B8A8_UNORM] Texture GBuffer_Material; [Always = UnorderedAccess | RenderTarget] [Size = ViewportContext::frame_size] [Format = R32_TYPELESS] Texture GBuffer_DepthMips; [Always = DepthStencil] [Size = ViewportContext::frame_size] [Format = D24_UNORM_S8_UINT] Texture GBuffer_Quality; [PrevFor = GBuffer_Normals] [Write] Texture GBuffer_NormalsPrev; diff --git a/sources/Prism/defs/sss.prism b/sources/Prism/defs/sss.prism new file mode 100644 index 000000000..120f7e30d --- /dev/null +++ b/sources/Prism/defs/sss.prism @@ -0,0 +1,162 @@ +# Screen-space subsurface scattering for the SUBSURFACE shading model +# (common/shading_model.hlsl): a separable bilateral Gaussian blur of the +# diffuse light, in screen space, applied only to pixels whose GBuffer_Material +# says SUBSURFACE. +# +# It has to run where the target holds diffuse light and nothing else, since +# blurring the final image would smear reflections and highlights too: +# - FSR/DLSS-SR/native: between NRD_IndirectCombine (the last diffuse writer) +# and ReflCombine (the first specular one), blurring ResultTexture in place. +# - DLSS-RR: RTXCombine computes everything in one dispatch, so for +# SUBSURFACE pixels it writes only diffuse into ResultTextureRTXNoise and +# parks the specular part in RTXCombineSpecular; the vertical pass adds it +# back after blurring. +# Horizontal and vertical halves are separate PassNodes: the vertical one +# reads what the horizontal one wrote (SSSTemp) and updates the target. +# +# SSS_Classify finds the 16x16 screen tiles that contain at least one SUBSURFACE +# pixel and appends their positions to SSSTiles; the blur passes then dispatch +# indirectly over just those tiles (same scheme as VSM's tile lists), so a frame +# with no subsurface material costs one cheap classify dispatch and nothing else. + +namespace Subsurface +{ + [Bind = DefaultLayout::Instance0] + struct SSSClassifyParams + { + GBuffer gbuffer; + } + + [Bind = DefaultLayout::Instance1] + struct SSSTilesAppend + { + AppendStructuredBuffer tiles; + } + + # Read-side counterpart of SSSTilesAppend: the same buffer as a plain list. + [Bind = DefaultLayout::Instance1] + struct SSSTilesRead + { + StructuredBuffer tiles; + } + + [Bind = DefaultLayout::Instance0] + struct SSSBlurParams + { + GBuffer gbuffer; + # H: the lit target (radiance, divided by albedo to get irradiance). + # V: SSSTemp (already irradiance). + Texture2D source; + # DLSS-RR only: RTXCombine's parked specular term, added back by V. + [Auto = Texture_Null] + Texture2D specular; + RWTexture2D target; + int add_specular; + } + + ComputePSO SSSClassify + { + root = DefaultLayout; + + [EntryPoint = CS_CLASSIFY] + compute = "postprocess/sss_classify.hlsl"; + } + + ComputePSO SSSBlurH + { + root = DefaultLayout; + + [EntryPoint = CS_H] + compute = "postprocess/sss_blur.hlsl"; + } + + ComputePSO SSSBlurV + { + root = DefaultLayout; + + [EntryPoint = CS_V] + compute = "postprocess/sss_blur.hlsl"; + } + + [Static] + [Compute] + [RunAlways] + PassNode SSS_Classify + { + [Always = Read] Texture GBuffer_Albedo; + [Always = Read] Texture GBuffer_Normals; + [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; + [Always = Read] Texture GBuffer_Speed; + [Always = None] Texture GBuffer_DepthMips; + # Worst case every tile qualifies. `true` = counted: an AppendStructuredBuffer + # needs a real GPU counter, which is also what the indirect args are copied from. + [Always = UnorderedAccess] [Size = area(tiles(ViewportContext::frame_size, 16))] [Counted] + StructuredBuffer SSSTiles; + } + + [Static] + [Compute] + [SetupCondition = UpscalerSelectors::upscaler_type != UpscalerType::DLSSRR] + PassNode SSS_BlurH + { + [Always = Read] Texture GBuffer_Albedo; + [Always = Read] Texture GBuffer_Normals; + [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; + [Always = Read] Texture GBuffer_Speed; + [Always = None] Texture GBuffer_DepthMips; + [Always = Read] StructuredBuffer SSSTiles; + [Always = Read] Texture ResultTexture; + [Always = UnorderedAccess] [Size = ViewportContext::frame_size] [Format = R16G16B16A16_FLOAT] Texture SSSTemp; + } + + [Static] + [Compute] + [SetupCondition = UpscalerSelectors::upscaler_type != UpscalerType::DLSSRR] + PassNode SSS_BlurV + { + [Always = Read] Texture GBuffer_Albedo; + [Always = Read] Texture GBuffer_Normals; + [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; + [Always = Read] Texture GBuffer_Speed; + [Always = None] Texture GBuffer_DepthMips; + [Always = Read] StructuredBuffer SSSTiles; + [Always = Read] Texture SSSTemp; + [Always = UnorderedAccess] Texture ResultTexture; + } + + [Static] + [Compute] + [SetupCondition = UpscalerSelectors::upscaler_type == UpscalerType::DLSSRR] + PassNode SSSRR_BlurH + { + [Always = Read] Texture GBuffer_Albedo; + [Always = Read] Texture GBuffer_Normals; + [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; + [Always = Read] Texture GBuffer_Speed; + [Always = None] Texture GBuffer_DepthMips; + [Always = Read] StructuredBuffer SSSTiles; + [Always = Read] Texture ResultTextureRTXNoise; + [Always = UnorderedAccess] [Size = ViewportContext::frame_size] [Format = R16G16B16A16_FLOAT] Texture SSSTemp; + } + + [Static] + [Compute] + [SetupCondition = UpscalerSelectors::upscaler_type == UpscalerType::DLSSRR] + PassNode SSSRR_BlurV + { + [Always = Read] Texture GBuffer_Albedo; + [Always = Read] Texture GBuffer_Normals; + [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; + [Always = Read] Texture GBuffer_Speed; + [Always = None] Texture GBuffer_DepthMips; + [Always = Read] StructuredBuffer SSSTiles; + [Always = Read] Texture SSSTemp; + [Always = Read] Texture RTXCombineSpecular; + [Always = UnorderedAccess] Texture ResultTextureRTXNoise; + } +} diff --git a/sources/Prism/defs/test.prism b/sources/Prism/defs/test.prism index e9d17de60..b11382a51 100644 --- a/sources/Prism/defs/test.prism +++ b/sources/Prism/defs/test.prism @@ -142,8 +142,15 @@ namespace Frame # for DLSS-RR -- see nrd_sig_test.prism's own PassNode comment and # [[project-nrd-integration]]. [Async]NRD_IndirectCombine; + # Subsurface blur: after the last diffuse writer, before the first + # specular one, so the target holds only diffuse light for it (sss.prism). + [Async]SSS_Classify; + [Async]SSS_BlurH; + [Async]SSS_BlurV; [Async]ReflCombine; [Async]RTXCombine; + [Async]SSSRR_BlurH; + [Async]SSSRR_BlurV; # RTX-reference shadow (see vsm.prism's own VSMSelectors::shadow_source # comment, [[project-nrd-integration]]) -- runs after VSM's own chain # above so its overwrite of ResultTexture's shadow term is the last diff --git a/sources/Prism/defs/voxel.prism b/sources/Prism/defs/voxel.prism index 5860d17da..641a4a812 100644 --- a/sources/Prism/defs/voxel.prism +++ b/sources/Prism/defs/voxel.prism @@ -76,6 +76,12 @@ namespace GI [Auto = Texture_Null] RWTexture2D noise; + # Clearcoat reflection (RGB radiance, A hit distance), written only by + # ReflectionRTX's full-res raygen for CLEARCOAT pixels -- a second ray at the + # coat's own roughness. Not a denoiser input; added by the combine passes. + [Auto = Texture_Null] + RWTexture2D coat; + [Auto = Texture_Null] RWTexture2D DirAndPdf; [Auto = Texture_Null] @@ -349,6 +355,7 @@ namespace Reflections { GBuffer gbuffer; Texture2D reflection; + Texture2D coat; RWTexture2D target; } @@ -379,6 +386,10 @@ namespace GI { GBuffer gbuffer; Texture2D reflection; + Texture2D coat; + # SUBSURFACE pixels only: the specular part, parked for the SSS blur to add + # back after it has blurred the diffuse part (see sss.prism). + RWTexture2D specular_out; Texture2D indirect; Texture2D shadow; @@ -442,6 +453,7 @@ namespace Meshes # actually reads it). [Always = Read] Texture GBuffer_Albedo; [Always = Read] Texture GBuffer_Normals; + [Always = Read] Texture GBuffer_Material; [Always = Read] Texture GBuffer_Specular; [Always = Read] Texture GBuffer_Speed; [Always = None] Texture GBuffer_DepthMips; @@ -583,6 +595,9 @@ namespace Reflections [Always = UnorderedAccess] [Size = ViewportContext::frame_size] [Format = R16G16B16A16_FLOAT] Texture RTXReflectionNoise; [Always = UnorderedAccess] [Size = ViewportContext::frame_size] [Format = R16G16B16A16_FLOAT] Texture RTXReflectionDirPdf; + # Clearcoat reflection, see VoxelOutput::coat. + [Always = Read] Texture GBuffer_Material; + [Always = UnorderedAccess] [Size = ViewportContext::frame_size] [Format = R16G16B16A16_FLOAT] Texture RTXReflectionCoat; } } @@ -729,6 +744,9 @@ namespace Reflections # NRD REBLUR_SPECULAR's denoised output (NRD_REBLUR_Execute, see # [[project-nrd-integration]]) -- the only reflection denoiser now. [Always = Read] Texture RTXReflectionDenoised; + # Clearcoat reflection, added undenoised (a near-mirror ray barely needs it). + [Always = Read] Texture GBuffer_Material; + [Always = Read] Texture RTXReflectionCoat; } } @@ -765,8 +783,12 @@ namespace GI [Always = Read] Texture RTXReflectionNoise; [Always = Read] Texture RTXIndirectNoise; [Always = Read] Texture RTXShadowNoise; + # Clearcoat: sun highlight and reflection, see common/coat.hlsl. + [Always = Read] Texture GBuffer_Material; + [Always = Read] Texture RTXReflectionCoat; [Always = UnorderedAccess] [Size = ViewportContext::frame_size] [Format = R16G16B16A16_FLOAT] Texture ResultTextureRTXNoise; + [Always = UnorderedAccess] [Size = ViewportContext::frame_size] [Format = R16G16B16A16_FLOAT] Texture RTXCombineSpecular; } namespace Voxel diff --git a/sources/Prism/defs/vsm.prism b/sources/Prism/defs/vsm.prism index 1c4852d9d..fb5a8d3d9 100644 --- a/sources/Prism/defs/vsm.prism +++ b/sources/Prism/defs/vsm.prism @@ -1157,6 +1157,7 @@ namespace Shadows [Always = Read] Texture GBuffer_Albedo; [Always = Read] Texture GBuffer_Normals; [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; [Always = Read] Texture GBuffer_Speed; [Always = None] Texture GBuffer_DepthMips; [Always = Read] Texture VSM_Atlas; @@ -1215,6 +1216,7 @@ namespace Shadows [Always = Read] Texture GBuffer_Albedo; [Always = Read] Texture GBuffer_Normals; [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; [Always = Read] Texture GBuffer_Speed; [Always = None] Texture GBuffer_DepthMips; [Always = Read] Texture VSM_Atlas; @@ -1242,6 +1244,7 @@ namespace Shadows [Always = Read] Texture GBuffer_Albedo; [Always = Read] Texture GBuffer_Normals; [Always = Read] Texture GBuffer_Specular; + [Always = Read] Texture GBuffer_Material; [Always = Read] Texture GBuffer_Speed; [Always = None] Texture GBuffer_DepthMips; [Always = UnorderedAccess] Texture ResultTexture; diff --git a/sources/RenderSystem/Assets/AssetExplorer.cpp b/sources/RenderSystem/Assets/AssetExplorer.cpp index a10f57683..81666fa26 100644 --- a/sources/RenderSystem/Assets/AssetExplorer.cpp +++ b/sources/RenderSystem/Assets/AssetExplorer.cpp @@ -1,5 +1,6 @@ module Graphics:AssetExplorer; import :Materials.UniversalMaterial; +import :Materials.MaterialX; import :Materials.PreviewSession; import :MeshAsset; import :BinaryAsset; @@ -319,7 +320,7 @@ namespace GUI menu->add_item("Import asset")->on_click = [this](menu_list_element::ptr e) { - auto files = Application::get().file_open("Choose your destiny", "", "dds|*.dds|" "png|*.png|" "jpg|*.jpg|" "obj|*.obj|" "fbx|*.fbx|" "glb|*.glb|" "gltf|*.gltf|" "blend|*.blend|" "all|*.*|" "|"); + auto files = Application::get().file_open("Choose your destiny", "", "dds|*.dds|" "png|*.png|" "jpg|*.jpg|" "obj|*.obj|" "fbx|*.fbx|" "glb|*.glb|" "gltf|*.gltf|" "blend|*.blend|" "mtlx|*.mtlx|" "all|*.*|" "|"); struct material_info { @@ -386,6 +387,14 @@ namespace GUI for (auto s : files) { + // Not lower-cased like the rest: the importer resolves the + // textures the file names relative to this path. + if (to_lower(s.substr(s.find_last_of(".") + 1)) == "mtlx") + { + MaterialXLibrary::get().import_file(std::filesystem::path(s)); + continue; + } + s = to_lower(s); std::string ext = to_lower(s.substr(s.find_last_of(".") + 1)); diff --git a/sources/RenderSystem/Assets/AssetRenderer.cpp b/sources/RenderSystem/Assets/AssetRenderer.cpp index 7e97fdae2..71fe069db 100644 --- a/sources/RenderSystem/Assets/AssetRenderer.cpp +++ b/sources/RenderSystem/Assets/AssetRenderer.cpp @@ -68,6 +68,7 @@ class SceneRenderWorkflow rtv.GetSpecular() = gbuffer.specular.renderTarget; rtv.GetMotion() = gbuffer.speed.renderTarget; rtv.GetObject_Id() = gbuffer.object_id.renderTarget; + rtv.GetMaterial() = gbuffer.material.renderTarget; rtv.GetDepth() = gbuffer.depth.depthStencil; gbuffer.compiled = rtv.compile(*command_list); } diff --git a/sources/RenderSystem/Context/Context.ixx b/sources/RenderSystem/Context/Context.ixx index 6636f497c..a13904143 100644 --- a/sources/RenderSystem/Context/Context.ixx +++ b/sources/RenderSystem/Context/Context.ixx @@ -123,6 +123,7 @@ export{ HAL::Texture2DView specular; HAL::Texture2DView speed; HAL::Texture2DView object_id; + HAL::Texture2DView material; HAL::Texture2DView quality; @@ -172,6 +173,12 @@ export{ context.GBuffer_Quality; }; + template + concept Has_GBuffer_Material = requires(T& context) + { + context.GBuffer_Material; + }; + template concept Has_GBuffer_ObjectID = requires(T& context) { @@ -187,6 +194,7 @@ export{ Handlers::Texture GBuffer_Specular = FrameGraph::ResourceID::GBuffer_Specular; Handlers::Texture GBuffer_Speed = FrameGraph::ResourceID::GBuffer_Speed; Handlers::Texture GBuffer_ObjectID = FrameGraph::ResourceID::GBuffer_ObjectID; + Handlers::Texture GBuffer_Material = FrameGraph::ResourceID::GBuffer_Material; Handlers::Texture GBuffer_DepthMips = FrameGraph::ResourceID::GBuffer_DepthMips; Handlers::Texture GBuffer_DepthPrev = FrameGraph::ResourceID::GBuffer_DepthPrev; @@ -223,6 +231,7 @@ export{ if constexpr(Has_GBuffer_Quality) if (context.GBuffer_Quality) result.quality = *context.GBuffer_Quality; if constexpr(Has_GBuffer_DepthMips) if (context.GBuffer_DepthMips) result.depth_mips = *context.GBuffer_DepthMips; if constexpr(Has_GBuffer_ObjectID) if (context.GBuffer_ObjectID) result.object_id = *context.GBuffer_ObjectID; + if constexpr(Has_GBuffer_Material) if (context.GBuffer_Material) result.material = *context.GBuffer_Material; diff --git a/sources/RenderSystem/Context/Context2.cpp b/sources/RenderSystem/Context/Context2.cpp index bd7922a35..c8df35b74 100644 --- a/sources/RenderSystem/Context/Context2.cpp +++ b/sources/RenderSystem/Context/Context2.cpp @@ -19,4 +19,7 @@ void GBuffer::SetTable(Table::Meshes::GBuffer& table) table.GetSpecular() = specular.texture2D; table.GetDepth() = depth_mips.texture2D; table.GetMotion() = speed.texture2D; + // Null in passes that don't declare GBuffer_Material; only the lighting + // passes that sample it (VSM, PSSM) do. + table.GetMaterial() = material.texture2D; } diff --git a/sources/RenderSystem/Effects/Subsurface/SSS.cpp b/sources/RenderSystem/Effects/Subsurface/SSS.cpp new file mode 100644 index 000000000..938e61583 --- /dev/null +++ b/sources/RenderSystem/Effects/Subsurface/SSS.cpp @@ -0,0 +1,144 @@ +module Graphics:SSS; + +import RenderSystem; +import Graphics; +import HAL; +import Core; + +#include "../../FrameGraph/autogen/pass_defaults.h" + +using namespace FrameGraph; +using namespace HAL; + +// setup() is fully generated (sss.prism's own [SetupCondition]s). SSS_Classify +// lists the 16x16 tiles holding a SUBSURFACE pixel; the four blur passes share +// one shape and dispatch indirectly over that list: horizontal reads the lit +// target and writes SSSTemp, vertical reads SSSTemp and updates the target; the +// DLSS-RR pair differs only in which texture is the target and in adding +// RTXCombine's parked specular. + +namespace +{ + // Indirect-dispatch arguments for the tile list: {tile count, 1, 1}. The + // classify pass copies the list's own GPU counter into the first word; the + // blur passes (later passes, so Graph's single shared per-queue args buffer + // would be overwritten in between) only read it. Created on first use, + // owned by the graph, the same way VSM owns its tile-list args. + struct SSSTileArgs + { + HAL::StructuredBufferView args; + }; + + HAL::StructuredBufferView& tile_args(FrameContext& context) + { + auto& holder = context.graph->get_context(); + + if (!holder.args.resource) + { + auto& device = RenderSystem::get().device(); + holder.args = HAL::StructuredBufferView(device, 1); + + DispatchArguments init{ 0, 1, 1 }; + auto upload = device.get_upload_list(); + upload->get_copy().update(holder.args, 0, std::span{ &init, 1 }); + upload->execute_and_wait(); + } + + return holder.args; + } + + template + void dispatch_blur(Data& data, FrameContext& context, bool vertical, HAL::Texture2DView source, HAL::Texture2DView target, + const HAL::Texture2DView* specular) + { + auto& command_list = context.get_list(); + GBuffer gbuffer = GBufferViewDesc::actualize(data); + auto& compute = command_list->get_compute(); + + context.graph->set_slot(SlotID::FrameInfo, compute); + + if (vertical) + compute.set_pipeline(); + else + compute.set_pipeline(); + + { + Slots::Subsurface::SSSBlurParams params; + gbuffer.SetTable(params.GetGbuffer()); + params.GetSource() = source.texture2D; + params.GetTarget() = target.rwTexture2D; + + if (specular) + { + params.GetSpecular() = specular->texture2D; + params.GetAdd_specular() = 1; + } + + compute.set(params); + } + + { + Slots::Subsurface::SSSTilesRead tiles; + tiles.GetTiles() = data.SSSTiles->structuredBuffer; + compute.set(tiles); + } + + compute.exec_indirect(tile_args(context), 1); + } +} + +void PassDefault::render(Passes::Subsurface::SSS_Classify::Context& data, FrameContext& context) +{ + PROFILE_GPU(L"sss_classify"); + + auto& command_list = context.get_list(); + GBuffer gbuffer = GBufferViewDesc::actualize(data); + auto& compute = command_list->get_compute(); + auto& args = tile_args(context); + + { + Slots::Subsurface::SSSClassifyParams params; + gbuffer.SetTable(params.GetGbuffer()); + compute.set(params); + } + + { + Slots::Subsurface::SSSTilesAppend tiles; + tiles.GetTiles() = data.SSSTiles->appendStructuredBuffer; + compute.set(tiles); + } + + compute.clear_counter(*data.SSSTiles); + compute.set_pipeline(); + compute.dispatch(gbuffer.albedo.get_size(), ivec2{ 16, 16 }); + + // Same render() as the append above, the counter-to-args copy VSM's tile + // lists use: no shader needed to turn the count into dispatch arguments. + command_list->get_copy().copy_buffer(args.resource.get(), 0, + data.SSSTiles->get_counter_buffer().get(), data.SSSTiles->get_counter_offset(), 4); +} + +void PassDefault::render(Passes::Subsurface::SSS_BlurH::Context& data, FrameContext& context) +{ + PROFILE_GPU(L"sss_blur_h"); + dispatch_blur(data, context, false, *data.ResultTexture, *data.SSSTemp, nullptr); +} + +void PassDefault::render(Passes::Subsurface::SSS_BlurV::Context& data, FrameContext& context) +{ + PROFILE_GPU(L"sss_blur_v"); + dispatch_blur(data, context, true, *data.SSSTemp, *data.ResultTexture, nullptr); +} + +void PassDefault::render(Passes::Subsurface::SSSRR_BlurH::Context& data, FrameContext& context) +{ + PROFILE_GPU(L"sss_rr_blur_h"); + dispatch_blur(data, context, false, *data.ResultTextureRTXNoise, *data.SSSTemp, nullptr); +} + +void PassDefault::render(Passes::Subsurface::SSSRR_BlurV::Context& data, FrameContext& context) +{ + PROFILE_GPU(L"sss_rr_blur_v"); + HAL::Texture2DView specular = *data.RTXCombineSpecular; + dispatch_blur(data, context, true, *data.SSSTemp, *data.ResultTextureRTXNoise, &specular); +} diff --git a/sources/RenderSystem/Effects/Subsurface/SSS.ixx b/sources/RenderSystem/Effects/Subsurface/SSS.ixx new file mode 100644 index 000000000..9a923c2c2 --- /dev/null +++ b/sources/RenderSystem/Effects/Subsurface/SSS.ixx @@ -0,0 +1,5 @@ +// Screen-space subsurface scattering blur passes -- no exports of their own, see +// sss.prism for what they do and where they sit in the pipeline. +export module Graphics:SSS; + +import HAL; diff --git a/sources/RenderSystem/Effects/VoxelGI/RTXCombine.cpp b/sources/RenderSystem/Effects/VoxelGI/RTXCombine.cpp index b5133a440..be34ad055 100644 --- a/sources/RenderSystem/Effects/VoxelGI/RTXCombine.cpp +++ b/sources/RenderSystem/Effects/VoxelGI/RTXCombine.cpp @@ -34,6 +34,8 @@ void PassDefault::render( Slots::GI::RTXCombine combine; gbuffer.SetTable(combine.GetGbuffer()); combine.GetReflection() = data.RTXReflectionNoise->texture2D; + combine.GetCoat() = data.RTXReflectionCoat->texture2D; + combine.GetSpecular_out() = data.RTXCombineSpecular->rwTexture2D; combine.GetIndirect() = data.RTXIndirectNoise->texture2D; combine.GetShadow() = data.RTXShadowNoise->texture2D; combine.GetTarget() = data.ResultTextureRTXNoise->rwTexture2D; diff --git a/sources/RenderSystem/Effects/VoxelGI/ReflectionRTX.cpp b/sources/RenderSystem/Effects/VoxelGI/ReflectionRTX.cpp index ba4daf8a6..c3c6ae2b9 100644 --- a/sources/RenderSystem/Effects/VoxelGI/ReflectionRTX.cpp +++ b/sources/RenderSystem/Effects/VoxelGI/ReflectionRTX.cpp @@ -106,6 +106,7 @@ void PassDefault::render( Slots::GI::Voxel::VoxelOutput output; output.GetNoise() = noisy_output.rwTexture2D; output.GetDirAndPdf() = dir_and_pdf.rwTexture2D; + output.GetCoat() = data.RTXReflectionCoat->rwTexture2D; output.GetBlueNoise() = data.BlueNoise->texture2D; // See ReflectionRTXHalf's own comment on the same binding block. { diff --git a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp index 04830cbcc..31632778b 100644 --- a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp +++ b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp @@ -604,6 +604,7 @@ VoxelGI::VoxelGI(Scene::ptr& scene, VSM& vsm) :scene(scene), vsm(vsm), VariableC Slots::Reflections::ReflectionCombine combine; gbuffer.SetTable(combine.GetGbuffer()); combine.GetReflection() = data.RTXReflectionDenoised->texture2D; + combine.GetCoat() = data.RTXReflectionCoat->texture2D; combine.GetTarget() = data.ResultTexture->rwTexture2D; compute.set(combine); } diff --git a/sources/RenderSystem/FrameGraph/autogen/context_deps.h b/sources/RenderSystem/FrameGraph/autogen/context_deps.h index 407682626..77555ccb0 100644 --- a/sources/RenderSystem/FrameGraph/autogen/context_deps.h +++ b/sources/RenderSystem/FrameGraph/autogen/context_deps.h @@ -35,7 +35,7 @@ namespace FrameGraph // .prism edit renumbers those enums, a plan from before the edit would not fail // to load, it would misapply -- wrong resource, wrong barriers. A plan whose // header does not carry this exact value must be rejected. -constexpr unsigned long long generated_id_space_hash = 2794941794314498384ull; +constexpr unsigned long long generated_id_space_hash = 10368715043874216170ull; // One bit per context field, in (struct, field) declaration order. enum class ContextFieldID : unsigned int @@ -399,6 +399,10 @@ static inline const PassContextDeps pass_context_deps[] = { ContextField::None, ContextField::None, true }, + { PassID::FGT_ProduceShadow, + ContextField::None, + ContextField::None, + true }, { PassID::FGT_AppendTiles, ContextField::None, ContextField::None, @@ -542,6 +546,26 @@ static inline const PassContextDeps pass_context_deps[] = { ContextField::UpscalerSelectors_upscaling_enabled, ContextField::None, true }, + { PassID::SSS_Classify, + ContextField::None, + ContextField::None, + true }, + { PassID::SSS_BlurH, + ContextField::UpscalerSelectors_upscaler_type, + ContextField::None, + true }, + { PassID::SSS_BlurV, + ContextField::UpscalerSelectors_upscaler_type, + ContextField::None, + true }, + { PassID::SSSRR_BlurH, + ContextField::UpscalerSelectors_upscaler_type, + ContextField::None, + true }, + { PassID::SSSRR_BlurV, + ContextField::UpscalerSelectors_upscaler_type, + ContextField::None, + true }, { PassID::stencil_renderer, ContextField::None, ContextField::None, diff --git a/sources/RenderSystem/FrameGraph/autogen/enums.h b/sources/RenderSystem/FrameGraph/autogen/enums.h index 126344f15..27b6ba7ae 100644 --- a/sources/RenderSystem/FrameGraph/autogen/enums.h +++ b/sources/RenderSystem/FrameGraph/autogen/enums.h @@ -76,6 +76,7 @@ export Library::FGT_CopyResultToSink FGT_CopyResultToSink; Library::FGT_ProduceBloomInputs FGT_ProduceBloomInputs; Library::FGT_ProduceGBuffer FGT_ProduceGBuffer; + Library::FGT_ProduceShadow FGT_ProduceShadow; Library::FGT_AppendTiles FGT_AppendTiles; Library::FGT_CopyTileList FGT_CopyTileList; Library::FGT_CaptureAlbedo FGT_CaptureAlbedo; @@ -109,6 +110,11 @@ export Library::CubeMapDownsample CubeMapDownsample; Library::CubeMapEnviromentProcessor CubeMapEnviromentProcessor; Library::SMAA SMAA; + Library::SSS_Classify SSS_Classify; + Library::SSS_BlurH SSS_BlurH; + Library::SSS_BlurV SSS_BlurV; + Library::SSSRR_BlurH SSSRR_BlurH; + Library::SSSRR_BlurV SSSRR_BlurV; Library::stencil_renderer stencil_renderer; Library::Tonemap Tonemap; Library::UI_PreDraw UI_PreDraw; diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Denoise/NRD/NRD_IndirectCombine.h b/sources/RenderSystem/FrameGraph/autogen/pass/Denoise/NRD/NRD_IndirectCombine.h index db8e82e55..471c56e7e 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Denoise/NRD/NRD_IndirectCombine.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Denoise/NRD/NRD_IndirectCombine.h @@ -29,6 +29,9 @@ namespace Denoise Handlers::Texture GBuffer_Normals = ResourceID::GBuffer_Normals; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; @@ -62,6 +65,7 @@ namespace Denoise { builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); @@ -77,6 +81,7 @@ namespace Denoise { FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; @@ -88,6 +93,7 @@ namespace Denoise { cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); @@ -106,6 +112,7 @@ namespace Denoise { builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); @@ -119,6 +126,7 @@ namespace Denoise static inline const FrameGraph::ResourceAccess resource_accesses[] = { { ResourceID::GBuffer_Albedo, false }, { ResourceID::GBuffer_Normals, false }, + { ResourceID::GBuffer_Material, false }, { ResourceID::GBuffer_Specular, false }, { ResourceID::GBuffer_Speed, false }, { ResourceID::GBuffer_DepthMips, false }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Denoise/NRD/NRD_ShadowCombine.h b/sources/RenderSystem/FrameGraph/autogen/pass/Denoise/NRD/NRD_ShadowCombine.h index 6227ba0ba..5eaf7a6ff 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Denoise/NRD/NRD_ShadowCombine.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Denoise/NRD/NRD_ShadowCombine.h @@ -32,6 +32,9 @@ namespace Denoise Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; @@ -63,6 +66,7 @@ namespace Denoise builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); builder.need(data.VSM_ShadowDenoised, FrameGraph::ResourceFlags::Read); @@ -78,6 +82,7 @@ namespace Denoise FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex VSM_ShadowDenoised = FrameGraph::ChainIndex::Unresolved; @@ -89,6 +94,7 @@ namespace Denoise cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.VSM_ShadowDenoised = FrameGraph::TaskBuilder::cache_slot(data.VSM_ShadowDenoised, ResourceID::VSM_ShadowDenoised); @@ -107,6 +113,7 @@ namespace Denoise builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); builder.load(data.VSM_ShadowDenoised, ResourceID::VSM_ShadowDenoised, cache.VSM_ShadowDenoised); @@ -120,6 +127,7 @@ namespace Denoise { ResourceID::GBuffer_Albedo, false }, { ResourceID::GBuffer_Normals, false }, { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, { ResourceID::GBuffer_Speed, false }, { ResourceID::GBuffer_DepthMips, false }, { ResourceID::VSM_ShadowDenoised, false }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Editor/AssetGBuffer.h b/sources/RenderSystem/FrameGraph/autogen/pass/Editor/AssetGBuffer.h index 36068b109..c8efc1650 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Editor/AssetGBuffer.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Editor/AssetGBuffer.h @@ -39,6 +39,9 @@ namespace Editor Handlers::Texture GBuffer_ObjectID = ResourceID::GBuffer_ObjectID; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_DepthMips = ResourceID::GBuffer_DepthMips; @@ -91,6 +94,7 @@ namespace Editor builder.create(data.GBuffer_Specular, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::RenderTarget); builder.create(data.GBuffer_Speed, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::RenderTarget); builder.create(data.GBuffer_ObjectID, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R32_UINT, 1, 1 }, FrameGraph::ResourceFlags::RenderTarget); + builder.create(data.GBuffer_Material, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::RenderTarget); builder.create(data.GBuffer_DepthMips, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R32_TYPELESS, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess | FrameGraph::ResourceFlags::RenderTarget); builder.create(data.GBuffer_HiZ, { ivec3(builder.graph->get_context().frame_size / 8, 0), HAL::Format::R32_TYPELESS, 1, 0 }, FrameGraph::ResourceFlags::DepthStencil | FrameGraph::ResourceFlags::Static); builder.create(data.GBuffer_HiZ_UAV, { ivec3(builder.graph->get_context().frame_size / 8, 0), HAL::Format::R32_FLOAT, 1, 0 }, FrameGraph::ResourceFlags::UnorderedAccess); @@ -108,6 +112,7 @@ namespace Editor FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_ObjectID = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_HiZ = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_HiZ_UAV = FrameGraph::ChainIndex::Unresolved; @@ -122,6 +127,7 @@ namespace Editor cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_ObjectID = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_ObjectID, ResourceID::GBuffer_ObjectID); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.GBuffer_HiZ = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_HiZ, ResourceID::GBuffer_HiZ); cache.GBuffer_HiZ_UAV = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_HiZ_UAV, ResourceID::GBuffer_HiZ_UAV); @@ -143,6 +149,7 @@ namespace Editor builder.create_versioned(data.GBuffer_Specular, cache.GBuffer_Specular, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }); builder.create_versioned(data.GBuffer_Speed, cache.GBuffer_Speed, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16_FLOAT, 1, 1 }); builder.create_versioned(data.GBuffer_ObjectID, cache.GBuffer_ObjectID, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R32_UINT, 1, 1 }); + builder.create_versioned(data.GBuffer_Material, cache.GBuffer_Material, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }); builder.create_versioned(data.GBuffer_DepthMips, cache.GBuffer_DepthMips, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R32_TYPELESS, 1, 1 }); builder.create_versioned(data.GBuffer_HiZ, cache.GBuffer_HiZ, { ivec3(builder.graph->get_context().frame_size / 8, 0), HAL::Format::R32_TYPELESS, 1, 0 }); builder.create_versioned(data.GBuffer_HiZ_UAV, cache.GBuffer_HiZ_UAV, { ivec3(builder.graph->get_context().frame_size / 8, 0), HAL::Format::R32_FLOAT, 1, 0 }); @@ -159,6 +166,7 @@ namespace Editor { ResourceID::GBuffer_Specular, true }, { ResourceID::GBuffer_Speed, true }, { ResourceID::GBuffer_ObjectID, true }, + { ResourceID::GBuffer_Material, true }, { ResourceID::GBuffer_DepthMips, true }, { ResourceID::GBuffer_HiZ, true }, { ResourceID::GBuffer_HiZ_UAV, true }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Editor/AssetPipeline.pipeline.h b/sources/RenderSystem/FrameGraph/autogen/pass/Editor/AssetPipeline.pipeline.h index cc2f56e09..13bff460d 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Editor/AssetPipeline.pipeline.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Editor/AssetPipeline.pipeline.h @@ -84,6 +84,7 @@ namespace Editor L"GBuffer_Specular", L"GBuffer_Speed", L"GBuffer_ObjectID", + L"GBuffer_Material", L"GBuffer_DepthMips", L"GBuffer_HiZ", L"GBuffer_HiZ_UAV", @@ -189,6 +190,14 @@ namespace Editor static inline const FrameGraph::PrecompiledState GBuffer_ObjectID_c0_states[] = { { true, { GBuffer_ObjectID_c0_pass_refs + 0, 1 } }, }; + static inline const FrameGraph::PassRef GBuffer_Material_c0_pass_refs[] = { + { PassID::AssetGBuffer, 0 }, + { PassID::PSSM_Combine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Material_c0_states[] = { + { true, { GBuffer_Material_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Material_c0_pass_refs + 1, 1 } }, + }; static inline const FrameGraph::PassRef GBuffer_DepthMips_c0_pass_refs[] = { { PassID::AssetGBuffer, 0 }, { PassID::PSSM_GenerateMask, 0 }, @@ -359,6 +368,7 @@ namespace Editor { ResourceID::GBuffer_Specular, 0, GBuffer_Specular_c0_states }, { ResourceID::GBuffer_Speed, 0, GBuffer_Speed_c0_states }, { ResourceID::GBuffer_ObjectID, 0, GBuffer_ObjectID_c0_states }, + { ResourceID::GBuffer_Material, 0, GBuffer_Material_c0_states }, { ResourceID::GBuffer_DepthMips, 0, GBuffer_DepthMips_c0_states }, { ResourceID::GBuffer_HiZ, 0, GBuffer_HiZ_c0_states }, { ResourceID::GBuffer_HiZ_UAV, 0, GBuffer_HiZ_UAV_c0_states }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Frame/MainPipeline.pipeline.h b/sources/RenderSystem/FrameGraph/autogen/pass/Frame/MainPipeline.pipeline.h index a8765fd8b..4776fc24d 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Frame/MainPipeline.pipeline.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Frame/MainPipeline.pipeline.h @@ -45,8 +45,13 @@ #include "../Shadows/VSM/Dev/VSM_DebugClassifyOverlay.h" #include "../Denoise/NRD/NRD_REBLUR_Execute.h" #include "../Denoise/NRD/NRD_IndirectCombine.h" +#include "../Subsurface/SSS_Classify.h" +#include "../Subsurface/SSS_BlurH.h" +#include "../Subsurface/SSS_BlurV.h" #include "../Reflections/ReflCombine.h" #include "../GI/RTXCombine.h" +#include "../Subsurface/SSSRR_BlurH.h" +#include "../Subsurface/SSSRR_BlurV.h" #include "../Denoise/NRD/NRD_SIGMA_Execute.h" #include "../Denoise/NRD/NRD_ShadowCombine.h" #include "../GI/DDGI/Dev/DDGIDebug.h" @@ -169,8 +174,13 @@ namespace Frame Passes::Shadows::VSM::Dev::VSM_DebugClassifyOverlay::Name.ptr, Passes::Denoise::NRD::NRD_REBLUR_Execute::Name.ptr, Passes::Denoise::NRD::NRD_IndirectCombine::Name.ptr, + Passes::Subsurface::SSS_Classify::Name.ptr, + Passes::Subsurface::SSS_BlurH::Name.ptr, + Passes::Subsurface::SSS_BlurV::Name.ptr, Passes::Reflections::ReflCombine::Name.ptr, Passes::GI::RTXCombine::Name.ptr, + Passes::Subsurface::SSSRR_BlurH::Name.ptr, + Passes::Subsurface::SSSRR_BlurV::Name.ptr, Passes::Denoise::NRD::NRD_SIGMA_Execute::Name.ptr, Passes::Denoise::NRD::NRD_ShadowCombine::Name.ptr, Passes::GI::DDGI::Dev::DDGIDebug::Name.ptr, @@ -218,6 +228,7 @@ namespace Frame L"GBuffer_Specular", L"GBuffer_Speed", L"GBuffer_ObjectID", + L"GBuffer_Material", L"GBuffer_DepthMips", L"GBuffer_Quality", L"GBuffer_NormalsPrev", @@ -256,6 +267,7 @@ namespace Frame L"RTXReflectionDirPdfHalf", L"RTXReflectionNoise", L"RTXReflectionDirPdf", + L"RTXReflectionCoat", L"RTXIndirectNoiseHalf", L"RTXIndirectNoise", L"DDGI_ProbeRayRadiance", @@ -283,7 +295,10 @@ namespace Frame L"RTXIndirectDenoised", L"RTXIndirectDenoisedPreview", L"RTXReflectionDenoised", + L"SSSTiles", + L"SSSTemp", L"ResultTextureRTXNoise", + L"RTXCombineSpecular", L"VSM_ShadowDenoised", L"ResultTextureNew", L"ExposureState", @@ -495,15 +510,20 @@ namespace Frame { PassID::VSM_Unshadowed, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, { PassID::NRD_IndirectCombine, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::ReflCombine, 0 }, { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, { PassID::NRD_ShadowCombine, 0 }, { PassID::UpscalingDLSSRR, 0 }, { PassID::VoxelDebug, 0 }, }; static inline const FrameGraph::PrecompiledState GBuffer_Albedo_c0_states[] = { { true, { GBuffer_Albedo_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Albedo_c0_pass_refs + 1, 23 } }, + { false, { GBuffer_Albedo_c0_pass_refs + 1, 28 } }, }; static inline const FrameGraph::PassRef GBuffer_Normals_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -526,14 +546,19 @@ namespace Frame { PassID::VSM_Unshadowed, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, { PassID::NRD_IndirectCombine, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::ReflCombine, 0 }, { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, { PassID::NRD_ShadowCombine, 0 }, { PassID::VoxelDebug, 0 }, }; static inline const FrameGraph::PrecompiledState GBuffer_Normals_c0_states[] = { { true, { GBuffer_Normals_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Normals_c0_pass_refs + 1, 23 } }, + { false, { GBuffer_Normals_c0_pass_refs + 1, 28 } }, }; static inline const FrameGraph::PassRef GBuffer_Depth_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -567,14 +592,19 @@ namespace Frame { PassID::VSM_Unshadowed, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, { PassID::NRD_IndirectCombine, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::ReflCombine, 0 }, { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, { PassID::NRD_ShadowCombine, 0 }, { PassID::VoxelDebug, 0 }, }; static inline const FrameGraph::PrecompiledState GBuffer_Specular_c0_states[] = { { true, { GBuffer_Specular_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Specular_c0_pass_refs + 1, 21 } }, + { false, { GBuffer_Specular_c0_pass_refs + 1, 26 } }, }; static inline const FrameGraph::PassRef GBuffer_Speed_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -595,8 +625,13 @@ namespace Frame { PassID::VSM_Unshadowed, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, { PassID::NRD_IndirectCombine, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::ReflCombine, 0 }, { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, { PassID::NRD_ShadowCombine, 0 }, { PassID::BloomBuild, 0 }, { PassID::UpscalingDLSS, 0 }, @@ -605,7 +640,7 @@ namespace Frame }; static inline const FrameGraph::PrecompiledState GBuffer_Speed_c0_states[] = { { true, { GBuffer_Speed_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Speed_c0_pass_refs + 1, 24 } }, + { false, { GBuffer_Speed_c0_pass_refs + 1, 29 } }, }; static inline const FrameGraph::PassRef GBuffer_ObjectID_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -615,6 +650,27 @@ namespace Frame { true, { GBuffer_ObjectID_c0_pass_refs + 0, 1 } }, { false, { GBuffer_ObjectID_c0_pass_refs + 1, 1 } }, }; + static inline const FrameGraph::PassRef GBuffer_Material_c0_pass_refs[] = { + { PassID::Scene, 0 }, + { PassID::GBufferDownsampler, 0 }, + { PassID::ReflectionRTX, 0 }, + { PassID::VSM_ShadowResolve, 0 }, + { PassID::VSM_Combine, 0 }, + { PassID::VSM_Unshadowed, 0 }, + { PassID::NRD_IndirectCombine, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, + { PassID::ReflCombine, 0 }, + { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Material_c0_states[] = { + { true, { GBuffer_Material_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Material_c0_pass_refs + 1, 14 } }, + }; static inline const FrameGraph::PassRef GBuffer_DepthMips_c0_pass_refs[] = { { PassID::Scene, 0 }, { PassID::GBufferDownsampler, 0 }, @@ -635,15 +691,20 @@ namespace Frame { PassID::VSM_Unshadowed, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, { PassID::NRD_IndirectCombine, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::ReflCombine, 0 }, { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, { PassID::NRD_ShadowCombine, 0 }, { PassID::DDGIDebug, 0 }, { PassID::VoxelDebug, 0 }, }; static inline const FrameGraph::PrecompiledState GBuffer_DepthMips_c0_states[] = { { true, { GBuffer_DepthMips_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_DepthMips_c0_pass_refs + 1, 23 } }, + { false, { GBuffer_DepthMips_c0_pass_refs + 1, 28 } }, }; static inline const FrameGraph::PassRef GBuffer_Quality_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -741,6 +802,8 @@ namespace Frame { PassID::VSM_Unshadowed, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, { PassID::NRD_IndirectCombine, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::ReflCombine, 0 }, { PassID::NRD_ShadowCombine, 0 }, { PassID::DDGIDebug, 0 }, @@ -754,11 +817,13 @@ namespace Frame { true, { ResultTexture_c0_pass_refs + 3, 1 } }, { true, { ResultTexture_c0_pass_refs + 4, 1 } }, { true, { ResultTexture_c0_pass_refs + 5, 1 } }, - { true, { ResultTexture_c0_pass_refs + 6, 1 } }, + { false, { ResultTexture_c0_pass_refs + 6, 1 } }, { true, { ResultTexture_c0_pass_refs + 7, 1 } }, { true, { ResultTexture_c0_pass_refs + 8, 1 } }, { true, { ResultTexture_c0_pass_refs + 9, 1 } }, - { false, { ResultTexture_c0_pass_refs + 10, 1 } }, + { true, { ResultTexture_c0_pass_refs + 10, 1 } }, + { true, { ResultTexture_c0_pass_refs + 11, 1 } }, + { false, { ResultTexture_c0_pass_refs + 12, 1 } }, }; static inline const FrameGraph::PassRef GBuffer_TempColor_c0_pass_refs[] = { { PassID::GBufferDownsampler, 0 }, @@ -1211,6 +1276,15 @@ namespace Frame static inline const FrameGraph::PrecompiledState RTXReflectionDirPdf_c0_states[] = { { true, { RTXReflectionDirPdf_c0_pass_refs + 0, 1 } }, }; + static inline const FrameGraph::PassRef RTXReflectionCoat_c0_pass_refs[] = { + { PassID::ReflectionRTX, 0 }, + { PassID::ReflCombine, 0 }, + { PassID::RTXCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState RTXReflectionCoat_c0_states[] = { + { true, { RTXReflectionCoat_c0_pass_refs + 0, 1 } }, + { false, { RTXReflectionCoat_c0_pass_refs + 1, 2 } }, + }; static inline const FrameGraph::PassRef RTXIndirectNoiseHalf_c0_pass_refs[] = { { PassID::IndirectRTXHalf, 0 }, { PassID::IndirectRTX, 0 }, @@ -1440,13 +1514,48 @@ namespace Frame { true, { RTXReflectionDenoised_c0_pass_refs + 0, 1 } }, { false, { RTXReflectionDenoised_c0_pass_refs + 1, 1 } }, }; + static inline const FrameGraph::PassRef SSSTiles_c0_pass_refs[] = { + { PassID::SSS_Classify, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, + }; + static inline const FrameGraph::PrecompiledState SSSTiles_c0_states[] = { + { true, { SSSTiles_c0_pass_refs + 0, 1 } }, + { false, { SSSTiles_c0_pass_refs + 1, 4 } }, + }; + static inline const FrameGraph::PassRef SSSTemp_c0_pass_refs[] = { + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, + }; + static inline const FrameGraph::PrecompiledState SSSTemp_c0_states[] = { + { true, { SSSTemp_c0_pass_refs + 0, 1 } }, + { false, { SSSTemp_c0_pass_refs + 1, 1 } }, + { true, { SSSTemp_c0_pass_refs + 2, 1 } }, + { false, { SSSTemp_c0_pass_refs + 3, 1 } }, + }; static inline const FrameGraph::PassRef ResultTextureRTXNoise_c0_pass_refs[] = { { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, { PassID::UpscalingDLSSRR, 0 }, }; static inline const FrameGraph::PrecompiledState ResultTextureRTXNoise_c0_states[] = { { true, { ResultTextureRTXNoise_c0_pass_refs + 0, 1 } }, { false, { ResultTextureRTXNoise_c0_pass_refs + 1, 1 } }, + { true, { ResultTextureRTXNoise_c0_pass_refs + 2, 1 } }, + { false, { ResultTextureRTXNoise_c0_pass_refs + 3, 1 } }, + }; + static inline const FrameGraph::PassRef RTXCombineSpecular_c0_pass_refs[] = { + { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurV, 0 }, + }; + static inline const FrameGraph::PrecompiledState RTXCombineSpecular_c0_states[] = { + { true, { RTXCombineSpecular_c0_pass_refs + 0, 1 } }, + { false, { RTXCombineSpecular_c0_pass_refs + 1, 1 } }, }; static inline const FrameGraph::PassRef VSM_ShadowDenoised_c0_pass_refs[] = { { PassID::NRD_SIGMA_Execute, 0 }, @@ -1657,6 +1766,7 @@ namespace Frame { ResourceID::GBuffer_Specular, 0, GBuffer_Specular_c0_states }, { ResourceID::GBuffer_Speed, 0, GBuffer_Speed_c0_states }, { ResourceID::GBuffer_ObjectID, 0, GBuffer_ObjectID_c0_states }, + { ResourceID::GBuffer_Material, 0, GBuffer_Material_c0_states }, { ResourceID::GBuffer_DepthMips, 0, GBuffer_DepthMips_c0_states }, { ResourceID::GBuffer_Quality, 0, GBuffer_Quality_c0_states }, { ResourceID::GBuffer_NormalsPrev, 0, GBuffer_NormalsPrev_c0_states }, @@ -1695,6 +1805,7 @@ namespace Frame { ResourceID::RTXReflectionDirPdfHalf, 0, RTXReflectionDirPdfHalf_c0_states }, { ResourceID::RTXReflectionNoise, 0, RTXReflectionNoise_c0_states }, { ResourceID::RTXReflectionDirPdf, 0, RTXReflectionDirPdf_c0_states }, + { ResourceID::RTXReflectionCoat, 0, RTXReflectionCoat_c0_states }, { ResourceID::RTXIndirectNoiseHalf, 0, RTXIndirectNoiseHalf_c0_states }, { ResourceID::RTXIndirectNoise, 0, RTXIndirectNoise_c0_states }, { ResourceID::DDGI_ProbeRayRadiance, 0, DDGI_ProbeRayRadiance_c0_states }, @@ -1722,7 +1833,10 @@ namespace Frame { ResourceID::RTXIndirectDenoised, 0, RTXIndirectDenoised_c0_states }, { ResourceID::RTXIndirectDenoisedPreview, 0, RTXIndirectDenoisedPreview_c0_states }, { ResourceID::RTXReflectionDenoised, 0, RTXReflectionDenoised_c0_states }, + { ResourceID::SSSTiles, 0, SSSTiles_c0_states }, + { ResourceID::SSSTemp, 0, SSSTemp_c0_states }, { ResourceID::ResultTextureRTXNoise, 0, ResultTextureRTXNoise_c0_states }, + { ResourceID::RTXCombineSpecular, 0, RTXCombineSpecular_c0_states }, { ResourceID::VSM_ShadowDenoised, 0, VSM_ShadowDenoised_c0_states }, { ResourceID::ResultTexture, 1, ResultTexture_c1_states }, { ResourceID::ExposureState, 0, ExposureState_c0_states }, @@ -2364,10 +2478,37 @@ namespace Frame { PassID::VSM_ShadowResolve, 0 }, { PassID::VSM_Unshadowed, 0 }, }; + static inline const FrameGraph::PassRef SSS_Classify_0_prev[] = { + { PassID::Scene, 0 }, + }; + static inline const FrameGraph::PassRef SSS_BlurH_0_prev[] = { + { PassID::NRD_IndirectCombine, 0 }, + { PassID::ResultCreation, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::Scene, 0 }, + { PassID::VSM_Combine, 0 }, + { PassID::VSM_DebugClassifyOverlay, 0 }, + { PassID::VSM_ShadowResolve, 0 }, + { PassID::VSM_Unshadowed, 0 }, + }; + static inline const FrameGraph::PassRef SSS_BlurV_0_prev[] = { + { PassID::NRD_IndirectCombine, 0 }, + { PassID::ResultCreation, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::Scene, 0 }, + { PassID::VSM_Combine, 0 }, + { PassID::VSM_DebugClassifyOverlay, 0 }, + { PassID::VSM_ShadowResolve, 0 }, + { PassID::VSM_Unshadowed, 0 }, + }; static inline const FrameGraph::PassRef ReflCombine_0_prev[] = { { PassID::NRD_IndirectCombine, 0 }, { PassID::NRD_REBLUR_Execute, 0 }, + { PassID::ReflectionRTX, 0 }, { PassID::ResultCreation, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, @@ -2380,6 +2521,21 @@ namespace Frame { PassID::Scene, 0 }, { PassID::ShadowRTX, 0 }, }; + static inline const FrameGraph::PassRef SSSRR_BlurH_0_prev[] = { + { PassID::RTXCombine, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::Scene, 0 }, + }; + static inline const FrameGraph::PassRef SSSRR_BlurV_0_prev[] = { + { PassID::RTXCombine, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, + { PassID::SSS_Classify, 0 }, + { PassID::Scene, 0 }, + }; static inline const FrameGraph::PassRef NRD_SIGMA_Execute_0_prev[] = { { PassID::NRD_GBufferPack, 0 }, { PassID::ShadowRTX, 0 }, @@ -2390,6 +2546,8 @@ namespace Frame { PassID::NRD_SIGMA_Execute, 0 }, { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, @@ -2425,6 +2583,8 @@ namespace Frame { PassID::ReflectionRTX, 0 }, { PassID::ReflectionRTXHalf, 0 }, { PassID::ResultCreation, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, @@ -2437,6 +2597,8 @@ namespace Frame { PassID::NRD_ShadowCombine, 0 }, { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VSM_DebugClassifyOverlay, 0 }, @@ -2452,6 +2614,8 @@ namespace Frame { PassID::PreScene, 0 }, { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::Sky, 0 }, { PassID::VSM_Combine, 0 }, @@ -2465,6 +2629,8 @@ namespace Frame { PassID::NRD_ShadowCombine, 0 }, { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::Sky, 0 }, { PassID::TranslucentRTX, 0 }, @@ -2483,6 +2649,8 @@ namespace Frame { PassID::NRD_ShadowCombine, 0 }, { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Sky, 0 }, { PassID::TranslucentRTX, 0 }, { PassID::VSM_Combine, 0 }, @@ -2498,6 +2666,8 @@ namespace Frame { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, { PassID::SMAA, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Sky, 0 }, { PassID::TranslucentRTX, 0 }, { PassID::VSM_Combine, 0 }, @@ -2514,6 +2684,8 @@ namespace Frame { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, { PassID::SMAA, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::Sky, 0 }, { PassID::TranslucentRTX, 0 }, @@ -2534,6 +2706,10 @@ namespace Frame { PassID::ReflectionRTX, 0 }, { PassID::ResultCreation, 0 }, { PassID::SMAA, 0 }, + { PassID::SSSRR_BlurH, 0 }, + { PassID::SSSRR_BlurV, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::Sky, 0 }, { PassID::TranslucentRTX, 0 }, @@ -2553,6 +2729,8 @@ namespace Frame { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, { PassID::SMAA, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Sky, 0 }, { PassID::TranslucentRTX, 0 }, { PassID::UpscalingDLSS, 0 }, @@ -2573,6 +2751,8 @@ namespace Frame { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, { PassID::SMAA, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Sky, 0 }, { PassID::TranslucentRTX, 0 }, { PassID::UpscalingDLSS, 0 }, @@ -2593,6 +2773,8 @@ namespace Frame { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, { PassID::SMAA, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Sky, 0 }, { PassID::TranslucentRTX, 0 }, { PassID::UpscalingDLSS, 0 }, @@ -2613,6 +2795,8 @@ namespace Frame { PassID::ReflCombine, 0 }, { PassID::ResultCreation, 0 }, { PassID::SMAA, 0 }, + { PassID::SSS_BlurH, 0 }, + { PassID::SSS_BlurV, 0 }, { PassID::Scene, 0 }, { PassID::Sky, 0 }, { PassID::Tonemap, 0 }, @@ -2698,8 +2882,13 @@ namespace Frame { PassID::VSM_DebugClassifyOverlay, 0, true, VSM_DebugClassifyOverlay_0_prev }, { PassID::NRD_REBLUR_Execute, 0, false, NRD_REBLUR_Execute_0_prev }, { PassID::NRD_IndirectCombine, 0, true, NRD_IndirectCombine_0_prev }, + { PassID::SSS_Classify, 0, true, SSS_Classify_0_prev }, + { PassID::SSS_BlurH, 0, true, SSS_BlurH_0_prev }, + { PassID::SSS_BlurV, 0, true, SSS_BlurV_0_prev }, { PassID::ReflCombine, 0, true, ReflCombine_0_prev }, { PassID::RTXCombine, 0, true, RTXCombine_0_prev }, + { PassID::SSSRR_BlurH, 0, true, SSSRR_BlurH_0_prev }, + { PassID::SSSRR_BlurV, 0, true, SSSRR_BlurV_0_prev }, { PassID::NRD_SIGMA_Execute, 0, false, NRD_SIGMA_Execute_0_prev }, { PassID::NRD_ShadowCombine, 0, true, NRD_ShadowCombine_0_prev }, { PassID::DDGIDebug, 0, false, DDGIDebug_0_prev }, @@ -2861,11 +3050,16 @@ namespace Frame graph.add_library_pass(PassSetupDefault::setup, vSM_DebugClassifyOverlay.render_func, (vSM_DebugClassifyOverlay.flags)); graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); + graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); + graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); + graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); // Setup is generated (PassSetupDefault, pass_defaults.h); the owner // only supplies render_func, so that is what gates registration. if (reflCombine.render_func) graph.add_library_pass(PassSetupDefault::setup, reflCombine.render_func, (reflCombine.flags)); graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); + graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); + graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags & ~FrameGraph::PassFlags::Compute)); diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_NRDShadowCombinePipeline.pipeline.h b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_NRDShadowCombinePipeline.pipeline.h new file mode 100644 index 000000000..6a6bbf7da --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_NRDShadowCombinePipeline.pipeline.h @@ -0,0 +1,209 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ + +#include "../FrameGraphTests/FGT_ProduceGBuffer.h" +#include "../FrameGraphTests/FGT_ProduceShadow.h" +#include "../Denoise/NRD/NRD_ShadowCombine.h" +#include "../FrameGraphTests/FGT_CopyResultToSink.h" +#include "../../pass_defaults.h" + +using namespace FrameGraph; +namespace Pipelines +{ +namespace FrameGraphTests +{ + + class FGT_NRDShadowCombinePipeline : public PipelineBase + { + public: + + Passes::FrameGraphTests::FGT_ProduceGBuffer fGT_ProduceGBuffer; + Passes::FrameGraphTests::FGT_ProduceShadow fGT_ProduceShadow; + Passes::FrameGraphTests::FGT_CopyResultToSink fGT_CopyResultToSink; + + static inline const wchar_t* const pass_names[] = { + Passes::FrameGraphTests::FGT_ProduceGBuffer::Name.ptr, + Passes::FrameGraphTests::FGT_ProduceShadow::Name.ptr, + Passes::Denoise::NRD::NRD_ShadowCombine::Name.ptr, + Passes::FrameGraphTests::FGT_CopyResultToSink::Name.ptr, + }; + static constexpr uint32_t pass_count = std::size(pass_names); + + std::span GetUsedPassNamesList() const override + { + return pass_names; + } + + static inline const wchar_t* const resource_names[] = { + L"GBuffer_Albedo", + L"GBuffer_Normals", + L"GBuffer_Specular", + L"GBuffer_Material", + L"GBuffer_Speed", + L"GBuffer_DepthMips", + L"ResultTexture", + L"VSM_ShadowDenoised", + L"FGT_Sink", + }; + static constexpr uint32_t resource_count = std::size(resource_names); + + std::span GetUsedResourcesList() const override + { + return resource_names; + } + // Precomputed per-resource RW-state timeline (see FrameGraph::PrecompiledResourceInfo). + // Each resource owns its pass-ref pool and its states; a state's passes are + // a span into that resource's own _pass_refs[]. + static inline const FrameGraph::PassRef GBuffer_Albedo_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Albedo_c0_states[] = { + { true, { GBuffer_Albedo_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Albedo_c0_pass_refs + 1, 1 } }, + }; + static inline const FrameGraph::PassRef GBuffer_Normals_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Normals_c0_states[] = { + { true, { GBuffer_Normals_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Normals_c0_pass_refs + 1, 1 } }, + }; + static inline const FrameGraph::PassRef GBuffer_Specular_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Specular_c0_states[] = { + { true, { GBuffer_Specular_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Specular_c0_pass_refs + 1, 1 } }, + }; + static inline const FrameGraph::PassRef GBuffer_Material_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Material_c0_states[] = { + { true, { GBuffer_Material_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Material_c0_pass_refs + 1, 1 } }, + }; + static inline const FrameGraph::PassRef GBuffer_Speed_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Speed_c0_states[] = { + { true, { GBuffer_Speed_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Speed_c0_pass_refs + 1, 1 } }, + }; + static inline const FrameGraph::PassRef GBuffer_DepthMips_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_DepthMips_c0_states[] = { + { true, { GBuffer_DepthMips_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_DepthMips_c0_pass_refs + 1, 1 } }, + }; + static inline const FrameGraph::PassRef ResultTexture_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + { PassID::FGT_CopyResultToSink, 0 }, + }; + static inline const FrameGraph::PrecompiledState ResultTexture_c0_states[] = { + { true, { ResultTexture_c0_pass_refs + 0, 1 } }, + { true, { ResultTexture_c0_pass_refs + 1, 1 } }, + { false, { ResultTexture_c0_pass_refs + 2, 1 } }, + }; + static inline const FrameGraph::PassRef VSM_ShadowDenoised_c0_pass_refs[] = { + { PassID::FGT_ProduceShadow, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledState VSM_ShadowDenoised_c0_states[] = { + { true, { VSM_ShadowDenoised_c0_pass_refs + 0, 1 } }, + { false, { VSM_ShadowDenoised_c0_pass_refs + 1, 1 } }, + }; + static inline const FrameGraph::PassRef FGT_Sink_c0_pass_refs[] = { + { PassID::FGT_CopyResultToSink, 0 }, + }; + static inline const FrameGraph::PrecompiledState FGT_Sink_c0_states[] = { + { true, { FGT_Sink_c0_pass_refs + 0, 1 } }, + }; + static inline const FrameGraph::PrecompiledResourceInfo resource_infos[] = { + { ResourceID::GBuffer_Albedo, 0, GBuffer_Albedo_c0_states }, + { ResourceID::GBuffer_Normals, 0, GBuffer_Normals_c0_states }, + { ResourceID::GBuffer_Specular, 0, GBuffer_Specular_c0_states }, + { ResourceID::GBuffer_Material, 0, GBuffer_Material_c0_states }, + { ResourceID::GBuffer_Speed, 0, GBuffer_Speed_c0_states }, + { ResourceID::GBuffer_DepthMips, 0, GBuffer_DepthMips_c0_states }, + { ResourceID::ResultTexture, 0, ResultTexture_c0_states }, + { ResourceID::VSM_ShadowDenoised, 0, VSM_ShadowDenoised_c0_states }, + { ResourceID::FGT_Sink, 0, FGT_Sink_c0_states }, + }; + static constexpr uint32_t resource_info_count = std::size(resource_infos); + // Precomputed pass instances with queue type and prev-pass dependency edges + // (static resolve_dependencies). Each pass owns its __prev[] refs. + static inline const FrameGraph::PassRef NRD_ShadowCombine_0_prev[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::FGT_ProduceShadow, 0 }, + }; + static inline const FrameGraph::PassRef FGT_CopyResultToSink_0_prev[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::NRD_ShadowCombine, 0 }, + }; + static inline const FrameGraph::PrecompiledPass precompiled_passes[] = { + { PassID::FGT_ProduceGBuffer, 0, false, {} }, + { PassID::FGT_ProduceShadow, 0, false, {} }, + { PassID::NRD_ShadowCombine, 0, false, NRD_ShadowCombine_0_prev }, + { PassID::FGT_CopyResultToSink, 0, false, FGT_CopyResultToSink_0_prev }, + }; + static constexpr uint32_t precompiled_pass_count = std::size(precompiled_passes); + + std::span GetResourceInfos() const override + { + return resource_infos; + } + + std::span GetPrecompiledPasses() const override + { + return precompiled_passes; + } + + // [PreSetup] side effects for the passes IN THIS PIPELINE, run once per + // frame after add_passes() and before graph.setup(). Per-pipeline, not + // global: a pass can appear in more than one pipeline (CubeSky is in both + // MainPipeline and AssetPipeline) driving a different Graph each time, and + // a pass in the main pipeline only must not fire while the asset graph is + // being built. See PassDefault::pre_setup()'s own comment for what the + // hook is for. + void run_pre_setups(FrameGraph::Graph& graph) + { + } + + // [Compute] on a PassNode says the pass CAN run on the compute queue; [Async] + // on the pipeline entry says it SHOULD here. Async is opt-in per pipeline, so + // an untagged compute pass runs inline on the direct queue - no fence pair for + // work that has nothing to overlap with. + void add_passes(FrameGraph::Graph& graph) + { + graph.set_pipeline(this); + + // Setup is generated (PassSetupDefault, pass_defaults.h); the owner + // only supplies render_func, so that is what gates registration. + if (fGT_ProduceGBuffer.render_func) + graph.add_library_pass(PassSetupDefault::setup, fGT_ProduceGBuffer.render_func, (fGT_ProduceGBuffer.flags & ~FrameGraph::PassFlags::Compute)); + // Setup is generated (PassSetupDefault, pass_defaults.h); the owner + // only supplies render_func, so that is what gates registration. + if (fGT_ProduceShadow.render_func) + graph.add_library_pass(PassSetupDefault::setup, fGT_ProduceShadow.render_func, (fGT_ProduceShadow.flags & ~FrameGraph::PassFlags::Compute)); + graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags & ~FrameGraph::PassFlags::Compute)); + // Setup is generated (PassSetupDefault, pass_defaults.h); the owner + // only supplies render_func, so that is what gates registration. + if (fGT_CopyResultToSink.render_func) + graph.add_library_pass(PassSetupDefault::setup, fGT_CopyResultToSink.render_func, (fGT_CopyResultToSink.flags & ~FrameGraph::PassFlags::Compute)); + } + }; +} + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_ProduceGBuffer.h b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_ProduceGBuffer.h index 5db63609e..b8ffc0cde 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_ProduceGBuffer.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_ProduceGBuffer.h @@ -30,6 +30,9 @@ namespace FrameGraphTests Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; @@ -57,6 +60,7 @@ namespace FrameGraphTests builder.create(data.GBuffer_Albedo, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::CopyDest); builder.create(data.GBuffer_Normals, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::CopyDest); builder.create(data.GBuffer_Specular, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::CopyDest); + builder.create(data.GBuffer_Material, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::CopyDest); builder.create(data.GBuffer_Speed, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R16G16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::CopyDest); builder.create(data.GBuffer_DepthMips, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R32_TYPELESS, 1, 1 }, FrameGraph::ResourceFlags::CopyDest); builder.create(data.ResultTexture, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess); @@ -71,6 +75,7 @@ namespace FrameGraphTests FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex ResultTexture = FrameGraph::ChainIndex::Unresolved; @@ -81,6 +86,7 @@ namespace FrameGraphTests cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.ResultTexture = FrameGraph::TaskBuilder::cache_slot(data.ResultTexture, ResourceID::ResultTexture); @@ -98,6 +104,7 @@ namespace FrameGraphTests builder.create_versioned(data.GBuffer_Albedo, cache.GBuffer_Albedo, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }); builder.create_versioned(data.GBuffer_Normals, cache.GBuffer_Normals, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }); builder.create_versioned(data.GBuffer_Specular, cache.GBuffer_Specular, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }); + builder.create_versioned(data.GBuffer_Material, cache.GBuffer_Material, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }); builder.create_versioned(data.GBuffer_Speed, cache.GBuffer_Speed, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R16G16_FLOAT, 1, 1 }); builder.create_versioned(data.GBuffer_DepthMips, cache.GBuffer_DepthMips, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R32_TYPELESS, 1, 1 }); builder.create_versioned(data.ResultTexture, cache.ResultTexture, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }); @@ -110,6 +117,7 @@ namespace FrameGraphTests { ResourceID::GBuffer_Albedo, true }, { ResourceID::GBuffer_Normals, true }, { ResourceID::GBuffer_Specular, true }, + { ResourceID::GBuffer_Material, true }, { ResourceID::GBuffer_Speed, true }, { ResourceID::GBuffer_DepthMips, true }, { ResourceID::ResultTexture, true }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_ProduceShadow.h b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_ProduceShadow.h new file mode 100644 index 000000000..f58451c0f --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_ProduceShadow.h @@ -0,0 +1,100 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "../../PassNodeBase.h" + +using namespace FrameGraph; + +namespace Passes +{ +namespace FrameGraphTests +{ + + class FGT_ProduceShadow : public PassNodeBase + { + public: + struct Context + { + + + Handlers::Texture VSM_ShadowDenoised = ResourceID::VSM_ShadowDenoised; + + + // Resources this pass always creates with a fixed desc, generated from + // each field's own [Size]/[Format] annotation (plus [Always] for the + // creation flags, or [Always]+[Recreate]+[RecreateFlags] for a field + // that needs its original chain link before recreating a new one). A + // field that ALSO carries [Optional] creates only under that + // condition -- need_always() (above) emits a complementary need() for + // the SAME field under the negated condition, using the same + // [Always] flags, e.g. a [Multiple] pass where instance 0 creates a + // shared resource and every other instance just needs it (see + // PSSM_Cascade, pssm.prism). Called by TypedPass::setup() after + // setup_func returns true - not a substitute for setup_func's own + // create()/recreate() calls for anything whose Desc depends on + // runtime state. + static void create_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.create(data.VSM_ShadowDenoised, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::CopyDest); + } + // Which chain link each handler field resolved to, one named slot per + // field. Filled from a live frame's finished Context and applied on a + // replayed one, so a replay neither re-runs create_always/need_always nor + // depends on the order they made their calls in. The id is not stored: + // the field fixes it. + struct Cache + { + FrameGraph::ChainIndex VSM_ShadowDenoised = FrameGraph::ChainIndex::Unresolved; + }; + + static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) + { + cache.VSM_ShadowDenoised = FrameGraph::TaskBuilder::cache_slot(data.VSM_ShadowDenoised, ResourceID::VSM_ShadowDenoised); + } + + // Replay counterpart of create_always/need_always. A field this pass + // creates gets its desc recomputed on the link the cache names, so descs + // follow the current context instead of being stored in the plan; every + // other field is only pointed at its link. Each link is created by exactly + // one pass and nothing here reads another resource's desc, so passes can + // load in any order. A [Recreate] without [Size]/[Format] copies the + // previous link's desc, which LoadGraph does once every pass has loaded. + static void load_from_cache([[maybe_unused]] Context& data, [[maybe_unused]] const Cache& cache, [[maybe_unused]] const FrameGraph::TaskBuilder& builder) + { + builder.create_versioned(data.VSM_ShadowDenoised, cache.VSM_ShadowDenoised, { ivec3(builder.graph->get_context().size, 0), HAL::Format::R8_UNORM, 1, 1 }); + } + + // Resources this pass touches, in declaration order, each paired with + // whether the pass writes it (own [Write], or the view usage's + // [Write] / [Write = {leaves...}] for resources inside a view group). + static inline const FrameGraph::ResourceAccess resource_accesses[] = { + { ResourceID::VSM_ShadowDenoised, true }, + }; + static constexpr uint resource_count = std::size(resource_accesses); + }; + + + std::span GetUsedResourcesList() const override + { + return std::span(Context::resource_accesses, Context::resource_count); + } + + static constexpr LiteralWStr Name{L"FGT_ProduceShadow"}; + + static constexpr PassID ID = PassID::FGT_ProduceShadow; + + + using setup_func_type = std::function; + using render_func_type = std::function; + + render_func_type render_func; + + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::General; + }; +} + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMDepthAnalysisPipeline.pipeline.h b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMDepthAnalysisPipeline.pipeline.h index 28d6a09d6..3d358c925 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMDepthAnalysisPipeline.pipeline.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMDepthAnalysisPipeline.pipeline.h @@ -37,6 +37,7 @@ namespace FrameGraphTests L"GBuffer_Albedo", L"GBuffer_Normals", L"GBuffer_Specular", + L"GBuffer_Material", L"GBuffer_Speed", L"GBuffer_DepthMips", L"ResultTexture", @@ -75,6 +76,12 @@ namespace FrameGraphTests { true, { GBuffer_Specular_c0_pass_refs + 0, 1 } }, { false, { GBuffer_Specular_c0_pass_refs + 1, 1 } }, }; + static inline const FrameGraph::PassRef GBuffer_Material_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Material_c0_states[] = { + { true, { GBuffer_Material_c0_pass_refs + 0, 1 } }, + }; static inline const FrameGraph::PassRef GBuffer_Speed_c0_pass_refs[] = { { PassID::FGT_ProduceGBuffer, 0 }, { PassID::VSM_DepthAnalysis, 0 }, @@ -107,6 +114,7 @@ namespace FrameGraphTests { ResourceID::GBuffer_Albedo, 0, GBuffer_Albedo_c0_states }, { ResourceID::GBuffer_Normals, 0, GBuffer_Normals_c0_states }, { ResourceID::GBuffer_Specular, 0, GBuffer_Specular_c0_states }, + { ResourceID::GBuffer_Material, 0, GBuffer_Material_c0_states }, { ResourceID::GBuffer_Speed, 0, GBuffer_Speed_c0_states }, { ResourceID::GBuffer_DepthMips, 0, GBuffer_DepthMips_c0_states }, { ResourceID::ResultTexture, 0, ResultTexture_c0_states }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMFullChainPipeline.pipeline.h b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMFullChainPipeline.pipeline.h index 5d4b6595e..12cec948d 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMFullChainPipeline.pipeline.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMFullChainPipeline.pipeline.h @@ -70,6 +70,7 @@ namespace FrameGraphTests L"GBuffer_Albedo", L"GBuffer_Normals", L"GBuffer_Specular", + L"GBuffer_Material", L"GBuffer_Speed", L"GBuffer_DepthMips", L"ResultTexture", @@ -152,6 +153,15 @@ namespace FrameGraphTests { true, { GBuffer_Specular_c0_pass_refs + 0, 1 } }, { false, { GBuffer_Specular_c0_pass_refs + 1, 6 } }, }; + static inline const FrameGraph::PassRef GBuffer_Material_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::VSM_ShadowResolve, 0 }, + { PassID::VSM_Combine, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Material_c0_states[] = { + { true, { GBuffer_Material_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Material_c0_pass_refs + 1, 2 } }, + }; static inline const FrameGraph::PassRef GBuffer_Speed_c0_pass_refs[] = { { PassID::FGT_ProduceGBuffer, 0 }, { PassID::VSM_DepthAnalysis, 0 }, @@ -359,6 +369,7 @@ namespace FrameGraphTests { ResourceID::GBuffer_Albedo, 0, GBuffer_Albedo_c0_states }, { ResourceID::GBuffer_Normals, 0, GBuffer_Normals_c0_states }, { ResourceID::GBuffer_Specular, 0, GBuffer_Specular_c0_states }, + { ResourceID::GBuffer_Material, 0, GBuffer_Material_c0_states }, { ResourceID::GBuffer_Speed, 0, GBuffer_Speed_c0_states }, { ResourceID::GBuffer_DepthMips, 0, GBuffer_DepthMips_c0_states }, { ResourceID::ResultTexture, 0, ResultTexture_c0_states }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMUnshadowedPipeline.pipeline.h b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMUnshadowedPipeline.pipeline.h index 3772eb7fd..cb271d57a 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMUnshadowedPipeline.pipeline.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/FrameGraphTests/FGT_VSMUnshadowedPipeline.pipeline.h @@ -40,6 +40,7 @@ namespace FrameGraphTests L"GBuffer_Albedo", L"GBuffer_Normals", L"GBuffer_Specular", + L"GBuffer_Material", L"GBuffer_Speed", L"GBuffer_DepthMips", L"ResultTexture", @@ -78,6 +79,14 @@ namespace FrameGraphTests { true, { GBuffer_Specular_c0_pass_refs + 0, 1 } }, { false, { GBuffer_Specular_c0_pass_refs + 1, 1 } }, }; + static inline const FrameGraph::PassRef GBuffer_Material_c0_pass_refs[] = { + { PassID::FGT_ProduceGBuffer, 0 }, + { PassID::VSM_Unshadowed, 0 }, + }; + static inline const FrameGraph::PrecompiledState GBuffer_Material_c0_states[] = { + { true, { GBuffer_Material_c0_pass_refs + 0, 1 } }, + { false, { GBuffer_Material_c0_pass_refs + 1, 1 } }, + }; static inline const FrameGraph::PassRef GBuffer_Speed_c0_pass_refs[] = { { PassID::FGT_ProduceGBuffer, 0 }, { PassID::VSM_Unshadowed, 0 }, @@ -114,6 +123,7 @@ namespace FrameGraphTests { ResourceID::GBuffer_Albedo, 0, GBuffer_Albedo_c0_states }, { ResourceID::GBuffer_Normals, 0, GBuffer_Normals_c0_states }, { ResourceID::GBuffer_Specular, 0, GBuffer_Specular_c0_states }, + { ResourceID::GBuffer_Material, 0, GBuffer_Material_c0_states }, { ResourceID::GBuffer_Speed, 0, GBuffer_Speed_c0_states }, { ResourceID::GBuffer_DepthMips, 0, GBuffer_DepthMips_c0_states }, { ResourceID::ResultTexture, 0, ResultTexture_c0_states }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/GI/RTXCombine.h b/sources/RenderSystem/FrameGraph/autogen/pass/GI/RTXCombine.h index a12745be9..b4da9faa9 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/GI/RTXCombine.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/GI/RTXCombine.h @@ -45,9 +45,18 @@ namespace GI Handlers::Texture RTXShadowNoise = ResourceID::RTXShadowNoise; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + + Handlers::Texture RTXReflectionCoat = ResourceID::RTXReflectionCoat; + + Handlers::Texture ResultTextureRTXNoise = ResourceID::ResultTextureRTXNoise; + Handlers::Texture RTXCombineSpecular = ResourceID::RTXCombineSpecular; + + // Resources this pass always needs whenever it runs, generated from // each field's own [Always=X] annotation (further gated by [Optional=X] // when present -- a raw bool expression, e.g. builder.exists(...) or a @@ -72,6 +81,8 @@ namespace GI builder.need(data.RTXReflectionNoise, FrameGraph::ResourceFlags::Read); builder.need(data.RTXIndirectNoise, FrameGraph::ResourceFlags::Read); builder.need(data.RTXShadowNoise, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); + builder.need(data.RTXReflectionCoat, FrameGraph::ResourceFlags::Read); } // Resources this pass always creates with a fixed desc, generated from @@ -90,6 +101,7 @@ namespace GI static void create_always(Context& data, FrameGraph::TaskBuilder& builder) { builder.create(data.ResultTextureRTXNoise, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess); + builder.create(data.RTXCombineSpecular, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess); } // Which chain link each handler field resolved to, one named slot per // field. Filled from a live frame's finished Context and applied on a @@ -106,7 +118,10 @@ namespace GI FrameGraph::ChainIndex RTXReflectionNoise = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex RTXIndirectNoise = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex RTXShadowNoise = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex RTXReflectionCoat = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex ResultTextureRTXNoise = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex RTXCombineSpecular = FrameGraph::ChainIndex::Unresolved; }; static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) @@ -119,7 +134,10 @@ namespace GI cache.RTXReflectionNoise = FrameGraph::TaskBuilder::cache_slot(data.RTXReflectionNoise, ResourceID::RTXReflectionNoise); cache.RTXIndirectNoise = FrameGraph::TaskBuilder::cache_slot(data.RTXIndirectNoise, ResourceID::RTXIndirectNoise); cache.RTXShadowNoise = FrameGraph::TaskBuilder::cache_slot(data.RTXShadowNoise, ResourceID::RTXShadowNoise); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); + cache.RTXReflectionCoat = FrameGraph::TaskBuilder::cache_slot(data.RTXReflectionCoat, ResourceID::RTXReflectionCoat); cache.ResultTextureRTXNoise = FrameGraph::TaskBuilder::cache_slot(data.ResultTextureRTXNoise, ResourceID::ResultTextureRTXNoise); + cache.RTXCombineSpecular = FrameGraph::TaskBuilder::cache_slot(data.RTXCombineSpecular, ResourceID::RTXCombineSpecular); } // Replay counterpart of create_always/need_always. A field this pass @@ -139,7 +157,10 @@ namespace GI builder.load(data.RTXReflectionNoise, ResourceID::RTXReflectionNoise, cache.RTXReflectionNoise); builder.load(data.RTXIndirectNoise, ResourceID::RTXIndirectNoise, cache.RTXIndirectNoise); builder.load(data.RTXShadowNoise, ResourceID::RTXShadowNoise, cache.RTXShadowNoise); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); + builder.load(data.RTXReflectionCoat, ResourceID::RTXReflectionCoat, cache.RTXReflectionCoat); builder.create_versioned(data.ResultTextureRTXNoise, cache.ResultTextureRTXNoise, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }); + builder.create_versioned(data.RTXCombineSpecular, cache.RTXCombineSpecular, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }); } // Resources this pass touches, in declaration order, each paired with @@ -154,7 +175,10 @@ namespace GI { ResourceID::RTXReflectionNoise, false }, { ResourceID::RTXIndirectNoise, false }, { ResourceID::RTXShadowNoise, false }, + { ResourceID::GBuffer_Material, false }, + { ResourceID::RTXReflectionCoat, false }, { ResourceID::ResultTextureRTXNoise, true }, + { ResourceID::RTXCombineSpecular, true }, }; static constexpr uint resource_count = std::size(resource_accesses); }; diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Meshes/GBufferDownsampler.h b/sources/RenderSystem/FrameGraph/autogen/pass/Meshes/GBufferDownsampler.h index 41d19f1b9..7237a557d 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Meshes/GBufferDownsampler.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Meshes/GBufferDownsampler.h @@ -27,6 +27,9 @@ namespace Meshes Handlers::Texture GBuffer_Normals = ResourceID::GBuffer_Normals; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; @@ -84,6 +87,7 @@ namespace Meshes { builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); @@ -124,6 +128,7 @@ namespace Meshes { FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; @@ -143,6 +148,7 @@ namespace Meshes { cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); @@ -169,6 +175,7 @@ namespace Meshes { builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); @@ -190,6 +197,7 @@ namespace Meshes static inline const FrameGraph::ResourceAccess resource_accesses[] = { { ResourceID::GBuffer_Albedo, false }, { ResourceID::GBuffer_Normals, false }, + { ResourceID::GBuffer_Material, false }, { ResourceID::GBuffer_Specular, false }, { ResourceID::GBuffer_Speed, false }, { ResourceID::GBuffer_DepthMips, false }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Meshes/Scene.h b/sources/RenderSystem/FrameGraph/autogen/pass/Meshes/Scene.h index ceac45f7e..15cff1725 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Meshes/Scene.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Meshes/Scene.h @@ -39,6 +39,9 @@ namespace Meshes Handlers::Texture GBuffer_ObjectID = ResourceID::GBuffer_ObjectID; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_DepthMips = ResourceID::GBuffer_DepthMips; @@ -116,6 +119,7 @@ namespace Meshes builder.create(data.GBuffer_Specular, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::RenderTarget); builder.create(data.GBuffer_Speed, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::RenderTarget); builder.create(data.GBuffer_ObjectID, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R32_UINT, 1, 1 }, FrameGraph::ResourceFlags::RenderTarget); + builder.create(data.GBuffer_Material, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }, FrameGraph::ResourceFlags::RenderTarget); builder.create(data.GBuffer_DepthMips, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R32_TYPELESS, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess | FrameGraph::ResourceFlags::RenderTarget); builder.create_prev(data.GBuffer_DepthPrev, data.GBuffer_DepthMips); builder.create(data.GBuffer_Quality, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::D24_UNORM_S8_UINT, 1, 1 }, FrameGraph::ResourceFlags::DepthStencil); @@ -135,6 +139,7 @@ namespace Meshes FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_ObjectID = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Quality = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_NormalsPrev = FrameGraph::ChainIndex::Unresolved; @@ -152,6 +157,7 @@ namespace Meshes cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_ObjectID = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_ObjectID, ResourceID::GBuffer_ObjectID); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.GBuffer_Quality = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Quality, ResourceID::GBuffer_Quality); cache.GBuffer_NormalsPrev = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_NormalsPrev, ResourceID::GBuffer_NormalsPrev); @@ -176,6 +182,7 @@ namespace Meshes builder.create_versioned(data.GBuffer_Specular, cache.GBuffer_Specular, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }); builder.create_versioned(data.GBuffer_Speed, cache.GBuffer_Speed, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16_FLOAT, 1, 1 }); builder.create_versioned(data.GBuffer_ObjectID, cache.GBuffer_ObjectID, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R32_UINT, 1, 1 }); + builder.create_versioned(data.GBuffer_Material, cache.GBuffer_Material, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R8G8B8A8_UNORM, 1, 1 }); builder.create_versioned(data.GBuffer_DepthMips, cache.GBuffer_DepthMips, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R32_TYPELESS, 1, 1 }); builder.create_versioned(data.GBuffer_Quality, cache.GBuffer_Quality, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::D24_UNORM_S8_UINT, 1, 1 }); builder.load(data.GBuffer_NormalsPrev, ResourceID::GBuffer_NormalsPrev, cache.GBuffer_NormalsPrev); @@ -195,6 +202,7 @@ namespace Meshes { ResourceID::GBuffer_Specular, true }, { ResourceID::GBuffer_Speed, true }, { ResourceID::GBuffer_ObjectID, true }, + { ResourceID::GBuffer_Material, true }, { ResourceID::GBuffer_DepthMips, true }, { ResourceID::GBuffer_Quality, true }, { ResourceID::GBuffer_NormalsPrev, true }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Reflections/ReflCombine.h b/sources/RenderSystem/FrameGraph/autogen/pass/Reflections/ReflCombine.h index 14ffd6cb2..2ce70d79f 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Reflections/ReflCombine.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Reflections/ReflCombine.h @@ -42,6 +42,12 @@ namespace Reflections Handlers::Texture RTXReflectionDenoised = ResourceID::RTXReflectionDenoised; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + + Handlers::Texture RTXReflectionCoat = ResourceID::RTXReflectionCoat; + + // Resources this pass always needs whenever it runs, generated from // each field's own [Always=X] annotation (further gated by [Optional=X] // when present -- a raw bool expression, e.g. builder.exists(...) or a @@ -65,6 +71,8 @@ namespace Reflections builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); builder.need(data.ResultTexture, FrameGraph::ResourceFlags::UnorderedAccess); builder.need(data.RTXReflectionDenoised, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); + builder.need(data.RTXReflectionCoat, FrameGraph::ResourceFlags::Read); } // Which chain link each handler field resolved to, one named slot per // field. Filled from a live frame's finished Context and applied on a @@ -80,6 +88,8 @@ namespace Reflections FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex ResultTexture = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex RTXReflectionDenoised = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex RTXReflectionCoat = FrameGraph::ChainIndex::Unresolved; }; static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) @@ -91,6 +101,8 @@ namespace Reflections cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.ResultTexture = FrameGraph::TaskBuilder::cache_slot(data.ResultTexture, ResourceID::ResultTexture); cache.RTXReflectionDenoised = FrameGraph::TaskBuilder::cache_slot(data.RTXReflectionDenoised, ResourceID::RTXReflectionDenoised); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); + cache.RTXReflectionCoat = FrameGraph::TaskBuilder::cache_slot(data.RTXReflectionCoat, ResourceID::RTXReflectionCoat); } // Replay counterpart of create_always/need_always. A field this pass @@ -109,6 +121,8 @@ namespace Reflections builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); builder.load(data.ResultTexture, ResourceID::ResultTexture, cache.ResultTexture); builder.load(data.RTXReflectionDenoised, ResourceID::RTXReflectionDenoised, cache.RTXReflectionDenoised); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); + builder.load(data.RTXReflectionCoat, ResourceID::RTXReflectionCoat, cache.RTXReflectionCoat); } // Resources this pass touches, in declaration order, each paired with @@ -122,6 +136,8 @@ namespace Reflections { ResourceID::GBuffer_DepthMips, false }, { ResourceID::ResultTexture, true }, { ResourceID::RTXReflectionDenoised, false }, + { ResourceID::GBuffer_Material, false }, + { ResourceID::RTXReflectionCoat, false }, }; static constexpr uint resource_count = std::size(resource_accesses); }; diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Reflections/ReflectionRTX.h b/sources/RenderSystem/FrameGraph/autogen/pass/Reflections/ReflectionRTX.h index 4b91343f0..d25abd8c7 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Reflections/ReflectionRTX.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Reflections/ReflectionRTX.h @@ -75,6 +75,12 @@ namespace Reflections Handlers::Texture RTXReflectionDirPdf = ResourceID::RTXReflectionDirPdf; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + + Handlers::Texture RTXReflectionCoat = ResourceID::RTXReflectionCoat; + + // Resources this pass always needs whenever it runs, generated from // each field's own [Always=X] annotation (further gated by [Optional=X] // when present -- a raw bool expression, e.g. builder.exists(...) or a @@ -111,6 +117,7 @@ namespace Reflections builder.need(data.DDGI_ProbeResidency, FrameGraph::ResourceFlags::Read); if (builder.graph->get_context().enabled) builder.need(data.DDGI_ProbeResidencyPending, FrameGraph::ResourceFlags::UnorderedAccess); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); } // Resources this pass always creates with a fixed desc, generated from @@ -130,6 +137,7 @@ namespace Reflections { builder.create(data.RTXReflectionNoise, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess); builder.create(data.RTXReflectionDirPdf, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess); + builder.create(data.RTXReflectionCoat, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess); } // Which chain link each handler field resolved to, one named slot per // field. Filled from a live frame's finished Context and applied on a @@ -156,6 +164,8 @@ namespace Reflections FrameGraph::ChainIndex DDGI_ProbeResidencyPending = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex RTXReflectionNoise = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex RTXReflectionDirPdf = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex RTXReflectionCoat = FrameGraph::ChainIndex::Unresolved; }; static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) @@ -178,6 +188,8 @@ namespace Reflections cache.DDGI_ProbeResidencyPending = FrameGraph::TaskBuilder::cache_slot(data.DDGI_ProbeResidencyPending, ResourceID::DDGI_ProbeResidencyPending); cache.RTXReflectionNoise = FrameGraph::TaskBuilder::cache_slot(data.RTXReflectionNoise, ResourceID::RTXReflectionNoise); cache.RTXReflectionDirPdf = FrameGraph::TaskBuilder::cache_slot(data.RTXReflectionDirPdf, ResourceID::RTXReflectionDirPdf); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); + cache.RTXReflectionCoat = FrameGraph::TaskBuilder::cache_slot(data.RTXReflectionCoat, ResourceID::RTXReflectionCoat); } // Replay counterpart of create_always/need_always. A field this pass @@ -207,6 +219,8 @@ namespace Reflections builder.load(data.DDGI_ProbeResidencyPending, ResourceID::DDGI_ProbeResidencyPending, cache.DDGI_ProbeResidencyPending); builder.create_versioned(data.RTXReflectionNoise, cache.RTXReflectionNoise, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }); builder.create_versioned(data.RTXReflectionDirPdf, cache.RTXReflectionDirPdf, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); + builder.create_versioned(data.RTXReflectionCoat, cache.RTXReflectionCoat, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }); } // Resources this pass touches, in declaration order, each paired with @@ -231,6 +245,8 @@ namespace Reflections { ResourceID::DDGI_ProbeResidencyPending, true }, { ResourceID::RTXReflectionNoise, true }, { ResourceID::RTXReflectionDirPdf, true }, + { ResourceID::GBuffer_Material, false }, + { ResourceID::RTXReflectionCoat, true }, }; static constexpr uint resource_count = std::size(resource_accesses); }; diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/PSSM/PSSM_Combine.h b/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/PSSM/PSSM_Combine.h index 7308996b9..8be1429ae 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/PSSM/PSSM_Combine.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/PSSM/PSSM_Combine.h @@ -34,6 +34,9 @@ namespace Shadows Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; @@ -70,6 +73,7 @@ namespace Shadows builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); if (!builder.exists(data.ShadowMask)) @@ -89,6 +93,7 @@ namespace Shadows FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex LightMask = FrameGraph::ChainIndex::Unresolved; @@ -102,6 +107,7 @@ namespace Shadows cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.LightMask = FrameGraph::TaskBuilder::cache_slot(data.LightMask, ResourceID::LightMask); @@ -122,6 +128,7 @@ namespace Shadows builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); builder.load(data.LightMask, ResourceID::LightMask, cache.LightMask); @@ -137,6 +144,7 @@ namespace Shadows { ResourceID::GBuffer_Albedo, false }, { ResourceID::GBuffer_Normals, false }, { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, { ResourceID::GBuffer_Speed, false }, { ResourceID::GBuffer_DepthMips, false }, { ResourceID::LightMask, false }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_Combine.h b/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_Combine.h index 274315f6a..0b55bf706 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_Combine.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_Combine.h @@ -32,6 +32,9 @@ namespace Shadows Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; @@ -74,6 +77,7 @@ namespace Shadows builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); builder.need(data.VSM_Atlas, FrameGraph::ResourceFlags::Read); @@ -94,6 +98,7 @@ namespace Shadows FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex VSM_Atlas = FrameGraph::ChainIndex::Unresolved; @@ -109,6 +114,7 @@ namespace Shadows cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.VSM_Atlas = FrameGraph::TaskBuilder::cache_slot(data.VSM_Atlas, ResourceID::VSM_Atlas); @@ -131,6 +137,7 @@ namespace Shadows builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); builder.load(data.VSM_Atlas, ResourceID::VSM_Atlas, cache.VSM_Atlas); @@ -148,6 +155,7 @@ namespace Shadows { ResourceID::GBuffer_Albedo, false }, { ResourceID::GBuffer_Normals, false }, { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, { ResourceID::GBuffer_Speed, false }, { ResourceID::GBuffer_DepthMips, false }, { ResourceID::VSM_Atlas, false }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_ShadowResolve.h b/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_ShadowResolve.h index c3767ad05..234a689ba 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_ShadowResolve.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_ShadowResolve.h @@ -35,6 +35,9 @@ namespace Shadows Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; @@ -96,6 +99,7 @@ namespace Shadows builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); builder.need(data.VSM_Atlas, FrameGraph::ResourceFlags::Read); @@ -140,6 +144,7 @@ namespace Shadows FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex VSM_Atlas = FrameGraph::ChainIndex::Unresolved; @@ -162,6 +167,7 @@ namespace Shadows cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.VSM_Atlas = FrameGraph::TaskBuilder::cache_slot(data.VSM_Atlas, ResourceID::VSM_Atlas); @@ -191,6 +197,7 @@ namespace Shadows builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); builder.load(data.VSM_Atlas, ResourceID::VSM_Atlas, cache.VSM_Atlas); @@ -215,6 +222,7 @@ namespace Shadows { ResourceID::GBuffer_Albedo, false }, { ResourceID::GBuffer_Normals, false }, { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, { ResourceID::GBuffer_Speed, false }, { ResourceID::GBuffer_DepthMips, false }, { ResourceID::VSM_Atlas, false }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_Unshadowed.h b/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_Unshadowed.h index 98e0a0770..76844f1e1 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_Unshadowed.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Shadows/VSM/VSM_Unshadowed.h @@ -32,6 +32,9 @@ namespace Shadows Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; @@ -60,6 +63,7 @@ namespace Shadows builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); builder.need(data.ResultTexture, FrameGraph::ResourceFlags::UnorderedAccess); @@ -74,6 +78,7 @@ namespace Shadows FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; FrameGraph::ChainIndex ResultTexture = FrameGraph::ChainIndex::Unresolved; @@ -84,6 +89,7 @@ namespace Shadows cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); cache.ResultTexture = FrameGraph::TaskBuilder::cache_slot(data.ResultTexture, ResourceID::ResultTexture); @@ -101,6 +107,7 @@ namespace Shadows builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); builder.load(data.ResultTexture, ResourceID::ResultTexture, cache.ResultTexture); @@ -113,6 +120,7 @@ namespace Shadows { ResourceID::GBuffer_Albedo, false }, { ResourceID::GBuffer_Normals, false }, { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, { ResourceID::GBuffer_Speed, false }, { ResourceID::GBuffer_DepthMips, false }, { ResourceID::ResultTexture, true }, diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSSRR_BlurH.h b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSSRR_BlurH.h new file mode 100644 index 000000000..c7e18c2a3 --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSSRR_BlurH.h @@ -0,0 +1,182 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "../../PassNodeBase.h" + +using namespace FrameGraph; + +namespace Passes +{ +namespace Subsurface +{ + + class SSSRR_BlurH : public PassNodeBase + { + public: + struct Context + { + + + Handlers::Texture GBuffer_Albedo = ResourceID::GBuffer_Albedo; + + + Handlers::Texture GBuffer_Normals = ResourceID::GBuffer_Normals; + + + Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + + + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; + + + Handlers::Texture GBuffer_DepthMips = ResourceID::GBuffer_DepthMips; + + + Handlers::StructuredBuffer SSSTiles = ResourceID::SSSTiles; + + + Handlers::Texture ResultTextureRTXNoise = ResourceID::ResultTextureRTXNoise; + + + Handlers::Texture SSSTemp = ResourceID::SSSTemp; + + + // Resources this pass always needs whenever it runs, generated from + // each field's own [Always=X] annotation (further gated by [Optional=X] + // when present -- a raw bool expression, e.g. builder.exists(...) or a + // context-read flag, deciding whether this specific field is actually + // needed this frame), or, for a View-typed field (e.g. `GBuffer + // gbuffer;`), every leaf the View itself marks [Always=X] that this + // pass's own [Write=...] on that field doesn't already cover. A field + // that ALSO carries [Size]/[Format] (so create_always() below creates + // it under its own [Optional] condition) gets the negated condition + // here instead -- "need what some other instance/frame already + // created" is the complement of "create it this time." Called by + // TypedPass::setup() after setup_func returns true - not a + // substitute for setup_func's own need()/create() calls for anything + // else conditional. + static void need_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); + builder.need(data.SSSTiles, FrameGraph::ResourceFlags::Read); + builder.need(data.ResultTextureRTXNoise, FrameGraph::ResourceFlags::Read); + } + + // Resources this pass always creates with a fixed desc, generated from + // each field's own [Size]/[Format] annotation (plus [Always] for the + // creation flags, or [Always]+[Recreate]+[RecreateFlags] for a field + // that needs its original chain link before recreating a new one). A + // field that ALSO carries [Optional] creates only under that + // condition -- need_always() (above) emits a complementary need() for + // the SAME field under the negated condition, using the same + // [Always] flags, e.g. a [Multiple] pass where instance 0 creates a + // shared resource and every other instance just needs it (see + // PSSM_Cascade, pssm.prism). Called by TypedPass::setup() after + // setup_func returns true - not a substitute for setup_func's own + // create()/recreate() calls for anything whose Desc depends on + // runtime state. + static void create_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.create(data.SSSTemp, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess); + } + // Which chain link each handler field resolved to, one named slot per + // field. Filled from a live frame's finished Context and applied on a + // replayed one, so a replay neither re-runs create_always/need_always nor + // depends on the order they made their calls in. The id is not stored: + // the field fixes it. + struct Cache + { + FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTiles = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex ResultTextureRTXNoise = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTemp = FrameGraph::ChainIndex::Unresolved; + }; + + static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) + { + cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); + cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); + cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); + cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); + cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); + cache.SSSTiles = FrameGraph::TaskBuilder::cache_slot(data.SSSTiles, ResourceID::SSSTiles); + cache.ResultTextureRTXNoise = FrameGraph::TaskBuilder::cache_slot(data.ResultTextureRTXNoise, ResourceID::ResultTextureRTXNoise); + cache.SSSTemp = FrameGraph::TaskBuilder::cache_slot(data.SSSTemp, ResourceID::SSSTemp); + } + + // Replay counterpart of create_always/need_always. A field this pass + // creates gets its desc recomputed on the link the cache names, so descs + // follow the current context instead of being stored in the plan; every + // other field is only pointed at its link. Each link is created by exactly + // one pass and nothing here reads another resource's desc, so passes can + // load in any order. A [Recreate] without [Size]/[Format] copies the + // previous link's desc, which LoadGraph does once every pass has loaded. + static void load_from_cache([[maybe_unused]] Context& data, [[maybe_unused]] const Cache& cache, [[maybe_unused]] const FrameGraph::TaskBuilder& builder) + { + builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); + builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); + builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); + builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); + builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); + builder.load(data.SSSTiles, ResourceID::SSSTiles, cache.SSSTiles); + builder.load(data.ResultTextureRTXNoise, ResourceID::ResultTextureRTXNoise, cache.ResultTextureRTXNoise); + builder.create_versioned(data.SSSTemp, cache.SSSTemp, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }); + } + + // Resources this pass touches, in declaration order, each paired with + // whether the pass writes it (own [Write], or the view usage's + // [Write] / [Write = {leaves...}] for resources inside a view group). + static inline const FrameGraph::ResourceAccess resource_accesses[] = { + { ResourceID::GBuffer_Albedo, false }, + { ResourceID::GBuffer_Normals, false }, + { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, + { ResourceID::GBuffer_Speed, false }, + { ResourceID::GBuffer_DepthMips, false }, + { ResourceID::SSSTiles, false }, + { ResourceID::ResultTextureRTXNoise, false }, + { ResourceID::SSSTemp, true }, + }; + static constexpr uint resource_count = std::size(resource_accesses); + }; + + + std::span GetUsedResourcesList() const override + { + return std::span(Context::resource_accesses, Context::resource_count); + } + + static constexpr LiteralWStr Name{L"SSSRR_BlurH"}; + + static constexpr PassID ID = PassID::SSSRR_BlurH; + + + using setup_func_type = std::function; + using render_func_type = std::function; + + render_func_type render_func; + + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + }; +} + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSSRR_BlurV.h b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSSRR_BlurV.h new file mode 100644 index 000000000..a891bb9e0 --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSSRR_BlurV.h @@ -0,0 +1,173 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "../../PassNodeBase.h" + +using namespace FrameGraph; + +namespace Passes +{ +namespace Subsurface +{ + + class SSSRR_BlurV : public PassNodeBase + { + public: + struct Context + { + + + Handlers::Texture GBuffer_Albedo = ResourceID::GBuffer_Albedo; + + + Handlers::Texture GBuffer_Normals = ResourceID::GBuffer_Normals; + + + Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + + + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; + + + Handlers::Texture GBuffer_DepthMips = ResourceID::GBuffer_DepthMips; + + + Handlers::StructuredBuffer SSSTiles = ResourceID::SSSTiles; + + + Handlers::Texture SSSTemp = ResourceID::SSSTemp; + + + Handlers::Texture RTXCombineSpecular = ResourceID::RTXCombineSpecular; + + + Handlers::Texture ResultTextureRTXNoise = ResourceID::ResultTextureRTXNoise; + + + // Resources this pass always needs whenever it runs, generated from + // each field's own [Always=X] annotation (further gated by [Optional=X] + // when present -- a raw bool expression, e.g. builder.exists(...) or a + // context-read flag, deciding whether this specific field is actually + // needed this frame), or, for a View-typed field (e.g. `GBuffer + // gbuffer;`), every leaf the View itself marks [Always=X] that this + // pass's own [Write=...] on that field doesn't already cover. A field + // that ALSO carries [Size]/[Format] (so create_always() below creates + // it under its own [Optional] condition) gets the negated condition + // here instead -- "need what some other instance/frame already + // created" is the complement of "create it this time." Called by + // TypedPass::setup() after setup_func returns true - not a + // substitute for setup_func's own need()/create() calls for anything + // else conditional. + static void need_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); + builder.need(data.SSSTiles, FrameGraph::ResourceFlags::Read); + builder.need(data.SSSTemp, FrameGraph::ResourceFlags::Read); + builder.need(data.RTXCombineSpecular, FrameGraph::ResourceFlags::Read); + builder.need(data.ResultTextureRTXNoise, FrameGraph::ResourceFlags::UnorderedAccess); + } + // Which chain link each handler field resolved to, one named slot per + // field. Filled from a live frame's finished Context and applied on a + // replayed one, so a replay neither re-runs create_always/need_always nor + // depends on the order they made their calls in. The id is not stored: + // the field fixes it. + struct Cache + { + FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTiles = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTemp = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex RTXCombineSpecular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex ResultTextureRTXNoise = FrameGraph::ChainIndex::Unresolved; + }; + + static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) + { + cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); + cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); + cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); + cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); + cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); + cache.SSSTiles = FrameGraph::TaskBuilder::cache_slot(data.SSSTiles, ResourceID::SSSTiles); + cache.SSSTemp = FrameGraph::TaskBuilder::cache_slot(data.SSSTemp, ResourceID::SSSTemp); + cache.RTXCombineSpecular = FrameGraph::TaskBuilder::cache_slot(data.RTXCombineSpecular, ResourceID::RTXCombineSpecular); + cache.ResultTextureRTXNoise = FrameGraph::TaskBuilder::cache_slot(data.ResultTextureRTXNoise, ResourceID::ResultTextureRTXNoise); + } + + // Replay counterpart of create_always/need_always. A field this pass + // creates gets its desc recomputed on the link the cache names, so descs + // follow the current context instead of being stored in the plan; every + // other field is only pointed at its link. Each link is created by exactly + // one pass and nothing here reads another resource's desc, so passes can + // load in any order. A [Recreate] without [Size]/[Format] copies the + // previous link's desc, which LoadGraph does once every pass has loaded. + static void load_from_cache([[maybe_unused]] Context& data, [[maybe_unused]] const Cache& cache, [[maybe_unused]] const FrameGraph::TaskBuilder& builder) + { + builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); + builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); + builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); + builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); + builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); + builder.load(data.SSSTiles, ResourceID::SSSTiles, cache.SSSTiles); + builder.load(data.SSSTemp, ResourceID::SSSTemp, cache.SSSTemp); + builder.load(data.RTXCombineSpecular, ResourceID::RTXCombineSpecular, cache.RTXCombineSpecular); + builder.load(data.ResultTextureRTXNoise, ResourceID::ResultTextureRTXNoise, cache.ResultTextureRTXNoise); + } + + // Resources this pass touches, in declaration order, each paired with + // whether the pass writes it (own [Write], or the view usage's + // [Write] / [Write = {leaves...}] for resources inside a view group). + static inline const FrameGraph::ResourceAccess resource_accesses[] = { + { ResourceID::GBuffer_Albedo, false }, + { ResourceID::GBuffer_Normals, false }, + { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, + { ResourceID::GBuffer_Speed, false }, + { ResourceID::GBuffer_DepthMips, false }, + { ResourceID::SSSTiles, false }, + { ResourceID::SSSTemp, false }, + { ResourceID::RTXCombineSpecular, false }, + { ResourceID::ResultTextureRTXNoise, true }, + }; + static constexpr uint resource_count = std::size(resource_accesses); + }; + + + std::span GetUsedResourcesList() const override + { + return std::span(Context::resource_accesses, Context::resource_count); + } + + static constexpr LiteralWStr Name{L"SSSRR_BlurV"}; + + static constexpr PassID ID = PassID::SSSRR_BlurV; + + + using setup_func_type = std::function; + using render_func_type = std::function; + + render_func_type render_func; + + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + }; +} + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_BlurH.h b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_BlurH.h new file mode 100644 index 000000000..b87037254 --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_BlurH.h @@ -0,0 +1,182 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "../../PassNodeBase.h" + +using namespace FrameGraph; + +namespace Passes +{ +namespace Subsurface +{ + + class SSS_BlurH : public PassNodeBase + { + public: + struct Context + { + + + Handlers::Texture GBuffer_Albedo = ResourceID::GBuffer_Albedo; + + + Handlers::Texture GBuffer_Normals = ResourceID::GBuffer_Normals; + + + Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + + + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; + + + Handlers::Texture GBuffer_DepthMips = ResourceID::GBuffer_DepthMips; + + + Handlers::StructuredBuffer SSSTiles = ResourceID::SSSTiles; + + + Handlers::Texture ResultTexture = ResourceID::ResultTexture; + + + Handlers::Texture SSSTemp = ResourceID::SSSTemp; + + + // Resources this pass always needs whenever it runs, generated from + // each field's own [Always=X] annotation (further gated by [Optional=X] + // when present -- a raw bool expression, e.g. builder.exists(...) or a + // context-read flag, deciding whether this specific field is actually + // needed this frame), or, for a View-typed field (e.g. `GBuffer + // gbuffer;`), every leaf the View itself marks [Always=X] that this + // pass's own [Write=...] on that field doesn't already cover. A field + // that ALSO carries [Size]/[Format] (so create_always() below creates + // it under its own [Optional] condition) gets the negated condition + // here instead -- "need what some other instance/frame already + // created" is the complement of "create it this time." Called by + // TypedPass::setup() after setup_func returns true - not a + // substitute for setup_func's own need()/create() calls for anything + // else conditional. + static void need_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); + builder.need(data.SSSTiles, FrameGraph::ResourceFlags::Read); + builder.need(data.ResultTexture, FrameGraph::ResourceFlags::Read); + } + + // Resources this pass always creates with a fixed desc, generated from + // each field's own [Size]/[Format] annotation (plus [Always] for the + // creation flags, or [Always]+[Recreate]+[RecreateFlags] for a field + // that needs its original chain link before recreating a new one). A + // field that ALSO carries [Optional] creates only under that + // condition -- need_always() (above) emits a complementary need() for + // the SAME field under the negated condition, using the same + // [Always] flags, e.g. a [Multiple] pass where instance 0 creates a + // shared resource and every other instance just needs it (see + // PSSM_Cascade, pssm.prism). Called by TypedPass::setup() after + // setup_func returns true - not a substitute for setup_func's own + // create()/recreate() calls for anything whose Desc depends on + // runtime state. + static void create_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.create(data.SSSTemp, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }, FrameGraph::ResourceFlags::UnorderedAccess); + } + // Which chain link each handler field resolved to, one named slot per + // field. Filled from a live frame's finished Context and applied on a + // replayed one, so a replay neither re-runs create_always/need_always nor + // depends on the order they made their calls in. The id is not stored: + // the field fixes it. + struct Cache + { + FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTiles = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex ResultTexture = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTemp = FrameGraph::ChainIndex::Unresolved; + }; + + static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) + { + cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); + cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); + cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); + cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); + cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); + cache.SSSTiles = FrameGraph::TaskBuilder::cache_slot(data.SSSTiles, ResourceID::SSSTiles); + cache.ResultTexture = FrameGraph::TaskBuilder::cache_slot(data.ResultTexture, ResourceID::ResultTexture); + cache.SSSTemp = FrameGraph::TaskBuilder::cache_slot(data.SSSTemp, ResourceID::SSSTemp); + } + + // Replay counterpart of create_always/need_always. A field this pass + // creates gets its desc recomputed on the link the cache names, so descs + // follow the current context instead of being stored in the plan; every + // other field is only pointed at its link. Each link is created by exactly + // one pass and nothing here reads another resource's desc, so passes can + // load in any order. A [Recreate] without [Size]/[Format] copies the + // previous link's desc, which LoadGraph does once every pass has loaded. + static void load_from_cache([[maybe_unused]] Context& data, [[maybe_unused]] const Cache& cache, [[maybe_unused]] const FrameGraph::TaskBuilder& builder) + { + builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); + builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); + builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); + builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); + builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); + builder.load(data.SSSTiles, ResourceID::SSSTiles, cache.SSSTiles); + builder.load(data.ResultTexture, ResourceID::ResultTexture, cache.ResultTexture); + builder.create_versioned(data.SSSTemp, cache.SSSTemp, { ivec3(builder.graph->get_context().frame_size, 0), HAL::Format::R16G16B16A16_FLOAT, 1, 1 }); + } + + // Resources this pass touches, in declaration order, each paired with + // whether the pass writes it (own [Write], or the view usage's + // [Write] / [Write = {leaves...}] for resources inside a view group). + static inline const FrameGraph::ResourceAccess resource_accesses[] = { + { ResourceID::GBuffer_Albedo, false }, + { ResourceID::GBuffer_Normals, false }, + { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, + { ResourceID::GBuffer_Speed, false }, + { ResourceID::GBuffer_DepthMips, false }, + { ResourceID::SSSTiles, false }, + { ResourceID::ResultTexture, false }, + { ResourceID::SSSTemp, true }, + }; + static constexpr uint resource_count = std::size(resource_accesses); + }; + + + std::span GetUsedResourcesList() const override + { + return std::span(Context::resource_accesses, Context::resource_count); + } + + static constexpr LiteralWStr Name{L"SSS_BlurH"}; + + static constexpr PassID ID = PassID::SSS_BlurH; + + + using setup_func_type = std::function; + using render_func_type = std::function; + + render_func_type render_func; + + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + }; +} + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_BlurV.h b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_BlurV.h new file mode 100644 index 000000000..7e3ef5daf --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_BlurV.h @@ -0,0 +1,165 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "../../PassNodeBase.h" + +using namespace FrameGraph; + +namespace Passes +{ +namespace Subsurface +{ + + class SSS_BlurV : public PassNodeBase + { + public: + struct Context + { + + + Handlers::Texture GBuffer_Albedo = ResourceID::GBuffer_Albedo; + + + Handlers::Texture GBuffer_Normals = ResourceID::GBuffer_Normals; + + + Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + + + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; + + + Handlers::Texture GBuffer_DepthMips = ResourceID::GBuffer_DepthMips; + + + Handlers::StructuredBuffer SSSTiles = ResourceID::SSSTiles; + + + Handlers::Texture SSSTemp = ResourceID::SSSTemp; + + + Handlers::Texture ResultTexture = ResourceID::ResultTexture; + + + // Resources this pass always needs whenever it runs, generated from + // each field's own [Always=X] annotation (further gated by [Optional=X] + // when present -- a raw bool expression, e.g. builder.exists(...) or a + // context-read flag, deciding whether this specific field is actually + // needed this frame), or, for a View-typed field (e.g. `GBuffer + // gbuffer;`), every leaf the View itself marks [Always=X] that this + // pass's own [Write=...] on that field doesn't already cover. A field + // that ALSO carries [Size]/[Format] (so create_always() below creates + // it under its own [Optional] condition) gets the negated condition + // here instead -- "need what some other instance/frame already + // created" is the complement of "create it this time." Called by + // TypedPass::setup() after setup_func returns true - not a + // substitute for setup_func's own need()/create() calls for anything + // else conditional. + static void need_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); + builder.need(data.SSSTiles, FrameGraph::ResourceFlags::Read); + builder.need(data.SSSTemp, FrameGraph::ResourceFlags::Read); + builder.need(data.ResultTexture, FrameGraph::ResourceFlags::UnorderedAccess); + } + // Which chain link each handler field resolved to, one named slot per + // field. Filled from a live frame's finished Context and applied on a + // replayed one, so a replay neither re-runs create_always/need_always nor + // depends on the order they made their calls in. The id is not stored: + // the field fixes it. + struct Cache + { + FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTiles = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTemp = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex ResultTexture = FrameGraph::ChainIndex::Unresolved; + }; + + static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) + { + cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); + cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); + cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); + cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); + cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); + cache.SSSTiles = FrameGraph::TaskBuilder::cache_slot(data.SSSTiles, ResourceID::SSSTiles); + cache.SSSTemp = FrameGraph::TaskBuilder::cache_slot(data.SSSTemp, ResourceID::SSSTemp); + cache.ResultTexture = FrameGraph::TaskBuilder::cache_slot(data.ResultTexture, ResourceID::ResultTexture); + } + + // Replay counterpart of create_always/need_always. A field this pass + // creates gets its desc recomputed on the link the cache names, so descs + // follow the current context instead of being stored in the plan; every + // other field is only pointed at its link. Each link is created by exactly + // one pass and nothing here reads another resource's desc, so passes can + // load in any order. A [Recreate] without [Size]/[Format] copies the + // previous link's desc, which LoadGraph does once every pass has loaded. + static void load_from_cache([[maybe_unused]] Context& data, [[maybe_unused]] const Cache& cache, [[maybe_unused]] const FrameGraph::TaskBuilder& builder) + { + builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); + builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); + builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); + builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); + builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); + builder.load(data.SSSTiles, ResourceID::SSSTiles, cache.SSSTiles); + builder.load(data.SSSTemp, ResourceID::SSSTemp, cache.SSSTemp); + builder.load(data.ResultTexture, ResourceID::ResultTexture, cache.ResultTexture); + } + + // Resources this pass touches, in declaration order, each paired with + // whether the pass writes it (own [Write], or the view usage's + // [Write] / [Write = {leaves...}] for resources inside a view group). + static inline const FrameGraph::ResourceAccess resource_accesses[] = { + { ResourceID::GBuffer_Albedo, false }, + { ResourceID::GBuffer_Normals, false }, + { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, + { ResourceID::GBuffer_Speed, false }, + { ResourceID::GBuffer_DepthMips, false }, + { ResourceID::SSSTiles, false }, + { ResourceID::SSSTemp, false }, + { ResourceID::ResultTexture, true }, + }; + static constexpr uint resource_count = std::size(resource_accesses); + }; + + + std::span GetUsedResourcesList() const override + { + return std::span(Context::resource_accesses, Context::resource_count); + } + + static constexpr LiteralWStr Name{L"SSS_BlurV"}; + + static constexpr PassID ID = PassID::SSS_BlurV; + + + using setup_func_type = std::function; + using render_func_type = std::function; + + render_func_type render_func; + + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + }; +} + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_Classify.h b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_Classify.h new file mode 100644 index 000000000..67a3b29a6 --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/Subsurface/SSS_Classify.h @@ -0,0 +1,166 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "../../PassNodeBase.h" + +using namespace FrameGraph; + +namespace Passes +{ +namespace Subsurface +{ + + class SSS_Classify : public PassNodeBase + { + public: + struct Context + { + + + Handlers::Texture GBuffer_Albedo = ResourceID::GBuffer_Albedo; + + + Handlers::Texture GBuffer_Normals = ResourceID::GBuffer_Normals; + + + Handlers::Texture GBuffer_Specular = ResourceID::GBuffer_Specular; + + + Handlers::Texture GBuffer_Material = ResourceID::GBuffer_Material; + + + Handlers::Texture GBuffer_Speed = ResourceID::GBuffer_Speed; + + + Handlers::Texture GBuffer_DepthMips = ResourceID::GBuffer_DepthMips; + + + Handlers::StructuredBuffer SSSTiles = ResourceID::SSSTiles; + + + // Resources this pass always needs whenever it runs, generated from + // each field's own [Always=X] annotation (further gated by [Optional=X] + // when present -- a raw bool expression, e.g. builder.exists(...) or a + // context-read flag, deciding whether this specific field is actually + // needed this frame), or, for a View-typed field (e.g. `GBuffer + // gbuffer;`), every leaf the View itself marks [Always=X] that this + // pass's own [Write=...] on that field doesn't already cover. A field + // that ALSO carries [Size]/[Format] (so create_always() below creates + // it under its own [Optional] condition) gets the negated condition + // here instead -- "need what some other instance/frame already + // created" is the complement of "create it this time." Called by + // TypedPass::setup() after setup_func returns true - not a + // substitute for setup_func's own need()/create() calls for anything + // else conditional. + static void need_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.need(data.GBuffer_Albedo, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Normals, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Specular, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Material, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_Speed, FrameGraph::ResourceFlags::Read); + builder.need(data.GBuffer_DepthMips, FrameGraph::ResourceFlags::None); + } + + // Resources this pass always creates with a fixed desc, generated from + // each field's own [Size]/[Format] annotation (plus [Always] for the + // creation flags, or [Always]+[Recreate]+[RecreateFlags] for a field + // that needs its original chain link before recreating a new one). A + // field that ALSO carries [Optional] creates only under that + // condition -- need_always() (above) emits a complementary need() for + // the SAME field under the negated condition, using the same + // [Always] flags, e.g. a [Multiple] pass where instance 0 creates a + // shared resource and every other instance just needs it (see + // PSSM_Cascade, pssm.prism). Called by TypedPass::setup() after + // setup_func returns true - not a substitute for setup_func's own + // create()/recreate() calls for anything whose Desc depends on + // runtime state. + static void create_always(Context& data, FrameGraph::TaskBuilder& builder) + { + builder.create(data.SSSTiles, { Math::Area(Math::DivideByMultiple(builder.graph->get_context().frame_size, 16)), true }, FrameGraph::ResourceFlags::UnorderedAccess); + } + // Which chain link each handler field resolved to, one named slot per + // field. Filled from a live frame's finished Context and applied on a + // replayed one, so a replay neither re-runs create_always/need_always nor + // depends on the order they made their calls in. The id is not stored: + // the field fixes it. + struct Cache + { + FrameGraph::ChainIndex GBuffer_Albedo = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Normals = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Specular = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Material = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_Speed = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex GBuffer_DepthMips = FrameGraph::ChainIndex::Unresolved; + FrameGraph::ChainIndex SSSTiles = FrameGraph::ChainIndex::Unresolved; + }; + + static void save_to_cache([[maybe_unused]] const Context& data, [[maybe_unused]] Cache& cache) + { + cache.GBuffer_Albedo = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo); + cache.GBuffer_Normals = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Normals, ResourceID::GBuffer_Normals); + cache.GBuffer_Specular = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Specular, ResourceID::GBuffer_Specular); + cache.GBuffer_Material = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Material, ResourceID::GBuffer_Material); + cache.GBuffer_Speed = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_Speed, ResourceID::GBuffer_Speed); + cache.GBuffer_DepthMips = FrameGraph::TaskBuilder::cache_slot(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips); + cache.SSSTiles = FrameGraph::TaskBuilder::cache_slot(data.SSSTiles, ResourceID::SSSTiles); + } + + // Replay counterpart of create_always/need_always. A field this pass + // creates gets its desc recomputed on the link the cache names, so descs + // follow the current context instead of being stored in the plan; every + // other field is only pointed at its link. Each link is created by exactly + // one pass and nothing here reads another resource's desc, so passes can + // load in any order. A [Recreate] without [Size]/[Format] copies the + // previous link's desc, which LoadGraph does once every pass has loaded. + static void load_from_cache([[maybe_unused]] Context& data, [[maybe_unused]] const Cache& cache, [[maybe_unused]] const FrameGraph::TaskBuilder& builder) + { + builder.load(data.GBuffer_Albedo, ResourceID::GBuffer_Albedo, cache.GBuffer_Albedo); + builder.load(data.GBuffer_Normals, ResourceID::GBuffer_Normals, cache.GBuffer_Normals); + builder.load(data.GBuffer_Specular, ResourceID::GBuffer_Specular, cache.GBuffer_Specular); + builder.load(data.GBuffer_Material, ResourceID::GBuffer_Material, cache.GBuffer_Material); + builder.load(data.GBuffer_Speed, ResourceID::GBuffer_Speed, cache.GBuffer_Speed); + builder.load(data.GBuffer_DepthMips, ResourceID::GBuffer_DepthMips, cache.GBuffer_DepthMips); + builder.create_versioned(data.SSSTiles, cache.SSSTiles, { Math::Area(Math::DivideByMultiple(builder.graph->get_context().frame_size, 16)), true }); + } + + // Resources this pass touches, in declaration order, each paired with + // whether the pass writes it (own [Write], or the view usage's + // [Write] / [Write = {leaves...}] for resources inside a view group). + static inline const FrameGraph::ResourceAccess resource_accesses[] = { + { ResourceID::GBuffer_Albedo, false }, + { ResourceID::GBuffer_Normals, false }, + { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Material, false }, + { ResourceID::GBuffer_Speed, false }, + { ResourceID::GBuffer_DepthMips, false }, + { ResourceID::SSSTiles, true }, + }; + static constexpr uint resource_count = std::size(resource_accesses); + }; + + + std::span GetUsedResourcesList() const override + { + return std::span(Context::resource_accesses, Context::resource_count); + } + + static constexpr LiteralWStr Name{L"SSS_Classify"}; + + static constexpr PassID ID = PassID::SSS_Classify; + + + using setup_func_type = std::function; + using render_func_type = std::function; + + render_func_type render_func; + + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + }; +} + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass_defaults.cpp b/sources/RenderSystem/FrameGraph/autogen/pass_defaults.cpp index 5e43e4352..f622d6303 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass_defaults.cpp +++ b/sources/RenderSystem/FrameGraph/autogen/pass_defaults.cpp @@ -453,6 +453,13 @@ FrameGraph::SetupResult PassSetupDefault::setup( + Passes::FrameGraphTests::FGT_ProduceShadow::Context& data, FrameGraph::TaskBuilder& builder) +{ + return FrameGraph::SetupResult::NeedsRender; +} + + FrameGraph::SetupResult PassSetupDefault::setup( Passes::FrameGraphTests::FGT_AppendTiles::Context& data, FrameGraph::TaskBuilder& builder) { @@ -702,6 +709,49 @@ FrameGraph::SetupResult PassSetupDefault::setup( } +FrameGraph::SetupResult PassDefault::setup( + Passes::Subsurface::SSS_Classify::Context& data, FrameGraph::TaskBuilder& builder) +{ + return FrameGraph::SetupResult::NeedsRender; +} + + +FrameGraph::SetupResult PassDefault::setup( + Passes::Subsurface::SSS_BlurH::Context& data, FrameGraph::TaskBuilder& builder) +{ + if (!(builder.graph->get_context().upscaler_type != ::Post::Upscale::UpscalerType::DLSSRR)) + return FrameGraph::SetupResult::Disabled; + return FrameGraph::SetupResult::NeedsRender; +} + + +FrameGraph::SetupResult PassDefault::setup( + Passes::Subsurface::SSS_BlurV::Context& data, FrameGraph::TaskBuilder& builder) +{ + if (!(builder.graph->get_context().upscaler_type != ::Post::Upscale::UpscalerType::DLSSRR)) + return FrameGraph::SetupResult::Disabled; + return FrameGraph::SetupResult::NeedsRender; +} + + +FrameGraph::SetupResult PassDefault::setup( + Passes::Subsurface::SSSRR_BlurH::Context& data, FrameGraph::TaskBuilder& builder) +{ + if (!(builder.graph->get_context().upscaler_type == ::Post::Upscale::UpscalerType::DLSSRR)) + return FrameGraph::SetupResult::Disabled; + return FrameGraph::SetupResult::NeedsRender; +} + + +FrameGraph::SetupResult PassDefault::setup( + Passes::Subsurface::SSSRR_BlurV::Context& data, FrameGraph::TaskBuilder& builder) +{ + if (!(builder.graph->get_context().upscaler_type == ::Post::Upscale::UpscalerType::DLSSRR)) + return FrameGraph::SetupResult::Disabled; + return FrameGraph::SetupResult::NeedsRender; +} + + FrameGraph::SetupResult PassSetupDefault::setup( Passes::Editor::stencil_renderer::Context& data, FrameGraph::TaskBuilder& builder) { diff --git a/sources/RenderSystem/FrameGraph/autogen/pass_defaults.h b/sources/RenderSystem/FrameGraph/autogen/pass_defaults.h index c2d127c0e..f2d4da639 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass_defaults.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass_defaults.h @@ -383,6 +383,86 @@ struct PassDefault }; +template<> +struct PassDefault +{ + static constexpr bool enabled = true; + static constexpr FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + + // setup() is declared here regardless -- if this pass carries + // [RunAlways]/[SetupCondition]/[RenderCondition], its DEFINITION is + // generated into autogen/pass_defaults.cpp instead of a hand-written one + // in some .cpp (see that file's own header comment for why it's a + // separate translation unit, not inlined here). + static FrameGraph::SetupResult setup(Passes::Subsurface::SSS_Classify::Context& data, FrameGraph::TaskBuilder& builder); + static void render(Passes::Subsurface::SSS_Classify::Context& data, FrameGraph::FrameContext& context); +}; + + +template<> +struct PassDefault +{ + static constexpr bool enabled = true; + static constexpr FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + + // setup() is declared here regardless -- if this pass carries + // [RunAlways]/[SetupCondition]/[RenderCondition], its DEFINITION is + // generated into autogen/pass_defaults.cpp instead of a hand-written one + // in some .cpp (see that file's own header comment for why it's a + // separate translation unit, not inlined here). + static FrameGraph::SetupResult setup(Passes::Subsurface::SSS_BlurH::Context& data, FrameGraph::TaskBuilder& builder); + static void render(Passes::Subsurface::SSS_BlurH::Context& data, FrameGraph::FrameContext& context); +}; + + +template<> +struct PassDefault +{ + static constexpr bool enabled = true; + static constexpr FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + + // setup() is declared here regardless -- if this pass carries + // [RunAlways]/[SetupCondition]/[RenderCondition], its DEFINITION is + // generated into autogen/pass_defaults.cpp instead of a hand-written one + // in some .cpp (see that file's own header comment for why it's a + // separate translation unit, not inlined here). + static FrameGraph::SetupResult setup(Passes::Subsurface::SSS_BlurV::Context& data, FrameGraph::TaskBuilder& builder); + static void render(Passes::Subsurface::SSS_BlurV::Context& data, FrameGraph::FrameContext& context); +}; + + +template<> +struct PassDefault +{ + static constexpr bool enabled = true; + static constexpr FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + + // setup() is declared here regardless -- if this pass carries + // [RunAlways]/[SetupCondition]/[RenderCondition], its DEFINITION is + // generated into autogen/pass_defaults.cpp instead of a hand-written one + // in some .cpp (see that file's own header comment for why it's a + // separate translation unit, not inlined here). + static FrameGraph::SetupResult setup(Passes::Subsurface::SSSRR_BlurH::Context& data, FrameGraph::TaskBuilder& builder); + static void render(Passes::Subsurface::SSSRR_BlurH::Context& data, FrameGraph::FrameContext& context); +}; + + +template<> +struct PassDefault +{ + static constexpr bool enabled = true; + static constexpr FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; + + // setup() is declared here regardless -- if this pass carries + // [RunAlways]/[SetupCondition]/[RenderCondition], its DEFINITION is + // generated into autogen/pass_defaults.cpp instead of a hand-written one + // in some .cpp (see that file's own header comment for why it's a + // separate translation unit, not inlined here). + static FrameGraph::SetupResult setup(Passes::Subsurface::SSSRR_BlurV::Context& data, FrameGraph::TaskBuilder& builder); + static void render(Passes::Subsurface::SSSRR_BlurV::Context& data, FrameGraph::FrameContext& context); +}; + + template<> struct PassDefault { @@ -1154,6 +1234,17 @@ struct PassSetupDefault }; +template<> +struct PassSetupDefault +{ + static constexpr bool enabled = true; + + // Generated from this pass's [RunAlways]/[SetupCondition]/ + // [RenderCondition]; defined in autogen/pass_defaults.cpp. + static FrameGraph::SetupResult setup(Passes::FrameGraphTests::FGT_ProduceShadow::Context& data, FrameGraph::TaskBuilder& builder); +}; + + template<> struct PassSetupDefault { diff --git a/sources/RenderSystem/FrameGraph/autogen/pass_ids.h b/sources/RenderSystem/FrameGraph/autogen/pass_ids.h index 5dc3f150a..d3715180d 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass_ids.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass_ids.h @@ -66,6 +66,7 @@ namespace FrameGraph FGT_CopyResultToSink, FGT_ProduceBloomInputs, FGT_ProduceGBuffer, + FGT_ProduceShadow, FGT_AppendTiles, FGT_CopyTileList, FGT_CaptureAlbedo, @@ -99,6 +100,11 @@ namespace FrameGraph CubeMapDownsample, CubeMapEnviromentProcessor, SMAA, + SSS_Classify, + SSS_BlurH, + SSS_BlurV, + SSSRR_BlurH, + SSSRR_BlurV, stencil_renderer, Tonemap, UI_PreDraw, diff --git a/sources/RenderSystem/FrameGraph/autogen/passes.ixx b/sources/RenderSystem/FrameGraph/autogen/passes.ixx index 592a1bec9..0cad73435 100644 --- a/sources/RenderSystem/FrameGraph/autogen/passes.ixx +++ b/sources/RenderSystem/FrameGraph/autogen/passes.ixx @@ -70,6 +70,7 @@ export import "../defines.h"; #include "pass/FrameGraphTests/FGT_CopyResultToSink.h" #include "pass/FrameGraphTests/FGT_ProduceBloomInputs.h" #include "pass/FrameGraphTests/FGT_ProduceGBuffer.h" +#include "pass/FrameGraphTests/FGT_ProduceShadow.h" #include "pass/FrameGraphTests/FGT_AppendTiles.h" #include "pass/FrameGraphTests/FGT_CopyTileList.h" #include "pass/FrameGraphTests/FGT_CaptureAlbedo.h" @@ -103,6 +104,11 @@ export import "../defines.h"; #include "pass/Environment/CubeMapDownsample.h" #include "pass/Environment/CubeMapEnviromentProcessor.h" #include "pass/Post/AA/SMAA.h" +#include "pass/Subsurface/SSS_Classify.h" +#include "pass/Subsurface/SSS_BlurH.h" +#include "pass/Subsurface/SSS_BlurV.h" +#include "pass/Subsurface/SSSRR_BlurH.h" +#include "pass/Subsurface/SSSRR_BlurV.h" #include "pass/Editor/stencil_renderer.h" #include "pass/Post/Tonemap.h" #include "pass/UI/UI_PreDraw.h" @@ -157,6 +163,7 @@ export import "../defines.h"; #include "pass/FrameGraphTests/FGT_TonemapPipeline.pipeline.h" #include "pass/FrameGraphTests/FGT_BloomPipeline.pipeline.h" #include "pass/FrameGraphTests/FGT_VSMUnshadowedPipeline.pipeline.h" +#include "pass/FrameGraphTests/FGT_NRDShadowCombinePipeline.pipeline.h" #include "pass/FrameGraphTests/FGT_VSMDepthAnalysisPipeline.pipeline.h" #include "pass/FrameGraphTests/FGT_VSMFullChainPipeline.pipeline.h" #include "pass/FrameGraphTests/FGT_TileListPipeline.pipeline.h" @@ -420,6 +427,10 @@ export namespace Passes using ::Passes::FrameGraphTests::FGT_ProduceGBuffer; } namespace FrameGraphTests + { + using ::Passes::FrameGraphTests::FGT_ProduceShadow; + } + namespace FrameGraphTests { using ::Passes::FrameGraphTests::FGT_AppendTiles; } @@ -587,6 +598,26 @@ export namespace Passes using ::Passes::Post::AA::SMAA; } } + namespace Subsurface + { + using ::Passes::Subsurface::SSS_Classify; + } + namespace Subsurface + { + using ::Passes::Subsurface::SSS_BlurH; + } + namespace Subsurface + { + using ::Passes::Subsurface::SSS_BlurV; + } + namespace Subsurface + { + using ::Passes::Subsurface::SSSRR_BlurH; + } + namespace Subsurface + { + using ::Passes::Subsurface::SSSRR_BlurV; + } namespace Editor { using ::Passes::Editor::stencil_renderer; @@ -871,6 +902,10 @@ export namespace Pipelines using ::Pipelines::FrameGraphTests::FGT_VSMUnshadowedPipeline; } namespace FrameGraphTests + { + using ::Pipelines::FrameGraphTests::FGT_NRDShadowCombinePipeline; + } + namespace FrameGraphTests { using ::Pipelines::FrameGraphTests::FGT_VSMDepthAnalysisPipeline; } diff --git a/sources/RenderSystem/FrameGraph/autogen/resource_ids.h b/sources/RenderSystem/FrameGraph/autogen/resource_ids.h index f0e7663e5..27b8f19b2 100644 --- a/sources/RenderSystem/FrameGraph/autogen/resource_ids.h +++ b/sources/RenderSystem/FrameGraph/autogen/resource_ids.h @@ -16,6 +16,7 @@ namespace FrameGraph GBuffer_Specular, GBuffer_Speed, GBuffer_ObjectID, + GBuffer_Material, GBuffer_DepthMips, GBuffer_HiZ, GBuffer_HiZ_UAV, @@ -67,6 +68,7 @@ namespace FrameGraph FGT_MipChain, FGT_CubeMipChain, sky_cubemap_filtered_diffuse, + VSM_ShadowDenoised, FGT_TileList, FGT_TileListCopy, RTXIndirectNoise, @@ -86,7 +88,6 @@ namespace FrameGraph RTXIndirectDenoisedPreview, VSM_ShadowNoise, VSM_PCSS_ShadowNoise, - VSM_ShadowDenoised, PSSM_Depths, PSSM_Cameras, LightMask, @@ -101,12 +102,15 @@ namespace FrameGraph sky_cubemap, SMAA_edges, SMAA_blend, + SSSTiles, + SSSTemp, + ResultTextureRTXNoise, + RTXCombineSpecular, axis_id_buffer, ExposureHistogram, UI_PreDraw_Sync, NormalRoughness, SpecularAlbedo, - ResultTextureRTXNoise, GBuffer_TempColor, GBuffer_HalfDepth, GBuffer_HalfNormals, @@ -120,6 +124,7 @@ namespace FrameGraph VoxelDebug, VoxelLighted, RTXReflectionDirPdf, + RTXReflectionCoat, RTXShadowNoise, RTXIndirectNoiseHalf, VoxelAlbedo, @@ -153,6 +158,7 @@ namespace FrameGraph "GBuffer_Specular", "GBuffer_Speed", "GBuffer_ObjectID", + "GBuffer_Material", "GBuffer_DepthMips", "GBuffer_HiZ", "GBuffer_HiZ_UAV", @@ -204,6 +210,7 @@ namespace FrameGraph "FGT_MipChain", "FGT_CubeMipChain", "sky_cubemap_filtered_diffuse", + "VSM_ShadowDenoised", "FGT_TileList", "FGT_TileListCopy", "RTXIndirectNoise", @@ -223,7 +230,6 @@ namespace FrameGraph "RTXIndirectDenoisedPreview", "VSM_ShadowNoise", "VSM_PCSS_ShadowNoise", - "VSM_ShadowDenoised", "PSSM_Depths", "PSSM_Cameras", "LightMask", @@ -238,12 +244,15 @@ namespace FrameGraph "sky_cubemap", "SMAA_edges", "SMAA_blend", + "SSSTiles", + "SSSTemp", + "ResultTextureRTXNoise", + "RTXCombineSpecular", "axis_id_buffer", "ExposureHistogram", "UI_PreDraw_Sync", "NormalRoughness", "SpecularAlbedo", - "ResultTextureRTXNoise", "GBuffer_TempColor", "GBuffer_HalfDepth", "GBuffer_HalfNormals", @@ -257,6 +266,7 @@ namespace FrameGraph "VoxelDebug", "VoxelLighted", "RTXReflectionDirPdf", + "RTXReflectionCoat", "RTXShadowNoise", "RTXIndirectNoiseHalf", "VoxelAlbedo", diff --git a/sources/RenderSystem/GUI/Elements/ColorPicker.cpp b/sources/RenderSystem/GUI/Elements/ColorPicker.cpp index 2da3a00a6..479b92611 100644 --- a/sources/RenderSystem/GUI/Elements/ColorPicker.cpp +++ b/sources/RenderSystem/GUI/Elements/ColorPicker.cpp @@ -75,34 +75,95 @@ namespace GUI rgb_label->height_size = size_type::FIXED; add_child(rgb_label); - float default_h = std::max(4 * (SLIDER_HEIGHT + SLIDER_GAP) - SLIDER_GAP, PREVIEW_SIZE) - + LABEL_GAP + LABEL_HEIGHT; width_size = size_type::FIXED; height_size = size_type::FIXED; - size = {300, default_h}; + size = {300, 0}; - float w = size->x; - float slider_x = PREVIEW_SIZE + 8.0f; - float slider_w = w - slider_x - 4.0f; - float row_stride = SLIDER_HEIGHT + SLIDER_GAP; + relayout(); + } + + void color_picker::set_channels(color_channels value) + { + channels = value; + relayout(); + update_display(); + } + + void color_picker::set_display(color_display value) + { + display = value; + relayout(); + } - preview->pos = {0, 0}; + // Positions are absolute, so everything is recomputed from the two + // flag sets: sliders stack from the top for each enabled channel, the + // swatch sits to their left when shown, and the read-out labels go on + // one row underneath. The picker's height follows its content. + void color_picker::relayout() + { + const bool show_preview = check(display & color_display::Preview); + const bool show_hex = check(display & color_display::Hex); + const bool show_rgb = check(display & color_display::Rgb); + + float_slider* sliders[4] = {slider_r.get(), slider_g.get(), slider_b.get(), slider_a.get()}; + const color_channels bits[4] = {color_channels::R, color_channels::G, color_channels::B, color_channels::A}; + + const float w = size->x; + const float slider_x = show_preview ? PREVIEW_SIZE + 8.0f : 0.0f; + const float slider_w = w - slider_x - 4.0f; + const float row_stride = SLIDER_HEIGHT + SLIDER_GAP; + + int rows = 0; - float_slider* sliders[4] = {slider_r.get(), slider_g.get(), slider_b.get(), slider_a.get()}; for (int i = 0; i < 4; i++) { - sliders[i]->pos = {slider_x, i * row_stride}; + bool enabled = check(channels & bits[i]); + sliders[i]->visible = enabled; + + if (!enabled) + continue; + + sliders[i]->pos = {slider_x, rows * row_stride}; sliders[i]->size = {slider_w, SLIDER_HEIGHT}; + rows++; } - float label_y = PREVIEW_SIZE + LABEL_GAP; - float label_w = w / 2.0f - 4.0f; + preview->visible = show_preview; + preview->pos = {0, 0}; + + float content_h = rows ? rows * row_stride - SLIDER_GAP : 0.0f; + + if (show_preview) + content_h = std::max(content_h, PREVIEW_SIZE); - hex_label->pos = {0, label_y}; - hex_label->size = {label_w, LABEL_HEIGHT}; + hex_label->visible = show_hex; + rgb_label->visible = show_rgb; - rgb_label->pos = {label_w + 8.0f, label_y}; - rgb_label->size = {w - label_w - 8.0f, LABEL_HEIGHT}; + float height = content_h; + + if (show_hex || show_rgb) + { + float label_y = content_h > 0 ? content_h + LABEL_GAP : 0.0f; + height = label_y + LABEL_HEIGHT; + + if (show_hex && show_rgb) + { + float label_w = w / 2.0f - 4.0f; + + hex_label->pos = {0, label_y}; + hex_label->size = {label_w, LABEL_HEIGHT}; + rgb_label->pos = {label_w + 8.0f, label_y}; + rgb_label->size = {w - label_w - 8.0f, LABEL_HEIGHT}; + } + else + { + auto* only = show_hex ? hex_label.get() : rgb_label.get(); + only->pos = {0, label_y}; + only->size = {w, LABEL_HEIGHT}; + } + } + + size = {w, height}; } float4 color_picker::get_color() const @@ -164,13 +225,36 @@ namespace GUI int gi = static_cast(current_color.y * 255.0f + 0.5f); int bi = static_cast(current_color.z * 255.0f + 0.5f); + int ai = static_cast(current_color.w * 255.0f + 0.5f); + const bool alpha = check(channels & color_channels::A); + char hex[16]; - snprintf(hex, sizeof(hex), "#%02X%02X%02X", ri, gi, bi); + + if (alpha) + snprintf(hex, sizeof(hex), "#%02X%02X%02X%02X", ri, gi, bi, ai); + else + snprintf(hex, sizeof(hex), "#%02X%02X%02X", ri, gi, bi); + hex_label->text = hex; - char rgb[32]; - snprintf(rgb, sizeof(rgb), "rgb(%d %d %d)", ri, gi, bi); - rgb_label->text = rgb; + // Only the enabled channels, named by their letters: "rgb(26 26 26)", + // "rgba(26 26 26 255)", "rg(26 26)". + std::string names; + std::string values; + const int component[4] = {ri, gi, bi, ai}; + const char letters[4] = {'r', 'g', 'b', 'a'}; + const color_channels bits[4] = {color_channels::R, color_channels::G, color_channels::B, color_channels::A}; + + for (int i = 0; i < 4; i++) + { + if (!check(channels & bits[i])) + continue; + + names += letters[i]; + values += (values.empty() ? "" : " ") + std::to_string(component[i]); + } + + rgb_label->text = names + "(" + values + ")"; } } // namespace Elements diff --git a/sources/RenderSystem/GUI/Elements/ColorPicker.ixx b/sources/RenderSystem/GUI/Elements/ColorPicker.ixx index 48fe1ec8f..8f4fabb03 100644 --- a/sources/RenderSystem/GUI/Elements/ColorPicker.ixx +++ b/sources/RenderSystem/GUI/Elements/ColorPicker.ixx @@ -8,6 +8,29 @@ export namespace GUI { namespace Elements { + // Which components the picker edits. A disabled channel keeps whatever + // value set_color() gave it and just isn't shown or editable. + enum class color_channels : uint + { + None = 0, + R = 1 << 0, + G = 1 << 1, + B = 1 << 2, + A = 1 << 3, + RGB = 7, + RGBA = 15 + }; + + // Which read-outs are shown besides the sliders. + enum class color_display : uint + { + None = 0, + Preview = 1 << 0, // swatch + Hex = 1 << 1, // #RRGGBB (#RRGGBBAA with alpha) + Rgb = 1 << 2, // rgb(r g b) of the enabled channels, 0-255 + All = 7 + }; + class color_picker : public base { colored_rect::ptr preview; @@ -21,9 +44,13 @@ export namespace GUI float4 current_color = {0, 0, 0, 1}; bool setting_color = false; + color_channels channels = color_channels::RGBA; + color_display display = color_display::All; + void on_slider_changed(); void update_slider_gradients(); void update_display(); + void relayout(); public: using ptr = s_ptr; @@ -35,6 +62,9 @@ export namespace GUI float4 get_color() const; void set_color(float4 color); + + void set_channels(color_channels value); + void set_display(color_display value); }; } } diff --git a/sources/RenderSystem/GUI/Elements/FlowGraph/Canvas.cpp b/sources/RenderSystem/GUI/Elements/FlowGraph/Canvas.cpp index f7778f950..a5dc0e679 100644 --- a/sources/RenderSystem/GUI/Elements/FlowGraph/Canvas.cpp +++ b/sources/RenderSystem/GUI/Elements/FlowGraph/Canvas.cpp @@ -523,10 +523,40 @@ bool GUI::Elements::FlowGraph::canvas::on_mouse_action(mouse_action action, mous auto& nodes = ::FlowGraph::FlowSystem::get().get_all(); vec2 t = (pos - vec2(contents->get_render_bounds().pos)) / contents->scale; + // A '/' in a registered name nests the entry in submenus + // ("MaterialX/math/add_float" -> MaterialX > math > add_float); + // names without one stay at the top level in registration order. + std::map submenus; + for (auto& n : nodes) { auto nptr = &n; - menu->add_item(n.name)->on_click = [nptr, t, this](menu_list_element::ptr e) + + std::vector parts; + std::stringstream name_stream(n.name); + std::string part; + + while (std::getline(name_stream, part, '/')) + parts.push_back(part); + + if (parts.empty()) + continue; + + menu_list::ptr parent = menu; + std::string path; + + for (size_t i = 0; i + 1 < parts.size(); i++) + { + path += parts[i] + "/"; + auto it = submenus.find(path); + + if (it == submenus.end()) + it = submenus.emplace(path, parent->add_item(parts[i])->get_menu()).first; + + parent = it->second; + } + + parent->add_item(parts.back())->on_click = [nptr, t, this](menu_list_element::ptr e) { ::FlowGraph::window::ptr node = nptr->create(); diff --git a/sources/RenderSystem/Graphics.ixx b/sources/RenderSystem/Graphics.ixx index 3c1bf02db..a1e7a32c4 100644 --- a/sources/RenderSystem/Graphics.ixx +++ b/sources/RenderSystem/Graphics.ixx @@ -36,6 +36,7 @@ export import :AssetExplorer; export import :Materials.Values; +export import :Materials.MaterialX; export import :Materials.UniversalMaterial; export import :Materials.PreviewSession; export import :Materials.ShaderMaterial; @@ -66,6 +67,7 @@ export import :NRD_SIGMA_Execute; export import :NRD_IndirectCombine; export import :NRD_ShadowCombine; export import :RTXCombine; +export import :SSS; export import :BlueNoise; export import :SceneFrameRenderer; diff --git a/sources/RenderSystem/Materials/MaterialX.cpp b/sources/RenderSystem/Materials/MaterialX.cpp new file mode 100644 index 000000000..17b2e6f0f --- /dev/null +++ b/sources/RenderSystem/Materials/MaterialX.cpp @@ -0,0 +1,1587 @@ +module; +// MaterialX's headers are mutually dependent (Element.h / Interface.h / +// Definition.h include each other) and don't survive being imported as +// header units, so they're included textually here instead of through a +// wrapper module. +#include +#include +#include +#include +#include + +module Graphics:Materials.MaterialX; + +import Core; +import GUI; +import :TextureAsset; +import :Materials.UniversalMaterial; + +REGISTER_TYPE(MaterialXNode); +CEREAL_REGISTER_POLYMORPHIC_RELATION(MaterialNode, MaterialXNode); +CEREAL_FORCE_REGISTER(MaterialXNode); +CEREAL_FORCE_REGISTER_RELATION(MaterialNode, MaterialXNode); + +namespace +{ + bool map_type(const std::string& mx_type, ShaderParamType& out) + { + auto& p = ShaderParams::get(); + + if (mx_type == "float") out = p.FLOAT1; + else if (mx_type == "integer") out = p.INT; + else if (mx_type == "boolean") out = p.BOOL; + else if (mx_type == "vector2") out = p.FLOAT2; + else if (mx_type == "vector3" || mx_type == "color3") out = p.FLOAT3; + else if (mx_type == "vector4" || mx_type == "color4") out = p.FLOAT4; + else return false; + + return true; + } + + // "0.5, 0.5, 0.5" -> {0.5, 0.5, 0.5}; anything unparsable is skipped. + std::vector parse_floats(const std::string& s) + { + std::vector res; + std::stringstream ss(s); + std::string item; + + while (std::getline(ss, item, ',')) + { + try { res.push_back(std::stof(item)); } + catch (const std::exception&) + { + // MaterialX booleans. + if (item.find("true") != std::string::npos) + res.push_back(1.0f); + else if (item.find("false") != std::string::npos) + res.push_back(0.0f); + } + } + + return res; + } + + // MaterialX geometry properties the graph can supply: its own texcoord and + // position inputs. Normals/tangents/bitangents have no graph input yet, so + // those stay at the zero default. + bool is_uv_prop(const std::string& p) { return p == "UV0"; } + bool is_position_prop(const std::string& p) { return p == "Pobject" || p == "Pworld"; } + + bool is_geometry_category(const std::string& c) { return c == "normal" || c == "tangent" || c == "bitangent"; } + + // Texture sampling isn't a plain function call (the texture is a bindless + // index and the shader supplies the sampler and lod), so these two nodes + // emit their own code in MaterialXNode::operator(). Their `file` input is + // a TEXTURE port fed by a TextureAssetNode, like SamplingNode's. + bool is_image_category(const std::string& c) { return c == "image" || c == "tiledimage" || c == "triplanarprojection"; } + + // Shading-frame global (mx_stdlib.hlsl) behind a defaultgeomprop: all of + // N/T/B in any space resolve to the world-space frame. Anything else + // (Pview, ...) has no source yet and stays zero. + std::string geometry_global(const std::string& prop) + { + if (prop == "Nworld" || prop == "Nobject") + return "mx_geom_normal"; + + if (prop == "Tworld" || prop == "Tobject") + return "mx_geom_tangent"; + + if (prop == "Bworld" || prop == "Bobject") + return "mx_geom_bitangent"; + + return "float3(0.0, 0.0, 0.0)"; + } + + std::string trim(const std::string& s) + { + auto b = s.find_first_not_of(" \t"); + auto e = s.find_last_not_of(" \t"); + return b == std::string::npos ? std::string() : s.substr(b, e - b + 1); + } + + // "1" stays valid inside float3(1, 0, 0), but a bare scalar default of + // "1" would be an int literal -- spell floats as floats. + std::string float_literal(const std::string& s) + { + auto t = trim(s); + + if (t.empty()) + return "0.0"; + + if (t.find_first_of(".eE") == std::string::npos && t.find_first_of("nN") == std::string::npos) + t += ".0"; + + return t; + } + + // HLSL literal for a MaterialX default value string ("0.5, 0.5, 0.5"). + // An absent value becomes zero of that type. + std::string default_literal(const ShaderParamType& type, const std::string& value) + { + if (type.M == 3) // INT + return trim(value).empty() ? "0" : trim(value); + + if (type.M == 4) // BOOL + return trim(value) == "true" ? "true" : "false"; + + if (type.N == 1) + return float_literal(value); + + std::vector comps; + std::stringstream ss(value); + std::string item; + + while (std::getline(ss, item, ',')) + comps.push_back(float_literal(item)); + + // Missing or short values pad with zero rather than emit a call + // with the wrong component count. + while ((int)comps.size() < type.N) + comps.push_back("0.0"); + + std::string res = type.name + "("; + + for (int i = 0; i < type.N; i++) + res += (i ? ", " : "") + comps[i]; + + return res + ")"; + } + + // Names of every mx_* identifier that appears followed by '(' in the + // stdlib source: the set of HLSL functions a nodedef can resolve to. + std::set scan_functions(const std::string& source) + { + std::set res; + + for (size_t pos = source.find("mx_"); pos != std::string::npos; pos = source.find("mx_", pos + 1)) + { + if (pos > 0 && (std::isalnum((unsigned char)source[pos - 1]) || source[pos - 1] == '_')) + continue; + + size_t end = pos; + + while (end < source.size() && (std::isalnum((unsigned char)source[end]) || source[end] == '_')) + end++; + + size_t next = end; + + while (next < source.size() && (source[next] == ' ' || source[next] == '\t')) + next++; + + if (next < source.size() && source[next] == '(') + res.insert(source.substr(pos, end - pos)); + } + + return res; + } +} + +size_t MaterialXLibrary::register_nodes(const std::filesystem::path& root, const std::filesystem::path& hlsl_stdlib) +{ + defs.clear(); + index.clear(); + + std::string hlsl; + { + std::ifstream file(hlsl_stdlib, std::ios::binary); + + if (!file) + { + Log::get() << Log::LEVEL_ERROR << "MaterialX: cannot read " << hlsl_stdlib.string() << Log::endl; + return 0; + } + + std::stringstream ss; + ss << file.rdbuf(); + hlsl = ss.str(); + + // Implementations spill into generated files pulled in with #include + // "name" (relative to the stdlib file), which count as implemented too. + for (size_t pos = hlsl.find("#include \""); pos != std::string::npos; pos = hlsl.find("#include \"", pos + 1)) + { + size_t begin = pos + 10; + size_t end = hlsl.find('"', begin); + + if (end == std::string::npos) + break; + + std::ifstream included(hlsl_stdlib.parent_path() / hlsl.substr(begin, end - begin), std::ios::binary); + + if (!included) + continue; + + std::stringstream inc; + inc << included.rdbuf(); + hlsl += "\n" + inc.str(); + } + } + + auto implemented = scan_functions(hlsl); + library_root = root; + + try + { + auto doc = MaterialX::createDocument(); + MaterialX::loadLibraries({ MaterialX::FilePath("libraries/stdlib") }, MaterialX::FileSearchPath(root.string()), doc); + + for (auto& nd : doc->getNodeDefs()) + { + std::string function = "mx_" + nd->getNodeString(); + const std::string name = nd->getName(); + + if (name.size() > 2 && name.compare(name.size() - 2, 2, "FA") == 0) + function += "_fa"; + + // A nodedef family whose members differ only in output type + // (worleynoise2d float/vector2/vector3) can't share one HLSL + // overload set, so it may name its variants mx__. + const bool is_image = is_image_category(nd->getNodeString()); + + if (is_image) + function = "mx_image"; + else if (!implemented.count(function)) + { + auto outputs = nd->getActiveOutputs(); + + if (outputs.empty()) + continue; + + function += "_" + outputs[0]->getType(); + } + + if (!is_image && !implemented.count(function)) + continue; + + MxNodeDef def; + def.name = name; + def.category = nd->getNodeString(); + def.group = nd->getNodeGroup(); + def.function = function; + + bool supported = true; + + for (auto& in : nd->getActiveInputs()) + { + MxPortDef port; + port.name = in->getName(); + port.mx_type = in->getType(); + + // The geometry nodes' `space` (model/object/world) is a string, + // which the graph can't carry. They always answer in world + // space, so the input is simply not exposed. + if (port.mx_type == "string" && is_geometry_category(def.category)) + continue; + + if (is_image) + { + // Address modes, layers and frame ranges are strings the + // graph can't carry; the engine's sampler decides wrapping. + if (port.mx_type == "string" || port.name == "frameoffset") + continue; + + if (port.mx_type == "filename") + { + port.type = ShaderParams::get().TEXTURE; + def.inputs.push_back(port); + continue; + } + } + + supported &= map_type(port.mx_type, port.type); + port.default_hlsl = supported ? default_literal(port.type, in->getValueString()) : ""; + port.geomprop = in->getAttribute("defaultgeomprop"); + + // Frame vectors come from the shader's shading-frame globals + // (mx_set_geometry); an unconnected input reads them directly. + if (!port.geomprop.empty() && !is_uv_prop(port.geomprop) && !is_position_prop(port.geomprop)) + port.default_hlsl = geometry_global(port.geomprop); + + // int and bool inputs are editable constants too, held as floats. + if (supported && (port.type.M == 3 || port.type.M == 4)) + { + std::string text = in->getValueString(); + port.default_values = { text == "true" ? 1.0f : text == "false" ? 0.0f : (parse_floats(text).empty() ? 0.0f : parse_floats(text)[0]) }; + + if (port.type.M == 3) + { + auto hi = parse_floats(in->getAttribute("uisoftmax")); + port.soft_min = 0.0f; + port.soft_max = hi.empty() ? std::max(8.0f, port.default_values[0] * 2) : hi[0]; + + // enum="Distance,Solid" enumvalues="0,1" + std::stringstream names(in->getAttribute("enum")); + std::string item; + + while (std::getline(names, item, ',')) + port.enum_names.push_back(trim(item)); + + port.enum_values = parse_floats(in->getAttribute("enumvalues")); + + if (port.enum_values.size() != port.enum_names.size()) + { + port.enum_values.clear(); + + for (size_t e = 0; e < port.enum_names.size(); e++) + port.enum_values.push_back((float)e); + } + } + } + + if (supported && port.type.M == 1) + { + port.default_values = parse_floats(in->getValueString()); + port.default_values.resize(port.type.N, 0.0f); + + // Slider range: MaterialX's own soft limits when given, + // otherwise [0,1] widened to cover the default. + auto lo = parse_floats(in->getAttribute("uisoftmin")); + auto hi = parse_floats(in->getAttribute("uisoftmax")); + float d = port.default_values[0]; + + port.soft_min = lo.empty() ? std::min(0.0f, d * 2) : lo[0]; + port.soft_max = hi.empty() ? std::max(1.0f, d * 2) : hi[0]; + } + + def.inputs.push_back(port); + } + + // A nodedef declares either explicit outputs (multi-output + // nodes such as separate3) or just a `type`, meaning one + // output named "out". + auto outputs = nd->getActiveOutputs(); + + if (outputs.empty()) + { + MxPortDef port; + port.name = "out"; + port.mx_type = nd->getType(); + supported &= map_type(port.mx_type, port.type); + def.outputs.push_back(port); + } + else + for (auto& out : outputs) + { + MxPortDef port; + port.name = out->getName(); + port.mx_type = out->getType(); + supported &= map_type(port.mx_type, port.type); + def.outputs.push_back(port); + } + + if (!supported || def.outputs.empty()) + continue; + + index[def.name] = defs.size(); + defs.push_back(std::move(def)); + } + } + catch (const std::exception& e) + { + Log::get() << Log::LEVEL_ERROR << "MaterialX: failed to load standard library from " + << root.string() << ": " << e.what() << Log::endl; + defs.clear(); + index.clear(); + return 0; + } + + // Registered in (group, name) order so each submenu reads alphabetically + // regardless of the order nodedefs appear in the library file. + std::vector ordered; + + for (auto& def : defs) + ordered.push_back(&def); + + std::sort(ordered.begin(), ordered.end(), [](const MxNodeDef* a, const MxNodeDef* b) + { + return std::tie(a->group, a->name) < std::tie(b->group, b->name); + }); + + for (auto* def_ptr : ordered) + { + auto& def = *def_ptr; + std::string short_name = def.name.rfind("ND_", 0) == 0 ? def.name.substr(3) : def.name; + auto copy = def; + + FlowGraph::FlowSystem::get().register_node("MaterialX/" + def.group + "/" + short_name, [copy]() -> FlowGraph::window::ptr + { + return std::make_shared(copy); + }); + } + + Log::get() << Log::LEVEL_INFO << "MaterialX: registered " << defs.size() << " nodes" << Log::endl; + return defs.size(); +} + +const MxNodeDef* MaterialXLibrary::find(const std::string& nodedef_name) const +{ + auto it = index.find(nodedef_name); + return it == index.end() ? nullptr : &defs[it->second]; +} + +MaterialXNode::MaterialXNode(const MxNodeDef& def) : nodedef(def.name) +{ + // Title shown on the node: the nodedef name without MaterialX's ND_ prefix. + name = def.name.rfind("ND_", 0) == 0 ? def.name.substr(3) : def.name; + + for (auto& in : def.inputs) + register_input(in.name, in.type); + + for (auto& out : def.outputs) + register_output(out.name, out.type); + + uniforms.assign(def.inputs.size(), nullptr); + ensure_uniforms(); +} + +void MaterialXNode::ensure_uniforms() +{ + auto* def = MaterialXLibrary::get().find(nodedef); + + if (!def) + return; + + uniforms.resize(def->inputs.size()); + + for (size_t i = 0; i < def->inputs.size(); i++) + { + auto& in = def->inputs[i]; + + if (uniforms[i] || in.type.M == 2 || !in.geomprop.empty()) + continue; + + auto u = std::make_shared(); + u->name = in.name; + // int and bool are stored as float and cast where used. + u->type = (in.type.M == 3 || in.type.M == 4) ? ShaderParams::get().FLOAT1 : in.type; + + for (int c = 0; c < u->type.N && c < (int)in.default_values.size(); c++) + std::memcpy(&u->value.raw_data[c], &in.default_values[c], sizeof(float)); + + uniforms[i] = u; + } +} + +void MaterialXNode::on_add(::FlowGraph::graph* g) +{ + MaterialNode::on_add(g); + + auto* mat_graph = dynamic_cast(g); + auto* def = MaterialXLibrary::get().find(nodedef); + + if (!mat_graph || !def) + return; + + // A real, visible wire rather than a hidden default: the graph's + // texcoord/position values only exist as that function's own parameters, + // so there is nothing to name from inside this node. + for (size_t i = 0; i < def->inputs.size() && i < input_parametres.size(); i++) + { + if (input_parametres[i]->has_input()) + continue; + + if (is_uv_prop(def->inputs[i].geomprop)) + mat_graph->get_texcoord()->link(input_parametres[i]); + else if (is_position_prop(def->inputs[i].geomprop)) + mat_graph->get_position()->link(input_parametres[i]); + } +} + +GUI::base::ptr MaterialXNode::create_editor_window() +{ + auto* def = MaterialXLibrary::get().find(nodedef); + auto panel = std::make_shared(); + panel->docking = GUI::dock::TOP; + // The node body sizes itself from its children, so rows docked TOP have + // no parent width to stretch to unless the panel fixes one. + panel->width_size = GUI::size_type::FIXED; + panel->height_size = GUI::size_type::MATCH_CHILDREN; + bool any_slider = false; + + // The color picker is wider than a slider row needs to be. + bool any_color = false; + + for (size_t i = 0; def && i < def->inputs.size() && i < uniforms.size(); i++) + any_color |= uniforms[i] && (def->inputs[i].mx_type == "color3" || def->inputs[i].mx_type == "color4"); + + panel->size = { any_color ? 300.0f : 200.0f, 0 }; + + for (size_t i = 0; def && i < def->inputs.size() && i < uniforms.size(); i++) + { + auto uniform = uniforms[i]; + + if (!uniform) + continue; + + auto& port = def->inputs[i]; + + // Colors get the same picker the Vector node uses; alpha (color4) is + // the picker's own alpha slider, and is ignored for color3. + if (port.mx_type == "color3" || port.mx_type == "color4") + { + auto make_gap = [] + { + auto gap = std::make_shared(); + gap->docking = GUI::dock::TOP; + gap->height_size = GUI::size_type::FIXED; + gap->size = { 0, 8 }; + return gap; + }; + + panel->add_child(make_gap()); + + auto title = std::make_shared(); + title->text = port.name; + title->docking = GUI::dock::TOP; + title->height_size = GUI::size_type::FIXED; + title->size = { 0, 18 }; + title->magnet_text = FW1_LEFT | FW1_VCENTER | FW1_NOWORDWRAP; + panel->add_child(title); + + float rgba[4] = { 0, 0, 0, 1 }; + + for (int c = 0; c < port.type.N; c++) + std::memcpy(&rgba[c], &uniform->value.raw_data[c], sizeof(float)); + + const int components = port.type.N; + auto picker = std::make_shared(); + picker->docking = GUI::dock::TOP; + picker->set_channels(components == 3 ? GUI::Elements::color_channels::RGB : GUI::Elements::color_channels::RGBA); + picker->set_color(float4(rgba[0], rgba[1], rgba[2], rgba[3])); + picker->on_change.register_handler(nullptr, [uniform, components](float4 color) + { + float v[4] = { color.x, color.y, color.z, color.w }; + + for (int c = 0; c < components; c++) + std::memcpy(&uniform->value.raw_data[c], &v[c], sizeof(float)); + + uniform->on_change(uniform.get()); + }); + panel->add_child(picker); + panel->add_child(make_gap()); + + any_slider = true; + continue; + } + + // bool: a checkbox. int: a drop-down when MaterialX lists an enum, + // otherwise a slider that snaps to whole numbers. Both edit the float + // uniform the value is stored in. + if (port.type.M == 4) + { + float current; + std::memcpy(¤t, &uniform->value.raw_data[0], sizeof(float)); + + auto box = std::make_shared(); + box->docking = GUI::dock::TOP; + box->get_label()->text = port.name; + box->get_check()->set_checked(current != 0.0f); + box->on_check = [uniform](bool checked) + { + float v = checked ? 1.0f : 0.0f; + std::memcpy(&uniform->value.raw_data[0], &v, sizeof(float)); + uniform->on_change(uniform.get()); + }; + + panel->add_child(box); + any_slider = true; + continue; + } + + if (port.type.M == 3 && !port.enum_names.empty()) + { + float current; + std::memcpy(¤t, &uniform->value.raw_data[0], sizeof(float)); + + auto row = std::make_shared(); + row->docking = GUI::dock::TOP; + row->x_type = GUI::pos_x_type::LEFT; + + auto label = std::make_shared(); + label->text = port.name; + row->add_child(label); + + auto combo = std::make_shared(); + + for (size_t e = 0; e < port.enum_names.size(); e++) + { + auto item = combo->add_item(port.enum_names[e]); + float value = port.enum_values[e]; + item->on_select = [uniform, value]() + { + std::memcpy(&uniform->value.raw_data[0], &value, sizeof(float)); + uniform->on_change(uniform.get()); + }; + + if (value == current) + combo->get_label()->text = port.enum_names[e]; + } + + // combo_box does not size itself to its items. + float widest = 0; + for (auto& n : port.enum_names) + widest = std::max(widest, Text::Engine::get().measure(n, { 16, Text::Weight::Light }).x); + + combo->size = { widest + 35.0f, combo->size->y }; + row->add_child(combo); + + panel->add_child(row); + any_slider = true; + continue; + } + + for (int c = 0; c < port.type.N; c++) + { + float current; + std::memcpy(¤t, &uniform->value.raw_data[c], sizeof(float)); + + auto row = std::make_shared(); + row->docking = GUI::dock::TOP; + row->height_size = GUI::size_type::FIXED; + row->size = { 0, 22 }; + + auto label = std::make_shared(); + label->text = port.type.N == 1 ? port.name : port.name + "." + "xyzw"[c]; + label->docking = GUI::dock::LEFT; + label->width_size = GUI::size_type::FIXED; + label->size = { 70, 0 }; + label->magnet_text = FW1_LEFT | FW1_VCENTER | FW1_NOWORDWRAP; + row->add_child(label); + + auto slider = std::make_shared(); + slider->min = port.soft_min; + slider->max = port.soft_max; + slider->value = current; + slider->docking = GUI::dock::FILL; + slider->height_size = GUI::size_type::FIXED; + slider->size = { 0, 14 }; + slider->y_type = GUI::pos_y_type::CENTER; + const bool whole = port.type.M == 3; + slider->on_change = [uniform, c, whole](float v) + { + if (whole) + v = std::round(v); + + std::memcpy(&uniform->value.raw_data[c], &v, sizeof(float)); + uniform->on_change(uniform.get()); + }; + row->add_child(slider); + + panel->add_child(row); + any_slider = true; + } + } + + // No editable constants (only int/bool or geometry inputs): keep the + // node's default live preview. + if (!any_slider) + return MaterialNode::create_editor_window(); + + return panel; +} + +void MaterialXNode::reset_for() +{ + MaterialNode::reset_for(); + + auto* def = MaterialXLibrary::get().find(nodedef); + + if (!def) + return; + + // A node only runs once every input has been set, and an unconnected + // input is never set by anyone -- seed those with the nodedef default, + // exactly as if a constant node were wired in. + for (size_t i = 0; i < input_parametres.size() && i < def->inputs.size(); i++) + if (!input_parametres[i]->has_input()) + input_parametres[i]->put(shader_parameter(def->inputs[i].default_hlsl, def->inputs[i].type)); +} + +void MaterialXNode::operator()(MaterialContext* c) +{ + auto* def = MaterialXLibrary::get().find(nodedef); + + // The registry is rebuilt from the stdlib files at startup; a graph that + // references a nodedef this build doesn't implement just emits nothing. + if (!def) + { + Log::get() << Log::LEVEL_WARNING << "MaterialX: unknown nodedef " << nodedef << Log::endl; + return; + } + + auto mat_graph = static_cast(owner); + + std::string args; + std::vector values; + + for (size_t i = 0; i < def->inputs.size(); i++) + { + auto& port = input_parametres[i]; + + // Unconnected float inputs read their editable uniform; connected ones + // (and the int/bool baked defaults) use the value that arrived. + auto value = (!port->has_input() && i < uniforms.size() && uniforms[i]) + ? c->create_value(uniforms[i]) + : port->get(); + + values.push_back(value); + + if (def->inputs[i].type != ShaderParams::get().TEXTURE) + args += (i ? ", " : "") + coerce(value, def->inputs[i].type); + } + + if (is_image_category(def->category)) + { + auto find = [&](const char* input_name) + { + for (size_t i = 0; i < def->inputs.size(); i++) + if (def->inputs[i].name == input_name) + return (int)i; + + return -1; + }; + + auto get = [&](const char* input_name, const ShaderParamType& type) + { + int i = find(input_name); + return i < 0 ? std::string() : coerce(values[i], type); + }; + + auto& out_type = def->outputs[0].type; + + // triplanarprojection: the same image sampled along each axis from the + // position, blended by the normal (MaterialX's NG_triplanarprojection). + if (def->category == "triplanarprojection") + { + static const char* swizzle[] = { "", ".x", ".xy", ".xyz", "" }; + static const char* files[] = { "filex", "filey", "filez" }; + static const char* axis_name[] = { "x", "y", "z" }; + + const auto position = get("position", ShaderParams::get().FLOAT3); + const auto up = get("upaxis", ShaderParams::get().INT); + auto weights = mat_graph->add_value(ShaderParams::get().FLOAT3, + "mx_triplanar_weights(" + get("normal", ShaderParams::get().FLOAT3) + ", " + get("blend", ShaderParams::get().FLOAT1) + ")"); + + std::string sum; + + for (int axis = 0; axis < 3; axis++) + { + const int slot = find(files[axis]); + std::string color; + + if (slot >= 0 && input_parametres[slot]->has_input()) + { + auto sampled = mat_graph->add_value(ShaderParams::get().FLOAT4, + "sample(" + values[slot].name + ", Sampler, mx_triplanar_uv(" + std::to_string(axis) + ", " + position + ", " + up + "), lod)"); + color = sampled.name + swizzle[out_type.N]; + } + else + color = get("default", out_type); + + sum += (axis ? " + (" : "(") + color + ") * " + weights.name + "." + axis_name[axis]; + } + + output_parametres[0]->put(mat_graph->add_value(out_type, sum)); + return; + } + + const int file = find("file"); + + // Nothing linked to `file` (the seeded value of an unconnected input + // is not an input): the MaterialX `default`, as if the image were missing. + if (file < 0 || !input_parametres[file]->has_input()) + { + output_parametres[0]->put(mat_graph->add_value(out_type, get("default", out_type))); + return; + } + + std::string uv = get("texcoord", ShaderParams::get().FLOAT2); + + // tiledimage's nodegraph: ((uv * tiling - offset) / imagesize) * tilesize. + if (def->category == "tiledimage") + uv = "(((" + uv + " * " + get("uvtiling", ShaderParams::get().FLOAT2) + " - " + get("uvoffset", ShaderParams::get().FLOAT2) + + ") / " + get("realworldimagesize", ShaderParams::get().FLOAT2) + ") * " + get("realworldtilesize", ShaderParams::get().FLOAT2) + ")"; + + // Same call SamplingNode emits; `Sampler` and `lod` are the shader's own. + auto sampled = mat_graph->add_value(ShaderParams::get().FLOAT4, + "sample(" + values[file].name + ", Sampler, " + uv + ", lod)"); + + static const char* swizzle[] = { "", ".x", ".xy", ".xyz", "" }; + output_parametres[0]->put(mat_graph->add_value(out_type, sampled.name + swizzle[out_type.N])); + return; + } + + if (def->outputs.size() == 1) + { + auto res = mat_graph->add_value(def->outputs[0].type, def->function + "(" + args + ")"); + output_parametres[0]->put(res); + return; + } + + // Several outputs: declare each, then pass them as trailing out-parameters. + std::vector outs; + std::string call = def->function + "(" + args; + + for (auto& out : def->outputs) + { + outs.push_back(mat_graph->add_value(out.type)); + call += (call.back() == '(' ? "" : ", ") + outs.back().name; + } + + mat_graph->add_function(call + ")"); + + for (size_t i = 0; i < outs.size(); i++) + output_parametres[i]->put(outs[i]); +} + +bool MaterialXNode::set_constant(const std::string& input, const std::vector& values) +{ + auto* def = MaterialXLibrary::get().find(nodedef); + + if (!def) + return false; + + for (size_t i = 0; i < def->inputs.size() && i < uniforms.size(); i++) + { + if (def->inputs[i].name != input || !uniforms[i]) + continue; + + for (int c = 0; c < def->inputs[i].type.N && c < (int)values.size(); c++) + std::memcpy(&uniforms[i]->value.raw_data[c], &values[c], sizeof(float)); + + return true; + } + + return false; +} + +std::vector MaterialXNode::get_constant(const std::string& input) const +{ + auto* def = MaterialXLibrary::get().find(nodedef); + std::vector res; + + if (!def) + return res; + + for (size_t i = 0; i < def->inputs.size() && i < uniforms.size(); i++) + { + if (def->inputs[i].name != input || !uniforms[i]) + continue; + + res.resize(def->inputs[i].type.N); + std::memcpy(res.data(), &uniforms[i]->value.raw_data[0], res.size() * sizeof(float)); + } + + return res; +} + +::FlowGraph::input::ptr MaterialXNode::get_input_by_name(const std::string& input) +{ + auto* def = MaterialXLibrary::get().find(nodedef); + + if (def) + for (size_t i = 0; i < def->inputs.size(); i++) + if (def->inputs[i].name == input) + return get_input((int)i); + + return nullptr; +} + +// ---- .mtlx import ----------------------------------------------------------- + +namespace +{ + using OutPtr = ::FlowGraph::output::ptr; + + // A surface-shader parameter: wired to another node's output, or a constant. + struct Channel + { + OutPtr src; + std::vector value; + }; + + // Which parameter of each surface shader feeds which output of the + // material graph. The graph has one base color, metallic, roughness, + // glow, opacity and normal, plus the lobes and transmission outputs listed + // below; everything else the shader models is dropped. + struct SurfaceMapping + { + const char* category; + const char* base_color; + const char* base_weight; // multiplies base_color; "" = none + const char* metalness; + const char* roughness; + const char* emission_color; + const char* emission_weight; // "" = none + const char* opacity; + const char* normal; + const char* coat; + const char* coat_roughness; + const char* sheen; // weight; gltf_pbr has only a colour, see build_surface + const char* sheen_roughness; + const char* sheen_color; // colour whose saturation sets the sheen tint; "" = white + const char* anisotropy; // strength 0..1; "" = none + const char* anisotropy_rotation; // in turns; "" = none + const char* subsurface; // weight + const char* subsurface_radius; // colour: mean free path per channel + const char* subsurface_scale; // float multiplier on the radius + const char* subsurface_color; // diffuse colour of the scattering part; "" = none + const char* transmission; // weight; "" = none + const char* transmission_color; // tint / absorption colour + const char* transmission_depth; // distance over which a solid absorbs to that colour + const char* ior; + const char* thin_walled; // bool: true = thin-walled, false = solid volume + const char* thickness; // gltf_pbr: volume thickness, 0 = thin-walled + const char* alpha_mode; // gltf_pbr: 0 opaque, 1 mask, 2 blend; "" = decide from the opacity itself + const char* alpha_cutoff; // gltf_pbr + const char* opacity_threshold; // UsdPreviewSurface: > 0 cuts out at it, 0 blends + const char* film_weight; // thin film weight; "" = always on when there is a thickness + const char* film_thickness; // nanometres + const char* film_ior; + }; + + const SurfaceMapping surface_mappings[] = + { + { "standard_surface", "base_color", "base", "metalness", "specular_roughness", "emission_color", "emission", "opacity", "normal", "coat", "coat_roughness", "sheen", "sheen_roughness", "sheen_color", "specular_anisotropy", "specular_rotation", "subsurface", "subsurface_radius", "subsurface_scale", "subsurface_color", "transmission", "transmission_color", "transmission_depth", "specular_IOR", "thin_walled", "", "", "", "", "", "thin_film_thickness", "thin_film_IOR" }, + { "open_pbr_surface", "base_color", "base_weight", "base_metalness", "specular_roughness", "emission_color", "emission_luminance", "geometry_opacity", "geometry_normal", "coat_weight", "coat_roughness", "fuzz_weight", "fuzz_roughness", "fuzz_color", "specular_roughness_anisotropy", "", "subsurface_weight", "subsurface_radius_scale", "subsurface_radius", "subsurface_color", "transmission_weight", "transmission_color", "transmission_depth", "specular_ior", "geometry_thin_walled", "", "", "", "", "thin_film_weight", "thin_film_thickness", "thin_film_ior" }, + { "gltf_pbr", "base_color", "", "metallic", "roughness", "emissive", "emissive_strength", "alpha", "normal", "clearcoat", "clearcoat_roughness", "sheen_color", "sheen_roughness", "sheen_color", "", "", "", "", "", "", "transmission", "attenuation_color", "attenuation_distance", "ior", "", "thickness", "alpha_mode", "alpha_cutoff", "", "iridescence", "iridescence_thickness", "iridescence_ior" }, + { "UsdPreviewSurface", "diffuseColor", "", "metallic", "roughness", "emissiveColor", "", "opacity", "normal", "clearcoat", "clearcoatRoughness", "", "", "", "", "", "", "", "", "", "", "", "", "ior", "", "", "", "", "opacityThreshold", "", "", "" }, + }; + + class MtlxImporter + { + MaterialGraph::ptr graph; + std::filesystem::path dir; + std::map> built; + std::map textures; + std::vector> placed; + + void place(const ::FlowGraph::window::ptr& node, int depth) + { + graph->register_node(node); + placed.push_back({ node, depth }); + } + + // The graph auto-wires UV/position inputs when a node is added; an + // explicit connection from the file replaces that. + void unlink_auto(const ::FlowGraph::input::ptr& port) + { + graph->get_texcoord()->unlink(port); + graph->get_position()->unlink(port); + } + + static std::string value_string(const MaterialX::InputPtr& in) + { + if (in->hasInterfaceName()) + if (auto iface = in->getInterfaceInput()) + return iface->getResolvedValueString(); + + // A node is its value. It is folded here because a node with + // no connected input never runs in the graph, so one built as a node + // would feed the shader a value that was never declared. + if (auto n = in->getConnectedNode()) + if (n->getCategory() == "constant") + if (auto v = n->getInput("value")) + return v->getResolvedValueString(); + + return in->getResolvedValueString(); + } + + OutPtr texture_source(const std::string& file, bool to_linear) + { + std::filesystem::path full = std::filesystem::path(file).is_absolute() ? std::filesystem::path(file) : dir / file; + auto& asset = textures[full.string()]; + + if (!asset) + { + auto* raw = new TextureAsset(full); + raw->try_register(); + asset = raw->get_ptr(); + } + + auto node = std::make_shared(asset, to_linear); + place(node, 1); + return node->get_output(0); + } + + OutPtr output_of(const std::shared_ptr& node, const MxNodeDef& def, const std::string& name) + { + for (size_t i = 0; i < def.outputs.size(); i++) + if (name.empty() || def.outputs[i].name == name) + return node->get_output((int)i); + + return node->get_output(0); + } + + OutPtr build(const MaterialX::NodePtr& node, const std::string& out_name, int depth) + { + auto nodedef = node->getNodeDef(); + auto* def = nodedef ? MaterialXLibrary::get().find(nodedef->getName()) : nullptr; + + if (!def) + { + Log::get() << Log::LEVEL_WARNING << "MaterialX import: node '" << node->getName() << "' (" + << node->getCategory() << ") has no implementation here; its connections are left at defaults" << Log::endl; + return nullptr; + } + + auto& mx = built[node->getNamePath()]; + + if (!mx) + { + mx = std::make_shared(*def); + place(mx, depth); + + for (size_t i = 0; i < def->inputs.size(); i++) + if (auto in = node->getInput(def->inputs[i].name)) + connect(*mx, *def, i, in, depth + 1); + } + + return output_of(mx, *def, out_name); + } + + OutPtr source_of(const MaterialX::InputPtr& in, int depth) + { + if (in->hasInterfaceName()) + { + auto iface = in->getInterfaceInput(); + return iface ? source_of(iface, depth) : nullptr; + } + + if (!in->hasNodeName() && !in->hasNodeGraphString()) + return nullptr; + + if (auto n = in->getConnectedNode()) + { + if (n->getCategory() == "constant") + return nullptr; // see value_string + + return build(n, in->getOutputString(), depth); + } + + if (auto o = in->getConnectedOutput()) + if (auto n = o->getConnectedNode()) + return build(n, o->getOutputString(), depth); + + return nullptr; + } + + void connect(MaterialXNode& mx, const MxNodeDef& def, size_t i, const MaterialX::InputPtr& in, int depth) + { + auto port = mx.get_input((int)i); + auto& pd = def.inputs[i]; + + if (pd.type == ShaderParams::get().TEXTURE) + { + std::string file = value_string(in); + + if (file.empty()) + return; + + const bool color = def.outputs[0].mx_type == "color3" || def.outputs[0].mx_type == "color4"; + texture_source(file, color)->link(port); + return; + } + + if (auto src = source_of(in, depth)) + { + unlink_auto(port); + src->link(port); + return; + } + + std::string text = value_string(in); + + if (text.empty()) + return; + + if (pd.type.M == 1) + { + mx.set_constant(pd.name, parse_floats(text)); + return; + } + + // int/bool: held as a float uniform like the rest. + float v = 0; + + try { v = text == "true" ? 1.0f : text == "false" ? 0.0f : std::stof(text); } + catch (const std::exception&) { return; } + + mx.set_constant(pd.name, { v }); + } + + Channel channel(const MaterialX::NodePtr& shader, const char* name, int depth) + { + Channel res; + + if (!*name) + return res; + + if (auto in = shader->getInput(name)) + { + if ((res.src = source_of(in, depth))) + return res; + + res.value = parse_floats(value_string(in)); + } + + if (res.value.empty()) + if (auto nd = shader->getNodeDef()) + // Active, not plain getInput: standard_surface's nodedef + // inherits its inputs from a versioned parent nodedef. + if (auto di = nd->getActiveInput(name)) + res.value = parse_floats(di->getValueString()); + + return res; + } + + static bool is_one(const Channel& c) + { + return !c.src && c.value.size() == 1 && std::abs(c.value[0] - 1.0f) < 1e-6f; + } + + // color * weight, folded when both are constants and skipped when the + // weight is a constant 1. + Channel scale(const Channel& color, const Channel& weight, int depth) + { + if (is_one(weight) || (!weight.src && weight.value.empty())) + return color; + + if (!color.src && !weight.src) + { + Channel res = color; + + for (auto& v : res.value) + v *= weight.value[0]; + + return res; + } + + auto* def = MaterialXLibrary::get().find("ND_multiply_color3FA"); + + if (!def) + return color; + + auto node = std::make_shared(*def); + place(node, depth); + feed(*node, 0, "in1", color); + feed(*node, 1, "in2", weight); + + Channel res; + res.src = node->get_output(0); + return res; + } + + // mix(a, b, weight): a where weight is 0, b where it is 1. Constants are + // folded; otherwise MaterialX's own mix node does it. + Channel lerp_color(const Channel& a, const Channel& b, const Channel& weight, int depth) + { + if (!a.src && !b.src && !weight.src && a.value.size() >= 3 && b.value.size() >= 3 && !weight.value.empty()) + { + const float t = std::clamp(weight.value[0], 0.0f, 1.0f); + Channel res = a; + + for (int i = 0; i < 3; i++) + res.value[i] = a.value[i] + (b.value[i] - a.value[i]) * t; + + return res; + } + + auto* def = MaterialXLibrary::get().find("ND_mix_color3FA"); + + if (!def) + return a; + + auto node = std::make_shared(*def); + place(node, depth); + feed(*node, 0, "fg", b); + feed(*node, 1, "bg", a); + feed(*node, 2, "mix", weight); + + Channel res; + res.src = node->get_output(0); + return res; + } + + void feed(MaterialXNode& node, int index, const char* name, const Channel& c) + { + if (c.src) + { + auto port = node.get_input(index); + unlink_auto(port); + c.src->link(port); + } + else + node.set_constant(name, c.value); + } + + void emit(const Channel& c, const OutPtr& target, int components) + { + if (c.src) + { + c.src->link(target); + return; + } + + if (c.value.empty()) + return; + + if (components == 1) + { + auto node = std::make_shared(c.value[0]); + place(node, 0); + node->get_output(0)->link(target); + return; + } + + float v[4] = { c.value[0], c.value[0], c.value[0], 1.0f }; + + if (c.value.size() >= 3) + for (int i = 0; i < 3; i++) + v[i] = c.value[i]; + + if (c.value.size() >= 4) + v[3] = c.value[3]; + + auto node = std::make_shared(vec4(v[0], v[1], v[2], v[3])); + place(node, 0); + node->get_output(0)->link(target); + } + + static bool nonzero(const Channel& c) + { + if (c.src) + return true; + + for (float v : c.value) + if (v != 0.0f) + return true; + + return false; + } + + public: + MtlxImporter(MaterialGraph::ptr g, std::filesystem::path d) : graph(std::move(g)), dir(std::move(d)) {} + + bool build_surface(const MaterialX::NodePtr& shader) + { + const SurfaceMapping* map = nullptr; + + for (auto& m : surface_mappings) + if (shader->getCategory() == m.category) + map = &m; + + if (!map) + { + Log::get() << Log::LEVEL_WARNING << "MaterialX import: surface shader '" << shader->getCategory() + << "' is not supported (standard_surface, open_pbr_surface, gltf_pbr, UsdPreviewSurface are)" << Log::endl; + return false; + } + + const int d = 1; + + auto base_color = scale(channel(shader, map->base_color, d), channel(shader, map->base_weight, d), d); + emit(channel(shader, map->metalness, d), graph->get_mettalic(), 1); + emit(channel(shader, map->roughness, d), graph->get_roughness(), 1); + + auto emission = scale(channel(shader, map->emission_color, d), channel(shader, map->emission_weight, d), d); + + if (nonzero(emission)) + emit(emission, graph->get_glow(), 4); + + // Clearcoat: only wired when the layer does something, since the + // shading model it selects costs a second specular lobe. + auto coat = channel(shader, map->coat, d); + const bool coated = nonzero(coat); + + if (coated) + { + emit(coat, graph->get_coat(), 1); + emit(channel(shader, map->coat_roughness, d), graph->get_coat_roughness(), 1); + } + + // Sheen: a material has one shading model, so a coat wins over it. + auto sheen = channel(shader, map->sheen, d); + + const bool sheened = !coated && nonzero(sheen); + + if (sheened) + { + // gltf_pbr's sheen is a colour with no separate weight: its + // strongest component stands in for one. + if (sheen.value.size() > 1) + sheen.value = { *std::max_element(sheen.value.begin(), sheen.value.end()) }; + + emit(sheen, graph->get_sheen(), 1); + emit(channel(shader, map->sheen_roughness, d), graph->get_sheen_roughness(), 1); + + // The shading model has one byte for the sheen colour: how far it + // follows the base colour. A constant colour's saturation is that + // (white is untinted, a saturated colour is tinted like the base). + auto colour = channel(shader, map->sheen_color, d); + + if (!colour.src && colour.value.size() >= 3) + { + const float hi = *std::max_element(colour.value.begin(), colour.value.begin() + 3); + const float lo = *std::min_element(colour.value.begin(), colour.value.begin() + 3); + + if (hi > 1e-4f) + emit({ nullptr, { (hi - lo) / hi } }, graph->get_sheen_tint(), 1); + } + } + else if (coated && nonzero(sheen)) + Log::get() << Log::LEVEL_WARNING << "MaterialX import: material has both coat and sheen; only the coat is kept" << Log::endl; + + // Anisotropy last: the lowest-priority shading model, so it is only + // used when neither a coat nor a sheen took the material's one slot. + bool anisotropic = false; + auto anisotropy = channel(shader, map->anisotropy, d); + + if (nonzero(anisotropy)) + { + if (coated || sheened) + Log::get() << Log::LEVEL_WARNING << "MaterialX import: material has anisotropy and a coat or sheen; anisotropy is dropped" << Log::endl; + else + { + emit(anisotropy, graph->get_anisotropy(), 1); + emit(channel(shader, map->anisotropy_rotation, d), graph->get_anisotropy_rotation(), 1); + anisotropic = true; + } + } + + // Subsurface: lowest priority of the shading models. The weight, + // radius colour and radius scale are separate outputs; the shader + // packs weight * scale * radius. subsurface_color is not applied -- + // the base colour is what gets blurred. + auto subsurface = channel(shader, map->subsurface, d); + bool subsurfaced = false; + + if (nonzero(subsurface)) + { + if (coated || sheened || anisotropic) + Log::get() << Log::LEVEL_WARNING << "MaterialX import: material has subsurface and another shading model; subsurface is dropped" << Log::endl; + else + { + subsurfaced = true; + emit(subsurface, graph->get_subsurface_weight(), 1); + emit(channel(shader, map->subsurface_radius, d), graph->get_subsurface_radius(), 4); + emit(channel(shader, map->subsurface_scale, d), graph->get_subsurface_scale(), 1); + + // The scattering part has its own diffuse colour: blend the + // base colour towards it by the subsurface weight (the weight + // is clamped to 1, as in MaterialX). + if (*map->subsurface_color) + base_color = lerp_color(base_color, channel(shader, map->subsurface_color, d), subsurface, d); + } + } + + // Thin film (anodised metal, oil): the lowest-priority shading model. A + // film weight of zero, or no thickness, is no film. + auto film = channel(shader, map->film_thickness, d); + auto film_weight = channel(shader, map->film_weight, d); + const bool filmed = nonzero(film) && (!*map->film_weight || nonzero(film_weight)); + + if (filmed) + { + if (coated || sheened || anisotropic || subsurfaced) + Log::get() << Log::LEVEL_WARNING << "MaterialX import: material has a thin film and another shading model; the film is dropped" << Log::endl; + else + { + emit(film, graph->get_thin_film_thickness(), 1); + emit(channel(shader, map->film_ior, d), graph->get_thin_film_ior(), 1); + } + } + + emit(base_color, graph->get_base_color(), 4); + + // Transmission: the weight being wired is what makes the material + // translucent (universal_material::resolve_transparency_mode). A + // solid (not thin-walled) surface gets a positive thickness, which is + // how the graph tells it from a thin one. + auto transmission = channel(shader, map->transmission, d); + + if (nonzero(transmission)) + { + emit(transmission, graph->get_transmission_weight(), 1); + emit(channel(shader, map->ior, d), graph->get_ior(), 1); + emit(channel(shader, map->transmission_color, d), graph->get_transmission_color(), 4); + emit(channel(shader, map->transmission_depth, d), graph->get_transmission_depth(), 1); + + if (*map->thin_walled) + { + auto thin = channel(shader, map->thin_walled, d); + + if (!thin.src && !thin.value.empty() && thin.value[0] < 0.5f) + emit({ nullptr, { 1.0f } }, graph->get_thickness(), 1); + } + else if (*map->thickness) + emit(channel(shader, map->thickness, d), graph->get_thickness(), 1); + } + + auto opacity = channel(shader, map->opacity, d); + + // Only wire opacity when it does something; an all-ones constant + // is the graph's default. + bool opaque = !opacity.src; + + for (float v : opacity.value) + opaque &= v >= 0.999f; + + // Cutout (foliage: discard below a threshold) or blend (partial + // coverage, drawn as a thin translucent layer). glTF and USD say which; + // otherwise a constant opacity blends and a textured one cuts out. + enum class Alpha { Auto, Cutout, Blend } alpha = Alpha::Auto; + float cutoff = 0.5f; + + if (*map->alpha_mode) + { + auto mode = channel(shader, map->alpha_mode, d).value; + const int m = mode.empty() ? 0 : (int)mode[0]; + + if (m == 0) + opaque = true; + else if (m == 1) + { + alpha = Alpha::Cutout; + auto c = channel(shader, map->alpha_cutoff, d).value; + + if (!c.empty()) + cutoff = c[0]; + } + else + alpha = Alpha::Blend; + } + else if (*map->opacity_threshold) + { + auto t = channel(shader, map->opacity_threshold, d).value; + + if (!t.empty() && t[0] > 0.0f) + { + alpha = Alpha::Cutout; + cutoff = t[0]; + } + else + alpha = Alpha::Blend; + } + + if (!opaque) + { + emit(opacity, graph->get_opacity(), 1); + + if (alpha == Alpha::Auto) + alpha = opacity.src ? Alpha::Cutout : Alpha::Blend; + + if (alpha == Alpha::Blend) + emit({ nullptr, { 0.0f } }, graph->get_opacity_threshold(), 1); + else if (std::abs(cutoff - 0.5f) > 1e-4f) + emit({ nullptr, { cutoff } }, graph->get_opacity_threshold(), 1); + } + + if (auto normal = shader->getInput(map->normal)) + { + auto n = normal->getConnectedNode(); + + // The graph's normal output takes the tangent-space texel as + // stored; MaterialX's normalmap node decodes it, so the map + // goes straight to the output instead. + if (n && n->getCategory() == "normalmap") + { + if (auto in = n->getInput("in")) + if (auto src = source_of(in, d)) + src->link(graph->get_normals()); + } + else if (n || normal->hasNodeGraphString()) + Log::get() << Log::LEVEL_WARNING << "MaterialX import: only normalmap nodes are supported on the normal input" << Log::endl; + } + + return true; + } + + // Columns by distance from the graph output, rows in creation order. + void layout() + { + int max_depth = 0; + + for (auto& p : placed) + max_depth = std::max(max_depth, p.second); + + std::map rows; + + for (auto& p : placed) + p.first->pos = vec2(40.0f + (max_depth - p.second) * 330.0f, 40.0f + rows[p.second]++ * 300.0f); + } + }; +} + +std::vector> MaterialXLibrary::build_graphs(const std::filesystem::path& file) +{ + std::vector> result; + struct Shader { std::string name; MaterialX::NodePtr node; }; + std::vector shaders; + MaterialX::DocumentPtr doc; + + try + { + auto lib = MaterialX::createDocument(); + MaterialX::FilePathVec folders; + + for (const char* name : { "libraries/stdlib", "libraries/pbrlib", "libraries/bxdf" }) + if (std::filesystem::exists(library_root / name)) + folders.push_back(MaterialX::FilePath(name)); + + MaterialX::loadLibraries(folders, MaterialX::FileSearchPath(library_root.string()), lib); + + doc = MaterialX::createDocument(); + MaterialX::readFromXmlFile(doc, MaterialX::FilePath(file.string()), MaterialX::FileSearchPath(file.parent_path().string())); + doc->importLibrary(lib); + + for (auto& m : doc->getMaterialNodes()) + if (auto in = m->getInput("surfaceshader")) + if (auto shader = in->getConnectedNode()) + shaders.push_back({ m->getName(), shader }); + + if (shaders.empty()) + for (auto& n : doc->getNodes()) + if (n->getType() == "surfaceshader") + shaders.push_back({ n->getName(), n }); + } + catch (const std::exception& e) + { + Log::get() << Log::LEVEL_ERROR << "MaterialX import: cannot read " << file.string() << ": " << e.what() << Log::endl; + return result; + } + + if (shaders.empty()) + { + Log::get() << Log::LEVEL_WARNING << "MaterialX import: " << file.string() << " has no surface material" << Log::endl; + return result; + } + + for (auto& shader : shaders) + { + auto graph = std::make_shared(); + MtlxImporter importer(graph, file.parent_path()); + + if (!importer.build_surface(shader.node)) + continue; + + importer.layout(); + result.push_back({ shader.name, graph }); + } + + return result; +} + +size_t MaterialXLibrary::import_file(const std::filesystem::path& file) +{ + size_t count = 0; + + for (auto& [name, graph] : build_graphs(file)) + { + auto* material = new materials::universal_material(graph); + material->register_new(std::wstring(name.begin(), name.end())); + count++; + } + + Log::get() << Log::LEVEL_INFO << "MaterialX import: " << file.string() << " -> " << count << " material(s)" << Log::endl; + return count; +} diff --git a/sources/RenderSystem/Materials/MaterialX.ixx b/sources/RenderSystem/Materials/MaterialX.ixx new file mode 100644 index 000000000..4bb8ecb05 --- /dev/null +++ b/sources/RenderSystem/Materials/MaterialX.ixx @@ -0,0 +1,139 @@ +export module Graphics:Materials.MaterialX; + +import Core; +import GUI; +import :Materials.Values; + +export +{ + +struct MxPortDef +{ + std::string name; + std::string mx_type; // MaterialX type name: "float", "color3", ... + ShaderParamType type; + std::string default_hlsl; // HLSL literal of `type`, used while the input is unconnected + + // Float components of the default (empty for int/bool), and the soft + // range MaterialX suggests for a UI slider. + std::vector default_values; + float soft_min = 0; + float soft_max = 1; + + // MaterialX "defaultgeomprop": what an unconnected input reads from the + // mesh instead of a constant (UV0, Pobject, ...). Empty for plain values. + std::string geomprop; + + // int inputs with a MaterialX `enum` (names and the values they stand for); + // shown as a drop-down instead of a slider. + std::vector enum_names; + std::vector enum_values; +}; + +// One implemented MaterialX nodedef, in graph-ready form. MaterialX types +// that have no HLSL numeric equivalent (matrix, string, filename, closures) +// make the whole nodedef unavailable rather than partially representable. +struct MxNodeDef +{ + std::string name; // ND_add_float + std::string category; // add + std::string group; // math + std::string function; // HLSL function in mx_stdlib.hlsl + std::vector inputs; + std::vector outputs; +}; + +// MaterialX standard library, loaded once from /libraries. The +// MaterialX library is only used to read nodedefs; HLSL for each node comes +// from workdir/shaders/materialx/mx_stdlib.hlsl (see its header for how a +// nodedef is matched to a function). +class MaterialXLibrary : public Singleton +{ + friend class Singleton; + + std::vector defs; + std::map index; + std::filesystem::path library_root; + + MaterialXLibrary() = default; + +public: + // Reads a .mtlx file and registers one material asset per surface material + // in it (or per bare surface shader node when the file has no material + // nodes). Textures are registered as assets, resolved against the file's + // folder. Returns the number of materials created; problems are logged. + size_t import_file(const std::filesystem::path& file); + + // The graph half of import_file: one named material graph per surface in + // the file, without creating assets for them. + std::vector> build_graphs(const std::filesystem::path& file); + + // Loads /libraries/stdlib and scans for implemented + // functions, then adds a node-picker entry for every implemented nodedef. + // Returns the number of registered nodes (0 on failure, which is logged). + size_t register_nodes(const std::filesystem::path& root, const std::filesystem::path& hlsl_stdlib); + + const MxNodeDef* find(const std::string& nodedef_name) const; + const std::vector& get_all() const { return defs; } +}; + +// One generic node for every MaterialX nodedef: ports and HLSL call come from +// the registry entry named by `nodedef`. Unconnected inputs emit the +// nodedef's default literal. +class MaterialXNode : public MaterialNode +{ + std::string nodedef; + + // One per input, null where the input has no editable constant (inputs that + // read geometry, and textures). int and bool inputs are float uniforms too, + // cast where they are used. A uniform lives in MaterialCB, so editing it + // never recompiles the shader and materials with the same graph keep sharing + // a PSO. + std::vector uniforms; + + MaterialXNode() = default; + + // Creates the uniform of every editable input that lacks one: all of them for + // a fresh node, and the int/bool ones for a graph saved before those were + // uniforms (their slots load as null). + void ensure_uniforms(); + +protected: + void reset_for() override; + +public: + using ptr = s_ptr; + + explicit MaterialXNode(const MxNodeDef& def); + + void operator()(MaterialContext* c) override; + + // Wires UV0 / position inputs to the graph's own texcoord / position + // inputs when the node is dropped into a MaterialGraph. + void on_add(::FlowGraph::graph* g) override; + + GUI::base::ptr create_editor_window() override; + + // Sets the editable constant of a float-typed input (used by the .mtlx + // importer). False when the input doesn't exist or has no constant. + bool set_constant(const std::string& input, const std::vector& values); + + // Current constant of a float-typed input (empty if it has none). + std::vector get_constant(const std::string& input) const; + + const std::string& get_nodedef() const { return nodedef; } + + // Null when the nodedef has no input of that name. + ::FlowGraph::input::ptr get_input_by_name(const std::string& input); + +private: + SERIALIZE() + { + SAVE_PARENT(MaterialNode); + ar& NVP(nodedef); + ar& NVP(uniforms); + IF_LOAD() ensure_uniforms(); + } +}; + +} diff --git a/sources/RenderSystem/Materials/Values.cpp b/sources/RenderSystem/Materials/Values.cpp index 06a3d482b..6c87dab65 100644 --- a/sources/RenderSystem/Materials/Values.cpp +++ b/sources/RenderSystem/Materials/Values.cpp @@ -190,7 +190,7 @@ void MaterialContext::start(std::string orig_file, MaterialGraph* graph) graph->get_base_color()->set_enabled(true); graph->get_roughness()->set_enabled(true); graph->get_opacity()->set_enabled(true); - graph->get_refraction()->set_enabled(true); + graph->get_ior()->set_enabled(true); graph->set_translucent_outputs_enabled(true); graph->get_tess_displacement()->set_enabled(false); @@ -239,7 +239,7 @@ void MaterialContext::start(std::string orig_file, MaterialGraph* graph) graph->get_mettalic()->set_enabled(false); graph->get_roughness()->set_enabled(false); graph->get_opacity()->set_enabled(false); - graph->get_refraction()->set_enabled(false); + graph->get_ior()->set_enabled(false); graph->set_translucent_outputs_enabled(false); graph->get_base_color()->set_enabled(false); @@ -273,7 +273,7 @@ void MaterialContext::start(std::string orig_file, MaterialGraph* graph) graph->get_base_color()->set_enabled(true); graph->get_roughness()->set_enabled(true); graph->get_opacity()->set_enabled(true); - graph->get_refraction()->set_enabled(true); + graph->get_ior()->set_enabled(true); graph->set_translucent_outputs_enabled(true); graph->get_tess_displacement()->set_enabled(false); @@ -458,7 +458,16 @@ void MaterialFunction::on_finish() { if (!output_parametres[i]->enabled) continue; - add_function(f_output_names[o++] + " = " + output_parametres[i]->get().name); + // A linked value can be a different numeric width/kind than the port + // (links between numeric types are allowed, see ShaderParamType:: + // can_cast) -- convert to the port's own type, which is what the + // `out` parameter was declared with in generate_output(). + auto value = output_parametres[i]->get(); + + if (auto* dst = dynamic_cast(output_parametres[i]->type.get())) + value.name = coerce(value, *dst); + + add_function(f_output_names[o++] + " = " + value.name); }; for (unsigned int i = 0; i < outputs.size(); i++) @@ -707,11 +716,39 @@ MaterialGraph::MaterialGraph() i_glow = register_output(/*ShaderParams::get().FLOAT4, */"glow", ShaderParams::get().FLOAT4); // Registered after glow so they append to the end of the COMPILED_FUNC output list. i_opacity = register_output("opacity", ShaderParams::get().FLOAT1); - i_refraction = register_output("refraction", ShaderParams::get().FLOAT1); + i_refraction = register_output("ior", ShaderParams::get().FLOAT1); // Translucent-only (refraction wired): see TranslucentRTX, raytracing.prism. i_thickness = register_output("thickness", ShaderParams::get().FLOAT1); - i_transmission = register_output("transmission", ShaderParams::get().FLOAT1); - i_absorption_distance = register_output("absorption distance", ShaderParams::get().FLOAT1); + i_transmission = register_output("transmission weight", ShaderParams::get().FLOAT1); + i_absorption_distance = register_output("transmission depth", ShaderParams::get().FLOAT1); + i_coat = register_output("coat weight", ShaderParams::get().FLOAT1); + i_coat_roughness = register_output("coat roughness", ShaderParams::get().FLOAT1); + i_coat->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_coat_roughness->default_value = shader_parameter("0.1", ShaderParams::get().FLOAT1); + i_sheen = register_output("sheen weight", ShaderParams::get().FLOAT1); + i_sheen_roughness = register_output("sheen roughness", ShaderParams::get().FLOAT1); + i_sheen->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_sheen_roughness->default_value = shader_parameter("0.3", ShaderParams::get().FLOAT1); + i_anisotropy = register_output("anisotropy", ShaderParams::get().FLOAT1); + i_anisotropy_rotation = register_output("anisotropy rotation", ShaderParams::get().FLOAT1); + i_anisotropy->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_anisotropy_rotation->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_sss_weight = register_output("subsurface weight", ShaderParams::get().FLOAT1); + i_sss_radius = register_output("subsurface radius", ShaderParams::get().FLOAT3); + i_sss_scale = register_output("subsurface scale", ShaderParams::get().FLOAT1); + i_sss_weight->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_sss_radius->default_value = shader_parameter("float3(1,1,1)", ShaderParams::get().FLOAT3); + i_sss_scale->default_value = shader_parameter("1.0", ShaderParams::get().FLOAT1); + i_sheen_tint = register_output("sheen tint", ShaderParams::get().FLOAT1); + i_sheen_tint->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_transmission_color = register_output("transmission color", ShaderParams::get().FLOAT3); + i_transmission_color->default_value = shader_parameter("float3(-1,-1,-1)", ShaderParams::get().FLOAT3); + i_opacity_threshold = register_output("opacity threshold", ShaderParams::get().FLOAT1); + i_opacity_threshold->default_value = shader_parameter("0.5", ShaderParams::get().FLOAT1); + i_thin_film_thickness = register_output("thin film thickness", ShaderParams::get().FLOAT1); + i_thin_film_ior = register_output("thin film ior", ShaderParams::get().FLOAT1); + i_thin_film_thickness->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_thin_film_ior->default_value = shader_parameter("1.5", ShaderParams::get().FLOAT1); i_base_color->default_value = shader_parameter("float4(0,0,0,1)", ShaderParams::get().FLOAT4); i_metallic->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); @@ -745,6 +782,11 @@ FlowGraph::input::ptr MaterialGraph::get_texcoord() return texcoord; } +FlowGraph::input::ptr MaterialGraph::get_position() +{ + return position; +} + FlowGraph::output::ptr MaterialGraph::get_base_color() { return i_base_color; @@ -784,7 +826,7 @@ FlowGraph::output::ptr MaterialGraph::get_opacity() return i_opacity; } -FlowGraph::output::ptr MaterialGraph::get_refraction() +FlowGraph::output::ptr MaterialGraph::get_ior() { return i_refraction; } @@ -794,21 +836,233 @@ FlowGraph::output::ptr MaterialGraph::get_thickness() return i_thickness; } -FlowGraph::output::ptr MaterialGraph::get_transmission() +FlowGraph::output::ptr MaterialGraph::get_transmission_weight() { return i_transmission; } -FlowGraph::output::ptr MaterialGraph::get_absorption_distance() +FlowGraph::output::ptr MaterialGraph::get_transmission_color() +{ + return i_transmission_color; +} + +FlowGraph::output::ptr MaterialGraph::get_transmission_depth() { return i_absorption_distance; } +FlowGraph::output::ptr MaterialGraph::get_opacity_threshold() +{ + return i_opacity_threshold; +} + +FlowGraph::output::ptr MaterialGraph::get_thin_film_thickness() +{ + return i_thin_film_thickness; +} + +FlowGraph::output::ptr MaterialGraph::get_thin_film_ior() +{ + return i_thin_film_ior; +} + +FlowGraph::output::ptr MaterialGraph::get_coat() +{ + return i_coat; +} + +FlowGraph::output::ptr MaterialGraph::get_coat_roughness() +{ + return i_coat_roughness; +} + +FlowGraph::output::ptr MaterialGraph::get_sheen() +{ + return i_sheen; +} + +FlowGraph::output::ptr MaterialGraph::get_sheen_roughness() +{ + return i_sheen_roughness; +} + +FlowGraph::output::ptr MaterialGraph::get_anisotropy() +{ + return i_anisotropy; +} + +FlowGraph::output::ptr MaterialGraph::get_anisotropy_rotation() +{ + return i_anisotropy_rotation; +} + +FlowGraph::output::ptr MaterialGraph::get_subsurface_weight() +{ + return i_sss_weight; +} + +FlowGraph::output::ptr MaterialGraph::get_subsurface_radius() +{ + return i_sss_radius; +} + +FlowGraph::output::ptr MaterialGraph::get_subsurface_scale() +{ + return i_sss_scale; +} + +FlowGraph::output::ptr MaterialGraph::get_sheen_tint() +{ + return i_sheen_tint; +} + +void MaterialGraph::set_coat_outputs_enabled(bool enabled) +{ + i_coat->set_enabled(enabled); + i_coat_roughness->set_enabled(enabled); + i_sheen->set_enabled(enabled); + i_sheen_roughness->set_enabled(enabled); + i_anisotropy->set_enabled(enabled); + i_anisotropy_rotation->set_enabled(enabled); + i_sss_weight->set_enabled(enabled); + i_sss_radius->set_enabled(enabled); + i_sss_scale->set_enabled(enabled); + i_sheen_tint->set_enabled(enabled); +} + +// Ports are serialized with the graph, so a graph saved before the coat +// outputs existed comes back without them. They go last in the COMPILED_FUNC +// output list, so appending reproduces the order a new graph has. +void MaterialGraph::restore_coat_outputs() +{ + // The constructor's own ports were dropped when the saved port list was + // loaded over them, so the members are stale until looked up again. + i_coat.reset(); + i_coat_roughness.reset(); + i_sheen.reset(); + i_sheen_roughness.reset(); + i_anisotropy.reset(); + i_anisotropy_rotation.reset(); + i_sss_weight.reset(); + i_sss_radius.reset(); + i_sss_scale.reset(); + i_sheen_tint.reset(); + i_transmission_color.reset(); + i_opacity_threshold.reset(); + i_thin_film_thickness.reset(); + i_thin_film_ior.reset(); + + for (auto& out : output_parametres) + { + // Transparency ports were renamed to MaterialX's names. + if (out->name == "refraction") + out->name = "ior"; + else if (out->name == "transmission") + out->name = "transmission weight"; + else if (out->name == "absorption distance") + out->name = "transmission depth"; + + if (out->name == "coat weight") + i_coat = out; + else if (out->name == "coat roughness") + i_coat_roughness = out; + else if (out->name == "sheen weight") + i_sheen = out; + else if (out->name == "sheen roughness") + i_sheen_roughness = out; + else if (out->name == "anisotropy") + i_anisotropy = out; + else if (out->name == "anisotropy rotation") + i_anisotropy_rotation = out; + else if (out->name == "subsurface weight") + i_sss_weight = out; + else if (out->name == "subsurface radius") + i_sss_radius = out; + else if (out->name == "subsurface scale") + i_sss_scale = out; + else if (out->name == "sheen tint") + i_sheen_tint = out; + else if (out->name == "transmission color") + i_transmission_color = out; + else if (out->name == "opacity threshold") + i_opacity_threshold = out; + else if (out->name == "thin film thickness") + i_thin_film_thickness = out; + else if (out->name == "thin film ior") + i_thin_film_ior = out; + } + + if (!i_coat) + i_coat = register_output("coat weight", ShaderParams::get().FLOAT1); + + if (!i_coat_roughness) + i_coat_roughness = register_output("coat roughness", ShaderParams::get().FLOAT1); + + if (!i_sheen) + i_sheen = register_output("sheen weight", ShaderParams::get().FLOAT1); + + if (!i_sheen_roughness) + i_sheen_roughness = register_output("sheen roughness", ShaderParams::get().FLOAT1); + + if (!i_anisotropy) + i_anisotropy = register_output("anisotropy", ShaderParams::get().FLOAT1); + + if (!i_anisotropy_rotation) + i_anisotropy_rotation = register_output("anisotropy rotation", ShaderParams::get().FLOAT1); + + if (!i_sss_weight) + i_sss_weight = register_output("subsurface weight", ShaderParams::get().FLOAT1); + + if (!i_sss_radius) + i_sss_radius = register_output("subsurface radius", ShaderParams::get().FLOAT3); + + if (!i_sss_scale) + i_sss_scale = register_output("subsurface scale", ShaderParams::get().FLOAT1); + + if (!i_sheen_tint) + i_sheen_tint = register_output("sheen tint", ShaderParams::get().FLOAT1); + + if (!i_transmission_color) + i_transmission_color = register_output("transmission color", ShaderParams::get().FLOAT3); + + if (!i_opacity_threshold) + i_opacity_threshold = register_output("opacity threshold", ShaderParams::get().FLOAT1); + + if (!i_thin_film_thickness) + i_thin_film_thickness = register_output("thin film thickness", ShaderParams::get().FLOAT1); + + if (!i_thin_film_ior) + i_thin_film_ior = register_output("thin film ior", ShaderParams::get().FLOAT1); + + i_coat->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_coat_roughness->default_value = shader_parameter("0.1", ShaderParams::get().FLOAT1); + i_sheen->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_sheen_roughness->default_value = shader_parameter("0.3", ShaderParams::get().FLOAT1); + i_anisotropy->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_anisotropy_rotation->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_sss_weight->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_sss_radius->default_value = shader_parameter("float3(1,1,1)", ShaderParams::get().FLOAT3); + i_sss_scale->default_value = shader_parameter("1.0", ShaderParams::get().FLOAT1); + i_sheen_tint->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_transmission_color->default_value = shader_parameter("float3(-1,-1,-1)", ShaderParams::get().FLOAT3); + i_opacity_threshold->default_value = shader_parameter("0.5", ShaderParams::get().FLOAT1); + i_thin_film_thickness->default_value = shader_parameter("0.0", ShaderParams::get().FLOAT1); + i_thin_film_ior->default_value = shader_parameter("1.5", ShaderParams::get().FLOAT1); +} + +// Toggled together with the coat outputs: both are "extra" outputs that every +// full (non-tessellation) variant of COMPILED_FUNC takes and the tessellation +// variant omits. void MaterialGraph::set_translucent_outputs_enabled(bool enabled) { + set_coat_outputs_enabled(enabled); i_thickness->set_enabled(enabled); i_transmission->set_enabled(enabled); i_absorption_distance->set_enabled(enabled); + i_transmission_color->set_enabled(enabled); + i_opacity_threshold->set_enabled(enabled); + i_thin_film_thickness->set_enabled(enabled); + i_thin_film_ior->set_enabled(enabled); } void MaterialGraph::start(MaterialContext* context) @@ -1171,6 +1425,13 @@ GUI::base::ptr TextureAssetNode::create_editor_window() return back; } +// M=1 is a float scalar/vector, M=3/4 are INT/BOOL; M=2 (TEXTURE) is not +// numeric and never converts. +static bool is_numeric_type(const ShaderParamType& t) +{ + return t.M == 1 || t.M == 3 || t.M == 4; +} + bool ShaderParamType::can_cast(parameter_type* other) { VectorType* vtype = dynamic_cast(other); @@ -1183,8 +1444,51 @@ bool ShaderParamType::can_cast(parameter_type* other) if (N == -1 || type->N == -1) return M == type->M; - bool res = M == type->M && N == type->N; - return res; + if (M == type->M && N == type->N) + return true; + + // Any numeric type links to any other; the width/kind mismatch is + // resolved by coerce() where the value is consumed (MaterialXNode inputs + // and MaterialFunction's output assignment). + return is_numeric_type(*this) && is_numeric_type(*type); +} + +std::string coerce(const shader_parameter& src, const ShaderParamType& dst) +{ + const auto& s = src.type; + + if (s.M == dst.M && s.N == dst.N) + return src.name; + + if (!is_numeric_type(s) || !is_numeric_type(dst)) + return src.name; + + const std::string& n = src.name; + const bool src_scalar = s.M != 1 || s.N == 1; + + // Scalar destinations (float, int, bool): take .x of a vector, then cast. + if (dst.M != 1 || dst.N == 1) + { + std::string x = (s.M == 1 && s.N > 1) ? "(" + n + ").x" : n; + return "((" + dst.name + ")(" + x + "))"; + } + + // Float vector destinations. + if (src_scalar) + return "((" + dst.name + ")(" + (s.M == 1 ? n : "(float)(" + n + ")") + "))"; + + if (s.N > dst.N) + { + static const char* swizzle[] = { "", ".x", ".xy", ".xyz", ".xyzw" }; + return "(" + n + ")" + swizzle[dst.N]; + } + + // s.N < dst.N, both vectors (2->3, 2->4, 3->4): pad with 0, w gets 1. + std::string pad; + for (int i = s.N; i < dst.N; i++) + pad += (i == 3 ? ", 1" : ", 0"); + + return dst.name + "(" + n + pad + ")"; } REGISTER_TYPE(MaterialContext); diff --git a/sources/RenderSystem/Materials/Values.ixx b/sources/RenderSystem/Materials/Values.ixx index 974ce1fcb..e824a4167 100644 --- a/sources/RenderSystem/Materials/Values.ixx +++ b/sources/RenderSystem/Materials/Values.ixx @@ -91,7 +91,18 @@ struct ShaderParams:public Singleton // (all M=1) or VectorType::can_cast's M==1 check, so the GUI won't let // it link to a plain float/vector port. const ShaderParamType TEXTURE = ShaderParamType(2,1,"uint"); + // MaterialX integer/boolean. Distinct M values (not M=1) so operator== + // and uniform packing never confuse them with FLOAT1, which shares N=1. + const ShaderParamType INT = ShaderParamType(3,1,"int"); + const ShaderParamType BOOL = ShaderParamType(4,1,"bool"); }; + +// Converts `src` to an HLSL expression of type `dst`: splats a scalar, +// truncates with a swizzle, extends with 0/1 (w=1 for a 4th component, 0 +// for the rest), and casts between float/int/bool. Same-type values pass +// through unchanged. Only numeric types (M=1 float, INT, BOOL) convert; +// anything else (TEXTURE) is returned as-is. +std::string coerce(const shader_parameter& src, const ShaderParamType& dst); template class TemplatedParameter: public T { @@ -439,13 +450,53 @@ class MaterialGraph : public MaterialFunction FlowGraph::output::ptr i_glow; // Registered after i_glow so they append to the end of the generated // COMPILED_FUNC output list (keeps existing output order stable). + // Transparency, in MaterialX's vocabulary. The members keep their original + // names (they are the serialization keys); the ports and getters are named + // after MaterialX: i_refraction is "ior", i_transmission "transmission + // weight", i_absorption_distance "transmission depth". thickness is 0 for a + // thin-walled surface and positive for a solid one (MaterialX thin_walled). FlowGraph::output::ptr i_opacity; FlowGraph::output::ptr i_refraction; FlowGraph::output::ptr i_thickness; FlowGraph::output::ptr i_transmission; FlowGraph::output::ptr i_absorption_distance; + // Clearcoat lobe (shading model CLEARCOAT, see common/shading_model.hlsl). + // Not serialized as members: graphs saved before these existed have no + // such ports, so after loading they are looked up by name and added when + // missing (restore_coat_outputs), which keeps the saved format unchanged. + FlowGraph::output::ptr i_coat; + FlowGraph::output::ptr i_coat_roughness; + // Sheen lobe (shading model SHEEN); same load-time restoration as the coat. + FlowGraph::output::ptr i_sheen; + FlowGraph::output::ptr i_sheen_roughness; + // Anisotropic specular (shading model ANISOTROPIC): strength, and the + // tangent's rotation about the normal in turns (0..1). + FlowGraph::output::ptr i_anisotropy; + FlowGraph::output::ptr i_anisotropy_rotation; + // Subsurface scattering (shading model SUBSURFACE): weight, the per-channel + // mean free path and a scale on it, in scene units. + FlowGraph::output::ptr i_sss_weight; + FlowGraph::output::ptr i_sss_radius; + FlowGraph::output::ptr i_sss_scale; + // How far the sheen colour follows the base colour: 0 white, 1 tinted like it. + // Registered last so graphs saved before it existed gain it at the same position. + FlowGraph::output::ptr i_sheen_tint; + // Colour of transmitted light / what a solid absorbs to over the depth. + // Negative (the default) means "use the base colour", which is what graphs + // saved before this output existed did. + FlowGraph::output::ptr i_transmission_color; + // Where opacity cuts out (UsdPreviewSurface's opacityThreshold, glTF's + // alpha cutoff). Left unwired it is 0.5, the old fixed cutout. Wired to a + // constant 0 the material is blended instead: see + // universal_material::resolve_transparency_mode. + FlowGraph::output::ptr i_opacity_threshold; + // Thin-film interference on the specular layer (shading model THINFILM): + // film thickness in nanometres (0 = none) and its index of refraction. + FlowGraph::output::ptr i_thin_film_thickness; + FlowGraph::output::ptr i_thin_film_ior; void set_translucent_defaults(); + void restore_coat_outputs(); public: using ptr = s_ptr; @@ -465,6 +516,7 @@ class MaterialGraph : public MaterialFunction virtual ~MaterialGraph(); virtual GUI::base::ptr create_editor_window() override; FlowGraph::input::ptr get_texcoord(); + FlowGraph::input::ptr get_position(); FlowGraph::output::ptr get_base_color(); @@ -476,11 +528,26 @@ class MaterialGraph : public MaterialFunction FlowGraph::output::ptr get_roughness(); FlowGraph::output::ptr get_tess_displacement(); FlowGraph::output::ptr get_opacity(); - FlowGraph::output::ptr get_refraction(); + FlowGraph::output::ptr get_ior(); FlowGraph::output::ptr get_thickness(); - FlowGraph::output::ptr get_transmission(); - FlowGraph::output::ptr get_absorption_distance(); + FlowGraph::output::ptr get_transmission_weight(); + FlowGraph::output::ptr get_transmission_color(); + FlowGraph::output::ptr get_transmission_depth(); + FlowGraph::output::ptr get_opacity_threshold(); + FlowGraph::output::ptr get_thin_film_thickness(); + FlowGraph::output::ptr get_thin_film_ior(); + FlowGraph::output::ptr get_coat(); + FlowGraph::output::ptr get_coat_roughness(); + FlowGraph::output::ptr get_sheen(); + FlowGraph::output::ptr get_sheen_roughness(); + FlowGraph::output::ptr get_anisotropy(); + FlowGraph::output::ptr get_anisotropy_rotation(); + FlowGraph::output::ptr get_subsurface_weight(); + FlowGraph::output::ptr get_subsurface_radius(); + FlowGraph::output::ptr get_subsurface_scale(); + FlowGraph::output::ptr get_sheen_tint(); void set_translucent_outputs_enabled(bool enabled); + void set_coat_outputs_enabled(bool enabled); virtual void start(MaterialContext* context); private: SERIALIZE() @@ -512,6 +579,7 @@ class MaterialGraph : public MaterialFunction i_opacity->default_value = shader_parameter("1.0", ShaderParams::get().FLOAT1); i_refraction->default_value = shader_parameter("1.0", ShaderParams::get().FLOAT1); set_translucent_defaults(); + IF_LOAD() restore_coat_outputs(); } }; @@ -640,6 +708,7 @@ class VectorNode : public MaterialNode using ptr = s_ptr; VectorNode(vec4 value); + vec4 get_value() const { return uniform->value.f4_value; } static ptr create_default() { return std::make_shared(vec4(1,1,1,1)); } @@ -666,6 +735,7 @@ class ScalarNode : public MaterialNode using ptr = s_ptr; ScalarNode(float value); + float get_value() const { return uniform->value.f_value; } static ptr create_default() { return std::make_shared(1.0f); } diff --git a/sources/RenderSystem/Materials/universal_material.cpp b/sources/RenderSystem/Materials/universal_material.cpp index 79aac6fbb..8073812ae 100644 --- a/sources/RenderSystem/Materials/universal_material.cpp +++ b/sources/RenderSystem/Materials/universal_material.cpp @@ -1,4 +1,4 @@ -module Graphics:Materials.UniversalMaterial; +module Graphics:Materials.UniversalMaterial; import RenderSystem; @@ -465,11 +465,31 @@ void materials::universal_material::generate_texture_handles() } -// Refraction (IOR) wired -> Translucent; else opacity wired -> Masked; else Opaque. +// Opacity wired with its threshold wired to a constant 0 means a blended surface +// (partial coverage, drawn as a thin translucent layer); any other threshold, or +// none, is a cutout. +static bool opacity_is_blended(MaterialGraph* g) +{ + if (!g || !g->get_opacity()->has_input()) + return false; + + auto threshold = g->get_opacity_threshold(); + + if (threshold->input_connections.empty()) + return false; + + auto* constant = dynamic_cast((*threshold->input_connections.begin())->from->owner); + return constant && constant->get_value() <= 0.0f; +} + +// IOR or transmission weight wired, or a blended opacity -> Translucent; else +// opacity wired -> Masked; else Opaque. void materials::universal_material::resolve_transparency_mode() { auto g = graph.get(); - if (g && g->get_refraction()->has_input()) + opacity_blend = opacity_is_blended(g.get()); + + if (g && (opacity_blend || g->get_ior()->has_input() || g->get_transmission_weight()->has_input())) transparency_mode = Meshes::TransparencyMode::Translucent; else if (g && g->get_opacity()->has_input()) transparency_mode = Meshes::TransparencyMode::Masked; @@ -500,6 +520,9 @@ void materials::universal_material::generate_material() else if (transparency_mode == Meshes::TransparencyMode::Translucent) context->hit_shader.macros.emplace_back("TRANSLUCENT", "1"); + if (opacity_blend) + context->hit_shader.macros.emplace_back("TRANSLUCENT_BLEND", "1"); + // The hit shaders read MaterialInfo through the hit group's local data, // which Vulkan delivers as the shader record (see Prism's slot template). // Vulkan-only so D3D12's library cache keys stay as they were. diff --git a/sources/RenderSystem/Materials/universal_material.ixx b/sources/RenderSystem/Materials/universal_material.ixx index 9fe4d0d0c..c65d4fa8f 100644 --- a/sources/RenderSystem/Materials/universal_material.ixx +++ b/sources/RenderSystem/Materials/universal_material.ixx @@ -1,4 +1,4 @@ -export module Graphics:Materials.UniversalMaterial; +export module Graphics:Materials.UniversalMaterial; import :Materials.Values; @@ -189,6 +189,8 @@ export namespace materials // PSOs, raster passes and RT instance flags, so it is per material, // unlike the per-pixel graph values it is derived from. Meshes::TransparencyMode transparency_mode = Meshes::TransparencyMode::Opaque; + // Translucent because of a blended opacity rather than transmission. + bool opacity_blend = false; void resolve_transparency_mode(); void generate_texture_handles(); diff --git a/sources/RenderSystem/Mesh/AssimpLoader.cpp b/sources/RenderSystem/Mesh/AssimpLoader.cpp index 1e609ecf3..c65b3b8da 100644 --- a/sources/RenderSystem/Mesh/AssimpLoader.cpp +++ b/sources/RenderSystem/Mesh/AssimpLoader.cpp @@ -1,4 +1,4 @@ -module; +module; // Assimp macros don't propagate through the named module boundary — redefine them here. #define AI_SUCCESS aiReturn_SUCCESS #define AI_MATKEY_COLOR_DIFFUSE "$clr.diffuse", 0, 0 @@ -1057,8 +1057,8 @@ std::shared_ptr MeshData::load_assimp(const std::string& file_name, re float file_ior; if (AI_SUCCESS == native_material->Get(AI_MATKEY_REFRACTI, file_ior) && file_ior > 1.0f) ior = file_ior; - link_scalar(ior, graph->get_refraction(), "IOR -> refraction"); - link_scalar(transmission, graph->get_transmission(), "transmission"); + link_scalar(ior, graph->get_ior(), "IOR"); + link_scalar(transmission, graph->get_transmission_weight(), "transmission"); // glTF KHR_materials_volume: 0 = thin-walled, same convention as ours. float thickness; @@ -1067,7 +1067,7 @@ std::shared_ptr MeshData::load_assimp(const std::string& file_name, re float attenuation_distance; if (AI_SUCCESS == native_material->Get(AI_MATKEY_VOLUME_ATTENUATION_DISTANCE, attenuation_distance) && attenuation_distance > 0.0f) - link_scalar(attenuation_distance, graph->get_absorption_distance(), "absorption distance"); + link_scalar(attenuation_distance, graph->get_transmission_depth(), "transmission depth"); } } diff --git a/sources/RenderSystem/Scene/SceneSystem.cpp b/sources/RenderSystem/Scene/SceneSystem.cpp index 31abacd58..b05ea1753 100644 --- a/sources/RenderSystem/Scene/SceneSystem.cpp +++ b/sources/RenderSystem/Scene/SceneSystem.cpp @@ -47,6 +47,7 @@ void PassDefault::render( rtv.GetSpecular() = gbuffer.specular.renderTarget; rtv.GetMotion() = gbuffer.speed.renderTarget; rtv.GetObject_Id() = gbuffer.object_id.renderTarget; + rtv.GetMaterial() = gbuffer.material.renderTarget; rtv.GetDepth() = gbuffer.depth.depthStencil; gbuffer.compiled = rtv.compile(*command_list); } diff --git a/sources/Spectrum/main.cpp b/sources/Spectrum/main.cpp index 63afbd064..3e3f75686 100644 --- a/sources/Spectrum/main.cpp +++ b/sources/Spectrum/main.cpp @@ -1731,6 +1731,11 @@ class GraphRender : public Window, public GUI::user_interface } }); }; + file->add_item("Import MaterialX...")->on_click = [this](GUI::Elements::menu_list_element::ptr elem) + { + for (auto& path : Application::get().file_open("Choose a MaterialX file", "", "mtlx|*.mtlx|" "all|*.*|" "|")) + MaterialXLibrary::get().import_file(std::filesystem::path(path)); + }; file->add_item("Save")->on_click = [this](GUI::Elements::menu_list_element::ptr elem) { if (!drawer) return; @@ -2348,6 +2353,8 @@ int APIENTRY WinMain(_In_ HINSTANCE hinst, return nullptr; }); + MaterialXLibrary::get().register_nodes(to_path("materialx"), to_path("shaders/materialx/mx_stdlib.hlsl")); + auto result_code = 0; SetupDebug(); diff --git a/sources/Test/Tests/Test.MaterialX.ixx b/sources/Test/Tests/Test.MaterialX.ixx new file mode 100644 index 000000000..35e7d1266 --- /dev/null +++ b/sources/Test/Tests/Test.MaterialX.ixx @@ -0,0 +1,1573 @@ +export module Test.MaterialX; + +#define TEST_MODULE_ID MaterialX + +export import Test.Framework; +export import Test.HAL.TextureUtils; + +import Core; +import HAL; +import RenderSystem; +import Graphics; + +// Tests for the MaterialX node library: the registry, every node's HLSL, node +// values evaluated on the GPU, and the .mtlx importer. +// +// Values come from two places. Simple nodes have a result that follows from +// their definition, written out by hand. The noise functions have no closed +// form, so their expected values come from a separate Python port of OSL's +// noise (the algorithm MaterialX's own shaders use), written independently of +// the HLSL: a transcription slip in one shows up as a disagreement. The +// script is workdir/materialx/noise_reference.py; its output is what the noise +// tables below hold. + +// One GPU category for the node and importer tests: creating and resetting the +// graphics system twice in a row (one category each) crashed intermittently +// when the second create followed the first reset. +SETUP_CATEGORY(MaterialX.GPU, []() { + if (!GraphicsSystem::create()) + Test::TestRegistry::Instance().SkipCategory("MaterialX.GPU", "no suitable GPU device found"); +}); + +TEARDOWN_CATEGORY(MaterialX.GPU, []() { + GraphicsSystem::reset(); +}); + +export namespace Test +{ + namespace mx_detail + { + struct Case + { + std::string name; + std::string expr; // HLSL expression convertible through mxt() + float4 expected; + float tolerance = 1e-4f; + }; + + inline std::string read_text(const std::filesystem::path& path) + { + std::ifstream file(path, std::ios::binary); + std::stringstream ss; + ss << file.rdbuf(); + return ss.str(); + } + + // mx_stdlib.hlsl with its #include "mx_generated.hlsl" spliced in, so a + // test shader needs no include handler. + inline std::string stdlib_source() + { + auto dir = to_path(L"shaders/materialx"); + auto text = read_text(dir / "mx_stdlib.hlsl"); + auto at = text.find("#include \"mx_generated.hlsl\""); + + if (at != std::string::npos) + text.replace(at, std::string("#include \"mx_generated.hlsl\"").size(), read_text(dir / "mx_generated.hlsl")); + + return text; + } + + inline void ensure_library() + { + static bool loaded = false; + + if (!loaded) + { + MaterialXLibrary::get().register_nodes(to_path(L"materialx"), to_path(L"shaders/materialx/mx_stdlib.hlsl")); + loaded = true; + } + } + + inline std::string errors_only(const std::string& log) + { + std::string res; + std::istringstream lines(log); + + for (std::string line; std::getline(lines, line);) + if (line.find("error") != std::string::npos) + res += line + "\n"; + + return res; + } + + // Evaluates one case per pixel into a float target and reads it back. + // `prelude` is extra HLSL placed after the MaterialX stdlib. + inline std::vector evaluate(const std::vector& cases, const std::string& prelude = "") + { + auto& device = RenderSystem::get().device(); + const uint width = (uint)cases.size(); + + std::string source = stdlib_source() + "\n" + prelude + "\n"; + source += R"hlsl( +float4 mxt(float v) { return float4(v, 0, 0, 0); } +float4 mxt(int v) { return float4(v, 0, 0, 0); } +float4 mxt(bool v) { return float4(v ? 1 : 0, 0, 0, 0); } +float4 mxt(float2 v) { return float4(v, 0, 0); } +float4 mxt(float3 v) { return float4(v, 0); } +float4 mxt(float4 v) { return v; } + +float4 VS(uint vid : SV_VertexID) : SV_Position +{ + float2 uv = float2((vid & 1) * 2.0, (vid >> 1) * 2.0); + return float4(uv * 2.0 - 1.0, 0.0, 1.0); +} + +float4 PS(float4 pos : SV_Position) : SV_Target +{ + switch ((int)pos.x) + { +)hlsl"; + + for (size_t i = 0; i < cases.size(); i++) + source += " case " + std::to_string(i) + ": return mxt(" + cases[i].expr + ");\n"; + + source += " }\n return float4(-12345, 0, 0, 0);\n}\n"; + + std::string errors; + auto compiled = ::HAL::ShaderCompiler::get().Compile_Shader(source, {}, "ps_6_8", "PS", HAL::ShaderOptions::None, nullptr, "mx_cases", &errors); + ASSERT_EQ(std::string(), errors_only(errors)); + ASSERT_TRUE(compiled.has_value()); + + auto tex = std::make_shared(device, + HAL::ResourceDesc::Tex2D(HAL::Format::R32G32B32A32_FLOAT, { width, 1 }, 1, 1, HAL::ResFlags::RenderTarget), + HAL::HeapType::DEFAULT); + + SimpleGraphicsPSO mpso("TestMaterialXCases"); + mpso.root_signature = Layouts::DefaultLayout; + mpso.vertex = { source, "VS", HAL::ShaderOptions::None, {}, true }; + mpso.pixel = { source, "PS", HAL::ShaderOptions::None, {}, true }; + mpso.rtv_formats = { HAL::Format::R32G32B32A32_FLOAT }; + mpso.enable_depth = false; + mpso.cull = HAL::CullMode::None; + mpso.topology = HAL::PrimitiveTopologyType::TRIANGLE; + auto pso = mpso.create(device); + + auto& queue = device.get_queue(HAL::CommandListType::DIRECT); + auto list = queue->get_free_list(); + list->begin(L"MaterialXCases"); + + HAL::Texture2DView view(tex, *list); + HAL::CompiledRT compiled_rt; + compiled_rt.table_rtv = view.renderTarget; + + auto& gfx = list->get_graphics(); + gfx.set_rtv(compiled_rt, HAL::RTOptions::Default | HAL::RTOptions::ClearColor, 0, 0, vec4(0, 0, 0, 0)); + gfx.set_pipeline(pso); + gfx.set_topology(HAL::PrimitiveTopologyType::TRIANGLE); + gfx.draw(3); + list->execute_and_wait(); + + auto data = readback_texture(tex.get()); + auto& mip = data->array[0]->mips[0]; + + std::vector res(width); + + for (uint x = 0; x < width; x++) + { + auto px = reinterpret_cast(mip->data.data()) + x * 4; + res[x] = float4(px[0], px[1], px[2], px[3]); + } + + return res; + } + + inline void check(const std::vector& cases, const std::string& prelude = "") + { + auto values = evaluate(cases, prelude); + + std::string failures; + + for (size_t i = 0; i < cases.size(); i++) + { + auto& e = cases[i].expected; + auto& a = values[i]; + + if (std::abs(a.x - e.x) > cases[i].tolerance || std::abs(a.y - e.y) > cases[i].tolerance || + std::abs(a.z - e.z) > cases[i].tolerance || std::abs(a.w - e.w) > cases[i].tolerance) + { + std::ostringstream ss; + ss << cases[i].name << ": expected (" << e.x << ", " << e.y << ", " << e.z << ", " << e.w + << ") got (" << a.x << ", " << a.y << ", " << a.z << ", " << a.w << ")\n"; + failures += ss.str(); + } + } + + ASSERT_EQ(std::string(), failures); + } + } + + TEST(MaterialX.Registry, LoadsStandardLibrary) + { + mx_detail::ensure_library(); + auto& lib = MaterialXLibrary::get(); + + ASSERT_TRUE(lib.get_all().size() >= 360); + + // ND_mix declares (fg, bg, mix): arguments are passed in that order. + auto* mix = lib.find("ND_mix_color3"); + ASSERT_TRUE(mix != nullptr); + ASSERT_EQ(size_t(3), mix->inputs.size()); + ASSERT_EQ(std::string("fg"), mix->inputs[0].name); + ASSERT_EQ(std::string("bg"), mix->inputs[1].name); + ASSERT_EQ(std::string("mix"), mix->inputs[2].name); + + // Defined by a node graph / inline expression, so only present through + // the generated file. + ASSERT_TRUE(lib.find("ND_contrast_color3") != nullptr); + ASSERT_TRUE(lib.find("ND_screen_color3") != nullptr); + + // Implemented by hand, with its geometry default. + auto* noise = lib.find("ND_noise3d_float"); + ASSERT_TRUE(noise != nullptr); + ASSERT_EQ(std::string("Pobject"), noise->inputs[2].geomprop); + + // Image nodes sample through a texture port, not a filename. + auto* image = lib.find("ND_image_color3"); + ASSERT_TRUE(image != nullptr); + ASSERT_TRUE(image->inputs[0].type == ShaderParams::get().TEXTURE); + + for (auto& def : lib.get_all()) + { + ASSERT_TRUE(!def.function.empty()); + ASSERT_TRUE(!def.outputs.empty()); + } + } + + // Every registered node, called with its own default arguments, compiles. + // This is the check that catches an HLSL signature drifting from its nodedef. + TEST(MaterialX.Library, EveryNodeCompiles) + { + mx_detail::ensure_library(); + + std::string source = mx_detail::stdlib_source(); + size_t count = 0; + + for (auto& def : MaterialXLibrary::get().get_all()) + { + // Texture nodes emit their own sampling code, not a function call. + if (def.function == "mx_image") + continue; + + std::string body, args; + + for (size_t i = 0; i < def.inputs.size(); i++) + { + auto& in = def.inputs[i]; + body += " " + in.type.name + " i" + std::to_string(i) + " = " + in.default_hlsl + ";\n"; + args += (i ? ", " : "") + std::string("i") + std::to_string(i); + } + + if (def.outputs.size() == 1) + body += " " + def.outputs[0].type.name + " r = " + def.function + "(" + args + ");\n"; + else + { + for (size_t i = 0; i < def.outputs.size(); i++) + { + body += " " + def.outputs[i].type.name + " r" + std::to_string(i) + ";\n"; + args += (args.empty() ? "" : ", ") + std::string("r") + std::to_string(i); + } + + body += " " + def.function + "(" + args + ");\n"; + } + + source += "void test_" + def.name + "()\n{\n" + body + "}\n"; + count++; + } + + source += "float4 PS() : SV_Target { return 0; }\n"; + + std::string errors; + ::HAL::ShaderCompiler::get().Compile_Shader(source, {}, "ps_6_8", "PS", HAL::ShaderOptions::None, nullptr, "mx_all_nodes", &errors); + + ASSERT_TRUE(count >= 300); + ASSERT_EQ(std::string(), mx_detail::errors_only(errors)); + } + + // Arithmetic, compositing and color nodes against values that follow from + // their definitions. + TEST(MaterialX.GPU, MathAndColor) + { + using mx_detail::Case; + + mx_detail::check({ + { "add", "mx_add(float3(1, 2, 3), float3(0.5, 0.5, 0.5))", { 1.5f, 2.5f, 3.5f, 0 } }, + { "subtract_fa", "mx_subtract_fa(float3(1, 2, 3), 0.5f)", { 0.5f, 1.5f, 2.5f, 0 } }, + { "divide", "mx_divide(1.0f, 4.0f)", { 0.25f, 0, 0, 0 } }, + { "modulo negative", "mx_modulo(-0.25f, 1.0f)", { 0.75f, 0, 0, 0 } }, + { "mix is fg-first", "mx_mix(float3(1, 0, 0), float3(0, 0, 1), 0.25f)", { 0.25f, 0, 0.75f, 0 } }, + { "smoothstep mid", "mx_smoothstep(0.5f, 0.0f, 1.0f)", { 0.5f, 0, 0, 0 } }, + { "smoothstep q", "mx_smoothstep(0.25f, 0.0f, 1.0f)", { 0.15625f, 0, 0, 0 } }, + { "remap", "mx_remap(0.5f, 0.0f, 1.0f, 10.0f, 20.0f)", { 15.0f, 0, 0, 0 } }, + { "range", "mx_range(0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 10.0f, false)", { 5.0f, 0, 0, 0 } }, + { "clamp", "mx_clamp(3.0f, 0.0f, 1.0f)", { 1.0f, 0, 0, 0 } }, + { "ifgreater", "mx_ifgreater(2.0f, 1.0f, 10.0f, 20.0f)", { 10.0f, 0, 0, 0 } }, + { "ifequal", "mx_ifequal(2.0f, 1.0f, 10.0f, 20.0f)", { 20.0f, 0, 0, 0 } }, + { "combine3", "mx_combine3(1.0f, 2.0f, 3.0f)", { 1, 2, 3, 0 } }, + { "luminance", "mx_luminance(float3(1, 1, 1), float3(0.2126, 0.7152, 0.0722))", { 1.0f, 0, 0, 0 } }, + { "hsv to rgb", "mx_hsvtorgb(float3(0.5, 1.0, 1.0))", { 0, 1, 1, 0 } }, + { "rgb to hsv", "mx_rgbtohsv(float3(0, 1, 0))", { 1.0f / 3.0f, 1, 1, 0 } }, + { "hsv roundtrip", "mx_hsvtorgb(mx_rgbtohsv(float3(0.2, 0.6, 0.9)))", { 0.2f, 0.6f, 0.9f, 0 } }, + { "contrast", "mx_contrast_color3(float3(0.25, 0.5, 1.0), float3(2, 2, 2), float3(0.5, 0.5, 0.5))", { 0, 0.5f, 1.5f, 0 } }, + { "saturate to gray","mx_saturate_color3(float3(1, 0, 0), 0.0f, float3(0.2, 0.7, 0.1))", { 0.2f, 0.2f, 0.2f, 0 } }, + { "screen", "mx_screen_color3(float3(0.5, 0.5, 0.5), float3(0.5, 0.5, 0.5), 1.0f)", { 0.75f, 0.75f, 0.75f, 0 } }, + { "plus half mix", "mx_plus_color3(float3(0.25, 0.25, 0.25), float3(0.5, 0.5, 0.5), 0.5f)", { 0.625f, 0.625f, 0.625f, 0 } }, + { "burn", "mx_burn(0.5f, 0.75f, 1.0f)", { 0.5f, 0, 0, 0 } }, + { "dodge", "mx_dodge(0.5f, 0.25f, 1.0f)", { 0.5f, 0, 0, 0 } }, + { "rotate2d 90", "mx_rotate2d(float2(1, 0), 90.0f)", { 0, -1, 0, 0 } }, + { "rotate3d 90", "mx_rotate3d(float3(1, 0, 0), 90.0f, float3(0, 0, 1))", { 0, -1, 0, 0 } }, + { "trianglewave", "mx_trianglewave_float(0.75f)", { 0.25f, 0, 0, 0 } }, + { "reflect", "mx_reflect_vector3(float3(1, -1, 0), float3(0, 1, 0))", { 1, 1, 0, 0 } }, + { "ramplr", "mx_ramplr(0.0f, 1.0f, float2(0.25, 0.0))", { 0.25f, 0, 0, 0 } }, + { "checker even", "mx_checkerboard(float3(1, 0, 0), float3(0, 0, 1), float2(2, 2), float2(0, 0), float2(0.25, 0.25))", { 1, 0, 0, 0 } }, + { "checker odd", "mx_checkerboard(float3(1, 0, 0), float3(0, 0, 1), float2(2, 2), float2(0, 0), float2(0.75, 0.25))", { 0, 0, 1, 0 } }, + { "circle inside", "mx_circle_float(float2(0.5, 0.5), float2(0.5, 0.5), 0.25f)", { 1, 0, 0, 0 } }, + { "circle outside", "mx_circle_float(float2(0.9, 0.5), float2(0.5, 0.5), 0.25f)", { 0, 0, 0, 0 } }, + { "normalmap flat", "mx_normalmap(float3(0.5, 0.5, 1.0), 1.0f, float3(0, 0, 1), float3(1, 0, 0), float3(0, 1, 0))", { 0, 0, 1, 0 } }, + }); + } + + // Noise against the independent Python port: lattice points, golden values + // and the relations between nodes (one-octave fractal is plain noise). + TEST(MaterialX.GPU, Noise) + { + using mx_detail::Case; + + mx_detail::check({ + { "perlin2 a", "mx_noise2d(1.0f, 0.0f, float2(0.3, 0.7))", { 0.040744f, 0, 0, 0 } }, + { "perlin2 b", "mx_noise2d(1.0f, 0.0f, float2(1.5, 2.25))", { 0.12825f, 0, 0, 0 } }, + { "perlin2 c", "mx_noise2d(1.0f, 0.0f, float2(-0.8, 3.1))", { 0.314508f, 0, 0, 0 } }, + { "perlin2 d", "mx_noise2d(1.0f, 0.0f, float2(10.2, -4.6))", { 0.257257f, 0, 0, 0 } }, + { "perlin2 centre", "mx_noise2d(1.0f, 0.0f, float2(0.5, 0.5))", { 0.0f, 0, 0, 0 } }, + { "perlin3 a", "mx_noise3d(1.0f, 0.0f, float3(0.3, 0.7, 0.1))", { 0.189201f, 0, 0, 0 } }, + { "perlin3 b", "mx_noise3d(1.0f, 0.0f, float3(1.5, 2.25, -3.5))", { -0.008511f, 0, 0, 0 } }, + { "perlin3 c", "mx_noise3d(1.0f, 0.0f, float3(-0.8, 3.1, 0.9))", { -0.035441f, 0, 0, 0 } }, + { "perlin3 d", "mx_noise3d(1.0f, 0.0f, float3(10.2, -4.6, 7.7))", { 0.04258f, 0, 0, 0 } }, + { "perlin3 centre", "mx_noise3d(1.0f, 0.0f, float3(0.5, 0.5, 0.5))", { 0.12275f, 0, 0, 0 } }, + { "perlin lattice", "mx_noise3d(1.0f, 0.0f, float3(2, 3, 4))", { 0.0f, 0, 0, 0 } }, + { "amplitude and pivot", "mx_noise3d(2.0f, 0.5f, float3(0.3, 0.7, 0.1))", { 2 * 0.189201f + 0.5f, 0, 0, 0 } }, + { "cell2 a", "mx_cellnoise2d(float2(0.3, 0.7))", { 0.860313f, 0, 0, 0 } }, + { "cell2 b", "mx_cellnoise2d(float2(1.5, 2.25))", { 0.063351f, 0, 0, 0 } }, + { "cell2 same cell", "mx_cellnoise2d(float2(0.9, 0.1))", { 0.860313f, 0, 0, 0 } }, + { "cell3 a", "mx_cellnoise3d(float3(0.3, 0.7, 0.1))", { 0.611068f, 0, 0, 0 } }, + { "cell3 b", "mx_cellnoise3d(float3(1.5, 2.25, -3.5))", { 0.603939f, 0, 0, 0 } }, + { "worley2 a", "mx_worleynoise2d_float(float2(0.3, 0.7), 1.0f, 0)", { 0.600949f, 0, 0, 0 } }, + { "worley2 b", "mx_worleynoise2d_float(float2(1.5, 2.25), 1.0f, 0)", { 0.34915f, 0, 0, 0 } }, + { "worley2 c", "mx_worleynoise2d_float(float2(-0.8, 3.1), 1.0f, 0)", { 0.446465f, 0, 0, 0 } }, + { "fractal2 a", "mx_fractal2d(1.0f, 3, 2.0f, 0.5f, float2(0.3, 0.7))", { -0.259577f, 0, 0, 0 } }, + { "fractal2 b", "mx_fractal2d(1.0f, 3, 2.0f, 0.5f, float2(1.5, 2.25))", { 0.21095f, 0, 0, 0 } }, + { "fractal3 a", "mx_fractal3d(1.0f, 3, 2.0f, 0.5f, float3(0.3, 0.7, 0.1))", { 0.245121f, 0, 0, 0 } }, + { "fractal3 b", "mx_fractal3d(1.0f, 3, 2.0f, 0.5f, float3(1.5, 2.25, -3.5))", { -0.254011f, 0, 0, 0 } }, + { "fractal one octave", "mx_fractal3d(1.0f, 1, 2.0f, 0.5f, float3(0.3, 0.7, 0.1))", { 0.189201f, 0, 0, 0 } }, + }); + } + + // The clearcoat sun highlight (common/coat.hlsl). At perfect mirror + // alignment N = V = L with NL = NV = 1: D = 1/(pi a^2), V = 1/4, F = F0 = + // 0.04, and the lobe is scaled by pi like the rest of the direct term, so + // the result is 0.01 / a^2 -- exactly 100 for coat roughness 0.1 (a = 0.01). + // The `time` and `frame` nodes read the value the shader sets per frame + // (mx_set_time), and the triplanar helpers match MaterialX's nodegraph. + TEST(MaterialX.GPU, TimeAndTriplanar) + { + using mx_detail::Case; + + mx_detail::check({ + { "time", "time_probe()", { 2, 0, 0, 0 } }, + { "frame", "frame_probe()", { 48, 0, 0, 0 } }, + + // Weights: normalised |n|, sharpened by 1/blend, renormalised. + { "weights along x", "mx_triplanar_weights(float3(1, 0, 0), 1.0f)", { 1, 0, 0, 0 } }, + { "weights diagonal", "mx_triplanar_weights(normalize(float3(1, 1, 1)), 1.0f)", { 1.0f / 3, 1.0f / 3, 1.0f / 3, 0 }, 1e-4f }, + { "weights blend 1", "mx_triplanar_weights(float3(0.75, 0.25, 0.0), 1.0f)", { 0.75f, 0.25f, 0, 0 }, 1e-4f }, + { "weights sharp", "mx_triplanar_weights(float3(0.75, 0.25, 0.0), 0.03f)", { 1, 0, 0, 0 }, 1e-3f }, + + // UVs per projection axis and upaxis (NG_triplanarprojection's switches). + { "uv x, z up", "mx_triplanar_uv(0, float3(1, 2, 3), 2)", { 2, 3, 0, 0 } }, + { "uv x, y up", "mx_triplanar_uv(0, float3(1, 2, 3), 1)", { 3, 2, 0, 0 } }, + { "uv y, z up", "mx_triplanar_uv(1, float3(1, 2, 3), 2)", { 1, 3, 0, 0 } }, + { "uv y, x up", "mx_triplanar_uv(1, float3(1, 2, 3), 0)", { 3, 1, 0, 0 } }, + { "uv z, z up", "mx_triplanar_uv(2, float3(1, 2, 3), 2)", { 1, 2, 0, 0 } }, + { "uv z, x up", "mx_triplanar_uv(2, float3(1, 2, 3), 0)", { -2, 1, 0, 0 } }, + }, R"hlsl( +float time_probe() { mx_set_time(2.0); return mx_time(24.0); } +float frame_probe() { mx_set_time(2.0); return mx_frame(); } +)hlsl"); + } + + TEST(MaterialX.GPU, ClearcoatSunSpecular) + { + using mx_detail::Case; + + // The lobe headers include each other; splice them in order with the + // includes removed, so the test shader needs no include handler. + auto dir = to_path(L"shaders/common"); + std::string lobes; + + for (const char* name : { "shading_model.hlsl", "coat.hlsl", "sheen.hlsl", "aniso.hlsl", "iridescence.hlsl", "lobes.hlsl", "medium.hlsl" }) + { + auto text = mx_detail::read_text(dir / name); + + for (auto at = text.find("#include \""); at != std::string::npos; at = text.find("#include \"")) + text.erase(at, text.find('\n', at) - at); + + lobes += text + "\n"; + } + + const std::string up = "float3(0, 1, 0)"; + const std::string coated = "pack_material(SHADING_MODEL_CLEARCOAT, float3(1.0, 0.1, 0.0))"; + const std::string sheen = "pack_material(SHADING_MODEL_SHEEN, float3(1.0, 0.5, 0.0))"; + + mx_detail::check({ + { "mirror alignment", "coat_sun_specular(" + coated + ", " + up + ", " + up + ", " + up + ", 1.0)", { 100, 100, 100, 0 }, 0.5f }, + { "half weight", "coat_sun_specular(pack_material(SHADING_MODEL_CLEARCOAT, float3(0.5, 0.1, 0.0)), " + up + ", " + up + ", " + up + ", 1.0)", { 50, 50, 50, 0 }, 0.5f }, + { "in shadow", "coat_sun_specular(" + coated + ", " + up + ", " + up + ", " + up + ", 0.0)", { 0, 0, 0, 0 } }, + { "light below horizon","coat_sun_specular(" + coated + ", " + up + ", " + up + ", float3(0, -1, 0), 1.0)", { 0, 0, 0, 0 } }, + { "standard material", "coat_sun_specular(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 1.0)", { 0, 0, 0, 0 } }, + { "reflection head-on", "coat_reflection(" + coated + ", " + up + ", " + up + ", float3(1, 2, 3))", { 0.04f, 0.08f, 0.12f, 0 } }, + { "reflection grazing", "coat_reflection(" + coated + ", " + up + ", float3(1, 0, 0), float3(1, 2, 3))", { 1, 2, 3, 0 } }, + { "reflection half weight", "coat_reflection(pack_material(SHADING_MODEL_CLEARCOAT, float3(0.5, 0.1, 0.0)), " + up + ", " + up + ", float3(1, 2, 3))", { 0.02f, 0.04f, 0.06f, 0 } }, + { "reflection standard", "coat_reflection(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", float3(1, 2, 3))", { 0, 0, 0, 0 } }, + { "model round trip", "float(material_model(pack_material(SHADING_MODEL_CLEARCOAT, float3(0.2, 0.3, 0.4))))", { 1, 0, 0, 0 } }, + { "param round trip", "material_params(pack_material(SHADING_MODEL_CLEARCOAT, float3(0.2, 0.3, 0.4)))", { 0.2f, 0.3f, 0.4f, 0 }, 1e-4f }, + { "graph coat off", "float(material_model(pack_material_from_graph(0.0, 0.5, 0.0, 0.3, 0.0, 0.0, float3(0, 1, 0), float3(1, 0, 0))))", { 0, 0, 0, 0 } }, + { "graph coat on", "float(material_model(pack_material_from_graph(0.8, 0.5, 0.0, 0.3, 0.0, 0.0, float3(0, 1, 0), float3(1, 0, 0))))", { 1, 0, 0, 0 } }, + // A coat and a sheen on one material: one shading model, the coat wins. + { "graph coat beats sheen", "float(material_model(pack_material_from_graph(0.8, 0.5, 0.9, 0.3, 0.0, 0.0, float3(0, 1, 0), float3(1, 0, 0))))", { 1, 0, 0, 0 } }, + { "graph sheen", "float(material_model(pack_material_from_graph(0.0, 0.5, 0.9, 0.3, 0.0, 0.0, float3(0, 1, 0), float3(1, 0, 0))))", { 2, 0, 0, 0 } }, + + // Subsurface: lowest priority, radius = weight * scale * radius, sqrt-encoded. + { "graph subsurface", "float(material_model(pack_material_from_graph(0.0, 0.5, 0.0, 0.3, 0.0, 0.0, 1.0, float3(1, 1, 1), 1.0, " + up + ", float3(1, 0, 0))))", { 4, 0, 0, 0 } }, + { "aniso beats subsurface", "float(material_model(pack_material_from_graph(0.0, 0.5, 0.0, 0.3, 0.6, 0.0, 1.0, float3(1, 1, 1), 1.0, " + up + ", float3(1, 0, 0))))", { 3, 0, 0, 0 } }, + { "subsurface radius", "sss_decode_radius(material_params(pack_material_from_graph(0.0, 0.5, 0.0, 0.3, 0.0, 0.0, 1.0, float3(0.1, 0.4, 1.6), 1.0, " + up + ", float3(1, 0, 0))))", { 0.1f, 0.4f, 1.6f, 0 }, 1e-4f }, + { "subsurface scaled", "sss_decode_radius(material_params(pack_material_from_graph(0.0, 0.5, 0.0, 0.3, 0.0, 0.0, 0.5, float3(1, 1, 1), 2.0, " + up + ", float3(1, 0, 0))))", { 1, 1, 1, 0 }, 1e-4f }, + // An 8-bit target keeps small radii to a few percent thanks to the sqrt encoding. + { "subsurface 8-bit", "sss_decode_radius(round(sss_encode_radius(float3(0.01, 0.05, 0.5)) * 255.0) / 255.0)", { 0.0104f, 0.0498f, 0.4980f, 0 }, 2e-3f }, + + // Anisotropy: one shading model, ranked below coat and sheen. + { "graph anisotropy", "float(material_model(pack_material_from_graph(0.0, 0.5, 0.0, 0.3, 0.6, 0.0, " + up + ", float3(1, 0, 0))))", { 3, 0, 0, 0 } }, + { "sheen beats aniso", "float(material_model(pack_material_from_graph(0.0, 0.5, 0.9, 0.3, 0.6, 0.0, " + up + ", float3(1, 0, 0))))", { 2, 0, 0, 0 } }, + // The tangent survives the encode/decode through the normal-only frame, up to + // sign (a tangent and its negation are the same highlight)... + { "tangent round trip", "abs(dot(aniso_decode_tangent(float3(0.3333333, 0.6666667, 0.6666667), aniso_encode_tangent(float3(0.3333333, 0.6666667, 0.6666667), normalize(cross(float3(0.3333333, 0.6666667, 0.6666667), float3(0, 0, 1))))), normalize(cross(float3(0.3333333, 0.6666667, 0.6666667), float3(0, 0, 1)))))", { 1, 0, 0, 0 }, 1e-3f }, + // ...and a quarter-turn rotation lands on the bitangent. + { "rotated tangent", "abs(dot(aniso_decode_tangent(" + up + ", material_params(pack_material_from_graph(0.0, 0.0, 0.0, 0.0, 0.5, 0.25, " + up + ", float3(1, 0, 0))).y), cross(" + up + ", float3(1, 0, 0))))", { 1, 0, 0, 0 }, 1e-3f }, + // Mirror alignment (N = V = L): isotropic limit is 0.25 * F / a^2 = 4 for + // roughness 0.5 (a = 0.25), F = 1; with anisotropy 0.5 the alphas are + // 0.375 / 0.125, giving 0.25 / (0.375 * 0.125) = 5.3333. + { "aniso isotropic limit", "aniso_sun_specular(pack_material(SHADING_MODEL_ANISOTROPIC, float3(0.0, 0.0, 0.0)), " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5)", { 4, 4, 4, 0 }, 1e-3f }, + { "aniso stretched", "aniso_sun_specular(pack_material(SHADING_MODEL_ANISOTROPIC, float3(0.5, 0.0, 0.0)), " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5)", { 5.3333f, 5.3333f, 5.3333f, 0 }, 2e-3f }, + // With V up and T = +x, B = -z: a light tilted toward the tangent (wider alpha) + // is closer to the highlight than one tilted the same angle toward the bitangent. + { "aniso is directional", "float(aniso_sun_specular(pack_material(SHADING_MODEL_ANISOTROPIC, float3(0.8, 0.0, 0.0)), " + up + ", " + up + ", normalize(float3(0.3, 1, 0)), 1.0, float3(1, 1, 1), 0.4).x > aniso_sun_specular(pack_material(SHADING_MODEL_ANISOTROPIC, float3(0.8, 0.0, 0.0)), " + up + ", " + up + ", normalize(float3(0, 1, -0.3)), 1.0, float3(1, 1, 1), 0.4).x)", { 1, 0, 0, 0 } }, + { "aniso in shadow", "aniso_sun_specular(pack_material(SHADING_MODEL_ANISOTROPIC, float3(0.5, 0.0, 0.0)), " + up + ", " + up + ", " + up + ", 0.0, float3(1, 1, 1), 0.5)", { 0, 0, 0, 0 } }, + { "aniso on standard", "aniso_sun_specular(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5)", { 0, 0, 0, 0 } }, + + // The base layer sun highlight metals need (no diffuse, so otherwise no sun light). + // Mirror alignment, F0 = 1, roughness 0.5 (a = 0.25): 0.25 * F / a^2 = 4, as the + // anisotropic model's isotropic limit; scaled by metalness; not for anisotropic pixels. + { "metal sun highlight", "base_sun_specular(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5, 1.0)", { 4, 4, 4, 0 }, 1e-3f }, + { "half metal", "base_sun_specular(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5, 0.5)", { 2, 2, 2, 0 }, 1e-3f }, + { "dielectric no highlight", "base_sun_specular(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 1.0, float3(0.04, 0.04, 0.04), 0.5, 0.0)", { 0, 0, 0, 0 } }, + { "metal coloured by F0", "base_sun_specular(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 1.0, float3(1.0, 0.5, 0.25), 0.5, 1.0)", { 4, 2, 1, 0 }, 1e-3f }, + { "metal in shadow", "base_sun_specular(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 0.0, float3(1, 1, 1), 0.5, 1.0)", { 0, 0, 0, 0 } }, + { "metal light below horizon", "base_sun_specular(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", float3(0, -1, 0), 1.0, float3(1, 1, 1), 0.5, 1.0)", { 0, 0, 0, 0 } }, + { "aniso keeps its own", "base_sun_specular(pack_material(SHADING_MODEL_ANISOTROPIC, float3(0.5, 0.0, 0.0)), " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5, 1.0)", { 0, 0, 0, 0 } }, + { "lobes include the base highlight", "material_sun_lobes(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5, float3(1, 1, 1), 1.0)", { 4, 4, 4, 0 }, 1e-3f }, + + // A sheen tinted like the base colour scales the lobe by the colour normalised to + // its strongest channel; tint 0 leaves it white. + { "sheen tinted", "sheen_sun_specular(pack_material(SHADING_MODEL_SHEEN, float3(1.0, 0.5, 1.0)), " + up + ", " + up + ", float3(0.6, 0.8, 0), 1.0, float3(1.0, 0.5, 0.25))", { 0.006f, 0.003f, 0.0015f, 0 }, 1e-5f }, + { "sheen untinted", "sheen_sun_specular(" + sheen + ", " + up + ", " + up + ", float3(0.6, 0.8, 0), 1.0, float3(1.0, 0.5, 0.25))", { 0.006f, 0.006f, 0.006f, 0 }, 1e-5f }, + { "graph sheen tint", "material_params(pack_material_from_graph(0.0, 0.5, 0.9, 0.3, 0.0, 0.0, 0.0, float3(1, 1, 1), 1.0, 0.7, " + up + ", float3(1, 0, 0))).z", { 0.7f, 0, 0, 0 }, 1e-4f }, + // Ambient sheen scales linearly with weight and with the ambient radiance, is + // zero for other models, and stays a fraction of the incoming light (albedo < 1). + { "sheen ambient half weight", "sheen_indirect(pack_material(SHADING_MODEL_SHEEN, float3(0.5, 0.5, 0.0)), " + up + ", " + up + ", 1.0, 1.0) / sheen_indirect(" + sheen + ", " + up + ", " + up + ", 1.0, 1.0)", { 0.5f, 0.5f, 0.5f, 0 }, 1e-4f }, + { "sheen ambient radiance", "sheen_indirect(" + sheen + ", " + up + ", " + up + ", 1.0, 3.0) / sheen_indirect(" + sheen + ", " + up + ", " + up + ", 1.0, 1.0)", { 3, 3, 3, 0 }, 1e-4f }, + { "sheen ambient other model", "sheen_indirect(" + coated + ", " + up + ", " + up + ", 1.0, 1.0)", { 0, 0, 0, 0 } }, + { "sheen ambient bounded", "float3(sheen_directional_albedo(1.0, 0.25) > 0 && sheen_directional_albedo(1.0, 0.25) < 1, sheen_directional_albedo(0.2, 0.25) > 0 && sheen_directional_albedo(0.2, 0.25) < 1, 0)", { 1, 1, 0, 0 } }, + // Absorbing solids: the tint is reached after `depth`; depth 0 is no volume, with the + // tint taken once at the interface instead (not an absorption over zero distance). + { "medium sigma", "medium_sigma(float3(0.5, 0.25, 1.0), 1.0)", { 0.693147f, 1.386294f, 0, 0 }, 1e-4f }, + { "medium sigma, deeper", "medium_sigma(float3(0.5, 0.25, 1.0), 2.0)", { 0.346574f, 0.693147f, 0, 0 }, 1e-4f }, + { "medium without depth", "medium_sigma(float3(0.5, 0.25, 1.0), 0.0)", { 0, 0, 0, 0 } }, + { "entry tint, volume", "medium_entry_tint(float3(0.5, 0.25, 1.0), 1.0)", { 1, 1, 1, 0 } }, + { "entry tint, no depth", "medium_entry_tint(float3(0.5, 0.25, 1.0), 0.0)", { 0.5f, 0.25f, 1.0f, 0 } }, + // Thin film (iridescence.hlsl). No film is the plain Schlick Fresnel: + // 0.7 + 0.3 * 0.4^5. The coloured results are the same algorithm evaluated + // independently in double precision (titanium F0 under a 2.488 film). + { "film: none", "iridescent_fresnel(0.6, 0.0, 1.5, float3(0.7, 0.7, 0.7))", { 0.703072f, 0.703072f, 0.703072f, 0 }, 1e-4f }, + { "film: 20 nm titanium", "iridescent_fresnel(1.0, 20.0, 2.488, float3(0.441, 0.4, 0.361))", { 0.4143f, 0.3210f, 0.2540f, 0 }, 5e-3f }, + { "film: 80 nm titanium", "iridescent_fresnel(1.0, 80.0, 2.488, float3(0.441, 0.4, 0.361))", { 0.0610f, 0.2420f, 0.3360f, 0 }, 5e-3f }, + { "film: F0 round trip", "iri_schlick(iridescent_f0(0.8, 80.0, 2.488, float3(0.441, 0.4, 0.361)), 0.8) - iridescent_fresnel(0.8, 80.0, 2.488, float3(0.441, 0.4, 0.361))", { 0, 0, 0, 0 }, 2e-3f }, + { "film: packed", "float3(thinfilm_thickness(pack_material_from_graph(0.0, 0.1, 0.0, 0.3, 0.0, 0.0, 0.0, float3(1, 1, 1), 1.0, 0.0, 80.0, 2.0, " + up + ", float3(1, 0, 0))), thinfilm_ior(pack_material_from_graph(0.0, 0.1, 0.0, 0.3, 0.0, 0.0, 0.0, float3(1, 1, 1), 1.0, 0.0, 80.0, 2.0, " + up + ", float3(1, 0, 0))), material_model(pack_material_from_graph(0.0, 0.1, 0.0, 0.3, 0.0, 0.0, 0.0, float3(1, 1, 1), 1.0, 0.0, 80.0, 2.0, " + up + ", float3(1, 0, 0))))", { 80.0f, 2.0f, 5.0f, 0 }, 1e-3f }, + { "film: metal highlight", "base_sun_specular(pack_material(SHADING_MODEL_THINFILM, float3(0.08, 0.744, 0.0)), " + up + ", " + up + ", " + up + ", 1.0, float3(0.441, 0.4, 0.361), 0.5, 1.0)", { 0.244f, 0.968f, 1.344f, 0 }, 2e-2f }, + // The coat takes F of the light before the base layer sees it: 1 - weight * F. + { "coat attenuation head-on", "coat_base_attenuation(" + coated + ", " + up + ", " + up + ")", { 0.96f, 0, 0, 0 }, 1e-4f }, + { "coat attenuation grazing", "coat_base_attenuation(" + coated + ", " + up + ", float3(1, 0, 0))", { 0.0f, 0, 0, 0 }, 1e-4f }, + { "coat attenuation half", "coat_base_attenuation(pack_material(SHADING_MODEL_CLEARCOAT, float3(0.5, 0.1, 0.0)), " + up + ", " + up + ")", { 0.98f, 0, 0, 0 }, 1e-4f }, + { "no attenuation standard", "coat_base_attenuation(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ")", { 1, 0, 0, 0 } }, + { "no attenuation sheen", "coat_base_attenuation(" + sheen + ", " + up + ", " + up + ")", { 1, 0, 0, 0 } }, + + // Sheen, N = up. Head-on view, light at (0.6, 0.8, 0): NH^2 = 0.9 so + // sin^2 = 0.1; a = 0.25 -> D = 6 * 0.1^2 / (2 pi), visibility 1/4, + // NL = 0.8, scaled by pi: exactly 0.006. + { "sheen head-on", "sheen_sun_specular(" + sheen + ", " + up + ", " + up + ", float3(0.6, 0.8, 0), 1.0)", { 0.006f, 0.006f, 0.006f, 0 }, 1e-5f }, + // V = (0.8, 0.6, 0), L = (0.6, 0.8, 0): NV 0.6, NL 0.8, NH^2 = 0.5. + { "sheen oblique", "sheen_sun_specular(" + sheen + ", " + up + ", float3(0.8, 0.6, 0), float3(0.6, 0.8, 0), 1.0)", { 0.16304f, 0.16304f, 0.16304f, 0 }, 2e-4f }, + { "sheen half weight", "sheen_sun_specular(pack_material(SHADING_MODEL_SHEEN, float3(0.5, 0.5, 0.0)), " + up + ", " + up + ", float3(0.6, 0.8, 0), 1.0)", { 0.003f, 0.003f, 0.003f, 0 }, 1e-5f }, + { "sheen in shadow", "sheen_sun_specular(" + sheen + ", " + up + ", " + up + ", float3(0.6, 0.8, 0), 0.0)", { 0, 0, 0, 0 } }, + { "sheen on coat", "sheen_sun_specular(" + coated + ", " + up + ", " + up + ", float3(0.6, 0.8, 0), 1.0)", { 0, 0, 0, 0 } }, + { "coat on sheen", "coat_sun_specular(" + sheen + ", " + up + ", " + up + ", " + up + ", 1.0)", { 0, 0, 0, 0 } }, + { "lobes: sheen", "material_sun_lobes(" + sheen + ", " + up + ", " + up + ", float3(0.6, 0.8, 0), 1.0, float3(1, 1, 1), 0.5)", { 0.006f, 0.006f, 0.006f, 0 }, 1e-5f }, + { "lobes: coat", "material_sun_lobes(" + coated + ", " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5)", { 100, 100, 100, 0 }, 0.5f }, + { "lobes: standard", "material_sun_lobes(pack_material(SHADING_MODEL_STANDARD, 0.0), " + up + ", " + up + ", " + up + ", 1.0, float3(1, 1, 1), 0.5)", { 0, 0, 0, 0 } }, + }, lobes); + } + + // The subsurface blur passes (sss.prism) on a synthetic G-buffer: the lit + // target is blurred along x then y for SUBSURFACE pixels only. A uniform + // region must come back unchanged (the kernel is normalised), a step must + // smooth into a monotonic ramp that stays within the input range, and + // other pixels must not be touched at all. + TEST(MaterialX.GPU, SubsurfaceBlur) + { + auto& device = RenderSystem::get().device(); + constexpr uint W = 128, H = 128; + const auto rw = HAL::ResFlags::ShaderResource | HAL::ResFlags::UnorderedAccess; + + auto make = [&](HAL::Format format, HAL::ResFlags flags) + { + return std::make_shared(device, + HAL::ResourceDesc::Tex2D(format, { W, H }, 1, 1, flags), HAL::HeapType::DEFAULT); + }; + + auto albedo_tex = make(HAL::Format::R8G8B8A8_UNORM, HAL::ResFlags::ShaderResource); + auto normals_tex = make(HAL::Format::R8G8B8A8_UNORM, HAL::ResFlags::ShaderResource); + auto material_tex = make(HAL::Format::R8G8B8A8_UNORM, HAL::ResFlags::ShaderResource); + auto depth_tex = make(HAL::Format::R32_FLOAT, HAL::ResFlags::ShaderResource); + auto result_tex = make(HAL::Format::R32G32B32A32_FLOAT, rw); + auto temp_tex = make(HAL::Format::R32G32B32A32_FLOAT, rw); + + // Rows 0-7: a step (lit on the left half). Rows 70-77: uniformly lit. Rows + // 20-29 and 100-109: not subsurface, with a marker value that must + // survive. The groups are further apart than the widest kernel reaches + // (48 px), so they cannot blur into each other vertically; taps are 3 px + // apart, so a thin separator row would simply be hopped over. + std::vector albedo(W * H * 4), normals(W * H * 4), material(W * H * 4); + std::vector depth(W * H, 0.5f), result(W * H * 4); + + for (uint y = 0; y < H; y++) + for (uint x = 0; x < W; x++) + { + const size_t i = (size_t)y * W + x; + const bool step_rows = y < 8; + const bool sss = step_rows || (y >= 70 && y < 78); + const float lit = step_rows ? (x < W / 2 ? 1.0f : 0.0f) : 1.0f; + + albedo[i * 4 + 0] = albedo[i * 4 + 1] = albedo[i * 4 + 2] = 128; + albedo[i * 4 + 3] = 0; + normals[i * 4 + 0] = normals[i * 4 + 1] = 128; + normals[i * 4 + 2] = 255; + normals[i * 4 + 3] = 128; + + // SUBSURFACE = 4 in alpha, radius at the encoding's maximum. + material[i * 4 + 0] = material[i * 4 + 1] = material[i * 4 + 2] = sss ? 255 : 0; + material[i * 4 + 3] = sss ? 4 : 0; + + const float v = sss ? lit : 7.0f; + result[i * 4 + 0] = result[i * 4 + 1] = result[i * 4 + 2] = v; + result[i * 4 + 3] = 1.0f; + } + + { + auto upload = device.get_upload_list(); + auto put = [&](HAL::TextureResource* tex, const void* src, uint bytes_per_pixel) + { + upload->get_copy().update_texture(tex, ivec3(0, 0, 0), ivec3(W, H, 1), 0, + reinterpret_cast(src), W * bytes_per_pixel); + }; + put(albedo_tex.get(), albedo.data(), 4); + put(normals_tex.get(), normals.data(), 4); + put(material_tex.get(), material.data(), 4); + put(depth_tex.get(), depth.data(), 4); + put(result_tex.get(), result.data(), 16); + upload->execute_and_wait(); + } + + camera cam; + cam.set_projection_params(Math::pi / 4, float(W) / H, 0.1f, 100.0f); + cam.update(); + + auto& queue = device.get_queue(HAL::CommandListType::DIRECT); + + // The blur is dispatched indirectly over the 16x16 tiles SSSClassify lists, + // with the list's GPU counter copied into the dispatch arguments, as the + // frame graph passes do (sss.prism). + HAL::StructuredBufferView tile_list(device, (W / 16) * (H / 16), HAL::counterType::SELF, + HAL::ResFlags::ShaderResource | HAL::ResFlags::UnorderedAccess); + HAL::StructuredBufferView tile_args(device, 1); + { + DispatchArguments init{ 0, 1, 1 }; + auto upload = device.get_upload_list(); + upload->get_copy().update(tile_args, 0, std::span{ &init, 1 }); + upload->execute_and_wait(); + } + + auto bind_gbuffer = [&](Slots::Subsurface::SSSBlurParams& params, HAL::Texture2DView& albedo_view, HAL::Texture2DView& normals_view, + HAL::Texture2DView& material_view, HAL::Texture2DView& depth_view) + { + params.GetGbuffer().GetAlbedo() = albedo_view.texture2D; + params.GetGbuffer().GetNormals() = normals_view.texture2D; + params.GetGbuffer().GetMaterial() = material_view.texture2D; + // The table has no [Auto] for these; the blur never samples them. + params.GetGbuffer().GetSpecular() = albedo_view.texture2D; + params.GetGbuffer().GetMotion() = albedo_view.texture2D; + params.GetGbuffer().GetDepth() = depth_view.texture2D; + }; + + // read_counter delivers its value from another thread, possibly after + // execute_and_wait returns, so it is waited for explicitly. + std::atomic tile_count{ -1 }; + + { + auto list = queue->get_free_list(); + list->begin(L"SubsurfaceClassify"); + + HAL::Texture2DView albedo_view(albedo_tex, *list); + HAL::Texture2DView normals_view(normals_tex, *list); + HAL::Texture2DView material_view(material_tex, *list); + HAL::Texture2DView depth_view(depth_tex, *list); + + auto& compute = list->get_compute(); + compute.set_signature(Layouts::DefaultLayout); + + Slots::Subsurface::SSSClassifyParams params; + params.GetGbuffer().GetAlbedo() = albedo_view.texture2D; + params.GetGbuffer().GetNormals() = normals_view.texture2D; + params.GetGbuffer().GetMaterial() = material_view.texture2D; + params.GetGbuffer().GetSpecular() = albedo_view.texture2D; + params.GetGbuffer().GetMotion() = albedo_view.texture2D; + params.GetGbuffer().GetDepth() = depth_view.texture2D; + compute.set(params); + + Slots::Subsurface::SSSTilesAppend tiles; + tiles.GetTiles() = tile_list.appendStructuredBuffer; + compute.set(tiles); + + compute.clear_counter(tile_list); + compute.set_pipeline(); + compute.dispatch(ivec2{ int(W), int(H) }, ivec2{ 16, 16 }); + list->get_copy().copy_buffer(tile_args.resource.get(), 0, tile_list.get_counter_buffer().get(), tile_list.get_counter_offset(), 4); + list->get_copy().read_counter(tile_list, [&](uint count) { tile_count = int(count); }); + list->execute_and_wait(); + } + + // Only tiles holding subsurface pixels are listed: rows 0-7 touch tile row + // 0 and rows 70-77 touch tile row 4, eight tile columns each. + for (int waited = 0; tile_count.load() < 0 && waited < 500; waited++) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + ASSERT_EQ(16, tile_count.load()); + + // One command list per pass: the second reads what the first wrote. + auto run = [&](bool vertical, std::shared_ptr source_tex, std::shared_ptr target_tex) + { + auto list = queue->get_free_list(); + list->begin(L"SubsurfaceBlur"); + + HAL::Texture2DView albedo_view(albedo_tex, *list); + HAL::Texture2DView normals_view(normals_tex, *list); + HAL::Texture2DView material_view(material_tex, *list); + HAL::Texture2DView depth_view(depth_tex, *list); + HAL::Texture2DView source_view(source_tex, *list); + HAL::Texture2DView target_view(target_tex, *list); + + auto& compute = list->get_compute(); + compute.set_signature(Layouts::DefaultLayout); + + { + Slots::Frame::FrameInfo frameInfo; + frameInfo.GetCamera() = cam.camera_cb.current; + frameInfo.GetBrdf() = EngineAssets::brdf.get_asset()->get_texture()->texture_3d().texture3D; + frameInfo.GetBestFitNormals() = EngineAssets::best_fit_normals.get_asset()->get_texture()->texture_2d().texture2D; + compute.set(frameInfo); + } + + if (vertical) + compute.set_pipeline(); + else + compute.set_pipeline(); + + Slots::Subsurface::SSSBlurParams params; + bind_gbuffer(params, albedo_view, normals_view, material_view, depth_view); + params.GetSource() = source_view.texture2D; + params.GetTarget() = target_view.rwTexture2D; + compute.set(params); + + Slots::Subsurface::SSSTilesRead tiles; + tiles.GetTiles() = tile_list.structuredBuffer; + compute.set(tiles); + + compute.exec_indirect(tile_args, 1); + list->execute_and_wait(); + }; + + run(false, result_tex, temp_tex); + run(true, temp_tex, result_tex); + + auto data = readback_texture(result_tex.get()); + auto& mip = data->array[0]->mips[0]; + auto at = [&](uint x, uint y) { return reinterpret_cast(mip->data.data() + (size_t)y * mip->width_stride)[x * 4]; }; + + // Uniform rows come back as they went in. + std::string failures; + for (uint x = 0; x < W; x++) + if (std::abs(at(x, 72) - 1.0f) > 1e-3f) + failures += "uniform row x=" + std::to_string(x) + " is " + std::to_string(at(x, 72)) + "\n"; + + // The step: bounded, monotonic, genuinely smoothed, and symmetric about the edge. + for (uint x = 0; x < W; x++) + { + const float v = at(x, 4); + + if (v < -1e-3f || v > 1.0f + 1e-3f) + failures += "step out of range at x=" + std::to_string(x) + ": " + std::to_string(v) + "\n"; + + // Taps sit 3 px apart, so a hard edge leaves ~1% ripple; more than that is a bug. + if (x > 0 && v > at(x - 1, 4) + 0.02f) + failures += "step not monotonic at x=" + std::to_string(x) + "\n"; + } + + if (!(at(W / 2 - 1, 4) < 0.99f && at(W / 2, 4) > 0.01f)) + failures += "step was not blurred: " + std::to_string(at(W / 2 - 1, 4)) + " / " + std::to_string(at(W / 2, 4)) + "\n"; + + if (std::abs(at(W / 2 - 1, 4) + at(W / 2, 4) - 1.0f) > 0.08f) + failures += "step not symmetric about the edge: " + std::to_string(at(W / 2 - 1, 4)) + " + " + std::to_string(at(W / 2, 4)) + "\n"; + + // Non-subsurface pixels are left alone. + for (uint y : { 20u, 25u, 29u, 100u, 105u, 109u }) + for (uint x = 0; x < W; x++) + if (at(x, y) != 7.0f) + failures += "non-subsurface pixel changed at " + std::to_string(x) + "," + std::to_string(y) + "\n"; + + ASSERT_EQ(std::string(), failures); + } + + // The importer reads a .mtlx file and registers a material asset for it. + TEST(MaterialX.GPU, SampleFile) + { + mx_detail::ensure_library(); + + auto sample = to_path(L"materialx/samples/checker_standard_surface.mtlx"); + ASSERT_EQ(size_t(1), MaterialXLibrary::get().import_file(sample)); + } + + namespace mx_import + { + using Graphs = std::vector>; + + // Writes the document to a scratch file (textures are named by absolute + // path, so the location doesn't matter) and builds its graphs. + inline Graphs build(const std::string& body) + { + mx_detail::ensure_library(); + + static int counter = 0; + auto dir = std::filesystem::temp_directory_path() / "spectrum_mx_tests"; + std::filesystem::create_directories(dir); + auto file = dir / ("case_" + std::to_string(counter++) + ".mtlx"); + + { + std::ofstream out(file, std::ios::binary); + out << "\n\n" << body << "\n\n"; + } + + return MaterialXLibrary::get().build_graphs(file); + } + + // A surface shader node plus the material that uses it. + inline std::string surface(const std::string& category, const std::string& inputs, const std::string& extra_nodes = "") + { + return extra_nodes + + "<" + category + " name=\"shader\" type=\"surfaceshader\">\n" + inputs + "\n\n" + ""; + } + + inline ::FlowGraph::Node* source(const ::FlowGraph::parameter::ptr& port) + { + if (!port || port->input_connections.empty()) + return nullptr; + + return (*port->input_connections.begin())->from->owner; + } + + template + T* source_as(const ::FlowGraph::parameter::ptr& port) + { + return dynamic_cast(source(port)); + } + + inline std::string close(const std::vector& got, const std::vector& want) + { + if (got.size() != want.size()) + return "size " + std::to_string(got.size()) + " != " + std::to_string(want.size()); + + for (size_t i = 0; i < got.size(); i++) + if (std::abs(got[i] - want[i]) > 1e-5f) + return "component " + std::to_string(i) + ": " + std::to_string(got[i]) + " != " + std::to_string(want[i]); + + return {}; + } + + inline std::vector value_of(VectorNode* node) { auto v = node->get_value(); return { v.x, v.y, v.z, v.w }; } + + // Constant-only inputs: the graph outputs come out as the folded values. + inline void check_constants(const Graphs& graphs, std::vector base_color, float metallic, float roughness) + { + ASSERT_EQ(size_t(1), graphs.size()); + auto& g = graphs[0].second; + + auto* base = source_as(g->get_base_color()); + auto* met = source_as(g->get_mettalic()); + auto* rough = source_as(g->get_roughness()); + ASSERT_TRUE(base && met && rough); + + ASSERT_EQ(std::string(), close(value_of(base), base_color)); + ASSERT_EQ(std::string(), close({ met->get_value() }, { metallic })); + ASSERT_EQ(std::string(), close({ rough->get_value() }, { roughness })); + } + } + + TEST(MaterialX.GPU, StandardSurfaceConstants) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"( + + + )")); + + // base * base_color is folded into one constant, alpha 1. + check_constants(graphs, { 0.1f, 0.2f, 0.3f, 1.0f }, 0.7f, 0.35f); + ASSERT_EQ(std::string("mat"), graphs[0].first); + + // A default-valued emission, opacity and normal leave their outputs unwired. + auto& g = graphs[0].second; + ASSERT_TRUE(source(g->get_glow()) == nullptr); + ASSERT_TRUE(source(g->get_opacity()) == nullptr); + ASSERT_TRUE(source(g->get_normals()) == nullptr); + } + + // Parameters the file leaves out take the shader's own defaults, which + // standard_surface inherits from a versioned parent nodedef. + TEST(MaterialX.GPU, ShaderDefaults) + { + using namespace mx_import; + + check_constants(build(surface("standard_surface", "")), { 0.8f, 0.8f, 0.8f, 1.0f }, 0.0f, 0.2f); + } + + TEST(MaterialX.GPU, EmissionAndOpacity) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"( + + )")); + + ASSERT_EQ(size_t(1), graphs.size()); + auto& g = graphs[0].second; + + auto* glow = source_as(g->get_glow()); + auto* opacity = source_as(g->get_opacity()); + ASSERT_TRUE(glow && opacity); + + ASSERT_EQ(std::string(), close(value_of(glow), { 2.0f, 1.0f, 0.0f, 1.0f })); + ASSERT_EQ(std::string(), close({ opacity->get_value() }, { 0.5f })); + } + + // Coat and sheen map to the graph's lobe outputs; a material gets one shading + // model, so the coat wins when both are set. + TEST(MaterialX.GPU, CoatAndSheen) + { + using namespace mx_import; + + auto coated = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), coated.size()); + auto& c = coated[0].second; + ASSERT_EQ(std::string(), close({ source_as(c->get_coat())->get_value() }, { 0.7f })); + ASSERT_EQ(std::string(), close({ source_as(c->get_coat_roughness())->get_value() }, { 0.15f })); + ASSERT_TRUE(source(c->get_sheen()) == nullptr); + + auto cloth = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), cloth.size()); + auto& s = cloth[0].second; + ASSERT_EQ(std::string(), close({ source_as(s->get_sheen())->get_value() }, { 0.9f })); + ASSERT_EQ(std::string(), close({ source_as(s->get_sheen_roughness())->get_value() }, { 0.35f })); + ASSERT_TRUE(source(s->get_coat()) == nullptr); + + // gltf_pbr's sheen is a colour: its strongest component becomes the weight. + auto gltf = build(surface("gltf_pbr", + R"( + )")); + ASSERT_EQ(size_t(1), gltf.size()); + ASSERT_EQ(std::string(), close({ source_as(gltf[0].second->get_sheen())->get_value() }, { 0.5f })); + + // The sheen colour's saturation becomes the tint: white is untinted, + // a saturated colour follows the base colour. (0.5 - 0.1) / 0.5 = 0.8. + ASSERT_EQ(std::string(), close({ source_as(gltf[0].second->get_sheen_tint())->get_value() }, { 0.8f })); + + auto white_sheen = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), white_sheen.size()); + ASSERT_EQ(std::string(), close({ source_as(white_sheen[0].second->get_sheen_tint())->get_value() }, { 0.0f })); + + auto both = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), both.size()); + ASSERT_TRUE(source(both[0].second->get_coat()) != nullptr); + ASSERT_TRUE(source(both[0].second->get_sheen()) == nullptr); + } + + TEST(MaterialX.GPU, Subsurface) + { + using namespace mx_import; + + auto skin = build(surface("standard_surface", + R"( + + )")); + ASSERT_EQ(size_t(1), skin.size()); + auto& g = skin[0].second; + ASSERT_EQ(std::string(), close({ source_as(g->get_subsurface_weight())->get_value() }, { 1.0f })); + ASSERT_EQ(std::string(), close({ source_as(g->get_subsurface_scale())->get_value() }, { 0.05f })); + auto v = source_as(g->get_subsurface_radius())->get_value(); + ASSERT_EQ(std::string(), close({ v.x, v.y, v.z }, { 1.0f, 0.35f, 0.2f })); + + // Lowest priority of the shading models. + auto anisotropic = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), anisotropic.size()); + ASSERT_TRUE(source(anisotropic[0].second->get_anisotropy()) != nullptr); + ASSERT_TRUE(source(anisotropic[0].second->get_subsurface_weight()) == nullptr); + + // No subsurface weight, nothing wired. + auto plain = build(surface("standard_surface", "")); + ASSERT_EQ(size_t(1), plain.size()); + ASSERT_TRUE(source(plain[0].second->get_subsurface_weight()) == nullptr); + } + + // Transmission maps onto the graph's transparency outputs, in MaterialX's own + // names. A wired transmission weight is what makes the material translucent. + TEST(MaterialX.GPU, Transmission) + { + using namespace mx_import; + + auto glass = build(surface("standard_surface", + R"( + + + )")); + ASSERT_EQ(size_t(1), glass.size()); + auto& g = glass[0].second; + ASSERT_EQ(std::string(), close({ source_as(g->get_transmission_weight())->get_value() }, { 0.8f })); + ASSERT_EQ(std::string(), close({ source_as(g->get_ior())->get_value() }, { 1.33f })); + ASSERT_EQ(std::string(), close({ source_as(g->get_transmission_depth())->get_value() }, { 0.4f })); + auto tint = source_as(g->get_transmission_color())->get_value(); + ASSERT_EQ(std::string(), close({ tint.x, tint.y, tint.z }, { 0.9f, 0.95f, 1.0f })); + // thin_walled defaults to false: a solid volume, which the graph expresses as thickness > 0. + ASSERT_TRUE(source(g->get_thickness()) != nullptr); + + auto thin = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), thin.size()); + ASSERT_TRUE(source(thin[0].second->get_transmission_weight()) != nullptr); + ASSERT_TRUE(source(thin[0].second->get_thickness()) == nullptr); + + auto openpbr = build(surface("open_pbr_surface", + R"( + )")); + ASSERT_EQ(size_t(1), openpbr.size()); + ASSERT_EQ(std::string(), close({ source_as(openpbr[0].second->get_transmission_weight())->get_value() }, { 0.6f })); + ASSERT_EQ(std::string(), close({ source_as(openpbr[0].second->get_ior())->get_value() }, { 1.45f })); + + auto gltf = build(surface("gltf_pbr", + R"( + + )")); + ASSERT_EQ(size_t(1), gltf.size()); + ASSERT_EQ(std::string(), close({ source_as(gltf[0].second->get_thickness())->get_value() }, { 0.2f })); + ASSERT_EQ(std::string(), close({ source_as(gltf[0].second->get_transmission_depth())->get_value() }, { 2.0f })); + + // No transmission: the material stays opaque. + auto opaque = build(surface("standard_surface", "")); + ASSERT_EQ(size_t(1), opaque.size()); + ASSERT_TRUE(source(opaque[0].second->get_transmission_weight()) == nullptr); + ASSERT_TRUE(source(opaque[0].second->get_ior()) == nullptr); + } + + // Thin film maps onto the graph's film outputs; it is the lowest-priority + // shading model, so another one wins. + TEST(MaterialX.GPU, ThinFilm) + { + using namespace mx_import; + + auto film = build(surface("standard_surface", + R"( + + )")); + ASSERT_EQ(size_t(1), film.size()); + ASSERT_EQ(std::string(), close({ source_as(film[0].second->get_thin_film_thickness())->get_value() }, { 20.0f })); + ASSERT_EQ(std::string(), close({ source_as(film[0].second->get_thin_film_ior())->get_value() }, { 2.488f })); + + auto none = build(surface("standard_surface", "")); + ASSERT_EQ(size_t(1), none.size()); + ASSERT_TRUE(source(none[0].second->get_thin_film_thickness()) == nullptr); + + auto coated = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), coated.size()); + ASSERT_TRUE(source(coated[0].second->get_thin_film_thickness()) == nullptr); + + // OpenPBR's film weight gates it. + auto off = build(surface("open_pbr_surface", + R"( + )")); + ASSERT_EQ(size_t(1), off.size()); + ASSERT_TRUE(source(off[0].second->get_thin_film_thickness()) == nullptr); + } + + // The transparency mode follows from the wiring: transmission (or IOR) wired + // -> Translucent, else opacity wired -> Masked, else Opaque. Generating the + // material also builds the translucent hit shader variant for the first. + TEST(MaterialX.GPU, TransparencyModes) + { + using namespace mx_import; + + auto mode_of = [&](const std::string& inputs, const std::string& extra = "", const std::string& category = "standard_surface") + { + auto graphs = build(surface(category, inputs, extra)); + + if (graphs.size() != 1) + throw Test::TestFailure("import produced " + std::to_string(graphs.size()) + " graphs"); + + materials::universal_material material(graphs[0].second); + material.generate_material(); + return material.get_transparency_mode(); + }; + + ASSERT_EQ((int)Meshes::TransparencyMode::Opaque, (int)mode_of("")); + ASSERT_EQ((int)Meshes::TransparencyMode::Translucent, (int)mode_of(R"()")); + + // A constant opacity is a blended surface; one driven by a node is a cutout. + ASSERT_EQ((int)Meshes::TransparencyMode::Translucent, (int)mode_of(R"()")); + ASSERT_EQ((int)Meshes::TransparencyMode::Masked, (int)mode_of( + R"()", R"()")); + + // glTF says which: mask cuts out, blend blends, the default ignores alpha. + ASSERT_EQ((int)Meshes::TransparencyMode::Masked, (int)mode_of(R"()", "", "gltf_pbr")); + ASSERT_EQ((int)Meshes::TransparencyMode::Translucent, (int)mode_of(R"()", "", "gltf_pbr")); + ASSERT_EQ((int)Meshes::TransparencyMode::Opaque, (int)mode_of(R"()", "", "gltf_pbr")); + } + + // A constant node feeding a matching-type input generates a shader that builds. + TEST(MaterialX.GPU, ConstantNodeInput) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"()", + R"()")); + ASSERT_EQ(size_t(1), graphs.size()); + + materials::universal_material material(graphs[0].second); + material.generate_material(); + // Folded into the input's constant rather than built as a node. + auto* rough = source_as(graphs[0].second->get_roughness()); + ASSERT_TRUE(rough != nullptr); + ASSERT_EQ(std::string(), close({ rough->get_value() }, { 0.35f })); + } + + // Opacity's cutoff: a blended surface wires the threshold to 0, a cutout keeps + // the default 0.5 unless the file sets another. + TEST(MaterialX.GPU, OpacityThreshold) + { + using namespace mx_import; + + auto threshold = [&](const std::string& category, const std::string& inputs) -> std::pair + { + auto graphs = build(surface(category, inputs)); + + if (graphs.size() != 1) + throw Test::TestFailure("import produced " + std::to_string(graphs.size()) + " graphs"); + + auto* node = source_as(graphs[0].second->get_opacity_threshold()); + return { node != nullptr, node ? node->get_value() : 0.5f }; + }; + + auto blend = threshold("standard_surface", R"()"); + ASSERT_TRUE(blend.first); + ASSERT_EQ(std::string(), close({ blend.second }, { 0.0f })); + + auto cutout = threshold("gltf_pbr", R"()"); + ASSERT_TRUE(cutout.first); + ASSERT_EQ(std::string(), close({ cutout.second }, { 0.3f })); + + // The default cutoff is the graph's own default, so nothing is wired. + ASSERT_TRUE(!threshold("gltf_pbr", R"()").first); + + auto usd_cutout = threshold("UsdPreviewSurface", R"()"); + ASSERT_TRUE(usd_cutout.first); + ASSERT_EQ(std::string(), close({ usd_cutout.second }, { 0.2f })); + + // USD: a zero threshold blends. + auto usd_blend = threshold("UsdPreviewSurface", R"()"); + ASSERT_TRUE(usd_blend.first); + ASSERT_EQ(std::string(), close({ usd_blend.second }, { 0.0f })); + } + + // A shader input wired to a output (nodegraph="g" output="out"), the + // way most exported materials are written, builds the graph's nodes instead of + // being dropped: the inputs end up driven, not at constant defaults. + TEST(MaterialX.GPU, NodeGraphInputs) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"( + )", + R"( + + + + + )")); + + ASSERT_EQ(size_t(1), graphs.size()); + auto& g = graphs[0].second; + + ASSERT_TRUE(source(g->get_base_color()) != nullptr); + ASSERT_TRUE(dynamic_cast(source(g->get_base_color())) == nullptr); + ASSERT_TRUE(source(g->get_roughness()) != nullptr); + ASSERT_TRUE(dynamic_cast(source(g->get_roughness())) == nullptr); + } + + // GBufferDownsampler's roughness verdict decides which tiles trace their own + // full-res reflection ray instead of copying the (isotropic) half-res one. A tile + // holding an anisotropic pixel has to trace regardless of roughness and + // metalness; the existing "glossy and metallic" rule still applies. + TEST(MaterialX.GPU, TileClassifyAnisotropic) + { + auto& device = RenderSystem::get().device(); + constexpr uint W = 64, H = 64, T = W / 8; + const auto rw = HAL::ResFlags::ShaderResource | HAL::ResFlags::UnorderedAccess; + + auto make = [&](HAL::Format format, uint w, uint h, HAL::ResFlags flags) + { + return std::make_shared(device, + HAL::ResourceDesc::Tex2D(format, { w, h }, 1, 1, flags), HAL::HeapType::DEFAULT); + }; + + auto albedo_tex = make(HAL::Format::R8G8B8A8_UNORM, W, H, HAL::ResFlags::ShaderResource); + auto normals_tex = make(HAL::Format::R8G8B8A8_UNORM, W, H, HAL::ResFlags::ShaderResource); + auto material_tex = make(HAL::Format::R8G8B8A8_UNORM, W, H, HAL::ResFlags::ShaderResource); + auto depth_tex = make(HAL::Format::R32_FLOAT, W, H, HAL::ResFlags::ShaderResource); + auto half_depth = make(HAL::Format::R32_FLOAT, W / 2, H / 2, rw); + auto half_normals = make(HAL::Format::R8G8B8A8_UNORM, W / 2, H / 2, rw); + auto tile_mask = make(HAL::Format::R8_UINT, W, H, rw); + auto tile_flags = make(HAL::Format::R8_UINT, T, T, rw); + auto rough_flags = make(HAL::Format::R8_UINT, T, T, rw); + + // A flat, rough, dielectric scene: nothing qualifies by itself. + std::vector albedo(W * H * 4, 0), normals(W * H * 4, 0), material(W * H * 4, 0); + std::vector depth(W * H, 0.5f); + + for (uint i = 0; i < W * H; i++) + { + normals[i * 4 + 0] = normals[i * 4 + 1] = 128; + normals[i * 4 + 2] = 255; + normals[i * 4 + 3] = 230; // roughness 0.9: above the 0.5 threshold + } + + auto pixel = [&](uint x, uint y) { return (size_t)(y * W + x) * 4; }; + + // Tile (2, 3): one anisotropic pixel, nothing else special. + material[pixel(20, 25) + 3] = 3; + // Tile (5, 5): glossy and metallic -- the pre-existing rule, as a control. + for (uint y = 40; y < 48; y++) + for (uint x = 40; x < 48; x++) + { + albedo[pixel(x, y) + 3] = 255; + normals[pixel(x, y) + 3] = 25; // roughness 0.1 + } + // Tile (6, 1): metallic but rough, and tile (1, 6): glossy but dielectric -- + // each satisfies only half of the rule, so neither qualifies. + for (uint y = 8; y < 16; y++) + for (uint x = 48; x < 56; x++) + albedo[pixel(x, y) + 3] = 255; + for (uint y = 48; y < 56; y++) + for (uint x = 8; x < 16; x++) + normals[pixel(x, y) + 3] = 25; + + { + auto upload = device.get_upload_list(); + auto put = [&](HAL::TextureResource* tex, const void* src, uint w, uint h, uint bytes_per_pixel) + { + upload->get_copy().update_texture(tex, ivec3(0, 0, 0), ivec3(w, h, 1), 0, + reinterpret_cast(src), w * bytes_per_pixel); + }; + put(albedo_tex.get(), albedo.data(), W, H, 4); + put(normals_tex.get(), normals.data(), W, H, 4); + put(material_tex.get(), material.data(), W, H, 4); + put(depth_tex.get(), depth.data(), W, H, 4); + upload->execute_and_wait(); + } + + const auto counted = HAL::ResFlags::ShaderResource | HAL::ResFlags::UnorderedAccess; + const uint tiles = T * T; + HAL::StructuredBufferView hi(device, tiles, HAL::counterType::SELF, counted); + HAL::StructuredBufferView low(device, tiles, HAL::counterType::SELF, counted); + HAL::StructuredBufferView rough_hi(device, tiles, HAL::counterType::SELF, counted); + HAL::StructuredBufferView rough_low(device, tiles, HAL::counterType::SELF, counted); + + { + auto& queue = device.get_queue(HAL::CommandListType::DIRECT); + auto list = queue->get_free_list(); + list->begin(L"TileClassify"); + + HAL::Texture2DView albedo_view(albedo_tex, *list); + HAL::Texture2DView normals_view(normals_tex, *list); + HAL::Texture2DView material_view(material_tex, *list); + HAL::Texture2DView depth_view(depth_tex, *list); + HAL::Texture2DView half_depth_view(half_depth, *list); + HAL::Texture2DView half_normals_view(half_normals, *list); + HAL::Texture2DView mask_view(tile_mask, *list); + HAL::Texture2DView flags_view(tile_flags, *list); + HAL::Texture2DView rough_flags_view(rough_flags, *list); + + auto& compute = list->get_compute(); + compute.set_signature(Layouts::DefaultLayout); + + Slots::Meshes::TileClassifyData params; + params.GetGbuffer().GetAlbedo() = albedo_view.texture2D; + params.GetGbuffer().GetNormals() = normals_view.texture2D; + params.GetGbuffer().GetMaterial() = material_view.texture2D; + params.GetGbuffer().GetSpecular() = albedo_view.texture2D; + params.GetGbuffer().GetMotion() = albedo_view.texture2D; + params.GetGbuffer().GetDepth() = depth_view.texture2D; + params.GetHalf_depth() = half_depth_view.rwTexture2D; + params.GetHalf_normals() = half_normals_view.rwTexture2D; + params.GetTile_hi() = hi.appendStructuredBuffer; + params.GetTile_low() = low.appendStructuredBuffer; + params.GetTile_mask() = mask_view.rwTexture2D; + params.GetTile_flags() = flags_view.rwTexture2D; + params.GetTile_roughness_hi() = rough_hi.appendStructuredBuffer; + params.GetTile_roughness_low() = rough_low.appendStructuredBuffer; + params.GetTile_roughness_flags() = rough_flags_view.rwTexture2D; + params.GetRoughness_threshold() = 0.5f; + params.GetMetallic_threshold() = 0.05f; + compute.set(params); + + compute.clear_counter(hi); + compute.clear_counter(low); + compute.clear_counter(rough_hi); + compute.clear_counter(rough_low); + compute.set_pipeline(); + compute.dispatch(ivec2{ int(W), int(H) }, ivec2{ 8, 8 }); + list->execute_and_wait(); + } + + auto data = readback_texture(rough_flags.get()); + auto& mip = data->array[0]->mips[0]; + auto flag = [&](uint x, uint y) { return int(mip->data[(size_t)y * mip->width_stride + x]); }; + + std::string failures; + + for (uint y = 0; y < T; y++) + for (uint x = 0; x < T; x++) + { + const int expected = (x == 2 && y == 3) || (x == 5 && y == 5) ? 1 : 0; + + if (flag(x, y) != expected) + failures += "tile (" + std::to_string(x) + "," + std::to_string(y) + ") is " + std::to_string(flag(x, y)) + + ", expected " + std::to_string(expected) + "\n"; + } + + ASSERT_EQ(std::string(), failures); + } + + // subsurface_color is the diffuse colour of the scattering part: the base + // colour blends towards it by the subsurface weight. + TEST(MaterialX.GPU, SubsurfaceColor) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"( + + )")); + ASSERT_EQ(size_t(1), graphs.size()); + ASSERT_EQ(std::string(), close(value_of(source_as(graphs[0].second->get_base_color())), { 0.5f, 0.3f, 0.2f, 1.0f })); + + // Left alone when another shading model took the slot (subsurface is dropped). + auto coated = build(surface("standard_surface", + R"( + + + )")); + ASSERT_EQ(size_t(1), coated.size()); + ASSERT_EQ(std::string(), close(value_of(source_as(coated[0].second->get_base_color())), { 0.8f, 0.5f, 0.4f, 1.0f })); + } + + // triplanarprojection: three image files become three texture inputs, and its + // upaxis is an editable int constant. + TEST(MaterialX.GPU, TriplanarImport) + { + using namespace mx_import; + + std::string icon = std::filesystem::absolute(to_path(L"resources/icon.jpg")).generic_string(); + auto file = [&](const char* name) { return std::string(""; }; + + auto graphs = build(surface("standard_surface", + R"()", + "" + file("filex") + file("filey") + file("filez") + + "")); + + ASSERT_EQ(size_t(1), graphs.size()); + auto* tri = source_as(graphs[0].second->get_base_color()); + ASSERT_TRUE(tri != nullptr); + ASSERT_EQ(std::string("ND_triplanarprojection_color3"), tri->get_nodedef()); + + for (const char* name : { "filex", "filey", "filez" }) + ASSERT_TRUE(source_as(tri->get_input_by_name(name)) != nullptr); + + ASSERT_EQ(std::string(), close(tri->get_constant("upaxis"), { 1.0f })); + ASSERT_EQ(std::string(), close(tri->get_constant("blend"), { 0.5f })); + } + + TEST(MaterialX.GPU, Anisotropy) + { + using namespace mx_import; + + auto brushed = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), brushed.size()); + auto& g = brushed[0].second; + ASSERT_EQ(std::string(), close({ source_as(g->get_anisotropy())->get_value() }, { 0.8f })); + ASSERT_EQ(std::string(), close({ source_as(g->get_anisotropy_rotation())->get_value() }, { 0.25f })); + + // Lowest priority: a coat or sheen takes the one shading-model slot. + auto coated = build(surface("standard_surface", + R"( + )")); + ASSERT_EQ(size_t(1), coated.size()); + ASSERT_TRUE(source(coated[0].second->get_coat()) != nullptr); + ASSERT_TRUE(source(coated[0].second->get_anisotropy()) == nullptr); + + auto open = build(surface("open_pbr_surface", + R"()")); + ASSERT_EQ(size_t(1), open.size()); + ASSERT_EQ(std::string(), close({ source_as(open[0].second->get_anisotropy())->get_value() }, { 0.6f })); + } + + // Each supported surface shader maps its own parameter names. + TEST(MaterialX.GPU, OtherSurfaceShaders) + { + using namespace mx_import; + + check_constants(build(surface("gltf_pbr", + R"( + + )")), + { 0.2f, 0.4f, 0.6f, 1.0f }, 0.3f, 0.8f); + + check_constants(build(surface("UsdPreviewSurface", + R"( + + )")), + { 0.2f, 0.4f, 0.6f, 1.0f }, 0.25f, 0.75f); + + check_constants(build(surface("open_pbr_surface", + R"( + + + )")), + { 0.1f, 0.2f, 0.3f, 1.0f }, 0.1f, 0.45f); + } + + // Connected inputs become nodes: their constants carry over, their + // connections are rebuilt, and unconnected UV inputs still read the + // graph's texcoord. + TEST(MaterialX.GPU, ConnectedNodes) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"( + )", + R"( + + + + + + + )")); + + ASSERT_EQ(size_t(1), graphs.size()); + auto& g = graphs[0].second; + + auto* checker = source_as(g->get_base_color()); + auto* noise = source_as(g->get_roughness()); + ASSERT_TRUE(checker && noise); + + ASSERT_EQ(std::string("ND_checkerboard_color3"), checker->get_nodedef()); + ASSERT_EQ(std::string("ND_noise2d_float"), noise->get_nodedef()); + + ASSERT_EQ(std::string(), close(checker->get_constant("color1"), { 0.9f, 0.2f, 0.1f })); + ASSERT_EQ(std::string(), close(checker->get_constant("uvtiling"), { 8.0f, 8.0f })); + ASSERT_EQ(std::string(), close(noise->get_constant("amplitude"), { 0.25f })); + ASSERT_EQ(std::string(), close(noise->get_constant("pivot"), { 0.45f })); + + // color2 was not in the file: still the nodedef default. + ASSERT_EQ(std::string(), close(checker->get_constant("color2"), { 0.0f, 0.0f, 0.0f })); + + auto* graph_node = dynamic_cast<::FlowGraph::Node*>(g.get()); + ASSERT_TRUE(source(noise->get_input_by_name("texcoord")) == graph_node); + ASSERT_TRUE(source(checker->get_input_by_name("texcoord")) == graph_node); + } + + // Integer and boolean inputs are float uniforms like the rest, so the + // importer sets them as constants on the node and they stay editable. + TEST(MaterialX.GPU, IntegerInput) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"()", + R"( + + )")); + + ASSERT_EQ(size_t(1), graphs.size()); + auto* fractal = source_as(graphs[0].second->get_roughness()); + ASSERT_TRUE(fractal != nullptr); + + ASSERT_TRUE(source(fractal->get_input_by_name("octaves")) == nullptr); + ASSERT_EQ(std::string(), close(fractal->get_constant("octaves"), { 5.0f })); + + // An input the file leaves out keeps the nodedef default (3 octaves). + auto defaulted = build(surface("standard_surface", + R"()", + R"()")); + ASSERT_EQ(size_t(1), defaulted.size()); + auto* plain = source_as(defaulted[0].second->get_roughness()); + ASSERT_TRUE(plain != nullptr); + ASSERT_EQ(std::string(), close(plain->get_constant("octaves"), { 3.0f })); + + // A boolean: range's doclamp defaults to false. + auto clamped = build(surface("standard_surface", + R"()", + R"()")); + ASSERT_EQ(size_t(1), clamped.size()); + auto* range = source_as(clamped[0].second->get_roughness()); + ASSERT_TRUE(range != nullptr); + ASSERT_EQ(std::string(), close(range->get_constant("doclamp"), { 1.0f })); + } + + // Image files become texture assets feeding the image node's file port; a + // normalmap node is bypassed so the texel reaches the graph's normal output + // as stored. + TEST(MaterialX.GPU, TexturesAndNormalMap) + { + using namespace mx_import; + + std::string icon = std::filesystem::absolute(to_path(L"resources/icon.jpg")).generic_string(); + + auto graphs = build(surface("standard_surface", + R"( + )", + "\n" + "\n" + "")); + + ASSERT_EQ(size_t(1), graphs.size()); + auto& g = graphs[0].second; + + auto* albedo = source_as(g->get_base_color()); + ASSERT_TRUE(albedo != nullptr); + ASSERT_EQ(std::string("ND_image_color3"), albedo->get_nodedef()); + ASSERT_TRUE(source_as(albedo->get_input_by_name("file")) != nullptr); + + auto* normal = source_as(g->get_normals()); + ASSERT_TRUE(normal != nullptr); + ASSERT_EQ(std::string("ND_image_vector3"), normal->get_nodedef()); + ASSERT_TRUE(source_as(normal->get_input_by_name("file")) != nullptr); + } + + TEST(MaterialX.GPU, MultipleMaterialsAndRejects) + { + using namespace mx_import; + + auto two = build( + "\n" + "\n" + "\n" + ""); + + ASSERT_EQ(size_t(2), two.size()); + ASSERT_EQ(std::string("first"), two[0].first); + ASSERT_EQ(std::string("second"), two[1].first); + ASSERT_EQ(std::string(), close({ source_as(two[0].second->get_mettalic())->get_value() }, { 0.1f })); + ASSERT_EQ(std::string(), close({ source_as(two[1].second->get_mettalic())->get_value() }, { 0.9f })); + + // A shader the importer has no mapping for, and a file with no surface at all. + ASSERT_EQ(size_t(0), build(surface("disney_principled", "")).size()); + ASSERT_EQ(size_t(0), build("").size()); + } + + // A node the library can't build leaves the input at the shader's default + // instead of failing the import. + TEST(MaterialX.GPU, UnsupportedNodeFallsBack) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"()", + // hextiledimage: no implementation yet. + R"()")); + + ASSERT_EQ(size_t(1), graphs.size()); + auto* base = source_as(graphs[0].second->get_base_color()); + ASSERT_TRUE(base != nullptr); + ASSERT_EQ(std::string(), close(value_of(base), { 0.8f, 0.8f, 0.8f, 1.0f })); + } + + // geomcolor has no vertex colours to read, so it is white: a graph that + // multiplies by it still builds, driven rather than left at defaults. + TEST(MaterialX.GPU, GeomColorIsWhite) + { + using namespace mx_import; + + auto graphs = build(surface("standard_surface", + R"()", + R"()")); + + ASSERT_EQ(size_t(1), graphs.size()); + auto* src = source(graphs[0].second->get_base_color()); + ASSERT_TRUE(src != nullptr); + ASSERT_TRUE(dynamic_cast(src) == nullptr); + } +} diff --git a/sources/Test/Tests/Test.RenderSystem.FrameGraph.ixx b/sources/Test/Tests/Test.RenderSystem.FrameGraph.ixx index 32c83cf81..e7a71c7e8 100644 --- a/sources/Test/Tests/Test.RenderSystem.FrameGraph.ixx +++ b/sources/Test/Tests/Test.RenderSystem.FrameGraph.ixx @@ -379,12 +379,12 @@ namespace struct SyntheticGBuffer { int w, h; - std::vector albedo, normals, specular; // RGBA8 + std::vector albedo, normals, specular, material; // RGBA8; material zero = STANDARD std::vector speed; // RG16F, zero std::vector depth; // R32 SyntheticGBuffer(int w, int h) : w(w), h(h), - albedo((size_t)w * h * 4), normals((size_t)w * h * 4), specular((size_t)w * h * 4, 0), + albedo((size_t)w * h * 4), normals((size_t)w * h * 4), specular((size_t)w * h * 4, 0), material((size_t)w * h * 4, 0), speed((size_t)w * h * 2, 0), depth((size_t)w * h) { const uint8_t band_albedo[4][4] = { { 230, 60, 40, 0 }, { 60, 200, 80, 0 }, { 50, 90, 220, 0 }, { 200, 200, 200, 128 } }; @@ -417,10 +417,17 @@ namespace put(data.GBuffer_Albedo, albedo.data(), 4); put(data.GBuffer_Normals, normals.data(), 4); put(data.GBuffer_Specular, specular.data(), 4); + put(data.GBuffer_Material, material.data(), 4); put(data.GBuffer_Speed, speed.data(), 4); put(data.GBuffer_DepthMips, depth.data(), 4); } + // A metallic pixel also gets the base layer's GGX sun highlight + // (common/lobes.hlsl base_sun_specular), which depends on the camera and + // is not mirrored here: unshadowed() is its diffuse part only, a lower + // bound for those pixels. + bool metallic(int x, int y) const { return albedo[((size_t)y * w + x) * 4 + 3] > 0; } + // vsm.hlsl's unshadowed_result(): 0 on sky, else // saturate(dot(N, L)) * albedo.rgb * (1 - albedo.a). vec3 unshadowed(int x, int y, vec3 sun) const @@ -1031,7 +1038,25 @@ export namespace Test r.graph.get_context().frame_size = { w, h }; r.graph.get_context().upscale_size = { w, h }; r.graph.get_context().sunDir = sun; - r.on_frame = add_frame_info; + // A defined camera: metallic pixels' sun highlight depends on the view vector, and + // an unset one leaves it to whatever the constant buffer held (Debug zero-fills + // it with 0xCC, Profile does not), so the golden would differ between builds. + camera cam; + cam.position = vec3(0.0f, 5.0f, -10.0f); + cam.target = vec3(0.0f, 0.0f, 0.0f); + cam.set_projection_params(Math::pi / 4, kReferenceAspect, 0.1f, 1000.0f); + cam.update(); + r.on_frame = [&](Graph& g) { + g.add_slot_generator([&](Graph& g) { + Slots::Frame::FrameInfo fi; + fi.GetSunDir().xyz = g.get_context().sunDir; + fi.GetCamera() = cam.camera_cb.current; + fi.GetPrevCamera() = cam.camera_cb.current; + fi.GetBrdf() = EngineAssets::brdf.get_asset()->get_texture()->texture_3d().texture3D; + fi.GetBestFitNormals() = EngineAssets::best_fit_normals.get_asset()->get_texture()->texture_2d().texture2D; + g.register_slot_setter(fi.compile(*g.builder.current_frame)); + }); + }; r.run_frames(p, sink, ivec2(w, h)); auto result = read_sink(sink.get()); @@ -1044,12 +1069,109 @@ export namespace Test const float got[3] = { half_to_float_normal(px[0]), half_to_float_normal(px[1]), half_to_float_normal(px[2]) }; const float exp[3] = { want.x, want.y, want.z }; for (int c = 0; c < 3; ++c) - if (std::abs(got[c] - exp[c]) > 2e-3f + 2e-3f * exp[c]) + if (gbuffer.metallic(x, y) ? got[c] < exp[c] - (2e-3f + 2e-3f * exp[c]) + : std::abs(got[c] - exp[c]) > 2e-3f + 2e-3f * exp[c]) throw Test::TestFailure("pixel (" + std::to_string(x) + "," + std::to_string(y) + ") channel " + std::to_string(c) + " is " + std::to_string(got[c]) + ", expected " + std::to_string(exp[c])); } - Test::check_texture_data_reference(result, "fg_vsm_unshadowed"); + // A pixel or two on a lobe edge land either side of a rounding step between + // Debug and optimised builds. + Test::check_texture_data_reference(result, "fg_vsm_unshadowed", { .max_mismatch_fraction = 0.0001 }); + } + + // NRD_ShadowCombine (the RTX-reference shadow source) with a fully lit shadow + // must light every pixel exactly as VSM_Unshadowed does: both end in the same + // diffuse term plus coat attenuation and material_sun_lobes. The material bands + // cover coat, sheen, anisotropy and a half-metallic standard surface, so a + // source that drops the lobes (as this one once did) differs from the other. + TEST(RenderSystem.FrameGraph, Effect_NRDShadowCombine_MatchesVSMLighting) + { + constexpr int w = kReferenceWidth, h = kReferenceHeight; + const vec3 sun = vec3(0.3f, 0.8f, 0.5f).normalize(); + SyntheticGBuffer gbuffer(w, h); + const uint8_t band_material[4][4] = { { 255, 77, 0, 1 }, { 255, 128, 0, 2 }, { 200, 128, 0, 3 }, { 0, 0, 0, 0 } }; + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) + { + std::memcpy(&gbuffer.material[((size_t)y * w + x) * 4], band_material[std::min(x * 4 / w, 3)], 4); + gbuffer.depth[(size_t)y * w + x] = y < h / 5 ? 0.0f : 0.02f; + } + + camera cam; + cam.position = vec3(0.0f, 5.0f, -10.0f); + cam.target = vec3(0.0f, 0.0f, 0.0f); + cam.set_projection_params(Math::pi / 4, kReferenceAspect, 0.1f, 1000.0f); + cam.update(); + + auto render = [&](auto& pipeline, bool rtx_reference, auto setup) + { + auto sink = make_sink(w, h, 1, 1, HAL::Format::R16G16B16A16_FLOAT); + pipeline.fGT_ProduceGBuffer.render_func = [&](auto& data, FrameContext& ctx) { gbuffer.upload(data, ctx); }; + pipeline.fGT_CopyResultToSink.render_func = [](auto& data, FrameContext& ctx) { + ctx.get_list()->get_copy().copy_texture(data.FGT_Sink.info->resource, 0, data.ResultTexture.info->resource, 0); + }; + setup(pipeline); + + GraphRunner r; + r.graph.get_context().frame_size = { w, h }; + r.graph.get_context().upscale_size = { w, h }; + r.graph.get_context().sunDir = sun; + r.graph.get_context().supported = false; + r.graph.get_context().shadow_source = rtx_reference ? Shadows::VSM::ShadowSource::RTXReference : Shadows::VSM::ShadowSource::VSM; + r.graph.get_context().rtx_supported = rtx_reference; + r.on_frame = [&](Graph& g) { + g.add_slot_generator([&](Graph& g) { + Slots::Frame::FrameInfo fi; + fi.GetSunDir().xyz = sun; + fi.GetCamera() = cam.camera_cb.current; + fi.GetPrevCamera() = cam.camera_cb.current; + fi.GetBrdf() = EngineAssets::brdf.get_asset()->get_texture()->texture_3d().texture3D; + fi.GetBestFitNormals() = EngineAssets::best_fit_normals.get_asset()->get_texture()->texture_2d().texture2D; + g.register_slot_setter(fi.compile(*g.builder.current_frame)); + }); + }; + r.run_frames(pipeline, sink, ivec2(w, h)); + return read_sink(sink.get()); + }; + + FGT_VSMUnshadowedPipeline vsm_pipeline; + VSM vsm(vsm_pipeline); + auto vsm_result = render(vsm_pipeline, false, [](auto&) {}); + + const std::vector lit(size_t(w) * h, 255); + FGT_NRDShadowCombinePipeline nrd_pipeline; + auto nrd_result = render(nrd_pipeline, true, [&](auto& p) { + p.fGT_ProduceShadow.render_func = [&](auto& data, FrameContext& ctx) { + ctx.get_list()->get_copy().update_texture(data.VSM_ShadowDenoised.info->resource, ivec3(0, 0, 0), ivec3(w, h, 1), 0, + reinterpret_cast(lit.data()), w); + }; + }); + + auto& a = vsm_result->array[0]->mips[0]; + auto& b = nrd_result->array[0]->mips[0]; + int differs_from_diffuse = 0; + for (int y = h / 5; y < h; ++y) + for (int x = 0; x < w; ++x) + { + auto pa = reinterpret_cast(a->data.data() + (size_t)y * a->width_stride) + x * 4; + auto pb = reinterpret_cast(b->data.data() + (size_t)y * b->width_stride) + x * 4; + const vec3 diffuse = gbuffer.unshadowed(x, y, sun); + const float d[3] = { diffuse.x, diffuse.y, diffuse.z }; + for (int c = 0; c < 3; ++c) + { + const float want = half_to_float_normal(pa[c]), got = half_to_float_normal(pb[c]); + if (!(std::abs(got - want) <= 2e-3f + 2e-3f * want)) + throw Test::TestFailure("pixel (" + std::to_string(x) + "," + std::to_string(y) + ") channel " + + std::to_string(c) + " is " + std::to_string(got) + " with NRD_ShadowCombine, " + std::to_string(want) + " with VSM"); + if (std::abs(want - d[c]) > 2e-3f + 2e-3f * d[c]) + ++differs_from_diffuse; + } + } + + // Guards the comparison itself: if neither source added any lobe, the match above is vacuous. + if (differs_from_diffuse == 0) + throw Test::TestFailure("no pixel differs from the plain diffuse term; the lobes were never exercised"); } // VSM_DepthAnalysis on async compute over a synthetic depth buffer (a far diff --git a/sources/Test/main.cpp b/sources/Test/main.cpp index 665dc3c47..8c103d26f 100644 --- a/sources/Test/main.cpp +++ b/sources/Test/main.cpp @@ -11,6 +11,7 @@ import Test.HAL; import Test.HAL.SubresRangeMap; import Test.RenderSystem.FrameGraph; import Test.SceneParity; +import Test.MaterialX; import Core; import HAL; import RenderSystem; diff --git a/vcpkg.json b/vcpkg.json index c0bcef35e..69166b6c4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -13,6 +13,7 @@ "cereal", "dstorage", "jinja2cpp", + "materialx", {"name" :"directx-dxc", "version>=":"2026-05-27"}, {"name" :"directx12-agility", "version>=":"1.619.3"}, {"name" :"directx-headers", "version>=":"1.619.1"}, diff --git a/workdir/materialx/gen_generated_hlsl.py b/workdir/materialx/gen_generated_hlsl.py new file mode 100644 index 000000000..4c063aad1 --- /dev/null +++ b/workdir/materialx/gen_generated_hlsl.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +"""Generates workdir/shaders/materialx/mx_generated.hlsl. + +Every MaterialX stdlib nodedef that is not hand-written in mx_stdlib.hlsl is +defined in MaterialX either by an inline expression ("sourcecode" in +genglsl/stdlib_genglsl_impl.mtlx) or by a nodegraph of other nodes +(stdlib_ng.mtlx). Both are mechanical to turn into HLSL, so they are generated +instead of hand-ported. Nodes that MaterialX implements with a .glsl file +(hsvtorgb, rotate, splits, ...) are hand-ported in mx_stdlib.hlsl. + +Usage: python gen_generated_hlsl.py [--libs ] [--verbose] + Defaults to workdir/materialx/libraries, so the output only depends on + files committed to the repo, except for stdlib_ng.mtlx and the genglsl + impl file, which are read from the vcpkg install of MaterialX. + +Naming: a generated function is mx_[_fa]_, the same +typed spelling MaterialXLibrary::register_nodes falls back to. Parameters are +named i_ because `in`/`out` are HLSL keywords and MaterialX uses both. +""" +import os +import re +import sys +import xml.etree.ElementTree as ET + +REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +VCPKG_LIBS = os.path.join(REPO, "vcpkg_installed", "x64-windows", "x64-windows", "libraries", "stdlib") +HLSL_DIR = os.path.join(REPO, "workdir", "shaders", "materialx") +HAND = os.path.join(HLSL_DIR, "mx_stdlib.hlsl") +OUT = os.path.join(HLSL_DIR, "mx_generated.hlsl") + +TYPES = {"float": "float", "integer": "int", "boolean": "bool", + "vector2": "float2", "vector3": "float3", "color3": "float3", + "vector4": "float4", "color4": "float4"} +WIDTH = {"float": 1, "float2": 2, "float3": 3, "float4": 4} + +verbose = "--verbose" in sys.argv + + +def literal(mx_type, value): + h = TYPES[mx_type] + value = (value or "").strip() + if h == "int": + return value or "0" + if h == "bool": + return "true" if value == "true" else "false" + + def f(s): + s = s.strip() or "0.0" + s = s if re.search(r"[.eEnN]", s) else s + ".0" + # Suffixed so template deduction sees `float`, not a literal type. + return s + "f" + + if h == "float": + return f(value) + comps = [f(c) for c in value.split(",")] if value else [] + comps += ["0.0"] * (WIDTH[h] - len(comps)) + return "%s(%s)" % (h, ", ".join(comps[:WIDTH[h]])) + + +class NodeDef: + def __init__(self, el): + self.name = el.get("name") + self.category = el.get("node") + self.inputs = [(i.get("name"), i.get("type"), i.get("value"), i.get("defaultgeomprop")) + for i in el.findall("input")] + self.outputs = [(o.get("name"), o.get("type")) for o in el.findall("output")] or [("out", el.get("type"))] + self.fa = self.name.endswith("FA") + types = {t for _, t, _, _ in self.inputs} | {t for _, t in self.outputs} + self.supported = types <= set(TYPES) + base = "mx_" + self.category + ("_fa" if self.fa else "") + self.generic = base + self.typed = base + "_" + self.outputs[0][1] + + def signature(self): + return tuple(TYPES[t] for _, t, _, _ in self.inputs) + + +def main(): + libs = VCPKG_LIBS + for i, a in enumerate(sys.argv): + if a == "--libs": + libs = sys.argv[i + 1] + + defs_root = ET.parse(os.path.join(libs, "stdlib_defs.mtlx")).getroot() + ng_root = ET.parse(os.path.join(libs, "stdlib_ng.mtlx")).getroot() + impl_root = ET.parse(os.path.join(libs, "genglsl", "stdlib_genglsl_impl.mtlx")).getroot() + + nodedefs = {} + by_category = {} + for el in defs_root.iter("nodedef"): + nd = NodeDef(el) + nodedefs[nd.name] = nd + by_category.setdefault(nd.category, []).append(nd) + + hand_src = open(HAND).read() + hand = set(re.findall(r"(? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/libraries/bxdf/gltf_pbr.mtlx b/workdir/materialx/libraries/bxdf/gltf_pbr.mtlx new file mode 100644 index 000000000..edc7d40ec --- /dev/null +++ b/workdir/materialx/libraries/bxdf/gltf_pbr.mtlx @@ -0,0 +1,808 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/libraries/bxdf/open_pbr_surface.mtlx b/workdir/materialx/libraries/bxdf/open_pbr_surface.mtlx new file mode 100644 index 000000000..5fe0236d0 --- /dev/null +++ b/workdir/materialx/libraries/bxdf/open_pbr_surface.mtlx @@ -0,0 +1,673 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/libraries/bxdf/standard_surface.mtlx b/workdir/materialx/libraries/bxdf/standard_surface.mtlx new file mode 100644 index 000000000..495958448 --- /dev/null +++ b/workdir/materialx/libraries/bxdf/standard_surface.mtlx @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/libraries/bxdf/usd_preview_surface.mtlx b/workdir/materialx/libraries/bxdf/usd_preview_surface.mtlx new file mode 100644 index 000000000..f89e561c3 --- /dev/null +++ b/workdir/materialx/libraries/bxdf/usd_preview_surface.mtlx @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/libraries/pbrlib/pbrlib_defs.mtlx b/workdir/materialx/libraries/pbrlib/pbrlib_defs.mtlx new file mode 100644 index 000000000..0f8f5c57a --- /dev/null +++ b/workdir/materialx/libraries/pbrlib/pbrlib_defs.mtlx @@ -0,0 +1,465 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/libraries/pbrlib/pbrlib_ng.mtlx b/workdir/materialx/libraries/pbrlib/pbrlib_ng.mtlx new file mode 100644 index 000000000..8e8678675 --- /dev/null +++ b/workdir/materialx/libraries/pbrlib/pbrlib_ng.mtlx @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/libraries/stdlib/stdlib_defs.mtlx b/workdir/materialx/libraries/stdlib/stdlib_defs.mtlx new file mode 100644 index 000000000..f8d743323 --- /dev/null +++ b/workdir/materialx/libraries/stdlib/stdlib_defs.mtlx @@ -0,0 +1,5157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/noise_reference.py b/workdir/materialx/noise_reference.py new file mode 100644 index 000000000..4c49c846f --- /dev/null +++ b/workdir/materialx/noise_reference.py @@ -0,0 +1,80 @@ +# Independent port of MaterialX/OSL noise (genglsl/lib/mx_noise.glsl) to Python. +import math, struct +M = 0xFFFFFFFF +def rotl(x,k): return ((x<>(32-k))) & M +def bjfinal(a,b,c): + c^=b; c=(c-rotl(b,14))&M + a^=c; a=(a-rotl(c,11))&M + b^=a; b=(b-rotl(a,25))&M + c^=b; c=(c-rotl(b,16))&M + a^=c; a=(a-rotl(c,4))&M + b^=a; b=(b-rotl(a,14))&M + c^=b; c=(c-rotl(b,24))&M + return c +def hash_int(*v): + n=len(v); s=(0xdeadbeef+(n<<2)+13)&M + a=b=c=s + a=(a+(v[0]&M))&M + if n>1: b=(b+(v[1]&M))&M + if n>2: c=(c+(v[2]&M))&M + return bjfinal(a,b,c) +def f32(x): return struct.unpack('f',struct.pack('f',x))[0] +def floorfrac(x): + i=math.floor(x); return i, f32(x-i) +def fade(t): return t*t*t*(t*(t*6-15)+10) +def grad2(h,x,y): + h&=7; u=x if h<4 else y; v=2*(y if h<4 else x) + return (-u if h&1 else u)+(-v if h&2 else v) +def grad3(h,x,y,z): + h&=15; u=x if h<8 else y + v=y if h<4 else (x if h in (12,14) else z) + return (-u if h&1 else u)+(-v if h&2 else v) +def perlin2(x,y): + X,fx=floorfrac(x); Y,fy=floorfrac(y); u=fade(fx); v=fade(fy) + g=lambda dx,dy: grad2(hash_int(X+dx,Y+dy), fx-dx, fy-dy) + s1=1-u + r=(1-v)*(g(0,0)*s1+g(1,0)*u)+v*(g(0,1)*s1+g(1,1)*u) + return 0.6616*r +def perlin3(x,y,z): + X,fx=floorfrac(x); Y,fy=floorfrac(y); Z,fz=floorfrac(z) + u=fade(fx); v=fade(fy); w=fade(fz) + g=lambda dx,dy,dz: grad3(hash_int(X+dx,Y+dy,Z+dz), fx-dx, fy-dy, fz-dz) + s1=1-u; t1=1-v; r1=1-w + r=(r1*(t1*(g(0,0,0)*s1+g(1,0,0)*u)+v*(g(0,1,0)*s1+g(1,1,0)*u))+ + w*(t1*(g(0,0,1)*s1+g(1,0,1)*u)+v*(g(0,1,1)*s1+g(1,1,1)*u))) + return 0.9820*r +def cell2(x,y): return hash_int(math.floor(x),math.floor(y))/4294967295.0 +def cell3(x,y,z): return hash_int(math.floor(x),math.floor(y),math.floor(z))/4294967295.0 +pts2=[(0.3,0.7),(1.5,2.25),(-0.8,3.1),(10.2,-4.6),(0.5,0.5)] +pts3=[(0.3,0.7,0.1),(1.5,2.25,-3.5),(-0.8,3.1,0.9),(10.2,-4.6,7.7),(0.5,0.5,0.5)] +print("perlin2",[round(perlin2(*p),6) for p in pts2]) +print("perlin3",[round(perlin3(*p),6) for p in pts3]) +print("cell2",[round(cell2(*p),6) for p in pts2]) +print("cell3",[round(cell3(*p),6) for p in pts3]) + +def cell_vec3_2(x,y): + ix,iy=math.floor(x),math.floor(y) + return [hash_int(ix,iy,k)/4294967295.0 for k in range(3)] +def worley2(px,py,jitter=1.0): + X,fx=floorfrac(px); Y,fy=floorfrac(py) + best=1e6 + for x in (-1,0,1): + for y in (-1,0,1): + off=cell_vec3_2(x+X,y+Y)[:2] + cx=x+(off[0]-0.5)*jitter+0.5; cy=y+(off[1]-0.5)*jitter+0.5 + d=(cx-fx)**2+(cy-fy)**2 + best=min(best,d) + return math.sqrt(best) +def fractal2(x,y,octaves=3,lac=2.0,dim=0.5): + r=0;a=1 + for _ in range(octaves): + r+=a*perlin2(x,y); a*=dim; x*=lac; y*=lac + return r +def fractal3(x,y,z,octaves=3,lac=2.0,dim=0.5): + r=0;a=1 + for _ in range(octaves): + r+=a*perlin3(x,y,z); a*=dim; x*=lac; y*=lac; z*=lac + return r +print("worley2",[round(worley2(*p),6) for p in pts2]) +print("fractal2",[round(fractal2(*p),6) for p in pts2]) +print("fractal3",[round(fractal3(*p),6) for p in pts3]) diff --git a/workdir/materialx/samples/brushed_metal.mtlx b/workdir/materialx/samples/brushed_metal.mtlx new file mode 100644 index 000000000..a0667a3d7 --- /dev/null +++ b/workdir/materialx/samples/brushed_metal.mtlx @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/workdir/materialx/samples/checker_standard_surface.mtlx b/workdir/materialx/samples/checker_standard_surface.mtlx new file mode 100644 index 000000000..3fde70bde --- /dev/null +++ b/workdir/materialx/samples/checker_standard_surface.mtlx @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/samples/coated_paint.mtlx b/workdir/materialx/samples/coated_paint.mtlx new file mode 100644 index 000000000..4efba68fe --- /dev/null +++ b/workdir/materialx/samples/coated_paint.mtlx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/workdir/materialx/samples/skin.mtlx b/workdir/materialx/samples/skin.mtlx new file mode 100644 index 000000000..47bc0a787 --- /dev/null +++ b/workdir/materialx/samples/skin.mtlx @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/workdir/materialx/samples/velvet.mtlx b/workdir/materialx/samples/velvet.mtlx new file mode 100644 index 000000000..c7c5c7611 --- /dev/null +++ b/workdir/materialx/samples/velvet.mtlx @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/workdir/shaders/autogen/SSSBlurParams.h b/workdir/shaders/autogen/SSSBlurParams.h new file mode 100644 index 000000000..5113c22a3 --- /dev/null +++ b/workdir/shaders/autogen/SSSBlurParams.h @@ -0,0 +1,41 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#ifndef SLOT_4 + #define SLOT_4 +#else + #error Slot 4 is already used +#endif + +#include "layout/DefaultLayout.h" +#include "tables/SSSBlurParams.h" + +#ifndef CB_DEFINED +#define CB_DEFINED +struct CB { uint offset; }; +#endif + +#ifdef __spirv__ +struct _CB_SSSBlurParams { uint offset; }; +#ifdef HAL_SHADER_RECORD_SSSBlurParams +// A ray tracing hit shader's local root constant: on Vulkan it is the shader +// record's data, right after the group handle in the hit table entry. +[[vk::shader_record_ext]] ConstantBuffer<_CB_SSSBlurParams> pass_SSSBlurParams; +#else +static _CB_SSSBlurParams pass_SSSBlurParams = { _hal_push.s4 }; +#endif +#else +ConstantBuffer pass_SSSBlurParams: register(b4, space4); +#endif +ConstantBuffer CreateSSSBlurParams() +{ + return ResourceDescriptorHeap[pass_SSSBlurParams.offset]; +} + +#ifndef NO_GLOBAL +static const ConstantBuffer sSSBlurParams_global = CreateSSSBlurParams(); +ConstantBuffer GetSSSBlurParams() { return sSSBlurParams_global; } +#endif \ No newline at end of file diff --git a/workdir/shaders/autogen/SSSClassifyParams.h b/workdir/shaders/autogen/SSSClassifyParams.h new file mode 100644 index 000000000..d64cf4da8 --- /dev/null +++ b/workdir/shaders/autogen/SSSClassifyParams.h @@ -0,0 +1,41 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#ifndef SLOT_4 + #define SLOT_4 +#else + #error Slot 4 is already used +#endif + +#include "layout/DefaultLayout.h" +#include "tables/SSSClassifyParams.h" + +#ifndef CB_DEFINED +#define CB_DEFINED +struct CB { uint offset; }; +#endif + +#ifdef __spirv__ +struct _CB_SSSClassifyParams { uint offset; }; +#ifdef HAL_SHADER_RECORD_SSSClassifyParams +// A ray tracing hit shader's local root constant: on Vulkan it is the shader +// record's data, right after the group handle in the hit table entry. +[[vk::shader_record_ext]] ConstantBuffer<_CB_SSSClassifyParams> pass_SSSClassifyParams; +#else +static _CB_SSSClassifyParams pass_SSSClassifyParams = { _hal_push.s4 }; +#endif +#else +ConstantBuffer pass_SSSClassifyParams: register(b4, space4); +#endif +ConstantBuffer CreateSSSClassifyParams() +{ + return ResourceDescriptorHeap[pass_SSSClassifyParams.offset]; +} + +#ifndef NO_GLOBAL +static const ConstantBuffer sSSClassifyParams_global = CreateSSSClassifyParams(); +ConstantBuffer GetSSSClassifyParams() { return sSSClassifyParams_global; } +#endif \ No newline at end of file diff --git a/workdir/shaders/autogen/SSSTilesAppend.h b/workdir/shaders/autogen/SSSTilesAppend.h new file mode 100644 index 000000000..52b6e753c --- /dev/null +++ b/workdir/shaders/autogen/SSSTilesAppend.h @@ -0,0 +1,41 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#ifndef SLOT_5 + #define SLOT_5 +#else + #error Slot 5 is already used +#endif + +#include "layout/DefaultLayout.h" +#include "tables/SSSTilesAppend.h" + +#ifndef CB_DEFINED +#define CB_DEFINED +struct CB { uint offset; }; +#endif + +#ifdef __spirv__ +struct _CB_SSSTilesAppend { uint offset; }; +#ifdef HAL_SHADER_RECORD_SSSTilesAppend +// A ray tracing hit shader's local root constant: on Vulkan it is the shader +// record's data, right after the group handle in the hit table entry. +[[vk::shader_record_ext]] ConstantBuffer<_CB_SSSTilesAppend> pass_SSSTilesAppend; +#else +static _CB_SSSTilesAppend pass_SSSTilesAppend = { _hal_push.s5 }; +#endif +#else +ConstantBuffer pass_SSSTilesAppend: register(b5, space5); +#endif +ConstantBuffer CreateSSSTilesAppend() +{ + return ResourceDescriptorHeap[pass_SSSTilesAppend.offset]; +} + +#ifndef NO_GLOBAL +static const ConstantBuffer sSSTilesAppend_global = CreateSSSTilesAppend(); +ConstantBuffer GetSSSTilesAppend() { return sSSTilesAppend_global; } +#endif \ No newline at end of file diff --git a/workdir/shaders/autogen/SSSTilesRead.h b/workdir/shaders/autogen/SSSTilesRead.h new file mode 100644 index 000000000..169329be0 --- /dev/null +++ b/workdir/shaders/autogen/SSSTilesRead.h @@ -0,0 +1,41 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#ifndef SLOT_5 + #define SLOT_5 +#else + #error Slot 5 is already used +#endif + +#include "layout/DefaultLayout.h" +#include "tables/SSSTilesRead.h" + +#ifndef CB_DEFINED +#define CB_DEFINED +struct CB { uint offset; }; +#endif + +#ifdef __spirv__ +struct _CB_SSSTilesRead { uint offset; }; +#ifdef HAL_SHADER_RECORD_SSSTilesRead +// A ray tracing hit shader's local root constant: on Vulkan it is the shader +// record's data, right after the group handle in the hit table entry. +[[vk::shader_record_ext]] ConstantBuffer<_CB_SSSTilesRead> pass_SSSTilesRead; +#else +static _CB_SSSTilesRead pass_SSSTilesRead = { _hal_push.s5 }; +#endif +#else +ConstantBuffer pass_SSSTilesRead: register(b5, space5); +#endif +ConstantBuffer CreateSSSTilesRead() +{ + return ResourceDescriptorHeap[pass_SSSTilesRead.offset]; +} + +#ifndef NO_GLOBAL +static const ConstantBuffer sSSTilesRead_global = CreateSSSTilesRead(); +ConstantBuffer GetSSSTilesRead() { return sSSTilesRead_global; } +#endif \ No newline at end of file diff --git a/workdir/shaders/autogen/rt/GBuffer.h b/workdir/shaders/autogen/rt/GBuffer.h index bab96e4ca..4234433d0 100644 --- a/workdir/shaders/autogen/rt/GBuffer.h +++ b/workdir/shaders/autogen/rt/GBuffer.h @@ -15,6 +15,7 @@ namespace Meshes float4 specular: SV_Target2; float2 motion: SV_Target3; uint object_id: SV_Target4; + float4 material: SV_Target5; }; } using Meshes::GBuffer; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/GBuffer.h b/workdir/shaders/autogen/tables/GBuffer.h index f8e2213fd..60a89149e 100644 --- a/workdir/shaders/autogen/tables/GBuffer.h +++ b/workdir/shaders/autogen/tables/GBuffer.h @@ -16,12 +16,14 @@ namespace Meshes uint specular; // RenderTarget uint motion; // RenderTarget uint object_id; // RenderTarget + uint material; // RenderTarget uint depth; // DepthStencil Texture2D GetAlbedo() { return ResourceDescriptorHeap[albedo]; } Texture2D GetNormals() { return ResourceDescriptorHeap[normals]; } Texture2D GetSpecular() { return ResourceDescriptorHeap[specular]; } Texture2D GetMotion() { return ResourceDescriptorHeap[motion]; } Texture2D GetObject_id() { return ResourceDescriptorHeap[object_id]; } + Texture2D GetMaterial() { return ResourceDescriptorHeap[material]; } Texture2D GetDepth() { return ResourceDescriptorHeap[depth]; } }; } diff --git a/workdir/shaders/autogen/tables/RTXCombine.h b/workdir/shaders/autogen/tables/RTXCombine.h index 9a26023bd..350be0d60 100644 --- a/workdir/shaders/autogen/tables/RTXCombine.h +++ b/workdir/shaders/autogen/tables/RTXCombine.h @@ -13,12 +13,16 @@ namespace GI struct RTXCombine { uint reflection; // Texture2D + uint coat; // Texture2D uint indirect; // Texture2D uint shadow; // Texture2D + uint specular_out; // RWTexture2D uint target; // RWTexture2D GBuffer gbuffer; // GBuffer GBuffer GetGbuffer() { return gbuffer; } Texture2D GetReflection() { return ResourceDescriptorHeap[reflection]; } + Texture2D GetCoat() { return ResourceDescriptorHeap[coat]; } + RWTexture2D GetSpecular_out() { return ResourceDescriptorHeap[specular_out]; } Texture2D GetIndirect() { return ResourceDescriptorHeap[indirect]; } Texture2D GetShadow() { return ResourceDescriptorHeap[shadow]; } RWTexture2D GetTarget() { return ResourceDescriptorHeap[target]; } diff --git a/workdir/shaders/autogen/tables/ReflectionCombine.h b/workdir/shaders/autogen/tables/ReflectionCombine.h index 97e610f8d..0ef1a2219 100644 --- a/workdir/shaders/autogen/tables/ReflectionCombine.h +++ b/workdir/shaders/autogen/tables/ReflectionCombine.h @@ -13,10 +13,12 @@ namespace Reflections struct ReflectionCombine { uint reflection; // Texture2D + uint coat; // Texture2D uint target; // RWTexture2D GBuffer gbuffer; // GBuffer GBuffer GetGbuffer() { return gbuffer; } Texture2D GetReflection() { return ResourceDescriptorHeap[reflection]; } + Texture2D GetCoat() { return ResourceDescriptorHeap[coat]; } RWTexture2D GetTarget() { return ResourceDescriptorHeap[target]; } }; } diff --git a/workdir/shaders/autogen/tables/SSSBlurParams.h b/workdir/shaders/autogen/tables/SSSBlurParams.h new file mode 100644 index 000000000..5b0d8fa99 --- /dev/null +++ b/workdir/shaders/autogen/tables/SSSBlurParams.h @@ -0,0 +1,27 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "sig_hlsl.hlsl" +#include "enums.h" +#include "GBuffer.h" +namespace Subsurface +{ + struct SSSBlurParams + { + int add_specular; // int + uint source; // Texture2D + uint specular; // Texture2D + uint target; // RWTexture2D + GBuffer gbuffer; // GBuffer + GBuffer GetGbuffer() { return gbuffer; } + int GetAdd_specular() { return add_specular; } + Texture2D GetSource() { return ResourceDescriptorHeap[source]; } + Texture2D GetSpecular() { return ResourceDescriptorHeap[specular]; } + RWTexture2D GetTarget() { return ResourceDescriptorHeap[target]; } + }; +} +using Subsurface::SSSBlurParams; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/SSSClassifyParams.h b/workdir/shaders/autogen/tables/SSSClassifyParams.h new file mode 100644 index 000000000..6300f23e9 --- /dev/null +++ b/workdir/shaders/autogen/tables/SSSClassifyParams.h @@ -0,0 +1,19 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "sig_hlsl.hlsl" +#include "enums.h" +#include "GBuffer.h" +namespace Subsurface +{ + struct SSSClassifyParams + { + GBuffer gbuffer; // GBuffer + GBuffer GetGbuffer() { return gbuffer; } + }; +} +using Subsurface::SSSClassifyParams; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/SSSTilesAppend.h b/workdir/shaders/autogen/tables/SSSTilesAppend.h new file mode 100644 index 000000000..a7a81b1ed --- /dev/null +++ b/workdir/shaders/autogen/tables/SSSTilesAppend.h @@ -0,0 +1,18 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "sig_hlsl.hlsl" +#include "enums.h" +namespace Subsurface +{ + struct SSSTilesAppend + { + uint tiles; // AppendStructuredBuffer + AppendStructuredBuffer GetTiles() { return ResourceDescriptorHeap[tiles]; } + }; +} +using Subsurface::SSSTilesAppend; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/SSSTilesRead.h b/workdir/shaders/autogen/tables/SSSTilesRead.h new file mode 100644 index 000000000..4a01a5bf4 --- /dev/null +++ b/workdir/shaders/autogen/tables/SSSTilesRead.h @@ -0,0 +1,18 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by Prism from .prism files in sources/Prism/defs/ +// Changes will be lost on next generation. Edit the .prism source files instead. +// ============================================================================ +#pragma once +#include "sig_hlsl.hlsl" +#include "enums.h" +namespace Subsurface +{ + struct SSSTilesRead + { + uint tiles; // StructuredBuffer + StructuredBuffer GetTiles() { return ResourceDescriptorHeap[tiles]; } + }; +} +using Subsurface::SSSTilesRead; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/TranslucentPayload.h b/workdir/shaders/autogen/tables/TranslucentPayload.h index 88f156d82..ac51cb18d 100644 --- a/workdir/shaders/autogen/tables/TranslucentPayload.h +++ b/workdir/shaders/autogen/tables/TranslucentPayload.h @@ -15,6 +15,7 @@ namespace Raytrace float dist : read(closesthit,miss,caller) : write(closesthit,miss,caller); float3 normal : read(closesthit,caller) : write(closesthit,caller); float3 albedo : read(closesthit,caller) : write(closesthit,caller); + float3 transmission_color : read(closesthit,caller) : write(closesthit,caller); float roughness : read(closesthit,caller) : write(closesthit,caller); float ior : read(closesthit,caller) : write(closesthit,caller); float thickness : read(closesthit,caller) : write(closesthit,caller); @@ -24,6 +25,7 @@ namespace Raytrace float GetDist() { return dist; } float3 GetNormal() { return normal; } float3 GetAlbedo() { return albedo; } + float3 GetTransmission_color() { return transmission_color; } float GetRoughness() { return roughness; } float GetIor() { return ior; } float GetThickness() { return thickness; } diff --git a/workdir/shaders/autogen/tables/VoxelOutput.h b/workdir/shaders/autogen/tables/VoxelOutput.h index f9c38620b..4dbb47079 100644 --- a/workdir/shaders/autogen/tables/VoxelOutput.h +++ b/workdir/shaders/autogen/tables/VoxelOutput.h @@ -19,6 +19,7 @@ namespace GI uint ddgi_visibility; // Texture2DArray uint ddgi_residency; // StructuredBuffer uint noise; // RWTexture2D + uint coat; // RWTexture2D uint DirAndPdf; // RWTexture2D uint noiseRaw; // RWTexture2D uint shadow_noise; // RWTexture2D @@ -34,6 +35,7 @@ namespace GI DDGIInfo GetDdgi_cascade3() { return ddgi_cascade3; } DDGIInfo GetDdgi_cascade4() { return ddgi_cascade4; } RWTexture2D GetNoise() { return ResourceDescriptorHeap[noise]; } + RWTexture2D GetCoat() { return ResourceDescriptorHeap[coat]; } RWTexture2D GetDirAndPdf() { return ResourceDescriptorHeap[DirAndPdf]; } Texture2D GetBlueNoise() { return ResourceDescriptorHeap[blueNoise]; } RWTexture2D GetNoiseRaw() { return ResourceDescriptorHeap[noiseRaw]; } diff --git a/workdir/shaders/common/aniso.hlsl b/workdir/shaders/common/aniso.hlsl new file mode 100644 index 000000000..ae4060c8f --- /dev/null +++ b/workdir/shaders/common/aniso.hlsl @@ -0,0 +1,68 @@ +#ifndef ANISO_HLSL +#define ANISO_HLSL + +#include "shading_model.hlsl" + +// Anisotropic GGX (Burley's parameterisation, as in Filament): the roughness +// along the tangent is widened and along the bitangent narrowed by the +// anisotropy, so highlights stretch perpendicular to the brushing direction. +// +// The engine's direct sun term has no base-layer specular at all (the base +// specular comes from reflections), so for ANISOTROPIC pixels this adds the +// whole sun highlight of the base layer, scaled by pi like the coat/sheen lobes. + +void aniso_alphas(float roughness, float anisotropy, out float at, out float ab) +{ + float a = roughness * roughness; + at = max(a * (1.0 + anisotropy), 0.002); + ab = max(a * (1.0 - anisotropy), 0.002); +} + +float aniso_D_ggx(float NH, float TH, float BH, float at, float ab) +{ + float a2 = at * ab; + float3 v = float3(ab * TH, at * BH, a2 * NH); + float v2 = dot(v, v); + float w2 = a2 / v2; + return a2 * w2 * w2 / 3.14159265; +} + +float aniso_V_smith(float at, float ab, float TV, float BV, float TL, float BL, float NV, float NL) +{ + float lambda_v = NL * length(float3(at * TV, ab * BV, NV)); + float lambda_l = NV * length(float3(at * TL, ab * BL, NL)); + return 0.5 / max(lambda_v + lambda_l, 1e-5); +} + +// F0: the base layer's specular colour (dielectric 0.04 blended to albedo by +// metalness); roughness is perceptual, as stored in the G-buffer. +float3 aniso_sun_specular(float4 material, float3 N, float3 V, float3 L, float shadow, float3 F0, float roughness) +{ + if (material_model(material) != SHADING_MODEL_ANISOTROPIC) + return 0; + + float anisotropy = material_params(material).x; + float3 T = aniso_decode_tangent(N, material_params(material).y); + float3 B = cross(N, T); + + float at, ab; + aniso_alphas(roughness, anisotropy, at, ab); + + float NL = saturate(dot(N, L)); + float NV = max(dot(N, V), 1e-4); + + if (NL <= 0.0) + return 0; + + float3 H = normalize(V + L); + float NH = saturate(dot(N, H)); + float VH = saturate(dot(V, H)); + + float3 F = F0 + (1.0 - F0) * pow(1.0 - VH, 5.0); + float spec = aniso_D_ggx(NH, dot(T, H), dot(B, H), at, ab) + * aniso_V_smith(at, ab, dot(T, V), dot(B, V), dot(T, L), dot(B, L), NV, NL); + + return shadow * F * spec * NL * 3.14159265; +} + +#endif diff --git a/workdir/shaders/common/coat.hlsl b/workdir/shaders/common/coat.hlsl new file mode 100644 index 000000000..08eaccb96 --- /dev/null +++ b/workdir/shaders/common/coat.hlsl @@ -0,0 +1,85 @@ +#ifndef COAT_HLSL +#define COAT_HLSL + +#include "shading_model.hlsl" + +// Clearcoat lobe for direct sun light: a second, dielectric GGX specular layer +// over the base. The engine's direct term is Lambert-only (shadow * NL * +// albedo, no 1/pi -- i.e. sun irradiance is taken as pi), so the specular term +// is scaled by pi to sit in the same units. +// +// Not modelled: the coat darkening the base layer below it, and the coat's own +// refraction of the view/light rays. At the coat's fixed IOR 1.5 the darkening +// is a few percent, which is below what the rest of the lighting resolves. + +#define COAT_F0 0.04 // dielectric, IOR 1.5 + +float coat_D_ggx(float NH, float a) +{ + float a2 = a * a; + float d = NH * NH * (a2 - 1.0) + 1.0; + return a2 / (3.14159265 * d * d); +} + +// Height-correlated Smith visibility, already divided by 4 NL NV. +float coat_V_smith(float NL, float NV, float a) +{ + float a2 = a * a; + float gv = NL * sqrt(NV * NV * (1.0 - a2) + a2); + float gl = NV * sqrt(NL * NL * (1.0 - a2) + a2); + return 0.5 / max(gv + gl, 1e-5); +} + +// Mirror-direction radiance seen through the coat: the coat's Fresnel reflectance +// (Schlick, from the view angle) times its weight. `radiance` comes from a ray +// traced at the coat's own roughness (ReflectionRTX's coat output). +float3 coat_reflection(float4 material, float3 N, float3 V, float3 radiance) +{ + if (material_model(material) != SHADING_MODEL_CLEARCOAT) + return 0; + + float NV = saturate(dot(N, V)); + float F = COAT_F0 + (1.0 - COAT_F0) * pow(1.0 - NV, 5.0); + return material_params(material).x * F * radiance; +} + +// What reaches the base layer under a clearcoat: the coat reflects F of the +// light on the way in. The way out is left out, at IOR 1.5 it is a few +// percent. 1 for materials without a coat. +float coat_base_attenuation(float4 material, float3 N, float3 V) +{ + if (material_model(material) != SHADING_MODEL_CLEARCOAT) + return 1.0; + + float NV = saturate(dot(N, V)); + float F = COAT_F0 + (1.0 - COAT_F0) * pow(1.0 - NV, 5.0); + return 1.0 - material_params(material).x * F; +} + +float3 coat_sun_specular(float4 material, float3 N, float3 V, float3 L, float shadow) +{ + if (material_model(material) != SHADING_MODEL_CLEARCOAT) + return 0; + + float weight = material_params(material).x; + // Perceptual roughness squared; floored so a mirror coat does not collapse + // the highlight to a single sub-pixel point. + float a = max(material_params(material).y * material_params(material).y, 0.002); + + float NL = saturate(dot(N, L)); + float NV = max(dot(N, V), 1e-4); + + if (NL <= 0.0) + return 0; + + float3 H = normalize(V + L); + float NH = saturate(dot(N, H)); + float VH = saturate(dot(V, H)); + + float F = COAT_F0 + (1.0 - COAT_F0) * pow(1.0 - VH, 5.0); + float spec = coat_D_ggx(NH, a) * coat_V_smith(NL, NV, a) * F; + + return shadow * weight * spec * NL * 3.14159265; +} + +#endif diff --git a/workdir/shaders/common/common.hlsl b/workdir/shaders/common/common.hlsl index 6bcc57a4a..dc09f7ac7 100644 --- a/workdir/shaders/common/common.hlsl +++ b/workdir/shaders/common/common.hlsl @@ -402,6 +402,25 @@ float3 SampleReflectionVector(float3 view_direction, float3 normal, float roughn return mul(reflected_direction_tbn, inv_tbn_transform); } +// Reflection direction for one anisotropic GGX sample (VNDF sampling, with the +// roughness stretched along T): same role as SampleReflectionVector, which is +// the isotropic case. `view_direction` points from the camera to the surface; +// `alpha` is the already-squared roughness, as the reflection raygen holds it. +float3 SampleAnisoReflectionVector(float3 view_direction, float3 N, float3 T, float alpha, float anisotropy, float2 random) +{ + float at = max(alpha * (1.0 + anisotropy), 0.002); + float ab = max(alpha * (1.0 - anisotropy), 0.002); + float3 B = cross(N, T); + + float3 Ve = -view_direction; + float3 Ve_local = float3(dot(Ve, T), dot(Ve, B), dot(Ve, N)); + + float3 Ne = SampleGGXVNDF(Ve_local, at, ab, random.x, random.y); + float3 H = Ne.x * T + Ne.y * B + Ne.z * N; + + return reflect(view_direction, H); +} + #endif \ No newline at end of file diff --git a/workdir/shaders/common/iridescence.hlsl b/workdir/shaders/common/iridescence.hlsl new file mode 100644 index 000000000..10c3e1107 --- /dev/null +++ b/workdir/shaders/common/iridescence.hlsl @@ -0,0 +1,133 @@ +#ifndef IRIDESCENCE_HLSL +#define IRIDESCENCE_HLSL + +// Thin-film interference on a specular surface (anodised metal, oil, soap): +// Fresnel of a conductor or dielectric under a film of thickness d and index n. +// +// Belcour & Barla, "A Practical Extension to Microfacet Theory for the Modeling +// of Varying Iridescence", SIGGRAPH 2017: the Airy summation of the first two +// reflection orders, with the spectral response folded into a Gaussian fit of the +// CIE matching functions. This is the formulation glTF's KHR_materials_iridescence +// uses, so the same film parameters look the same. + +static const float IRI_PI = 3.14159265; + +float iri_ior_to_f0(float transmitted, float incident) +{ + float r = (transmitted - incident) / (transmitted + incident); + return r * r; +} + +float3 iri_ior_to_f0(float3 transmitted, float incident) +{ + float3 r = (transmitted - incident) / (transmitted + incident); + return r * r; +} + +float3 iri_f0_to_ior(float3 f0) +{ + float3 s = sqrt(clamp(f0, 0.0, 0.9999)); + return (1.0 + s) / (1.0 - s); +} + +float3 iri_schlick(float3 f0, float cos_theta) +{ + float x = saturate(1.0 - cos_theta); + float x2 = x * x; + return f0 + (1.0 - f0) * (x2 * x2 * x); +} + +float iri_schlick(float f0, float cos_theta) +{ + float x = saturate(1.0 - cos_theta); + float x2 = x * x; + return f0 + (1.0 - f0) * (x2 * x2 * x); +} + +// The optical path difference's spectral weight, as linear Rec.709 RGB. +float3 iri_sensitivity(float opd, float3 shift) +{ + float phase = 2.0 * IRI_PI * opd * 1.0e-9; + float3 val = float3(5.4856e-13, 4.4201e-13, 5.2481e-13); + float3 pos = float3(1.6810e+06, 1.7953e+06, 2.2084e+06); + float3 var = float3(4.3278e+09, 9.3046e+09, 6.6121e+09); + + float3 xyz = val * sqrt(2.0 * IRI_PI * var) * cos(pos * phase + shift) * exp(-phase * phase * var); + xyz.x += 9.7470e-14 * sqrt(2.0 * IRI_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift.x) * exp(-4.5282e+09 * phase * phase); + xyz /= 1.0685e-7; + + return float3( + 3.2404542 * xyz.x - 1.5371385 * xyz.y - 0.4985314 * xyz.z, + -0.9692660 * xyz.x + 1.8760108 * xyz.y + 0.0415560 * xyz.z, + 0.0556434 * xyz.x - 0.2040259 * xyz.y + 1.0572252 * xyz.z); +} + +// Fresnel reflectance of base (F0 at normal incidence) under the film. +// thickness in nanometres, film_ior the film's index of refraction. +float3 iridescent_fresnel(float cos_theta, float thickness, float film_ior, float3 base_f0) +{ + const float outside = 1.0; + + // No film: the bare interface. (The interference sum does not vanish exactly + // at zero thickness, so this is not left to the formula.) + if (thickness < 0.5) + return iri_schlick(base_f0, cos_theta); + + // A film thinner than a few nm is no film: blend to the bare interface. + float ior = lerp(outside, film_ior, smoothstep(0.0, 0.03, thickness)); + + float sin2 = (outside / ior) * (outside / ior) * (1.0 - cos_theta * cos_theta); + float cos2sq = 1.0 - sin2; + + // Total internal reflection inside the film. + if (cos2sq < 0.0) + return float3(1, 1, 1); + + float cos2 = sqrt(cos2sq); + + float r0 = iri_ior_to_f0(ior, outside); + float r12 = iri_schlick(r0, cos_theta); + float t121 = 1.0 - r12; + float phi12 = ior < outside ? IRI_PI : 0.0; + float phi21 = IRI_PI - phi12; + + float3 base_ior = iri_f0_to_ior(base_f0); + float3 r1 = iri_ior_to_f0(base_ior, ior); + float3 r23 = iri_schlick(r1, cos2); + + float3 phi23 = float3(base_ior.x < ior ? IRI_PI : 0.0, base_ior.y < ior ? IRI_PI : 0.0, base_ior.z < ior ? IRI_PI : 0.0); + + float opd = 2.0 * ior * thickness * cos2; + float3 phi = float3(phi21, phi21, phi21) + phi23; + + float3 r123 = clamp(r12 * r23, 1e-5, 0.9999); + float3 root = sqrt(r123); + float3 rs = (t121 * t121) * r23 / (1.0 - r123); + + // Zeroth order, then the first two interference orders. + float3 result = r12 + rs; + float3 cm = rs - t121; + + [unroll] + for (int m = 1; m <= 2; m++) + { + cm *= root; + result += cm * (2.0 * iri_sensitivity(m * opd, m * phi)); + } + + return max(result, 0.0); +} + +// The F0 that gives the iridescent reflectance back through a plain Schlick +// Fresnel at this angle (f90 = 1): lets the split-sum environment BRDF, which is +// parameterised by F0, carry the film. +float3 iridescent_f0(float cos_theta, float thickness, float film_ior, float3 base_f0) +{ + float3 f = iridescent_fresnel(cos_theta, thickness, film_ior, base_f0); + float x = saturate(1.0 - cos_theta); + float x5 = x * x * x * x * x; + + return saturate((f - x5) / max(1.0 - x5, 1e-3)); +} + +#endif diff --git a/workdir/shaders/common/lobes.hlsl b/workdir/shaders/common/lobes.hlsl new file mode 100644 index 000000000..1942abb36 --- /dev/null +++ b/workdir/shaders/common/lobes.hlsl @@ -0,0 +1,65 @@ +#ifndef LOBES_HLSL +#define LOBES_HLSL + +#include "coat.hlsl" +#include "sheen.hlsl" +#include "aniso.hlsl" +#include "iridescence.hlsl" + +// The base layer's own sun highlight for metals. The engine's direct term is +// Lambert-only (shadow * NL * albedo * (1 - metallic)), so a metal -- which has no +// diffuse -- received no sun light at all and showed only what its reflection ray +// found. This adds the GGX highlight a metal needs (F0 = its base colour), +// weighted by metalness: dielectrics keep the look they had, since their specular +// comes from reflections. Anisotropic pixels have their own base highlight. +float3 base_sun_specular(float4 material, float3 N, float3 V, float3 L, float shadow, float3 F0, float roughness, float metallic) +{ + if (metallic <= 0.001 || material_model(material) == SHADING_MODEL_ANISOTROPIC) + return 0; + + float a = max(roughness * roughness, 0.002); + float NL = saturate(dot(N, L)); + float NV = max(dot(N, V), 1e-4); + + if (NL <= 0.0) + return 0; + + float3 H = normalize(V + L); + float NH = saturate(dot(N, H)); + float VH = saturate(dot(V, H)); + + float3 F = F0 + (1.0 - F0) * pow(1.0 - VH, 5.0); + + if (material_model(material) == SHADING_MODEL_THINFILM) + F = iridescent_fresnel(VH, thinfilm_thickness(material), thinfilm_ior(material), F0); + + return metallic * shadow * F * (coat_D_ggx(NH, a) * coat_V_smith(NL, NV, a) * NL * 3.14159265); +} + +// Every extra specular lobe a material's shading model adds to the direct sun +// term, behind one call so the lighting passes do not each list them. +// +// F0 and the perceptual roughness are the base layer's (G-buffer albedo/metallic +// and the normals alpha); only the anisotropic model reads them, since it is the +// one that replaces the base specular instead of adding a layer above it. +float3 material_sun_lobes(float4 material, float3 N, float3 V, float3 L, float shadow, float3 F0, float roughness, float3 albedo, float metallic) +{ + return coat_sun_specular(material, N, V, L, shadow) + + sheen_sun_specular(material, N, V, L, shadow, albedo) + + aniso_sun_specular(material, N, V, L, shadow, F0, roughness) + + base_sun_specular(material, N, V, L, shadow, F0, roughness, metallic); +} + +// Without a metalness: no base highlight. +float3 material_sun_lobes(float4 material, float3 N, float3 V, float3 L, float shadow, float3 F0, float roughness, float3 albedo) +{ + return material_sun_lobes(material, N, V, L, shadow, F0, roughness, albedo, 0.0); +} + +// Without a base colour: white sheen. +float3 material_sun_lobes(float4 material, float3 N, float3 V, float3 L, float shadow, float3 F0, float roughness) +{ + return material_sun_lobes(material, N, V, L, shadow, F0, roughness, 1.0); +} + +#endif diff --git a/workdir/shaders/common/medium.hlsl b/workdir/shaders/common/medium.hlsl new file mode 100644 index 000000000..72ebe29db --- /dev/null +++ b/workdir/shaders/common/medium.hlsl @@ -0,0 +1,23 @@ +#ifndef MEDIUM_HLSL +#define MEDIUM_HLSL + +// Absorption of a transmissive solid, as MaterialX's standard_surface and +// OpenPBR describe it: `tint` is the colour light has after travelling `depth` +// through the medium. A depth of zero (the files' default) means no volume at all: +// the tint is applied once, at the interface, instead. + +static const float MEDIUM_MIN_DEPTH = 1e-4; + +// Beer-Lambert coefficient per unit distance. +float3 medium_sigma(float3 tint, float depth) +{ + return depth > MEDIUM_MIN_DEPTH ? -log(max(tint, 0.001)) / depth : float3(0, 0, 0); +} + +// What the interface itself multiplies the transmitted light by. +float3 medium_entry_tint(float3 tint, float depth) +{ + return depth > MEDIUM_MIN_DEPTH ? float3(1, 1, 1) : tint; +} + +#endif diff --git a/workdir/shaders/common/shading_model.hlsl b/workdir/shaders/common/shading_model.hlsl new file mode 100644 index 000000000..f73a60964 --- /dev/null +++ b/workdir/shaders/common/shading_model.hlsl @@ -0,0 +1,166 @@ +#ifndef SHADING_MODEL_HLSL +#define SHADING_MODEL_HLSL + +// GBuffer_Material (RGBA8_UNORM): alpha is the shading model, rgb are up to +// three parameters whose meaning depends on it. Cleared to 0 = STANDARD, so +// anything that doesn't write the target (sky, decals, debug draws) lights as a +// plain metallic-roughness surface. +// +// STANDARD -- rgb unused +// CLEARCOAT -- r: coat weight, g: coat roughness, b: unused +// SHEEN -- r: sheen weight, g: sheen roughness, b: tint (0 white, 1 like the base colour) +// ANISOTROPIC-- r: anisotropy, g: tangent angle / pi in the normal's own frame +// (see material_frame / aniso_encode_tangent), b: unused +// SUBSURFACE -- rgb: scatter radius per channel, sqrt-encoded (sss_encode_radius) +// THINFILM -- r: film thickness / THINFILM_MAX_NM, g: (film index - 1) / 2 +// +// A material has one model, not a combination: that is what keeps this at four +// bytes per pixel. + +#define SHADING_MODEL_STANDARD 0 +#define SHADING_MODEL_CLEARCOAT 1 +#define SHADING_MODEL_SHEEN 2 +#define SHADING_MODEL_ANISOTROPIC 3 +#define SHADING_MODEL_SUBSURFACE 4 +#define SHADING_MODEL_THINFILM 5 + +// Film thickness is stored in nanometres / this, so 8 bits give ~4 nm steps -- fine +// enough for the colour shifts, which change over tens of nm. +#define THINFILM_MAX_NM 1000.0 + +// Scatter radius is stored as sqrt(radius / SSS_MAX_RADIUS): 8 bits linear would +// resolve nothing below 1/255 of the maximum, and skin-like radii are small. +#define SSS_MAX_RADIUS 4.0 + +float4 pack_material(uint model, float3 params) +{ + return float4(saturate(params), model / 255.0); +} + +uint material_model(float4 packed) +{ + return (uint)round(packed.a * 255.0); +} + +float3 material_params(float4 packed) +{ + return packed.rgb; +} + +// An orthonormal frame around N that depends on nothing but N, so a pass that +// only has the G-buffer normal rebuilds the exact frame the material pass used +// (Duff et al., "Building an Orthonormal Basis, Revisited"). Continuous except +// across N.z = 0, where it jumps; the stored tangent angle is relative to it. +void material_frame(float3 N, out float3 U, out float3 B) +{ + float s = N.z >= 0.0 ? 1.0 : -1.0; + float a = -1.0 / (s + N.z); + float b = N.x * N.y * a; + U = float3(1.0 + s * N.x * N.x * a, s * b, -s * N.x); + B = float3(b, s + N.y * N.y * a, -N.y); +} + +// Tangent -> G-buffer value in [0,1): the angle of T in the frame above, folded +// to [0, pi) because a tangent and its negation describe the same highlight. +float aniso_encode_tangent(float3 N, float3 T) +{ + float3 U, B; + material_frame(N, U, B); + + float phi = atan2(dot(T, B), dot(T, U)); + + if (phi < 0.0) + phi += 3.14159265; + + return saturate(phi / 3.14159265); +} + +float3 aniso_decode_tangent(float3 N, float encoded) +{ + float3 U, B; + material_frame(N, U, B); + + float phi = encoded * 3.14159265; + return cos(phi) * U + sin(phi) * B; +} + +float3 sss_encode_radius(float3 radius) +{ + return sqrt(saturate(radius / SSS_MAX_RADIUS)); +} + +float3 sss_decode_radius(float3 encoded) +{ + return encoded * encoded * SSS_MAX_RADIUS; +} + +float thinfilm_thickness(float4 packed) +{ + return packed.r * THINFILM_MAX_NM; +} + +float thinfilm_ior(float4 packed) +{ + return 1.0 + packed.g * 2.0; +} + +// What the material graph's extra outputs select. A lobe that is effectively +// off stays STANDARD so lighting skips it. One model per material, in priority +// order: coat, sheen, anisotropy, subsurface, thin film. `rotation` is the tangent's turn +// about N in turns; N and T are the shading normal and the mesh tangent; the +// subsurface radius is weight * scale * radius, in scene units. +float4 pack_material_from_graph(float coat, float coat_roughness, float sheen, float sheen_roughness, + float anisotropy, float rotation, + float sss_weight, float3 sss_radius, float sss_scale, float sheen_tint, + float film_thickness, float film_ior, + float3 N, float3 T) +{ + if (coat > 0.001) + return pack_material(SHADING_MODEL_CLEARCOAT, float3(coat, coat_roughness, 0.0)); + + if (sheen > 0.001) + return pack_material(SHADING_MODEL_SHEEN, float3(sheen, sheen_roughness, sheen_tint)); + + if (anisotropy > 0.001) + { + T = normalize(T - N * dot(N, T)); + float theta = rotation * 6.28318531; + T = T * cos(theta) + cross(N, T) * sin(theta); + return pack_material(SHADING_MODEL_ANISOTROPIC, float3(anisotropy, aniso_encode_tangent(N, T), 0.0)); + } + + if (sss_weight > 0.001) + return pack_material(SHADING_MODEL_SUBSURFACE, sss_encode_radius(sss_radius * (sss_scale * sss_weight))); + + if (film_thickness > 1.0) + return pack_material(SHADING_MODEL_THINFILM, float3(film_thickness / THINFILM_MAX_NM, (film_ior - 1.0) / 2.0, 0.0)); + + return pack_material(SHADING_MODEL_STANDARD, 0.0); +} + +// Without a thin film. +float4 pack_material_from_graph(float coat, float coat_roughness, float sheen, float sheen_roughness, + float anisotropy, float rotation, + float sss_weight, float3 sss_radius, float sss_scale, float sheen_tint, + float3 N, float3 T) +{ + return pack_material_from_graph(coat, coat_roughness, sheen, sheen_roughness, anisotropy, rotation, sss_weight, sss_radius, sss_scale, sheen_tint, 0.0, 1.5, N, T); +} + +// Without a sheen tint. +float4 pack_material_from_graph(float coat, float coat_roughness, float sheen, float sheen_roughness, + float anisotropy, float rotation, + float sss_weight, float3 sss_radius, float sss_scale, + float3 N, float3 T) +{ + return pack_material_from_graph(coat, coat_roughness, sheen, sheen_roughness, anisotropy, rotation, sss_weight, sss_radius, sss_scale, 0.0, N, T); +} + +// Without subsurface -- for callers (and tests) that only deal in the other lobes. +float4 pack_material_from_graph(float coat, float coat_roughness, float sheen, float sheen_roughness, + float anisotropy, float rotation, float3 N, float3 T) +{ + return pack_material_from_graph(coat, coat_roughness, sheen, sheen_roughness, anisotropy, rotation, 0.0, 1.0, 1.0, N, T); +} + +#endif diff --git a/workdir/shaders/common/sheen.hlsl b/workdir/shaders/common/sheen.hlsl new file mode 100644 index 000000000..148096a9e --- /dev/null +++ b/workdir/shaders/common/sheen.hlsl @@ -0,0 +1,104 @@ +#ifndef SHEEN_HLSL +#define SHEEN_HLSL + +#include "shading_model.hlsl" + +// Sheen lobe for direct sun light: the microfiber ("Charlie") distribution of +// Estevez & Kulla with Neubelt's visibility, as in Filament. It peaks at +// grazing angles, which is what gives cloth its soft rim. Scaled by pi like +// the coat lobe (the engine's direct term is Lambert without 1/pi). +// +// Not modelled: sheen colour (white), and the base layer being darkened by the +// sheen's directional albedo -- a few percent at typical weights. +// +// Indirect light and reflections get no sheen term: it is a broad lobe, and +// the indirect passes have no per-lobe input to put it in. + +float sheen_D_charlie(float NH, float a) +{ + float inv_a = 1.0 / a; + float sin2h = max(1.0 - NH * NH, 0.0078125); // keeps the pow() finite at NH = 1 + return (2.0 + inv_a) * pow(sin2h, inv_a * 0.5) / (2.0 * 3.14159265); +} + +float sheen_V_neubelt(float NL, float NV) +{ + return 1.0 / (4.0 * (NL + NV - NL * NV)); +} + +// The sheen colour: white, or the base colour normalised to its strongest +// channel when the material asks for it (cloth whose sheen has the weave hue). +float3 sheen_color(float4 material, float3 albedo) +{ + float tint = material_params(material).z; + float3 base = albedo / max(max(albedo.x, max(albedo.y, albedo.z)), 1e-3); + return lerp(1.0, base, tint); +} + +// Directional albedo of the sheen lobe, E(NV) = integral of f_s * NL over the +// hemisphere: how much of a uniform ambient radiance the lobe reflects toward V. +// There is no closed form for the Charlie lobe, so it is integrated with a fixed +// set of 32 evenly spread directions (uniform in cos theta, golden-angle in phi). +// Smooth enough for a lobe this broad; used only for ambient light. +float sheen_directional_albedo(float NV, float a) +{ + const int N = 32; + float sin_v = sqrt(max(1.0 - NV * NV, 0.0)); + float3 V = float3(sin_v, 0.0, NV); + float sum = 0.0; + + [unroll] + for (int i = 0; i < N; i++) + { + float z = (i + 0.5) / N; + float r = sqrt(1.0 - z * z); + float phi = i * 2.39996323; + float3 L = float3(r * cos(phi), r * sin(phi), z); + float NH = normalize(V + L).z; + sum += sheen_D_charlie(NH, a) * sheen_V_neubelt(z, NV) * z; + } + + return sum * (2.0 * 3.14159265 / N); +} + +float3 sheen_sun_specular(float4 material, float3 N, float3 V, float3 L, float shadow, float3 albedo) +{ + if (material_model(material) != SHADING_MODEL_SHEEN) + return 0; + + float weight = material_params(material).x; + // Perceptual roughness squared, floored: a tiny alpha makes pow() underflow. + float a = max(material_params(material).y * material_params(material).y, 0.02); + + float NL = saturate(dot(N, L)); + float NV = max(dot(N, V), 1e-4); + + if (NL <= 0.0) + return 0; + + float NH = saturate(dot(N, normalize(V + L))); + + return sheen_color(material, albedo) * (shadow * weight * sheen_D_charlie(NH, a) * sheen_V_neubelt(NL, NV) * NL * 3.14159265); +} + +// White sheen, for callers with no base colour to tint by. +float3 sheen_sun_specular(float4 material, float3 N, float3 V, float3 L, float shadow) +{ + return sheen_sun_specular(material, N, V, L, shadow, 1.0); +} + +// The sheen's share of indirect light: the lobe's directional albedo times the +// ambient radiance (`indirect`, the same quantity a diffuse surface multiplies by +// its albedo). Brightens the rim of cloth in shade, where only GI reaches it. +float3 sheen_indirect(float4 material, float3 N, float3 V, float3 albedo, float3 indirect) +{ + if (material_model(material) != SHADING_MODEL_SHEEN) + return 0; + + float a = max(material_params(material).y * material_params(material).y, 0.02); + float NV = max(dot(N, V), 1e-4); + + return sheen_color(material, albedo) * (material_params(material).x * sheen_directional_albedo(NV, a)) * indirect; +} + +#endif diff --git a/workdir/shaders/materials/material_preview_3d.hlsl b/workdir/shaders/materials/material_preview_3d.hlsl index 5610ce11f..2eaf19aee 100644 --- a/workdir/shaders/materials/material_preview_3d.hlsl +++ b/workdir/shaders/materials/material_preview_3d.hlsl @@ -17,6 +17,7 @@ #include "gbuffer/mesh_shader.hlsl" #include "autogen/MaterialPreviewInfo.h" +#include "materialx/mx_stdlib.hlsl" #define Sampler linearSampler @@ -45,7 +46,7 @@ float4 tile_sample(Texture2D tex, SamplerState s, float2 tc, Texture2D tex, SamplerState s, float2 tc, Texture2D -- the nodedef's node category (add, mix, smoothstep, ...). +// One name serves every type variant (float, vector2/3/4, color3/4): +// MaterialXNode coerces each argument to its nodedef-declared type before +// the call, so HLSL overload resolution picks the variant. +// +// The "FA" (float amount) nodedefs, e.g. ND_add_vector3FA, take a float as +// their second operand. DXC does not apply partial ordering between +// (T, T) and (T, float) templates -- the scalar case is ambiguous -- so +// those forms live under mx__fa instead. +// +// Nothing here may depend on common.hlsl: the material preview shaders +// include this file without it. Semantics follow MaterialX's own genglsl +// library (libraries/stdlib/genglsl). Where HLSL and GLSL differ: GLSL mod() +// is x - y*floor(x/y), not fmod(); mix() is lerp(); fract() is frac(). + +#ifndef MX_STDLIB_HLSL +#define MX_STDLIB_HLSL + +// ---- geometry --------------------------------------------------------------- +// +// World-space shading frame of the point being shaded. COMPILED_FUNC only +// receives position and texcoord, so a stage that has a frame sets these +// before calling it (mx_set_geometry); stages without one (VSM depth) keep the +// defaults. MaterialXNode reads them for inputs whose nodedef default is +// Nworld / Tworld / Bworld, and for the standalone normal/tangent/bitangent nodes. + +static float3 mx_geom_normal = float3(0, 0, 1); +static float3 mx_geom_tangent = float3(1, 0, 0); +static float3 mx_geom_bitangent = float3(0, 1, 0); + +void mx_set_geometry(float3 normal, float3 tangent, float3 bitangent) +{ + mx_geom_normal = normal; + mx_geom_tangent = tangent; + mx_geom_bitangent = bitangent; +} + +// Seconds since start, for the `time` and `frame` nodes. Set alongside the +// frame; previews and the VSM depth pass keep 0 (a still image). +static float mx_geom_time = 0; + +void mx_set_time(float seconds) +{ + mx_geom_time = seconds; +} + +float mx_time(float fps) { return mx_geom_time; } +float mx_frame() { return mx_geom_time * 24.0; } // MaterialX frames are 24 fps by default + +// The geometry nodes take MaterialX's `space` string, which the graph has no +// type for (see MaterialXLibrary), so they always return world space. +float3 mx_normal() { return mx_geom_normal; } +float3 mx_tangent(int index) { return mx_geom_tangent; } +float3 mx_bitangent(int index) { return mx_geom_bitangent; } + +// The engine's meshes carry no vertex colours, so geomcolor is the neutral white a +// document would get from a mesh without any: a graph that multiplies by it +// still builds and renders as if it were absent. +float mx_geomcolor_float(int index) { return 1.0; } +float3 mx_geomcolor_color3(int index) { return float3(1, 1, 1); } +float4 mx_geomcolor_color4(int index) { return float4(1, 1, 1, 1); } + +// Tangent-space normal (encoded 0..1, as the texture stores it) to the frame +// N/T/B. All-zero input means "no map": the flat normal. +float3 mx_normalmap(float3 value, float2 normal_scale, float3 N, float3 T, float3 B) +{ + value = dot(value, value) == 0.0 ? float3(0, 0, 1) : value * 2.0 - 1.0; + return normalize(T * value.x * normal_scale.x + B * value.y * normal_scale.y + N * value.z); +} + +float3 mx_normalmap(float3 value, float normal_scale, float3 N, float3 T, float3 B) +{ + return mx_normalmap(value, float2(normal_scale, normal_scale), N, T, B); +} + +// ---- triplanar projection --------------------------------------------------- +// MaterialX's NG_triplanarprojection, split into the two pieces the C++ side +// needs around the texture samples (see MaterialXNode::operator()). + +float3 mx_triplanar_weights(float3 n, float blend) +{ + float3 w = abs(n); + w /= max(w.x + w.y + w.z, 1e-6); + w = pow(w, 1.0 / max(blend, 0.03)); + return w / max(w.x + w.y + w.z, 1e-6); +} + +// UV of the image projected along `axis` (0 = X, 1 = Y, 2 = Z); `up` is the +// nodedef's upaxis, which only reorients the X-, Y- and Z-facing images. +float2 mx_triplanar_uv(int axis, float3 p, int up) +{ + if (axis == 0) + return up < 2 ? float2(p.z, p.y) : float2(p.y, p.z); + + if (axis == 1) + return up == 0 ? float2(p.z, p.x) : float2(p.x, p.z); + + return up == 0 ? float2(-p.y, p.x) : float2(p.x, p.y); +} + +// ---- math: binary arithmetic ------------------------------------------------ + +template T mx_add(T a, T b) { return a + b; } +template T mx_add_fa(T a, float b) { return a + b; } +template T mx_subtract(T a, T b) { return a - b; } +template T mx_subtract_fa(T a, float b) { return a - b; } +template T mx_multiply(T a, T b) { return a * b; } +template T mx_multiply_fa(T a, float b) { return a * b; } +template T mx_divide(T a, T b) { return a / b; } +template T mx_divide_fa(T a, float b) { return a / b; } + +template T mx_modulo(T a, T b) { return a - b * floor(a / b); } +template T mx_modulo_fa(T a, float b) { return a - b * floor(a / b); } + +template T mx_power(T a, T b) { return pow(a, b); } +template T mx_power_fa(T a, float b) { return pow(a, b); } + +template T mx_min(T a, T b) { return min(a, b); } +template T mx_min_fa(T a, float b) { return min(a, b); } +template T mx_max(T a, T b) { return max(a, b); } +template T mx_max_fa(T a, float b) { return max(a, b); } + +template T mx_atan2(T iny, T inx) { return atan2(iny, inx); } +template T mx_atan2_fa(T iny, float inx) { return atan2(iny, inx); } + +// ---- math: unary ------------------------------------------------------------ + +template T mx_absval(T a) { return abs(a); } +template T mx_floor(T a) { return floor(a); } +template T mx_ceil(T a) { return ceil(a); } +template T mx_sin(T a) { return sin(a); } +template T mx_cos(T a) { return cos(a); } +template T mx_tan(T a) { return tan(a); } +template T mx_asin(T a) { return asin(a); } +template T mx_acos(T a) { return acos(a); } +template T mx_sqrt(T a) { return sqrt(a); } +template T mx_ln(T a) { return log(a); } +template T mx_exp(T a) { return exp(a); } +template T mx_sign(T a) { return sign(a); } +template T mx_invert(T a, T amount) { return amount - a; } +template T mx_invert_fa(T a, float amount) { return amount - a; } + +// ---- math: ranges ----------------------------------------------------------- + +template T mx_clamp(T a, T lo, T hi) { return clamp(a, lo, hi); } +template T mx_clamp_fa(T a, float lo, float hi) { return clamp(a, lo, hi); } + +// MaterialX orders smoothstep's inputs (in, low, high); HLSL's takes (low, high, x). +template T mx_smoothstep(T a, T lo, T hi) { return smoothstep(lo, hi, a); } +template T mx_smoothstep_fa(T a, float lo, float hi) { return smoothstep(lo, hi, a); } + +// remap: map a from [inlow, inhigh] to [outlow, outhigh]. +template T mx_remap(T a, T inlow, T inhigh, T outlow, T outhigh) +{ + T t = (a - inlow) / (inhigh - inlow); + return outlow + t * (outhigh - outlow); +} +template T mx_remap_fa(T a, float inlow, float inhigh, float outlow, float outhigh) +{ + T t = (a - inlow) / (inhigh - inlow); + return outlow + t * (outhigh - outlow); +} + +// range: remap + optional gamma and clamp. +template T mx_range(T a, T inlow, T inhigh, T gamma, T outlow, T outhigh, bool doclamp) +{ + T result = (a - inlow) / (inhigh - inlow); + result = pow(max(result, 0), 1.0 / gamma); + result = outlow + result * (outhigh - outlow); + return doclamp ? clamp(result, outlow, outhigh) : result; +} + +// ---- compositing ------------------------------------------------------------ + +// The nodedef declares its inputs as (fg, bg, mix) -- not the bg-first order +// MaterialX's documentation uses -- and arguments are passed positionally in +// nodedef order, so fg comes first here. The vector forms take either a +// per-channel amount (same type as fg/bg) or a single float; the non-template +// overloads handle the latter, since a (T, T, float) template is ambiguous +// with (T, T, T) when T is float. +template T mx_mix(T fg, T bg, T amount) { return lerp(bg, fg, amount); } +float2 mx_mix(float2 fg, float2 bg, float amount) { return lerp(bg, fg, amount); } +float3 mx_mix(float3 fg, float3 bg, float amount) { return lerp(bg, fg, amount); } +float4 mx_mix(float4 fg, float4 bg, float amount) { return lerp(bg, fg, amount); } + +// ---- logic and selection ---------------------------------------------------- + +template T mx_ifgreater(float value1, float value2, T in1, T in2) { return value1 > value2 ? in1 : in2; } +template T mx_ifgreatereq(float value1, float value2, T in1, T in2) { return value1 >= value2 ? in1 : in2; } +template T mx_ifequal(float value1, float value2, T in1, T in2) { return value1 == value2 ? in1 : in2; } + +// Boolean-output forms: no in1/in2, the node just returns the comparison. +bool mx_ifgreater(float value1, float value2) { return value1 > value2; } +bool mx_ifgreatereq(float value1, float value2) { return value1 >= value2; } +bool mx_ifequal(float value1, float value2) { return value1 == value2; } + +// ---- vector math ------------------------------------------------------------ + +template float mx_dotproduct(T a, T b) { return dot(a, b); } +float3 mx_crossproduct(float3 a, float3 b) { return cross(a, b); } +template float mx_magnitude(T a) { return length(a); } +template T mx_normalize(T a) { return normalize(a); } + +// ---- channels / conversion -------------------------------------------------- + +float2 mx_combine2(float a, float b) { return float2(a, b); } +float4 mx_combine2(float3 a, float b) { return float4(a, b); } +float4 mx_combine2(float2 a, float2 b) { return float4(a, b); } +float3 mx_combine3(float a, float b, float c) { return float3(a, b, c); } +float4 mx_combine4(float a, float b, float c, float d) { return float4(a, b, c, d); } + +// separate2/3/4 have one output per channel, so they use out-parameters. +void mx_separate2(float2 v, out float outx, out float outy) { outx = v.x; outy = v.y; } +void mx_separate3(float3 v, out float outx, out float outy, out float outz) { outx = v.x; outy = v.y; outz = v.z; } +void mx_separate4(float4 v, out float outr, out float outg, out float outb, out float outa) { outr = v.x; outg = v.y; outb = v.z; outa = v.w; } + +float mx_luminance(float3 c, float3 lumacoeffs) { return dot(c, lumacoeffs); } +float mx_luminance(float4 c, float4 lumacoeffs) { return dot(c.xyz, lumacoeffs.xyz); } + +// ---- procedural ------------------------------------------------------------- + +// ramplr/ramptb: linear ramp from valuel to valuer (valuet to valueb) over +// the texcoord's u (v), clamped to [0,1]. +template T mx_ramplr(T valuel, T valuer, float2 texcoord) +{ + return lerp(valuel, valuer, clamp(texcoord.x, 0.0, 1.0)); +} +template T mx_ramptb(T valuet, T valueb, float2 texcoord) +{ + return lerp(valuet, valueb, clamp(texcoord.y, 0.0, 1.0)); +} + +// checkerboard: color1 on even cells, color2 on odd, after scaling and +// offsetting the texcoord. Matches mx_checkerboard.glsl. +float3 mx_checkerboard(float3 color1, float3 color2, float2 uvscale, float2 uvoffset, float2 texcoord) +{ + float2 uv = texcoord * uvscale + uvoffset; + uv = floor(uv); + float check = fmod(uv.x + uv.y, 2.0); + return lerp(color1, color2, abs(check)); +} + +// ---- noise ------------------------------------------------------------------ +// +// Port of MaterialX's genglsl/lib/mx_noise.glsl, itself derived from Open +// Shading Language's oslnoise.h (Copyright (c) 2009-2010 Sony Pictures +// Imageworks Inc., et al.; BSD 3-Clause, notice reproduced in MaterialX's +// library). Results are meant to match OSL/MaterialX bit for bit, so the hash +// and the gradient tables must not be "improved". Helpers use the mxn_ prefix: +// anything named mx_( would be mistaken for a node implementation. + +int mxn_floor(float x) { return (int)floor(x); } + +float mxn_floorfrac(float x, out int i) +{ + i = mxn_floor(x); + return x - (float)i; +} + +float mxn_select(bool b, float t, float f) { return b ? t : f; } +float mxn_negate_if(float val, bool b) { return b ? -val : val; } + +float mxn_bilerp(float v0, float v1, float v2, float v3, float s, float t) +{ + float s1 = 1.0 - s; + return (1.0 - t) * (v0 * s1 + v1 * s) + t * (v2 * s1 + v3 * s); +} +float3 mxn_bilerp(float3 v0, float3 v1, float3 v2, float3 v3, float s, float t) +{ + float s1 = 1.0 - s; + return (1.0 - t) * (v0 * s1 + v1 * s) + t * (v2 * s1 + v3 * s); +} + +float mxn_trilerp(float v0, float v1, float v2, float v3, float v4, float v5, float v6, float v7, float s, float t, float r) +{ + float s1 = 1.0 - s; + float t1 = 1.0 - t; + float r1 = 1.0 - r; + return r1 * (t1 * (v0 * s1 + v1 * s) + t * (v2 * s1 + v3 * s)) + + r * (t1 * (v4 * s1 + v5 * s) + t * (v6 * s1 + v7 * s)); +} +float3 mxn_trilerp(float3 v0, float3 v1, float3 v2, float3 v3, float3 v4, float3 v5, float3 v6, float3 v7, float s, float t, float r) +{ + float s1 = 1.0 - s; + float t1 = 1.0 - t; + float r1 = 1.0 - r; + return r1 * (t1 * (v0 * s1 + v1 * s) + t * (v2 * s1 + v3 * s)) + + r * (t1 * (v4 * s1 + v5 * s) + t * (v6 * s1 + v7 * s)); +} + +float mxn_gradient_float(uint hash, float x, float y) +{ + uint h = hash & 7u; + float u = mxn_select(h < 4u, x, y); + float v = 2.0 * mxn_select(h < 4u, y, x); + return mxn_negate_if(u, (h & 1u) != 0u) + mxn_negate_if(v, (h & 2u) != 0u); +} +float mxn_gradient_float(uint hash, float x, float y, float z) +{ + uint h = hash & 15u; + float u = mxn_select(h < 8u, x, y); + float v = mxn_select(h < 4u, y, mxn_select((h == 12u) || (h == 14u), x, z)); + return mxn_negate_if(u, (h & 1u) != 0u) + mxn_negate_if(v, (h & 2u) != 0u); +} +float3 mxn_gradient_vec3(uint3 hash, float x, float y) +{ + return float3(mxn_gradient_float(hash.x, x, y), mxn_gradient_float(hash.y, x, y), mxn_gradient_float(hash.z, x, y)); +} +float3 mxn_gradient_vec3(uint3 hash, float x, float y, float z) +{ + return float3(mxn_gradient_float(hash.x, x, y, z), mxn_gradient_float(hash.y, x, y, z), mxn_gradient_float(hash.z, x, y, z)); +} + +// Normalise the unnormalised gradients above; measured, not chosen. +float mxn_gradient_scale2d(float v) { return 0.6616 * v; } +float mxn_gradient_scale3d(float v) { return 0.9820 * v; } +float3 mxn_gradient_scale2d(float3 v) { return 0.6616 * v; } +float3 mxn_gradient_scale3d(float3 v) { return 0.9820 * v; } + +uint mxn_rotl32(uint x, int k) { return (x << k) | (x >> (32 - k)); } + +void mxn_bjmix(inout uint a, inout uint b, inout uint c) +{ + a -= c; a ^= mxn_rotl32(c, 4); c += b; + b -= a; b ^= mxn_rotl32(a, 6); a += c; + c -= b; c ^= mxn_rotl32(b, 8); b += a; + a -= c; a ^= mxn_rotl32(c, 16); c += b; + b -= a; b ^= mxn_rotl32(a, 19); a += c; + c -= b; c ^= mxn_rotl32(b, 4); b += a; +} + +uint mxn_bjfinal(uint a, uint b, uint c) +{ + c ^= b; c -= mxn_rotl32(b, 14); + a ^= c; a -= mxn_rotl32(c, 11); + b ^= a; b -= mxn_rotl32(a, 25); + c ^= b; c -= mxn_rotl32(b, 16); + a ^= c; a -= mxn_rotl32(c, 4); + b ^= a; b -= mxn_rotl32(a, 14); + c ^= b; c -= mxn_rotl32(b, 24); + return c; +} + +float mxn_bits_to_01(uint bits) { return (float)bits / (float)0xffffffffu; } + +float mxn_fade(float t) { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); } + +uint mxn_hash_int(int x) +{ + uint seed = 0xdeadbeefu + (1u << 2u) + 13u; + return mxn_bjfinal(seed + (uint)x, seed, seed); +} + +uint mxn_hash_int(int x, int y) +{ + uint a = 0xdeadbeefu + (2u << 2u) + 13u; + uint b = a; + uint c = a; + a += (uint)x; + b += (uint)y; + return mxn_bjfinal(a, b, c); +} + +uint mxn_hash_int(int x, int y, int z) +{ + uint a = 0xdeadbeefu + (3u << 2u) + 13u; + uint b = a; + uint c = a; + a += (uint)x; + b += (uint)y; + c += (uint)z; + return mxn_bjfinal(a, b, c); +} + +uint3 mxn_hash_vec3(int x, int y) +{ + uint h = mxn_hash_int(x, y); + return uint3(h & 0xFFu, (h >> 8) & 0xFFu, (h >> 16) & 0xFFu); +} + +uint3 mxn_hash_vec3(int x, int y, int z) +{ + uint h = mxn_hash_int(x, y, z); + return uint3(h & 0xFFu, (h >> 8) & 0xFFu, (h >> 16) & 0xFFu); +} + +float mxn_perlin_float(float2 p) +{ + int X, Y; + float fx = mxn_floorfrac(p.x, X); + float fy = mxn_floorfrac(p.y, Y); + float u = mxn_fade(fx); + float v = mxn_fade(fy); + float result = mxn_bilerp( + mxn_gradient_float(mxn_hash_int(X, Y ), fx, fy ), + mxn_gradient_float(mxn_hash_int(X + 1, Y ), fx - 1.0, fy ), + mxn_gradient_float(mxn_hash_int(X, Y + 1), fx, fy - 1.0), + mxn_gradient_float(mxn_hash_int(X + 1, Y + 1), fx - 1.0, fy - 1.0), + u, v); + return mxn_gradient_scale2d(result); +} + +float mxn_perlin_float(float3 p) +{ + int X, Y, Z; + float fx = mxn_floorfrac(p.x, X); + float fy = mxn_floorfrac(p.y, Y); + float fz = mxn_floorfrac(p.z, Z); + float u = mxn_fade(fx); + float v = mxn_fade(fy); + float w = mxn_fade(fz); + float result = mxn_trilerp( + mxn_gradient_float(mxn_hash_int(X, Y, Z ), fx, fy, fz ), + mxn_gradient_float(mxn_hash_int(X + 1, Y, Z ), fx - 1.0, fy, fz ), + mxn_gradient_float(mxn_hash_int(X, Y + 1, Z ), fx, fy - 1.0, fz ), + mxn_gradient_float(mxn_hash_int(X + 1, Y + 1, Z ), fx - 1.0, fy - 1.0, fz ), + mxn_gradient_float(mxn_hash_int(X, Y, Z + 1), fx, fy, fz - 1.0), + mxn_gradient_float(mxn_hash_int(X + 1, Y, Z + 1), fx - 1.0, fy, fz - 1.0), + mxn_gradient_float(mxn_hash_int(X, Y + 1, Z + 1), fx, fy - 1.0, fz - 1.0), + mxn_gradient_float(mxn_hash_int(X + 1, Y + 1, Z + 1), fx - 1.0, fy - 1.0, fz - 1.0), + u, v, w); + return mxn_gradient_scale3d(result); +} + +float3 mxn_perlin_vec3(float2 p) +{ + int X, Y; + float fx = mxn_floorfrac(p.x, X); + float fy = mxn_floorfrac(p.y, Y); + float u = mxn_fade(fx); + float v = mxn_fade(fy); + float3 result = mxn_bilerp( + mxn_gradient_vec3(mxn_hash_vec3(X, Y ), fx, fy ), + mxn_gradient_vec3(mxn_hash_vec3(X + 1, Y ), fx - 1.0, fy ), + mxn_gradient_vec3(mxn_hash_vec3(X, Y + 1), fx, fy - 1.0), + mxn_gradient_vec3(mxn_hash_vec3(X + 1, Y + 1), fx - 1.0, fy - 1.0), + u, v); + return mxn_gradient_scale2d(result); +} + +float3 mxn_perlin_vec3(float3 p) +{ + int X, Y, Z; + float fx = mxn_floorfrac(p.x, X); + float fy = mxn_floorfrac(p.y, Y); + float fz = mxn_floorfrac(p.z, Z); + float u = mxn_fade(fx); + float v = mxn_fade(fy); + float w = mxn_fade(fz); + float3 result = mxn_trilerp( + mxn_gradient_vec3(mxn_hash_vec3(X, Y, Z ), fx, fy, fz ), + mxn_gradient_vec3(mxn_hash_vec3(X + 1, Y, Z ), fx - 1.0, fy, fz ), + mxn_gradient_vec3(mxn_hash_vec3(X, Y + 1, Z ), fx, fy - 1.0, fz ), + mxn_gradient_vec3(mxn_hash_vec3(X + 1, Y + 1, Z ), fx - 1.0, fy - 1.0, fz ), + mxn_gradient_vec3(mxn_hash_vec3(X, Y, Z + 1), fx, fy, fz - 1.0), + mxn_gradient_vec3(mxn_hash_vec3(X + 1, Y, Z + 1), fx - 1.0, fy, fz - 1.0), + mxn_gradient_vec3(mxn_hash_vec3(X, Y + 1, Z + 1), fx, fy - 1.0, fz - 1.0), + mxn_gradient_vec3(mxn_hash_vec3(X + 1, Y + 1, Z + 1), fx - 1.0, fy - 1.0, fz - 1.0), + u, v, w); + return mxn_gradient_scale3d(result); +} + +float mxn_cell_float(float2 p) +{ + return mxn_bits_to_01(mxn_hash_int(mxn_floor(p.x), mxn_floor(p.y))); +} + +float mxn_cell_float(float3 p) +{ + return mxn_bits_to_01(mxn_hash_int(mxn_floor(p.x), mxn_floor(p.y), mxn_floor(p.z))); +} + +float3 mxn_cell_vec3(float2 p) +{ + int ix = mxn_floor(p.x); + int iy = mxn_floor(p.y); + return float3( + mxn_bits_to_01(mxn_hash_int(ix, iy, 0)), + mxn_bits_to_01(mxn_hash_int(ix, iy, 1)), + mxn_bits_to_01(mxn_hash_int(ix, iy, 2))); +} + +float3 mxn_cell_vec3(float3 p) +{ + uint a = 0xdeadbeefu + (4u << 2u) + 13u; + uint b = a; + uint c = a; + a += (uint)mxn_floor(p.x); + b += (uint)mxn_floor(p.y); + c += (uint)mxn_floor(p.z); + mxn_bjmix(a, b, c); + return float3( + mxn_bits_to_01(mxn_bjfinal(a, b, c)), + mxn_bits_to_01(mxn_bjfinal(a + 1u, b, c)), + mxn_bits_to_01(mxn_bjfinal(a + 2u, b, c))); +} + +float mxn_fractal_float(float2 p, int octaves, float lacunarity, float diminish) +{ + float result = 0.0; + float amplitude = 1.0; + for (int i = 0; i < octaves; ++i) + { + result += amplitude * mxn_perlin_float(p); + amplitude *= diminish; + p *= lacunarity; + } + return result; +} + +float mxn_fractal_float(float3 p, int octaves, float lacunarity, float diminish) +{ + float result = 0.0; + float amplitude = 1.0; + for (int i = 0; i < octaves; ++i) + { + result += amplitude * mxn_perlin_float(p); + amplitude *= diminish; + p *= lacunarity; + } + return result; +} + +float3 mxn_fractal_vec3(float2 p, int octaves, float lacunarity, float diminish) +{ + float3 result = 0.0; + float amplitude = 1.0; + for (int i = 0; i < octaves; ++i) + { + result += amplitude * mxn_perlin_vec3(p); + amplitude *= diminish; + p *= lacunarity; + } + return result; +} + +float3 mxn_fractal_vec3(float3 p, int octaves, float lacunarity, float diminish) +{ + float3 result = 0.0; + float amplitude = 1.0; + for (int i = 0; i < octaves; ++i) + { + result += amplitude * mxn_perlin_vec3(p); + amplitude *= diminish; + p *= lacunarity; + } + return result; +} + +float2 mxn_worley_cell_position(int x, int y, int xoff, int yoff, float jitter) +{ + float2 off = mxn_cell_vec3(float2(x + xoff, y + yoff)).xy; + off -= 0.5; + off *= jitter; + off += 0.5; + return float2(x, y) + off; +} + +float3 mxn_worley_cell_position(int x, int y, int z, int xoff, int yoff, int zoff, float jitter) +{ + float3 off = mxn_cell_vec3(float3(x + xoff, y + yoff, z + zoff)); + off -= 0.5; + off *= jitter; + off += 0.5; + return float3(x, y, z) + off; +} + +// metric: 0 Euclidean (squared here, rooted by the caller), 1 distance^2, +// 2 Manhattan, 3 Chebyshev. The stdlib nodedefs only ever pass 0. +float mxn_worley_distance(float2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + float2 diff = mxn_worley_cell_position(x, y, xoff, yoff, jitter) - p; + if (metric == 2) + return abs(diff.x) + abs(diff.y); + if (metric == 3) + return max(abs(diff.x), abs(diff.y)); + return dot(diff, diff); +} + +float mxn_worley_distance(float3 p, int x, int y, int z, int xoff, int yoff, int zoff, float jitter, int metric) +{ + float3 diff = mxn_worley_cell_position(x, y, z, xoff, yoff, zoff, jitter) - p; + if (metric == 2) + return abs(diff.x) + abs(diff.y) + abs(diff.z); + if (metric == 3) + return max(max(abs(diff.x), abs(diff.y)), abs(diff.z)); + return dot(diff, diff); +} + +// Nearest three feature distances of a 2D cell neighbourhood, plus the +// offset of the nearest feature point (for the "Solid" style). +void mxn_worley_search(float2 p, float jitter, int metric, out float3 sqdist, out float2 minpos, out float2 localpos) +{ + int X, Y; + localpos = float2(mxn_floorfrac(p.x, X), mxn_floorfrac(p.y, Y)); + sqdist = 1e6; + minpos = 0; + + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float dist = mxn_worley_distance(localpos, x, y, X, Y, jitter, metric); + float2 cellpos = mxn_worley_cell_position(x, y, X, Y, jitter) - localpos; + + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } +} + +void mxn_worley_search(float3 p, float jitter, int metric, out float3 sqdist, out float3 minpos, out float3 localpos) +{ + int X, Y, Z; + localpos = float3(mxn_floorfrac(p.x, X), mxn_floorfrac(p.y, Y), mxn_floorfrac(p.z, Z)); + sqdist = 1e6; + minpos = 0; + + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + for (int z = -1; z <= 1; ++z) + { + float dist = mxn_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); + float3 cellpos = mxn_worley_cell_position(x, y, z, X, Y, Z, jitter) - localpos; + + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + minpos = cellpos; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } +} + +// One search serves the float, vector2 and vector3 forms: they differ only in +// how many of the three nearest distances they return. +float3 mxn_worley(float2 p, float jitter, int style) +{ + float3 sqdist; float2 minpos; float2 localpos; + mxn_worley_search(p, jitter, 0, sqdist, minpos, localpos); + + if (style == 1) + return mxn_cell_vec3(minpos + p); + + return sqrt(sqdist); +} + +float3 mxn_worley(float3 p, float jitter, int style) +{ + float3 sqdist; float3 minpos; float3 localpos; + mxn_worley_search(p, jitter, 0, sqdist, minpos, localpos); + + if (style == 1) + return mxn_cell_vec3(minpos + p); + + return sqrt(sqdist); +} + +// Solid style of the scalar form hashes the cell with the scalar cell noise, +// which is not the first channel of the vec3 one; keep it separate. +float mxn_worley_float(float2 p, float jitter, int style) +{ + if (style == 1) + { + float3 sqdist; float2 minpos; float2 localpos; + mxn_worley_search(p, jitter, 0, sqdist, minpos, localpos); + return mxn_cell_float(minpos + p); + } + + return mxn_worley(p, jitter, style).x; +} + +float mxn_worley_float(float3 p, float jitter, int style) +{ + if (style == 1) + { + float3 sqdist; float3 minpos; float3 localpos; + mxn_worley_search(p, jitter, 0, sqdist, minpos, localpos); + return mxn_cell_float(minpos + p); + } + + return mxn_worley(p, jitter, style).x; +} + +// 4D cell noise, used by flake only. +uint mxn_hash_int(int x, int y, int z, int xx) +{ + uint a = 0xdeadbeefu + (4u << 2u) + 13u; + uint b = a; + uint c = a; + a += (uint)x; + b += (uint)y; + c += (uint)z; + mxn_bjmix(a, b, c); + a += (uint)xx; + return mxn_bjfinal(a, b, c); +} + +float mxn_cell_float(float4 p) +{ + return mxn_bits_to_01(mxn_hash_int(mxn_floor(p.x), mxn_floor(p.y), mxn_floor(p.z), mxn_floor(p.w))); +} + +float3 mxn_cell_vec3(float4 p) +{ + uint a = 0xdeadbeefu + (5u << 2u) + 13u; + uint b = a; + uint c = a; + a += (uint)mxn_floor(p.x); + b += (uint)mxn_floor(p.y); + c += (uint)mxn_floor(p.z); + mxn_bjmix(a, b, c); + a += (uint)mxn_floor(p.w); + return float3( + mxn_bits_to_01(mxn_bjfinal(a, b, c)), + mxn_bits_to_01(mxn_bjfinal(a, b + 1u, c)), + mxn_bits_to_01(mxn_bjfinal(a, b + 2u, c))); +} + +// -- nodes -- + +float mx_noise2d(float amplitude, float pivot, float2 texcoord) { return mxn_perlin_float(texcoord) * amplitude + pivot; } +float2 mx_noise2d(float2 amplitude, float pivot, float2 texcoord) { return mxn_perlin_vec3(texcoord).xy * amplitude + pivot; } +float3 mx_noise2d(float3 amplitude, float pivot, float2 texcoord) { return mxn_perlin_vec3(texcoord) * amplitude + pivot; } +float4 mx_noise2d(float4 amplitude, float pivot, float2 texcoord) +{ + return float4(mxn_perlin_vec3(texcoord), mxn_perlin_float(texcoord + float2(19, 73))) * amplitude + pivot; +} + +float mx_noise3d(float amplitude, float pivot, float3 position) { return mxn_perlin_float(position) * amplitude + pivot; } +float2 mx_noise3d(float2 amplitude, float pivot, float3 position) { return mxn_perlin_vec3(position).xy * amplitude + pivot; } +float3 mx_noise3d(float3 amplitude, float pivot, float3 position) { return mxn_perlin_vec3(position) * amplitude + pivot; } +float4 mx_noise3d(float4 amplitude, float pivot, float3 position) +{ + return float4(mxn_perlin_vec3(position), mxn_perlin_float(position + float3(19, 73, 29))) * amplitude + pivot; +} + +float mx_fractal2d(float amplitude, int octaves, float lacunarity, float diminish, float2 texcoord) +{ + return mxn_fractal_float(texcoord, octaves, lacunarity, diminish) * amplitude; +} +float2 mx_fractal2d(float2 amplitude, int octaves, float lacunarity, float diminish, float2 texcoord) +{ + return float2(mxn_fractal_float(texcoord, octaves, lacunarity, diminish), + mxn_fractal_float(texcoord + float2(19, 193), octaves, lacunarity, diminish)) * amplitude; +} +float3 mx_fractal2d(float3 amplitude, int octaves, float lacunarity, float diminish, float2 texcoord) +{ + return mxn_fractal_vec3(texcoord, octaves, lacunarity, diminish) * amplitude; +} +float4 mx_fractal2d(float4 amplitude, int octaves, float lacunarity, float diminish, float2 texcoord) +{ + return float4(mxn_fractal_vec3(texcoord, octaves, lacunarity, diminish), + mxn_fractal_float(texcoord + float2(19, 193), octaves, lacunarity, diminish)) * amplitude; +} + +float mx_fractal3d(float amplitude, int octaves, float lacunarity, float diminish, float3 position) +{ + return mxn_fractal_float(position, octaves, lacunarity, diminish) * amplitude; +} +float2 mx_fractal3d(float2 amplitude, int octaves, float lacunarity, float diminish, float3 position) +{ + return float2(mxn_fractal_float(position, octaves, lacunarity, diminish), + mxn_fractal_float(position + float3(19, 193, 17), octaves, lacunarity, diminish)) * amplitude; +} +float3 mx_fractal3d(float3 amplitude, int octaves, float lacunarity, float diminish, float3 position) +{ + return mxn_fractal_vec3(position, octaves, lacunarity, diminish) * amplitude; +} +float4 mx_fractal3d(float4 amplitude, int octaves, float lacunarity, float diminish, float3 position) +{ + return float4(mxn_fractal_vec3(position, octaves, lacunarity, diminish), + mxn_fractal_float(position + float3(19, 193, 17), octaves, lacunarity, diminish)) * amplitude; +} + +float mx_cellnoise2d(float2 texcoord) { return mxn_cell_float(texcoord); } +float mx_cellnoise3d(float3 position) { return mxn_cell_float(position); } + +// The worley nodedefs differ only in output type, so they are looked up as +// mx__ (see MaterialXLibrary::register_nodes). +float mx_worleynoise2d_float(float2 texcoord, float jitter, int style) { return mxn_worley_float(texcoord, jitter, style); } +float2 mx_worleynoise2d_vector2(float2 texcoord, float jitter, int style) { return mxn_worley(texcoord, jitter, style).xy; } +float3 mx_worleynoise2d_vector3(float2 texcoord, float jitter, int style) { return mxn_worley(texcoord, jitter, style); } +float mx_worleynoise3d_float(float3 position, float jitter, int style) { return mxn_worley_float(position, jitter, style); } +float2 mx_worleynoise3d_vector2(float3 position, float jitter, int style) { return mxn_worley(position, jitter, style).xy; } +float3 mx_worleynoise3d_vector3(float3 position, float jitter, int style) { return mxn_worley(position, jitter, style); } + +// ---- color ------------------------------------------------------------------ +// From MaterialX's lib/mx_hsv.glsl (also derived from OSL; Foley & van Dam). + +float3 mxn_hsvtorgb(float3 hsv) +{ + float h = hsv.x; + float s = hsv.y; + float v = hsv.z; + + if (s < 0.0001) + return float3(v, v, v); + + h = 6.0 * (h - floor(h)); + int hi = (int)trunc(h); + float f = h - (float)hi; + float p = v * (1.0 - s); + float q = v * (1.0 - s * f); + float t = v * (1.0 - s * (1.0 - f)); + + if (hi == 0) return float3(v, t, p); + if (hi == 1) return float3(q, v, p); + if (hi == 2) return float3(p, v, t); + if (hi == 3) return float3(p, q, v); + if (hi == 4) return float3(t, p, v); + return float3(v, p, q); +} + +float3 mxn_rgbtohsv(float3 c) +{ + float r = c.x; + float g = c.y; + float b = c.z; + float maxcomp = max(r, max(g, b)); + float delta = maxcomp - min(r, min(g, b)); + float s = maxcomp > 0.0 ? delta / maxcomp : 0.0; + float h = 0.0; + + if (s > 0.0) + { + if (r >= maxcomp) h = (g - b) / delta; + else if (g >= maxcomp) h = 2.0 + (b - r) / delta; + else h = 4.0 + (r - g) / delta; + + h *= 1.0 / 6.0; + + if (h < 0.0) + h += 1.0; + } + + return float3(h, s, maxcomp); +} + +float3 mx_hsvtorgb(float3 c) { return mxn_hsvtorgb(c); } +float4 mx_hsvtorgb(float4 c) { return float4(mxn_hsvtorgb(c.xyz), c.w); } +float3 mx_rgbtohsv(float3 c) { return mxn_rgbtohsv(c); } +float4 mx_rgbtohsv(float4 c) { return float4(mxn_rgbtohsv(c.xyz), c.w); } + +float4 mx_premult(float4 c) { return float4(c.xyz * c.w, c.w); } +float4 mx_unpremult(float4 c) { return float4(c.xyz / c.w, c.w); } + +// burn/dodge/disjointover: the "mix" blend of MaterialX compositing. +template T mx_burn(T fg, T bg, float mixval) +{ + T r = mixval * (1.0 - (1.0 - bg) / fg) + (1.0 - mixval) * bg; + return select(abs(fg) < 1e-8, (T)0, r); +} + +template T mx_dodge(T fg, T bg, float mixval) +{ + T r = mixval * (bg / (1.0 - fg)) + (1.0 - mixval) * bg; + return select(abs(1.0 - fg) < 1e-8, (T)0, r); +} + +float4 mx_disjointover(float4 fg, float4 bg, float mixval) +{ + float summed = fg.w + bg.w; + float3 rgb; + + if (summed <= 1.0) + rgb = fg.xyz + bg.xyz; + else if (abs(bg.w) < 1e-8) + rgb = 0.0; + else + rgb = fg.xyz + bg.xyz * ((1.0 - fg.w) / bg.w); + + float4 result = float4(rgb, min(summed, 1.0)); + return result * mixval + (1.0 - mixval) * bg; +} + +// ---- geometry-independent transforms ---------------------------------------- + +float2 mx_rotate2d(float2 v, float amount) +{ + float a = radians(amount); + float sa = sin(a); + float ca = cos(a); + return float2(ca * v.x + sa * v.y, -sa * v.x + ca * v.y); +} + +// Rodrigues; MaterialX uses v' = v * M, hence the cross product order. +float3 mx_rotate3d(float3 v, float amount, float3 axis) +{ + axis = normalize(axis); + float a = radians(amount); + float s = sin(a); + float c = cos(a); + return v * c + cross(v, axis) * s + axis * dot(axis, v) * (1.0 - c); +} + +// MaterialX antialiases the split edge with screen derivatives (mx_aastep). +// COMPILED_FUNC also runs in ray-tracing hit shaders, where ddx/ddy do not +// exist, so the edge here is a hard step. +template T mx_splitlr(T valuel, T valuer, float center, float2 texcoord) +{ + return lerp(valuel, valuer, step(center, texcoord.x)); +} + +template T mx_splittb(T valuet, T valueb, float center, float2 texcoord) +{ + return lerp(valueb, valuet, step(center, texcoord.y)); +} + +// ---- flake ------------------------------------------------------------------ +// From lib/mx_flake.glsl. normal/tangent/bitangent are the shading frame; they +// default to geometry inputs that MaterialXNode does not provide yet, so the +// flake normal is only meaningful once those are linked. + +float3 mxn_rotate_flake(float3 p, float3 i) +{ + float theta = 3.14159265358979 * 2.0 * i.x; + float phi = 3.14159265358979 * 2.0 * i.y; + float z = i.z * 2.0; + float r = sqrt(z); + float vx = sin(phi) * r; + float vy = cos(phi) * r; + float vz = sqrt(2.0 - z); + float s_theta = sin(theta); + float c_theta = cos(theta); + float sx = vx * c_theta - vy * s_theta; + float sy = vx * s_theta + vy * c_theta; + + // GLSL mat3(...) is column-major: the arguments below are the columns. + return float3(vx * sx - c_theta, vx * sy - s_theta, vx * vz) * p.x + + float3(vy * sx + s_theta, vy * sy - c_theta, vy * vz) * p.y + + float3(vz * sx, vz * sy, 1.0 - z) * p.z; +} + +// Numerically fitted curve of flake density to per-cell probability. +float mxn_flake_density_to_probability(float x) +{ + const float4 abcd = float4(-26.19771808, 26.39663835, 85.53857017, -102.35069432); + const float2 ef = float2(-101.42634862, 118.45082288); + float xx = x * x; + return (abcd.x * xx + abcd.y * x) / (abcd.z * xx * x + abcd.w * xx + ef.x * x + ef.y); +} + +void mxn_flake(float size, float roughness, float coverage, float3 position, + float3 normal, float3 tangent, float3 bitangent, + out int id, out float rand, out float presence, out float3 flakenormal) +{ + float probability = mxn_flake_density_to_probability(clamp(coverage, 0.0, 1.0)); + const float flake_diameter = 1.5 / sqrt(3.0); + + float3 P = position / size; + float3 base_P = floor(P); + float flake_priority = 0.0; + float3 flake_cell = 0.0; + + for (int i = -1; i < 2; ++i) + { + for (int j = -1; j < 2; ++j) + { + for (int k = -1; k < 2; ++k) + { + float3 cell_pos = base_P + float3(i, j, k); + float3 PP = P - cell_pos - 0.5; + + if (dot(PP, PP) >= flake_diameter * flake_diameter * 3.0) + continue; + + if (mxn_cell_float(cell_pos) > probability) + continue; + + float priority = mxn_cell_float(float4(cell_pos, 3.0)); + + if (priority < flake_priority) + continue; + + PP = mxn_rotate_flake(PP, mxn_cell_vec3(cell_pos)); + + if (abs(PP.x) <= flake_diameter && abs(PP.y) <= flake_diameter && abs(PP.z) <= flake_diameter) + { + flake_priority = priority; + flake_cell = cell_pos; + } + } + } + } + + id = 0; + rand = 0.0; + presence = 0.0; + flakenormal = normal; + + if (flake_priority <= 0.0) + return; + + float3 flake_noise = mxn_cell_vec3(float4(flake_cell, 2.0)); + float xi0 = flake_noise.x; + float xi1 = flake_noise.y; + rand = flake_noise.z; + id = (int)(rand * 16777215.0); // 2^24-1 is exact in float32 + presence = flake_priority; + + float phi = 3.14159265358979 * 2.0 * xi0; + float tan_theta = roughness * roughness * sqrt(xi1) / sqrt(1.0 - xi1); + float sin_theta = tan_theta / sqrt(1.0 + tan_theta * tan_theta); + float cos_theta = sqrt(1.0 - sin_theta * sin_theta); + flakenormal = normalize(tangent * cos(phi) * sin_theta + bitangent * sin(phi) * sin_theta + normal * cos_theta); +} + +void mx_flake2d(float size, float roughness, float coverage, float2 texcoord, + float3 normal, float3 tangent, float3 bitangent, + out int id, out float rand, out float presence, out float3 flakenormal) +{ + mxn_flake(size, roughness, coverage, float3(texcoord, 0.0), normal, tangent, bitangent, id, rand, presence, flakenormal); +} + +void mx_flake3d(float size, float roughness, float coverage, float3 position, + float3 normal, float3 tangent, float3 bitangent, + out int id, out float rand, out float presence, out float3 flakenormal) +{ + mxn_flake(size, roughness, coverage, position, normal, tangent, bitangent, id, rand, presence, flakenormal); +} + +// Everything MaterialX defines as an inline expression or a nodegraph is +// generated (workdir/materialx/gen_generated_hlsl.py) and sees the functions above. +#include "mx_generated.hlsl" + +#endif diff --git a/workdir/shaders/nrd/nrd_indirect_combine.hlsl b/workdir/shaders/nrd/nrd_indirect_combine.hlsl index 57bf1dab4..36b79a327 100644 --- a/workdir/shaders/nrd/nrd_indirect_combine.hlsl +++ b/workdir/shaders/nrd/nrd_indirect_combine.hlsl @@ -2,6 +2,8 @@ #include "../autogen/FrameInfo.h" #include "../autogen/NRD_IndirectCombineParams.h" +#include "../common/coat.hlsl" +#include "../common/sheen.hlsl" // REBLUR_BackEnd_UnpackRadianceAndNormHitDist, to decode the REBLUR-denoised // indirect signal below (see [[project-nrd-integration]]) -- self-contained, @@ -47,7 +49,14 @@ void CS(uint3 dispatchID : SV_DispatchThreadID) float3 indirect = REBLUR_BackEnd_UnpackRadianceAndNormHitDist(GetNRD_IndirectCombineParams().GetIndirect()[tc]).rgb; float NoV = dot(normal, v); - float3 color = albedo.rgb * IntegrateBRDF(roughness, metallic, -NoV).x * indirect; + // (1 - metallic): a metal has no diffuse for bounce light to light, as in + // rtx_combine.hlsl's identical term. + float4 material = gbuffer.GetMaterial()[tc]; + float3 color = albedo.rgb * IntegrateBRDF(roughness, metallic, -NoV).x * indirect * (1 - metallic) + * coat_base_attenuation(material, normal, -v); + + // Cloth: the sheen lobe's own share of the ambient light (common/sheen.hlsl). + color += sheen_indirect(material, normal, -v, albedo.rgb, indirect); GetNRD_IndirectCombineParams().GetTarget()[tc] += float4(color, 1); } diff --git a/workdir/shaders/nrd/nrd_shadow_combine.hlsl b/workdir/shaders/nrd/nrd_shadow_combine.hlsl index a713fcc8e..6867086d3 100644 --- a/workdir/shaders/nrd/nrd_shadow_combine.hlsl +++ b/workdir/shaders/nrd/nrd_shadow_combine.hlsl @@ -7,6 +7,9 @@ // shadow below (see [[project-nrd-integration]]) -- self-contained, same as // nrd_indirect_combine.hlsl's own #include of this file. #include "3rdparty/NRD.hlsli" +#include "../common/lobes.hlsl" +#include "../common/coat.hlsl" +#include "../common/shading_model.hlsl" static const GBuffer gbuffer = GetNRD_ShadowCombineParams().GetGbuffer(); @@ -40,5 +43,21 @@ void CS(uint3 dispatchID : SV_DispatchThreadID) float3 light_dir = normalize(GetFrameInfo().GetSunDir().xyz); float NL = saturate(dot(normal, light_dir)); - GetNRD_ShadowCombineParams().GetTarget()[tc] = float4(shadow * (NL * albedo.rgb * (1 - metallic)), 1); + float3 result = shadow * (NL * albedo.rgb * (1 - metallic)); + + // Same coat/sheen/aniso/metal sun lobes as vsm_resolve_combine; without them + // this source would drop every non-STANDARD material's highlight. + float4 material = gbuffer.GetMaterial()[tc]; + if (material_model(material) != SHADING_MODEL_STANDARD || metallic > 0.001) + { + float2 itc = (float2(tc) + 0.5) / dims; + Camera camera = GetFrameInfo().GetCamera(); + float3 wpos = depth_to_wpos(raw_z, itc, camera.GetInvViewProj()); + float3 view = normalize(camera.GetPosition() - wpos); + result *= coat_base_attenuation(material, normal, view); + result += material_sun_lobes(material, normal, view, light_dir, shadow, + lerp(0.04, albedo.rgb, metallic), max(MIN_ROUGHNESS, gbuffer.GetNormals()[tc].w), albedo.rgb, metallic); + } + + GetNRD_ShadowCombineParams().GetTarget()[tc] = float4(result, 1); } diff --git a/workdir/shaders/postprocess/downsample.hlsl b/workdir/shaders/postprocess/downsample.hlsl index 7f1a44d00..604fefe59 100644 --- a/workdir/shaders/postprocess/downsample.hlsl +++ b/workdir/shaders/postprocess/downsample.hlsl @@ -19,6 +19,10 @@ static const TileClassifyData params = GetTileClassifyData(); static const GBuffer gbuffer = params.GetGbuffer(); +// Included for material_model(): anisotropic pixels force their tile to a full-res +// specular trace (see the roughness verdict below). +#include "../common/shading_model.hlsl" + // Tile classified Hi if the spread between the tile's 16 block // representatives' raw depth exceeds this. Per-pixel mask (inside Hi tiles // only, in principle) uses a tighter threshold since it is judging a single @@ -32,6 +36,8 @@ groupshared float g_metallic[64]; groupshared float g_blockDepth[16]; groupshared float g_blockRoughness[16]; groupshared float g_blockMetallic[16]; +// Set when any pixel of the tile has the ANISOTROPIC shading model. +groupshared uint g_anisotropic; [numthreads(8, 8, 1)] void CS( @@ -45,6 +51,9 @@ void CS( uint2 pix = min(dispatchID.xy, dims - 1); + if (groupIndex == 0) + g_anisotropic = 0; + float depth = gbuffer.GetDepth()[pix]; float4 normal_raw = gbuffer.GetNormals()[pix]; // .w = metallic (universal_material.hlsl: result.albedo = float4(albedo.rgb, metallic)). @@ -56,6 +65,14 @@ void CS( GroupMemoryBarrierWithGroupSync(); + // The half-res reflection the Low tiles copy is isotropic (that pass has no + // material target), so a tile holding anisotropic pixels has to trace its own + // full-res rays, where the stretched lobe is sampled -- whatever its roughness. + // After the barrier so the clear above is visible to every thread; the barrier + // below orders these writes before the leader's read. + if (depth != 0 && material_model(gbuffer.GetMaterial()[pix]) == SHADING_MODEL_ANISOTROPIC) + InterlockedOr(g_anisotropic, 1); + // One leader thread per 2x2 block (16 per 8x8 tile) picks the closest // real sample and writes the half-res output + this block's entry in // the tile-classification LDS. @@ -131,8 +148,8 @@ void CS( // some pixel is both glossy enough to show detail AND metallic // enough for that detail to survive the downstream multiply (see // TileClassifyData's own comment, pssm.prism). - uint roughness_hi = (min_roughness < params.GetRoughness_threshold() && - max_metallic > params.GetMetallic_threshold()) ? 1 : 0; + uint roughness_hi = ((min_roughness < params.GetRoughness_threshold() && + max_metallic > params.GetMetallic_threshold()) || g_anisotropic != 0) ? 1 : 0; if (roughness_hi) params.GetTile_roughness_hi().Append(groupID.xy); else diff --git a/workdir/shaders/postprocess/reflection_combine.hlsl b/workdir/shaders/postprocess/reflection_combine.hlsl index 8cdfa1b44..797f17486 100644 --- a/workdir/shaders/postprocess/reflection_combine.hlsl +++ b/workdir/shaders/postprocess/reflection_combine.hlsl @@ -4,6 +4,8 @@ #include "../autogen/FrameInfo.h" #include "../autogen/ReflectionCombine.h" +#include "../common/coat.hlsl" +#include "../common/iridescence.hlsl" // REBLUR_BackEnd_UnpackRadianceAndNormHitDist, to decode NRD REBLUR_SPECULAR's // denoised reflection signal below (see [[project-nrd-integration]]). @@ -79,7 +81,15 @@ void CS( // -- unpack before use. float3 reflection = REBLUR_BackEnd_UnpackRadianceAndNormHitDist(GetReflectionCombine().GetReflection()[dispatchID.xy]).rgb; - float3 color = get_PBR(albedo, reflection.xyz, normal, v, roughness, metallic); + float4 material = gbuffer.GetMaterial()[tc]; + float3 specular_color = albedo; + if (material_model(material) == SHADING_MODEL_THINFILM) + specular_color = iridescent_f0(saturate(dot(normal, -v)), thinfilm_thickness(material), thinfilm_ior(material), albedo); + + float3 color = get_PBR(specular_color, reflection.xyz, normal, v, roughness, metallic) * coat_base_attenuation(material, normal, -v); + + // Clearcoat reflection (common/coat.hlsl), added undenoised. + color += coat_reflection(material, normal, -v, GetReflectionCombine().GetCoat()[tc].rgb); // output[index] = float4(lighting, 1); GetReflectionCombine().GetTarget()[dispatchID.xy]+=float4(color,0); // diff --git a/workdir/shaders/postprocess/sss_blur.hlsl b/workdir/shaders/postprocess/sss_blur.hlsl new file mode 100644 index 000000000..59a6cac41 --- /dev/null +++ b/workdir/shaders/postprocess/sss_blur.hlsl @@ -0,0 +1,174 @@ +#include "../common/common.hlsl" +#include "../common/shading_model.hlsl" + +#include "../autogen/FrameInfo.h" +#include "../autogen/SSSBlurParams.h" +#include "../autogen/SSSTilesRead.h" + +// Screen-space subsurface scattering, one axis per dispatch (see sss.prism for +// where it runs and why). Only SUBSURFACE pixels are touched. +// +// The blur works on irradiance: the first (horizontal) pass divides the lit +// radiance by albedo, so the albedo's own texture detail is not blurred away, +// and the second multiplies it back. Both use the same albedo floor, so a +// pixel with nothing to blur comes back exactly as it went in. + +static const GBuffer gbuffer = GetSSSBlurParams().GetGbuffer(); + +static const int TAPS = 16; // per side, so 33 taps per axis +static const float ALBEDO_FLOOR = 0.05; +// Taps are at most 3 px apart (3 * MAX_SIGMA_PX / TAPS): sparser than that and a wide +// kernel turns into visible copies of the image instead of a blur. +static const float MAX_SIGMA_PX = 16.0; +// The scattering profile is a sum of three Gaussians (the separable-SSS +// approximation of Jimenez et al.): a narrow core that keeps detail, a body, and +// a wide tail that carries the soft red glow of skin-like media. Scales are +// relative to the sigma derived from the radius, weights sum to 1, and the mix +// keeps the variance of the single Gaussian it replaces (sum a * s^2 = 1.03). +static const float3 PROFILE_WEIGHT = float3(0.5, 0.35, 0.15); +static const float3 PROFILE_SCALE = float3(0.4, 1.0, 2.0); +static const float PROFILE_WIDEST = 2.0; +// Sigma of a single Gaussian standing in for the mean free path: ~all of its +// energy lies within the radius. +static const float SIGMA_PER_RADIUS = 0.5; + +// Profile of one colour channel at distance d (px); each Gaussian is normalised, so +// the narrow core is tall and the tail low, as they should be. +float profile(float d, float sigma) +{ + float3 s = max(PROFILE_SCALE * sigma, 0.05); + return dot(PROFILE_WEIGHT / s * exp(-0.5 * d * d / (s * s)), float3(1, 1, 1)); +} + +float3 profile3(float d, float3 sigma) +{ + return float3(profile(d, sigma.x), profile(d, sigma.y), profile(d, sigma.z)); +} + +float view_depth(float raw_z, Camera camera) +{ + return camera.GetProj()._34 * raw_z / (raw_z - camera.GetProj()._33); +} + +void blur_axis(uint2 pixel, int2 dir, bool from_radiance, bool final_pass) +{ + float2 dims; + GetSSSBlurParams().GetTarget().GetDimensions(dims.x, dims.y); + + int2 tc = int2(pixel); + if (any(tc >= int2(dims))) + return; + + float raw_z = gbuffer.GetDepth()[tc]; + if (raw_z == 0) + return; + + float4 material = gbuffer.GetMaterial()[tc]; + if (material_model(material) != SHADING_MODEL_SUBSURFACE) + return; + + Camera camera = GetFrameInfo().GetCamera(); + float z = view_depth(raw_z, camera); + float3 n = normalize(gbuffer.GetNormals()[tc].xyz * 2 - 1); + + float3 albedo = max(gbuffer.GetAlbedo()[tc].rgb, ALBEDO_FLOOR); + + // Pixels per world unit at this depth, along the blur axis. + float focal = dir.x != 0 ? 0.5 * dims.x * camera.GetProj()._11 : 0.5 * dims.y * camera.GetProj()._22; + float3 sigma_world = sss_decode_radius(material_params(material)) * SIGMA_PER_RADIUS; + float3 sigma_px = min(sigma_world * focal / max(z, 1e-3), MAX_SIGMA_PX / PROFILE_WIDEST); + // Reach of the widest Gaussian in the mix. + float max_sigma_px = PROFILE_WIDEST * max(sigma_px.x, max(sigma_px.y, sigma_px.z)); + float max_sigma_world = max(sigma_world.x, max(sigma_world.y, sigma_world.z)); + + float3 center = GetSSSBlurParams().GetSource()[tc].rgb; + if (from_radiance) + center /= albedo; + + float3 result = center; + + // Under a quarter of a pixel nothing is gathered beyond the centre tap. + if (max_sigma_px > 0.25) + { + // Taps cover +-3 sigma of the widest Gaussian of the widest channel; a channel with a narrower + // sigma just gets small weights on the outer ones. + float step = max(1.0, 3.0 * max_sigma_px / TAPS); + // Hard cut on the depth gap: past this the neighbour is another surface. + float depth_tolerance = max(2.0 * max_sigma_world, 0.02); + + float3 centre_weight = profile3(0, sigma_px); + float3 sum = center * centre_weight; + float3 weight_sum = centre_weight; + + [loop] + for (int i = -TAPS; i <= TAPS; i++) + { + if (i == 0) + continue; + + int2 p = tc + dir * (int)round(i * step); + if (any(p < 0) || any(p >= int2(dims))) + continue; + + float raw_zn = gbuffer.GetDepth()[p]; + if (raw_zn == 0) + continue; + + if (material_model(gbuffer.GetMaterial()[p]) != SHADING_MODEL_SUBSURFACE) + continue; + + if (abs(view_depth(raw_zn, camera) - z) > depth_tolerance) + continue; + + // Do not blur around creases. + if (dot(n, normalize(gbuffer.GetNormals()[p].xyz * 2 - 1)) < 0.5) + continue; + + float3 e = GetSSSBlurParams().GetSource()[p].rgb; + if (from_radiance) + e /= max(gbuffer.GetAlbedo()[p].rgb, ALBEDO_FLOOR); + + float d = i * step; + float3 w = profile3(d, sigma_px); + + sum += e * w; + weight_sum += w; + } + + result = sum / weight_sum; + } + + if (!final_pass) + { + GetSSSBlurParams().GetTarget()[tc] = float4(result, 1); + return; + } + + float3 lit = result * albedo; + + // DLSS-RR: RTXCombine left only the diffuse part in the target and parked the + // specular part separately. + if (GetSSSBlurParams().GetAdd_specular() != 0) + lit += GetSSSBlurParams().GetSpecular()[tc].rgb; + + GetSSSBlurParams().GetTarget()[tc] = float4(lit, 1); +} + +// Dispatched indirectly, one 16x16 group per tile SSS_Classify listed: +// SV_GroupID.x indexes the list. +uint2 tile_pixel(uint3 groupID, uint3 groupThreadID) +{ + return GetSSSTilesRead().GetTiles()[groupID.x] * 16 + groupThreadID.xy; +} + +[numthreads(16, 16, 1)] +void CS_H(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThreadID) +{ + blur_axis(tile_pixel(groupID, groupThreadID), int2(1, 0), true, false); +} + +[numthreads(16, 16, 1)] +void CS_V(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThreadID) +{ + blur_axis(tile_pixel(groupID, groupThreadID), int2(0, 1), false, true); +} diff --git a/workdir/shaders/postprocess/sss_classify.hlsl b/workdir/shaders/postprocess/sss_classify.hlsl new file mode 100644 index 000000000..2d95083c9 --- /dev/null +++ b/workdir/shaders/postprocess/sss_classify.hlsl @@ -0,0 +1,35 @@ +#include "../common/common.hlsl" +#include "../common/shading_model.hlsl" + +#include "../autogen/SSSClassifyParams.h" +#include "../autogen/SSSTilesAppend.h" + +// One 16x16 group per screen tile: if any pixel of the tile is SUBSURFACE the +// tile's position goes into the list the blur passes dispatch over (see +// sss.prism). A tile only needs to be listed when it contains such a pixel: +// the blur reads neighbouring tiles but only ever writes its own pixels. + +static const GBuffer gbuffer = GetSSSClassifyParams().GetGbuffer(); + +groupshared uint tile_has_subsurface; + +[numthreads(16, 16, 1)] +void CS_CLASSIFY(uint3 groupID : SV_GroupID, uint3 dispatchID : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) +{ + if (groupIndex == 0) + tile_has_subsurface = 0; + + GroupMemoryBarrierWithGroupSync(); + + float2 dims; + gbuffer.GetDepth().GetDimensions(dims.x, dims.y); + + if (all(dispatchID.xy < uint2(dims))) + if (gbuffer.GetDepth()[dispatchID.xy] != 0 && material_model(gbuffer.GetMaterial()[dispatchID.xy]) == SHADING_MODEL_SUBSURFACE) + InterlockedOr(tile_has_subsurface, 1); + + GroupMemoryBarrierWithGroupSync(); + + if (groupIndex == 0 && tile_has_subsurface != 0) + GetSSSTilesAppend().GetTiles().Append(groupID.xy); +} diff --git a/workdir/shaders/rtx/raytracing.hlsl b/workdir/shaders/rtx/raytracing.hlsl index 25b1bd6f9..8338f1a37 100644 --- a/workdir/shaders/rtx/raytracing.hlsl +++ b/workdir/shaders/rtx/raytracing.hlsl @@ -23,6 +23,8 @@ #include "../autogen/VoxelScreen.h" #include "../autogen/VoxelInfo.h" #include "../nrd/3rdparty/NRD.hlsli" +#include "../common/coat.hlsl" +#include "../common/aniso.hlsl" #include "../autogen/VoxelOutput.h" @@ -622,6 +624,61 @@ void MyRaygenShaderIndirectRTXHalfRes() TraceIndirectDiffuse(half_gbuffer.GetDepth(), half_gbuffer.GetNormals(), voxel_output.GetNoise(), voxel_output.GetBlueNoise()); } +// Clearcoat reflection: for CLEARCOAT pixels, a second ray at the coat's own +// (much lower) roughness, stored raw -- a near-mirror ray has little variance, +// so it skips the denoiser the base lobe goes through and is simply added by +// the combine passes (weighted by the coat's Fresnel there). Every other pixel +// gets 0, which the combine passes also skip on the material model. +void TraceCoatReflection(Texture2D depth_tex, Texture2D normal_tex, Texture2D material_tex, RWTexture2D tex_coat, Texture2D blueNoiseTex) +{ + uint2 itc = DispatchRaysIndex().xy; + tex_coat[itc] = 0; + + float raw_z = depth_tex[itc]; + if (raw_z == 0) + return; + + float4 material = material_tex[itc]; + if (material_model(material) != SHADING_MODEL_CLEARCOAT) + return; + + const FrameInfo frame = CreateFrameInfo(); + const Raytracing raytracing = CreateRaytracing(); + + float2 tc = float2(itc + 0.5f) / DispatchRaysDimensions().xy; + float3 pos = depth_to_wpos(raw_z, tc, frame.GetCamera().GetInvViewProj()); + float3 normal = normalize(normal_tex[itc].xyz * 2 - 1); + float3 view = -normalize(frame.GetCamera().GetPosition() - pos); + + // Same convention as the base lobe: perceptual roughness, squared. + float roughness = pow(max(MIN_ROUGHNESS, material_params(material).y), 2); + + // Different seed than the base ray's (same blue-noise texel), so the two + // rays of a pixel do not share a jitter. + float2 seed = frac(blueNoiseTex.Load(int3((itc + uint2(37, 91)) % 128, 0)) + 0.5); + float3 dir = SampleReflectionVector(view, normal, roughness, seed); + + [raypayload] + RayPayload payload; + payload.color = float4(0, 0, 0, 0); + payload.albedo = 0; + payload.hit_normal = 0; + payload.recursion = 0; + payload.dist = 0; + payload.cone.angle = 0; + payload.cone.width = 0; + payload.use_vsm_shadow = 0; + + RayDesc ray; + ray.Origin = pos; + ray.Direction = dir; + ray.TMin = 0.05; + ray.TMax = 10000.0; + ColorPass(raytracing.GetScene(), ray, RAY_FLAG_NONE, payload); + + tex_coat[itc] = float4(payload.color.rgb, payload.dist); +} + // Shared body for MyRaygenShaderReflectionRTXOnly/MyRaygenShaderReflectionRTXHalfRes // (see voxel.prism's PassNode ReflectionRTX/ReflectionRTXHalf): pure DXR, one // ray per pixel, blue-noise-jittered direction (SampleReflectionVector -- @@ -631,7 +688,7 @@ void MyRaygenShaderIndirectRTXHalfRes() // [[project-nrd-integration]]). Parameterized on depth/normals/outputs so // full-res and half-res dispatches share the actual trace logic, same shape // as TraceIndirectDiffuse above. -void TraceReflection(Texture2D depth_tex, Texture2D normal_tex, RWTexture2D tex_noise, RWTexture2D tex_dir_pdf, Texture2D blueNoiseTex) +void TraceReflection(Texture2D depth_tex, Texture2D normal_tex, RWTexture2D tex_noise, RWTexture2D tex_dir_pdf, Texture2D blueNoiseTex, Texture2D material_tex, bool use_material) { uint2 itc = DispatchRaysIndex().xy; float2 tc = float2(itc + 0.5f) / DispatchRaysDimensions().xy; @@ -662,6 +719,16 @@ void TraceReflection(Texture2D depth_tex, Texture2D normal_tex, R float2 seed = blueNoiseTex.Load(int3(itc % 128, 0)); float3 dir = SampleReflectionVector(view, normal, roughness, seed); + // Anisotropic base specular: stretch the lobe along the material's tangent. + // The half-res pass has no material target and stays isotropic. + if (use_material) + { + float4 material = material_tex[itc]; + + if (material_model(material) == SHADING_MODEL_ANISOTROPIC) + dir = SampleAnisoReflectionVector(view, normal, aniso_decode_tangent(normal, material_params(material).y), roughness, material_params(material).x, seed); + } + [raypayload] RayPayload payload; payload.color = float4(0, 0, 0, 0); @@ -759,6 +826,10 @@ void MyRaygenShaderReflectionRTXOnly() const RWTexture2D tex_noise = voxel_output.GetNoise(); const RWTexture2D tex_dir_pdf = voxel_output.GetDirAndPdf(); + // Independent of the base lobe's tile classification: a coated pixel needs + // its own ray even where the base reflection is reused from half res. + TraceCoatReflection(voxel_screen.GetGbuffer().GetDepth(), voxel_screen.GetGbuffer().GetNormals(), voxel_screen.GetGbuffer().GetMaterial(), voxel_output.GetCoat(), voxel_output.GetBlueNoise()); + // Skip the fresh ray only when BOTH tile axes say Low -- a geometric // edge makes the half-res buffer untrustworthy regardless of material, // and a glossy+metallic surface needs real detail regardless of how @@ -773,7 +844,7 @@ void MyRaygenShaderReflectionRTXOnly() return; } - TraceReflection(voxel_screen.GetGbuffer().GetDepth(), voxel_screen.GetGbuffer().GetNormals(), tex_noise, tex_dir_pdf, voxel_output.GetBlueNoise()); + TraceReflection(voxel_screen.GetGbuffer().GetDepth(), voxel_screen.GetGbuffer().GetNormals(), tex_noise, tex_dir_pdf, voxel_output.GetBlueNoise(), voxel_screen.GetGbuffer().GetMaterial(), true); } // Always-on half-res base layer for ReflectionRTXHalf (see voxel.prism's @@ -786,7 +857,7 @@ void MyRaygenShaderReflectionRTXHalfRes() const VoxelOutput voxel_output = CreateVoxelOutput(); const IndirectRTXHalfGBuffer half_gbuffer = CreateIndirectRTXHalfGBuffer(); - TraceReflection(half_gbuffer.GetDepth(), half_gbuffer.GetNormals(), voxel_output.GetNoise(), voxel_output.GetDirAndPdf(), voxel_output.GetBlueNoise()); + TraceReflection(half_gbuffer.GetDepth(), half_gbuffer.GetNormals(), voxel_output.GetNoise(), voxel_output.GetDirAndPdf(), voxel_output.GetBlueNoise(), half_gbuffer.GetNormals(), false); } diff --git a/workdir/shaders/rtx/rtx_combine.hlsl b/workdir/shaders/rtx/rtx_combine.hlsl index c131beea6..b41d26fa0 100644 --- a/workdir/shaders/rtx/rtx_combine.hlsl +++ b/workdir/shaders/rtx/rtx_combine.hlsl @@ -2,6 +2,8 @@ #include "../autogen/FrameInfo.h" #include "../autogen/RTXCombine.h" +#include "../common/lobes.hlsl" +#include "../common/shading_model.hlsl" static const Camera camera = GetFrameInfo().GetCamera(); static const GBuffer gbuffer = GetRTXCombine().GetGbuffer(); @@ -74,25 +76,51 @@ void CS(uint3 dispatchID : SV_DispatchThreadID) float shadow = GetRTXCombine().GetShadow()[tc].r; float3 direct = albedo.rgb * NdotL * shadow * (1 - metallic); + // Clearcoat (common/coat.hlsl): this pass replaces the VSM/PSSM result + // under DLSS-RR, so the coat's sun highlight has to be added here too. + float4 material = gbuffer.GetMaterial()[tc]; + // The coat reflects part of the light before it reaches the base layer. + const float base_attenuation = coat_base_attenuation(material, normal, -v); + direct *= base_attenuation; + direct += material_sun_lobes(material, normal, -v, sun_dir, shadow, lerp(0.04, albedo.rgb, metallic), max(MIN_ROUGHNESS, gbuffer.GetNormals()[tc].w), albedo.rgb, metallic); + // Reflections: identical weighting to ReflectionCombine. Raw RTX trace // output now (RGB=hit color, A=hit distance, not REBLUR-packed) -- NRD // doesn't run under DLSS-RR any more, DLSS-RR does its own // reconstruction/denoising on this exact signal (see RTXCombine's own // comment, voxel.prism). float3 reflection = GetRTXCombine().GetReflection()[tc].rgb; - float3 refl_color = get_PBR(albedo.rgb, reflection, normal, v, roughness, metallic); + float3 specular_color = albedo.rgb; + if (material_model(material) == SHADING_MODEL_THINFILM) + specular_color = iridescent_f0(saturate(dot(normal, -v)), thinfilm_thickness(material), thinfilm_ior(material), albedo.rgb); + + float3 refl_color = get_PBR(specular_color, reflection, normal, v, roughness, metallic) * base_attenuation; // Indirect GI: diffuse bounce light, weighted by the surface's own // albedo (diffuse response) rather than the specular BRDF above, and // rolled off by metallic (metals have ~no diffuse term). Same raw shape // as reflection above. float3 indirect = GetRTXCombine().GetIndirect()[tc].rgb; - float3 gi_color = albedo.rgb * indirect * (1 - metallic); + float3 gi_color = albedo.rgb * indirect * (1 - metallic) * base_attenuation + + sheen_indirect(material, normal, -v, albedo.rgb, indirect); // Self-emission: not shadowed, not weighted by any BRDF -- a glowing // surface looks the same lit or unlit. Indirect GI/reflection rays get // this for free via MyClosestHitShader's own payload.color (see its // comment) -- this is only needed here for direct visibility, since this // pass reads the GBuffer rather than recomputing the material. - GetRTXCombine().GetTarget()[tc] = float4(direct + refl_color + gi_color + glow, 1); + // Coat reflection: its own near-mirror ray (ReflectionRTX's coat output). + float3 coat_refl = coat_reflection(material, normal, -v, GetRTXCombine().GetCoat()[tc].rgb); + + // SUBSURFACE pixels: only the diffuse part goes to the target, where the + // subsurface blur (sss.prism) works on it; the specular part is parked for + // that pass to add back afterwards, so reflections are not blurred. + if (material_model(material) == SHADING_MODEL_SUBSURFACE) + { + GetRTXCombine().GetTarget()[tc] = float4(direct + gi_color, 1); + GetRTXCombine().GetSpecular_out()[tc] = float4(refl_color + coat_refl + glow, 0); + return; + } + + GetRTXCombine().GetTarget()[tc] = float4(direct + refl_color + coat_refl + gi_color + glow, 1); } diff --git a/workdir/shaders/rtx/translucency.hlsl b/workdir/shaders/rtx/translucency.hlsl index d4e38e7cd..8f45d900c 100644 --- a/workdir/shaders/rtx/translucency.hlsl +++ b/workdir/shaders/rtx/translucency.hlsl @@ -10,6 +10,7 @@ // is exactly this pixel's lit scene colour -- no ray needed for it. // Solid (thickness > 0): refracted in and out; the traced in-medium distance // drives Beer-Lambert absorption. Once bent, the ray needs a real scene query. +#include "../common/medium.hlsl" #include "../autogen/FrameInfo.h" #include "../autogen/Raytracing.h" #include "../autogen/TranslucentRTXData.h" @@ -218,6 +219,7 @@ void TranslucentRaygenShader() float ior = max(hit.ior, 1.0001); float transmission = saturate(hit.transmission); float3 albedo = saturate(hit.albedo); + float3 tint = saturate(hit.transmission_color); bool thin = hit.thickness <= 0; float roughness = saturate(hit.roughness); float alpha = roughness * roughness; @@ -247,7 +249,7 @@ void TranslucentRaygenShader() if (thin) { - throughput *= (1 - R) * transmission * albedo; + throughput *= (1 - R) * transmission * tint; if (alpha >= TRANSLUCENT_MIN_ALPHA) { @@ -287,8 +289,8 @@ void TranslucentRaygenShader() throughput *= 1 - F; if (entering) { - throughput *= transmission; - sigma = -log(max(albedo, 0.001)) / max(hit.absorption_distance, 0.0001); + throughput *= transmission * medium_entry_tint(tint, hit.absorption_distance); + sigma = medium_sigma(tint, hit.absorption_distance); } dir = normalize(refr); pos = hit_pos - N * TRANSLUCENT_RAY_EPS; diff --git a/workdir/shaders/rtx/universal_material_raytracing.hlsl b/workdir/shaders/rtx/universal_material_raytracing.hlsl index 44b65626a..f17d0c8c1 100644 --- a/workdir/shaders/rtx/universal_material_raytracing.hlsl +++ b/workdir/shaders/rtx/universal_material_raytracing.hlsl @@ -28,6 +28,8 @@ #include "shadows/vsm/vsm_impl_resolve.hlsl" #include "common/common.hlsl" +#include "common/lobes.hlsl" +#include "materialx/mx_stdlib.hlsl" //#define REFRACTION #define Sampler linearSampler #define GetMaterialInfo CreateMaterialInfo @@ -67,12 +69,22 @@ float4 sample(uint itex, SamplerState s, float2 tc, float lod) } #endif -void COMPILED_FUNC(in float3 a, in float2 b, out float4 c, out float d, out float e, out float4 f, out float4 g, out float h, out float ior, out float thickness, out float transmission, out float absorption_distance, float lod); +void COMPILED_FUNC(in float3 a, in float2 b, out float4 c, out float d, out float e, out float4 f, out float4 g, out float h, out float ior, out float thickness, out float transmission, out float absorption_distance, out float coat, out float coat_roughness, out float sheen, out float sheen_roughness, out float anisotropy, out float aniso_rotation, out float sss_weight, out float3 sss_radius, out float sss_scale, out float sheen_tint, out float3 transmission_color, out float opacity_threshold, out float thin_film_thickness, out float thin_film_ior, float lod); + +// World-space shading frame for MaterialX geometry nodes (see mx_stdlib.hlsl). +// Only valid in hit shaders: ObjectToWorld3x4() needs a hit. +void SetMaterialXGeometry(mesh_vertex_input v) +{ + float3 n = normalize(mul(ObjectToWorld3x4(), float4(v.normal, 0))); + float3 t = normalize(mul(ObjectToWorld3x4(), float4(v.tangent.xyz, 0))); + mx_set_geometry(n, t, cross(n, t) * v.tangent.w); + mx_set_time(CreateFrameInfo().GetTime().y); +} // Evaluate the material at the current hit and return just what a shadow ray // needs: surface colour (tint) and opacity. Mirrors the setup in the color hit. -void ShadowSurface(in MyAttributes attr, out float4 color, out float opacity, out float surface_transmission) +void ShadowSurface(in MyAttributes attr, out float4 color, out float opacity, out float surface_transmission, out float cutoff) { SceneData sceneData = CreateSceneData(); RaytraceInstanceInfo instance = sceneData.GetRaytraceInstanceInfo()[InstanceID()]; @@ -92,10 +104,20 @@ void ShadowSurface(in MyAttributes attr, out float4 color, out float opacity, ou t.v.pos = WorldRayOrigin() + WorldRayDirection() * RayTCurrent(); color = 1; opacity = 1; - float metallic = 1, roughness = 1, refraction = 1, thickness = 0, transmission = 1, absorption_distance = 1; + float metallic = 1, roughness = 1, refraction = 1, thickness = 0, transmission = 1, absorption_distance = 1, coat = 0, coat_roughness = 0.1, sheen = 0, sheen_roughness = 0.3, anisotropy = 0, aniso_rotation = 0, sss_weight = 0, sss_scale = 1, sheen_tint = 0; float3 sss_radius = float3(1, 1, 1), transmission_color = float3(-1, -1, -1); float opacity_threshold = 0.5, thin_film_thickness = 0, thin_film_ior = 1.5; float4 normal = 0, glow = 0; - COMPILED_FUNC(t.v.pos, t.v.tc, color, metallic, roughness, normal, glow, opacity, refraction, thickness, transmission, absorption_distance, t.lod); + SetMaterialXGeometry(t.v); + COMPILED_FUNC(t.v.pos, t.v.tc, color, metallic, roughness, normal, glow, opacity, refraction, thickness, transmission, absorption_distance, coat, coat_roughness, sheen, sheen_roughness, anisotropy, aniso_rotation, sss_weight, sss_radius, sss_scale, sheen_tint, transmission_color, opacity_threshold, thin_film_thickness, thin_film_ior, t.lod); surface_transmission = transmission; + cutoff = opacity_threshold; +#if defined(TRANSLUCENT_BLEND) + // Blended opacity: light passes in proportion to what is not covered. + surface_transmission = 1 - saturate(opacity); + transmission_color = float3(1, 1, 1); +#endif + // An unset transmission colour (negative) means the base colour tints it. + if (transmission_color.x >= 0) + color.rgb = transmission_color; } @@ -153,20 +175,20 @@ float3 vsm_shadow_lookup(float3 wpos, float3 normal, float3 light_dir) [shader("anyhit")] void MyAnyHitShader(inout RayPayload payload, in MyAttributes attr) { - float4 color; float opacity, surface_transmission; - ShadowSurface(attr, color, opacity, surface_transmission); + float4 color; float opacity, surface_transmission, cutoff; + ShadowSurface(attr, color, opacity, surface_transmission, cutoff); - if (opacity < 0.5) + if (opacity < cutoff) IgnoreHit(); } [shader("anyhit")] void ColorShadowAnyHitShader([raypayload] inout ColorShadowPayload payload, in MyAttributes attr) { - float4 color; float opacity, surface_transmission; - ShadowSurface(attr, color, opacity, surface_transmission); + float4 color; float opacity, surface_transmission, cutoff; + ShadowSurface(attr, color, opacity, surface_transmission, cutoff); - if (opacity < 0.5) + if (opacity < cutoff) IgnoreHit(); } @@ -180,13 +202,13 @@ void ColorShadowClosestHitShader([raypayload] inout ColorShadowPayload payload, payload.dist = RayTCurrent(); // report the occluder distance to the caller. #if defined(TRANSLUCENT) - float4 color; float opacity, surface_transmission; - ShadowSurface(attr, color, opacity, surface_transmission); + float4 color; float opacity, surface_transmission, cutoff; + ShadowSurface(attr, color, opacity, surface_transmission, cutoff); payload.transmittance *= saturate(color.rgb) * saturate(surface_transmission); #elif defined(TRANSPARENT) - float4 color; float opacity, surface_transmission; - ShadowSurface(attr, color, opacity, surface_transmission); + float4 color; float opacity, surface_transmission, cutoff; + ShadowSurface(attr, color, opacity, surface_transmission, cutoff); // Light through the glass is tinted by its colour; opacity darkens it (full // opacity == opaque == blocks). The caller advances past and keeps tracing. @@ -217,18 +239,29 @@ void TranslucentClosestHitShader([raypayload] inout TranslucentPayload payload, t.v.pos = WorldRayOrigin() + WorldRayDirection() * RayTCurrent(); float4 color = 1; - float metallic = 0, roughness = 0, opacity = 1, refraction = 1, thickness = 0, transmission = 1, absorption_distance = 1; + float metallic = 0, roughness = 0, opacity = 1, refraction = 1, thickness = 0, transmission = 1, absorption_distance = 1, coat = 0, coat_roughness = 0.1, sheen = 0, sheen_roughness = 0.3, anisotropy = 0, aniso_rotation = 0, sss_weight = 0, sss_scale = 1, sheen_tint = 0; float3 sss_radius = float3(1, 1, 1), transmission_color = float3(-1, -1, -1); float opacity_threshold = 0.5, thin_film_thickness = 0, thin_film_ior = 1.5; float4 normal = 0, glow = 0; - COMPILED_FUNC(t.v.pos, t.v.tc, color, metallic, roughness, normal, glow, opacity, refraction, thickness, transmission, absorption_distance, t.lod); + SetMaterialXGeometry(t.v); + COMPILED_FUNC(t.v.pos, t.v.tc, color, metallic, roughness, normal, glow, opacity, refraction, thickness, transmission, absorption_distance, coat, coat_roughness, sheen, sheen_roughness, anisotropy, aniso_rotation, sss_weight, sss_radius, sss_scale, sheen_tint, transmission_color, opacity_threshold, thin_film_thickness, thin_film_ior, t.lod); // Inverse-transpose of the node's full local->world matrix (row-vector // multiply by the inverse), so non-uniform scale doesn't skew it either. node_data node = sceneData.GetNodes()[instance.GetNode_offset()]; float3 world_normal = normalize(mul(t.v.normal, (float3x3)node.GetNode_inverse_matrix())); +#if defined(TRANSLUCENT_BLEND) + // Blended opacity is a thin, non-refracting layer: what the surface does not + // cover shows what is behind it. + transmission = 1 - saturate(opacity); + transmission_color = float3(1, 1, 1); + refraction = 1; + thickness = 0; +#endif + payload.dist = RayTCurrent(); payload.normal = world_normal; payload.albedo = color.rgb; + payload.transmission_color = transmission_color.x >= 0 ? transmission_color : color.rgb; payload.roughness = roughness; payload.ior = refraction; payload.thickness = thickness; @@ -281,14 +314,15 @@ void MyClosestHitShader(inout RayPayload payload, in MyAttributes attr) float4 normal = 0; float4 glow = 0; float opacity = 1; - float refraction = 1, thickness = 0, transmission = 1, absorption_distance = 1; + float refraction = 1, thickness = 0, transmission = 1, absorption_distance = 1, coat = 0, coat_roughness = 0.1, sheen = 0, sheen_roughness = 0.3, anisotropy = 0, aniso_rotation = 0, sss_weight = 0, sss_scale = 1, sheen_tint = 0; float3 sss_radius = float3(1, 1, 1), transmission_color = float3(-1, -1, -1); float opacity_threshold = 0.5, thin_film_thickness = 0, thin_film_ior = 1.5; t.lod += log2(abs(payload.cone.width + payload.cone.angle * RayTCurrent())); t.lod -= log2(abs(dot(normalize(WorldRayDirection()), t.v.normal))); - COMPILED_FUNC(t.v.pos, t.v.tc, color, metallic, roughness, normal, glow, opacity, refraction, thickness, transmission, absorption_distance, t.lod); + SetMaterialXGeometry(t.v); + COMPILED_FUNC(t.v.pos, t.v.tc, color, metallic, roughness, normal, glow, opacity, refraction, thickness, transmission, absorption_distance, coat, coat_roughness, sheen, sheen_roughness, anisotropy, aniso_rotation, sss_weight, sss_radius, sss_scale, sheen_tint, transmission_color, opacity_threshold, thin_film_thickness, thin_film_ior, t.lod); // Transparent-aware sun visibility (float3 transmittance instead of a bool). // Iterate the shadow ray through transparent occluders (advancing past each @@ -342,7 +376,14 @@ void MyClosestHitShader(inout RayPayload payload, in MyAttributes attr) // glowing surfaces at all: both just forward this payload.color through // denoise + combine (rtx_combine.hlsl / reflection_combine.hlsl), which // weight it as arriving radiance without caring where it came from. - payload.color = float4(color.rgb * NdotL * sun_vis * (1 - metallic) + glow.rgb, 1.0); + // A metal has no diffuse term, so (1 - metallic) alone left it black wherever a + // reflection or GI ray saw it. The sun highlight (GGX, F0 = base colour) is what + // a metal reflects of the sun; the rest of what it shows comes from its own + // reflection of the world, which a single bounce does not trace. + float3 hit_sun_specular = base_sun_specular(float4(0, 0, 0, 0), normalize(t.v.normal), -normalize(WorldRayDirection()), + normalize(frame.GetSunDir().xyz), 1.0, lerp(0.04, color.rgb, metallic), max(MIN_ROUGHNESS, roughness), metallic); + + payload.color = float4(color.rgb * NdotL * sun_vis * (1 - metallic) + hit_sun_specular * sun_vis + glow.rgb, 1.0); payload.dist = RayTCurrent(); // DDGIProbeTrace's own feedback-term consumer -- see this payload // field's own comment (raytracing.prism) for why these two exist. diff --git a/workdir/shaders/shadows/pssm/pssm.hlsl b/workdir/shaders/shadows/pssm/pssm.hlsl index 3d1a8a89d..253d32205 100644 --- a/workdir/shaders/shadows/pssm/pssm.hlsl +++ b/workdir/shaders/shadows/pssm/pssm.hlsl @@ -11,6 +11,7 @@ static const GBuffer gbuffer = GetPSSMLighting().GetGbuffer(); #include "../../common/pbr.hlsl" +#include "../../common/lobes.hlsl" #define SCALE 1 #include "../../common/rect.hlsl" @@ -266,7 +267,11 @@ EnvBRDF = 0;*/ //float3 refl = CookTorrance_GGX_sample(light_dir, info,Fk); //return float4(info.pos, 1); -return float4(shadow * (saturate(NL) * info.albedo * (1 - info.metallic)), 1); +float4 material = gbuffer.GetMaterial().SampleLevel(pointClampSampler, tc, 0); +float3 result = shadow * (saturate(NL) * info.albedo * (1 - info.metallic)) * coat_base_attenuation(material, info.normal, info.view); +result += material_sun_lobes(material, info.normal, info.view, light_dir, shadow, + lerp(0.04, info.albedo, info.metallic), max(MIN_ROUGHNESS, gbuffer.GetNormals().SampleLevel(pointClampSampler, tc, 0).w), info.albedo, info.metallic); +return float4(result, 1); //return float4(PBR(direct, reflection, info.albedo, info.normal, info.view, 0.2, info.roughness, packed_0.w), 1); } diff --git a/workdir/shaders/shadows/vsm/vsm.hlsl b/workdir/shaders/shadows/vsm/vsm.hlsl index 9621b0093..f0c812b3e 100644 --- a/workdir/shaders/shadows/vsm/vsm.hlsl +++ b/workdir/shaders/shadows/vsm/vsm.hlsl @@ -11,6 +11,7 @@ static const GBuffer gbuffer = GetVSMLighting().GetGbuffer(); #include "../../common/pbr.hlsl" +#include "../../common/lobes.hlsl" // Only the resolve half of VSM_impl.hlsl's old contents (get_shadow_vsm, // vsm_pcf_shadow, debug helpers) -- see VSM_impl_search.hlsl's own top // comment for why this is split from VSM_BlockerSearch.hlsl's own include. @@ -100,7 +101,11 @@ float4 combine_result(float2 tc, uint2 pixel) float2 EnvBRDF = GetBRDF(info.roughness, 0, 0.5 + 0.5 * NL) * GetBRDF(info.roughness, 0, 0.5 + 0.5 * NV); - return float4(shadow * (NL * info.albedo * (1 - info.metallic)), 1); + float4 material = gbuffer.GetMaterial().SampleLevel(pointClampSampler, tc, 0); + float3 result = shadow * (NL * info.albedo * (1 - info.metallic)) * coat_base_attenuation(material, info.normal, info.view); + result += material_sun_lobes(material, info.normal, info.view, light_dir, shadow, + lerp(0.04, info.albedo, info.metallic), max(MIN_ROUGHNESS, gbuffer.GetNormals().SampleLevel(pointClampSampler, tc, 0).w), info.albedo, info.metallic); + return float4(result, 1); } // VSM_Unshadowed: the same direct lighting as combine_result, without a shadow @@ -112,8 +117,22 @@ float4 unshadowed_result(float2 tc) float4 packed_0 = gbuffer.GetAlbedo().SampleLevel(pointClampSampler, tc, 0); float3 normal = normalize(gbuffer.GetNormals().SampleLevel(pointClampSampler, tc, 0).xyz * 2 - 1); - float NL = saturate(dot(normal, normalize(GetFrameInfo().GetSunDir().xyz))); - return float4(NL * packed_0.rgb * (1 - packed_0.w), 1); + float3 light_dir = normalize(GetFrameInfo().GetSunDir().xyz); + float NL = saturate(dot(normal, light_dir)); + float3 result = NL * packed_0.rgb * (1 - packed_0.w); + + float4 material = gbuffer.GetMaterial().SampleLevel(pointClampSampler, tc, 0); + if (material_model(material) != SHADING_MODEL_STANDARD || packed_0.w > 0.001) + { + Camera camera = GetFrameInfo().GetCamera(); + float3 wpos = depth_to_wpos(raw_z, tc, camera.GetInvViewProj()); + float3 view = normalize(camera.GetPosition() - wpos); + result *= coat_base_attenuation(material, normal, view); + result += material_sun_lobes(material, normal, view, light_dir, 1.0, + lerp(0.04, packed_0.rgb, packed_0.w), max(MIN_ROUGHNESS, gbuffer.GetNormals().SampleLevel(pointClampSampler, tc, 0).w), packed_0.rgb, packed_0.w); + } + + return float4(result, 1); } [numthreads(16, 16, 1)] diff --git a/workdir/shaders/shadows/vsm/vsm_shadow_resolve.hlsl b/workdir/shaders/shadows/vsm/vsm_shadow_resolve.hlsl index 8239e3ba8..be65a00e7 100644 --- a/workdir/shaders/shadows/vsm/vsm_shadow_resolve.hlsl +++ b/workdir/shaders/shadows/vsm/vsm_shadow_resolve.hlsl @@ -21,6 +21,8 @@ static const GBuffer gbuffer = GetVSMLighting().GetGbuffer(); +#include "../../common/lobes.hlsl" + // Real value the shadow uses (vsm_pcf_shadow below); moved up here (from just // above vsm_pcf_shadow, where it used to live) so CS_FULL_LIT/CS_FULL_SHADOW // can also use it for their own diagnostic-only shadow_noise packing -- @@ -64,14 +66,30 @@ uint2 resolve_pixel(uint3 groupID, uint3 groupThreadID) // stays legible over dark/black albedo. Reads GetVSMConstants() directly // rather than threading a param through every one of this function's // callers -- same reasoning as reading GetFrameInfo() directly above. -float4 vsm_resolve_combine(float3 albedo, float metallic, float3 normal, float shadow) +float4 vsm_resolve_combine(float3 albedo, float metallic, float3 normal, float shadow, float2 tc) { if (GetVSMConstants().GetDebug_view() == VSMDebugView::ShadowOnly) return float4(shadow, shadow, shadow, 1); float3 light_dir = normalize(GetFrameInfo().GetSunDir().xyz); float NL = saturate(dot(normal, light_dir)); - return float4(shadow * (NL * albedo * (1 - metallic)), 1); + float3 result = shadow * (NL * albedo * (1 - metallic)); + + // Clearcoat sun highlight (common/coat.hlsl). STANDARD pixels stop at the + // material read; only coated ones reconstruct the view vector. + float4 material = gbuffer.GetMaterial().SampleLevel(pointClampSampler, tc, 0); + if (material_model(material) != SHADING_MODEL_STANDARD || metallic > 0.001) + { + Camera camera = GetFrameInfo().GetCamera(); + float raw_z = gbuffer.GetDepth().SampleLevel(pointClampSampler, tc, 0); + float3 wpos = depth_to_wpos(raw_z, tc, camera.GetInvViewProj()); + float3 view = normalize(camera.GetPosition() - wpos); + result *= coat_base_attenuation(material, normal, view); + result += material_sun_lobes(material, normal, view, light_dir, shadow, + lerp(0.04, albedo, metallic), max(MIN_ROUGHNESS, gbuffer.GetNormals().SampleLevel(pointClampSampler, tc, 0).w), albedo, metallic); + } + + return float4(result, 1); } // full-lit: stage 1 already proved every pixel in this tile is confidently @@ -105,7 +123,7 @@ void CS_FULL_LIT(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThrea float4 packed_0 = gbuffer.GetAlbedo().SampleLevel(pointClampSampler, tc, 0); float3 normal = normalize(gbuffer.GetNormals().SampleLevel(pointClampSampler, tc, 0).xyz * 2 - 1); - GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(packed_0.rgb, packed_0.w, normal, 1.0); + GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(packed_0.rgb, packed_0.w, normal, 1.0, tc); // Diagnostic-only: confidently lit == no occluder. GetVSMLighting().GetShadow_noise()[pixel] = NRD_FP16_MAX; } @@ -284,7 +302,7 @@ void CS_SHADOW_BLUR(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupTh int probe_level = get_vsm_level(c, probe_pos_ls); if (probe_level < 0) { - GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, 1.0); + GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, 1.0, tc); return; } float probe_texel_world_size = c.GetLevel_info(probe_level).z / c.GetPage_size(); @@ -295,7 +313,7 @@ void CS_SHADOW_BLUR(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupTh int level = get_vsm_level(c, pos_ls); if (level < 0) { - GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, 1.0); + GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, 1.0, tc); lighting.GetShadow_noise()[pixel] = NRD_FP16_MAX; return; } @@ -304,7 +322,7 @@ void CS_SHADOW_BLUR(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupTh uint slot = get_vsm_slot(c, lighting.GetPage_table(), pos_ls, level, resolved_level); if (slot == VSM_INVALID_SLOT) { - GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, 1.0); + GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, 1.0, tc); lighting.GetShadow_noise()[pixel] = NRD_FP16_MAX; return; } @@ -316,7 +334,7 @@ void CS_SHADOW_BLUR(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupTh if (pos_l.z < 0 || pos_l.z > 1 || any(light_tc < 0) || any(light_tc > 1)) { - GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, 1.0); + GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, 1.0, tc); lighting.GetShadow_noise()[pixel] = NRD_FP16_MAX; return; } @@ -552,7 +570,7 @@ void CS_SHADOW_BLUR(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupTh if (c.GetUse_contact_shadow()) shadow = min(shadow, lighting.GetContact_shadow()[pixel]); - GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, shadow); + GetVSMLighting().GetResult()[pixel] = vsm_resolve_combine(albedo, metallic, normal, shadow, tc); // Diagnostic-only (see this file's own top comment) -- packed and // written regardless of the contact-shadow blend above, which only diff --git a/workdir/test_references/fg_vsm_unshadowed.png b/workdir/test_references/fg_vsm_unshadowed.png index d4383ba62..0e07beba9 100644 Binary files a/workdir/test_references/fg_vsm_unshadowed.png and b/workdir/test_references/fg_vsm_unshadowed.png differ diff --git a/workdir/test_references/rtx_material_tester.png b/workdir/test_references/rtx_material_tester.png index 672b469e0..6aae572fa 100644 Binary files a/workdir/test_references/rtx_material_tester.png and b/workdir/test_references/rtx_material_tester.png differ diff --git a/workdir/test_results/fg_vsm_full_chain_actual.png b/workdir/test_results/fg_vsm_full_chain_actual.png deleted file mode 100644 index f461d8581..000000000 Binary files a/workdir/test_results/fg_vsm_full_chain_actual.png and /dev/null differ diff --git a/workdir/test_results/fg_vsm_full_chain_diff.png b/workdir/test_results/fg_vsm_full_chain_diff.png deleted file mode 100644 index d4209e391..000000000 Binary files a/workdir/test_results/fg_vsm_full_chain_diff.png and /dev/null differ diff --git a/workdir/test_results/gui_full_screen_actual.png b/workdir/test_results/gui_full_screen_actual.png deleted file mode 100644 index 88418e094..000000000 Binary files a/workdir/test_results/gui_full_screen_actual.png and /dev/null differ diff --git a/workdir/test_results/gui_full_screen_diff.png b/workdir/test_results/gui_full_screen_diff.png deleted file mode 100644 index de28967c4..000000000 Binary files a/workdir/test_results/gui_full_screen_diff.png and /dev/null differ diff --git a/workdir/test_results/mesh_direct_normal_actual.png b/workdir/test_results/mesh_direct_normal_actual.png deleted file mode 100644 index 4c04d7be5..000000000 Binary files a/workdir/test_results/mesh_direct_normal_actual.png and /dev/null differ diff --git a/workdir/test_results/mesh_direct_normal_diff.png b/workdir/test_results/mesh_direct_normal_diff.png deleted file mode 100644 index aa668533d..000000000 Binary files a/workdir/test_results/mesh_direct_normal_diff.png and /dev/null differ diff --git a/workdir/test_results/meshlets_lod_levels_actual.png b/workdir/test_results/meshlets_lod_levels_actual.png deleted file mode 100644 index e3ec574a1..000000000 Binary files a/workdir/test_results/meshlets_lod_levels_actual.png and /dev/null differ diff --git a/workdir/test_results/meshlets_lod_levels_diff.png b/workdir/test_results/meshlets_lod_levels_diff.png deleted file mode 100644 index 4bac6fed0..000000000 Binary files a/workdir/test_results/meshlets_lod_levels_diff.png and /dev/null differ diff --git a/workdir/test_results/meshlets_per_meshlet_colors_actual.png b/workdir/test_results/meshlets_per_meshlet_colors_actual.png deleted file mode 100644 index 356382a62..000000000 Binary files a/workdir/test_results/meshlets_per_meshlet_colors_actual.png and /dev/null differ diff --git a/workdir/test_results/meshlets_per_meshlet_colors_diff.png b/workdir/test_results/meshlets_per_meshlet_colors_diff.png deleted file mode 100644 index 3f49531e6..000000000 Binary files a/workdir/test_results/meshlets_per_meshlet_colors_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_default_albedo_actual.png b/workdir/test_results/parity_default_albedo_actual.png deleted file mode 100644 index 82b3a50b1..000000000 Binary files a/workdir/test_results/parity_default_albedo_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_default_albedo_diff.png b/workdir/test_results/parity_default_albedo_diff.png deleted file mode 100644 index 888995126..000000000 Binary files a/workdir/test_results/parity_default_albedo_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_default_normals_actual.png b/workdir/test_results/parity_default_normals_actual.png deleted file mode 100644 index fc2f270f5..000000000 Binary files a/workdir/test_results/parity_default_normals_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_default_normals_diff.png b/workdir/test_results/parity_default_normals_diff.png deleted file mode 100644 index 3046b24b7..000000000 Binary files a/workdir/test_results/parity_default_normals_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_default_result_actual.png b/workdir/test_results/parity_default_result_actual.png deleted file mode 100644 index 1a4247916..000000000 Binary files a/workdir/test_results/parity_default_result_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_default_result_diff.png b/workdir/test_results/parity_default_result_diff.png deleted file mode 100644 index 294f1afbd..000000000 Binary files a/workdir/test_results/parity_default_result_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_indirect_denoised_actual.png b/workdir/test_results/parity_default_rtx_indirect_denoised_actual.png deleted file mode 100644 index c95d73461..000000000 Binary files a/workdir/test_results/parity_default_rtx_indirect_denoised_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_indirect_denoised_diff.png b/workdir/test_results/parity_default_rtx_indirect_denoised_diff.png deleted file mode 100644 index 10e17b71c..000000000 Binary files a/workdir/test_results/parity_default_rtx_indirect_denoised_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_indirect_noise_actual.png b/workdir/test_results/parity_default_rtx_indirect_noise_actual.png deleted file mode 100644 index c63499d7a..000000000 Binary files a/workdir/test_results/parity_default_rtx_indirect_noise_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_indirect_noise_diff.png b/workdir/test_results/parity_default_rtx_indirect_noise_diff.png deleted file mode 100644 index 9d6fd553c..000000000 Binary files a/workdir/test_results/parity_default_rtx_indirect_noise_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_reflection_denoised_actual.png b/workdir/test_results/parity_default_rtx_reflection_denoised_actual.png deleted file mode 100644 index ac08bdcec..000000000 Binary files a/workdir/test_results/parity_default_rtx_reflection_denoised_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_reflection_denoised_diff.png b/workdir/test_results/parity_default_rtx_reflection_denoised_diff.png deleted file mode 100644 index 19ffec065..000000000 Binary files a/workdir/test_results/parity_default_rtx_reflection_denoised_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_reflection_noise_actual.png b/workdir/test_results/parity_default_rtx_reflection_noise_actual.png deleted file mode 100644 index 2c6d28cb8..000000000 Binary files a/workdir/test_results/parity_default_rtx_reflection_noise_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_reflection_noise_diff.png b/workdir/test_results/parity_default_rtx_reflection_noise_diff.png deleted file mode 100644 index 51b31c3c8..000000000 Binary files a/workdir/test_results/parity_default_rtx_reflection_noise_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_reflection_noise_half_actual.png b/workdir/test_results/parity_default_rtx_reflection_noise_half_actual.png deleted file mode 100644 index 6565809be..000000000 Binary files a/workdir/test_results/parity_default_rtx_reflection_noise_half_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_default_rtx_reflection_noise_half_diff.png b/workdir/test_results/parity_default_rtx_reflection_noise_half_diff.png deleted file mode 100644 index d6c871ff0..000000000 Binary files a/workdir/test_results/parity_default_rtx_reflection_noise_half_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_no_rt_lighting_albedo_actual.png b/workdir/test_results/parity_no_rt_lighting_albedo_actual.png deleted file mode 100644 index 82b3a50b1..000000000 Binary files a/workdir/test_results/parity_no_rt_lighting_albedo_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_no_rt_lighting_albedo_diff.png b/workdir/test_results/parity_no_rt_lighting_albedo_diff.png deleted file mode 100644 index 888995126..000000000 Binary files a/workdir/test_results/parity_no_rt_lighting_albedo_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_no_rt_lighting_normals_actual.png b/workdir/test_results/parity_no_rt_lighting_normals_actual.png deleted file mode 100644 index fc2f270f5..000000000 Binary files a/workdir/test_results/parity_no_rt_lighting_normals_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_no_rt_lighting_normals_diff.png b/workdir/test_results/parity_no_rt_lighting_normals_diff.png deleted file mode 100644 index 3046b24b7..000000000 Binary files a/workdir/test_results/parity_no_rt_lighting_normals_diff.png and /dev/null differ diff --git a/workdir/test_results/parity_no_rt_lighting_result_actual.png b/workdir/test_results/parity_no_rt_lighting_result_actual.png deleted file mode 100644 index 274666c93..000000000 Binary files a/workdir/test_results/parity_no_rt_lighting_result_actual.png and /dev/null differ diff --git a/workdir/test_results/parity_no_rt_lighting_result_diff.png b/workdir/test_results/parity_no_rt_lighting_result_diff.png deleted file mode 100644 index a052564df..000000000 Binary files a/workdir/test_results/parity_no_rt_lighting_result_diff.png and /dev/null differ diff --git a/workdir/test_results/rtx_material_tester_actual.png b/workdir/test_results/rtx_material_tester_actual.png deleted file mode 100644 index da17e8656..000000000 Binary files a/workdir/test_results/rtx_material_tester_actual.png and /dev/null differ diff --git a/workdir/test_results/rtx_material_tester_actual_small.temp.png b/workdir/test_results/rtx_material_tester_actual_small.temp.png deleted file mode 100644 index bd80da570..000000000 Binary files a/workdir/test_results/rtx_material_tester_actual_small.temp.png and /dev/null differ diff --git a/workdir/test_results/rtx_material_tester_diff.png b/workdir/test_results/rtx_material_tester_diff.png deleted file mode 100644 index f76e9702f..000000000 Binary files a/workdir/test_results/rtx_material_tester_diff.png and /dev/null differ diff --git a/workdir/test_results/sampler_probe_actual.png b/workdir/test_results/sampler_probe_actual.png deleted file mode 100644 index 1b0c8132f..000000000 Binary files a/workdir/test_results/sampler_probe_actual.png and /dev/null differ diff --git a/workdir/test_results/sampler_probe_diff.png b/workdir/test_results/sampler_probe_diff.png deleted file mode 100644 index 250a3dc8b..000000000 Binary files a/workdir/test_results/sampler_probe_diff.png and /dev/null differ