diff --git a/.clang-format-ignore b/.clang-format-ignore index 88b92226c2..b04ef57bab 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -2,10 +2,8 @@ Source/Falcor/Rendering Source/Falcor/Scene Source/Mogwai Source/plugins/importers/USDImporter -Source/RenderPasses/PathTracer # Explicitly whitelisted files -!Source/Falcor/Rendering/Materials/BSDFs -!Source/RenderPasses/DLSSPass/ -!Source/Falcor/Utils/fast_vector.h +!Source/Falcor/Rendering/Materials +!Source/Falcor/Scene/Material diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fcc7144f78..020549aadf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: jobs: windows-vs2022: name: Windows VS2022 - runs-on: windows-latest + runs-on: windows-2022 strategy: matrix: include: @@ -18,7 +18,7 @@ jobs: - config: Debug steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v7 with: submodules: recursive - name: Setup MSVC @@ -34,7 +34,7 @@ jobs: shell: cmd windows-ninja-msvc: name: Windows Ninja/MSVC - runs-on: windows-latest + runs-on: windows-2022 env: CMAKE_EXE: tools\.packman\cmake\bin\cmake.exe CMAKE_BUILD_PRESET: windows-ninja-msvc @@ -45,7 +45,7 @@ jobs: - config: Debug steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v7 with: submodules: recursive - name: Setup MSVC @@ -74,7 +74,7 @@ jobs: - config: Debug steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v7 with: submodules: recursive - name: Setup system dependencies diff --git a/.gitignore b/.gitignore index eca595be00..faf50d40d5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # Test output folder. /tests/data/ +/test-results/ # Media folders. /media @@ -31,3 +32,7 @@ *.tlog *.bak *.swp + +token.txt + +customjob.yml diff --git a/.vscode-default/launch.json b/.vscode-default/launch.json index 4269e3b4e0..11d97c7a18 100644 --- a/.vscode-default/launch.json +++ b/.vscode-default/launch.json @@ -63,25 +63,5 @@ ], "visualizerFile": "${workspaceFolder}/Source/Falcor/Falcor.natvis" }, - { - // Launch configuration for Python example. - "name": "Python Example", - "type": "cppvsdbg", - "request": "launch", - "windows": { - "program": "${config:python.defaultInterpreterPath}" - }, - "args": [ "${workspaceFolder}/scripts/internal/python/simple.py" ], - "stopAtEntry": false, - "cwd": "${command:cmake.launchTargetDirectory}", - "environment": [ - { - "name": "FALCOR_DEVMODE", - "value": "1" - } - ], - "internalConsoleOptions": "openOnSessionStart", - "visualizerFile": "${workspaceFolder}/Source/Falcor/Falcor.natvis" - } ] } diff --git a/CMakeLists.txt b/CMakeLists.txt index 365785a2b1..571cec5657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ set_property(CACHE FALCOR_ENABLE_ASSERTS PROPERTY STRINGS AUTO ON OFF) # Enable/disable the profiler. set(FALCOR_ENABLE_PROFILER ON CACHE BOOL "Enable profiler") -# Enable/disable using system Python distribution. This requires Python 3.7 to be available. +# Enable/disable using system Python distribution. set(FALCOR_USE_SYSTEM_PYTHON OFF CACHE BOOL "Use system Python distribution") # Enable/disable USD. @@ -123,7 +123,6 @@ endif() # CUDA # ----------------------------------------------------------------------------- - # Set default CUDA architecture to 75 (Turing). # This can be overwritten for individual targets using the CUDA_ARCHITECTURE property. if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) @@ -177,11 +176,11 @@ if(NOT SETUP_CONFIGURATION_TYPES) if(CMAKE_CONFIGURATION_TYPES) # multi config generator - set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) + set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo" CACHE STRING "" FORCE) else() # single config generator set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build") - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release") + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo") endif() endif() @@ -266,7 +265,7 @@ function(target_copy_shaders target output_dir) ) endif() endforeach() -endfunction() +endfunction() # ----------------------------------------------------------------------------- # Data file handling diff --git a/CMakePresets.json b/CMakePresets.json index 8d52e7b138..4558389228 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -172,6 +172,12 @@ "configurePreset": "windows-ninja-msvc", "configuration": "Release" }, + { + "name": "windows-ninja-msvc-relwithdebinfo", + "displayName": "RelWithDebInfo", + "configurePreset": "windows-ninja-msvc", + "configuration": "RelWithDebInfo" + }, { "name": "windows-ninja-msvc-debug", "displayName": "Debug", @@ -184,6 +190,12 @@ "configurePreset": "windows-ninja-msvc-ci", "configuration": "Release" }, + { + "name": "windows-ninja-msvc-ci-relwithdebinfo", + "displayName": "RelWithDebInfo", + "configurePreset": "windows-ninja-msvc-ci", + "configuration": "RelWithDebInfo" + }, { "name": "windows-ninja-msvc-ci-debug", "displayName": "Debug", @@ -196,6 +208,12 @@ "configurePreset": "linux-clang", "configuration": "Release" }, + { + "name": "linux-clang-relwithdebinfo", + "displayName": "RelWithDebInfo", + "configurePreset": "linux-clang", + "configuration": "RelWithDebInfo" + }, { "name": "linux-clang-debug", "displayName": "Debug", @@ -208,6 +226,12 @@ "configurePreset": "linux-clang-ci", "configuration": "Release" }, + { + "name": "linux-clang-ci-relwithdebinfo", + "displayName": "RelWithDebInfo", + "configurePreset": "linux-clang-ci", + "configuration": "RelWithDebInfo" + }, { "name": "linux-clang-ci-debug", "displayName": "Debug", @@ -220,6 +244,12 @@ "configurePreset": "linux-gcc", "configuration": "Release" }, + { + "name": "linux-gcc-relwithdebinfo", + "displayName": "RelWithDebInfo", + "configurePreset": "linux-gcc", + "configuration": "RelWithDebInfo" + }, { "name": "linux-gcc-debug", "displayName": "Debug", @@ -232,6 +262,12 @@ "configurePreset": "linux-gcc-ci", "configuration": "Release" }, + { + "name": "linux-gcc-ci-relwithdebinfo", + "displayName": "RelWithDebInfo", + "configurePreset": "linux-gcc-ci", + "configuration": "RelWithDebInfo" + }, { "name": "linux-gcc-ci-debug", "displayName": "Debug", diff --git a/README.md b/README.md index 67f88faf0a..979d77ecd0 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@ Features include: * Raytracing support * Python scripting support * Render graph system to build modular renderers -* Common rendering techniques such post-processing effects +* Common rendering techniques such as post-processing effects * Unbiased path tracer +* Advanced variance reduction techniques like ReSTIR PT Enhanced * Integration of various RTX SDKs such as DLSS, RTXDI and NRD ## Prerequisites @@ -18,19 +19,19 @@ Features include: - Visual Studio 2022 - [Windows 10 SDK (10.0.19041.0) for Windows 10, version 2004](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/) - A GPU which supports DirectX Raytracing, such as the NVIDIA Titan V or GeForce RTX -- NVIDIA driver 466.11 or newer +- NVIDIA driver 535 or newer Optional: - Windows 10 Graphics Tools. To run DirectX 12 applications with the debug layer enabled, you must install this. There are two ways to install it: - Click the Windows button and type `Optional Features`, in the window that opens click `Add a feature` and select `Graphics Tools`. - Download an offline package from [here](https://docs.microsoft.com/en-us/windows-hardware/test/hlk/windows-hardware-lab-kit#supplemental-content-for-graphics-media-and-mean-time-between-failures-mtbf-tests). Choose a ZIP file that matches the OS version you are using (not the SDK version used for building Falcor). The ZIP includes a document which explains how to install the graphics tools. -- NVAPI, CUDA, OptiX (see below) +- CUDA, OptiX (see below) ## Building Falcor -Falcor uses the [CMake](https://cmake.org) build system. Additional information on how to use Falcor with CMake is available in the [CMake](docs/development/cmake.md) development documetation page. +Falcor uses the [CMake](https://cmake.org) build system. Additional information on how to use Falcor with CMake is available in the [CMake](docs/development/cmake.md) development documentation page. ### Visual Studio -If you are working with Visual Studio 2022, you can setup a native Visual Studio solution by running `setup_vs2022.bat` after cloning this repository. The solution files are written to `build/windows-vs2022` and the binary output is located in `build/windows-vs2022/bin`. +If you are working with Visual Studio 2022, you can set up a native Visual Studio solution by running `setup_vs2022.bat` after cloning this repository. The solution files are written to `build/windows-vs2022` and the binary output is located in `build/windows-vs2022/bin`. ### Visual Studio Code If you are working with Visual Studio Code, run `setup.bat` after cloning this repository. This will setup a VS Code workspace in the `.vscode` folder with sensible defaults (only if `.vscode` does not exist yet). When opening the project folder in VS Code, it will prompt to install recommended extensions. We recommend you do, but at least make sure that _CMake Tools_ is installed. To build Falcor, you can select the configure preset by executing the _CMake: Select Configure Preset_ action (Ctrl+Shift+P). Choose the _Windows Ninja/MSVC_ preset. Then simply hit _Build_ (or press F7) to build the project. The binary output is located in `build/windows-ninja-msvc/bin`. @@ -47,7 +48,7 @@ sudo apt install xorg-dev libgtk-3-dev You can use the same instructions for building Falcor as described in the _Visual Studio Code_ section above, simply choose the _Linux/GCC_ preset. ### Configure Presets -Falcor uses _CMake Presets_ store in `CMakePresets.json` to provide a set of commonly used build configurations. You can get the full list of available configure presets running `cmake --list-presets`: +Falcor uses _CMake Presets_ stored in `CMakePresets.json` to provide a set of commonly used build configurations. You can get the full list of available configure presets running `cmake --list-presets`: ``` $ cmake --list-presets @@ -69,10 +70,6 @@ For more information on how to use Falcor as a Python module see [Falcor In Pyth ## Microsoft DirectX 12 Agility SDK Falcor uses the [Microsoft DirectX 12 Agility SDK](https://devblogs.microsoft.com/directx/directx12agility/) to get access to the latest DirectX 12 features. Applications can enable the Agility SDK by putting `FALCOR_EXPORT_D3D12_AGILITY_SDK` in the main `.cpp` file. `Mogwai`, `FalcorTest` and `RenderGraphEditor` have the Agility SDK enabled by default. -## NVAPI -To enable NVAPI support, head over to https://developer.nvidia.com/nvapi and download the latest version of NVAPI (this build is tested against version R535). -Extract the content of the zip file into `external/packman/` and rename `R535-developer` to `nvapi`. - ## NSight Aftermath To enable NSight Aftermath support, head over to https://developer.nvidia.com/nsight-aftermath and download the latest version of Aftermath (this build is tested against version 2023.1). Extract the content of the zip file into `external/packman/aftermath`. @@ -83,7 +80,7 @@ To enable CUDA support, download and install [CUDA 11.6.2](https://developer.nvi See the `CudaInterop` sample application located in `Source/Samples/CudaInterop` for an example of how to use CUDA. ## OptiX -If you want to use Falcor's OptiX functionality (specifically the `OptixDenoiser` render pass) download the [OptiX SDK](https://developer.nvidia.com/designworks/optix/download) (Falcor is currently tested against OptiX version 7.3) After running the installer, link or copy the OptiX SDK folder into `external/packman/optix` (i.e., file `external/packman/optix/include/optix.h` should exist). +If you want to use Falcor's OptiX functionality (specifically the `OptixDenoiser` render pass) download the [OptiX SDK](https://developer.nvidia.com/designworks/optix/download) (Falcor is currently tested against OptiX version 8.0) After running the installer, link or copy the OptiX SDK folder into `external/packman/optix` (i.e., file `external/packman/optix/include/optix.h` should exist). Note: You also need CUDA installed to compile the `OptixDenoiser` render pass, see above for details. @@ -107,7 +104,7 @@ Note that these SDKs are not under the same license as Falcor, see [LICENSE.md]( ## Citation If you use Falcor in a research project leading to a publication, please cite the project. -The BibTex entry is +The BibTeX entry is ```bibtex @Misc{Kallweit22, diff --git a/Source/Falcor/CMakeLists.txt b/Source/Falcor/CMakeLists.txt index 55c098a55f..001d675871 100644 --- a/Source/Falcor/CMakeLists.txt +++ b/Source/Falcor/CMakeLists.txt @@ -346,6 +346,7 @@ target_sources(Falcor PRIVATE Scene/SceneCache.h Scene/SceneDefines.slangh Scene/SceneIDs.h + Scene/SceneIDsPython.cpp Scene/SceneRayQueryInterface.slang Scene/SceneTypes.slang Scene/Shading.slang @@ -556,7 +557,6 @@ target_sources(Falcor PRIVATE Utils/NVAPI.slang Utils/NVAPI.slangh Utils/ObjectID.h - Utils/ObjectIDPython.h Utils/PathResolving.cpp Utils/PathResolving.h Utils/Properties.cpp @@ -932,7 +932,7 @@ endif() target_compile_definitions(Falcor PUBLIC - $<$:NDEBUG> + $<$:NDEBUG> $<$:_DEBUG> # Windows. $<$:NOMINMAX> # do not define min/max macros diff --git a/Source/Falcor/Core/API/Aftermath.h b/Source/Falcor/Core/API/Aftermath.h index 0ff0a91521..a9b5186d27 100644 --- a/Source/Falcor/Core/API/Aftermath.h +++ b/Source/Falcor/Core/API/Aftermath.h @@ -40,6 +40,7 @@ #include "Core/Macros.h" +#include #include namespace Falcor diff --git a/Source/Falcor/Core/API/Device.cpp b/Source/Falcor/Core/API/Device.cpp index 38db0faf4d..d177578078 100644 --- a/Source/Falcor/Core/API/Device.cpp +++ b/Source/Falcor/Core/API/Device.cpp @@ -391,6 +391,10 @@ inline Device::SupportedFeatures querySupportedFeatures(gfx::IDevice* pDevice) result |= Device::SupportedFeatures::WaveOperations; } + if (pDevice->hasFeature("cooperative-vector")) + { + result |= Device::SupportedFeatures::CoopVector; + } return result; } @@ -403,6 +407,8 @@ inline ShaderModel querySupportedShaderModel(gfx::IDevice* pDevice) ShaderModel level; }; const SMLevel levels[] = { + {"sm_6_9", ShaderModel::SM6_9}, + {"sm_6_8", ShaderModel::SM6_8}, {"sm_6_7", ShaderModel::SM6_7}, {"sm_6_6", ShaderModel::SM6_6}, {"sm_6_5", ShaderModel::SM6_5}, @@ -450,6 +456,12 @@ Device::Device(const Desc& desc) : mDesc(desc) FALCOR_THROW("Vulkan device not supported."); #endif + if (mDesc.type == Type::Vulkan && mDesc.enableDebugLayer) + { + mDesc.enableDebugLayer = false; + logWarning("Vulkan debug layer is not supported in Falcor. Disabling it."); + } + gfx::IDevice::Desc gfxDesc = {}; gfxDesc.deviceType = getGfxDeviceType(mDesc.type); gfxDesc.slang.slangGlobalSession = mSlangGlobalSession; @@ -498,8 +510,7 @@ Device::Device(const Desc& desc) : mDesc(desc) // Setup debug layer. FALCOR_GFX_CALL(gfxSetDebugCallback(&gGFXDebugCallBack)); - if (mDesc.enableDebugLayer) - gfx::gfxEnableDebugLayer(); + gfx::gfxEnableDebugLayer(mDesc.enableDebugLayer); // Get list of available GPUs. const auto gpus = getGPUs(mDesc.type); @@ -550,12 +561,14 @@ Device::Device(const Desc& desc) : mDesc(desc) #endif #if FALCOR_NVAPI_AVAILABLE - // Explicitly check for SER support via NVAPI. - // Slang currently relies on NVAPI to implement the SER API but cannot check it's availibility - // due to not being shipped with NVAPI for licensing reasons. if (getType() == Type::D3D12) { ID3D12Device* pD3D12Device = getNativeHandle().as(); + + // Explicitly check for SER support via NVAPI. + // Slang currently relies on NVAPI to implement the SER API but cannot check it's availibility + // due to not being shipped with NVAPI for licensing reasons. + // First check for avalibility of SER API (HitObject). bool supportSER = false; NvAPI_Status ret = NvAPI_D3D12_IsNvShaderExtnOpCodeSupported(pD3D12Device, NV_EXTN_OP_HIT_OBJECT_REORDER_THREAD, &supportSER); @@ -569,6 +582,38 @@ Device::Device(const Desc& desc) : mDesc(desc) ); if (ret == NVAPI_OK && reorderingCaps == NVAPI_D3D12_RAYTRACING_THREAD_REORDERING_CAP_STANDARD) mSupportedFeatures |= SupportedFeatures::RaytracingReordering; + +#if FALCOR_ENABLE_COOP_VECTOR_D3D12 + // Check for CoopVector support via NVAPI. + bool supportsCoopVector = false; + NvU32 propertyCount = 0; + ret = NvAPI_D3D12_GetPhysicalDeviceCooperativeVectorProperties(pD3D12Device, &propertyCount, NULL); + FALCOR_CHECK(ret == NVAPI_OK, "Failed to query CoopVector properties via NVAPI."); + + if (propertyCount > 0) + { + std::vector properties(propertyCount); + ret = NvAPI_D3D12_GetPhysicalDeviceCooperativeVectorProperties(pD3D12Device, &propertyCount, properties.data()); + FALCOR_CHECK(ret == NVAPI_OK, "Failed to query CoopVector properties via NVAPI."); + + for (NvU32 i = 0; i < propertyCount; i++) + { + const auto& props = properties[i]; + FALCOR_CHECK(props.version == NVAPI_COOPERATIVE_VECTOR_PROPERTIES_VER, "CoopVector properties version mismatch."); + + // Check for FP16 support. + if (props.inputType == NVAPI_COOPERATIVE_VECTOR_COMPONENT_TYPE_FLOAT16 && + props.inputInterpretation == NVAPI_COOPERATIVE_VECTOR_COMPONENT_TYPE_FLOAT16 && + props.biasInterpretation == NVAPI_COOPERATIVE_VECTOR_COMPONENT_TYPE_FLOAT16 && + props.matrixInterpretation == NVAPI_COOPERATIVE_VECTOR_COMPONENT_TYPE_FLOAT16 && + props.resultType == NVAPI_COOPERATIVE_VECTOR_COMPONENT_TYPE_FLOAT16 && props.transpose == true) + supportsCoopVector = true; + } + + if (supportsCoopVector) + mSupportedFeatures |= Device::SupportedFeatures::CoopVector; + } +#endif } #endif if (getType() == Type::Vulkan) diff --git a/Source/Falcor/Core/API/Device.h b/Source/Falcor/Core/API/Device.h index 987bb98285..fee78a823f 100644 --- a/Source/Falcor/Core/API/Device.h +++ b/Source/Falcor/Core/API/Device.h @@ -186,6 +186,7 @@ class FALCOR_API Device : public Object WaveOperations = 0x200, ShaderExecutionReorderingAPI = 0x400, ///< On D3D12 and Vulkan, this means SER API is available (in the future this will be part of the shader model). RaytracingReordering = 0x800, ///< On D3D12, this means SER is supported on the hardware. + CoopVector = 0x1000, ///< Cooperative vector extension is supported. // clang-format on }; diff --git a/Source/Falcor/Core/API/FBO.cpp b/Source/Falcor/Core/API/FBO.cpp index 63e38f8187..4ebe7d9314 100644 --- a/Source/Falcor/Core/API/FBO.cpp +++ b/Source/Falcor/Core/API/FBO.cpp @@ -31,6 +31,7 @@ #include "Core/Error.h" #include "Core/ObjectPython.h" #include "Utils/Scripting/ScriptBindings.h" +#include "Utils/Math/FNVHash.h" namespace Falcor { @@ -158,23 +159,19 @@ ResourceBindFlags getBindFlags(bool isDepth, bool allowUav) size_t Fbo::DescHash::operator()(const Fbo::Desc& d) const { - size_t hash = 0; - std::hash u32hash; - std::hash bhash; + FNVHash64 hasher; + for (uint32_t i = 0; i < getMaxColorTargetCount(); i++) { - uint32_t format = (uint32_t)d.getColorTargetFormat(i); - format <<= i; - hash |= u32hash(format) >> i; - hash |= bhash(d.isColorTargetUav(i)) << i; + hasher.insert(d.getColorTargetFormat(i)); + hasher.insert(d.isColorTargetUav(i)); } - uint32_t format = (uint32_t)d.getDepthStencilFormat(); - hash |= u32hash(format); - hash |= bhash(d.isDepthStencilUav()); - hash |= u32hash(d.getSampleCount()); + hasher.insert(d.getDepthStencilFormat()); + hasher.insert(d.isDepthStencilUav()); + hasher.insert(d.getSampleCount()); - return hash; + return hasher.get(); } bool Fbo::Desc::operator==(const Fbo::Desc& other) const @@ -469,9 +466,18 @@ void Fbo::calcAndValidateProperties() const } // The GraphicsState class relies on stable pointers of the descriptor to - // walk the tree of cached state objects. Store the desc in a global set - // and get a pointer to it. - mpDesc = &(*(getGlobalDescCache().insert(mTempDesc).first)); + // walk the tree of cached state objects. We store the heap allocated + // descriptor in the global map indexed by the descriptor, as the map + // elements can move upon rehash and are not stable. + { + auto& cache = getGlobalDescCache(); + auto it = cache.find(mTempDesc); + if (it == cache.end()) + { + it = cache.insert(std::make_pair(mTempDesc, std::make_unique(mTempDesc))).first; + } + mpDesc = it->second.get(); + } } ref Fbo::getColorTexture(uint32_t index) const diff --git a/Source/Falcor/Core/API/FBO.h b/Source/Falcor/Core/API/FBO.h index ad332c2d46..2d4d82e211 100644 --- a/Source/Falcor/Core/API/FBO.h +++ b/Source/Falcor/Core/API/FBO.h @@ -136,7 +136,7 @@ class FALCOR_API Fbo : public Object std::size_t operator()(const Desc& d) const; }; - using DescCache = std::unordered_set; + using DescCache = std::unordered_map, Fbo::DescHash>; /** * Used to tell some functions to attach all array slices of a specific mip-level. diff --git a/Source/Falcor/Core/API/Resource.cpp b/Source/Falcor/Core/API/Resource.cpp index 66e2cac7c3..a346db7c16 100644 --- a/Source/Falcor/Core/API/Resource.cpp +++ b/Source/Falcor/Core/API/Resource.cpp @@ -231,6 +231,7 @@ void Resource::breakStrongReferenceToDevice() FALCOR_SCRIPT_BINDING(Resource) { - pybind11::class_>(m, "Resource"); + pybind11::class_> resource(m, "Resource"); + resource.def_property_readonly("device", &Resource::getDevice); } } // namespace Falcor diff --git a/Source/Falcor/Core/API/Shared/D3D12ConstantBufferView.cpp b/Source/Falcor/Core/API/Shared/D3D12ConstantBufferView.cpp index cf58fb3c38..3da1858a0f 100644 --- a/Source/Falcor/Core/API/Shared/D3D12ConstantBufferView.cpp +++ b/Source/Falcor/Core/API/Shared/D3D12ConstantBufferView.cpp @@ -25,7 +25,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -#pragma once #include "D3D12ConstantBufferView.h" #include "Core/API/Device.h" #include "Core/API/NativeHandleTraits.h" diff --git a/Source/Falcor/Core/API/Texture.cpp b/Source/Falcor/Core/API/Texture.cpp index 2a61931452..5f08422da1 100644 --- a/Source/Falcor/Core/API/Texture.cpp +++ b/Source/Falcor/Core/API/Texture.cpp @@ -48,8 +48,6 @@ namespace Falcor { namespace { -static constexpr bool kTopDown = true; // Memory layout when loading from file - gfx::IResource::Type getGfxResourceType(Texture::Type type) { switch (type) @@ -272,17 +270,25 @@ ref Texture::createMippedFromFiles( mips.reserve(paths.size()); size_t combinedSize = 0; std::filesystem::path fullPathMip0; + const bool topDown = is_set(importFlags, Bitmap::ImportFlags::FlipVertical) ? false : true; for (const auto& path : paths) { Bitmap::UniqueConstPtr pBitmap; if (hasExtension(path, "dds")) { - pBitmap = ImageIO::loadBitmapFromDDS(path); + if (!topDown) + { + logWarning("Error when loading ''. DDS loader requires top-down layout.", path); + } + else + { + pBitmap = ImageIO::loadBitmapFromDDS(path); + } } else { - pBitmap = Bitmap::createFromFile(path, kTopDown, importFlags); + pBitmap = Bitmap::createFromFile(path, topDown, importFlags); } if (!pBitmap) { @@ -376,9 +382,15 @@ ref Texture::createFromFile( return nullptr; } + const bool topDown = is_set(importFlags, Bitmap::ImportFlags::FlipVertical) ? false : true; ref pTex; if (hasExtension(path, "dds")) { + if (!topDown) + { + logWarning("Error when loading ''. DDS loader requires top-down layout.", path); + return nullptr; + } try { pTex = ImageIO::loadTextureFromDDS(pDevice, path, loadAsSrgb); @@ -390,7 +402,7 @@ ref Texture::createFromFile( } else { - Bitmap::UniqueConstPtr pBitmap = Bitmap::createFromFile(path, kTopDown, importFlags); + Bitmap::UniqueConstPtr pBitmap = Bitmap::createFromFile(path, topDown, importFlags); if (pBitmap) { ResourceFormat texFormat = pBitmap->getFormat(); @@ -824,6 +836,8 @@ inline void texture_from_numpy(Texture& self, pybind11::ndarray FALCOR_CHECK(dataSize == subresourceSize, "numpy array is doesn't match the subresource size ({} != {})", dataSize, subresourceSize); self.setSubresourceBlob(subresource, data.data(), dataSize); + // Submit while the caller-owned ndarray data is still alive. + self.getDevice()->getRenderContext()->submit(false); } FALCOR_SCRIPT_BINDING(Texture) diff --git a/Source/Falcor/Core/API/Types.h b/Source/Falcor/Core/API/Types.h index 4326248dd2..27ab5e6bb9 100644 --- a/Source/Falcor/Core/API/Types.h +++ b/Source/Falcor/Core/API/Types.h @@ -43,6 +43,8 @@ enum class ShaderModel : uint32_t SM6_5 = 65, SM6_6 = 66, SM6_7 = 67, + SM6_8 = 68, + SM6_9 = 69, }; FALCOR_ENUM_INFO( ShaderModel, @@ -56,6 +58,8 @@ FALCOR_ENUM_INFO( {ShaderModel::SM6_5, "SM6_5"}, {ShaderModel::SM6_6, "SM6_6"}, {ShaderModel::SM6_7, "SM6_7"}, + {ShaderModel::SM6_8, "SM6_8"}, + {ShaderModel::SM6_9, "SM6_9"}, } ); FALCOR_ENUM_REGISTER(ShaderModel); diff --git a/Source/Falcor/Core/Macros.h b/Source/Falcor/Core/Macros.h index 44931cab68..8bad668773 100644 --- a/Source/Falcor/Core/Macros.h +++ b/Source/Falcor/Core/Macros.h @@ -81,7 +81,7 @@ * D3D12 Agility SDK. */ #if FALCOR_HAS_D3D12_AGILITY_SDK -#define FALCOR_D3D12_AGILITY_SDK_VERSION 4 +#define FALCOR_D3D12_AGILITY_SDK_VERSION 717 #define FALCOR_D3D12_AGILITY_SDK_PATH ".\\D3D12\\" // To enable the D3D12 Agility SDK, this macro needs to be added to the main source file of the executable. #define FALCOR_EXPORT_D3D12_AGILITY_SDK \ diff --git a/Source/Falcor/Core/Platform/OS.cpp b/Source/Falcor/Core/Platform/OS.cpp index cd5233e2f5..50aa24b13c 100644 --- a/Source/Falcor/Core/Platform/OS.cpp +++ b/Source/Falcor/Core/Platform/OS.cpp @@ -62,6 +62,7 @@ inline std::vector getInitialShaderDirectories() std::vector developmentDirectories = { // First we search in source folders. getProjectDirectory() / "Source" / "Falcor", + getProjectDirectory() / "Source" / "Modules", getProjectDirectory() / "Source", getProjectDirectory() / "Source" / "Tools" / "FalcorTest", // Then we search in deployment folder (necessary to pickup NVAPI and other third-party shaders). @@ -106,7 +107,7 @@ bool isDevelopmentMode() bool isSamePath(const std::filesystem::path& lhs, const std::filesystem::path& rhs) { - return std::filesystem::weakly_canonical(lhs) == std::filesystem::weakly_canonical(rhs); + return std::filesystem::path(lhs).lexically_normal() == std::filesystem::path(rhs).lexically_normal(); } std::filesystem::path findFileInDirectories(const std::filesystem::path& path, fstd::span directories) diff --git a/Source/Falcor/Core/Program/ProgramManager.cpp b/Source/Falcor/Core/Program/ProgramManager.cpp index 180d2da6a1..30bd0ac3ab 100644 --- a/Source/Falcor/Core/Program/ProgramManager.cpp +++ b/Source/Falcor/Core/Program/ProgramManager.cpp @@ -105,6 +105,7 @@ ProgramManager::ProgramManager(Device* pDevice) : mpDevice(pDevice) // Set global shader defines DefineList globalDefines = { {"FALCOR_NVAPI_AVAILABLE", (FALCOR_NVAPI_AVAILABLE && mpDevice->getType() == Device::Type::D3D12) ? "1" : "0"}, + {"FALCOR_COOP_VECTOR_AVAILABLE", mpDevice->isFeatureSupported(Device::SupportedFeatures::CoopVector) ? "1" : "0"}, #if FALCOR_NVAPI_AVAILABLE {"NV_SHADER_EXTN_SLOT", "u999"}, {"__SHADER_TARGET_MAJOR", std::to_string(getShaderModelMajorVersion(mpDevice->getSupportedShaderModel()))}, @@ -676,15 +677,6 @@ SlangCompileRequest* ProgramManager::createSlangCompileRequest(const Program& pr targetDesc.forceGLSLScalarBufferLayout = true; - if (getEnvironmentVariable("FALCOR_USE_SLANG_SPIRV_BACKEND") == "1" || program.mDesc.useSPIRVBackend) - { - targetDesc.flags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; - } - else - { - targetDesc.flags &= ~SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; - } - const char* targetMacroName; // Pick the right target based on the current graphics API @@ -732,7 +724,7 @@ SlangCompileRequest* ProgramManager::createSlangCompileRequest(const Program& pr // Setup additional compiler options. std::vector compilerOptionEntries; auto addIntOption = [&compilerOptionEntries](slang::CompilerOptionName name, int value) { - compilerOptionEntries.push_back({name, {slang::CompilerOptionValueKind::Int, 1, value, nullptr, nullptr}}); + compilerOptionEntries.push_back({name, {slang::CompilerOptionValueKind::Int, value, 0, nullptr, nullptr}}); }; auto addStringOption = [&compilerOptionEntries](slang::CompilerOptionName name, const char* value) { compilerOptionEntries.push_back({name, {slang::CompilerOptionValueKind::String, 0, 0, value, nullptr}}); @@ -743,7 +735,14 @@ SlangCompileRequest* ProgramManager::createSlangCompileRequest(const Program& pr // Column major option can be useful when compiling external shader sources that don't depend // on anything Falcor. bool useColumnMajor = is_set(compilerFlags, SlangCompilerFlags::MatrixLayoutColumnMajor); - addIntOption(useColumnMajor ? slang::CompilerOptionName::MatrixLayoutColumn : slang::CompilerOptionName::MatrixLayoutRow, 1); + sessionDesc.defaultMatrixLayoutMode = useColumnMajor ? SLANG_MATRIX_LAYOUT_COLUMN_MAJOR : SLANG_MATRIX_LAYOUT_ROW_MAJOR; + // TODO: Controlling matrix layout using options doesn't work. Slang seems to ignore them. + // addIntOption(useColumnMajor ? slang::CompilerOptionName::MatrixLayoutColumn : slang::CompilerOptionName::MatrixLayoutRow, 1); + + if (getEnvironmentVariable("FALCOR_USE_SLANG_SPIRV_BACKEND") == "1" || program.mDesc.useSPIRVBackend) + addIntOption(slang::CompilerOptionName::EmitSpirvDirectly, 1); + else + addIntOption(slang::CompilerOptionName::EmitSpirvViaGLSL, 1); // New versions of slang default to short-circuiting for logical and/or operators. // Facor is still written with the assumption that these operators do not short-circuit. diff --git a/Source/Falcor/Core/State/GraphicsState.cpp b/Source/Falcor/Core/State/GraphicsState.cpp index fa8e76c06a..0b91b333c1 100644 --- a/Source/Falcor/Core/State/GraphicsState.cpp +++ b/Source/Falcor/Core/State/GraphicsState.cpp @@ -110,9 +110,9 @@ ref GraphicsState::getGSO(const ProgramVars* pVars) else { pGso = mpDevice->createGraphicsStateObject(mDesc); - mDesc = pGso->getDesc(); pGso->breakStrongReferenceToDevice(); mpGsoGraph->setCurrentNodeData(pGso); + mDesc = pGso->getDesc(); } } return pGso; diff --git a/Source/Falcor/Core/Version.h b/Source/Falcor/Core/Version.h index c50685793e..be97521877 100644 --- a/Source/Falcor/Core/Version.h +++ b/Source/Falcor/Core/Version.h @@ -31,7 +31,7 @@ #include -#define FALCOR_MAJOR_VERSION 8 +#define FALCOR_MAJOR_VERSION 9 #define FALCOR_MINOR_VERSION 0 namespace Falcor diff --git a/Source/Falcor/DiffRendering/DiffSceneIO.slang b/Source/Falcor/DiffRendering/DiffSceneIO.slang index 877726cfd9..7c7da6788c 100644 --- a/Source/Falcor/DiffRendering/DiffSceneIO.slang +++ b/Source/Falcor/DiffRendering/DiffSceneIO.slang @@ -67,6 +67,7 @@ struct DiffSceneIO [Differentiable] [PreferRecompute] + [__NoSideEffect] void fwd_loadVertexPositionsW( DifferentialPair dpGradInfo, GeometryInstanceID instanceID, @@ -150,6 +151,7 @@ struct DiffSceneIO [Differentiable] [PreferRecompute] + [__NoSideEffect] void fwd_loadVertexNormalsW( DifferentialPair dpGradInfo, GeometryInstanceID instanceID, @@ -213,6 +215,7 @@ struct DiffSceneIO [Differentiable] [PreferRecompute] + [__NoSideEffect] void fwd_loadVertexTangentsW( DifferentialPair dpGradInfo, GeometryInstanceID instanceID, diff --git a/Source/Falcor/DiffRendering/DiffSceneQuery.slang b/Source/Falcor/DiffRendering/DiffSceneQuery.slang index b22057cc7f..9a14cb1da4 100644 --- a/Source/Falcor/DiffRendering/DiffSceneQuery.slang +++ b/Source/Falcor/DiffRendering/DiffSceneQuery.slang @@ -97,6 +97,7 @@ struct SceneQueryAD : IDifferentiable // Differentiable `traceRayInline` function. [Differentiable] [PreferRecompute] + [__NoSideEffect] bool traceRayInlineAD(const RayAD rayAD, out IntersectionAD isect, DiffIntersectionMode mode) { Ray ray = Ray(detach(rayAD.origin), detach(rayAD.direction), 0.f, FLT_MAX); diff --git a/Source/Falcor/GlobalState.cpp b/Source/Falcor/GlobalState.cpp index b65b5b066d..54b812f0eb 100644 --- a/Source/Falcor/GlobalState.cpp +++ b/Source/Falcor/GlobalState.cpp @@ -33,6 +33,7 @@ namespace Falcor static SceneBuilder* spActivePythonSceneBuilder; // TODO: REMOVEGLOBAL static ref spActivePythonRenderGraphDevice; // TODO: REMOVEGLOBAL +static AssetResolver* spActiveAssetResolver; // TODO: REMOVEGLOBAL void setActivePythonSceneBuilder(SceneBuilder* pSceneBuilder) { @@ -48,6 +49,8 @@ SceneBuilder& accessActivePythonSceneBuilder() AssetResolver& getActiveAssetResolver() { + if (spActiveAssetResolver) + return *spActiveAssetResolver; return spActivePythonSceneBuilder ? spActivePythonSceneBuilder->getAssetResolver() : AssetResolver::getDefaultResolver(); } @@ -68,4 +71,23 @@ ref accessActivePythonRenderGraphDevice() return spActivePythonRenderGraphDevice; } +ref accessActivePythonSceneBuilderDevice() +{ + if (spActivePythonSceneBuilder) + return spActivePythonSceneBuilder->getDevice(); + if (!spActivePythonRenderGraphDevice) + FALCOR_THROW("This can only be called in a Python scene building context!"); + return spActivePythonRenderGraphDevice; +} + +/// Allows overriding the asset resolver explicitly. This one always takes precedence. +AssetResolver* getRawAssetResolver() +{ + return spActiveAssetResolver; +} +void setRawAssetResolver(AssetResolver* assetResolver) +{ + spActiveAssetResolver = assetResolver; +} + } // namespace Falcor diff --git a/Source/Falcor/GlobalState.h b/Source/Falcor/GlobalState.h index 214f5619b6..c1ca6ae310 100644 --- a/Source/Falcor/GlobalState.h +++ b/Source/Falcor/GlobalState.h @@ -53,4 +53,12 @@ FALCOR_API void setActivePythonRenderGraphDevice(ref pDevice); FALCOR_API ref getActivePythonRenderGraphDevice(); FALCOR_API ref accessActivePythonRenderGraphDevice(); +/// Returns device from accessActivePythonSceneBuilder if available, accessActivePythonRenderGraphDevice if not. +/// This allows providing Device by means other than SceneBuilder +ref accessActivePythonSceneBuilderDevice(); + +/// Allows overriding the asset resolver explicitly. This one always takes precedence. +FALCOR_API AssetResolver* getRawAssetResolver(); +FALCOR_API void setRawAssetResolver(AssetResolver*); + } // namespace Falcor diff --git a/Source/Falcor/RenderGraph/RenderGraph.cpp b/Source/Falcor/RenderGraph/RenderGraph.cpp index 4a6be3c3c9..d30bbb3502 100644 --- a/Source/Falcor/RenderGraph/RenderGraph.cpp +++ b/Source/Falcor/RenderGraph/RenderGraph.cpp @@ -113,7 +113,7 @@ void RenderGraph::setScene(const ref& pScene) if (mpScene == pScene) return; - // @skallweit: check that scene resides on the same GPU device + // @skallweit: check that scene resides on the same GPU device mpScene = pScene; for (auto& it : mNodeData) @@ -446,6 +446,7 @@ void RenderGraph::execute(RenderContext* pRenderContext) if (!compile(pRenderContext, log)) FALCOR_THROW("Failed to compile render graph:\n{}", log); + FALCOR_ASSERT(mpExe); RenderGraphExe::Context c{ pRenderContext, mPassesDictionary, mCompilerDeps.defaultResourceProps.dims, mCompilerDeps.defaultResourceProps.format}; diff --git a/Source/Falcor/RenderGraph/RenderGraph.h b/Source/Falcor/RenderGraph/RenderGraph.h index d526495239..10a204e529 100644 --- a/Source/Falcor/RenderGraph/RenderGraph.h +++ b/Source/Falcor/RenderGraph/RenderGraph.h @@ -378,7 +378,6 @@ class FALCOR_API RenderGraph : public Object std::unique_ptr mpExe; ///< Helper for allocating resources and executing the graph. RenderGraphCompiler::Dependencies mCompilerDeps; ///< Data needed by the graph compiler. bool mRecompile = false; ///< Set to true to trigger a recompilation after any graph changes (topology/scene/size/passes/etc.) - friend class RenderGraphUI; friend class RenderGraphExporter; friend class RenderGraphCompiler; diff --git a/Source/Falcor/Rendering/Lights/EmissiveLightSampler.slang b/Source/Falcor/Rendering/Lights/EmissiveLightSampler.slang index 640db6f7ee..b4c74af690 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveLightSampler.slang +++ b/Source/Falcor/Rendering/Lights/EmissiveLightSampler.slang @@ -63,7 +63,7 @@ __exported import Utils.Sampling.SampleGeneratorInterface; */ struct NullEmissiveSampler : IEmissiveLightSampler { - bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls) + bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls, bool isPrevFrame=false) { ls = {}; diff --git a/Source/Falcor/Rendering/Lights/EmissiveLightSamplerHelpers.slang b/Source/Falcor/Rendering/Lights/EmissiveLightSamplerHelpers.slang index dded05abd1..844baf36e8 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveLightSamplerHelpers.slang +++ b/Source/Falcor/Rendering/Lights/EmissiveLightSamplerHelpers.slang @@ -51,11 +51,11 @@ import Rendering.Lights.EmissiveLightSamplerInterface; \param[out] ls Light sample. Only valid if true is returned. \return True if a sample was generated, false otherwise. */ -bool sampleTriangle(const float3 posW, const uint triangleIndex, const float2 u, out TriangleLightSample ls) +bool sampleTriangle(const float3 posW, const uint triangleIndex, const float2 u, out TriangleLightSample ls, bool isPrevFrame) { ls = {}; ls.triangleIndex = triangleIndex; - const EmissiveTriangle tri = gScene.lightCollection.getTriangle(triangleIndex); + const EmissiveTriangle tri = gScene.lightCollection.getTriangle(triangleIndex, isPrevFrame); // Sample the triangle uniformly. const float3 barycentrics = sample_triangle(u); @@ -78,7 +78,7 @@ bool sampleTriangle(const float3 posW, const uint triangleIndex, const float2 u, float2 uv = tri.getTexCoord(barycentrics); ls.Le = gScene.materials.evalEmissive(tri.materialID, uv); - // DEMO21: Evaluate light profile. + // Evaluate light profile. if (gScene.materials.hasLightProfile(tri.materialID)) { ls.Le *= gScene.materials.evalLightProfile(dot(ls.normalW, -ls.dir)); @@ -90,8 +90,10 @@ bool sampleTriangle(const float3 posW, const uint triangleIndex, const float2 u, float denom = max(FLT_MIN, cosTheta * tri.area); ls.pdf = distSqr / denom; + ls.geometryTerm = cosTheta / distSqr; + // Save light sample triangle barycentric coordinates - ls.uv = u; + ls.uv = barycentrics.yz; // TODO: We can simplify the expressions by using the unnormalized quantities for computing the pdf. //ls.pdf = -2.f * distSqr * ls.distance / (dot(N, L); // Optimized (except N would have to be properly flipped above, before normalW is computed). diff --git a/Source/Falcor/Rendering/Lights/EmissiveLightSamplerInterface.slang b/Source/Falcor/Rendering/Lights/EmissiveLightSamplerInterface.slang index c5fffd8cb3..6ea99f365b 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveLightSamplerInterface.slang +++ b/Source/Falcor/Rendering/Lights/EmissiveLightSamplerInterface.slang @@ -43,6 +43,7 @@ struct TriangleLightSample float3 Le; ///< Emitted radiance. This is zero if the light is back-facing or sample is invalid. float pdf; ///< Probability density with respect to solid angle from the shading point. The range is [0,inf] (inclusive), where pdf == 0.0 indicated an invalid sample. float2 uv; ///< Light sample barycentric coords over the triangle + float geometryTerm; ///< for computing jacobian in GRIS }; /** Describes a light sample at a hit point on an emissive triangle. @@ -67,7 +68,7 @@ interface IEmissiveLightSampler \param[out] ls Light sample. Only valid if true is returned. \return True if a sample was generated, false otherwise. */ - bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls); + bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls, bool isPrevFrame=false); /** Evaluate the PDF associated with selecting an emissive triangle. \param[in] posW Shading point in world space. diff --git a/Source/Falcor/Rendering/Lights/EmissivePowerSampler.cpp b/Source/Falcor/Rendering/Lights/EmissivePowerSampler.cpp index 277c061784..d0e927e91c 100644 --- a/Source/Falcor/Rendering/Lights/EmissivePowerSampler.cpp +++ b/Source/Falcor/Rendering/Lights/EmissivePowerSampler.cpp @@ -46,7 +46,7 @@ namespace Falcor // Check if light collection has changed. if (mLightCollectionUpdateFlags != ILightCollection::UpdateFlags::None) { - mNeedsRebuild = true; + //mNeedsRebuild = true; // because current lightCollection doesn't support light intensity/color change, we don't need to rebuild mLightCollectionUpdateFlags = ILightCollection::UpdateFlags::None; } diff --git a/Source/Falcor/Rendering/Lights/EmissivePowerSampler.slang b/Source/Falcor/Rendering/Lights/EmissivePowerSampler.slang index 60e30c3fea..ade792a874 100644 --- a/Source/Falcor/Rendering/Lights/EmissivePowerSampler.slang +++ b/Source/Falcor/Rendering/Lights/EmissivePowerSampler.slang @@ -58,7 +58,7 @@ struct EmissivePowerSampler : IEmissiveLightSampler \param[out] ls Light sample. Only valid if true is returned. \return True if a sample was generated, false otherwise. */ - bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls) + bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls, bool isPrevFrame=false) { ls = {}; @@ -83,7 +83,7 @@ struct EmissivePowerSampler : IEmissiveLightSampler // Sample the triangle uniformly. float2 u = sampleNext2D(sg); - if (!sampleTriangle(posW, triangleIndex, u, ls)) return false; + if (!sampleTriangle(posW, triangleIndex, u, ls, isPrevFrame)) return false; // The final probability density is the product of the sampling probabilities. ls.pdf *= triangleSelectionPdf; diff --git a/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.slang b/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.slang index cd073b2089..7f9dbb7a92 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.slang +++ b/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.slang @@ -52,7 +52,7 @@ struct EmissiveUniformSampler : IEmissiveLightSampler \param[out] ls Light sample. Only valid if true is returned. \return True if a sample was generated, false otherwise. */ - bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls) + bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls, bool isPrevFrame=false) { ls = {}; @@ -67,7 +67,11 @@ struct EmissiveUniformSampler : IEmissiveLightSampler // Sample the triangle uniformly. float2 u = sampleNext2D(sg); - if (!sampleTriangle(posW, triangleIndex, u, ls)) return false; + +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif + if (!sampleTriangle(posW, triangleIndex, u, ls, isPrevFrame)) return false; // The final probability density is the product of the sampling probabilities. ls.pdf *= triangleSelectionPdf; diff --git a/Source/Falcor/Rendering/Lights/EnvMapSampler.slang b/Source/Falcor/Rendering/Lights/EnvMapSampler.slang index 18fdf0873c..f02acdb37a 100644 --- a/Source/Falcor/Rendering/Lights/EnvMapSampler.slang +++ b/Source/Falcor/Rendering/Lights/EnvMapSampler.slang @@ -61,9 +61,9 @@ struct EnvMapSampler * \param[in] dir World space direction. * \param[in] lod Texture level of detail for envmap evaluation. */ - float3 eval(float3 dir, float lod = 0.f) + float3 eval(float3 dir, float lod = 0.f, bool isPrevFrame = false) { - return gScene.envMap.eval(dir, lod); + return gScene.envMap.eval(dir, lod, isPrevFrame); } /** @@ -72,7 +72,7 @@ struct EnvMapSampler * \param[out] result Result struct. * \param[in] lod Texture level of detail for envmap evaluation. */ - bool sample(const float2 rnd, out EnvMapSample result, float lod = 0.f) + bool sample(const float2 rnd, out EnvMapSample result, float lod = 0.f, bool isPrevFrame = false) { float2 p = rnd; // Random sample in [0,1)^2. uint2 pos = 0; // Top-left texel pos of current 2x2 region. @@ -141,9 +141,9 @@ struct EnvMapSampler float avg_w = importanceMap.Load(int3(0, 0, importanceBaseMip)); // 1x1 mip holds integral over importance map. TODO: Replace by constant or rescale in setup so that the integral is 1.0 float pdf = importanceMap[pos] / avg_w; - result.dir = gScene.envMap.toWorld(dir); + result.dir = gScene.envMap.toWorld(dir, isPrevFrame); result.pdf = pdf * M_1_4PI; - result.Le = gScene.envMap.eval(result.dir, lod); + result.Le = gScene.envMap.eval(result.dir, lod, isPrevFrame); return true; } @@ -155,9 +155,9 @@ struct EnvMapSampler \param[in] dir World space direction (normalized). \return Probability density function evaluated for direction 'dir'. */ - float evalPdf(float3 dir) + float evalPdf(float3 dir, bool isPrevFrame = false) { - float2 uv = ndir_to_oct_equal_area_unorm(gScene.envMap.toLocal(dir)); + float2 uv = ndir_to_oct_equal_area_unorm(gScene.envMap.toLocal(dir, isPrevFrame)); float avg_w = importanceMap.Load(int3(0, 0, importanceBaseMip)); // 1x1 mip holds integral over importance map. TODO: Replace by constant or rescale in setup so that the integral is 1.0 float pdf = importanceMap.SampleLevel(importanceSampler, uv, 0) / avg_w; return pdf * (1.f / M_4PI); diff --git a/Source/Falcor/Rendering/Lights/LightBVHSampler.slang b/Source/Falcor/Rendering/Lights/LightBVHSampler.slang index cdf8fcf327..fdd29afd36 100644 --- a/Source/Falcor/Rendering/Lights/LightBVHSampler.slang +++ b/Source/Falcor/Rendering/Lights/LightBVHSampler.slang @@ -72,7 +72,7 @@ struct LightBVHSampler : IEmissiveLightSampler \param[out] ls Light sample. Only valid if true is returned. \return True if a sample was generated, false otherwise. */ - bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls) + bool sampleLight(const float3 posW, const float3 normalW, const bool upperHemisphere, inout S sg, out TriangleLightSample ls, bool isPrevFrame=false) { ls = {}; @@ -82,11 +82,20 @@ struct LightBVHSampler : IEmissiveLightSampler uint triangleIndex; float trianglePdf; float uLight = sampleNext1D(sg); - if (!sampleLightViaBVH(posW, normalW, upperHemisphere, uLight, trianglePdf, triangleIndex)) return false; + if (!sampleLightViaBVH(posW, normalW, upperHemisphere, uLight, trianglePdf, triangleIndex)) + { +#if PAD_RANDOM_NUMBERS + sampleNext3D(sg); +#endif + return false; + } // Sample a point on the triangle uniformly. float2 u = sampleNext2D(sg); - if (!sampleTriangle(posW, triangleIndex, u, ls)) return false; +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif + if (!sampleTriangle(posW, triangleIndex, u, ls, isPrevFrame)) return false; // The final probability density is the product of the sampling probabilities. ls.pdf *= trianglePdf; @@ -109,7 +118,7 @@ struct LightBVHSampler : IEmissiveLightSampler // Evaluate PDF using the BVH. // Load the triangle bitmask as 2x32 bits instead of uint64_t due to driver bug. - // TODO: Change buffer to uint64_t format and remove this workaround when http://nvbugs/2817745 is fixed. + // TODO: Change buffer to uint64_t format once the driver issue is fixed. //uint64_t bitmask = _lightBVH.triangleBitmasks[hit.triangleIndex]; uint2 tmp = _lightBVH.triangleBitmasks[triangleIndex]; uint64_t bitmask = ((uint64_t)tmp.y << 32) | tmp.x; diff --git a/Source/Falcor/Rendering/Lights/LightHelpers.slang b/Source/Falcor/Rendering/Lights/LightHelpers.slang index 22d749ea00..1de035d38d 100644 --- a/Source/Falcor/Rendering/Lights/LightHelpers.slang +++ b/Source/Falcor/Rendering/Lights/LightHelpers.slang @@ -260,11 +260,20 @@ bool samplePointLight(const float3 shadingPosW, const LightData light, out Analy bool sampleLight(const float3 shadingPosW, const LightData light, inout S sg, out AnalyticLightSample ls) { // Sample the light based on its type: point, directional, or area. - switch (light.type) +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif + switch (LightType(light.type)) { case LightType::Point: +#if PAD_RANDOM_NUMBERS + sampleNext2D(sg); +#endif return samplePointLight(shadingPosW, light, ls); case LightType::Directional: +#if PAD_RANDOM_NUMBERS + sampleNext2D(sg); +#endif return sampleDirectionalLight(shadingPosW, light, ls); case LightType::Rect: return sampleRectAreaLight(shadingPosW, light, sampleNext2D(sg), ls); @@ -291,7 +300,7 @@ bool evalLightApproximate(const float3 shadingPosW, const LightData light, out A { ls = {}; - switch (light.type) + switch (LightType(light.type)) { case LightType::Point: return samplePointLight(shadingPosW, light, ls); diff --git a/Source/Falcor/Rendering/Materials/AnisotropicGGX.slang b/Source/Falcor/Rendering/Materials/AnisotropicGGX.slang index 8e8c0eebfc..16ee1ae037 100644 --- a/Source/Falcor/Rendering/Materials/AnisotropicGGX.slang +++ b/Source/Falcor/Rendering/Materials/AnisotropicGGX.slang @@ -35,27 +35,21 @@ import Utils.Sampling.SampleGeneratorInterface; */ struct AnisotropicGGX : IDifferentiable { - float2 alpha; + float2 alpha = {}; [Differentiable] - __init(float2 alpha) - { - this.alpha = alpha; - } + __init() {} + + [Differentiable] + __init(float2 alpha) { this.alpha = alpha; } /** A 'singular' distribution is one which would be more ideally treated as an ideal mirror. It can't be sampled reliably. */ - bool isSingular() - { - return all(alpha < 1e-3f); - } + bool isSingular() { return all(alpha < 1e-3f); } [Differentiable] - float getIsotropicRoughness() - { - return max(alpha.x, alpha.y); - } + float getIsotropicRoughness() { return max(alpha.x, alpha.y); } /** Computes the Smith lambda function. This function is symmetric with respect to direction, i.e. smithLambda(v) = smithLambda(-v). The result returned is equivalent to assuming v.z > 0. @@ -66,7 +60,8 @@ struct AnisotropicGGX : IDifferentiable float cosThetaSq = v.z * v.z; float sinThetaSq = max(1.0f - cosThetaSq, 0.0f); float tanThetaSq = sinThetaSq / cosThetaSq; - if (isinf(tanThetaSq)) return 0.0f; + if (isinf(tanThetaSq)) + return 0.0f; float2 cosSinPhiSq = sinThetaSq == 0.0f ? float2(1.0f, 0.0f) : float2(v.x * v.x, v.y * v.y) / sinThetaSq; float alphaSq = dot(cosSinPhiSq, alpha * alpha); @@ -78,10 +73,7 @@ struct AnisotropicGGX : IDifferentiable for multiple scattering on microfacets. */ [Differentiable] - float G1(float3 w) - { - return 1.0f / (1.0f + smithLambda(w)); - } + float G1(float3 w) { return 1.0f / (1.0f + smithLambda(w)); } /** Compute the bistatic Smith shadowing function for reflection. This will be off for refraction, where we would have to use the beta @@ -89,23 +81,22 @@ struct AnisotropicGGX : IDifferentiable multiple scattering on microfacet dielectrics as well. */ [Differentiable] - float G2(float3 wo, float3 wi) - { - return 1.0f / (1.0f + smithLambda(wo) + smithLambda(wi)); - } + float G2(float3 wo, float3 wi) { return 1.0f / (1.0f + smithLambda(wo) + smithLambda(wi)); } /** Computes the normal distribution function, i.e. D(h) - */ + */ [Differentiable] float evalNDF(float3 h) { float cosThetaSq = h.z * h.z; float sinThetaSq = max(1.0f - cosThetaSq, 0.0f); float tanThetaSq = sinThetaSq / cosThetaSq; - if (isinf(tanThetaSq)) return 0.0f; + if (isinf(tanThetaSq)) + return 0.0f; float cosThetaQu = cosThetaSq * cosThetaSq; - if (cosThetaQu < 1e-16f) return 0.0f; + if (cosThetaQu < 1e-16f) + return 0.0f; float2 cosSinPhiSq = sinThetaSq == 0.0f ? float2(1.0f, 0.0f) : float2(h.x * h.x, h.y * h.y) / sinThetaSq; float2 alphaSq = cosSinPhiSq / (alpha * alpha); @@ -114,19 +105,17 @@ struct AnisotropicGGX : IDifferentiable } /** Computes the distribution of visible normals, i.e. D_wi(h) - */ + */ [Differentiable] - float evalVNDF(float3 w, float3 h) - { - return w.z == 0.0f ? 0.0f : (G1(w) * abs(dot(w, h)) * evalNDF(h)) / abs(w.z); - } + float evalVNDF(float3 w, float3 h) { return w.z == 0.0f ? 0.0f : (G1(w) * abs(dot(w, h)) * evalNDF(h)) / abs(w.z); } /** Samples the distribution of normals visible from direction w. - */ + */ float3 sample(float3 w, inout S sg) { float3 wh = normalize(float3(alpha.x * w.x, alpha.y * w.y, w.z)); - if (wh.z < 0.0f) wh = -wh; + if (wh.z < 0.0f) + wh = -wh; float3 T1 = dot(wh.xy, wh.xy) > 0.0f ? normalize(float3(-wh.y, wh.x, 0.0f)) : float3(1.0f, 0.0f, 0.0f); float3 T2 = cross(wh, T1); @@ -143,9 +132,6 @@ struct AnisotropicGGX : IDifferentiable } /** Returns the PDF of sampling normal h from VNDF(w) - */ - float evalPDF(float3 w, float3 h) - { - return evalVNDF(w, h); - } + */ + float evalPDF(float3 w, float3 h) { return evalVNDF(w, h); } }; diff --git a/Source/Falcor/Rendering/Materials/BSDFConfig.slangh b/Source/Falcor/Rendering/Materials/BSDFConfig.slangh index be34036de8..20006756bc 100644 --- a/Source/Falcor/Rendering/Materials/BSDFConfig.slangh +++ b/Source/Falcor/Rendering/Materials/BSDFConfig.slangh @@ -34,16 +34,16 @@ TODO: This file will be removed when we've settled on a new standard material definition. */ -#define DiffuseBrdfLambert 0 -#define DiffuseBrdfDisney 1 -#define DiffuseBrdfFrostbite 2 +#define DiffuseBrdfLambert 0 +#define DiffuseBrdfDisney 1 +#define DiffuseBrdfFrostbite 2 #ifndef DiffuseBrdf #define DiffuseBrdf DiffuseBrdfFrostbite #endif -#define SpecularMaskingFunctionSmithGGXSeparable 0 ///< Used by UE4. -#define SpecularMaskingFunctionSmithGGXCorrelated 1 ///< Used by Frostbite. This is the more accurate form (default). +#define SpecularMaskingFunctionSmithGGXSeparable 0 ///< Used by UE4. +#define SpecularMaskingFunctionSmithGGXCorrelated 1 ///< Used by Frostbite. This is the more accurate form (default). #ifndef SpecularMaskingFunction #define SpecularMaskingFunction SpecularMaskingFunctionSmithGGXCorrelated diff --git a/Source/Falcor/Rendering/Materials/BSDFIntegrator.cpp b/Source/Falcor/Rendering/Materials/BSDFIntegrator.cpp index 2ee1150ef3..ab03d84f7c 100644 --- a/Source/Falcor/Rendering/Materials/BSDFIntegrator.cpp +++ b/Source/Falcor/Rendering/Materials/BSDFIntegrator.cpp @@ -32,138 +32,163 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/BSDFIntegrator.cs.slang"; - const char kParameterBlock[] = "gIntegrator"; +namespace +{ +const char kShaderFile[] = "Rendering/Materials/BSDFIntegrator.cs.slang"; +const char kParameterBlock[] = "gIntegrator"; - /// Integration grid size. Do not change, the programs are specialized for this size. - /// The shader takes currently 8x8 stratified samples per grid cell, for a total of 4096x4096 samples over the hemisphere. - const uint2 kGridSize = { 512, 512 }; - } +/// Integration grid size. Do not change, the programs are specialized for this size. +/// The shader takes currently 8x8 stratified samples per grid cell, for a total of 4096x4096 samples over the hemisphere. +const uint2 kGridSize = {512, 512}; +const uint2 kTileSize = {32, 32}; +} // namespace - BSDFIntegrator::BSDFIntegrator(ref pDevice, const ref& pScene) - : mpDevice(pDevice) - , mpScene(pScene) - { - FALCOR_CHECK(pDevice != nullptr, "'pDevice' must be a valid device"); - FALCOR_CHECK(pScene != nullptr, "'pScene' must be a valid scene"); - - if (!mpDevice->isShaderModelSupported(ShaderModel::SM6_6)) - FALCOR_THROW("BSDFIntegrator requires Shader Model 6.6 support"); - - // Create programs. - ProgramDesc desc; - desc.addShaderModules(pScene->getShaderModules()); - desc.addShaderLibrary(kShaderFile); - desc.addTypeConformances(pScene->getTypeConformances()); - auto defines = pScene->getSceneDefines(); - ProgramDesc descFinal = desc; - - desc.csEntry("mainIntegration"); - mpIntegrationPass = ComputePass::create(mpDevice, desc, defines); - descFinal.csEntry("mainFinal"); - mpFinalPass = ComputePass::create(mpDevice, descFinal, defines); - - // Compute number of intermediate results. - uint3 groupSize = mpIntegrationPass->getThreadGroupSize(); - FALCOR_ASSERT(groupSize.x == 32 && groupSize.y == 32 && groupSize.z == 1); - uint groupThreadCount = groupSize.x * groupSize.y * groupSize.z; - mResultCount = (kGridSize.x * kGridSize.y) / groupThreadCount; - FALCOR_ASSERT(mResultCount * groupThreadCount == kGridSize.x * kGridSize.y); - - uint3 finalGroupSize = mpFinalPass->getThreadGroupSize(); - FALCOR_ASSERT(finalGroupSize.x == 256 && finalGroupSize.y == 1 && finalGroupSize.z == 1); - FALCOR_ASSERT(finalGroupSize.x == mResultCount); - } +BSDFIntegrator::BSDFIntegrator(ref pDevice, const ref& pScene) : mpDevice(pDevice), mpScene(pScene) +{ + FALCOR_CHECK(pDevice != nullptr, "'pDevice' must be a valid device"); + FALCOR_CHECK(pScene != nullptr, "'pScene' must be a valid scene"); + + if (!mpDevice->isShaderModelSupported(ShaderModel::SM6_6)) + FALCOR_THROW("BSDFIntegrator requires Shader Model 6.6 support"); + + // Create programs. + ProgramDesc desc; + desc.addShaderModules(pScene->getShaderModules()); + desc.addShaderLibrary(kShaderFile); + desc.addTypeConformances(pScene->getTypeConformances()); + auto defines = pScene->getSceneDefines(); + ProgramDesc descFinal = desc; + + desc.csEntry("mainIntegration"); + mpIntegrationPass = ComputePass::create(mpDevice, desc, defines); + descFinal.csEntry("mainFinal"); + mpFinalPass = ComputePass::create(mpDevice, descFinal, defines); + + // Compute number of intermediate results. + uint3 groupSize = mpIntegrationPass->getThreadGroupSize(); + FALCOR_ASSERT(groupSize.x == 1024 && groupSize.y == 1 && groupSize.z == 1); + uint groupThreadCount = groupSize.x * groupSize.y * groupSize.z; + mResultCount = (kGridSize.x * kGridSize.y) / groupThreadCount; + FALCOR_ASSERT(mResultCount * groupThreadCount == kGridSize.x * kGridSize.y); + + uint3 finalGroupSize = mpFinalPass->getThreadGroupSize(); + FALCOR_ASSERT(finalGroupSize.x == 256 && finalGroupSize.y == 1 && finalGroupSize.z == 1); + FALCOR_ASSERT(finalGroupSize.x == mResultCount); +} - float3 BSDFIntegrator::integrateIsotropic(RenderContext* pRenderContext, const MaterialID materialID, float cosTheta) - { - std::vector cosThetas(1, cosTheta); - auto results = integrateIsotropic(pRenderContext, materialID, cosThetas); - return results[0]; - } +float3 BSDFIntegrator::integrateIsotropic(RenderContext* pRenderContext, const MaterialID materialID, float cosTheta) +{ + std::vector cosThetas(1, cosTheta); + auto results = integrateIsotropic(pRenderContext, materialID, cosThetas); + return results[0]; +} - std::vector BSDFIntegrator::integrateIsotropic(RenderContext* pRenderContext, const MaterialID materialID, const std::vector& cosThetas) - { - FALCOR_ASSERT(mpScene); - FALCOR_CHECK(materialID.get() < mpScene->getMaterialCount(), "'materialID' is out of range"); - FALCOR_CHECK(!cosThetas.empty(), "'cosThetas' array is empty"); +std::vector BSDFIntegrator::integrateIsotropic( + RenderContext* pRenderContext, + const MaterialID materialID, + const std::vector& cosThetas +) +{ + FALCOR_ASSERT(mpScene); + FALCOR_CHECK(materialID.get() < mpScene->getMaterialCount(), "'materialID' is out of range"); + FALCOR_CHECK(!cosThetas.empty(), "'cosThetas' array is empty"); - CpuTimer timer; - timer.update(); + CpuTimer timer; + timer.update(); - // Upload cos theta angles. - FALCOR_ASSERT(cosThetas.size() <= std::numeric_limits::max()); - const uint32_t gridCount = (uint32_t)cosThetas.size(); + // Upload cos theta angles. + FALCOR_ASSERT(cosThetas.size() <= std::numeric_limits::max()); + const uint32_t gridCount = (uint32_t)cosThetas.size(); - if (!mpCosThetaBuffer || mpCosThetaBuffer->getElementCount() < gridCount) - { - mpCosThetaBuffer = mpDevice->createStructuredBuffer(sizeof(float), gridCount, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, cosThetas.data(), false); - } - else - { - mpCosThetaBuffer->setBlob(cosThetas.data(), 0, cosThetas.size() * sizeof(cosThetas[0])); - } + if (!mpCosThetaBuffer || mpCosThetaBuffer->getElementCount() < gridCount) + { + mpCosThetaBuffer = mpDevice->createStructuredBuffer( + sizeof(float), gridCount, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, cosThetas.data(), false + ); + } + else + { + mpCosThetaBuffer->setBlob(cosThetas.data(), 0, cosThetas.size() * sizeof(cosThetas[0])); + } - // Allocate buffer for intermediate and final results. - uint32_t elemCount = gridCount * mResultCount; - if (!mpResultBuffer || mpResultBuffer->getElementCount() < elemCount) - { - mpResultBuffer = mpDevice->createStructuredBuffer(sizeof(float3), elemCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); - } - if (!mpFinalResultBuffer || mpFinalResultBuffer->getElementCount() < gridCount) - { - mpFinalResultBuffer = mpDevice->createStructuredBuffer(sizeof(float3), gridCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); - mpStagingBuffer = mpDevice->createStructuredBuffer(sizeof(float3), gridCount, ResourceBindFlags::None, MemoryType::ReadBack, nullptr, false); - } + // Allocate buffer for intermediate and final results. + uint32_t elemCount = gridCount * mResultCount; + if (!mpResultBuffer || mpResultBuffer->getElementCount() < elemCount) + { + mpResultBuffer = mpDevice->createStructuredBuffer( + sizeof(float3), + elemCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); + } + if (!mpFinalResultBuffer || mpFinalResultBuffer->getElementCount() < gridCount) + { + mpFinalResultBuffer = mpDevice->createStructuredBuffer( + sizeof(float3), + gridCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); + mpStagingBuffer = + mpDevice->createStructuredBuffer(sizeof(float3), gridCount, ResourceBindFlags::None, MemoryType::ReadBack, nullptr, false); + } - // Execute GPU passes. - integrationPass(pRenderContext, materialID, gridCount); - finalPass(pRenderContext, gridCount); + // Execute GPU passes. + integrationPass(pRenderContext, materialID, gridCount); + finalPass(pRenderContext, gridCount); - // Copy result to staging buffer. - pRenderContext->copyBufferRegion(mpStagingBuffer.get(), 0, mpFinalResultBuffer.get(), 0, sizeof(float3) * gridCount); + // Copy result to staging buffer. + pRenderContext->copyBufferRegion(mpStagingBuffer.get(), 0, mpFinalResultBuffer.get(), 0, sizeof(float3) * gridCount); - // Wait for results to be available. - pRenderContext->submit(true); + // Wait for results to be available. + pRenderContext->submit(true); - // Read back final results. - const float3* finalResults = reinterpret_cast(mpStagingBuffer->map()); - std::vector output(finalResults, finalResults + gridCount); - mpStagingBuffer->unmap(); + // Read back final results. + const float3* finalResults = reinterpret_cast(mpStagingBuffer->map()); + std::vector output(finalResults, finalResults + gridCount); + mpStagingBuffer->unmap(); - timer.update(); - logInfo("Finished BSDF integration for {} incident directions in {} seconds.", cosThetas.size(), timer.delta()); + timer.update(); + logInfo("Finished BSDF integration for {} incident directions in {} seconds.", cosThetas.size(), timer.delta()); - return output; - } + return output; +} - void BSDFIntegrator::integrationPass(RenderContext* pRenderContext, const MaterialID materialID, const uint32_t gridCount) const - { - FALCOR_ASSERT(mpIntegrationPass); - auto var = mpIntegrationPass->getRootVar()[kParameterBlock]; - var["gridSize"] = kGridSize; - var["gridCount"] = gridCount; - var["resultCount"] = mResultCount; - var["materialID"] = materialID.getSlang(); - var["cosThetas"] = mpCosThetaBuffer; - var["results"] = mpResultBuffer; - - mpScene->bindShaderData(mpIntegrationPass->getRootVar()["gScene"]); - mpIntegrationPass->execute(pRenderContext, uint3(kGridSize, gridCount)); - } +void BSDFIntegrator::integrationPass(RenderContext* pRenderContext, const MaterialID materialID, const uint32_t gridCount) const +{ + FALCOR_ASSERT(mpIntegrationPass); + auto var = mpIntegrationPass->getRootVar()[kParameterBlock]; + var["gridSize"] = kGridSize; + var["gridCount"] = gridCount; + var["resultCount"] = mResultCount; + var["materialID"] = materialID.getSlang(); + var["cosThetas"] = mpCosThetaBuffer; + var["results"] = mpResultBuffer; + + // The integration pass is arranged as 32x32 tiles over the 2D grid. + // The threads in the first dispatch dimension are mapped to locations in the tile in the shader. + const uint3 groupCount = uint3(kGridSize / kTileSize, gridCount); + const uint3 dispatchDim = {1024, 1, 1}; + + mpScene->bindShaderData(mpIntegrationPass->getRootVar()["gScene"]); + mpIntegrationPass->execute(pRenderContext, groupCount * dispatchDim); +} - void BSDFIntegrator::finalPass(RenderContext* pRenderContext, const uint32_t gridCount) const - { - FALCOR_ASSERT(mpFinalPass); - auto var = mpFinalPass->getRootVar()[kParameterBlock]; - var["gridCount"] = gridCount; - var["resultCount"] = mResultCount; - var["results"] = mpResultBuffer; - var["finalResults"] = mpFinalResultBuffer; +void BSDFIntegrator::finalPass(RenderContext* pRenderContext, const uint32_t gridCount) const +{ + FALCOR_ASSERT(mpFinalPass); + auto var = mpFinalPass->getRootVar()[kParameterBlock]; + var["gridCount"] = gridCount; + var["resultCount"] = mResultCount; + var["results"] = mpResultBuffer; + var["finalResults"] = mpFinalResultBuffer; - mpFinalPass->execute(pRenderContext, uint3(mResultCount, gridCount, 1)); + mpFinalPass->execute(pRenderContext, uint3(mResultCount, gridCount, 1)); #if 0 // DEBUG: Final accumulation on the CPU. @@ -179,5 +204,5 @@ namespace Falcor } mpResultBuffer->unmap(); #endif - } } +} // namespace Falcor diff --git a/Source/Falcor/Rendering/Materials/BSDFIntegrator.cs.slang b/Source/Falcor/Rendering/Materials/BSDFIntegrator.cs.slang index 1df3dd74d8..4b271d1652 100644 --- a/Source/Falcor/Rendering/Materials/BSDFIntegrator.cs.slang +++ b/Source/Falcor/Rendering/Materials/BSDFIntegrator.cs.slang @@ -34,21 +34,23 @@ import Utils.Sampling.UniformSampleGenerator; /** Utility for integrating an isotropic BSDF over the hemisphere. The code assumes thread group size 1024 and wave size 32. - The execution is arranged in groups of 32x32 threads over the 2D integration grid. + The execution is arranged as 32x32 tiles over the 2D integration grid. + The threads in each thread group are mapped in scanline order to locations in the tile. The third dimension is the grid index (= which incident direction to use). The result is reduced to one value per thread group before writing out. */ struct BSDFIntegrator { - uint2 gridSize; ///< Size of integration grid over the hemisphere. - uint gridCount; ///< Integration grid count. One incident direction per grid. - uint resultCount; ///< Number of intermediate results per grid. - uint materialID; ///< Material ID in the scene. - StructuredBuffer cosThetas; ///< Cosine theta of incident directions. - RWStructuredBuffer results; ///< Intermediate results. One value per thread group. - RWStructuredBuffer finalResults; ///< Final results. One value per grid. + uint2 gridSize; ///< Size of integration grid over the hemisphere. + uint gridCount; ///< Integration grid count. One incident direction per grid. + uint resultCount; ///< Number of intermediate results per grid. + uint materialID; ///< Material ID in the scene. + StructuredBuffer cosThetas; ///< Cosine theta of incident directions. + RWStructuredBuffer results; ///< Intermediate results. One value per thread group. + RWStructuredBuffer finalResults; ///< Final results. One value per grid. static const uint2 kSampleCount = { 8, 8 }; ///< Number of stratified samples per grid cell. + static const uint2 kTileSize = { 32, 32 }; static const uint kWaveSize = 32; static groupshared float3 sharedResult[1024 / kWaveSize]; // Note Slang requires static when declaring groupshared inside struct. @@ -56,9 +58,13 @@ struct BSDFIntegrator /** Integrate BSDF over the hemisphere. The function is executed with one thread per grid cell, and the result reduced to a single value per thread group. */ - void executeIntegration(const uint2 threadID, const uint groupThreadIdx, const uint3 groupID) + void executeIntegration(const uint groupThreadIdx, const uint3 groupID) { - if (groupID.z >= gridCount) return; + if (groupID.z >= gridCount) + return; + + // Map thread to 2D location in integration grid. + const uint2 gridID = groupID.xy * kTileSize + uint2(groupThreadIdx % kTileSize.x, groupThreadIdx / kTileSize.x); // Setup shading data struct. ShadingData sd = {}; @@ -83,14 +89,14 @@ struct BSDFIntegrator // The samples are currently placed at the center of each strata. float3 value = {}; - UniformSampleGenerator sg = UniformSampleGenerator(threadID, 0); + UniformSampleGenerator sg = UniformSampleGenerator(gridID, 0); for (uint sy = 0; sy < kSampleCount.y; sy++) { for (uint sx = 0; sx < kSampleCount.x; sx++) { // Compute sample position in [0,1]^2 and map to hemisphere. float2 offset = (float2(sx, sy) + 0.5f) * (1.f / kSampleCount); - float2 u = (float2(threadID) + offset) / gridSize; + float2 u = (float2(gridID) + offset) / gridSize; float pdf = 0.f; float3 wo = sample_cosine_hemisphere_concentric(u, pdf); @@ -111,19 +117,22 @@ struct BSDFIntegrator value = WaveActiveSum(value); if (WaveIsFirstLane()) { - sharedResult[groupThreadIdx / kWaveSize] = value; + const uint warpId = groupThreadIdx / kWaveSize; + sharedResult[warpId] = value; } GroupMemoryBarrierWithGroupSync(); } /** Reduce result over thread group and write to memory. - */ + */ void reduceGroup(float3 value, const uint groupThreadIdx, const uint3 groupID, const uint kGroupSize = 1024) { + const uint warpCount = kGroupSize / kWaveSize; + reduceWarpToShared(value, groupThreadIdx); // First warp in group accumulates values from shared memory. - if (groupThreadIdx < (kGroupSize / kWaveSize)) + if (groupThreadIdx < warpCount) { value = sharedResult[groupThreadIdx]; value = WaveActiveSum(value); @@ -131,7 +140,7 @@ struct BSDFIntegrator // First thread in group stores result to global memory. if (groupThreadIdx == 0) { - uint groupIdx = (gridSize.x / 32 /* group width */) * groupID.y + groupID.x; + uint groupIdx = (gridSize.x / kTileSize.x) * groupID.y + groupID.x; uint dstIdx = resultCount * groupID.z + groupIdx; results[dstIdx] = value * (1.f / kGroupSize); @@ -140,10 +149,13 @@ struct BSDFIntegrator } /** Reduce results buffer to a single final value per grid. - */ + */ void executeFinal(const uint groupThreadIdx, const uint gridIdx, const uint kGroupSize = 256) { - if (gridIdx >= gridCount) return; + if (gridIdx >= gridCount) + return; + + const uint warpCount = kGroupSize / kWaveSize; uint srcIdx = resultCount * gridIdx + groupThreadIdx; float3 value = results[srcIdx]; @@ -151,7 +163,7 @@ struct BSDFIntegrator reduceWarpToShared(value, groupThreadIdx); // First warp in group accumulates values from shared memory. - if (groupThreadIdx < (kGroupSize / kWaveSize)) + if (groupThreadIdx < warpCount) { value = sharedResult[groupThreadIdx]; value = WaveActiveSum(value); @@ -168,15 +180,15 @@ struct BSDFIntegrator ParameterBlock gIntegrator; //[WaveSize(32)] -[numthreads(32, 32, 1)] -void mainIntegration(uint3 threadID : SV_DispatchThreadID, uint groupThreadIdx : SV_GroupIndex, uint3 groupID : SV_GroupID) +[numthreads(1024, 1, 1)] +void mainIntegration(uint groupThreadIdx: SV_GroupIndex, uint3 groupID: SV_GroupID) { - gIntegrator.executeIntegration(threadID.xy, groupThreadIdx, groupID); + gIntegrator.executeIntegration(groupThreadIdx, groupID); } //[WaveSize(32)] [numthreads(256, 1, 1)] -void mainFinal(uint3 threadID : SV_DispatchThreadID) +void mainFinal(uint3 threadID: SV_DispatchThreadID) { gIntegrator.executeFinal(threadID.x, threadID.y); } diff --git a/Source/Falcor/Rendering/Materials/BSDFIntegrator.h b/Source/Falcor/Rendering/Materials/BSDFIntegrator.h index d6b2fab94a..49f745e0af 100644 --- a/Source/Falcor/Rendering/Materials/BSDFIntegrator.h +++ b/Source/Falcor/Rendering/Materials/BSDFIntegrator.h @@ -37,44 +37,44 @@ namespace Falcor { - /** Utility class for BSDF integration. - */ - class FALCOR_API BSDFIntegrator - { - public: - /// Constructor. - BSDFIntegrator(ref pDevice, const ref& pScene); +/** Utility class for BSDF integration. + */ +class FALCOR_API BSDFIntegrator +{ +public: + /// Constructor. + BSDFIntegrator(ref pDevice, const ref& pScene); - /** Integrate the BSDF for a material given a single incident direction. - The BSDF is assumed to be isotropic and is integrated over outgoing directions in the upper hemisphere. - \param[in] pRenderContext The context. - \param[in] materialID The material to integrate. - \param[in] cosTheta Cosine theta angle of incident direction. - \return The integral value. - */ - float3 integrateIsotropic(RenderContext* pRenderContext, const MaterialID materialID, const float cosTheta); + /** Integrate the BSDF for a material given a single incident direction. + The BSDF is assumed to be isotropic and is integrated over outgoing directions in the upper hemisphere. + \param[in] pRenderContext The context. + \param[in] materialID The material to integrate. + \param[in] cosTheta Cosine theta angle of incident direction. + \return The integral value. + */ + float3 integrateIsotropic(RenderContext* pRenderContext, const MaterialID materialID, const float cosTheta); - /** Integrate the BSDF for a material given an array of incident directions. - The BSDF is assumed to be isotropic and is integrated over outgoing directions in the upper hemisphere. - \param[in] pRenderContext The context. - \param[in] materialID The material to integrate. - \param[in] cosThetas Cosine theta angles of incident directions. - \return Array of integral values. - */ - std::vector integrateIsotropic(RenderContext* pRenderContext, const MaterialID materialID, const std::vector& cosThetas); + /** Integrate the BSDF for a material given an array of incident directions. + The BSDF is assumed to be isotropic and is integrated over outgoing directions in the upper hemisphere. + \param[in] pRenderContext The context. + \param[in] materialID The material to integrate. + \param[in] cosThetas Cosine theta angles of incident directions. + \return Array of integral values. + */ + std::vector integrateIsotropic(RenderContext* pRenderContext, const MaterialID materialID, const std::vector& cosThetas); - private: - void integrationPass(RenderContext* pRenderContext, const MaterialID materialID, const uint32_t gridCount) const; - void finalPass(RenderContext* pRenderContext, const uint32_t gridCount) const; +private: + void integrationPass(RenderContext* pRenderContext, const MaterialID materialID, const uint32_t gridCount) const; + void finalPass(RenderContext* pRenderContext, const uint32_t gridCount) const; - ref mpDevice; - ref mpScene; - ref mpIntegrationPass; ///< Integration pass. - ref mpFinalPass; ///< Final reduction pass. - ref mpCosThetaBuffer; ///< Buffer for uploading incident cos theta angles. - ref mpResultBuffer; ///< Buffer for intermediate results. - ref mpFinalResultBuffer; ///< Buffer for final results after reduction. - ref mpStagingBuffer; ///< Staging buffer for readback of final results. - uint32_t mResultCount; ///< Number of intermediate results per integration grid. - }; -} + ref mpDevice; + ref mpScene; + ref mpIntegrationPass; ///< Integration pass. + ref mpFinalPass; ///< Final reduction pass. + ref mpCosThetaBuffer; ///< Buffer for uploading incident cos theta angles. + ref mpResultBuffer; ///< Buffer for intermediate results. + ref mpFinalResultBuffer; ///< Buffer for final results after reduction. + ref mpStagingBuffer; ///< Staging buffer for readback of final results. + uint32_t mResultCount; ///< Number of intermediate results per integration grid. +}; +} // namespace Falcor diff --git a/Source/Falcor/Rendering/Materials/BSDFs/BeerBTDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/BeerBTDF.slang index dfa780fb91..1fa3073f87 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/BeerBTDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/BeerBTDF.slang @@ -33,8 +33,10 @@ __exported import Rendering.Materials.IBSDF; */ struct BeerBTDF : IBSDF { - float3 absorption; ///< Absorption times thickness - float pathshortening; ///< Grazing angle saturation control; typically calculated with an IOR + float3 absorption = {}; ///< Absorption times thickness + float pathshortening = {}; ///< Grazing angle saturation control; typically calculated with an IOR + + __init() {} __init(float3 absorption, float pathshortening) { @@ -87,7 +89,7 @@ struct BeerBTDF : IBSDF RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.f, 0.f); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/DielectricPlateBSDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/DielectricPlateBSDF.slang index 42247ad03d..5767a6d216 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/DielectricPlateBSDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/DielectricPlateBSDF.slang @@ -40,9 +40,9 @@ __exported import Rendering.Materials.IBSDF; */ struct DielectricPlateBSDF : IBSDF { - AnisotropicGGX D; ///< Reflection roughness - AnisotropicGGX DInside; ///< Tranmission roughness - float eta; ///< IOR + AnisotropicGGX D = {}; ///< Reflection roughness + AnisotropicGGX DInside = {}; ///< Tranmission roughness + float eta = {}; ///< IOR float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg, BSDFContext bc) { @@ -206,7 +206,7 @@ struct DielectricPlateBSDF : IBSDF RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = D.alpha; return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/DiffuseSpecularBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/DiffuseSpecularBRDF.slang index d700722c5f..a06a8d2845 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/DiffuseSpecularBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/DiffuseSpecularBRDF.slang @@ -40,9 +40,11 @@ __exported import Scene.Material.DiffuseSpecularData; */ struct DiffuseSpecularBRDF : IBSDF { - float3 diffuse; ///< Diffuse albedo. - float3 specular; ///< Specular albedo. - float roughness; ///< Roughness (linear). + float3 diffuse = {}; ///< Diffuse albedo. + float3 specular = {}; ///< Specular albedo. + float roughness = {}; ///< Roughness (linear). + + __init() {} /** * Initialize using parameters from the Disney parameterization. @@ -142,7 +144,7 @@ struct DiffuseSpecularBRDF : IBSDF RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(roughness); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/DisneyDiffuseBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/DisneyDiffuseBRDF.slang index eec3af44ff..3f73611adc 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/DisneyDiffuseBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/DisneyDiffuseBRDF.slang @@ -59,6 +59,9 @@ struct DisneyDiffuseBRDF : IBSDF, IDifferentiable BSDFContext bc ) { +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); lobeType = (uint)LobeType::DiffuseReflection; @@ -104,7 +107,7 @@ struct DisneyDiffuseBRDF : IBSDF, IDifferentiable RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.5f, 0.5f); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/FrostbiteDiffuseBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/FrostbiteDiffuseBRDF.slang index bd7890dcc2..f23aa21265 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/FrostbiteDiffuseBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/FrostbiteDiffuseBRDF.slang @@ -38,8 +38,8 @@ __exported import Rendering.Materials.IBSDF; */ struct FrostbiteDiffuseBRDF : IBSDF, IDifferentiable { - float3 albedo; ///< Diffuse albedo. - float roughness; ///< Roughness before remapping. + float3 albedo = {}; ///< Diffuse albedo. + float roughness = {}; ///< Roughness before remapping. [Differentiable] float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) @@ -60,6 +60,9 @@ struct FrostbiteDiffuseBRDF : IBSDF, IDifferentiable BSDFContext bc ) { +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); lobeType = (uint)LobeType::DiffuseReflection; @@ -107,7 +110,7 @@ struct FrostbiteDiffuseBRDF : IBSDF, IDifferentiable RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.5f, 0.5f); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBRDF.slang index 72af6a25c1..83d00672dd 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBRDF.slang @@ -36,7 +36,9 @@ __exported import Rendering.Materials.IBSDF; */ struct LambertDiffuseBRDF : IBSDF, IDifferentiable { - float3 albedo; ///< Diffuse albedo. + float3 albedo = {}; ///< Diffuse albedo. + + __init() {} [Differentiable] __init(float3 albedo) { this.albedo = albedo; } @@ -60,6 +62,9 @@ struct LambertDiffuseBRDF : IBSDF, IDifferentiable BSDFContext bc ) { +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); lobeType = (uint)LobeType::DiffuseReflection; @@ -90,7 +95,7 @@ struct LambertDiffuseBRDF : IBSDF, IDifferentiable RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.5f, 0.5f); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBTDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBTDF.slang index ec3293bab1..ac08162c91 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBTDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBTDF.slang @@ -30,12 +30,13 @@ import Utils.Math.MathHelpers; __exported import Rendering.Materials.IBSDF; + /** * Lambertian diffuse transmission. */ struct LambertDiffuseBTDF : IBSDF, IDifferentiable { - float3 albedo; ///< Diffuse albedo. + float3 albedo = {}; ///< Diffuse albedo. [Differentiable] float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) @@ -56,6 +57,9 @@ struct LambertDiffuseBTDF : IBSDF, IDifferentiable BSDFContext bc ) { +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); wo.z = -wo.z; lobeType = (uint)LobeType::DiffuseTransmission; @@ -87,7 +91,7 @@ struct LambertDiffuseBTDF : IBSDF, IDifferentiable RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.5f, 0.5f); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/OrenNayarBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/OrenNayarBRDF.slang index d6179be37f..066d4a8d2a 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/OrenNayarBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/OrenNayarBRDF.slang @@ -36,8 +36,10 @@ __exported import Rendering.Materials.IBSDF; */ struct OrenNayarBRDF : IBSDF { - float3 albedo; ///< Diffuse albedo. - float roughness; ///< Rougness + float3 albedo = {}; ///< Diffuse albedo. + float roughness = {}; ///< Rougness + + __init() {} __init(float3 albedo, float roughness) { @@ -121,7 +123,7 @@ struct OrenNayarBRDF : IBSDF RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.5f, 0.5f); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang index 48b2fa074e..62d49b2587 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang @@ -36,8 +36,10 @@ __exported import Rendering.Materials.IBSDF; */ struct SheenBSDF : IBSDF { - float3 color; ///< Sheen reflectivity - float roughness; ///< Surface roughness (max 1.0) + float3 color = {}; ///< Sheen reflectivity + float roughness = {}; ///< Surface roughness (max 1.0) + + __init() {} __init(float3 color, float roughness) { @@ -188,7 +190,7 @@ struct SheenBSDF : IBSDF RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.5f, 0.5f); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/SimpleBTDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/SimpleBTDF.slang index 688fde21cb..6bf3f245be 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/SimpleBTDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/SimpleBTDF.slang @@ -33,7 +33,9 @@ __exported import Rendering.Materials.IBSDF; */ struct SimpleBTDF : IBSDF { - float3 transmittance; ///< Transmittance colour (i.e. tint) + float3 transmittance = {}; ///< Transmittance colour (i.e. tint) + + __init() {} __init(float3 transmittance) { this.transmittance = transmittance; } @@ -76,7 +78,7 @@ struct SimpleBTDF : IBSDF RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.f, 0.f); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/SpecularMicrofacet.slang b/Source/Falcor/Rendering/Materials/BSDFs/SpecularMicrofacet.slang index 7b39fe222d..fc3c0ba14b 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/SpecularMicrofacet.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/SpecularMicrofacet.slang @@ -48,9 +48,9 @@ __exported import Rendering.Materials.IBSDF; */ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable { - float3 albedo; ///< Specular albedo. - float alpha; ///< GGX width parameter. - uint activeLobes; ///< BSDF lobes to include for sampling and evaluation. See LobeType.slang. + float3 albedo = {}; ///< Specular albedo. + float alpha = {}; ///< GGX width parameter. + uint activeLobes = {}; ///< BSDF lobes to include for sampling and evaluation. See LobeType.slang. bool hasLobe(LobeType lobeType) { return (activeLobes & (uint)lobeType) != 0; } @@ -101,6 +101,10 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable if (wi.z < kMinCosTheta) return false; +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif + #if EnableDeltaBSDF // Handle delta reflection. if (alpha == 0.f) @@ -108,6 +112,10 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable if (!hasLobe(LobeType::DeltaReflection)) return false; +#if PAD_RANDOM_NUMBERS + sampleNext2D(sg); +#endif + wo = float3(-wi.x, -wi.y, wi.z); pdf = 0.f; weight = evalFresnelSchlick(albedo, 1.f, wi.z); @@ -134,8 +142,8 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable #if ExplicitSampleWeights // For testing. - pdf = evalPdf(wi, wo); - weight = eval(wi, wo, sg) / pdf; + pdf = evalPdf(wi, wo, bc); + weight = eval(wi, wo, sg, bc) / pdf; lobeType = (uint)LobeType::SpecularReflection; return true; #endif @@ -194,7 +202,7 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(alpha); return r; } @@ -205,10 +213,10 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable */ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable { - float3 transmissionAlbedo; ///< Transmission albedo. - float alpha; ///< GGX width parameter. - float eta; ///< Relative index of refraction (etaI / etaT). - uint activeLobes; ///< BSDF lobes to include for sampling and evaluation. See LobeType.slang. + float3 transmissionAlbedo = {}; ///< Transmission albedo. + float alpha = {}; ///< GGX width parameter. + float eta = {}; ///< Relative index of refraction (etaI / etaT). + uint activeLobes = {}; ///< BSDF lobes to include for sampling and evaluation. See LobeType.slang. bool hasLobe(LobeType lobeType) { return (activeLobes & (uint)lobeType) != 0; } @@ -288,6 +296,10 @@ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable if (!(hasReflection || hasTransmission)) return false; +#if PAD_RANDOM_NUMBERS + sampleNext2D(sg); +#endif + float cosThetaT; float F = evalFresnelDielectric(eta, wi.z, cosThetaT); @@ -302,7 +314,7 @@ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable } pdf = 0.f; - weight = isReflection ? float3(1.f) : transmissionAlbedo; + weight = isReflection ? float3(1.f) : transmissionAlbedo * eta * eta; if (!(hasReflection && hasTransmission)) weight *= float3(isReflection ? F : 1.f - F); wo = isReflection ? float3(-wi.x, -wi.y, wi.z) : float3(-wi.x * eta, -wi.y * eta, -cosThetaT); @@ -354,8 +366,8 @@ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable #if ExplicitSampleWeights // For testing. - pdf = evalPdf(wi, wo); - weight = pdf > 0.f ? eval(wi, wo, sg) / pdf : float3(0.f); + pdf = evalPdf(wi, wo, bc); + weight = pdf > 0.f ? eval(wi, wo, sg, bc) / pdf : float3(0.f); return true; #endif @@ -464,7 +476,7 @@ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(alpha); return r; } diff --git a/Source/Falcor/Rendering/Materials/BSDFs/StandardBSDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/StandardBSDF.slang index d686cd6eb4..66a4a7cbad 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/StandardBSDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/StandardBSDF.slang @@ -58,18 +58,21 @@ static const float kMinGGXAlpha = 0.0064f; */ struct StandardBSDFData : IDifferentiable { - float3 diffuse; ///< Diffuse albedo. - float3 specular; ///< Specular albedo. - float roughness; ///< This is the original roughness, before remapping. - float metallic; ///< Metallic parameter, blends between dielectric and conducting BSDFs. - float eta; ///< Relative index of refraction (incident IoR / transmissive IoR). - float3 transmission; ///< Transmission color. - float diffuseTransmission; ///< Diffuse transmission, blends between diffuse reflection and transmission lobes. - float specularTransmission; ///< Specular transmission, blends between opaque dielectric BRDF and specular transmissive BSDF. - float3 volumeScattering; ///< Volume scattering parameters from the volume. - float volumeAnsiotropy; ///< Mean cosine of the volumetric phase function. - bool hasEntryPointVolumeProperties; ///< Flag that specifies from where sigmaA is taken from (volume or surface). - bool hasSigmaSGreaterZero; ///< Flag that indicates if we have to do albedo conversion + float3 diffuse = {}; ///< Diffuse albedo. + float3 specular = {}; ///< Specular albedo. + float roughness = {}; ///< This is the original roughness, before remapping. + float metallic = {}; ///< Metallic parameter, blends between dielectric and conducting BSDFs. + float eta = {}; ///< Relative index of refraction (incident IoR / transmissive IoR). + float3 transmission = {}; ///< Transmission color. + float diffuseTransmission = {}; ///< Diffuse transmission, blends between diffuse reflection and transmission lobes. + float specularTransmission = {}; ///< Specular transmission, blends between opaque dielectric BRDF and specular transmissive BSDF. + float3 volumeScattering = {}; ///< Volume scattering parameters from the volume. + float volumeAnsiotropy = {}; ///< Mean cosine of the volumetric phase function. + bool hasEntryPointVolumeProperties = {}; ///< Flag that specifies from where sigmaA is taken from (volume or surface). + bool hasSigmaSGreaterZero = {}; ///< Flag that indicates if we have to do albedo conversion + + [Differentiable] + __init() {} [Differentiable] __init(const DiffMaterialData diffData) @@ -95,25 +98,27 @@ struct StandardBSDFData : IDifferentiable struct StandardBSDF : IBSDF, IDifferentiable { #if DiffuseBrdf == DiffuseBrdfLambert - LambertDiffuseBRDF diffuseReflection; + LambertDiffuseBRDF diffuseReflection = {}; #elif DiffuseBrdf == DiffuseBrdfDisney - DisneyDiffuseBRDF diffuseReflection; + DisneyDiffuseBRDF diffuseReflection = {}; #elif DiffuseBrdf == DiffuseBrdfFrostbite - FrostbiteDiffuseBRDF diffuseReflection; + FrostbiteDiffuseBRDF diffuseReflection = {}; #endif - LambertDiffuseBTDF diffuseTransmission; + LambertDiffuseBTDF diffuseTransmission = {}; - SpecularMicrofacetBRDF specularReflection; - SpecularMicrofacetBSDF specularTransmission; + SpecularMicrofacetBRDF specularReflection = {}; + SpecularMicrofacetBSDF specularTransmission = {}; - float diffTrans; ///< Mix between diffuse BRDF and diffuse BTDF. - float specTrans; ///< Mix between dielectric BRDF and specular BSDF. + float diffTrans = {}; ///< Mix between diffuse BRDF and diffuse BTDF. + float specTrans = {}; ///< Mix between dielectric BRDF and specular BSDF. // No need to differentiate probabilities. - no_diff float pDiffuseReflection; ///< Probability for sampling the diffuse BRDF. - no_diff float pDiffuseTransmission; ///< Probability for sampling the diffuse BTDF. - no_diff float pSpecularReflection; ///< Probability for sampling the specular BRDF. - no_diff float pSpecularTransmission; ///< Probability for sampling the specular BSDF. + no_diff float pDiffuseReflection = {}; ///< Probability for sampling the diffuse BRDF. + no_diff float pDiffuseTransmission = {}; ///< Probability for sampling the diffuse BTDF. + no_diff float pSpecularReflection = {}; ///< Probability for sampling the specular BRDF. + no_diff float pSpecularTransmission = {}; ///< Probability for sampling the specular BSDF. + + int BSDFIndex; /** * Initialize a new instance. @@ -124,6 +129,9 @@ struct StandardBSDF : IBSDF, IDifferentiable [Differentiable] __init(const float3 wi, const MaterialHeader mtl, const StandardBSDFData data) { + // For ReSTIR PT + BSDFIndex = mtl.getBSDFIndex(); + // TODO: Currently specular reflection and transmission lobes are not properly separated. // This leads to incorrect behaviour if only the specular reflection or transmission lobe is selected. // Things work fine as long as both or none are selected. @@ -227,14 +235,18 @@ struct StandardBSDF : IBSDF, IDifferentiable [Differentiable] float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { + uint mask; + if (BSDFIndex == -1) mask = 0xf; // by default, compute pdf for all lobes + else mask = 1 << BSDFIndex; + float3 result = 0.f; - if (pDiffuseReflection > 0.f) + if (pDiffuseReflection > 0.f && (mask & 0x1)) result += (1.f - specTrans) * (1.f - diffTrans) * diffuseReflection.eval(wi, wo, sg, bc); - if (pDiffuseTransmission > 0.f) + if (pDiffuseTransmission > 0.f && (mask & 0x2)) result += (1.f - specTrans) * diffTrans * diffuseTransmission.eval(wi, wo, sg, bc); - if (pSpecularReflection > 0.f) + if (pSpecularReflection > 0.f && (mask & 0x4)) result += (1.f - specTrans) * specularReflection.eval(wi, wo, sg, bc); - if (pSpecularTransmission > 0.f) + if (pSpecularTransmission > 0.f && (mask & 0x8)) result += specTrans * (specularTransmission.eval(wi, wo, sg, bc)); return result; } @@ -248,12 +260,28 @@ struct StandardBSDF : IBSDF, IDifferentiable inout S sg, BSDFContext bc ) + { + uint BSDFIndex; + return sample(wi, wo, pdf, weight, lobeType, BSDFIndex, sg, bc); + } + + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + out uint BSDFIndex, + inout S sg, + BSDFContext bc + ) { // Default initialization to avoid divergence at returns. wo = {}; weight = {}; pdf = 0.f; lobeType = (uint)LobeType::DiffuseReflection; + BSDFIndex = 0; bool valid = false; float uSelect = sampleNext1D(sg); @@ -271,6 +299,7 @@ struct StandardBSDF : IBSDF, IDifferentiable pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo, bc); if (pSpecularTransmission > 0.f) pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); + BSDFIndex = 0; } else if (uSelect < pDiffuseReflection + pDiffuseTransmission) { @@ -281,7 +310,8 @@ struct StandardBSDF : IBSDF, IDifferentiable // if (pDiffuseReflection > 0.f) pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo, bc); // if (pSpecularReflection > 0.f) pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo, bc); if (pSpecularTransmission > 0.f) - pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); + pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); + BSDFIndex = 1; } else if (uSelect < pDiffuseReflection + pDiffuseTransmission + pSpecularReflection) { @@ -294,6 +324,7 @@ struct StandardBSDF : IBSDF, IDifferentiable // if (pDiffuseTransmission > 0.f) pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo, bc); if (pSpecularTransmission > 0.f) pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); + BSDFIndex = 2; } else if (pSpecularTransmission > 0.f) { @@ -307,6 +338,7 @@ struct StandardBSDF : IBSDF, IDifferentiable pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo, bc); if (pSpecularReflection > 0.f) pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo, bc); + BSDFIndex = 3; } return valid; @@ -315,13 +347,16 @@ struct StandardBSDF : IBSDF, IDifferentiable float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { float pdf = 0.f; - if (pDiffuseReflection > 0.f) + uint mask; + if (BSDFIndex == -1) mask = 0xf; // by default, compute pdf for all lobes + else mask = 1 << BSDFIndex; + if (pDiffuseReflection > 0.f && (mask & 0x1)) pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo, bc); - if (pDiffuseTransmission > 0.f) + if (pDiffuseTransmission > 0.f && (mask & 0x2)) pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo, bc); - if (pSpecularReflection > 0.f) + if (pSpecularReflection > 0.f && (mask & 0x4)) pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo, bc); - if (pSpecularTransmission > 0.f) + if (pSpecularTransmission > 0.f && (mask & 0x8)) pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); return pdf; } @@ -346,9 +381,20 @@ struct StandardBSDF : IBSDF, IDifferentiable RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; float alpha = specularReflection.alpha; r.roughnessBSDFNotation = float2(alpha < kMinGGXAlpha ? 0.f : alpha); return r; } + + float2 getReflectionMotionVectorProbabilities(const float3 wi) + { + float F = 0.f; + if (pSpecularTransmission > 0.f) + { + float cosThetaT; + F = evalFresnelDielectric(specularTransmission.eta, wi.z, cosThetaT); + } + return float2(pDiffuseReflection, pSpecularReflection + pSpecularTransmission * F); + } }; diff --git a/Source/Falcor/Rendering/Materials/ClothBRDF.slang b/Source/Falcor/Rendering/Materials/ClothBRDF.slang index a01093dadb..302e1aa020 100644 --- a/Source/Falcor/Rendering/Materials/ClothBRDF.slang +++ b/Source/Falcor/Rendering/Materials/ClothBRDF.slang @@ -33,7 +33,7 @@ __exported import Rendering.Materials.IBSDF; // Uncomment to enable subsurface scattering approximation. // Note we need framework support for supplying a subsurface color before it's useful. -//#define MATERIAL_HAS_SUBSURFACE_COLOR +// #define MATERIAL_HAS_SUBSURFACE_COLOR /** Cloth BRDF based on: - Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886". @@ -42,21 +42,30 @@ __exported import Rendering.Materials.IBSDF; */ struct ClothBRDF : IBSDF { - float roughness; ///< Linear roughness. - float3 f0; ///< Specular reflectance at normal incidence. - float3 diffuseColor; ///< Diffuse albedo. - float3 subsurfaceColor; ///< Subsurface color. + float roughness; ///< Linear roughness. + float3 f0; ///< Specular reflectance at normal incidence. + float3 diffuseColor; ///< Diffuse albedo. + float3 subsurfaceColor; ///< Subsurface color. // Implementation of IBSDF interface float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { - if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); + if (min(wi.z, wo.z) < kMinCosTheta) + return float3(0.f); return evalWeight(wi, wo) * M_1_PI * wo.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); lobeType = (uint)LobeType::DiffuseReflection; @@ -73,7 +82,8 @@ struct ClothBRDF : IBSDF float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { - if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; + if (min(wi.z, wo.z) < kMinCosTheta) + return 0.f; return M_1_PI * wo.z; } @@ -84,14 +94,11 @@ struct ClothBRDF : IBSDF return AlbedoContributions(diffuseColor, 1.f - diffuseColor, 0.f, 0.f); } - float3 getIorAsReflectance() - { - return float3(1.f); - } + float3 getIorAsReflectance() { return float3(1.f); } RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = (roughness + 1.f) * 0.5f; return r; } @@ -153,20 +160,11 @@ struct ClothBRDF : IBSDF /** Microfacet visibility function for cloth. Based on Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886". */ - float V_Neubelt(float NoI, float NoO) - { - return 1.f / (4.f * (NoO + NoI - NoO * NoI)); - } + float V_Neubelt(float NoI, float NoO) { return 1.f / (4.f * (NoO + NoI - NoO * NoI)); } - float diffuseLambert() - { - return M_1_PI; - } + float diffuseLambert() { return M_1_PI; } /** Energy conserving wrapped diffuse term, does *not* include the divide by pi. - */ - float diffuseWrapped(float NoO, float w) - { - return saturate((NoO + w) / ((1.f + w) * (1.f + w))); - } + */ + float diffuseWrapped(float NoO, float w) { return saturate((NoO + w) / ((1.f + w) * (1.f + w))); } }; diff --git a/Source/Falcor/Rendering/Materials/ClothMaterial.slang b/Source/Falcor/Rendering/Materials/ClothMaterial.slang index 3608c3217e..2e359fe1b2 100644 --- a/Source/Falcor/Rendering/Materials/ClothMaterial.slang +++ b/Source/Falcor/Rendering/Materials/ClothMaterial.slang @@ -58,9 +58,9 @@ struct ClothMaterial : MaterialBase, IMaterial float F0 = f * f; brdf.diffuseColor = baseColor; - brdf.subsurfaceColor = float3(0.5f); // TODO: Add material parameter for this. - brdf.roughness = spec.g; // TODO: Linear or squared roughness? - brdf.f0 = float3(F0); // TODO: What's appropriate here? + brdf.subsurfaceColor = float3(0.5f); // TODO: Add material parameter for this. + brdf.roughness = spec.g; // TODO: Linear or squared roughness? + brdf.f0 = float3(F0); // TODO: What's appropriate here? #if defined(SCENE_DIFFUSE_ALBEDO_MULTIPLIER) // DEMO21 @@ -87,7 +87,13 @@ struct ClothMaterial : MaterialBase, IMaterial } [Differentiable] - ClothMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + ClothMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/ClothMaterialInstance.slang b/Source/Falcor/Rendering/Materials/ClothMaterialInstance.slang index 024f91577f..d6c90b80d6 100644 --- a/Source/Falcor/Rendering/Materials/ClothMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/ClothMaterialInstance.slang @@ -32,10 +32,10 @@ __exported import Rendering.Materials.ClothBRDF; import Utils.Math.MathHelpers; /** Implementation of a simple cloth BSDF. -*/ + */ struct ClothMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. ClothBRDF brdf; __init(const ShadingFrame sf, const ClothBRDF brdf) @@ -49,7 +49,8 @@ struct ClothMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); return brdf.eval(wiLocal, woLocal, sg, BSDFContext()); } @@ -65,10 +66,7 @@ struct ClothMaterialInstance : MaterialInstanceBase, IMaterialInstance return sampleReference(sd, sg, result); } - float evalPdf(const ShadingData sd, const float3 wo, bool useImportanceSampling = true) - { - return evalPdfReference(sd, wo); - } + float evalPdf(const ShadingData sd, const float3 wo, bool useImportanceSampling = true) { return evalPdfReference(sd, wo); } BSDFProperties getProperties(const ShadingData sd) { @@ -87,11 +85,7 @@ struct ClothMaterialInstance : MaterialInstanceBase, IMaterialInstance return p; } - uint getLobeTypes(const ShadingData sd) - { - return (uint)LobeType::DiffuseReflection; - } - + uint getLobeTypes(const ShadingData sd) { return (uint)LobeType::DiffuseReflection; } // Additional functions @@ -108,7 +102,8 @@ struct ClothMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 woLocal = sample_cosine_hemisphere_concentric(sampleNext2D(sg), result.pdf); // pdf = cos(theta) / pi result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) + return false; result.weight = brdf.eval(wiLocal, woLocal, sg, BSDFContext()) / result.pdf; result.lobeType = (uint)LobeType::DiffuseReflection; @@ -126,7 +121,8 @@ struct ClothMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return 0.f; return woLocal.z * M_1_PI; // pdf = cos(theta) / pi } diff --git a/Source/Falcor/Rendering/Materials/Fresnel.slang b/Source/Falcor/Rendering/Materials/Fresnel.slang index f4ebcc3a0e..00695a264d 100644 --- a/Source/Falcor/Rendering/Materials/Fresnel.slang +++ b/Source/Falcor/Rendering/Materials/Fresnel.slang @@ -122,12 +122,11 @@ float evalFresnelConductor(float eta, float k, float cosThetaI) float sinThetaIQu = sinThetaISq * sinThetaISq; float innerTerm = eta * eta - k * k - sinThetaISq; - float aSqPlusBSq = sqrt(max(innerTerm*innerTerm + 4.0f * eta * eta * k * k, 0.0f)); + float aSqPlusBSq = sqrt(max(innerTerm * innerTerm + 4.0f * eta * eta * k * k, 0.0f)); float a = sqrt(max((aSqPlusBSq + innerTerm) * 0.5f, 0.0f)); - float Rs = ((aSqPlusBSq + cosThetaISq) - (2.0f * a * cosThetaI))/ - ((aSqPlusBSq + cosThetaISq) + (2.0f * a * cosThetaI)); - float Rp = ((cosThetaISq * aSqPlusBSq + sinThetaIQu) - (2.0f * a * cosThetaI * sinThetaISq))/ + float Rs = ((aSqPlusBSq + cosThetaISq) - (2.0f * a * cosThetaI)) / ((aSqPlusBSq + cosThetaISq) + (2.0f * a * cosThetaI)); + float Rp = ((cosThetaISq * aSqPlusBSq + sinThetaIQu) - (2.0f * a * cosThetaI * sinThetaISq)) / ((cosThetaISq * aSqPlusBSq + sinThetaIQu) + (2.0f * a * cosThetaI * sinThetaISq)); return 0.5f * (Rs + Rs * Rp); @@ -163,4 +162,3 @@ float reflectanceToIOR(float3 reflectance) return (-iorI - 2.f * iorI * sqrt(r) - iorI * r) / (r - 1.f); } - diff --git a/Source/Falcor/Rendering/Materials/HairChiang16.slang b/Source/Falcor/Rendering/Materials/HairChiang16.slang index 25c2b20786..73715f2e3f 100644 --- a/Source/Falcor/Rendering/Materials/HairChiang16.slang +++ b/Source/Falcor/Rendering/Materials/HairChiang16.slang @@ -35,19 +35,19 @@ __exported import Rendering.Materials.IBSDF; // Enable explicitly computing sampling weights using eval(wo, wi) / evalPdf(wo, wi). // This is for testing only, as many terms of the equation cancel out allowing to save on computation. -#define USE_BCSDF_IMPORTANCE_SAMPLING 1 +#define USE_BCSDF_IMPORTANCE_SAMPLING 1 /** BSDF parameters for the hair BSDF. These are needed for initializing a `HairChiang16` instance. */ struct HairChiang16Data { - float3 baseColor; ///< Base color. + float3 baseColor; ///< Base color. float betaM; float betaN; float alpha; - float IoR; ///< Index of refraction of the medium "behind" the surface. - float eta; ///< Relative index of refraction (incident IoR / transmissive IoR). + float IoR; ///< Index of refraction of the medium "behind" the surface. + float eta; ///< Relative index of refraction (incident IoR / transmissive IoR). } /** Hair BCSDF from "A Practical and Controllable Hair and Fur Model for Production Path Tracing", Chiang et al. 2016. @@ -71,7 +71,8 @@ struct HairChiang16 : IBSDF float s; float sin2kAlpha[3], cos2kAlpha[3]; - [mutating] void precompute() + [mutating] + void precompute() { gammaI = asin(clamp(h, -1.f, 1.f)); @@ -80,7 +81,8 @@ struct HairChiang16 : IBSDF v[1] = 0.25f * v[0]; v[2] = 4 * v[0]; [unroll] - for (uint p = 3; p <= kMaxScatterEvents; p++) v[p] = v[2]; + for (uint p = 3; p <= kMaxScatterEvents; p++) + v[p] = v[2]; // Compute azimuthal logistic scale factor s = kSqrtPiOver8 * (0.265f * betaN + 1.194f * betaN * betaN + 5.372f * pow(betaN, 22.f)); @@ -110,7 +112,7 @@ struct HairChiang16 : IBSDF eta = data.eta; // Compute offset h azimuthally with the unit circle cross section. - float3 wiProj = normalize(sd.V - dot(sd.V, sd.frame.T) * sd.frame.T); // Project wi to the (B, N) plane. + float3 wiProj = normalize(sd.V - dot(sd.V, sd.frame.T) * sd.frame.T); // Project wi to the (B, N) plane. float3 wiProjPerp = cross(wiProj, sd.frame.T); h = dot(sd.frame.N, wiProjPerp); @@ -182,7 +184,15 @@ struct HairChiang16 : IBSDF return result; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { #if !USE_BCSDF_IMPORTANCE_SAMPLING wo = sample_sphere(sampleNext2D(sg)); @@ -221,7 +231,8 @@ struct HairChiang16 : IBSDF p = i + 1; vp = v[i + 1]; } - else done = true; + else + done = true; } float sinThetaIp, cosThetaIp; @@ -387,14 +398,11 @@ struct HairChiang16 : IBSDF return AlbedoContributions(1.f, 0.f, 0.f, 0.f); } - float3 getIorAsReflectance() - { - return float3(1.f); - } + float3 getIorAsReflectance() { return float3(1.f); } RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.5f); return r; } @@ -402,7 +410,7 @@ struct HairChiang16 : IBSDF // private /** Attenuation function Ap. - */ + */ void Ap(float cosThetaI, float3 T, out float3 ap[kMaxScatterEvents + 1]) { float cosGammaI = sqrt(max(0.f, 1.f - h * h)); @@ -412,14 +420,15 @@ struct HairChiang16 : IBSDF ap[0] = float3(f); ap[1] = T * (1 - f) * (1 - f); [unroll] - for (uint p = 2; p < kMaxScatterEvents; p++) ap[p] = ap[p - 1] * T * f; + for (uint p = 2; p < kMaxScatterEvents; p++) + ap[p] = ap[p - 1] * T * f; // Compute attenuation term accounting for remaining orders of scattering. ap[kMaxScatterEvents] = ap[kMaxScatterEvents - 1] * T * f / (float3(1.f) - T * f); } /** Compute a discrete pdf for sampling Ap (which BCSDF lobe). - */ + */ void computeApPdf(float cosThetaI, out float apPdf[kMaxScatterEvents + 1]) { float sinThetaI = sqrt(max(0.f, 1.f - cosThetaI * cosThetaI)); @@ -450,7 +459,8 @@ struct HairChiang16 : IBSDF float invSumY = 1.f / sumY; [unroll] - for (uint p = 0; p <= kMaxScatterEvents; p++) apPdf[p] *= invSumY; + for (uint p = 0; p <= kMaxScatterEvents; p++) + apPdf[p] *= invSumY; } }; @@ -459,7 +469,7 @@ struct HairChiang16 : IBSDF *******************************************************************/ /** Longitudinal scattering function Mp. -*/ + */ float Mp(float cosThetaI, float cosThetaO, float sinThetaI, float sinThetaO, float v) { float a = cosThetaI * cosThetaO / v; @@ -478,7 +488,8 @@ float I0(float x) [unroll] for (uint i = 0; i < 10; i++) { - if (i > 1) ifact *= i; + if (i > 1) + ifact *= i; val += x2i / (ifact * ifact * i4); x2i *= x * x; i4 *= 4; @@ -499,15 +510,17 @@ float logI0(float x) } /** Azimuthal scattering function Np. -*/ + */ float Np(float phi, int p, float s, float gammaI, float gammaT) { float dphi = phi - phiFunction(p, gammaI, gammaT); // Remap dphi to [-pi, pi]. dphi = fmod(dphi, M_2PI); - if (dphi > M_PI) dphi -= M_2PI; - if (dphi < -M_PI) dphi += M_2PI; + if (dphi > M_PI) + dphi -= M_2PI; + if (dphi < -M_PI) + dphi += M_2PI; return trimmedLogistic(dphi, s, -M_PI, M_PI); } @@ -542,7 +555,7 @@ float sampleTrimmedLogistic(float u, float s, float a, float b) } /** Mapping from color to sigmaA. -*/ + */ float3 sigmaAFromColor(float3 color, float betaN) { float tmp = 5.969f - 0.215f * betaN + 2.532f * betaN * betaN - 10.73f * pow(betaN, 3) + 5.574f * pow(betaN, 4) + 0.245f * pow(betaN, 5); diff --git a/Source/Falcor/Rendering/Materials/HairMaterial.slang b/Source/Falcor/Rendering/Materials/HairMaterial.slang index c59e8da71c..8b1c7e4b96 100644 --- a/Source/Falcor/Rendering/Materials/HairMaterial.slang +++ b/Source/Falcor/Rendering/Materials/HairMaterial.slang @@ -71,7 +71,13 @@ struct HairMaterial : MaterialBase, IMaterial } [Differentiable] - HairMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + HairMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/HairMaterialInstance.slang b/Source/Falcor/Rendering/Materials/HairMaterialInstance.slang index 385ea82c8b..54541f9c9c 100644 --- a/Source/Falcor/Rendering/Materials/HairMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/HairMaterialInstance.slang @@ -42,7 +42,7 @@ import Utils.Math.MathHelpers; */ struct HairMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. HairChiang16Data data; __init(const ShadingFrame sf, const HairChiang16Data data) diff --git a/Source/Falcor/Rendering/Materials/IBSDF.slang b/Source/Falcor/Rendering/Materials/IBSDF.slang index e6e28915c3..a0c429e011 100644 --- a/Source/Falcor/Rendering/Materials/IBSDF.slang +++ b/Source/Falcor/Rendering/Materials/IBSDF.slang @@ -46,13 +46,13 @@ static const float kMinCosTheta = 1e-6f; */ /** Albedo is split into four components. They have to sum up to 1.0. -*/ + */ struct AlbedoContributions { - float3 reflection; ///< Energy reflected into upper hemisphere - float3 absorptionR; ///< Energy absorbed upper hemisphere - float3 transmission; ///< Energy transmitted into upper hemisphere - float3 absorptionT; ///< Energy absorbed lower hemisphere + float3 reflection; ///< Energy reflected into upper hemisphere + float3 absorptionR; ///< Energy absorbed upper hemisphere + float3 transmission; ///< Energy transmitted into upper hemisphere + float3 absorptionT; ///< Energy absorbed lower hemisphere __init(float3 r, float3 ar, float3 t, float3 at) { @@ -65,9 +65,9 @@ struct AlbedoContributions struct BSDFContext { - float iorI; ///< IOR from incidence medium - float iorT; ///< IOR trom transmission medium - bool inited; ///< Flag to indicate if the struct was initialized + float iorI; ///< IOR from incidence medium + float iorT; ///< IOR trom transmission medium + bool inited; ///< Flag to indicate if the struct was initialized __init(float iorI_, float iorT_) { @@ -84,34 +84,26 @@ struct BSDFContext } } -AlbedoContributions operator+(const AlbedoContributions a, const AlbedoContributions b) +AlbedoContributions operator +(const AlbedoContributions a, const AlbedoContributions b) { return AlbedoContributions( - a.reflection + b.reflection, - a.absorptionR + b.absorptionR, - a.transmission + b.transmission, - a.absorptionT + b.absorptionT ); + a.reflection + b.reflection, a.absorptionR + b.absorptionR, a.transmission + b.transmission, a.absorptionT + b.absorptionT + ); } AlbedoContributions operator*(const AlbedoContributions a, const AlbedoContributions b) { return AlbedoContributions( - a.reflection * b.reflection, - a.absorptionR * b.absorptionR, - a.transmission * b.transmission, - a.absorptionT * b.absorptionT); + a.reflection * b.reflection, a.absorptionR * b.absorptionR, a.transmission * b.transmission, a.absorptionT * b.absorptionT + ); } AlbedoContributions operator*(const float f, const AlbedoContributions b) { - return AlbedoContributions( - f * b.reflection, - f * b.absorptionR, - f * b.transmission, - f * b.absorptionT); + return AlbedoContributions(f * b.reflection, f * b.absorptionR, f * b.transmission, f * b.absorptionT); } -AlbedoContributions operator+=(AlbedoContributions a, const AlbedoContributions b) +AlbedoContributions operator +=(AlbedoContributions a, const AlbedoContributions b) { a.reflection += b.reflection; a.absorptionR += b.absorptionR; @@ -123,8 +115,8 @@ AlbedoContributions operator+=(AlbedoContributions a, const AlbedoContributions struct RoughnessInformation { - float2 roughnessBSDFNotation; ///< This is the (mostly meaningless) non-universal roughness of the BSDF - float3 scratch; + float2 roughnessBSDFNotation; ///< This is the (mostly meaningless) non-universal roughness of the BSDF + float3 scratch; __init() { @@ -153,7 +145,15 @@ interface IBSDF \param[in,out] sg Sample generator. \return Returns true if successful. */ - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc); + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ); /** Evaluates the directional pdf for sampling outgoing direction wo. \param[in] wi Incident direction. diff --git a/Source/Falcor/Rendering/Materials/IMaterial.slang b/Source/Falcor/Rendering/Materials/IMaterial.slang index 7a3d3c359e..c03d221b77 100644 --- a/Source/Falcor/Rendering/Materials/IMaterial.slang +++ b/Source/Falcor/Rendering/Materials/IMaterial.slang @@ -63,13 +63,24 @@ interface IMaterial \param[in] hints Optional hints to control the behavior of the material instance. Combination of `MaterialInstanceHints` flags. \return MaterialInstance implementing the `IMaterialInstance` interface. */ - MaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints = (uint)MaterialInstanceHints::None); + MaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints = (uint)MaterialInstanceHints::None + ); /** A differentiable version of `setupMaterialInstance`. This operation returns an additional `DiffMaterialData` structure that contains differentiable parameters. */ [Differentiable] - MaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints = (uint)MaterialInstanceHints::None); + MaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints = (uint)MaterialInstanceHints::None + ); /** Returns volume properties that are homogeneous for the whole volume (do not depend on the hitpoint). */ diff --git a/Source/Falcor/Rendering/Materials/IMaterialInstance.slang b/Source/Falcor/Rendering/Materials/IMaterialInstance.slang index 8385960a24..16b4f1da24 100644 --- a/Source/Falcor/Rendering/Materials/IMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/IMaterialInstance.slang @@ -38,17 +38,41 @@ import Rendering.Volumes.PhaseFunction; import Scene.Material.VolumeProperties; /** Describes a BSDF sample. -*/ + */ struct BSDFSample { - float3 wo; ///< Sampled direction in world space (normalized). - float pdf; ///< pdf with respect to solid angle for the sampled direction (wo). - float3 weight; ///< Sample weight f(wi, wo) * dot(wo, n) / pdf(wo). - uint lobeType; ///< Sampled lobeType. This is a combination of LobeType flags (see LobeType.slang). + float3 wo = {}; ///< Sampled direction in world space (normalized). + float pdf = {}; ///< pdf with respect to solid angle for the sampled direction (wo). + float3 weight = {}; ///< Sample weight f(wi, wo) * dot(wo, n) / pdf(wo). + uint lobeTypeAndBSDFIndex = {}; ///< Lower 16 bits store Sampled lobeType. This is a combination of LobeType flags (see LobeType.slang). + /// The upper 16 bits of lobeTypeAndBSDFIndex store the BSDF index for materials that support BSDF indexing. + + bool isLobe(LobeType type) { return (lobeTypeAndBSDFIndex & uint(type)) != 0; } + + property lobeType : uint + { + get + { + return lobeTypeAndBSDFIndex & 0xFFFF; + } - bool isLobe(LobeType type) + set + { + lobeTypeAndBSDFIndex = (lobeTypeAndBSDFIndex & 0xFFFF0000) | (newValue & 0x0000FFFF); + } + } + + property BSDFIndex : uint { - return (lobeType & uint(type)) != 0; + get + { + return lobeTypeAndBSDFIndex >> 16; + } + + set + { + lobeTypeAndBSDFIndex = (lobeTypeAndBSDFIndex & 0x0000FFFF) | (newValue << 16); + } } }; @@ -60,22 +84,27 @@ struct BSDFSample */ struct BSDFProperties { - float3 emission; ///< Radiance emitted in the incident direction (wi). - float roughness; ///< Surface roughness on a perceptually linear scale, where 0.0 = perfectly smooth and 1.0 = maximum roughness. - float3 guideNormal; ///< Representative shading normal in world space that includes effects of normal mapping. Useful as guide for denoisers. - uint flags; ///< Flags storing additional properties. + float3 emission = {}; ///< Radiance emitted in the incident direction (wi). + float roughness = {}; ///< Surface roughness on a perceptually linear scale, where 0.0 = perfectly smooth and 1.0 = maximum roughness. + float3 guideNormal = {}; ///< Representative shading normal in world space that includes effects of normal mapping. Useful as guide for + ///< denoisers. + uint flags = {}; ///< Flags storing additional properties. // Approximate directional-hemispherical reflectance/transmittance of the BSDF (black-sky albedo). // The exact values are given by the integrals of the BSDF over wo given an incident direction wi. // The terms are separated into diffuse and non-diffuse components. Due to energy conservation, the sum is expected to be <= 1.0. - float3 diffuseReflectionAlbedo; ///< Directional-hemispherical diffuse reflectance. This is the ratio of total energy diffusely reflected to the energy incident along wi. - float3 diffuseTransmissionAlbedo; ///< Directional-hemispherical diffuse transmittance. This is the ratio of total energy diffusely transmitted to the energy incident along wi. - float3 specularReflectionAlbedo; ///< Directional-hemispherical specular reflectance. This is the ratio of total energy non-diffusely reflected to the energy incident along wi. - float3 specularTransmissionAlbedo; ///< Directional-hemispherical specular transmittance. This is the ratio of total energy non-diffusely transmitted to the energy incident along wi. + float3 diffuseReflectionAlbedo = {}; ///< Directional-hemispherical diffuse reflectance. This is the ratio of total energy diffusely + ///< reflected to the energy incident along wi. + float3 diffuseTransmissionAlbedo = {}; ///< Directional-hemispherical diffuse transmittance. This is the ratio of total energy diffusely + ///< transmitted to the energy incident along wi. + float3 specularReflectionAlbedo = {}; ///< Directional-hemispherical specular reflectance. This is the ratio of total energy + ///< non-diffusely reflected to the energy incident along wi. + float3 specularTransmissionAlbedo = {}; ///< Directional-hemispherical specular transmittance. This is the ratio of total energy + ///< non-diffusely transmitted to the energy incident along wi. // Approximate specular reflectance. This is the color of specular reflection. // The diffuse reflectance is approximated as a Lambertian given by the diffuse albedo above. - float3 specularReflectance; ///< Specular reflectance at normal incidence (F0). This is in the range [0,1]. + float3 specularReflectance = {}; ///< Specular reflectance at normal incidence (F0). This is in the range [0,1]. enum Flags : uint { @@ -84,7 +113,10 @@ struct BSDFProperties property isTransmissive : bool { - get { return (flags & (uint)Flags::IsTransmissive) != 0; } + get + { + return (flags & (uint)Flags::IsTransmissive) != 0; + } } }; @@ -168,6 +200,32 @@ interface IMaterialInstance */ BSDFProperties getProperties(const ShadingData sd); + /** Compute the probabilities of using diffuse and specular reflection motion vectors. + the two probabilities should sum <= 1, if the sum is < 1, there is probability for specular refraction motion vector + \param[in] sd Shading data. + \return returns a float2, storing the probability for the diffuse reflection mvec and specular reflection mvec, respectively + */ + float2 getReflectionMotionVectorProbabilities(const ShadingData sd); + + /** Return whether the material can use a simple roughness value (with the help of BSDF-indexing) in the reconnection criteria. + \return True if the material supports simple roughness threshold, false otherwise + */ + bool supportSimpleRoughnessThreshold(); + + /** Return whether the material instance supports BSDF indexing. + \return True if the material supports BSDF indexing, false otherwise + */ + bool supportBSDFIndexing(); + + /** Determine whether a vertex is considered rough based on the BSDF sample. + \param[in] sd Shading data. + \param[in] sampledBSDFIndex BSDF index of the sample. Not looked at if BSDF indexing not supported + \param[in] samplePdf PDF of the sample. Can be used to approximate roughness by 1.f/pdf^2 + \param[in] roughnessThreshold roughness threshold specified. + \return True if the vertex is considered rough based on the sample, false otherwise + */ + bool isSampleRough(const ShadingData sd, int sampledBSDFIndex, float samplePdf, float roughnessThreshold); + /** Return the set of available BSDF lobes. \param[in] sd Shading data. \return A combination of LobeType flags (see LobeType.slang). @@ -189,11 +247,28 @@ interface IMaterialInstance [open] struct MaterialInstanceBase { - bool hasVolumeProperties() + float2 getReflectionMotionVectorProbabilities(const ShadingData sd) + { + return float2(1.f, 0.f); + } + + bool supportSimpleRoughnessThreshold() { return false; } + bool supportBSDFIndexing() + { + return false; + } + + bool isSampleRough(const ShadingData sd, int sampledBSDFIndex, float samplePdf, float roughnessThreshold) + { + return samplePdf <= 1.f / (roughnessThreshold * roughnessThreshold); + } + + bool hasVolumeProperties() { return false; } + VolumeProperties getVolumeProperties() { VolumeProperties result; @@ -201,7 +276,5 @@ struct MaterialInstanceBase result.sigmaS = 0.f; result.phaseFunction = NullPhaseFunction(); return result; - } } - diff --git a/Source/Falcor/Rendering/Materials/INDF.slang b/Source/Falcor/Rendering/Materials/INDF.slang index 1766692c16..e7858c41b2 100644 --- a/Source/Falcor/Rendering/Materials/INDF.slang +++ b/Source/Falcor/Rendering/Materials/INDF.slang @@ -27,48 +27,47 @@ **************************************************************************/ __exported import Utils.Sampling.SampleGeneratorInterface; - /** Interface for NDFs (normal distribution functions) from microfacet theory. - - These are assumed to be "stretch-invariant", which means only a few methods - need to be implemented for evaluating and sampling from the underlying - distribution of slopes. - In particular, it is sufficient to characterize the slope distribution in - a "standard configuration", meaning it is isotropic with a fixed unit roughness - of alpha = 1.0. This then allows derivation of more general anisotropic variants - and accurate Smith masking functions. - - See "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" by - Eric Heitz 2014 for more details. - */ - interface INDF - { - /** Evaluates the slope distribution in the standard configuration. - - \param[in] slope 2D slope. - \return P22_std(slope). - */ - float evalP22Std(float2 slope); - - /** Evaluates the projected area (sigma) of the heighfield in the standard configuration. - - \param[in] dir Evaluated direction in local space, stretched into the standard configuration. - \return sigma_std(dir). - */ - float evalSigmaStd(float3 dir); - - /** Samples a slope proportionally to P22_std(slope). - - \param[in,out] sg Sample generator. - \return Sampled slope. - */ - float2 sampleP22Std(inout S sg); - - /** Samples a slope proportionally to only the visible P22_std(slope). - - \param[in] wi Incident direction, stretched into the standard configuration. - \param[in,out] sg Sample generator. - \return Sampled slope. - */ - float2 sampleVisibleP22Std(float3 wi, inout S sg); - } - \ No newline at end of file +/** Interface for NDFs (normal distribution functions) from microfacet theory. + + These are assumed to be "stretch-invariant", which means only a few methods + need to be implemented for evaluating and sampling from the underlying + distribution of slopes. + In particular, it is sufficient to characterize the slope distribution in + a "standard configuration", meaning it is isotropic with a fixed unit roughness + of alpha = 1.0. This then allows derivation of more general anisotropic variants + and accurate Smith masking functions. + + See "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" by + Eric Heitz 2014 for more details. +*/ +interface INDF +{ + /** Evaluates the slope distribution in the standard configuration. + + \param[in] slope 2D slope. + \return P22_std(slope). + */ + float evalP22Std(float2 slope); + + /** Evaluates the projected area (sigma) of the heighfield in the standard configuration. + + \param[in] dir Evaluated direction in local space, stretched into the standard configuration. + \return sigma_std(dir). + */ + float evalSigmaStd(float3 dir); + + /** Samples a slope proportionally to P22_std(slope). + + \param[in,out] sg Sample generator. + \return Sampled slope. + */ + float2 sampleP22Std(inout S sg); + + /** Samples a slope proportionally to only the visible P22_std(slope). + + \param[in] wi Incident direction, stretched into the standard configuration. + \param[in,out] sg Sample generator. + \return Sampled slope. + */ + float2 sampleVisibleP22Std(float3 wi, inout S sg); +} diff --git a/Source/Falcor/Rendering/Materials/InteriorList.slang b/Source/Falcor/Rendering/Materials/InteriorList.slang index 7eaefae480..aff2f96587 100644 --- a/Source/Falcor/Rendering/Materials/InteriorList.slang +++ b/Source/Falcor/Rendering/Materials/InteriorList.slang @@ -40,91 +40,67 @@ struct InteriorList { - static const uint kNoMaterial = 0xffffffff; + static const uint kNoMaterial = 0xffffffff; - static const uint kMaterialBits = 28; - static const uint kNestedPriorityBits = 4; + static const uint kMaterialBits = 28; + static const uint kNestedPriorityBits = 4; - static const uint kMaterialOffset = 0; + static const uint kMaterialOffset = 0; static const uint kNestedPriorityOffset = kMaterialOffset + kMaterialBits; - static const uint kMaterialMask = ((1 << kMaterialBits) - 1) << kMaterialOffset; - static const uint kNestedPriorityMask = ((1 << kNestedPriorityBits) - 1) << kNestedPriorityOffset; + static const uint kMaterialMask = ((1 << kMaterialBits) - 1) << kMaterialOffset; + static const uint kNestedPriorityMask = ((1 << kNestedPriorityBits) - 1) << kNestedPriorityOffset; - static const uint kMaxNestedPriority = ((1 << kNestedPriorityBits) - 1); + static const uint kMaxNestedPriority = ((1 << kNestedPriorityBits) - 1); - uint slots[INTERIOR_LIST_SLOT_COUNT]; + uint slots[INTERIOR_LIST_SLOT_COUNT] = {}; /** Make an active material slot given the material and priority. \param[in] materialID Material ID. \param[in] nestedPriority Nested priority, 0 is reserved for empty slots. \return Returns the encoded slot. */ - uint makeSlot(uint materialID, uint nestedPriority) - { - return (nestedPriority << kNestedPriorityOffset) | (materialID & kMaterialMask); - } + uint makeSlot(uint materialID, uint nestedPriority) { return (nestedPriority << kNestedPriorityOffset) | (materialID & kMaterialMask); } /** Check if a slot is active. \param[in] slot Slot value. \return Returns true if slot is active. */ - bool isSlotActive(uint slot) - { - return slot != 0; - } + bool isSlotActive(uint slot) { return slot != 0; } /** Check if the interior list is empty. \return Returns true if interior list is empty, false otherwise. */ - bool isEmpty() - { - return !isSlotActive(slots[0]); - } + bool isEmpty() { return !isSlotActive(slots[0]); } /** Get the nested priority from a slot. \param[in] slot Slot value. \param Returns the nested priority or 0 for empty slots. */ - uint getSlotNestedPriority(uint slot) - { - return slot >> kNestedPriorityOffset; - } + uint getSlotNestedPriority(uint slot) { return slot >> kNestedPriorityOffset; } /** Get the material ID from a slot. \param[in] slot Slot value. \return Returns the material ID or 0 for empty slots. */ - uint getSlotMaterialID(uint slot) - { - return slot & kMaterialMask; - } + uint getSlotMaterialID(uint slot) { return slot & kMaterialMask; } /** Return current highest nested priority on interior list. Because the interior list is sorted by nested priority and empty slots have priority 0, we can simple fetch the first slot. \return Returns the highest nested priority on the interior list. */ - uint getTopNestedPriority() - { - return getSlotNestedPriority(slots[0]); - } + uint getTopNestedPriority() { return getSlotNestedPriority(slots[0]); } /** Return the material ID with the highest priority on the interior list. \return Returns the material ID or kNoMaterial if interior list is empty. */ - uint getTopMaterialID() - { - return isSlotActive(slots[0]) ? getSlotMaterialID(slots[0]) : kNoMaterial; - } + uint getTopMaterialID() { return isSlotActive(slots[0]) ? getSlotMaterialID(slots[0]) : kNoMaterial; } /** Return the material ID with the 2nd highest priority on the interior list. \return Returns the material ID or kNoMaterial if interior list is empty. */ - uint getNextMaterialID() - { - return isSlotActive(slots[1]) ? getSlotMaterialID(slots[1]) : kNoMaterial; - } + uint getNextMaterialID() { return isSlotActive(slots[1]) ? getSlotMaterialID(slots[1]) : kNoMaterial; } /** Check if an intersection with a given surface is a true intersection. True intersection occurs if nested priority of intersected mesh is equal or higher @@ -145,13 +121,15 @@ struct InteriorList \param[in] nestedPriority Nested priority of intersected surface, with 0 reserved for the highest possible priority. \param[in] entering True if material is entered, false if material is left. */ - [mutating] void handleIntersection(uint materialID, uint nestedPriority, bool entering) + [mutating] + void handleIntersection(uint materialID, uint nestedPriority, bool entering) { // Remap priority 0 to the highest priority to allow sorting by high->low priority, // and as internally 0 is reserved for empty slots. - if (nestedPriority == 0) nestedPriority = kMaxNestedPriority; + if (nestedPriority == 0) + nestedPriority = kMaxNestedPriority; - // The following loop is manually unrolled below to avoid stack allocation. + // The following loop is manually unrolled below to avoid stack allocation. #if 0 for (uint slotIndex = 0; slotIndex < INTERIOR_LIST_SLOT_COUNT; ++slotIndex) { @@ -221,16 +199,17 @@ struct InteriorList } /** Sort the interior list by priority. - */ - [mutating] void sortSlots() + */ + [mutating] + void sortSlots() { -#define CSWAP(_a, _b) \ - if (slots[_a] < slots[_b]) \ - { \ - uint tmp = slots[_a]; \ - slots[_a] = slots[_b]; \ - slots[_b] = tmp; \ - } +#define CSWAP(_a, _b) \ + if (slots[_a] < slots[_b]) \ + { \ + uint tmp = slots[_a]; \ + slots[_a] = slots[_b]; \ + slots[_b] = tmp; \ + } // sorting networks: http://pages.ripco.net/~jgamble/nw.html #if INTERIOR_LIST_SLOT_COUNT == 2 diff --git a/Source/Falcor/Rendering/Materials/InteriorListHelpers.slang b/Source/Falcor/Rendering/Materials/InteriorListHelpers.slang index 10c1877f6f..05131e5ec1 100644 --- a/Source/Falcor/Rendering/Materials/InteriorListHelpers.slang +++ b/Source/Falcor/Rendering/Materials/InteriorListHelpers.slang @@ -44,11 +44,13 @@ float computeOutsideIoR(const InteriorList interiorList, const uint materialID, { // If exiting the currently highest priority material, look at the next element // on the stack to find out what is on the outside. - if (outsideMaterialID == materialID) outsideMaterialID = interiorList.getNextMaterialID(); + if (outsideMaterialID == materialID) + outsideMaterialID = interiorList.getNextMaterialID(); } // If no material, assume the default IoR for vacuum. - if (outsideMaterialID == InteriorList::kNoMaterial) return 1.f; + if (outsideMaterialID == InteriorList::kNoMaterial) + return 1.f; return gScene.materials.evalIoR(outsideMaterialID); } diff --git a/Source/Falcor/Rendering/Materials/IsotropicGGX.slang b/Source/Falcor/Rendering/Materials/IsotropicGGX.slang index af1e47759a..e1b4fa4d5d 100644 --- a/Source/Falcor/Rendering/Materials/IsotropicGGX.slang +++ b/Source/Falcor/Rendering/Materials/IsotropicGGX.slang @@ -30,13 +30,14 @@ /** Evaluates the GGX (Trowbridge-Reitz) normal distribution function (D). - Introduced by Trowbridge and Reitz, "Average irregularity representation of a rough surface for ray reflection", Journal of the Optical Society of America, vol. 65(5), 1975. - See the correct normalization factor in Walter et al. https://dl.acm.org/citation.cfm?id=2383874 - We use the simpler, but equivalent expression in Eqn 19 from http://blog.selfshadow.com/publications/s2012-shading-course/hoffman/s2012_pbs_physics_math_notes.pdf + Introduced by Trowbridge and Reitz, "Average irregularity representation of a rough surface for ray reflection", Journal of the Optical + Society of America, vol. 65(5), 1975. See the correct normalization factor in Walter et al. https://dl.acm.org/citation.cfm?id=2383874 We + use the simpler, but equivalent expression in Eqn 19 from + http://blog.selfshadow.com/publications/s2012-shading-course/hoffman/s2012_pbs_physics_math_notes.pdf - For microfacet models, D is evaluated for the direction h to find the density of potentially active microfacets (those for which microfacet normal m = h). - The 'alpha' parameter is the standard GGX width, e.g., it is the square of the linear roughness parameter in Disney's BRDF. - Note there is a singularity (0/0 = NaN) at NdotH = 1 and alpha = 0, so alpha should be clamped to some epsilon. + For microfacet models, D is evaluated for the direction h to find the density of potentially active microfacets (those for which + microfacet normal m = h). The 'alpha' parameter is the standard GGX width, e.g., it is the square of the linear roughness parameter in + Disney's BRDF. Note there is a singularity (0/0 = NaN) at NdotH = 1 and alpha = 0, so alpha should be clamped to some epsilon. \param[in] alpha GGX width parameter (should be clamped to small epsilon beforehand). \param[in] cosTheta Dot product between shading normal and half vector, in positive hemisphere. @@ -147,7 +148,8 @@ float3 sampleGGX_VNDF(float alpha, float3 wi, float2 u, out float pdf) [Differentiable] float evalG1GGX(float alphaSqr, float cosTheta) { - if (cosTheta <= 0) return 0; + if (cosTheta <= 0) + return 0; float cosThetaSqr = cosTheta * cosTheta; float tanThetaSqr = max(1 - cosThetaSqr, 0) / cosThetaSqr; return 2 / (1 + sqrt(1 + alphaSqr * tanThetaSqr)); @@ -162,7 +164,8 @@ float evalG1GGX(float alphaSqr, float cosTheta) [Differentiable] float evalLambdaGGX(float alphaSqr, float cosTheta) { - if (cosTheta <= 0) return 0; + if (cosTheta <= 0) + return 0; float cosThetaSqr = cosTheta * cosTheta; float tanThetaSqr = max(1 - cosThetaSqr, 0) / cosThetaSqr; return 0.5 * (-1 + sqrt(1 + alphaSqr * tanThetaSqr)); @@ -230,49 +233,21 @@ float3 approxSpecularIntegralGGX(float3 specularReflectance, float alpha, float // Select coefficients based on BRDF version being in use (either separable or correlated G term) #if SpecularMaskingFunction == SpecularMaskingFunctionSmithGGXSeparable - float2x2 M1 = float2x2( - 0.99044f, -1.28514f, - 1.29678f, -0.755907f - ); - - float3x3 M2 = float3x3( - 1.0f, 2.92338f, 59.4188f, - 20.3225f, -27.0302f, 222.592f, - 121.563f, 626.13f, 316.627f - ); - - float2x2 M3 = float2x2( - 0.0365463f, 3.32707f, - 9.0632f, -9.04756f - ); - - float3x3 M4 = float3x3( - 1.0f, 3.59685f, -1.36772f, - 9.04401f, -16.3174f, 9.22949f, - 5.56589f, 19.7886f, -20.2123f - ); + float2x2 M1 = float2x2(0.99044f, -1.28514f, 1.29678f, -0.755907f); + + float3x3 M2 = float3x3(1.0f, 2.92338f, 59.4188f, 20.3225f, -27.0302f, 222.592f, 121.563f, 626.13f, 316.627f); + + float2x2 M3 = float2x2(0.0365463f, 3.32707f, 9.0632f, -9.04756f); + + float3x3 M4 = float3x3(1.0f, 3.59685f, -1.36772f, 9.04401f, -16.3174f, 9.22949f, 5.56589f, 19.7886f, -20.2123f); #elif SpecularMaskingFunction == SpecularMaskingFunctionSmithGGXCorrelated - float2x2 M1 = float2x2( - 0.995367f, -1.38839f, - -0.24751f, 1.97442f - ); - - float3x3 M2 = float3x3( - 1.0f, 2.68132f, 52.366f, - 16.0932f, -3.98452f, 59.3013f, - -5.18731f, 255.259f, 2544.07f - ); - - float2x2 M3 = float2x2( - -0.0564526f, 3.82901f, - 16.91f, -11.0303f - ); - - float3x3 M4 = float3x3( - 1.0f, 4.11118f, -1.37886f, - 19.3254f, -28.9947f, 16.9514f, - 0.545386f, 96.0994f, -79.4492f - ); + float2x2 M1 = float2x2(0.995367f, -1.38839f, -0.24751f, 1.97442f); + + float3x3 M2 = float3x3(1.0f, 2.68132f, 52.366f, 16.0932f, -3.98452f, 59.3013f, -5.18731f, 255.259f, 2544.07f); + + float2x2 M3 = float2x2(-0.0564526f, 3.82901f, 16.91f, -11.0303f); + + float3x3 M4 = float3x3(1.0f, 4.11118f, -1.37886f, 19.3254f, -28.9947f, 16.9514f, 0.545386f, 96.0994f, -79.4492f); #endif float bias = dot(mul(M1, X.xy), Y.xy) * rcp(dot(mul(M2, X.xyw), Y.xyw)); diff --git a/Source/Falcor/Rendering/Materials/LayeredBSDF.slang b/Source/Falcor/Rendering/Materials/LayeredBSDF.slang index 70516129af..112d997464 100644 --- a/Source/Falcor/Rendering/Materials/LayeredBSDF.slang +++ b/Source/Falcor/Rendering/Materials/LayeredBSDF.slang @@ -67,12 +67,7 @@ struct LayeredBSDF : IBSDF this.maxDepth = maxDepth; } - BSDFContext initBSDFContext( - const bool scatterTop, - const float3 wi, - const float iors[2], - const float iorOutside, - ) + BSDFContext initBSDFContext(const bool scatterTop, const float3 wi, const float iors[2], const float iorOutside, ) { BSDFContext bc; if (wi.z >= 0.f) @@ -119,6 +114,7 @@ struct LayeredBSDF : IBSDF bool sampleTopOrBottom(bool sampleTop, float3 wi, out BSDFSample bs, inout S sg, BSDFContext bc) { + bs = {}; if (sampleTop) { return top.sample(wi, bs.wo, bs.pdf, bs.weight, bs.lobeType, sg, bc); @@ -157,15 +153,18 @@ struct LayeredBSDF : IBSDF BSDFContext bcLocal = BSDFContext(bc.iorI, bc.iorT); - if (z == exitZ) result += evalTopOrBottom(exitTop, wi, wo, sg, bcLocal); + if (z == exitZ) + result += evalTopOrBottom(exitTop, wi, wo, sg, bcLocal); BSDFSample bs; - if (!sampleTopOrBottom(enterTop, wi, bs, sg, bcLocal)) return result; - if (bs.isLobe(LobeType::Reflection)) return result; + if (!sampleTopOrBottom(enterTop, wi, bs, sg, bcLocal)) + return result; + if (bs.isLobe(LobeType::Reflection)) + return result; float3 w = bs.wo; float3 weight = bs.weight; - HenyeyGreensteinPhaseFunction phase = {g}; + HenyeyGreensteinPhaseFunction phase = { g }; for (int depth = 0; depth < maxDepth; ++depth) { @@ -179,7 +178,8 @@ struct LayeredBSDF : IBSDF float3 phaseW; float phasePDF; float3 phaseWeight; - if (!phase.sample(-w, phaseW, phasePDF, phaseWeight, sg)) return false; + if (!phase.sample(-w, phaseW, phasePDF, phaseWeight, sg)) + return false; weight *= albedo * phaseWeight; w = phaseW; z = zp; @@ -196,24 +196,36 @@ struct LayeredBSDF : IBSDF bool scatterTop = z == 1.0f; bcLocal = initBSDFContext(scatterTop, -w, iors, iorOutside); - if (z == exitZ) result += weight * evalTopOrBottom(exitTop, -w, wo, sg, bcLocal); - if (!sampleTopOrBottom(scatterTop, -w, bs, sg, bcLocal)) return result; + if (z == exitZ) + result += weight * evalTopOrBottom(exitTop, -w, wo, sg, bcLocal); + if (!sampleTopOrBottom(scatterTop, -w, bs, sg, bcLocal)) + return result; weight *= bs.weight; w = bs.wo; - if (bs.isLobe(LobeType::Transmission)) return result; + if (bs.isLobe(LobeType::Transmission)) + return result; float rr = min(luminance(weight), 1.0f); if (depth > 3 && rr < 0.25f) { - if (sampleNext1D(sg) >= rr) return false; + if (sampleNext1D(sg) >= rr) + return false; weight /= rr; } } return result; } - bool sample(const float3 wi, out float3 wo, out float pdfOut, out float3 weightOut, out uint lobeType, inout S sg, BSDFContext bc) + bool sample( + const float3 wi, + out float3 wo, + out float pdfOut, + out float3 weightOut, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = {}; pdfOut = {}; @@ -230,7 +242,8 @@ struct LayeredBSDF : IBSDF BSDFContext bcLocal = BSDFContext(bc.iorI, bc.iorT); BSDFSample bs; - if (!sampleTopOrBottom(enterTop, wi, bs, sg, bcLocal)) return false; + if (!sampleTopOrBottom(enterTop, wi, bs, sg, bcLocal)) + return false; if (bs.isLobe(LobeType::Reflection)) { wo = bs.wo; @@ -244,7 +257,7 @@ struct LayeredBSDF : IBSDF float3 weight = bs.weight; float pdf = bs.pdf; float z = enterTop ? 1.0f : 0.0f; - HenyeyGreensteinPhaseFunction phase = {g}; + HenyeyGreensteinPhaseFunction phase = { g }; for (int depth = 0; depth < maxDepth; ++depth) { @@ -258,7 +271,8 @@ struct LayeredBSDF : IBSDF float3 phaseW; float phasePDF; float3 phaseWeight; - if (!phase.sample(-w, phaseW, phasePDF, phaseWeight, sg)) return false; + if (!phase.sample(-w, phaseW, phasePDF, phaseWeight, sg)) + return false; pdf *= phasePDF; weight *= albedo * phaseWeight; w = phaseW; @@ -275,7 +289,8 @@ struct LayeredBSDF : IBSDF } bool scatterTop = z == 1.0f; bcLocal = initBSDFContext(scatterTop, -w, iors, iorOutside); - if (!sampleTopOrBottom(scatterTop, -w, bs, sg, bcLocal)) return false; + if (!sampleTopOrBottom(scatterTop, -w, bs, sg, bcLocal)) + return false; weight *= bs.weight; w = bs.wo; pdf *= bs.pdf; @@ -285,9 +300,8 @@ struct LayeredBSDF : IBSDF wo = w; pdfOut = evalPdf(wi, w, bc); weightOut = weight; - lobeType = - (uint)(w.z*wi.z > 0.0f ? LobeType::Reflection : LobeType::Transmission) & - (uint)(bs.isLobe(LobeType::Delta) ? LobeType::Delta : LobeType::Specular); + lobeType = (uint)(w.z * wi.z > 0.0f ? LobeType::Reflection : LobeType::Transmission) & + (uint)(bs.isLobe(LobeType::Delta) ? LobeType::Delta : LobeType::Specular); return true; } @@ -295,7 +309,8 @@ struct LayeredBSDF : IBSDF float rr = min(luminance(weight), 1.0f); if (depth > 3 && rr < 0.25f) { - if (sampleNext1D(sg) >= rr) return false; + if (sampleNext1D(sg) >= rr) + return false; weight /= rr; pdf *= rr; } @@ -324,7 +339,8 @@ struct LayeredBSDF : IBSDF float pdfCos = abs(wi.z) * M_1_PI; float pdfSingle = 0.0f; - if (enterTop == exitTop) pdfSingle = evalPdfTopOrBottom(exitTop, wi, wo, bcLocal); + if (enterTop == exitTop) + pdfSingle = evalPdfTopOrBottom(exitTop, wi, wo, bcLocal); return lerp(pdfSingle, pdfCos, 0.5f); } @@ -335,10 +351,7 @@ struct LayeredBSDF : IBSDF return AlbedoContributions(1.f, 0.f, 0.f, 0.f); } - float3 getIorAsReflectance() - { - return float3(1.f); - } + float3 getIorAsReflectance() { return float3(1.f); } RoughnessInformation getRoughnessInformation(const float3 wi) { diff --git a/Source/Falcor/Rendering/Materials/LobeType.slang b/Source/Falcor/Rendering/Materials/LobeType.slang index 38b2dc06d1..fc6e45f449 100644 --- a/Source/Falcor/Rendering/Materials/LobeType.slang +++ b/Source/Falcor/Rendering/Materials/LobeType.slang @@ -31,32 +31,32 @@ BEGIN_NAMESPACE_FALCOR /** Flags representing the various lobes of a BSDF. -*/ + */ // TODO: Specify the backing type when Slang issue has been resolved enum class LobeType // : uint32_t { - None = 0x00, + None = 0x00, - DiffuseReflection = 0x01, - SpecularReflection = 0x02, - DeltaReflection = 0x04, + DiffuseReflection = 0x01, + SpecularReflection = 0x02, + DeltaReflection = 0x04, - DiffuseTransmission = 0x10, - SpecularTransmission = 0x20, - DeltaTransmission = 0x40, + DiffuseTransmission = 0x10, + SpecularTransmission = 0x20, + DeltaTransmission = 0x40, - Diffuse = 0x11, - Specular = 0x22, - Delta = 0x44, - NonDelta = 0x33, + Diffuse = 0x11, + Specular = 0x22, + Delta = 0x44, + NonDelta = 0x33, - Reflection = 0x0f, - Transmission = 0xf0, + Reflection = 0x0f, + Transmission = 0xf0, - NonDeltaReflection = 0x03, - NonDeltaTransmission = 0x30, + NonDeltaReflection = 0x03, + NonDeltaTransmission = 0x30, - All = 0xff, + All = 0xff, }; END_NAMESPACE_FALCOR diff --git a/Source/Falcor/Rendering/Materials/MERLCommon.slang b/Source/Falcor/Rendering/Materials/MERLCommon.slang index 434f8009fd..21da612af6 100644 --- a/Source/Falcor/Rendering/Materials/MERLCommon.slang +++ b/Source/Falcor/Rendering/Materials/MERLCommon.slang @@ -28,7 +28,7 @@ #include "Utils/Math/MathConstants.slangh" /** MERL functions shared between materials using the MERL database. -*/ + */ struct MERLCommon { /** Evaluate the MERL BRDF in the local frame. @@ -50,7 +50,6 @@ struct MERLCommon return f * wo.z; } - // Internal helpers static const uint kBRDFSamplingResThetaH = 90; @@ -80,7 +79,7 @@ struct MERLCommon } /** Rotate vector along axis. - */ + */ static float3 rotateVector(const float3 v, const float3 axis, const float angle) { float c = cos(angle); @@ -96,10 +95,11 @@ struct MERLCommon } /** Map thetaH to index. This is a non-linear mapping. - */ + */ static uint getThetaHIndex(float thetaH) { - if (thetaH <= 0.f) return 0; + if (thetaH <= 0.f) + return 0; // Map thetaH in [0, pi/2] to idx in [0, 89]. int idx = (int)(sqrt(thetaH * (float)M_2_PI) * kBRDFSamplingResThetaH); @@ -107,7 +107,7 @@ struct MERLCommon } /** Map thetaD to index. This is a linear mapping. - */ + */ static uint getThetaDIndex(float thetaD) { // Map thetaD in [0, pi/2] to idx in [0, 89]. @@ -116,11 +116,12 @@ struct MERLCommon } /** Map phiD to index. This is a linear mapping. - */ + */ static uint getPhiDIndex(float phiD) { // Because of reciprocity, the BRDF is unchanged under phiD -> phiD + M_PI. - if (phiD < 0.f) phiD += (float)M_PI; + if (phiD < 0.f) + phiD += (float)M_PI; // Map phiD in [0, pi] to idx in [0, 179]. int idx = (int)(phiD * (float)M_1_PI * (kBRDFSamplingResPhiD / 2)); diff --git a/Source/Falcor/Rendering/Materials/MERLMaterial.slang b/Source/Falcor/Rendering/Materials/MERLMaterial.slang index cea11839e3..2ee3cfcffb 100644 --- a/Source/Falcor/Rendering/Materials/MERLMaterial.slang +++ b/Source/Falcor/Rendering/Materials/MERLMaterial.slang @@ -68,7 +68,13 @@ struct MERLMaterial : MaterialBase, IMaterial } [Differentiable] - MERLMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + MERLMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/MERLMaterialInstance.slang b/Source/Falcor/Rendering/Materials/MERLMaterialInstance.slang index 46adefbbf4..e97f09fb34 100644 --- a/Source/Falcor/Rendering/Materials/MERLMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/MERLMaterialInstance.slang @@ -41,12 +41,12 @@ import Scene.Scene; #define VISUALIZE_FITTED_BRDF 0 /** Implementation of the BSDF for the measured MERL material. -*/ + */ struct MERLMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. - uint bufferID; ///< Buffer ID in material system where BRDF data is stored. - float3 albedo; ///< Approximate albedo. + ShadingFrame sf; ///< Shading frame in world space. + uint bufferID; ///< Buffer ID in material system where BRDF data is stored. + float3 albedo; ///< Approximate albedo. DiffuseSpecularBRDF fittedBrdf; __init(const ShadingFrame sf, const uint bufferID, const float3 albedo, const DiffuseSpecularData extraData) @@ -64,7 +64,8 @@ struct MERLMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); #if VISUALIZE_FITTED_BRDF return fittedBrdf.eval(wiLocal, woLocal, sg, BSDFContext()); @@ -85,22 +86,25 @@ struct MERLMaterialInstance : MaterialInstanceBase, IMaterialInstance #if SAMPLE_FITTED_BRDF float3 woLocal = {}; - if (!fittedBrdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext())) return false; + if (!fittedBrdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext())) + return false; result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) + return false; #if !VISUALIZE_FITTED_BRDF result.weight = evalLocal(wiLocal, woLocal) / result.pdf; #endif #else // !SAMPLE_FITTED_BRDF - // Draw cosine-weighted sample over the hemisphere. - // TODO: Implement better importance sampling. + // Draw cosine-weighted sample over the hemisphere. + // TODO: Implement better importance sampling. float3 woLocal = sample_cosine_hemisphere_concentric(sampleNext2D(sg), result.pdf); // pdf = cos(theta) / pi result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) + return false; #if VISUALIZE_FITTED_BRDF result.weight = fittedBrdf.eval(wiLocal, woLocal, sg) / result.pdf; @@ -118,7 +122,8 @@ struct MERLMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return 0.f; #if SAMPLE_FITTED_BRDF return fittedBrdf.evalPdf(wiLocal, woLocal, BSDFContext()); @@ -144,11 +149,7 @@ struct MERLMaterialInstance : MaterialInstanceBase, IMaterialInstance return p; } - uint getLobeTypes(const ShadingData sd) - { - return (uint)LobeType::DiffuseReflection | (uint)LobeType::SpecularReflection; - } - + uint getLobeTypes(const ShadingData sd) { return (uint)LobeType::DiffuseReflection | (uint)LobeType::SpecularReflection; } // Internal helpers diff --git a/Source/Falcor/Rendering/Materials/MERLMixMaterial.slang b/Source/Falcor/Rendering/Materials/MERLMixMaterial.slang index aa064301b8..a752494bfa 100644 --- a/Source/Falcor/Rendering/Materials/MERLMixMaterial.slang +++ b/Source/Falcor/Rendering/Materials/MERLMixMaterial.slang @@ -39,7 +39,12 @@ struct MERLMixMaterial : MaterialBase, IMaterial // Specify that the associated `MaterialInstance` type is `MERLMixMaterialInstance`. typedef MERLMixMaterialInstance MaterialInstance; - MERLMixMaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + MERLMixMaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { let explicitLod = ExplicitLodTextureSampler(0.f); @@ -94,7 +99,13 @@ struct MERLMixMaterial : MaterialBase, IMaterial } [Differentiable] - MERLMixMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + MERLMixMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/MERLMixMaterialInstance.slang b/Source/Falcor/Rendering/Materials/MERLMixMaterialInstance.slang index ee3b7dcddf..847dab373c 100644 --- a/Source/Falcor/Rendering/Materials/MERLMixMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/MERLMixMaterialInstance.slang @@ -39,17 +39,24 @@ import Scene.Scene; #define SAMPLE_FITTED_BRDF 1 /** Implementation of the BSDF for the measured MERLMix material. -*/ + */ struct MERLMixMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. - uint bufferID; ///< Buffer ID in material system where BRDF data is stored. - uint byteOffset; ///< Offset in bytes into BRDF data buffer. - float3 albedo; ///< Approximate albedo. + ShadingFrame sf; ///< Shading frame in world space. + uint bufferID; ///< Buffer ID in material system where BRDF data is stored. + uint byteOffset; ///< Offset in bytes into BRDF data buffer. + float3 albedo; ///< Approximate albedo. uint brdfIndex; DiffuseSpecularBRDF fittedBrdf; - __init(const ShadingFrame sf, const uint bufferID, const uint byteOffset, const float3 albedo, const uint brdfIndex, const DiffuseSpecularData extraData) + __init( + const ShadingFrame sf, + const uint bufferID, + const uint byteOffset, + const float3 albedo, + const uint brdfIndex, + const DiffuseSpecularData extraData + ) { this.sf = sf; this.bufferID = bufferID; @@ -66,7 +73,8 @@ struct MERLMixMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); return evalLocal(wiLocal, woLocal); } @@ -82,14 +90,16 @@ struct MERLMixMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = {}; #if SAMPLE_FITTED_BRDF - if (!fittedBrdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext())) return false; + if (!fittedBrdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext())) + return false; #else woLocal = sample_cosine_hemisphere_concentric(sampleNext2D(sg), result.pdf); result.lobeType = (uint)LobeType::DiffuseReflection; #endif result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) + return false; result.weight = evalLocal(wiLocal, woLocal) / result.pdf; @@ -101,7 +111,8 @@ struct MERLMixMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return 0.f; #if SAMPLE_FITTED_BRDF return fittedBrdf.evalPdf(wiLocal, woLocal, BSDFContext()); @@ -126,11 +137,7 @@ struct MERLMixMaterialInstance : MaterialInstanceBase, IMaterialInstance return p; } - uint getLobeTypes(const ShadingData sd) - { - return (uint)LobeType::DiffuseReflection | (uint)LobeType::SpecularReflection; - } - + uint getLobeTypes(const ShadingData sd) { return (uint)LobeType::DiffuseReflection | (uint)LobeType::SpecularReflection; } // Internal helpers diff --git a/Source/Falcor/Rendering/Materials/MaterialInstanceHints.slang b/Source/Falcor/Rendering/Materials/MaterialInstanceHints.slang index c735a0373e..c727e1b0c7 100644 --- a/Source/Falcor/Rendering/Materials/MaterialInstanceHints.slang +++ b/Source/Falcor/Rendering/Materials/MaterialInstanceHints.slang @@ -28,12 +28,12 @@ #include "Utils/HostDeviceShared.slangh" // For bit packing macros /** Hints passed in when creating a material instance. -*/ + */ enum MaterialInstanceHints : uint { None = 0, - DisableNormalMapping = 0x1, ///< Turn off normal mapping if supported. - AdjustShadingNormal = 0x2, ///< Adjust shading normal to reduce risk of back-facing hits. + DisableNormalMapping = 0x1, ///< Turn off normal mapping if supported. + AdjustShadingNormal = 0x2, ///< Adjust shading normal to reduce risk of back-facing hits. }; bool isNormalMappingEnabled(uint hints) diff --git a/Source/Falcor/Rendering/Materials/Microfacet.slang b/Source/Falcor/Rendering/Materials/Microfacet.slang index 43f164ff33..8c392db26b 100644 --- a/Source/Falcor/Rendering/Materials/Microfacet.slang +++ b/Source/Falcor/Rendering/Materials/Microfacet.slang @@ -78,17 +78,11 @@ struct Microfacet /** A 'singular' distribution is one which would be more ideally treated as an ideal mirror. It can't be sampled reliably. */ - bool isSingular() - { - return all(alpha < 1e-3f); - } + bool isSingular() { return all(alpha < 1e-3f); } /** Returns the isotropic roughness, i.e. the maximum along one of the tangent directions. - */ - float getIsotropicRoughness() - { - return max(alpha.x, alpha.y); - } + */ + float getIsotropicRoughness() { return max(alpha.x, alpha.y); } /** Evaluates the NDF (D) for a given microfacet normal. This returns the density of potentially active microfacets (those for which the microfacet normal matches @@ -128,10 +122,7 @@ struct Microfacet \param[in] wm Microfacet normal in local space, in the positive hemisphere. \return D(wm) * dot(wm, n). */ - float evalPdfFull(float3 wm) - { - return evalD(wm) * wm.z; - } + float evalPdfFull(float3 wm) { return evalD(wm) * wm.z; } /** Evaluates the PDF of sampling the visible NDF, see also `sampleVisible`. @@ -139,10 +130,7 @@ struct Microfacet \param[in] wm Microfacet normal in local space, in the positive hemisphere. \return D_wi(wm) = dot(wm, wi) * D(wm) / sigma(-wi). */ - float evalPdfVisible(float3 wi, float3 wm) - { - return max(0.0f, dot(wm, wi)) * evalD(wm) / evalSigma(-wi); - } + float evalPdfVisible(float3 wi, float3 wm) { return max(0.0f, dot(wm, wi)) * evalD(wm) / evalSigma(-wi); } /** Samples a microfacet normal proportionally to D(wm) * dot(wm, n). @@ -203,10 +191,7 @@ struct Microfacet \param[in] w Evaluated direction, in local space. */ - float evalG1(float3 w) - { - return abs(w.z) / evalSigma(-sign(w.z) * w); - } + float evalG1(float3 w) { return abs(w.z) / evalSigma(-sign(w.z) * w); } /** Evaluates the separable form of the Smith shadowing-masking function. @@ -216,10 +201,7 @@ struct Microfacet \param[in] wo Outgoing direction in local space. \return G2_separable(wi, wo). */ - float evalG2Separable(float3 wi, float3 wo) - { - return evalG1(wi) * evalG1(wo); - } + float evalG2Separable(float3 wi, float3 wo) { return evalG1(wi) * evalG1(wo); } /** Evaluates the height-correlated form of the Smith shadowing-masking function. diff --git a/Source/Falcor/Rendering/Materials/NDF.slang b/Source/Falcor/Rendering/Materials/NDF.slang index afa1bbc4e0..3035275980 100644 --- a/Source/Falcor/Rendering/Materials/NDF.slang +++ b/Source/Falcor/Rendering/Materials/NDF.slang @@ -27,156 +27,158 @@ **************************************************************************/ #include "Utils/Math/MathConstants.slangh" - import Utils.Math.MathHelpers; - __exported import Rendering.Materials.INDF; - - /** Implementation of the Trowbridge-Reitz normal distribution function. - - Originally from "Average irregularity representation of a rough surface for ray reflection" - by Trowbridge and Reitz 1975. - - Also known as GGX distribution from "Microfacet Models for Refraction through Rough Surfaces" - by Walter et al. 2007. - */ - struct TrowbridgeReitzNDF : INDF - { - __init() {} - - float evalP22Std(float2 slope) - { - // See "Photorealistic Surface Rendering with Microfacet Theory" - // by Jonathan Dupuy 2015, Table 2.II. - float tmp = (1.0f + slope.x * slope.x + slope.y * slope.y); - return M_1_PI / (tmp * tmp); - } - - float evalSigmaStd(float3 dir) - { - // See "Photorealistic Surface Rendering with Microfacet Theory" - // by Jonathan Dupuy 2015, Table 2.III (rearranged). - return 0.5f * (1.0f - dir.z); - } - - float2 sampleP22Std(inout S sg) - { - // See "Importance Sampling Microfacet-Based BSDFs with the Distribution of Visible Normals" - // (Supplemental Material 2/2) by Heitz and d'Eon 2014. - float2 u = sampleNext2D(sg); - float phi = M_2PI * u.y; - return float2(cos(phi), sin(phi)) * sqrt(u.x) / sqrt(1.0f - u.x); - } - - float2 sampleVisibleP22Std(float3 wi, inout S sg) - { - // From "Sampling the GGX Distribution of Visible Normals" - // by Eric Heitz 2018. - - // Orthonormal basis (with special case if cross product is zero). - float lensqr = wi.x * wi.x + wi.y * wi.y; - float3 T1 = lensqr > 0.0f ? float3(-wi.y, wi.x, 0.0f) / sqrt(lensqr) : float3(1.0f, 0.0f, 0.0f); - float3 T2 = cross(wi, T1); - - // Parameterization of the projected area. - float2 p = sample_disk_concentric(sampleNext2D(sg)); - float s = 0.5f * (1.0f + wi.z); - p.y = (1.0f - s) * sqrt(1.0f - p.x * p.x) + s * p.y; - - // Reprojection to the hemisphere. - float3 mStd = p.x * T1 + p.y * T2 + sqrt(max(0.0f, 1.0f - p.x * p.x - p.y * p.y)) * wi; - - // Transform to a slope. - return float2(-mStd.x / mStd.z, -mStd.y / mStd.z); - } - } - - /** Implementation of the Beckmann-Spizzichino normal distribution function. - - Supports elliptical anisotropy and visible normals sampling. - - Originally from "The scattering of electromagnetic waves from rough surfaces" - by Beckmann and Spizzichino, 1963. - */ - struct BeckmannSpizzichinoNDF : INDF - { - __init() {} - - float evalP22Std(float2 slope) - { - // See "Photorealistic Surface Rendering with Microfacet Theory" - // by Jonathan Dupuy 2015, Table 2.II. - return M_1_PI * exp(-(slope.x * slope.x + slope.y * slope.y)); - } - - float evalSigmaStd(float3 dir) - { - // See "Photorealistic Surface Rendering with Microfacet Theory" - // by Jonathan Dupuy 2015, Table 2.III (rearranged). - if (dir.z >= 1.f) - { - return 0.f; - } - else if (dir.z <= -1.f) - { - return 1.f; - } - - float cosTheta = -dir.z; - - if (cosTheta < -0.9f) - { - // For rays that are very steep in the up-welling direction, the analytical expression - // becomes extremely inaccurate---and this can lead to unnormalized (or even negative!) - // PDFs for the visible slopes. - - // The following expression (derived by Eugene d'Eon) is based on a series expansion of - // the erf() expression and stabilizes the function evaluation. - float u = cosTheta; - float u2 = u * u; - float u4 = u2 * u2; - float u6 = u2 * u4; - float u8 = u2 * u6; - float u10 = u2 * u8; - return (0.004407731121466846f * exp(u2 / (-1.0f + u2)) * pow(1.0f - u2, 1.5f) * (945.0f - 4200.0f * u2 + 7050.0f * u4 - 5328.0f * u6 + 1565.0f * u8)) / u10; - } - - float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta)); - float nu = cosTheta / sinTheta; - float tmp = exp(-nu * nu) / M_SQRTPI; - return 0.5f * (cosTheta * (1.0f + erf(nu)) + sinTheta * tmp); - } - - float2 sampleP22Std(inout S sg) - { - // See "Importance Sampling Microfacet-Based BSDFs with the Distribution of Visible Normals" - // (Supplemental Material 2/2) by Heitz and d'Eon 2014. - float2 u = sampleNext2D(sg); - float phi = M_2PI * u.y; - return float2(cos(phi), sin(phi)) * sqrt(-log(1.0f - u.x)); - } - - float2 sampleVisibleP22Std(float3 wi, inout S sg) - { - float thetaI = 0.0f; - float phiI = 0.0f; - if (wi.z < 0.99999f) - { - // Avoid a singularity of atan2 at theta == 0. - thetaI = acos(wi.z); - phiI = atan2(wi.y, wi.x); - } - - float2 u = sampleNext2D(sg); - - if (thetaI < 1e-4f) - { - // Trivial sampling for normal incidence. - float tmp = sqrt(-log(1.0f - u.x)); - float phi = M_2PI * u.y; - return tmp * float2(cos(phi), sin(phi)); - } - - float slopeX; - #if 0 +import Utils.Math.MathHelpers; +__exported import Rendering.Materials.INDF; + +/** Implementation of the Trowbridge-Reitz normal distribution function. + + Originally from "Average irregularity representation of a rough surface for ray reflection" + by Trowbridge and Reitz 1975. + + Also known as GGX distribution from "Microfacet Models for Refraction through Rough Surfaces" + by Walter et al. 2007. +*/ +struct TrowbridgeReitzNDF : INDF +{ + __init() {} + + float evalP22Std(float2 slope) + { + // See "Photorealistic Surface Rendering with Microfacet Theory" + // by Jonathan Dupuy 2015, Table 2.II. + float tmp = (1.0f + slope.x * slope.x + slope.y * slope.y); + return M_1_PI / (tmp * tmp); + } + + float evalSigmaStd(float3 dir) + { + // See "Photorealistic Surface Rendering with Microfacet Theory" + // by Jonathan Dupuy 2015, Table 2.III (rearranged). + return 0.5f * (1.0f - dir.z); + } + + float2 sampleP22Std(inout S sg) + { + // See "Importance Sampling Microfacet-Based BSDFs with the Distribution of Visible Normals" + // (Supplemental Material 2/2) by Heitz and d'Eon 2014. + float2 u = sampleNext2D(sg); + float phi = M_2PI * u.y; + return float2(cos(phi), sin(phi)) * sqrt(u.x) / sqrt(1.0f - u.x); + } + + float2 sampleVisibleP22Std(float3 wi, inout S sg) + { + // From "Sampling the GGX Distribution of Visible Normals" + // by Eric Heitz 2018. + + // Orthonormal basis (with special case if cross product is zero). + float lensqr = wi.x * wi.x + wi.y * wi.y; + float3 T1 = lensqr > 0.0f ? float3(-wi.y, wi.x, 0.0f) / sqrt(lensqr) : float3(1.0f, 0.0f, 0.0f); + float3 T2 = cross(wi, T1); + + // Parameterization of the projected area. + float2 p = sample_disk_concentric(sampleNext2D(sg)); + float s = 0.5f * (1.0f + wi.z); + p.y = (1.0f - s) * sqrt(1.0f - p.x * p.x) + s * p.y; + + // Reprojection to the hemisphere. + float3 mStd = p.x * T1 + p.y * T2 + sqrt(max(0.0f, 1.0f - p.x * p.x - p.y * p.y)) * wi; + + // Transform to a slope. + return float2(-mStd.x / mStd.z, -mStd.y / mStd.z); + } +} + +/** Implementation of the Beckmann-Spizzichino normal distribution function. + + Supports elliptical anisotropy and visible normals sampling. + + Originally from "The scattering of electromagnetic waves from rough surfaces" + by Beckmann and Spizzichino, 1963. +*/ +struct BeckmannSpizzichinoNDF : INDF +{ + __init() {} + + float evalP22Std(float2 slope) + { + // See "Photorealistic Surface Rendering with Microfacet Theory" + // by Jonathan Dupuy 2015, Table 2.II. + return M_1_PI * exp(-(slope.x * slope.x + slope.y * slope.y)); + } + + float evalSigmaStd(float3 dir) + { + // See "Photorealistic Surface Rendering with Microfacet Theory" + // by Jonathan Dupuy 2015, Table 2.III (rearranged). + if (dir.z >= 1.f) + { + return 0.f; + } + else if (dir.z <= -1.f) + { + return 1.f; + } + + float cosTheta = -dir.z; + + if (cosTheta < -0.9f) + { + // For rays that are very steep in the up-welling direction, the analytical expression + // becomes extremely inaccurate---and this can lead to unnormalized (or even negative!) + // PDFs for the visible slopes. + + // The following expression (derived by Eugene d'Eon) is based on a series expansion of + // the erf() expression and stabilizes the function evaluation. + float u = cosTheta; + float u2 = u * u; + float u4 = u2 * u2; + float u6 = u2 * u4; + float u8 = u2 * u6; + float u10 = u2 * u8; + return (0.004407731121466846f * exp(u2 / (-1.0f + u2)) * pow(1.0f - u2, 1.5f) * + (945.0f - 4200.0f * u2 + 7050.0f * u4 - 5328.0f * u6 + 1565.0f * u8)) / + u10; + } + + float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta)); + float nu = cosTheta / sinTheta; + float tmp = exp(-nu * nu) / M_SQRTPI; + return 0.5f * (cosTheta * (1.0f + erf(nu)) + sinTheta * tmp); + } + + float2 sampleP22Std(inout S sg) + { + // See "Importance Sampling Microfacet-Based BSDFs with the Distribution of Visible Normals" + // (Supplemental Material 2/2) by Heitz and d'Eon 2014. + float2 u = sampleNext2D(sg); + float phi = M_2PI * u.y; + return float2(cos(phi), sin(phi)) * sqrt(-log(1.0f - u.x)); + } + + float2 sampleVisibleP22Std(float3 wi, inout S sg) + { + float thetaI = 0.0f; + float phiI = 0.0f; + if (wi.z < 0.99999f) + { + // Avoid a singularity of atan2 at theta == 0. + thetaI = acos(wi.z); + phiI = atan2(wi.y, wi.x); + } + + float2 u = sampleNext2D(sg); + + if (thetaI < 1e-4f) + { + // Trivial sampling for normal incidence. + float tmp = sqrt(-log(1.0f - u.x)); + float phi = M_2PI * u.y; + return tmp * float2(cos(phi), sin(phi)); + } + + float slopeX; +#if 0 // The following implements a numerical inversion scheme for the CDF of visible slopes from // "An Improved Visible Normal Sampling Routine for the Beckmann Distribution" // by Wenzel Jakob 2014. @@ -237,128 +239,126 @@ } slopeX = erfinv(b); - #else - // This alternative scheme is an *analytical* sampling routine for the desired PDF that was - // derived based on the "triangle-cut parameterization" that is an alternative to the commonly - // used CDF inversion technique. Details about the theoretical background are described in: - // "Can't Invert the CDF? The Triangle-Cut Parameterization of the Region under the Curve" - // by Eric Heitz 2020. - // - // It works by first, sampling from an approximate proxy PDF which has an exact sampling scheme - // available, followed by a correction scheme that generates samples according the the desired - // target PDF. - // Our proxy PDF is a Gaussian that is truncated to the domain (-inf, cotThetaI] with its mean - // aligned with the maximum of the target PDF and with a standard deviation that (approximately) - // matches the tail of the target PDF. - - float cosThetaI = wi.z; - float sinThetaI = sqrt(1.0f - cosThetaI * cosThetaI); - float tanThetaI = sinThetaI / cosThetaI; - float cotThetaI = 1.0f / tanThetaI; - - if (cosThetaI > -0.9f) - { - // The mean of the proxy distribution (i.e. the maximum of the target PDF) has a closed-form expression. - float m = (cosThetaI - sqrt(cosThetaI * cosThetaI + 2.f * sinThetaI * sinThetaI)) / (2.f * sinThetaI); - // The standard deviation was matched empirically and is well approximated using a degree-4 polynomial. - float s = -0.0141259425f; - s = 0.0435127509f + s * thetaI; - s = -0.0464141286f + s * thetaI; - s = -0.0153781569f + s * thetaI; - s = 0.6710745099f + s * thetaI; - // The proxy distribution is clamped to zero where the target PDF evaluates to zero. - float xMax = cotThetaI; - - // Follow the steps of the triangle-cut inversion scheme. - - // Sample from the proxy PDF, here done via the analytic inverse of its CDF. - float erfTmp = erf((xMax - m) / (M_SQRT2 * s)); - float xa = m - M_SQRT2 * s * erfinv(1.f - u.x * (1.f + erfTmp)); - - // Evaluate the target PDF f(ax), CDF F(ax), and PDF derivative f'(ax) for the sampled point. - float sigma = 0.5f * (cosThetaI * (1.f + erf(cotThetaI)) + sinThetaI / M_SQRTPI * exp(-cotThetaI * cotThetaI)); - float invSigma = 1.f / sigma; - float expXa = exp(-xa * xa) / M_SQRTPI; - - float fxa = (-xa * sinThetaI + cosThetaI) * expXa; - fxa *= invSigma; - - float Fxa = 0.5f * (cosThetaI + cosThetaI * erf(xa) + expXa * sinThetaI); - Fxa *= invSigma; - - float dfxa = expXa * (-2.f * xa * cosThetaI + (-1.f + 2.f * xa * xa) * sinThetaI); - dfxa *= invSigma; - - // Compute the other coordinates of the cut triangle. - float xb = xa + 2.f * (u.x - Fxa) / fxa; - float ya = fxa; - float yb = 0.f; - - // Compute the infinitesimal thicknesses, via an evaluation of the proxy PDF. - float wa = fxa * fxa + 2.f * (u.x - Fxa) * dfxa; - float pProxy = exp(-(m - xa) * (m - xa) / (2.f * s * s)) * sqrt(M_2_PI) / ( s * (1.f + erfTmp)); - float wb = 2.f * fxa * pProxy - wa; - - // Sample thickness density. - float t = u.y * (wa + wb) / (wb + sqrt((1.f - u.y) * wb * wb + u.y * wa * wa)); - - // Interpolate (xa, ya) and (xb, yb). - float x = xa * t + xb * (1.f - t); - float y = ya * t + yb * (1.f - t); - - // The x-coordinate is now distribution according to the target PDF. - slopeX = x; - - // The y-coordinate can be re-mapped to a (third) uniform variate by - // dividing by the target PDF evaluated at x and can subsequently be - // reused to sample the y-coordinate of the slope. - u.y = y / (invSigma * (-x * sinThetaI + cosThetaI) / M_SQRTPI * exp(-x * x)); - } - else - { - // All hope for numerical stability is lost in the case of very steep up-welling rays. - // Instead, fall back on an expression derived by Eugene d'Eon that becomes exact in - // the limit of mu -> -1. - // This tends to be an extremely good approximation in these cases, but note that this - // is likely never going to pass any statistical (e.g. Chi^2) test comparing it to the - // PDF. - float t; - float split = pow(-cosThetaI, 1.3f); - if (u.x < split) - { - u.x = u.x / split; - // Note that an additional (third) random number needs to be created for this - // scheme. This technically breaks sample stratification---but based on the - // up-welling ray direction we are likely already in a case where many random - // numbers are being used to evaluate a stochastic BSDF. - t = u.x * sampleNext1D(sg); - } - else - { - u.x = (u.x - split) / (1.0f - split); - t = 1.0f - erf(sqrt(-log(u.x))); - } - - #if 0 +#else + // This alternative scheme is an *analytical* sampling routine for the desired PDF that was + // derived based on the "triangle-cut parameterization" that is an alternative to the commonly + // used CDF inversion technique. Details about the theoretical background are described in: + // "Can't Invert the CDF? The Triangle-Cut Parameterization of the Region under the Curve" + // by Eric Heitz 2020. + // + // It works by first, sampling from an approximate proxy PDF which has an exact sampling scheme + // available, followed by a correction scheme that generates samples according the the desired + // target PDF. + // Our proxy PDF is a Gaussian that is truncated to the domain (-inf, cotThetaI] with its mean + // aligned with the maximum of the target PDF and with a standard deviation that (approximately) + // matches the tail of the target PDF. + + float cosThetaI = wi.z; + float sinThetaI = sqrt(1.0f - cosThetaI * cosThetaI); + float tanThetaI = sinThetaI / cosThetaI; + float cotThetaI = 1.0f / tanThetaI; + + if (cosThetaI > -0.9f) + { + // The mean of the proxy distribution (i.e. the maximum of the target PDF) has a closed-form expression. + float m = (cosThetaI - sqrt(cosThetaI * cosThetaI + 2.f * sinThetaI * sinThetaI)) / (2.f * sinThetaI); + // The standard deviation was matched empirically and is well approximated using a degree-4 polynomial. + float s = -0.0141259425f; + s = 0.0435127509f + s * thetaI; + s = -0.0464141286f + s * thetaI; + s = -0.0153781569f + s * thetaI; + s = 0.6710745099f + s * thetaI; + // The proxy distribution is clamped to zero where the target PDF evaluates to zero. + float xMax = cotThetaI; + + // Follow the steps of the triangle-cut inversion scheme. + + // Sample from the proxy PDF, here done via the analytic inverse of its CDF. + float erfTmp = erf((xMax - m) / (M_SQRT2 * s)); + float xa = m - M_SQRT2 * s * erfinv(1.f - u.x * (1.f + erfTmp)); + + // Evaluate the target PDF f(ax), CDF F(ax), and PDF derivative f'(ax) for the sampled point. + float sigma = 0.5f * (cosThetaI * (1.f + erf(cotThetaI)) + sinThetaI / M_SQRTPI * exp(-cotThetaI * cotThetaI)); + float invSigma = 1.f / sigma; + float expXa = exp(-xa * xa) / M_SQRTPI; + + float fxa = (-xa * sinThetaI + cosThetaI) * expXa; + fxa *= invSigma; + + float Fxa = 0.5f * (cosThetaI + cosThetaI * erf(xa) + expXa * sinThetaI); + Fxa *= invSigma; + + float dfxa = expXa * (-2.f * xa * cosThetaI + (-1.f + 2.f * xa * xa) * sinThetaI); + dfxa *= invSigma; + + // Compute the other coordinates of the cut triangle. + float xb = xa + 2.f * (u.x - Fxa) / fxa; + float ya = fxa; + float yb = 0.f; + + // Compute the infinitesimal thicknesses, via an evaluation of the proxy PDF. + float wa = fxa * fxa + 2.f * (u.x - Fxa) * dfxa; + float pProxy = exp(-(m - xa) * (m - xa) / (2.f * s * s)) * sqrt(M_2_PI) / (s * (1.f + erfTmp)); + float wb = 2.f * fxa * pProxy - wa; + + // Sample thickness density. + float t = u.y * (wa + wb) / (wb + sqrt((1.f - u.y) * wb * wb + u.y * wa * wa)); + + // Interpolate (xa, ya) and (xb, yb). + float x = xa * t + xb * (1.f - t); + float y = ya * t + yb * (1.f - t); + + // The x-coordinate is now distribution according to the target PDF. + slopeX = x; + + // The y-coordinate can be re-mapped to a (third) uniform variate by + // dividing by the target PDF evaluated at x and can subsequently be + // reused to sample the y-coordinate of the slope. + u.y = y / (invSigma * (-x * sinThetaI + cosThetaI) / M_SQRTPI * exp(-x * x)); + } + else + { + // All hope for numerical stability is lost in the case of very steep up-welling rays. + // Instead, fall back on an expression derived by Eugene d'Eon that becomes exact in + // the limit of mu -> -1. + // This tends to be an extremely good approximation in these cases, but note that this + // is likely never going to pass any statistical (e.g. Chi^2) test comparing it to the + // PDF. + float t; + float split = pow(-cosThetaI, 1.3f); + if (u.x < split) + { + u.x = u.x / split; + // Note that an additional (third) random number needs to be created for this + // scheme. This technically breaks sample stratification---but based on the + // up-welling ray direction we are likely already in a case where many random + // numbers are being used to evaluate a stochastic BSDF. + t = u.x * sampleNext1D(sg); + } + else + { + u.x = (u.x - split) / (1.0f - split); + t = 1.0f - erf(sqrt(-log(u.x))); + } + +#if 0 slopeX = erfinv(-1.f + t + t * erf(cotThetaI)); - #else - // Because the `erf` and `erfinv` functions also become numerically unstable in these - // cases, fall back to a series expansion of these terms. - float cotThetaI2 = cotThetaI * cotThetaI; - float cotThetaI3 = cotThetaI * cotThetaI2; - - float tmp = 2.0f * cotThetaI2 + log(8.0f) - 2.0f * log((t - 2.0f * cotThetaI2 * t) / cotThetaI3); - slopeX = -sqrt(tmp - log(tmp)) * M_1_SQRT2; - #endif - } - #endif - float slopeY = erfinv(2.0f * u.y - 1.0f); - - // Rotate. - float sinPhiI = sin(phiI); - float cosPhiI = cos(phiI); - return float2(cosPhiI * slopeX - sinPhiI * slopeY, - sinPhiI * slopeX + cosPhiI * slopeY); - } - } - \ No newline at end of file +#else + // Because the `erf` and `erfinv` functions also become numerically unstable in these + // cases, fall back to a series expansion of these terms. + float cotThetaI2 = cotThetaI * cotThetaI; + float cotThetaI3 = cotThetaI * cotThetaI2; + + float tmp = 2.0f * cotThetaI2 + log(8.0f) - 2.0f * log((t - 2.0f * cotThetaI2 * t) / cotThetaI3); + slopeX = -sqrt(tmp - log(tmp)) * M_1_SQRT2; +#endif + } +#endif + float slopeY = erfinv(2.0f * u.y - 1.0f); + + // Rotate. + float sinPhiI = sin(phiI); + float cosPhiI = cos(phiI); + return float2(cosPhiI * slopeX - sinPhiI * slopeY, sinPhiI * slopeX + cosPhiI * slopeY); + } +} diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang index 41efd7efc2..3ed30f4227 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang @@ -36,7 +36,12 @@ struct PBRTCoatedConductorMaterial : MaterialBase, IMaterial typedef PBRTCoatedConductorMaterialInstance MaterialInstance; - PBRTCoatedConductorMaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTCoatedConductorMaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -61,13 +66,21 @@ struct PBRTCoatedConductorMaterial : MaterialBase, IMaterial adjustShadingNormal(sd, sf); } - PBRTCoatedConductorMaterialInstance mi = {sf, AnisotropicGGX(roughnesses.rg), AnisotropicGGX(roughnesses.ba), eta, baseColor, transColor, 32}; + PBRTCoatedConductorMaterialInstance mi = { + sf, AnisotropicGGX(roughnesses.rg), AnisotropicGGX(roughnesses.ba), eta, baseColor, transColor, 32 + }; return mi; } [Differentiable] - PBRTCoatedConductorMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTCoatedConductorMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterialInstance.slang index 9c71b42701..6e469d98e7 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterialInstance.slang @@ -33,7 +33,7 @@ import Rendering.Materials.PBRT.PBRTDielectricMaterialInstance; struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. AnisotropicGGX interfaceD; AnisotropicGGX conductorD; float interfaceEta; @@ -46,10 +46,11 @@ struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInst float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); - PBRTDielectricBSDF top = {interfaceD, interfaceEta}; - PBRTConductorBSDF bottom = {conductorD, conductorEta, conductorK}; + PBRTDielectricBSDF top = { interfaceD, interfaceEta }; + PBRTConductorBSDF bottom = { conductorD, conductorEta, conductorK }; const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); return LayeredBSDF(top, bottom).eval(wiLocal, woLocal, sg, bc); @@ -66,8 +67,8 @@ struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInst float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = {}; - PBRTDielectricBSDF top = {interfaceD, interfaceEta}; - PBRTConductorBSDF bottom = {conductorD, conductorEta, conductorK}; + PBRTDielectricBSDF top = { interfaceD, interfaceEta }; + PBRTConductorBSDF bottom = { conductorD, conductorEta, conductorK }; // We flip the material so that on both sides we coat the conductor with the dielectric. Note that this has little implications // since we evaluate black when hitting the inside. @@ -75,7 +76,8 @@ struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInst bool valid = LayeredBSDF(top, bottom).sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, bc); result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) + return false; return valid; } @@ -85,10 +87,11 @@ struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInst float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return 0.f; - PBRTDielectricBSDF top = {interfaceD, interfaceEta}; - PBRTConductorBSDF bottom = {conductorD, conductorEta, conductorK}; + PBRTDielectricBSDF top = { interfaceD, interfaceEta }; + PBRTConductorBSDF bottom = { conductorD, conductorEta, conductorK }; const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); return LayeredBSDF(top, bottom).evalPdf(wiLocal, woLocal, bc); @@ -107,8 +110,8 @@ struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInst uint getLobeTypes(const ShadingData sd) { - return (uint)(interfaceD.isSingular() ? LobeType::DeltaReflection : LobeType::SpecularReflection) - | (uint)(conductorD.isSingular() ? LobeType::DeltaReflection : LobeType::SpecularReflection); + return (uint)(interfaceD.isSingular() ? LobeType::DeltaReflection : LobeType::SpecularReflection) | + (uint)(conductorD.isSingular() ? LobeType::DeltaReflection : LobeType::SpecularReflection); } }; diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang index 040fef7495..0f12723e3d 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang @@ -36,7 +36,12 @@ struct PBRTCoatedDiffuseMaterial : MaterialBase, IMaterial typedef PBRTCoatedDiffuseMaterialInstance MaterialInstance; - PBRTCoatedDiffuseMaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTCoatedDiffuseMaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -58,13 +63,19 @@ struct PBRTCoatedDiffuseMaterial : MaterialBase, IMaterial adjustShadingNormal(sd, sf); } - PBRTCoatedDiffuseMaterialInstance mi = {sf, AnisotropicGGX(roughness), eta, baseColor, 32}; + PBRTCoatedDiffuseMaterialInstance mi = { sf, AnisotropicGGX(roughness), eta, baseColor, 32 }; return mi; } [Differentiable] - PBRTCoatedDiffuseMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTCoatedDiffuseMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterialInstance.slang index 23f4ac5400..768e24f57f 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterialInstance.slang @@ -33,7 +33,7 @@ import Rendering.Materials.PBRT.PBRTDiffuseMaterialInstance; struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. AnisotropicGGX interfaceD; float interfaceEta; float3 diffuseAlbedo; @@ -44,10 +44,11 @@ struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstan float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); - PBRTDielectricBSDF top = {interfaceD, interfaceEta}; - PBRTDiffuseBSDF bottom = {diffuseAlbedo}; + PBRTDielectricBSDF top = { interfaceD, interfaceEta }; + PBRTDiffuseBSDF bottom = { diffuseAlbedo }; const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); return LayeredBSDF(top, bottom).eval(wiLocal, woLocal, sg, bc); @@ -64,8 +65,8 @@ struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstan float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = {}; - PBRTDielectricBSDF top = {interfaceD, interfaceEta}; - PBRTDiffuseBSDF bottom = {diffuseAlbedo}; + PBRTDielectricBSDF top = { interfaceD, interfaceEta }; + PBRTDiffuseBSDF bottom = { diffuseAlbedo }; // We flip the material so that on both sides we coat the conductor with the dielectric. Note that this has little implications // since we evaluate black when hitting the inside. @@ -73,7 +74,8 @@ struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstan bool valid = LayeredBSDF(top, bottom).sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, bc); result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) + return false; return valid; } @@ -83,10 +85,11 @@ struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstan float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return 0.f; - PBRTDielectricBSDF top = {interfaceD, interfaceEta}; - PBRTDiffuseBSDF bottom = {diffuseAlbedo}; + PBRTDielectricBSDF top = { interfaceD, interfaceEta }; + PBRTDiffuseBSDF bottom = { diffuseAlbedo }; const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); return LayeredBSDF(top, bottom).evalPdf(wiLocal, woLocal, bc); @@ -105,7 +108,8 @@ struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstan uint getLobeTypes(const ShadingData sd) { - return (uint)(LobeType::DiffuseReflection) | (uint)(interfaceD.isSingular() ? LobeType::DeltaReflection : LobeType::SpecularReflection); + return (uint)(LobeType::DiffuseReflection) | + (uint)(interfaceD.isSingular() ? LobeType::DeltaReflection : LobeType::SpecularReflection); } }; diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterial.slang index 77404ad2ac..68ed440015 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterial.slang @@ -37,7 +37,12 @@ struct PBRTConductorMaterial : MaterialBase, IMaterial typedef PBRTConductorMaterialInstance MaterialInstance; - PBRTConductorMaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTConductorMaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -59,13 +64,19 @@ struct PBRTConductorMaterial : MaterialBase, IMaterial adjustShadingNormal(sd, sf); } - PBRTConductorMaterialInstance mi = {sf, {AnisotropicGGX(roughness), baseColor, transColor}}; + PBRTConductorMaterialInstance mi = { sf, PBRTConductorBSDF( AnisotropicGGX(roughness), baseColor, transColor ) }; return mi; } [Differentiable] - PBRTConductorMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTConductorMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -96,7 +107,7 @@ struct PBRTConductorMaterial : MaterialBase, IMaterial no_diff adjustShadingNormal(sd, sf); } - PBRTConductorMaterialInstance mi = { sf, { AnisotropicGGX(roughness), baseColor, transColor } }; + PBRTConductorMaterialInstance mi = { sf, no_diff PBRTConductorBSDF(AnisotropicGGX(roughness), baseColor, transColor) }; diffData.sf = sf; uint offset = 0; diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterialInstance.slang index 38c161a0f2..c92b3d16e0 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterialInstance.slang @@ -33,9 +33,18 @@ import Rendering.Materials.Fresnel; struct PBRTConductorBSDF : IBSDF, IDifferentiable { - AnisotropicGGX D; - float3 eta; - float3 k; + AnisotropicGGX D = {}; + float3 eta = {}; + float3 k = {}; + + __init() {} + + __init(AnisotropicGGX D_, float3 eta_, float3 k_) + { + D = D_; + eta = eta_; + k = k_; + } [Differentiable] __init(const DiffMaterialData diffData) @@ -50,7 +59,8 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable [Differentiable] float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg, BSDFContext bc) { - if (D.isSingular() || min(wiLocal.z, woLocal.z) < 0.0f) return float3(0.0f); + if (D.isSingular() || min(wiLocal.z, woLocal.z) < 0.0f) + return float3(0.0f); float3 h = normalize(wiLocal + woLocal); float wiDotH = abs(dot(wiLocal, h)); @@ -59,14 +69,23 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable return D.G2(woLocal, wiLocal) * D.evalNDF(h) * F / (4.0f * abs(wiLocal.z)); } - bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) + bool sample( + float3 wiLocal, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = {}; pdf = {}; weight = {}; lobeType = {}; - if (wiLocal.z < 0.0f) return false; + if (wiLocal.z < 0.0f) + return false; if (D.isSingular()) { @@ -80,7 +99,8 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable float3 h = D.sample(wiLocal, sg); float3 woLocal = reflect(-wiLocal, h); - if (woLocal.z < 0.0f) return false; + if (woLocal.z < 0.0f) + return false; float3 F = evalFresnelConductor(eta, k, dot(h, wiLocal)); wo = woLocal; @@ -94,7 +114,8 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable float evalPdf(const float3 wiLocal, const float3 woLocal, BSDFContext bc) { - if (D.isSingular() || min(wiLocal.z, woLocal.z) < 0.0f) return 0.0f; + if (D.isSingular() || min(wiLocal.z, woLocal.z) < 0.0f) + return 0.0f; float3 h = normalize(wiLocal + woLocal); return D.evalPDF(wiLocal, h) / (4.0f * abs(dot(wiLocal, h))); } @@ -105,14 +126,11 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable return AlbedoContributions(r, 1.0f - r, 0.0f, 0.0f); } - float3 getIorAsReflectance() - { - return float3(1.f); - } + float3 getIorAsReflectance() { return float3(1.f); } RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = D.alpha; return r; } @@ -120,7 +138,7 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. PBRTConductorBSDF bsdf; float3 eval(const ShadingData sd, const float3 wo, inout S sg) @@ -128,7 +146,8 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); return bsdf.eval(wiLocal, woLocal, sg, BSDFContext()); } @@ -139,7 +158,8 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = diffData.sf.toLocal(sd.V); float3 woLocal = diffData.sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); PBRTConductorBSDF bsdfAD = PBRTConductorBSDF(diffData); return bsdfAD.eval(wiLocal, woLocal, sg, BSDFContext()); @@ -153,7 +173,8 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance bool valid = bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext()); result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) + return false; return valid; } @@ -163,7 +184,8 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return 0.f; return bsdf.evalPdf(wiLocal, woLocal, BSDFContext()); } @@ -179,6 +201,16 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance return p; } + bool supportSimpleRoughnessThreshold() + { + return true; // single specular + } + + bool isSampleRough(const ShadingData sd, int sampledBSDFIndex, float samplePdf, float roughnessThreshold) + { + return getProperties(sd).roughness > roughnessThreshold; + } + uint getLobeTypes(const ShadingData sd) { return (uint)(bsdf.D.isSingular() ? LobeType::DeltaReflection : LobeType::SpecularReflection); diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterial.slang index 68b4a7cc78..2ee09b4c33 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterial.slang @@ -36,7 +36,12 @@ struct PBRTDielectricMaterial : MaterialBase, IMaterial typedef PBRTDielectricMaterialInstance MaterialInstance; - PBRTDielectricMaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTDielectricMaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -57,13 +62,19 @@ struct PBRTDielectricMaterial : MaterialBase, IMaterial adjustShadingNormal(sd, sf); } - PBRTDielectricMaterialInstance mi = {sf, {AnisotropicGGX(roughness), eta}}; + PBRTDielectricMaterialInstance mi = { sf, { AnisotropicGGX(roughness), eta } }; return mi; } [Differentiable] - PBRTDielectricMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTDielectricMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterialInstance.slang index fc371d138c..3d973cdf14 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterialInstance.slang @@ -33,14 +33,15 @@ import Utils.Debug.PixelDebug; struct PBRTDielectricBSDF : IBSDF { - AnisotropicGGX D; - float eta; + AnisotropicGGX D = {}; + float eta = {}; float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg, BSDFContext bc) { const float relEta = bc.iorT / bc.iorI; - if (D.isSingular() || relEta == 1.0f || wiLocal.z * woLocal.z == 0) return float3(0.0f); + if (D.isSingular() || relEta == 1.0f || wiLocal.z * woLocal.z == 0) + return float3(0.0f); bool reflect = wiLocal.z * woLocal.z > 0.0f; const float relEtaInverse = 1.f / relEta; @@ -48,14 +49,24 @@ struct PBRTDielectricBSDF : IBSDF h *= sign(h.z); float wiDotH = dot(wiLocal, h); float woDotH = dot(woLocal, h); - if (wiDotH * wiLocal.z < 0.0f || woDotH * woLocal.z < 0.0f) return float3(0.0f); + if (wiDotH * wiLocal.z < 0.0f || woDotH * woLocal.z < 0.0f) + return float3(0.0f); float F = evalFresnelDielectric(relEtaInverse, abs(wiDotH)); return reflect ? D.G2(woLocal, wiLocal) * D.evalNDF(h) * F / (4.0f * abs(wiLocal.z)) - : D.G2(woLocal, wiLocal) * D.evalNDF(h) * (1.0f - F) * abs(wiDotH * woDotH / (sqr(woDotH + wiDotH / relEta) * abs(wiLocal.z))); + : D.G2(woLocal, wiLocal) * D.evalNDF(h) * (1.0f - F) * + abs(wiDotH * woDotH / (sqr(woDotH + wiDotH / relEta) * abs(wiLocal.z))); } - bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) + bool sample( + float3 wiLocal, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = {}; pdf = {}; @@ -93,10 +104,11 @@ struct PBRTDielectricBSDF : IBSDF if (sampleNext1D(sg) < F) { float3 woLocal = reflect(-wiLocal, h); - if (wiLocal.z * woLocal.z < 0.0f) return false; + if (wiLocal.z * woLocal.z < 0.0f) + return false; wo = woLocal; - weight = D.G2(woLocal, wiLocal)/D.G1(wiLocal); + weight = D.G2(woLocal, wiLocal) / D.G1(wiLocal); lobeType = (uint)LobeType::SpecularReflection; pdf = F * D.evalPDF(wiLocal, h) / (4.0f * abs(wiDotH)); } @@ -104,10 +116,11 @@ struct PBRTDielectricBSDF : IBSDF { float3 woLocal = refract(-wiLocal, sign(wiLocal.z) * h, relEtaInverse); float woDotH = dot(h, woLocal); - if (wiLocal.z * woLocal.z > 0.0f) return false; + if (wiLocal.z * woLocal.z > 0.0f) + return false; wo = woLocal; - weight = D.G2(woLocal, wiLocal)/D.G1(wiLocal); + weight = D.G2(woLocal, wiLocal) / D.G1(wiLocal); lobeType = (uint)LobeType::SpecularTransmission; pdf = (1.0f - F) * D.evalPDF(wiLocal, h) * abs(woDotH) / sqr(woDotH + wiDotH / relEta); } @@ -119,14 +132,16 @@ struct PBRTDielectricBSDF : IBSDF { const float relEta = bc.iorT / bc.iorI; const float relEtaInverse = 1.f / relEta; - if (D.isSingular() || relEta == 1.0f || wiLocal.z * woLocal.z == 0) return 0.0f; + if (D.isSingular() || relEta == 1.0f || wiLocal.z * woLocal.z == 0) + return 0.0f; bool reflect = wiLocal.z * woLocal.z > 0.0f; float3 h = reflect ? normalize(wiLocal + woLocal) : normalize(wiLocal / relEta + woLocal); h *= sign(h.z); float wiDotH = dot(wiLocal, h); float woDotH = dot(woLocal, h); - if (wiDotH * wiLocal.z < 0.0f || woDotH * woLocal.z < 0.0f) return 0.0f; + if (wiDotH * wiLocal.z < 0.0f || woDotH * woLocal.z < 0.0f) + return 0.0f; float F = evalFresnelDielectric(relEtaInverse, abs(wiDotH)); return reflect ? F * D.evalPDF(wiLocal, h) / (4.0f * abs(wiDotH)) @@ -148,15 +163,12 @@ struct PBRTDielectricBSDF : IBSDF RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = D.alpha; return r; } - float getEta() - { - return eta; - } + float getEta() { return eta; } }; /** PBRT dielectric material instance. @@ -165,7 +177,7 @@ struct PBRTDielectricBSDF : IBSDF */ struct PBRTDielectricMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. PBRTDielectricBSDF bsdf; float3 eval(const ShadingData sd, const float3 wo, inout S sg) @@ -173,9 +185,10 @@ struct PBRTDielectricMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) return float3(0.f); + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) + return float3(0.f); - BSDFContext bc = sd.frontFacing? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); + BSDFContext bc = sd.frontFacing ? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); return bsdf.eval(wiLocal, woLocal, sg, bc); } @@ -190,12 +203,13 @@ struct PBRTDielectricMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = {}; - BSDFContext bc = sd.frontFacing? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); + BSDFContext bc = sd.frontFacing ? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); if (!bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, bc)) return false; result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, result.wo, true)) return false; + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, result.wo, true)) + return false; return true; } @@ -205,9 +219,10 @@ struct PBRTDielectricMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) return 0.f; + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) + return 0.f; - BSDFContext bc = sd.frontFacing? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); + BSDFContext bc = sd.frontFacing ? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); return bsdf.evalPdf(wiLocal, woLocal, bc); } @@ -225,6 +240,16 @@ struct PBRTDielectricMaterialInstance : MaterialInstanceBase, IMaterialInstance return p; } + bool supportSimpleRoughnessThreshold() + { + return true; // single specular + } + + bool isSampleRough(const ShadingData sd, int sampledBSDFIndex, float samplePdf, float roughnessThreshold) + { + return getProperties(sd).roughness > roughnessThreshold; + } + uint getLobeTypes(const ShadingData sd) { return bsdf.D.isSingular() ? ((uint)LobeType::DeltaReflection | (uint)LobeType::DeltaTransmission) diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterial.slang index df5768998c..e4eb548425 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterial.slang @@ -37,7 +37,12 @@ struct PBRTDiffuseMaterial : MaterialBase, IMaterial typedef PBRTDiffuseMaterialInstance MaterialInstance; - PBRTDiffuseMaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTDiffuseMaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -56,13 +61,19 @@ struct PBRTDiffuseMaterial : MaterialBase, IMaterial adjustShadingNormal(sd, sf); } - PBRTDiffuseMaterialInstance mi = {sf, {baseColor}}; + PBRTDiffuseMaterialInstance mi = { sf, { baseColor } }; return mi; } [Differentiable] - PBRTDiffuseMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTDiffuseMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -88,7 +99,7 @@ struct PBRTDiffuseMaterial : MaterialBase, IMaterial no_diff adjustShadingNormal(sd, sf); } - PBRTDiffuseMaterialInstance mi = { sf, {baseColor} }; + PBRTDiffuseMaterialInstance mi = { sf, { baseColor } }; diffData.sf = sf; uint offset = 0; diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterialInstance.slang index 892eb02df1..c10bc31994 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterialInstance.slang @@ -34,7 +34,7 @@ import Utils.Math.MathHelpers; struct PBRTDiffuseBSDF : IBSDF, IDifferentiable { - float3 albedo; + float3 albedo = {}; [Differentiable] float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg, BSDFContext bc) @@ -42,7 +42,15 @@ struct PBRTDiffuseBSDF : IBSDF, IDifferentiable return albedo * max(woLocal.z, 0.0f) * M_1_PI; } - bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) + bool sample( + float3 wiLocal, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); weight = albedo; @@ -51,24 +59,18 @@ struct PBRTDiffuseBSDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wiLocal, const float3 woLocal, BSDFContext bc) - { - return max(woLocal.z, 0.0f) * M_1_PI; - } + float evalPdf(const float3 wiLocal, const float3 woLocal, BSDFContext bc) { return max(woLocal.z, 0.0f) * M_1_PI; } AlbedoContributions evalAlbedo(const float3 wi, const LobeType lobetype) { return AlbedoContributions(albedo, 1.0f - albedo, 0.0f, 0.0f); } - float3 getIorAsReflectance() - { - return float3(1.f); - } + float3 getIorAsReflectance() { return float3(1.f); } RoughnessInformation getRoughnessInformation(const float3 wi) { - RoughnessInformation r; + RoughnessInformation r = {}; r.roughnessBSDFNotation = float2(0.5f, 0.5f); return r; } @@ -76,7 +78,7 @@ struct PBRTDiffuseBSDF : IBSDF, IDifferentiable struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. PBRTDiffuseBSDF bsdf; float3 eval(const ShadingData sd, const float3 wo, inout S sg) @@ -84,7 +86,8 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); return bsdf.eval(wiLocal, woLocal, sg, BSDFContext()); } @@ -95,7 +98,8 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = diffData.sf.toLocal(sd.V); float3 woLocal = diffData.sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); uint offset = 0; float3 albedo = diffData.read<3>(offset); @@ -112,10 +116,10 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance bool valid = bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext()); result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) + return false; return valid; - } float evalPdf(const ShadingData sd, const float3 wo, bool useImportanceSampling = true) @@ -123,7 +127,8 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return 0.f; return bsdf.evalPdf(wiLocal, woLocal, BSDFContext()); } @@ -139,9 +144,16 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance return p; } - uint getLobeTypes(const ShadingData sd) + bool supportSimpleRoughnessThreshold() { - return (uint)LobeType::DiffuseReflection; + return true; // single diffuse layer } + bool isSampleRough(const ShadingData sd, int sampledBSDFIndex, float samplePdf, float roughnessThreshold) + { + return true; + } + + uint getLobeTypes(const ShadingData sd) { return (uint)LobeType::DiffuseReflection; } + }; diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterial.slang index 87bc52b9c2..7a0013e16c 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterial.slang @@ -36,7 +36,12 @@ struct PBRTDiffuseTransmissionMaterial : MaterialBase, IMaterial typedef PBRTDiffuseTransmissionMaterialInstance MaterialInstance; - PBRTDiffuseTransmissionMaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTDiffuseTransmissionMaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -56,13 +61,19 @@ struct PBRTDiffuseTransmissionMaterial : MaterialBase, IMaterial adjustShadingNormal(sd, sf); } - PBRTDiffuseTransmissionMaterialInstance mi = {sf, baseColor, transColor}; + PBRTDiffuseTransmissionMaterialInstance mi = { sf, baseColor, transColor }; return mi; } [Differentiable] - PBRTDiffuseTransmissionMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + PBRTDiffuseTransmissionMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterialInstance.slang index a7fa14c6f4..7f520e3f00 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterialInstance.slang @@ -37,7 +37,7 @@ import Utils.Math.MathHelpers; */ struct PBRTDiffuseTransmissionMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. float3 albedoR; float3 albedoT; @@ -46,7 +46,8 @@ struct PBRTDiffuseTransmissionMaterialInstance : MaterialInstanceBase, IMaterial float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) return float3(0.f); + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) + return float3(0.f); float3 albedo = (wiLocal.z * woLocal.z > 0.0f) ? albedoR : albedoT; return albedo * abs(woLocal.z) * M_1_PI; @@ -62,11 +63,13 @@ struct PBRTDiffuseTransmissionMaterialInstance : MaterialInstanceBase, IMaterial { float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sample_cosine_hemisphere_concentric(sampleNext2D(sg), result.pdf); - if (wiLocal.z < 0.0f) woLocal = -woLocal; + if (wiLocal.z < 0.0f) + woLocal = -woLocal; float PR = luminance(albedoR); float PT = luminance(albedoT); - if (PR + PT == 0.0f) return false; + if (PR + PT == 0.0f) + return false; if (sampleNext1D(sg) < PT / (PR + PT)) { woLocal = -woLocal; @@ -82,7 +85,8 @@ struct PBRTDiffuseTransmissionMaterialInstance : MaterialInstanceBase, IMaterial } result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, result.wo, true)) return false; + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, result.wo, true)) + return false; return true; } @@ -92,11 +96,13 @@ struct PBRTDiffuseTransmissionMaterialInstance : MaterialInstanceBase, IMaterial float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) return 0.f; + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) + return 0.f; float PR = luminance(albedoR); float PT = luminance(albedoT); - if (PR + PT == 0.0f) return 0.0f; + if (PR + PT == 0.0f) + return 0.0f; float P = (wiLocal.z * woLocal.z > 0.0f) ? PR / (PR + PT) : PT / (PR + PT); return P * abs(woLocal.z) * M_1_PI; @@ -115,9 +121,6 @@ struct PBRTDiffuseTransmissionMaterialInstance : MaterialInstanceBase, IMaterial return p; } - uint getLobeTypes(const ShadingData sd) - { - return (uint)LobeType::DiffuseReflection | (uint)LobeType::DiffuseTransmission; - } + uint getLobeTypes(const ShadingData sd) { return (uint)LobeType::DiffuseReflection | (uint)LobeType::DiffuseTransmission; } }; diff --git a/Source/Falcor/Rendering/Materials/RGLAcquisition.cpp b/Source/Falcor/Rendering/Materials/RGLAcquisition.cpp index 94776c0c04..bf75a2ab12 100644 --- a/Source/Falcor/Rendering/Materials/RGLAcquisition.cpp +++ b/Source/Falcor/Rendering/Materials/RGLAcquisition.cpp @@ -33,219 +33,217 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/RGLAcquisition.cs.slang"; - const char kParameterBlock[] = "gAcquisition"; - - // Number of data points in the incident-phi domain. We do isotropic only, so 1 sample is enough. - const uint kPhiSize = 1; - // Number of data points in the incident-theta domain. - const uint kThetaSize = 8; - // Number of data points for half vector (theta, phi) domain. - const uint2 kNDFSize = uint2(128, 2); - // Number of grid points to sample for numerically integrating the NDF cross section. - const uint2 kSigmaIntegrationGrid = uint2(128, 128); - - // Size of the VNDF and measured data in the wi and wo domain. wi is dictated by phiSize/thetaSize. - // We choose the same defaults as the RGL paper for the wo domain. - const uint4 kVNDFSize = uint4(kPhiSize, kThetaSize, 128, 128); - const uint4 kLumiSize = uint4(kPhiSize, kThetaSize, 32, 32); - // Number of power iterations to use for extracting the largest eigen vector. - // Same number as in the RGL paper (seems to be enough). - const uint kEigenVectorPowerIterations = 4; - // There does not seem to be a good way of finding out the wave size from the host side. - // Assume a minimum of 16 lanes. Underestimating the true number just costs more memory, - // overestimating it causes out-of-bounds access. - const uint kMinWaveSize = 16; - - // Precomputed number of elements in each of the arrays, for convenience. - const uint kNDFN = kNDFSize.x * kNDFSize.y; - const uint kSigmaIntegrationN = kSigmaIntegrationGrid.x * kSigmaIntegrationGrid.y; - const uint kLumiN = kLumiSize.x * kLumiSize.y * kLumiSize.z * kLumiSize.w; - const uint kVNDFN = kVNDFSize.x * kVNDFSize.y * kVNDFSize.z * kVNDFSize.w; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/RGLAcquisition.cs.slang"; +const char kParameterBlock[] = "gAcquisition"; + +// Number of data points in the incident-phi domain. We do isotropic only, so 1 sample is enough. +const uint kPhiSize = 1; +// Number of data points in the incident-theta domain. +const uint kThetaSize = 8; +// Number of data points for half vector (theta, phi) domain. +const uint2 kNDFSize = uint2(128, 2); +// Number of grid points to sample for numerically integrating the NDF cross section. +const uint2 kSigmaIntegrationGrid = uint2(128, 128); + +// Size of the VNDF and measured data in the wi and wo domain. wi is dictated by phiSize/thetaSize. +// We choose the same defaults as the RGL paper for the wo domain. +const uint4 kVNDFSize = uint4(kPhiSize, kThetaSize, 128, 128); +const uint4 kLumiSize = uint4(kPhiSize, kThetaSize, 32, 32); +// Number of power iterations to use for extracting the largest eigen vector. +// Same number as in the RGL paper (seems to be enough). +const uint kEigenVectorPowerIterations = 4; +// There does not seem to be a good way of finding out the wave size from the host side. +// Assume a minimum of 16 lanes. Underestimating the true number just costs more memory, +// overestimating it causes out-of-bounds access. +const uint kMinWaveSize = 16; + +// Precomputed number of elements in each of the arrays, for convenience. +const uint kNDFN = kNDFSize.x * kNDFSize.y; +const uint kSigmaIntegrationN = kSigmaIntegrationGrid.x * kSigmaIntegrationGrid.y; +const uint kLumiN = kLumiSize.x * kLumiSize.y * kLumiSize.z * kLumiSize.w; +const uint kVNDFN = kVNDFSize.x * kVNDFSize.y * kVNDFSize.z * kVNDFSize.w; +} // namespace + +RGLAcquisition::RGLAcquisition(ref pDevice, const ref& pScene) : mpDevice(pDevice), mpScene(pScene) +{ + FALCOR_CHECK(mpDevice != nullptr, "'pDevice' must be a valid device"); + FALCOR_CHECK(pScene != nullptr, "'pScene' must be a valid scene"); - RGLAcquisition::RGLAcquisition(ref pDevice, const ref& pScene) - : mpDevice(pDevice) - , mpScene(pScene) + ProgramDesc descBase; + descBase.addShaderModules(pScene->getShaderModules()); + descBase.addShaderLibrary(kShaderFile); + descBase.addTypeConformances(pScene->getTypeConformances()); + auto defines = pScene->getSceneDefines(); + + auto addEntryPoint = [&](const char* name) + { + auto desc = descBase; + desc.csEntry(name); + return ComputePass::create(mpDevice, desc, defines); + }; + + mpRetroReflectionPass = addEntryPoint("measureRetroreflection"); + mpBuildKernelPass = addEntryPoint("buildPowerIterationKernel"); + mpPowerIterationPass = addEntryPoint("powerIteration"); + mpIntegrateSigmaPass = addEntryPoint("integrateSigma"); + mpSumSigmaPass = addEntryPoint("sumSigma"); + mpComputeThetaPass = addEntryPoint("computeTheta"); + mpComputeVNDFPass = addEntryPoint("computeVNDF"); + mpAcquireBRDFPass = addEntryPoint("acquireBRDF"); + + auto createStructured = [&](size_t elemSize, size_t count, const void* srcData = nullptr) { - FALCOR_CHECK(mpDevice != nullptr, "'pDevice' must be a valid device"); - FALCOR_CHECK(pScene != nullptr, "'pScene' must be a valid scene"); + return mpDevice->createStructuredBuffer( + uint32_t(elemSize), + uint32_t(count), + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + srcData, + false + ); + }; + + mpNDFDirectionsBuffer = createStructured(sizeof(float3), kNDFN); + mpRetroBuffer = createStructured(sizeof(float), kNDFN); + mpNDFKernelBuffer = createStructured(sizeof(float), kNDFN * kNDFN); + mpNDFBufferTmp = createStructured(sizeof(float), kNDFN); + mpNDFBuffer = createStructured(sizeof(float), kNDFN); + mpSigmaBuffer = createStructured(sizeof(float), kSigmaIntegrationN * kNDFN / kMinWaveSize); + mpThetaBuffer = createStructured(sizeof(float), kThetaSize); + mpPhiBuffer = createStructured(sizeof(float), kPhiSize); + mpVNDFBuffer = createStructured(sizeof(float), kVNDFN); + mpLumiBuffer = createStructured(sizeof(float), kLumiN); + mpRGBBuffer = createStructured(sizeof(float), kLumiN * 3); + mpVNDFCondBuffer = createStructured(sizeof(float), kVNDFN); + mpVNDFMargBuffer = createStructured(sizeof(float), kVNDFN / kVNDFSize.z); +} - ProgramDesc descBase; - descBase.addShaderModules(pScene->getShaderModules()); - descBase.addShaderLibrary(kShaderFile); - descBase.addTypeConformances(pScene->getTypeConformances()); - auto defines = pScene->getSceneDefines(); +void RGLAcquisition::acquireIsotropic(RenderContext* pRenderContext, const MaterialID materialID) +{ + FALCOR_ASSERT(mpScene); + FALCOR_CHECK(materialID.get() < mpScene->getMaterialCount(), "'materialID' is out of range"); - auto addEntryPoint = [&](const char* name) - { - auto desc = descBase; - desc.csEntry(name); - return ComputePass::create(mpDevice, desc, defines); - }; - - mpRetroReflectionPass = addEntryPoint("measureRetroreflection"); - mpBuildKernelPass = addEntryPoint("buildPowerIterationKernel"); - mpPowerIterationPass = addEntryPoint("powerIteration"); - mpIntegrateSigmaPass = addEntryPoint("integrateSigma"); - mpSumSigmaPass = addEntryPoint("sumSigma"); - mpComputeThetaPass = addEntryPoint("computeTheta"); - mpComputeVNDFPass = addEntryPoint("computeVNDF"); - mpAcquireBRDFPass = addEntryPoint("acquireBRDF"); - - auto createStructured = [&](size_t elemSize, size_t count, const void* srcData = nullptr) - { - return mpDevice->createStructuredBuffer( - uint32_t(elemSize), - uint32_t(count), - ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, - MemoryType::DeviceLocal, - srcData, - false - ); - }; - - mpNDFDirectionsBuffer = createStructured(sizeof(float3), kNDFN); - mpRetroBuffer = createStructured(sizeof(float), kNDFN); - mpNDFKernelBuffer = createStructured(sizeof(float), kNDFN * kNDFN); - mpNDFBufferTmp = createStructured(sizeof(float), kNDFN); - mpNDFBuffer = createStructured(sizeof(float), kNDFN); - mpSigmaBuffer = createStructured(sizeof(float), kSigmaIntegrationN * kNDFN / kMinWaveSize); - mpThetaBuffer = createStructured(sizeof(float), kThetaSize); - mpPhiBuffer = createStructured(sizeof(float), kPhiSize); - mpVNDFBuffer = createStructured(sizeof(float), kVNDFN); - mpLumiBuffer = createStructured(sizeof(float), kLumiN); - mpRGBBuffer = createStructured(sizeof(float), kLumiN * 3); - mpVNDFCondBuffer = createStructured(sizeof(float), kVNDFN); - mpVNDFMargBuffer = createStructured(sizeof(float), kVNDFN / kVNDFSize.z); - } + CpuTimer timer; + timer.update(); - void RGLAcquisition::acquireIsotropic(RenderContext* pRenderContext, const MaterialID materialID) + auto setupParams = [&](const ref& pPass) { - FALCOR_ASSERT(mpScene); - FALCOR_CHECK(materialID.get() < mpScene->getMaterialCount(), "'materialID' is out of range"); - - CpuTimer timer; - timer.update(); - - auto setupParams = [&](const ref& pPass) - { - auto var = pPass->getRootVar()[kParameterBlock]; - var["materialID"] = materialID.getSlang(); - - var["ndfSize"] = kNDFSize; - var["phiSize"] = kPhiSize; - var["vndfSize"] = kVNDFSize; - var["lumiSize"] = kLumiSize; - var["thetaSize"] = kThetaSize; - var["sigmaIntegrationGrid"] = kSigmaIntegrationGrid; - - var["retroReflection"] = mpRetroBuffer; - var["ndfDirections"] = mpNDFDirectionsBuffer; - var["ndfKernel"] = mpNDFKernelBuffer; - var["ndf"] = mpNDFBuffer; - var["sigma"] = mpSigmaBuffer; - var["phis"] = mpPhiBuffer; - var["thetas"] = mpThetaBuffer; - var["vndfBuf"] = mpVNDFBuffer; - var["vndfMarginalBuf"] = mpVNDFMargBuffer; - var["vndfConditionalBuf"] = mpVNDFCondBuffer; - var["lumi"] = mpLumiBuffer; - var["rgb"] = mpRGBBuffer; - - mpScene->bindShaderData(pPass->getRootVar()["gScene"]); - }; - - setupParams(mpRetroReflectionPass); - setupParams(mpBuildKernelPass); - setupParams(mpPowerIterationPass); - setupParams(mpIntegrateSigmaPass); - setupParams(mpSumSigmaPass); - setupParams(mpComputeThetaPass); - setupParams(mpComputeVNDFPass); - setupParams(mpAcquireBRDFPass); - - // Phase 1: Measure retroreflections and build kernel matrix of Fredholm problem. - mpRetroReflectionPass->execute(pRenderContext, uint3(kNDFSize, 1)); - mpBuildKernelPass->execute(pRenderContext, uint3(kNDFN, kNDFN, 1)); - - // Numerically extract the biggest eigen vector of kernel matrix; this is the NDF. - std::vector ones(kNDFN, 1.0f); - mpNDFBuffer->setBlob(ones.data(), 0, sizeof(float) * kNDFN); - auto var = mpPowerIterationPass->getRootVar()[kParameterBlock]; - for (uint i = 0; i < kEigenVectorPowerIterations; ++i) - { - var["ndf"] = mpNDFBuffer; - var["ndfTmp"] = mpNDFBufferTmp; - mpPowerIterationPass->execute(pRenderContext, uint3(kNDFSize, 1)); - std::swap(mpNDFBuffer, mpNDFBufferTmp); - } + auto var = pPass->getRootVar()[kParameterBlock]; + var["materialID"] = materialID.getSlang(); + + var["ndfSize"] = kNDFSize; + var["phiSize"] = kPhiSize; + var["vndfSize"] = kVNDFSize; + var["lumiSize"] = kLumiSize; + var["thetaSize"] = kThetaSize; + var["sigmaIntegrationGrid"] = kSigmaIntegrationGrid; + + var["retroReflection"] = mpRetroBuffer; + var["ndfDirections"] = mpNDFDirectionsBuffer; + var["ndfKernel"] = mpNDFKernelBuffer; + var["ndf"] = mpNDFBuffer; + var["sigma"] = mpSigmaBuffer; + var["phis"] = mpPhiBuffer; + var["thetas"] = mpThetaBuffer; + var["vndfBuf"] = mpVNDFBuffer; + var["vndfMarginalBuf"] = mpVNDFMargBuffer; + var["vndfConditionalBuf"] = mpVNDFCondBuffer; + var["lumi"] = mpLumiBuffer; + var["rgb"] = mpRGBBuffer; + + mpScene->bindShaderData(pPass->getRootVar()["gScene"]); + }; + + setupParams(mpRetroReflectionPass); + setupParams(mpBuildKernelPass); + setupParams(mpPowerIterationPass); + setupParams(mpIntegrateSigmaPass); + setupParams(mpSumSigmaPass); + setupParams(mpComputeThetaPass); + setupParams(mpComputeVNDFPass); + setupParams(mpAcquireBRDFPass); + + // Phase 1: Measure retroreflections and build kernel matrix of Fredholm problem. + mpRetroReflectionPass->execute(pRenderContext, uint3(kNDFSize, 1)); + mpBuildKernelPass->execute(pRenderContext, uint3(kNDFN, kNDFN, 1)); + + // Numerically extract the biggest eigen vector of kernel matrix; this is the NDF. + std::vector ones(kNDFN, 1.0f); + mpNDFBuffer->setBlob(ones.data(), 0, sizeof(float) * kNDFN); + auto var = mpPowerIterationPass->getRootVar()[kParameterBlock]; + for (uint i = 0; i < kEigenVectorPowerIterations; ++i) + { + var["ndf"] = mpNDFBuffer; + var["ndfTmp"] = mpNDFBufferTmp; + mpPowerIterationPass->execute(pRenderContext, uint3(kNDFSize, 1)); + std::swap(mpNDFBuffer, mpNDFBufferTmp); + } - // Phase 2: Numerically integrate the projected microfacet area corresponding to NDF. - mpIntegrateSigmaPass->execute(pRenderContext, uint3(kSigmaIntegrationGrid.x, kSigmaIntegrationGrid.y, kNDFN)); - mpSumSigmaPass->execute(pRenderContext, uint3(kNDFSize.x, kNDFSize.y, 1)); + // Phase 2: Numerically integrate the projected microfacet area corresponding to NDF. + mpIntegrateSigmaPass->execute(pRenderContext, uint3(kSigmaIntegrationGrid.x, kSigmaIntegrationGrid.y, kNDFN)); + mpSumSigmaPass->execute(pRenderContext, uint3(kNDFSize.x, kNDFSize.y, 1)); - // Normalize NDF and projected areas. - std::vector ndfCPU = mpNDFBuffer->getElements(0, kNDFN); - std::vector sigmaCPU = mpSigmaBuffer->getElements(0, kNDFN); - for (uint y = 0; y < kNDFSize.y; ++y) + // Normalize NDF and projected areas. + std::vector ndfCPU = mpNDFBuffer->getElements(0, kNDFN); + std::vector sigmaCPU = mpSigmaBuffer->getElements(0, kNDFN); + for (uint y = 0; y < kNDFSize.y; ++y) + { + // First entry of sigma in each row corresponds to integrating NDF(wm) with theta_i=0 + // and is equivalent to the normalization constant for the NDF. + // Use this factor to properly normalize NDF now and adjust sigmas accordingly. + float norm = 1.0f / sigmaCPU[y * kNDFSize.x]; + for (uint x = 0; x < kNDFSize.x; ++x) { - // First entry of sigma in each row corresponds to integrating NDF(wm) with theta_i=0 - // and is equivalent to the normalization constant for the NDF. - // Use this factor to properly normalize NDF now and adjust sigmas accordingly. - float norm = 1.0f / sigmaCPU[y * kNDFSize.x]; - for (uint x = 0; x < kNDFSize.x; ++x) - { - sigmaCPU[x + y * kNDFSize.x] *= norm; - ndfCPU[x + y * kNDFSize.x] *= norm; - } + sigmaCPU[x + y * kNDFSize.x] *= norm; + ndfCPU[x + y * kNDFSize.x] *= norm; } - mpSigmaBuffer->setBlob(sigmaCPU.data(), 0, sigmaCPU.size() * sizeof(float)); - mpNDFBuffer ->setBlob( ndfCPU.data(), 0, ndfCPU.size() * sizeof(float)); + } + mpSigmaBuffer->setBlob(sigmaCPU.data(), 0, sigmaCPU.size() * sizeof(float)); + mpNDFBuffer->setBlob(ndfCPU.data(), 0, ndfCPU.size() * sizeof(float)); - // Phase 3: Determine which angles to measure (currently just theta - we don't support anisotropy). - mpComputeThetaPass->execute(pRenderContext, uint3(kThetaSize, 1, 1)); + // Phase 3: Determine which angles to measure (currently just theta - we don't support anisotropy). + mpComputeThetaPass->execute(pRenderContext, uint3(kThetaSize, 1, 1)); - // Phase 4: Compute the VNDF and make it samplable. - mpComputeVNDFPass->execute(pRenderContext, uint3(kVNDFSize.x, kVNDFSize.y, kVNDFSize.z * kVNDFSize.w)); + // Phase 4: Compute the VNDF and make it samplable. + mpComputeVNDFPass->execute(pRenderContext, uint3(kVNDFSize.x, kVNDFSize.y, kVNDFSize.z * kVNDFSize.w)); - std::vector vndfBuf = mpVNDFBuffer->getElements(); - SamplableDistribution4D vndfDist(vndfBuf.data(), kVNDFSize); - mpVNDFBuffer ->setBlob(vndfDist.getPDF(), 0, sizeof(float) * kVNDFN); - mpVNDFCondBuffer->setBlob(vndfDist.getConditional(), 0, sizeof(float) * kVNDFN); - mpVNDFMargBuffer->setBlob(vndfDist.getMarginal(), 0, sizeof(float) * kVNDFN / kVNDFSize.z); + std::vector vndfBuf = mpVNDFBuffer->getElements(); + SamplableDistribution4D vndfDist(vndfBuf.data(), kVNDFSize); + mpVNDFBuffer->setBlob(vndfDist.getPDF(), 0, sizeof(float) * kVNDFN); + mpVNDFCondBuffer->setBlob(vndfDist.getConditional(), 0, sizeof(float) * kVNDFN); + mpVNDFMargBuffer->setBlob(vndfDist.getMarginal(), 0, sizeof(float) * kVNDFN / kVNDFSize.z); - // Phase 5: Perform the actual BRDF measurement. - mpAcquireBRDFPass->execute(pRenderContext, uint3(kLumiSize.x, kLumiSize.y, kLumiSize.z * kLumiSize.w)); + // Phase 5: Perform the actual BRDF measurement. + mpAcquireBRDFPass->execute(pRenderContext, uint3(kLumiSize.x, kLumiSize.y, kLumiSize.z * kLumiSize.w)); - timer.update(); - logInfo("Finished BSDF acquisition in {} seconds.", timer.delta()); - } + timer.update(); + logInfo("Finished BSDF acquisition in {} seconds.", timer.delta()); +} - RGLFile RGLAcquisition::toRGLFile() - { - RGLFile result; - - std::vector theta = mpThetaBuffer->getElements(); - std::vector phi = mpPhiBuffer ->getElements(); - std::vector sigma = mpSigmaBuffer->getElements(); - std::vector ndf = mpNDFBuffer ->getElements(); - std::vector vndf = mpVNDFBuffer ->getElements(); - std::vector rgb = mpRGBBuffer ->getElements(); - std::vector lumi = mpLumiBuffer ->getElements(); - - std::string description = "Virtually measured BRDF"; - result.addField("description", RGLFile::UInt8, std::vector{{uint(description.size())}}, description.c_str()); - result.addField("phi_i", RGLFile::Float32, std::vector{{kPhiSize}}, phi.data()); - result.addField("theta_i", RGLFile::Float32, std::vector{{kThetaSize}}, theta.data()); - result.addField("sigma", RGLFile::Float32, std::vector{{kNDFSize.y, kNDFSize.x}}, sigma.data()); - result.addField("ndf", RGLFile::Float32, std::vector{{kNDFSize.y, kNDFSize.x}}, ndf.data()); - result.addField("vndf", RGLFile::Float32, std::vector{{kVNDFSize.x, kVNDFSize.y, kVNDFSize.z, kVNDFSize.w}}, vndf.data()); - result.addField("luminance", RGLFile::Float32, std::vector{{kLumiSize.x, kLumiSize.y, kLumiSize.z, kLumiSize.w}}, lumi.data()); - result.addField("rgb", RGLFile::Float32, std::vector{{kLumiSize.x, kLumiSize.y, 3, kLumiSize.z, kLumiSize.w}}, rgb.data()); - - return result; - } +RGLFile RGLAcquisition::toRGLFile() +{ + RGLFile result; + + std::vector theta = mpThetaBuffer->getElements(); + std::vector phi = mpPhiBuffer->getElements(); + std::vector sigma = mpSigmaBuffer->getElements(); + std::vector ndf = mpNDFBuffer->getElements(); + std::vector vndf = mpVNDFBuffer->getElements(); + std::vector rgb = mpRGBBuffer->getElements(); + std::vector lumi = mpLumiBuffer->getElements(); + + std::string description = "Virtually measured BRDF"; + result.addField("description", RGLFile::UInt8, std::vector{{uint(description.size())}}, description.c_str()); + result.addField("phi_i", RGLFile::Float32, std::vector{{kPhiSize}}, phi.data()); + result.addField("theta_i", RGLFile::Float32, std::vector{{kThetaSize}}, theta.data()); + result.addField("sigma", RGLFile::Float32, std::vector{{kNDFSize.y, kNDFSize.x}}, sigma.data()); + result.addField("ndf", RGLFile::Float32, std::vector{{kNDFSize.y, kNDFSize.x}}, ndf.data()); + result.addField("vndf", RGLFile::Float32, std::vector{{kVNDFSize.x, kVNDFSize.y, kVNDFSize.z, kVNDFSize.w}}, vndf.data()); + result.addField("luminance", RGLFile::Float32, std::vector{{kLumiSize.x, kLumiSize.y, kLumiSize.z, kLumiSize.w}}, lumi.data()); + result.addField("rgb", RGLFile::Float32, std::vector{{kLumiSize.x, kLumiSize.y, 3, kLumiSize.z, kLumiSize.w}}, rgb.data()); + + return result; } +} // namespace Falcor diff --git a/Source/Falcor/Rendering/Materials/RGLAcquisition.cs.slang b/Source/Falcor/Rendering/Materials/RGLAcquisition.cs.slang index dea0d41daf..bba20506d2 100644 --- a/Source/Falcor/Rendering/Materials/RGLAcquisition.cs.slang +++ b/Source/Falcor/Rendering/Materials/RGLAcquisition.cs.slang @@ -53,14 +53,14 @@ struct RGLAcquisition RWStructuredBuffer phis; // Size phiSize RWStructuredBuffer vndf; // Size phiSize * thetaSize * vndfSize.z * vndfSize.w (vndfSize.xy == phiSize, thetaSize) - ByteAddressBuffer vndfBuf; // Size phiSize * thetaSize * vndfSize.z * vndfSize.w - ByteAddressBuffer vndfMarginalBuf; // Size phiSize * thetaSize * vndfSize.z * vndfSize.w - ByteAddressBuffer vndfConditionalBuf; // Size phiSize * thetaSize * vndfSize.z * vndfSize.w + ByteAddressBuffer vndfBuf; // Size phiSize * thetaSize * vndfSize.z * vndfSize.w + ByteAddressBuffer vndfMarginalBuf; // Size phiSize * thetaSize * vndfSize.z * vndfSize.w + ByteAddressBuffer vndfConditionalBuf; // Size phiSize * thetaSize * vndfSize.z * vndfSize.w RWBuffer lumi; // Size phiSize * thetaSize * lumiSize.z * lumiSize.w (lumiSize.xy == phiSize, thetaSize) RWBuffer rgb; // Size phiSize * thetaSize * lumiSize.z * lumiSize.w - static const uint2 kSampleCount = {8, 8}; + static const uint2 kSampleCount = { 8, 8 }; ShadingData buildShadingData(float3 wi) { @@ -79,10 +79,7 @@ struct RGLAcquisition // Clamp directions to lie above the cutoff imposed by the BRDF interface. // Otherwise we measure zero reflectance at glancing angles. - float3 clampDirection(float3 x) - { - return normalize(float3(x.xy, max(x.z, kMinCosTheta * 2.0f))); - } + float3 clampDirection(float3 x) { return normalize(float3(x.xy, max(x.z, kMinCosTheta * 2.0f))); } void measureRetroreflection(const uint2 threadID) { @@ -141,7 +138,7 @@ struct RGLAcquisition return lerp(lerp(v00, v10, uv.x), lerp(v01, v11, uv.x), uv.y); } - float evalNDF (float2 uv) { return interpolate(ndf, uv, ndfSize); } + float evalNDF(float2 uv) { return interpolate(ndf, uv, ndfSize); } float evalSigma(float2 uv) { return interpolate(sigma, uv, ndfSize); } @@ -229,7 +226,7 @@ struct RGLAcquisition float2 unitH = float2(idxH) / float2(vndfSize.zw - 1); float3 wi = fromSpherical(sphericalI); - float3 h = fromSpherical(unitToSpherical(unitH)); + float3 h = fromSpherical(unitToSpherical(unitH)); uint vndfIdx = (threadID.x * vndfSize.y + threadID.y) * vndfSize.z * vndfSize.w + threadID.z; @@ -256,7 +253,7 @@ struct RGLAcquisition float2 unitI = sphericalToUnit(sphericalI); float3 wi = fromSpherical(sphericalI); - float3 h = fromSpherical(sphericalH); + float3 h = fromSpherical(sphericalH); float3 wo = h * 2.0f * dot(h, wi) - wi; float3 fr; @@ -283,8 +280,8 @@ struct RGLAcquisition fr *= 4.0f * evalSigma(unitI) / evalNDF(unitH); uint sliceSize = lumiSize.z * lumiSize.w; - uint lumiBase = (threadID.x * lumiSize.y + threadID.y) * sliceSize + threadID.z; - uint rgbBase = (threadID.x * lumiSize.y + threadID.y) * sliceSize * 3 + threadID.z; + uint lumiBase = (threadID.x * lumiSize.y + threadID.y) * sliceSize + threadID.z; + uint rgbBase = (threadID.x * lumiSize.y + threadID.y) * sliceSize * 3 + threadID.z; lumi[lumiBase] = luminance(fr); rgb[rgbBase + sliceSize * 0] = fr.x; @@ -295,71 +292,78 @@ struct RGLAcquisition ParameterBlock gAcquisition; - [numthreads(16, 16, 1)] -void measureRetroreflection(uint3 threadID : SV_DispatchThreadID) +void measureRetroreflection(uint3 threadID: SV_DispatchThreadID) { - if (any(threadID.xy >= gAcquisition.ndfSize)) return; + if (any(threadID.xy >= gAcquisition.ndfSize)) + return; gAcquisition.measureRetroreflection(threadID.xy); } [numthreads(16, 16, 1)] -void buildPowerIterationKernel(uint3 threadID : SV_DispatchThreadID) +void buildPowerIterationKernel(uint3 threadID: SV_DispatchThreadID) { int N = gAcquisition.ndfSize.x * gAcquisition.ndfSize.y; - if (any(threadID >= N)) return; + if (any(threadID >= N)) + return; gAcquisition.buildPowerIterationKernel(threadID.xy); } [numthreads(16, 16, 1)] -void powerIteration(uint3 threadID : SV_DispatchThreadID) +void powerIteration(uint3 threadID: SV_DispatchThreadID) { - if (any(threadID.xy >= gAcquisition.ndfSize)) return; + if (any(threadID.xy >= gAcquisition.ndfSize)) + return; gAcquisition.powerIteration(threadID.xy); } [numthreads(16, 16, 1)] -void integrateSigma(uint3 threadID : SV_DispatchThreadID) +void integrateSigma(uint3 threadID: SV_DispatchThreadID) { int N = gAcquisition.ndfSize.x * gAcquisition.ndfSize.y; - if (any(threadID >= int3(gAcquisition.sigmaIntegrationGrid, N))) return; + if (any(threadID >= int3(gAcquisition.sigmaIntegrationGrid, N))) + return; gAcquisition.integrateSigma(threadID); } [numthreads(16, 16, 1)] -void sumSigma(uint3 threadID : SV_DispatchThreadID) +void sumSigma(uint3 threadID: SV_DispatchThreadID) { - if (any(threadID.xy >= gAcquisition.ndfSize)) return; + if (any(threadID.xy >= gAcquisition.ndfSize)) + return; gAcquisition.sumSigma(threadID.xy); } [numthreads(16, 16, 1)] -void computeTheta(uint3 threadID : SV_DispatchThreadID) +void computeTheta(uint3 threadID: SV_DispatchThreadID) { - if (threadID.x >= gAcquisition.thetaSize) return; + if (threadID.x >= gAcquisition.thetaSize) + return; gAcquisition.computeTheta(threadID.x); } [numthreads(16, 16, 1)] -void computeVNDF(uint3 threadID : SV_DispatchThreadID) +void computeVNDF(uint3 threadID: SV_DispatchThreadID) { int N = gAcquisition.vndfSize.z * gAcquisition.vndfSize.w; - if (any(threadID >= int3(gAcquisition.vndfSize.xy, N))) return; + if (any(threadID >= int3(gAcquisition.vndfSize.xy, N))) + return; gAcquisition.computeVNDF(threadID); } [numthreads(16, 16, 1)] -void acquireBRDF(uint3 threadID : SV_DispatchThreadID) +void acquireBRDF(uint3 threadID: SV_DispatchThreadID) { int N = gAcquisition.lumiSize.z * gAcquisition.lumiSize.w; - if (any(threadID >= int3(gAcquisition.lumiSize.xy, N))) return; + if (any(threadID >= int3(gAcquisition.lumiSize.xy, N))) + return; gAcquisition.acquireBRDF(threadID); } diff --git a/Source/Falcor/Rendering/Materials/RGLAcquisition.h b/Source/Falcor/Rendering/Materials/RGLAcquisition.h index 0848e9028f..0352a64221 100644 --- a/Source/Falcor/Rendering/Materials/RGLAcquisition.h +++ b/Source/Falcor/Rendering/Materials/RGLAcquisition.h @@ -35,44 +35,44 @@ namespace Falcor { - /** This class allows taking a virtual measurement of a BRDF - and converting it into the parametrization proposed by - Dupuy & Jakob, "An Adaptive Parameterization for Efficient - Material Acquisition and Rendering". - */ - class FALCOR_API RGLAcquisition - { - public: - /// Constructor. - RGLAcquisition(ref pDevice, const ref& pScene); +/** This class allows taking a virtual measurement of a BRDF + and converting it into the parametrization proposed by + Dupuy & Jakob, "An Adaptive Parameterization for Efficient + Material Acquisition and Rendering". +*/ +class FALCOR_API RGLAcquisition +{ +public: + /// Constructor. + RGLAcquisition(ref pDevice, const ref& pScene); - void acquireIsotropic(RenderContext* pRenderContext, const MaterialID materialID); - RGLFile toRGLFile(); + void acquireIsotropic(RenderContext* pRenderContext, const MaterialID materialID); + RGLFile toRGLFile(); - private: - ref mpDevice; - ref mpScene; - ref mpRetroReflectionPass; - ref mpBuildKernelPass; - ref mpPowerIterationPass; - ref mpIntegrateSigmaPass; - ref mpSumSigmaPass; - ref mpComputeThetaPass; - ref mpComputeVNDFPass; - ref mpAcquireBRDFPass; +private: + ref mpDevice; + ref mpScene; + ref mpRetroReflectionPass; + ref mpBuildKernelPass; + ref mpPowerIterationPass; + ref mpIntegrateSigmaPass; + ref mpSumSigmaPass; + ref mpComputeThetaPass; + ref mpComputeVNDFPass; + ref mpAcquireBRDFPass; - ref mpNDFDirectionsBuffer; ///< Stores hemispherical directions of entries in the NDF table. - ref mpRetroBuffer; ///< 2D table storing measured retroreflecton of BRDF. - ref mpNDFKernelBuffer; ///< Stores kernel matrix of Fredholm problem for retrieving NDF. - ref mpNDFBuffer; ///< 2D table storing the retrieved NDF. - ref mpNDFBufferTmp; - ref mpSigmaBuffer; ///< 2D table of projected microfacet area, integrated numerically. - ref mpThetaBuffer; ///< 1D tables storing angles at which measurements are taken. - ref mpPhiBuffer; - ref mpVNDFBuffer; ///< 4D table (over wi x wo domains) containing the visible distribution of normals. - ref mpVNDFMargBuffer; ///< Auxiliary buffers for sampling the VNDF. - ref mpVNDFCondBuffer; - ref mpLumiBuffer; ///< 4D table of measured luminance and RGB reflectance of RGB. - ref mpRGBBuffer; - }; -} + ref mpNDFDirectionsBuffer; ///< Stores hemispherical directions of entries in the NDF table. + ref mpRetroBuffer; ///< 2D table storing measured retroreflecton of BRDF. + ref mpNDFKernelBuffer; ///< Stores kernel matrix of Fredholm problem for retrieving NDF. + ref mpNDFBuffer; ///< 2D table storing the retrieved NDF. + ref mpNDFBufferTmp; + ref mpSigmaBuffer; ///< 2D table of projected microfacet area, integrated numerically. + ref mpThetaBuffer; ///< 1D tables storing angles at which measurements are taken. + ref mpPhiBuffer; + ref mpVNDFBuffer; ///< 4D table (over wi x wo domains) containing the visible distribution of normals. + ref mpVNDFMargBuffer; ///< Auxiliary buffers for sampling the VNDF. + ref mpVNDFCondBuffer; + ref mpLumiBuffer; ///< 4D table of measured luminance and RGB reflectance of RGB. + ref mpRGBBuffer; +}; +} // namespace Falcor diff --git a/Source/Falcor/Rendering/Materials/RGLCommon.slang b/Source/Falcor/Rendering/Materials/RGLCommon.slang index 01f93e685e..888f111c0b 100644 --- a/Source/Falcor/Rendering/Materials/RGLCommon.slang +++ b/Source/Falcor/Rendering/Materials/RGLCommon.slang @@ -154,10 +154,7 @@ struct Brick2D int2 size; ByteAddressBuffer data; - static Brick2D fromManagedBuffers(int2 size, uint dataBufferID) - { - return Brick2D(size, gScene.materials.getBuffer(dataBufferID)); - } + static Brick2D fromManagedBuffers(int2 size, uint dataBufferID) { return Brick2D(size, gScene.materials.getBuffer(dataBufferID)); } __init(int2 size, ByteAddressBuffer data) { @@ -171,9 +168,9 @@ struct Brick2D int2 pos = clamp(int2(uv), int2(0), size - 2); uv = saturate(uv - pos); int idx = pos.x + pos.y * size.x; - float val00 = loadf(data, idx ); - float val10 = loadf(data, idx + 1 ); - float val01 = loadf(data, idx + size.x); + float val00 = loadf(data, idx); + float val10 = loadf(data, idx + 1); + float val01 = loadf(data, idx + size.x); float val11 = loadf(data, idx + 1 + size.x); return lerp(lerp(val00, val10, uv.x), lerp(val01, val11, uv.x), uv.y); } @@ -195,10 +192,7 @@ struct Brick4D int4 size; ByteAddressBuffer data; - static Brick4D fromManagedBuffers(int4 size, uint dataBufferID) - { - return Brick4D(size, gScene.materials.getBuffer(dataBufferID)); - } + static Brick4D fromManagedBuffers(int4 size, uint dataBufferID) { return Brick4D(size, gScene.materials.getBuffer(dataBufferID)); } __init(int4 size, ByteAddressBuffer data) { @@ -208,9 +202,9 @@ struct Brick4D float interpolate(int base, int2 stride, float2 uv) { - float val00 = loadf(data, base ); - float val10 = loadf(data, base + stride.x ); - float val01 = loadf(data, base + stride.y); + float val00 = loadf(data, base); + float val10 = loadf(data, base + stride.x); + float val01 = loadf(data, base + stride.y); float val11 = loadf(data, base + stride.x + stride.y); return lerp(lerp(val00, val10, uv.x), lerp(val01, val11, uv.x), uv.y); } @@ -280,9 +274,9 @@ struct InterpolatedDistribution2D float interpolate(ByteAddressBuffer buf, int base, int2 stride, float2 uv) { - float val00 = loadf(buf, base ); - float val10 = loadf(buf, base + stride.x ); - float val01 = loadf(buf, base + stride.y); + float val00 = loadf(buf, base); + float val10 = loadf(buf, base + stride.x); + float val01 = loadf(buf, base + stride.y); float val11 = loadf(buf, base + stride.x + stride.y); return lerp(lerp(val00, val10, uv.x), lerp(val01, val11, uv.x), uv.y); } @@ -290,9 +284,9 @@ struct InterpolatedDistribution2D float4 gather(ByteAddressBuffer buf, int base, int4 stride, float2 uv) { return float4( - interpolate(buf, base , stride.xy, uv), - interpolate(buf, base + stride.z, stride.xy, uv), - interpolate(buf, base + stride.w , stride.xy, uv), + interpolate(buf, base, stride.xy, uv), + interpolate(buf, base + stride.z, stride.xy, uv), + interpolate(buf, base + stride.w, stride.xy, uv), interpolate(buf, base + stride.w + stride.z, stride.xy, uv) ); } @@ -345,10 +339,7 @@ struct InterpolatedDistribution2D return a - base; } - float evalQuadratic(float a, float b, float u) - { - return a * u + (b - a) * u * u * 0.5f; - } + float evalQuadratic(float a, float b, float u) { return a * u + (b - a) * u * u * 0.5f; } float solveQuadraticInterpolant(float a, float b, float x) { @@ -377,7 +368,7 @@ struct InterpolatedDistribution2D int baseConditional = dot(int3(sliceI, row), strideConditional); float rowBase = interpolate(marginalCdf, baseMarginal + row, strideMarginal, uv); - float rowA = interpolate(conditionalCdf, baseConditional + size.z - 1, strideConditional.xy, uv); + float rowA = interpolate(conditionalCdf, baseConditional + size.z - 1, strideConditional.xy, uv); float rowB = interpolate(conditionalCdf, baseConditional + size.z * 2 - 1, strideConditional.xy, uv); float rowF = solveQuadraticInterpolant(rowA, rowB, xi.y - rowBase); @@ -412,7 +403,7 @@ struct InterpolatedDistribution2D int baseConditional = dot(int3(sliceI, row), strideConditional); float rowBase = interpolate(marginalCdf, baseMarginal + row, strideMarginal, uv); - float rowA = interpolate(conditionalCdf, baseConditional + size.z - 1, strideConditional.xy, uv); + float rowA = interpolate(conditionalCdf, baseConditional + size.z - 1, strideConditional.xy, uv); float rowB = interpolate(conditionalCdf, baseConditional + size.z * 2 - 1, strideConditional.xy, uv); float xiY = rowBase + evalQuadratic(rowA, rowB, rowF); @@ -421,7 +412,8 @@ struct InterpolatedDistribution2D float colA = lerp(corners.x, corners.y, rowF); float colB = lerp(corners.z, corners.w, rowF); float xiX = (colBase + evalQuadratic(colA, colB, colF)) / lerp(rowA, rowB, rowF); - float pdf = lerp(colA, colB, colF) * (size.z - 1) * (size.w - 1);; + float pdf = lerp(colA, colB, colF) * (size.z - 1) * (size.w - 1); + ; return float3(xiX, xiY, pdf); } diff --git a/Source/Falcor/Rendering/Materials/RGLMaterial.slang b/Source/Falcor/Rendering/Materials/RGLMaterial.slang index ca1056c43b..6df09a576e 100644 --- a/Source/Falcor/Rendering/Materials/RGLMaterial.slang +++ b/Source/Falcor/Rendering/Materials/RGLMaterial.slang @@ -82,7 +82,13 @@ struct RGLMaterial : MaterialBase, IMaterial } [Differentiable] - RGLMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + RGLMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { diffData = DiffMaterialData(); return no_diff setupMaterialInstance(ms, sd, lod, hints); diff --git a/Source/Falcor/Rendering/Materials/RGLMaterialInstance.slang b/Source/Falcor/Rendering/Materials/RGLMaterialInstance.slang index cad64858b6..f28e70c492 100644 --- a/Source/Falcor/Rendering/Materials/RGLMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/RGLMaterialInstance.slang @@ -34,14 +34,14 @@ import Utils.Math.MathHelpers; import Scene.Scene; /** Implementation of the BSDF for the measured RGL material. -*/ + */ struct RGLMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. + ShadingFrame sf; ///< Shading frame in world space. float2 slice; float sigma; - uint2 ndfVndfSize; ///< Packed size of NDF and last two dimensions of VNDF LUT (first two match lumiSize). - uint2 lumiSize; ///< Packed size of luminance LUT. Dimensions identical to RGB LUT. + uint2 ndfVndfSize; ///< Packed size of NDF and last two dimensions of VNDF LUT (first two match lumiSize). + uint2 lumiSize; ///< Packed size of luminance LUT. Dimensions identical to RGB LUT. uint ndfBufID; uint vndfBufID; uint lumiBufID; @@ -50,7 +50,7 @@ struct RGLMaterialInstance : MaterialInstanceBase, IMaterialInstance uint lumiMarginalBufID; uint vndfConditionalBufID; uint lumiConditionalBufID; - float3 albedo; ///< Approximate albedo, only to satisfy getProperties. + float3 albedo; ///< Approximate albedo, only to satisfy getProperties. __init(const ShadingFrame sf, const float2 slice, const float sigma, const float3 albedo, const RGLMaterialData data) { @@ -75,20 +75,22 @@ struct RGLMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); uint4 ndfsSize = unpackSize(ndfVndfSize); uint4 lumiSize = unpackSize(this.lumiSize); uint4 vndfSize = uint4(lumiSize.xy, ndfsSize.zw); let ndf = Brick2D.fromManagedBuffers(ndfsSize.xy, ndfBufID); - let rgb = Brick4D.fromManagedBuffers(lumiSize , rgbBufID); + let rgb = Brick4D.fromManagedBuffers(lumiSize, rgbBufID); let vndf = InterpolatedDistribution2D.fromManagedBuffers(vndfSize, vndfMarginalBufID, vndfConditionalBufID, vndfBufID); let lumi = InterpolatedDistribution2D.fromManagedBuffers(lumiSize, lumiMarginalBufID, lumiConditionalBufID, lumiBufID); float3 h = normalize(wiLocal + woLocal); float2 sphericalI = toSpherical(wiLocal); float2 sphericalH = toSpherical(h); - if (lumiSize.x == 1) sphericalH.y -= sphericalI.y; // Isotropic case + if (lumiSize.x == 1) + sphericalH.y -= sphericalI.y; // Isotropic case float2 unitH = sphericalToUnit(sphericalH); unitH.y = frac(unitH.y); @@ -118,7 +120,7 @@ struct RGLMaterialInstance : MaterialInstanceBase, IMaterialInstance uint4 lumiSize = unpackSize(this.lumiSize); uint4 vndfSize = uint4(lumiSize.xy, ndfsSize.zw); let ndf = Brick2D.fromManagedBuffers(ndfsSize.xy, ndfBufID); - let rgb = Brick4D.fromManagedBuffers(lumiSize , rgbBufID); + let rgb = Brick4D.fromManagedBuffers(lumiSize, rgbBufID); let vndf = InterpolatedDistribution2D.fromManagedBuffers(vndfSize, vndfMarginalBufID, vndfConditionalBufID, vndfBufID); let lumi = InterpolatedDistribution2D.fromManagedBuffers(lumiSize, lumiMarginalBufID, lumiConditionalBufID, lumiBufID); @@ -149,8 +151,10 @@ struct RGLMaterialInstance : MaterialInstanceBase, IMaterialInstance result.wo = sf.fromLocal(woLocal); - if (jacobian == 0.0f || pdf == 0.0f) return false; - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo)) return false; + if (jacobian == 0.0f || pdf == 0.0f) + return false; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo)) + return false; result.pdf = pdf; result.weight = f / pdf; @@ -164,7 +168,8 @@ struct RGLMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) + return 0.f; uint4 ndfsSize = unpackSize(ndfVndfSize); uint4 lumiSize = unpackSize(this.lumiSize); @@ -192,7 +197,8 @@ struct RGLMaterialInstance : MaterialInstanceBase, IMaterialInstance float jacobian = max(2.0f * M_PI * M_PI * unitH.x * length(h.xy), 1e-6f) * 4.0f * dot(wiLocal, h); float pdf = vndfPdf * lumiPdf / jacobian; - if (jacobian == 0.0f) return 0.0f; + if (jacobian == 0.0f) + return 0.0f; return pdf; } diff --git a/Source/Falcor/Rendering/Materials/StandardMaterial.slang b/Source/Falcor/Rendering/Materials/StandardMaterial.slang index 0c0b3de8fa..ec6bbdb70a 100644 --- a/Source/Falcor/Rendering/Materials/StandardMaterial.slang +++ b/Source/Falcor/Rendering/Materials/StandardMaterial.slang @@ -47,7 +47,12 @@ struct StandardMaterial : MaterialBase, IMaterial // Specify that the associated `BSDF` type is `StandardMaterialInstance`. typedef StandardMaterialInstance MaterialInstance; - StandardMaterialInstance setupMaterialInstance(const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + StandardMaterialInstance setupMaterialInstance( + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); @@ -133,7 +138,13 @@ struct StandardMaterial : MaterialBase, IMaterial } [Differentiable] - StandardMaterialInstance setupDiffMaterialInstance(out DiffMaterialData diffData, const MaterialSystem ms, const ShadingData sd, const ITextureSampler lod, const uint hints) + StandardMaterialInstance setupDiffMaterialInstance( + out DiffMaterialData diffData, + const MaterialSystem ms, + const ShadingData sd, + const ITextureSampler lod, + const uint hints + ) { SamplerState s = ms.getTextureSampler(header.getDefaultTextureSamplerID()); diff --git a/Source/Falcor/Rendering/Materials/StandardMaterialInstance.slang b/Source/Falcor/Rendering/Materials/StandardMaterialInstance.slang index d14cd90db1..eb71f9ce79 100644 --- a/Source/Falcor/Rendering/Materials/StandardMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/StandardMaterialInstance.slang @@ -49,9 +49,9 @@ import Scene.Material.VolumeProperties; */ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance { - ShadingFrame sf; ///< Shading frame in world space. - StandardBSDFData data; ///< BSDF parameters. - float3 emission; ///< Radiance emitted in the incident direction (wi). + ShadingFrame sf; ///< Shading frame in world space. + StandardBSDFData data; ///< BSDF parameters. + float3 emission; ///< Radiance emitted in the incident direction (wi). __init(const ShadingFrame sf, const StandardBSDFData data, const float3 emission) { @@ -65,7 +65,8 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); StandardBSDF bsdf = StandardBSDF(wiLocal, sd.mtl, data); @@ -78,7 +79,8 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = diffData.sf.toLocal(sd.V); float3 woLocal = diffData.sf.toLocal(wo); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo)) + return float3(0.f); StandardBSDFData dataAD = StandardBSDFData(diffData); StandardBSDF bsdfAD = StandardBSDF(wiLocal, sd.mtl, dataAD); @@ -99,12 +101,13 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance else { StandardBSDF bsdf = StandardBSDF(wiLocal, sd.mtl, data); - if (!bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext())) + if (!bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, result.BSDFIndex, sg, BSDFContext())) return false; } result.wo = sf.fromLocal(woLocal); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, result.wo)) return false; + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, result.wo)) + return false; return true; } @@ -114,7 +117,8 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = sf.toLocal(wo); - if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo)) return 0.f; + if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo)) + return 0.f; if (!useImportanceSampling) { @@ -149,21 +153,38 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance // Pass on our specular reflectance field unmodified. p.specularReflectance = data.specular; - if (data.diffuseTransmission > 0.f || data.specularTransmission > 0.f) p.flags |= (uint)BSDFProperties::Flags::IsTransmissive; + if (data.diffuseTransmission > 0.f || data.specularTransmission > 0.f) + p.flags |= (uint)BSDFProperties::Flags::IsTransmissive; return p; } - uint getLobeTypes(const ShadingData sd) + float2 getReflectionMotionVectorProbabilities(const ShadingData sd) { - return StandardBSDF::getLobeTypes(data); + float3 wiLocal = sf.toLocal(sd.V); + StandardBSDF bsdf = StandardBSDF(wiLocal, sd.mtl, data); + return bsdf.getReflectionMotionVectorProbabilities(wiLocal); + } + + bool supportSimpleRoughnessThreshold() + { + return true; } - bool hasVolumeProperties() + bool supportBSDFIndexing() { - return data.hasEntryPointVolumeProperties; + return true; } + bool isSampleRough(const ShadingData sd, int sampledBSDFIndex, float samplePdf, float roughnessThreshold) + { + return sampledBSDFIndex <= 1 || getProperties(sd).roughness > roughnessThreshold; + } + + uint getLobeTypes(const ShadingData sd) { return StandardBSDF::getLobeTypes(data); } + + bool hasVolumeProperties() { return data.hasEntryPointVolumeProperties; } + VolumeProperties getVolumeProperties() { VolumeProperties result; @@ -172,8 +193,8 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance // Convert color to sigmaA. See MxLayeredMaterialInstance fro details const float g = data.volumeAnsiotropy; const float3 c = data.diffuse; - const float3 s = 4.09712f + 4.20863f*c - sqrt(9.59217f + 41.6808f * c + 17.7126 * c * c); - const float3 alpha = (1.f - s*s) / (1.f - g * s*s); + const float3 s = 4.09712f + 4.20863f * c - sqrt(9.59217f + 41.6808f * c + 17.7126 * c * c); + const float3 alpha = (1.f - s * s) / (1.f - g * s * s); result.sigmaA = select(alpha > 0.f, (1.f - alpha) * (data.volumeScattering) / alpha, 0.f); } @@ -181,9 +202,8 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance result.sigmaA = 0.f; result.sigmaS = data.volumeScattering; - result.phaseFunction = NullPhaseFunction(); // g is taken from the volume and set in InternalPathTracer + result.phaseFunction = NullPhaseFunction(); return result; - } // Additional functions @@ -199,7 +219,15 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance \param[in,out] sg Sample generator. \return True if a sample was generated, false otherwise. */ - bool sampleReference(const ShadingData sd, const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sampleReference( + const ShadingData sd, + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg + ) { weight = {}; lobeType = {}; @@ -214,12 +242,20 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance { wo.z = -wo.z; } +#if PAD_RANDOM_NUMBERS + sampleNext1D(sg); +#endif pdf *= 0.5f; - if (min(abs(wi.z), abs(wo.z)) < kMinCosTheta || pdf == 0.f) return false; + if (min(abs(wi.z), abs(wo.z)) < kMinCosTheta || pdf == 0.f) + return false; } else { - if (min(wi.z, wo.z) < kMinCosTheta || pdf == 0.f) return false; +#if PAD_RANDOM_NUMBERS + sampleNext2D(sg); +#endif + if (min(wi.z, wo.z) < kMinCosTheta || pdf == 0.f) + return false; } StandardBSDF bsdf = StandardBSDF(wi, sd.mtl, data); @@ -242,12 +278,14 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance if (isTransmissive) { - if (min(abs(wi.z), abs(wo.z)) < kMinCosTheta) return 0.f; + if (min(abs(wi.z), abs(wo.z)) < kMinCosTheta) + return 0.f; return 0.5f * wo.z * M_1_PI; // pdf = 0.5 * cos(theta) / pi } else { - if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; + if (min(wi.z, wo.z) < kMinCosTheta) + return 0.f; return wo.z * M_1_PI; // pdf = cos(theta) / pi } } diff --git a/Source/Falcor/Rendering/Materials/TexLODHelpers.slang b/Source/Falcor/Rendering/Materials/TexLODHelpers.slang index bf4936b938..77b34223ba 100644 --- a/Source/Falcor/Rendering/Materials/TexLODHelpers.slang +++ b/Source/Falcor/Rendering/Materials/TexLODHelpers.slang @@ -40,19 +40,20 @@ */ #include "Utils/Math/MathConstants.slangh" +import Utils.Math.MathHelpers; import Rendering.Materials.TexLODTypes; import Scene.SceneTypes; // Needed for ray bounce helpers. // Modes for calculating spread angle from curvature -#define TEXLOD_SPREADANGLE_RTG1 0 // 0: Original approach derived from RTG. -#define TEXLOD_SPREADANGLE_ARC_LENGTH_UNOPTIMIZED 1 // 1: New arc-length integration approach, unoptimized. -#define TEXLOD_SPREADANGLE_ARC_LENGTH_OPTIMIZED 2 // 2: New arc-length integration approach, optimized. +#define TEXLOD_SPREADANGLE_RTG1 0 // 0: Original approach derived from RTG. +#define TEXLOD_SPREADANGLE_ARC_LENGTH_UNOPTIMIZED 1 // 1: New arc-length integration approach, unoptimized. +#define TEXLOD_SPREADANGLE_ARC_LENGTH_OPTIMIZED 2 // 2: New arc-length integration approach, optimized. // Chose one of modes above, default to optimized arc length approach (2) #define TEXLOD_SPREADANGLE_FROM_CURVATURE_MODE TEXLOD_SPREADANGLE_ARC_LENGTH_OPTIMIZED // Uncomment to use FP16 for ray cone payload -//#define USE_RAYCONES_WITH_FP16_IN_RAYPAYLOAD +// #define USE_RAYCONES_WITH_FP16_IN_RAYPAYLOAD // ---------------------------------------------------------------------------- // Ray cone helpers @@ -66,16 +67,18 @@ import Scene.SceneTypes; // Needed for ray bounce helpers. struct RayCone { #ifndef USE_RAYCONES_WITH_FP16_IN_RAYPAYLOAD - float width; - float spreadAngle; - float getWidth() { return width; } - float getSpreadAngle() { return spreadAngle; } + float width = {}; + float spreadAngle = {}; + float getWidth() { return width; } + float getSpreadAngle() { return spreadAngle; } #else - uint widthSpreadAngleFP16; - float getWidth() { return f16tof32(widthSpreadAngleFP16 >> 16); } - float getSpreadAngle() { return f16tof32(widthSpreadAngleFP16); } + uint widthSpreadAngleFP16 = {}; + float getWidth() { return f16tof32(widthSpreadAngleFP16 >> 16); } + float getSpreadAngle() { return f16tof32(widthSpreadAngleFP16); } #endif + __init() {} + /** Initializes a ray cone struct. \param[in] width The width of the ray cone. \param[in] angle The angle of the ray cone. @@ -119,12 +122,12 @@ struct RayCone \param[in] normal Normal at the hit point. \return The level of detail, lambda. */ - float computeLOD(float triLODConstant, float3 rayDir, float3 normal) // Note: call propagateDistance() before computeLOD(). + float computeLOD(float triLODConstant, float3 rayDir, float3 normal) // Note: call propagateDistance() before computeLOD(). { float lambda = triLODConstant; // Constant per triangle. float distTerm = abs(getWidth()); float normalTerm = abs(dot(rayDir, normal)); - lambda += log2(distTerm / normalTerm); + lambda += safeLog2(distTerm / normalTerm); return lambda; } }; @@ -146,9 +149,9 @@ float computeRayConeTriangleLODValue(float3 vertices[3], float2 txcoords[3], flo float3 edge01 = mul(worldMat, vertices[1] - vertices[0]); float3 edge02 = mul(worldMat, vertices[2] - vertices[0]); - float3 triangleNormal = cross(edge01, edge02); // In world space, by design. - float Pa = length(triangleNormal); // Twice the area of the triangle. - return 0.5f * log2(Ta / Pa); // Value used by texture LOD cones model. + float3 triangleNormal = cross(edge01, edge02); // In world space, by design. + float Pa = length(triangleNormal); // Twice the area of the triangle. + return 0.5f * safeLog2(Ta / Pa); // Value used by texture LOD cones model. } /** Compute the triangle LOD value based on triangle vertices and texture coordinates, used by ray cones. @@ -249,14 +252,24 @@ float computeSpreadAngleFromCurvatureIso(float curvature, float rayConeWidth, fl \param[in] texGradientX First gradient of texture coordinates, which can be fed into SampleGrad(). \param[in] texGradientY Second gradient of texture coordinates, which can be fed into SampleGrad(). */ -void computeAnisotropicEllipseAxes(float3 intersectionPoint, float3 faceNormal, float3 rayConeDir, - float rayConeRadiusAtIntersection, float3 positions[3], float2 txcoords[3], float2 interpolatedTexCoordsAtIntersection, - out float2 texGradientX, out float2 texGradientY) +void computeAnisotropicEllipseAxes( + float3 intersectionPoint, + float3 faceNormal, + float3 rayConeDir, + float rayConeRadiusAtIntersection, + float3 positions[3], + float2 txcoords[3], + float2 interpolatedTexCoordsAtIntersection, + out float2 texGradientX, + out float2 texGradientY +) { // Compute ellipse axes. - float3 ellipseAxis0 = rayConeDir - dot(faceNormal, rayConeDir) * faceNormal; // Project rayConeDir onto the plane. - float3 rayDirPlaneProjection0 = ellipseAxis0 - dot(rayConeDir, ellipseAxis0) * rayConeDir; // Project axis onto the plane defined by the ray cone dir. - ellipseAxis0 *= rayConeRadiusAtIntersection / max(0.0001f, length(rayDirPlaneProjection0)); // Using uniform triangles to find the scale. + float3 ellipseAxis0 = rayConeDir - dot(faceNormal, rayConeDir) * faceNormal; // Project rayConeDir onto the plane. + float3 rayDirPlaneProjection0 = ellipseAxis0 - dot(rayConeDir, ellipseAxis0) * rayConeDir; // Project axis onto the plane defined by the + // ray cone dir. + ellipseAxis0 *= rayConeRadiusAtIntersection / max(0.0001f, length(rayDirPlaneProjection0)); // Using uniform triangles to find the + // scale. float3 ellipseAxis1 = cross(faceNormal, ellipseAxis0); float3 rayDirPlaneProjection1 = ellipseAxis1 - dot(rayConeDir, ellipseAxis1) * rayConeDir; @@ -342,8 +355,8 @@ void rotate2DPlusMinus(float2 vec, float angle, out float2 rotatedVecPlus, out f float sy = s * vec.y; float sx = s * vec.x; float cy = c * vec.y; - rotatedVecPlus = float2(cx - sy, +sx + cy); // Rotate +angle, - rotatedVecMinus = float2(cx + sy, -sx + cy); // Rotate -angle. + rotatedVecPlus = float2(cx - sy, +sx + cy); // Rotate +angle, + rotatedVecMinus = float2(cx + sy, -sx + cy); // Rotate -angle. } /** Helper function that returns an orthogonal vector to the in vector: 90 degrees counter-clockwise rotation. @@ -355,7 +368,8 @@ float2 orthogonal(float2 vec) return float2(-vec.y, vec.x); } -/** Computes RayCone for a given refracted ray direction. Note that the incident ray cone should be called with propagateDistance(hitT); before computeRayConeForRefraction() is called. +/** Computes RayCone for a given refracted ray direction. Note that the incident ray cone should be called with propagateDistance(hitT); + before computeRayConeForRefraction() is called. \param[in,out] rayCone A ray cone to be refracted, result is returned here as well. \param[in] rayOrg Ray origin. \param[in] rayDir Ray direction. @@ -365,8 +379,16 @@ float2 orthogonal(float2 vec) \param[in] eta Ratio of indices of refraction (enteringIndexOfRefraction / exitingIndexOfRefraction). \param[in] refractedRayDir The refracted ray direction. */ -void computeRayConeForRefraction(inout RayCone rayCone, float3 rayOrg, float3 rayDir, float3 hitPoint, float3 normal, float normalSpreadAngle, - float eta, float3 refractedRayDir) +void computeRayConeForRefraction( + inout RayCone rayCone, + float3 rayOrg, + float3 rayDir, + float3 hitPoint, + float3 normal, + float normalSpreadAngle, + float eta, + float3 refractedRayDir +) { // We have refractedRayDir, which is the direction of the refracted ray cone, // but we also need the rayCone.width and the rayCone.spreadAngle. These are computed in 2D, @@ -374,9 +396,9 @@ void computeRayConeForRefraction(inout RayCone rayCone, float3 rayOrg, float3 ra float3 xAxis = normalize(rayDir - normal * dot(normal, rayDir)); float3 yAxis = normal; - float2 refractedDir2D = float2(dot(refractedRayDir, xAxis), dot(refractedRayDir, yAxis)); // Project to 2D. - float2 incidentDir2D = float2(dot(rayDir, xAxis), dot(rayDir, yAxis)); // Project to 2D. - float2 incidentDir2D_u, incidentDir2D_l; // Upper (_u) and lower (_l) line of ray cone in 2D. + float2 refractedDir2D = float2(dot(refractedRayDir, xAxis), dot(refractedRayDir, yAxis)); // Project to 2D. + float2 incidentDir2D = float2(dot(rayDir, xAxis), dot(rayDir, yAxis)); // Project to 2D. + float2 incidentDir2D_u, incidentDir2D_l; // Upper (_u) and lower (_l) line of ray cone in 2D. float2 incidentDirOrtho2D = orthogonal(incidentDir2D); float widthSign = rayCone.getWidth() > 0.0f ? 1.0f : -1.0f; @@ -385,8 +407,8 @@ void computeRayConeForRefraction(inout RayCone rayCone, float3 rayOrg, float3 ra // Note: since we assume that the incident ray cone has been propagated to the hitpoint, we start the width-vector // from the origin (0,0), and so, we do not need to add rayOrigin2D to tu and tl. - float2 tu = +incidentDirOrtho2D * rayCone.getWidth() * 0.5f; // Top, upper point on the incoming ray cone (in 2D). - float2 tl = -tu; // Top, lower point on the incoming ray cone (in 2D). + float2 tu = +incidentDirOrtho2D * rayCone.getWidth() * 0.5f; // Top, upper point on the incoming ray cone (in 2D). + float2 tl = -tu; // Top, lower point on the incoming ray cone (in 2D). // Intersect 2D rays (tu + t * incidentDir2D_u, and similar for _l) with y = 0. // Optimized becuase y will always be 0.0f, so only need to compute x. float hitPoint_u_x = tu.x + incidentDir2D_u.x * (-tu.y / incidentDir2D_u.y); @@ -437,12 +459,20 @@ void computeRayConeForRefraction(inout RayCone rayCone, float3 rayOrg, float3 ra \param[out] refractedRayDir The refracted ray direction (unless the ray was totally internally reflcted (TIR:ed). \return Whether the ray was not totally internally reflected, i.e., returns true without TIR, and false in cases of TIR */ -bool refractRayCone(inout RayCone rayCone, float3 rayOrg, float3 rayDir, float3 hitPoint, float3 normal, float normalSpreadAngle, - float eta, out float3 refractedRayDir) +bool refractRayCone( + inout RayCone rayCone, + float3 rayOrg, + float3 rayDir, + float3 hitPoint, + float3 normal, + float normalSpreadAngle, + float eta, + out float3 refractedRayDir +) { if (!refractWithTIR(rayDir, normal, eta, refractedRayDir)) { - return false; // total internal reflection + return false; // total internal reflection } computeRayConeForRefraction(rayCone, rayOrg, rayDir, hitPoint, normal, normalSpreadAngle, eta, refractedRayDir); @@ -460,16 +490,18 @@ bool refractRayCone(inout RayCone rayCone, float3 rayOrg, float3 rayDir, float3 */ struct RayDiff { - float3 dOdx; - float3 dOdy; - float3 dDdx; - float3 dDdy; + float3 dOdx = {}; + float3 dOdy = {}; + float3 dDdx = {}; + float3 dDdy = {}; - float3 getdOdx() { return dOdx; } // These are not super-useful right now, but TODO to add FP16 version later on + float3 getdOdx() { return dOdx; } // These are not super-useful right now, but TODO to add FP16 version later on float3 getdOdy() { return dOdy; } float3 getdDdx() { return dDdx; } float3 getdDdy() { return dDdy; } + __init() {} + /** Initializes a ray differential struct. \param[in] dOdx The differential ray origin in x. \param[in] dOdy The differential ray origin in y. @@ -493,10 +525,10 @@ struct RayDiff */ RayDiff propagate(float3 O, float3 D, float t, float3 N) { - float3 dodx = getdOdx() + t * getdDdx(); // Part of Igehy Equation 10. + float3 dodx = getdOdx() + t * getdDdx(); // Part of Igehy Equation 10. float3 dody = getdOdy() + t * getdDdy(); - float rcpDN = 1.0f / dot(D, N); // Igehy Equations 10 and 12. + float rcpDN = 1.0f / dot(D, N); // Igehy Equations 10 and 12. float dtdx = -dot(dodx, N) * rcpDN; float dtdy = -dot(dody, N) * rcpDN; dodx += D * dtdx; @@ -506,7 +538,8 @@ struct RayDiff } }; -/** Computes the ray direction differential under the assumption that getCameraRayDir() is implemented as shown in the code which is commented out just below. +/** Computes the ray direction differential under the assumption that getCameraRayDir() is implemented as shown in the code which is + commented out just below. \param[in] nonNormalizedCameraRaydir Non-normalized camera ray direction. \param[in] cameraRight Camera right vector. \param[in] cameraUp Camera up vector. @@ -519,10 +552,18 @@ struct RayDiff { float2 p = (pixel.xy + float2(0.5f, 0.5f)) / frameDim.xy; // Pixel center on image plane in [0,1] where (0,0) is top-left float2 ndc = float2(2, -2) * p + float2(-1, 1); - return ndc.x * gCamera.cameraU + ndc.y * gCamera.cameraV + gCamera.cameraW; // rayDir = world-space direction to point on image plane (unnormalized) + return ndc.x * gCamera.cameraU + ndc.y * gCamera.cameraV + gCamera.cameraW; // rayDir = world-space direction to point on image + plane (unnormalized) } */ -void computeRayDirectionDifferentials(float3 nonNormalizedCameraRaydir, float3 cameraRight, float3 cameraUp, float2 viewportDims, out float3 dDdx, out float3 dDdy) +void computeRayDirectionDifferentials( + float3 nonNormalizedCameraRaydir, + float3 cameraRight, + float3 cameraUp, + float2 viewportDims, + out float3 dDdx, + out float3 dDdy +) { // Igehy Equation 8, adapted to getRayDirection() above. float dd = dot(nonNormalizedCameraRaydir, nonNormalizedCameraRaydir); @@ -542,21 +583,28 @@ void computeRayDirectionDifferentials(float3 nonNormalizedCameraRaydir, float3 c \param[out] dBarydx Differential barycentric coordinates in x. Note that we skip the third component, since w=1-u-v and thus dw/dx=-du/dx-dv/dx. \param[out] dBarydy Differential barycentric coordinates in y. Note that we skip the third component, since w=1-u-v and thus dw/dy=-du/dy-dv/dy. */ -void computeBarycentricDifferentials(RayDiff rayDiff, float3 rayDir, float3 edge01, float3 edge02, - float3 faceNormalW, out float2 dBarydx, out float2 dBarydy) +void computeBarycentricDifferentials( + RayDiff rayDiff, + float3 rayDir, + float3 edge01, + float3 edge02, + float3 faceNormalW, + out float2 dBarydx, + out float2 dBarydy +) { - float3 Nu = cross(edge02, faceNormalW); // Igehy "Normal-Interpolated Triangles", page 182 SIGGRAPH 1999. + float3 Nu = cross(edge02, faceNormalW); // Igehy "Normal-Interpolated Triangles", page 182 SIGGRAPH 1999. float3 Nv = cross(edge01, faceNormalW); - float3 Lu = Nu / (dot(Nu, edge01)); // Plane equations for the triangle edges, scaled in order to make the dot with the opposive vertex = 1. + float3 Lu = Nu / (dot(Nu, edge01)); // Plane equations for the triangle edges, scaled in order to make the dot with the opposive vertex + // = 1. float3 Lv = Nv / (dot(Nv, edge02)); - dBarydx.x = dot(Lu, rayDiff.getdOdx()); // du / dx. - dBarydx.y = dot(Lv, rayDiff.getdOdx()); // dv / dx. - dBarydy.x = dot(Lu, rayDiff.getdOdy()); // du / dy. - dBarydy.y = dot(Lv, rayDiff.getdOdy()); // dv / dy. + dBarydx.x = dot(Lu, rayDiff.getdOdx()); // du / dx. + dBarydx.y = dot(Lv, rayDiff.getdOdx()); // dv / dx. + dBarydy.x = dot(Lu, rayDiff.getdOdy()); // du / dy. + dBarydy.y = dot(Lv, rayDiff.getdOdy()); // dv / dy. } - /** Interpolates vertex values using differential barycentrics for a single float per vertex. \param[in] dBarydx Differential barycentric coordinates in x. \param[in] dBarydy Differential barycentric coordinates in y. @@ -611,11 +659,19 @@ void interpolateDifferentials(float2 dBarydx, float2 dBarydy, float3 vertexValue \param[out] dNdx Differential normal in the x-direction. \param[out] dNdy Differential normal in the y-direction. */ -void computeNormalDifferentials(RayDiff rayDiff, float3 nonNormalizedInterpolatedNormalW, - float2 dBarydx, float2 dBarydy, float3 normals[3], out float3 dNdx, out float3 dNdy) +void computeNormalDifferentials( + RayDiff rayDiff, + float3 nonNormalizedInterpolatedNormalW, + float2 dBarydx, + float2 dBarydy, + float3 normals[3], + out float3 dNdx, + out float3 dNdy +) { // Differential normal (see "Normal-Interpolated Triangles" in Igehy's paper). - float NN = dot(nonNormalizedInterpolatedNormalW, nonNormalizedInterpolatedNormalW); // normal must be unnormalized! (otherwise NN would be 1). + float NN = dot(nonNormalizedInterpolatedNormalW, nonNormalizedInterpolatedNormalW); // normal must be unnormalized! (otherwise NN would + // be 1). float rcpNN = 1.0f / (NN * sqrt(NN)); float3 dndx, dndy; @@ -634,8 +690,15 @@ void computeNormalDifferentials(RayDiff rayDiff, float3 nonNormalizedInterpolate \param[in] dBarydy Differential barycentric coordinates wrt y. \param[in] normalsW The triangle's three normalized normals in world space. */ -void reflectRayDifferential(inout RayDiff rayDiff, float3 rayDir, float3 nonNormalizedInterpolatedNormalW, - float3 normalizedInterpolatedNormalW, float2 dBarydx, float2 dBarydy, float3 normalsW[3]) +void reflectRayDifferential( + inout RayDiff rayDiff, + float3 rayDir, + float3 nonNormalizedInterpolatedNormalW, + float3 normalizedInterpolatedNormalW, + float2 dBarydx, + float2 dBarydy, + float3 normalsW[3] +) { float3 dNdx, dNdy; computeNormalDifferentials(rayDiff, nonNormalizedInterpolatedNormalW, dBarydx, dBarydy, normalsW, dNdx, dNdy); @@ -667,9 +730,20 @@ void reflectRayDifferential(inout RayDiff rayDiff, float3 rayDir, float3 nonNorm \param[in] NdotDprime The dot product between the normal and the refracted direction (Eq. 17, Igehy) \param[in] mu An intermediate term used by Igehy to compute the refracted ray differential (Eq. 17, Igehy). See the overloaded refractRayDifferential() function just below. */ -void computeRayDifferentialRefraction(inout RayDiff rayDiff, float3 rayDir, float3 nonNormalizedInterpolatedNormalW, - float3 normalizedInterpolatedNormalW, float2 dBarydx, float2 dBarydy, float3 normalsW[3], - float eta, float3 refractedDir, float NdotD, float NdotDprime, float mu) +void computeRayDifferentialRefraction( + inout RayDiff rayDiff, + float3 rayDir, + float3 nonNormalizedInterpolatedNormalW, + float3 normalizedInterpolatedNormalW, + float2 dBarydx, + float2 dBarydy, + float3 normalsW[3], + float eta, + float3 refractedDir, + float NdotD, + float NdotDprime, + float mu +) { float3 dNdx, dNdy; computeNormalDifferentials(rayDiff, nonNormalizedInterpolatedNormalW, dBarydx, dBarydy, normalsW, dNdx, dNdy); @@ -678,12 +752,12 @@ void computeRayDifferentialRefraction(inout RayDiff rayDiff, float3 rayDir, floa float dDNdx = dot(rayDiff.getdDdx(), normalizedInterpolatedNormalW) + dot(rayDir, dNdx); float dDNdy = dot(rayDiff.getdDdy(), normalizedInterpolatedNormalW) + dot(rayDir, dNdy); - float dMudx = eta * (1.0f + eta * NdotD / NdotDprime) * dDNdx; // Equation 19. - float dMudy = eta * (1.0f + eta * NdotD / NdotDprime) * dDNdy; // Equation 19. + float dMudx = eta * (1.0f + eta * NdotD / NdotDprime) * dDNdx; // Equation 19. + float dMudy = eta * (1.0f + eta * NdotD / NdotDprime) * dDNdy; // Equation 19. float3 dOdx = rayDiff.getdOdx(); float3 dOdy = rayDiff.getdOdy(); - float3 dDdx = eta * rayDiff.getdDdx() - (mu * dNdx + dMudx * normalizedInterpolatedNormalW); // Equation 18. - float3 dDdy = eta * rayDiff.getdDdy() - (mu * dNdy + dMudy * normalizedInterpolatedNormalW); // Equation 18. + float3 dDdx = eta * rayDiff.getdDdx() - (mu * dNdx + dMudx * normalizedInterpolatedNormalW); // Equation 18. + float3 dDdy = eta * rayDiff.getdDdy() - (mu * dNdy + dMudy * normalizedInterpolatedNormalW); // Equation 18. rayDiff = RayDiff(dOdx, dOdy, dDdx, dDdy); } @@ -699,18 +773,37 @@ void computeRayDifferentialRefraction(inout RayDiff rayDiff, float3 rayDir, floa \param[in] eta The ratio of indices of refraction. \param[in] refractedDir The direction of the refracted direction. */ -void computeRayDifferentialRefraction(inout RayDiff rayDiff, float3 rayDir, float3 nonNormalizedInterpolatedNormalW, - float3 normalizedInterpolatedNormalW, float2 dBarydx, float2 dBarydy, float3 normalsW[3], - float eta, float3 refractedDir) +void computeRayDifferentialRefraction( + inout RayDiff rayDiff, + float3 rayDir, + float3 nonNormalizedInterpolatedNormalW, + float3 normalizedInterpolatedNormalW, + float2 dBarydx, + float2 dBarydy, + float3 normalsW[3], + float eta, + float3 refractedDir +) { float NdotD = dot(normalizedInterpolatedNormalW, rayDir); float k = eta * eta * (1.0f - NdotD * NdotD); - float NdotDprime = sqrt(1.0f - k); // Equation 17. - float mu = eta * NdotD + NdotDprime; // Equation 17. - - computeRayDifferentialRefraction(rayDiff, rayDir, nonNormalizedInterpolatedNormalW, - normalizedInterpolatedNormalW, dBarydx, dBarydy, normalsW, - eta, refractedDir, NdotD, NdotDprime, mu); + float NdotDprime = sqrt(1.0f - k); // Equation 17. + float mu = eta * NdotD + NdotDprime; // Equation 17. + + computeRayDifferentialRefraction( + rayDiff, + rayDir, + nonNormalizedInterpolatedNormalW, + normalizedInterpolatedNormalW, + dBarydx, + dBarydy, + normalsW, + eta, + refractedDir, + NdotD, + NdotDprime, + mu + ); } /** Refract ray differentials using interpolated vertex attributes. Takes existing refracted ray direction. @@ -725,14 +818,41 @@ void computeRayDifferentialRefraction(inout RayDiff rayDiff, float3 rayDir, floa \param[out] dUVdx The differential of the texture coordinates in pixel coordinate x. \param[out] dUVdy The differential of the texture coordinates in pixel coordinate y. */ -void refractRayDiffUsingVertexData(VertexData v, StaticVertexData triangleVertices[3], float3 barycentrics, float3 rayDir, float3 refractedDir, float eta, float4x4 worldMat, float3x3 worldInvTransposeMat, - inout RayDiff rayDiff, out float2 dUVdx, out float2 dUVdy) +void refractRayDiffUsingVertexData( + VertexData v, + StaticVertexData triangleVertices[3], + float3 barycentrics, + float3 rayDir, + float3 refractedDir, + float eta, + float4x4 worldMat, + float3x3 worldInvTransposeMat, + inout RayDiff rayDiff, + out float2 dUVdx, + out float2 dUVdy +) { - float3 unnormalizedN; // Non-normalized interpolated normal for ray differential scatter. - float3 normals[3]; // Non-normalized normals for ray differential scatter. + float3 unnormalizedN; // Non-normalized interpolated normal for ray differential scatter. + float3 normals[3]; // Non-normalized normals for ray differential scatter. float2 dBarydx, dBarydy; - prepareRayDiffAtHitPoint(v, triangleVertices, barycentrics, rayDir, worldMat, worldInvTransposeMat, rayDiff, unnormalizedN, normals, dBarydx, dBarydy, dUVdx, dUVdy); - computeRayDifferentialRefraction(rayDiff, rayDir, unnormalizedN, normalize(unnormalizedN), dBarydx, dBarydy, normals, eta, refractedDir); + prepareRayDiffAtHitPoint( + v, + triangleVertices, + barycentrics, + rayDir, + worldMat, + worldInvTransposeMat, + rayDiff, + unnormalizedN, + normals, + dBarydx, + dBarydy, + dUVdx, + dUVdy + ); + computeRayDifferentialRefraction( + rayDiff, rayDir, unnormalizedN, normalize(unnormalizedN), dBarydx, dBarydy, normals, eta, refractedDir + ); } /** Refracts a ray differential. Actually refract the ray and compute the new ray direction. @@ -747,25 +867,46 @@ void refractRayDiffUsingVertexData(VertexData v, StaticVertexData triangleVertic \param[out] refractedRayDir The refracted ray direction (unless the ray was totally internally reflcted (TIR:ed). \return Whether the ray was not totally internally reflected, i.e., returns true without TIR, and false in cases of TIR */ -bool refractRayDifferential(inout RayDiff rayDiff, float3 rayDir, float3 nonNormalizedInterpolatedNormalW, - float3 normalizedInterpolatedNormalW, float2 dBarydx, float2 dBarydy, float3 normalsW[3], - float eta, out float3 refractedDir) +bool refractRayDifferential( + inout RayDiff rayDiff, + float3 rayDir, + float3 nonNormalizedInterpolatedNormalW, + float3 normalizedInterpolatedNormalW, + float2 dBarydx, + float2 dBarydy, + float3 normalsW[3], + float eta, + out float3 refractedDir +) { float NdotD = dot(normalizedInterpolatedNormalW, rayDir); float k = eta * eta * (1.0f - NdotD * NdotD); if (k > 1.0f) { refractedDir = float3(0.0f, 0.0f, 0.0f); - return false; // Total internal reflection occured. + return false; // Total internal reflection occured. } - float NdotDprime = sqrt(1.0f - k); // Equation 17. - float mu = eta * NdotD + NdotDprime; // Equation 17. - refractedDir = eta * rayDir - mu * normalizedInterpolatedNormalW; // Equation 16. - - computeRayDifferentialRefraction(rayDiff, rayDir, nonNormalizedInterpolatedNormalW, normalizedInterpolatedNormalW, dBarydx, dBarydy, normalsW, eta, refractedDir, NdotD, NdotDprime, mu); - - return true; // Success, i.e., no total internal reflection. + float NdotDprime = sqrt(1.0f - k); // Equation 17. + float mu = eta * NdotD + NdotDprime; // Equation 17. + refractedDir = eta * rayDir - mu * normalizedInterpolatedNormalW; // Equation 16. + + computeRayDifferentialRefraction( + rayDiff, + rayDir, + nonNormalizedInterpolatedNormalW, + normalizedInterpolatedNormalW, + dBarydx, + dBarydy, + normalsW, + eta, + refractedDir, + NdotD, + NdotDprime, + mu + ); + + return true; // Success, i.e., no total internal reflection. } /** Prepares vertices for ray differentials so that computations happen in world space. @@ -780,8 +921,18 @@ bool refractRayDifferential(inout RayDiff rayDiff, float3 rayDir, float3 nonNorm \param[out] unnormalizedN Interpolated, unnormalized normal. \param[out] txcoords Texture coordinates. */ -void prepareVerticesForRayDiffs(float3 rayDir, StaticVertexData vertices[3], float4x4 worldMat, float3x3 worldInvTransposeMat, float3 barycentrics, - out float3 edge01, out float3 edge02, out float3 normals[3], out float3 unnormalizedN, out float2 txcoords[3]) +void prepareVerticesForRayDiffs( + float3 rayDir, + StaticVertexData vertices[3], + float4x4 worldMat, + float3x3 worldInvTransposeMat, + float3 barycentrics, + out float3 edge01, + out float3 edge02, + out float3 normals[3], + out float3 unnormalizedN, + out float2 txcoords[3] +) { // Transform relevant data to world space. edge01 = mul((float3x3)worldMat, vertices[1].position - vertices[0].position); @@ -824,12 +975,27 @@ void prepareVerticesForRayDiffs(float3 rayDir, StaticVertexData vertices[3], flo \param[out] dUVdx Differential texture coordinates wrt x. \param[out] dUVdy Differential texture coordinates wrt y. */ -void prepareRayDiffAtHitPoint(VertexData v, StaticVertexData triangleVertices[3], float3 barycentrics, float3 rayDir, float4x4 worldMat, float3x3 worldInvTransposeMat, - RayDiff rayDiff, out float3 unnormalizedN, out float3 normals[3], out float2 dBarydx, out float2 dBarydy, out float2 dUVdx, out float2 dUVdy) +void prepareRayDiffAtHitPoint( + VertexData v, + StaticVertexData triangleVertices[3], + float3 barycentrics, + float3 rayDir, + float4x4 worldMat, + float3x3 worldInvTransposeMat, + RayDiff rayDiff, + out float3 unnormalizedN, + out float3 normals[3], + out float2 dBarydx, + out float2 dBarydy, + out float2 dUVdx, + out float2 dUVdy +) { float3 edge01, edge02; float2 txcoords[3]; - prepareVerticesForRayDiffs(rayDir, triangleVertices, worldMat, worldInvTransposeMat, barycentrics, edge01, edge02, normals, unnormalizedN, txcoords); + prepareVerticesForRayDiffs( + rayDir, triangleVertices, worldMat, worldInvTransposeMat, barycentrics, edge01, edge02, normals, unnormalizedN, txcoords + ); computeBarycentricDifferentials(rayDiff, rayDir, edge01, edge02, v.faceNormalW, dBarydx, dBarydy); interpolateDifferentials(dBarydx, dBarydy, txcoords, dUVdx, dUVdy); } @@ -845,13 +1011,36 @@ void prepareRayDiffAtHitPoint(VertexData v, StaticVertexData triangleVertices[3] \param[out] dUVdx The differential of the texture coordinates in pixel coordinate x. \param[out] dUVdy The differential of the texture coordinates in pixel coordinate y. */ -void reflectRayDiffUsingVertexData(VertexData v, StaticVertexData triangleVertices[3], float3 barycentrics, float3 rayDir, float4x4 worldMat, float3x3 worldInvTransposeMat, - inout RayDiff rayDiff, out float2 dUVdx, out float2 dUVdy) +void reflectRayDiffUsingVertexData( + VertexData v, + StaticVertexData triangleVertices[3], + float3 barycentrics, + float3 rayDir, + float4x4 worldMat, + float3x3 worldInvTransposeMat, + inout RayDiff rayDiff, + out float2 dUVdx, + out float2 dUVdy +) { - float3 unnormalizedN; // Non-normalized interpolated normal for ray differential scatter. - float3 normals[3]; // Non-normalized normals for ray differential scatter. + float3 unnormalizedN; // Non-normalized interpolated normal for ray differential scatter. + float3 normals[3]; // Non-normalized normals for ray differential scatter. float2 dBarydx, dBarydy; - prepareRayDiffAtHitPoint(v, triangleVertices, barycentrics, rayDir, worldMat, worldInvTransposeMat, rayDiff, unnormalizedN, normals, dBarydx, dBarydy, dUVdx, dUVdy); + prepareRayDiffAtHitPoint( + v, + triangleVertices, + barycentrics, + rayDir, + worldMat, + worldInvTransposeMat, + rayDiff, + unnormalizedN, + normals, + dBarydx, + dBarydy, + dUVdx, + dUVdy + ); reflectRayDifferential(rayDiff, rayDir, unnormalizedN, v.normalW, dBarydx, dBarydy, normals); } @@ -869,7 +1058,7 @@ float computeEnvironmentMapLOD(float spreadAngle, Texture2D environmentMap) { uint txw, txh; environmentMap.GetDimensions(txw, txh); - return log2(abs(spreadAngle) * txh * M_1_PI); // From chapter 21 in Ray Tracing Gems. + return safeLog2(abs(spreadAngle) * txh * M_1_PI); // From chapter 21 in Ray Tracing Gems. } /** Compute the LOD used when performing a lookup in an environment map when using ray differentials @@ -882,16 +1071,15 @@ float computeEnvironmentMapLOD(float3 dDdx, float3 dDdy, Texture2D environmentMa { uint txw, txh; environmentMap.GetDimensions(txw, txh); - return log2(length(dDdx + dDdy) * txh * M_1_PI); // From chapter 21 in Ray Tracing Gems. + return safeLog2(length(dDdx + dDdy) * txh * M_1_PI); // From chapter 21 in Ray Tracing Gems. } - // ---------------------------------------------------------------------------- // Curvature estimation helpers // ---------------------------------------------------------------------------- /** Generic interface for the triangle curvature estimators. -*/ + */ interface ITriangleCurvatureEstimator { /** Returns the estimated curvature from vertex attributes for ray tracing. @@ -902,23 +1090,22 @@ interface ITriangleCurvatureEstimator float eval(float3 edge01, float3 edge02, float3 edge12, float curvature01, float curvature02, float curvature12); }; - struct TriangleCurvature_Average : ITriangleCurvatureEstimator { float eval(float3 edge01, float3 edge02, float3 edge12, float curvature01, float curvature02, float curvature12) { - return ((curvature01 + curvature02 + curvature12) / 3.0f); // Average triangle curvature. + return ((curvature01 + curvature02 + curvature12) / 3.0f); // Average triangle curvature. } }; - struct TriangleCurvature_Max : ITriangleCurvatureEstimator { float eval(float3 edge01, float3 edge02, float3 edge12, float curvature01, float curvature02, float curvature12) { float minCurvature = min(curvature01, min(curvature02, curvature12)); float maxCurvature = max(curvature01, max(curvature02, curvature12)); - return maxCurvature > abs(minCurvature) ? maxCurvature : minCurvature; // Return maximum of magnitudes with sign to get a conservative estimate. + return maxCurvature > abs(minCurvature) ? maxCurvature : minCurvature; // Return maximum of magnitudes with sign to get a + // conservative estimate. } }; @@ -965,49 +1152,63 @@ struct TriangleCurvature_Directional : ITriangleCurvatureEstimator { float3 usedDir = rayDir; -#if 1 // Interpolate using 2 closest angles. +#if 1 // Interpolate using 2 closest angles. float a01 = acos(abs(dot(usedDir, normalize(edge01)))); float a02 = acos(abs(dot(usedDir, normalize(edge02)))); float a12 = acos(abs(dot(usedDir, normalize(edge12)))); if (a01 > a02) { - if (a01 > a12) return (curvature02 * a12 + curvature12 * a02) / (a02 + a12); - else return (curvature01 * a02 + curvature02 * a01) / (a01 + a02); + if (a01 > a12) + return (curvature02 * a12 + curvature12 * a02) / (a02 + a12); + else + return (curvature01 * a02 + curvature02 * a01) / (a01 + a02); } else { - if (a02 > a12) return (curvature01 * a12 + curvature12 * a01) / (a01 + a12); - else return (curvature01 * a02 + curvature02 * a01) / (a01 + a02); + if (a02 > a12) + return (curvature01 * a12 + curvature12 * a01) / (a01 + a12); + else + return (curvature01 * a02 + curvature02 * a01) / (a01 + a02); } -#elif 0 // Interpolate using dp with closest edges. +#elif 0 // Interpolate using dp with closest edges. float d01 = abs(dot(usedDir, normalize(edge01))); float d02 = abs(dot(usedDir, normalize(edge02))); float d12 = abs(dot(usedDir, normalize(edge12))); if (d01 < d02) { - if (d01 < d12) return (curvature02 * d02 + curvature12 * d12) / (d02 + d12); - else return (curvature01 * d01 + curvature02 * d02) / (d01 + d02); + if (d01 < d12) + return (curvature02 * d02 + curvature12 * d12) / (d02 + d12); + else + return (curvature01 * d01 + curvature02 * d02) / (d01 + d02); } else { - if (d02 < d12) return (curvature01 * d01 + curvature12 * d12) / (d01 + d12); - else return (curvature01 * d01 + curvature02 * d02) / (d01 + d02); + if (d02 < d12) + return (curvature01 * d01 + curvature12 * d12) / (d01 + d12); + else + return (curvature01 * d01 + curvature02 * d02) / (d01 + d02); } -#elif 0 // Interpolate using angles from min/max k. +#elif 0 // Interpolate using angles from min/max k. float3 minEdge; float3 maxEdge; - if (curvature01 == minCurvature) minEdge = edge01; - else if (curvature02 == minCurvature) minEdge = edge02; - else minEdge = edge12; + if (curvature01 == minCurvature) + minEdge = edge01; + else if (curvature02 == minCurvature) + minEdge = edge02; + else + minEdge = edge12; - if (curvature01 == maxCurvature) maxEdge = edge01; - else if (curvature02 == maxCurvature) maxEdge = edge02; - else maxEdge = edge12; + if (curvature01 == maxCurvature) + maxEdge = edge01; + else if (curvature02 == maxCurvature) + maxEdge = edge02; + else + maxEdge = edge12; #if 1 float aMin = acos(abs(dot(usedDir, normalize(minEdge)))); @@ -1050,13 +1251,19 @@ struct TriangleCurvature_EllipseVis : ITriangleCurvatureEstimator // Find min/max k edges. float3 minEdge; float3 maxEdge; - if (curvature01 == minCurvature) minEdge = edge01; - else if (curvature02 == minCurvature) minEdge = edge02; - else minEdge = edge12; + if (curvature01 == minCurvature) + minEdge = edge01; + else if (curvature02 == minCurvature) + minEdge = edge02; + else + minEdge = edge12; - if (curvature01 == maxCurvature) maxEdge = edge01; - else if (curvature02 == maxCurvature) maxEdge = edge02; - else maxEdge = edge12; + if (curvature01 == maxCurvature) + maxEdge = edge01; + else if (curvature02 == maxCurvature) + maxEdge = edge02; + else + maxEdge = edge12; // Transform WS triangle edges to tangent space. float3x3 worldToTangent = (float3x3(a1 / l1, a2 / l2, geoNormal)); @@ -1070,8 +1277,10 @@ struct TriangleCurvature_EllipseVis : ITriangleCurvatureEstimator float2 ellipseEq = float2(l1, l2); // Distance of the intersection of the edge vectors with the ellipse. - float aaE0 = (ellipseEq.x * ellipseEq.y) / sqrt(ellipseEq.x * ellipseEq.x * edge0TS.y * edge0TS.y + ellipseEq.y * ellipseEq.y * edge0TS.x * edge0TS.x); - float aaE1 = (ellipseEq.x * ellipseEq.y) / sqrt(ellipseEq.x * ellipseEq.x * edge1TS.y * edge1TS.y + ellipseEq.y * ellipseEq.y * edge1TS.x * edge1TS.x); + float aaE0 = (ellipseEq.x * ellipseEq.y) / + sqrt(ellipseEq.x * ellipseEq.x * edge0TS.y * edge0TS.y + ellipseEq.y * ellipseEq.y * edge0TS.x * edge0TS.x); + float aaE1 = (ellipseEq.x * ellipseEq.y) / + sqrt(ellipseEq.x * ellipseEq.x * edge1TS.y * edge1TS.y + ellipseEq.y * ellipseEq.y * edge1TS.x * edge1TS.x); float maxA = max(aaE0, aaE1); // Scale by clipped length and normalize by max. @@ -1087,8 +1296,10 @@ struct TriangleCurvature_EllipseVis : ITriangleCurvatureEstimator if (sk0 != sk1) { - if (sign(rayConeAngle) == sk0) return k0; - else return k1; + if (sign(rayConeAngle) == sk0) + return k0; + else + return k1; } else { @@ -1109,6 +1320,6 @@ struct TriangleCurvature_Zero : ITriangleCurvatureEstimator { float eval(float3 edge01, float3 edge02, float3 edge12, float curvature01, float curvature02, float curvature12) { - return 0.0f; // Zero triangle curvature -- fastest method. Could be sufficiently good for secondary bounces. + return 0.0f; // Zero triangle curvature -- fastest method. Could be sufficiently good for secondary bounces. } }; diff --git a/Source/Falcor/Rendering/Materials/TexLODTypes.slang b/Source/Falcor/Rendering/Materials/TexLODTypes.slang index c079be2377..4a1308f5f9 100644 --- a/Source/Falcor/Rendering/Materials/TexLODTypes.slang +++ b/Source/Falcor/Rendering/Materials/TexLODTypes.slang @@ -27,7 +27,7 @@ **************************************************************************/ /** Enums for texture level-of-detail -- see TexLODHelpers.slang -*/ + */ #pragma once #include "Utils/HostDeviceShared.slangh" @@ -45,12 +45,15 @@ enum class TexLODMode : uint32_t Stochastic = 3, }; -FALCOR_ENUM_INFO(TexLODMode, { - { TexLODMode::Mip0, "Mip0" }, - { TexLODMode::RayCones, "RayCones" }, - { TexLODMode::RayDiffs, "RayDiffs" }, - { TexLODMode::Stochastic, "Stochastic" }, -}); +FALCOR_ENUM_INFO( + TexLODMode, + { + { TexLODMode::Mip0, "Mip0" }, + { TexLODMode::RayCones, "RayCones" }, + { TexLODMode::RayDiffs, "RayDiffs" }, + { TexLODMode::Stochastic, "Stochastic" }, + } +); FALCOR_ENUM_REGISTER(TexLODMode); /** This enum is shared between CPU/GPU. @@ -64,10 +67,13 @@ enum class RayConeMode : uint32_t Unified = 1, }; -FALCOR_ENUM_INFO(RayConeMode, { - { RayConeMode::Combo, "Combo" }, - { RayConeMode::Unified, "Unified" }, -}); +FALCOR_ENUM_INFO( + RayConeMode, + { + { RayConeMode::Combo, "Combo" }, + { RayConeMode::Unified, "Unified" }, + } +); FALCOR_ENUM_REGISTER(RayConeMode); END_NAMESPACE_FALCOR diff --git a/Source/Falcor/Rendering/RTXDI/PackedTypes.slang b/Source/Falcor/Rendering/RTXDI/PackedTypes.slang index 4da4d58d8c..f82e7567fa 100644 --- a/Source/Falcor/Rendering/RTXDI/PackedTypes.slang +++ b/Source/Falcor/Rendering/RTXDI/PackedTypes.slang @@ -30,9 +30,9 @@ */ struct PackedPolymorphicLight { - uint header; - uint3 data0; - uint4 data1; + uint header = {}; + uint3 data0 = {}; + uint4 data1 = {}; }; /** Packed version of SurfaceData (defined in SurfaceData.slang). diff --git a/Source/Falcor/Rendering/RTXDI/PolymorphicLight.slang b/Source/Falcor/Rendering/RTXDI/PolymorphicLight.slang index bd8c46e1d4..651bb541b1 100644 --- a/Source/Falcor/Rendering/RTXDI/PolymorphicLight.slang +++ b/Source/Falcor/Rendering/RTXDI/PolymorphicLight.slang @@ -80,13 +80,13 @@ extension PackedPolymorphicLight */ struct PolymorphicLightSample { - float3 position; ///< Position on the light in world-space. - float3 normal; ///< Normal on the light in world-space. - float3 direction; ///< Direction from the shading point to the light in world-space. - float distance; ///< Distance from the shading point to the light in world-space. - float3 Li; ///< Incident radiance at the shading point (unshadowed), pre-divided by the PDF. - float pdf; ///< PDF wrt. solid angle. - uint flags; ///< Light type enum. + float3 position = {}; ///< Position on the light in world-space. + float3 normal = {}; ///< Normal on the light in world-space. + float3 direction = {}; ///< Direction from the shading point to the light in world-space. + float distance = {}; ///< Distance from the shading point to the light in world-space. + float3 Li = {}; ///< Incident radiance at the shading point (unshadowed), pre-divided by the PDF. + float pdf = {}; ///< PDF wrt. solid angle. + uint flags = {}; ///< Light type enum. property PolymorphicLightType lightType { @@ -131,11 +131,13 @@ interface IPolymorphicLight */ struct EmissiveLight : IPolymorphicLight { - uint triIdx; ///< Global triangle index in the scene's LightCollection. - float3 position; ///< Position in world-space. - float3 edge0; ///< First triangle edge in world-space. - float3 edge1; ///< Second triangle edge in world-space. - float averageRadiance; ///< Average emitted radiance (scalar). + uint triIdx = {}; ///< Global triangle index in the scene's LightCollection. + float3 position = {}; ///< Position in world-space. + float3 edge0 = {}; ///< First triangle edge in world-space. + float3 edge1 = {}; ///< Second triangle edge in world-space. + float averageRadiance = {}; ///< Average emitted radiance (scalar). + + __init() {} __init(const PackedPolymorphicLight packed) { @@ -236,7 +238,9 @@ struct EmissiveLight : IPolymorphicLight */ struct AnalyticLight : IPolymorphicLight { - uint lightID; + uint lightID = {}; + + __init() {} __init(const PackedPolymorphicLight packed) { @@ -305,6 +309,9 @@ struct AnalyticLight : IPolymorphicLight */ struct EnvLight : IPolymorphicLight { + __init() + {} + __init(const PackedPolymorphicLight packed) { } @@ -355,7 +362,9 @@ struct EnvLight : IPolymorphicLight */ struct PolymorphicLight : IPolymorphicLight { - PackedPolymorphicLight packed; + PackedPolymorphicLight packed = {}; + + __init() {} __init(const PackedPolymorphicLight packed) { diff --git a/Source/Falcor/Rendering/RTXDI/SurfaceData.slang b/Source/Falcor/Rendering/RTXDI/SurfaceData.slang index abec1896a6..9c56ea5baa 100644 --- a/Source/Falcor/Rendering/RTXDI/SurfaceData.slang +++ b/Source/Falcor/Rendering/RTXDI/SurfaceData.slang @@ -38,14 +38,14 @@ struct SurfaceData { // The following fields are stored in PackedSurfaceData. - float3 position; ///< Position in world-space. - float viewDepth; ///< Distance from viewer to surface (negative for invalid surface). - uint packedNormal; ///< Packed surface normal in world-space (octahedral mapping). - uint packedWeights; ///< Packed diffuse/specular reflectance and roughness. + float3 position = {}; ///< Position in world-space. + float viewDepth = -1.f; ///< Distance from viewer to surface (negative for invalid surface). + uint packedNormal = {}; ///< Packed surface normal in world-space (octahedral mapping). + uint packedWeights = {}; ///< Packed diffuse/specular reflectance and roughness. // The following fields are *NOT* stored in PackedSurfaceData. - float3 viewDir; ///< Direction to the viewer in world-space. + float3 viewDir = {}; ///< Direction to the viewer in world-space. // True if surface data is valid. property bool valid @@ -88,13 +88,7 @@ struct SurfaceData set { packedWeights = (packedWeights & ~0xff000000) | (packUnorm8(newValue) << 24); } } - /** Initialize an invalid surface. - */ - __init() - { - this = {}; - viewDepth = -1.f; - } + __init() {} /** Initialize a surface. \param[in] packed Packed surface data. diff --git a/Source/Falcor/Scene/Animation/Animation.cpp b/Source/Falcor/Scene/Animation/Animation.cpp index ef43693491..d6c085fc3d 100644 --- a/Source/Falcor/Scene/Animation/Animation.cpp +++ b/Source/Falcor/Scene/Animation/Animation.cpp @@ -27,7 +27,6 @@ **************************************************************************/ #include "Animation.h" #include "AnimationController.h" -#include "Utils/ObjectIDPython.h" #include "Utils/Math/Common.h" #include "Utils/Scripting/ScriptBindings.h" #include "Scene/Transform.h" diff --git a/Source/Falcor/Scene/Camera/Camera.cpp b/Source/Falcor/Scene/Camera/Camera.cpp index d997fcec23..98e8743e43 100644 --- a/Source/Falcor/Scene/Camera/Camera.cpp +++ b/Source/Falcor/Scene/Camera/Camera.cpp @@ -91,6 +91,7 @@ namespace Falcor if (mPrevData.jitterX != mData.jitterX) mChanges |= Changes::Jitter; if (mPrevData.jitterY != mData.jitterY) mChanges |= Changes::Jitter; + mPrevDataForBinding = mPrevData; mPrevData = mData; return getChanges(); @@ -264,6 +265,7 @@ namespace Falcor { calculateCameraParameters(); var["data"].setBlob(mData); + var["prevData"].setBlob(mPrevDataForBinding); } void Camera::setPatternGenerator(const ref& pGenerator, const float2& scale) diff --git a/Source/Falcor/Scene/Camera/Camera.h b/Source/Falcor/Scene/Camera/Camera.h index a92f7ae58b..88137c559c 100644 --- a/Source/Falcor/Scene/Camera/Camera.h +++ b/Source/Falcor/Scene/Camera/Camera.h @@ -79,6 +79,10 @@ namespace Falcor */ float getFocalLength() const { return mData.focalLength; } + /** Get whether the camera preserves frame height (true) or frame width (false). + */ + bool getPreserveHeight() const { return mPreserveHeight; } + /** Set the camera's film plane height in mm. */ void setFrameHeight(float height) { mData.frameHeight = height; mPreserveHeight = true; mDirty = true; } @@ -301,6 +305,7 @@ namespace Falcor void calculateCameraParameters() const; mutable CameraData mData; CameraData mPrevData; + CameraData mPrevDataForBinding; struct { diff --git a/Source/Falcor/Scene/Camera/Camera.slang b/Source/Falcor/Scene/Camera/Camera.slang index 792bb3b364..fd48cd0185 100644 --- a/Source/Falcor/Scene/Camera/Camera.slang +++ b/Source/Falcor/Scene/Camera/Camera.slang @@ -32,6 +32,7 @@ import Utils.Math.MathHelpers; struct Camera { CameraData data; + CameraData prevData; float3 getPosition() { return data.posW; } float4x4 getViewProj() { return data.viewProjMat; } @@ -44,16 +45,26 @@ struct Camera \return Returns the camera ray. */ Ray computeRayPinhole(uint2 pixel, uint2 frameDim, bool applyJitter = true) + { + return computeRayPinhole(data, pixel, frameDim, applyJitter); + } + + Ray computeRayPinholePrevFrame(uint2 pixel, uint2 frameDim, bool applyJitter = true) + { + return computeRayPinhole(prevData, pixel, frameDim, applyJitter); + } + + Ray computeRayPinhole(const CameraData camData, uint2 pixel, uint2 frameDim, bool applyJitter = true) { Ray ray; // Compute the normalized ray direction assuming a pinhole camera. - ray.origin = data.posW; + ray.origin = camData.posW; ray.dir = normalize(computeNonNormalizedRayDirPinhole(pixel, frameDim, applyJitter)); - float invCos = 1.f / dot(normalize(data.cameraW), ray.dir); - ray.tMin = data.nearZ * invCos; - ray.tMax = data.farZ * invCos; + float invCos = 1.f / dot(normalize(camData.cameraW), ray.dir); + ray.tMin = camData.nearZ * invCos; + ray.tMax = camData.farZ * invCos; return ray; } @@ -85,26 +96,51 @@ struct Camera \param[in] u Uniform 2D sample. \return Returns the camera ray. */ - Ray computeRayThinlens(uint2 pixel, uint2 frameDim, float2 u) + Ray computeRayThinlens(uint2 pixel, uint2 frameDim, float2 u, bool applyJitter = true) + { + float2 u0 = float2(0.5f, 0.5f); + if (applyJitter) u0 += frameDim * float2(-data.jitterX, data.jitterY); + return computeRayGeneral(data, pixel, frameDim, u0, u); + } + + Ray computeRayThinlensPrevFrame(uint2 pixel, uint2 frameDim, float2 u, bool applyJitter = true) + { + float2 u0 = float2(0.5f, 0.5f); + if (applyJitter) u0 += frameDim * float2(-prevData.jitterX, prevData.jitterY); + return computeRayGeneral(prevData, pixel, frameDim, u0, u); + } + + // u0 is subpixel uv and u1 is lens uv + Ray computeRayThinlensSubPixelRandom(float2 pixel, uint2 frameDim, float2 u0, float2 u1) + { + return computeRayGeneral(data, pixel, frameDim, u0, u1); + } + + Ray computeRayThinlensSubPixelRandomPrevFrame(float2 pixel, uint2 frameDim, float2 u0, float2 u1) + { + return computeRayGeneral(prevData, pixel, frameDim, u0, u1); + } + + Ray computeRayGeneral(const CameraData camData, float2 pixel, uint2 frameDim, float2 u0, float2 u1) { Ray ray; // Sample position in screen space in [0,1] with origin at the top-left corner. // The camera jitter offsets the sample by +-0.5 pixels from the pixel center. - float2 p = (pixel + float2(0.5f, 0.5f)) / frameDim + float2(-data.jitterX, data.jitterY); + float2 p = float2(pixel + u0) / frameDim; float2 ndc = float2(2, -2) * p + float2(-1, 1); // Compute the normalized ray direction assuming a thin-lens camera. - ray.origin = data.posW; - ray.dir = ndc.x * data.cameraU + ndc.y * data.cameraV + data.cameraW; - float2 apertureSample = sample_disk(u); // Sample lies in the unit disk [-1,1]^2 + ray.origin = camData.posW; + ray.dir = ndc.x * camData.cameraU + ndc.y * camData.cameraV + camData.cameraW; + float2 apertureSample = sample_disk(u1); // Sample lies in the unit disk [-1,1]^2 float3 rayTarget = ray.origin + ray.dir; - ray.origin += data.apertureRadius * (apertureSample.x * normalize(data.cameraU) + apertureSample.y * normalize(data.cameraV)); + ray.origin += camData.apertureRadius * (apertureSample.x * normalize(camData.cameraU) + apertureSample.y * normalize(camData.cameraV)); ray.dir = normalize(rayTarget - ray.origin); - float invCos = 1.f / dot(normalize(data.cameraW), ray.dir); - ray.tMin = data.nearZ * invCos; - ray.tMax = data.farZ * invCos; + float invCos = 1.f / dot(normalize(camData.cameraW), ray.dir); + ray.tMin = camData.nearZ * invCos; + ray.tMax = camData.farZ * invCos; return ray; } diff --git a/Source/Falcor/Scene/HitInfo.slang b/Source/Falcor/Scene/HitInfo.slang index ec6ccaa6db..dd1b7d801f 100644 --- a/Source/Falcor/Scene/HitInfo.slang +++ b/Source/Falcor/Scene/HitInfo.slang @@ -67,9 +67,9 @@ typedef uint4 PackedHitInfo; */ struct GeometryHit { - GeometryInstanceID instanceID; - uint primitiveIndex; - float2 barycentrics; + GeometryInstanceID instanceID = {}; + uint primitiveIndex = 0; + float2 barycentrics = float2(0.f); /** Return the barycentric weights. */ @@ -101,8 +101,6 @@ struct TriangleHit : GeometryHit barycentrics.x = asfloat(packed[2]); barycentrics.y = asfloat(packed[3]); #endif -#else - this = {}; #endif } @@ -130,7 +128,7 @@ struct TriangleHit : GeometryHit */ struct DisplacedTriangleHit : GeometryHit { - float displacement; + float displacement = 0.f; __init(const PackedHitInfo packed) { @@ -140,8 +138,6 @@ struct DisplacedTriangleHit : GeometryHit const uint uy = ((packed[2] & 0xff) << 16) | (packed[3] >> 16); barycentrics = float2(ux, uy) * (1.f / 16777215); displacement = f16tof32(packed[3]); -#else - this = {}; #endif } @@ -172,8 +168,6 @@ struct CurveHit : GeometryHit HitInfo::unpackHeader(packed, instanceID, primitiveIndex); barycentrics.x = asfloat(packed[2]); barycentrics.y = asfloat(packed[3]); -#else - this = {}; #endif } @@ -198,8 +192,8 @@ struct CurveHit : GeometryHit */ struct SDFGridHit { - GeometryInstanceID instanceID; - SDFGridHitData hitData; + GeometryInstanceID instanceID = {}; + SDFGridHitData hitData = {}; __init(const PackedHitInfo packed) { @@ -220,8 +214,6 @@ struct SDFGridHit #endif -#else - this = {}; #endif // SCENE_HAS_GEOMETRY_TYPE(GEOMETRY_TYPE_SDF_GRID) } @@ -317,7 +309,7 @@ struct HitInfo */ __init() { - data = {}; + data = PackedHitInfo(0); } /** Initialize hit info from a packed hit info. diff --git a/Source/Falcor/Scene/IScene.cpp b/Source/Falcor/Scene/IScene.cpp index 924318a718..ad4068dbff 100644 --- a/Source/Falcor/Scene/IScene.cpp +++ b/Source/Falcor/Scene/IScene.cpp @@ -25,7 +25,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -#pragma once #include "IScene.h" #include "Core/API/RenderContext.h" #include "Core/Program/ShaderVar.h" diff --git a/Source/Falcor/Scene/Lights/EnvMap.cpp b/Source/Falcor/Scene/Lights/EnvMap.cpp index c3123f1330..2981975597 100644 --- a/Source/Falcor/Scene/Lights/EnvMap.cpp +++ b/Source/Falcor/Scene/Lights/EnvMap.cpp @@ -96,6 +96,7 @@ namespace Falcor // Set variables. var["data"].setBlob(mData); + var["prevData"].setBlob(mPrevDataForBinding); // Bind resources. var["envMap"].setTexture(mpEnvMap); @@ -106,6 +107,8 @@ namespace Falcor { mChanges = Changes::None; + mPrevDataForBinding = mPrevData; + if (mData.transform != mPrevData.transform) mChanges |= Changes::Transform; if (mData.intensity != mPrevData.intensity) mChanges |= Changes::Intensity; if (any(mData.tint != mPrevData.tint)) mChanges |= Changes::Intensity; @@ -142,7 +145,7 @@ namespace Falcor pybind11::class_> envMap(m, "EnvMap"); auto createFromFile = [](const std::filesystem::path &path) { - ref envMap = EnvMap::createFromFile(accessActivePythonSceneBuilder().getDevice(), getActiveAssetResolver().resolvePath(path)); + ref envMap = EnvMap::createFromFile(accessActivePythonSceneBuilderDevice(), getActiveAssetResolver().resolvePath(path)); if (!envMap) FALCOR_THROW("Failed to load environment map from '{}'.", path); return envMap; diff --git a/Source/Falcor/Scene/Lights/EnvMap.h b/Source/Falcor/Scene/Lights/EnvMap.h index 99f8fdaf4d..1d855249cc 100644 --- a/Source/Falcor/Scene/Lights/EnvMap.h +++ b/Source/Falcor/Scene/Lights/EnvMap.h @@ -135,6 +135,7 @@ namespace Falcor EnvMapData mData; EnvMapData mPrevData; + EnvMapData mPrevDataForBinding; float3 mRotation = { 0.f, 0.f, 0.f }; diff --git a/Source/Falcor/Scene/Lights/EnvMap.slang b/Source/Falcor/Scene/Lights/EnvMap.slang index 3c8d0a3ed7..e47d15c5e1 100644 --- a/Source/Falcor/Scene/Lights/EnvMap.slang +++ b/Source/Falcor/Scene/Lights/EnvMap.slang @@ -35,8 +35,8 @@ struct EnvMap Texture2D envMap; ///< Environment map texture. SamplerState envSampler; ///< Environment map texture sampler. - EnvMapData data; ///< Environment map data. - + EnvMapData data; ///< Environment map data. + EnvMapData prevData; ///< Environment map data (previous frame). /** Returns the dimensions of the env map texture. */ uint2 getDimensions() @@ -47,24 +47,24 @@ struct EnvMap } /** Evaluates the radiance at a given texel. - */ - float3 evalTexel(uint2 coord) + */ + float3 evalTexel(uint2 coord, bool isPrevFrame) { - return envMap[coord].rgb * getIntensity(); + return envMap[coord].rgb * getIntensity(isPrevFrame); } /** Evaluates the radiance at a given uv coordinate. - */ - float3 eval(float2 uv, float lod = 0.f) + */ + float3 eval(float2 uv, float lod = 0.f, bool isPrevFrame = false) { - return envMap.SampleLevel(envSampler, uv, lod).rgb * getIntensity(); + return envMap.SampleLevel(envSampler, uv, lod).rgb * getIntensity(isPrevFrame); } /** Evaluates the radiance coming from world space direction 'dir'. - */ - float3 eval(float3 dir, float lod = 0.f) + */ + float3 eval(float3 dir, float lod = 0.f, bool isPrevFrame = false) { - return eval(worldToUv(dir), lod); + return eval(worldToUv(dir, isPrevFrame), lod, isPrevFrame); } /** Transform direction in local space to uv coordinates. @@ -82,39 +82,39 @@ struct EnvMap } /** Transform direction in world space to uv coordinates. - */ - float2 worldToUv(float3 dir) + */ + float2 worldToUv(float3 dir, bool isPrevFrame = false) { - return localToUv(toLocal(dir)); + return localToUv(toLocal(dir, isPrevFrame)); } /** Transform uv coordinates to direction in world space. - */ - float3 uvToWorld(float2 uv) + */ + float3 uvToWorld(float2 uv, bool isPrevFrame = false) { - return toWorld(uvToLocal(uv)); + return toWorld(uvToLocal(uv), isPrevFrame); } /** Transform direction from local to world space. - */ - float3 toWorld(float3 dir) + */ + float3 toWorld(float3 dir, bool isPrevFrame = false) { // TODO: For identity transform we might want to skip this statically. - return mul((float3x3)data.transform, dir); + return mul((float3x3)(isPrevFrame ? prevData.transform : data.transform), dir); } /** Transform direction from world to local space. - */ - float3 toLocal(float3 dir) + */ + float3 toLocal(float3 dir, bool isPrevFrame = false) { // TODO: For identity transform we might want to skip this statically. - return mul((float3x3)data.invTransform, dir); + return mul((float3x3)(isPrevFrame ? prevData.invTransform : data.invTransform), dir); } /** Get the intensity scaling factor (including tint). - */ - float3 getIntensity() + */ + float3 getIntensity(bool isPrevFrame = false) { - return data.intensity * data.tint; + return isPrevFrame ? prevData.intensity * prevData.tint : data.intensity * data.tint; } }; diff --git a/Source/Falcor/Scene/Lights/Light.cpp b/Source/Falcor/Scene/Lights/Light.cpp index 3e6b468994..dda7fb2c30 100644 --- a/Source/Falcor/Scene/Lights/Light.cpp +++ b/Source/Falcor/Scene/Lights/Light.cpp @@ -171,6 +171,11 @@ namespace Falcor mPrevData = mData; } + ref PointLight::clone() const + { + return make_ref(*this); + } + void PointLight::setWorldDirection(const float3& dir) { if (!(length(dir) > 0.f)) // NaNs propagate @@ -239,6 +244,11 @@ namespace Falcor mPrevData = mData; } + ref DirectionalLight::clone() const + { + return make_ref(*this); + } + void DirectionalLight::renderUI(Gui::Widgets& widget) { Light::renderUI(widget); @@ -276,6 +286,11 @@ namespace Falcor mPrevData = mData; } + ref DistantLight::clone() const + { + return make_ref(*this); + } + void DistantLight::renderUI(Gui::Widgets& widget) { Light::renderUI(widget); @@ -363,6 +378,11 @@ namespace Falcor // RectLight + ref RectLight::clone() const + { + return make_ref(*this); + } + void RectLight::update() { AnalyticAreaLight::update(); @@ -374,6 +394,11 @@ namespace Falcor // DiscLight + ref DiscLight::clone() const + { + return make_ref(*this); + } + void DiscLight::update() { AnalyticAreaLight::update(); @@ -386,6 +411,11 @@ namespace Falcor // SphereLight + ref SphereLight::clone() const + { + return make_ref(*this); + } + void SphereLight::update() { AnalyticAreaLight::update(); diff --git a/Source/Falcor/Scene/Lights/Light.h b/Source/Falcor/Scene/Lights/Light.h index f5b6539050..a981edb91f 100644 --- a/Source/Falcor/Scene/Lights/Light.h +++ b/Source/Falcor/Scene/Lights/Light.h @@ -48,6 +48,10 @@ namespace Falcor public: virtual ~Light() = default; + /** Create a clone of the light, with completely identical data. + */ + virtual ref clone() const = 0; + /** Set the light parameters into a shader variable. To use this you need to include/import 'ShaderCommon' inside your shader. */ virtual void bindShaderData(const ShaderVar& var); @@ -152,6 +156,10 @@ namespace Falcor PointLight(const std::string& name); ~PointLight() = default; + /** Create a clone of the light, with completely identical data. + */ + ref clone() const override; + /** Render UI elements for this light. */ void renderUI(Gui::Widgets& widget) override; @@ -209,6 +217,10 @@ namespace Falcor DirectionalLight(const std::string& name); ~DirectionalLight() = default; + /** Create a clone of the light, with completely identical data. + */ + ref clone() const override; + /** Render UI elements for this light. */ void renderUI(Gui::Widgets& widget) override; @@ -244,6 +256,10 @@ namespace Falcor DistantLight(const std::string& name); ~DistantLight() = default; + /** Create a clone of the light, with completely identical data. + */ + ref clone() const override; + /** Render UI elements for this light. */ void renderUI(Gui::Widgets& widget) override; @@ -331,6 +347,10 @@ namespace Falcor RectLight(const std::string& name) : AnalyticAreaLight(name, LightType::Rect) {} ~RectLight() = default; + /** Create a clone of the light, with completely identical data. + */ + ref clone() const override; + private: virtual void update() override; }; @@ -345,6 +365,10 @@ namespace Falcor DiscLight(const std::string& name) : AnalyticAreaLight(name, LightType::Disc) {} ~DiscLight() = default; + /** Create a clone of the light, with completely identical data. + */ + ref clone() const override; + private: virtual void update() override; }; @@ -359,6 +383,10 @@ namespace Falcor SphereLight(const std::string& name) : AnalyticAreaLight(name, LightType::Sphere) {} ~SphereLight() = default; + /** Create a clone of the light, with completely identical data. + */ + ref clone() const override; + private: virtual void update() override; }; diff --git a/Source/Falcor/Scene/Lights/LightCollection.cpp b/Source/Falcor/Scene/Lights/LightCollection.cpp index 72f77ea8ad..255ff77170 100644 --- a/Source/Falcor/Scene/Lights/LightCollection.cpp +++ b/Source/Falcor/Scene/Lights/LightCollection.cpp @@ -103,13 +103,23 @@ namespace Falcor if (pUpdateStatus) pUpdateStatus->lightsUpdateInfo.push_back(updateFlags); } + std::swap(mpTriangleData, mpTriangleDataPrev); + // Update light data if needed. if (!updatedLights.empty()) { updateTrianglePositions(pRenderContext, *mpScene, updatedLights); + mTriangleDataInSync = false; // current now diverges from prev (motion). mUpdateFlagsSignal(UpdateFlags::MatrixChanged); return true; } + else if (!mTriangleDataInSync && mpTriangleData && mpTriangleDataPrev) + { + // First static frame after motion: mpTriangleDataPrev holds the last correct frame (via swap above); + // resync mpTriangleData to match it once, then both stay in sync for free via the no-op swap each frame after. + pRenderContext->copyResource(mpTriangleData.get(), mpTriangleDataPrev.get()); + mTriangleDataInSync = true; + } return false; } @@ -267,14 +277,21 @@ namespace Falcor // Create GPU buffers. mpTriangleData = mpDevice->createStructuredBuffer(mpTriangleListBuilder->getRootVar()["gTriangleData"], mTriangleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); mpTriangleData->setName("LightCollection::mpTriangleData"); - if (mpTriangleData->getStructSize() != sizeof(PackedEmissiveTriangle)) FALCOR_THROW("Struct PackedEmissiveTriangle size mismatch between CPU/GPU"); + FALCOR_CHECK(mpTriangleData->getStructSize() == sizeof(PackedEmissiveTriangle), "Struct PackedEmissiveTriangle size mismatch between CPU/GPU"); + + mpTriangleDataPrev = mpDevice->createStructuredBuffer(mpTriangleListBuilder->getRootVar()["gTriangleData"], mTriangleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); + mpTriangleDataPrev->setName("LightCollection::mpTriangleDataPrev"); + FALCOR_CHECK(mpTriangleDataPrev->getStructSize() == sizeof(PackedEmissiveTriangle), "Struct PackedEmissiveTriangle size mismatch between CPU/GPU"); mpFluxData = mpDevice->createStructuredBuffer(mpFinalizeIntegration->getRootVar()["gFluxData"], mTriangleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); mpFluxData->setName("LightCollection::mpFluxData"); - if (mpFluxData->getStructSize() != sizeof(EmissiveFlux)) FALCOR_THROW("Struct EmissiveFlux size mismatch between CPU/GPU"); + FALCOR_CHECK(mpFluxData->getStructSize() == sizeof(EmissiveFlux), "Struct EmissiveFlux size mismatch between CPU/GPU"); // Compute triangle data (vertices, uv-coordinates, materialID) for all mesh lights. buildTriangleList(pRenderContext, scene); + // copy triangle data to triangleDataPrev + pRenderContext->copyResource(mpTriangleDataPrev.get(), mpTriangleData.get()); + mTriangleDataInSync = true; } void LightCollection::prepareMeshData(const Scene& scene) @@ -303,14 +320,29 @@ namespace Falcor if (instanceCount > 0) { std::vector triangleOffsets(instanceCount, MeshLightData::kInvalidIndex); + std::vector sceneMeshPrimIDs; + for (const auto& it : mMeshLights) { FALCOR_ASSERT(it.instanceID < instanceCount); triangleOffsets[it.instanceID] = it.triangleOffset; + + for (uint32_t j = 0; j < it.triangleCount; j++) + { + sceneMeshPrimIDs.push_back(uint2(it.instanceID, j)); + } } + FALCOR_CHECK(sceneMeshPrimIDs.size() == mTriangleCount, "sceneMeshPrimIDs.size() != mTriangleCount in LightCollection.cpp"); + mpPerMeshInstanceOffset = mpDevice->createStructuredBuffer(sizeof(uint32_t), (uint32_t)triangleOffsets.size(), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, triangleOffsets.data(), false); mpPerMeshInstanceOffset->setName("LightCollection::mpPerMeshInstanceOffset"); + + if (sceneMeshPrimIDs.size() > 0) + { + mpSceneMeshPrimIDList = mpDevice->createStructuredBuffer(sizeof(uint2), (uint32_t)sceneMeshPrimIDs.size(), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, sceneMeshPrimIDs.data(), false); + mpSceneMeshPrimIDList->setName("LightCollection::mpSceneMeshPrimIDList"); + } } } @@ -585,12 +617,14 @@ namespace Falcor // Bind buffers. var["perMeshInstanceOffset"] = mpPerMeshInstanceOffset; // Can be nullptr + var["sceneMeshPrimIDList"] = mpSceneMeshPrimIDList; if (mTriangleCount > 0) { // These buffers must exist if triangle count is > 0. FALCOR_ASSERT(mpTriangleData && mpFluxData && mpMeshData); var["triangleData"] = mpTriangleData; + var["triangleDataPrev"] = mpTriangleDataPrev; var["fluxData"] = mpFluxData; var["meshData"] = mpMeshData; @@ -608,6 +642,12 @@ namespace Falcor } } + void LightCollection::updateTriangleDataShaderBinding(const ShaderVar& var) const + { + var["triangleData"] = mpTriangleData; + var["triangleDataPrev"] = mpTriangleDataPrev; + } + void LightCollection::copyDataToStagingBuffer(RenderContext* pRenderContext) const { if (mStagingBufferValid) return; @@ -715,6 +755,7 @@ namespace Falcor if (mpFluxData) m += mpFluxData->getSize(); if (mpMeshData) m += mpMeshData->getSize(); if (mpPerMeshInstanceOffset) m += mpPerMeshInstanceOffset->getSize(); + if (mpSceneMeshPrimIDList) m += mpSceneMeshPrimIDList->getSize(); if (mpStagingBuffer) m += mpStagingBuffer->getSize(); if (mIntegrator.pResultBuffer) m += mIntegrator.pResultBuffer->getSize(); return m; diff --git a/Source/Falcor/Scene/Lights/LightCollection.h b/Source/Falcor/Scene/Lights/LightCollection.h index 2cc9471cef..12569cc36b 100644 --- a/Source/Falcor/Scene/Lights/LightCollection.h +++ b/Source/Falcor/Scene/Lights/LightCollection.h @@ -89,6 +89,8 @@ namespace Falcor */ void bindShaderData(const ShaderVar& var) const override; + void updateTriangleDataShaderBinding(const ShaderVar& var) const; + /** Returns the total number of triangle lights (may include culled triangles). */ uint32_t getTotalLightCount() const override { return mTriangleCount; } @@ -164,11 +166,14 @@ namespace Falcor // GPU resources for the mesh lights and emissive triangles. ref mpTriangleData; ///< Per-triangle geometry data for emissive triangles (mTriangleCount elements). + ref mpTriangleDataPrev; + bool mTriangleDataInSync = false; ref mpActiveTriangleList; ///< List of active (non-culled) emissive triangle. ref mpTriToActiveList; ///< Mapping of all light triangles to index in mActiveTriangleList. ref mpFluxData; ///< Per-triangle flux data for emissive triangles (mTriangleCount elements). ref mpMeshData; ///< Per-mesh data for emissive meshes (mMeshLights.size() elements). ref mpPerMeshInstanceOffset; ///< Per-mesh instance offset into emissive triangles array (Scene::getMeshInstanceCount() elements). + ref mpSceneMeshPrimIDList; /// (mTriangleCount elemetns) mutable ref mpStagingBuffer; ///< Staging buffer used for retrieving the vertex positions, texture coordinates and light IDs from the GPU. ref mpStagingFence; ///< Fence used for waiting on the staging buffer being filled in. diff --git a/Source/Falcor/Scene/Lights/LightCollection.slang b/Source/Falcor/Scene/Lights/LightCollection.slang index 2cdfebaa6a..937f8984d5 100644 --- a/Source/Falcor/Scene/Lights/LightCollection.slang +++ b/Source/Falcor/Scene/Lights/LightCollection.slang @@ -45,13 +45,16 @@ struct LightCollection uint meshCount; ///< Total number of mesh lights. // These buffers are only valid if triangleCount > 0. - [root] StructuredBuffer triangleData; ///< Per-triangle geometry data for emissive triangles. + [root] StructuredBuffer triangleData; ///< Per-triangle geometry data for emissive triangles. + [root] StructuredBuffer triangleDataPrev; ///< Per-triangle geometry data for emissive triangles. StructuredBuffer activeTriangles; ///< Indices of all active (non-culled) emissive triangles. This is only valid if activeTriangleCount > 0. - StructuredBuffer triToActiveMapping; ///< Mapping from triangle index to active triangle index. kInvalidIndex if triangle is not active. + StructuredBuffer triToActiveMapping; ///< Mapping from triangle index to active triangle index. kInvalidIndex if triangle is not active. + // WARNING: the current ReSTIR PT temporal resampling assumes flux doesn't change with time, otherwise bias will be introduced StructuredBuffer fluxData; ///< Per-triangle flux data for emissive triangles. StructuredBuffer meshData; ///< Per-mesh data for emissive meshes. StructuredBuffer perMeshInstanceOffset; ///< Per-mesh instance offset into emissive triangles array, or kInvalidIndex if mesh has no emissive triangles. + StructuredBuffer sceneMeshPrimIDList; ///< Per-triangle lookup data for which scene mesh ID and primitive ID they are> static const uint kInvalidIndex = 0xffffffff; @@ -75,9 +78,10 @@ struct LightCollection \param[in] triIdx Emissive triangle index. \return Emissive triangle data. */ - EmissiveTriangle getTriangle(uint triIdx) + EmissiveTriangle getTriangle(uint triIdx, bool isPrevFrame = false) { - return triangleData[triIdx].unpack(); + if (isPrevFrame) return triangleDataPrev[triIdx].unpack(); + else return triangleData[triIdx].unpack(); } /** Returns the average radiance for a given triangle. @@ -101,6 +105,11 @@ struct LightCollection return offset != kInvalidIndex ? offset + primitiveIndex : kInvalidIndex; } + uint2 getSceneMeshPrimID(uint triIdx) + { + return sceneMeshPrimIDList[triIdx]; + } + /** Return active triangle index for a given triangle. \param[in] triIdx Emissive triangle index. \return Active triangle index, or kInvalidIndex if not an active triangle. @@ -114,6 +123,8 @@ struct LightCollection \param[in] triIdx Emissive triangle index. \return Mesh light index. */ + + // WARNING: the current ReSTIR PT temporal resampling assumes light idx/mesh data doesn't change with time, otherwise bias will be introduced uint getLightIdx(uint triIdx) { return getTriangle(triIdx).lightIdx; diff --git a/Source/Falcor/Scene/Material/AlphaTest.slang b/Source/Falcor/Scene/Material/AlphaTest.slang index 1e0a378f92..7d960fbe6e 100644 --- a/Source/Falcor/Scene/Material/AlphaTest.slang +++ b/Source/Falcor/Scene/Material/AlphaTest.slang @@ -27,7 +27,7 @@ **************************************************************************/ /** List of alpha testing modes. -*/ + */ enum AlphaTestMode { Disabled, @@ -40,7 +40,7 @@ enum AlphaTestMode This works for both raster and raytrace passes. */ #ifndef _ALPHA_TEST_MODE -#define _ALPHA_TEST_MODE (uint)AlphaTestMode::Basic +#define _ALPHA_TEST_MODE (uint) AlphaTestMode::Basic #endif static const AlphaTestMode kAlphaTestMode = AlphaTestMode(_ALPHA_TEST_MODE); @@ -77,14 +77,14 @@ bool evalAlphaTest(float alpha, float threshold, float3 posW) } /** Evaluates alpha testing using hard thresholding. -*/ + */ bool evalBasicAlphaTest(float alpha, float threshold) { return alpha < threshold; } /** Evaluates alpha testing using hashed thresholding. -*/ + */ bool evalHashedAlphaTest(float alpha, float materialThreshold, float hashedThreshold) { float compareTo = hashedThreshold <= 0 ? materialThreshold : saturate(hashedThreshold); @@ -112,25 +112,16 @@ float calculateHashedAlpha(float3 hashInputCoord, float hashScale, bool useAniso // Find the discretized derivatives of our coordinates float3 anisoDeriv = max(abs(ddx(hashInputCoord)), abs(ddy(hashInputCoord))); - float3 anisoScales = float3(0.707f / (hashScale * anisoDeriv.x), - 0.707f / (hashScale * anisoDeriv.y), - 0.707f / (hashScale * anisoDeriv.z)); + float3 anisoScales = + float3(0.707f / (hashScale * anisoDeriv.x), 0.707f / (hashScale * anisoDeriv.y), 0.707f / (hashScale * anisoDeriv.z)); // Find log-discretized noise scales - float3 scaleFlr = float3(exp2(floor(log2(anisoScales.x))), - exp2(floor(log2(anisoScales.y))), - exp2(floor(log2(anisoScales.z)))); - float3 scaleCeil = float3(exp2(ceil(log2(anisoScales.x))), - exp2(ceil(log2(anisoScales.y))), - exp2(ceil(log2(anisoScales.z)))); + float3 scaleFlr = float3(exp2(floor(log2(anisoScales.x))), exp2(floor(log2(anisoScales.y))), exp2(floor(log2(anisoScales.z)))); + float3 scaleCeil = float3(exp2(ceil(log2(anisoScales.x))), exp2(ceil(log2(anisoScales.y))), exp2(ceil(log2(anisoScales.z)))); // Compute alpha thresholds at our two noise scales - float2 alpha = float2(sineHash3D(floor(scaleFlr * hashInputCoord)), - sineHash3D(floor(scaleCeil * hashInputCoord))); + float2 alpha = float2(sineHash3D(floor(scaleFlr * hashInputCoord)), sineHash3D(floor(scaleCeil * hashInputCoord))); // Factor to linearly interpolate with - float3 fractLoc = float3(frac(log2(anisoScales.x)), - frac(log2(anisoScales.y)), - frac(log2(anisoScales.z))); - float2 toCorners = float2(length(fractLoc), - length(float3(1.0f, 1.0f, 1.0f) - fractLoc)); + float3 fractLoc = float3(frac(log2(anisoScales.x)), frac(log2(anisoScales.y)), frac(log2(anisoScales.z))); + float2 toCorners = float2(length(fractLoc), length(float3(1.0f, 1.0f, 1.0f) - fractLoc)); float lerpFactor = toCorners.x / (toCorners.x + toCorners.y); // Interpolate alpha threshold from noise at two scales float x = (1 - lerpFactor) * alpha.x + lerpFactor * alpha.y; diff --git a/Source/Falcor/Scene/Material/BasicMaterial.cpp b/Source/Falcor/Scene/Material/BasicMaterial.cpp index 634baad75e..e7e9a092bb 100644 --- a/Source/Falcor/Scene/Material/BasicMaterial.cpp +++ b/Source/Falcor/Scene/Material/BasicMaterial.cpp @@ -38,656 +38,736 @@ namespace Falcor { - namespace - { - static_assert((sizeof(MaterialHeader) + sizeof(BasicMaterialData)) <= sizeof(MaterialDataBlob), "Total material data size is too large"); - static_assert(static_cast(ShadingModel::Count) <= (1u << BasicMaterialData::kShadingModelBits), "ShadingModel bit count exceeds the maximum"); - static_assert(static_cast(NormalMapType::Count) <= (1u << BasicMaterialData::kNormalMapTypeBits), "NormalMapType bit count exceeds the maximum"); - static_assert(BasicMaterialData::kTotalFlagsBits <= 32, "BasicMaterialData flags bit count exceeds the maximum"); +namespace +{ +static_assert((sizeof(MaterialHeader) + sizeof(BasicMaterialData)) <= sizeof(MaterialDataBlob), "Total material data size is too large"); +static_assert( + static_cast(ShadingModel::Count) <= (1u << BasicMaterialData::kShadingModelBits), + "ShadingModel bit count exceeds the maximum" +); +static_assert( + static_cast(NormalMapType::Count) <= (1u << BasicMaterialData::kNormalMapTypeBits), + "NormalMapType bit count exceeds the maximum" +); +static_assert(BasicMaterialData::kTotalFlagsBits <= 32, "BasicMaterialData flags bit count exceeds the maximum"); + +// Constants. +const float kMaxVolumeAnisotropy = 0.99f; +} // namespace + +BasicMaterial::BasicMaterial(ref pDevice, const std::string& name, MaterialType type) : Material(pDevice, name, type) +{ + mHeader.setIsBasicMaterial(true); + mHeader.setIoR(1.5h); - // Constants. - const float kMaxVolumeAnisotropy = 0.99f; - } + // Setup common texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::Displacement] = {"displacement", TextureChannelFlags::RGB, false}; - BasicMaterial::BasicMaterial(ref pDevice, const std::string& name, MaterialType type) - : Material(pDevice, name, type) - { - mHeader.setIsBasicMaterial(true); - mHeader.setIoR(1.5h); + // Call update functions to ensure a valid initial state based on default material parameters. + updateAlphaMode(); + updateNormalMapType(); + updateEmissiveFlag(); + updateDeltaSpecularFlag(); +} - // Setup common texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::Displacement] = { "displacement", TextureChannelFlags::RGB, false }; +bool BasicMaterial::renderUI(Gui::Widgets& widget) +{ + // Render the base class UI first. + bool changed = Material::renderUI(widget); - // Call update functions to ensure a valid initial state based on default material parameters. - updateAlphaMode(); - updateNormalMapType(); - updateEmissiveFlag(); - updateDeltaSpecularFlag(); - } + // We're re-using the material's update flags here to track changes. + // Cache the previous flag so we can restore it before returning. + UpdateFlags prevUpdates = mUpdates; + mUpdates = UpdateFlags::None; - bool BasicMaterial::renderUI(Gui::Widgets& widget) + if (auto pTexture = getBaseColorTexture()) { - // Render the base class UI first. - bool changed = Material::renderUI(widget); + bool hasAlpha = isAlphaSupported() && doesFormatHaveAlpha(pTexture->getFormat()); + bool alphaConst = mIsTexturedAlphaConstant && hasAlpha; + bool colorConst = mIsTexturedBaseColorConstant; - // We're re-using the material's update flags here to track changes. - // Cache the previous flag so we can restore it before returning. - UpdateFlags prevUpdates = mUpdates; - mUpdates = UpdateFlags::None; + std::string str = + fmt::format("Texture info: {}x{} ({})", pTexture->getWidth(), pTexture->getHeight(), to_string(pTexture->getFormat())); + if (colorConst && !alphaConst) + str += " (color constant)"; + else if (!colorConst && alphaConst) + str += " (alpha constant)"; + else if (colorConst && alphaConst) + str += " (color and alpha constant)"; // Shouldn't happen - if (auto pTexture = getBaseColorTexture()) - { - bool hasAlpha = isAlphaSupported() && doesFormatHaveAlpha(pTexture->getFormat()); - bool alphaConst = mIsTexturedAlphaConstant && hasAlpha; - bool colorConst = mIsTexturedBaseColorConstant; - - std::string str = fmt::format("Texture info: {}x{} ({})", pTexture->getWidth(), pTexture->getHeight(), to_string(pTexture->getFormat())); - if (colorConst && !alphaConst) str += " (color constant)"; - else if (!colorConst && alphaConst) str += " (alpha constant)"; - else if (colorConst && alphaConst) str += " (color and alpha constant)"; // Shouldn't happen + widget.text("Base color: " + pTexture->getSourcePath().string()); + widget.text(str); - widget.text("Base color: " + pTexture->getSourcePath().string()); - widget.text(str); - - if (colorConst || alphaConst) - { - float4 baseColor = getBaseColor(); - if (widget.var("Base color", baseColor, 0.f, 1.f, 0.01f)) setBaseColor(baseColor); - } - - widget.image("Base color", pTexture.get(), float2(100.f)); - if (widget.button("Remove texture##BaseColor")) setBaseColorTexture(nullptr); - } - else + if (colorConst || alphaConst) { float4 baseColor = getBaseColor(); - if (widget.var("Base color", baseColor, 0.f, 1.f, 0.01f)) setBaseColor(baseColor); + if (widget.var("Base color", baseColor, 0.f, 1.f, 0.01f)) + setBaseColor(baseColor); } - if (auto pTexture = getSpecularTexture()) - { - widget.text("Specular params: " + pTexture->getSourcePath().string()); - widget.text("Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + to_string(pTexture->getFormat()) + ")"); - widget.image("Specular params", pTexture.get(), float2(100.f)); - if (widget.button("Remove texture##Specular")) setSpecularTexture(nullptr); - } - else - { - float4 specularParams = getSpecularParams(); - if (widget.var("Specular params", specularParams, 0.f, 1.f, 0.01f)) setSpecularParams(specularParams); - widget.tooltip("The encoding depends on the material type"); + widget.image("Base color", pTexture.get(), float2(100.f)); + if (widget.button("Remove texture##BaseColor")) + setBaseColorTexture(nullptr); + } + else + { + float4 baseColor = getBaseColor(); + if (widget.var("Base color", baseColor, 0.f, 1.f, 0.01f)) + setBaseColor(baseColor); + } - renderSpecularUI(widget); // Let derived classes draw additional UI elements. - } + if (auto pTexture = getSpecularTexture()) + { + widget.text("Specular params: " + pTexture->getSourcePath().string()); + widget.text( + "Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + + to_string(pTexture->getFormat()) + ")" + ); + widget.image("Specular params", pTexture.get(), float2(100.f)); + if (widget.button("Remove texture##Specular")) + setSpecularTexture(nullptr); + } + else + { + float4 specularParams = getSpecularParams(); + if (widget.var("Specular params", specularParams, 0.f, 1.f, 0.01f)) + setSpecularParams(specularParams); + widget.tooltip("The encoding depends on the material type"); - if (auto pTexture = getNormalMap()) - { - widget.text("Normal map: " + pTexture->getSourcePath().string()); - widget.text("Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + to_string(pTexture->getFormat()) + ")"); - widget.image("Normal map", pTexture.get(), float2(100.f)); - if (widget.button("Remove texture##NormalMap")) setNormalMap(nullptr); - } + renderSpecularUI(widget); // Let derived classes draw additional UI elements. + } - if (auto pTexture = getDisplacementMap()) - { - widget.text("Displacement map: " + pTexture->getSourcePath().string()); - widget.text("Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + to_string(pTexture->getFormat()) + ")"); - widget.image("Displacement map", pTexture.get(), float2(100.f)); - if (widget.button("Remove texture##DisplacementMap")) setDisplacementMap(nullptr); + if (auto pTexture = getNormalMap()) + { + widget.text("Normal map: " + pTexture->getSourcePath().string()); + widget.text( + "Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + + to_string(pTexture->getFormat()) + ")" + ); + widget.image("Normal map", pTexture.get(), float2(100.f)); + if (widget.button("Remove texture##NormalMap")) + setNormalMap(nullptr); + } - float scale = getDisplacementScale(); - if (widget.var("Displacement scale", scale)) setDisplacementScale(scale); + if (auto pTexture = getDisplacementMap()) + { + widget.text("Displacement map: " + pTexture->getSourcePath().string()); + widget.text( + "Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + + to_string(pTexture->getFormat()) + ")" + ); + widget.image("Displacement map", pTexture.get(), float2(100.f)); + if (widget.button("Remove texture##DisplacementMap")) + setDisplacementMap(nullptr); + + float scale = getDisplacementScale(); + if (widget.var("Displacement scale", scale)) + setDisplacementScale(scale); + + float offset = getDisplacementOffset(); + if (widget.var("Displacement offset", offset)) + setDisplacementOffset(offset); + } - float offset = getDisplacementOffset(); - if (widget.var("Displacement offset", offset)) setDisplacementOffset(offset); - } + if (auto pTexture = getTransmissionTexture()) + { + widget.text("Transmission color: " + pTexture->getSourcePath().string()); + widget.text( + "Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + + to_string(pTexture->getFormat()) + ")" + ); + widget.image("Transmission color", pTexture.get(), float2(100.f)); + if (widget.button("Remove texture##Transmission")) + setTransmissionTexture(nullptr); + } + else + { + float3 transmissionColor = getTransmissionColor(); + if (widget.var("Transmission", transmissionColor, 0.f, 1.f, 0.01f)) + setTransmissionColor(transmissionColor); + } - if (auto pTexture = getTransmissionTexture()) - { - widget.text("Transmission color: " + pTexture->getSourcePath().string()); - widget.text("Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + to_string(pTexture->getFormat()) + ")"); - widget.image("Transmission color", pTexture.get(), float2(100.f)); - if (widget.button("Remove texture##Transmission")) setTransmissionTexture(nullptr); - } - else - { - float3 transmissionColor = getTransmissionColor(); - if (widget.var("Transmission", transmissionColor, 0.f, 1.f, 0.01f)) setTransmissionColor(transmissionColor); - } + float diffuseTransmission = getDiffuseTransmission(); + if (widget.var("Diffuse transmission", diffuseTransmission, 0.f, 1.f, 0.01f)) + setDiffuseTransmission(diffuseTransmission); - float diffuseTransmission = getDiffuseTransmission(); - if (widget.var("Diffuse transmission", diffuseTransmission, 0.f, 1.f, 0.01f)) setDiffuseTransmission(diffuseTransmission); + float specularTransmission = getSpecularTransmission(); + if (widget.var("Specular transmission", specularTransmission, 0.f, 1.f, 0.01f)) + setSpecularTransmission(specularTransmission); - float specularTransmission = getSpecularTransmission(); - if (widget.var("Specular transmission", specularTransmission, 0.f, 1.f, 0.01f)) setSpecularTransmission(specularTransmission); + float IoR = getIndexOfRefraction(); + if (widget.var("Index of refraction", IoR, 1.f, std::numeric_limits::max(), 0.01f)) + setIndexOfRefraction(IoR); - float IoR = getIndexOfRefraction(); - if (widget.var("Index of refraction", IoR, 1.f, std::numeric_limits::max(), 0.01f)) setIndexOfRefraction(IoR); + float3 volumeAbsorption = getVolumeAbsorption(); + if (widget.var("Absorption coefficient", volumeAbsorption, 0.f, std::numeric_limits::max(), 0.01f)) + setVolumeAbsorption(volumeAbsorption); - float3 volumeAbsorption = getVolumeAbsorption(); - if (widget.var("Absorption coefficient", volumeAbsorption, 0.f, std::numeric_limits::max(), 0.01f)) setVolumeAbsorption(volumeAbsorption); + float3 volumeScattering = getVolumeScattering(); + if (widget.var("Scattering coefficient", volumeScattering, 0.f, std::numeric_limits::max(), 0.01f)) + setVolumeScattering(volumeScattering); - float3 volumeScattering = getVolumeScattering(); - if (widget.var("Scattering coefficient", volumeScattering, 0.f, std::numeric_limits::max(), 0.01f)) setVolumeScattering(volumeScattering); + float volumeAnisotropy = getVolumeAnisotropy(); + if (widget.var("Anisotropy (g)", volumeAnisotropy, -1.f, 1.f, 0.01f)) + setVolumeAnisotropy(volumeAnisotropy); - float volumeAnisotropy = getVolumeAnisotropy(); - if (widget.var("Anisotropy (g)", volumeAnisotropy, -1.f, 1.f, 0.01f)) setVolumeAnisotropy(volumeAnisotropy); + if (isAlphaSupported()) + { + // Show alpha range. This is not directly editable. + float2 alphaRange = mAlphaRange; + widget.var("Alpha range", alphaRange); + } - if (isAlphaSupported()) - { - // Show alpha range. This is not directly editable. - float2 alphaRange = mAlphaRange; - widget.var("Alpha range", alphaRange); - } + // Restore update flags. + changed |= mUpdates != UpdateFlags::None; + markUpdates(prevUpdates | mUpdates); - // Restore update flags. - changed |= mUpdates != UpdateFlags::None; - markUpdates(prevUpdates | mUpdates); + return changed; +} - return changed; - } +Material::UpdateFlags BasicMaterial::update(MaterialSystem* pOwner) +{ + FALCOR_ASSERT(pOwner); - Material::UpdateFlags BasicMaterial::update(MaterialSystem* pOwner) + if (mUpdates != Material::UpdateFlags::None) { - FALCOR_ASSERT(pOwner); + // Adjust material sidedness based on current parameters. + // TODO: Remove when single-sided transmissive materials are supported. + adjustDoubleSidedFlag(); + + // Prepare displacement maps for rendering. + prepareDisplacementMapForRendering(); + + // Update texture handles. + updateTextureHandle(pOwner, TextureSlot::BaseColor, mData.texBaseColor); + updateTextureHandle(pOwner, TextureSlot::Specular, mData.texSpecular); + updateTextureHandle(pOwner, TextureSlot::Emissive, mData.texEmissive); + updateTextureHandle(pOwner, TextureSlot::Transmission, mData.texTransmission); + updateTextureHandle(pOwner, TextureSlot::Normal, mData.texNormalMap); + updateTextureHandle(pOwner, TextureSlot::Displacement, mData.texDisplacementMap); + + // Update default sampler. + updateDefaultTextureSamplerID(pOwner, mpDefaultSampler); + + // Update displacement samplers. + uint prevFlags = mData.flags; + mData.setDisplacementMinSamplerID(pOwner->addTextureSampler(mpDisplacementMinSampler)); + mData.setDisplacementMaxSamplerID(pOwner->addTextureSampler(mpDisplacementMaxSampler)); + if (mData.flags != prevFlags) + mUpdates |= Material::UpdateFlags::DataChanged; + + // Assume any update for an emissive materials can change its emissive properties. + if (mUpdates != Material::UpdateFlags::None && isEmissive()) + mUpdates |= Material::UpdateFlags::EmissiveChanged; + } - if (mUpdates != Material::UpdateFlags::None) - { - // Adjust material sidedness based on current parameters. - // TODO: Remove when single-sided transmissive materials are supported. - adjustDoubleSidedFlag(); - - // Prepare displacement maps for rendering. - prepareDisplacementMapForRendering(); - - // Update texture handles. - updateTextureHandle(pOwner, TextureSlot::BaseColor, mData.texBaseColor); - updateTextureHandle(pOwner, TextureSlot::Specular, mData.texSpecular); - updateTextureHandle(pOwner, TextureSlot::Emissive, mData.texEmissive); - updateTextureHandle(pOwner, TextureSlot::Transmission, mData.texTransmission); - updateTextureHandle(pOwner, TextureSlot::Normal, mData.texNormalMap); - updateTextureHandle(pOwner, TextureSlot::Displacement, mData.texDisplacementMap); - - // Update default sampler. - updateDefaultTextureSamplerID(pOwner, mpDefaultSampler); - - // Update displacement samplers. - uint prevFlags = mData.flags; - mData.setDisplacementMinSamplerID(pOwner->addTextureSampler(mpDisplacementMinSampler)); - mData.setDisplacementMaxSamplerID(pOwner->addTextureSampler(mpDisplacementMaxSampler)); - if (mData.flags != prevFlags) mUpdates |= Material::UpdateFlags::DataChanged; - - // Assume any update for an emissive materials can change its emissive properties. - if (mUpdates != Material::UpdateFlags::None && isEmissive()) - mUpdates |= Material::UpdateFlags::EmissiveChanged; - } + auto flags = mUpdates; + mUpdates = Material::UpdateFlags::None; - auto flags = mUpdates; - mUpdates = Material::UpdateFlags::None; + return flags; +} - return flags; - } +bool BasicMaterial::isDisplaced() const +{ + return hasTextureSlotData(Material::TextureSlot::Displacement); +} - bool BasicMaterial::isDisplaced() const +void BasicMaterial::setAlphaMode(AlphaMode alphaMode) +{ + if (!isAlphaSupported()) { - return hasTextureSlotData(Material::TextureSlot::Displacement); + FALCOR_ASSERT(getAlphaMode() == AlphaMode::Opaque); + logWarning( + "Alpha is not supported by material type '{}'. Ignoring call to setAlphaMode() for material '{}'.", + to_string(getType()), + getName() + ); + return; } - - void BasicMaterial::setAlphaMode(AlphaMode alphaMode) + if (mHeader.getAlphaMode() != alphaMode) { - if (!isAlphaSupported()) - { - FALCOR_ASSERT(getAlphaMode() == AlphaMode::Opaque); - logWarning("Alpha is not supported by material type '{}'. Ignoring call to setAlphaMode() for material '{}'.", to_string(getType()), getName()); - return; - } - if (mHeader.getAlphaMode() != alphaMode) - { - mHeader.setAlphaMode(alphaMode); - markUpdates(UpdateFlags::DataChanged); - } + mHeader.setAlphaMode(alphaMode); + markUpdates(UpdateFlags::DataChanged); } +} - void BasicMaterial::setAlphaThreshold(float alphaThreshold) +void BasicMaterial::setAlphaThreshold(float alphaThreshold) +{ + if (!isAlphaSupported()) { - if (!isAlphaSupported()) - { - logWarning("Alpha is not supported by material type '{}'. Ignoring call to setAlphaThreshold() for material '{}'.", to_string(getType()), getName()); - return; - } - if (mHeader.getAlphaThreshold() != (float16_t)alphaThreshold) - { - mHeader.setAlphaThreshold((float16_t)alphaThreshold); - markUpdates(UpdateFlags::DataChanged); - updateAlphaMode(); - } + logWarning( + "Alpha is not supported by material type '{}'. Ignoring call to setAlphaThreshold() for material '{}'.", + to_string(getType()), + getName() + ); + return; } - - void BasicMaterial::setDefaultTextureSampler(const ref& pSampler) + if (mHeader.getAlphaThreshold() != (float16_t)alphaThreshold) { - if (pSampler != mpDefaultSampler) - { - mpDefaultSampler = pSampler; - - // Create derived samplers for displacement Min/Max filtering. - Sampler::Desc desc = pSampler->getDesc(); - desc.setMaxAnisotropy(16); // Set 16x anisotropic filtering for improved min/max precision per triangle. - desc.setReductionMode(TextureReductionMode::Min); - mpDisplacementMinSampler = mpDevice->createSampler(desc); - desc.setReductionMode(TextureReductionMode::Max); - mpDisplacementMaxSampler = mpDevice->createSampler(desc); - - markUpdates(UpdateFlags::ResourcesChanged); - } + mHeader.setAlphaThreshold((float16_t)alphaThreshold); + markUpdates(UpdateFlags::DataChanged); + updateAlphaMode(); } +} - bool BasicMaterial::setTexture(const TextureSlot slot, const ref& pTexture) +void BasicMaterial::setDefaultTextureSampler(const ref& pSampler) +{ + if (pSampler != mpDefaultSampler) { - if (!Material::setTexture(slot, pTexture)) return false; + mpDefaultSampler = pSampler; - // Update additional metadata about texture usage. - switch (slot) - { - case TextureSlot::BaseColor: - if (pTexture) - { - // Assume the texture is non-constant and has full alpha range. - // This may be changed later by optimizeTexture(). - mAlphaRange = float2(0.f, 1.f); - mIsTexturedBaseColorConstant = mIsTexturedAlphaConstant = false; - } - updateAlphaMode(); - updateDeltaSpecularFlag(); - break; - case TextureSlot::Specular: - updateDeltaSpecularFlag(); - break; - case TextureSlot::Normal: - updateNormalMapType(); - break; - case TextureSlot::Emissive: - updateEmissiveFlag(); - break; - case TextureSlot::Displacement: - mDisplacementMapChanged = true; - markUpdates(UpdateFlags::DisplacementChanged); - break; - default: - break; - } + // Create derived samplers for displacement Min/Max filtering. + Sampler::Desc desc = pSampler->getDesc(); + desc.setMaxAnisotropy(16); // Set 16x anisotropic filtering for improved min/max precision per triangle. + desc.setReductionMode(TextureReductionMode::Min); + mpDisplacementMinSampler = mpDevice->createSampler(desc); + desc.setReductionMode(TextureReductionMode::Max); + mpDisplacementMaxSampler = mpDevice->createSampler(desc); - return true; + markUpdates(UpdateFlags::ResourcesChanged); } +} - void BasicMaterial::optimizeTexture(const TextureSlot slot, const TextureAnalyzer::Result& texInfo, TextureOptimizationStats& stats) - { - FALCOR_ASSERT(getTexture(slot) != nullptr); - TextureChannelFlags channelMask = getTextureSlotInfo(slot).mask; +bool BasicMaterial::setTexture(const TextureSlot slot, const ref& pTexture) +{ + if (!Material::setTexture(slot, pTexture)) + return false; - switch (slot) - { - case TextureSlot::BaseColor: + // Update additional metadata about texture usage. + switch (slot) + { + case TextureSlot::BaseColor: + if (pTexture) { - bool previouslyOpaque = isOpaque(); - - auto pBaseColor = getBaseColorTexture(); - bool hasAlpha = isAlphaSupported() && pBaseColor && doesFormatHaveAlpha(pBaseColor->getFormat()); - bool isColorConstant = texInfo.isConstant(TextureChannelFlags::RGB); - bool isAlphaConstant = texInfo.isConstant(TextureChannelFlags::Alpha); + // Assume the texture is non-constant and has full alpha range. + // This may be changed later by optimizeTexture(). + mAlphaRange = float2(0.f, 1.f); + mIsTexturedBaseColorConstant = mIsTexturedAlphaConstant = false; + } + updateAlphaMode(); + updateDeltaSpecularFlag(); + break; + case TextureSlot::Specular: + updateDeltaSpecularFlag(); + break; + case TextureSlot::Normal: + updateNormalMapType(); + break; + case TextureSlot::Emissive: + updateEmissiveFlag(); + break; + case TextureSlot::Displacement: + mDisplacementMapChanged = true; + markUpdates(UpdateFlags::DisplacementChanged); + break; + default: + break; + } - // Update the alpha range. - if (hasAlpha) mAlphaRange = float2(texInfo.minValue.a, texInfo.maxValue.a); + return true; +} - // Update base color parameter and texture. - float4 baseColor = getBaseColor(); - if (isColorConstant) - { - baseColor = float4(texInfo.value.xyz(), baseColor.a); - mIsTexturedBaseColorConstant = true; - } - if (hasAlpha && isAlphaConstant) - { - baseColor = float4(baseColor.xyz(), texInfo.value.a); - mIsTexturedAlphaConstant = true; - } - setBaseColor(baseColor); +void BasicMaterial::optimizeTexture(const TextureSlot slot, const TextureAnalyzer::Result& texInfo, TextureOptimizationStats& stats) +{ + FALCOR_ASSERT(getTexture(slot) != nullptr); + TextureChannelFlags channelMask = getTextureSlotInfo(slot).mask; - if (isColorConstant && (!hasAlpha || isAlphaConstant)) - { - clearTexture(Material::TextureSlot::BaseColor); - stats.texturesRemoved[(size_t)slot]++; - } - else if (isColorConstant) - { - // We don't have a way to specify constant base color with non-constant alpha since they share a texture slot. - // Count number of cases and issue a perf warning later instead. - stats.constantBaseColor++; - } + switch (slot) + { + case TextureSlot::BaseColor: + { + bool previouslyOpaque = isOpaque(); - updateAlphaMode(); + auto pBaseColor = getBaseColorTexture(); + bool hasAlpha = isAlphaSupported() && pBaseColor && doesFormatHaveAlpha(pBaseColor->getFormat()); + bool isColorConstant = texInfo.isConstant(TextureChannelFlags::RGB); + bool isAlphaConstant = texInfo.isConstant(TextureChannelFlags::Alpha); - if (!previouslyOpaque && isOpaque()) stats.disabledAlpha++; + // Update the alpha range. + if (hasAlpha) + mAlphaRange = float2(texInfo.minValue.a, texInfo.maxValue.a); - break; + // Update base color parameter and texture. + float4 baseColor = getBaseColor(); + if (isColorConstant) + { + baseColor = float4(texInfo.value.xyz(), baseColor.a); + mIsTexturedBaseColorConstant = true; } - case TextureSlot::Specular: + if (hasAlpha && isAlphaConstant) { - if (texInfo.isConstant(channelMask)) - { - clearTexture(Material::TextureSlot::Specular); - setSpecularParams(texInfo.value); - stats.texturesRemoved[(size_t)slot]++; - } - break; + baseColor = float4(baseColor.xyz(), texInfo.value.a); + mIsTexturedAlphaConstant = true; } - case TextureSlot::Emissive: + setBaseColor(baseColor); + + if (isColorConstant && (!hasAlpha || isAlphaConstant)) { - if (texInfo.isConstant(channelMask)) - { - clearTexture(Material::TextureSlot::Emissive); - setEmissiveColor(texInfo.value.xyz()); - stats.texturesRemoved[(size_t)slot]++; - } - break; + clearTexture(Material::TextureSlot::BaseColor); + stats.texturesRemoved[(size_t)slot]++; } - case TextureSlot::Normal: + else if (isColorConstant) { - // Determine which channels of the normal map are used. - switch (getNormalMapType()) - { - case NormalMapType::RG: - channelMask = TextureChannelFlags::Red | TextureChannelFlags::Green; - break; - case NormalMapType::RGB: - channelMask = TextureChannelFlags::RGB; - break; - default: - logWarning("BasicMaterial::optimizeTexture() - Unsupported normal map mode"); - channelMask = TextureChannelFlags::RGBA; - break; - } + // We don't have a way to specify constant base color with non-constant alpha since they share a texture slot. + // Count number of cases and issue a perf warning later instead. + stats.constantBaseColor++; + } - if (texInfo.isConstant(channelMask)) - { - // We don't have a way to specify constant normal map value. - // Count number of cases and issue a perf warning later instead. - stats.constantNormalMaps++; - } - break; + updateAlphaMode(); + + if (!previouslyOpaque && isOpaque()) + stats.disabledAlpha++; + + break; + } + case TextureSlot::Specular: + { + if (texInfo.isConstant(channelMask)) + { + clearTexture(Material::TextureSlot::Specular); + setSpecularParams(texInfo.value); + stats.texturesRemoved[(size_t)slot]++; } - case TextureSlot::Transmission: + break; + } + case TextureSlot::Emissive: + { + if (texInfo.isConstant(channelMask)) { - if (texInfo.isConstant(channelMask)) - { - clearTexture(Material::TextureSlot::Transmission); - setTransmissionColor(texInfo.value.xyz()); - stats.texturesRemoved[(size_t)slot]++; - } - break; + clearTexture(Material::TextureSlot::Emissive); + setEmissiveColor(texInfo.value.xyz()); + stats.texturesRemoved[(size_t)slot]++; } - case TextureSlot::Displacement: + break; + } + case TextureSlot::Normal: + { + // Determine which channels of the normal map are used. + switch (getNormalMapType()) { - // Nothing to do here, displacement texture is prepared when calling prepareDisplacementMap(). + case NormalMapType::RG: + channelMask = TextureChannelFlags::Red | TextureChannelFlags::Green; + break; + case NormalMapType::RGB: + channelMask = TextureChannelFlags::RGB; break; - } default: - FALCOR_THROW("'slot' refers to unexpected texture slot {}", (uint32_t)slot); + logWarning("BasicMaterial::optimizeTexture() - Unsupported normal map mode"); + channelMask = TextureChannelFlags::RGBA; + break; } - } - bool BasicMaterial::isAlphaSupported() const - { - return getTextureSlotInfo(TextureSlot::BaseColor).hasChannel(TextureChannelFlags::Alpha); + if (texInfo.isConstant(channelMask)) + { + // We don't have a way to specify constant normal map value. + // Count number of cases and issue a perf warning later instead. + stats.constantNormalMaps++; + } + break; } - - void BasicMaterial::prepareDisplacementMapForRendering() + case TextureSlot::Transmission: { - if (auto pDisplacementMap = getDisplacementMap(); pDisplacementMap && mDisplacementMapChanged) + if (texInfo.isConstant(channelMask)) { - RenderContext* pRenderContext = mpDevice->getRenderContext(); + clearTexture(Material::TextureSlot::Transmission); + setTransmissionColor(texInfo.value.xyz()); + stats.texturesRemoved[(size_t)slot]++; + } + break; + } + case TextureSlot::Displacement: + { + // Nothing to do here, displacement texture is prepared when calling prepareDisplacementMap(). + break; + } + default: + FALCOR_THROW("'slot' refers to unexpected texture slot {}", (uint32_t)slot); + } +} - // Creates RGBA texture with MIP pyramid containing average, min, max values. - Falcor::ResourceFormat oldFormat = pDisplacementMap->getFormat(); +bool BasicMaterial::isAlphaSupported() const +{ + return getTextureSlotInfo(TextureSlot::BaseColor).hasChannel(TextureChannelFlags::Alpha); +} - // Replace texture with a 4 component one if necessary. - if (getFormatChannelCount(oldFormat) < 4) +void BasicMaterial::prepareDisplacementMapForRendering() +{ + if (auto pDisplacementMap = getDisplacementMap(); pDisplacementMap && mDisplacementMapChanged) + { + RenderContext* pRenderContext = mpDevice->getRenderContext(); + + // Creates RGBA texture with MIP pyramid containing average, min, max values. + Falcor::ResourceFormat oldFormat = pDisplacementMap->getFormat(); + + // Replace texture with a 4 component one if necessary. + if (getFormatChannelCount(oldFormat) < 4) + { + Falcor::ResourceFormat newFormat = ResourceFormat::RGBA16Float; + ResourceBindFlags bf = pDisplacementMap->getBindFlags() | ResourceBindFlags::UnorderedAccess | ResourceBindFlags::RenderTarget; + ref newDisplacementTex = mpDevice->createTexture2D( + pDisplacementMap->getWidth(), + pDisplacementMap->getHeight(), + newFormat, + pDisplacementMap->getArraySize(), + Resource::kMaxPossible, + nullptr, + bf + ); + + // Copy base level. + uint32_t arraySize = pDisplacementMap->getArraySize(); + for (uint32_t a = 0; a < arraySize; a++) { - Falcor::ResourceFormat newFormat = ResourceFormat::RGBA16Float; - ResourceBindFlags bf = pDisplacementMap->getBindFlags() | ResourceBindFlags::UnorderedAccess | ResourceBindFlags::RenderTarget; - ref newDisplacementTex = mpDevice->createTexture2D(pDisplacementMap->getWidth(), pDisplacementMap->getHeight(), newFormat, pDisplacementMap->getArraySize(), Resource::kMaxPossible, nullptr, bf); - - // Copy base level. - uint32_t arraySize = pDisplacementMap->getArraySize(); - for (uint32_t a = 0; a < arraySize; a++) - { - auto srv = pDisplacementMap->getSRV(0, 1, a, 1); - auto rtv = newDisplacementTex->getRTV(0, a, 1); - const TextureReductionMode redModes[] = { TextureReductionMode::Standard, TextureReductionMode::Standard, TextureReductionMode::Standard, TextureReductionMode::Standard }; - const float4 componentsTransform[] = { float4(1.0f, 0.0f, 0.0f, 0.0f), float4(1.0f, 0.0f, 0.0f, 0.0f), float4(1.0f, 0.0f, 0.0f, 0.0f), float4(1.0f, 0.0f, 0.0f, 0.0f) }; - pRenderContext->blit(srv, rtv, RenderContext::kMaxRect, RenderContext::kMaxRect, TextureFilteringMode::Linear, redModes, componentsTransform); - } - - pDisplacementMap = newDisplacementTex; - setDisplacementMap(newDisplacementTex); + auto srv = pDisplacementMap->getSRV(0, 1, a, 1); + auto rtv = newDisplacementTex->getRTV(0, a, 1); + const TextureReductionMode redModes[] = { + TextureReductionMode::Standard, + TextureReductionMode::Standard, + TextureReductionMode::Standard, + TextureReductionMode::Standard}; + const float4 componentsTransform[] = { + float4(1.0f, 0.0f, 0.0f, 0.0f), + float4(1.0f, 0.0f, 0.0f, 0.0f), + float4(1.0f, 0.0f, 0.0f, 0.0f), + float4(1.0f, 0.0f, 0.0f, 0.0f)}; + pRenderContext->blit( + srv, rtv, RenderContext::kMaxRect, RenderContext::kMaxRect, TextureFilteringMode::Linear, redModes, componentsTransform + ); } - // Build min/max MIPS. - pDisplacementMap->generateMips(pRenderContext, true); + pDisplacementMap = newDisplacementTex; + setDisplacementMap(newDisplacementTex); } - mDisplacementMapChanged = false; - } - void BasicMaterial::setDisplacementScale(float scale) - { - if (mData.displacementScale != scale) - { - mData.displacementScale = scale; - markUpdates(UpdateFlags::DataChanged | UpdateFlags::DisplacementChanged); - } + // Build min/max MIPS. + pDisplacementMap->generateMips(pRenderContext, true); } + mDisplacementMapChanged = false; +} - void BasicMaterial::setDisplacementOffset(float offset) +void BasicMaterial::setDisplacementScale(float scale) +{ + if (mData.displacementScale != scale) { - if (mData.displacementOffset != offset) - { - mData.displacementOffset = offset; - markUpdates(UpdateFlags::DataChanged | UpdateFlags::DisplacementChanged); - } + mData.displacementScale = scale; + markUpdates(UpdateFlags::DataChanged | UpdateFlags::DisplacementChanged); } +} - void BasicMaterial::setBaseColor(const float4& color) +void BasicMaterial::setDisplacementOffset(float offset) +{ + if (mData.displacementOffset != offset) { - if (any(mData.baseColor != (float16_t4)color)) - { - mData.baseColor = (float16_t4)color; - markUpdates(UpdateFlags::DataChanged); - updateAlphaMode(); - updateDeltaSpecularFlag(); - } + mData.displacementOffset = offset; + markUpdates(UpdateFlags::DataChanged | UpdateFlags::DisplacementChanged); } +} - void BasicMaterial::setSpecularParams(const float4& color) +void BasicMaterial::setBaseColor(const float4& color) +{ + if (any(mData.baseColor != (float16_t4)color)) { - if (any(mData.specular != (float16_t4)color)) - { - mData.specular = (float16_t4)color; - markUpdates(UpdateFlags::DataChanged); - updateDeltaSpecularFlag(); - } + mData.baseColor = (float16_t4)color; + markUpdates(UpdateFlags::DataChanged); + updateAlphaMode(); + updateDeltaSpecularFlag(); } +} - void BasicMaterial::setTransmissionColor(const float3& transmissionColor) +void BasicMaterial::setSpecularParams(const float4& color) +{ + if (any(mData.specular != (float16_t4)color)) { - if (any(mData.transmission != (float16_t3)transmissionColor)) - { - mData.transmission = (float16_t3)transmissionColor; - markUpdates(UpdateFlags::DataChanged); - } + mData.specular = (float16_t4)color; + markUpdates(UpdateFlags::DataChanged); + updateDeltaSpecularFlag(); } +} - void BasicMaterial::setDiffuseTransmission(float diffuseTransmission) +void BasicMaterial::setTransmissionColor(const float3& transmissionColor) +{ + if (any(mData.transmission != (float16_t3)transmissionColor)) { - if (mData.diffuseTransmission != (float16_t)diffuseTransmission) - { - mData.diffuseTransmission = (float16_t)diffuseTransmission; - markUpdates(UpdateFlags::DataChanged); - updateDeltaSpecularFlag(); - } + mData.transmission = (float16_t3)transmissionColor; + markUpdates(UpdateFlags::DataChanged); } +} - void BasicMaterial::setSpecularTransmission(float specularTransmission) +void BasicMaterial::setDiffuseTransmission(float diffuseTransmission) +{ + if (mData.diffuseTransmission != (float16_t)diffuseTransmission) { - if (mData.specularTransmission != (float16_t)specularTransmission) - { - mData.specularTransmission = (float16_t)specularTransmission; - markUpdates(UpdateFlags::DataChanged); - updateDeltaSpecularFlag(); - } + mData.diffuseTransmission = (float16_t)diffuseTransmission; + markUpdates(UpdateFlags::DataChanged); + updateDeltaSpecularFlag(); } +} - void BasicMaterial::setVolumeAbsorption(const float3& volumeAbsorption) +void BasicMaterial::setSpecularTransmission(float specularTransmission) +{ + if (mData.specularTransmission != (float16_t)specularTransmission) { - if (any(mData.volumeAbsorption != (float16_t3)volumeAbsorption)) - { - mData.volumeAbsorption = (float16_t3)volumeAbsorption; - markUpdates(UpdateFlags::DataChanged); - } + mData.specularTransmission = (float16_t)specularTransmission; + markUpdates(UpdateFlags::DataChanged); + updateDeltaSpecularFlag(); } +} - void BasicMaterial::setVolumeScattering(const float3& volumeScattering) +void BasicMaterial::setVolumeAbsorption(const float3& volumeAbsorption) +{ + if (any(mData.volumeAbsorption != (float16_t3)volumeAbsorption)) { - if (any(mData.volumeScattering != (float16_t3)volumeScattering)) - { - mData.volumeScattering = (float16_t3)volumeScattering; - markUpdates(UpdateFlags::DataChanged); - } + mData.volumeAbsorption = (float16_t3)volumeAbsorption; + markUpdates(UpdateFlags::DataChanged); } +} - void BasicMaterial::setVolumeAnisotropy(float volumeAnisotropy) +void BasicMaterial::setVolumeScattering(const float3& volumeScattering) +{ + if (any(mData.volumeScattering != (float16_t3)volumeScattering)) { - auto clampedAnisotropy = math::clamp(volumeAnisotropy, -kMaxVolumeAnisotropy, kMaxVolumeAnisotropy); - if (mData.volumeAnisotropy != (float16_t)clampedAnisotropy) - { - mData.volumeAnisotropy = (float16_t)clampedAnisotropy; - markUpdates(UpdateFlags::DataChanged); - } + mData.volumeScattering = (float16_t3)volumeScattering; + markUpdates(UpdateFlags::DataChanged); } +} - bool BasicMaterial::isEqual(const ref& pOther) const +void BasicMaterial::setVolumeAnisotropy(float volumeAnisotropy) +{ + auto clampedAnisotropy = math::clamp(volumeAnisotropy, -kMaxVolumeAnisotropy, kMaxVolumeAnisotropy); + if (mData.volumeAnisotropy != (float16_t)clampedAnisotropy) { - auto other = dynamic_ref_cast(pOther); - if (!other) return false; - - return (*this) == (*other); + mData.volumeAnisotropy = (float16_t)clampedAnisotropy; + markUpdates(UpdateFlags::DataChanged); } +} - bool BasicMaterial::operator==(const BasicMaterial& other) const - { - if (!isBaseEqual(other)) return false; +bool BasicMaterial::isEqual(const ref& pOther) const +{ + auto other = dynamic_ref_cast(pOther); + if (!other) + return false; -#define compare_field(_a) if (mData._a != other.mData._a) return false -#define compare_vec_field(_a) if (any(mData._a != other.mData._a)) return false - compare_field(flags); - compare_field(displacementScale); - compare_field(displacementOffset); - compare_vec_field(baseColor); - compare_vec_field(specular); - compare_vec_field(emissive); - compare_field(emissiveFactor); - compare_field(diffuseTransmission); - compare_field(specularTransmission); - compare_vec_field(transmission); - compare_vec_field(volumeAbsorption); - compare_field(volumeAnisotropy); - compare_vec_field(volumeScattering); + return (*this) == (*other); +} + +bool BasicMaterial::operator==(const BasicMaterial& other) const +{ + if (!isBaseEqual(other)) + return false; + +#define compare_field(_a) \ + if (mData._a != other.mData._a) \ + return false +#define compare_vec_field(_a) \ + if (any(mData._a != other.mData._a)) \ + return false + compare_field(flags); + compare_field(displacementScale); + compare_field(displacementOffset); + compare_vec_field(baseColor); + compare_vec_field(specular); + compare_vec_field(emissive); + compare_field(emissiveFactor); + compare_field(diffuseTransmission); + compare_field(specularTransmission); + compare_vec_field(transmission); + compare_vec_field(volumeAbsorption); + compare_field(volumeAnisotropy); + compare_vec_field(volumeScattering); #undef compare_field #undef compare_vec_field - // Compare the sampler descs directly to identify functional differences. - if (mpDefaultSampler->getDesc() != other.mpDefaultSampler->getDesc()) return false; - if (mpDisplacementMinSampler->getDesc() != other.mpDisplacementMinSampler->getDesc()) return false; - if (mpDisplacementMaxSampler->getDesc() != other.mpDisplacementMaxSampler->getDesc()) return false; + // Compare the sampler descs directly to identify functional differences. + if (mpDefaultSampler->getDesc() != other.mpDefaultSampler->getDesc()) + return false; + if (mpDisplacementMinSampler->getDesc() != other.mpDisplacementMinSampler->getDesc()) + return false; + if (mpDisplacementMaxSampler->getDesc() != other.mpDisplacementMaxSampler->getDesc()) + return false; - return true; - } + return true; +} - void BasicMaterial::updateAlphaMode() +void BasicMaterial::updateAlphaMode() +{ + if (!isAlphaSupported()) { - if (!isAlphaSupported()) - { - FALCOR_ASSERT(getAlphaMode() == AlphaMode::Opaque); - return; - } - - // Set alpha range to the constant alpha value if non-textured. - bool hasAlpha = getBaseColorTexture() && doesFormatHaveAlpha(getBaseColorTexture()->getFormat()); - float alpha = ((float4)mData.baseColor).a; - if (!hasAlpha) mAlphaRange = float2(alpha); - - // Decide if we need to run the alpha test. - // This is derived from the current alpha threshold and conservative alpha range. - // If the test will never fail we disable it. This optimization assumes basic alpha thresholding. - // We could also optimize for the case of the test always failing by adding a 'Transparent' mode. - // This is however expected to be rare and probably not worth the runtime cost of an extra branch. - // TODO: Check if optimizing for always-fail is worth it. - // TODO: Update the logic if other alpha modes are added. - bool useAlpha = mAlphaRange.x < getAlphaThreshold(); - setAlphaMode(useAlpha ? AlphaMode::Mask : AlphaMode::Opaque); + FALCOR_ASSERT(getAlphaMode() == AlphaMode::Opaque); + return; } - void BasicMaterial::updateNormalMapType() + // Set alpha range to the constant alpha value if non-textured. + bool hasAlpha = getBaseColorTexture() && doesFormatHaveAlpha(getBaseColorTexture()->getFormat()); + float alpha = ((float4)mData.baseColor).a; + if (!hasAlpha) + mAlphaRange = float2(alpha); + + // Decide if we need to run the alpha test. + // This is derived from the current alpha threshold and conservative alpha range. + // If the test will never fail we disable it. This optimization assumes basic alpha thresholding. + // We could also optimize for the case of the test always failing by adding a 'Transparent' mode. + // This is however expected to be rare and probably not worth the runtime cost of an extra branch. + // TODO: Check if optimizing for always-fail is worth it. + // TODO: Update the logic if other alpha modes are added. + bool useAlpha = mAlphaRange.x < getAlphaThreshold(); + setAlphaMode(useAlpha ? AlphaMode::Mask : AlphaMode::Opaque); +} + +void BasicMaterial::updateNormalMapType() +{ + NormalMapType type = detectNormalMapType(getNormalMap()); + if (mData.getNormalMapType() != type) { - NormalMapType type = detectNormalMapType(getNormalMap()); - if (mData.getNormalMapType() != type) - { - mData.setNormalMapType(type); - markUpdates(UpdateFlags::DataChanged); - } + mData.setNormalMapType(type); + markUpdates(UpdateFlags::DataChanged); } +} - void BasicMaterial::updateEmissiveFlag() +void BasicMaterial::updateEmissiveFlag() +{ + bool isEmissive = false; + if (mData.emissiveFactor > 0.f) { - bool isEmissive = false; - if (mData.emissiveFactor > 0.f) - { - isEmissive = hasTextureSlotData(Material::TextureSlot::Emissive) || any(mData.emissive != float3(0.f)); - } - if (mHeader.isEmissive() != isEmissive) - { - mHeader.setEmissive(isEmissive); - markUpdates(UpdateFlags::DataChanged | UpdateFlags::EmissiveChanged); - } + isEmissive = hasTextureSlotData(Material::TextureSlot::Emissive) || any(mData.emissive != float3(0.f)); } - - void BasicMaterial::adjustDoubleSidedFlag() + if (mHeader.isEmissive() != isEmissive) { - bool doubleSided = isDoubleSided(); + mHeader.setEmissive(isEmissive); + markUpdates(UpdateFlags::DataChanged | UpdateFlags::EmissiveChanged); + } +} - // Make double sided if diffuse or specular transmission is used. - // Note this convention will eventually change to allow single-sided transmissive materials. - if ((float)mData.diffuseTransmission > 0.f || (float)mData.specularTransmission > 0.f) doubleSided = true; +void BasicMaterial::adjustDoubleSidedFlag() +{ + bool doubleSided = isDoubleSided(); - // Make double sided if displaced since backfacing surfaces can become frontfacing. - if (isDisplaced()) doubleSided = true; + // Make double sided if diffuse or specular transmission is used. + // Note this convention will eventually change to allow single-sided transmissive materials. + if ((float)mData.diffuseTransmission > 0.f || (float)mData.specularTransmission > 0.f) + doubleSided = true; - setDoubleSided(doubleSided); - } + // Make double sided if displaced since backfacing surfaces can become frontfacing. + if (isDisplaced()) + doubleSided = true; - FALCOR_SCRIPT_BINDING(BasicMaterial) - { - FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) + setDoubleSided(doubleSided); +} - pybind11::class_> material(m, "BasicMaterial"); - material.def_property("baseColor", &BasicMaterial::getBaseColor, &BasicMaterial::setBaseColor); - material.def_property("specularParams", &BasicMaterial::getSpecularParams, &BasicMaterial::setSpecularParams); - material.def_property("transmissionColor", &BasicMaterial::getTransmissionColor, &BasicMaterial::setTransmissionColor); - material.def_property("diffuseTransmission", &BasicMaterial::getDiffuseTransmission, &BasicMaterial::setDiffuseTransmission); - material.def_property("specularTransmission", &BasicMaterial::getSpecularTransmission, &BasicMaterial::setSpecularTransmission); - material.def_property("volumeAbsorption", &BasicMaterial::getVolumeAbsorption, &BasicMaterial::setVolumeAbsorption); - material.def_property("volumeScattering", &BasicMaterial::getVolumeScattering, &BasicMaterial::setVolumeScattering); - material.def_property("volumeAnisotropy", &BasicMaterial::getVolumeAnisotropy, &BasicMaterial::setVolumeAnisotropy); - material.def_property("indexOfRefraction", &BasicMaterial::getIndexOfRefraction, &BasicMaterial::setIndexOfRefraction); - material.def_property("displacementScale", &BasicMaterial::getDisplacementScale, &BasicMaterial::setDisplacementScale); - material.def_property("displacementOffset", &BasicMaterial::getDisplacementOffset, &BasicMaterial::setDisplacementOffset); - } +FALCOR_SCRIPT_BINDING(BasicMaterial) +{ + FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) + + pybind11::class_> material(m, "BasicMaterial"); + material.def_property("baseColor", &BasicMaterial::getBaseColor, &BasicMaterial::setBaseColor); + material.def_property("specularParams", &BasicMaterial::getSpecularParams, &BasicMaterial::setSpecularParams); + material.def_property("transmissionColor", &BasicMaterial::getTransmissionColor, &BasicMaterial::setTransmissionColor); + material.def_property("diffuseTransmission", &BasicMaterial::getDiffuseTransmission, &BasicMaterial::setDiffuseTransmission); + material.def_property("specularTransmission", &BasicMaterial::getSpecularTransmission, &BasicMaterial::setSpecularTransmission); + material.def_property("volumeAbsorption", &BasicMaterial::getVolumeAbsorption, &BasicMaterial::setVolumeAbsorption); + material.def_property("volumeScattering", &BasicMaterial::getVolumeScattering, &BasicMaterial::setVolumeScattering); + material.def_property("volumeAnisotropy", &BasicMaterial::getVolumeAnisotropy, &BasicMaterial::setVolumeAnisotropy); + material.def_property("indexOfRefraction", &BasicMaterial::getIndexOfRefraction, &BasicMaterial::setIndexOfRefraction); + material.def_property("displacementScale", &BasicMaterial::getDisplacementScale, &BasicMaterial::setDisplacementScale); + material.def_property("displacementOffset", &BasicMaterial::getDisplacementOffset, &BasicMaterial::setDisplacementOffset); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/BasicMaterial.h b/Source/Falcor/Scene/Material/BasicMaterial.h index 2d78199834..1a19bad528 100644 --- a/Source/Falcor/Scene/Material/BasicMaterial.h +++ b/Source/Falcor/Scene/Material/BasicMaterial.h @@ -31,263 +31,264 @@ namespace Falcor { - /** Base class for basic non-layered materials. +/** Base class for basic non-layered materials. - Texture channel layout: + Texture channel layout: - Displacement - - RGB - Displacement data - - A - Unused + Displacement + - RGB - Displacement data + - A - Unused - See additional texture channels defined in derived classes. + See additional texture channels defined in derived classes. +*/ +class FALCOR_API BasicMaterial : public Material +{ + FALCOR_OBJECT(BasicMaterial) +public: + /** Render the UI. + \return True if the material was modified. */ - class FALCOR_API BasicMaterial : public Material - { - FALCOR_OBJECT(BasicMaterial) - public: - /** Render the UI. - \return True if the material was modified. - */ - virtual bool renderUI(Gui::Widgets& widget) override; - - /** Update material. This prepares the material for rendering. - \param[in] pOwner The material system that this material is used with. - \return Updates since last call to update(). - */ - Material::UpdateFlags update(MaterialSystem* pOwner) override; - - /** Returns true if the material has a displacement map. - */ - bool isDisplaced() const override; - - /** Compares material to another material. - \param[in] pOther Other material. - \return true if all materials properties *except* the name are identical. - */ - bool isEqual(const ref& pOther) const override; - - /** Set the alpha mode. - */ - void setAlphaMode(AlphaMode alphaMode) override; - - /** Set the alpha threshold. The threshold is only used when alpha mode != AlphaMode::Opaque. - */ - void setAlphaThreshold(float alphaThreshold) override; - - /** Set one of the available texture slots. - The call is ignored if the slot doesn't exist. - \param[in] slot The texture slot. - \param[in] pTexture The texture. - \return True if the texture slot was changed, false otherwise. - */ - bool setTexture(const TextureSlot slot, const ref& pTexture) override; - - /** Optimize texture usage for the given texture slot. - This function may replace constant textures by uniform material parameters etc. - \param[in] slot The texture slot. - \param[in] texInfo Information about the texture bound to this slot. - \param[out] stats Optimization stats passed back to the caller. - */ - void optimizeTexture(const TextureSlot slot, const TextureAnalyzer::Result& texInfo, TextureOptimizationStats& stats) override; - - /** Set the default texture sampler for the material. - */ - void setDefaultTextureSampler(const ref& pSampler) override; - - /** Get the default texture sampler for the material. - */ - ref getDefaultTextureSampler() const override { return mpDefaultSampler; } - - /** Get the material data blob for uploading to the GPU. - */ - virtual MaterialDataBlob getDataBlob() const override { return prepareDataBlob(mData); } - - - // Additional member functions for BasicMaterial - - /** Set the base color texture. - */ - void setBaseColorTexture(const ref& pBaseColor) { setTexture(TextureSlot::BaseColor, pBaseColor); } - - /** Get the base color texture. - */ - ref getBaseColorTexture() const { return getTexture(TextureSlot::BaseColor); } - - /** Set the specular texture. - */ - void setSpecularTexture(const ref& pSpecular) { setTexture(TextureSlot::Specular, pSpecular); } - - /** Get the specular texture. - */ - ref getSpecularTexture() const { return getTexture(TextureSlot::Specular); } - - /** Set the emissive texture. - */ - void setEmissiveTexture(const ref& pEmissive) { setTexture(TextureSlot::Emissive, pEmissive); } - - /** Get the emissive texture. - */ - ref getEmissiveTexture() const { return getTexture(TextureSlot::Emissive); } - - /** Set the specular transmission texture. - */ - void setTransmissionTexture(const ref& pTransmission) { setTexture(TextureSlot::Transmission, pTransmission); } - - /** Get the specular transmission texture. - */ - ref getTransmissionTexture() const { return getTexture(TextureSlot::Transmission); } - - /** Set the normal map. - */ - void setNormalMap(const ref& pNormalMap) { setTexture(TextureSlot::Normal, pNormalMap); } - - /** Get the normal map. - */ - ref getNormalMap() const { return getTexture(TextureSlot::Normal); } - - /** Set the displacement map. - */ - void setDisplacementMap(const ref& pDisplacementMap) { setTexture(TextureSlot::Displacement, pDisplacementMap); } - - /** Get the displacement map. - */ - ref getDisplacementMap() const { return getTexture(TextureSlot::Displacement); } - - /** Set the displacement scale. - */ - void setDisplacementScale(float scale); - - /** Get the displacement scale. - */ - float getDisplacementScale() const { return mData.displacementScale; } - - /** Set the displacement offset. - */ - void setDisplacementOffset(float offset); - - /** Get the displacement offset. - */ - float getDisplacementOffset() const { return mData.displacementOffset; } - - /** Set the base color. - */ - void setBaseColor(const float4& color); - void setBaseColor3(const float3& color) { setBaseColor(float4(color, getBaseColor().w)); } - - /** Get the base color. - */ - float4 getBaseColor() const { return (float4)mData.baseColor; } - float3 getBaseColor3() const { return getBaseColor().xyz(); } - - /** Set the specular parameters. - */ - void setSpecularParams(const float4& color); - - /** Get the specular parameters. - */ - float4 getSpecularParams() const { return (float4)mData.specular; } + virtual bool renderUI(Gui::Widgets& widget) override; + + /** Update material. This prepares the material for rendering. + \param[in] pOwner The material system that this material is used with. + \return Updates since last call to update(). + */ + Material::UpdateFlags update(MaterialSystem* pOwner) override; + + /** Returns true if the material has a displacement map. + */ + bool isDisplaced() const override; + + /** Compares material to another material. + \param[in] pOther Other material. + \return true if all materials properties *except* the name are identical. + */ + bool isEqual(const ref& pOther) const override; + + /** Set the alpha mode. + */ + void setAlphaMode(AlphaMode alphaMode) override; + + /** Set the alpha threshold. The threshold is only used when alpha mode != AlphaMode::Opaque. + */ + void setAlphaThreshold(float alphaThreshold) override; + + /** Set one of the available texture slots. + The call is ignored if the slot doesn't exist. + \param[in] slot The texture slot. + \param[in] pTexture The texture. + \return True if the texture slot was changed, false otherwise. + */ + bool setTexture(const TextureSlot slot, const ref& pTexture) override; + + /** Optimize texture usage for the given texture slot. + This function may replace constant textures by uniform material parameters etc. + \param[in] slot The texture slot. + \param[in] texInfo Information about the texture bound to this slot. + \param[out] stats Optimization stats passed back to the caller. + */ + void optimizeTexture(const TextureSlot slot, const TextureAnalyzer::Result& texInfo, TextureOptimizationStats& stats) override; + + /** Set the default texture sampler for the material. + */ + void setDefaultTextureSampler(const ref& pSampler) override; + + /** Get the default texture sampler for the material. + */ + ref getDefaultTextureSampler() const override { return mpDefaultSampler; } + + /** Get the material data blob for uploading to the GPU. + */ + virtual MaterialDataBlob getDataBlob() const override { return prepareDataBlob(mData); } + + // Additional member functions for BasicMaterial + + /** Set the base color texture. + */ + void setBaseColorTexture(const ref& pBaseColor) { setTexture(TextureSlot::BaseColor, pBaseColor); } + + /** Get the base color texture. + */ + ref getBaseColorTexture() const { return getTexture(TextureSlot::BaseColor); } + + /** Set the specular texture. + */ + void setSpecularTexture(const ref& pSpecular) { setTexture(TextureSlot::Specular, pSpecular); } + + /** Get the specular texture. + */ + ref getSpecularTexture() const { return getTexture(TextureSlot::Specular); } + + /** Set the emissive texture. + */ + void setEmissiveTexture(const ref& pEmissive) { setTexture(TextureSlot::Emissive, pEmissive); } + + /** Get the emissive texture. + */ + ref getEmissiveTexture() const { return getTexture(TextureSlot::Emissive); } + + /** Set the specular transmission texture. + */ + void setTransmissionTexture(const ref& pTransmission) { setTexture(TextureSlot::Transmission, pTransmission); } + + /** Get the specular transmission texture. + */ + ref getTransmissionTexture() const { return getTexture(TextureSlot::Transmission); } + + /** Set the normal map. + */ + void setNormalMap(const ref& pNormalMap) { setTexture(TextureSlot::Normal, pNormalMap); } - /** Set the transmission color. - */ - void setTransmissionColor(const float3& transmissionColor); + /** Get the normal map. + */ + ref getNormalMap() const { return getTexture(TextureSlot::Normal); } - /** Get the transmission color. - */ - float3 getTransmissionColor() const { return (float3)mData.transmission; } + /** Set the displacement map. + */ + void setDisplacementMap(const ref& pDisplacementMap) { setTexture(TextureSlot::Displacement, pDisplacementMap); } - /** Set the diffuse transmission. - */ - void setDiffuseTransmission(float diffuseTransmission); + /** Get the displacement map. + */ + ref getDisplacementMap() const { return getTexture(TextureSlot::Displacement); } - /** Get the diffuse transmission. - */ - float getDiffuseTransmission() const { return (float)mData.diffuseTransmission; } + /** Set the displacement scale. + */ + void setDisplacementScale(float scale); - /** Set the specular transmission. - */ - void setSpecularTransmission(float specularTransmission); - - /** Get the specular transmission. - */ - float getSpecularTransmission() const { return (float)mData.specularTransmission; } - - /** Set the volume absorption (absorption coefficient). - */ - void setVolumeAbsorption(const float3& volumeAbsorption); - - /** Get the volume absorption (absorption coefficient). - */ - float3 getVolumeAbsorption() const { return (float3)mData.volumeAbsorption; } - - /** Set the volume scattering (scattering coefficient). - */ - void setVolumeScattering(const float3& volumeScattering); - - /** Get the volume scattering (scattering coefficient). - */ - float3 getVolumeScattering() const { return (float3)mData.volumeScattering; } - - /** Set the volume phase function anisotropy (g). - */ - void setVolumeAnisotropy(float volumeAnisotropy); - - /** Get the volume phase function anisotropy (g). - */ - float getVolumeAnisotropy() const { return (float)mData.volumeAnisotropy; } - - /** Get the normal map type. - */ - NormalMapType getNormalMapType() const { return mData.getNormalMapType(); } - - /** Returns the material data struct. - */ - const BasicMaterialData& getData() const { return mData; } - - /** Comparison operator. - \return True if all materials properties *except* the name are identical. - */ - bool operator==(const BasicMaterial& other) const; + /** Get the displacement scale. + */ + float getDisplacementScale() const { return mData.displacementScale; } - protected: - BasicMaterial(ref pDevice, const std::string& name, MaterialType type); - - bool isAlphaSupported() const; - void prepareDisplacementMapForRendering(); - void adjustDoubleSidedFlag(); - void updateAlphaMode(); - void updateNormalMapType(); - void updateEmissiveFlag(); - virtual void updateDeltaSpecularFlag() {} - - virtual void renderSpecularUI(Gui::Widgets& widget) {} - virtual void setEmissiveColor(const float3& color) {} - - BasicMaterialData mData; ///< Material parameters. + /** Set the displacement offset. + */ + void setDisplacementOffset(float offset); - ref mpDefaultSampler; - ref mpDisplacementMinSampler; - ref mpDisplacementMaxSampler; + /** Get the displacement offset. + */ + float getDisplacementOffset() const { return mData.displacementOffset; } - // Additional data for texture usage. - float2 mAlphaRange = float2(0.f, 1.f); ///< Conservative range of opacity (alpha) values for the material. - bool mIsTexturedBaseColorConstant = false; ///< Flag indicating if the color channels of the base color texture are constant. - bool mIsTexturedAlphaConstant = false; ///< Flag indicating if the alpha channel of the base color texture is constant. - bool mDisplacementMapChanged = false; ///< Flag indicating of displacement map has changed. + /** Set the base color. + */ + void setBaseColor(const float4& color); + void setBaseColor3(const float3& color) { setBaseColor(float4(color, getBaseColor().w)); } - friend class SceneCache; - }; + /** Get the base color. + */ + float4 getBaseColor() const { return (float4)mData.baseColor; } + float3 getBaseColor3() const { return getBaseColor().xyz(); } - inline std::string to_string(ShadingModel model) + /** Set the specular parameters. + */ + void setSpecularParams(const float4& color); + + /** Get the specular parameters. + */ + float4 getSpecularParams() const { return (float4)mData.specular; } + + /** Set the transmission color. + */ + void setTransmissionColor(const float3& transmissionColor); + + /** Get the transmission color. + */ + float3 getTransmissionColor() const { return (float3)mData.transmission; } + + /** Set the diffuse transmission. + */ + void setDiffuseTransmission(float diffuseTransmission); + + /** Get the diffuse transmission. + */ + float getDiffuseTransmission() const { return (float)mData.diffuseTransmission; } + + /** Set the specular transmission. + */ + void setSpecularTransmission(float specularTransmission); + + /** Get the specular transmission. + */ + float getSpecularTransmission() const { return (float)mData.specularTransmission; } + + /** Set the volume absorption (absorption coefficient). + */ + void setVolumeAbsorption(const float3& volumeAbsorption); + + /** Get the volume absorption (absorption coefficient). + */ + float3 getVolumeAbsorption() const { return (float3)mData.volumeAbsorption; } + + /** Set the volume scattering (scattering coefficient). + */ + void setVolumeScattering(const float3& volumeScattering); + + /** Get the volume scattering (scattering coefficient). + */ + float3 getVolumeScattering() const { return (float3)mData.volumeScattering; } + + /** Set the volume phase function anisotropy (g). + */ + void setVolumeAnisotropy(float volumeAnisotropy); + + /** Get the volume phase function anisotropy (g). + */ + float getVolumeAnisotropy() const { return (float)mData.volumeAnisotropy; } + + /** Get the normal map type. + */ + NormalMapType getNormalMapType() const { return mData.getNormalMapType(); } + + /** Returns the material data struct. + */ + const BasicMaterialData& getData() const { return mData; } + + /** Comparison operator. + \return True if all materials properties *except* the name are identical. + */ + bool operator==(const BasicMaterial& other) const; + +protected: + BasicMaterial(ref pDevice, const std::string& name, MaterialType type); + + bool isAlphaSupported() const; + void prepareDisplacementMapForRendering(); + void adjustDoubleSidedFlag(); + void updateAlphaMode(); + void updateNormalMapType(); + void updateEmissiveFlag(); + virtual void updateDeltaSpecularFlag() {} + + virtual void renderSpecularUI(Gui::Widgets& widget) {} + virtual void setEmissiveColor(const float3& color) {} + + BasicMaterialData mData; ///< Material parameters. + + ref mpDefaultSampler; + ref mpDisplacementMinSampler; + ref mpDisplacementMaxSampler; + + // Additional data for texture usage. + float2 mAlphaRange = float2(0.f, 1.f); ///< Conservative range of opacity (alpha) values for the material. + bool mIsTexturedBaseColorConstant = false; ///< Flag indicating if the color channels of the base color texture are constant. + bool mIsTexturedAlphaConstant = false; ///< Flag indicating if the alpha channel of the base color texture is constant. + bool mDisplacementMapChanged = false; ///< Flag indicating of displacement map has changed. + + friend class SceneCache; +}; + +inline std::string to_string(ShadingModel model) +{ + switch (model) { - switch (model) - { -#define tostr(t_) case ShadingModel::t_: return #t_; - tostr(MetalRough); - tostr(SpecGloss); +#define tostr(t_) \ + case ShadingModel::t_: \ + return #t_; + tostr(MetalRough); + tostr(SpecGloss); #undef tostr - default: - FALCOR_UNREACHABLE(); - return ""; - } + default: + FALCOR_UNREACHABLE(); + return ""; } } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/BasicMaterialData.slang b/Source/Falcor/Scene/Material/BasicMaterialData.slang index 0e4a9d904a..bf3e01004d 100644 --- a/Source/Falcor/Scene/Material/BasicMaterialData.slang +++ b/Source/Falcor/Scene/Material/BasicMaterialData.slang @@ -48,24 +48,25 @@ BEGIN_NAMESPACE_FALCOR struct BasicMaterialData { // MaterialHeader (16B) is stored just before this struct in memory. - uint flags = 0; ///< Material flags and packed sampler IDs. See accessors below. - float emissiveFactor = 1.f; ///< Multiplication factor for the emissive color to control light intensity. Range [0,inf). + uint flags = 0; ///< Material flags and packed sampler IDs. See accessors below. + float emissiveFactor = 1.f; ///< Multiplication factor for the emissive color to control light intensity. Range [0,inf). - float16_t4 baseColor = float16_t4(1.h); ///< Material base color (RGB) and opacity (A). - float16_t4 specular = float16_t4(0.h); ///< Material specular channel encoding occlusion (R), roughness (G), metallic (B) in the default MetalRough mode. In SpecGloss mode specular color (RGB) and glossiness (A). + float16_t4 baseColor = float16_t4(1.h); ///< Material base color (RGB) and opacity (A). + float16_t4 specular = float16_t4(0.h); ///< Material specular channel encoding occlusion (R), roughness (G), metallic (B) in the default + ///< MetalRough mode. In SpecGloss mode specular color (RGB) and glossiness (A). - float3 emissive = float3(0.f); ///< Emissive color (RGB). - float16_t specularTransmission = 0.h; ///< Specular transmission. Range [0,1]. - float16_t3 transmission = float16_t3(1.h); ///< Transmission color. - float16_t diffuseTransmission = 0.h; ///< Diffuse transmission. Range [0,1]. + float3 emissive = float3(0.f); ///< Emissive color (RGB). + float16_t specularTransmission = 0.h; ///< Specular transmission. Range [0,1]. + float16_t3 transmission = float16_t3(1.h); ///< Transmission color. + float16_t diffuseTransmission = 0.h; ///< Diffuse transmission. Range [0,1]. - float16_t3 volumeScattering = float16_t3(0.h); ///< Volume scattering coefficient. Range [0,inf). - float16_t _pad0 = 0.h; - float16_t3 volumeAbsorption = float16_t3(0.h); ///< Volume absorption coefficient. Range [0,inf). - float16_t volumeAnisotropy = 0.h; ///< Volume phase function anisotropy (g). Range [-1,1]. + float16_t3 volumeScattering = float16_t3(0.h); ///< Volume scattering coefficient. Range [0,inf). + float16_t _pad0 = 0.h; + float16_t3 volumeAbsorption = float16_t3(0.h); ///< Volume absorption coefficient. Range [0,inf). + float16_t volumeAnisotropy = 0.h; ///< Volume phase function anisotropy (g). Range [-1,1]. - float displacementScale = 0.f; ///< Displacement scale value. - float displacementOffset = 0.f; ///< Displacement offset value. + float displacementScale = 0.f; ///< Displacement scale value. + float displacementOffset = 0.f; ///< Displacement offset value. // Texture handles (4B each). TextureHandle texBaseColor; @@ -88,31 +89,46 @@ struct BasicMaterialData static constexpr uint kTotalFlagsBits = kMaxSamplerIDOffset + MaterialHeader::kSamplerIDBits; /** Set shading model. This is only used for the standard material. - */ + */ SETTER_DECL void setShadingModel(ShadingModel model) { flags = PACK_BITS(kShadingModelBits, kShadingModelOffset, flags, (uint)model); } /** Get shading model. This is only used for the standard material. - */ + */ ShadingModel getShadingModel() CONST_FUNCTION { return ShadingModel(EXTRACT_BITS(kShadingModelBits, kShadingModelOffset, flags)); } /** Set normal map type. - */ - SETTER_DECL void setNormalMapType(NormalMapType type) { flags = PACK_BITS(kNormalMapTypeBits, kNormalMapTypeOffset, flags, (uint)type); } + */ + SETTER_DECL void setNormalMapType(NormalMapType type) + { + flags = PACK_BITS(kNormalMapTypeBits, kNormalMapTypeOffset, flags, (uint)type); + } /** Get normal map type. - */ + */ NormalMapType getNormalMapType() CONST_FUNCTION { return NormalMapType(EXTRACT_BITS(kNormalMapTypeBits, kNormalMapTypeOffset, flags)); } - SETTER_DECL void setHasEntryPointVolumeProperties(bool poe) { flags = PACK_BITS(kEntryPointVolumePropertiesBits, kEntryPointVolumePropertiesOffset, flags, (uint)poe); } - bool getHasEntryPointVolumeProperties() CONST_FUNCTION { return bool(EXTRACT_BITS(kEntryPointVolumePropertiesBits, kEntryPointVolumePropertiesOffset, flags)); } + SETTER_DECL void setHasEntryPointVolumeProperties(bool poe) + { + flags = PACK_BITS(kEntryPointVolumePropertiesBits, kEntryPointVolumePropertiesOffset, flags, (uint)poe); + } + bool getHasEntryPointVolumeProperties() CONST_FUNCTION + { + return bool(EXTRACT_BITS(kEntryPointVolumePropertiesBits, kEntryPointVolumePropertiesOffset, flags)); + } /** Set displacement map min/max sampler ID. - */ - SETTER_DECL void setDisplacementMinSamplerID(uint samplerID) { flags = PACK_BITS(MaterialHeader::kSamplerIDBits, kMinSamplerIDOffset, flags, samplerID); } - SETTER_DECL void setDisplacementMaxSamplerID(uint samplerID) { flags = PACK_BITS(MaterialHeader::kSamplerIDBits, kMaxSamplerIDOffset, flags, samplerID); } + */ + SETTER_DECL void setDisplacementMinSamplerID(uint samplerID) + { + flags = PACK_BITS(MaterialHeader::kSamplerIDBits, kMinSamplerIDOffset, flags, samplerID); + } + SETTER_DECL void setDisplacementMaxSamplerID(uint samplerID) + { + flags = PACK_BITS(MaterialHeader::kSamplerIDBits, kMaxSamplerIDOffset, flags, samplerID); + } /** Get displacement map min/max sampler ID. - */ + */ uint getDisplacementMinSamplerID() CONST_FUNCTION { return EXTRACT_BITS(MaterialHeader::kSamplerIDBits, kMinSamplerIDOffset, flags); } uint getDisplacementMaxSamplerID() CONST_FUNCTION { return EXTRACT_BITS(MaterialHeader::kSamplerIDBits, kMaxSamplerIDOffset, flags); } }; diff --git a/Source/Falcor/Scene/Material/ClothMaterial.cpp b/Source/Falcor/Scene/Material/ClothMaterial.cpp index d0e885feb1..ad935e40de 100644 --- a/Source/Falcor/Scene/Material/ClothMaterial.cpp +++ b/Source/Falcor/Scene/Material/ClothMaterial.cpp @@ -31,58 +31,55 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/ClothMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/ClothMaterial.slang"; +} - ClothMaterial::ClothMaterial(ref pDevice, const std::string& name) - : BasicMaterial(pDevice, name, MaterialType::Cloth) - { - // Setup additional texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = { "baseColor", TextureChannelFlags::RGBA, true }; - mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = { "specular", TextureChannelFlags::Green, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - } +ClothMaterial::ClothMaterial(ref pDevice, const std::string& name) : BasicMaterial(pDevice, name, MaterialType::Cloth) +{ + // Setup additional texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = {"baseColor", TextureChannelFlags::RGBA, true}; + mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = {"specular", TextureChannelFlags::Green, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; +} - ProgramDesc::ShaderModuleList ClothMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +ProgramDesc::ShaderModuleList ClothMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - TypeConformanceList ClothMaterial::getTypeConformances() const - { - return { {{"ClothMaterial", "IMaterial"}, (uint32_t)MaterialType::Cloth} }; - } +TypeConformanceList ClothMaterial::getTypeConformances() const +{ + return {{{"ClothMaterial", "IMaterial"}, (uint32_t)MaterialType::Cloth}}; +} - void ClothMaterial::renderSpecularUI(Gui::Widgets& widget) - { - float roughness = getRoughness(); - if (widget.var("Roughness", roughness, 0.f, 1.f, 0.01f)) setRoughness(roughness); - } +void ClothMaterial::renderSpecularUI(Gui::Widgets& widget) +{ + float roughness = getRoughness(); + if (widget.var("Roughness", roughness, 0.f, 1.f, 0.01f)) + setRoughness(roughness); +} - void ClothMaterial::setRoughness(float roughness) +void ClothMaterial::setRoughness(float roughness) +{ + if (mData.specular[1] != (float16_t)roughness) { - if (mData.specular[1] != (float16_t)roughness) - { - mData.specular[1] = (float16_t)roughness; - markUpdates(UpdateFlags::DataChanged); - } + mData.specular[1] = (float16_t)roughness; + markUpdates(UpdateFlags::DataChanged); } +} - FALCOR_SCRIPT_BINDING(ClothMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(ClothMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - pybind11::class_> material(m, "ClothMaterial"); - auto create = [] (const std::string& name) - { - return ClothMaterial::create(accessActivePythonSceneBuilder().getDevice(), name); - }; - material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED + pybind11::class_> material(m, "ClothMaterial"); + auto create = [](const std::string& name) { return ClothMaterial::create(accessActivePythonSceneBuilderDevice(), name); }; + material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED - material.def_property("roughness", &ClothMaterial::getRoughness, &ClothMaterial::setRoughness); - } + material.def_property("roughness", &ClothMaterial::getRoughness, &ClothMaterial::setRoughness); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/ClothMaterial.h b/Source/Falcor/Scene/Material/ClothMaterial.h index 8ee4bc39ad..3387fb3ad8 100644 --- a/Source/Falcor/Scene/Material/ClothMaterial.h +++ b/Source/Falcor/Scene/Material/ClothMaterial.h @@ -30,43 +30,43 @@ namespace Falcor { - /** Class representing a cloth material. +/** Class representing a cloth material. - Texture channel layout: + Texture channel layout: - BaseColor - - RGB - Base Color - - A - Opacity - Specular - - R - Unused - - G - Roughness - - B - Unused - - A - Unused - Normal - - 3-Channel standard normal map, or 2-Channel BC5 format + BaseColor + - RGB - Base Color + - A - Opacity + Specular + - R - Unused + - G - Roughness + - B - Unused + - A - Unused + Normal + - 3-Channel standard normal map, or 2-Channel BC5 format - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API ClothMaterial : public BasicMaterial - { - FALCOR_OBJECT(ClothMaterial) - public: - static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); }; + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API ClothMaterial : public BasicMaterial +{ + FALCOR_OBJECT(ClothMaterial) +public: + static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); }; - ClothMaterial(ref pDevice, const std::string& name); + ClothMaterial(ref pDevice, const std::string& name); - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - /** Set the roughness. - */ - void setRoughness(float roughness); + /** Set the roughness. + */ + void setRoughness(float roughness); - /** Get the roughness. - */ - float getRoughness() const { return (float)mData.specular[1]; } + /** Get the roughness. + */ + float getRoughness() const { return (float)mData.specular[1]; } - protected: - void renderSpecularUI(Gui::Widgets& widget) override; - }; -} +protected: + void renderSpecularUI(Gui::Widgets& widget) override; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/DiffuseSpecularData.slang b/Source/Falcor/Scene/Material/DiffuseSpecularData.slang index 73a5f88126..6f8b32630b 100644 --- a/Source/Falcor/Scene/Material/DiffuseSpecularData.slang +++ b/Source/Falcor/Scene/Material/DiffuseSpecularData.slang @@ -35,26 +35,28 @@ BEGIN_NAMESPACE_FALCOR */ struct DiffuseSpecularData { - float3 baseColor; ///< Base color in linear space. - float roughness; ///< Linearly perceptual roughness. The specular lobe uses the squared roughness. + float3 baseColor; ///< Base color in linear space. + float roughness; ///< Linearly perceptual roughness. The specular lobe uses the squared roughness. float specular; float metallic; - float lossValue; ///< Loss value when fitted to a target BRDF. Not used for rendering. + float lossValue; ///< Loss value when fitted to a target BRDF. Not used for rendering. #ifdef HOST_CODE - bool operator== (const DiffuseSpecularData& other) const + bool operator ==(const DiffuseSpecularData& other) const { - if (any(baseColor != other.baseColor)) return false; - if (roughness != other.roughness) return false; - if (specular != other.specular) return false; - if (metallic != other.metallic) return false; - if (lossValue != other.lossValue) return false; + if (any(baseColor != other.baseColor)) + return false; + if (roughness != other.roughness) + return false; + if (specular != other.specular) + return false; + if (metallic != other.metallic) + return false; + if (lossValue != other.lossValue) + return false; return true; } - bool operator!= (const DiffuseSpecularData& other) const - { - return !(*this == other); - } + bool operator !=(const DiffuseSpecularData& other) const { return !(*this == other); } #endif }; diff --git a/Source/Falcor/Scene/Material/DiffuseSpecularUtils.cpp b/Source/Falcor/Scene/Material/DiffuseSpecularUtils.cpp index 56aaa32784..698104aa9a 100644 --- a/Source/Falcor/Scene/Material/DiffuseSpecularUtils.cpp +++ b/Source/Falcor/Scene/Material/DiffuseSpecularUtils.cpp @@ -36,70 +36,70 @@ using json = nlohmann::json; namespace Falcor { - namespace +namespace +{ +bool get_to(const json& j, float3& v) +{ + if (!j.is_array()) + return false; + if (j.size() != v.length()) + return false; + j[0].get_to(v.x); + j[1].get_to(v.y); + j[2].get_to(v.z); + return true; +} +} // namespace + +bool DiffuseSpecularUtils::loadJSONData(const std::filesystem::path& path, DiffuseSpecularData& data) +{ + // Set default parameters for json data is not available. + // The default is a mix between diffuse and specular with medium roughness. + data = {}; + data.baseColor = float3(0.5f); + data.roughness = 0.5f; + data.metallic = 0.5f; + + // Try loading data from json file. + std::ifstream ifs(path); + if (!ifs.good()) { - bool get_to(const json& j, float3& v) - { - if (!j.is_array()) - return false; - if (j.size() != v.length()) - return false; - j[0].get_to(v.x); - j[1].get_to(v.y); - j[2].get_to(v.z); - return true; - } + logWarning("DiffuseSpecularUtils: Failed to open file '{}' for reading.", path); + return false; } - bool DiffuseSpecularUtils::loadJSONData(const std::filesystem::path& path, DiffuseSpecularData& data) + try { - // Set default parameters for json data is not available. - // The default is a mix between diffuse and specular with medium roughness. - data = {}; - data.baseColor = float3(0.5f); - data.roughness = 0.5f; - data.metallic = 0.5f; - - // Try loading data from json file. - std::ifstream ifs(path); - if (!ifs.good()) - { - logWarning("DiffuseSpecularUtils: Failed to open file '{}' for reading.", path); - return false; - } - - try - { - json doc = json::parse(ifs); + json doc = json::parse(ifs); - DiffuseSpecularData d = {}; - float3 baseColorSRGB = {}; - get_to(doc["base_color_srgb"], baseColorSRGB); - d.baseColor = sRGBToLinear(baseColorSRGB); - d.roughness = doc["roughness"]; - d.specular = doc["specular"]; - d.metallic = doc["metallic"]; - d.lossValue = doc["loss_value"]; + DiffuseSpecularData d = {}; + float3 baseColorSRGB = {}; + get_to(doc["base_color_srgb"], baseColorSRGB); + d.baseColor = sRGBToLinear(baseColorSRGB); + d.roughness = doc["roughness"]; + d.specular = doc["specular"]; + d.metallic = doc["metallic"]; + d.lossValue = doc["loss_value"]; - data = d; - } - catch (const json::exception& e) - { - logWarning("DiffuseSpecularUtils: Error ({}) when parsing file '{}'.", std::string(e.what()), path); - return false; - } - - return true; + data = d; } - - bool DiffuseSpecularUtils::renderUI(Gui::Widgets& widget, DiffuseSpecularData& data) + catch (const json::exception& e) { - DiffuseSpecularData prevData = data; - widget.rgbColor("baseColor", data.baseColor); - widget.var("roughness", data.roughness, 0.f, 1.f); - widget.var("metallic", data.metallic, 0.f, 1.f); - widget.var("specular", data.specular, 0.f, 1.f); - - return data != prevData; + logWarning("DiffuseSpecularUtils: Error ({}) when parsing file '{}'.", std::string(e.what()), path); + return false; } + + return true; +} + +bool DiffuseSpecularUtils::renderUI(Gui::Widgets& widget, DiffuseSpecularData& data) +{ + DiffuseSpecularData prevData = data; + widget.rgbColor("baseColor", data.baseColor); + widget.var("roughness", data.roughness, 0.f, 1.f); + widget.var("metallic", data.metallic, 0.f, 1.f); + widget.var("specular", data.specular, 0.f, 1.f); + + return data != prevData; } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/DiffuseSpecularUtils.h b/Source/Falcor/Scene/Material/DiffuseSpecularUtils.h index c3c4cd5472..dc45172c3e 100644 --- a/Source/Falcor/Scene/Material/DiffuseSpecularUtils.h +++ b/Source/Falcor/Scene/Material/DiffuseSpecularUtils.h @@ -32,12 +32,12 @@ namespace Falcor { - struct DiffuseSpecularData; +struct DiffuseSpecularData; - class DiffuseSpecularUtils - { - public: - static bool loadJSONData(const std::filesystem::path& path, DiffuseSpecularData& data); - static bool renderUI(Gui::Widgets& widget, DiffuseSpecularData& data); - }; -} +class DiffuseSpecularUtils +{ +public: + static bool loadJSONData(const std::filesystem::path& path, DiffuseSpecularData& data); + static bool renderUI(Gui::Widgets& widget, DiffuseSpecularData& data); +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/HairMaterial.cpp b/Source/Falcor/Scene/Material/HairMaterial.cpp index b83d261c32..c57f246476 100644 --- a/Source/Falcor/Scene/Material/HairMaterial.cpp +++ b/Source/Falcor/Scene/Material/HairMaterial.cpp @@ -31,59 +31,57 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/HairMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/HairMaterial.slang"; +} - HairMaterial::HairMaterial(ref pDevice, const std::string& name) - : BasicMaterial(pDevice, name, MaterialType::Hair) - { - // Setup additional texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = { "baseColor", TextureChannelFlags::RGB, true }; // Note: No alpha support - mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = { "specular", TextureChannelFlags::RGB, false }; - } +HairMaterial::HairMaterial(ref pDevice, const std::string& name) : BasicMaterial(pDevice, name, MaterialType::Hair) +{ + // Setup additional texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = {"baseColor", TextureChannelFlags::RGB, true}; // Note: No alpha support + mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = {"specular", TextureChannelFlags::RGB, false}; +} - ProgramDesc::ShaderModuleList HairMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +ProgramDesc::ShaderModuleList HairMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - TypeConformanceList HairMaterial::getTypeConformances() const - { - return { {{"HairMaterial", "IMaterial"}, (uint32_t)MaterialType::Hair} }; - } +TypeConformanceList HairMaterial::getTypeConformances() const +{ + return {{{"HairMaterial", "IMaterial"}, (uint32_t)MaterialType::Hair}}; +} - float3 HairMaterial::sigmaAFromConcentration(float ce, float cp) - { - float3 eumelaninSigmaA(0.419f, 0.697f, 1.37f); - float3 pheomelaninSigmaA(0.187f, 0.4f, 1.05f); - return ce * eumelaninSigmaA + cp * pheomelaninSigmaA; - } +float3 HairMaterial::sigmaAFromConcentration(float ce, float cp) +{ + float3 eumelaninSigmaA(0.419f, 0.697f, 1.37f); + float3 pheomelaninSigmaA(0.187f, 0.4f, 1.05f); + return ce * eumelaninSigmaA + cp * pheomelaninSigmaA; +} - float3 HairMaterial::sigmaAFromColor(float3 color, float betaN) - { - const float tmp = 5.969f - 0.215f * betaN + 2.532f * betaN * betaN - 10.73f * std::pow(betaN, 3) + 5.574f * std::pow(betaN, 4) + 0.245f * std::pow(betaN, 5); - float3 sqrtSigmaA = log(max(color, float3(1e-4f))) / tmp; - return sqrtSigmaA * sqrtSigmaA; - } +float3 HairMaterial::sigmaAFromColor(float3 color, float betaN) +{ + const float tmp = 5.969f - 0.215f * betaN + 2.532f * betaN * betaN - 10.73f * std::pow(betaN, 3) + 5.574f * std::pow(betaN, 4) + + 0.245f * std::pow(betaN, 5); + float3 sqrtSigmaA = log(max(color, float3(1e-4f))) / tmp; + return sqrtSigmaA * sqrtSigmaA; +} - float3 HairMaterial::colorFromSigmaA(float3 sigmaA, float betaN) - { - const float tmp = 5.969f - 0.215f * betaN + 2.532f * betaN * betaN - 10.73f * std::pow(betaN, 3) + 5.574f * std::pow(betaN, 4) + 0.245f * std::pow(betaN, 5); - return exp(sqrt(sigmaA) * tmp); - } +float3 HairMaterial::colorFromSigmaA(float3 sigmaA, float betaN) +{ + const float tmp = 5.969f - 0.215f * betaN + 2.532f * betaN * betaN - 10.73f * std::pow(betaN, 3) + 5.574f * std::pow(betaN, 4) + + 0.245f * std::pow(betaN, 5); + return exp(sqrt(sigmaA) * tmp); +} - FALCOR_SCRIPT_BINDING(HairMaterial) - { - using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) +FALCOR_SCRIPT_BINDING(HairMaterial) +{ + using namespace pybind11::literals; + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - pybind11::class_> material(m, "HairMaterial"); - auto create = [] (const std::string& name) - { - return HairMaterial::create(accessActivePythonSceneBuilder().getDevice(), name); - }; - material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED - } + pybind11::class_> material(m, "HairMaterial"); + auto create = [](const std::string& name) { return HairMaterial::create(accessActivePythonSceneBuilderDevice(), name); }; + material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/HairMaterial.h b/Source/Falcor/Scene/Material/HairMaterial.h index 4b22d0148e..688199d6ca 100644 --- a/Source/Falcor/Scene/Material/HairMaterial.h +++ b/Source/Falcor/Scene/Material/HairMaterial.h @@ -30,42 +30,42 @@ namespace Falcor { - /** Class representing a hair material. +/** Class representing a hair material. - Texture channel layout: + Texture channel layout: - BaseColor - - RGB - Absorption coefficient, sigmaA - - A - Opacity - Specular - - R - Longitudinal roughness, betaM - - G - Azimuthal roughness, betaN - - B - The angle that the small scales on the surface of hair are offset from the base cylinder (in degrees). - - A - Unused + BaseColor + - RGB - Absorption coefficient, sigmaA + - A - Opacity + Specular + - R - Longitudinal roughness, betaM + - G - Azimuthal roughness, betaN + - B - The angle that the small scales on the surface of hair are offset from the base cylinder (in degrees). + - A - Unused - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API HairMaterial : public BasicMaterial - { - FALCOR_OBJECT(HairMaterial) - public: - static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); }; + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API HairMaterial : public BasicMaterial +{ + FALCOR_OBJECT(HairMaterial) +public: + static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); }; - HairMaterial(ref pDevice, const std::string& name); + HairMaterial(ref pDevice, const std::string& name); - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - /** Compute sigmaA from eumelanin and pheomelanin concentration. - */ - static float3 sigmaAFromConcentration(float ce, float cp); + /** Compute sigmaA from eumelanin and pheomelanin concentration. + */ + static float3 sigmaAFromConcentration(float ce, float cp); - /** Compute sigmaA from RGB color. - */ - static float3 sigmaAFromColor(float3 color, float betaN); + /** Compute sigmaA from RGB color. + */ + static float3 sigmaAFromColor(float3 color, float betaN); - /** Compute RGB color from sigmaA (inverse of sigmaAFromColor). - */ - static float3 colorFromSigmaA(float3 sigmaA, float betaN); - }; -} + /** Compute RGB color from sigmaA (inverse of sigmaAFromColor). + */ + static float3 colorFromSigmaA(float3 sigmaA, float betaN); +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MERLFile.cpp b/Source/Falcor/Scene/Material/MERLFile.cpp index d1ec7b59c0..6561d43b67 100644 --- a/Source/Falcor/Scene/Material/MERLFile.cpp +++ b/Source/Falcor/Scene/Material/MERLFile.cpp @@ -35,181 +35,190 @@ namespace Falcor { - namespace - { - // Angular sampling resolution of the measured data. - const size_t kBRDFSamplingResThetaH = 90; - const size_t kBRDFSamplingResThetaD = 90; - const size_t kBRDFSamplingResPhiD = 360; - - // Scale factors for the RGB channels of the measured data. - const double kRedScale = 1.0 / 1500.0; - const double kGreenScale = 1.15 / 1500.0; - const double kBlueScale = 1.66 / 1500.0; - - const uint32_t kAlbedoLUTSize = MERLMaterialData::kAlbedoLUTSize; - } - - MERLFile::MERLFile(const std::filesystem::path& path) - { - if (!loadBRDF(path)) - FALCOR_THROW("Failed to load MERL BRDF from '{}'", path); - } - - bool MERLFile::loadBRDF(const std::filesystem::path& path) - { - mDesc = {}; - mData.clear(); - mAlbedoLUT.clear(); - - std::ifstream ifs(path, std::ios_base::in | std::ios_base::binary); - if (!ifs.good()) - { - logWarning("MERLFile: Failed to open file '{}'.", path); - return false; - } +namespace +{ +// Angular sampling resolution of the measured data. +const size_t kBRDFSamplingResThetaH = 90; +const size_t kBRDFSamplingResThetaD = 90; +const size_t kBRDFSamplingResPhiD = 360; - // Load header. - int dims[3] = {}; - ifs.read(reinterpret_cast(dims), sizeof(int) * 3); +// Scale factors for the RGB channels of the measured data. +const double kRedScale = 1.0 / 1500.0; +const double kGreenScale = 1.15 / 1500.0; +const double kBlueScale = 1.66 / 1500.0; - size_t n = (size_t)dims[0] * dims[1] * dims[2]; - if (n != kBRDFSamplingResThetaH * kBRDFSamplingResThetaD * kBRDFSamplingResPhiD / 2) - { - logWarning("MERLFile: Dimensions don't match in file '{}'.", path); - return false; - } +const uint32_t kAlbedoLUTSize = MERLMaterialData::kAlbedoLUTSize; +} // namespace - // Load BRDF data. - std::vector data(3 * n); - ifs.read(reinterpret_cast(data.data()), sizeof(double) * 3 * n); - if (!ifs.good()) - { - logWarning("MERLFile: Failed to load BRDF data from file '{}'.", path); - return false; - } +MERLFile::MERLFile(const std::filesystem::path& path) +{ + if (!loadBRDF(path)) + FALCOR_THROW("Failed to load MERL BRDF from '{}'", path); +} - mDesc.path = path; - mDesc.name = path.stem().string(); +bool MERLFile::loadBRDF(const std::filesystem::path& path) +{ + mDesc = {}; + mData.clear(); + mAlbedoLUT.clear(); - prepareData(dims, data); + std::ifstream ifs(path, std::ios_base::in | std::ios_base::binary); + if (!ifs.good()) + { + logWarning("MERLFile: Failed to open file '{}'.", path); + return false; + } - // Load JSON sidecar file if it exists. - const auto jsonPath = std::filesystem::path(path).replace_extension("json"); - if (!DiffuseSpecularUtils::loadJSONData(jsonPath, mDesc.extraData)) - logWarning("MERLFile: Failed to load associated JSON data for BRDF '{}'.", mDesc.name); + // Load header. + int dims[3] = {}; + ifs.read(reinterpret_cast(dims), sizeof(int) * 3); - logInfo("Loaded MERL BRDF '{}'.", mDesc.name); - return true; + size_t n = (size_t)dims[0] * dims[1] * dims[2]; + if (n != kBRDFSamplingResThetaH * kBRDFSamplingResThetaD * kBRDFSamplingResPhiD / 2) + { + logWarning("MERLFile: Dimensions don't match in file '{}'.", path); + return false; } - void MERLFile::prepareData(const int dims[3], const std::vector& data) + // Load BRDF data. + std::vector data(3 * n); + ifs.read(reinterpret_cast(data.data()), sizeof(double) * 3 * n); + if (!ifs.good()) { - // Convert BRDF samples to fp32 precision and interleave RGB channels. - const size_t n = (size_t)dims[0] * dims[1] * dims[2]; + logWarning("MERLFile: Failed to load BRDF data from file '{}'.", path); + return false; + } - FALCOR_ASSERT(data.size() == 3 * n); - mData.resize(n); + mDesc.path = path; + mDesc.name = path.stem().string(); - size_t negCount = 0; - size_t infCount = 0; - size_t nanCount = 0; + prepareData(dims, data); - for (size_t i = 0; i < n; i++) - { - float3& v = mData[i]; + // Load JSON sidecar file if it exists. + const auto jsonPath = std::filesystem::path(path).replace_extension("json"); + if (!DiffuseSpecularUtils::loadJSONData(jsonPath, mDesc.extraData)) + logWarning("MERLFile: Failed to load associated JSON data for BRDF '{}'.", mDesc.name); - // Extract RGB and apply scaling. - v.x = static_cast(data[i] * kRedScale); - v.y = static_cast(data[i + n] * kGreenScale); - v.z = static_cast(data[i + 2 * n] * kBlueScale); + logInfo("Loaded MERL BRDF '{}'.", mDesc.name); + return true; +} - // Validate data point and set to zero if invalid. - bool isNeg = v.x < 0.f || v.y < 0.f || v.z < 0.f; - bool isInf = std::isinf(v.x) || std::isinf(v.y) || std::isinf(v.z); - bool isNaN = std::isnan(v.x) || std::isnan(v.y) || std::isnan(v.z); +void MERLFile::prepareData(const int dims[3], const std::vector& data) +{ + // Convert BRDF samples to fp32 precision and interleave RGB channels. + const size_t n = (size_t)dims[0] * dims[1] * dims[2]; - if (isNeg) negCount++; - if (isInf) infCount++; - if (isNaN) nanCount++; + FALCOR_ASSERT(data.size() == 3 * n); + mData.resize(n); - if (isInf || isNaN) v = float3(0.f); - else if (isNeg) v = max(v, float3(0.f)); - } + size_t negCount = 0; + size_t infCount = 0; + size_t nanCount = 0; - if (negCount > 0) logWarning("MERL BRDF {} has {} samples with negative values. Clamped to zero.", mDesc.name, negCount); - if (infCount > 0) logWarning("MERL BRDF {} has {} samples with inf values. Sample set to zero.", mDesc.name, infCount); - if (nanCount > 0) logWarning("MERL BRDF {} has {} samples with NaN values. Sample set to zero.", mDesc.name, nanCount); + for (size_t i = 0; i < n; i++) + { + float3& v = mData[i]; + + // Extract RGB and apply scaling. + v.x = static_cast(data[i] * kRedScale); + v.y = static_cast(data[i + n] * kGreenScale); + v.z = static_cast(data[i + 2 * n] * kBlueScale); + + // Validate data point and set to zero if invalid. + bool isNeg = v.x < 0.f || v.y < 0.f || v.z < 0.f; + bool isInf = std::isinf(v.x) || std::isinf(v.y) || std::isinf(v.z); + bool isNaN = std::isnan(v.x) || std::isnan(v.y) || std::isnan(v.z); + + if (isNeg) + negCount++; + if (isInf) + infCount++; + if (isNaN) + nanCount++; + + if (isInf || isNaN) + v = float3(0.f); + else if (isNeg) + v = max(v, float3(0.f)); } - const std::vector& MERLFile::prepareAlbedoLUT(ref pDevice) - { - if (!mAlbedoLUT.empty()) - return mAlbedoLUT; + if (negCount > 0) + logWarning("MERL BRDF {} has {} samples with negative values. Clamped to zero.", mDesc.name, negCount); + if (infCount > 0) + logWarning("MERL BRDF {} has {} samples with inf values. Sample set to zero.", mDesc.name, infCount); + if (nanCount > 0) + logWarning("MERL BRDF {} has {} samples with NaN values. Sample set to zero.", mDesc.name, nanCount); +} - FALCOR_CHECK(!mDesc.path.empty(), "No BRDF loaded"); - const auto texPath = mDesc.path.replace_extension("dds"); +const std::vector& MERLFile::prepareAlbedoLUT(ref pDevice, bool useCache) +{ + if (!mAlbedoLUT.empty()) + return mAlbedoLUT; - // Try loading cached albedo lookup table. - if (std::filesystem::is_regular_file(texPath)) - { - const auto albedoLut = ImageIO::loadBitmapFromDDS(texPath); - - if (albedoLut->getFormat() == kAlbedoLUTFormat && - albedoLut->getWidth() == kAlbedoLUTSize && albedoLut->getHeight() == 1) - { - const float4* data = reinterpret_cast(albedoLut->getData()); - mAlbedoLUT.resize(kAlbedoLUTSize); - std::copy(data, data + kAlbedoLUTSize, mAlbedoLUT.begin()); - - logInfo("Loaded albedo LUT from '{}'.", texPath.string()); - return mAlbedoLUT; - } - } + FALCOR_CHECK(!mDesc.path.empty(), "No BRDF loaded"); + const auto texPath = mDesc.path.replace_extension("dds"); - // Failed to load a valid lookup table. We'll recompute it. - computeAlbedoLUT(pDevice, kAlbedoLUTSize); - FALCOR_ASSERT(mAlbedoLUT.size() == kAlbedoLUTSize); + // Try loading cached albedo lookup table. + if (useCache && std::filesystem::is_regular_file(texPath)) + { + const auto albedoLut = ImageIO::loadBitmapFromDDS(texPath); - // Cache lookup table as texture on disk. + if (albedoLut->getFormat() == kAlbedoLUTFormat && albedoLut->getWidth() == kAlbedoLUTSize && albedoLut->getHeight() == 1) { - const uint8_t* data = reinterpret_cast(mAlbedoLUT.data()); - const auto albedoLut = Bitmap::create(mAlbedoLUT.size(), 1, kAlbedoLUTFormat, data); + const float4* data = reinterpret_cast(albedoLut->getData()); + mAlbedoLUT.resize(kAlbedoLUTSize); + std::copy(data, data + kAlbedoLUTSize, mAlbedoLUT.begin()); - ImageIO::saveToDDS(texPath, *albedoLut, ImageIO::CompressionMode::None, false); - logInfo("Saved albedo LUT to '{}'.", texPath); + logInfo("Loaded albedo LUT from '{}'.", texPath.string()); + return mAlbedoLUT; } - - return mAlbedoLUT; } - void MERLFile::computeAlbedoLUT(ref pDevice, const size_t binCount) + // Failed to load a valid lookup table. We'll recompute it. + computeAlbedoLUT(pDevice, kAlbedoLUTSize); + FALCOR_ASSERT(mAlbedoLUT.size() == kAlbedoLUTSize); + + // Cache lookup table as texture on disk. + if (useCache) { - logInfo("MERLFile: Computing albedo LUT for MERL BRDF '{}'...", mDesc.name); + const uint8_t* data = reinterpret_cast(mAlbedoLUT.data()); + const auto albedoLut = Bitmap::create(mAlbedoLUT.size(), 1, kAlbedoLUTFormat, data); + + ImageIO::saveToDDS(texPath, *albedoLut, ImageIO::CompressionMode::None, false); + logInfo("Saved albedo LUT to '{}'.", texPath); + } + + return mAlbedoLUT; +} - std::vector cosThetas(binCount); - for (uint32_t i = 0; i < binCount; i++) cosThetas[i] = (float)(i + 1) / binCount; +void MERLFile::computeAlbedoLUT(ref pDevice, const size_t binCount) +{ + logInfo("MERLFile: Computing albedo LUT for MERL BRDF '{}'...", mDesc.name); - // Create MERL material based on loaded data. - ref pMaterial = make_ref(pDevice, *this); + std::vector cosThetas(binCount); + for (uint32_t i = 0; i < binCount; i++) + cosThetas[i] = (float)(i + 1) / binCount; - // Create and update dummy scene containing the material. - Scene::SceneData sceneData; - sceneData.pMaterials = std::make_unique(pDevice); - MaterialID materialID = sceneData.pMaterials->addMaterial(pMaterial); + // Create MERL material based on loaded data. + ref pMaterial = make_ref(pDevice, *this); - ref pScene = Scene::create(pDevice, std::move(sceneData)); - pScene->update(pDevice->getRenderContext(), 0.0); + // Create and update dummy scene containing the material. + Scene::SceneData sceneData; + sceneData.pMaterials = std::make_unique(pDevice); + MaterialID materialID = sceneData.pMaterials->addMaterial(pMaterial); - // Create BSDF integrator utility. - BSDFIntegrator integrator(pDevice, pScene); + ref pScene = Scene::create(pDevice, std::move(sceneData)); + pScene->update(pDevice->getRenderContext(), 0.0); - // Integrate BSDF. - auto albedos = integrator.integrateIsotropic(pDevice->getRenderContext(), materialID, cosThetas); + // Create BSDF integrator utility. + BSDFIntegrator integrator(pDevice, pScene); - // Copy result into RGBA format needed for texture creation. - mAlbedoLUT.resize(binCount); - for (uint32_t i = 0; i < binCount; i++) - mAlbedoLUT[i] = float4(albedos[i], 1.f); - } + // Integrate BSDF. + auto albedos = integrator.integrateIsotropic(pDevice->getRenderContext(), materialID, cosThetas); + + // Copy result into RGBA format needed for texture creation. + mAlbedoLUT.resize(binCount); + for (uint32_t i = 0; i < binCount; i++) + mAlbedoLUT[i] = float4(albedos[i], 1.f); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MERLFile.h b/Source/Falcor/Scene/Material/MERLFile.h index ec68937c55..494db8d0aa 100644 --- a/Source/Falcor/Scene/Material/MERLFile.h +++ b/Source/Falcor/Scene/Material/MERLFile.h @@ -35,52 +35,52 @@ namespace Falcor { - class Device; +class Device; - /** Class for loading a measured material from the MERL BRDF database. - Additional metadata is loaded along with the BRDF if available. - */ - class FALCOR_API MERLFile +/** Class for loading a measured material from the MERL BRDF database. + Additional metadata is loaded along with the BRDF if available. +*/ +class FALCOR_API MERLFile +{ +public: + struct Desc { - public: - struct Desc - { - std::string name; ///< Name of the BRDF. - std::filesystem::path path; ///< Full path to the loaded BRDF. - DiffuseSpecularData extraData = {}; ///< Parameters for a best fit BRDF approximation. - }; + std::string name; ///< Name of the BRDF. + std::filesystem::path path; ///< Full path to the loaded BRDF. + DiffuseSpecularData extraData = {}; ///< Parameters for a best fit BRDF approximation. + }; - static constexpr ResourceFormat kAlbedoLUTFormat = ResourceFormat::RGBA32Float; + static constexpr ResourceFormat kAlbedoLUTFormat = ResourceFormat::RGBA32Float; - MERLFile() = default; + MERLFile() = default; - /** Constructs a new object and loads a MERL BRDF. Throws on error. - \param[in] path Path to the binary MERL file. - */ - MERLFile(const std::filesystem::path& path); + /** Constructs a new object and loads a MERL BRDF. Throws on error. + \param[in] path Path to the binary MERL file. + */ + MERLFile(const std::filesystem::path& path); - /** Loads a MERL BRDF. - \param[in] path Path to the binary MERL file. - \return True if the BRDF was successfully loaded. - */ - bool loadBRDF(const std::filesystem::path& path); + /** Loads a MERL BRDF. + \param[in] path Path to the binary MERL file. + \return True if the BRDF was successfully loaded. + */ + bool loadBRDF(const std::filesystem::path& path); - /** Prepare an albedo lookup table. - The table is loaded from disk or recomputed if needed. - \param[in] pDevice The device. - \return Albedo lookup table that can be used with `kAlbedoLUTFormat`. - */ - const std::vector& prepareAlbedoLUT(ref pDevice); + /** Prepare an albedo lookup table. + The table is loaded from disk or recomputed if needed. + \param[in] pDevice The device. + \return Albedo lookup table that can be used with `kAlbedoLUTFormat`. + */ + const std::vector& prepareAlbedoLUT(ref pDevice, bool useCache = true); - const Desc& getDesc() const { return mDesc; } - const std::vector& getData() const { return mData; } + const Desc& getDesc() const { return mDesc; } + const std::vector& getData() const { return mData; } - private: - void prepareData(const int dims[3], const std::vector& data); - void computeAlbedoLUT(ref pDevice, const size_t binCount); +private: + void prepareData(const int dims[3], const std::vector& data); + void computeAlbedoLUT(ref pDevice, const size_t binCount); - Desc mDesc; ///< BRDF description and sampling parameters. - std::vector mData; ///< BRDF data in RGB float format. - std::vector mAlbedoLUT; ///< Precomputed albedo lookup table. - }; -} + Desc mDesc; ///< BRDF description and sampling parameters. + std::vector mData; ///< BRDF data in RGB float format. + std::vector mAlbedoLUT; ///< Precomputed albedo lookup table. +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MERLMaterial.cpp b/Source/Falcor/Scene/Material/MERLMaterial.cpp index 0721096c01..f3d00755ac 100644 --- a/Source/Falcor/Scene/Material/MERLMaterial.cpp +++ b/Source/Falcor/Scene/Material/MERLMaterial.cpp @@ -36,132 +36,135 @@ namespace Falcor { - namespace - { - static_assert((sizeof(MaterialHeader) + sizeof(MERLMaterialData)) <= sizeof(MaterialDataBlob), "MERLMaterialData is too large"); +namespace +{ +static_assert((sizeof(MaterialHeader) + sizeof(MERLMaterialData)) <= sizeof(MaterialDataBlob), "MERLMaterialData is too large"); - const char kShaderFile[] = "Rendering/Materials/MERLMaterial.slang"; - } +const char kShaderFile[] = "Rendering/Materials/MERLMaterial.slang"; +} // namespace - MERLMaterial::MERLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path) - : Material(pDevice, name, MaterialType::MERL) - { - FALCOR_CHECK(!path.empty(), "Missing path."); +MERLMaterial::MERLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path) + : Material(pDevice, name, MaterialType::MERL) +{ + FALCOR_CHECK(!path.empty(), "Missing path."); + + MERLFile merlFile(path); + init(merlFile); + + // Create albedo LUT texture. + // TODO: Make this a 1D texture when supported by the material system. See + auto lut = merlFile.prepareAlbedoLUT(mpDevice); + FALCOR_CHECK(!lut.empty() && sizeof(lut[0]) == sizeof(float4), "Expected albedo LUT in float4 format."); + static_assert(MERLFile::kAlbedoLUTFormat == ResourceFormat::RGBA32Float); + mpAlbedoLUT = + mpDevice->createTexture2D((uint32_t)lut.size(), 1, MERLFile::kAlbedoLUTFormat, 1, 1, lut.data(), ResourceBindFlags::ShaderResource); +} - MERLFile merlFile(path); - init(merlFile); +MERLMaterial::MERLMaterial(ref pDevice, const MERLFile& merlFile) : Material(pDevice, "", MaterialType::MERL) +{ + init(merlFile); +} - // Create albedo LUT texture. - auto lut = merlFile.prepareAlbedoLUT(mpDevice); - FALCOR_CHECK(!lut.empty() && sizeof(lut[0]) == sizeof(float4), "Expected albedo LUT in float4 format."); - static_assert(MERLFile::kAlbedoLUTFormat == ResourceFormat::RGBA32Float); - mpAlbedoLUT = mpDevice->createTexture2D((uint32_t)lut.size(), 1, MERLFile::kAlbedoLUTFormat, 1, 1, lut.data(), ResourceBindFlags::ShaderResource); - } +void MERLMaterial::init(const MERLFile& merlFile) +{ + mPath = merlFile.getDesc().path; + mBRDFName = merlFile.getDesc().name; + mData.extraData = merlFile.getDesc().extraData; + + // Create GPU buffer. + const auto& brdf = merlFile.getData(); + FALCOR_CHECK(!brdf.empty() && sizeof(brdf[0]) == sizeof(float3), "Expected BRDF data in float3 format."); + mpBRDFData = + mpDevice->createBuffer(brdf.size() * sizeof(brdf[0]), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, brdf.data()); + + // Create sampler for albedo LUT. + Sampler::Desc desc; + desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Point, TextureFilteringMode::Point); + desc.setAddressingMode(TextureAddressingMode::Clamp, TextureAddressingMode::Clamp, TextureAddressingMode::Clamp); + desc.setMaxAnisotropy(1); + mpLUTSampler = mpDevice->createSampler(desc); + + markUpdates(Material::UpdateFlags::ResourcesChanged); +} - MERLMaterial::MERLMaterial(ref pDevice, const MERLFile& merlFile) - : Material(pDevice, "", MaterialType::MERL) - { - init(merlFile); - } +bool MERLMaterial::renderUI(Gui::Widgets& widget) +{ + bool changed = false; - void MERLMaterial::init(const MERLFile& merlFile) - { - mPath = merlFile.getDesc().path; - mBRDFName = merlFile.getDesc().name; - mData.extraData = merlFile.getDesc().extraData; - - // Create GPU buffer. - const auto& brdf = merlFile.getData(); - FALCOR_CHECK(!brdf.empty() && sizeof(brdf[0]) == sizeof(float3), "Expected BRDF data in float3 format."); - mpBRDFData = mpDevice->createBuffer(brdf.size() * sizeof(brdf[0]), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, brdf.data()); - - // Create sampler for albedo LUT. - Sampler::Desc desc; - desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Point, TextureFilteringMode::Point); - desc.setAddressingMode(TextureAddressingMode::Clamp, TextureAddressingMode::Clamp, TextureAddressingMode::Clamp); - desc.setMaxAnisotropy(1); - mpLUTSampler = mpDevice->createSampler(desc); - - markUpdates(Material::UpdateFlags::ResourcesChanged); - } + widget.text("MERL BRDF " + mBRDFName); + widget.tooltip("Full path the BRDF was loaded from:\n" + mPath.string(), true); - bool MERLMaterial::renderUI(Gui::Widgets& widget) + if (auto g = widget.group("Approx diffuse/specular sampling")) { - bool changed = false; - - widget.text("MERL BRDF " + mBRDFName); - widget.tooltip("Full path the BRDF was loaded from:\n" + mPath.string(), true); - - if (auto g = widget.group("Approx diffuse/specular sampling")) + if (DiffuseSpecularUtils::renderUI(g, mData.extraData)) { - if (DiffuseSpecularUtils::renderUI(g, mData.extraData)) - { - markUpdates(Material::UpdateFlags::DataChanged); - changed = true; - } + markUpdates(Material::UpdateFlags::DataChanged); + changed = true; } - - return changed; } - Material::UpdateFlags MERLMaterial::update(MaterialSystem* pOwner) - { - FALCOR_ASSERT(pOwner); - - auto flags = Material::UpdateFlags::None; - if (mUpdates != Material::UpdateFlags::None) - { - uint32_t bufferID = pOwner->addBuffer(mpBRDFData); - uint32_t samplerID = pOwner->addTextureSampler(mpLUTSampler); + return changed; +} - if (mData.bufferID != bufferID || mData.samplerID != samplerID) - { - mUpdates |= Material::UpdateFlags::DataChanged; - } - mData.bufferID = bufferID; - mData.samplerID = samplerID; +Material::UpdateFlags MERLMaterial::update(MaterialSystem* pOwner) +{ + FALCOR_ASSERT(pOwner); - updateTextureHandle(pOwner, mpAlbedoLUT, mData.texAlbedoLUT); + auto flags = Material::UpdateFlags::None; + if (mUpdates != Material::UpdateFlags::None) + { + uint32_t bufferID = pOwner->addBuffer(mpBRDFData); + uint32_t samplerID = pOwner->addTextureSampler(mpLUTSampler); - flags |= mUpdates; - mUpdates = Material::UpdateFlags::None; + if (mData.bufferID != bufferID || mData.samplerID != samplerID) + { + mUpdates |= Material::UpdateFlags::DataChanged; } + mData.bufferID = bufferID; + mData.samplerID = samplerID; - return flags; + updateTextureHandle(pOwner, mpAlbedoLUT, mData.texAlbedoLUT); + + flags |= mUpdates; + mUpdates = Material::UpdateFlags::None; } - bool MERLMaterial::isEqual(const ref& pOther) const - { - auto other = dynamic_ref_cast(pOther); - if (!other) return false; + return flags; +} - if (!isBaseEqual(*other)) return false; - if (mPath != other->mPath) return false; +bool MERLMaterial::isEqual(const ref& pOther) const +{ + auto other = dynamic_ref_cast(pOther); + if (!other) + return false; - return true; - } + if (!isBaseEqual(*other)) + return false; + if (mPath != other->mPath) + return false; - ProgramDesc::ShaderModuleList MERLMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } + return true; +} - TypeConformanceList MERLMaterial::getTypeConformances() const - { - return { {{"MERLMaterial", "IMaterial"}, (uint32_t)MaterialType::MERL} }; - } +ProgramDesc::ShaderModuleList MERLMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - FALCOR_SCRIPT_BINDING(MERLMaterial) - { - using namespace pybind11::literals; +TypeConformanceList MERLMaterial::getTypeConformances() const +{ + return {{{"MERLMaterial", "IMaterial"}, (uint32_t)MaterialType::MERL}}; +} - FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) +FALCOR_SCRIPT_BINDING(MERLMaterial) +{ + using namespace pybind11::literals; - pybind11::class_> material(m, "MERLMaterial"); - auto create = [] (const std::string& name, const std::filesystem::path& path) - { - return MERLMaterial::create(accessActivePythonSceneBuilder().getDevice(), name, getActiveAssetResolver().resolvePath(path)); - }; - material.def(pybind11::init(create), "name"_a, "path"_a); // PYTHONDEPRECATED - } + FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) + + pybind11::class_> material(m, "MERLMaterial"); + auto create = [](const std::string& name, const std::filesystem::path& path) + { return MERLMaterial::create(accessActivePythonSceneBuilderDevice(), name, getActiveAssetResolver().resolvePath(path)); }; + material.def(pybind11::init(create), "name"_a, "path"_a); // PYTHONDEPRECATED } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MERLMaterial.h b/Source/Falcor/Scene/Material/MERLMaterial.h index 384ba59aef..ec5ac9d468 100644 --- a/Source/Falcor/Scene/Material/MERLMaterial.h +++ b/Source/Falcor/Scene/Material/MERLMaterial.h @@ -31,42 +31,45 @@ namespace Falcor { - class MERLFile; +class MERLFile; - /** Class representing a measured material from the MERL BRDF database. +/** Class representing a measured material from the MERL BRDF database. - For details refer to: - Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan. - "A Data-Driven Reflectance Model". ACM Transactions on Graphics, - vol. 22(3), 2003, pages 759-769. - */ - class FALCOR_API MERLMaterial : public Material + For details refer to: + Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan. + "A Data-Driven Reflectance Model". ACM Transactions on Graphics, + vol. 22(3), 2003, pages 759-769. +*/ +class FALCOR_API MERLMaterial : public Material +{ + FALCOR_OBJECT(MERLMaterial) +public: + static ref create(ref pDevice, const std::string& name, const std::filesystem::path& path) { - FALCOR_OBJECT(MERLMaterial) - public: - static ref create(ref pDevice, const std::string& name, const std::filesystem::path& path) { return make_ref(pDevice, name, path); } + return make_ref(pDevice, name, path); + } - MERLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path); - MERLMaterial(ref pDevice, const MERLFile& merlFile); + MERLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path); + MERLMaterial(ref pDevice, const MERLFile& merlFile); - bool renderUI(Gui::Widgets& widget) override; - Material::UpdateFlags update(MaterialSystem* pOwner) override; - bool isEqual(const ref& pOther) const override; - MaterialDataBlob getDataBlob() const override { return prepareDataBlob(mData); } - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + bool renderUI(Gui::Widgets& widget) override; + Material::UpdateFlags update(MaterialSystem* pOwner) override; + bool isEqual(const ref& pOther) const override; + MaterialDataBlob getDataBlob() const override { return prepareDataBlob(mData); } + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - size_t getMaxBufferCount() const override { return 1; } + size_t getMaxBufferCount() const override { return 1; } - protected: - void init(const MERLFile& merlFile); +protected: + void init(const MERLFile& merlFile); - std::filesystem::path mPath; ///< Full path to the BRDF loaded. - std::string mBRDFName; ///< This is the file basename without extension. + std::filesystem::path mPath; ///< Full path to the BRDF loaded. + std::string mBRDFName; ///< This is the file basename without extension. - MERLMaterialData mData; ///< Material parameters. - ref mpBRDFData; ///< GPU buffer holding all BRDF data as float3 array. - ref mpAlbedoLUT; ///< Precomputed albedo lookup table. - ref mpLUTSampler; ///< Sampler for accessing the LUT texture. - }; -} + MERLMaterialData mData; ///< Material parameters. + ref mpBRDFData; ///< GPU buffer holding all BRDF data as float3 array. + ref mpAlbedoLUT; ///< Precomputed albedo lookup table. + ref mpLUTSampler; ///< Sampler for accessing the LUT texture. +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MERLMaterialData.slang b/Source/Falcor/Scene/Material/MERLMaterialData.slang index 00bbcfb285..32d5ec7a34 100644 --- a/Source/Falcor/Scene/Material/MERLMaterialData.slang +++ b/Source/Falcor/Scene/Material/MERLMaterialData.slang @@ -39,7 +39,7 @@ __exported import Scene.Material.DiffuseSpecularData; BEGIN_NAMESPACE_FALCOR /** This is a host/device structure that describes a measured MERL material. -*/ + */ struct MERLMaterialData { // MaterialHeader (16B) is stored just before this struct in memory. diff --git a/Source/Falcor/Scene/Material/MERLMixMaterial.cpp b/Source/Falcor/Scene/Material/MERLMixMaterial.cpp index 74a30d6916..c08d633dae 100644 --- a/Source/Falcor/Scene/Material/MERLMixMaterial.cpp +++ b/Source/Falcor/Scene/Material/MERLMixMaterial.cpp @@ -38,287 +38,309 @@ namespace Falcor { - namespace - { - static_assert((sizeof(MaterialHeader) + sizeof(MERLMixMaterialData)) <= sizeof(MaterialDataBlob), "MERLMixMaterialData is too large"); - static_assert(static_cast(NormalMapType::Count) <= (1u << MERLMixMaterialData::kNormalMapTypeBits), "NormalMapType bit count exceeds the maximum"); +namespace +{ +static_assert((sizeof(MaterialHeader) + sizeof(MERLMixMaterialData)) <= sizeof(MaterialDataBlob), "MERLMixMaterialData is too large"); +static_assert( + static_cast(NormalMapType::Count) <= (1u << MERLMixMaterialData::kNormalMapTypeBits), + "NormalMapType bit count exceeds the maximum" +); - const char kShaderFile[] = "Rendering/Materials/MERLMixMaterial.slang"; - } +const char kShaderFile[] = "Rendering/Materials/MERLMixMaterial.slang"; +} // namespace + +MERLMixMaterial::MERLMixMaterial(ref pDevice, const std::string& name, const std::vector& paths) + : Material(pDevice, name, MaterialType::MERLMix) +{ + FALCOR_CHECK(!paths.empty(), "MERLMixMaterial: Expected at least one path."); + + // Setup texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Index] = {"index", TextureChannelFlags::Red, false}; + + // Load all BRDFs. + mBRDFs.resize(paths.size()); + std::vector extraData(paths.size()); + std::vector albedoLut; + BufferAllocator buffer(128, 0 /* raw buffer */, 128, ResourceBindFlags::ShaderResource); + MERLFile merlFile; - MERLMixMaterial::MERLMixMaterial(ref pDevice, const std::string& name, const std::vector& paths) - : Material(pDevice, name, MaterialType::MERLMix) + for (size_t i = 0; i < paths.size(); i++) { - FALCOR_CHECK(!paths.empty(), "MERLMixMaterial: Expected at least one path."); + if (!merlFile.loadBRDF(paths[i])) + FALCOR_THROW("MERLMixMaterial: Failed to load BRDF from '{}'.", paths[i]); + + auto& desc = mBRDFs[i]; + desc.path = merlFile.getDesc().path; + desc.name = merlFile.getDesc().name; + extraData[i] = merlFile.getDesc().extraData; + + // Copy BRDF samples into shared data buffer. + const auto& brdf = merlFile.getData(); + FALCOR_CHECK(!brdf.empty() && sizeof(brdf[0]) == sizeof(float3), "Expected BRDF data in float3 format."); + desc.byteSize = brdf.size() * sizeof(brdf[0]); + desc.byteOffset = buffer.allocate(desc.byteSize); + buffer.setBlob(brdf.data(), desc.byteOffset, desc.byteSize); + + // Copy albedo LUT into shared table. + const auto& lut = merlFile.prepareAlbedoLUT(mpDevice); + FALCOR_CHECK(lut.size() == MERLMixMaterialData::kAlbedoLUTSize, "MERLMixMaterial: Unexpected albedo LUT size."); + albedoLut.insert(albedoLut.end(), lut.begin(), lut.end()); + } - // Setup texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Index] = { "index", TextureChannelFlags::Red, false }; + mData.brdfCount = static_cast(mBRDFs.size()); + mData.byteStride = mBRDFs.size() > 1 ? mBRDFs[1].byteOffset : 0; + for (size_t i = 0; i < mBRDFs.size(); i++) + { + FALCOR_CHECK(mBRDFs[i].byteOffset == i * mData.byteStride, "MERLMixMaterial: Unexpected stride."); + } - // Load all BRDFs. - mBRDFs.resize(paths.size()); - std::vector extraData(paths.size()); - std::vector albedoLut; - BufferAllocator buffer(128, 0 /* raw buffer */, 128, ResourceBindFlags::ShaderResource); - MERLFile merlFile; + // Upload extra data for sampling. + { + mData.extraDataStride = (uint32_t)sizeof(DiffuseSpecularData); + size_t byteSize = extraData.size() * mData.extraDataStride; + mData.extraDataOffset = buffer.allocate(byteSize); + buffer.setBlob(extraData.data(), mData.extraDataOffset, byteSize); + } - for (size_t i = 0; i < paths.size(); i++) - { - if (!merlFile.loadBRDF(paths[i])) - FALCOR_THROW("MERLMixMaterial: Failed to load BRDF from '{}'.", paths[i]); - - auto& desc = mBRDFs[i]; - desc.path = merlFile.getDesc().path; - desc.name = merlFile.getDesc().name; - extraData[i] = merlFile.getDesc().extraData; - - // Copy BRDF samples into shared data buffer. - const auto& brdf = merlFile.getData(); - FALCOR_CHECK(!brdf.empty() && sizeof(brdf[0]) == sizeof(float3), "Expected BRDF data in float3 format."); - desc.byteSize = brdf.size() * sizeof(brdf[0]); - desc.byteOffset = buffer.allocate(desc.byteSize); - buffer.setBlob(brdf.data(), desc.byteOffset, desc.byteSize); - - // Copy albedo LUT into shared table. - const auto& lut = merlFile.prepareAlbedoLUT(mpDevice); - FALCOR_CHECK(lut.size() == MERLMixMaterialData::kAlbedoLUTSize, "MERLMixMaterial: Unexpected albedo LUT size."); - albedoLut.insert(albedoLut.end(), lut.begin(), lut.end()); - } + // Create GPU data buffer. + mpBRDFData = buffer.getGPUBuffer(mpDevice); + + // Create albedo LUT as 2D texture parameterization over (cosTehta, brdfIndex). + mpAlbedoLUT = mpDevice->createTexture2D( + MERLMixMaterialData::kAlbedoLUTSize, + mData.brdfCount, + MERLFile::kAlbedoLUTFormat, + 1, + 1, + albedoLut.data(), + ResourceBindFlags::ShaderResource + ); + + // Create sampler for albedo LUT. + { + Sampler::Desc desc; + desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Point, TextureFilteringMode::Point); + desc.setAddressingMode(TextureAddressingMode::Clamp, TextureAddressingMode::Clamp, TextureAddressingMode::Clamp); + desc.setMaxAnisotropy(1); + mpLUTSampler = mpDevice->createSampler(desc); + } - mData.brdfCount = static_cast(mBRDFs.size()); - mData.byteStride = mBRDFs.size() > 1 ? mBRDFs[1].byteOffset : 0; - for (size_t i = 0; i < mBRDFs.size(); i++) - { - FALCOR_CHECK(mBRDFs[i].byteOffset == i * mData.byteStride, "MERLMixMaterial: Unexpected stride."); - } + // Create sampler for index map. Using point sampling as indices are not interpolatable. + { + Sampler::Desc desc; + desc.setFilterMode(TextureFilteringMode::Point, TextureFilteringMode::Point, TextureFilteringMode::Point); + desc.setAddressingMode(TextureAddressingMode::Wrap, TextureAddressingMode::Wrap, TextureAddressingMode::Wrap); + desc.setMaxAnisotropy(1); + mpIndexSampler = mpDevice->createSampler(desc); + } - // Upload extra data for sampling. - { - mData.extraDataStride = (uint32_t)sizeof(DiffuseSpecularData); - size_t byteSize = extraData.size() * mData.extraDataStride; - mData.extraDataOffset = buffer.allocate(byteSize); - buffer.setBlob(extraData.data(), mData.extraDataOffset, byteSize); - } + updateNormalMapType(); + updateIndexMapType(); - // Create GPU data buffer. - mpBRDFData = buffer.getGPUBuffer(mpDevice); + markUpdates(Material::UpdateFlags::ResourcesChanged); +} - // Create albedo LUT as 2D texture parameterization over (cosTehta, brdfIndex). - mpAlbedoLUT = mpDevice->createTexture2D(MERLMixMaterialData::kAlbedoLUTSize, mData.brdfCount, MERLFile::kAlbedoLUTFormat, 1, 1, albedoLut.data(), ResourceBindFlags::ShaderResource); +bool MERLMixMaterial::renderUI(Gui::Widgets& widget) +{ + bool changed = Material::renderUI(widget); - // Create sampler for albedo LUT. - { - Sampler::Desc desc; - desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Point, TextureFilteringMode::Point); - desc.setAddressingMode(TextureAddressingMode::Clamp, TextureAddressingMode::Clamp, TextureAddressingMode::Clamp); - desc.setMaxAnisotropy(1); - mpLUTSampler = mpDevice->createSampler(desc); - } + // We're re-using the material's update flags here to track changes. + // Cache the previous flag so we can restore it before returning. + UpdateFlags prevUpdates = mUpdates; + mUpdates = UpdateFlags::None; - // Create sampler for index map. Using point sampling as indices are not interpolatable. + // Display BRDF info. + widget.text(fmt::format("Loaded MERL BRDFs: {}", mBRDFs.size())); + if (auto g = widget.group("BRDFs")) + { + for (size_t i = 0; i < mBRDFs.size(); i++) { - Sampler::Desc desc; - desc.setFilterMode(TextureFilteringMode::Point, TextureFilteringMode::Point, TextureFilteringMode::Point); - desc.setAddressingMode(TextureAddressingMode::Wrap, TextureAddressingMode::Wrap, TextureAddressingMode::Wrap); - desc.setMaxAnisotropy(1); - mpIndexSampler = mpDevice->createSampler(desc); + g.text(fmt::format("ID {}: {}", i, mBRDFs[i].name)); } - - updateNormalMapType(); - updateIndexMapType(); - - markUpdates(Material::UpdateFlags::ResourcesChanged); } - bool MERLMixMaterial::renderUI(Gui::Widgets& widget) + // Display texture info. + if (auto pTexture = getNormalMap()) { - bool changed = Material::renderUI(widget); - - // We're re-using the material's update flags here to track changes. - // Cache the previous flag so we can restore it before returning. - UpdateFlags prevUpdates = mUpdates; - mUpdates = UpdateFlags::None; + widget.text("Normal map: " + pTexture->getSourcePath().string()); + widget.text( + "Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + + to_string(pTexture->getFormat()) + ")" + ); + widget.image("Normal map", pTexture.get(), float2(100.f)); + if (widget.button("Remove texture##NormalMap")) + setNormalMap(nullptr); + } + else + { + widget.text("Normal map: N/A"); + } - // Display BRDF info. - widget.text(fmt::format("Loaded MERL BRDFs: {}", mBRDFs.size())); - if (auto g = widget.group("BRDFs")) - { - for (size_t i = 0; i < mBRDFs.size(); i++) - { - g.text(fmt::format("ID {}: {}", i, mBRDFs[i].name)); - } - } + if (auto pTexture = getTexture(TextureSlot::Index)) + { + widget.text("Index map: " + pTexture->getSourcePath().string()); + widget.text( + "Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + + to_string(pTexture->getFormat()) + ")" + ); + if (widget.button("Remove texture##IndexMap")) + setTexture(TextureSlot::Index, nullptr); + } + else + { + widget.text("Index map: N/A"); + } - // Display texture info. - if (auto pTexture = getNormalMap()) - { - widget.text("Normal map: " + pTexture->getSourcePath().string()); - widget.text("Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + to_string(pTexture->getFormat()) + ")"); - widget.image("Normal map", pTexture.get(), float2(100.f)); - if (widget.button("Remove texture##NormalMap")) setNormalMap(nullptr); - } - else - { - widget.text("Normal map: N/A"); - } + // Restore update flags. + changed |= mUpdates != UpdateFlags::None; + markUpdates(prevUpdates | mUpdates); - if (auto pTexture = getTexture(TextureSlot::Index)) - { - widget.text("Index map: " + pTexture->getSourcePath().string()); - widget.text("Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + to_string(pTexture->getFormat()) + ")"); - if (widget.button("Remove texture##IndexMap")) setTexture(TextureSlot::Index, nullptr); - } - else - { - widget.text("Index map: N/A"); - } + return changed; +} - // Restore update flags. - changed |= mUpdates != UpdateFlags::None; - markUpdates(prevUpdates | mUpdates); +Material::UpdateFlags MERLMixMaterial::update(MaterialSystem* pOwner) +{ + FALCOR_ASSERT(pOwner); - return changed; + auto flags = Material::UpdateFlags::None; + if (mUpdates != Material::UpdateFlags::None) + { + // Update buffers. + uint32_t bufferID = pOwner->addBuffer(mpBRDFData); + if (mData.bufferID != bufferID) + mUpdates |= Material::UpdateFlags::DataChanged; + mData.bufferID = bufferID; + + // Update texture handles. + updateTextureHandle(pOwner, TextureSlot::Normal, mData.texNormalMap); + updateTextureHandle(pOwner, TextureSlot::Index, mData.texIndexMap); + updateTextureHandle(pOwner, mpAlbedoLUT, mData.texAlbedoLUT); + + // Update samplers. + uint prevFlags = mData.flags; + mData.setLUTSamplerID(pOwner->addTextureSampler(mpLUTSampler)); + mData.setIndexSamplerID(pOwner->addTextureSampler(mpIndexSampler)); + if (mData.flags != prevFlags) + mUpdates |= Material::UpdateFlags::DataChanged; + + updateDefaultTextureSamplerID(pOwner, mpDefaultSampler); + + flags |= mUpdates; + mUpdates = Material::UpdateFlags::None; } - Material::UpdateFlags MERLMixMaterial::update(MaterialSystem* pOwner) - { - FALCOR_ASSERT(pOwner); + return flags; +} - auto flags = Material::UpdateFlags::None; - if (mUpdates != Material::UpdateFlags::None) - { - // Update buffers. - uint32_t bufferID = pOwner->addBuffer(mpBRDFData); - if (mData.bufferID != bufferID) - mUpdates |= Material::UpdateFlags::DataChanged; - mData.bufferID = bufferID; - - // Update texture handles. - updateTextureHandle(pOwner, TextureSlot::Normal, mData.texNormalMap); - updateTextureHandle(pOwner, TextureSlot::Index, mData.texIndexMap); - updateTextureHandle(pOwner, mpAlbedoLUT, mData.texAlbedoLUT); - - // Update samplers. - uint prevFlags = mData.flags; - mData.setLUTSamplerID(pOwner->addTextureSampler(mpLUTSampler)); - mData.setIndexSamplerID(pOwner->addTextureSampler(mpIndexSampler)); - if (mData.flags != prevFlags) - mUpdates |= Material::UpdateFlags::DataChanged; - - updateDefaultTextureSamplerID(pOwner, mpDefaultSampler); - - flags |= mUpdates; - mUpdates = Material::UpdateFlags::None; - } +bool MERLMixMaterial::isEqual(const ref& pOther) const +{ + auto other = dynamic_ref_cast(pOther); + if (!other) + return false; - return flags; - } + if (!isBaseEqual(*other)) + return false; - bool MERLMixMaterial::isEqual(const ref& pOther) const + // Check if the list loaded BRDFs is identical. + if (mBRDFs.size() != other->mBRDFs.size()) + return false; + for (size_t i = 0; i < mBRDFs.size(); i++) { - auto other = dynamic_ref_cast(pOther); - if (!other) return false; + if (!(mBRDFs[i] == other->mBRDFs[i])) + return false; + } - if (!isBaseEqual(*other)) return false; + // Compare samplers. + if (mpDefaultSampler->getDesc() != other->mpDefaultSampler->getDesc()) + return false; - // Check if the list loaded BRDFs is identical. - if (mBRDFs.size() != other->mBRDFs.size()) return false; - for (size_t i = 0; i < mBRDFs.size(); i++) - { - if (!(mBRDFs[i] == other->mBRDFs[i])) - return false; - } + return true; +} - // Compare samplers. - if (mpDefaultSampler->getDesc() != other->mpDefaultSampler->getDesc()) return false; +ProgramDesc::ShaderModuleList MERLMixMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - return true; - } +TypeConformanceList MERLMixMaterial::getTypeConformances() const +{ + return {{{"MERLMixMaterial", "IMaterial"}, (uint32_t)MaterialType::MERLMix}}; +} - ProgramDesc::ShaderModuleList MERLMixMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +bool MERLMixMaterial::setTexture(const TextureSlot slot, const ref& pTexture) +{ + if (!Material::setTexture(slot, pTexture)) + return false; - TypeConformanceList MERLMixMaterial::getTypeConformances() const + // Update additional metadata about texture usage. + switch (slot) { - return { {{"MERLMixMaterial", "IMaterial"}, (uint32_t)MaterialType::MERLMix} }; + case TextureSlot::Normal: + updateNormalMapType(); + break; + case TextureSlot::Index: + updateIndexMapType(); + break; + default: + break; } - bool MERLMixMaterial::setTexture(const TextureSlot slot, const ref& pTexture) - { - if (!Material::setTexture(slot, pTexture)) return false; - - // Update additional metadata about texture usage. - switch (slot) - { - case TextureSlot::Normal: - updateNormalMapType(); - break; - case TextureSlot::Index: - updateIndexMapType(); - break; - default: - break; - } - - return true; - } + return true; +} - void MERLMixMaterial::setDefaultTextureSampler(const ref& pSampler) +void MERLMixMaterial::setDefaultTextureSampler(const ref& pSampler) +{ + if (pSampler != mpDefaultSampler) { - if (pSampler != mpDefaultSampler) - { - mpDefaultSampler = pSampler; - markUpdates(UpdateFlags::ResourcesChanged); - } + mpDefaultSampler = pSampler; + markUpdates(UpdateFlags::ResourcesChanged); } +} - void MERLMixMaterial::updateNormalMapType() +void MERLMixMaterial::updateNormalMapType() +{ + NormalMapType type = detectNormalMapType(getNormalMap()); + if (mData.getNormalMapType() != type) { - NormalMapType type = detectNormalMapType(getNormalMap()); - if (mData.getNormalMapType() != type) - { - mData.setNormalMapType(type); - markUpdates(UpdateFlags::DataChanged); - } + mData.setNormalMapType(type); + markUpdates(UpdateFlags::DataChanged); } +} - void MERLMixMaterial::updateIndexMapType() +void MERLMixMaterial::updateIndexMapType() +{ + auto tex = getTexture(TextureSlot::Index); + if (tex) { - auto tex = getTexture(TextureSlot::Index); - if (tex) + // Verify that index map is in uncompressed 8-bit unorm format. + // The shader requires this because the BRDF index is computed by scaling the unorm value by 255. + ResourceFormat format = tex->getFormat(); + FALCOR_CHECK(!isSrgbFormat(format), "MERLMixMaterial: Index map must not be in SRGB format."); + + switch (format) { - // Verify that index map is in uncompressed 8-bit unorm format. - // The shader requires this because the BRDF index is computed by scaling the unorm value by 255. - ResourceFormat format = tex->getFormat(); - FALCOR_CHECK(!isSrgbFormat(format), "MERLMixMaterial: Index map must not be in SRGB format."); - - switch (format) - { - case ResourceFormat::R8Unorm: - case ResourceFormat::RG8Unorm: - case ResourceFormat::RGBA8Unorm: - case ResourceFormat::BGRA8Unorm: - case ResourceFormat::BGRX8Unorm: - break; - default: - FALCOR_THROW(fmt::format("MERLMixMaterial: Index map unsupported format ({}).", to_string(format))); - } + case ResourceFormat::R8Unorm: + case ResourceFormat::RG8Unorm: + case ResourceFormat::RGBA8Unorm: + case ResourceFormat::BGRA8Unorm: + case ResourceFormat::BGRX8Unorm: + break; + default: + FALCOR_THROW(fmt::format("MERLMixMaterial: Index map unsupported format ({}).", to_string(format))); } } +} - FALCOR_SCRIPT_BINDING(MERLMixMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(MERLMixMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) + FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) - pybind11::class_> material(m, "MERLMixMaterial"); - auto create = [](const std::string& name, const std::vector& paths) - { - return MERLMixMaterial::create(accessActivePythonSceneBuilder().getDevice(), name, paths); - }; - material.def(pybind11::init(create), "name"_a, "paths"_a); // PYTHONDEPRECATED - } + pybind11::class_> material(m, "MERLMixMaterial"); + auto create = [](const std::string& name, const std::vector& paths) + { return MERLMixMaterial::create(accessActivePythonSceneBuilderDevice(), name, paths); }; + material.def(pybind11::init(create), "name"_a, "paths"_a); // PYTHONDEPRECATED } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MERLMixMaterial.h b/Source/Falcor/Scene/Material/MERLMixMaterial.h index 4e1d2b5456..047b180427 100644 --- a/Source/Falcor/Scene/Material/MERLMixMaterial.h +++ b/Source/Falcor/Scene/Material/MERLMixMaterial.h @@ -31,65 +31,65 @@ namespace Falcor { - class BufferAllocator; +class BufferAllocator; - /** Measured material that can mix BRDFs from the MERL BRDF database. +/** Measured material that can mix BRDFs from the MERL BRDF database. - The class loads a list of MERL BRDFs and allows blending between them at runtime. - The blending can be textured to create mosaics of spatially varying BRDFs. + The class loads a list of MERL BRDFs and allows blending between them at runtime. + The blending can be textured to create mosaics of spatially varying BRDFs. - For details refer to: - Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan. - "A Data-Driven Reflectance Model". ACM Transactions on Graphics, - vol. 22(3), 2003, pages 759-769. - */ - class FALCOR_API MERLMixMaterial : public Material + For details refer to: + Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan. + "A Data-Driven Reflectance Model". ACM Transactions on Graphics, + vol. 22(3), 2003, pages 759-769. +*/ +class FALCOR_API MERLMixMaterial : public Material +{ + FALCOR_OBJECT(MERLMixMaterial) +public: + static ref create(ref pDevice, const std::string& name, const std::vector& paths) { - FALCOR_OBJECT(MERLMixMaterial) - public: - static ref create(ref pDevice, const std::string& name, const std::vector& paths) { return make_ref(pDevice, name, paths); } + return make_ref(pDevice, name, paths); + } - MERLMixMaterial(ref pDevice, const std::string& name, const std::vector& paths); + MERLMixMaterial(ref pDevice, const std::string& name, const std::vector& paths); - bool renderUI(Gui::Widgets& widget) override; - Material::UpdateFlags update(MaterialSystem* pOwner) override; - bool isEqual(const ref& pOther) const override; - MaterialDataBlob getDataBlob() const override { return prepareDataBlob(mData); } - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; - size_t getMaxBufferCount() const override { return 1; } + bool renderUI(Gui::Widgets& widget) override; + Material::UpdateFlags update(MaterialSystem* pOwner) override; + bool isEqual(const ref& pOther) const override; + MaterialDataBlob getDataBlob() const override { return prepareDataBlob(mData); } + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; + size_t getMaxBufferCount() const override { return 1; } - bool setTexture(const TextureSlot slot, const ref& pTexture) override; - void setDefaultTextureSampler(const ref& pSampler) override; - ref getDefaultTextureSampler() const override { return mpDefaultSampler; } + bool setTexture(const TextureSlot slot, const ref& pTexture) override; + void setDefaultTextureSampler(const ref& pSampler) override; + ref getDefaultTextureSampler() const override { return mpDefaultSampler; } - void setNormalMap(const ref& pNormalMap) { setTexture(TextureSlot::Normal, pNormalMap); } - ref getNormalMap() const { return getTexture(TextureSlot::Normal); } + void setNormalMap(const ref& pNormalMap) { setTexture(TextureSlot::Normal, pNormalMap); } + ref getNormalMap() const { return getTexture(TextureSlot::Normal); } - protected: - void updateNormalMapType(); - void updateIndexMapType(); +protected: + void updateNormalMapType(); + void updateIndexMapType(); - struct BRDFDesc - { - std::string name; ///< Name of the BRDF. This is the file basename without extension. - std::filesystem::path path; ///< Full path to the loaded BRDF file. - size_t byteOffset = 0; ///< Offset in bytes to where the BRDF data is stored in the shared data buffer. - size_t byteSize = 0; ///< Size in bytes of the BRDF data. + struct BRDFDesc + { + std::string name; ///< Name of the BRDF. This is the file basename without extension. + std::filesystem::path path; ///< Full path to the loaded BRDF file. + size_t byteOffset = 0; ///< Offset in bytes to where the BRDF data is stored in the shared data buffer. + size_t byteSize = 0; ///< Size in bytes of the BRDF data. - bool operator==(const BRDFDesc& rhs) const - { - return name == rhs.name && path == rhs.path; - } - }; + bool operator==(const BRDFDesc& rhs) const { return name == rhs.name && path == rhs.path; } + }; - std::vector mBRDFs; ///< List of loaded BRDFs. + std::vector mBRDFs; ///< List of loaded BRDFs. - MERLMixMaterialData mData; ///< Material parameters. - ref mpBRDFData; ///< GPU buffer holding all BRDF data as float3 arrays. - ref mpAlbedoLUT; ///< Precomputed albedo lookup table. - ref mpLUTSampler; ///< Sampler for accessing the LUT texture. - ref mpIndexSampler; ///< Sampler for accessing the index map. - ref mpDefaultSampler; - }; -} + MERLMixMaterialData mData; ///< Material parameters. + ref mpBRDFData; ///< GPU buffer holding all BRDF data as float3 arrays. + ref mpAlbedoLUT; ///< Precomputed albedo lookup table. + ref mpLUTSampler; ///< Sampler for accessing the LUT texture. + ref mpIndexSampler; ///< Sampler for accessing the index map. + ref mpDefaultSampler; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MERLMixMaterialData.slang b/Source/Falcor/Scene/Material/MERLMixMaterialData.slang index ead7c65379..3519417931 100644 --- a/Source/Falcor/Scene/Material/MERLMixMaterialData.slang +++ b/Source/Falcor/Scene/Material/MERLMixMaterialData.slang @@ -41,21 +41,21 @@ __exported import Scene.Material.MaterialData; BEGIN_NAMESPACE_FALCOR /** This is a host/device structure that describes a measured MERLMix material. -*/ + */ struct MERLMixMaterialData { // MaterialHeader (16B) is stored just before this struct in memory. - uint flags = 0; ///< Material flags and packed sampler IDs. See accessors below. - uint brdfCount = 0; ///< Number of loaded BRDFs. - uint byteStride = 0; ///< Stride in bytes between each BRDF in the data buffer. - uint bufferID = 0; ///< Buffer ID in material system where BRDF data is stored. - uint extraDataOffset = 0; ///< Offset in bytes to where extra data for sampling is stored. - uint extraDataStride = 0; ///< Stride in bytes between each struct of extra data in the data buffer. + uint flags = 0; ///< Material flags and packed sampler IDs. See accessors below. + uint brdfCount = 0; ///< Number of loaded BRDFs. + uint byteStride = 0; ///< Stride in bytes between each BRDF in the data buffer. + uint bufferID = 0; ///< Buffer ID in material system where BRDF data is stored. + uint extraDataOffset = 0; ///< Offset in bytes to where extra data for sampling is stored. + uint extraDataStride = 0; ///< Stride in bytes between each struct of extra data in the data buffer. // Texture handles (4B each). TextureHandle texNormalMap; TextureHandle texIndexMap; - TextureHandle texAlbedoLUT; ///< Texture handle for albedo LUT. + TextureHandle texAlbedoLUT; ///< Texture handle for albedo LUT. static constexpr uint kAlbedoLUTSize = 256; static constexpr uint kNormalMapTypeBits = 2; @@ -63,13 +63,22 @@ struct MERLMixMaterialData static constexpr uint kLUTSamplerIDOffset = kNormalMapTypeOffset + kNormalMapTypeBits; static constexpr uint kIndexSamplerIDOffset = kLUTSamplerIDOffset + MaterialHeader::kSamplerIDBits; - SETTER_DECL void setNormalMapType(NormalMapType type) { flags = PACK_BITS(kNormalMapTypeBits, kNormalMapTypeOffset, flags, (uint)type); } + SETTER_DECL void setNormalMapType(NormalMapType type) + { + flags = PACK_BITS(kNormalMapTypeBits, kNormalMapTypeOffset, flags, (uint)type); + } NormalMapType getNormalMapType() CONST_FUNCTION { return NormalMapType(EXTRACT_BITS(kNormalMapTypeBits, kNormalMapTypeOffset, flags)); } - SETTER_DECL void setLUTSamplerID(uint samplerID) { flags = PACK_BITS(MaterialHeader::kSamplerIDBits, kLUTSamplerIDOffset, flags, samplerID); } + SETTER_DECL void setLUTSamplerID(uint samplerID) + { + flags = PACK_BITS(MaterialHeader::kSamplerIDBits, kLUTSamplerIDOffset, flags, samplerID); + } uint getLUTSamplerID() CONST_FUNCTION { return EXTRACT_BITS(MaterialHeader::kSamplerIDBits, kLUTSamplerIDOffset, flags); } - SETTER_DECL void setIndexSamplerID(uint samplerID) { flags = PACK_BITS(MaterialHeader::kSamplerIDBits, kIndexSamplerIDOffset, flags, samplerID); } + SETTER_DECL void setIndexSamplerID(uint samplerID) + { + flags = PACK_BITS(MaterialHeader::kSamplerIDBits, kIndexSamplerIDOffset, flags, samplerID); + } uint getIndexSamplerID() CONST_FUNCTION { return EXTRACT_BITS(MaterialHeader::kSamplerIDBits, kIndexSamplerIDOffset, flags); } }; diff --git a/Source/Falcor/Scene/Material/Material.cpp b/Source/Falcor/Scene/Material/Material.cpp index 2ef909191d..c7a8753319 100644 --- a/Source/Falcor/Scene/Material/Material.cpp +++ b/Source/Falcor/Scene/Material/Material.cpp @@ -37,397 +37,417 @@ namespace Falcor { - namespace - { - static_assert(sizeof(TextureHandle) == 4); - static_assert(sizeof(MaterialHeader) == 16); - static_assert(sizeof(MaterialPayload) == 112); - static_assert(sizeof(MaterialDataBlob) == 128); - static_assert(static_cast(MaterialType::BuiltinCount) <= (1u << MaterialHeader::kMaterialTypeBits), "MaterialType count exceeds the maximum"); - static_assert(static_cast(AlphaMode::Count) <= (1u << MaterialHeader::kAlphaModeBits), "AlphaMode bit count exceeds the maximum"); - static_assert(static_cast(LobeType::All) < (1u << MaterialHeader::kLobeTypeBits), "LobeType bit count exceeds the maximum"); - static_assert(static_cast(TextureHandle::Mode::Count) <= (1u << TextureHandle::kModeBits), "TextureHandle::Mode bit count exceeds the maximum"); - static_assert(MaterialHeader::kTotalHeaderBitsX <= 32, "MaterialHeader bit count x exceeds the maximum"); - static_assert(MaterialHeader::kTotalHeaderBitsY <= 32, "MaterialHeader bit count y exceeds the maximum"); - static_assert(MaterialHeader::kTotalHeaderBitsZ <= 32, "MaterialHeader bit count z exceeds the maximum"); - static_assert(MaterialHeader::kTotalHeaderBitsW <= 32, "MaterialHeader bit count w exceeds the maximum"); - static_assert(MaterialHeader::kAlphaThresholdBits == 16, "MaterialHeader alpha threshold bit count must be 16"); - } - - bool operator==(const MaterialHeader& lhs, const MaterialHeader& rhs) - { - return all(lhs.packedData == rhs.packedData); - } +namespace +{ +static_assert(sizeof(TextureHandle) == 4); +static_assert(sizeof(MaterialHeader) == 16); +static_assert(sizeof(MaterialPayload) == 112); +static_assert(sizeof(MaterialDataBlob) == 128); +static_assert( + static_cast(MaterialType::BuiltinCount) <= (1u << MaterialHeader::kMaterialTypeBits), + "MaterialType count exceeds the maximum" +); +static_assert(static_cast(AlphaMode::Count) <= (1u << MaterialHeader::kAlphaModeBits), "AlphaMode bit count exceeds the maximum"); +static_assert(static_cast(LobeType::All) < (1u << MaterialHeader::kLobeTypeBits), "LobeType bit count exceeds the maximum"); +static_assert( + static_cast(TextureHandle::Mode::Count) <= (1u << TextureHandle::kModeBits), + "TextureHandle::Mode bit count exceeds the maximum" +); +static_assert(MaterialHeader::kTotalHeaderBitsX <= 32, "MaterialHeader bit count x exceeds the maximum"); +static_assert(MaterialHeader::kTotalHeaderBitsY <= 32, "MaterialHeader bit count y exceeds the maximum"); +static_assert(MaterialHeader::kTotalHeaderBitsZ <= 32, "MaterialHeader bit count z exceeds the maximum"); +static_assert(MaterialHeader::kTotalHeaderBitsW <= 32, "MaterialHeader bit count w exceeds the maximum"); +static_assert(MaterialHeader::kAlphaThresholdBits == 16, "MaterialHeader alpha threshold bit count must be 16"); +} // namespace + +bool operator==(const MaterialHeader& lhs, const MaterialHeader& rhs) +{ + return all(lhs.packedData == rhs.packedData); +} - Material::Material(ref pDevice, const std::string& name, MaterialType type) - : mpDevice(pDevice) - , mName(name) - { - mHeader.setMaterialType(type); - mHeader.setAlphaMode(AlphaMode::Opaque); - mHeader.setAlphaThreshold(0.5h); - mHeader.setActiveLobes(static_cast(LobeType::All)); - mHeader.setIoR(1.h); - } +Material::Material(ref pDevice, const std::string& name, MaterialType type) : mpDevice(pDevice), mName(name) +{ + mHeader.setMaterialType(type); + mHeader.setAlphaMode(AlphaMode::Opaque); + mHeader.setAlphaThreshold(0.5h); + mHeader.setActiveLobes(static_cast(LobeType::All)); + mHeader.setIoR(1.h); +} - bool Material::renderUI(Gui::Widgets& widget) - { - // We're re-using the material's update flags here to track changes. - // Cache the previous flag so we can restore it before returning. - UpdateFlags prevUpdates = mUpdates; - mUpdates = UpdateFlags::None; +bool Material::renderUI(Gui::Widgets& widget) +{ + // We're re-using the material's update flags here to track changes. + // Cache the previous flag so we can restore it before returning. + UpdateFlags prevUpdates = mUpdates; + mUpdates = UpdateFlags::None; - widget.text("Type: " + to_string(getType())); + widget.text("Type: " + to_string(getType())); - widget.text("Flags:"); - { - bool doubleSided = isDoubleSided(); - if (widget.checkbox("Double-sided", doubleSided)) setDoubleSided(doubleSided); + widget.text("Flags:"); + { + bool doubleSided = isDoubleSided(); + if (widget.checkbox("Double-sided", doubleSided)) + setDoubleSided(doubleSided); - bool thinSurface = isThinSurface(); - if (widget.checkbox("Thin surface", thinSurface)) setThinSurface(thinSurface); - } + bool thinSurface = isThinSurface(); + if (widget.checkbox("Thin surface", thinSurface)) + setThinSurface(thinSurface); + } - bool alphaTest = !isOpaque(); - widget.checkbox("Alpha test", alphaTest); - if (alphaTest) - { - // Show alpha threshold. This is not directly editable. - float alphaThreshold = getAlphaThreshold(); - widget.var("Alpha threshold", alphaThreshold); - } + bool alphaTest = !isOpaque(); + widget.checkbox("Alpha test", alphaTest); + if (alphaTest) + { + // Show alpha threshold. This is not directly editable. + float alphaThreshold = getAlphaThreshold(); + widget.var("Alpha threshold", alphaThreshold); + } - uint32_t nestedPriority = getNestedPriority(); - if (widget.var("Nested priority", nestedPriority, 0u, (1u << MaterialHeader::kNestedPriorityBits) - 1)) setNestedPriority(nestedPriority); + uint32_t nestedPriority = getNestedPriority(); + if (widget.var("Nested priority", nestedPriority, 0u, (1u << MaterialHeader::kNestedPriorityBits) - 1)) + setNestedPriority(nestedPriority); - // Restore update flags. - bool changed = mUpdates != UpdateFlags::None; - markUpdates(prevUpdates | mUpdates); + // Restore update flags. + bool changed = mUpdates != UpdateFlags::None; + markUpdates(prevUpdates | mUpdates); - return changed; - } + return changed; +} - void Material::setDoubleSided(bool doubleSided) +void Material::setDoubleSided(bool doubleSided) +{ + if (mHeader.isDoubleSided() != doubleSided) { - if (mHeader.isDoubleSided() != doubleSided) - { - mHeader.setDoubleSided(doubleSided); - markUpdates(UpdateFlags::DataChanged); - } + mHeader.setDoubleSided(doubleSided); + markUpdates(UpdateFlags::DataChanged); } +} - void Material::setThinSurface(bool thinSurface) +void Material::setThinSurface(bool thinSurface) +{ + if (mHeader.isThinSurface() != thinSurface) { - if (mHeader.isThinSurface() != thinSurface) - { - mHeader.setThinSurface(thinSurface); - markUpdates(UpdateFlags::DataChanged); - } + mHeader.setThinSurface(thinSurface); + markUpdates(UpdateFlags::DataChanged); } +} - void Material::setAlphaMode(AlphaMode alphaMode) - { - logWarning("Material '{}' of type '{}' does not support alpha. Ignoring call to setAlphaMode().", getName(), to_string(getType())); - } +void Material::setAlphaMode(AlphaMode alphaMode) +{ + logWarning("Material '{}' of type '{}' does not support alpha. Ignoring call to setAlphaMode().", getName(), to_string(getType())); +} - void Material::setAlphaThreshold(float alphaThreshold) - { - logWarning("Material '{}' of type '{}' does not support alpha. Ignoring call to setAlphaThreshold().", getName(), to_string(getType())); - } +void Material::setAlphaThreshold(float alphaThreshold) +{ + logWarning("Material '{}' of type '{}' does not support alpha. Ignoring call to setAlphaThreshold().", getName(), to_string(getType())); +} - void Material::setNestedPriority(uint32_t priority) +void Material::setNestedPriority(uint32_t priority) +{ + const uint32_t maxPriority = (1u << MaterialHeader::kNestedPriorityBits) - 1; + if (priority > maxPriority) { - const uint32_t maxPriority = (1u << MaterialHeader::kNestedPriorityBits) - 1; - if (priority > maxPriority) - { - logWarning("Requested nested priority {} for material '{}' is out of range. Clamping to {}.", priority, mName, maxPriority); - priority = maxPriority; - } - if (mHeader.getNestedPriority() != priority) - { - mHeader.setNestedPriority(priority); - markUpdates(UpdateFlags::DataChanged); - } + logWarning("Requested nested priority {} for material '{}' is out of range. Clamping to {}.", priority, mName, maxPriority); + priority = maxPriority; } - - void Material::setIndexOfRefraction(float IoR) + if (mHeader.getNestedPriority() != priority) { - if (mHeader.getIoR() != (float16_t)IoR) - { - mHeader.setIoR((float16_t)IoR); - markUpdates(UpdateFlags::DataChanged); - } + mHeader.setNestedPriority(priority); + markUpdates(UpdateFlags::DataChanged); } +} - const Material::TextureSlotInfo& Material::getTextureSlotInfo(const TextureSlot slot) const +void Material::setIndexOfRefraction(float IoR) +{ + if (mHeader.getIoR() != (float16_t)IoR) { - FALCOR_ASSERT((size_t)slot < mTextureSlotInfo.size()); - return mTextureSlotInfo[(size_t)slot]; + mHeader.setIoR((float16_t)IoR); + markUpdates(UpdateFlags::DataChanged); } +} + +const Material::TextureSlotInfo& Material::getTextureSlotInfo(const TextureSlot slot) const +{ + FALCOR_ASSERT((size_t)slot < mTextureSlotInfo.size()); + return mTextureSlotInfo[(size_t)slot]; +} - bool Material::hasTextureSlotData(const TextureSlot slot) const +bool Material::hasTextureSlotData(const TextureSlot slot) const +{ + FALCOR_ASSERT((size_t)slot < mTextureSlotInfo.size()); + return mTextureSlotData[(size_t)slot].pTexture != nullptr; +} + +bool Material::setTexture(const TextureSlot slot, const ref& pTexture) +{ + if (!hasTextureSlot(slot)) { - FALCOR_ASSERT((size_t)slot < mTextureSlotInfo.size()); - return mTextureSlotData[(size_t)slot].pTexture != nullptr; + logWarning("Material '{}' does not have texture slot '{}'. Ignoring call to setTexture().", getName(), to_string(slot)); + return false; } - bool Material::setTexture(const TextureSlot slot, const ref& pTexture) - { - if (!hasTextureSlot(slot)) - { - logWarning("Material '{}' does not have texture slot '{}'. Ignoring call to setTexture().", getName(), to_string(slot)); - return false; - } + if (pTexture == getTexture(slot)) + return false; - if (pTexture == getTexture(slot)) return false; + FALCOR_ASSERT((size_t)slot < mTextureSlotInfo.size()); + mTextureSlotData[(size_t)slot].pTexture = pTexture; - FALCOR_ASSERT((size_t)slot < mTextureSlotInfo.size()); - mTextureSlotData[(size_t)slot].pTexture = pTexture; + markUpdates(UpdateFlags::ResourcesChanged); + if (slot == TextureSlot::Emissive) + markUpdates(UpdateFlags::EmissiveChanged); - markUpdates(UpdateFlags::ResourcesChanged); - if (slot == TextureSlot::Emissive) - markUpdates(UpdateFlags::EmissiveChanged); + return true; +} - return true; - } +ref Material::getTexture(const TextureSlot slot) const +{ + if (!hasTextureSlot(slot)) + return nullptr; - ref Material::getTexture(const TextureSlot slot) const - { - if (!hasTextureSlot(slot)) return nullptr; + FALCOR_ASSERT((size_t)slot < mTextureSlotInfo.size()); + return mTextureSlotData[(size_t)slot].pTexture; +} - FALCOR_ASSERT((size_t)slot < mTextureSlotInfo.size()); - return mTextureSlotData[(size_t)slot].pTexture; +bool Material::loadTexture(TextureSlot slot, const std::filesystem::path& path, bool useSrgb) +{ + if (!hasTextureSlot(slot)) + { + logWarning("Material '{}' does not have texture slot '{}'. Ignoring call to loadTexture().", getName(), to_string(slot)); + return false; } - bool Material::loadTexture(TextureSlot slot, const std::filesystem::path& path, bool useSrgb) + auto texture = Texture::createFromFile(mpDevice, path, true, useSrgb && getTextureSlotInfo(slot).srgb); + if (texture) { - if (!hasTextureSlot(slot)) - { - logWarning("Material '{}' does not have texture slot '{}'. Ignoring call to loadTexture().", getName(), to_string(slot)); - return false; - } + setTexture(slot, texture); + // Wait for GPU to finish to prevent the upload heap from growing too large. Doing so after + // every texture creation is overly conservative, and will likely lead to performance issues + // due to the forced CPU/GPU sync. + mpDevice->wait(); + return true; + } - auto texture = Texture::createFromFile(mpDevice, path, true, useSrgb && getTextureSlotInfo(slot).srgb); - if (texture) - { - setTexture(slot, texture); - // Wait for GPU to finish to prevent the upload heap from growing too large. Doing so after - // every texture creation is overly conservative, and will likely lead to performance issues - // due to the forced CPU/GPU sync. - mpDevice->wait(); - return true; - } + return false; +} - return false; +uint2 Material::getMaxTextureDimensions() const +{ + uint2 dim = uint2(0); + for (uint32_t i = 0; i < (uint32_t)TextureSlot::Count; i++) + { + auto pTexture = getTexture((TextureSlot)i); + if (pTexture) + dim = max(dim, uint2(pTexture->getWidth(), pTexture->getHeight())); } + return dim; +} - uint2 Material::getMaxTextureDimensions() const +void Material::setTextureTransform(const Transform& textureTransform) +{ + mTextureTransform = textureTransform; +} + +ref Material::toBasicMaterial() +{ + if (mHeader.isBasicMaterial()) { - uint2 dim = uint2(0); - for (uint32_t i = 0; i < (uint32_t)TextureSlot::Count; i++) - { - auto pTexture = getTexture((TextureSlot)i); - if (pTexture) dim = max(dim, uint2(pTexture->getWidth(), pTexture->getHeight())); - } - return dim; + FALCOR_ASSERT(dynamic_ref_cast(ref(this))); + return static_ref_cast(ref(this)); } + return nullptr; +} + +void Material::markUpdates(UpdateFlags updates) +{ + // Mark updates locally in this material. + mUpdates |= updates; + + // Mark updates globally across all materials. + if (mUpdateCallback) + mUpdateCallback(updates); +} - void Material::setTextureTransform(const Transform& textureTransform) +void Material::updateTextureHandle(MaterialSystem* pOwner, const ref& pTexture, TextureHandle& handle) +{ + TextureHandle prevHandle = handle; + + // Update the given texture handle. + if (pTexture) { - mTextureTransform = textureTransform; + auto h = pOwner->getTextureManager().addTexture(pTexture); + FALCOR_ASSERT(h); + handle = h.toGpuHandle(); } - - ref Material::toBasicMaterial() + else { - if (mHeader.isBasicMaterial()) - { - FALCOR_ASSERT(dynamic_ref_cast(ref(this))); - return static_ref_cast(ref(this)); - } - return nullptr; + handle.setMode(TextureHandle::Mode::Uniform); + handle.setUdimEnabled(false); } + FALCOR_ASSERT(!handle.getUdimEnabled()); - void Material::markUpdates(UpdateFlags updates) - { - // Mark updates locally in this material. - mUpdates |= updates; + if (handle != prevHandle) + mUpdates |= Material::UpdateFlags::DataChanged; +} - // Mark updates globally across all materials. - if (mUpdateCallback) mUpdateCallback(updates); - } +void Material::updateTextureHandle(MaterialSystem* pOwner, const TextureSlot slot, TextureHandle& handle) +{ + auto pTexture = getTexture(slot); + updateTextureHandle(pOwner, pTexture, handle); - void Material::updateTextureHandle(MaterialSystem* pOwner, const ref& pTexture, TextureHandle& handle) + // The base color texture potentially contains the alpha mask in it's alpha channel. + // Set it as the alpha texture handle in the material header. + if (slot == TextureSlot::BaseColor) { - TextureHandle prevHandle = handle; + mHeader.setAlphaTextureHandle(handle); + mUpdates |= Material::UpdateFlags::DataChanged; + } +} - // Update the given texture handle. - if (pTexture) - { - auto h = pOwner->getTextureManager().addTexture(pTexture); - FALCOR_ASSERT(h); - handle = h.toGpuHandle(); - } - else - { - handle.setMode(TextureHandle::Mode::Uniform); - handle.setUdimEnabled(false); - } - FALCOR_ASSERT(!handle.getUdimEnabled()); +void Material::updateDefaultTextureSamplerID(MaterialSystem* pOwner, const ref& pSampler) +{ + const uint32_t samplerID = pOwner->addTextureSampler(pSampler); - if (handle != prevHandle) mUpdates |= Material::UpdateFlags::DataChanged; + if (mHeader.getDefaultTextureSamplerID() != samplerID) + { + mHeader.setDefaultTextureSamplerID(samplerID); + mUpdates |= Material::UpdateFlags::DataChanged; } +} - void Material::updateTextureHandle(MaterialSystem* pOwner, const TextureSlot slot, TextureHandle& handle) - { - auto pTexture = getTexture(slot); - updateTextureHandle(pOwner, pTexture, handle); +bool Material::isBaseEqual(const Material& other) const +{ + // This function compares all data in the base class between two materials *except* the name. + // It's a separate helper to ensure isEqual() is pure virtual and must be implemented in all derived classes. - // The base color texture potentially contains the alpha mask in it's alpha channel. - // Set it as the alpha texture handle in the material header. - if (slot == TextureSlot::BaseColor) - { - mHeader.setAlphaTextureHandle(handle); - mUpdates |= Material::UpdateFlags::DataChanged; - } - } + if (mHeader != other.mHeader) + return false; + if (mTextureTransform != other.mTextureTransform) + return false; - void Material::updateDefaultTextureSamplerID(MaterialSystem* pOwner, const ref& pSampler) + FALCOR_ASSERT(mTextureSlotInfo.size() == mTextureSlotData.size()); + for (size_t i = 0; i < mTextureSlotInfo.size(); i++) { - const uint32_t samplerID = pOwner->addTextureSampler(pSampler); - - if (mHeader.getDefaultTextureSamplerID() != samplerID) + // Compare texture slots. + // These checks are a bit redundant since identical material types are currently + // guaranteed to have the same set of slots, but this is future-proof if that changes. + auto slot = (TextureSlot)i; + if (hasTextureSlot(slot) != other.hasTextureSlot(slot)) + return false; + if (hasTextureSlot(slot)) { - mHeader.setDefaultTextureSamplerID(samplerID); - mUpdates |= Material::UpdateFlags::DataChanged; + if (mTextureSlotInfo[i] != other.mTextureSlotInfo[i]) + return false; + if (mTextureSlotData[i] != other.mTextureSlotData[i]) + return false; } } - bool Material::isBaseEqual(const Material& other) const - { - // This function compares all data in the base class between two materials *except* the name. - // It's a separate helper to ensure isEqual() is pure virtual and must be implemented in all derived classes. - - if (mHeader != other.mHeader) return false; - if (mTextureTransform != other.mTextureTransform) return false; + return true; +} - FALCOR_ASSERT(mTextureSlotInfo.size() == mTextureSlotData.size()); - for (size_t i = 0; i < mTextureSlotInfo.size(); i++) +NormalMapType Material::detectNormalMapType(const ref& pNormalMap) +{ + NormalMapType type = NormalMapType::None; + if (pNormalMap != nullptr) + { + switch (getFormatChannelCount(pNormalMap->getFormat())) { - // Compare texture slots. - // These checks are a bit redundant since identical material types are currently - // guaranteed to have the same set of slots, but this is future-proof if that changes. - auto slot = (TextureSlot)i; - if (hasTextureSlot(slot) != other.hasTextureSlot(slot)) return false; - if (hasTextureSlot(slot)) - { - if (mTextureSlotInfo[i] != other.mTextureSlotInfo[i]) return false; - if (mTextureSlotData[i] != other.mTextureSlotData[i]) return false; - } + case 2: + type = NormalMapType::RG; + break; + case 3: + case 4: // Some texture formats don't support RGB, only RGBA. We have no use for the alpha channel in the normal map. + type = NormalMapType::RGB; + break; + default: + logWarning("Unsupported normal map format: ", to_string(pNormalMap->getFormat())); } - - return true; } + return type; +} - NormalMapType Material::detectNormalMapType(const ref& pNormalMap) +FALCOR_SCRIPT_BINDING(Material) +{ + using namespace pybind11::literals; + + FALCOR_SCRIPT_BINDING_DEPENDENCY(Transform) + FALCOR_SCRIPT_BINDING_DEPENDENCY(Texture) + + pybind11::enum_ materialType(m, "MaterialType"); + materialType.value("Standard", MaterialType::Standard); + materialType.value("Cloth", MaterialType::Cloth); + materialType.value("Hair", MaterialType::Hair); + materialType.value("MERL", MaterialType::MERL); + materialType.value("MERLMix", MaterialType::MERLMix); + materialType.value("PBRTDiffuse", MaterialType::PBRTDiffuse); + materialType.value("PBRTDiffuseTransmission", MaterialType::PBRTDiffuseTransmission); + materialType.value("PBRTConductor", MaterialType::PBRTConductor); + materialType.value("PBRTDielectric", MaterialType::PBRTDielectric); + materialType.value("PBRTCoatedConductor", MaterialType::PBRTCoatedConductor); + materialType.value("PBRTCoatedDiffuse", MaterialType::PBRTCoatedDiffuse); + materialType.value("RGL", MaterialType::RGL); + + pybind11::enum_ alphaMode(m, "AlphaMode"); + alphaMode.value("Opaque", AlphaMode::Opaque); + alphaMode.value("Mask", AlphaMode::Mask); + + pybind11::enum_ textureSlot(m, "MaterialTextureSlot"); + textureSlot.value("BaseColor", Material::TextureSlot::BaseColor); + textureSlot.value("Specular", Material::TextureSlot::Specular); + textureSlot.value("Emissive", Material::TextureSlot::Emissive); + textureSlot.value("Normal", Material::TextureSlot::Normal); + textureSlot.value("Transmission", Material::TextureSlot::Transmission); + textureSlot.value("Displacement", Material::TextureSlot::Displacement); + textureSlot.value("Index", Material::TextureSlot::Index); + + // Register Material base class as IMaterial in python to allow deprecated script syntax. + // TODO: Remove workaround when all scripts have been updated to create derived Material classes. + pybind11::class_> material(m, "IMaterial"); // PYTHONDEPRECATED + material.def_property_readonly("type", &Material::getType); + material.def_property("name", &Material::getName, &Material::setName); + material.def_property("doubleSided", &Material::isDoubleSided, &Material::setDoubleSided); + material.def_property("thinSurface", &Material::isThinSurface, &Material::setThinSurface); + material.def_property_readonly("emissive", &Material::isEmissive); + material.def_property("alphaMode", &Material::getAlphaMode, &Material::setAlphaMode); + material.def_property("alphaThreshold", &Material::getAlphaThreshold, &Material::setAlphaThreshold); + material.def_property("nestedPriority", &Material::getNestedPriority, &Material::setNestedPriority); + material.def_property( + "textureTransform", pybind11::overload_cast<>(&Material::getTextureTransform, pybind11::const_), &Material::setTextureTransform + ); + + material.def("setTexture", &Material::setTexture, "slot"_a, "texture"_a); + material.def("getTexture", &Material::getTexture, "slot"_a); + auto loadTexture = [&](Material& self, Material::TextureSlot slot, const std::filesystem::path& path, bool useSrgb) + { return self.loadTexture(slot, getActiveAssetResolver().resolvePath(path), useSrgb); }; + material.def("loadTexture", loadTexture, "slot"_a, "path"_a, "useSrgb"_a = true); // PYTHONDEPRECATED + material.def("load_texture", loadTexture, "slot"_a, "path"_a, "use_srgb"_a = true); // PYTHONDEPRECATED + material.def("clearTexture", &Material::clearTexture, "slot"_a); + material.def("setRoughnessMollification", &Material::setRoughnessMollification, "value"_a); + + auto getMaterialParamLayoutDict = [&](MaterialType type) -> pybind11::dict + { + MaterialParamLayout layout = getMaterialParamLayout(type); + pybind11::dict dict; + for (const auto& entry : layout) + dict[entry.pythonName] = pybind11::dict("offset"_a = entry.offset, "size"_a = entry.size); + return dict; + }; + + auto getMaterialParamLayoutsDict = [getMaterialParamLayoutDict]() -> pybind11::dict { - NormalMapType type = NormalMapType::None; - if (pNormalMap != nullptr) + pybind11::dict dict; + for (uint32_t i = 0; i < uint32_t(MaterialType::BuiltinCount); ++i) { - switch (getFormatChannelCount(pNormalMap->getFormat())) - { - case 2: - type = NormalMapType::RG; - break; - case 3: - case 4: // Some texture formats don't support RGB, only RGBA. We have no use for the alpha channel in the normal map. - type = NormalMapType::RGB; - break; - default: - logWarning("Unsupported normal map format: ", to_string(pNormalMap->getFormat())); - } + auto type = static_cast(i); + auto name = to_string(type); + dict[name.c_str()] = getMaterialParamLayoutDict(type); } - return type; - } + return dict; + }; - FALCOR_SCRIPT_BINDING(Material) - { - using namespace pybind11::literals; - - FALCOR_SCRIPT_BINDING_DEPENDENCY(Transform) - FALCOR_SCRIPT_BINDING_DEPENDENCY(Texture) - - pybind11::enum_ materialType(m, "MaterialType"); - materialType.value("Standard", MaterialType::Standard); - materialType.value("Cloth", MaterialType::Cloth); - materialType.value("Hair", MaterialType::Hair); - materialType.value("MERL", MaterialType::MERL); - materialType.value("MERLMix", MaterialType::MERLMix); - materialType.value("PBRTDiffuse", MaterialType::PBRTDiffuse); - materialType.value("PBRTDiffuseTransmission", MaterialType::PBRTDiffuseTransmission); - materialType.value("PBRTConductor", MaterialType::PBRTConductor); - materialType.value("PBRTDielectric", MaterialType::PBRTDielectric); - materialType.value("PBRTCoatedConductor", MaterialType::PBRTCoatedConductor); - materialType.value("PBRTCoatedDiffuse", MaterialType::PBRTCoatedDiffuse); - materialType.value("RGL", MaterialType::RGL); - - pybind11::enum_ alphaMode(m, "AlphaMode"); - alphaMode.value("Opaque", AlphaMode::Opaque); - alphaMode.value("Mask", AlphaMode::Mask); - - pybind11::enum_ textureSlot(m, "MaterialTextureSlot"); - textureSlot.value("BaseColor", Material::TextureSlot::BaseColor); - textureSlot.value("Specular", Material::TextureSlot::Specular); - textureSlot.value("Emissive", Material::TextureSlot::Emissive); - textureSlot.value("Normal", Material::TextureSlot::Normal); - textureSlot.value("Transmission", Material::TextureSlot::Transmission); - textureSlot.value("Displacement", Material::TextureSlot::Displacement); - textureSlot.value("Index", Material::TextureSlot::Index); - - // Register Material base class as IMaterial in python to allow deprecated script syntax. - // TODO: Remove workaround when all scripts have been updated to create derived Material classes. - pybind11::class_> material(m, "IMaterial"); // PYTHONDEPRECATED - material.def_property_readonly("type", &Material::getType); - material.def_property("name", &Material::getName, &Material::setName); - material.def_property("doubleSided", &Material::isDoubleSided, &Material::setDoubleSided); - material.def_property("thinSurface", &Material::isThinSurface, &Material::setThinSurface); - material.def_property_readonly("emissive", &Material::isEmissive); - material.def_property("alphaMode", &Material::getAlphaMode, &Material::setAlphaMode); - material.def_property("alphaThreshold", &Material::getAlphaThreshold, &Material::setAlphaThreshold); - material.def_property("nestedPriority", &Material::getNestedPriority, &Material::setNestedPriority); - material.def_property("textureTransform", pybind11::overload_cast<>(&Material::getTextureTransform, pybind11::const_), &Material::setTextureTransform); - - material.def("setTexture", &Material::setTexture, "slot"_a, "texture"_a); - material.def("getTexture", &Material::getTexture, "slot"_a); - auto loadTexture = [&](Material& self, Material::TextureSlot slot, const std::filesystem::path& path, bool useSrgb) { - return self.loadTexture(slot, getActiveAssetResolver().resolvePath(path), useSrgb); - }; - material.def("loadTexture", loadTexture, "slot"_a, "path"_a, "useSrgb"_a = true); // PYTHONDEPRECATED - material.def("load_texture", loadTexture, "slot"_a, "path"_a, "use_srgb"_a = true); // PYTHONDEPRECATED - material.def("clearTexture", &Material::clearTexture, "slot"_a); - material.def("setRoughnessMollification", &Material::setRoughnessMollification, "value"_a); - - auto getMaterialParamLayoutDict = [&](MaterialType type) -> pybind11::dict { - MaterialParamLayout layout = getMaterialParamLayout(type); - pybind11::dict dict; - for (const auto& entry : layout) - dict[entry.pythonName] = pybind11::dict("offset"_a = entry.offset, "size"_a = entry.size); - return dict; - }; - - auto getMaterialParamLayoutsDict = [getMaterialParamLayoutDict]() -> pybind11::dict { - pybind11::dict dict; - for (uint32_t i = 0; i < uint32_t(MaterialType::BuiltinCount); ++i) - { - auto type = static_cast(i); - auto name = to_string(type); - dict[name.c_str()] = getMaterialParamLayoutDict(type); - } - return dict; - }; - - material.attr("PARAM_COUNT") = SerializedMaterialParams::kParamCount; - - m.def("get_material_param_layout", getMaterialParamLayoutDict, "type"_a); - m.attr("MATERIAL_PARAM_LAYOUTS") = getMaterialParamLayoutsDict(); - } + material.attr("PARAM_COUNT") = SerializedMaterialParams::kParamCount; + + m.def("get_material_param_layout", getMaterialParamLayoutDict, "type"_a); + m.attr("MATERIAL_PARAM_LAYOUTS") = getMaterialParamLayoutsDict(); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/Material.h b/Source/Falcor/Scene/Material/Material.h index f30fc1971c..679d77361b 100644 --- a/Source/Falcor/Scene/Material/Material.h +++ b/Source/Falcor/Scene/Material/Material.h @@ -49,364 +49,370 @@ namespace Falcor { - class MaterialSystem; - class BasicMaterial; +class MaterialSystem; +class BasicMaterial; - /** Abstract base class for materials. +/** Abstract base class for materials. + */ +class FALCOR_API Material : public Object +{ + FALCOR_OBJECT(Material) +public: + /** Flags indicating if and what was updated in the material. + */ + enum class UpdateFlags : uint32_t + { + None = 0x0, ///< Nothing updated. + CodeChanged = 0x1, ///< Material shader code changed. + DataChanged = 0x2, ///< Material data (parameters) changed. + ResourcesChanged = 0x4, ///< Material resources (textures, buffers, samplers) changed. + DisplacementChanged = 0x8, ///< Displacement mapping parameters changed (only for materials that support displacement). + EmissiveChanged = 0x10, ///< Material emissive properties changed. + }; + + /** Texture slots available for use. + A material does not need to expose/bind all slots. */ - class FALCOR_API Material : public Object + enum class TextureSlot { - FALCOR_OBJECT(Material) - public: - /** Flags indicating if and what was updated in the material. - */ - enum class UpdateFlags : uint32_t - { - None = 0x0, ///< Nothing updated. - CodeChanged = 0x1, ///< Material shader code changed. - DataChanged = 0x2, ///< Material data (parameters) changed. - ResourcesChanged = 0x4, ///< Material resources (textures, buffers, samplers) changed. - DisplacementChanged = 0x8, ///< Displacement mapping parameters changed (only for materials that support displacement). - EmissiveChanged = 0x10, ///< Material emissive properties changed. - }; - - /** Texture slots available for use. - A material does not need to expose/bind all slots. - */ - enum class TextureSlot - { - BaseColor, - Specular, - Emissive, - Normal, - Transmission, - Displacement, - Index, // For MERLMix material - - Count // Must be last - }; - - struct TextureSlotInfo - { - std::string name; ///< Name of texture slot. - TextureChannelFlags mask = TextureChannelFlags::None; ///< Mask of enabled texture channels. - bool srgb = false; ///< True if texture should be loaded in sRGB space. - - bool isEnabled() const { return mask != TextureChannelFlags::None; } - bool hasChannel(TextureChannelFlags channel) const { return is_set(mask, channel); } - bool operator==(const TextureSlotInfo& rhs) const { return name == rhs.name && mask == rhs.mask && srgb == rhs.srgb; } - bool operator!=(const TextureSlotInfo& rhs) const { return !((*this) == rhs); } - }; - - struct TextureSlotData - { - ref pTexture; ///< Texture bound to texture slot. - - bool hasData() const { return pTexture != nullptr; } - bool operator==(const TextureSlotData& rhs) const { return pTexture == rhs.pTexture; } - bool operator!=(const TextureSlotData& rhs) const { return !((*this) == rhs); } - }; - - struct TextureOptimizationStats - { - std::array texturesRemoved = {}; - size_t disabledAlpha = 0; - size_t constantBaseColor = 0; - size_t constantNormalMaps = 0; - }; - - virtual ~Material() = default; - - /** Render the UI. - \return True if the material was modified. - */ - virtual bool renderUI(Gui::Widgets& widget); - - /** Update material. This prepares the material for rendering. - \param[in] pOwner The material system that this material is used with. - \return Updates since last call to update(). - */ - virtual Material::UpdateFlags update(MaterialSystem* pOwner) = 0; - - /** Set the material name. - */ - virtual void setName(const std::string& name) { mName = name; } - - /** Get the material name. - */ - virtual const std::string& getName() const { return mName; } - - /** Get the material type. - */ - virtual MaterialType getType() const { return mHeader.getMaterialType(); } - - /** Returns true if the material is opaque. - */ - virtual bool isOpaque() const { return getAlphaMode() == AlphaMode::Opaque; } - - /** Returns true if the material has a displacement map. - */ - virtual bool isDisplaced() const { return false; } - - /** Returns true if the material is emissive. - */ - virtual bool isEmissive() const { return mHeader.isEmissive(); } - - /** Returns true if the material is dynamic. - Dynamic materials are updated every frame, otherwise `update()` is called reactively upon changes. - */ - virtual bool isDynamic() const { return false; } - - /** Compares material to another material. - \param[in] pOther Other material. - \return true if all materials properties *except* the name are identical. - */ - virtual bool isEqual(const ref& pOther) const = 0; - - /** Set the double-sided flag. This flag doesn't affect the cull state, just the shading. - */ - virtual void setDoubleSided(bool doubleSided); - - /** Returns true if the material is double-sided. - */ - virtual bool isDoubleSided() const { return mHeader.isDoubleSided(); } - - /** Set the thin surface flag. - */ - virtual void setThinSurface(bool thinSurface); - - /** Returns true if the material is a thin surface. - */ - virtual bool isThinSurface() const { return mHeader.isThinSurface(); } - - /** Set the alpha mode. - */ - virtual void setAlphaMode(AlphaMode alphaMode); - - /** Get the alpha mode. - */ - virtual AlphaMode getAlphaMode() const { return mHeader.getAlphaMode(); } - - /** Set the alpha threshold. The threshold is only used when alpha mode != AlphaMode::Opaque. - */ - virtual void setAlphaThreshold(float alphaThreshold); - - /** Get the alpha threshold. - */ - virtual float getAlphaThreshold() const { return (float)mHeader.getAlphaThreshold(); } - - /** Get the alpha mask texture handle. - */ - virtual TextureHandle getAlphaTextureHandle() const { return mHeader.getAlphaTextureHandle(); } - - /** Set the nested priority used for nested dielectrics. - */ - virtual void setNestedPriority(uint32_t priority); - - /** Get the nested priority used for nested dielectrics. - \return Nested priority, with 0 reserved for the highest possible priority. - */ - virtual uint32_t getNestedPriority() const { return mHeader.getNestedPriority(); } - - /** Set the index of refraction. - */ - virtual void setIndexOfRefraction(float IoR); - - /** Get the index of refraction. - */ - virtual float getIndexOfRefraction() const { return (float)mHeader.getIoR(); } - - /** Get information about a texture slot. - \param[in] slot The texture slot. - \return Info about the slot. If the slot doesn't exist isEnabled() returns false. - */ - virtual const TextureSlotInfo& getTextureSlotInfo(const TextureSlot slot) const; - - /** Check if material has a given texture slot. - \param[in] slot The texture slot. - \return True if the texture slot exists. Use getTexture() to check if a texture is bound. - */ - virtual bool hasTextureSlot(const TextureSlot slot) const { return getTextureSlotInfo(slot).isEnabled(); } - - /** Set one of the available texture slots. - The call is ignored with a warning if the slot doesn't exist. - \param[in] slot The texture slot. - \param[in] pTexture The texture. - \return True if the texture slot was changed, false otherwise. - */ - virtual bool setTexture(const TextureSlot slot, const ref& pTexture); - - /** Load one of the available texture slots. - The call is ignored with a warning if the slot doesn't exist. - \param[in] The texture slot. - \param[in] path Path to load texture from. - \param[in] useSrgb Load texture as sRGB format. - \return True if the texture was successfully loaded, false otherwise. - */ - virtual bool loadTexture(const TextureSlot slot, const std::filesystem::path& path, bool useSrgb = true); - - /** Clear one of the available texture slots. - The call is ignored with a warning if the slot doesn't exist. - \param[in] The texture slot. - */ - virtual void clearTexture(const TextureSlot slot) { setTexture(slot, nullptr); } - - /** Get one of the available texture slots. - \param[in] The texture slot. - \return Texture object if bound, or nullptr if unbound or slot doesn't exist. - */ - virtual ref getTexture(const TextureSlot slot) const; - - /** Optimize texture usage for the given texture slot. - This function may replace constant textures by uniform material parameters etc. - \param[in] slot The texture slot. - \param[in] texInfo Information about the texture bound to this slot. - \param[out] stats Optimization stats passed back to the caller. - */ - virtual void optimizeTexture(const TextureSlot slot, const TextureAnalyzer::Result& texInfo, TextureOptimizationStats& stats) {} - - /** Return the maximum dimensions of the bound textures. - */ - virtual uint2 getMaxTextureDimensions() const; - - /** Set the default texture sampler for the material. - */ - virtual void setDefaultTextureSampler(const ref& pSampler) {} - - /** Get the default texture sampler for the material. - */ - virtual ref getDefaultTextureSampler() const { return nullptr; } - - /** Set the material texture transform. - */ - virtual void setTextureTransform(const Transform& texTransform); - - /** Get a reference to the material texture transform. - */ - virtual Transform& getTextureTransform() { return mTextureTransform; } - - /** Get the material texture transform. - */ - virtual const Transform& getTextureTransform() const { return mTextureTransform; } - - /** Get a reference to the material header data. - */ - virtual const MaterialHeader& getHeader() const { return mHeader; } - - /** Get the material data blob for uploading to the GPU. - */ - virtual MaterialDataBlob getDataBlob() const = 0; - - /** Get shader modules for the material. - The shader modules must be added to any program using the material. - \return List of shader modules. - */ - virtual ProgramDesc::ShaderModuleList getShaderModules() const = 0; - - /** Get type conformances for the material. - The type conformances must be set on any program using the material. - */ - virtual TypeConformanceList getTypeConformances() const = 0; - - /** Get shader defines for the material. - The defines must be set on any program using the material. - */ - virtual DefineList getDefines() const { return {}; } - - /** Get the number of buffers used by this material. - */ - virtual size_t getMaxBufferCount() const { return 0; } - - /** Returns the maximum number of textures this material will use. - By default we use the number of texture slots. The reason for this is that, - for now, once the MaterialSystem has been set up with some number of texture slots, - it is not possible to allocate more. This limitation will be lifted in the future. - */ - virtual size_t getMaxTextureCount() const { return (size_t)Material::TextureSlot::Count; } - - /** Get the number of 3D textures used by this material. - */ - virtual size_t getMaxTexture3DCount() const { return 0; } - - // Temporary convenience function to downcast Material to BasicMaterial. - // This is because a large portion of the interface hasn't been ported to the Material base class yet. - // TODO: Remove this helper later - ref toBasicMaterial(); - - /** Size of the material instance the material produces. - Used to set `anyValueSize` on `IMaterialInstance` above the default (128B), for exceptionally large materials. - Large material instances can have a singificant performance impact. - */ - virtual size_t getMaterialInstanceByteSize() const { return 128; } - - virtual const MaterialParamLayout& getParamLayout() const { FALCOR_THROW("Material does not have a parameter layout."); } - virtual SerializedMaterialParams serializeParams() const { FALCOR_THROW("Material does not support serializing parameters."); } - virtual void deserializeParams(const SerializedMaterialParams& params) { FALCOR_THROW("Material does not support deserializing parameters."); } - - /** Set roughness mollification factor. - * Mollification smooths over highly glossy lobes in the distribution. - * The factor range from 0 (no mollification) to 1 (maximum mollification). - */ - virtual void setRoughnessMollification( float factor ) {}; - - protected: - Material(ref pDevice, const std::string& name, MaterialType type); - - using UpdateCallback = std::function; - void registerUpdateCallback(const UpdateCallback& updateCallback) { mUpdateCallback = updateCallback; } - void markUpdates(UpdateFlags updates); - bool hasTextureSlotData(const TextureSlot slot) const; - void updateTextureHandle(MaterialSystem* pOwner, const ref& pTexture, TextureHandle& handle); - void updateTextureHandle(MaterialSystem* pOwner, const TextureSlot slot, TextureHandle& handle); - void updateDefaultTextureSamplerID(MaterialSystem* pOwner, const ref& pSampler); - bool isBaseEqual(const Material& other) const; - - static NormalMapType detectNormalMapType(const ref& pNormalMap); - - template - MaterialDataBlob prepareDataBlob(const T& data) const - { - MaterialDataBlob blob = {}; - blob.header = mHeader; - static_assert(sizeof(mHeader) + sizeof(data) <= sizeof(blob)); - static_assert(offsetof(MaterialDataBlob, payload) == sizeof(MaterialHeader)); - std::memcpy(&blob.payload, &data, sizeof(data)); - return blob; - } - - ref mpDevice; - - std::string mName; ///< Name of the material. - MaterialHeader mHeader; ///< Material header data available in all material types. - Transform mTextureTransform; ///< Texture transform. This is currently applied at load time by pre-transforming the texture coordinates. - - std::array mTextureSlotInfo; ///< Information about texture slots. - std::array mTextureSlotData; ///< Data bound to texture slots. Only enabled slots can have any data. - - mutable UpdateFlags mUpdates = UpdateFlags::None; - UpdateCallback mUpdateCallback; ///< Callback to track updates with the material system this material is used with. - - friend class MaterialSystem; - friend class SceneCache; + BaseColor, + Specular, + Emissive, + Normal, + Transmission, + Displacement, + Index, // For MERLMix material + + Count // Must be last }; - inline std::string to_string(Material::TextureSlot slot) + struct TextureSlotInfo { - switch (slot) - { -#define tostr(a) case Material::TextureSlot::a: return #a; - tostr(BaseColor); - tostr(Specular); - tostr(Emissive); - tostr(Normal); - tostr(Transmission); - tostr(Displacement); - tostr(Index); -#undef tostr - default: - FALCOR_THROW("Invalid texture slot"); - } + std::string name; ///< Name of texture slot. + TextureChannelFlags mask = TextureChannelFlags::None; ///< Mask of enabled texture channels. + bool srgb = false; ///< True if texture should be loaded in sRGB space. + + bool isEnabled() const { return mask != TextureChannelFlags::None; } + bool hasChannel(TextureChannelFlags channel) const { return is_set(mask, channel); } + bool operator==(const TextureSlotInfo& rhs) const { return name == rhs.name && mask == rhs.mask && srgb == rhs.srgb; } + bool operator!=(const TextureSlotInfo& rhs) const { return !((*this) == rhs); } + }; + + struct TextureSlotData + { + ref pTexture; ///< Texture bound to texture slot. + + bool hasData() const { return pTexture != nullptr; } + bool operator==(const TextureSlotData& rhs) const { return pTexture == rhs.pTexture; } + bool operator!=(const TextureSlotData& rhs) const { return !((*this) == rhs); } + }; + + struct TextureOptimizationStats + { + std::array texturesRemoved = {}; + size_t disabledAlpha = 0; + size_t constantBaseColor = 0; + size_t constantNormalMaps = 0; + }; + + virtual ~Material() = default; + + /** Render the UI. + \return True if the material was modified. + */ + virtual bool renderUI(Gui::Widgets& widget); + + /** Update material. This prepares the material for rendering. + \param[in] pOwner The material system that this material is used with. + \return Updates since last call to update(). + */ + virtual Material::UpdateFlags update(MaterialSystem* pOwner) = 0; + + /** Set the material name. + */ + virtual void setName(const std::string& name) { mName = name; } + + /** Get the material name. + */ + virtual const std::string& getName() const { return mName; } + + /** Get the material type. + */ + virtual MaterialType getType() const { return mHeader.getMaterialType(); } + + /** Returns true if the material is opaque. + */ + virtual bool isOpaque() const { return getAlphaMode() == AlphaMode::Opaque; } + + /** Returns true if the material has a displacement map. + */ + virtual bool isDisplaced() const { return false; } + + /** Returns true if the material is emissive. + */ + virtual bool isEmissive() const { return mHeader.isEmissive(); } + + /** Returns true if the material is dynamic. + Dynamic materials are updated every frame, otherwise `update()` is called reactively upon changes. + */ + virtual bool isDynamic() const { return false; } + + /** Compares material to another material. + \param[in] pOther Other material. + \return true if all materials properties *except* the name are identical. + */ + virtual bool isEqual(const ref& pOther) const = 0; + + /** Set the double-sided flag. This flag doesn't affect the cull state, just the shading. + */ + virtual void setDoubleSided(bool doubleSided); + + /** Returns true if the material is double-sided. + */ + virtual bool isDoubleSided() const { return mHeader.isDoubleSided(); } + + /** Set the thin surface flag. + */ + virtual void setThinSurface(bool thinSurface); + + /** Returns true if the material is a thin surface. + */ + virtual bool isThinSurface() const { return mHeader.isThinSurface(); } + + /** Set the alpha mode. + */ + virtual void setAlphaMode(AlphaMode alphaMode); + + /** Get the alpha mode. + */ + virtual AlphaMode getAlphaMode() const { return mHeader.getAlphaMode(); } + + /** Set the alpha threshold. The threshold is only used when alpha mode != AlphaMode::Opaque. + */ + virtual void setAlphaThreshold(float alphaThreshold); + + /** Get the alpha threshold. + */ + virtual float getAlphaThreshold() const { return (float)mHeader.getAlphaThreshold(); } + + /** Get the alpha mask texture handle. + */ + virtual TextureHandle getAlphaTextureHandle() const { return mHeader.getAlphaTextureHandle(); } + + /** Set the nested priority used for nested dielectrics. + */ + virtual void setNestedPriority(uint32_t priority); + + /** Get the nested priority used for nested dielectrics. + \return Nested priority, with 0 reserved for the highest possible priority. + */ + virtual uint32_t getNestedPriority() const { return mHeader.getNestedPriority(); } + + /** Set the index of refraction. + */ + virtual void setIndexOfRefraction(float IoR); + + /** Get the index of refraction. + */ + virtual float getIndexOfRefraction() const { return (float)mHeader.getIoR(); } + + /** Get information about a texture slot. + \param[in] slot The texture slot. + \return Info about the slot. If the slot doesn't exist isEnabled() returns false. + */ + virtual const TextureSlotInfo& getTextureSlotInfo(const TextureSlot slot) const; + + /** Check if material has a given texture slot. + \param[in] slot The texture slot. + \return True if the texture slot exists. Use getTexture() to check if a texture is bound. + */ + virtual bool hasTextureSlot(const TextureSlot slot) const { return getTextureSlotInfo(slot).isEnabled(); } + + /** Set one of the available texture slots. + The call is ignored with a warning if the slot doesn't exist. + \param[in] slot The texture slot. + \param[in] pTexture The texture. + \return True if the texture slot was changed, false otherwise. + */ + virtual bool setTexture(const TextureSlot slot, const ref& pTexture); + + /** Load one of the available texture slots. + The call is ignored with a warning if the slot doesn't exist. + \param[in] The texture slot. + \param[in] path Path to load texture from. + \param[in] useSrgb Load texture as sRGB format. + \return True if the texture was successfully loaded, false otherwise. + */ + virtual bool loadTexture(const TextureSlot slot, const std::filesystem::path& path, bool useSrgb = true); + + /** Clear one of the available texture slots. + The call is ignored with a warning if the slot doesn't exist. + \param[in] The texture slot. + */ + virtual void clearTexture(const TextureSlot slot) { setTexture(slot, nullptr); } + + /** Get one of the available texture slots. + \param[in] The texture slot. + \return Texture object if bound, or nullptr if unbound or slot doesn't exist. + */ + virtual ref getTexture(const TextureSlot slot) const; + + /** Optimize texture usage for the given texture slot. + This function may replace constant textures by uniform material parameters etc. + \param[in] slot The texture slot. + \param[in] texInfo Information about the texture bound to this slot. + \param[out] stats Optimization stats passed back to the caller. + */ + virtual void optimizeTexture(const TextureSlot slot, const TextureAnalyzer::Result& texInfo, TextureOptimizationStats& stats) {} + + /** Return the maximum dimensions of the bound textures. + */ + virtual uint2 getMaxTextureDimensions() const; + + /** Set the default texture sampler for the material. + */ + virtual void setDefaultTextureSampler(const ref& pSampler) {} + + /** Get the default texture sampler for the material. + */ + virtual ref getDefaultTextureSampler() const { return nullptr; } + + /** Set the material texture transform. + */ + virtual void setTextureTransform(const Transform& texTransform); + + /** Get a reference to the material texture transform. + */ + virtual Transform& getTextureTransform() { return mTextureTransform; } + + /** Get the material texture transform. + */ + virtual const Transform& getTextureTransform() const { return mTextureTransform; } + + /** Get a reference to the material header data. + */ + virtual const MaterialHeader& getHeader() const { return mHeader; } + + /** Get the material data blob for uploading to the GPU. + */ + virtual MaterialDataBlob getDataBlob() const = 0; + + /** Get shader modules for the material. + The shader modules must be added to any program using the material. + \return List of shader modules. + */ + virtual ProgramDesc::ShaderModuleList getShaderModules() const = 0; + + /** Get type conformances for the material. + The type conformances must be set on any program using the material. + */ + virtual TypeConformanceList getTypeConformances() const = 0; + + /** Get shader defines for the material. + The defines must be set on any program using the material. + */ + virtual DefineList getDefines() const { return {}; } + + /** Get the number of buffers used by this material. + */ + virtual size_t getMaxBufferCount() const { return 0; } + + /** Returns the maximum number of textures this material will use. + By default we use the number of texture slots. The reason for this is that, + for now, once the MaterialSystem has been set up with some number of texture slots, + it is not possible to allocate more. This limitation will be lifted in the future. + */ + virtual size_t getMaxTextureCount() const { return (size_t)Material::TextureSlot::Count; } + + /** Get the number of 3D textures used by this material. + */ + virtual size_t getMaxTexture3DCount() const { return 0; } + + // Temporary convenience function to downcast Material to BasicMaterial. + // This is because a large portion of the interface hasn't been ported to the Material base class yet. + // TODO: Remove this helper later + ref toBasicMaterial(); + + /** Size of the material instance the material produces. + Used to set `anyValueSize` on `IMaterialInstance` above the default (128B), for exceptionally large materials. + Large material instances can have a singificant performance impact. + */ + virtual size_t getMaterialInstanceByteSize() const { return 128; } + + virtual const MaterialParamLayout& getParamLayout() const { FALCOR_THROW("Material does not have a parameter layout."); } + virtual SerializedMaterialParams serializeParams() const { FALCOR_THROW("Material does not support serializing parameters."); } + virtual void deserializeParams(const SerializedMaterialParams& params) + { + FALCOR_THROW("Material does not support deserializing parameters."); } - FALCOR_ENUM_CLASS_OPERATORS(Material::UpdateFlags); + /** Set roughness mollification factor. + * Mollification smooths over highly glossy lobes in the distribution. + * The factor range from 0 (no mollification) to 1 (maximum mollification). + */ + virtual void setRoughnessMollification(float factor){}; + +protected: + Material(ref pDevice, const std::string& name, MaterialType type); + + using UpdateCallback = std::function; + void registerUpdateCallback(const UpdateCallback& updateCallback) { mUpdateCallback = updateCallback; } + void markUpdates(UpdateFlags updates); + bool hasTextureSlotData(const TextureSlot slot) const; + void updateTextureHandle(MaterialSystem* pOwner, const ref& pTexture, TextureHandle& handle); + void updateTextureHandle(MaterialSystem* pOwner, const TextureSlot slot, TextureHandle& handle); + void updateDefaultTextureSamplerID(MaterialSystem* pOwner, const ref& pSampler); + bool isBaseEqual(const Material& other) const; + + static NormalMapType detectNormalMapType(const ref& pNormalMap); + + template + MaterialDataBlob prepareDataBlob(const T& data) const + { + MaterialDataBlob blob = {}; + blob.header = mHeader; + static_assert(sizeof(mHeader) + sizeof(data) <= sizeof(blob)); + static_assert(offsetof(MaterialDataBlob, payload) == sizeof(MaterialHeader)); + std::memcpy(&blob.payload, &data, sizeof(data)); + return blob; + } + + ref mpDevice; + + std::string mName; ///< Name of the material. + MaterialHeader mHeader; ///< Material header data available in all material types. + Transform mTextureTransform; ///< Texture transform. This is currently applied at load time by pre-transforming the texture coordinates. + + std::array mTextureSlotInfo; ///< Information about texture slots. + std::array mTextureSlotData; ///< Data bound to texture slots. Only enabled slots can have + ///< any data. + + mutable UpdateFlags mUpdates = UpdateFlags::None; + UpdateCallback mUpdateCallback; ///< Callback to track updates with the material system this material is used with. + + friend class MaterialSystem; + friend class SceneCache; +}; + +inline std::string to_string(Material::TextureSlot slot) +{ + switch (slot) + { +#define tostr(a) \ + case Material::TextureSlot::a: \ + return #a; + tostr(BaseColor); + tostr(Specular); + tostr(Emissive); + tostr(Normal); + tostr(Transmission); + tostr(Displacement); + tostr(Index); +#undef tostr + default: + FALCOR_THROW("Invalid texture slot"); + } } + +FALCOR_ENUM_CLASS_OPERATORS(Material::UpdateFlags); +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MaterialData.slang b/Source/Falcor/Scene/Material/MaterialData.slang index be6184e95b..e67a04c6db 100644 --- a/Source/Falcor/Scene/Material/MaterialData.slang +++ b/Source/Falcor/Scene/Material/MaterialData.slang @@ -39,7 +39,7 @@ __exported import TextureHandle; BEGIN_NAMESPACE_FALCOR /** This is a host/device structure for material header data for all material types (16B). -*/ + */ struct MaterialHeader { uint4 packedData = {}; @@ -49,8 +49,9 @@ struct MaterialHeader static constexpr uint kLobeTypeBits = 8; // Only 6 bits needed if packing LobeType static constexpr uint kSamplerIDBits = 8; static constexpr uint kAlphaModeBits = 1; + static constexpr uint kBSDFIndexBits = 4; static constexpr uint kAlphaThresholdBits = 16; // Using float16_t format - static constexpr uint kIoRBits = 16; // Using float16_t format + static constexpr uint kIoRBits = 16; // Using float16_t format // packedData.x bit layout static constexpr uint kMaterialTypeOffset = 0; @@ -69,8 +70,9 @@ struct MaterialHeader static constexpr uint kSamplerIDOffset = kAlphaModeOffset + kAlphaModeBits; static constexpr uint kEnableLightProfileOffset = kSamplerIDOffset + kSamplerIDBits; static constexpr uint kDeltaSpecularFlagOffset = kEnableLightProfileOffset + 1; + static constexpr uint kBSDFIndexOffset = kDeltaSpecularFlagOffset + 1; - static constexpr uint kTotalHeaderBitsY = kDeltaSpecularFlagOffset + 1; + static constexpr uint kTotalHeaderBitsY = kBSDFIndexOffset + 1; // packedData.z bit layout static constexpr uint kIoROffset = 0; @@ -81,32 +83,47 @@ struct MaterialHeader static constexpr uint kTotalHeaderBitsW = 0; /** Set material type. - */ - SETTER_DECL void setMaterialType(MaterialType type) { packedData.x = PACK_BITS(kMaterialTypeBits, kMaterialTypeOffset, packedData.x, (uint)type); } + */ + SETTER_DECL void setMaterialType(MaterialType type) + { + packedData.x = PACK_BITS(kMaterialTypeBits, kMaterialTypeOffset, packedData.x, (uint)type); + } /** Get material type. - */ - MaterialType getMaterialType() CONST_FUNCTION { return MaterialType(EXTRACT_BITS(kMaterialTypeBits, kMaterialTypeOffset, packedData.x)); } + */ + MaterialType getMaterialType() CONST_FUNCTION + { + return MaterialType(EXTRACT_BITS(kMaterialTypeBits, kMaterialTypeOffset, packedData.x)); + } /** Set alpha testing mode. - */ + */ SETTER_DECL void setAlphaMode(AlphaMode mode) { packedData.y = PACK_BITS(kAlphaModeBits, kAlphaModeOffset, packedData.y, (uint)mode); } /** Get material type. - */ + */ AlphaMode getAlphaMode() CONST_FUNCTION { return AlphaMode(EXTRACT_BITS(kAlphaModeBits, kAlphaModeOffset, packedData.y)); } /** Set alpha threshold. - */ - SETTER_DECL void setAlphaThreshold(float16_t alphaThreshold) { packedData.y = PACK_BITS_UNSAFE(kAlphaThresholdBits, kAlphaThresholdOffset, packedData.y, (uint)asuint16(alphaThreshold)); } + */ + SETTER_DECL void setAlphaThreshold(float16_t alphaThreshold) + { + packedData.y = PACK_BITS_UNSAFE(kAlphaThresholdBits, kAlphaThresholdOffset, packedData.y, (uint)asuint16(alphaThreshold)); + } /** Get alpha threshold. - */ - float16_t getAlphaThreshold() CONST_FUNCTION { return asfloat16((uint16_t)EXTRACT_BITS(kAlphaThresholdBits, kAlphaThresholdOffset, packedData.y)); } + */ + float16_t getAlphaThreshold() CONST_FUNCTION + { + return asfloat16((uint16_t)EXTRACT_BITS(kAlphaThresholdBits, kAlphaThresholdOffset, packedData.y)); + } /** Set the nested priority used for nested dielectrics. - */ - SETTER_DECL void setNestedPriority(uint32_t priority) { packedData.x = PACK_BITS(kNestedPriorityBits, kNestedPriorityOffset, packedData.x, priority); } + */ + SETTER_DECL void setNestedPriority(uint32_t priority) + { + packedData.x = PACK_BITS(kNestedPriorityBits, kNestedPriorityOffset, packedData.x, priority); + } /** Get the nested priority used for nested dielectrics. \return Nested priority, with 0 reserved for the highest possible priority. @@ -116,7 +133,10 @@ struct MaterialHeader /** Set active BSDF lobes. \param[in] activeLobes Bit mask of active lobes. See LobeType. */ - SETTER_DECL void setActiveLobes(uint activeLobes) { packedData.x = PACK_BITS(kLobeTypeBits, kLobeTypeOffset, packedData.x, activeLobes); } + SETTER_DECL void setActiveLobes(uint activeLobes) + { + packedData.x = PACK_BITS(kLobeTypeBits, kLobeTypeOffset, packedData.x, activeLobes); + } /** Get active BSDF lobes. \return Bit mask of active lobes. See LobeType. @@ -124,61 +144,84 @@ struct MaterialHeader uint getActiveLobes() CONST_FUNCTION { return EXTRACT_BITS(kLobeTypeBits, kLobeTypeOffset, packedData.x); } /** Set default texture sampler ID. - */ - SETTER_DECL void setDefaultTextureSamplerID(uint samplerID) { packedData.y = PACK_BITS(kSamplerIDBits, kSamplerIDOffset, packedData.y, samplerID); } + */ + SETTER_DECL void setDefaultTextureSamplerID(uint samplerID) + { + packedData.y = PACK_BITS(kSamplerIDBits, kSamplerIDOffset, packedData.y, samplerID); + } /** Get default texture sampler ID. - */ + */ uint getDefaultTextureSamplerID() CONST_FUNCTION { return EXTRACT_BITS(kSamplerIDBits, kSamplerIDOffset, packedData.y); } /** Set double-sided flag. - */ - SETTER_DECL void setDoubleSided(bool doubleSided) { packedData.x = PACK_BITS(1, kDoubleSidedFlagOffset, packedData.x, doubleSided ? 1 : 0); } + */ + SETTER_DECL void setDoubleSided(bool doubleSided) + { + packedData.x = PACK_BITS(1, kDoubleSidedFlagOffset, packedData.x, doubleSided ? 1 : 0); + } /** Get double-sided flag. - */ + */ bool isDoubleSided() CONST_FUNCTION { return packedData.x & (1u << kDoubleSidedFlagOffset); } /** Set thin surface flag. - */ - SETTER_DECL void setThinSurface(bool thinSurface) { packedData.x = PACK_BITS(1, kThinSurfaceFlagOffset, packedData.x, thinSurface ? 1 : 0); } + */ + SETTER_DECL void setThinSurface(bool thinSurface) + { + packedData.x = PACK_BITS(1, kThinSurfaceFlagOffset, packedData.x, thinSurface ? 1 : 0); + } /** Get thin surface flag. - */ + */ bool isThinSurface() CONST_FUNCTION { return packedData.x & (1u << kThinSurfaceFlagOffset); } /** Set emissive flag. - */ + */ SETTER_DECL void setEmissive(bool isEmissive) { packedData.x = PACK_BITS(1, kEmissiveFlagOffset, packedData.x, isEmissive ? 1 : 0); } /** Get emissive flag. - */ + */ bool isEmissive() CONST_FUNCTION { return packedData.x & (1u << kEmissiveFlagOffset); } /** Set basic material flag. This flag is an optimization to allow quick type checking. - */ - SETTER_DECL void setIsBasicMaterial(bool isBasicMaterial) { packedData.x = PACK_BITS(1, kIsBasicMaterialFlagOffset, packedData.x, isBasicMaterial ? 1 : 0); } + */ + SETTER_DECL void setIsBasicMaterial(bool isBasicMaterial) + { + packedData.x = PACK_BITS(1, kIsBasicMaterialFlagOffset, packedData.x, isBasicMaterial ? 1 : 0); + } /** Get basic material flag. This flag is an optimization to allow quick type checking. - */ + */ bool isBasicMaterial() CONST_FUNCTION { return packedData.x & (1u << kIsBasicMaterialFlagOffset); } /** Set light profile enable flag. // DEMO21 - */ - SETTER_DECL void setEnableLightProfile(bool enableLightProfile) { packedData.y = PACK_BITS(1, kEnableLightProfileOffset, packedData.y, enableLightProfile ? 1 : 0); } + */ + SETTER_DECL void setEnableLightProfile(bool enableLightProfile) + { + packedData.y = PACK_BITS(1, kEnableLightProfileOffset, packedData.y, enableLightProfile ? 1 : 0); + } /** Get light profile enable flag. - */ + */ bool isLightProfileEnabled() CONST_FUNCTION { return packedData.y & (1u << kEnableLightProfileOffset); } /** Set delta specular flag. This flag is set if the material only has delta specular reflection/transmission (and can skip NEE). - */ - SETTER_DECL void setDeltaSpecular(bool deltaSpecular) { packedData.y = PACK_BITS(1, kDeltaSpecularFlagOffset, packedData.y, deltaSpecular ? 1 : 0); } + */ + SETTER_DECL void setDeltaSpecular(bool deltaSpecular) + { + packedData.y = PACK_BITS(1, kDeltaSpecularFlagOffset, packedData.y, deltaSpecular ? 1 : 0); + } /** Get delta specular flag. This flag is set if the material only has delta specular reflection/transmission (and can skip NEE). - */ + */ bool isDeltaSpecular() CONST_FUNCTION { return packedData.y & (1u << kDeltaSpecularFlagOffset); } + // -1 means all BSDFs, therefore, an uninitialized field represents ALL + SETTER_DECL void setBSDFIndex(int BSDFIndex) { packedData.y = PACK_BITS(kBSDFIndexBits, kBSDFIndexOffset, packedData.y, BSDFIndex + 1); } + + int getBSDFIndex() CONST_FUNCTION { return int(EXTRACT_BITS(kBSDFIndexBits, kBSDFIndexOffset, packedData.y)) - 1; } + /// Set index of refraction. SETTER_DECL void setIoR(float16_t ior) { packedData.z = PACK_BITS_UNSAFE(kIoRBits, kIoROffset, packedData.z, (uint)asuint16(ior)); } @@ -192,8 +235,8 @@ struct MaterialHeader TextureHandle getAlphaTextureHandle() CONST_FUNCTION { return TextureHandle(packedData.w); } #ifdef HOST_CODE - friend bool operator==(const MaterialHeader& lhs, const MaterialHeader& rhs); - friend bool operator!=(const MaterialHeader& lhs, const MaterialHeader& rhs) { return !(lhs == rhs); } + friend bool operator ==(const MaterialHeader& lhs, const MaterialHeader& rhs); + friend bool operator !=(const MaterialHeader& lhs, const MaterialHeader& rhs) { return !(lhs == rhs); } #endif }; @@ -213,7 +256,7 @@ struct MaterialPayload */ struct MaterialDataBlob { - MaterialHeader header; // 16B + MaterialHeader header; // 16B MaterialPayload payload; // 112B }; diff --git a/Source/Falcor/Scene/Material/MaterialFactory.slang b/Source/Falcor/Scene/Material/MaterialFactory.slang index 4fa115b748..f7f6251973 100644 --- a/Source/Falcor/Scene/Material/MaterialFactory.slang +++ b/Source/Falcor/Scene/Material/MaterialFactory.slang @@ -63,7 +63,9 @@ extension MaterialSystem \param[in] hints Optional hints to control the behavior of the material instance. Combination of `MaterialInstanceHints` flags. \return A material instance. */ - IMaterialInstance getMaterialInstance(const ShadingData sd, L lod, const uint hints = (uint)MaterialInstanceHints::None) + IMaterialInstance getMaterialInstance( + const ShadingData sd, L lod, const uint hints = (uint)MaterialInstanceHints::None + ) { let material = getMaterial(sd.materialID); return material.setupMaterialInstance(this, sd, lod, hints); diff --git a/Source/Falcor/Scene/Material/MaterialParamLayout.slangh b/Source/Falcor/Scene/Material/MaterialParamLayout.slangh index 852ed0c18e..a5dd87d3e9 100644 --- a/Source/Falcor/Scene/Material/MaterialParamLayout.slangh +++ b/Source/Falcor/Scene/Material/MaterialParamLayout.slangh @@ -35,19 +35,19 @@ namespace Falcor { -namespace detail -{ -static constexpr float kMaterialParamEpsilon = 1e-4f; -template -T clampMaterialParam(T value, T min = T(kMaterialParamEpsilon), T max = T(1.f - kMaterialParamEpsilon)) -{ - return math::clamp(value, min, max); -} -} // namespace detail + namespace detail + { + static constexpr float kMaterialParamEpsilon = 1e-4f; + template + T clampMaterialParam(T value, T min = T(kMaterialParamEpsilon), T max = T(1.f - kMaterialParamEpsilon)) + { + return math::clamp(value, min, max); + } + } // namespace detail } // namespace Falcor #define _EMIT_MATERIAL_PARAM_LAYOUT_ENTRY(type, offset, name, getter, setter, pythonName, ...) \ - MaterialParamLayoutEntry{#name, pythonName, sizeof(type) / 4, offset}, + MaterialParamLayoutEntry { #name, pythonName, sizeof(type) / 4, offset }, #define _EMIT_MATERIAL_PARAM_SERIALIZE(type, offset, name, getter, setter, pythonName, ...) \ { \ @@ -69,7 +69,7 @@ T clampMaterialParam(T value, T min = T(kMaterialParamEpsilon), T max = T(1.f - { \ static const MaterialParamLayout& layout() \ { \ - static MaterialParamLayout layout = {params_(_EMIT_MATERIAL_PARAM_LAYOUT_ENTRY)}; \ + static MaterialParamLayout layout = { params_(_EMIT_MATERIAL_PARAM_LAYOUT_ENTRY) }; \ return layout; \ } \ static SerializedMaterialParams serialize(const materialName_* material) \ @@ -89,7 +89,7 @@ T clampMaterialParam(T value, T min = T(kMaterialParamEpsilon), T max = T(1.f - import Scene.Material.MaterialParamLayout; -#define _EMIT_MATERIAL_PARAM_FIELD(type, offset, name, getter, setter, pythonName, ...) static MaterialParamLayoutEntry name = {offset}; +#define _EMIT_MATERIAL_PARAM_FIELD(type, offset, name, getter, setter, pythonName, ...) static MaterialParamLayoutEntry name = { offset }; #define DEFINE_MATERIAL_PARAM_LAYOUT(materialName_, params_) \ struct materialName_##ParamLayout \ diff --git a/Source/Falcor/Scene/Material/MaterialSystem.cpp b/Source/Falcor/Scene/Material/MaterialSystem.cpp index 4e0d1f24a8..b6aff51d8c 100644 --- a/Source/Falcor/Scene/Material/MaterialSystem.cpp +++ b/Source/Falcor/Scene/Material/MaterialSystem.cpp @@ -36,777 +36,814 @@ namespace Falcor { - namespace - { - const std::string kShaderFilename = "Scene/Material/MaterialSystem.slang"; - const std::string kMaterialDataName = "materialData"; - const std::string kMaterialSamplersName = "materialSamplers"; - const std::string kMaterialTexturesName = "materialTextures"; - const std::string kMaterialBuffersName = "materialBuffers"; - const std::string kMaterialTextures3DName = "materialTextures3D"; - const std::string kLightProfile = "lightProfile"; - - const size_t kMaxSamplerCount = 1ull << MaterialHeader::kSamplerIDBits; - const size_t kMaxTextureCount = 1ull << TextureHandle::kTextureIDBits; - const size_t kMaxBufferCountPerMaterial = 1; // This is a conservative estimation of how many buffer descriptors to allocate per material. Most materials don't use any auxiliary data buffers. - - // Helper to check if a material is a standard material using the SpecGloss shading model. - // We keep track of these as an optimization because most scenes do not use this shading model. - bool isSpecGloss(const ref& pMaterial) - { - if (pMaterial->getType() == MaterialType::Standard) - { - return static_ref_cast(pMaterial)->getShadingModel() == ShadingModel::SpecGloss; - } - return false; - } +namespace +{ +const std::string kShaderFilename = "Scene/Material/MaterialSystem.slang"; +const std::string kMaterialDataName = "materialData"; +const std::string kMaterialSamplersName = "materialSamplers"; +const std::string kMaterialTexturesName = "materialTextures"; +const std::string kMaterialBuffersName = "materialBuffers"; +const std::string kMaterialTextures3DName = "materialTextures3D"; +const std::string kLightProfile = "lightProfile"; + +const size_t kMaxSamplerCount = 1ull << MaterialHeader::kSamplerIDBits; +const size_t kMaxTextureCount = 1ull << TextureHandle::kTextureIDBits; +const size_t kMaxBufferCountPerMaterial = 1; // This is a conservative estimation of how many buffer descriptors to allocate per material. + // Most materials don't use any auxiliary data buffers. + +// Helper to check if a material is a standard material using the SpecGloss shading model. +// We keep track of these as an optimization because most scenes do not use this shading model. +bool isSpecGloss(const ref& pMaterial) +{ + if (pMaterial->getType() == MaterialType::Standard) + { + return static_ref_cast(pMaterial)->getShadingModel() == ShadingModel::SpecGloss; } + return false; +} +} // namespace - MaterialSystem::MaterialSystem(ref pDevice) - : mpDevice(pDevice) - { - FALCOR_ASSERT(kMaxSamplerCount <= mpDevice->getLimits().maxShaderVisibleSamplers); +MaterialSystem::MaterialSystem(ref pDevice) + : mpDevice(pDevice) +{ + FALCOR_ASSERT(kMaxSamplerCount <= mpDevice->getLimits().maxShaderVisibleSamplers); + + mpFence = mpDevice->createFence(); + mpTextureManager = std::make_unique(mpDevice, kMaxTextureCount); - mpFence = mpDevice->createFence(); - mpTextureManager = std::make_unique(mpDevice, kMaxTextureCount); + // Create a default texture sampler. + Sampler::Desc desc; + desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Linear, TextureFilteringMode::Linear); + desc.setMaxAnisotropy(8); + mpDefaultTextureSampler = mpDevice->createSampler(desc); +} - // Create a default texture sampler. - Sampler::Desc desc; - desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Linear, TextureFilteringMode::Linear); - desc.setMaxAnisotropy(8); - mpDefaultTextureSampler = mpDevice->createSampler(desc); +void MaterialSystem::renderUI(Gui::Widgets& widget) +{ + widget.text("Material types:"); + auto materialTypes = getMaterialTypes(); + for (auto materialType : materialTypes) + { + uint32_t count = getMaterialCountByType(materialType); + std::string name = to_string(materialType); + widget.text(fmt::format(" {}: {}", name, count)); } - void MaterialSystem::renderUI(Gui::Widgets& widget) + widget.text(""); + widget.text("Materials:"); + + auto showMaterial = [&](uint32_t materialID, const std::string& label) { - widget.text("Material types:"); - auto materialTypes = getMaterialTypes(); - for (auto materialType : materialTypes) + const auto& pMaterial = mMaterials[materialID]; + if (auto materialGroup = widget.group(label)) { - uint32_t count = getMaterialCountByType(materialType); - std::string name = to_string(materialType); - widget.text(fmt::format(" {}: {}", name, count)); + if (pMaterial->renderUI(materialGroup)) + uploadMaterial(materialID); } + }; - widget.text(""); - widget.text("Materials:"); - - auto showMaterial = [&](uint32_t materialID, const std::string& label) { - const auto& pMaterial = mMaterials[materialID]; - if (auto materialGroup = widget.group(label)) - { - if (pMaterial->renderUI(materialGroup)) uploadMaterial(materialID); - } - }; - - widget.checkbox("Sort by name", mSortMaterialsByName); - if (mSortMaterialsByName) + widget.checkbox("Sort by name", mSortMaterialsByName); + if (mSortMaterialsByName) + { + for (uint32_t materialID : mSortedMaterialIndices) { - for (uint32_t materialID : mSortedMaterialIndices) - { - auto label = mMaterials[materialID]->getName() + " (#" + std::to_string(materialID) + ")"; - showMaterial(materialID, label); - } + auto label = mMaterials[materialID]->getName() + " (#" + std::to_string(materialID) + ")"; + showMaterial(materialID, label); } - else + } + else + { + uint32_t materialID = 0; + for (auto& material : mMaterials) { - uint32_t materialID = 0; - for (auto& material : mMaterials) - { - auto label = std::to_string(materialID) + ": " + material->getName(); - showMaterial(materialID, label); - materialID++; - } + auto label = std::to_string(materialID) + ": " + material->getName(); + showMaterial(materialID, label); + materialID++; } } +} - void MaterialSystem::updateUI() - { - // Construct a vector of indices that sort the materials by case-insensitive name. - // TODO: Material could have changed names since last sort. This currently isn't detected. - mSortedMaterialIndices.resize(mMaterials.size()); - std::iota(mSortedMaterialIndices.begin(), mSortedMaterialIndices.end(), 0); - std::sort(mSortedMaterialIndices.begin(), mSortedMaterialIndices.end(), [this](uint32_t a, uint32_t b) { +void MaterialSystem::updateUI() +{ + // Construct a vector of indices that sort the materials by case-insensitive name. + // TODO: Material could have changed names since last sort. This currently isn't detected. + mSortedMaterialIndices.resize(mMaterials.size()); + std::iota(mSortedMaterialIndices.begin(), mSortedMaterialIndices.end(), 0); + std::sort( + mSortedMaterialIndices.begin(), + mSortedMaterialIndices.end(), + [this](uint32_t a, uint32_t b) + { const std::string& astr = mMaterials[a]->getName(); const std::string& bstr = mMaterials[b]->getName(); - const auto r = std::mismatch(astr.begin(), astr.end(), bstr.begin(), bstr.end(), [](uint8_t l, uint8_t r) { return tolower(l) == tolower(r); }); + const auto r = std::mismatch( + astr.begin(), astr.end(), bstr.begin(), bstr.end(), [](uint8_t l, uint8_t r) { return tolower(l) == tolower(r); } + ); return r.second != bstr.end() && (r.first == astr.end() || tolower(*r.first) < tolower(*r.second)); - }); - } - - void MaterialSystem::setDefaultTextureSampler(const ref& pSampler) - { - mpDefaultTextureSampler = pSampler; - for (const auto& pMaterial : mMaterials) - { - pMaterial->setDefaultTextureSampler(pSampler); } - } + ); +} - uint32_t MaterialSystem::addTextureSampler(const ref& pSampler) +void MaterialSystem::setDefaultTextureSampler(const ref& pSampler) +{ + mpDefaultTextureSampler = pSampler; + for (const auto& pMaterial : mMaterials) { - FALCOR_ASSERT(pSampler); - auto isEqual = [&pSampler](const ref& pOther) { - return pSampler->getDesc() == pOther->getDesc(); - }; - - // Reuse previously added samplers. We compare by sampler desc. - if (auto it = std::find_if(mTextureSamplers.begin(), mTextureSamplers.end(), isEqual); it != mTextureSamplers.end()) - { - return (uint32_t)std::distance(mTextureSamplers.begin(), it); - } - - // Add sampler. - if (mTextureSamplers.size() >= kMaxSamplerCount) - { - FALCOR_THROW("Too many samplers"); - } - const uint32_t samplerID = static_cast(mTextureSamplers.size()); + pMaterial->setDefaultTextureSampler(pSampler); + } +} - mTextureSamplers.push_back(pSampler); - mSamplersChanged = true; +uint32_t MaterialSystem::addTextureSampler(const ref& pSampler) +{ + FALCOR_ASSERT(pSampler); + auto isEqual = [&pSampler](const ref& pOther) { return pSampler->getDesc() == pOther->getDesc(); }; - return samplerID; + // Reuse previously added samplers. We compare by sampler desc. + if (auto it = std::find_if(mTextureSamplers.begin(), mTextureSamplers.end(), isEqual); it != mTextureSamplers.end()) + { + return (uint32_t)std::distance(mTextureSamplers.begin(), it); } - uint32_t MaterialSystem::addBuffer(const ref& pBuffer) + // Add sampler. + if (mTextureSamplers.size() >= kMaxSamplerCount) { - FALCOR_ASSERT(pBuffer); + FALCOR_THROW("Too many samplers"); + } + const uint32_t samplerID = static_cast(mTextureSamplers.size()); - // Reuse previously added buffers. We compare by pointer as the contents of the buffers is unknown. - if (auto it = std::find_if(mBuffers.begin(), mBuffers.end(), [&](auto pOther) { return pBuffer == pOther; }); it != mBuffers.end()) - { - return (uint32_t)std::distance(mBuffers.begin(), it); - } + mTextureSamplers.push_back(pSampler); + mSamplersChanged = true; - // Add buffer. - FALCOR_ASSERT(!mMaterialsChanged); - if (mBuffers.size() >= mBufferDescCount) - { - FALCOR_THROW("Too many buffers"); - } - const uint32_t bufferID = static_cast(mBuffers.size()); + return samplerID; +} - mBuffers.push_back(pBuffer); - mBuffersChanged = true; +uint32_t MaterialSystem::addBuffer(const ref& pBuffer) +{ + FALCOR_ASSERT(pBuffer); - return bufferID; + // Reuse previously added buffers. We compare by pointer as the contents of the buffers is unknown. + if (auto it = std::find_if(mBuffers.begin(), mBuffers.end(), [&](auto pOther) { return pBuffer == pOther; }); it != mBuffers.end()) + { + return (uint32_t)std::distance(mBuffers.begin(), it); } - void MaterialSystem::replaceBuffer(uint32_t id, const ref& pBuffer) + // Add buffer. + FALCOR_ASSERT(!mMaterialsChanged); + if (mBuffers.size() >= mBufferDescCount) { - FALCOR_ASSERT(pBuffer); - FALCOR_CHECK(id < mBuffers.size(), "'id' is out of bounds."); - - mBuffers[id] = pBuffer; - mBuffersChanged = true; + FALCOR_THROW("Too many buffers"); } + const uint32_t bufferID = static_cast(mBuffers.size()); - uint32_t MaterialSystem::addTexture3D(const ref& pTexture) - { - FALCOR_ASSERT(pTexture && pTexture->getType() == Texture::Type::Texture3D && pTexture->getSampleCount() == 1); + mBuffers.push_back(pBuffer); + mBuffersChanged = true; - // Reuse previously added texture. - if (auto it = std::find_if(mTextures3D.begin(), mTextures3D.end(), [&](auto pOther) { return pTexture == pOther; }); it != mTextures3D.end()) - return (uint32_t)std::distance(mTextures3D.begin(), it); + return bufferID; +} - if (mTextures3D.size() >= mTexture3DDescCount) - FALCOR_THROW("Too many 3D textures"); +void MaterialSystem::replaceBuffer(uint32_t id, const ref& pBuffer) +{ + FALCOR_ASSERT(pBuffer); + FALCOR_CHECK(id < mBuffers.size(), "'id' is out of bounds."); - const uint32_t textureID = static_cast(mTextures3D.size()); + mBuffers[id] = pBuffer; + mBuffersChanged = true; +} - mTextures3D.push_back(pTexture); - mTextures3DChanged = true; +uint32_t MaterialSystem::addTexture3D(const ref& pTexture) +{ + FALCOR_ASSERT(pTexture && pTexture->getType() == Texture::Type::Texture3D && pTexture->getSampleCount() == 1); - return textureID; - } + // Reuse previously added texture. + if (auto it = std::find_if(mTextures3D.begin(), mTextures3D.end(), [&](auto pOther) { return pTexture == pOther; }); + it != mTextures3D.end()) + return (uint32_t)std::distance(mTextures3D.begin(), it); - MaterialID MaterialSystem::addMaterial(const ref& pMaterial) - { - FALCOR_CHECK(pMaterial != nullptr, "'pMaterial' is missing"); + if (mTextures3D.size() >= mTexture3DDescCount) + FALCOR_THROW("Too many 3D textures"); - // Reuse previously added materials. - if (auto it = std::find(mMaterials.begin(), mMaterials.end(), pMaterial); it != mMaterials.end()) - { - return MaterialID{(size_t)std::distance(mMaterials.begin(), it) }; - } + const uint32_t textureID = static_cast(mTextures3D.size()); - // Add material. - if (mMaterials.size() >= std::numeric_limits::max()) - { - FALCOR_THROW("Too many materials"); - } - const MaterialID materialID{ mMaterials.size() }; + mTextures3D.push_back(pTexture); + mTextures3DChanged = true; - if (pMaterial->getDefaultTextureSampler() == nullptr) - { - pMaterial->setDefaultTextureSampler(mpDefaultTextureSampler); - } + return textureID; +} - pMaterial->registerUpdateCallback([this](auto flags) { mMaterialUpdates |= flags; }); - mMaterials.push_back(pMaterial); - mMaterialsChanged = true; +MaterialID MaterialSystem::addMaterial(const ref& pMaterial) +{ + FALCOR_CHECK(pMaterial != nullptr, "'pMaterial' is missing"); - return materialID; + // Reuse previously added materials. + if (auto it = std::find(mMaterials.begin(), mMaterials.end(), pMaterial); it != mMaterials.end()) + { + return MaterialID{(size_t)std::distance(mMaterials.begin(), it)}; } - void MaterialSystem::removeMaterial(const MaterialID materialID) + // Add material. + if (mMaterials.size() >= std::numeric_limits::max()) { - FALCOR_CHECK(materialID.isValid() && materialID.get() < mMaterials.size(), "Material ID is invalid."); + FALCOR_THROW("Too many materials"); + } + const MaterialID materialID{mMaterials.size()}; - // Mark descriptors used by the deleted material as reserved. - // This is a workaround until we have dynamically sized descriptor arrays and proper resource tracking. - const auto& material = mMaterials[materialID.get()]; - mReservedTextureDescCount += material->getMaxTextureCount(); - mReservedBufferDescCount += material->getMaxBufferCount(); - mReservedTexture3DDescCount += material->getMaxTexture3DCount(); + if (pMaterial->getDefaultTextureSampler() == nullptr) + { + pMaterial->setDefaultTextureSampler(mpDefaultTextureSampler); + } - // Remove textures that were used by the material and loaded via the texture manager. - mpTextureManager->removeTextures(material.get()); + pMaterial->registerUpdateCallback([this](auto flags) { mMaterialUpdates |= flags; }); + mMaterials.push_back(pMaterial); + mMaterialsChanged = true; - // Remove the material. - mMaterials[materialID.get()] = nullptr; - mMaterialsChanged = true; - } + return materialID; +} - void MaterialSystem::replaceMaterial(const MaterialID materialID, const ref& pReplacement) - { - FALCOR_CHECK(materialID.isValid() && materialID.get() < mMaterials.size(), "Material ID is invalid."); - FALCOR_CHECK(pReplacement != nullptr, "Replacement material is missing."); +void MaterialSystem::removeMaterial(const MaterialID materialID) +{ + FALCOR_CHECK(materialID.isValid() && materialID.get() < mMaterials.size(), "Material ID is invalid."); - // Remove the previous material. - removeMaterial(materialID); + // Mark descriptors used by the deleted material as reserved. + // This is a workaround until we have dynamically sized descriptor arrays and proper resource tracking. + const auto& material = mMaterials[materialID.get()]; + mReservedTextureDescCount += material->getMaxTextureCount(); + mReservedBufferDescCount += material->getMaxBufferCount(); + mReservedTexture3DDescCount += material->getMaxTexture3DCount(); - // Prepare replacement material. - if (pReplacement->getDefaultTextureSampler() == nullptr) - { - pReplacement->setDefaultTextureSampler(mpDefaultTextureSampler); - } - pReplacement->registerUpdateCallback([this](auto flags) { mMaterialUpdates |= flags; }); + // Remove textures that were used by the material and loaded via the texture manager. + mpTextureManager->removeTextures(material.get()); - // Replace the material. - mMaterials[materialID.get()] = pReplacement; - mMaterialsChanged = true; - } + // Remove the material. + mMaterials[materialID.get()] = nullptr; + mMaterialsChanged = true; +} - void MaterialSystem::replaceMaterial(const ref& pMaterial, const ref& pReplacement) - { - FALCOR_CHECK(pMaterial != nullptr, "'pMaterial' is missing"); +void MaterialSystem::replaceMaterial(const MaterialID materialID, const ref& pReplacement) +{ + FALCOR_CHECK(materialID.isValid() && materialID.get() < mMaterials.size(), "Material ID is invalid."); + FALCOR_CHECK(pReplacement != nullptr, "Replacement material is missing."); - // Find material to replace. - if (auto it = std::find(mMaterials.begin(), mMaterials.end(), pMaterial); it != mMaterials.end()) - { - auto materialID = MaterialID{ (size_t)std::distance(mMaterials.begin(), it) }; - replaceMaterial(materialID, pReplacement); - } - else - { - FALCOR_THROW("Material does not exist"); - } - } + // Remove the previous material. + removeMaterial(materialID); - uint32_t MaterialSystem::getMaterialCountByType(const MaterialType type) const + // Prepare replacement material. + if (pReplacement->getDefaultTextureSampler() == nullptr) { - FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); - size_t index = (size_t)type; - return (index < mMaterialCountByType.size()) ? mMaterialCountByType[index] : 0; + pReplacement->setDefaultTextureSampler(mpDefaultTextureSampler); } + pReplacement->registerUpdateCallback([this](auto flags) { mMaterialUpdates |= flags; }); - std::set MaterialSystem::getMaterialTypes() const - { - FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); - return mMaterialTypes; - } + // Replace the material. + mMaterials[materialID.get()] = pReplacement; + mMaterialsChanged = true; +} - bool MaterialSystem::hasMaterialType(MaterialType type) const - { - FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); - return mMaterialTypes.find(type) != mMaterialTypes.end(); - } +void MaterialSystem::replaceMaterial(const ref& pMaterial, const ref& pReplacement) +{ + FALCOR_CHECK(pMaterial != nullptr, "'pMaterial' is missing"); - bool MaterialSystem::hasMaterial(const MaterialID materialID) const + // Find material to replace. + if (auto it = std::find(mMaterials.begin(), mMaterials.end(), pMaterial); it != mMaterials.end()) { - // The materials are sequentially indexed without gaps for now. Check if the ID is within range. - return materialID.isValid() ? materialID.get() < mMaterials.size() : false; + auto materialID = MaterialID{(size_t)std::distance(mMaterials.begin(), it)}; + replaceMaterial(materialID, pReplacement); } - - const ref& MaterialSystem::getMaterial(const MaterialID materialID) const + else { - FALCOR_CHECK(materialID.get() < mMaterials.size(), "Material ID is out of range."); - return mMaterials[materialID.get()]; + FALCOR_THROW("Material does not exist"); } +} + +uint32_t MaterialSystem::getMaterialCountByType(const MaterialType type) const +{ + FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); + size_t index = (size_t)type; + return (index < mMaterialCountByType.size()) ? mMaterialCountByType[index] : 0; +} + +std::set MaterialSystem::getMaterialTypes() const +{ + FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); + return mMaterialTypes; +} + +bool MaterialSystem::hasMaterialType(MaterialType type) const +{ + FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); + return mMaterialTypes.find(type) != mMaterialTypes.end(); +} - ref MaterialSystem::getMaterialByName(const std::string& name) const +bool MaterialSystem::hasMaterial(const MaterialID materialID) const +{ + // The materials are sequentially indexed without gaps for now. Check if the ID is within range. + return materialID.isValid() ? materialID.get() < mMaterials.size() : false; +} + +const ref& MaterialSystem::getMaterial(const MaterialID materialID) const +{ + FALCOR_CHECK(materialID.get() < mMaterials.size(), "Material ID is out of range."); + return mMaterials[materialID.get()]; +} + +ref MaterialSystem::getMaterialByName(const std::string& name) const +{ + for (const auto& pMaterial : mMaterials) { - for (const auto& pMaterial : mMaterials) - { - if (pMaterial->getName() == name) return pMaterial; - } - return nullptr; + if (pMaterial->getName() == name) + return pMaterial; } + return nullptr; +} - size_t MaterialSystem::removeDuplicateMaterials(std::vector& idMap) - { - std::vector> uniqueMaterials; - idMap.resize(mMaterials.size()); +size_t MaterialSystem::removeDuplicateMaterials(std::vector& idMap) +{ + std::vector> uniqueMaterials; + idMap.resize(mMaterials.size()); - // Find unique set of materials. - for (MaterialID id{ 0 }; id.get() < mMaterials.size(); ++id) + // Find unique set of materials. + for (MaterialID id{0}; id.get() < mMaterials.size(); ++id) + { + const auto& pMaterial = mMaterials[id.get()]; + auto it = + std::find_if(uniqueMaterials.begin(), uniqueMaterials.end(), [&pMaterial](const auto& m) { return m->isEqual(pMaterial); }); + if (it == uniqueMaterials.end()) { - const auto& pMaterial = mMaterials[id.get()]; - auto it = std::find_if(uniqueMaterials.begin(), uniqueMaterials.end(), [&pMaterial](const auto& m) { return m->isEqual(pMaterial); }); - if (it == uniqueMaterials.end()) - { - idMap[id.get()] = MaterialID{ uniqueMaterials.size() }; - uniqueMaterials.push_back(pMaterial); - } - else - { - logInfo("Removing duplicate material '{}' (duplicate of '{}').", pMaterial->getName(), (*it)->getName()); - idMap[id.get()] = MaterialID{ (size_t)std::distance(uniqueMaterials.begin(), it) }; - } + idMap[id.get()] = MaterialID{uniqueMaterials.size()}; + uniqueMaterials.push_back(pMaterial); } - - size_t removed = mMaterials.size() - uniqueMaterials.size(); - if (removed > 0) + else { - mMaterials = uniqueMaterials; - mMaterialsChanged = true; + logInfo("Removing duplicate material '{}' (duplicate of '{}').", pMaterial->getName(), (*it)->getName()); + idMap[id.get()] = MaterialID{(size_t)std::distance(uniqueMaterials.begin(), it)}; } - - return removed; } - void MaterialSystem::optimizeMaterials() + size_t removed = mMaterials.size() - uniqueMaterials.size(); + if (removed > 0) { - // Gather a list of all textures to analyze. - std::vector, Material::TextureSlot>> materialSlots; - std::vector> textures; - size_t maxCount = mMaterials.size() * (size_t)Material::TextureSlot::Count; - materialSlots.reserve(maxCount); - textures.reserve(maxCount); + mMaterials = uniqueMaterials; + mMaterialsChanged = true; + } - for (const auto& pMaterial : mMaterials) + return removed; +} + +void MaterialSystem::optimizeMaterials() +{ + // Gather a list of all textures to analyze. + std::vector, Material::TextureSlot>> materialSlots; + std::vector> textures; + size_t maxCount = mMaterials.size() * (size_t)Material::TextureSlot::Count; + materialSlots.reserve(maxCount); + textures.reserve(maxCount); + + for (const auto& pMaterial : mMaterials) + { + for (uint32_t i = 0; i < (uint32_t)Material::TextureSlot::Count; i++) { - for (uint32_t i = 0; i < (uint32_t)Material::TextureSlot::Count; i++) + auto slot = (Material::TextureSlot)i; + if (auto pTexture = pMaterial->getTexture(slot)) { - auto slot = (Material::TextureSlot)i; - if (auto pTexture = pMaterial->getTexture(slot)) - { - materialSlots.push_back({ pMaterial, slot }); - textures.push_back(pTexture); - } + materialSlots.push_back({pMaterial, slot}); + textures.push_back(pTexture); } } + } - if (textures.empty()) return; + if (textures.empty()) + return; - // Analyze the textures. - logInfo("Analyzing {} material textures.", textures.size()); + // Analyze the textures. + logInfo("Analyzing {} material textures.", textures.size()); - RenderContext* pRenderContext = mpDevice->getRenderContext(); + RenderContext* pRenderContext = mpDevice->getRenderContext(); - TextureAnalyzer analyzer(mpDevice); - auto pResults = mpDevice->createBuffer(textures.size() * TextureAnalyzer::getResultSize(), ResourceBindFlags::UnorderedAccess); - analyzer.analyze(pRenderContext, textures, pResults); + TextureAnalyzer analyzer(mpDevice); + auto pResults = mpDevice->createBuffer(textures.size() * TextureAnalyzer::getResultSize(), ResourceBindFlags::UnorderedAccess); + analyzer.analyze(pRenderContext, textures, pResults); - // Copy result to staging buffer for readback. - // This is mostly to avoid a full flush and the associated perf warning. - // We do not have any other useful GPU work, but unrelated GPU tasks can be in flight. - auto pResultsStaging = mpDevice->createBuffer(textures.size() * TextureAnalyzer::getResultSize(), ResourceBindFlags::None, MemoryType::ReadBack); - pRenderContext->copyResource(pResultsStaging.get(), pResults.get()); - pRenderContext->submit(false); - pRenderContext->signal(mpFence.get()); + // Copy result to staging buffer for readback. + // This is mostly to avoid a full flush and the associated perf warning. + // We do not have any other useful GPU work, but unrelated GPU tasks can be in flight. + auto pResultsStaging = + mpDevice->createBuffer(textures.size() * TextureAnalyzer::getResultSize(), ResourceBindFlags::None, MemoryType::ReadBack); + pRenderContext->copyResource(pResultsStaging.get(), pResults.get()); + pRenderContext->submit(false); + pRenderContext->signal(mpFence.get()); - // Wait for results to become available. Then optimize the materials. - mpFence->wait(); - const TextureAnalyzer::Result* results = static_cast(pResultsStaging->map()); - Material::TextureOptimizationStats stats = {}; + // Wait for results to become available. Then optimize the materials. + mpFence->wait(); + const TextureAnalyzer::Result* results = static_cast(pResultsStaging->map()); + Material::TextureOptimizationStats stats = {}; - for (size_t i = 0; i < textures.size(); i++) - { - materialSlots[i].first->optimizeTexture(materialSlots[i].second, results[i], stats); - } + for (size_t i = 0; i < textures.size(); i++) + { + materialSlots[i].first->optimizeTexture(materialSlots[i].second, results[i], stats); + } - pResultsStaging->unmap(); + pResultsStaging->unmap(); - // Log optimization stats. - if (size_t totalRemoved = std::accumulate(stats.texturesRemoved.begin(), stats.texturesRemoved.end(), 0ull); totalRemoved > 0) + // Log optimization stats. + if (size_t totalRemoved = std::accumulate(stats.texturesRemoved.begin(), stats.texturesRemoved.end(), 0ull); totalRemoved > 0) + { + logInfo("Optimized materials by removing {} constant textures.", totalRemoved); + for (size_t slot = 0; slot < (size_t)Material::TextureSlot::Count; slot++) { - logInfo("Optimized materials by removing {} constant textures.", totalRemoved); - for (size_t slot = 0; slot < (size_t)Material::TextureSlot::Count; slot++) - { - logInfo(padStringToLength(" " + to_string((Material::TextureSlot)slot) + ":", 26) + std::to_string(stats.texturesRemoved[slot])); - } + logInfo( + padStringToLength(" " + to_string((Material::TextureSlot)slot) + ":", 26) + std::to_string(stats.texturesRemoved[slot]) + ); } - - if (stats.disabledAlpha > 0) logInfo("Optimized materials by disabling alpha test for {} materials.", stats.disabledAlpha); - if (stats.constantBaseColor > 0) logWarning("Materials have {} base color maps of constant value with non-constant alpha channel.", stats.constantBaseColor); - if (stats.constantNormalMaps > 0) logWarning("Materials have {} normal maps of constant value. Please update the asset to optimize performance.", stats.constantNormalMaps); } - Material::UpdateFlags MaterialSystem::update(bool forceUpdate) + if (stats.disabledAlpha > 0) + logInfo("Optimized materials by disabling alpha test for {} materials.", stats.disabledAlpha); + if (stats.constantBaseColor > 0) + logWarning("Materials have {} base color maps of constant value with non-constant alpha channel.", stats.constantBaseColor); + if (stats.constantNormalMaps > 0) + logWarning( + "Materials have {} normal maps of constant value. Please update the asset to optimize performance.", stats.constantNormalMaps + ); +} + +Material::UpdateFlags MaterialSystem::update(bool forceUpdate) +{ + // Some materials only determine the required number of textures during their update. + // If the number of required textures is greated than getMaxTextureCount() reported, + // we could run out of texture slots. This is why metadata is reupdated again, + // after the material's update() has been called. (A typical example is UDIM, the number + // of actually used texture slots depends on the number of files on drive) + bool reupdateMetadata = false; + + // If materials were added/removed since last update, we update all metadata + // and trigger re-creation of the parameter block and update of all materials. + if (forceUpdate || mMaterialsChanged) { - // Some materials only determine the required number of textures during their update. - // If the number of required textures is greated than getMaxTextureCount() reported, - // we could run out of texture slots. This is why metadata is reupdated again, - // after the material's update() has been called. (A typical example is UDIM, the number - // of actually used texture slots depends on the number of files on drive) - bool reupdateMetadata = false; + updateMetadata(); + updateUI(); - // If materials were added/removed since last update, we update all metadata - // and trigger re-creation of the parameter block and update of all materials. - if (forceUpdate || mMaterialsChanged) - { - updateMetadata(); - updateUI(); + mpMaterialsBlock = nullptr; + mMaterialsChanged = false; + forceUpdate = true; + reupdateMetadata = true; + } - mpMaterialsBlock = nullptr; - mMaterialsChanged = false; - forceUpdate = true; - reupdateMetadata = true; - } + // Update all materials. + // Do either a full update of all materials with deferred texture loading, or an update of just the dynamic materials. + // We track per-material update flags along with the combined update flags across all materials. + // Note that materials can record updates in between calls to update() and/or return flags from their update() calls. + Material::UpdateFlags updateFlags = Material::UpdateFlags::None; + mMaterialsUpdateFlags.resize(mMaterials.size()); + std::fill(mMaterialsUpdateFlags.begin(), mMaterialsUpdateFlags.end(), Material::UpdateFlags::None); - // Update all materials. - // Do either a full update of all materials with deferred texture loading, or an update of just the dynamic materials. - // We track per-material update flags along with the combined update flags across all materials. - // Note that materials can record updates in between calls to update() and/or return flags from their update() calls. - Material::UpdateFlags updateFlags = Material::UpdateFlags::None; - mMaterialsUpdateFlags.resize(mMaterials.size()); - std::fill(mMaterialsUpdateFlags.begin(), mMaterialsUpdateFlags.end(), Material::UpdateFlags::None); - - auto updateMaterial = [&](const MaterialID materialID) { - auto& pMaterial = getMaterial(materialID); - if (pMaterial->mpDevice != mpDevice) - FALCOR_THROW("Material '{}' was created with a different device than the MaterialSystem.", pMaterial->getName()); - Material::UpdateFlags flags = pMaterial->update(this); - // Record update flags. - mMaterialsUpdateFlags[materialID.get()] = flags; - updateFlags |= flags; - }; - - if (forceUpdate || mMaterialUpdates != Material::UpdateFlags::None) - { - mpTextureManager->beginDeferredLoading(); + auto updateMaterial = [&](const MaterialID materialID) + { + auto& pMaterial = getMaterial(materialID); + if (pMaterial->mpDevice != mpDevice) + FALCOR_THROW("Material '{}' was created with a different device than the MaterialSystem.", pMaterial->getName()); + Material::UpdateFlags flags = pMaterial->update(this); + // Record update flags. + mMaterialsUpdateFlags[materialID.get()] = flags; + updateFlags |= flags; + }; - for (size_t materialIdx = 0; materialIdx < mMaterials.size(); ++materialIdx) - updateMaterial(MaterialID{ materialIdx }); + if (forceUpdate || mMaterialUpdates != Material::UpdateFlags::None) + { + mpTextureManager->beginDeferredLoading(); - mpTextureManager->endDeferredLoading(); - } - else - { - for (const auto& materialID : mDynamicMaterialIDs) - updateMaterial(materialID); - } + for (size_t materialIdx = 0; materialIdx < mMaterials.size(); ++materialIdx) + updateMaterial(MaterialID{materialIdx}); - if (reupdateMetadata) - { - updateMetadata(); - reupdateMetadata = false; - } + mpTextureManager->endDeferredLoading(); + } + else + { + for (const auto& materialID : mDynamicMaterialIDs) + updateMaterial(materialID); + } - if (mpLightProfile && !mLightProfileBaked) - { - mpLightProfile->bake(mpDevice->getRenderContext()); - mLightProfileBaked = true; - } + if (reupdateMetadata) + { + updateMetadata(); + reupdateMetadata = false; + } - // Include updates recorded since last update. - // After this point no more material changes are expected. - updateFlags |= mMaterialUpdates; - mMaterialUpdates = Material::UpdateFlags::None; + if (mpLightProfile && !mLightProfileBaked) + { + mpLightProfile->bake(mpDevice->getRenderContext()); + mLightProfileBaked = true; + } - // Create parameter block if needed. - if (!mpMaterialsBlock) - { - createParameterBlock(); + // Include updates recorded since last update. + // After this point no more material changes are expected. + updateFlags |= mMaterialUpdates; + mMaterialUpdates = Material::UpdateFlags::None; - // Set update flags if parameter block changes. - // TODO: We may want to introduce MaterialSystem::UpdateFlags instead of re-using the material flags. - updateFlags |= Material::UpdateFlags::DataChanged | Material::UpdateFlags::ResourcesChanged; + // Create parameter block if needed. + if (!mpMaterialsBlock) + { + createParameterBlock(); - forceUpdate = true; // Trigger full upload of all materials. - } + // Set update flags if parameter block changes. + // TODO: We may want to introduce MaterialSystem::UpdateFlags instead of re-using the material flags. + updateFlags |= Material::UpdateFlags::DataChanged | Material::UpdateFlags::ResourcesChanged; - // Upload all modified materials. - if (forceUpdate || is_set(updateFlags, Material::UpdateFlags::DataChanged)) + forceUpdate = true; // Trigger full upload of all materials. + } + + // Upload all modified materials. + if (forceUpdate || is_set(updateFlags, Material::UpdateFlags::DataChanged)) + { + for (uint32_t materialID = 0; materialID < (uint32_t)mMaterials.size(); ++materialID) { - for (uint32_t materialID = 0; materialID < (uint32_t)mMaterials.size(); ++materialID) + if (forceUpdate || is_set(mMaterialsUpdateFlags[materialID], Material::UpdateFlags::DataChanged)) { - if (forceUpdate || is_set(mMaterialsUpdateFlags[materialID], Material::UpdateFlags::DataChanged)) - { - uploadMaterial(materialID); - } + uploadMaterial(materialID); } } + } - auto blockVar = mpMaterialsBlock->getRootVar(); + auto blockVar = mpMaterialsBlock->getRootVar(); - // Update samplers. - if (forceUpdate || mSamplersChanged) + // Update samplers. + if (forceUpdate || mSamplersChanged) + { + auto var = blockVar[kMaterialSamplersName]; + for (size_t i = 0; i < mTextureSamplers.size(); i++) { - auto var = blockVar[kMaterialSamplersName]; - for (size_t i = 0; i < mTextureSamplers.size(); i++) - { - var[i] = mTextureSamplers[i]; - } - mSamplersChanged = false; + var[i] = mTextureSamplers[i]; } + mSamplersChanged = false; + } - // Update textures. - if (forceUpdate || is_set(updateFlags, Material::UpdateFlags::ResourcesChanged)) - { - FALCOR_ASSERT(!mMaterialsChanged); - mpTextureManager->bindShaderData(blockVar[kMaterialTexturesName], mTextureDescCount, - blockVar["udimIndirection"]); - } + // Update textures. + if (forceUpdate || is_set(updateFlags, Material::UpdateFlags::ResourcesChanged)) + { + FALCOR_ASSERT(!mMaterialsChanged); + mpTextureManager->bindShaderData(blockVar[kMaterialTexturesName], mTextureDescCount, blockVar["udimIndirection"]); + } - // Update buffers. - if (forceUpdate || mBuffersChanged) + // Update buffers. + if (forceUpdate || mBuffersChanged) + { + auto var = blockVar[kMaterialBuffersName]; + for (size_t i = 0; i < mBuffers.size(); i++) { - auto var = blockVar[kMaterialBuffersName]; - for (size_t i = 0; i < mBuffers.size(); i++) - { - var[i] = mBuffers[i]; - } - mBuffersChanged = false; + var[i] = mBuffers[i]; } + mBuffersChanged = false; + } - // Update 3D textures. - if (forceUpdate || mTextures3DChanged) + // Update 3D textures. + if (forceUpdate || mTextures3DChanged) + { + auto var = blockVar[kMaterialTextures3DName]; + for (size_t i = 0; i < mTextures3D.size(); i++) { - auto var = blockVar[kMaterialTextures3DName]; - for (size_t i = 0; i < mTextures3D.size(); i++) - { - var[i] = mTextures3D[i]; - } - mTextures3DChanged = false; + var[i] = mTextures3D[i]; } + mTextures3DChanged = false; + } - // Update shader modules and type conformances. - // This is done by iterating over all materials to query their properties. - // We de-duplicate the result by material type to store the unique set of shader modules and type conformances. - // Note that this means the shader code for all materials of the same type is assumed to be identical. - if (forceUpdate || is_set(updateFlags, Material::UpdateFlags::CodeChanged)) - { - mShaderModules.clear(); - mTypeConformances.clear(); + // Update shader modules and type conformances. + // This is done by iterating over all materials to query their properties. + // We de-duplicate the result by material type to store the unique set of shader modules and type conformances. + // Note that this means the shader code for all materials of the same type is assumed to be identical. + if (forceUpdate || is_set(updateFlags, Material::UpdateFlags::CodeChanged)) + { + mShaderModules.clear(); + mTypeConformances.clear(); - for (const auto& pMaterial : mMaterials) + for (const auto& pMaterial : mMaterials) + { + const auto materialType = pMaterial->getType(); + if (mTypeConformances.find(materialType) == mTypeConformances.end()) { - const auto materialType = pMaterial->getType(); - if (mTypeConformances.find(materialType) == mTypeConformances.end()) + auto modules = pMaterial->getShaderModules(); + // To avoid duplicating modules + for (auto& m : modules) { - auto modules = pMaterial->getShaderModules(); - mShaderModules.insert(mShaderModules.end(), modules.begin(), modules.end()); - mTypeConformances[materialType] = pMaterial->getTypeConformances(); + if (std::find(mShaderModules.begin(), mShaderModules.end(), m) == mShaderModules.end()) + mShaderModules.push_back(m); } + mTypeConformances[materialType] = pMaterial->getTypeConformances(); } } - - FALCOR_CHECK(mMaterialUpdates == Material::UpdateFlags::None, "Unexpected material updates."); - return updateFlags; } - void MaterialSystem::updateMetadata() - { - mTextureDescCount = mReservedTextureDescCount; - mBufferDescCount = mReservedBufferDescCount; - mTexture3DDescCount = mReservedTexture3DDescCount; + FALCOR_CHECK(mMaterialUpdates == Material::UpdateFlags::None, "Unexpected material updates."); + return updateFlags; +} - mMaterialCountByType.resize(getMaterialTypeCount()); - std::fill(mMaterialCountByType.begin(), mMaterialCountByType.end(), 0); - mMaterialTypes.clear(); - mHasSpecGlossStandardMaterial = false; - mDynamicMaterialIDs.clear(); +void MaterialSystem::updateMetadata() +{ + mTextureDescCount = mReservedTextureDescCount; + mBufferDescCount = mReservedBufferDescCount; + mTexture3DDescCount = mReservedTexture3DDescCount; + + mMaterialCountByType.resize(getMaterialTypeCount()); + std::fill(mMaterialCountByType.begin(), mMaterialCountByType.end(), 0); + mMaterialTypes.clear(); + mHasSpecGlossStandardMaterial = false; + mDynamicMaterialIDs.clear(); + + for (size_t materialIdx = 0; materialIdx < mMaterials.size(); materialIdx++) + { + const auto& pMaterial = mMaterials[materialIdx]; + const MaterialID materialID{materialIdx}; + + if (pMaterial->isDynamic()) + mDynamicMaterialIDs.push_back(materialID); + + // Update descriptor counts. These counts will be reported by getDefines(). + // TODO: Remove this when unbounded descriptor arrays are supported (#1321). + mTextureDescCount += pMaterial->getMaxTextureCount(); + mBufferDescCount += pMaterial->getMaxBufferCount(); + mTexture3DDescCount += pMaterial->getMaxTexture3DCount(); + + // Update material type info. + size_t index = (size_t)pMaterial->getType(); + FALCOR_ASSERT(index < mMaterialCountByType.size()); + mMaterialCountByType[index]++; + mMaterialTypes.insert(pMaterial->getType()); + if (isSpecGloss(pMaterial)) + mHasSpecGlossStandardMaterial = true; + } + + FALCOR_CHECK( + mMaterialTypes.find(MaterialType::Unknown) == mMaterialTypes.end(), + "Unknown material type found. Make sure all material types are registered." + ); +} - for (size_t materialIdx = 0; materialIdx < mMaterials.size(); materialIdx++) - { - const auto& pMaterial = mMaterials[materialIdx]; - const MaterialID materialID{ materialIdx }; - - if (pMaterial->isDynamic()) - mDynamicMaterialIDs.push_back(materialID); - - // Update descriptor counts. These counts will be reported by getDefines(). - // TODO: Remove this when unbounded descriptor arrays are supported (#1321). - mTextureDescCount += pMaterial->getMaxTextureCount(); - mBufferDescCount += pMaterial->getMaxBufferCount(); - mTexture3DDescCount += pMaterial->getMaxTexture3DCount(); - - // Update material type info. - size_t index = (size_t)pMaterial->getType(); - FALCOR_ASSERT(index < mMaterialCountByType.size()); - mMaterialCountByType[index]++; - mMaterialTypes.insert(pMaterial->getType()); - if (isSpecGloss(pMaterial)) mHasSpecGlossStandardMaterial = true; - } +MaterialSystem::MaterialStats MaterialSystem::getStats() const +{ + FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); - FALCOR_CHECK(mMaterialTypes.find(MaterialType::Unknown) == mMaterialTypes.end(), "Unknown material type found. Make sure all material types are registered."); - } + MaterialStats s = {}; + + s.materialTypeCount = mMaterialTypes.size(); + s.materialCount = mMaterials.size(); + s.materialOpaqueCount = 0; + s.materialMemoryInBytes += mpMaterialDataBuffer ? mpMaterialDataBuffer->getSize() : 0; - MaterialSystem::MaterialStats MaterialSystem::getStats() const + for (const auto& pMaterial : mMaterials) { - FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); + if (pMaterial->isOpaque()) + s.materialOpaqueCount++; + } - MaterialStats s = {}; + const auto textureStats = mpTextureManager->getStats(); + s.textureCount = textureStats.textureCount; + s.textureCompressedCount = textureStats.textureCompressedCount; + s.textureTexelCount = textureStats.textureTexelCount; + s.textureTexelChannelCount = textureStats.textureTexelChannelCount; + s.textureMemoryInBytes = textureStats.textureMemoryInBytes; - s.materialTypeCount = mMaterialTypes.size(); - s.materialCount = mMaterials.size(); - s.materialOpaqueCount = 0; - s.materialMemoryInBytes += mpMaterialDataBuffer ? mpMaterialDataBuffer->getSize() : 0; + return s; +} - for (const auto& pMaterial : mMaterials) - { - if (pMaterial->isOpaque()) s.materialOpaqueCount++; - } +void MaterialSystem::loadLightProfile(const std::filesystem::path& absoluteFilename, bool normalize) +{ + FALCOR_ASSERT(absoluteFilename.is_absolute()); + mpLightProfile = LightProfile::createFromIesProfile(mpDevice, absoluteFilename, normalize); + mLightProfileBaked = false; +} - const auto textureStats = mpTextureManager->getStats(); - s.textureCount = textureStats.textureCount; - s.textureCompressedCount = textureStats.textureCompressedCount; - s.textureTexelCount = textureStats.textureTexelCount; - s.textureTexelChannelCount = textureStats.textureTexelChannelCount; - s.textureMemoryInBytes = textureStats.textureMemoryInBytes; +void MaterialSystem::getDefines(DefineList& defines) const +{ + FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); - return s; - } + size_t materialInstanceByteSize = 0; + for (auto& it : mMaterials) + materialInstanceByteSize = std::max(materialInstanceByteSize, it->getMaterialInstanceByteSize()); - void MaterialSystem::loadLightProfile(const std::filesystem::path& absoluteFilename, bool normalize) - { - FALCOR_ASSERT(absoluteFilename.is_absolute()); - mpLightProfile = LightProfile::createFromIesProfile(mpDevice, absoluteFilename, normalize); - mLightProfileBaked = false; - } + defines.add("MATERIAL_SYSTEM_SAMPLER_DESC_COUNT", std::to_string(kMaxSamplerCount)); + defines.add("MATERIAL_SYSTEM_TEXTURE_DESC_COUNT", std::to_string(mTextureDescCount)); + defines.add("MATERIAL_SYSTEM_BUFFER_DESC_COUNT", std::to_string(mBufferDescCount)); + defines.add("MATERIAL_SYSTEM_TEXTURE_3D_DESC_COUNT", std::to_string(mTexture3DDescCount)); + defines.add("MATERIAL_SYSTEM_UDIM_INDIRECTION_ENABLED", mpTextureManager->getUdimIndirectionCount() > 0 ? "1" : "0"); + defines.add("MATERIAL_SYSTEM_HAS_SPEC_GLOSS_MATERIALS", mHasSpecGlossStandardMaterial ? "1" : "0"); + defines.add("MATERIAL_SYSTEM_USE_LIGHT_PROFILE", mpLightProfile != nullptr ? "1" : "0"); + defines.add("FALCOR_MATERIAL_INSTANCE_SIZE", std::to_string(materialInstanceByteSize)); - void MaterialSystem::getDefines(DefineList& defines) const + // Add defines specified by the materials. + // We ensure that two materials cannot set the same define to mismatching values. + for (const auto& material : mMaterials) { - FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); - - size_t materialInstanceByteSize = 0; - for (auto& it : mMaterials) - materialInstanceByteSize = std::max(materialInstanceByteSize, it->getMaterialInstanceByteSize()); - - defines.add("MATERIAL_SYSTEM_SAMPLER_DESC_COUNT", std::to_string(kMaxSamplerCount)); - defines.add("MATERIAL_SYSTEM_TEXTURE_DESC_COUNT", std::to_string(mTextureDescCount)); - defines.add("MATERIAL_SYSTEM_BUFFER_DESC_COUNT", std::to_string(mBufferDescCount)); - defines.add("MATERIAL_SYSTEM_TEXTURE_3D_DESC_COUNT", std::to_string(mTexture3DDescCount)); - defines.add("MATERIAL_SYSTEM_UDIM_INDIRECTION_ENABLED", mpTextureManager->getUdimIndirectionCount() > 0 ? "1" : "0"); - defines.add("MATERIAL_SYSTEM_HAS_SPEC_GLOSS_MATERIALS", mHasSpecGlossStandardMaterial ? "1" : "0"); - defines.add("MATERIAL_SYSTEM_USE_LIGHT_PROFILE", mpLightProfile != nullptr ? "1" : "0"); - defines.add("FALCOR_MATERIAL_INSTANCE_SIZE", std::to_string(materialInstanceByteSize)); - - // Add defines specified by the materials. - // We ensure that two materials cannot set the same define to mismatching values. - for (const auto& material : mMaterials) + DefineList materialDefines = material->getDefines(); + for (const auto& [name, value] : materialDefines) { - DefineList materialDefines = material->getDefines(); - for (const auto& [name, value] : materialDefines) + if (auto it = defines.find(name); it != defines.end()) { - if (auto it = defines.find(name); it != defines.end()) - { - FALCOR_CHECK(it->second == value, "Mismatching values '{}' and '{}' for material define '{}'.", it->second, value, name); - } - else - { - defines.add(name, value); - } + FALCOR_CHECK(it->second == value, "Mismatching values '{}' and '{}' for material define '{}'.", it->second, value, name); + } + else + { + defines.add(name, value); } } } +} - void MaterialSystem::getTypeConformances(TypeConformanceList& typeConformances) const - { - for (const auto& it : mTypeConformances) - { - typeConformances.add(it.second); - } - typeConformances.add("NullPhaseFunction", "IPhaseFunction", 0); - typeConformances.add("IsotropicPhaseFunction", "IPhaseFunction", 1); - typeConformances.add("HenyeyGreensteinPhaseFunction", "IPhaseFunction", 2); - typeConformances.add("DualHenyeyGreensteinPhaseFunction", "IPhaseFunction", 3); - } - - TypeConformanceList MaterialSystem::getTypeConformances(const MaterialType type) const +void MaterialSystem::getTypeConformances(TypeConformanceList& typeConformances) const +{ + for (const auto& it : mTypeConformances) { - auto it = mTypeConformances.find(type); - if (it == mTypeConformances.end()) - { - FALCOR_THROW(fmt::format("No type conformances for material type '{}'.", to_string(type))); - } - return it->second; + typeConformances.add(it.second); } + typeConformances.add("NullPhaseFunction", "IPhaseFunction", 0); + typeConformances.add("IsotropicPhaseFunction", "IPhaseFunction", 1); + typeConformances.add("HenyeyGreensteinPhaseFunction", "IPhaseFunction", 2); + typeConformances.add("DualHenyeyGreensteinPhaseFunction", "IPhaseFunction", 3); +} - ProgramDesc::ShaderModuleList MaterialSystem::getShaderModules() const +TypeConformanceList MaterialSystem::getTypeConformances(const MaterialType type) const +{ + auto it = mTypeConformances.find(type); + if (it == mTypeConformances.end()) { - FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); - return mShaderModules; + FALCOR_THROW(fmt::format("No type conformances for material type '{}'.", to_string(type))); } + return it->second; +} - void MaterialSystem::getShaderModules(ProgramDesc::ShaderModuleList& shaderModuleList) const - { - FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); - shaderModuleList.insert(shaderModuleList.end(), mShaderModules.begin(), mShaderModules.end()); - } +ProgramDesc::ShaderModuleList MaterialSystem::getShaderModules() const +{ + FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); + return mShaderModules; +} - void MaterialSystem::bindShaderData(const ShaderVar& var) const - { - FALCOR_CHECK(mpMaterialsBlock != nullptr && !mMaterialsChanged, "Parameter block is not ready. Call update() first."); - var = mpMaterialsBlock; - } +void MaterialSystem::getShaderModules(ProgramDesc::ShaderModuleList& shaderModuleList) const +{ + FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); + shaderModuleList.insert(shaderModuleList.end(), mShaderModules.begin(), mShaderModules.end()); +} - void MaterialSystem::createParameterBlock() - { - // Create parameter block. - DefineList defines = getDefines(); - defines.add("MATERIAL_SYSTEM_PARAMETER_BLOCK"); - auto pPass = ComputePass::create(mpDevice, kShaderFilename, "main", defines); - auto pReflector = pPass->getProgram()->getReflector()->getParameterBlock("gMaterialsBlock"); - FALCOR_ASSERT(pReflector); +void MaterialSystem::bindShaderData(const ShaderVar& var) const +{ + FALCOR_CHECK(mpMaterialsBlock != nullptr && !mMaterialsChanged, "Parameter block is not ready. Call update() first."); + var = mpMaterialsBlock; +} - mpMaterialsBlock = ParameterBlock::create(mpDevice, pReflector); - FALCOR_ASSERT(mpMaterialsBlock); +void MaterialSystem::createParameterBlock() +{ + // Create parameter block. + DefineList defines = getDefines(); + defines.add("MATERIAL_SYSTEM_PARAMETER_BLOCK"); + auto pPass = ComputePass::create(mpDevice, kShaderFilename, "main", defines); + auto pReflector = pPass->getProgram()->getReflector()->getParameterBlock("gMaterialsBlock"); + FALCOR_ASSERT(pReflector); - // Verify that the material data struct size on the GPU matches the host-side size. - auto reflVar = mpMaterialsBlock->getReflection()->findMember(kMaterialDataName); - FALCOR_ASSERT(reflVar); - auto reflResType = reflVar->getType()->asResourceType(); - FALCOR_ASSERT(reflResType && reflResType->getType() == ReflectionResourceType::Type::StructuredBuffer); - auto byteSize = reflResType->getStructType()->getByteSize(); - if (byteSize != sizeof(MaterialDataBlob)) - { - FALCOR_THROW("MaterialSystem material data buffer has unexpected struct size"); - } + mpMaterialsBlock = ParameterBlock::create(mpDevice, pReflector); + FALCOR_ASSERT(mpMaterialsBlock); - auto blockVar = mpMaterialsBlock->getRootVar(); + // Verify that the material data struct size on the GPU matches the host-side size. + auto reflVar = mpMaterialsBlock->getReflection()->findMember(kMaterialDataName); + FALCOR_ASSERT(reflVar); + auto reflResType = reflVar->getType()->asResourceType(); + FALCOR_ASSERT(reflResType && reflResType->getType() == ReflectionResourceType::Type::StructuredBuffer); + auto byteSize = reflResType->getStructType()->getByteSize(); + if (byteSize != sizeof(MaterialDataBlob)) + { + FALCOR_THROW("MaterialSystem material data buffer has unexpected struct size"); + } - // Create materials data buffer. - if (!mMaterials.empty() && (!mpMaterialDataBuffer || mpMaterialDataBuffer->getElementCount() < mMaterials.size())) - { - mpMaterialDataBuffer = mpDevice->createStructuredBuffer(blockVar[kMaterialDataName], (uint32_t)mMaterials.size(), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, nullptr, false); - mpMaterialDataBuffer->setName("MaterialSystem::mpMaterialDataBuffer"); - } + auto blockVar = mpMaterialsBlock->getRootVar(); - // Bind resources to parameter block. - blockVar[kMaterialDataName] = !mMaterials.empty() ? mpMaterialDataBuffer : nullptr; - blockVar["materialCount"] = getMaterialCount(); - if (mpLightProfile) - { - FALCOR_ASSERT(mLightProfileBaked); - mpLightProfile->bindShaderData(blockVar[kLightProfile]); - } + // Create materials data buffer. + if (!mMaterials.empty() && (!mpMaterialDataBuffer || mpMaterialDataBuffer->getElementCount() < mMaterials.size())) + { + mpMaterialDataBuffer = mpDevice->createStructuredBuffer( + blockVar[kMaterialDataName], + (uint32_t)mMaterials.size(), + ResourceBindFlags::ShaderResource, + MemoryType::DeviceLocal, + nullptr, + false + ); + mpMaterialDataBuffer->setName("MaterialSystem::mpMaterialDataBuffer"); } - void MaterialSystem::uploadMaterial(const uint32_t materialID) + // Bind resources to parameter block. + blockVar[kMaterialDataName] = !mMaterials.empty() ? mpMaterialDataBuffer : nullptr; + blockVar["materialCount"] = getMaterialCount(); + if (mpLightProfile) { - FALCOR_ASSERT(materialID < mMaterials.size()); - const auto& pMaterial = mMaterials[materialID]; - FALCOR_ASSERT(pMaterial); - - // TODO: On initial upload of materials, we could improve this by not having separate calls to setElement() - // but instead prepare a buffer containing all data. - FALCOR_ASSERT(mpMaterialDataBuffer); - mpMaterialDataBuffer->setElement(materialID, pMaterial->getDataBlob()); + FALCOR_ASSERT(mLightProfileBaked); + mpLightProfile->bindShaderData(blockVar[kLightProfile]); } } + +void MaterialSystem::uploadMaterial(const uint32_t materialID) +{ + FALCOR_ASSERT(materialID < mMaterials.size()); + const auto& pMaterial = mMaterials[materialID]; + FALCOR_ASSERT(pMaterial); + + // TODO: On initial upload of materials, we could improve this by not having separate calls to setElement() + // but instead prepare a buffer containing all data. + FALCOR_ASSERT(mpMaterialDataBuffer); + mpMaterialDataBuffer->setElement(materialID, pMaterial->getDataBlob()); +} +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MaterialSystem.h b/Source/Falcor/Scene/Material/MaterialSystem.h index 852e07689c..eb153d1843 100644 --- a/Source/Falcor/Scene/Material/MaterialSystem.h +++ b/Source/Falcor/Scene/Material/MaterialSystem.h @@ -42,270 +42,278 @@ namespace Falcor { - class LightProfile; +class LightProfile; - /** This class represents a material system. +/** This class represents a material system. - It holds a collection of materials and their associated resources. - The host side has interfaces for managing the materials, rendering - the UI, and uploading/updating GPU buffers, etc. + It holds a collection of materials and their associated resources. + The host side has interfaces for managing the materials, rendering + the UI, and uploading/updating GPU buffers, etc. - The matching shader side Slang module holds all GPU resources and - has interfaces for preparing the material at a shading point. + The matching shader side Slang module holds all GPU resources and + has interfaces for preparing the material at a shading point. - The update() function must be called before using the material system. - It ensures all GPU data is uploaded and ready for use. - */ - class FALCOR_API MaterialSystem + The update() function must be called before using the material system. + It ensures all GPU data is uploaded and ready for use. +*/ +class FALCOR_API MaterialSystem +{ +public: + struct MaterialStats { - public: - struct MaterialStats - { - uint64_t materialTypeCount = 0; ///< Number of material types. - uint64_t materialCount = 0; ///< Number of materials. - uint64_t materialOpaqueCount = 0; ///< Number of materials that are opaque. - uint64_t materialMemoryInBytes = 0; ///< Total memory in bytes used by the material data. - uint64_t textureCount = 0; ///< Number of unique textures. A texture can be referenced by multiple materials. - uint64_t textureCompressedCount = 0; ///< Number of unique compressed textures. - uint64_t textureTexelCount = 0; ///< Total number of texels in all textures. - uint64_t textureTexelChannelCount = 0; ///< Total number of texel channels in all textures. - uint64_t textureMemoryInBytes = 0; ///< Total memory in bytes used by the textures. - }; - - /** Constructor. Throws an exception if creation failed. - */ - MaterialSystem(ref pDevice); - - /** Render the UI. - */ - void renderUI(Gui::Widgets& widget); - - /** Update material system. This prepares all resources for rendering. - */ - Material::UpdateFlags update(bool forceUpdate); - - /** Get shader defines. - These need to be set before binding the material system parameter block. - Adds defines to an existing list, rather than creating a new list. - \param[in,out] defines List of type defines. - */ - void getDefines(DefineList& defines) const; - DefineList getDefines() const - { - DefineList result; - getDefines(result); - return result; - } - - /** Get type conformances for all material types used. - These need to be set on a program before using the material system in shaders - that need to create a material of *any* type, such as compute or raygen shaders. - \param[in,out] conformances List of type conformances. - */ - void getTypeConformances(TypeConformanceList& conformances) const; - TypeConformanceList getTypeConformances() const - { - TypeConformanceList typeConformances; - getTypeConformances(typeConformances); - return typeConformances; - } - - /** Get type conformances for a given material type in use. - \param[in] type Material type. - \return List of type conformances. - */ - TypeConformanceList getTypeConformances(const MaterialType type) const; - - /** Get shader modules for all materials in use. - The shader modules must be added to any program using the material system. - \param[in,out] shaderModuleList List of shader modules. - */ - void getShaderModules(ProgramDesc::ShaderModuleList& shaderModuleList) const; - - /** Get shader modules for all materials in use. - The shader modules must be added to any program using the material system. - \return List of shader modules. - */ - ProgramDesc::ShaderModuleList getShaderModules() const; - - /** Bind the material system to a shader var. - */ - void bindShaderData(const ShaderVar& var) const; - - /** Set a default texture sampler to use for all materials. - */ - void setDefaultTextureSampler(const ref& pSampler); - - /** Add a texture sampler. - If an identical sampler already exists, the sampler is not added and the existing ID returned. - \param[in] pSampler The sampler. - \return The ID of the sampler. - */ - uint32_t addTextureSampler(const ref& pSampler); - - /** Get the total number of texture samplers. - */ - uint32_t getTextureSamplerCount() const { return (uint32_t)mTextureSamplers.size(); } - - /** Get a texture sampler by ID. - */ - const ref& getTextureSampler(const uint32_t samplerID) const { return mTextureSamplers[samplerID]; } - - /** Add a buffer resource to be managed. - \param[in] pBuffer The buffer. - \return The ID of the buffer. - */ - uint32_t addBuffer(const ref& pBuffer); - - /** Replace a previously managed buffer by a new buffer. - \param[in] id The ID of the buffer. - \param[in] pBuffer The buffer. - */ - void replaceBuffer(uint32_t id, const ref& pBuffer); - - /** Get the total number of managed buffers. - */ - uint32_t getBufferCount() const { return (uint32_t)mBuffers.size(); } - - /** Add a 3D texture resource to be managed. - \param[in] pTexture The texture. - \return The ID of the texture. - */ - uint32_t addTexture3D(const ref& pTexture); - - /** Get the total number of 3D textures. - */ - uint32_t getTexture3DCount() const { return (uint32_t)mTextures3D.size(); } - - /** Add a material. - If an identical material already exists, the material is not added and the existing ID returned. - \param[in] pMaterial The material. - \return The ID of the material. - */ - MaterialID addMaterial(const ref& pMaterial); - - /** Remove a material. - \param[in] materialID The ID of the material to remove. - */ - void removeMaterial(const MaterialID materialID); - - /** Replace a material. - \param materialID The ID of the material to replace. - \param pReplacement The material to replace it with. - */ - void replaceMaterial(const MaterialID materialID, const ref& pReplacement); - void replaceMaterial(const ref& pMaterial, const ref& pReplacement); - - /** Get a list of all materials. - */ - const std::vector>& getMaterials() const { return mMaterials; } - - /** Get the total number of materials. - */ - uint32_t getMaterialCount() const { return (uint32_t)mMaterials.size(); } - - /** Get the number of materials of the given type. - */ - uint32_t getMaterialCountByType(const MaterialType type) const; - - /** Get the set of all material types used. - */ - std::set getMaterialTypes() const; - - /** Check if material of the given type is used. - */ - bool hasMaterialType(MaterialType type) const; - - /** Check if a material with the given ID exists. - \param[in] materialID The material ID. - \return True if the material exists. - */ - bool hasMaterial(const MaterialID materialID) const; - - /** Get a material by ID. - \param[in] materialID The material ID. - \return The material, or throws if the material doesn't exist. - */ - const ref& getMaterial(const MaterialID materialID) const; - - /** Get a material by name. - \return The material, or nullptr if material doesn't exist. - */ - ref getMaterialByName(const std::string& name) const; - - /** Remove all duplicate materials. - \param[in] idMap Vector that holds for each material the ID of the material that replaces it. - \return The number of materials removed. - */ - size_t removeDuplicateMaterials(std::vector& idMap); - - /** Optimize materials. - This function analyzes textures and replaces constant textures by uniform material parameters. - */ - void optimizeMaterials(); - - /** Get stats for the material system. This can be a slow operation. - */ - MaterialStats getStats() const; - - /** Get texture manager. This holds all textures. - */ - TextureManager& getTextureManager() { return *mpTextureManager; } - - - void loadLightProfile(const std::filesystem::path& absoluteFilename, bool normalize); - - const LightProfile* getLightProfile() const { return mpLightProfile.get(); } - private: - void updateMetadata(); - void updateUI(); - void createParameterBlock(); - void uploadMaterial(const uint32_t materialID); - - ref mpDevice; - - std::vector> mMaterials; ///< List of all materials. - std::vector mMaterialsUpdateFlags; ///< List of all material update flags, after the update() calls - std::unique_ptr mpTextureManager; ///< Texture manager holding all material textures. - ProgramDesc::ShaderModuleList mShaderModules; ///< Shader modules for all materials in use. - std::map mTypeConformances; ///< Type conformances for each material type in use. - ref mpLightProfile; ///< Global light profile. - bool mLightProfileBaked = true; - - - // Metadata - size_t mTextureDescCount = 0; ///< Number of texture descriptors in GPU descriptor array. This variable is for book-keeping until unbounded descriptor arrays are supported (see #1321). - size_t mBufferDescCount = 0; ///< Number of buffer descriptors in GPU descriptor array. This variable is for book-keeping until unbounded descriptor arrays are supported (see #1321). - size_t mTexture3DDescCount = 0; ///< Number of 3D texture descriptors in GPU descriptor array. This variable is for book-keeping until unbounded descriptor arrays are supported (see #1321). - size_t mReservedTextureDescCount = 0; ///< Number of reserved texture descriptors in GPU descriptor array. This variable is for book-keeping until unbounded descriptor arrays are supported (see #1321). - size_t mReservedBufferDescCount = 0; ///< Number of reserved buffer descriptors in GPU descriptor array. This variable is for book-keeping until unbounded descriptor arrays are supported (see #1321). - size_t mReservedTexture3DDescCount = 0; ///< Number of reserved 3D texture descriptors in GPU descriptor array. This variable is for book-keeping until unbounded descriptor arrays are supported (see #1321). - std::vector mMaterialCountByType; ///< Number of materials of each type, indexed by MaterialType. - std::set mMaterialTypes; ///< Set of all material types used. - bool mHasSpecGlossStandardMaterial = false; ///< True if standard materials using the SpecGloss shading model exist. - std::vector mDynamicMaterialIDs; ///< Material IDs for all dynamic materials. - - bool mSamplersChanged = false; ///< Flag indicating if samplers were added/removed since last update. - bool mBuffersChanged = false; ///< Flag indicating if buffers were added/removed since last update. - bool mTextures3DChanged = false; ///< Flag indicating if 3D textures were added/removed since last update. - bool mMaterialsChanged = false; ///< Flag indicating if materials were added/removed since last update. Per-material updates are tracked by each material's update flags. - - Material::UpdateFlags mMaterialUpdates = Material::UpdateFlags::None; ///< Material updates across all materials since last update. - - // GPU resources - ref mpFence; - ref mpMaterialsBlock; ///< Parameter block for binding all material resources. - ref mpMaterialDataBuffer; ///< GPU buffer holding all material data. - ref mpDefaultTextureSampler; ///< Default texture sampler to use for all materials. - std::vector> mTextureSamplers; ///< Texture sampler states. These are indexed by ID in the materials. - std::vector> mBuffers; ///< Buffers used by the materials. These are indexed by ID in the materials. - std::vector> mTextures3D; ///< 3D textures used by the materials. These are indexed by ID in the materials. - - // UI variables - std::vector mSortedMaterialIndices; ///< Indices of materials, sorted alphabetically by case-insensitive name. - bool mSortMaterialsByName = false; ///< If true, display materials sorted by name, rather than by ID. - - friend class Material; - friend class SceneCache; + uint64_t materialTypeCount = 0; ///< Number of material types. + uint64_t materialCount = 0; ///< Number of materials. + uint64_t materialOpaqueCount = 0; ///< Number of materials that are opaque. + uint64_t materialMemoryInBytes = 0; ///< Total memory in bytes used by the material data. + uint64_t textureCount = 0; ///< Number of unique textures. A texture can be referenced by multiple materials. + uint64_t textureCompressedCount = 0; ///< Number of unique compressed textures. + uint64_t textureTexelCount = 0; ///< Total number of texels in all textures. + uint64_t textureTexelChannelCount = 0; ///< Total number of texel channels in all textures. + uint64_t textureMemoryInBytes = 0; ///< Total memory in bytes used by the textures. }; + + /** Constructor. Throws an exception if creation failed. + */ + MaterialSystem(ref pDevice); + + /** Render the UI. + */ + void renderUI(Gui::Widgets& widget); + + /** Update material system. This prepares all resources for rendering. + */ + Material::UpdateFlags update(bool forceUpdate); + + /** Get shader defines. + These need to be set before binding the material system parameter block. + Adds defines to an existing list, rather than creating a new list. + \param[in,out] defines List of type defines. + */ + void getDefines(DefineList& defines) const; + DefineList getDefines() const + { + DefineList result; + getDefines(result); + return result; + } + + /** Get type conformances for all material types used. + These need to be set on a program before using the material system in shaders + that need to create a material of *any* type, such as compute or raygen shaders. + \param[in,out] conformances List of type conformances. + */ + void getTypeConformances(TypeConformanceList& conformances) const; + TypeConformanceList getTypeConformances() const + { + TypeConformanceList typeConformances; + getTypeConformances(typeConformances); + return typeConformances; + } + + /** Get type conformances for a given material type in use. + \param[in] type Material type. + \return List of type conformances. + */ + TypeConformanceList getTypeConformances(const MaterialType type) const; + + /** Get shader modules for all materials in use. + The shader modules must be added to any program using the material system. + \param[in,out] shaderModuleList List of shader modules. + */ + void getShaderModules(ProgramDesc::ShaderModuleList& shaderModuleList) const; + + /** Get shader modules for all materials in use. + The shader modules must be added to any program using the material system. + \return List of shader modules. + */ + ProgramDesc::ShaderModuleList getShaderModules() const; + + /** Bind the material system to a shader var. + */ + void bindShaderData(const ShaderVar& var) const; + + /** Set a default texture sampler to use for all materials. + */ + void setDefaultTextureSampler(const ref& pSampler); + + /** Add a texture sampler. + If an identical sampler already exists, the sampler is not added and the existing ID returned. + \param[in] pSampler The sampler. + \return The ID of the sampler. + */ + uint32_t addTextureSampler(const ref& pSampler); + + /** Get the total number of texture samplers. + */ + uint32_t getTextureSamplerCount() const { return (uint32_t)mTextureSamplers.size(); } + + /** Get a texture sampler by ID. + */ + const ref& getTextureSampler(const uint32_t samplerID) const { return mTextureSamplers[samplerID]; } + + /** Add a buffer resource to be managed. + \param[in] pBuffer The buffer. + \return The ID of the buffer. + */ + uint32_t addBuffer(const ref& pBuffer); + + /** Replace a previously managed buffer by a new buffer. + \param[in] id The ID of the buffer. + \param[in] pBuffer The buffer. + */ + void replaceBuffer(uint32_t id, const ref& pBuffer); + + /** Get the total number of managed buffers. + */ + uint32_t getBufferCount() const { return (uint32_t)mBuffers.size(); } + + /** Add a 3D texture resource to be managed. + \param[in] pTexture The texture. + \return The ID of the texture. + */ + uint32_t addTexture3D(const ref& pTexture); + + /** Get the total number of 3D textures. + */ + uint32_t getTexture3DCount() const { return (uint32_t)mTextures3D.size(); } + + /** Add a material. + If an identical material already exists, the material is not added and the existing ID returned. + \param[in] pMaterial The material. + \return The ID of the material. + */ + MaterialID addMaterial(const ref& pMaterial); + + /** Remove a material. + \param[in] materialID The ID of the material to remove. + */ + void removeMaterial(const MaterialID materialID); + + /** Replace a material. + \param materialID The ID of the material to replace. + \param pReplacement The material to replace it with. + */ + void replaceMaterial(const MaterialID materialID, const ref& pReplacement); + void replaceMaterial(const ref& pMaterial, const ref& pReplacement); + + /** Get a list of all materials. + */ + const std::vector>& getMaterials() const { return mMaterials; } + + /** Get the total number of materials. + */ + uint32_t getMaterialCount() const { return (uint32_t)mMaterials.size(); } + + /** Get the number of materials of the given type. + */ + uint32_t getMaterialCountByType(const MaterialType type) const; + + /** Get the set of all material types used. + */ + std::set getMaterialTypes() const; + + /** Check if material of the given type is used. + */ + bool hasMaterialType(MaterialType type) const; + + /** Check if a material with the given ID exists. + \param[in] materialID The material ID. + \return True if the material exists. + */ + bool hasMaterial(const MaterialID materialID) const; + + /** Get a material by ID. + \param[in] materialID The material ID. + \return The material, or throws if the material doesn't exist. + */ + const ref& getMaterial(const MaterialID materialID) const; + + /** Get a material by name. + \return The material, or nullptr if material doesn't exist. + */ + ref getMaterialByName(const std::string& name) const; + + /** Remove all duplicate materials. + \param[in] idMap Vector that holds for each material the ID of the material that replaces it. + \return The number of materials removed. + */ + size_t removeDuplicateMaterials(std::vector& idMap); + + /** Optimize materials. + This function analyzes textures and replaces constant textures by uniform material parameters. + */ + void optimizeMaterials(); + + /** Get stats for the material system. This can be a slow operation. + */ + MaterialStats getStats() const; + + /** Get texture manager. This holds all textures. + */ + TextureManager& getTextureManager() { return *mpTextureManager; } + + + void loadLightProfile(const std::filesystem::path& absoluteFilename, bool normalize); + + const LightProfile* getLightProfile() const { return mpLightProfile.get(); } + +private: + void updateMetadata(); + void updateUI(); + void createParameterBlock(); + void uploadMaterial(const uint32_t materialID); + + ref mpDevice; + + std::vector> mMaterials; ///< List of all materials. + std::vector mMaterialsUpdateFlags; ///< List of all material update flags, after the update() calls + std::unique_ptr mpTextureManager; ///< Texture manager holding all material textures. + ProgramDesc::ShaderModuleList mShaderModules; ///< Shader modules for all materials in use. + std::map mTypeConformances; ///< Type conformances for each material type in use. + ref mpLightProfile; ///< Global light profile. + bool mLightProfileBaked = true; + + + // Metadata + size_t mTextureDescCount = 0; ///< Number of texture descriptors in GPU descriptor array. This variable is for book-keeping until + ///< unbounded descriptor arrays are supported (see #1321). + size_t mBufferDescCount = 0; ///< Number of buffer descriptors in GPU descriptor array. This variable is for book-keeping until + ///< unbounded descriptor arrays are supported (see #1321). + size_t mTexture3DDescCount = 0; ///< Number of 3D texture descriptors in GPU descriptor array. This variable is for book-keeping until + ///< unbounded descriptor arrays are supported (see #1321). + size_t mReservedTextureDescCount = 0; ///< Number of reserved texture descriptors in GPU descriptor array. This variable is for + ///< book-keeping until unbounded descriptor arrays are supported (see #1321). + size_t mReservedBufferDescCount = 0; ///< Number of reserved buffer descriptors in GPU descriptor array. This variable is for + ///< book-keeping until unbounded descriptor arrays are supported (see #1321). + size_t mReservedTexture3DDescCount = 0; ///< Number of reserved 3D texture descriptors in GPU descriptor array. This variable is for + ///< book-keeping until unbounded descriptor arrays are supported (see #1321). + std::vector mMaterialCountByType; ///< Number of materials of each type, indexed by MaterialType. + std::set mMaterialTypes; ///< Set of all material types used. + bool mHasSpecGlossStandardMaterial = false; ///< True if standard materials using the SpecGloss shading model exist. + std::vector mDynamicMaterialIDs; ///< Material IDs for all dynamic materials. + + bool mSamplersChanged = false; ///< Flag indicating if samplers were added/removed since last update. + bool mBuffersChanged = false; ///< Flag indicating if buffers were added/removed since last update. + bool mTextures3DChanged = false; ///< Flag indicating if 3D textures were added/removed since last update. + bool mMaterialsChanged = false; ///< Flag indicating if materials were added/removed since last update. Per-material updates are tracked + ///< by each material's update flags. + + Material::UpdateFlags mMaterialUpdates = Material::UpdateFlags::None; ///< Material updates across all materials since last update. + + // GPU resources + ref mpFence; + ref mpMaterialsBlock; ///< Parameter block for binding all material resources. + ref mpMaterialDataBuffer; ///< GPU buffer holding all material data. + ref mpDefaultTextureSampler; ///< Default texture sampler to use for all materials. + std::vector> mTextureSamplers; ///< Texture sampler states. These are indexed by ID in the materials. + std::vector> mBuffers; ///< Buffers used by the materials. These are indexed by ID in the materials. + std::vector> mTextures3D; ///< 3D textures used by the materials. These are indexed by ID in the materials. + + // UI variables + std::vector mSortedMaterialIndices; ///< Indices of materials, sorted alphabetically by case-insensitive name. + bool mSortMaterialsByName = false; ///< If true, display materials sorted by name, rather than by ID. + + friend class Material; + friend class SceneCache; }; +}; // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MaterialSystem.slang b/Source/Falcor/Scene/Material/MaterialSystem.slang index 59cc62c79c..fd4172435c 100644 --- a/Source/Falcor/Scene/Material/MaterialSystem.slang +++ b/Source/Falcor/Scene/Material/MaterialSystem.slang @@ -43,12 +43,13 @@ __exported import Scene.Material.VolumeProperties; #endif /** This struct represents the GPU-side material system. -*/ + */ struct MaterialSystem { - uint materialCount; ///< Total number of materials. - StructuredBuffer materialData; ///< Material parameters. The format of the data blob depends on the material type. - SamplerState materialSamplers[MATERIAL_SYSTEM_SAMPLER_DESC_COUNT]; ///< Sampler states for all materials. TODO: Make this an unbounded array (see #1321). + uint materialCount; ///< Total number of materials. + StructuredBuffer materialData; ///< Material parameters. The format of the data blob depends on the material type. + SamplerState materialSamplers[MATERIAL_SYSTEM_SAMPLER_DESC_COUNT]; ///< Sampler states for all materials. TODO: Make this an unbounded + ///< array (see #1321). /// Texture resources for all materials. TODO: Make this an unbounded array (see #1321). Texture2D materialTextures[ArrayMax<1, MATERIAL_SYSTEM_TEXTURE_DESC_COUNT>.value]; @@ -75,41 +76,26 @@ struct MaterialSystem StructuredBuffer udimIndirection; /** Get the total number of materials. - */ - uint getMaterialCount() - { - return materialCount; - } + */ + uint getMaterialCount() { return materialCount; } /** Get the type of the given material. - */ - MaterialType getMaterialType(const uint materialID) - { - return materialData[materialID].header.getMaterialType(); - } + */ + MaterialType getMaterialType(const uint materialID) { return materialData[materialID].header.getMaterialType(); } /** Get the material header for the given material. - */ - MaterialHeader getMaterialHeader(const uint materialID) - { - return materialData[materialID].header; - } + */ + MaterialHeader getMaterialHeader(const uint materialID) { return materialData[materialID].header; } /** Get the material data blob for the given material. The format of the data blob depends on the material type. */ - MaterialDataBlob getMaterialDataBlob(const uint materialID) - { - return materialData[materialID]; - } + MaterialDataBlob getMaterialDataBlob(const uint materialID) { return materialData[materialID]; } /** Returns true if the given material is a basic material. This function is currently used by legacy code that doesn't all material types, but may be removed in the future. */ - bool isBasicMaterial(const uint materialID) - { - return materialData[materialID].header.isBasicMaterial(); - } + bool isBasicMaterial(const uint materialID) { return materialData[materialID].header.isBasicMaterial(); } /** Get the material data for a basic material. Note that no type checking is done. The caller is responsible for ensuring that the given material is a basic material. @@ -128,10 +114,7 @@ struct MaterialSystem #ifdef FALCOR_VULKAN [ForceInline] #endif - SamplerState getTextureSampler(const uint samplerID) - { - return materialSamplers[samplerID]; - } + SamplerState getTextureSampler(const uint samplerID) { return materialSamplers[samplerID]; } /** Get the default texture sampler for a material. Note: the use of [ForceInline] causes Slang to inline this function in its output source. @@ -148,16 +131,10 @@ struct MaterialSystem } /** Get a buffer by ID. - */ - ByteAddressBuffer getBuffer(const uint bufferID) - { - return materialBuffers[bufferID]; - } + */ + ByteAddressBuffer getBuffer(const uint bufferID) { return materialBuffers[bufferID]; } - Texture3D getTexture3D(const uint textureID) - { - return materialTextures3D[textureID]; - } + Texture3D getTexture3D(const uint textureID) { return materialTextures3D[textureID]; } /** Check if a material has displacement. \param[in] materialID Material ID @@ -216,10 +193,7 @@ struct MaterialSystem \param[in] materialID Material ID \return Index of refraction. */ - float evalIoR(const uint materialID) - { - return getMaterialHeader(materialID).getIoR(); - } + float evalIoR(const uint materialID) { return getMaterialHeader(materialID).getIoR(); } /** Resolves TextureHandle from possibly-UDIM to standard TextureHandle \param[in] handle Texture handle with possibly UDIM flag @@ -305,7 +279,7 @@ struct MaterialSystem \param[in] lod Method for computing texture level-of-detail, must implement the `ITextureSampler` interface. \return Sampled color. */ - float4 sampleTexture(TextureHandle handle, SamplerState s, float2 uv, const float4 uniformValue, L lod) + float4 sampleTexture(TextureHandle handle, SamplerState s, float2 uv, const float4 uniformValue, L lod) { handle = getResolvedTextureHandle(handle, uv); switch (handle.getMode()) @@ -320,7 +294,7 @@ struct MaterialSystem } /** Load displacement mapping data for the given material. - */ + */ void loadDisplacementData(const uint materialID, out DisplacementData displacementData) { // Note we can't zero initialize struct containing resources. @@ -355,6 +329,7 @@ struct MaterialSystem } // If the material isn't displaced, return dummy resources at index 0 instead. + // Note the dummy resources must come from the same descriptor arrays as otherwise, or we get a dxc error (see displacementData.texture = materialTextures[textureID]; displacementData.samplerState = materialSamplers[samplerID]; displacementData.samplerStateMin = materialSamplers[displacementMinSamplerID]; @@ -372,7 +347,8 @@ struct MaterialSystem // Load the material header first and early out if alpha test not supported. const MaterialHeader header = materialData[materialID].header; - if (header.getAlphaMode() != AlphaMode::Mask) return false; + if (header.getAlphaMode() != AlphaMode::Mask) + return false; SamplerState s = getTextureSampler(header.getDefaultTextureSamplerID()); float alpha = sampleTexture(header.getAlphaTextureHandle(), s, v.texC, float4(1.f), lod).a; @@ -394,10 +370,7 @@ struct MaterialSystem return alphaTest(v, materialID, explicitLOD); } - bool lightProfilesEnabled() - { - return kUseLightProfile; - } + bool lightProfilesEnabled() { return kUseLightProfile; } /// Returns true when the material uses LightProfile bool hasLightProfile(const uint materialID) diff --git a/Source/Falcor/Scene/Material/MaterialTextureLoader.cpp b/Source/Falcor/Scene/Material/MaterialTextureLoader.cpp index aeda33fd2e..f8bbca36d4 100644 --- a/Source/Falcor/Scene/Material/MaterialTextureLoader.cpp +++ b/Source/Falcor/Scene/Material/MaterialTextureLoader.cpp @@ -30,55 +30,57 @@ namespace Falcor { - MaterialTextureLoader::MaterialTextureLoader(TextureManager& textureManager, bool useSrgb) - : mUseSrgb(useSrgb) - , mTextureManager(textureManager) - { - } +MaterialTextureLoader::MaterialTextureLoader(TextureManager& textureManager, bool useSrgb) + : mUseSrgb(useSrgb), mTextureManager(textureManager) +{} - MaterialTextureLoader::~MaterialTextureLoader() +MaterialTextureLoader::~MaterialTextureLoader() +{ + assignTextures(); +} + +void MaterialTextureLoader::loadTexture(const ref& pMaterial, Material::TextureSlot slot, const std::filesystem::path& path) +{ + FALCOR_ASSERT(pMaterial); + if (!pMaterial->hasTextureSlot(slot)) { - assignTextures(); + logWarning( + "MaterialTextureLoader::loadTexture() - Material '{}' does not have texture slot '{}'. Ignoring call.", + pMaterial->getName(), + to_string(slot) + ); + return; } - void MaterialTextureLoader::loadTexture(const ref& pMaterial, Material::TextureSlot slot, const std::filesystem::path& path) - { - FALCOR_ASSERT(pMaterial); - if (!pMaterial->hasTextureSlot(slot)) - { - logWarning("MaterialTextureLoader::loadTexture() - Material '{}' does not have texture slot '{}'. Ignoring call.", pMaterial->getName(), to_string(slot)); - return; - } + bool srgb = mUseSrgb && pMaterial->getTextureSlotInfo(slot).srgb; - bool srgb = mUseSrgb && pMaterial->getTextureSlotInfo(slot).srgb; + // Request texture to be loaded. + auto handle = mTextureManager.loadTexture( + path, + true /*mips*/, + srgb, + ResourceBindFlags::ShaderResource, + true /*async*/, + Bitmap::ImportFlags::None, + nullptr /*search dirs*/, + nullptr /*load count*/, + pMaterial.get() + ); - // Request texture to be loaded. - auto handle = mTextureManager.loadTexture( - path, - true /*mips*/, - srgb, - ResourceBindFlags::ShaderResource, - true /*async*/, - Bitmap::ImportFlags::None, - nullptr /*search dirs*/, - nullptr /*load count*/, - pMaterial.get() - ); + // Store assignment to material for later. + mTextureAssignments.emplace_back(TextureAssignment{pMaterial, slot, handle}); +} - // Store assignment to material for later. - mTextureAssignments.emplace_back(TextureAssignment{ pMaterial, slot, handle }); - } +void MaterialTextureLoader::assignTextures() +{ + mTextureManager.waitForAllTexturesLoading(); - void MaterialTextureLoader::assignTextures() + // Assign textures to materials. + for (const auto& assignment : mTextureAssignments) { - mTextureManager.waitForAllTexturesLoading(); - - // Assign textures to materials. - for (const auto& assignment : mTextureAssignments) - { - auto pTexture = mTextureManager.getTexture(assignment.handle); - assignment.pMaterial->setTexture(assignment.textureSlot, pTexture); - } - mTextureAssignments.clear(); + auto pTexture = mTextureManager.getTexture(assignment.handle); + assignment.pMaterial->setTexture(assignment.textureSlot, pTexture); } + mTextureAssignments.clear(); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MaterialTextureLoader.h b/Source/Falcor/Scene/Material/MaterialTextureLoader.h index 15582efc19..2183f9fc3a 100644 --- a/Source/Falcor/Scene/Material/MaterialTextureLoader.h +++ b/Source/Falcor/Scene/Material/MaterialTextureLoader.h @@ -34,43 +34,41 @@ namespace Falcor { - /** Helper class to load material textures using the texture manager. +/** Helper class to load material textures using the texture manager. - Calling `loadTexture` does not assign the texture to the material right away. - Instead, an asynchronous texture load request is issued and a reference for the - material assignment is stored. When the client destroys the instance of the - `MaterialTextureLoader`, it blocks until all textures are loaded and assigns - them to the materials. - */ - class FALCOR_API MaterialTextureLoader - { - public: - MaterialTextureLoader(TextureManager& textureManager, bool useSrgb); - ~MaterialTextureLoader(); + Calling `loadTexture` does not assign the texture to the material right away. + Instead, an asynchronous texture load request is issued and a reference for the + material assignment is stored. When the client destroys the instance of the + `MaterialTextureLoader`, it blocks until all textures are loaded and assigns + them to the materials. +*/ +class FALCOR_API MaterialTextureLoader +{ +public: + MaterialTextureLoader(TextureManager& textureManager, bool useSrgb); + ~MaterialTextureLoader(); - /** Request loading a material texture. - \param[in] pMaterial Material to load texture into. - \param[in] slot Slot to load texture into. - \param[in] path Texture file path. - */ - void loadTexture(const ref& pMaterial, Material::TextureSlot slot, const std::filesystem::path& path); + /** Request loading a material texture. + \param[in] pMaterial Material to load texture into. + \param[in] slot Slot to load texture into. + \param[in] path Texture file path. + */ + void loadTexture(const ref& pMaterial, Material::TextureSlot slot, const std::filesystem::path& path); - void finishLoading() - { - assignTextures(); - } - private: - void assignTextures(); + void finishLoading() { assignTextures(); } - struct TextureAssignment - { - ref pMaterial; - Material::TextureSlot textureSlot; - TextureManager::CpuTextureHandle handle; - }; +private: + void assignTextures(); - bool mUseSrgb; - std::vector mTextureAssignments; - TextureManager& mTextureManager; + struct TextureAssignment + { + ref pMaterial; + Material::TextureSlot textureSlot; + TextureManager::CpuTextureHandle handle; }; -} + + bool mUseSrgb; + std::vector mTextureAssignments; + TextureManager& mTextureManager; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/MaterialTypeRegistry.cpp b/Source/Falcor/Scene/Material/MaterialTypeRegistry.cpp index 9b8eca01fc..f407af825d 100644 --- a/Source/Falcor/Scene/Material/MaterialTypeRegistry.cpp +++ b/Source/Falcor/Scene/Material/MaterialTypeRegistry.cpp @@ -67,8 +67,13 @@ class MaterialTypeRegistry FALCOR_ASSERT_LT(mNextMaterialTypeID, 1u << MaterialHeader::kMaterialTypeBits); if (mNextMaterialTypeID >= (1u << MaterialHeader::kMaterialTypeBits)) { - FALCOR_THROW("Registered material {} would receive MaterialType {}, exceeding the maximum limit {} (given by MaterialHeader::kMaterialTypeBits)", - typeName, mNextMaterialTypeID, (1u << MaterialHeader::kMaterialTypeBits)); + FALCOR_THROW( + "Registered material {} would receive MaterialType {}, exceeding the maximum limit {} (given by " + "MaterialHeader::kMaterialTypeBits)", + typeName, + mNextMaterialTypeID, + (1u << MaterialHeader::kMaterialTypeBits) + ); } MaterialType type = MaterialType(mNextMaterialTypeID++); @@ -112,19 +117,19 @@ class MaterialTypeRegistry { // Register names for all built-in material types. mMaterialType2Name = { - { MaterialType::Unknown, "Unknown" }, - { MaterialType::Standard, "Standard" }, - { MaterialType::Cloth, "Cloth" }, - { MaterialType::Hair, "Hair" }, - { MaterialType::MERL, "MERL" }, - { MaterialType::MERLMix, "MERLMix" }, - { MaterialType::PBRTDiffuse, "PBRTDiffuse" }, - { MaterialType::PBRTDiffuseTransmission, "PBRTDiffuseTransmission" }, - { MaterialType::PBRTConductor, "PBRTConductor" }, - { MaterialType::PBRTDielectric, "PBRTDielectric" }, - { MaterialType::PBRTCoatedConductor, "PBRTCoatedConductor" }, - { MaterialType::PBRTCoatedDiffuse, "PBRTCoatedDiffuse" }, - { MaterialType::RGL, "RGL" }, + {MaterialType::Unknown, "Unknown"}, + {MaterialType::Standard, "Standard"}, + {MaterialType::Cloth, "Cloth"}, + {MaterialType::Hair, "Hair"}, + {MaterialType::MERL, "MERL"}, + {MaterialType::MERLMix, "MERLMix"}, + {MaterialType::PBRTDiffuse, "PBRTDiffuse"}, + {MaterialType::PBRTDiffuseTransmission, "PBRTDiffuseTransmission"}, + {MaterialType::PBRTConductor, "PBRTConductor"}, + {MaterialType::PBRTDielectric, "PBRTDielectric"}, + {MaterialType::PBRTCoatedConductor, "PBRTCoatedConductor"}, + {MaterialType::PBRTCoatedDiffuse, "PBRTCoatedDiffuse"}, + {MaterialType::RGL, "RGL"}, }; for (const auto& [type, name] : mMaterialType2Name) @@ -134,13 +139,14 @@ class MaterialTypeRegistry FALCOR_CHECK(mMaterialType2Name.size() == mMaterialName2Type.size(), "Material type names must be unique."); } - mutable std::mutex mMaterialTypeNameMutex; ///< Mutex to registering new material types - uint32_t mNextMaterialTypeID { uint32_t(MaterialType::BuiltinCount) + 1 }; ///< Holds id of the next free material type, used when creating material types dynamically - std::map mMaterialType2Name; ///< Map for to_string names + mutable std::mutex mMaterialTypeNameMutex; ///< Mutex to registering new material types + uint32_t mNextMaterialTypeID{uint32_t(MaterialType::BuiltinCount) + 1}; ///< Holds id of the next free material type, used when creating + ///< material types dynamically + std::map mMaterialType2Name; ///< Map for to_string names std::map mMaterialName2Type; ///< Map from name to type for deduplication when creating material types. }; -} +} // namespace MaterialType registerMaterialType(std::string typeName) { diff --git a/Source/Falcor/Scene/Material/MaterialTypeRegistry.h b/Source/Falcor/Scene/Material/MaterialTypeRegistry.h index ec8daae4d9..76b9c194a5 100644 --- a/Source/Falcor/Scene/Material/MaterialTypeRegistry.h +++ b/Source/Falcor/Scene/Material/MaterialTypeRegistry.h @@ -56,9 +56,9 @@ FALCOR_API std::string to_string(MaterialType type); FALCOR_API size_t getMaterialTypeCount(); /** Return the material parameter layout of serialized material parameters (for differentiable materials). -*/ + */ FALCOR_API MaterialParamLayout getMaterialParamLayout(MaterialType type); -} +} // namespace Falcor template<> struct fmt::formatter diff --git a/Source/Falcor/Scene/Material/MaterialTypes.slang b/Source/Falcor/Scene/Material/MaterialTypes.slang index 9c37111eed..addaa995f9 100644 --- a/Source/Falcor/Scene/Material/MaterialTypes.slang +++ b/Source/Falcor/Scene/Material/MaterialTypes.slang @@ -31,11 +31,11 @@ BEGIN_NAMESPACE_FALCOR /** Material type. -*/ + */ // TODO: Specify the backing type when Slang issue has been resolved enum class MaterialType // : uint32_t { - Unknown, ///< Unknown material type, e.g. type is not yet registered. + Unknown, ///< Unknown material type, e.g. type is not yet registered. Standard, Cloth, @@ -53,17 +53,17 @@ enum class MaterialType // : uint32_t }; /** Alpha mode. This specifies how alpha testing should be done. -*/ + */ enum class AlphaMode { - Opaque, ///< No alpha test. - Mask, ///< Alpha mask. + Opaque, ///< No alpha test. + Mask, ///< Alpha mask. Count // Must be last }; /** Shading models supported by the standard material. -*/ + */ enum class ShadingModel { MetalRough, @@ -73,12 +73,12 @@ enum class ShadingModel }; /** Normal map type. This specifies the encoding of a normal map. -*/ + */ enum class NormalMapType { - None, ///< Normal map is not used. - RGB, ///< Normal encoded in RGB channels in [0,1]. - RG, ///< Tangent space encoding in RG channels in [0,1]. + None, ///< Normal map is not used. + RGB, ///< Normal encoded in RGB channels in [0,1]. + RG, ///< Tangent space encoding in RG channels in [0,1]. Count // Must be last }; diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTCoatedConductorMaterial.cpp b/Source/Falcor/Scene/Material/PBRT/PBRTCoatedConductorMaterial.cpp index f4f11c15dd..5364851ab8 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTCoatedConductorMaterial.cpp +++ b/Source/Falcor/Scene/Material/PBRT/PBRTCoatedConductorMaterial.cpp @@ -31,66 +31,69 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang"; +} - PBRTCoatedConductorMaterial::PBRTCoatedConductorMaterial(ref pDevice, const std::string& name) - : BasicMaterial(pDevice, name, MaterialType::PBRTCoatedConductor) - { - // Setup additional texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = { "baseColor", TextureChannelFlags::RGBA, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Transmission] = { "transmission", TextureChannelFlags::RGB, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = { "specular", TextureChannelFlags::RGBA, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - } +PBRTCoatedConductorMaterial::PBRTCoatedConductorMaterial(ref pDevice, const std::string& name) + : BasicMaterial(pDevice, name, MaterialType::PBRTCoatedConductor) +{ + // Setup additional texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = {"baseColor", TextureChannelFlags::RGBA, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Transmission] = {"transmission", TextureChannelFlags::RGB, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = {"specular", TextureChannelFlags::RGBA, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; +} - ProgramDesc::ShaderModuleList PBRTCoatedConductorMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +ProgramDesc::ShaderModuleList PBRTCoatedConductorMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - TypeConformanceList PBRTCoatedConductorMaterial::getTypeConformances() const - { - return { {{"PBRTCoatedConductorMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTCoatedConductor} }; - } +TypeConformanceList PBRTCoatedConductorMaterial::getTypeConformances() const +{ + return {{{"PBRTCoatedConductorMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTCoatedConductor}}; +} - void PBRTCoatedConductorMaterial::renderSpecularUI(Gui::Widgets& widget) - { - float4 roughness = getRoughness(); - if (widget.var("Interface X Roughness", roughness.x, 0.f, 1.f, 0.01f)) setRoughness(roughness); - if (widget.var("Interface Y Roughness", roughness.y, 0.f, 1.f, 0.01f)) setRoughness(roughness); - if (widget.var("Conductor X Roughness", roughness.z, 0.f, 1.f, 0.01f)) setRoughness(roughness); - if (widget.var("Conductor Y Roughness", roughness.w, 0.f, 1.f, 0.01f)) setRoughness(roughness); - } +void PBRTCoatedConductorMaterial::renderSpecularUI(Gui::Widgets& widget) +{ + float4 roughness = getRoughness(); + if (widget.var("Interface X Roughness", roughness.x, 0.f, 1.f, 0.01f)) + setRoughness(roughness); + if (widget.var("Interface Y Roughness", roughness.y, 0.f, 1.f, 0.01f)) + setRoughness(roughness); + if (widget.var("Conductor X Roughness", roughness.z, 0.f, 1.f, 0.01f)) + setRoughness(roughness); + if (widget.var("Conductor Y Roughness", roughness.w, 0.f, 1.f, 0.01f)) + setRoughness(roughness); +} - void PBRTCoatedConductorMaterial::setRoughness(float4 roughness) +void PBRTCoatedConductorMaterial::setRoughness(float4 roughness) +{ + if (mData.specular[0] != (float16_t)roughness.x || mData.specular[1] != (float16_t)roughness.y || + mData.specular[2] != (float16_t)roughness.z || mData.specular[3] != (float16_t)roughness.w) { - if (mData.specular[0] != (float16_t)roughness.x || mData.specular[1] != (float16_t)roughness.y || - mData.specular[2] != (float16_t)roughness.z || mData.specular[3] != (float16_t)roughness.w) - { - mData.specular[0] = (float16_t)roughness.x; - mData.specular[1] = (float16_t)roughness.y; - mData.specular[2] = (float16_t)roughness.z; - mData.specular[3] = (float16_t)roughness.w; - markUpdates(UpdateFlags::DataChanged); - } + mData.specular[0] = (float16_t)roughness.x; + mData.specular[1] = (float16_t)roughness.y; + mData.specular[2] = (float16_t)roughness.z; + mData.specular[3] = (float16_t)roughness.w; + markUpdates(UpdateFlags::DataChanged); } +} - FALCOR_SCRIPT_BINDING(PBRTCoatedConductorMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(PBRTCoatedConductorMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - pybind11::class_> material(m, "PBRTCoatedConductorMaterial"); - auto create = [] (const std::string& name) - { - return PBRTCoatedConductorMaterial::create(accessActivePythonSceneBuilder().getDevice(), name); - }; - material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED + pybind11::class_> material( + m, "PBRTCoatedConductorMaterial" + ); + auto create = [](const std::string& name) { return PBRTCoatedConductorMaterial::create(accessActivePythonSceneBuilderDevice(), name); }; + material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED - material.def_property("roughness", &PBRTCoatedConductorMaterial::getRoughness, &PBRTCoatedConductorMaterial::setRoughness); - } + material.def_property("roughness", &PBRTCoatedConductorMaterial::getRoughness, &PBRTCoatedConductorMaterial::setRoughness); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTCoatedConductorMaterial.h b/Source/Falcor/Scene/Material/PBRT/PBRTCoatedConductorMaterial.h index 28de9f9204..633f711f3b 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTCoatedConductorMaterial.h +++ b/Source/Falcor/Scene/Material/PBRT/PBRTCoatedConductorMaterial.h @@ -30,51 +30,54 @@ namespace Falcor { - /** This class implements a coated conductor material, i.e. a - dielectric coating on top of a conductor base. Both conductor - and coating can be smooth or rough, isotropic or anisotropic. - The material simulates interreflection between the layers - using a Monte Carlo random walk. +/** This class implements a coated conductor material, i.e. a + dielectric coating on top of a conductor base. Both conductor + and coating can be smooth or rough, isotropic or anisotropic. + The material simulates interreflection between the layers + using a Monte Carlo random walk. - This class perfectly matches the PBRT "coatedconductor" material. + This class perfectly matches the PBRT "coatedconductor" material. - Texture channel layout: + Texture channel layout: - BaseColor - - RGB - Conductor complex Eta - - A - Opacity - Transmission - - RGB - Conductor complex k - Specular - - R - Interface X Roughness - - G - Interface Y Roughness - - B - Conductor X Roughness - - A - Conductor Y Roughness - Normal - - 3-Channel standard normal map, or 2-Channel BC5 format + BaseColor + - RGB - Conductor complex Eta + - A - Opacity + Transmission + - RGB - Conductor complex k + Specular + - R - Interface X Roughness + - G - Interface Y Roughness + - B - Conductor X Roughness + - A - Conductor Y Roughness + Normal + - 3-Channel standard normal map, or 2-Channel BC5 format - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API PBRTCoatedConductorMaterial : public BasicMaterial + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API PBRTCoatedConductorMaterial : public BasicMaterial +{ + FALCOR_OBJECT(PBRTCoatedConductorMaterial) +public: + static ref create(ref pDevice, const std::string& name) { - FALCOR_OBJECT(PBRTCoatedConductorMaterial) - public: - static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); } + return make_ref(pDevice, name); + } - PBRTCoatedConductorMaterial(ref pDevice, const std::string& name); + PBRTCoatedConductorMaterial(ref pDevice, const std::string& name); - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - /** Set the roughness. - */ - void setRoughness(float4 roughness); + /** Set the roughness. + */ + void setRoughness(float4 roughness); - /** Get the roughness. - */ - float4 getRoughness() const { return float4(mData.specular[0], mData.specular[1], mData.specular[2], mData.specular[3]); } + /** Get the roughness. + */ + float4 getRoughness() const { return float4(mData.specular[0], mData.specular[1], mData.specular[2], mData.specular[3]); } - protected: - void renderSpecularUI(Gui::Widgets& widget) override; - }; -} +protected: + void renderSpecularUI(Gui::Widgets& widget) override; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTCoatedDiffuseMaterial.cpp b/Source/Falcor/Scene/Material/PBRT/PBRTCoatedDiffuseMaterial.cpp index a8188ff4e6..b80910aa47 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTCoatedDiffuseMaterial.cpp +++ b/Source/Falcor/Scene/Material/PBRT/PBRTCoatedDiffuseMaterial.cpp @@ -31,60 +31,59 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang"; +} - PBRTCoatedDiffuseMaterial::PBRTCoatedDiffuseMaterial(ref pDevice, const std::string& name) - : BasicMaterial(pDevice, name, MaterialType::PBRTCoatedDiffuse) - { - // Setup additional texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = { "baseColor", TextureChannelFlags::RGBA, true }; - mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = { "specular", TextureChannelFlags::Red | TextureChannelFlags::Green, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - } +PBRTCoatedDiffuseMaterial::PBRTCoatedDiffuseMaterial(ref pDevice, const std::string& name) + : BasicMaterial(pDevice, name, MaterialType::PBRTCoatedDiffuse) +{ + // Setup additional texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = {"baseColor", TextureChannelFlags::RGBA, true}; + mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = {"specular", TextureChannelFlags::Red | TextureChannelFlags::Green, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; +} - ProgramDesc::ShaderModuleList PBRTCoatedDiffuseMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +ProgramDesc::ShaderModuleList PBRTCoatedDiffuseMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - TypeConformanceList PBRTCoatedDiffuseMaterial::getTypeConformances() const - { - return { {{"PBRTCoatedDiffuseMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTCoatedDiffuse} }; - } +TypeConformanceList PBRTCoatedDiffuseMaterial::getTypeConformances() const +{ + return {{{"PBRTCoatedDiffuseMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTCoatedDiffuse}}; +} - void PBRTCoatedDiffuseMaterial::renderSpecularUI(Gui::Widgets& widget) - { - float2 roughness = getRoughness(); - if (widget.var("Interface X Roughness", roughness.x, 0.f, 1.f, 0.01f)) setRoughness(roughness); - if (widget.var("Interface Y Roughness", roughness.y, 0.f, 1.f, 0.01f)) setRoughness(roughness); - } +void PBRTCoatedDiffuseMaterial::renderSpecularUI(Gui::Widgets& widget) +{ + float2 roughness = getRoughness(); + if (widget.var("Interface X Roughness", roughness.x, 0.f, 1.f, 0.01f)) + setRoughness(roughness); + if (widget.var("Interface Y Roughness", roughness.y, 0.f, 1.f, 0.01f)) + setRoughness(roughness); +} - void PBRTCoatedDiffuseMaterial::setRoughness(float2 roughness) +void PBRTCoatedDiffuseMaterial::setRoughness(float2 roughness) +{ + if (mData.specular[0] != (float16_t)roughness.x || mData.specular[1] != (float16_t)roughness.y) { - if (mData.specular[0] != (float16_t)roughness.x || mData.specular[1] != (float16_t)roughness.y) - { - mData.specular[0] = (float16_t)roughness.x; - mData.specular[1] = (float16_t)roughness.y; - markUpdates(UpdateFlags::DataChanged); - } + mData.specular[0] = (float16_t)roughness.x; + mData.specular[1] = (float16_t)roughness.y; + markUpdates(UpdateFlags::DataChanged); } +} - FALCOR_SCRIPT_BINDING(PBRTCoatedDiffuseMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(PBRTCoatedDiffuseMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - pybind11::class_> material(m, "PBRTCoatedDiffuseMaterial"); - auto create = [] (const std::string& name) - { - return PBRTCoatedDiffuseMaterial::create(accessActivePythonSceneBuilder().getDevice(), name); - }; - material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED + pybind11::class_> material(m, "PBRTCoatedDiffuseMaterial"); + auto create = [](const std::string& name) { return PBRTCoatedDiffuseMaterial::create(accessActivePythonSceneBuilderDevice(), name); }; + material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED - material.def_property("roughness", &PBRTCoatedDiffuseMaterial::getRoughness, &PBRTCoatedDiffuseMaterial::setRoughness); - } + material.def_property("roughness", &PBRTCoatedDiffuseMaterial::getRoughness, &PBRTCoatedDiffuseMaterial::setRoughness); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTCoatedDiffuseMaterial.h b/Source/Falcor/Scene/Material/PBRT/PBRTCoatedDiffuseMaterial.h index 83565736b8..0530eba72d 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTCoatedDiffuseMaterial.h +++ b/Source/Falcor/Scene/Material/PBRT/PBRTCoatedDiffuseMaterial.h @@ -30,47 +30,50 @@ namespace Falcor { - /** This class implements a coated diffuse material, i.e. a - dielectric coating on top of a Lamebrtian base. - The coating can be smooth or rough, isotropic or anisotropic. - The material simulates interreflection between the layers - using a Monte Carlo random walk. +/** This class implements a coated diffuse material, i.e. a + dielectric coating on top of a Lamebrtian base. + The coating can be smooth or rough, isotropic or anisotropic. + The material simulates interreflection between the layers + using a Monte Carlo random walk. - This class perfectly matches the PBRT "coateddiffuse" material. + This class perfectly matches the PBRT "coateddiffuse" material. - Texture channel layout: + Texture channel layout: - BaseColor - - RGB - Diffuse albedo - - A - Opacity - Specular - - R - Interface X Roughness - - G - Interface Y Roughness - Normal - - 3-Channel standard normal map, or 2-Channel BC5 format + BaseColor + - RGB - Diffuse albedo + - A - Opacity + Specular + - R - Interface X Roughness + - G - Interface Y Roughness + Normal + - 3-Channel standard normal map, or 2-Channel BC5 format - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API PBRTCoatedDiffuseMaterial : public BasicMaterial + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API PBRTCoatedDiffuseMaterial : public BasicMaterial +{ + FALCOR_OBJECT(PBRTCoatedDiffuseMaterial) +public: + static ref create(ref pDevice, const std::string& name) { - FALCOR_OBJECT(PBRTCoatedDiffuseMaterial) - public: - static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); } + return make_ref(pDevice, name); + } - PBRTCoatedDiffuseMaterial(ref pDevice, const std::string& name); + PBRTCoatedDiffuseMaterial(ref pDevice, const std::string& name); - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - /** Set the roughness. - */ - void setRoughness(float2 roughness); + /** Set the roughness. + */ + void setRoughness(float2 roughness); - /** Get the roughness. - */ - float2 getRoughness() const { return float2(mData.specular[0], mData.specular[1]); } + /** Get the roughness. + */ + float2 getRoughness() const { return float2(mData.specular[0], mData.specular[1]); } - protected: - void renderSpecularUI(Gui::Widgets& widget) override; - }; -} +protected: + void renderSpecularUI(Gui::Widgets& widget) override; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTConductorMaterial.cpp b/Source/Falcor/Scene/Material/PBRT/PBRTConductorMaterial.cpp index b51725990b..40ac2eac07 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTConductorMaterial.cpp +++ b/Source/Falcor/Scene/Material/PBRT/PBRTConductorMaterial.cpp @@ -32,76 +32,75 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTConductorMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTConductorMaterial.slang"; +} - PBRTConductorMaterial::PBRTConductorMaterial(ref pDevice, const std::string& name) - : BasicMaterial(pDevice, name, MaterialType::PBRTConductor) - { - // Setup additional texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = { "baseColor", TextureChannelFlags::RGBA, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Transmission] = { "transmission", TextureChannelFlags::RGB, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = { "specular", TextureChannelFlags::Red | TextureChannelFlags::Green, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - } +PBRTConductorMaterial::PBRTConductorMaterial(ref pDevice, const std::string& name) + : BasicMaterial(pDevice, name, MaterialType::PBRTConductor) +{ + // Setup additional texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = {"baseColor", TextureChannelFlags::RGBA, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Transmission] = {"transmission", TextureChannelFlags::RGB, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = {"specular", TextureChannelFlags::Red | TextureChannelFlags::Green, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; +} - ProgramDesc::ShaderModuleList PBRTConductorMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +ProgramDesc::ShaderModuleList PBRTConductorMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - TypeConformanceList PBRTConductorMaterial::getTypeConformances() const - { - return { {{"PBRTConductorMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTConductor} }; - } +TypeConformanceList PBRTConductorMaterial::getTypeConformances() const +{ + return {{{"PBRTConductorMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTConductor}}; +} - void PBRTConductorMaterial::renderSpecularUI(Gui::Widgets& widget) - { - float2 roughness = getRoughness(); - if (widget.var("X Roughness", roughness.x, 0.f, 1.f, 0.01f)) setRoughness(roughness); - if (widget.var("Y Roughness", roughness.y, 0.f, 1.f, 0.01f)) setRoughness(roughness); - } +void PBRTConductorMaterial::renderSpecularUI(Gui::Widgets& widget) +{ + float2 roughness = getRoughness(); + if (widget.var("X Roughness", roughness.x, 0.f, 1.f, 0.01f)) + setRoughness(roughness); + if (widget.var("Y Roughness", roughness.y, 0.f, 1.f, 0.01f)) + setRoughness(roughness); +} - void PBRTConductorMaterial::setRoughness(float2 roughness) +void PBRTConductorMaterial::setRoughness(float2 roughness) +{ + if (mData.specular[0] != (float16_t)roughness.x || mData.specular[1] != (float16_t)roughness.y) { - if (mData.specular[0] != (float16_t)roughness.x || mData.specular[1] != (float16_t)roughness.y) - { - mData.specular[0] = (float16_t)roughness.x; - mData.specular[1] = (float16_t)roughness.y; - markUpdates(UpdateFlags::DataChanged); - } + mData.specular[0] = (float16_t)roughness.x; + mData.specular[1] = (float16_t)roughness.y; + markUpdates(UpdateFlags::DataChanged); } +} - const MaterialParamLayout& PBRTConductorMaterial::getParamLayout() const - { - return PBRTConductorMaterialParamLayout::layout(); - } +const MaterialParamLayout& PBRTConductorMaterial::getParamLayout() const +{ + return PBRTConductorMaterialParamLayout::layout(); +} - SerializedMaterialParams PBRTConductorMaterial::serializeParams() const - { - return PBRTConductorMaterialParamLayout::serialize(this); - } +SerializedMaterialParams PBRTConductorMaterial::serializeParams() const +{ + return PBRTConductorMaterialParamLayout::serialize(this); +} - void PBRTConductorMaterial::deserializeParams(const SerializedMaterialParams& params) - { - PBRTConductorMaterialParamLayout::deserialize(this, params); - } +void PBRTConductorMaterial::deserializeParams(const SerializedMaterialParams& params) +{ + PBRTConductorMaterialParamLayout::deserialize(this, params); +} - FALCOR_SCRIPT_BINDING(PBRTConductorMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(PBRTConductorMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - pybind11::class_> material(m, "PBRTConductorMaterial"); - auto create = [] (const std::string& name) - { - return PBRTConductorMaterial::create(accessActivePythonSceneBuilder().getDevice(), name); - }; - material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED + pybind11::class_> material(m, "PBRTConductorMaterial"); + auto create = [](const std::string& name) { return PBRTConductorMaterial::create(accessActivePythonSceneBuilderDevice(), name); }; + material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED - material.def_property("roughness", &PBRTConductorMaterial::getRoughness, &PBRTConductorMaterial::setRoughness); - } + material.def_property("roughness", &PBRTConductorMaterial::getRoughness, &PBRTConductorMaterial::setRoughness); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTConductorMaterial.h b/Source/Falcor/Scene/Material/PBRT/PBRTConductorMaterial.h index a78b4fc587..2ee20739db 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTConductorMaterial.h +++ b/Source/Falcor/Scene/Material/PBRT/PBRTConductorMaterial.h @@ -30,55 +30,58 @@ namespace Falcor { - /** This class implements a conductor material. This means the - surface is purely reflective, and its albedo is dictated by - the conductor Fresnel equations (i.e. by a complex IoR). - The material can be act like a mirror (if roughness is 0), - or a (potentially anisotropic) GGX microfacet. +/** This class implements a conductor material. This means the + surface is purely reflective, and its albedo is dictated by + the conductor Fresnel equations (i.e. by a complex IoR). + The material can be act like a mirror (if roughness is 0), + or a (potentially anisotropic) GGX microfacet. - This class perfectly matches the PBRT "conductor" material. + This class perfectly matches the PBRT "conductor" material. - Texture channel layout: + Texture channel layout: - BaseColor - - RGB - Complex Eta - - A - Opacity - Transmission - - RGB - Complex k - Specular - - R - X Roughness - - G - Y Roughness - - B - Unused - - A - Unused - Normal - - 3-Channel standard normal map, or 2-Channel BC5 format + BaseColor + - RGB - Complex Eta + - A - Opacity + Transmission + - RGB - Complex k + Specular + - R - X Roughness + - G - Y Roughness + - B - Unused + - A - Unused + Normal + - 3-Channel standard normal map, or 2-Channel BC5 format - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API PBRTConductorMaterial : public BasicMaterial + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API PBRTConductorMaterial : public BasicMaterial +{ + FALCOR_OBJECT(PBRTConductorMaterial) +public: + static ref create(ref pDevice, const std::string& name) { - FALCOR_OBJECT(PBRTConductorMaterial) - public: - static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); } + return make_ref(pDevice, name); + } - PBRTConductorMaterial(ref pDevice, const std::string& name); + PBRTConductorMaterial(ref pDevice, const std::string& name); - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - /** Set the roughness. - */ - void setRoughness(float2 roughness); + /** Set the roughness. + */ + void setRoughness(float2 roughness); - /** Get the roughness. - */ - float2 getRoughness() const { return float2(mData.specular[0], mData.specular[1]); } + /** Get the roughness. + */ + float2 getRoughness() const { return float2(mData.specular[0], mData.specular[1]); } - const MaterialParamLayout& getParamLayout() const override; - SerializedMaterialParams serializeParams() const override; - void deserializeParams(const SerializedMaterialParams& params) override; + const MaterialParamLayout& getParamLayout() const override; + SerializedMaterialParams serializeParams() const override; + void deserializeParams(const SerializedMaterialParams& params) override; - protected: - void renderSpecularUI(Gui::Widgets& widget) override; - }; -} +protected: + void renderSpecularUI(Gui::Widgets& widget) override; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTDielectricMaterial.cpp b/Source/Falcor/Scene/Material/PBRT/PBRTDielectricMaterial.cpp index ac545c6eb5..c9dda3a2ba 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTDielectricMaterial.cpp +++ b/Source/Falcor/Scene/Material/PBRT/PBRTDielectricMaterial.cpp @@ -31,59 +31,58 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTDielectricMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTDielectricMaterial.slang"; +} - PBRTDielectricMaterial::PBRTDielectricMaterial(ref pDevice, const std::string& name) - : BasicMaterial(pDevice, name, MaterialType::PBRTDielectric) - { - // Setup additional texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = { "specular", TextureChannelFlags::Red | TextureChannelFlags::Green, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - } +PBRTDielectricMaterial::PBRTDielectricMaterial(ref pDevice, const std::string& name) + : BasicMaterial(pDevice, name, MaterialType::PBRTDielectric) +{ + // Setup additional texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = {"specular", TextureChannelFlags::Red | TextureChannelFlags::Green, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; +} - ProgramDesc::ShaderModuleList PBRTDielectricMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +ProgramDesc::ShaderModuleList PBRTDielectricMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - TypeConformanceList PBRTDielectricMaterial::getTypeConformances() const - { - return { {{"PBRTDielectricMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTDielectric} }; - } +TypeConformanceList PBRTDielectricMaterial::getTypeConformances() const +{ + return {{{"PBRTDielectricMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTDielectric}}; +} - void PBRTDielectricMaterial::renderSpecularUI(Gui::Widgets& widget) - { - float2 roughness = getRoughness(); - if (widget.var("X Roughness", roughness.x, 0.f, 1.f, 0.01f)) setRoughness(roughness); - if (widget.var("Y Roughness", roughness.y, 0.f, 1.f, 0.01f)) setRoughness(roughness); - } +void PBRTDielectricMaterial::renderSpecularUI(Gui::Widgets& widget) +{ + float2 roughness = getRoughness(); + if (widget.var("X Roughness", roughness.x, 0.f, 1.f, 0.01f)) + setRoughness(roughness); + if (widget.var("Y Roughness", roughness.y, 0.f, 1.f, 0.01f)) + setRoughness(roughness); +} - void PBRTDielectricMaterial::setRoughness(float2 roughness) +void PBRTDielectricMaterial::setRoughness(float2 roughness) +{ + if (mData.specular[0] != (float16_t)roughness.x || mData.specular[1] != (float16_t)roughness.y) { - if (mData.specular[0] != (float16_t)roughness.x || mData.specular[1] != (float16_t)roughness.y) - { - mData.specular[0] = (float16_t)roughness.x; - mData.specular[1] = (float16_t)roughness.y; - markUpdates(UpdateFlags::DataChanged); - } + mData.specular[0] = (float16_t)roughness.x; + mData.specular[1] = (float16_t)roughness.y; + markUpdates(UpdateFlags::DataChanged); } +} - FALCOR_SCRIPT_BINDING(PBRTDielectricMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(PBRTDielectricMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - pybind11::class_> material(m, "PBRTDielectricMaterial"); - auto create = [] (const std::string& name) - { - return PBRTDielectricMaterial::create(accessActivePythonSceneBuilder().getDevice(), name); - }; - material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED + pybind11::class_> material(m, "PBRTDielectricMaterial"); + auto create = [](const std::string& name) { return PBRTDielectricMaterial::create(accessActivePythonSceneBuilderDevice(), name); }; + material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED - material.def_property("roughness", &PBRTDielectricMaterial::getRoughness, &PBRTDielectricMaterial::setRoughness); - } + material.def_property("roughness", &PBRTDielectricMaterial::getRoughness, &PBRTDielectricMaterial::setRoughness); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTDielectricMaterial.h b/Source/Falcor/Scene/Material/PBRT/PBRTDielectricMaterial.h index 0e6a40a1d3..71a1f5a725 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTDielectricMaterial.h +++ b/Source/Falcor/Scene/Material/PBRT/PBRTDielectricMaterial.h @@ -30,45 +30,48 @@ namespace Falcor { - /** This class implements a dielectric material. This means the - surface has both (uncolored) reflection and refraction lobes. - The material can be act like an ideal smooth surface - (if roughness is 0), or a (potentially anisotropic) GGX microfacet. +/** This class implements a dielectric material. This means the + surface has both (uncolored) reflection and refraction lobes. + The material can be act like an ideal smooth surface + (if roughness is 0), or a (potentially anisotropic) GGX microfacet. - This class perfectly matches the PBRT "dielectric" material. + This class perfectly matches the PBRT "dielectric" material. - Texture channel layout: + Texture channel layout: - Specular - - R - X Roughness - - G - Y Roughness - - B - Unused - - A - Unused - Normal - - 3-Channel standard normal map, or 2-Channel BC5 format + Specular + - R - X Roughness + - G - Y Roughness + - B - Unused + - A - Unused + Normal + - 3-Channel standard normal map, or 2-Channel BC5 format - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API PBRTDielectricMaterial : public BasicMaterial + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API PBRTDielectricMaterial : public BasicMaterial +{ + FALCOR_OBJECT(PBRTDielectricMaterial) +public: + static ref create(ref pDevice, const std::string& name) { - FALCOR_OBJECT(PBRTDielectricMaterial) - public: - static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); } + return make_ref(pDevice, name); + } - PBRTDielectricMaterial(ref pDevice, const std::string& name); + PBRTDielectricMaterial(ref pDevice, const std::string& name); - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - /** Set the roughness. - */ - void setRoughness(float2 roughness); + /** Set the roughness. + */ + void setRoughness(float2 roughness); - /** Get the roughness. - */ - float2 getRoughness() const { return float2(mData.specular[0], mData.specular[1]); } + /** Get the roughness. + */ + float2 getRoughness() const { return float2(mData.specular[0], mData.specular[1]); } - protected: - void renderSpecularUI(Gui::Widgets& widget) override; - }; -} +protected: + void renderSpecularUI(Gui::Widgets& widget) override; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseMaterial.cpp b/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseMaterial.cpp index 93cbe19292..4b3ed9d216 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseMaterial.cpp +++ b/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseMaterial.cpp @@ -32,63 +32,57 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTDiffuseMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTDiffuseMaterial.slang"; +} - PBRTDiffuseMaterial::PBRTDiffuseMaterial(ref pDevice, const std::string& name) - : BasicMaterial(pDevice, name, MaterialType::PBRTDiffuse) - { - // Setup additional texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = { "baseColor", TextureChannelFlags::RGBA, true }; - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - } +PBRTDiffuseMaterial::PBRTDiffuseMaterial(ref pDevice, const std::string& name) + : BasicMaterial(pDevice, name, MaterialType::PBRTDiffuse) +{ + // Setup additional texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = {"baseColor", TextureChannelFlags::RGBA, true}; + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; +} - ProgramDesc::ShaderModuleList PBRTDiffuseMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +ProgramDesc::ShaderModuleList PBRTDiffuseMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - TypeConformanceList PBRTDiffuseMaterial::getTypeConformances() const - { - return { {{"PBRTDiffuseMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTDiffuse} }; - } +TypeConformanceList PBRTDiffuseMaterial::getTypeConformances() const +{ + return {{{"PBRTDiffuseMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTDiffuse}}; +} - const MaterialParamLayout& PBRTDiffuseMaterial::getParamLayout() const - { - return PBRTDiffuseMaterialParamLayout::layout(); - } +const MaterialParamLayout& PBRTDiffuseMaterial::getParamLayout() const +{ + return PBRTDiffuseMaterialParamLayout::layout(); +} - SerializedMaterialParams PBRTDiffuseMaterial::serializeParams() const - { - return PBRTDiffuseMaterialParamLayout::serialize(this); - } +SerializedMaterialParams PBRTDiffuseMaterial::serializeParams() const +{ + return PBRTDiffuseMaterialParamLayout::serialize(this); +} - void PBRTDiffuseMaterial::deserializeParams(const SerializedMaterialParams& params) - { - PBRTDiffuseMaterialParamLayout::deserialize(this, params); - } +void PBRTDiffuseMaterial::deserializeParams(const SerializedMaterialParams& params) +{ + PBRTDiffuseMaterialParamLayout::deserialize(this, params); +} - FALCOR_SCRIPT_BINDING(PBRTDiffuseMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(PBRTDiffuseMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - pybind11::class_> material(m, "PBRTDiffuseMaterial"); - auto create = [] (const std::string& name) - { - return PBRTDiffuseMaterial::create(accessActivePythonSceneBuilder().getDevice(), name); - }; - material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED - material.def(pybind11::init( - [](ref device, const std::string& name) - { - return PBRTDiffuseMaterial::create(device, name); - }), - "device"_a, - "name"_a = "" - ); // PYTHONDEPRECATED - } + pybind11::class_> material(m, "PBRTDiffuseMaterial"); + auto create = [](const std::string& name) { return PBRTDiffuseMaterial::create(accessActivePythonSceneBuilderDevice(), name); }; + material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED + material.def( + pybind11::init([](ref device, const std::string& name) { return PBRTDiffuseMaterial::create(device, name); }), + "device"_a, + "name"_a = "" + ); // PYTHONDEPRECATED } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseMaterial.h b/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseMaterial.h index 842573d04d..b41f8136c5 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseMaterial.h +++ b/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseMaterial.h @@ -30,34 +30,37 @@ namespace Falcor { - /** This class implements a Lambertian diffuse material, where - reflectance does not depend on wo. +/** This class implements a Lambertian diffuse material, where + reflectance does not depend on wo. - This class perfectly matches the PBRT "diffuse" material. + This class perfectly matches the PBRT "diffuse" material. - Texture channel layout: + Texture channel layout: - BaseColor - - RGB - Base Color - - A - Opacity - Normal - - 3-Channel standard normal map, or 2-Channel BC5 format + BaseColor + - RGB - Base Color + - A - Opacity + Normal + - 3-Channel standard normal map, or 2-Channel BC5 format - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API PBRTDiffuseMaterial : public BasicMaterial + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API PBRTDiffuseMaterial : public BasicMaterial +{ + FALCOR_OBJECT(PBRTDiffuseMaterial) +public: + static ref create(ref pDevice, const std::string& name) { - FALCOR_OBJECT(PBRTDiffuseMaterial) - public: - static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); } + return make_ref(pDevice, name); + } - PBRTDiffuseMaterial(ref pDevice, const std::string& name); + PBRTDiffuseMaterial(ref pDevice, const std::string& name); - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - const MaterialParamLayout& getParamLayout() const override; - SerializedMaterialParams serializeParams() const override; - void deserializeParams(const SerializedMaterialParams& params) override; - }; -} + const MaterialParamLayout& getParamLayout() const override; + SerializedMaterialParams serializeParams() const override; + void deserializeParams(const SerializedMaterialParams& params) override; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseTransmissionMaterial.cpp b/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseTransmissionMaterial.cpp index aac608c2b1..57368868ce 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseTransmissionMaterial.cpp +++ b/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseTransmissionMaterial.cpp @@ -31,41 +31,41 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterial.slang"; +} - PBRTDiffuseTransmissionMaterial::PBRTDiffuseTransmissionMaterial(ref pDevice, const std::string& name) - : BasicMaterial(pDevice, name, MaterialType::PBRTDiffuseTransmission) - { - // Setup additional texture slots. - mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = { "baseColor", TextureChannelFlags::RGBA, true }; - mTextureSlotInfo[(uint32_t)TextureSlot::Transmission] = { "transmission", TextureChannelFlags::RGB, true }; - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - } +PBRTDiffuseTransmissionMaterial::PBRTDiffuseTransmissionMaterial(ref pDevice, const std::string& name) + : BasicMaterial(pDevice, name, MaterialType::PBRTDiffuseTransmission) +{ + // Setup additional texture slots. + mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = {"baseColor", TextureChannelFlags::RGBA, true}; + mTextureSlotInfo[(uint32_t)TextureSlot::Transmission] = {"transmission", TextureChannelFlags::RGB, true}; + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; +} - ProgramDesc::ShaderModuleList PBRTDiffuseTransmissionMaterial::getShaderModules() const - { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; - } +ProgramDesc::ShaderModuleList PBRTDiffuseTransmissionMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} - TypeConformanceList PBRTDiffuseTransmissionMaterial::getTypeConformances() const - { - return { {{"PBRTDiffuseTransmissionMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTDiffuseTransmission} }; - } +TypeConformanceList PBRTDiffuseTransmissionMaterial::getTypeConformances() const +{ + return {{{"PBRTDiffuseTransmissionMaterial", "IMaterial"}, (uint32_t)MaterialType::PBRTDiffuseTransmission}}; +} - FALCOR_SCRIPT_BINDING(PBRTDiffuseTransmissionMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(PBRTDiffuseTransmissionMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - pybind11::class_> material(m, "PBRTDiffuseTransmissionMaterial"); - auto create = [] (const std::string& name) - { - return PBRTDiffuseTransmissionMaterial::create(accessActivePythonSceneBuilder().getDevice(), name); - }; - material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED - } + pybind11::class_> material( + m, "PBRTDiffuseTransmissionMaterial" + ); + auto create = [](const std::string& name) + { return PBRTDiffuseTransmissionMaterial::create(accessActivePythonSceneBuilderDevice(), name); }; + material.def(pybind11::init(create), "name"_a = ""); // PYTHONDEPRECATED } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseTransmissionMaterial.h b/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseTransmissionMaterial.h index 19b007eae0..09c0eb15da 100644 --- a/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseTransmissionMaterial.h +++ b/Source/Falcor/Scene/Material/PBRT/PBRTDiffuseTransmissionMaterial.h @@ -30,34 +30,37 @@ namespace Falcor { - /** This class implements a Lambertian diffuse transmission material. - The material has both reflective and transmissive lobes, both of - which are invariant to wo. The albedo of the reflective and transmissive - lobe are specified by BaseColor and Transmission, respectively. +/** This class implements a Lambertian diffuse transmission material. + The material has both reflective and transmissive lobes, both of + which are invariant to wo. The albedo of the reflective and transmissive + lobe are specified by BaseColor and Transmission, respectively. - This class perfectly matches the PBRT "diffusetransmission" material. + This class perfectly matches the PBRT "diffusetransmission" material. - Texture channel layout: + Texture channel layout: - BaseColor - - RGB - Base Color - - A - Opacity - Transmission - - RGB - Transmission color - Normal - - 3-Channel standard normal map, or 2-Channel BC5 format + BaseColor + - RGB - Base Color + - A - Opacity + Transmission + - RGB - Transmission color + Normal + - 3-Channel standard normal map, or 2-Channel BC5 format - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API PBRTDiffuseTransmissionMaterial : public BasicMaterial + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API PBRTDiffuseTransmissionMaterial : public BasicMaterial +{ + FALCOR_OBJECT(PBRTDiffuseTransmissionMaterial) +public: + static ref create(ref pDevice, const std::string& name) { - FALCOR_OBJECT(PBRTDiffuseTransmissionMaterial) - public: - static ref create(ref pDevice, const std::string& name) { return make_ref(pDevice, name); } + return make_ref(pDevice, name); + } - PBRTDiffuseTransmissionMaterial(ref pDevice, const std::string& name); + PBRTDiffuseTransmissionMaterial(ref pDevice, const std::string& name); - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; - }; -} + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/RGLCommon.cpp b/Source/Falcor/Scene/Material/RGLCommon.cpp index c480e1bcb5..79d0dd1425 100644 --- a/Source/Falcor/Scene/Material/RGLCommon.cpp +++ b/Source/Falcor/Scene/Material/RGLCommon.cpp @@ -30,76 +30,75 @@ namespace Falcor { - SamplableDistribution4D::SamplableDistribution4D(const float* pdf, uint4 size) - : mSize(size) - { - uint32_t N = size.x * size.y * size.z * size.w; +SamplableDistribution4D::SamplableDistribution4D(const float* pdf, uint4 size) : mSize(size) +{ + uint32_t N = size.x * size.y * size.z * size.w; - mPDF .reset(new float[N]); - mConditional.reset(new float[N]); - mMarginal .reset(new float[N / size.z]); + mPDF.reset(new float[N]); + mConditional.reset(new float[N]); + mMarginal.reset(new float[N / size.z]); - std::memcpy(mPDF.get(), pdf, N * sizeof(float)); + std::memcpy(mPDF.get(), pdf, N * sizeof(float)); - uint32_t sliceStride = size.z * size.w; - for (uint32_t i = 0; i < N; i += sliceStride) - { - build2DSlice(int2(size.z, size.w), mPDF.get() + i, mMarginal.get() + i / size.z, mConditional.get() + i); - } + uint32_t sliceStride = size.z * size.w; + for (uint32_t i = 0; i < N; i += sliceStride) + { + build2DSlice(int2(size.z, size.w), mPDF.get() + i, mMarginal.get() + i / size.z, mConditional.get() + i); } +} - void SamplableDistribution4D::build2DSlice(int2 size, float* pdf, float* marginalCDF, float* conditionalCDF) - { - FALCOR_ASSERT(pdf); - FALCOR_ASSERT(marginalCDF); - FALCOR_ASSERT(conditionalCDF); +void SamplableDistribution4D::build2DSlice(int2 size, float* pdf, float* marginalCDF, float* conditionalCDF) +{ + FALCOR_ASSERT(pdf); + FALCOR_ASSERT(marginalCDF); + FALCOR_ASSERT(conditionalCDF); - double tableSum = 0.0; + double tableSum = 0.0; + for (int i = 0; i < size.x * size.y; ++i) + { + tableSum += pdf[i]; + } + // Edge case: Whole slice is zero. Reset to uniform distribution. + if (tableSum == 0.0) + { for (int i = 0; i < size.x * size.y; ++i) { - tableSum += pdf[i]; - } - // Edge case: Whole slice is zero. Reset to uniform distribution. - if (tableSum == 0.0) - { - for (int i = 0; i < size.x * size.y; ++i) - { - pdf[i] = 1.0f / (size.x * size.y); - } - tableSum = 1.0; + pdf[i] = 1.0f / (size.x * size.y); } + tableSum = 1.0; + } - // Step 1: Build row sums. - for (int y = 0; y < size.y; ++y) + // Step 1: Build row sums. + for (int y = 0; y < size.y; ++y) + { + double rowSum = 0.0; + conditionalCDF[y * size.x] = float(rowSum); + for (int x = 1; x < size.x; ++x) { - double rowSum = 0.0; - conditionalCDF[y * size.x] = float(rowSum); - for (int x = 1; x < size.x; ++x) - { - int idx = x + y * size.x; - rowSum += (pdf[idx - 1] + pdf[idx]) * 0.5f; - conditionalCDF[idx] = float(rowSum); - } + int idx = x + y * size.x; + rowSum += (pdf[idx - 1] + pdf[idx]) * 0.5f; + conditionalCDF[idx] = float(rowSum); } + } - // Step 2: Build marginal. - double marginalSum = 0.0; - marginalCDF[0] = float(marginalSum); - for (int y = 1; y < size.y; ++y) - { - marginalSum += (conditionalCDF[y * size.x - 1] + conditionalCDF[(y + 1) * size.x - 1]) * 0.5f; - marginalCDF[y] = float(marginalSum); - } - // Step 3: Normalize all distributions. - for (int y = 0; y < size.y; ++y) - { - marginalCDF[y] /= float(marginalSum); - } + // Step 2: Build marginal. + double marginalSum = 0.0; + marginalCDF[0] = float(marginalSum); + for (int y = 1; y < size.y; ++y) + { + marginalSum += (conditionalCDF[y * size.x - 1] + conditionalCDF[(y + 1) * size.x - 1]) * 0.5f; + marginalCDF[y] = float(marginalSum); + } + // Step 3: Normalize all distributions. + for (int y = 0; y < size.y; ++y) + { + marginalCDF[y] /= float(marginalSum); + } - for (int i = 0; i < size.x * size.y; ++i) - { - pdf[i] /= float(marginalSum); - conditionalCDF[i] /= float(marginalSum); - } + for (int i = 0; i < size.x * size.y; ++i) + { + pdf[i] /= float(marginalSum); + conditionalCDF[i] /= float(marginalSum); } } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/RGLCommon.h b/Source/Falcor/Scene/Material/RGLCommon.h index 7871cc4af4..8d32c844c0 100644 --- a/Source/Falcor/Scene/Material/RGLCommon.h +++ b/Source/Falcor/Scene/Material/RGLCommon.h @@ -31,41 +31,41 @@ namespace Falcor { - /** This class represents a 2D table of 2D distributions. - The basic model is that given coordinates x, y, - we cut out a 2D `slice' from the 4D table and then - sample it to obtain coordinates z, w. +/** This class represents a 2D table of 2D distributions. + The basic model is that given coordinates x, y, + we cut out a 2D `slice' from the 4D table and then + sample it to obtain coordinates z, w. - Within each distribution, the PDFs are linearly interpolated - with respect to z, w. Also, slices are linearly interpolated - from the table with respect to x, y. + Within each distribution, the PDFs are linearly interpolated + with respect to z, w. Also, slices are linearly interpolated + from the table with respect to x, y. - To achieve this goal, we first build the marginal/conditional - distribution for each 2D slice of the 4D table similar to - Pharr et al., with the only twist is that the PDF is linearly - interpolated, i.e. the CDFs store the integral of a linearly - interpolated PDF instead of the straight sum of the PDF. + To achieve this goal, we first build the marginal/conditional + distribution for each 2D slice of the 4D table similar to + Pharr et al., with the only twist is that the PDF is linearly + interpolated, i.e. the CDFs store the integral of a linearly + interpolated PDF instead of the straight sum of the PDF. - The actual interpolation/sampling at runtime happens on the GPU - (see RGLCommon.slang) - */ - class SamplableDistribution4D - { - public: - SamplableDistribution4D(const float* pdf, uint4 size); + The actual interpolation/sampling at runtime happens on the GPU + (see RGLCommon.slang) +*/ +class SamplableDistribution4D +{ +public: + SamplableDistribution4D(const float* pdf, uint4 size); - const float* getPDF() { return mPDF.get(); } + const float* getPDF() { return mPDF.get(); } - const float* getMarginal() { return mMarginal.get(); } + const float* getMarginal() { return mMarginal.get(); } - const float* getConditional() { return mConditional.get(); } + const float* getConditional() { return mConditional.get(); } - private: - uint4 mSize; - std::unique_ptr mPDF; - std::unique_ptr mMarginal; - std::unique_ptr mConditional; +private: + uint4 mSize; + std::unique_ptr mPDF; + std::unique_ptr mMarginal; + std::unique_ptr mConditional; - void build2DSlice(int2 size, float* pdf, float* marginalCdf, float* conditionalCdf); - }; -} + void build2DSlice(int2 size, float* pdf, float* marginalCdf, float* conditionalCdf); +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/RGLFile.cpp b/Source/Falcor/Scene/Material/RGLFile.cpp index 378327c8cc..25bb04c1e4 100644 --- a/Source/Falcor/Scene/Material/RGLFile.cpp +++ b/Source/Falcor/Scene/Material/RGLFile.cpp @@ -30,241 +30,240 @@ namespace Falcor { - size_t RGLFile::fieldSize(FieldType type) +size_t RGLFile::fieldSize(FieldType type) +{ + switch (type) { - switch (type) - { - case UInt8: return 1; - case UInt32: return 4; - case Float32: return 4; - default: return 0; - } + case UInt8: + return 1; + case UInt32: + return 4; + case Float32: + return 4; + default: + return 0; } +} - const RGLFile::Field* RGLFile::getField(const std::string& name) const +const RGLFile::Field* RGLFile::getField(const std::string& name) const +{ + auto iter = mFieldMap.find(name); + return iter == mFieldMap.end() ? nullptr : &mFields[iter->second]; +} + +void RGLFile::addField(const std::string& name, FieldType type, const std::vector& shape, const void* data) +{ + Field field; + field.name = name; + field.type = type; + field.dim = uint32_t(shape.size()); + field.shape.reset(new uint64_t[field.dim]); + field.numElems = 1; + for (uint32_t i = 0; i < field.dim; ++i) { - auto iter = mFieldMap.find(name); - return iter == mFieldMap.end() ? nullptr : &mFields[iter->second]; + field.shape[i] = shape[i]; + field.numElems *= shape[i]; } + size_t N = fieldSize(type); + if (N == 0) + FALCOR_THROW("RGLFile::fieldSize: Invalid field type"); + + field.data.reset(new uint8_t[N * field.numElems]); + std::memcpy(field.data.get(), data, N * field.numElems); + + mFieldMap[name] = int(mFields.size()); + mFields.emplace_back(std::move(field)); +} - void RGLFile::addField(const std::string& name, FieldType type, const std::vector& shape, const void* data) +void RGLFile::validate() +{ + const Field* thetaI = getField("theta_i"); + const Field* phiI = getField("phi_i"); + const Field* sigma = getField("sigma"); + const Field* ndf = getField("ndf"); + const Field* vndf = getField("vndf"); + const Field* rgb = getField("rgb"); + const Field* luminance = getField("luminance"); + const Field* description = getField("description"); + + if (!thetaI || thetaI->type != Float32 || thetaI->dim != 1) { - Field field; - field.name = name; - field.type = type; - field.dim = uint32_t(shape.size()); - field.shape.reset(new uint64_t[field.dim]); - field.numElems = 1; - for (uint32_t i = 0; i < field.dim; ++i) - { - field.shape[i] = shape[i]; - field.numElems *= shape[i]; - } - size_t N = fieldSize(type); - if (N == 0) FALCOR_THROW("RGLFile::fieldSize: Invalid field type"); + FALCOR_THROW("theta_i field missing or invalid"); + } - field.data.reset(new uint8_t[N * field.numElems]); - std::memcpy(field.data.get(), data, N * field.numElems); + if (!phiI || phiI->type != Float32 || phiI->dim != 1) + { + FALCOR_THROW("phi_i field missing or invalid"); + } - mFieldMap[name] = int(mFields.size()); - mFields.emplace_back(std::move(field)); + if (!sigma || sigma->type != Float32 || sigma->dim != 2) + { + FALCOR_THROW("sigma field missing or invalid"); } - void RGLFile::validate() + if (!ndf || ndf->type != Float32 || ndf->dim != 2) { - const Field* thetaI = getField("theta_i"); - const Field* phiI = getField("phi_i"); - const Field* sigma = getField("sigma"); - const Field* ndf = getField("ndf"); - const Field* vndf = getField("vndf"); - const Field* rgb = getField("rgb"); - const Field* luminance = getField("luminance"); - const Field* description = getField("description"); - - if (!thetaI || thetaI->type != Float32 || thetaI->dim != 1) - { - FALCOR_THROW("theta_i field missing or invalid"); - } + FALCOR_THROW("ndf field missing or invalid"); + } - if (!phiI || phiI->type != Float32 || phiI->dim != 1) - { - FALCOR_THROW("phi_i field missing or invalid"); - } + if (!vndf || vndf->type != Float32 || vndf->dim != 4 || vndf->shape[0] != phiI->shape[0] || vndf->shape[1] != thetaI->shape[0]) + { + FALCOR_THROW("vndf field missing or invalid"); + } - if (!sigma || sigma->type != Float32 || sigma->dim != 2) - { - FALCOR_THROW("sigma field missing or invalid"); - } + if (!luminance || luminance->type != Float32 || luminance->dim != 4 || luminance->shape[0] != phiI->shape[0] || + luminance->shape[1] != thetaI->shape[0] || luminance->shape[2] != luminance->shape[3]) + { + FALCOR_THROW("luminance field missing or invalid"); + } - if (!ndf || ndf->type != Float32 || ndf->dim != 2) - { - FALCOR_THROW("ndf field missing or invalid"); - } + if (!rgb || rgb->type != Float32 || rgb->dim != 5 || rgb->shape[0] != phiI->shape[0] || rgb->shape[1] != thetaI->shape[0] || + rgb->shape[2] != 3 || rgb->shape[3] != luminance->shape[2] || rgb->shape[4] != luminance->shape[3]) + { + FALCOR_THROW("rgb field missing or invalid"); + } - if (!vndf || vndf->type != Float32 || vndf->dim != 4 || vndf->shape[0] != phiI->shape[0] - || vndf->shape[1] != thetaI->shape[0]) - { - FALCOR_THROW("vndf field missing or invalid"); - } + if (!description || description->type != UInt8) + { + FALCOR_THROW("Description field missing or invalid"); + } - if (!luminance || luminance->type != Float32 || luminance->dim != 4 - || luminance->shape[0] != phiI->shape[0] - || luminance->shape[1] != thetaI->shape[0] - || luminance->shape[2] != luminance->shape[3]) - { - FALCOR_THROW("luminance field missing or invalid"); - } + bool isotropic = phiI->shape[0] <= 2; + std::string descString(reinterpret_cast(description->data.get()), description->numElems); - if (!rgb || rgb->type != Float32 || rgb->dim != 5 || rgb->shape[0] != phiI->shape[0] - || rgb->shape[1] != thetaI->shape[0] || rgb->shape[2] != 3 - || rgb->shape[3] != luminance->shape[2] || rgb->shape[4] != luminance->shape[3]) - { - FALCOR_THROW("rgb field missing or invalid"); - } + mMeasurement = MeasurementData{thetaI, phiI, sigma, ndf, vndf, rgb, luminance, isotropic, std::move(descString)}; +} - if (!description || description->type != UInt8) - { - FALCOR_THROW("Description field missing or invalid"); - } +RGLFile::RGLFile(std::ifstream& in) +{ + auto readBytes = [&](void* dst, size_t size) { in.read(reinterpret_cast(dst), size); }; - bool isotropic = phiI->shape[0] <= 2; - std::string descString(reinterpret_cast(description->data.get()), description->numElems); + uint8_t header[12]; + readBytes(header, 12); - mMeasurement = MeasurementData{thetaI, phiI, sigma, ndf, vndf, rgb, luminance, isotropic, std::move(descString)}; - } + uint8_t version[2]; + readBytes(version, 2); + + uint32_t fieldCount; + readBytes(&fieldCount, 4); - RGLFile::RGLFile(std::ifstream& in) + if (strcmp(reinterpret_cast(header), "tensor_file")) { - auto readBytes = [&](void* dst, size_t size) - { - in.read(reinterpret_cast(dst), size); - }; + FALCOR_THROW("Invalid file header"); + } + if (version[0] != 1 || version[1] != 0) + { + FALCOR_THROW("Unsupported file version"); + } - uint8_t header[12]; - readBytes(header, 12); + for (uint32_t i = 0; i < fieldCount; ++i) + { + uint16_t nameLength; + readBytes(&nameLength, 2); - uint8_t version[2]; - readBytes(version, 2); + std::string fieldName(nameLength, '\0'); + readBytes(&fieldName[0], nameLength); - uint32_t fieldCount; - readBytes(&fieldCount, 4); + uint16_t fieldDim; + readBytes(&fieldDim, 2); - if (strcmp(reinterpret_cast(header), "tensor_file")) - { - FALCOR_THROW("Invalid file header"); - } - if (version[0] != 1 || version[1] != 0) - { - FALCOR_THROW("Unsupported file version"); - } + uint8_t fieldType; + readBytes(&fieldType, 1); - for (uint32_t i = 0; i < fieldCount; ++i) - { - uint16_t nameLength; - readBytes(&nameLength, 2); + uint64_t offset; + readBytes(&offset, 8); - std::string fieldName(nameLength, '\0'); - readBytes(&fieldName[0], nameLength); + Field field; + field.name = fieldName; + field.type = FieldType(fieldType); + field.dim = fieldDim; + field.shape.reset(new uint64_t[fieldDim]); + readBytes(field.shape.get(), 8 * fieldDim); - uint16_t fieldDim; - readBytes(&fieldDim, 2); + if (!in.good()) + FALCOR_THROW("Error parsing RGL field: File truncated"); - uint8_t fieldType; - readBytes(&fieldType, 1); + size_t elemSize = fieldSize(FieldType(fieldType)); + if (elemSize == 0) + { + // Unsupported field type - ignore, we don't need it. + continue; + } - uint64_t offset; - readBytes(&offset, 8); + uint64_t N = 1; + for (uint32_t j = 0; j < fieldDim; ++j) + { + N *= field.shape[j]; + } + field.numElems = N; - Field field; - field.name = fieldName; - field.type = FieldType(fieldType); - field.dim = fieldDim; - field.shape.reset(new uint64_t[fieldDim]); - readBytes(field.shape.get(), 8 * fieldDim); + field.data.reset(new uint8_t[N * elemSize]); - if (!in.good()) FALCOR_THROW("Error parsing RGL field: File truncated"); + uint64_t pos = in.tellg(); + in.seekg(offset); + readBytes(field.data.get(), N * elemSize); + in.seekg(pos); - size_t elemSize = fieldSize(FieldType(fieldType)); - if (elemSize == 0) - { - // Unsupported field type - ignore, we don't need it. - continue; - } + mFieldMap.insert(std::make_pair(std::string(fieldName), int(mFields.size()))); + mFields.emplace_back(std::move(field)); + } - uint64_t N = 1; - for (uint32_t j = 0; j < fieldDim; ++j) - { - N *= field.shape[j]; - } - field.numElems = N; + validate(); +} - field.data.reset(new uint8_t[N * elemSize]); +template +void write(std::ofstream& out, const T& t) +{ + out.write(reinterpret_cast(&t), sizeof(T)); +} - uint64_t pos = in.tellg(); - in.seekg(offset); - readBytes(field.data.get(), N * elemSize); - in.seekg(pos); +template +void write(std::ofstream& out, const T* t, size_t N) +{ + out.write(reinterpret_cast(t), sizeof(T) * N); +} - mFieldMap.insert(std::make_pair(std::string(fieldName), int(mFields.size()))); - mFields.emplace_back(std::move(field)); - } +void RGLFile::saveFile(std::ofstream& out) const +{ + out.write("tensor_file", 12); - validate(); - } + // Write header. + uint8_t version[] = {1, 0}; + write(out, version, 2); + write(out, uint32_t(mFields.size())); - template - void write(std::ofstream& out, const T& t) + // Compute size of header+field descriptions. + uint64_t writeOffset = 18; + for (const auto& field : mFields) { - out.write(reinterpret_cast(&t), sizeof(T)); + writeOffset += 13 + field.name.size() + 8 * field.dim; } - template - void write(std::ofstream& out, const T* t, size_t N) + // RGL data blocks seem to be aligned at 8 byte boundaries. + auto alignAddress = [&](uint64_t a) { return ((a + 7) / 8) * 8; }; + uint64_t alignedOffset = alignAddress(writeOffset); + uint64_t headerPadding = alignedOffset - writeOffset; + + // Write fields and keep track of where data blocks will start via alignedOffset. + for (const auto& field : mFields) { - out.write(reinterpret_cast(t), sizeof(T) * N); + write(out, uint16_t(field.name.size())); + write(out, field.name.data(), field.name.size()); + write(out, uint16_t(field.dim)); + write(out, uint8_t(field.type)); + write(out, alignedOffset); + write(out, field.shape.get(), field.dim); + alignedOffset += fieldSize(field.type) * field.numElems; + alignedOffset = alignAddress(alignedOffset); } - - void RGLFile::saveFile(std::ofstream& out) const + // Pad with zeros to get correct alignment. + uint8_t zeros[8] = {0}; + write(out, zeros, headerPadding); + for (size_t i = 0; i < mFields.size(); ++i) { - out.write("tensor_file", 12); - - // Write header. - uint8_t version[] = {1, 0}; - write(out, version, 2); - write(out, uint32_t(mFields.size())); - - // Compute size of header+field descriptions. - uint64_t writeOffset = 18; - for (const auto& field : mFields) - { - writeOffset += 13 + field.name.size() + 8 * field.dim; - } - - // RGL data blocks seem to be aligned at 8 byte boundaries. - auto alignAddress = [&](uint64_t a) { return ((a + 7) / 8) * 8; }; - uint64_t alignedOffset = alignAddress(writeOffset); - uint64_t headerPadding = alignedOffset - writeOffset; - - // Write fields and keep track of where data blocks will start via alignedOffset. - for (const auto& field : mFields) - { - write(out, uint16_t(field.name.size())); - write(out, field.name.data(), field.name.size()); - write(out, uint16_t(field.dim)); - write(out, uint8_t(field.type)); - write(out, alignedOffset); - write(out, field.shape.get(), field.dim); - alignedOffset += fieldSize(field.type) * field.numElems; - alignedOffset = alignAddress(alignedOffset); - } - // Pad with zeros to get correct alignment. - uint8_t zeros[8] = {0}; - write(out, zeros, headerPadding); - for (size_t i = 0; i < mFields.size(); ++i) - { - size_t length = mFields[i].numElems * fieldSize(mFields[i].type); - write(out, mFields[i].data.get(), length); - write(out, zeros, alignAddress(length) - length); - } + size_t length = mFields[i].numElems * fieldSize(mFields[i].type); + write(out, mFields[i].data.get(), length); + write(out, zeros, alignAddress(length) - length); } } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/RGLFile.h b/Source/Falcor/Scene/Material/RGLFile.h index 6f1d9f77bf..36e7e0c487 100644 --- a/Source/Falcor/Scene/Material/RGLFile.h +++ b/Source/Falcor/Scene/Material/RGLFile.h @@ -34,73 +34,70 @@ namespace Falcor { - /** Class representing a measured material file from the RGL BRDF database. - */ - class RGLFile +/** Class representing a measured material file from the RGL BRDF database. + */ +class RGLFile +{ +public: + /** There are many more field types, but none that we need. Ignore all other types. + */ + enum FieldType { - public: - /** There are many more field types, but none that we need. Ignore all other types. - */ - enum FieldType - { - UInt8 = 1, - UInt32 = 5, - Float32 = 10, - }; + UInt8 = 1, + UInt32 = 5, + Float32 = 10, + }; - struct Field - { - std::string name; - FieldType type; - uint32_t dim; - int64_t numElems; - std::unique_ptr shape; - std::unique_ptr data; - }; + struct Field + { + std::string name; + FieldType type; + uint32_t dim; + int64_t numElems; + std::unique_ptr shape; + std::unique_ptr data; + }; - /** Collected set of fields necessary to render the BRDF. - */ - struct MeasurementData - { - const Field* thetaI; - const Field* phiI; - const Field* sigma; - const Field* ndf; - const Field* vndf; - const Field* rgb; - const Field* luminance; - bool isotropic; - std::string description; - }; + /** Collected set of fields necessary to render the BRDF. + */ + struct MeasurementData + { + const Field* thetaI; + const Field* phiI; + const Field* sigma; + const Field* ndf; + const Field* vndf; + const Field* rgb; + const Field* luminance; + bool isotropic; + std::string description; + }; - RGLFile() = default; + RGLFile() = default; - /** Loads RGL measured BRDF file and validates contents. Throws Falcor::Exception on failure. - */ - RGLFile(std::ifstream& in); + /** Loads RGL measured BRDF file and validates contents. Throws Falcor::Exception on failure. + */ + RGLFile(std::ifstream& in); - void saveFile(std::ofstream& out) const; + void saveFile(std::ofstream& out) const; - const MeasurementData& data() const - { - return mMeasurement; - } + const MeasurementData& data() const { return mMeasurement; } - void addField(const std::string& name, FieldType type, const std::vector& shape, const void* data); + void addField(const std::string& name, FieldType type, const std::vector& shape, const void* data); - private: - std::unordered_map mFieldMap; - std::vector mFields; - MeasurementData mMeasurement; +private: + std::unordered_map mFieldMap; + std::vector mFields; + MeasurementData mMeasurement; - /** Make sure all required fields are present and have correct shape and dimensions, - then populates mMeasurement field if all fields are correct. - Throws Falcor::Exception on validation error. - */ - void validate(); + /** Make sure all required fields are present and have correct shape and dimensions, + then populates mMeasurement field if all fields are correct. + Throws Falcor::Exception on validation error. + */ + void validate(); - const Field* getField(const std::string& name) const; + const Field* getField(const std::string& name) const; - static size_t fieldSize(FieldType type); - }; -} + static size_t fieldSize(FieldType type); +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/RGLMaterial.cpp b/Source/Falcor/Scene/Material/RGLMaterial.cpp index 2acf934bc3..c39af6dab7 100644 --- a/Source/Falcor/Scene/Material/RGLMaterial.cpp +++ b/Source/Falcor/Scene/Material/RGLMaterial.cpp @@ -38,279 +38,298 @@ namespace Falcor { - namespace - { - static_assert((sizeof(MaterialHeader) + sizeof(RGLMaterialData)) <= sizeof(MaterialDataBlob), "RGLMaterialData is too large"); +namespace +{ +static_assert((sizeof(MaterialHeader) + sizeof(RGLMaterialData)) <= sizeof(MaterialDataBlob), "RGLMaterialData is too large"); - const char kShaderFile[] = "Rendering/Materials/RGLMaterial.slang"; +const char kShaderFile[] = "Rendering/Materials/RGLMaterial.slang"; - const uint32_t kAlbedoLUTSize = RGLMaterialData::kAlbedoLUTSize; - const ResourceFormat kAlbedoLUTFormat = ResourceFormat::RGBA32Float; +const uint32_t kAlbedoLUTSize = RGLMaterialData::kAlbedoLUTSize; +const ResourceFormat kAlbedoLUTFormat = ResourceFormat::RGBA32Float; - const std::string kLoadFile = "load"; - } +const std::string kLoadFile = "load"; +} // namespace - RGLMaterial::RGLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path) - : Material(pDevice, name, MaterialType::RGL) +RGLMaterial::RGLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path) + : Material(pDevice, name, MaterialType::RGL) +{ + FALCOR_CHECK(!path.empty(), "Missing path."); + + if (!loadBRDF(path)) { - FALCOR_CHECK(!path.empty(), "Missing path."); + FALCOR_THROW("RGLMaterial() - Failed to load BRDF from '{}'.", path); + } - if (!loadBRDF(path)) - { - FALCOR_THROW("RGLMaterial() - Failed to load BRDF from '{}'.", path); - } + // Create resources for albedo lookup table. + Sampler::Desc desc; + desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Point, TextureFilteringMode::Point); + desc.setAddressingMode(TextureAddressingMode::Clamp, TextureAddressingMode::Clamp, TextureAddressingMode::Clamp); + desc.setMaxAnisotropy(1); + mpSampler = mpDevice->createSampler(desc); - // Create resources for albedo lookup table. - Sampler::Desc desc; - desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Point, TextureFilteringMode::Point); - desc.setAddressingMode(TextureAddressingMode::Clamp, TextureAddressingMode::Clamp, TextureAddressingMode::Clamp); - desc.setMaxAnisotropy(1); - mpSampler = mpDevice->createSampler(desc); + prepareAlbedoLUT(mpDevice->getRenderContext()); +} - prepareAlbedoLUT(mpDevice->getRenderContext()); - } +bool RGLMaterial::renderUI(Gui::Widgets& widget) +{ + widget.text("RGL BRDF " + mBRDFName); + widget.text(mBRDFDescription); + widget.tooltip("Full path the BRDF was loaded from:\n" + mPath.string(), true); - bool RGLMaterial::renderUI(Gui::Widgets& widget) - { - widget.text("RGL BRDF " + mBRDFName); - widget.text(mBRDFDescription); - widget.tooltip("Full path the BRDF was loaded from:\n" + mPath.string(), true); + return false; +} - return false; - } +Material::UpdateFlags RGLMaterial::update(MaterialSystem* pOwner) +{ + FALCOR_ASSERT(pOwner); - Material::UpdateFlags RGLMaterial::update(MaterialSystem* pOwner) + auto flags = Material::UpdateFlags::None; + if (mUpdates != Material::UpdateFlags::None) { - FALCOR_ASSERT(pOwner); - - auto flags = Material::UpdateFlags::None; - if (mUpdates != Material::UpdateFlags::None) + auto checkBuffer = [&](ref& buf, uint& handle) { - auto checkBuffer = [&](ref& buf, uint& handle) + // If a BRDF was already loaded and we're just updating data, + // then buffer handles are already assigned. + // Replace the buffer contents instead of adding a new buffer. + if (mBRDFUploaded) { - // If a BRDF was already loaded and we're just updating data, - // then buffer handles are already assigned. - // Replace the buffer contents instead of adding a new buffer. - if (mBRDFUploaded) - { - pOwner->replaceBuffer(handle, buf); - } - else - { - uint32_t bufferID = pOwner->addBuffer(buf); - if (bufferID != handle) mUpdates |= Material::UpdateFlags::DataChanged; - handle = bufferID; - } - }; - - checkBuffer(mpThetaBuf, mData.thetaBufID); - checkBuffer(mpPhiBuf, mData.phiBufID); - checkBuffer(mpSigmaBuf, mData.sigmaBufID); - checkBuffer(mpNDFBuf, mData.ndfBufID); - checkBuffer(mpVNDFBuf, mData.vndfBufID); - checkBuffer(mpLumiBuf, mData.lumiBufID); - checkBuffer(mpRGBBuf, mData.rgbBufID); - checkBuffer(mpVNDFMarginalBuf, mData.vndfMarginalBufID); - checkBuffer(mpLumiMarginalBuf, mData.lumiMarginalBufID); - checkBuffer(mpVNDFConditionalBuf, mData.vndfConditionalBufID); - checkBuffer(mpLumiConditionalBuf, mData.lumiConditionalBufID); - updateTextureHandle(pOwner, mpAlbedoLUT, mData.texAlbedoLUT); - mBRDFUploaded = true; - - flags |= mUpdates; - mUpdates = Material::UpdateFlags::None; - } + pOwner->replaceBuffer(handle, buf); + } + else + { + uint32_t bufferID = pOwner->addBuffer(buf); + if (bufferID != handle) + mUpdates |= Material::UpdateFlags::DataChanged; + handle = bufferID; + } + }; - return flags; + checkBuffer(mpThetaBuf, mData.thetaBufID); + checkBuffer(mpPhiBuf, mData.phiBufID); + checkBuffer(mpSigmaBuf, mData.sigmaBufID); + checkBuffer(mpNDFBuf, mData.ndfBufID); + checkBuffer(mpVNDFBuf, mData.vndfBufID); + checkBuffer(mpLumiBuf, mData.lumiBufID); + checkBuffer(mpRGBBuf, mData.rgbBufID); + checkBuffer(mpVNDFMarginalBuf, mData.vndfMarginalBufID); + checkBuffer(mpLumiMarginalBuf, mData.lumiMarginalBufID); + checkBuffer(mpVNDFConditionalBuf, mData.vndfConditionalBufID); + checkBuffer(mpLumiConditionalBuf, mData.lumiConditionalBufID); + updateTextureHandle(pOwner, mpAlbedoLUT, mData.texAlbedoLUT); + mBRDFUploaded = true; + + flags |= mUpdates; + mUpdates = Material::UpdateFlags::None; } - bool RGLMaterial::isEqual(const ref& pOther) const - { - auto other = dynamic_ref_cast(pOther); - if (!other) return false; + return flags; +} + +bool RGLMaterial::isEqual(const ref& pOther) const +{ + auto other = dynamic_ref_cast(pOther); + if (!other) + return false; - if (!isBaseEqual(*other)) return false; - if (mPath != other->mPath) return false; + if (!isBaseEqual(*other)) + return false; + if (mPath != other->mPath) + return false; - return true; - } + return true; +} - ProgramDesc::ShaderModuleList RGLMaterial::getShaderModules() const +ProgramDesc::ShaderModuleList RGLMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} + +TypeConformanceList RGLMaterial::getTypeConformances() const +{ + return {{{"RGLMaterial", "IMaterial"}, (uint32_t)MaterialType::RGL}}; +} + +bool RGLMaterial::loadBRDF(const std::filesystem::path& path) +{ + std::ifstream ifs(path, std::ios_base::in | std::ios_base::binary); + if (!ifs.good()) { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; + logWarning("RGLMaterial::loadBRDF() - Failed to open file '{}'.", path); + return false; } - TypeConformanceList RGLMaterial::getTypeConformances() const + std::unique_ptr file; + try { - return { {{"RGLMaterial", "IMaterial"}, (uint32_t)MaterialType::RGL} }; + file.reset(new RGLFile(ifs)); } - - bool RGLMaterial::loadBRDF(const std::filesystem::path& path) + catch (const RuntimeError& e) { - std::ifstream ifs(path, std::ios_base::in | std::ios_base::binary); - if (!ifs.good()) - { - logWarning("RGLMaterial::loadBRDF() - Failed to open file '{}'.", path); - return false; - } - - std::unique_ptr file; - try - { - file.reset(new RGLFile(ifs)); - } - catch(const RuntimeError& e) - { - logWarning("RGLMaterial::loadBRDF() - Failed to parse RGL file '{}': {}.", path, e.what()); - return false; - } - - if (!ifs.good()) - { - logWarning("RGLMaterial::loadBRDF() - Failed to load BRDF data from file '{}': Read error.", path); - return false; - } - - auto theta = file->data().thetaI; - auto phi = file->data().phiI; - auto sigma = file->data().sigma; - auto ndf = file->data().ndf; - auto vndf = file->data().vndf; - auto lumi = file->data().luminance; - auto rgb = file->data().rgb; - - const uint64_t kMaxResolution = RGLMaterialData::kMaxResolution; - if (phi->shape[0] > kMaxResolution || theta->shape[0] > kMaxResolution || std::max(sigma->shape[0], sigma->shape[1]) > kMaxResolution - || std::max(ndf->shape[0], ndf->shape[1]) > kMaxResolution || std::max(vndf->shape[2], vndf->shape[3]) > kMaxResolution - || std::max(lumi->shape[2], lumi->shape[3]) > kMaxResolution) - { - logWarning("RGLMaterial::loadBRDF() - Failed to process BRDF data: Measurement resolution too large.", path); - return false; - } - - mPath = path; - mBRDFName = std::filesystem::path(path).stem().string(); - mBRDFDescription = file->data().description; - - mData.phiSize = uint(phi->shape[0]); - mData.thetaSize = uint(theta->shape[0]); - mData.sigmaSize = uint2(sigma->shape[1], sigma->shape[0]); - mData. ndfSize = uint2(ndf ->shape[1], ndf ->shape[0]); - mData. vndfSize = uint2(vndf ->shape[3], vndf ->shape[2]); - mData. lumiSize = uint2(lumi ->shape[3], lumi ->shape[2]); - - uint4 vndfSize = uint4(mData.phiSize, mData.thetaSize, mData.vndfSize.x, mData.vndfSize.y); - uint4 lumiSize = uint4(mData.phiSize, mData.thetaSize, mData.lumiSize.x, mData.lumiSize.y); - auto prod3 = [&](uint4 v) { return v.x * v.y * v.z; }; - auto prod4 = [&](uint4 v) { return v.x * v.y * v.z * v.w; }; - - SamplableDistribution4D vndfDist(reinterpret_cast(vndf->data.get()), vndfSize); - SamplableDistribution4D lumiDist(reinterpret_cast(lumi->data.get()), lumiSize); - - mpVNDFMarginalBuf = mpDevice->createBuffer(prod3(vndfSize) * 4, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, vndfDist.getMarginal()); - mpLumiMarginalBuf = mpDevice->createBuffer(prod3(lumiSize) * 4, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, lumiDist.getMarginal()); - mpVNDFConditionalBuf = mpDevice->createBuffer(prod4(vndfSize) * 4, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, vndfDist.getConditional()); - mpLumiConditionalBuf = mpDevice->createBuffer(prod4(lumiSize) * 4, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, lumiDist.getConditional()); + logWarning("RGLMaterial::loadBRDF() - Failed to parse RGL file '{}': {}.", path, e.what()); + return false; + } - mpThetaBuf = mpDevice->createBuffer(theta->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, theta->data.get()); - mpPhiBuf = mpDevice->createBuffer(phi ->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, phi ->data.get()); - mpSigmaBuf = mpDevice->createBuffer(sigma->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, sigma->data.get()); - mpNDFBuf = mpDevice->createBuffer(ndf ->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, ndf ->data.get()); - mpVNDFBuf = mpDevice->createBuffer(vndf ->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, vndfDist.getPDF()); - mpLumiBuf = mpDevice->createBuffer(lumi ->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, lumiDist.getPDF()); - mpRGBBuf = mpDevice->createBuffer(rgb ->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, rgb ->data.get()); + if (!ifs.good()) + { + logWarning("RGLMaterial::loadBRDF() - Failed to load BRDF data from file '{}': Read error.", path); + return false; + } - markUpdates(Material::UpdateFlags::ResourcesChanged); + auto theta = file->data().thetaI; + auto phi = file->data().phiI; + auto sigma = file->data().sigma; + auto ndf = file->data().ndf; + auto vndf = file->data().vndf; + auto lumi = file->data().luminance; + auto rgb = file->data().rgb; + + const uint64_t kMaxResolution = RGLMaterialData::kMaxResolution; + if (phi->shape[0] > kMaxResolution || theta->shape[0] > kMaxResolution || std::max(sigma->shape[0], sigma->shape[1]) > kMaxResolution || + std::max(ndf->shape[0], ndf->shape[1]) > kMaxResolution || std::max(vndf->shape[2], vndf->shape[3]) > kMaxResolution || + std::max(lumi->shape[2], lumi->shape[3]) > kMaxResolution) + { + logWarning("RGLMaterial::loadBRDF() - Failed to process BRDF data: Measurement resolution too large.", path); + return false; + } - logInfo("Loaded RGL BRDF '{}': {}.", mBRDFName, mBRDFDescription); + mPath = path; + mBRDFName = std::filesystem::path(path).stem().string(); + mBRDFDescription = file->data().description; + + mData.phiSize = uint(phi->shape[0]); + mData.thetaSize = uint(theta->shape[0]); + mData.sigmaSize = uint2(sigma->shape[1], sigma->shape[0]); + mData.ndfSize = uint2(ndf->shape[1], ndf->shape[0]); + mData.vndfSize = uint2(vndf->shape[3], vndf->shape[2]); + mData.lumiSize = uint2(lumi->shape[3], lumi->shape[2]); + + uint4 vndfSize = uint4(mData.phiSize, mData.thetaSize, mData.vndfSize.x, mData.vndfSize.y); + uint4 lumiSize = uint4(mData.phiSize, mData.thetaSize, mData.lumiSize.x, mData.lumiSize.y); + auto prod3 = [&](uint4 v) { return v.x * v.y * v.z; }; + auto prod4 = [&](uint4 v) { return v.x * v.y * v.z * v.w; }; + + SamplableDistribution4D vndfDist(reinterpret_cast(vndf->data.get()), vndfSize); + SamplableDistribution4D lumiDist(reinterpret_cast(lumi->data.get()), lumiSize); + + mpVNDFMarginalBuf = + mpDevice->createBuffer(prod3(vndfSize) * 4, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, vndfDist.getMarginal()); + mpLumiMarginalBuf = + mpDevice->createBuffer(prod3(lumiSize) * 4, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, lumiDist.getMarginal()); + mpVNDFConditionalBuf = + mpDevice->createBuffer(prod4(vndfSize) * 4, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, vndfDist.getConditional()); + mpLumiConditionalBuf = + mpDevice->createBuffer(prod4(lumiSize) * 4, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, lumiDist.getConditional()); + + mpThetaBuf = mpDevice->createBuffer( + theta->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, theta->data.get() + ); + mpPhiBuf = + mpDevice->createBuffer(phi->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, phi->data.get()); + mpSigmaBuf = mpDevice->createBuffer( + sigma->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, sigma->data.get() + ); + mpNDFBuf = + mpDevice->createBuffer(ndf->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, ndf->data.get()); + mpVNDFBuf = mpDevice->createBuffer( + vndf->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, vndfDist.getPDF() + ); + mpLumiBuf = mpDevice->createBuffer( + lumi->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, lumiDist.getPDF() + ); + mpRGBBuf = + mpDevice->createBuffer(rgb->numElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, rgb->data.get()); + + markUpdates(Material::UpdateFlags::ResourcesChanged); + + logInfo("Loaded RGL BRDF '{}': {}.", mBRDFName, mBRDFDescription); + + return true; +} - return true; - } +void RGLMaterial::prepareAlbedoLUT(RenderContext* pRenderContext) // TODO +{ + const auto texPath = mPath.replace_extension("dds"); - void RGLMaterial::prepareAlbedoLUT(RenderContext* pRenderContext) // TODO + // Try loading albedo lookup table. + if (std::filesystem::is_regular_file(texPath)) { - const auto texPath = mPath.replace_extension("dds"); + // Load 1D texture in non-SRGB format, no mips. + // If successful, verify dimensions/format/etc. match the expectations. + mpAlbedoLUT = Texture::createFromFile(mpDevice, texPath.string(), false, false, ResourceBindFlags::ShaderResource); - // Try loading albedo lookup table. - if (std::filesystem::is_regular_file(texPath)) + if (mpAlbedoLUT) { - // Load 1D texture in non-SRGB format, no mips. - // If successful, verify dimensions/format/etc. match the expectations. - mpAlbedoLUT = Texture::createFromFile(mpDevice, texPath.string(), false, false, ResourceBindFlags::ShaderResource); - - if (mpAlbedoLUT) + if (mpAlbedoLUT->getFormat() == kAlbedoLUTFormat && mpAlbedoLUT->getWidth() == kAlbedoLUTSize && + mpAlbedoLUT->getHeight() == 1 && mpAlbedoLUT->getDepth() == 1 && mpAlbedoLUT->getMipCount() == 1 && + mpAlbedoLUT->getArraySize() == 1) { - if (mpAlbedoLUT->getFormat() == kAlbedoLUTFormat && - mpAlbedoLUT->getWidth() == kAlbedoLUTSize && - mpAlbedoLUT->getHeight() == 1 && mpAlbedoLUT->getDepth() == 1 && - mpAlbedoLUT->getMipCount() == 1 && mpAlbedoLUT->getArraySize() == 1) - { - logInfo("Loaded albedo LUT from '{}'.", texPath.string()); - return; - } + logInfo("Loaded albedo LUT from '{}'.", texPath.string()); + return; } } + } - // Failed to load a valid lookup table. We'll recompute it. - computeAlbedoLUT(pRenderContext); + // Failed to load a valid lookup table. We'll recompute it. + computeAlbedoLUT(pRenderContext); - // Cache lookup table in texture on disk. - // TODO: Capture texture to DDS is not yet supported. Calling ImageIO directly for now. - //mpAlbedoLUT->captureToFile(0, 0, texPath.string(), Bitmap::FileFormat::DdsFile, Bitmap::ExportFlags::Uncompressed); - FALCOR_ASSERT(mpAlbedoLUT); - ImageIO::saveToDDS(pRenderContext, texPath.string(), mpAlbedoLUT, ImageIO::CompressionMode::None, false); + // Cache lookup table in texture on disk. + // TODO: Capture texture to DDS is not yet supported. Calling ImageIO directly for now. + // mpAlbedoLUT->captureToFile(0, 0, texPath.string(), Bitmap::FileFormat::DdsFile, Bitmap::ExportFlags::Uncompressed); + FALCOR_ASSERT(mpAlbedoLUT); + ImageIO::saveToDDS(pRenderContext, texPath.string(), mpAlbedoLUT, ImageIO::CompressionMode::None, false); - logInfo("Saved albedo LUT to '{}'.", texPath.string()); - } + logInfo("Saved albedo LUT to '{}'.", texPath.string()); +} - void RGLMaterial::computeAlbedoLUT(RenderContext* pRenderContext) // TODO - { - logInfo("Computing albedo LUT for RGL BRDF '{}'...", mBRDFName); +void RGLMaterial::computeAlbedoLUT(RenderContext* pRenderContext) // TODO +{ + logInfo("Computing albedo LUT for RGL BRDF '{}'...", mBRDFName); - std::vector cosThetas(kAlbedoLUTSize); - for (uint32_t i = 0; i < kAlbedoLUTSize; i++) cosThetas[i] = (float)(i + 1) / kAlbedoLUTSize; + std::vector cosThetas(kAlbedoLUTSize); + for (uint32_t i = 0; i < kAlbedoLUTSize; i++) + cosThetas[i] = (float)(i + 1) / kAlbedoLUTSize; - // Create copy of material to avoid changing our local state. - auto pMaterial = make_ref(*this); + // Create copy of material to avoid changing our local state. + auto pMaterial = make_ref(*this); - // Create and update scene containing the material. - Scene::SceneData sceneData; - sceneData.pMaterials = std::make_unique(mpDevice); - MaterialID materialID = sceneData.pMaterials->addMaterial(pMaterial); + // Create and update scene containing the material. + Scene::SceneData sceneData; + sceneData.pMaterials = std::make_unique(mpDevice); + MaterialID materialID = sceneData.pMaterials->addMaterial(pMaterial); - ref pScene = Scene::create(mpDevice, std::move(sceneData)); - pScene->update(pRenderContext, 0.0); + ref pScene = Scene::create(mpDevice, std::move(sceneData)); + pScene->update(pRenderContext, 0.0); - // Create BSDF integrator utility. - BSDFIntegrator integrator(mpDevice, pScene); + // Create BSDF integrator utility. + BSDFIntegrator integrator(mpDevice, pScene); - // Integrate BSDF. - // TODO: Measured BRDFs could potentially be anisotropic. - // It's unlikely this would affect the albedo significantly, and doing the integration - // properly would be more trouble than its worth. - auto albedos = integrator.integrateIsotropic(pRenderContext, materialID, cosThetas); + // Integrate BSDF. + // TODO: Measured BRDFs could potentially be anisotropic. + // It's unlikely this would affect the albedo significantly, and doing the integration + // properly would be more trouble than its worth. + auto albedos = integrator.integrateIsotropic(pRenderContext, materialID, cosThetas); - // Copy result into format needed for texture creation. - static_assert(kAlbedoLUTFormat == ResourceFormat::RGBA32Float); - std::vector initData(kAlbedoLUTSize, float4(0.f)); - for (uint32_t i = 0; i < kAlbedoLUTSize; i++) initData[i] = float4(albedos[i], 1.f); + // Copy result into format needed for texture creation. + static_assert(kAlbedoLUTFormat == ResourceFormat::RGBA32Float); + std::vector initData(kAlbedoLUTSize, float4(0.f)); + for (uint32_t i = 0; i < kAlbedoLUTSize; i++) + initData[i] = float4(albedos[i], 1.f); - // Create albedo LUT texture. - mpAlbedoLUT = mpDevice->createTexture2D(kAlbedoLUTSize, 1, kAlbedoLUTFormat, 1, 1, initData.data(), ResourceBindFlags::ShaderResource); - } + // Create albedo LUT texture. + // TODO: Make this a 1D texture when load/save is supported. See + mpAlbedoLUT = mpDevice->createTexture2D(kAlbedoLUTSize, 1, kAlbedoLUTFormat, 1, 1, initData.data(), ResourceBindFlags::ShaderResource); +} - FALCOR_SCRIPT_BINDING(RGLMaterial) - { - using namespace pybind11::literals; +FALCOR_SCRIPT_BINDING(RGLMaterial) +{ + using namespace pybind11::literals; - FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) + FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) - pybind11::class_> material(m, "RGLMaterial"); - auto create = [] (const std::string& name, const std::filesystem::path& path) - { - return RGLMaterial::create(accessActivePythonSceneBuilder().getDevice(), name, getActiveAssetResolver().resolvePath(path)); - }; - material.def(pybind11::init(create), "name"_a, "path"_a); // PYTHONDEPRECATED - material.def(kLoadFile.c_str(), &RGLMaterial::loadBRDF, "path"_a); - } + pybind11::class_> material(m, "RGLMaterial"); + auto create = [](const std::string& name, const std::filesystem::path& path) + { return RGLMaterial::create(accessActivePythonSceneBuilderDevice(), name, getActiveAssetResolver().resolvePath(path)); }; + material.def(pybind11::init(create), "name"_a, "path"_a); // PYTHONDEPRECATED + material.def(kLoadFile.c_str(), &RGLMaterial::loadBRDF, "path"_a); } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/RGLMaterial.h b/Source/Falcor/Scene/Material/RGLMaterial.h index 927fd88683..6a897665d5 100644 --- a/Source/Falcor/Scene/Material/RGLMaterial.h +++ b/Source/Falcor/Scene/Material/RGLMaterial.h @@ -32,57 +32,60 @@ namespace Falcor { - /** Class representing a measured material from the RGL BRDF database. +/** Class representing a measured material from the RGL BRDF database. - For details refer to: - Jonathan Dupuy, Wenzel Jakob - "An Adaptive Parameterization for Efficient Material Acquisition and Rendering". - Transactions on Graphics (Proc. SIGGRAPH Asia 2018) - */ - class FALCOR_API RGLMaterial : public Material + For details refer to: + Jonathan Dupuy, Wenzel Jakob + "An Adaptive Parameterization for Efficient Material Acquisition and Rendering". + Transactions on Graphics (Proc. SIGGRAPH Asia 2018) +*/ +class FALCOR_API RGLMaterial : public Material +{ + FALCOR_OBJECT(RGLMaterial) +public: + static ref create(ref pDevice, const std::string& name, const std::filesystem::path& path) { - FALCOR_OBJECT(RGLMaterial) - public: - static ref create(ref pDevice, const std::string& name, const std::filesystem::path& path) { return make_ref(pDevice, name, path); } + return make_ref(pDevice, name, path); + } - RGLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path); + RGLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path); - bool renderUI(Gui::Widgets& widget) override; - Material::UpdateFlags update(MaterialSystem* pOwner) override; - bool isEqual(const ref& pOther) const override; - MaterialDataBlob getDataBlob() const override { return prepareDataBlob(mData); } - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; + bool renderUI(Gui::Widgets& widget) override; + Material::UpdateFlags update(MaterialSystem* pOwner) override; + bool isEqual(const ref& pOther) const override; + MaterialDataBlob getDataBlob() const override { return prepareDataBlob(mData); } + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; - virtual size_t getMaxBufferCount() const override { return 12; } + virtual size_t getMaxBufferCount() const override { return 12; } - bool loadBRDF(const std::filesystem::path& path); + bool loadBRDF(const std::filesystem::path& path); - protected: - void prepareData(const int dims[3], const std::vector& data); - void prepareAlbedoLUT(RenderContext* pRenderContext); - void computeAlbedoLUT(RenderContext* pRenderContext); +protected: + void prepareData(const int dims[3], const std::vector& data); + void prepareAlbedoLUT(RenderContext* pRenderContext); + void computeAlbedoLUT(RenderContext* pRenderContext); - std::filesystem::path mPath; ///< Full path to the BRDF loaded. - std::string mBRDFName; ///< This is the file basename without extension. - std::string mBRDFDescription; ///< Description of the BRDF given in the BRDF file. + std::filesystem::path mPath; ///< Full path to the BRDF loaded. + std::string mBRDFName; ///< This is the file basename without extension. + std::string mBRDFDescription; ///< Description of the BRDF given in the BRDF file. - bool mBRDFUploaded = false; ///< True if BRDF data buffers have been uploaded to the material system. - RGLMaterialData mData; ///< Material parameters. - ref mpThetaBuf; - ref mpPhiBuf; - ref mpSigmaBuf; - ref mpNDFBuf; - ref mpVNDFBuf; - ref mpLumiBuf; - ref mpRGBBuf; - ref mpVNDFMarginalBuf; - ref mpLumiMarginalBuf; - ref mpVNDFConditionalBuf; - ref mpLumiConditionalBuf; - ref mpAlbedoLUT; ///< Precomputed albedo lookup table. - ref mpSampler; ///< Sampler for accessing BRDF textures. + bool mBRDFUploaded = false; ///< True if BRDF data buffers have been uploaded to the material system. + RGLMaterialData mData; ///< Material parameters. + ref mpThetaBuf; + ref mpPhiBuf; + ref mpSigmaBuf; + ref mpNDFBuf; + ref mpVNDFBuf; + ref mpLumiBuf; + ref mpRGBBuf; + ref mpVNDFMarginalBuf; + ref mpLumiMarginalBuf; + ref mpVNDFConditionalBuf; + ref mpLumiConditionalBuf; + ref mpAlbedoLUT; ///< Precomputed albedo lookup table. + ref mpSampler; ///< Sampler for accessing BRDF textures. - ref mBRDFTesting; - }; -} + ref mBRDFTesting; +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/RGLMaterialData.slang b/Source/Falcor/Scene/Material/RGLMaterialData.slang index a94e40841b..a52a5e114b 100644 --- a/Source/Falcor/Scene/Material/RGLMaterialData.slang +++ b/Source/Falcor/Scene/Material/RGLMaterialData.slang @@ -37,29 +37,29 @@ __exported import Scene.Material.TextureHandle; BEGIN_NAMESPACE_FALCOR /** This is a host/device structure that describes a measured MERL material. -*/ + */ struct RGLMaterialData { // MaterialHeader (16B) is stored just before this struct in memory. - uint phiSize = 0; ///< Number of angular sampling points. + uint phiSize = 0; ///< Number of angular sampling points. uint thetaSize = 0; - uint2 sigmaSize = uint2(0); ///< Size of sigma/NDF lookup tables. These are 2D. + uint2 sigmaSize = uint2(0); ///< Size of sigma/NDF lookup tables. These are 2D. uint2 ndfSize = uint2(0); - uint2 vndfSize = uint2(0); ///< Size of VNDF/luminance lookup tables. These are 4D (phi/theta x vndf/lumi size). + uint2 vndfSize = uint2(0); ///< Size of VNDF/luminance lookup tables. These are 4D (phi/theta x vndf/lumi size). uint2 lumiSize = uint2(0); - uint thetaBufID = 0; ///< Inclinations that were sampled by the goniophotometer. - uint phiBufID = 0; ///< Azimuths that were sampled by the goniophotometer. This has size 1 for isotropic materials. - uint sigmaBufID = 0; ///< Projected microfacet area. - uint ndfBufID = 0; ///< Best matching normal distribution function (NDF) from retroreflective measurements. - uint vndfBufID = 0; ///< Corresponding distribution of visible normals (VNDF). - uint lumiBufID = 0; ///< Luminance of RGB buffer, normalized (so that it is a PDF). - uint rgbBufID = 0; ///< RGB measurements in VNDF space. - uint vndfMarginalBufID = 0; ///< Marginal and conditional CDFs of VNDF/luminance to make them samplable. + uint thetaBufID = 0; ///< Inclinations that were sampled by the goniophotometer. + uint phiBufID = 0; ///< Azimuths that were sampled by the goniophotometer. This has size 1 for isotropic materials. + uint sigmaBufID = 0; ///< Projected microfacet area. + uint ndfBufID = 0; ///< Best matching normal distribution function (NDF) from retroreflective measurements. + uint vndfBufID = 0; ///< Corresponding distribution of visible normals (VNDF). + uint lumiBufID = 0; ///< Luminance of RGB buffer, normalized (so that it is a PDF). + uint rgbBufID = 0; ///< RGB measurements in VNDF space. + uint vndfMarginalBufID = 0; ///< Marginal and conditional CDFs of VNDF/luminance to make them samplable. uint lumiMarginalBufID = 0; uint vndfConditionalBufID = 0; uint lumiConditionalBufID = 0; - uint samplerID = 0; ///< Texture sampler ID for LUT sampler. - TextureHandle texAlbedoLUT = {};///< Texture handle for albedo LUT. + uint samplerID = 0; ///< Texture sampler ID for LUT sampler. + TextureHandle texAlbedoLUT = {}; ///< Texture handle for albedo LUT. // Maximum resolution allowed for the various lookup tables. static constexpr uint kMaxResolution = 0xffff; diff --git a/Source/Falcor/Scene/Material/ShadingUtils.slang b/Source/Falcor/Scene/Material/ShadingUtils.slang index 46ff3eb75e..bb9ee0e38c 100644 --- a/Source/Falcor/Scene/Material/ShadingUtils.slang +++ b/Source/Falcor/Scene/Material/ShadingUtils.slang @@ -35,14 +35,14 @@ __exported import Utils.Math.ShadingFrame; #define FALCOR_BACKFACE_BLACK 1 /** Convert RGB to normal (unnormalized). -*/ + */ float3 rgbToNormal(float3 rgb) { return rgb * 2.f - 1.f; } /** Convert RG to normal (unnormalized). -*/ + */ float3 rgToNormal(float2 rg) { float3 n; @@ -60,14 +60,16 @@ float getMetallic(float3 diffuse, float3 spec) { // This is based on the way that UE4 and Substance Painter 2 converts base+metallic+specular level to diffuse/spec colors // We don't have the specular level information, so the assumption is that it is equal to 0.5 (based on the UE4 documentation) - // Note that I'm using the luminance here instead of the actual colors. The reason is that there's no guaraentee that all RGB channels will end up with the same metallic value + // Note that I'm using the luminance here instead of the actual colors. The reason is that there's no guaraentee that all RGB channels + // will end up with the same metallic value float d = luminance(diffuse); float s = luminance(spec); - if (s == 0) return 0; + if (s == 0) + return 0; float a = 0.04; float b = s + d - 0.08; float c = 0.04 - s; - float root = sqrt(b*b - 0.16*c); + float root = sqrt(b * b - 0.16 * c); float m = (root - b) * 12.5; return m; } @@ -155,17 +157,20 @@ void adjustShadingNormal(const ShadingData sd, inout ShadingFrame sf) bool isValidHemisphereReflection(const ShadingData sd, const ShadingFrame sf, const float3 wiLocal, const float3 woLocal, const float3 wo) { // Check that wi/wo are in the upper hemisphere around the shading normal. - if (min(wiLocal.z, woLocal.z) < kMinCosTheta) return false; + if (min(wiLocal.z, woLocal.z) < kMinCosTheta) + return false; // Check that wi/wo are on the same geometric side. bool wiTop = sd.frontFacing; // The flag is computed dot(wi, faceN) >= 0.f. bool woTop = dot(wo, sd.faceN) >= 0.f; - if (wiTop != woTop) return false; + if (wiTop != woTop) + return false; #if FALCOR_BACKFACE_BLACK // Additionally check that we're on the same geometric side as the shading normal. bool shadingTop = dot(sf.N, sd.faceN) >= 0.f; - if (wiTop != shadingTop) return false; + if (wiTop != shadingTop) + return false; #endif return true; @@ -184,17 +189,20 @@ bool isValidHemisphereReflection(const ShadingData sd, const ShadingFrame sf, co bool isValidHemisphereTransmission(const ShadingData sd, const ShadingFrame sf, const float3 wiLocal, const float3 woLocal, const float3 wo) { // Check that wi/wo are in the upper/lower hemisphere around the shading normal. - if (min(wiLocal.z, -woLocal.z) < kMinCosTheta) return false; + if (min(wiLocal.z, -woLocal.z) < kMinCosTheta) + return false; // Check that wi/wo are on opposite geometric sides. bool wiTop = sd.frontFacing; // The flag is computed dot(wi, faceN) >= 0.f. bool woTop = dot(wo, sd.faceN) >= 0.f; - if (wiTop == woTop) return false; + if (wiTop == woTop) + return false; #if FALCOR_BACKFACE_BLACK // Additionally check that we're on the same geometric side as the shading normal. bool shadingTop = dot(sf.N, sd.faceN) >= 0.f; - if (wiTop != shadingTop) return false; + if (wiTop != shadingTop) + return false; #endif return true; @@ -211,13 +219,21 @@ bool isValidHemisphereTransmission(const ShadingData sd, const ShadingFrame sf, \param[in] fullSphere If true allow the full sphere of directions, otherwise wi in the lower hemisphere is rejected and back-faces are black. \return True if wi/wo represent valid vectors for reflection or transmission. */ -bool isValidHemisphereReflectionOrTransmission(const ShadingData sd, const ShadingFrame sf, const float3 wiLocal, const float3 woLocal, const float3 wo, const bool fullSphere = false) +bool isValidHemisphereReflectionOrTransmission( + const ShadingData sd, + const ShadingFrame sf, + const float3 wiLocal, + const float3 woLocal, + const float3 wo, + const bool fullSphere = false +) { const bool isTransmission = wiLocal.z * woLocal.z < 0.f; // Check that wi/wo are in the upper/lower hemisphere around the shading normal. float cosThetaI = fullSphere ? abs(wiLocal.z) : wiLocal.z; - if (min(cosThetaI, abs(woLocal.z)) < kMinCosTheta) return false; + if (min(cosThetaI, abs(woLocal.z)) < kMinCosTheta) + return false; // Check that wi/wo are on the same geometric side. bool wiTop = sd.frontFacing; // The flag is computed dot(wi, faceN) >= 0.f. @@ -225,11 +241,13 @@ bool isValidHemisphereReflectionOrTransmission(const ShadingData sd, const Shadi if (isTransmission) { - if (wiTop == woTop) return false; + if (wiTop == woTop) + return false; } else // reflection { - if (wiTop != woTop) return false; + if (wiTop != woTop) + return false; } #if FALCOR_BACKFACE_BLACK @@ -237,7 +255,8 @@ bool isValidHemisphereReflectionOrTransmission(const ShadingData sd, const Shadi { // Additionally check that we're on the same geometric side as the shading normal. bool shadingTop = dot(sf.N, sd.faceN) >= 0.f; - if (wiTop != shadingTop) return false; + if (wiTop != shadingTop) + return false; } #endif diff --git a/Source/Falcor/Scene/Material/StandardMaterial.cpp b/Source/Falcor/Scene/Material/StandardMaterial.cpp index 8303583dbf..3e338069ec 100644 --- a/Source/Falcor/Scene/Material/StandardMaterial.cpp +++ b/Source/Falcor/Scene/Material/StandardMaterial.cpp @@ -33,242 +33,257 @@ namespace Falcor { - namespace - { - const char kShaderFile[] = "Rendering/Materials/StandardMaterial.slang"; - } +namespace +{ +const char kShaderFile[] = "Rendering/Materials/StandardMaterial.slang"; +} + +StandardMaterial::StandardMaterial(ref pDevice, const std::string& name, ShadingModel shadingModel) + : BasicMaterial(pDevice, name, MaterialType::Standard) +{ + setShadingModel(shadingModel); + + // Setup additional texture slots. + bool specGloss = getShadingModel() == ShadingModel::SpecGloss; + mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = {specGloss ? "diffuse" : "baseColor", TextureChannelFlags::RGBA, true}; + mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = + specGloss ? TextureSlotInfo{"specular", TextureChannelFlags::RGBA, true} + : TextureSlotInfo{"spec", TextureChannelFlags::Green | TextureChannelFlags::Blue, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = {"normal", TextureChannelFlags::RGB, false}; + mTextureSlotInfo[(uint32_t)TextureSlot::Emissive] = {"emissive", TextureChannelFlags::RGB, true}; + mTextureSlotInfo[(uint32_t)TextureSlot::Transmission] = {"transmission", TextureChannelFlags::RGB, true}; +} + +bool StandardMaterial::renderUI(Gui::Widgets& widget) +{ + widget.text("Shading model: " + to_string(getShadingModel())); - StandardMaterial::StandardMaterial(ref pDevice, const std::string& name, ShadingModel shadingModel) - : BasicMaterial(pDevice, name, MaterialType::Standard) + // Render the base class UI first. + bool changed = BasicMaterial::renderUI(widget); + + // We're re-using the material's update flags here to track changes. + // Cache the previous flag so we can restore it before returning. + UpdateFlags prevUpdates = mUpdates; + mUpdates = UpdateFlags::None; + + if (auto pTexture = getEmissiveTexture()) { - setShadingModel(shadingModel); - - // Setup additional texture slots. - bool specGloss = getShadingModel() == ShadingModel::SpecGloss; - mTextureSlotInfo[(uint32_t)TextureSlot::BaseColor] = { specGloss ? "diffuse" : "baseColor", TextureChannelFlags::RGBA, true }; - mTextureSlotInfo[(uint32_t)TextureSlot::Specular] = specGloss ? TextureSlotInfo{ "specular", TextureChannelFlags::RGBA, true } : TextureSlotInfo{ "spec", TextureChannelFlags::Green | TextureChannelFlags::Blue, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Normal] = { "normal", TextureChannelFlags::RGB, false }; - mTextureSlotInfo[(uint32_t)TextureSlot::Emissive] = { "emissive", TextureChannelFlags::RGB, true }; - mTextureSlotInfo[(uint32_t)TextureSlot::Transmission] = { "transmission", TextureChannelFlags::RGB, true }; + widget.text("Emissive color: " + pTexture->getSourcePath().string()); + widget.text( + "Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + + to_string(pTexture->getFormat()) + ")" + ); + widget.image("Emissive color", pTexture.get(), float2(100.f)); + if (widget.button("Remove texture##Emissive")) + setEmissiveTexture(nullptr); } - - bool StandardMaterial::renderUI(Gui::Widgets& widget) + else { - widget.text("Shading model: " + to_string(getShadingModel())); - - // Render the base class UI first. - bool changed = BasicMaterial::renderUI(widget); - - // We're re-using the material's update flags here to track changes. - // Cache the previous flag so we can restore it before returning. - UpdateFlags prevUpdates = mUpdates; - mUpdates = UpdateFlags::None; - - if (auto pTexture = getEmissiveTexture()) - { - widget.text("Emissive color: " + pTexture->getSourcePath().string()); - widget.text("Texture info: " + std::to_string(pTexture->getWidth()) + "x" + std::to_string(pTexture->getHeight()) + " (" + to_string(pTexture->getFormat()) + ")"); - widget.image("Emissive color", pTexture.get(), float2(100.f)); - if (widget.button("Remove texture##Emissive")) setEmissiveTexture(nullptr); - } - else - { - float3 emissiveColor = getEmissiveColor(); - if (widget.var("Emissive color", emissiveColor, 0.f, 1.f, 0.01f)) setEmissiveColor(emissiveColor); - } - - float emissiveFactor = getEmissiveFactor(); - if (widget.var("Emissive factor", emissiveFactor, 0.f, std::numeric_limits::max(), 0.01f)) setEmissiveFactor(emissiveFactor); - - bool hasEntryPointVolumeProperties = getHasEntryPointVolumeProperties(); - if (widget.checkbox("Textured absorption coefficient", hasEntryPointVolumeProperties)) setHasEntryPointVolumeProperties(hasEntryPointVolumeProperties); - - // Restore update flags. - changed |= mUpdates != UpdateFlags::None; - markUpdates(prevUpdates | mUpdates); - - return changed; + float3 emissiveColor = getEmissiveColor(); + if (widget.var("Emissive color", emissiveColor, 0.f, 1.f, 0.01f)) + setEmissiveColor(emissiveColor); } - void StandardMaterial::updateDeltaSpecularFlag() + float emissiveFactor = getEmissiveFactor(); + if (widget.var("Emissive factor", emissiveFactor, 0.f, std::numeric_limits::max(), 0.01f)) + setEmissiveFactor(emissiveFactor); + + bool hasEntryPointVolumeProperties = getHasEntryPointVolumeProperties(); + if (widget.checkbox("Textured absorption coefficient", hasEntryPointVolumeProperties)) + setHasEntryPointVolumeProperties(hasEntryPointVolumeProperties); + + // Restore update flags. + changed |= mUpdates != UpdateFlags::None; + markUpdates(prevUpdates | mUpdates); + + return changed; +} + +void StandardMaterial::updateDeltaSpecularFlag() +{ + // Check if material has no diffuse lobe. + bool isNonDiffuse = + !hasTextureSlotData(TextureSlot::BaseColor) && all(getBaseColor().xyz() == float3(0.f)) && getDiffuseTransmission() == 0.f; + + // Check if material is fully specular transmissive. + bool isFullyTransmissive = getSpecularTransmission() >= 1.f; + + // Check if material only has delta reflection/transmission. + bool isDelta = false; + if (getShadingModel() == ShadingModel::MetalRough && !hasTextureSlotData(TextureSlot::Specular)) { - // Check if material has no diffuse lobe. - bool isNonDiffuse = !hasTextureSlotData(TextureSlot::BaseColor) && all(getBaseColor().xyz() == float3(0.f)) && getDiffuseTransmission() == 0.f; - - // Check if material is fully specular transmissive. - bool isFullyTransmissive = getSpecularTransmission() >= 1.f; - - // Check if material only has delta reflection/transmission. - bool isDelta = false; - if (getShadingModel() == ShadingModel::MetalRough && !hasTextureSlotData(TextureSlot::Specular)) - { - isDelta = getSpecularParams().g == 0.f; // Green component stores roughness in MetalRough mode. - if (getSpecularParams().b >= 1.f) isNonDiffuse = true; // Blue component stores metallic in MetalRough mode. If 1.0 there is no diffuse lobe. - } - - bool isDeltaSpecular = isDelta && (isNonDiffuse || isFullyTransmissive); - - if (mHeader.isDeltaSpecular() != isDeltaSpecular) - { - mHeader.setDeltaSpecular(isDeltaSpecular); - markUpdates(UpdateFlags::DataChanged); - } + isDelta = getSpecularParams().g == 0.f; // Green component stores roughness in MetalRough mode. + if (getSpecularParams().b >= 1.f) + isNonDiffuse = true; // Blue component stores metallic in MetalRough mode. If 1.0 there is no diffuse lobe. } - void StandardMaterial::renderSpecularUI(Gui::Widgets& widget) + bool isDeltaSpecular = isDelta && (isNonDiffuse || isFullyTransmissive); + + if (mHeader.isDeltaSpecular() != isDeltaSpecular) { - if (getShadingModel() == ShadingModel::MetalRough) - { - float roughness = getRoughness(); - if (widget.var("Roughness", roughness, 0.f, 1.f, 0.01f)) setRoughness(roughness); - - float metallic = getMetallic(); - if (widget.var("Metallic", metallic, 0.f, 1.f, 0.01f)) setMetallic(metallic); - } + mHeader.setDeltaSpecular(isDeltaSpecular); + markUpdates(UpdateFlags::DataChanged); } +} - ProgramDesc::ShaderModuleList StandardMaterial::getShaderModules() const +void StandardMaterial::renderSpecularUI(Gui::Widgets& widget) +{ + if (getShadingModel() == ShadingModel::MetalRough) { - return { ProgramDesc::ShaderModule::fromFile(kShaderFile) }; + float roughness = getRoughness(); + if (widget.var("Roughness", roughness, 0.f, 1.f, 0.01f)) + setRoughness(roughness); + + float metallic = getMetallic(); + if (widget.var("Metallic", metallic, 0.f, 1.f, 0.01f)) + setMetallic(metallic); } +} + +ProgramDesc::ShaderModuleList StandardMaterial::getShaderModules() const +{ + return {ProgramDesc::ShaderModule::fromFile(kShaderFile)}; +} + +TypeConformanceList StandardMaterial::getTypeConformances() const +{ + return {{{"StandardMaterial", "IMaterial"}, (uint32_t)MaterialType::Standard}}; +} + +void StandardMaterial::setShadingModel(ShadingModel model) +{ + FALCOR_CHECK(model == ShadingModel::MetalRough || model == ShadingModel::SpecGloss, "'model' must be MetalRough or SpecGloss"); - TypeConformanceList StandardMaterial::getTypeConformances() const + if (getShadingModel() != model) { - return { {{"StandardMaterial", "IMaterial"}, (uint32_t)MaterialType::Standard} }; + mData.setShadingModel(model); + markUpdates(UpdateFlags::DataChanged); + updateDeltaSpecularFlag(); } +} - void StandardMaterial::setShadingModel(ShadingModel model) +void StandardMaterial::setRoughness(float roughness) +{ + if (getShadingModel() != ShadingModel::MetalRough) { - FALCOR_CHECK(model == ShadingModel::MetalRough || model == ShadingModel::SpecGloss, "'model' must be MetalRough or SpecGloss"); - - if (getShadingModel() != model) - { - mData.setShadingModel(model); - markUpdates(UpdateFlags::DataChanged); - updateDeltaSpecularFlag(); - } + logWarning("Ignoring setRoughness(). Material '{}' does not use the metallic/roughness shading model.", mName); + return; } - void StandardMaterial::setRoughness(float roughness) + if (mData.specular[1] != (float16_t)roughness) { - if (getShadingModel() != ShadingModel::MetalRough) - { - logWarning("Ignoring setRoughness(). Material '{}' does not use the metallic/roughness shading model.", mName); - return; - } - - if (mData.specular[1] != (float16_t)roughness) - { - mData.specular[1] = (float16_t)roughness; - markUpdates(UpdateFlags::DataChanged); - updateDeltaSpecularFlag(); - } + mData.specular[1] = (float16_t)roughness; + markUpdates(UpdateFlags::DataChanged); + updateDeltaSpecularFlag(); } +} - void StandardMaterial::setMetallic(float metallic) +void StandardMaterial::setMetallic(float metallic) +{ + if (getShadingModel() != ShadingModel::MetalRough) { - if (getShadingModel() != ShadingModel::MetalRough) - { - logWarning("Ignoring setMetallic(). Material '{}' does not use the metallic/roughness shading model.", mName); - return; - } - - if (mData.specular[2] != (float16_t)metallic) - { - mData.specular[2] = (float16_t)metallic; - markUpdates(UpdateFlags::DataChanged); - updateDeltaSpecularFlag(); - } + logWarning("Ignoring setMetallic(). Material '{}' does not use the metallic/roughness shading model.", mName); + return; } - void StandardMaterial::setEmissiveColor(const float3& color) + if (mData.specular[2] != (float16_t)metallic) { - if (any(mData.emissive != color)) - { - mData.emissive = color; - markUpdates(UpdateFlags::DataChanged | UpdateFlags::EmissiveChanged); - updateEmissiveFlag(); - } + mData.specular[2] = (float16_t)metallic; + markUpdates(UpdateFlags::DataChanged); + updateDeltaSpecularFlag(); } +} - void StandardMaterial::setEmissiveFactor(float factor) +void StandardMaterial::setEmissiveColor(const float3& color) +{ + if (any(mData.emissive != color)) { - if (mData.emissiveFactor != factor) - { - mData.emissiveFactor = factor; - markUpdates(UpdateFlags::DataChanged | UpdateFlags::EmissiveChanged); - updateEmissiveFlag(); - } + mData.emissive = color; + markUpdates(UpdateFlags::DataChanged | UpdateFlags::EmissiveChanged); + updateEmissiveFlag(); } +} - void StandardMaterial::setHasEntryPointVolumeProperties(bool hasEntryPointVolumeProperties) +void StandardMaterial::setEmissiveFactor(float factor) +{ + if (mData.emissiveFactor != factor) { - if (mData.getHasEntryPointVolumeProperties() != hasEntryPointVolumeProperties) - { - mData.setHasEntryPointVolumeProperties(hasEntryPointVolumeProperties); - markUpdates(UpdateFlags::DataChanged); - } + mData.emissiveFactor = factor; + markUpdates(UpdateFlags::DataChanged | UpdateFlags::EmissiveChanged); + updateEmissiveFlag(); } +} - bool StandardMaterial::getHasEntryPointVolumeProperties() const +void StandardMaterial::setHasEntryPointVolumeProperties(bool hasEntryPointVolumeProperties) +{ + if (mData.getHasEntryPointVolumeProperties() != hasEntryPointVolumeProperties) { - return getShadingModel() == ShadingModel::SpecGloss ? false : mData.getHasEntryPointVolumeProperties(); + mData.setHasEntryPointVolumeProperties(hasEntryPointVolumeProperties); + markUpdates(UpdateFlags::DataChanged); } +} - DefineList StandardMaterial::getDefines() const - { - DefineList defines; +bool StandardMaterial::getHasEntryPointVolumeProperties() const +{ + return getShadingModel() == ShadingModel::SpecGloss ? false : mData.getHasEntryPointVolumeProperties(); +} - if (mData.getHasEntryPointVolumeProperties()) - defines.add("HAS_MATERIAL_VOLUME_PROPERITES", "1"); +DefineList StandardMaterial::getDefines() const +{ + DefineList defines; - return defines; - } + if (mData.getHasEntryPointVolumeProperties()) + defines.add("HAS_MATERIAL_VOLUME_PROPERITES", "1"); - const MaterialParamLayout& StandardMaterial::getParamLayout() const - { - FALCOR_CHECK(getShadingModel() == ShadingModel::MetalRough, "Only MetalRough shading model is supported in parameter layout."); - return StandardMaterialParamLayout::layout(); - } + return defines; +} - SerializedMaterialParams StandardMaterial::serializeParams() const - { - FALCOR_CHECK(getShadingModel() == ShadingModel::MetalRough, "Only MetalRough shading model is supported for serialization."); - return StandardMaterialParamLayout::serialize(this); - } +const MaterialParamLayout& StandardMaterial::getParamLayout() const +{ + FALCOR_CHECK(getShadingModel() == ShadingModel::MetalRough, "Only MetalRough shading model is supported in parameter layout."); + return StandardMaterialParamLayout::layout(); +} - void StandardMaterial::deserializeParams(const SerializedMaterialParams& params) - { - FALCOR_CHECK(getShadingModel() == ShadingModel::MetalRough, "Only MetalRough shading model is supported for serialization."); - StandardMaterialParamLayout::deserialize(this, params); - } +SerializedMaterialParams StandardMaterial::serializeParams() const +{ + FALCOR_CHECK(getShadingModel() == ShadingModel::MetalRough, "Only MetalRough shading model is supported for serialization."); + return StandardMaterialParamLayout::serialize(this); +} - FALCOR_SCRIPT_BINDING(StandardMaterial) - { - using namespace pybind11::literals; - - FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) - - pybind11::enum_ shadingModel(m, "ShadingModel"); - shadingModel.value("MetalRough", ShadingModel::MetalRough); - shadingModel.value("SpecGloss", ShadingModel::SpecGloss); - - pybind11::class_> material(m, "StandardMaterial"); - auto create = [] (const std::string& name, ShadingModel shadingModel) - { - return StandardMaterial::create(accessActivePythonSceneBuilder().getDevice(), name, shadingModel); - }; - material.def(pybind11::init(create), "name"_a = "", "model"_a = ShadingModel::MetalRough); // PYTHONDEPRECATED - - material.def_property("entryPointVolumeProperties", &StandardMaterial::getHasEntryPointVolumeProperties, &StandardMaterial::setHasEntryPointVolumeProperties); - material.def_property("roughness", &StandardMaterial::getRoughness, &StandardMaterial::setRoughness); - material.def_property("metallic", &StandardMaterial::getMetallic, &StandardMaterial::setMetallic); - material.def_property("emissiveColor", &StandardMaterial::getEmissiveColor, &StandardMaterial::setEmissiveColor); - material.def_property("emissiveFactor", &StandardMaterial::getEmissiveFactor, &StandardMaterial::setEmissiveFactor); - material.def_property_readonly("shadingModel", &StandardMaterial::getShadingModel); - - // Register alias Material -> StandardMaterial to allow deprecated script syntax. - // TODO: Remove workaround when all scripts have been updated to create StandardMaterial directly. - m.attr("Material") = m.attr("StandardMaterial"); // PYTHONDEPRECATED - } +void StandardMaterial::deserializeParams(const SerializedMaterialParams& params) +{ + FALCOR_CHECK(getShadingModel() == ShadingModel::MetalRough, "Only MetalRough shading model is supported for serialization."); + StandardMaterialParamLayout::deserialize(this, params); +} + +FALCOR_SCRIPT_BINDING(StandardMaterial) +{ + using namespace pybind11::literals; + + FALCOR_SCRIPT_BINDING_DEPENDENCY(BasicMaterial) + + pybind11::enum_ shadingModel(m, "ShadingModel"); + shadingModel.value("MetalRough", ShadingModel::MetalRough); + shadingModel.value("SpecGloss", ShadingModel::SpecGloss); + + pybind11::class_> material(m, "StandardMaterial"); + auto create = [](const std::string& name, ShadingModel shadingModel) + { return StandardMaterial::create(accessActivePythonSceneBuilderDevice(), name, shadingModel); }; + material.def(pybind11::init(create), "name"_a = "", "model"_a = ShadingModel::MetalRough); // PYTHONDEPRECATED + + material.def_property( + "entryPointVolumeProperties", + &StandardMaterial::getHasEntryPointVolumeProperties, + &StandardMaterial::setHasEntryPointVolumeProperties + ); + material.def_property("roughness", &StandardMaterial::getRoughness, &StandardMaterial::setRoughness); + material.def_property("metallic", &StandardMaterial::getMetallic, &StandardMaterial::setMetallic); + material.def_property("emissiveColor", &StandardMaterial::getEmissiveColor, &StandardMaterial::setEmissiveColor); + material.def_property("emissiveFactor", &StandardMaterial::getEmissiveFactor, &StandardMaterial::setEmissiveFactor); + material.def_property_readonly("shadingModel", &StandardMaterial::getShadingModel); + + // Register alias Material -> StandardMaterial to allow deprecated script syntax. + // TODO: Remove workaround when all scripts have been updated to create StandardMaterial directly. + m.attr("Material") = m.attr("StandardMaterial"); // PYTHONDEPRECATED } +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/StandardMaterial.h b/Source/Falcor/Scene/Material/StandardMaterial.h index 4f15733b95..d9fdef2798 100644 --- a/Source/Falcor/Scene/Material/StandardMaterial.h +++ b/Source/Falcor/Scene/Material/StandardMaterial.h @@ -30,120 +30,121 @@ namespace Falcor { - /** Class representing the standard material. - - Texture channel layout: - - ShadingModel::MetalRough - BaseColor - - RGB - Base Color - - A - Opacity - Specular - - R - Unused - - G - Roughness - - B - Metallic - - A - Unused - - ShadingModel::SpecGloss - BaseColor - - RGB - Diffuse Color - - A - Opacity - Specular - - RGB - Specular Color - - A - Gloss - - In all shading models: - - Normal - - 3-Channel standard normal map, or 2-Channel BC5 format - Emissive - - RGB - Emissive Color - - A - Unused - Transmission - - RGB - Transmission color - - A - Unused - - See additional texture channels defined in BasicMaterial. - */ - class FALCOR_API StandardMaterial : public BasicMaterial +/** Class representing the standard material. + + Texture channel layout: + + ShadingModel::MetalRough + BaseColor + - RGB - Base Color + - A - Opacity + Specular + - R - Unused + - G - Roughness + - B - Metallic + - A - Unused + + ShadingModel::SpecGloss + BaseColor + - RGB - Diffuse Color + - A - Opacity + Specular + - RGB - Specular Color + - A - Gloss + + In all shading models: + + Normal + - 3-Channel standard normal map, or 2-Channel BC5 format + Emissive + - RGB - Emissive Color + - A - Unused + Transmission + - RGB - Transmission color + - A - Unused + + See additional texture channels defined in BasicMaterial. +*/ +class FALCOR_API StandardMaterial : public BasicMaterial +{ + FALCOR_OBJECT(StandardMaterial) +public: + static ref create( + ref pDevice, + const std::string& name = "", + ShadingModel shadingModel = ShadingModel::MetalRough + ) { - FALCOR_OBJECT(StandardMaterial) - public: - static ref create(ref pDevice, const std::string& name = "", ShadingModel shadingModel = ShadingModel::MetalRough) - { - return make_ref(pDevice, name, shadingModel); - } - - StandardMaterial(ref pDevice, const std::string& name, ShadingModel shadingModel); - - /** Render the UI. - \return True if the material was modified. - */ - bool renderUI(Gui::Widgets& widget) override; - - ProgramDesc::ShaderModuleList getShaderModules() const override; - TypeConformanceList getTypeConformances() const override; - - /** Get the shading model. - */ - ShadingModel getShadingModel() const { return mData.getShadingModel(); } - - /** Set the roughness. - Only available for metallic/roughness shading model. - */ - void setRoughness(float roughness); - - /** Get the roughness. - Only available for metallic/roughness shading model. - */ - float getRoughness() const { return getShadingModel() == ShadingModel::MetalRough ? (float)mData.specular[1] : 0.f; } - - /** Set the metallic value. - Only available for metallic/roughness shading model. - */ - void setMetallic(float metallic); - - /** Get the metallic value. - Only available for metallic/roughness shading model. - */ - float getMetallic() const { return getShadingModel() == ShadingModel::MetalRough ? (float)mData.specular[2] : 0.f; } - - /** Set the emissive color. - */ - void setEmissiveColor(const float3& color) override; - - /** Set the emissive factor. - */ - void setEmissiveFactor(float factor); - - /** Get the emissive color. - */ - float3 getEmissiveColor() const { return mData.emissive; } - - /** Get the emissive factor. - */ - float getEmissiveFactor() const { return mData.emissiveFactor; } - - // DEMO21: The mesh will use the global IES profile (LightProfile) to modulate its emission - void setLightProfileEnabled( bool enabled ) - { - mHeader.setEnableLightProfile( enabled ); - } - - void setHasEntryPointVolumeProperties(bool hasEntryPointVolumeProperties); - - bool getHasEntryPointVolumeProperties() const; - - DefineList getDefines() const override; - - const MaterialParamLayout& getParamLayout() const override; - SerializedMaterialParams serializeParams() const override; - void deserializeParams(const SerializedMaterialParams& params) override; - - protected: - void updateDeltaSpecularFlag() override; - - void renderSpecularUI(Gui::Widgets& widget) override; - void setShadingModel(ShadingModel model); - }; -} + return make_ref(pDevice, name, shadingModel); + } + + StandardMaterial(ref pDevice, const std::string& name, ShadingModel shadingModel); + + /** Render the UI. + \return True if the material was modified. + */ + bool renderUI(Gui::Widgets& widget) override; + + ProgramDesc::ShaderModuleList getShaderModules() const override; + TypeConformanceList getTypeConformances() const override; + + /** Get the shading model. + */ + ShadingModel getShadingModel() const { return mData.getShadingModel(); } + + /** Set the roughness. + Only available for metallic/roughness shading model. + */ + void setRoughness(float roughness); + + /** Get the roughness. + Only available for metallic/roughness shading model. + */ + float getRoughness() const { return getShadingModel() == ShadingModel::MetalRough ? (float)mData.specular[1] : 0.f; } + + /** Set the metallic value. + Only available for metallic/roughness shading model. + */ + void setMetallic(float metallic); + + /** Get the metallic value. + Only available for metallic/roughness shading model. + */ + float getMetallic() const { return getShadingModel() == ShadingModel::MetalRough ? (float)mData.specular[2] : 0.f; } + + /** Set the emissive color. + */ + void setEmissiveColor(const float3& color) override; + + /** Set the emissive factor. + */ + void setEmissiveFactor(float factor); + + /** Get the emissive color. + */ + float3 getEmissiveColor() const { return mData.emissive; } + + /** Get the emissive factor. + */ + float getEmissiveFactor() const { return mData.emissiveFactor; } + + // DEMO21: The mesh will use the global IES profile (LightProfile) to modulate its emission + void setLightProfileEnabled(bool enabled) { mHeader.setEnableLightProfile(enabled); } + + void setHasEntryPointVolumeProperties(bool hasEntryPointVolumeProperties); + + bool getHasEntryPointVolumeProperties() const; + + DefineList getDefines() const override; + + const MaterialParamLayout& getParamLayout() const override; + SerializedMaterialParams serializeParams() const override; + void deserializeParams(const SerializedMaterialParams& params) override; + +protected: + void updateDeltaSpecularFlag() override; + + void renderSpecularUI(Gui::Widgets& widget) override; + void setShadingModel(ShadingModel model); +}; +} // namespace Falcor diff --git a/Source/Falcor/Scene/Material/TextureHandle.slang b/Source/Falcor/Scene/Material/TextureHandle.slang index 3355cbe0df..976d015ab4 100644 --- a/Source/Falcor/Scene/Material/TextureHandle.slang +++ b/Source/Falcor/Scene/Material/TextureHandle.slang @@ -66,37 +66,44 @@ struct TextureHandle #endif /** Set mode. - */ + */ SETTER_DECL void setMode(Mode mode) { packedData = PACK_BITS(kModeBits, kModeOffset, packedData, (uint)mode); } /** Get mode. - */ + */ Mode getMode() CONST_FUNCTION { return Mode(EXTRACT_BITS(kModeBits, kModeOffset, packedData)); } /** Set texture ID. This sets mode to Mode::Texture. */ - SETTER_DECL void setTextureID(uint texID) { setMode(Mode::Texture); packedData = PACK_BITS(kTextureIDBits, 0, packedData, texID); } + SETTER_DECL void setTextureID(uint texID) + { + setMode(Mode::Texture); + packedData = PACK_BITS(kTextureIDBits, 0, packedData, texID); + } /** Get texture ID. This operation is only valid if mode is Mode::Texture. - */ + */ uint getTextureID() CONST_FUNCTION { return EXTRACT_BITS(kTextureIDBits, 0, packedData); } /** Set whether the texture uses udim or not. */ - SETTER_DECL void setUdimEnabled(bool udimEnabled) { packedData = PACK_BITS(kUdimEnabledBits, kUdimEnabledOffset, packedData, udimEnabled ? 1 : 0); } + SETTER_DECL void setUdimEnabled(bool udimEnabled) + { + packedData = PACK_BITS(kUdimEnabledBits, kUdimEnabledOffset, packedData, udimEnabled ? 1 : 0); + } /** Set whether the texture uses udim or not. */ bool getUdimEnabled() CONST_FUNCTION { return IS_BIT_SET(packedData, kUdimEnabledOffset); } #ifdef HOST_CODE - bool operator==(const TextureHandle& rhs) const { return packedData == rhs.packedData; } - bool operator!=(const TextureHandle& rhs) const { return packedData != rhs.packedData; } + bool operator ==(const TextureHandle& rhs) const { return packedData == rhs.packedData; } + bool operator !=(const TextureHandle& rhs) const { return packedData != rhs.packedData; } #endif }; /** Struct storing information about a material texture. -*/ + */ struct TextureInfo { uint width; diff --git a/Source/Falcor/Scene/Material/TextureSampler.slang b/Source/Falcor/Scene/Material/TextureSampler.slang index 8f85bcfeb6..7665c3ad0a 100644 --- a/Source/Falcor/Scene/Material/TextureSampler.slang +++ b/Source/Falcor/Scene/Material/TextureSampler.slang @@ -33,7 +33,7 @@ interface ITextureSampler { /** Sample from a 2D texture using the level of detail computed by this method - */ + */ float4 sampleTexture(Texture2D t, SamplerState s, float2 uv); }; @@ -48,31 +48,20 @@ interface ITextureSampler */ struct ImplicitLodTextureSampler : ITextureSampler { - __init() - { - } + __init() {} - float4 sampleTexture(Texture2D t, SamplerState s, float2 uv) - { - return t.Sample(s, uv); - } + float4 sampleTexture(Texture2D t, SamplerState s, float2 uv) { return t.Sample(s, uv); } }; /** Texture sampling using an explicit scalar level of detail. -*/ + */ struct ExplicitLodTextureSampler : ITextureSampler { float lod; ///< The explicit level of detail to use - __init(float lod) - { - this.lod = lod; - } + __init(float lod) { this.lod = lod; } - float4 sampleTexture(Texture2D t, SamplerState s, float2 uv) - { - return t.SampleLevel(s, uv, lod); - } + float4 sampleTexture(Texture2D t, SamplerState s, float2 uv) { return t.SampleLevel(s, uv, lod); } }; /** Texture sampling using an explicit scalar level of detail using ray cones (with texture dimensions @@ -80,12 +69,10 @@ struct ExplicitLodTextureSampler : ITextureSampler */ struct ExplicitRayConesLodTextureSampler : ITextureSampler { - float rayconesLODWithoutTexDims; ///< this is \Delta_t, which is texture independent, plus the rest of the terms, except the texture size, which is added below + float rayconesLODWithoutTexDims; ///< this is \Delta_t, which is texture independent, plus the rest of the terms, except the texture + ///< size, which is added below - __init(float rayconesLODWithoutTexDims) - { - this.rayconesLODWithoutTexDims = rayconesLODWithoutTexDims; - } + __init(float rayconesLODWithoutTexDims) { this.rayconesLODWithoutTexDims = rayconesLODWithoutTexDims; } float4 sampleTexture(Texture2D t, SamplerState s, float2 uv) { @@ -96,18 +83,21 @@ struct ExplicitRayConesLodTextureSampler : ITextureSampler } }; - /** Texture sampling using an explicit scalar, i.e., isotropic, level of detail using ray diffs, with the final LOD computations done below, since they are dependent on texture dimensions. Use ExplicitGradientTextureSampler if you want anisotropic filtering with ray diffs. */ struct ExplicitRayDiffsIsotropicTextureSampler : ITextureSampler { - enum class Mode { IsotropicOpenGLStyle, IsotropicPBRTStyle }; + enum class Mode + { + IsotropicOpenGLStyle, + IsotropicPBRTStyle + }; static const Mode kMode = Mode::IsotropicOpenGLStyle; - float2 dUVdx; ///< derivatives in x over uv - float2 dUVdy; ///< derivatives in y over uv + float2 dUVdx; ///< derivatives in x over uv + float2 dUVdy; ///< derivatives in y over uv __init(float2 dUVdx, float2 dUVdy) { @@ -123,20 +113,20 @@ struct ExplicitRayDiffsIsotropicTextureSampler : ITextureSampler switch (kMode) { case Mode::IsotropicOpenGLStyle: - { - // Sharper, but alias sometimes for sharp edges textures. - const float2 duvdx = dUVdx * dim.x; - const float2 duvdy = dUVdy * dim.y; - const float lambda = 0.5f * log2(max(dot(duvdx, duvdx), dot(duvdy, duvdy))); - return t.SampleLevel(s, uv, lambda); - } + { + // Sharper, but alias sometimes for sharp edges textures. + const float2 duvdx = dUVdx * dim.x; + const float2 duvdy = dUVdy * dim.y; + const float lambda = 0.5f * log2(max(dot(duvdx, duvdx), dot(duvdy, duvdy))); + return t.SampleLevel(s, uv, lambda); + } case Mode::IsotropicPBRTStyle: - { - // PBRT style (much blurrier, but never (?) aliases). - const float filterWidth = 2.f * max(dim.x * max(abs(dUVdx.x), abs(dUVdy.x)), dim.y * max(abs(dUVdx.y), abs(dUVdy.y))); - const float lambda = log2(filterWidth); - return t.SampleLevel(s, uv, lambda); - } + { + // PBRT style (much blurrier, but never (?) aliases). + const float filterWidth = 2.f * max(dim.x * max(abs(dUVdx.x), abs(dUVdy.x)), dim.y * max(abs(dUVdx.y), abs(dUVdy.y))); + const float lambda = log2(filterWidth); + return t.SampleLevel(s, uv, lambda); + } } return float4(0.f); @@ -144,7 +134,7 @@ struct ExplicitRayDiffsIsotropicTextureSampler : ITextureSampler }; /** Texture sampling using explicit screen-space gradients. -*/ + */ struct ExplicitGradientTextureSampler : ITextureSampler { float2 gradX; ///< Gradient of texture coordinate in the screen-space X direction @@ -156,10 +146,7 @@ struct ExplicitGradientTextureSampler : ITextureSampler this.gradY = gradY; } - float4 sampleTexture(Texture2D t, SamplerState s, float2 uv) - { - return t.SampleGrad(s, uv, gradX, gradY); - } + float4 sampleTexture(Texture2D t, SamplerState s, float2 uv) { return t.SampleGrad(s, uv, gradX, gradY); } }; /** Texture sampling using filtered importance sampling @@ -167,14 +154,13 @@ struct ExplicitGradientTextureSampler : ITextureSampler */ struct StochasticTextureSampler : ITextureSampler { - float2 gradX; ///< Gradient of texture coordinate in the screen-space X direction - float2 gradY; ///< Gradient of texture coordiante in teh screen-space Y direction - float2 uvJitter; ///< Stochastic uv jitter - float lodJitter; ///< Stochastic lod jitter - float maxAnisotropy; ///< Max anisotropy (ratio between major and minor ellipse axes) - - __init( - float2 gradX, float2 gradY, float2 uvJitter, float lodJitter, float maxAnisotropy=64) + float2 gradX; ///< Gradient of texture coordinate in the screen-space X direction + float2 gradY; ///< Gradient of texture coordiante in teh screen-space Y direction + float2 uvJitter; ///< Stochastic uv jitter + float lodJitter; ///< Stochastic lod jitter + float maxAnisotropy; ///< Max anisotropy (ratio between major and minor ellipse axes) + + __init(float2 gradX, float2 gradY, float2 uvJitter, float lodJitter, float maxAnisotropy = 64) { this.gradX = gradX; this.gradY = gradY; @@ -184,9 +170,9 @@ struct StochasticTextureSampler : ITextureSampler } float4 sampleTexture(Texture2D t, SamplerState s, float2 uv) - { + { uint2 dim; - t.GetDimensions(dim.x, dim.y); + t.GetDimensions(dim.x, dim.y); let dudx = dim.x * gradX.x; let dvdx = dim.y * gradX.y; let dudy = dim.x * gradY.x; @@ -203,18 +189,18 @@ struct StochasticTextureSampler : ITextureSampler float minAxisLength = length(minAxis); float maxAxisLength = length(maxAxis); - + if (minAxisLength > 0 && (minAxisLength * maxAnisotropy) < maxAxisLength) { let scale = maxAxisLength / (minAxisLength * maxAnisotropy); minAxisLength *= scale; - } + } // No need to clamp to min and max lod levels, as HW should do it for free. let lod = log2(minAxisLength) + lodJitter; uv = uv * dim + uvJitter; - uv = (floor(uv) + 0.5) / dim; + uv = (floor(uv) + 0.5) / dim; return t.SampleLevel(s, uv, lod); } diff --git a/Source/Falcor/Scene/Material/VolumeProperties.slang b/Source/Falcor/Scene/Material/VolumeProperties.slang index da89cefd2a..6ee0a08626 100644 --- a/Source/Falcor/Scene/Material/VolumeProperties.slang +++ b/Source/Falcor/Scene/Material/VolumeProperties.slang @@ -29,7 +29,7 @@ import Rendering.Volumes.IPhaseFunction; struct VolumeProperties { - float3 sigmaA; ///< Absorption coefficient.. or maybe cross section. - float3 sigmaS; ///< Scattering coefficient.. or maybe cross section. + float3 sigmaA; ///< Absorption coefficient.. or maybe cross section. + float3 sigmaS; ///< Scattering coefficient.. or maybe cross section. IPhaseFunction phaseFunction; ///< Set to NullPhaseFunction when invalid } diff --git a/Source/Falcor/Scene/RaytracingInline.slang b/Source/Falcor/Scene/RaytracingInline.slang index 5d56119598..de1d38cadf 100644 --- a/Source/Falcor/Scene/RaytracingInline.slang +++ b/Source/Falcor/Scene/RaytracingInline.slang @@ -83,9 +83,12 @@ TriangleHit getCandidateTriangleHit(RayQuery rayQuery) // TODO: Pass UseAlphaTest as a template argument. -HitInfo traceSceneRayImpl(const bool useAlphaTest, inout RayQuery rayQuery, const Ray ray, out float hitT, uint rayFlags, uint instanceInclusionMask) +HitInfo traceSceneRayImpl(const bool useAlphaTest, inout RayQuery rayQuery, const Ray ray, out float hitT, uint rayFlags, uint instanceInclusionMask, bool isPrevFrame) { - rayQuery.TraceRayInline(gScene.rtAccel, rayFlags, instanceInclusionMask, ray.toRayDesc()); + if (isPrevFrame) + rayQuery.TraceRayInline(gScene.rtAccelPrev, rayFlags, instanceInclusionMask, ray.toRayDesc()); + else + rayQuery.TraceRayInline(gScene.rtAccel, rayFlags, instanceInclusionMask, ray.toRayDesc()); CurveIntersector::Attribs curveCommittedAttribs; DisplacedTriangleMeshIntersector::Attribs displacedTriangleMeshCommittedAttribs; @@ -225,7 +228,7 @@ HitInfo traceSceneRayImpl(const bool useAlphaTest, inout RayQue } [__NoSideEffect] // Required for the performance of reverse-mode auto-diff. -HitInfo traceSceneRay(const Ray ray, out float hitT, uint rayFlags, uint instanceInclusionMask) +HitInfo traceSceneRay(const Ray ray, out float hitT, uint rayFlags, uint instanceInclusionMask, bool isPrevFrame) { // TODO: Use a constant expression to derive static ray flags. @@ -236,7 +239,7 @@ HitInfo traceSceneRay(const Ray ray, out float hitT, uin #else RayQuery rayQuery; #endif - return traceSceneRayImpl(true, rayQuery, ray, hitT, rayFlags, instanceInclusionMask); + return traceSceneRayImpl(true, rayQuery, ray, hitT, rayFlags, instanceInclusionMask, isPrevFrame); } else { @@ -245,14 +248,17 @@ HitInfo traceSceneRay(const Ray ray, out float hitT, uin #else RayQuery rayQuery; #endif - return traceSceneRayImpl(false, rayQuery, ray, hitT, rayFlags, instanceInclusionMask); + return traceSceneRayImpl(false, rayQuery, ray, hitT, rayFlags, instanceInclusionMask, isPrevFrame); } } [__NoSideEffect] // Required for the performance of reverse-mode auto-diff. -bool traceSceneVisibilityRayImpl(const bool useAlphaTest, inout RayQuery rayQuery, const Ray ray, uint rayFlags, uint instanceInclusionMask) +bool traceSceneVisibilityRayImpl(const bool useAlphaTest, inout RayQuery rayQuery, const Ray ray, uint rayFlags, uint instanceInclusionMask, bool isPrevFrame) { - rayQuery.TraceRayInline(gScene.rtAccel, rayFlags, instanceInclusionMask, ray.toRayDesc()); + if (isPrevFrame) + rayQuery.TraceRayInline(gScene.rtAccelPrev, rayFlags, instanceInclusionMask, ray.toRayDesc()); + else + rayQuery.TraceRayInline(gScene.rtAccel, rayFlags, instanceInclusionMask, ray.toRayDesc()); while (rayQuery.Proceed()) { @@ -322,7 +328,7 @@ bool traceSceneVisibilityRayImpl(const bool useAlphaTest, inout return rayQuery.CommittedStatus() == COMMITTED_NOTHING; } -bool traceSceneVisibilityRay(const Ray ray, uint rayFlags, uint instanceInclusionMask) +bool traceSceneVisibilityRay(const Ray ray, uint rayFlags, uint instanceInclusionMask, bool isPrevFrame) { // TODO: Use a constant expression to derive static ray flags. @@ -333,7 +339,7 @@ bool traceSceneVisibilityRay(const Ray ray, uint rayFlag #else RayQuery rayQuery; #endif - return traceSceneVisibilityRayImpl(true, rayQuery, ray, rayFlags, instanceInclusionMask); + return traceSceneVisibilityRayImpl(true, rayQuery, ray, rayFlags, instanceInclusionMask, isPrevFrame); } else { @@ -342,7 +348,7 @@ bool traceSceneVisibilityRay(const Ray ray, uint rayFlag #else RayQuery rayQuery; #endif - return traceSceneVisibilityRayImpl(false, rayQuery, ray, rayFlags, instanceInclusionMask); + return traceSceneVisibilityRayImpl(false, rayQuery, ray, rayFlags, instanceInclusionMask, isPrevFrame); } } @@ -350,13 +356,13 @@ bool traceSceneVisibilityRay(const Ray ray, uint rayFlag */ struct SceneRayQuery : ISceneRayQuery { - HitInfo traceRay(const Ray ray, out float hitT, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff) + HitInfo traceRay(const Ray ray, out float hitT, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff, bool isPrevFrame=false) { - return traceSceneRay(ray, hitT, rayFlags, instanceInclusionMask); + return traceSceneRay(ray, hitT, rayFlags, instanceInclusionMask, isPrevFrame); } - bool traceVisibilityRay(const Ray ray, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff) + bool traceVisibilityRay(const Ray ray, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff, bool isPrevFrame = false) { - return traceSceneVisibilityRay(ray, rayFlags, instanceInclusionMask); + return traceSceneVisibilityRay(ray, rayFlags, instanceInclusionMask, isPrevFrame); } } diff --git a/Source/Falcor/Scene/SDFs/SDFGrid.cpp b/Source/Falcor/Scene/SDFs/SDFGrid.cpp index 2d7561a7d1..6b0981541b 100644 --- a/Source/Falcor/Scene/SDFs/SDFGrid.cpp +++ b/Source/Falcor/Scene/SDFs/SDFGrid.cpp @@ -480,14 +480,14 @@ namespace Falcor compressed = pybind11::cast(value); } } - return static_ref_cast(SDFSBS::create(accessActivePythonSceneBuilder().getDevice(), brickWidth, compressed, defaultGridWidth)); + return static_ref_cast(SDFSBS::create(accessActivePythonSceneBuilderDevice(), brickWidth, compressed, defaultGridWidth)); }; pybind11::class_> sdfGrid(m, "SDFGrid"); - sdfGrid.def_static("createNDGrid", [](float narrowBandThickness) { return static_ref_cast(NDSDFGrid::create(accessActivePythonSceneBuilder().getDevice(), narrowBandThickness)); }, "narrowBandThickness"_a); // PYTHONDEPRECATED - sdfGrid.def_static("createSVS", [](){ return static_ref_cast(SDFSVS::create(accessActivePythonSceneBuilder().getDevice())); }); // PYTHONDEPRECATED + sdfGrid.def_static("createNDGrid", [](float narrowBandThickness) { return static_ref_cast(NDSDFGrid::create(accessActivePythonSceneBuilderDevice(), narrowBandThickness)); }, "narrowBandThickness"_a); // PYTHONDEPRECATED + sdfGrid.def_static("createSVS", [](){ return static_ref_cast(SDFSVS::create(accessActivePythonSceneBuilderDevice())); }); // PYTHONDEPRECATED sdfGrid.def_static("createSBS", createSBS); // PYTHONDEPRECATED - sdfGrid.def_static("createSVO", [](){ return static_ref_cast(SDFSVO::create(accessActivePythonSceneBuilder().getDevice())); }); // PYTHONDEPRECATED + sdfGrid.def_static("createSVO", [](){ return static_ref_cast(SDFSVO::create(accessActivePythonSceneBuilderDevice())); }); // PYTHONDEPRECATED sdfGrid.def("loadValuesFromFile", [](SDFGrid& self, const std::filesystem::path& path) { return self.loadValuesFromFile(getActiveAssetResolver().resolvePath(path)); }, "path"_a diff --git a/Source/Falcor/Scene/SDFs/SDFGridHitData.slang b/Source/Falcor/Scene/SDFs/SDFGridHitData.slang index 08047101e8..ffcf5cc079 100644 --- a/Source/Falcor/Scene/SDFs/SDFGridHitData.slang +++ b/Source/Falcor/Scene/SDFs/SDFGridHitData.slang @@ -33,12 +33,12 @@ struct SDFGridHitData { #if SCENE_SDF_GRID_IMPLEMENTATION == SCENE_SDF_GRID_IMPLEMENTATION_NDSDF - uint lod; + uint lod = 0; #elif SCENE_SDF_GRID_IMPLEMENTATION == SCENE_SDF_GRID_IMPLEMENTATION_SVS || SCENE_SDF_GRID_IMPLEMENTATION == SCENE_SDF_GRID_IMPLEMENTATION_SBS - uint primitiveID; + uint primitiveID = 0; #elif SCENE_SDF_GRID_IMPLEMENTATION == SCENE_SDF_GRID_IMPLEMENTATION_SVO - uint svoIndex; + uint svoIndex = 0; #endif - float hitT; + float hitT = 0.f; }; diff --git a/Source/Falcor/Scene/Scene.cpp b/Source/Falcor/Scene/Scene.cpp index da5d4f2bcb..9ce2af4b42 100644 --- a/Source/Falcor/Scene/Scene.cpp +++ b/Source/Falcor/Scene/Scene.cpp @@ -40,7 +40,6 @@ #include "Core/API/RenderContext.h" #include "Core/API/IndirectCommands.h" #include "Utils/StringUtils.h" -#include "Utils/ObjectIDPython.h" #include "Utils/Math/Common.h" #include "Utils/Math/MathHelpers.h" #include "Utils/Math/Vector.h" @@ -360,6 +359,9 @@ namespace Falcor mSceneStats.emissiveMemoryInBytes = mpLightCollection->getMemoryUsageInBytes(); } + else + mpLightCollection->updateTriangleDataShaderBinding(mpSceneBlock->getRootVar()["lightCollection"]); + return mpLightCollection; } @@ -1233,8 +1235,8 @@ namespace Falcor // Perform setup that affects the scene defines. initSDFGrids(); - mHitInfo.init(*this, mUseCompressedHitInfo); updateGeometryTypes(); + mHitInfo.init(*this, mUseCompressedHitInfo); // Prepare the materials. // This sets up defines and materials parameter block, which are needed for creating the scene parameter block. @@ -1733,8 +1735,8 @@ namespace Falcor if (envMapChanges != EnvMap::Changes::None || mEnvMapChanged || forceUpdate) { if (envMapChanges != EnvMap::Changes::None) flags |= IScene::UpdateFlags::EnvMapPropertiesChanged; - mpEnvMap->bindShaderData(mpSceneBlock->getRootVar()[kEnvMap]); } + mpEnvMap->bindShaderData(mpSceneBlock->getRootVar()[kEnvMap]); // also binds prev frame data } mSceneStats.envMapMemoryInBytes = mpEnvMap ? mpEnvMap->getMemoryUsageInBytes() : 0; @@ -1841,6 +1843,8 @@ namespace Falcor { mUpdates = IScene::UpdateFlags::None; + initializePrevTlasCache(pRenderContext); + // Perform updates that may affect the scene defines. updateGeometryTypes(); mUpdates |= updateMaterials(false); @@ -3705,6 +3709,40 @@ namespace Falcor } } + void Scene::initializePrevTlasCache(RenderContext* pRenderContext) + { + for (auto& tlas : mTlasCache) + { + if (mPrevTlasCache.find(tlas.first) == mPrevTlasCache.end()) + { + mPrevTlasCache[tlas.first] = tlas.second; + + // create new buffer and accel + size_t resultDataMaxSize = tlas.second.pTlasBuffer->getSize(); + mPrevTlasCache[tlas.first].pTlasBuffer = + mpDevice->createBuffer(resultDataMaxSize, ResourceBindFlags::AccelerationStructure, MemoryType::DeviceLocal); + + RtAccelerationStructure::Desc asCreateDesc = {}; + asCreateDesc.setKind(RtAccelerationStructureKind::TopLevel); + asCreateDesc.setBuffer(mPrevTlasCache[tlas.first].pTlasBuffer, 0, mTlasPrebuildInfo.resultDataMaxSize); + mPrevTlasCache[tlas.first].pTlasObject = RtAccelerationStructure::create(mpDevice, asCreateDesc); + + // copy previous frame accel + pRenderContext->copyAccelerationStructure(mPrevTlasCache[tlas.first].pTlasObject.get(), tlas.second.pTlasObject.get(), RenderContext::RtAccelerationStructureCopyMode::Clone); + } + else + { + if (mpAnimationController->isEnabled()) + { + mPrevTlasCache[tlas.first].updateMode = tlas.second.updateMode; + // swap the buffers and accels, the buffer in tlasCache will be written if scene changes + std::swap(mPrevTlasCache[tlas.first].pTlasObject, tlas.second.pTlasObject); + std::swap(mPrevTlasCache[tlas.first].pTlasBuffer, tlas.second.pTlasBuffer); + } + } + } + } + void Scene::invalidateTlasCache() { for (auto& tlas : mTlasCache) @@ -3847,6 +3885,12 @@ namespace Falcor FALCOR_ASSERT(tlasIt != mTlasCache.end() && tlasIt->second.pTlasObject) mpSceneBlock->getRootVar()["rtAccel"].setAccelerationStructure(tlasIt->second.pTlasObject); + auto tlasItPrev = mPrevTlasCache.find(rayTypeCount); + if (tlasItPrev != mPrevTlasCache.end() && tlasItPrev->second.pTlasObject && mpAnimationController->isEnabled()) + mpSceneBlock->getRootVar()["rtAccelPrev"].setAccelerationStructure(tlasItPrev->second.pTlasObject); + else // use the current frame tlas instead + mpSceneBlock->getRootVar()["rtAccelPrev"].setAccelerationStructure(tlasIt->second.pTlasObject); + // Bind Scene parameter block. getCamera()->bindShaderData(mpSceneBlock->getRootVar()[kCamera]); // TODO REMOVE: Shouldn't be needed anymore? sceneVar = mpSceneBlock; @@ -4277,6 +4321,18 @@ namespace Falcor { using namespace pybind11::literals; + FALCOR_SCRIPT_BINDING_DEPENDENCY(NodeID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(MeshID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(CurveID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(CurveOrMeshID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(SdfDescID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(SdfGridID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(MaterialID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(LightID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(CameraID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(VolumeID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(GlobalGeometryID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) FALCOR_SCRIPT_BINDING_DEPENDENCY(Rectangle) FALCOR_SCRIPT_BINDING_DEPENDENCY(Light) @@ -4346,9 +4402,12 @@ namespace Falcor // Materials scene.def_property_readonly(kMaterials.c_str(), &Scene::getMaterials); - scene.def(kGetMaterial.c_str(), &Scene::getMaterial, "index"_a); // PYTHONDEPRECATED + //scene.def(kGetMaterial.c_str(), &Scene::getMaterial, "index"_a); // PYTHONDEPRECATED scene.def(kGetMaterial.c_str(), &Scene::getMaterialByName, "name"_a); // PYTHONDEPRECATED - scene.def("get_material", &Scene::getMaterial, "index"_a); + //scene.def("get_material", &Scene::getMaterial, "index"_a); + // TODO: MaterialID has no conversion from integer ID in python, adding integer ID accessors. Remove this WAR when MaterialID is fixed. + scene.def(kGetMaterial.c_str(), [](const Scene* pScene, uint32_t index) { return pScene->getMaterial(MaterialID{ index }); }, "index"_a); // PYTHONDEPRECATED + scene.def("get_material", [](const Scene* pScene, uint32_t index) { return pScene->getMaterial(MaterialID{ index }); }, "index"_a); // PYTHONDEPRECATED scene.def("get_material", &Scene::getMaterialByName, "name"_a); scene.def("addMaterial", &Scene::addMaterial, "material"_a); scene.def("getGeometryIDsForMaterial", [](const Scene* scene, const ref& pMaterial) diff --git a/Source/Falcor/Scene/Scene.h b/Source/Falcor/Scene/Scene.h index e52b94307a..15ecd9f014 100644 --- a/Source/Falcor/Scene/Scene.h +++ b/Source/Falcor/Scene/Scene.h @@ -465,7 +465,7 @@ namespace Falcor /** Returns true if there are active analytic lights and they should be used for lighting. */ - bool useAnalyticLights() const; + bool useAnalyticLights() const override; /** Returns true if there are active emissive lights and they should be used for lighting. */ @@ -863,7 +863,7 @@ namespace Falcor /** Get a list of all active lights in the scene. */ - const std::vector>& getActiveAnalyticLights() const { return mActiveLights; } + const std::vector>& getActiveAnalyticLights() const override { return mActiveLights; } /** Get the light collection representing all the mesh lights in the scene. The light collection is created lazily on the first call. It needs a render context. @@ -943,7 +943,7 @@ namespace Falcor /** Render the scene using raytracing. */ - void raytrace(RenderContext* pRenderContext, Program* pProgram, const ref& pVars, uint3 dispatchDims); + void raytrace(RenderContext* pRenderContext, Program* pProgram, const ref& pVars, uint3 dispatchDims) override; /** Render the UI. */ @@ -1041,7 +1041,7 @@ namespace Falcor \param[in] sceneVar Shader variable to set data into, usually the root var. \param[in] rayTypeCount Number of ray types in raygen program. Not needed for DXR 1.1. */ - void bindShaderDataForRaytracing(RenderContext* pRenderContext, const ShaderVar& sceneVar, uint32_t rayTypeCount = 0); + void bindShaderDataForRaytracing(RenderContext* pRenderContext, const ShaderVar& sceneVar, uint32_t rayTypeCount = 0) override; /** Get the name of the mesh with the given ID. */ @@ -1175,6 +1175,10 @@ namespace Falcor */ void buildTlas(RenderContext* pRenderContext, uint32_t rayTypeCount, bool perMeshHitEntry); + /** Initializes cache for previous TLAS. + */ + void initializePrevTlasCache(RenderContext* pRenderContext); + /** Invalidates the TLAS cache. */ void invalidateTlasCache(); @@ -1376,6 +1380,7 @@ namespace Falcor std::unordered_map mTlasCache; ///< Top Level Acceleration Structure for scene data cached per shader ray type count. ///< Number of ray types in program affects Shader Table indexing. + std::unordered_map mPrevTlasCache; /// stores the previous Tlas Cache. ref mpTlasScratch; ///< Scratch buffer used for TLAS builds. Can be shared as long as instance desc count is the same, which for now it is. RtAccelerationStructurePrebuildInfo mTlasPrebuildInfo; ///< This can be reused as long as the number of instance descs doesn't change. uint32_t mTlasLastBuiltRayCount = 0; ///< RayTypeCount of the last built TLAS, zero if there is none diff --git a/Source/Falcor/Scene/Scene.slang b/Source/Falcor/Scene/Scene.slang index bb0f4fa166..317a60a583 100644 --- a/Source/Falcor/Scene/Scene.slang +++ b/Source/Falcor/Scene/Scene.slang @@ -59,6 +59,7 @@ struct Scene { // Raytracing [root] RaytracingAccelerationStructure rtAccel; + [root] RaytracingAccelerationStructure rtAccelPrev; // Instance transforms [root] StructuredBuffer worldMatrices; @@ -483,13 +484,17 @@ struct Scene \param[out] Unpacked fetched vertices which can be used for further computations involving individual vertices. \return Interpolated vertex attributes. */ - VertexData getVertexData(const GeometryInstanceID instanceID, const uint triangleIndex, const float3 barycentrics, out StaticVertexData vertices[3]) + VertexData getVertexData(const GeometryInstanceID instanceID, const uint triangleIndex, const float3 barycentrics, out StaticVertexData vertices[3], bool isPrevFrame = false) { const uint3 vtxIndices = getIndices(instanceID, triangleIndex); vertices = { gScene.getVertex(vtxIndices[0]), gScene.getVertex(vtxIndices[1]), gScene.getVertex(vtxIndices[2]) }; - const float4x4 worldMat = gScene.getWorldMatrix(instanceID); - const float3x3 worldInvTransposeMat = getInverseTransposeWorldMatrix(instanceID); + float4x4 worldMat; + float3x3 worldInvTransposeMat; + if (isPrevFrame) worldMat = gScene.getPrevWorldMatrix(instanceID); + else worldMat = gScene.getWorldMatrix(instanceID); + if (isPrevFrame) worldInvTransposeMat = getPrevInverseTransposeWorldMatrix(instanceID); + else worldInvTransposeMat = getInverseTransposeWorldMatrix(instanceID); const bool isFrontFaceCW = isObjectFrontFaceCW(instanceID); return computeVertexData(vertices, worldMat, worldInvTransposeMat, isFrontFaceCW, barycentrics); } @@ -500,19 +505,19 @@ struct Scene \param[in] barycentrics Barycentric coordinates in the triangle. \return Interpolated vertex attributes. */ - VertexData getVertexData(const GeometryInstanceID instanceID, const uint triangleIndex, const float3 barycentrics) + VertexData getVertexData(const GeometryInstanceID instanceID, const uint triangleIndex, const float3 barycentrics, bool isPrevFrame = false) { StaticVertexData vertices[3]; - return getVertexData(instanceID, triangleIndex, barycentrics, vertices); + return getVertexData(instanceID, triangleIndex, barycentrics, vertices, isPrevFrame); } /** Returns the interpolated vertex attributes for a given hitpoint. \param[in] hit Triangle hit info. \return Interpolated vertex attributes. */ - VertexData getVertexData(const TriangleHit hit) + VertexData getVertexData(const TriangleHit hit, bool isPrevFrame = false) { - return getVertexData(hit.instanceID, hit.primitiveIndex, hit.getBarycentricWeights()); + return getVertexData(hit.instanceID, hit.primitiveIndex, hit.getBarycentricWeights(), isPrevFrame); } /** Returns the interpolated vertex attributes for a given hitpoint. @@ -520,22 +525,26 @@ struct Scene \param[out] Unpacked fetched vertices which can be used for further computations involving individual vertices. \return Interpolated vertex attributes. */ - VertexData getVertexData(const TriangleHit hit, out StaticVertexData vertices[3]) + VertexData getVertexData(const TriangleHit hit, out StaticVertexData vertices[3], bool isPrevFrame = false) { - return getVertexData(hit.instanceID, hit.primitiveIndex, hit.getBarycentricWeights(), vertices); + return getVertexData(hit.instanceID, hit.primitiveIndex, hit.getBarycentricWeights(), vertices, isPrevFrame); } /** Returns the interpolated vertex attributes for a given hitpoint. \param[in] hit Displaced triangle hit info. \return Interpolated vertex attributes. */ - VertexData getVertexData(const DisplacedTriangleHit hit, const float3 viewDir) + VertexData getVertexData(const DisplacedTriangleHit hit, const float3 viewDir, bool isPrevFrame = false) { const uint3 vtxIndices = getIndices(hit.instanceID, hit.primitiveIndex); const StaticVertexData vertices[3] = { gScene.getVertex(vtxIndices[0]), gScene.getVertex(vtxIndices[1]), gScene.getVertex(vtxIndices[2]) }; const float3 barycentrics = hit.getBarycentricWeights(); - const float4x4 worldMat = gScene.getWorldMatrix(hit.instanceID); - const float3x3 worldInvTransposeMat = getInverseTransposeWorldMatrix(hit.instanceID); + float4x4 worldMat; + float3x3 worldInvTransposeMat; + if (isPrevFrame) worldMat = gScene.getPrevWorldMatrix(hit.instanceID); + else worldMat = gScene.getWorldMatrix(hit.instanceID); + if (isPrevFrame) worldInvTransposeMat = getPrevInverseTransposeWorldMatrix(hit.instanceID); + else worldInvTransposeMat = getInverseTransposeWorldMatrix(hit.instanceID); const bool isFrontFaceCW = isObjectFrontFaceCW(hit.instanceID); VertexData v = computeVertexData(vertices, worldMat, worldInvTransposeMat, isFrontFaceCW, barycentrics); @@ -572,10 +581,10 @@ struct Scene \param[in] barycentrics Barycentric coordinates in the triangle. \return Interpolated vertex attributes. */ - VertexData getVertexDataRayCones(const GeometryInstanceID instanceID, const uint triangleIndex, const float3 barycentrics) + VertexData getVertexDataRayCones(const GeometryInstanceID instanceID, const uint triangleIndex, const float3 barycentrics, bool isPrevFrame = false) { StaticVertexData triangleVertices[3]; - VertexData v = getVertexData(instanceID, triangleIndex, barycentrics, triangleVertices); + VertexData v = getVertexData(instanceID, triangleIndex, barycentrics, triangleVertices, isPrevFrame); v.coneTexLODValue = computeRayConeTriangleLODValue(triangleVertices, float3x3(getWorldMatrix(instanceID))); return v; } diff --git a/Source/Falcor/Scene/SceneBuilder.cpp b/Source/Falcor/Scene/SceneBuilder.cpp index d904ef6d40..9ef793d6e7 100644 --- a/Source/Falcor/Scene/SceneBuilder.cpp +++ b/Source/Falcor/Scene/SceneBuilder.cpp @@ -36,7 +36,6 @@ #include "Utils/Timing/TimeReport.h" #include "Utils/Scripting/ScriptBindings.h" #include "Utils/Math/MathHelpers.h" -#include "Utils/ObjectIDPython.h" #include "Utils/NumericRange.h" #include #include @@ -2931,6 +2930,19 @@ namespace Falcor { using namespace pybind11::literals; + + FALCOR_SCRIPT_BINDING_DEPENDENCY(NodeID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(MeshID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(CurveID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(CurveOrMeshID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(SdfDescID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(SdfGridID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(MaterialID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(LightID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(CameraID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(VolumeID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(GlobalGeometryID) + FALCOR_SCRIPT_BINDING_DEPENDENCY(Scene) FALCOR_SCRIPT_BINDING_DEPENDENCY(TriangleMesh) FALCOR_SCRIPT_BINDING_DEPENDENCY(Material) @@ -2986,8 +2998,8 @@ namespace Falcor sceneBuilder.def("getMaterial", &SceneBuilder::getMaterial, "name"_a); sceneBuilder.def("loadMaterialTexture", &SceneBuilder::loadMaterialTexture, "material"_a, "slot"_a, "path"_a); sceneBuilder.def("waitForMaterialTextureLoading", &SceneBuilder::waitForMaterialTextureLoading); - sceneBuilder.def("addGridVolume", &SceneBuilder::addGridVolume, "gridVolume"_a, "nodeID"_a = NodeID::kInvalidID); - sceneBuilder.def("addVolume", &SceneBuilder::addGridVolume, "gridVolume"_a, "nodeID"_a = NodeID::kInvalidID); // PYTHONDEPRECATED + sceneBuilder.def("addGridVolume", &SceneBuilder::addGridVolume, "gridVolume"_a, "nodeID"_a = NodeID()); + sceneBuilder.def("addVolume", &SceneBuilder::addGridVolume, "gridVolume"_a, "nodeID"_a = NodeID()); // PYTHONDEPRECATED sceneBuilder.def("getGridVolume", &SceneBuilder::getGridVolume, "name"_a); sceneBuilder.def("getVolume", &SceneBuilder::getGridVolume, "name"_a); // PYTHONDEPRECATED sceneBuilder.def("addLight", &SceneBuilder::addLight, "light"_a); @@ -3003,8 +3015,17 @@ namespace Falcor node.transform = transform.getMatrix(); node.parent = parent; return pSceneBuilder->addNode(node); - }, "name"_a, "transform"_a = Transform(), "parent"_a = NodeID::kInvalidID); + }, "name"_a, "transform"_a = Transform(), "parent"_a = NodeID()); sceneBuilder.def("addMeshInstance", &SceneBuilder::addMeshInstance); + sceneBuilder.def( + "addMeshInstance", + [](SceneBuilder& builder, MeshID meshID, NodeID nodeID) + { + builder.addMeshInstance(nodeID, meshID); + }, + "meshID"_a, + "nodeID"_a + ); sceneBuilder.def("addSDFGridInstance", &SceneBuilder::addSDFGridInstance); sceneBuilder.def("addCustomPrimitive", &SceneBuilder::addCustomPrimitive); diff --git a/Source/Falcor/Scene/SceneIDsPython.cpp b/Source/Falcor/Scene/SceneIDsPython.cpp new file mode 100644 index 0000000000..6ba19b478f --- /dev/null +++ b/Source/Falcor/Scene/SceneIDsPython.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#pragma once + +#include "SceneIDs.h" +#include "Utils/Scripting/ScriptBindings.h" + +#include +#include + +namespace Falcor::scene1 +{ + +#define FALCOR_BIND_SCENEID(typename) \ + FALCOR_SCRIPT_BINDING(typename) \ + { \ + using namespace pybind11::literals; \ + namespace py = pybind11; \ + \ + pybind11::class_ id(m, #typename); \ + id.def(py::init<>()); \ + id.def("isValid", &typename ::isValid); \ + id.def("get", &typename ::get); \ + id.def("__str__", [](const typename& v) { return fmt::format("{}", v); }); \ + id.def("__repr__", [](const typename& v) { return fmt::format(#typename "({})", v); }); \ + } + + +FALCOR_BIND_SCENEID(NodeID) +FALCOR_BIND_SCENEID(MeshID) +FALCOR_BIND_SCENEID(CurveID) +FALCOR_BIND_SCENEID(CurveOrMeshID) +FALCOR_BIND_SCENEID(SdfDescID) +FALCOR_BIND_SCENEID(SdfGridID) +FALCOR_BIND_SCENEID(MaterialID) +FALCOR_BIND_SCENEID(LightID) +FALCOR_BIND_SCENEID(CameraID) +FALCOR_BIND_SCENEID(VolumeID) +FALCOR_BIND_SCENEID(GlobalGeometryID) + +#undef FALCOR_BIND_SCENEID + +} // namespace Falcor::scene1 diff --git a/Source/Falcor/Scene/SceneRayQueryInterface.slang b/Source/Falcor/Scene/SceneRayQueryInterface.slang index 700f4cb1ee..29ffa2e6ab 100644 --- a/Source/Falcor/Scene/SceneRayQueryInterface.slang +++ b/Source/Falcor/Scene/SceneRayQueryInterface.slang @@ -43,7 +43,7 @@ interface ISceneRayQuery \param[in] instanceInclusionMask Includes/rejects geometry based on instance mask. \return Returns Hit info, only valid when there was a hit. */ - HitInfo traceRay(const Ray ray, out float hitT, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff); + HitInfo traceRay(const Ray ray, out float hitT, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff, bool isPrevFrame = false); /** Trace a visibility ray against the scene. \param[in] ray Ray. @@ -51,5 +51,5 @@ interface ISceneRayQuery \param[in] instanceInclusionMask Includes/rejects geometry based on instance mask. \return Returns true if the ray endpoints are mutually visible (i.e. the ray does NOT intersect the scene). */ - bool traceVisibilityRay(const Ray ray, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff); + bool traceVisibilityRay(const Ray ray, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff, bool isPrevFrame = false); }; diff --git a/Source/Falcor/Scene/SceneTypes.slang b/Source/Falcor/Scene/SceneTypes.slang index 27f608de34..9cd57a2a11 100644 --- a/Source/Falcor/Scene/SceneTypes.slang +++ b/Source/Falcor/Scene/SceneTypes.slang @@ -48,9 +48,11 @@ BEGIN_NAMESPACE_FALCOR */ struct GeometryInstanceID { - uint index; ///< Global instance index. This is computed as InstanceID() + GeometryIndex(). + uint index = 0; ///< Global instance index. This is computed as InstanceID() + GeometryIndex(). #ifndef HOST_CODE + __init() {} + /** Construct a geometry instance ID. \param[in] instanceID The DXR InstanceID() system value. \param[in] geometryIndex The DXR GeometryIndex() system value. @@ -59,6 +61,11 @@ struct GeometryInstanceID { index = instanceID + geometryIndex; } + + __init(uint geometryInstaceID) + { + index = geometryInstaceID; + } #endif }; @@ -471,6 +478,7 @@ struct RWSplitVertexBuffer return data[bufferIndex][elementIndex]; } + [nonmutating] set { if (kBufferCount == 1) { diff --git a/Source/Falcor/Scene/ShadingData.slang b/Source/Falcor/Scene/ShadingData.slang index 1c8ee0864d..84e8bf3697 100644 --- a/Source/Falcor/Scene/ShadingData.slang +++ b/Source/Falcor/Scene/ShadingData.slang @@ -46,24 +46,24 @@ import Utils.Math.ShadingFrame; struct ShadingData : IDifferentiable { // Geometry data - float3 posW; ///< Shading hit position in world space. - float3 V; ///< View direction, -incident direction (-ray.dir) - float2 uv; ///< Texture mapping coordinates. + float3 posW = {}; ///< Shading hit position in world space. + float3 V = {}; ///< View direction, -incident direction (-ray.dir) + float2 uv = {}; ///< Texture mapping coordinates. - ShadingFrame frame; ///< Smooth interpolated shading frame in world space at the shading point. The normal is *not* automatically flipped for backfacing hits. - no_diff float3 faceN; ///< Face normal in world space, always on the front-facing side. - no_diff float4 tangentW; ///< Geometric tangent (xyz) and sign (w) in world space. This is used for orthogonalization. Not normalized, but it is guaranteed to be nonzero and sign (w) is +-1.0. - bool frontFacing; ///< True if primitive seen from the front-facing side, i.e., dot(V, faceN) >= 0.0. - no_diff float curveRadius; ///< Curve cross-sectional radius. Valid only for geometry generated from curves. + ShadingFrame frame = {}; ///< Smooth interpolated shading frame in world space at the shading point. The normal is *not* automatically flipped for backfacing hits. + no_diff float3 faceN = {}; ///< Face normal in world space, always on the front-facing side. + no_diff float4 tangentW = {}; ///< Geometric tangent (xyz) and sign (w) in world space. This is used for orthogonalization. Not normalized, but it is guaranteed to be nonzero and sign (w) is +-1.0. + bool frontFacing = {}; ///< True if primitive seen from the front-facing side, i.e., dot(V, faceN) >= 0.0. + no_diff float curveRadius = {}; ///< Curve cross-sectional radius. Valid only for geometry generated from curves. // Material data - MaterialHeader mtl; ///< Material header data. - uint materialID; ///< Material ID at shading location. - float IoR; ///< Index of refraction for the medium on the front-facing side (i.e. "outside" the material at the hit). + MaterialHeader mtl = {}; ///< Material header data. + uint materialID = {}; ///< Material ID at shading location. + float IoR = {}; ///< Index of refraction for the medium on the front-facing side (i.e. "outside" the material at the hit). - uint materialGradOffset; ///< Offset to the material gradient in the gradient buffer. - uint geometryGradOffset; ///< Offset to the geometry gradient in the gradient buffer. - uint threadID; ///< Thread ID for gradient aggregation with a hash grid. + uint materialGradOffset = {}; ///< Offset to the material gradient in the gradient buffer. + uint geometryGradOffset = {}; ///< Offset to the geometry gradient in the gradient buffer. + uint threadID = {}; ///< Thread ID for gradient aggregation with a hash grid. // Utility functions diff --git a/Source/Falcor/Scene/Volume/Grid.cpp b/Source/Falcor/Scene/Volume/Grid.cpp index 65579de08a..45ab432a0d 100644 --- a/Source/Falcor/Scene/Volume/Grid.cpp +++ b/Source/Falcor/Scene/Volume/Grid.cpp @@ -315,19 +315,19 @@ namespace Falcor auto createSphere = [] (float radius, float voxelSize, float blendRange) { - return Grid::createSphere(accessActivePythonSceneBuilder().getDevice(), radius, voxelSize, blendRange); + return Grid::createSphere(accessActivePythonSceneBuilderDevice(), radius, voxelSize, blendRange); }; grid.def_static("createSphere", createSphere, "radius"_a, "voxelSize"_a, "blendRange"_a = 3.f); // PYTHONDEPRECATED auto createBox = [] (float width, float height, float depth, float voxelSize, float blendRange) { - return Grid::createBox(accessActivePythonSceneBuilder().getDevice(), width, height, depth, voxelSize, blendRange); + return Grid::createBox(accessActivePythonSceneBuilderDevice(), width, height, depth, voxelSize, blendRange); }; grid.def_static("createBox", createBox, "width"_a, "height"_a, "depth"_a, "voxelSize"_a, "blendRange"_a = 3.f); // PYTHONDEPRECATED auto createFromFile = [] (const std::filesystem::path& path, const std::string& gridname) { - return Grid::createFromFile(accessActivePythonSceneBuilder().getDevice(), getActiveAssetResolver().resolvePath(path), gridname); + return Grid::createFromFile(accessActivePythonSceneBuilderDevice(), getActiveAssetResolver().resolvePath(path), gridname); }; grid.def_static("createFromFile", createFromFile, "path"_a, "gridname"_a); // PYTHONDEPRECATED } diff --git a/Source/Falcor/Scene/Volume/GridVolume.cpp b/Source/Falcor/Scene/Volume/GridVolume.cpp index 02936b44b0..228ee427d0 100644 --- a/Source/Falcor/Scene/Volume/GridVolume.cpp +++ b/Source/Falcor/Scene/Volume/GridVolume.cpp @@ -398,7 +398,7 @@ namespace Falcor volume.def_property("emissionTemperature", &GridVolume::getEmissionTemperature, &GridVolume::setEmissionTemperature); auto create = [] (const std::string& name) { - return GridVolume::create(accessActivePythonSceneBuilder().getDevice(), name); + return GridVolume::create(accessActivePythonSceneBuilderDevice(), name); }; volume.def(pybind11::init(create), "name"_a); // PYTHONDEPRECATED volume.def("loadGrid", diff --git a/Source/Falcor/Testing/UnitTest.cpp b/Source/Falcor/Testing/UnitTest.cpp index 2d7059c975..c0271892f8 100644 --- a/Source/Falcor/Testing/UnitTest.cpp +++ b/Source/Falcor/Testing/UnitTest.cpp @@ -110,6 +110,22 @@ class DevicePool mDevices[device->getType()].push_back(device); } + void primePool(const std::vector& tests) + { + std::set deviceTypes; + for (auto& test : tests) + { + if (!test.gpuFunc || test.deviceType == Device::Type::Default) + continue; + deviceTypes.insert(test.deviceType); + } + + if (deviceTypes.count(Device::Type::Vulkan) > 0) + releaseDevice(acquireDevice(Device::Type::Vulkan)); + if (deviceTypes.count(Device::Type::D3D12) > 0) + releaseDevice(acquireDevice(Device::Type::D3D12)); + } + private: Device::Desc mDefaultDesc; std::mutex mMutex; @@ -301,6 +317,7 @@ inline int32_t runTestsParallel(const RunOptions& options) // Gather tests. std::vector tests = enumerateTests(); tests = filterTests(tests, options.testSuiteFilter, options.testCaseFilter, options.tagFilter, options.deviceDesc.type); + devicePool.primePool(tests); std::vector results(tests.size()); @@ -393,6 +410,7 @@ inline int32_t runTestsSerial(const RunOptions& options) // Gather tests. std::vector tests = enumerateTests(); tests = filterTests(tests, options.testSuiteFilter, options.testCaseFilter, options.tagFilter, options.deviceDesc.type); + devicePool.primePool(tests); // Split tests into suites. std::map> suites; diff --git a/Source/Falcor/Testing/UnitTest.h b/Source/Falcor/Testing/UnitTest.h index 7370fbbcad..7317a8997e 100644 --- a/Source/Falcor/Testing/UnitTest.h +++ b/Source/Falcor/Testing/UnitTest.h @@ -621,17 +621,30 @@ using GPUUnitTestContext = unittest::GPUUnitTestContext; // clang-format on /** - * Macro definitions for the GPU unit testing framework. Note that they - * are all a single statement (including any additional << printed - * values). Thus, it's perfectly fine to write code like: + * Macro definitions for the GPU unit testing framework. + * All are in the form of + * if (!cond) + * generateMessages(...) * - * if (foo) // look, no braces - * EXPECT_EQ(x, y); + * As such, it shortcircuits both _MSG and << user inputs when the test passes, + * avoiding constructing the string just to throw it away. + * + * Beware, you cannot use the pattern of: + * if (cont) + * EXPECT() + * else + * EXPECT() + * + * as the else will tie to the if inside the conditional. */ -#define EXPECT_TRUE_MSG(expression, msg) FTEST_TEST_BOOLEAN(#expression, bool(expression), true, msg, FTEST_EXPECT_RESULT) -#define EXPECT_FALSE_MSG(expression, msg) FTEST_TEST_BOOLEAN(#expression, bool(expression), false, msg, FTEST_EXPECT_RESULT) -#define ASSERT_TRUE_MSG(expression, msg) FTEST_TEST_BOOLEAN(#expression, bool(expression), true, msg, FTEST_ASSERT_RESULT) -#define ASSERT_FALSE_MSG(expression, msg) FTEST_TEST_BOOLEAN(#expression, bool(expression), false, msg, FTEST_ASSERT_RESULT) +#define FTEST_TEST_BOOLEAN_SHORTCIRCUIT(valueStr, value, expected, userFailMsg, isAssert) \ + if (value != expected) \ + FTEST_TEST_BOOLEAN(valueStr, value, expected, userFailMsg, isAssert) + +#define EXPECT_TRUE_MSG(expression, msg) FTEST_TEST_BOOLEAN_SHORTCIRCUIT(#expression, bool(expression), true, msg, FTEST_EXPECT_RESULT) +#define EXPECT_FALSE_MSG(expression, msg) FTEST_TEST_BOOLEAN_SHORTCIRCUIT(#expression, bool(expression), false, msg, FTEST_EXPECT_RESULT) +#define ASSERT_TRUE_MSG(expression, msg) FTEST_TEST_BOOLEAN_SHORTCIRCUIT(#expression, bool(expression), true, msg, FTEST_ASSERT_RESULT) +#define ASSERT_FALSE_MSG(expression, msg) FTEST_TEST_BOOLEAN_SHORTCIRCUIT(#expression, bool(expression), false, msg, FTEST_ASSERT_RESULT) #define EXPECT_TRUE(expression) EXPECT_TRUE_MSG(expression, "") #define EXPECT_FALSE(expression) EXPECT_FALSE_MSG(expression, "") @@ -643,19 +656,23 @@ using GPUUnitTestContext = unittest::GPUUnitTestContext; #define EXPECT_MSG(expression, msg) EXPECT_TRUE_MSG(expression, msg) #define ASSERT_MSG(expression, msg) ASSERT_TRUE_MSG(expression, msg) -#define EXPECT_EQ_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperEQ, lhs, rhs, msg, FTEST_EXPECT_RESULT) -#define EXPECT_NE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperNE, lhs, rhs, msg, FTEST_EXPECT_RESULT) -#define EXPECT_LE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperLE, lhs, rhs, msg, FTEST_EXPECT_RESULT) -#define EXPECT_GE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperGE, lhs, rhs, msg, FTEST_EXPECT_RESULT) -#define EXPECT_LT_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperLT, lhs, rhs, msg, FTEST_EXPECT_RESULT) -#define EXPECT_GT_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperGT, lhs, rhs, msg, FTEST_EXPECT_RESULT) - -#define ASSERT_EQ_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperEQ, lhs, rhs, msg, FTEST_ASSERT_RESULT) -#define ASSERT_NE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperNE, lhs, rhs, msg, FTEST_ASSERT_RESULT) -#define ASSERT_LE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperLE, lhs, rhs, msg, FTEST_ASSERT_RESULT) -#define ASSERT_GE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperGE, lhs, rhs, msg, FTEST_ASSERT_RESULT) -#define ASSERT_LT_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperLT, lhs, rhs, msg, FTEST_ASSERT_RESULT) -#define ASSERT_GT_MSG(lhs, rhs, msg) FTEST_TEST_BINARY(CmpHelperGT, lhs, rhs, msg, FTEST_ASSERT_RESULT) +#define FTEST_TEST_BINARY_SHORTCIRCUIT(opHelper, lhs, rhs, userFailMsg, asserts) \ + if (!unittest:: opHelper ::compare(lhs, rhs)) \ + FTEST_TEST_BINARY(opHelper, lhs, rhs, userFailMsg, asserts) + +#define EXPECT_EQ_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperEQ, lhs, rhs, msg, FTEST_EXPECT_RESULT) +#define EXPECT_NE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperNE, lhs, rhs, msg, FTEST_EXPECT_RESULT) +#define EXPECT_LE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperLE, lhs, rhs, msg, FTEST_EXPECT_RESULT) +#define EXPECT_GE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperGE, lhs, rhs, msg, FTEST_EXPECT_RESULT) +#define EXPECT_LT_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperLT, lhs, rhs, msg, FTEST_EXPECT_RESULT) +#define EXPECT_GT_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperGT, lhs, rhs, msg, FTEST_EXPECT_RESULT) + +#define ASSERT_EQ_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperEQ, lhs, rhs, msg, FTEST_ASSERT_RESULT) +#define ASSERT_NE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperNE, lhs, rhs, msg, FTEST_ASSERT_RESULT) +#define ASSERT_LE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperLE, lhs, rhs, msg, FTEST_ASSERT_RESULT) +#define ASSERT_GE_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperGE, lhs, rhs, msg, FTEST_ASSERT_RESULT) +#define ASSERT_LT_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperLT, lhs, rhs, msg, FTEST_ASSERT_RESULT) +#define ASSERT_GT_MSG(lhs, rhs, msg) FTEST_TEST_BINARY_SHORTCIRCUIT(CmpHelperGT, lhs, rhs, msg, FTEST_ASSERT_RESULT) #define EXPECT_EQ(lhs, rhs) EXPECT_EQ_MSG(lhs, rhs, "") #define EXPECT_NE(lhs, rhs) EXPECT_NE_MSG(lhs, rhs, "") diff --git a/Source/Falcor/Utils/Algorithm/ParallelReduction.cpp b/Source/Falcor/Utils/Algorithm/ParallelReduction.cpp index e78d93e3e5..fb845a6f7f 100644 --- a/Source/Falcor/Utils/Algorithm/ParallelReduction.cpp +++ b/Source/Falcor/Utils/Algorithm/ParallelReduction.cpp @@ -46,7 +46,9 @@ ParallelReduction::ParallelReduction(ref pDevice) : mpDevice(pDevice) mpVars = ProgramVars::create(mpDevice, mpInitialProgram.get()); // Check assumptions on thread group sizes. The initial pass is a 2D dispatch, the final pass a 1D. - FALCOR_ASSERT(mpInitialProgram->getReflector()->getThreadGroupSize().z == 1); + FALCOR_ASSERT( + mpInitialProgram->getReflector()->getThreadGroupSize().y == 1 && mpInitialProgram->getReflector()->getThreadGroupSize().z == 1 + ); FALCOR_ASSERT( mpFinalProgram->getReflector()->getThreadGroupSize().y == 1 && mpFinalProgram->getReflector()->getThreadGroupSize().z == 1 ); @@ -141,7 +143,8 @@ void ParallelReduction::execute( FALCOR_ASSERT(resolution.x > 0 && resolution.y > 0); FALCOR_ASSERT(elementSize > 0); - const uint2 numTiles = div_round_up(resolution, mpInitialProgram->getReflector()->getThreadGroupSize().xy()); + const uint2 tileSize = {32, 32}; // Must match shader code. + const uint2 numTiles = div_round_up(resolution, tileSize); allocate(numTiles.x * numTiles.y, elementSize); FALCOR_ASSERT(mpBuffers[0]); FALCOR_ASSERT(mpBuffers[1]); @@ -168,7 +171,7 @@ void ParallelReduction::execute( var["gResult"] = mpBuffers[0]; mpState->setProgram(mpInitialProgram); - uint3 numGroups = div_round_up(uint3(resolution.x, resolution.y, 1), mpInitialProgram->getReflector()->getThreadGroupSize()); + uint3 numGroups = {numTiles.x, numTiles.y, 1}; pRenderContext->dispatch(mpState.get(), mpVars.get(), numGroups); } diff --git a/Source/Falcor/Utils/Algorithm/ParallelReduction.cs.slang b/Source/Falcor/Utils/Algorithm/ParallelReduction.cs.slang index 9ad96a7970..531b901c7b 100644 --- a/Source/Falcor/Utils/Algorithm/ParallelReduction.cs.slang +++ b/Source/Falcor/Utils/Algorithm/ParallelReduction.cs.slang @@ -70,9 +70,9 @@ groupshared DataType gIntermediateCache[32 /* = 1024 / 32 */]; groupshared DataType gIntermediateCache[64 /* = 1024 / 32 * 2 */]; #endif -DataType loadTexture(uint2 pixelCoords) +DataType loadTexture(uint2 pixel) { - DataType value = gInput[pixelCoords]; + DataType value = gInput[pixel]; #if FORMAT_CHANNELS < 4 // The default value for missing components is (0,0,0,1). Reset last component to zero for consistency. value.w = 0.f; @@ -83,22 +83,22 @@ DataType loadTexture(uint2 pixelCoords) /** * Performs reduction within a thread group and writes single result to the results buffer at 'dstIdx'. */ -void reduceSum(DataType value, uint dstIdx, uint groupThreadIdx) +void reduceSum(DataType value, const uint dstIdx, const uint warpId) { - // Add all elements within warp. Store result to shared memory. + // Add all elements within warp. First thread in warp stores result to shared memory. { value = WaveActiveSum(value); if (WaveIsFirstLane()) - gIntermediateCache[groupThreadIdx / 32] = value; + gIntermediateCache[warpId] = value; } GroupMemoryBarrierWithGroupSync(); - // Add all elements produced by the warps. - if (groupThreadIdx < 32) + // First warp adds all elements produced by the warps. + if (warpId == 0) { - value = gIntermediateCache[groupThreadIdx]; + value = gIntermediateCache[WaveGetLaneIndex()]; value = WaveActiveSum(value); - if (groupThreadIdx == 0) + if (WaveIsFirstLane()) gResult[dstIdx] = value; } } @@ -106,28 +106,28 @@ void reduceSum(DataType value, uint dstIdx, uint groupThreadIdx) /** * Take the min/max of all elements within a thread group and writes single result to the results buffer at 'dstIdx'. */ -void reduceMinMax(DataType minValue, DataType maxValue, uint dstIdx, uint groupThreadIdx) +void reduceMinMax(DataType minValue, DataType maxValue, const uint dstIdx, const uint warpId) { - // Add all elements within warp. Store result to shared memory. + // Add all elements within warp. First thread in warp stores result to shared memory. { minValue = WaveActiveMin(minValue); maxValue = WaveActiveMax(maxValue); if (WaveIsFirstLane()) { - gIntermediateCache[(groupThreadIdx / 32) * 2] = minValue; - gIntermediateCache[(groupThreadIdx / 32) * 2 + 1] = maxValue; + gIntermediateCache[warpId * 2] = minValue; + gIntermediateCache[warpId * 2 + 1] = maxValue; } } GroupMemoryBarrierWithGroupSync(); - // Take the min/max of all elements produced by the warps. - if (groupThreadIdx < 32) + // First warp takes the min/max of all elements produced by the warps. + if (warpId == 0) { - minValue = gIntermediateCache[groupThreadIdx * 2]; - maxValue = gIntermediateCache[groupThreadIdx * 2 + 1]; + minValue = gIntermediateCache[WaveGetLaneIndex() * 2]; + maxValue = gIntermediateCache[WaveGetLaneIndex() * 2 + 1]; minValue = WaveActiveMin(minValue); maxValue = WaveActiveMax(maxValue); - if (groupThreadIdx == 0) + if (WaveIsFirstLane()) { gResult[dstIdx * 2] = minValue; gResult[dstIdx * 2 + 1] = maxValue; @@ -135,40 +135,51 @@ void reduceMinMax(DataType minValue, DataType maxValue, uint dstIdx, uint groupT } } -[numthreads(32, 32, 1)] +/** + * The first pass loads values from a 2D texture and does an initial reduction. + * The dispatch is arranged as 32x32 tiles over the texture. + * The threads in each thread group are mapped in scanline order to pixels in the tile. + */ +[numthreads(1024, 1, 1)] void initialPass(uint3 globalThreadId: SV_DispatchThreadID, uint groupThreadIdx: SV_GroupIndex, uint3 groupId: SV_GroupID) { - const uint2 pixelCoords = globalThreadId.xy; const uint tileIdx = groupId.y * gNumTiles.x + groupId.x; + const uint warpId = groupThreadIdx / 32; // Assuming warps are assigned consecutively to the threads in the group in a 1D dispatch. + const uint2 pixel = groupId.xy * uint2(32, 32) + uint2(groupThreadIdx.x % 32, warpId); #if REDUCTION_TYPE == REDUCTION_TYPE_SUM // Load input from texture in tiles of 32x32 pixels. DataType value = 0; - if (all(pixelCoords < gResolution)) - value = loadTexture(pixelCoords); + if (all(pixel < gResolution)) + value = loadTexture(pixel); - reduceSum(value, tileIdx, groupThreadIdx); + reduceSum(value, tileIdx, warpId); #elif REDUCTION_TYPE == REDUCTION_TYPE_MINMAX // Load input from texture in tiles of 32x32 pixels. DataType minValue = kMaxValue; DataType maxValue = kMinValue; - if (all(pixelCoords < gResolution)) - minValue = maxValue = loadTexture(pixelCoords); + if (all(pixel < gResolution)) + minValue = maxValue = loadTexture(pixel); - reduceMinMax(minValue, maxValue, tileIdx, groupThreadIdx); + reduceMinMax(minValue, maxValue, tileIdx, warpId); #endif } +/** + * The second pass performs further reduction to compute a single value. + */ [numthreads(1024, 1, 1)] void finalPass(uint3 globalThreadId: SV_DispatchThreadID, uint groupThreadIdx: SV_GroupIndex, uint3 groupId: SV_GroupID) { + const uint warpId = groupThreadIdx / 32; // Assuming warps are assigned consecutively to the threads in the group in a 1D dispatch. + #if REDUCTION_TYPE == REDUCTION_TYPE_SUM // Load input from buffer written in previous pass. DataType value = 0; if (globalThreadId.x < gElems) value = gInputBuffer[globalThreadId.x]; - reduceSum(value, groupId.x, groupThreadIdx); + reduceSum(value, groupId.x, warpId); #elif REDUCTION_TYPE == REDUCTION_TYPE_MINMAX // Load input from buffer written in previous pass. DataType minValue = kMaxValue; @@ -179,6 +190,6 @@ void finalPass(uint3 globalThreadId: SV_DispatchThreadID, uint groupThreadIdx: S maxValue = gInputBuffer[globalThreadId.x * 2 + 1]; } - reduceMinMax(minValue, maxValue, groupId.x, groupThreadIdx); + reduceMinMax(minValue, maxValue, groupId.x, warpId); #endif } diff --git a/Source/Falcor/Utils/Geometry/GeometryHelpers.slang b/Source/Falcor/Utils/Geometry/GeometryHelpers.slang index a875e7bb5a..d76725bdda 100644 --- a/Source/Falcor/Utils/Geometry/GeometryHelpers.slang +++ b/Source/Falcor/Utils/Geometry/GeometryHelpers.slang @@ -69,6 +69,7 @@ float3 computeRayOrigin(float3 pos, float3 normal) [Differentiable] [PreferRecompute] +[__NoSideEffect] DifferentialPair fwd_computeRayOrigin(DifferentialPair pos, DifferentialPair normal) { return DifferentialPair(computeRayOrigin(pos.p, normal.p), pos.d); @@ -76,6 +77,7 @@ DifferentialPair fwd_computeRayOrigin(DifferentialPair pos, Diff [Differentiable] [PreferRecompute] +[__NoSideEffect] void bwd_computeRayOrigin(inout DifferentialPair pos, inout DifferentialPair normal, float3.Differential dOut) { pos = diffPair(pos.p, dOut); diff --git a/Source/Falcor/Utils/Image/Bitmap.cpp b/Source/Falcor/Utils/Image/Bitmap.cpp index b47bd02a22..102b6fd235 100644 --- a/Source/Falcor/Utils/Image/Bitmap.cpp +++ b/Source/Falcor/Utils/Image/Bitmap.cpp @@ -265,18 +265,22 @@ static FIBITMAP* convertToRGBA16Float(FIBITMAP* pDib) const BYTE* src_bits = (BYTE*)FreeImage_GetBits(pDib); BYTE* dst_bits = (BYTE*)FreeImage_GetBits(pNew); + const size_t src_bytes_per_pixel = bpp / 8; + for (uint32_t y = 0; y < height; y++) { - const FIRGBAF* src_pixel = (FIRGBAF*)src_bits; - FIRGBA16* dst_pixel = (tagFIRGBA16*)dst_bits; + FIRGBA16* dst_line = (tagFIRGBA16*)dst_bits; for (uint32_t x = 0; x < width; x++) { + // The stride on the src_pixel depends on the actual type (FIRRGBF or FIRRGBAF), + // so we have to cast on each pixel, rather than each line + const FIRGBAF& src_pixel = *(FIRGBAF*)(src_bits + x * src_bytes_per_pixel); // Convert pixels to float16_t directly, while adding a "dummy" alpha of 1.0 if source format doesn't have alpha. - dst_pixel[x].red = float16_t(src_pixel[x].red).toBits(); - dst_pixel[x].green = float16_t(src_pixel[x].green).toBits(); - dst_pixel[x].blue = float16_t(src_pixel[x].blue).toBits(); - dst_pixel[x].alpha = float16_t(type == FIT_RGBAF ? src_pixel[x].alpha : 1.0f).toBits(); + dst_line[x].red = float16_t(src_pixel.red).toBits(); + dst_line[x].green = float16_t(src_pixel.green).toBits(); + dst_line[x].blue = float16_t(src_pixel.blue).toBits(); + dst_line[x].alpha = float16_t(type == FIT_RGBAF ? src_pixel.alpha : 1.0f).toBits(); } src_bits += src_pitch; dst_bits += dst_pitch; @@ -602,9 +606,6 @@ void Bitmap::saveImage( FALCOR_CHECK(fileFormat != FileFormat::DdsFile, "Cannot save DDS files. Use ImageIO instead."); if (is_set(exportFlags, ExportFlags::Uncompressed) && is_set(exportFlags, ExportFlags::Lossy)) FALCOR_THROW("Incompatible flags: lossy cannot be combined with uncompressed."); - if (is_set(exportFlags, ExportFlags::ExrFloat16) && - (!is_set(exportFlags, ExportFlags::Uncompressed) || fileFormat != FileFormat::ExrFile)) - FALCOR_THROW("Incompatible flags: EXR float16 can only be set for uncompressed EXR files."); int flags = 0; FIBITMAP* pImage = nullptr; @@ -630,6 +631,15 @@ void Bitmap::saveImage( if (fileFormat == Bitmap::FileFormat::PfmFile || fileFormat == Bitmap::FileFormat::ExrFile) { + // If the original data format is half float, we want EXR to store half floats. + // Otherwise, we let EXR store 32b floats, unless half floats have been explicitly requested. + if (getFormatType(resourceFormat) == FormatType::Float && getNumChannelBits(resourceFormat, 0) == 16) + exportFlags |= ExportFlags::ExrFloat16; + + // FreeImage does not understand half floats. So even if we want to store half floats, + // we have to first upsample them to 32b floats, and then pass for FreeImage along with a flag + // to store them again as EXRs. + // The conversion also handles int/uint formats that are not supported by FreeImage. std::vector floatData; if (isConvertibleToRGBA32Float(resourceFormat)) { @@ -685,13 +695,15 @@ void Bitmap::saveImage( if (is_set(exportFlags, ExportFlags::Uncompressed)) { flags |= EXR_NONE; - if (!is_set(exportFlags, ExportFlags::ExrFloat16)) - flags |= EXR_FLOAT; } else if (is_set(exportFlags, ExportFlags::Lossy)) { flags |= EXR_B44 | EXR_ZIP; } + if (!is_set(exportFlags, ExportFlags::ExrFloat16)) + { + flags |= EXR_FLOAT; + } } } else diff --git a/Source/Falcor/Utils/Image/Bitmap.h b/Source/Falcor/Utils/Image/Bitmap.h index 88d593a66d..ae4507803b 100644 --- a/Source/Falcor/Utils/Image/Bitmap.h +++ b/Source/Falcor/Utils/Image/Bitmap.h @@ -55,6 +55,7 @@ class FALCOR_API Bitmap { None = 0u, ///< Default. ConvertToFloat16 = 1u << 0, ///< Convert HDR images to 16-bit float per channel on import. + FlipVertical = 1u << 1, ///< Flip image vertically on import. }; enum class FileFormat diff --git a/Source/Falcor/Utils/Logger.cpp b/Source/Falcor/Utils/Logger.cpp index 2af808d4a0..bf96ff285f 100644 --- a/Source/Falcor/Utils/Logger.cpp +++ b/Source/Falcor/Utils/Logger.cpp @@ -70,7 +70,7 @@ FILE* openLogFile() } // If we got here, we couldn't create a log file - FALCOR_UNREACHABLE(); + FALCOR_THROW("Failed to create a log file: {}", sLogFilePath); return pFile; } diff --git a/Source/Falcor/Utils/Math/FormatConversion.slang b/Source/Falcor/Utils/Math/FormatConversion.slang index c05d424d3d..d98c9a6fd2 100644 --- a/Source/Falcor/Utils/Math/FormatConversion.slang +++ b/Source/Falcor/Utils/Math/FormatConversion.slang @@ -266,18 +266,7 @@ uint packSnorm16(precise float v) */ float2 unpackSnorm2x16(uint packed) { - int2 bits = int2(packed << 16, packed) >> 16; - precise float2 unpacked = max((float2)bits / 32767.f, -1.0f); - return unpacked; -} - -/** - * Pack two floats into 16-bit snorm values in the lo/hi bits of a dword. - * @return Two 16-bit snorm in low/high bits. - */ -uint packSnorm2x16(precise float2 v) -{ - return (floatToSnorm16(v.x) & 0x0000ffff) | (floatToSnorm16(v.y) << 16); + return unpackSnorm2x16ToFloat(packed); } /////////////////////////////////////////////////////////////////////////////// @@ -305,14 +294,6 @@ uint packUnorm16(float v) return packUnorm16_unsafe(v); } -/** - * Pack two floats into 16-bit unorm values in a dword. - */ -uint packUnorm2x16(float2 v) -{ - return (packUnorm16(v.y) << 16) | packUnorm16(v.x); -} - /** * Pack two floats into 16-bit unorm values in a dword (unsafe version) * @param[in] v Two values assumed to be in [0,1]. @@ -337,7 +318,7 @@ float unpackUnorm16(uint packed) */ float2 unpackUnorm2x16(uint packed) { - return float2(packed & 0xffff, packed >> 16) * (1.f / 65535); + return unpackUnorm2x16ToFloat(packed); } /////////////////////////////////////////////////////////////////////////////// diff --git a/Source/Falcor/Utils/Math/MathHelpers.slang b/Source/Falcor/Utils/Math/MathHelpers.slang index f95df44ade..df15de8264 100644 --- a/Source/Falcor/Utils/Math/MathHelpers.slang +++ b/Source/Falcor/Utils/Math/MathHelpers.slang @@ -274,6 +274,17 @@ float2 sample_disk(float2 u) return p; } +float2 inverse_sample_disk(float2 p) +{ + float2 u; + float r = sqrt(p.x * p.x + p.y * p.y); + float phi = r > 0.0f ? atan2(p.y / r, p.x / r) : 0.0f; + if (phi < 0) phi += M_2PI; + u.x = r * r; + u.y = phi / M_2PI; + return u; +} + /** * Uniform sampling of direction within a cone * @param[in] u Uniform random number in [0,1)^2. @@ -443,6 +454,16 @@ float2 calcMotionVector(float2 pixelCrd, float4 prevPosH, float2 renderTargetDim ******************************************************************************/ +/** + * log2 function clamped to a valid domain. + * @param[in] x value whose clamped log2 is to be computed. + * @return The range of lod values that can be returned is [-126, 127]. + */ +float safeLog2(float x) +{ + return log2(clamp(x, FLT_MIN, FLT_MAX)); +} + /** * Inverts a 2x2 matrix. */ diff --git a/Source/Falcor/Utils/Math/ShadingFrame.slang b/Source/Falcor/Utils/Math/ShadingFrame.slang index 42958f596a..d722a26a67 100644 --- a/Source/Falcor/Utils/Math/ShadingFrame.slang +++ b/Source/Falcor/Utils/Math/ShadingFrame.slang @@ -33,9 +33,12 @@ import Utils.Math.MathHelpers; */ struct ShadingFrame : IDifferentiable { - float3 T; ///< Shading tangent. Normalized. - float3 B; ///< Shading bitangent. Normalized. - float3 N; ///< Shading normal. Normalized. + float3 T = {}; ///< Shading tangent. Normalized. + float3 B = {}; ///< Shading bitangent. Normalized. + float3 N = {}; ///< Shading normal. Normalized. + + [Differentiable] + __init() {} /** * Initialize shading frame from normal and tangent. diff --git a/Source/Falcor/Utils/PathResolving.cpp b/Source/Falcor/Utils/PathResolving.cpp index 41e2a6cab2..7ad0cd9553 100644 --- a/Source/Falcor/Utils/PathResolving.cpp +++ b/Source/Falcor/Utils/PathResolving.cpp @@ -93,7 +93,7 @@ ResolvedPaths resolveSearchPaths( result.resolved.insert(result.resolved.end(), standard.begin(), standard.end()); continue; } - std::filesystem::path path = std::filesystem::weakly_canonical(it2); + std::filesystem::path path = std::filesystem::path(it2).lexically_normal(); if (path.is_absolute()) result.resolved.push_back(std::move(path)); else @@ -120,7 +120,7 @@ std::filesystem::path resolvePath( if (path.is_absolute()) { if (fileChecker(path)) - return std::filesystem::weakly_canonical(path); + return std::filesystem::path(path).lexically_normal(); return std::filesystem::path(); } @@ -129,7 +129,7 @@ std::filesystem::path resolvePath( { std::filesystem::path result = currentWorkingDirectory / path; if (fileChecker(result)) - return std::filesystem::weakly_canonical(result); + return std::filesystem::path(result).lexically_normal(); return std::filesystem::path(); } @@ -138,7 +138,7 @@ std::filesystem::path resolvePath( { std::filesystem::path result = searchpath / path; if (fileChecker(result)) - return std::filesystem::weakly_canonical(result); + return std::filesystem::path(result).lexically_normal(); } return std::filesystem::path(); } diff --git a/Source/Falcor/Utils/Sampling/Pseudorandom/LCG.slang b/Source/Falcor/Utils/Sampling/Pseudorandom/LCG.slang index 8e281e3eb1..4fd572b2a6 100644 --- a/Source/Falcor/Utils/Sampling/Pseudorandom/LCG.slang +++ b/Source/Falcor/Utils/Sampling/Pseudorandom/LCG.slang @@ -41,7 +41,7 @@ struct LCG { - uint state; + uint state = {}; }; /** diff --git a/Source/Falcor/Utils/Sampling/Pseudorandom/Xorshift32.slang b/Source/Falcor/Utils/Sampling/Pseudorandom/Xorshift32.slang index ef41132168..31ad9be071 100644 --- a/Source/Falcor/Utils/Sampling/Pseudorandom/Xorshift32.slang +++ b/Source/Falcor/Utils/Sampling/Pseudorandom/Xorshift32.slang @@ -54,5 +54,5 @@ struct Xorshift32 return state; } - uint state; + uint state = {}; }; diff --git a/Source/Falcor/Utils/Sampling/Pseudorandom/Xoshiro.slang b/Source/Falcor/Utils/Sampling/Pseudorandom/Xoshiro.slang index 81af6b95e3..8c59441759 100644 --- a/Source/Falcor/Utils/Sampling/Pseudorandom/Xoshiro.slang +++ b/Source/Falcor/Utils/Sampling/Pseudorandom/Xoshiro.slang @@ -40,7 +40,7 @@ struct Xoshiro128StarStar { - uint state[4]; + uint state[4] = {}; }; uint rotl(const uint x, int k) diff --git a/Source/Falcor/Utils/Sampling/TinyUniformSampleGenerator.slang b/Source/Falcor/Utils/Sampling/TinyUniformSampleGenerator.slang index bf40e49101..fc3833fc66 100644 --- a/Source/Falcor/Utils/Sampling/TinyUniformSampleGenerator.slang +++ b/Source/Falcor/Utils/Sampling/TinyUniformSampleGenerator.slang @@ -40,10 +40,12 @@ export struct TinyUniformSampleGenerator : ISampleGenerator { struct Padded { - TinyUniformSampleGenerator internal; - uint3 _pad; + TinyUniformSampleGenerator internal = {}; + uint3 _pad = {}; }; + __init() {} + /** * Initializes the sample generator. * @param[in] seed Seed value. @@ -68,5 +70,5 @@ export struct TinyUniformSampleGenerator : ISampleGenerator [mutating] uint next() { return nextRandom(rng); } - LCG rng; ///< Simple LCG 32-bit pseudorandom number generator. + LCG rng = {}; ///< Simple LCG 32-bit pseudorandom number generator. }; diff --git a/Source/Falcor/Utils/Sampling/UniformSampleGenerator.slang b/Source/Falcor/Utils/Sampling/UniformSampleGenerator.slang index c7c36db3cb..d454a46aeb 100644 --- a/Source/Falcor/Utils/Sampling/UniformSampleGenerator.slang +++ b/Source/Falcor/Utils/Sampling/UniformSampleGenerator.slang @@ -42,9 +42,11 @@ export struct UniformSampleGenerator : ISampleGenerator { struct Padded { - UniformSampleGenerator internal; + UniformSampleGenerator internal = {}; } + __init() {} + /** * Initializes the sample generator for a given pixel and sample number. * @param[in] pixel Pixel id. @@ -76,5 +78,5 @@ export struct UniformSampleGenerator : ISampleGenerator [mutating] uint next() { return nextRandom(rng); } - Xoshiro128StarStar rng; + Xoshiro128StarStar rng = {}; }; diff --git a/Source/Falcor/Utils/Settings/AttributeFilters.h b/Source/Falcor/Utils/Settings/AttributeFilters.h index fdd43740aa..b24af6b2cf 100644 --- a/Source/Falcor/Utils/Settings/AttributeFilters.h +++ b/Source/Falcor/Utils/Settings/AttributeFilters.h @@ -83,9 +83,6 @@ class AttributeFilter if (attribute.is_null()) return {}; - if (!detail::TypeChecker::validType(attribute)) - throw detail::TypeError("Attribute's type does not match the requested type."); - if constexpr (std::is_arithmetic_v) { if (attribute.is_boolean()) diff --git a/Source/Falcor/Utils/Settings/Attributes.h b/Source/Falcor/Utils/Settings/Attributes.h index a235d087a8..7022562181 100644 --- a/Source/Falcor/Utils/Settings/Attributes.h +++ b/Source/Falcor/Utils/Settings/Attributes.h @@ -62,9 +62,6 @@ class Attributes if (attribute.is_null()) return {}; - if (!detail::TypeChecker::validType(attribute)) - throw detail::TypeError("Attribute's type does not match the requested type."); - // Handle return value of bool, if the actual is convertible to bool (from int, usually) if constexpr (std::is_same_v) { diff --git a/Source/Falcor/Utils/Settings/Settings.cpp b/Source/Falcor/Utils/Settings/Settings.cpp index faf5857c44..59460c09e6 100644 --- a/Source/Falcor/Utils/Settings/Settings.cpp +++ b/Source/Falcor/Utils/Settings/Settings.cpp @@ -118,6 +118,13 @@ bool Settings::addOptions(const std::filesystem::path& path) return true; } +void Settings::addOption(std::string_view name, const nlohmann::json& value) +{ + nlohmann::json options; + options[name] = value; + addOptions(options); +} + void Settings::addFilteredAttributes(const pybind11::dict& attributes) { addFilteredAttributes(pyjson::to_json(attributes)); diff --git a/Source/Falcor/Utils/Settings/Settings.h b/Source/Falcor/Utils/Settings/Settings.h index 6d3cae6fac..8bc92de1a8 100644 --- a/Source/Falcor/Utils/Settings/Settings.h +++ b/Source/Falcor/Utils/Settings/Settings.h @@ -31,18 +31,13 @@ #include "Core/Error.h" #include +#include #include #include #include #include -namespace pybind11 -{ -class dict; -class list; -} // namespace pybind11 - namespace Falcor { @@ -80,6 +75,7 @@ class FALCOR_API Settings void addOptions(const nlohmann::json& options); void addOptions(const pybind11::dict& options); void addOptions(const pybind11::list& options); + void addOption(std::string_view name, const nlohmann::json& value); /// Add options from a JSON file, returning true on success and false on failure bool addOptions(const std::filesystem::path& path); diff --git a/Source/Falcor/Utils/Settings/SettingsUtils.h b/Source/Falcor/Utils/Settings/SettingsUtils.h index 13dd6f9743..7e8fad3bc4 100644 --- a/Source/Falcor/Utils/Settings/SettingsUtils.h +++ b/Source/Falcor/Utils/Settings/SettingsUtils.h @@ -71,46 +71,6 @@ inline nlohmann::json flattenDictionary(const nlohmann::json& dict) return flattened; } -template, bool> = true> -inline bool isType(const nlohmann::json& json) -{ - return json.is_number() || json.is_boolean(); -} - -template, bool> = true> -inline bool isType(const nlohmann::json& json) -{ - return json.is_string(); -} - -template>, bool> = true> -inline bool isType(const nlohmann::json& json) -{ - if (!json.is_array()) - return false; - if (json.size() != N) - return false; - for (size_t i = 0; i < N; ++i) - if (!isType(json[i])) - return false; - return true; -} - -// The "gccfix" parameter is used to avoid "explicit specialization in non-namespace scope" in gcc. -// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282 -template -struct TypeChecker -{ - static bool validType(const nlohmann::json& json) { return isType(json); } -}; - -template -struct TypeChecker, gccfix> -{ - using ArrayType = std::array; - static bool validType(const nlohmann::json& json) { return isType(json); } -}; - class TypeError : public std::runtime_error { public: diff --git a/Source/Falcor/Utils/fast_vector.h b/Source/Falcor/Utils/fast_vector.h index 85b38bb78d..c9989361fe 100644 --- a/Source/Falcor/Utils/fast_vector.h +++ b/Source/Falcor/Utils/fast_vector.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace Falcor { diff --git a/Source/Modules/CMakeLists.txt b/Source/Modules/CMakeLists.txt index 26a66afcd6..ea92c6aa25 100644 --- a/Source/Modules/CMakeLists.txt +++ b/Source/Modules/CMakeLists.txt @@ -1 +1,3 @@ add_subdirectory(USDUtils) +add_subdirectory(ReSTIRPathTracing) +add_subdirectory(LightTileResampling) diff --git a/Source/Modules/LightTileResampling/CMakeLists.txt b/Source/Modules/LightTileResampling/CMakeLists.txt new file mode 100644 index 0000000000..3557401154 --- /dev/null +++ b/Source/Modules/LightTileResampling/CMakeLists.txt @@ -0,0 +1,33 @@ +add_library(LightTileResampling STATIC) + +target_sources(LightTileResampling PRIVATE + LightTileResampling.cpp + LightTileResampling.h + GenerateLightTiles.cs.slang + Lights.slang + LightSample.slang + StreamingRIS.slang + ReflectTypes.cs.slang +) + +target_copy_shaders(LightTileResampling LightTileResampling) + +target_source_group(LightTileResampling "Modules/") + +target_link_libraries(LightTileResampling + PUBLIC + Falcor +) + +target_include_directories(LightTileResampling + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +set_target_properties(LightTileResampling + PROPERTIES + POSITION_INDEPENDENT_CODE ON + LIBRARY_OUTPUT_DIRECTORY ${FALCOR_RUNTIME_OUTPUT_DIRECTORY} +) + +validate_headers(LightTileResampling) diff --git a/Source/Modules/LightTileResampling/GenerateLightTiles.cs.slang b/Source/Modules/LightTileResampling/GenerateLightTiles.cs.slang new file mode 100644 index 0000000000..ef3675d40d --- /dev/null +++ b/Source/Modules/LightTileResampling/GenerateLightTiles.cs.slang @@ -0,0 +1,96 @@ +/*************************************************************************** + # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Scene.Scene; +import Lights; +import Utils.Sampling.TinyUniformSampleGenerator; +using namespace LightTileResampling; + +struct GenerateLightTiles +{ + static const uint kLightTileCount = LIGHT_TILE_COUNT; + static const uint kLightTileSize = LIGHT_TILE_SIZE; + + static const uint kEnvLightSampleCount = ENV_LIGHT_SAMPLE_COUNT; + static const uint kEmissiveLightSampleCount = EMISSIVE_LIGHT_SAMPLE_COUNT; + static const uint kAnalyticLightSampleCount = ANALYTIC_LIGHT_SAMPLE_COUNT; + + RWStructuredBuffer lightTileData; + + Lights lights; + + uint frameIndex; + + float getStratifiedRandom(uint sampleIndex, uint sampleCount, float offset) + { + return frac(offset + float(sampleIndex) / float(sampleCount)); + } + + void process(uint tileIndex, uint sampleIndex) + { + if (tileIndex >= kLightTileCount || sampleIndex >= kLightTileSize) + return; + + uint bufferIndex = tileIndex * kLightTileSize + sampleIndex; + + TinyUniformSampleGenerator tileSg = TinyUniformSampleGenerator(tileIndex, frameIndex); + float offset = sampleNext1D(tileSg); + + TinyUniformSampleGenerator sg = TinyUniformSampleGenerator(bufferIndex, frameIndex); + + LightSample lightSample = LightSample::createInvalid(); + + if (sampleIndex < kEnvLightSampleCount) + { + float random = getStratifiedRandom(sampleIndex, kEnvLightSampleCount, offset); + lightSample = lights.sampleEnv(random, sg); + } + else if (sampleIndex < kEnvLightSampleCount + kEmissiveLightSampleCount) + { + float random = getStratifiedRandom(sampleIndex, kEmissiveLightSampleCount, offset); + lightSample = lights.sampleEmissive(random, sg); + } + else + { + float random = getStratifiedRandom(sampleIndex, kAnalyticLightSampleCount, offset); + lightSample = lights.sampleAnalytic(random, sg); + } + + lightTileData[bufferIndex] = lights.loadLightSample(lightSample).pack(); + } +}; + +cbuffer CB +{ + GenerateLightTiles gGenerateLightTiles; +} + +[numthreads(256, 1, 1)] +void main(uint3 dispatchThreadId: SV_DispatchThreadID) +{ + gGenerateLightTiles.process(dispatchThreadId.y, dispatchThreadId.x); +} diff --git a/Source/Modules/LightTileResampling/LightSample.slang b/Source/Modules/LightTileResampling/LightSample.slang new file mode 100644 index 0000000000..e8a59ecc7f --- /dev/null +++ b/Source/Modules/LightTileResampling/LightSample.slang @@ -0,0 +1,201 @@ +/*************************************************************************** + # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "Utils/Math/MathConstants.slangh" +import Scene.Scene; +import Utils.Math.MathHelpers; + +#ifndef LIGHT_SAMPLE_ENV_INDEX_BITS +#error "LightSample bit allocations not defined!" +#endif + +enum class LightKind +{ + Env = (0 << 30), + Emissive = (1 << 30), + Analytic = (2 << 30), +}; + +typedef uint PackedLightSample; + +namespace LightTileResampling +{ +struct LightSample +{ + static const uint kInvalid = 0xffffffff; + + static const uint kKindBits = 2; + static const uint kKindOffset = 30; + static const uint kKindMask = ((1 << kKindBits) - 1) << kKindOffset; + + static const uint kPositionIndexMask = ~kKindMask; + + static const uint kEnvIndexBits = LIGHT_SAMPLE_ENV_INDEX_BITS; + static const uint kEnvIndexMask = (1 << kEnvIndexBits) - 1; + + static const uint kEnvPositionBits = LIGHT_SAMPLE_ENV_POSITION_BITS; + static const uint kEnvPositionOffset = kEnvIndexBits; + static const uint kEnvPositionMask = ((1 << kEnvPositionBits) - 1) << kEnvPositionOffset; + + static const uint kEmissiveIndexBits = LIGHT_SAMPLE_EMISSIVE_INDEX_BITS; + static const uint kEmissiveIndexMask = (1 << kEmissiveIndexBits) - 1; + + static const uint kEmissivePositionBits = LIGHT_SAMPLE_EMISSIVE_POSITION_BITS; + static const uint kEmissivePositionOffset = kEmissiveIndexBits; + static const uint kEmissivePositionMask = ((1 << kEmissivePositionBits) - 1) << kEmissivePositionOffset; + + static const uint kAnalyticIndexBits = LIGHT_SAMPLE_ANALYTIC_INDEX_BITS; + static const uint kAnalyticIndexMask = (1 << kAnalyticIndexBits) - 1; + + static const uint kAnalyticPositionBits = LIGHT_SAMPLE_ANALYTIC_POSITION_BITS; + static const uint kAnalyticPositionOffset = kAnalyticIndexBits; + static const uint kAnalyticPositionMask = ((1 << kAnalyticPositionBits) - 1) << kAnalyticPositionOffset; + + static uint encodePosition(const float2 position, const uint bitsPerComponent) + { + const uint mask = (1 << bitsPerComponent) - 1; + uint2 tmp = uint2(position * float(1 << bitsPerComponent)) & mask; + return tmp.x | (tmp.y << bitsPerComponent); + } + + static float2 decodePosition(const uint position, const uint bitsPerComponent) + { + const uint mask = (1 << bitsPerComponent) - 1; + return (uint2(position, position >> bitsPerComponent) & mask) / float(1 << bitsPerComponent); + } + + uint data; + + static LightSample createInvalid() + { + LightSample ls; + ls.data = kInvalid; + return ls; + } + + static LightSample createEnv(uint index, uint random) + { + LightSample ls; + ls.data = uint(LightKind::Env) | (index & kEnvIndexMask) | (random & kEnvPositionMask); + return ls; + } + + static LightSample createEnv(const float3 dir) + { + uint2 dim = gScene.envMap.getDimensions(); + float2 uv = gScene.envMap.worldToUv(dir); + float2 texel = uv * dim; + uint index = uint(texel.y) * dim.x + uint(texel.x); + uint position = encodePosition(frac(texel), kEnvPositionBits / 2); + + LightSample ls; + ls.data = uint(LightKind::Env) | (index & kEnvIndexMask) | (position << kEnvPositionOffset); + return ls; + } + + static LightSample createEmissive(uint index, uint random) + { + LightSample ls; + ls.data = uint(LightKind::Emissive) | (index & kEmissiveIndexMask) | (random & kEmissivePositionMask); + return ls; + } + + static LightSample createEmissive(uint index, float2 uv) + { + uint position = encodePosition(uv, kEmissivePositionBits / 2); + + LightSample ls = {}; + ls.data = uint(LightKind::Emissive) | (index & kEmissiveIndexMask) | (position << kEmissivePositionOffset); + return ls; + } + + static LightSample createAnalytic(uint index, uint random) + { + LightSample ls; + ls.data = uint(LightKind::Analytic) | (index & kAnalyticIndexMask) | (random & kAnalyticPositionMask); + return ls; + } + + static LightSample createFromPacked(const PackedLightSample packed) + { + LightSample ls; + ls.unpack(packed); + return ls; + } + + PackedLightSample pack() { return PackedLightSample(data); } + + [mutating] + void unpack(const PackedLightSample packed) { data = uint(packed); } + + bool isValid() { return data != kInvalid; } + + LightKind getKind() { return LightKind(data & kKindMask); } + + uint getEnvIndex() { return data & kEnvIndexMask; } + uint getEmissiveIndex() { return data & kEmissiveIndexMask; } + uint getAnalyticIndex() { return data & kAnalyticIndexMask; } + + uint getIndex() + { + switch (getKind()) + { + case LightKind::Env: + return getEnvIndex(); + case LightKind::Emissive: + return getEmissiveIndex(); + case LightKind::Analytic: + return getAnalyticIndex(); + } + return 0; + } + + float2 getEnvPosition() { return decodePosition(data >> kEnvPositionOffset, kEnvPositionBits / 2); } + float2 getEmissivePosition() { return decodePosition(data >> kEmissivePositionOffset, kEmissivePositionBits / 2); } + float2 getAnalyticPosition() { return decodePosition(data >> kAnalyticPositionOffset, kAnalyticPositionBits / 2); } + + float3 getEnvWorldDir() + { + uint texel = getEnvIndex(); + uint2 dim = gScene.envMap.getDimensions(); + float2 uv = (float2(texel % dim.x, texel / dim.x) + getEnvPosition()) / dim; + return gScene.envMap.toWorld(latlong_map_to_world(uv)); + } + + float2 getEmissiveUV() + { + float2 uv = getEmissivePosition(); + return uv.x + uv.y > 1.f ? 1.f - uv : uv; + } + + float3 getEmissiveBarycentrics() + { + float2 uv = getEmissiveUV(); + return float3(1.f - uv.x - uv.y, uv.x, uv.y); + } +} +} diff --git a/Source/Modules/LightTileResampling/LightTileResampling.cpp b/Source/Modules/LightTileResampling/LightTileResampling.cpp new file mode 100644 index 0000000000..6bb6776db1 --- /dev/null +++ b/Source/Modules/LightTileResampling/LightTileResampling.cpp @@ -0,0 +1,455 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "LightTileResampling.h" +#include "Core/Error.h" +#include "Core/API/RenderContext.h" +#include "Utils/Logger.h" +#include "Utils/Timing/Profiler.h" +#include "Utils/Color/ColorHelpers.slang" + +namespace Falcor +{ + +namespace +{ +const char kReflectTypesFile[] = "LightTileResampling/ReflectTypes.cs.slang"; +const char kGenerateLightTilesFile[] = "LightTileResampling/GenerateLightTiles.cs.slang"; +} // namespace + +LightTileResampling::LightTileResampling(const ref& pScene, const Options& options) + : mpScene(pScene), mpDevice(pScene->getDevice()), mOptions(options) +{ + FALCOR_ASSERT(mpScene); + + ProgramDesc desc; + DefineList defines; + mpScene->getShaderDefines(defines); + defines.add(getDefines()); + desc.addShaderLibrary(kReflectTypesFile).csEntry("main"); + mpReflectTypes = ComputePass::create(mpDevice, desc, defines); + + mUpdateFlagsConnection = mpScene->getUpdateFlagsSignal().connect([&](IScene::UpdateFlags flags) { mUpdateFlags |= flags; }); +} + +void LightTileResampling::setOptions(const Options& options) +{ + if (std::memcmp(&mOptions, &options, sizeof(Options)) != 0) + { + mOptions = options; + mRecompile = true; + } +} + +bool LightTileResampling::renderUI(Gui::Widgets& widget) +{ + bool dirty = false; + + if (auto group = widget.group("Light Tile Resampling")) + { + if (auto lwGroup = group.group("Light selection weights")) + { + mRecompile |= lwGroup.var("Environment", mOptions.envLightWeight, 0.f, 1.f); + mRecompile |= lwGroup.var("Emissive", mOptions.emissiveLightWeight, 0.f, 1.f); + mRecompile |= lwGroup.var("Analytic", mOptions.analyticLightWeight, 0.f, 1.f); + } + + mRecompile |= group.checkbox("Emissive texture for sampling", mOptions.useEmissiveTextureForSampling); + mRecompile |= group.checkbox("Emissive texture for shading", mOptions.useEmissiveTextureForShading); + + mRecompile |= group.var("Light tile count", mOptions.lightTileCount, 1u, 1024u); + mRecompile |= group.var("Light tile size", mOptions.lightTileSize, 1u, 8192u); + mRecompile |= group.var("Screen tile size", mOptions.screenTileSize, 1u, 32u); + mRecompile |= group.var("Initial light sample count", mOptions.initialLightSampleCount, 1u, 1024u); + } + + dirty |= mRecompile; + + return dirty; +} + +void LightTileResampling::beginFrame(RenderContext* pRenderContext) +{ + if (is_set(mUpdateFlags, IScene::UpdateFlags::RecompileNeeded) || is_set(mUpdateFlags, IScene::UpdateFlags::GeometryChanged)) + mRecompile = true; + + prepareLighting(pRenderContext); + prepareResources(); + + if (mRecompile) + updatePrograms(); + + mUpdateFlags = IScene::UpdateFlags::None; +} + +void LightTileResampling::generateLightTiles(RenderContext* pRenderContext) +{ + FALCOR_PROFILE(pRenderContext, "LightTileResampling::generateLightTiles"); + + auto rootVar = mpGenerateLightTiles->getRootVar(); + mpScene->bindShaderData(rootVar["gScene"]); + + auto var = rootVar["CB"]["gGenerateLightTiles"]; + var["lightTileData"] = mpLightTileData; + setLightsShaderData(var["lights"]); + var["frameIndex"] = mFrameIndex; + + mpGenerateLightTiles->execute(pRenderContext, uint3(mOptions.lightTileSize, mOptions.lightTileCount, 1)); + mFrameIndex++; +} + +DefineList LightTileResampling::getDefines() const +{ + DefineList defines; + + // Tile and initial sampling defines. + defines.add("LIGHT_TILE_COUNT", std::to_string(mOptions.lightTileCount)); + defines.add("LIGHT_TILE_SIZE", std::to_string(mOptions.lightTileSize)); + defines.add("SCREEN_TILE_SIZE", std::to_string(mOptions.screenTileSize)); + defines.add("INITIAL_LIGHT_SAMPLE_COUNT", std::to_string(mOptions.initialLightSampleCount)); + + // Light sample bit allocation defines. + uint32_t envIndexBits = 26, envPositionBits = 4; + uint32_t emissiveIndexBits = 22, emissivePositionBits = 8; + uint32_t analyticIndexBits = 14, analyticPositionBits = 16; + + auto computeIndexPositionBits = [](AliasTable& aliasTable, uint32_t& indexBits, uint32_t& positionBits) + { + uint32_t count = aliasTable.getCount(); + indexBits = 0; + while (count > 0) + { + ++indexBits; + count >>= 1; + } + if (indexBits & 1) + ++indexBits; + if (indexBits >= 30) + FALCOR_THROW("Count too large to be represented in 30 bits"); + positionBits = 30 - indexBits; + }; + + if (mpEnvLightAliasTable) + computeIndexPositionBits(*mpEnvLightAliasTable, envIndexBits, envPositionBits); + if (mpEmissiveLightAliasTable) + computeIndexPositionBits(*mpEmissiveLightAliasTable, emissiveIndexBits, emissivePositionBits); + if (mpAnalyticLightAliasTable) + computeIndexPositionBits(*mpAnalyticLightAliasTable, analyticIndexBits, analyticPositionBits); + + defines.add("USE_ENV_LIGHT", mpScene->useEnvLight() ? "1" : "0"); + defines.add("USE_EMISSIVE_LIGHTS", mpScene->useEmissiveLights() ? "1" : "0"); + defines.add("USE_ANALYTIC_LIGHTS", mpScene->useAnalyticLights() ? "1" : "0"); + + defines.add("LIGHT_SAMPLE_ENV_INDEX_BITS", std::to_string(envIndexBits)); + defines.add("LIGHT_SAMPLE_ENV_POSITION_BITS", std::to_string(envPositionBits)); + defines.add("LIGHT_SAMPLE_EMISSIVE_INDEX_BITS", std::to_string(emissiveIndexBits)); + defines.add("LIGHT_SAMPLE_EMISSIVE_POSITION_BITS", std::to_string(emissivePositionBits)); + defines.add("LIGHT_SAMPLE_ANALYTIC_INDEX_BITS", std::to_string(analyticIndexBits)); + defines.add("LIGHT_SAMPLE_ANALYTIC_POSITION_BITS", std::to_string(analyticPositionBits)); + + defines.add("USE_EMISSIVE_TEXTURE_FOR_SAMPLING", mOptions.useEmissiveTextureForSampling ? "1" : "0"); + defines.add("USE_EMISSIVE_TEXTURE_FOR_SHADING", mOptions.useEmissiveTextureForShading ? "1" : "0"); + defines.add("USE_LOCAL_EMISSIVE_TRIANGLES", "0"); + + return defines; +} + +void LightTileResampling::setLightsShaderData(const ShaderVar& var) const +{ + var["envLightLuminance"] = mpEnvLightLuminance; + + if (mpEnvLightAliasTable) + mpEnvLightAliasTable->bindShaderData(var["envLightAliasTable"]); + if (mpEmissiveLightAliasTable) + mpEmissiveLightAliasTable->bindShaderData(var["emissiveLightAliasTable"]); + if (mpAnalyticLightAliasTable) + mpAnalyticLightAliasTable->bindShaderData(var["analyticLightAliasTable"]); + + var["envLightLuminanceFactor"] = mEnvLightLuminanceFactor; + + var["envLightSelectionProbability"] = mLightSelectionProbabilities.envLight; + var["emissiveLightSelectionProbability"] = mLightSelectionProbabilities.emissiveLights; + var["analyticLightSelectionProbability"] = mLightSelectionProbabilities.analyticLights; +} + +void LightTileResampling::prepareLighting(RenderContext* pRenderContext) +{ + if (is_set(mUpdateFlags, IScene::UpdateFlags::RenderSettingsChanged)) + mRecompile = true; + + if (mpScene->useEnvLight()) + { + if (is_set(mUpdateFlags, IScene::UpdateFlags::EnvMapChanged)) + mpEnvLightAliasTable = nullptr; + + const auto& envMap = mpScene->getEnvMap(); + if (!mpEnvLightLuminance || !mpEnvLightAliasTable) + { + const auto& texture = envMap->getEnvMap(); + auto luminances = computeEnvLightLuminance(pRenderContext, texture); + mpEnvLightLuminance = mpDevice->createTypedBuffer( + (uint32_t)luminances.size(), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, luminances.data() + ); + mpEnvLightAliasTable = buildEnvLightAliasTable(texture->getWidth(), texture->getHeight(), luminances, mRng); + mRecompile = true; + } + + mEnvLightLuminanceFactor = luminance(envMap->getIntensity() * envMap->getTint()); + } + else + { + if (mpEnvLightLuminance) + { + mpEnvLightLuminance = nullptr; + mpEnvLightAliasTable = nullptr; + mRecompile = true; + } + } + + if (mpScene->getRenderSettings().useEmissiveLights) + { + if (is_set(mUpdateFlags, IScene::UpdateFlags::GeometryChanged)) + mpEmissiveLightAliasTable = nullptr; + if (!mpEmissiveLightAliasTable) + { + auto lightCollection = mpScene->getILightCollection(pRenderContext); + lightCollection->update(pRenderContext); + if (lightCollection->getActiveLightCount(pRenderContext) > 0) + { + mpEmissiveLightAliasTable = buildEmissiveLightAliasTable(pRenderContext, lightCollection, mRng); + mRecompile = true; + } + } + } + else + { + if (mpEmissiveLightAliasTable) + { + mpEmissiveLightAliasTable = nullptr; + mRecompile = true; + } + } + + if (mpScene->useAnalyticLights()) + { + if (is_set(mUpdateFlags, IScene::UpdateFlags::LightCountChanged)) + mpAnalyticLightAliasTable = nullptr; + if (!mpAnalyticLightAliasTable) + { + std::vector> lights = mpScene->getActiveAnalyticLights(); + if (!lights.empty()) + { + mpAnalyticLightAliasTable = buildAnalyticLightAliasTable(pRenderContext, lights, mRng); + mRecompile = true; + } + } + } + else + { + if (mpAnalyticLightAliasTable) + { + mpAnalyticLightAliasTable = nullptr; + mRecompile = true; + } + } + + auto& probs = mLightSelectionProbabilities; + probs.envLight = mpEnvLightAliasTable ? mOptions.envLightWeight : 0.f; + probs.emissiveLights = mpEmissiveLightAliasTable ? mOptions.emissiveLightWeight : 0.f; + probs.analyticLights = mpAnalyticLightAliasTable ? mOptions.analyticLightWeight : 0.f; + float total = probs.envLight + probs.emissiveLights + probs.analyticLights; + if (total > 0.f) + { + probs.envLight /= total; + probs.emissiveLights /= total; + probs.analyticLights /= total; + } +} + +void LightTileResampling::prepareResources() +{ + auto reflectVar = mpReflectTypes->getRootVar(); + + uint32_t elementCount = mOptions.lightTileCount * mOptions.lightTileSize; + if (!mpLightTileData || mpLightTileData->getElementCount() < elementCount) + { + mpLightTileData = mpDevice->createStructuredBuffer( + reflectVar["lightTileData"], + elementCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); + } +} + +void LightTileResampling::updatePrograms() +{ + DefineList commonDefines; + mpScene->getShaderDefines(commonDefines); + commonDefines.add(getDefines()); + commonDefines.add("USE_ENV_LIGHT", mpScene->useEnvLight() ? "1" : "0"); + commonDefines.add("USE_EMISSIVE_LIGHTS", mpScene->useEmissiveLights() ? "1" : "0"); + commonDefines.add("USE_ANALYTIC_LIGHTS", mpScene->useAnalyticLights() ? "1" : "0"); + + // GenerateLightTiles-specific defines. + DefineList defines = commonDefines; + auto [envLightSampleCount, emissiveLightSampleCount, analyticLightSampleCount] = + mLightSelectionProbabilities.getSampleCount(mOptions.lightTileSize); + defines.add("ENV_LIGHT_SAMPLE_COUNT", std::to_string(envLightSampleCount)); + defines.add("EMISSIVE_LIGHT_SAMPLE_COUNT", std::to_string(emissiveLightSampleCount)); + defines.add("ANALYTIC_LIGHT_SAMPLE_COUNT", std::to_string(analyticLightSampleCount)); + + if (!mpGenerateLightTiles) + { + ProgramDesc desc; + desc.addShaderLibrary(kGenerateLightTilesFile).csEntry("main"); + mpGenerateLightTiles = ComputePass::create(mpDevice, desc, defines, false); + } + + mpGenerateLightTiles->getProgram()->addDefines(defines); + mpGenerateLightTiles->setVars(nullptr); + + mRecompile = false; +} + +std::vector LightTileResampling::computeEnvLightLuminance(RenderContext* pRenderContext, const ref& texture) +{ + FALCOR_ASSERT(texture); + + uint32_t width = texture->getWidth(); + uint32_t height = texture->getHeight(); + + std::vector texelsRaw; + if (getFormatType(texture->getFormat()) == FormatType::Float) + { + texelsRaw = pRenderContext->readTextureSubresource(texture.get(), 0); + } + else + { + auto floatTexture = mpDevice->createTexture2D( + width, height, ResourceFormat::RGBA32Float, 1, 1, nullptr, ResourceBindFlags::RenderTarget | ResourceBindFlags::ShaderResource + ); + pRenderContext->blit(texture->getSRV(), floatTexture->getRTV()); + texelsRaw = pRenderContext->readTextureSubresource(floatTexture.get(), 0); + } + + uint32_t texelCount = width * height; + uint32_t channelCount = getFormatChannelCount(texture->getFormat()); + const float* texels = reinterpret_cast(texelsRaw.data()); + + std::vector luminances(texelCount); + + if (channelCount == 1) + { + for (uint32_t i = 0; i < texelCount; ++i) + { + luminances[i] = texels[0]; + texels += channelCount; + } + } + else if (channelCount == 3 || channelCount == 4) + { + for (uint32_t i = 0; i < texelCount; ++i) + { + luminances[i] = luminance(float3(texels[0], texels[1], texels[2])); + texels += channelCount; + } + } + else + { + FALCOR_THROW("Invalid number of channels in env map"); + } + + return luminances; +} + +std::unique_ptr LightTileResampling::buildEnvLightAliasTable( + uint32_t width, + uint32_t height, + const std::vector& luminances, + std::mt19937& rng +) +{ + FALCOR_ASSERT(luminances.size() == width * height); + + std::vector weights(width * height); + + for (uint32_t i = 0, y = 0; y < height; ++y) + { + float theta = (float)M_PI * (y + 0.5f) / height; + float solidAngle = (2.f * (float)M_PI / width) * ((float)M_PI / height) * std::sin(theta); + + for (uint32_t x = 0; x < width; ++x, ++i) + { + weights[i] = luminances[i] * solidAngle; + } + } + + return std::make_unique(mpDevice, std::move(weights), rng); +} + +std::unique_ptr LightTileResampling::buildEmissiveLightAliasTable( + RenderContext* pRenderContext, + const ref& lightCollection, + std::mt19937& rng +) +{ + FALCOR_ASSERT(lightCollection); + + lightCollection->update(pRenderContext); + + const auto& triangles = lightCollection->getMeshLightTriangles(pRenderContext); + + std::vector weights(triangles.size()); + + for (size_t i = 0; i < weights.size(); ++i) + { + weights[i] = luminance(triangles[i].averageRadiance) * triangles[i].area; + } + + return std::make_unique(mpDevice, std::move(weights), rng); +} + +std::unique_ptr LightTileResampling::buildAnalyticLightAliasTable( + RenderContext* pRenderContext, + const std::vector>& lights, + std::mt19937& rng +) +{ + std::vector weights(lights.size()); + + for (size_t i = 0; i < weights.size(); ++i) + { + weights[i] = 1.f; + } + + return std::make_unique(mpDevice, std::move(weights), rng); +} + +} // namespace Falcor diff --git a/Source/Modules/LightTileResampling/LightTileResampling.h b/Source/Modules/LightTileResampling/LightTileResampling.h new file mode 100644 index 0000000000..47e9c8bba9 --- /dev/null +++ b/Source/Modules/LightTileResampling/LightTileResampling.h @@ -0,0 +1,152 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#pragma once +#include "Utils/Sampling/AliasTable.h" +#include "Scene/Scene.h" +#include "Scene/Lights/ILightCollection.h" +#include "Scene/Lights/Light.h" +#include +#include +#include +#include +#include + +namespace Falcor +{ + +class LightTileResampling +{ +public: + struct Options + { + float envLightWeight = 1.f; + float emissiveLightWeight = 1.f; + float analyticLightWeight = 1.f; + + bool useEmissiveTextureForSampling = false; + bool useEmissiveTextureForShading = true; + + uint32_t lightTileCount = 128; + uint32_t lightTileSize = 1024; + + uint32_t screenTileSize = 8; + uint32_t initialLightSampleCount = 32; + + Options() {} + + template + void serialize(Archive& ar) + { + ar("envLightWeight", envLightWeight); + ar("emissiveLightWeight", emissiveLightWeight); + ar("analyticLightWeight", analyticLightWeight); + ar("useEmissiveTextureForSampling", useEmissiveTextureForSampling); + ar("useEmissiveTextureForShading", useEmissiveTextureForShading); + ar("lightTileCount", lightTileCount); + ar("lightTileSize", lightTileSize); + ar("screenTileSize", screenTileSize); + ar("initialLightSampleCount", initialLightSampleCount); + } + }; + + LightTileResampling(const ref& pScene, const Options& options = Options()); + + void beginFrame(RenderContext* pRenderContext); + + void generateLightTiles(RenderContext* pRenderContext); + + DefineList getDefines() const; + + void setLightsShaderData(const ShaderVar& var) const; + + ref getLightTileData() const { return mpLightTileData; } + + const Options& getOptions() const { return mOptions; } + + void setOptions(const Options& options); + + bool renderUI(Gui::Widgets& widget); + +private: + void prepareLighting(RenderContext* pRenderContext); + void prepareResources(); + void updatePrograms(); + + std::vector computeEnvLightLuminance(RenderContext* pRenderContext, const ref& texture); + std::unique_ptr buildEnvLightAliasTable(uint32_t width, uint32_t height, const std::vector& luminances, std::mt19937& rng); + std::unique_ptr buildEmissiveLightAliasTable(RenderContext* pRenderContext, const ref& lightCollection, std::mt19937& rng); + std::unique_ptr buildAnalyticLightAliasTable(RenderContext* pRenderContext, const std::vector>& lights, std::mt19937& rng); + + ref mpScene; + ref mpDevice; + Options mOptions; + + sigs::Connection mUpdateFlagsConnection; + IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; + + std::mt19937 mRng; + + uint32_t mFrameIndex = 0; + + ref mpReflectTypes; + ref mpGenerateLightTiles; + + ref mpEnvLightLuminance; + float mEnvLightLuminanceFactor = 0.f; + + std::unique_ptr mpEnvLightAliasTable; + std::unique_ptr mpEmissiveLightAliasTable; + std::unique_ptr mpAnalyticLightAliasTable; + + ref mpLightTileData; + + bool mRecompile = true; + + struct + { + float envLight = 0.f; + float emissiveLights = 0.f; + float analyticLights = 0.f; + + std::tuple getSampleCount(uint32_t totalCount) + { + uint32_t envCount = (uint32_t)std::floor(envLight * totalCount); + uint32_t emissiveCount = (uint32_t)std::floor(emissiveLights * totalCount); + uint32_t analyticCount = (uint32_t)std::floor(analyticLights * totalCount); + if (envCount > 0) + envCount = totalCount - emissiveCount - analyticCount; + else if (emissiveCount > 0) + emissiveCount = totalCount - envCount - analyticCount; + else if (analyticCount > 0) + analyticCount = totalCount - envCount - emissiveCount; + return {envCount, emissiveCount, analyticCount}; + } + } mLightSelectionProbabilities; +}; + +} // namespace Falcor diff --git a/Source/Modules/LightTileResampling/Lights.slang b/Source/Modules/LightTileResampling/Lights.slang new file mode 100644 index 0000000000..0535b96d6d --- /dev/null +++ b/Source/Modules/LightTileResampling/Lights.slang @@ -0,0 +1,351 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "Utils/Math/MathConstants.slangh" +import Scene.Scene; +import Utils.Geometry.GeometryHelpers; +import Utils.Math.MathHelpers; +import Utils.Math.PackedFormats; +import Utils.Color.ColorHelpers; +import Utils.Sampling.AliasTable; +__exported import Utils.Sampling.SampleGeneratorInterface; +__exported import LightSample; + +struct EvaluatedLightSample +{ + float3 dir; + float distance; + float geomFactor; + float emission; + float pdf; +}; + +struct PackedLoadedLightSample +{ + uint lightSample; + uint type; + uint normal; + uint emission; + uint3 posOrDir; + uint pdf; +}; + +using LightTileResampling; + +struct LoadedLightSample +{ + enum class Type + { + Invalid, + Area, + AreaWithLightProfile, + Point, + Distant, + }; + + LightSample lightSample; + Type type = Type::Invalid; + float3 posOrDir; + float3 normal; + float emission; + float pdf; + + EvaluatedLightSample eval(const float3 pos) + { + EvaluatedLightSample result = {}; + + result.emission = emission; + result.pdf = pdf; + + switch (type) + { + case Type::Area: + case Type::AreaWithLightProfile: + { + float3 toLight = posOrDir - pos; + float distSqr = max(FLT_MIN, dot(toLight, toLight)); + + result.distance = sqrt(distSqr); + result.dir = toLight / result.distance; + result.geomFactor = max(0.f, dot(normal, -result.dir)) / distSqr; + + if (gScene.materials.lightProfilesEnabled()) + { + if (type == Type::AreaWithLightProfile) + { + result.emission *= gScene.materials.evalLightProfile(dot(normal, -result.dir)); + } + } + } + break; + case Type::Point: + { + float3 toLight = posOrDir - pos; + float distSqr = max(FLT_MIN, dot(toLight, toLight)); + + result.distance = sqrt(distSqr); + result.dir = toLight / result.distance; + result.geomFactor = 1.f / distSqr; + } + break; + case Type::Distant: + { + result.distance = Lights::kRayTMax; + result.dir = posOrDir; + result.geomFactor = 1.f; + } + break; + } + + return result; + } + + static LoadedLightSample createFromPacked(const PackedLoadedLightSample packed) + { + LoadedLightSample lls; + lls.unpack(packed); + return lls; + } + + PackedLoadedLightSample pack() + { + PackedLoadedLightSample packed; + packed.lightSample = lightSample.pack(); + packed.type = uint(type); + packed.normal = encodeNormal2x16(normal); + packed.emission = asuint(emission); + packed.posOrDir = asuint(posOrDir); + packed.pdf = asuint(pdf); + return packed; + } + + [mutating] + void unpack(const PackedLoadedLightSample packed) + { + lightSample.unpack(packed.lightSample); + type = LoadedLightSample::Type(packed.type); + normal = decodeNormal2x16(packed.normal); + emission = asfloat(packed.emission); + posOrDir = asfloat(packed.posOrDir); + pdf = asfloat(packed.pdf); + } +}; + +struct Lights +{ + static const bool kUseEnvLight = USE_ENV_LIGHT; + static const bool kUseEmissiveLights = USE_EMISSIVE_LIGHTS; + static const bool kUseAnalyticLights = USE_ANALYTIC_LIGHTS; + + static const bool kUseEmissiveTextureForSampling = USE_EMISSIVE_TEXTURE_FOR_SAMPLING; + static const bool kUseEmissiveTextureForShading = USE_EMISSIVE_TEXTURE_FOR_SHADING; + + static const float kRayTMax = FLT_MAX; + static const float kRayEpsilon = 0.f; + + Buffer envLightLuminance; + + AliasTable envLightAliasTable; + AliasTable emissiveLightAliasTable; + AliasTable analyticLightAliasTable; + + float envLightLuminanceFactor; + + float envLightSelectionProbability; + float emissiveLightSelectionProbability; + float analyticLightSelectionProbability; + + LightSample sampleEnv(float random, inout S sg) + { + return LightSample::createEnv(envLightAliasTable.sample(float2(random, sampleNext1D(sg))), sg.next()); + } + + LightSample sampleEmissive(float random, inout S sg) + { + return LightSample::createEmissive(emissiveLightAliasTable.sample(float2(random, sampleNext1D(sg))), sg.next()); + } + + LightSample sampleAnalytic(float random, inout S sg) + { + return LightSample::createAnalytic(analyticLightAliasTable.sample(float2(random, sampleNext1D(sg))), sg.next()); + } + + LoadedLightSample loadLightSample(const LightSample lightSample) + { + if (lightSample.isValid()) + { + switch (lightSample.getKind()) + { + case LightKind::Env: + return loadEnvLightSample(lightSample); + case LightKind::Emissive: + return loadEmissiveLightSample(lightSample); + case LightKind::Analytic: + return loadAnalyticLightSample(lightSample); + } + } + return { lightSample }; + } + + LoadedLightSample loadFromPacked(const PackedLightSample packed) { return loadLightSample(LightSample::createFromPacked(packed)); } + + LoadedLightSample loadEnvLightSample(const LightSample lightSample) + { + LoadedLightSample result = { lightSample, LoadedLightSample::Type::Distant }; + + result.posOrDir = lightSample.getEnvWorldDir(); + float luminance = envLightLuminance[lightSample.getEnvIndex()]; + result.emission = luminance * envLightLuminanceFactor; + result.pdf = envLightSelectionProbability * luminance / envLightAliasTable.weightSum; + + return result; + } + + LoadedLightSample loadEmissiveLightSample(const LightSample lightSample) + { + LoadedLightSample result = { lightSample, LoadedLightSample::Type::Area }; + + const EmissiveTriangle tri = gScene.lightCollection.getTriangle(lightSample.getEmissiveIndex()); + result.posOrDir = computeRayOrigin(tri.getPosition(lightSample.getEmissiveBarycentrics()), tri.normal); + result.normal = tri.normal; + float radiance = luminance(gScene.lightCollection.getAverageRadiance(lightSample.getEmissiveIndex())); + result.emission = radiance; + result.pdf = emissiveLightSelectionProbability * radiance / emissiveLightAliasTable.weightSum; + + if (kUseEmissiveTextureForSampling) + { + float2 texCoord = tri.getTexCoord(lightSample.getEmissiveBarycentrics()); + result.emission = luminance(gScene.materials.evalEmissive(tri.materialID, texCoord)); + } + + if (gScene.materials.hasLightProfile(tri.materialID)) + { + result.type = LoadedLightSample::Type::AreaWithLightProfile; + } + + return result; + } + + LoadedLightSample loadAnalyticLightSample(const LightSample lightSample) + { + LoadedLightSample result = { lightSample }; + + LightData light = gScene.getLight(lightSample.getAnalyticIndex()); + LightType type = LightType(light.type); + if (type == LightType::Point) + { + result.type = LoadedLightSample::Type::Point; + result.posOrDir = light.posW; + result.emission = luminance(light.intensity); + result.pdf = analyticLightSelectionProbability / analyticLightAliasTable.weightSum; + } + else if (type == LightType::Directional) + { + result.type = LoadedLightSample::Type::Distant; + result.posOrDir = -light.dirW; + result.emission = luminance(light.intensity); + result.pdf = analyticLightSelectionProbability / analyticLightAliasTable.weightSum; + } + else if (type == LightType::Distant) + { + result.type = LoadedLightSample::Type::Distant; + float3 dir = sample_cone(lightSample.getAnalyticPosition(), light.cosSubtendedAngle); + result.posOrDir = normalize(mul((float3x3)light.transMat, dir)); + result.emission = luminance(light.intensity); + result.pdf = analyticLightSelectionProbability / analyticLightAliasTable.weightSum; + } + else + { + result.pdf = 1.f; + } + + return result; + } + + EvaluatedLightSample evalLightSample(const LightSample lightSample, const float3 pos) { return loadLightSample(lightSample).eval(pos); } + + float3 evalEmission(const LightSample lightSample, float3 dir) + { + if (lightSample.isValid()) + { + switch (lightSample.getKind()) + { + case LightKind::Env: + return evalEnvEmission(lightSample); + case LightKind::Emissive: + return evalEmissiveEmission(lightSample, dir); + case LightKind::Analytic: + return evalAnalyticEmission(lightSample); + } + } + return float3(0.f); + } + + float3 evalEnvEmission(const LightSample lightSample) + { + float3 dir = lightSample.getEnvWorldDir(); + return gScene.envMap.eval(dir); + } + + float3 evalEmissiveEmission(const LightSample lightSample, float3 dir) + { + uint emissiveIndex = lightSample.getEmissiveIndex(); + const EmissiveTriangle tri = gScene.lightCollection.getTriangle(emissiveIndex); + float3 emission; + if (kUseEmissiveTextureForShading) + { + float2 texCoord = tri.getTexCoord(lightSample.getEmissiveBarycentrics()); + emission = gScene.materials.evalEmissive(tri.materialID, texCoord); + } + else + { + emission = gScene.lightCollection.getAverageRadiance(emissiveIndex); + } + + if (gScene.materials.hasLightProfile(tri.materialID)) + { + emission *= gScene.materials.evalLightProfile(dot(tri.normal, dir)); + } + + return emission; + } + + float3 evalAnalyticEmission(const LightSample lightSample) + { + LightData light = gScene.getLight(lightSample.getAnalyticIndex()); + LightType type = LightType(light.type); + switch (LightType(light.type)) + { + case LightType::Point: + case LightType::Directional: + case LightType::Distant: + return light.intensity; + default: + return float3(0.f); + } + } +}; diff --git a/Source/Tools/FalcorTest/Tests/Sampling/PointSetsTests.cs.slang b/Source/Modules/LightTileResampling/ReflectTypes.cs.slang similarity index 82% rename from Source/Tools/FalcorTest/Tests/Sampling/PointSetsTests.cs.slang rename to Source/Modules/LightTileResampling/ReflectTypes.cs.slang index 317cb612a5..a92842fa2c 100644 --- a/Source/Tools/FalcorTest/Tests/Sampling/PointSetsTests.cs.slang +++ b/Source/Modules/LightTileResampling/ReflectTypes.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -25,18 +25,8 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -import Internal.Utils.Sampling.LowDiscrepancy.PointSets; +import Lights; -RWStructuredBuffer result; +StructuredBuffer lightTileData; -cbuffer CB -{ - uint m; -}; - -[numthreads(256, 1, 1)] -void testPermutedTms(uint3 threadId: SV_DispatchThreadID) -{ - const uint i = threadId.x; - result[i] = generatePermutedTms(i, m); -} +void main() {} diff --git a/Source/Falcor/Utils/ObjectIDPython.h b/Source/Modules/LightTileResampling/StreamingRIS.slang similarity index 65% rename from Source/Falcor/Utils/ObjectIDPython.h rename to Source/Modules/LightTileResampling/StreamingRIS.slang index f3282c6bc3..7269437da6 100644 --- a/Source/Falcor/Utils/ObjectIDPython.h +++ b/Source/Modules/LightTileResampling/StreamingRIS.slang @@ -25,38 +25,40 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -#pragma once +__exported import Utils.Sampling.SampleGeneratorInterface; +import Lights; -#include "ObjectID.h" -#include +using LightTileResampling; -namespace pybind11::detail +struct RisState { -template -struct type_caster> -{ - using ObjectID = Falcor::ObjectID; - -public: - PYBIND11_TYPE_CASTER(ObjectID, const_name("ObjectID")); + LightSample lightSample; + float weightSum; + float M; + float weight; - bool load(handle src, bool) + static RisState createEmpty() { - PyObject* source = src.ptr(); - PyObject* tmp = PyNumber_Long(source); - if (!tmp) - return false; + RisState state = {}; + state.lightSample = LightSample::createInvalid(); + return state; + } +}; - typename ObjectID::IntType idValue = PyLong_AsUnsignedLong(tmp); - Py_DECREF(tmp); +bool streamingResampleStep(inout RisState state, const LightSample lightSample, float targetPdf, float sourcePdf, inout S sg) +{ + float sampleWeight = targetPdf / sourcePdf; - value = (idValue == ObjectID::kInvalidID) ? ObjectID() : ObjectID(idValue); - return !PyErr_Occurred(); - } + state.weightSum += sampleWeight; + state.M += 1.f; + + bool selectSample = sampleNext1D(sg) * state.weightSum < sampleWeight; - static handle cast(const ObjectID& src, return_value_policy /* policy */, handle /* parent */) + if (selectSample) { - return PyLong_FromUnsignedLong(src.get()); + state.lightSample = lightSample; + state.weight = targetPdf; } -}; -} // namespace pybind11::detail + + return selectSample; +} diff --git a/Source/Modules/ReSTIRPathTracing/CMakeLists.txt b/Source/Modules/ReSTIRPathTracing/CMakeLists.txt new file mode 100644 index 0000000000..5ccd2744d6 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/CMakeLists.txt @@ -0,0 +1,119 @@ +add_library(ReSTIRPathTracing STATIC) + +file(GLOB_RECURSE PAIRED_REUSE_PATTERN_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/PairedReusePattern/*.*" +) + +target_sources(ReSTIRPathTracing PRIVATE + ReSTIRPathTracing.cpp + ReSTIRPathTracing.h + PathReservoir.slang + ReflectTypes.cs.slang + ReSTIRPT.slang + ReSTIRPathTracingParams.slang + LoadShadingDataDynamic.slang + SpatialResampling.cs.slang + SpatialPathRetrace.cs.slang + SpatialPrecomputeShiftBuffer.cs.slang + TemporalResampling.cs.slang + TemporalPathRetrace.cs.slang + GenerateRetraceWorkload.cs.slang + HybridShiftTraceRays.slang + PathTracer.slang + Shift.slang + RetraceWorkQueue.slang + RetraceScheduleDefinition.slangh + FetchDenoiserData.cs.slang + FillSampleID.cs.slang + ComputeDuplicationMap.cs.slang + ResamplingCommon.slang + SpatialTraceShiftedPrimaryHits.cs.slang + TemporalTraceShiftedPrimaryHits.cs.slang + TemporalRobustCreateShiftVBuffer.cs.slang + TemporalRobustPathRetrace.cs.slang + TemporalRobustProduceWorkload.cs.slang + TemporalRobustResampling.cs.slang + TemporalPopulateFloatingReservoir.cs.slang + ComputeReuseMotionVector.cs.slang + TemporalReservoirSplatting.cs.slang + PairedNeighborTransform.slang + ${PAIRED_REUSE_PATTERN_SOURCES} +) + +foreach(file IN LISTS PAIRED_REUSE_PATTERN_SOURCES) + file(RELATIVE_PATH rel_path "${CMAKE_CURRENT_SOURCE_DIR}" "${file}") + get_filename_component(dir "${rel_path}" DIRECTORY) + string(REPLACE "/" "\\" group "${dir}") + source_group("${group}" FILES "${file}") +endforeach() + + +target_copy_shaders(ReSTIRPathTracing ReSTIRPathTracing) + +# Apply source grouping before adding generated files +target_source_group(ReSTIRPathTracing "Modules/") + +function(target_copy_LUTs target output_dir) + get_target_property(target_source_dir ${target} SOURCE_DIR) + if(NOT target_source_dir) + set(target_source_dir "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + set(shader_output_dir "${FALCOR_SHADER_OUTPUT_DIRECTORY}/${output_dir}") + + # Find all PNG files in PairedReusePattern directory + file(GLOB_RECURSE PAIRED_REUSE_PATTERN_IMAGES + "${target_source_dir}/PairedReusePattern/*.png" + "${target_source_dir}/PairedReusePattern/*.PNG" + ) + + # Generate corresponding output file paths + set(OUTPUT_FILES "") + foreach(src_file ${PAIRED_REUSE_PATTERN_IMAGES}) + file(RELATIVE_PATH rel_path "${target_source_dir}" "${src_file}") + set(dst_file "${shader_output_dir}/${rel_path}") + list(APPEND OUTPUT_FILES "${dst_file}") + endforeach() + + # Create custom command that only runs when source files are newer than outputs + add_custom_command( + OUTPUT ${OUTPUT_FILES} + COMMAND "${CMAKE_COMMAND}" -E make_directory "${shader_output_dir}" + COMMAND "${CMAKE_COMMAND}" + -D SRC:PATH=${target_source_dir} + -D DST:PATH=${shader_output_dir} + -P "${CMAKE_CURRENT_LIST_DIR}/copy_luts.cmake" + DEPENDS ${PAIRED_REUSE_PATTERN_IMAGES} + COMMENT "${target}: Copying LUTs" + VERBATIM + ) + + # Add the output files as sources to the target so it tracks them + target_sources(${target} PRIVATE ${OUTPUT_FILES}) + set_source_files_properties(${OUTPUT_FILES} PROPERTIES + GENERATED TRUE + HEADER_FILE_ONLY TRUE + SKIP_AUTOGEN TRUE + ) + # Put generated files in a separate source group to avoid path issues + source_group("Generated/LUTs" FILES ${OUTPUT_FILES}) +endfunction() + +target_copy_LUTs(ReSTIRPathTracing ReSTIRPathTracing) + +target_link_libraries(ReSTIRPathTracing + PUBLIC + Falcor +) + +target_include_directories(ReSTIRPathTracing + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +set_target_properties(ReSTIRPathTracing + PROPERTIES + POSITION_INDEPENDENT_CODE ON + LIBRARY_OUTPUT_DIRECTORY ${FALCOR_RUNTIME_OUTPUT_DIRECTORY} +) + +validate_headers(ReSTIRPathTracing) diff --git a/Source/Modules/ReSTIRPathTracing/ComputeDuplicationMap.cs.slang b/Source/Modules/ReSTIRPathTracing/ComputeDuplicationMap.cs.slang new file mode 100644 index 0000000000..f2c6f14f70 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/ComputeDuplicationMap.cs.slang @@ -0,0 +1,95 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import PathReservoir; +import ReSTIRPT; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; + +groupshared uint sampleIDs[32][32]; + +struct Pass +{ + ReSTIRPathTracingParams params; + Texture2D sampleIDTexture; + RWTexture2D duplicationMap; + void execute(const uint2 pixel, const uint2 blockPixel, const uint2 blockId, const uint blockIndex) + { + if (any(pixel >= params.frameDim)) return; + + // local memory + int2 topLeftPixel = 16 * blockId.xy; + + // [-8, 23] x [-8, 23] window around top Left Pixel + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) + { + int2 readPixel = int2(topLeftPixel.x - 8 + 2 * blockPixel.x + j, topLeftPixel.y - 8 + 2 * blockPixel.y + i); + sampleIDs[2 * blockPixel.y + i][2 * blockPixel.x + j] = + (any(readPixel >= params.frameDim || readPixel < 0)) ? 0 : sampleIDTexture[readPixel]; + } + + GroupMemoryBarrierWithGroupSync(); + + // check 17x17 neighborhood + + uint ownSample = sampleIDs[blockPixel.y + 8][blockPixel.x + 8]; + uint dupCount = 0; + + if (ownSample != 0) + { + for (int i = -8; i <= 8; i++) + for (int j = -8; j <= 8; j++) + { + if (any(pixel + int2(j, i) >= params.frameDim || pixel + int2(j, i) < int2(0, 0))) continue; + uint neighborSample = sampleIDs[blockPixel.y + i + 8][blockPixel.x + j + 8]; + if (ownSample == neighborSample && (i != 0 || j != 0)) + { + dupCount++; + } + } + } + + duplicationMap[pixel] = dupCount; + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(16, 16, 1)] +void main( uint3 dispatchThreadId: SV_DispatchThreadID, uint3 groupThreadID: SV_GroupThreadID, uint3 groupID: SV_GroupID, uint groupIndex: SV_GroupIndex) +{ + uint2 pixel = dispatchThreadId.xy; + if (any(pixel >= gPass.params.frameDim)) return; + printSetPixel(pixel); + logSetPixel(pixel); + + gPass.execute(dispatchThreadId.xy, groupThreadID.xy, groupID.xy, groupIndex); +} diff --git a/Source/Modules/ReSTIRPathTracing/ComputeReuseMotionVector.cs.slang b/Source/Modules/ReSTIRPathTracing/ComputeReuseMotionVector.cs.slang new file mode 100644 index 0000000000..bee6fd63e9 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/ComputeReuseMotionVector.cs.slang @@ -0,0 +1,154 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import ResamplingCommon; + +struct Pass +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + Texture2D temporalVBuffer; + Texture2D originalMotionVectors; + Texture2D deltaReflectionMotionVectors; + Texture2D deltaTransmissionMotionVectors; + RWTexture2D motionVectors; + StructuredBuffer prevReservoirs; + RWStructuredBuffer mergedPrevReservoirs; + void DoPass(const int2 pixel) + { + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.specMVecSgOffset()); + + float2 motionVector = originalMotionVectors[pixel]; + + HitInfo centralPrimaryHit; + ShadingData centralPrimarySd = getPixelShadingData(params, vbuffer, pixel, centralPrimaryHit); + + if (!centralPrimaryHit.isValid()) + { + motionVectors[pixel] = motionVector; + return; + } + + // Temporally Reliable Motion Vectors for Real-time Ray Tracing [Zeng et al. 2021] + if (restirpt.enableDisocclusionMotionVectors) + { + int2 prevPixel = pixel + motionVector * params.frameDim + 0.5; + + HitInfo prevPrimaryHit; + ShadingData prevPrimarySd = getPixelTemporalShadingData(params, temporalVBuffer, prevPixel, prevPrimaryHit); + + if (!isValidScreenRegion(params, prevPixel)) // zero motion vector + { + motionVector = -motionVector; + } + else if (prevPrimaryHit.isValid() && isReprojectedToOccluder(centralPrimarySd, prevPrimarySd)) + { + VertexData curFrameVertexData = getVertexData(prevPrimaryHit, 0.f, 0.f); + float3 occluderCurrPosW = curFrameVertexData.posW; + float4 currPosH = mul(gScene.camera.data.viewProjMatNoJitter, float4(occluderCurrPosW, 1.f)); + float2 currCrd = currPosH.xy / currPosH.w; + #ifdef FALCOR_FLIP_Y + currCrd *= float2(0.5, 0.5); + #else + currCrd *= float2(0.5, -0.5); + #endif + currCrd += 0.5f; + float2 occluderPixelPos = currCrd * params.frameDim; + motionVector = float2(-(occluderPixelPos - (prevPixel+0.5))) / params.frameDim; + } + } + + const IMaterialInstance centralMi = gScene.materials.getMaterialInstance( centralPrimarySd, ExplicitLodTextureSampler(0.f), getMaterialInstanceHints(centralPrimaryHit, true)); + + // if rcVertexLength == 1, we know from reservoir + // otherwise, we replay the initRandomSeed to see what we sample + float2 motionVectorSelectionProbabilities = centralMi.getReflectionMotionVectorProbabilities(centralPrimarySd); + float u = sampleNext1D(sg); + + // has both diffuse and refraction -> disable diffuse + if (motionVectorSelectionProbabilities.x > 0.f && motionVectorSelectionProbabilities.x + motionVectorSelectionProbabilities.y < 0.99f) + { + motionVectorSelectionProbabilities.y = motionVectorSelectionProbabilities.y / (1.f - motionVectorSelectionProbabilities.x); + motionVectorSelectionProbabilities.x = 0.f; + } + + + bool isSmooth = restirpt.enableSpecularMotionVectors && centralMi.getProperties(centralPrimarySd).roughness <= restirpt.specularMotionVectorRoughnessThreshold; + + if (!isSmooth) + motionVectorSelectionProbabilities = float2(1.f, 0.f); + + if (u < motionVectorSelectionProbabilities.x) + {} // do nothing + else if (u < motionVectorSelectionProbabilities.x + motionVectorSelectionProbabilities.y) + { + motionVector = deltaReflectionMotionVectors[pixel]; + } + else + { + motionVector = deltaTransmissionMotionVectors[pixel]; + } + motionVectors[pixel] = motionVector; + + } + + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gPass.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/FetchDenoiserData.cs.slang b/Source/Modules/ReSTIRPathTracing/FetchDenoiserData.cs.slang new file mode 100644 index 0000000000..66b1ff533e --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/FetchDenoiserData.cs.slang @@ -0,0 +1,259 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import ResamplingCommon; +import Rendering.Materials.IsotropicGGX; +import RenderPasses.Shared.Denoising.NRDConstants; + +#define is_valid(name) (is_valid_##name != 0) +struct FetchDenoiserData +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + Texture2D vbuffer; + StructuredBuffer reservoirs; + RWStructuredBuffer sampleGuideData; + Texture2D outputColor; + + RWTexture2D outputEmission; + RWTexture2D outputDiffuseRadianceHitDist; + RWTexture2D outputSpecularRadianceHitDist; + + RWTexture2D outputDeltaReflectionRadianceHitDist; + RWTexture2D outputDeltaTransmissionRadianceHitDist; + RWTexture2D outputResidualRadianceHitDist; + + void writeZeroValuesToBuffers(int2 pixel) + { + outputDeltaReflectionRadianceHitDist[pixel] = 0.f; + outputDeltaTransmissionRadianceHitDist[pixel] = 0.f; + outputResidualRadianceHitDist[pixel] = 0.f; + outputDiffuseRadianceHitDist[pixel] = 0.f; + outputSpecularRadianceHitDist[pixel] = 0.f; + } + + void DoPass(const uint2 pixel) + { + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = reservoirs[centralOffset]; + + Ray centralPrimaryRay; + HitInfo centralPrimaryHit; + ShadingData centralPrimarySd; + if (restirpt.kUseAreaReservoirs) + { + centralPrimaryRay = gScene.camera.computeRayThinlensSubPixelRandom(pixel, params.frameDim, centralReservoir.filmSample, centralReservoir.lensSample); + centralPrimaryHit = retracePrimaryRay(pixel, params.frameDim, centralReservoir.filmSample, centralReservoir.lensSample, false); + if (centralPrimaryHit.isValid()) centralPrimarySd = loadShadingData(centralPrimaryHit, centralPrimaryRay.origin, centralPrimaryRay.dir, false); + } + else + { + centralPrimaryRay = computeRayPredefinedThinLens(pixel, params.frameDim, params.frameCount); + centralPrimarySd = getPixelShadingData(params, vbuffer, pixel, centralPrimaryHit); + } + if (!centralPrimaryHit.isValid()) + { + if (FETCH_NRD_DATA_PASS) + { + writeZeroValuesToBuffers(pixel); + outputEmission[pixel] = restirpt.kUseAreaReservoirs ? float4(centralReservoir.weight * centralReservoir.integrand,0.f) : outputColor[pixel]; + } + return; + } + // Create material instance and query its properties. + let lod = ExplicitLodTextureSampler(0.f); + let hints = getMaterialInstanceHints(centralPrimaryHit, true); + let mi = gScene.materials.getMaterialInstance(centralPrimarySd, lod, hints); + let bsdfProperties = mi.getProperties(centralPrimarySd); + + // guide data should agree with the guide primary hit sent to the denoiser, if area reservoirs are used + if (restirpt.kUseAreaReservoirs && OUTPUT_GUIDE_DATA) + { + // Use sum of reflection/transmission albedo as they are denoised together. + sampleGuideData[centralOffset].setAlbedo(bsdfProperties.diffuseReflectionAlbedo + bsdfProperties.diffuseTransmissionAlbedo); + const float NdotV = saturate(dot(bsdfProperties.guideNormal, centralPrimarySd.V)); + const float ggxAlpha = bsdfProperties.roughness * bsdfProperties.roughness; + if (centralPrimarySd.mtl.getMaterialType() == MaterialType::Hair) + { + const float3 preintegratedSpecular = approxSpecularIntegralGGX(float3(0.04f), ggxAlpha, NdotV); + sampleGuideData[centralOffset].setSpecularAlbedo(preintegratedSpecular); + } + else + { + // Use pre-integrated FG term for specular + const float3 preintegratedSpecular = approxSpecularIntegralGGX(bsdfProperties.specularReflectance, ggxAlpha, NdotV); + sampleGuideData[centralOffset].setSpecularAlbedo(preintegratedSpecular); + } + } + + if (centralReservoir.weight == 0.f) + { + if (FETCH_NRD_DATA_PASS) + { + writeZeroValuesToBuffers(pixel); + outputEmission[pixel] = 0.f; + } + return; + } + + float3 sampleReflectance = 0.f; + float3 deltaSampleEmission = 0.f; + float hitDist = 0.f; + + bool isPrimaryBounceDelta = false; + bool isPrimaryBounceTransmission = false; + + float3 deltaReflectance = 0.f; + float3 emissionAlongDeltaPath = 0.f; + if (centralReservoir.pathFlags.rcVertexLength() == 1) + { + // this also writes hitDist sampled by diffuse BRDF on a smooth surface, but does not reduce the quality + const IMaterialInstance centralMi = gScene.materials.getMaterialInstance(centralPrimarySd, ExplicitLodTextureSampler(0.f)); + if (centralMi.getProperties(centralPrimarySd).roughness < params.specularRoughnessThreshold) + { + hitDist = length(loadVertexPosition(unpackReservoirHitInfo(centralReservoir.reconnectionHit)) - centralPrimarySd.posW); + } + } + else + { + hitDist = TraceDenoiserReplayPath( centralPrimaryHit, + centralPrimarySd, + centralReservoir.pathFlags, + centralReservoir.initRandomSeed, + isPrimaryBounceDelta, + isPrimaryBounceTransmission, + deltaReflectance, + emissionAlongDeltaPath); + } + + // NRD Data + if (FETCH_NRD_DATA_PASS) + { + if (isPrimaryBounceDelta) + { + float3 color = centralReservoir.integrand * centralReservoir.weight - emissionAlongDeltaPath; + float3 deltaReflectionIllumination = isPrimaryBounceTransmission ? 0 : color / deltaReflectance; + float3 deltaTransmissionIllumination = isPrimaryBounceTransmission ? color / deltaReflectance : 0; + float3 residual = isPrimaryBounceTransmission && all(deltaReflectance == 0.f) ? color : 0.f; + + outputResidualRadianceHitDist[pixel] = float4(residual, 0.f); + outputDeltaReflectionRadianceHitDist[pixel] = float4(deltaReflectionIllumination, 0.f); // hitDist in provided by nrdDeltaReflectionHitDist + outputDeltaTransmissionRadianceHitDist[pixel] = float4(deltaTransmissionIllumination, 0.f); + outputDiffuseRadianceHitDist[pixel] = 0.f; + outputSpecularRadianceHitDist[pixel] = 0.f; + outputEmission[pixel] = 0.f; + } + else + { + // Demodulate diffuse reflectance (albedo) from diffuse color. + float3 diffuseReflectance = bsdfProperties.diffuseReflectionAlbedo + bsdfProperties.diffuseTransmissionAlbedo; + float3 diffuseFactor = select(diffuseReflectance <= 0.f, 0.f, 1.f / diffuseReflectance); + + // Demodulate preintegrated specular reflectance from specular color. + // We use an approximate shading model with a single specular lobe. + float NdotV = saturate(dot(bsdfProperties.guideNormal, centralPrimarySd.V)); + float ggxAlpha = bsdfProperties.roughness * bsdfProperties.roughness; + float3 specularReflectance = max(kNRDMinReflectance, approxSpecularIntegralGGX(bsdfProperties.specularReflectionAlbedo, ggxAlpha, NdotV)); + + float3 diffuseBSDF = diffuseReflectance; + float3 specularBSDF = specularReflectance; + + float3 totalBSDF = diffuseBSDF + specularBSDF; + + float3 reflectingRadiance = restirpt.kUseAreaReservoirs ? + max(0.f, centralReservoir.integrand * centralReservoir.weight - bsdfProperties.emission) : + centralReservoir.integrand * centralReservoir.weight; + + float3 diffuse = diffuseBSDF / totalBSDF * reflectingRadiance; + float3 specular = specularBSDF / totalBSDF * reflectingRadiance; + if (totalBSDF.x == 0.f) + { + diffuse.x = 0.f; + specular.x = 0.f; + } + if (totalBSDF.y == 0.f) + { + diffuse.y = 0.f; + specular.y = 0.f; + } + if (totalBSDF.z == 0.f) + { + diffuse.z = 0.f; + specular.z = 0.f; + } + + float3 diffuseIllumination = diffuse * diffuseFactor; + + float3 specularFactor = select(specularReflectance <= 0.f, 0.f, 1.f / specularReflectance); + float3 specularIllumination = specular * specularFactor; + + outputEmission[pixel] = float4(bsdfProperties.emission, 1.f); + outputDiffuseRadianceHitDist[pixel] = float4(diffuseIllumination, hitDist); + outputSpecularRadianceHitDist[pixel] = float4(specularIllumination, hitDist); + + outputDeltaReflectionRadianceHitDist[pixel] = 0.f; + outputDeltaTransmissionRadianceHitDist[pixel] = 0.f; + outputResidualRadianceHitDist[pixel] = 0.f; + } + } + + } + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + } +}; + +cbuffer CB +{ + FetchDenoiserData gFetchDenoiserData; +} + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gFetchDenoiserData.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/FillSampleID.cs.slang b/Source/Modules/ReSTIRPathTracing/FillSampleID.cs.slang new file mode 100644 index 0000000000..e6fbd7b476 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/FillSampleID.cs.slang @@ -0,0 +1,58 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import PathReservoir; +import ReSTIRPT; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; + +struct Pass +{ + ReSTIRPathTracingParams params; + StructuredBuffer reservoirs; + RWTexture2D sampleIDTexture; + void execute(const uint2 pixel) + { + const uint offset = params.getReservoirOffset(pixel); + sampleIDTexture[pixel] = reservoirs[offset].weight == 0 ? 0 : reservoirs[offset].initRandomSeed; + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + uint2 pixel = dispatchThreadId.xy; + if (any(pixel >= gPass.params.frameDim)) return; + printSetPixel(pixel); + logSetPixel(pixel); + gPass.execute(pixel); +} diff --git a/Source/Modules/ReSTIRPathTracing/GenerateRetraceWorkload.cs.slang b/Source/Modules/ReSTIRPathTracing/GenerateRetraceWorkload.cs.slang new file mode 100644 index 0000000000..253c2df75f --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/GenerateRetraceWorkload.cs.slang @@ -0,0 +1,324 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Utils.Attributes; +import Utils.Color.ColorHelpers; +import Utils.Math.MathHelpers; +import ReSTIRPathTracing.ReSTIRPT; +import LoadShadingDataDynamic; +import RetraceWorkQueue; +import Scene.HitInfo; +import PathReservoir; +import Utils.Sampling.TinyUniformSampleGenerator; +import ResamplingCommon; +import PairedNeighborTransform; +import Utils.Debug.PixelDebug; + +static const uint kWarpCount = (kScreenTileDim.x * kScreenTileDim.y) / 32; +groupshared uint gRetraceWorkOffset[8 /* kWarpCount */]; + +struct RetraceWorkloadGenerator +{ + static const uint kNeighborOffsetCount = NEIGHBOR_OFFSET_COUNT; + static const uint kNeighborOffsetMask = kNeighborOffsetCount - 1; + + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + Texture2D temporalVbuffer; + Texture1D neighborOffsets; + Texture2D motionVectors; + + StructuredBuffer shiftedPrimaryHitBuffer; + + StructuredBuffer prevReservoirs; + StructuredBuffer reservoirs; + + RWStructuredBuffer neighborValidMask; + StructuredBuffer splatSampleInfo; + bool gEnableTemporalReprojection; + bool gNoResamplingForTemporalReuse; + uint gTemporalHistoryLength; + + int gSpatialRoundId; + int gNeighborCount; + float gGatherRadius; + + bool isSpatialPass; + bool gStochasticReprojection; + bool resetTemporalReservoirs; + bool areaReservoirUseNeighborRejection; + bool gUsePairedNeighbors; + + int temporalPassId; + + RetraceWorkQueue queues; + + int getNeighborCount() + { + return isSpatialPass ? gNeighborCount : 1; + } + + void execute(const uint2 tileID, const uint threadIdx) + { + // Map thread to pixel based on Morton order within tile. + // The tiles themselves are enumerated in scanline order on screen. + const uint2 tileOffset = tileID << kScreenTileBits; // Tile offset in pixels. + const uint2 pixel = deinterleave_8bit(threadIdx) + tileOffset; // Assumes 16x16 tile or smaller. A host-side assert checks this assumption. + + uint retraceWorkItemCount = 0; + uint retraceCandidateMask = 0; + + int neighborCount = getNeighborCount(); + + // Note: Do not terminate threads for out-of-bounds pixels because we need all threads active for the prefix sum pass below. + if (all(pixel < params.frameDim)) + { + var sg = TinyUniformSampleGenerator( pixel, restirpt.sgCount() * params.seed + restirpt.spatialOrTemporalSgOffset(isSpatialPass, gSpatialRoundId, temporalPassId)); + const uint centralOffset = params.getReservoirOffset(pixel); + + HitInfo centralPrimaryHit; + ShadingData centralPrimarySd = getPixelShadingData(params, vbuffer, pixel, centralPrimaryHit); + + if (centralPrimaryHit.isValid() || restirpt.kUseAreaReservoirs) + { + if (isSpatialPass) + { + float spatialRadius = gGatherRadius; + + PathReservoir centralReservoir = prevReservoirs[centralOffset]; + + float cSum = centralReservoir.M; + + float doFReconnectionShiftProbability = restirpt.doFReconnectionShiftProbability; + if (restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && restirpt.deriveDoFReconnectionShiftProbabilityFromCoC && gUsePairedNeighbors) + { + doFReconnectionShiftProbability = + 1.f - computeConfidenceWeightForTwoShifts(params.frameDim, centralPrimarySd.posW, !centralPrimaryHit.isValid() ); + } + + int doFReconnectionShiftCount = restirpt.useDoFReconnectionShift ? round(doFReconnectionShiftProbability * neighborCount) : 0; + + const uint startIndex = sampleNext1D(sg) * kNeighborOffsetCount; + var sharedSg = TinyUniformSampleGenerator( params.frameDim + 1, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId) ); + PairedNeighborTransform transform = PairedNeighborTransform(sharedSg, params.frameDim.x, params.frameDim.y); + + for (int i = 0; i < neighborCount; ++i) + { + int2 neighborPixel; + + if (gUsePairedNeighbors) + { + int dx, dy; + transform.readTransformedDelta(i, pixel.x, pixel.y, dx, dy); + neighborPixel = pixel + int2(dx, dy); + } + else + { + neighborPixel = getNextNeighborPixel(startIndex, neighborOffsets, pixel, i, spatialRadius); + } + + if (!isValidScreenRegion(params, neighborPixel)) + { + neighborValidMask[centralOffset].setValid(i, false); + continue; + } + + PathReservoir neighborReservoir = prevReservoirs[params.getReservoirOffset(neighborPixel)]; + + HitInfo neighborPrimaryHit; + ShadingData neighborPrimarySd = getPixelShadingData(params, vbuffer, neighborPixel, neighborPrimaryHit); + + if (restirpt.kUseAreaReservoirs && areaReservoirUseNeighborRejection) + { + if (centralPrimaryHit.isValid() != neighborPrimaryHit.isValid() || + centralPrimaryHit.isValid() && !isValidGeometry(centralPrimarySd, neighborPrimarySd, gUsePairedNeighbors)) + { + neighborValidMask[centralOffset].setValid(i, false); + continue; + } + } + + if (!restirpt.kUseAreaReservoirs && + (!neighborPrimaryHit.isValid() || !isValidGeometry(centralPrimarySd, neighborPrimarySd, gUsePairedNeighbors))) + { + neighborValidMask[centralOffset].setValid(i, false); + continue; + } + + if (restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && restirpt.deriveDoFReconnectionShiftProbabilityFromCoC && gUsePairedNeighbors) + { + float neighborDoFReconnectionShiftProbability = + 1.f - computeConfidenceWeightForTwoShifts(params.frameDim, neighborPrimarySd.posW, !neighborPrimaryHit.isValid() ); + int neighborDoFReconnectionShiftCount = round(neighborDoFReconnectionShiftProbability * neighborCount); + if ((i < doFReconnectionShiftCount) != (i < neighborDoFReconnectionShiftCount)) + { + neighborValidMask[centralOffset].setValid(i, false); + continue; + } + } + + neighborValidMask[centralOffset].setValid(i, true); + + cSum += neighborReservoir.M; + + if (ReSTIRPT::ShiftMapping(restirpt.kShiftMapping) == ReSTIRPT::ShiftMapping::Hybrid) + { + if (!gUsePairedNeighbors) + { + int p1 = int(centralReservoir.pathFlags.rcVertexLength() > 1); + if (restirpt.kUseAreaReservoirs) p1 = HitInfo(shiftedPrimaryHitBuffer[2 * neighborCount * centralOffset + 2 * i]).isValid() ? p1 : 0; + retraceWorkItemCount += p1; + retraceCandidateMask |= p1 << (2 * i); + } + int p2 = int(neighborReservoir.pathFlags.rcVertexLength() > 1); + if (restirpt.kUseAreaReservoirs) p2 = HitInfo(shiftedPrimaryHitBuffer[(gUsePairedNeighbors ? 1 : 2) * neighborCount * centralOffset + + (gUsePairedNeighbors ? i : 2 * i + 1)]).isValid() ? p2 : 0; + retraceWorkItemCount += p2; + retraceCandidateMask |= p2 << (2 * i + 1); + } + } + + neighborValidMask[centralOffset].setCSum(cSum); + } + else + { + + PathReservoir centralReservoir = reservoirs[centralOffset]; + + float2 motionVector = gEnableTemporalReprojection ? motionVectors[pixel] : 0.f; + float2 prevPixel = reprojectPixelForReuse(pixel, motionVector, params.frameDim, + restirpt.useAreaReSTIRBackprojection, gStochasticReprojection, sg); + bool isValid = true; + + if (!restirpt.useReservoirSplatting) + { + if (!restirpt.kUseAreaReservoirs) + { + HitInfo temporalPrimaryHit = getPixelTemporalHitInfo(params, temporalVbuffer, prevPixel); + isValid = temporalPrimaryHit.isValid(); + neighborValidMask[centralOffset].setValid(0, isValid); + } + else + { + if ((!restirpt.useAreaReSTIRBackprojection && !isValidScreenRegion(params, int2(prevPixel)) || + restirpt.useAreaReSTIRBackprojection && !isValidScreenRegionFloat(params, prevPixel))) isValid = false; + } + } + + if (ReSTIRPT::ShiftMapping(restirpt.kShiftMapping) == ReSTIRPT::ShiftMapping::Hybrid) + { + PathReservoir temporalReservoir = prevReservoirs[params.getReservoirOffset(restirpt.useAreaReSTIRBackprojection ? pixel : int2(prevPixel))]; + + bool isValid1 = isValid; + bool isValid2 = isValid; + if (restirpt.useReservoirSplatting) + { + isValid1 = splatSampleInfo[2 * centralOffset].z > 0.f; // Jacobian > 0 + isValid2 = splatSampleInfo[2 * centralOffset + 1].z > 0.f; + } + + int p1 = int(centralReservoir.pathFlags.rcVertexLength() > 1) && isValid1; + int p2 = int(temporalReservoir.pathFlags.rcVertexLength() > 1) && isValid2; + + if (temporalPassId == 0) + { + retraceWorkItemCount += p1; + retraceCandidateMask |= p1; + retraceWorkItemCount += p2; + retraceCandidateMask |= p2 << 1; + } + else + { + float doFReconnectionShiftProbability = restirpt.deriveDoFReconnectionShiftProbabilityFromCoC ? + 1.f - computeConfidenceWeightForTwoShifts( params.frameDim, centralPrimarySd.posW, !centralPrimaryHit.isValid() ) : + restirpt.doFReconnectionShiftProbability; + doFReconnectionShiftProbability = restirpt.useDoFReconnectionForTemporalReuse ? doFReconnectionShiftProbability : 0.f; + bool cameraChanged = hasCameraChanged(); + if (isBorderPixel(params, pixel) && !cameraChanged) doFReconnectionShiftProbability = 0.f; + + if (restirpt.useDoFReconnectionShift && doFReconnectionShiftProbability > 0.f) + { + retraceWorkItemCount += p1; + retraceCandidateMask |= p1; + + retraceWorkItemCount += p2; + retraceCandidateMask |= p2 << 1; + } + } + } + } + } + } + + const uint warpIdx = threadIdx >> 5; + + uint retraceWorkItems = WaveActiveSum(retraceWorkItemCount); + + if (WaveIsFirstLane()) + { + gRetraceWorkOffset[warpIdx] = retraceWorkItems; + } + GroupMemoryBarrierWithGroupSync(); + + if (threadIdx < kWarpCount) + { + uint retraceWorkItems = gRetraceWorkOffset[threadIdx]; + gRetraceWorkOffset[threadIdx] = queues.addCounter(retraceWorkItems); + } + GroupMemoryBarrierWithGroupSync(); + + if (all(pixel < params.frameDim)) + { + uint encodedRetraceWorkItem = (pixel.y << 12) | pixel.x; + uint dstIdx = gRetraceWorkOffset[warpIdx] + WavePrefixSum(retraceWorkItemCount); + int j = 0; + for (uint i = 0; i < 2 * neighborCount; i++) + { + if (retraceCandidateMask >> i & 1) + { + queues.retraceWorkItems.Store((dstIdx + j) * 4, encodedRetraceWorkItem + (i << 24)); + j++; + } + } + } + } +} + +cbuffer CB +{ + RetraceWorkloadGenerator gRetraceWorkloadGenerator; +} + +[numthreads(256, 1, 1)] +void main( + uint3 groupID: SV_GroupID, + uint3 groupThreadID: SV_GroupThreadID) +{ + gRetraceWorkloadGenerator.execute(groupID.xy, groupThreadID.x); +} diff --git a/Source/Modules/ReSTIRPathTracing/HybridShiftTraceRays.slang b/Source/Modules/ReSTIRPathTracing/HybridShiftTraceRays.slang new file mode 100644 index 0000000000..0b4c557943 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/HybridShiftTraceRays.slang @@ -0,0 +1,344 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "Utils/Math/MathConstants.slangh" + +import PathReservoir; +import Utils.Sampling.SampleGenerator; +import LoadShadingDataDynamic; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Scene.RaytracingInline; +import Scene.Scene; +import PathTracer; +import RenderPasses.Shared.Denoising.NRDData; +import Rendering.Lights.EmissiveLightSampler; + +struct ClosestHitQuery : IClosestHitQueryDynamic +{ + HitInfo traceRay(inout PathState path, const Ray ray, out float hitT, bool isPrevFrame) + { + SceneRayQuery sceneRayQuery; + return sceneRayQuery.traceRay(ray, hitT, RAY_FLAG_NONE, 0xff, isPrevFrame); + } +}; + +struct VisibilityQuery : IVisibilityQueryDynamic +{ + bool traceVisibilityRay(const Ray ray, bool isPrevFrame) + { + SceneRayQuery sceneRayQuery; + return sceneRayQuery.traceVisibilityRay(ray, RAY_FLAG_NONE, 0xff, isPrevFrame); + } +}; + +float3 TraceReplayedPath(HitInfo dstPrimaryHit, ShadingData dstPrimarySd, + ReSTIRPathFlags pathFlags, uint initRandomSeed, inout HitInfo rcPrevHit, inout float3 rcPrevHitWo, inout float rcFootprintFactor, bool isPrevFrame) +{ + if (!dstPrimaryHit.isValid()) return 0.f; + + bool isPrevFrameForCamera = isPrevFrame; + if (!gPathTracer.restirpt.kUsePrevFrameSceneData) isPrevFrame = false; + + PathState path; + generatePathForRandomReplay(dstPrimaryHit, dstPrimarySd, path, initRandomSeed, pathFlags); + + path.restirPTData.primaryRayFootprint = gPathTracer.restirpt.computePrimaryRayFootprint( dstPrimarySd.faceN, isPrevFrameForCamera ? gScene.camera.data.prevPosW : gScene.camera.data.posW, dstPrimarySd.V, dstPrimarySd.posW); + + const IMaterialInstance mi = gScene.materials.getMaterialInstance(dstPrimarySd, ExplicitLodTextureSampler(0), getMaterialInstanceHints(dstPrimaryHit, true)); + + // skipping random numbers and setting the thresholds + sampleNext1D(path.sg); // for BSDF RIS + sampleNext1D(path.sg); // for NEE RIS + gPathTracer.skipLightSampleRandomNumbers(path.sg); + if (kUseRussianRoulette) sampleNext1D(path.sg); + + bool valid = generateScatterRayRandomReplay(dstPrimarySd, mi, path); + + path.setPrevFrame(isPrevFrame); + + if (!valid || !path.isActive()) + { + path.terminate(); + return 0.f; + } + + bool terminateWithMiss = false; + + while (path.isActive()) + { + ClosestHitQuery chq; + nextHit(path, chq, isPrevFrame); + + if (path.isHit()) + { + VisibilityQuery vq; + handleHitReplayPath(path, vq); + } + else + { + terminateWithMiss = true; + handleMissReplayPath(path); + } + } + + bool shouldConnect = pathFlags.rcVertexLength() <= pathFlags.pathLength() + 1; + bool readyToConnect = path.getVertexIndex() - 1 == pathFlags.rcVertexLength() - 1; + + // Check if we need to terminate due to non-invertible shift + bool nonInvertable = (shouldConnect && (!readyToConnect || terminateWithMiss)) || path.getVertexIndex() == 0; + + float3 L = nonInvertable ? 0.f : path.L; + + if (shouldConnect && !nonInvertable) + { + rcPrevHit = path.restirPTData.pathRcInfo.reconnectionHit; + rcPrevHitWo = path.restirPTData.pathRcInfo.reconnectionWi; + rcFootprintFactor = path.restirPTData.pathRcInfo.footprintFactor; + L = path.thp; + } + + return L; +}; + + +// return the hit dist +// and the first non-delta surface hit reflectance of a delta path (if any) +float TraceDenoiserReplayPath( + HitInfo dstPrimaryHit, ShadingData dstPrimarySd, + ReSTIRPathFlags pathFlags, uint initRandomSeed, inout bool isPrimaryBounceDelta, inout bool isPrimaryBounceTransmission, inout float3 deltaReflectance, inout float3 emissionAlongDeltaPath) +{ + // if we need to do random replay of any kind, do it + PathState path; + generatePathForRandomReplay(dstPrimaryHit, dstPrimarySd, path, initRandomSeed, pathFlags); + + const IMaterialInstance mi = gScene.materials.getMaterialInstance(dstPrimarySd, ExplicitLodTextureSampler(0), getMaterialInstanceHints(dstPrimaryHit, true)); + // skipping random numbers and setting the thresholds + if (kUseRussianRoulette) sampleNext1D(path.sg); + sampleNext1D(path.sg); // for BSDF RIS + sampleNext1D(path.sg); // for NEE RIS + gPathTracer.skipLightSampleRandomNumbers(path.sg); + + bool valid = generateScatterRayRandomReplay(dstPrimarySd, mi, path); + + isPrimaryBounceDelta = path.isDelta(); + isPrimaryBounceTransmission = path.isTransmission(); + + if (!valid || !path.isActive() || !(path.isSpecular() || path.isDelta())) + { + return 0.f; + } + + float hitDist = 0.f; + + if (path.isActive()) + { + ClosestHitQuery chq; + nextHit(path, chq, false); + + if (path.isHit()) + { + hitDist = path.sceneLength; + } + else hitDist = HLF_MAX; + } + + if (isPrimaryBounceDelta && kFetchNRDDataPass) + { + path.L = 0; + + if (path.isHit() && path.isActive()) + { + VisibilityQuery vq; + + handleHitReplayPath(path, vq); + + while (path.isActive()) + { + ClosestHitQuery chq; + nextHit(path, chq, false); + + if (path.isHit()) + { + handleHitReplayPath(path, vq); + } + else + { + break; + } + } + + deltaReflectance = path.thp; + emissionAlongDeltaPath = path.L; + } + } + + return hitDist; +} + + +[ForceInline] +void traceTemporalUpdateNeeAtRc(inout PathReservoir reservoir, inout PathState path, ShadingData rcVertexSd, const IMaterialInstance mi, bool isPrevFrame) +{ + VisibilityQuery vq; + updateDirectLightingForRcVertex(path, rcVertexSd, mi, vq, true, isPrevFrame); + reservoir.reconnectionIncidentRadiance = path.restirPTData.pathRcInfo.rcThroughput; + reservoir.lightPdf = path.restirPTData.pathRcInfo.reconnectionJacobian; + reservoir.reconnectionWi = path.dir; + reservoir.pathFlags.insertLightType(path.restirPTData.pathRcInfo.pathFlags.lightType()); +} + +[ForceInline] +void traceTemporalUpdateEmissiveTriangleAtRc(inout PathReservoir reservoir, ShadingData rcVertexSd, ShadingData rcPrevSd, const IMaterialInstance mi, PathState path) +{ + BSDFProperties bsdfProperties = mi.getProperties(rcVertexSd); + reservoir.reconnectionIncidentRadiance = bsdfProperties.emission; + + TriangleHit triangleHit = path.hit.getTriangleHit(); + TriangleLightHit hit; + hit.triangleIndex = gScene.lightCollection.getTriangleIndex(triangleHit.instanceID, triangleHit.primitiveIndex); + hit.posW = rcVertexSd.posW; + // backfacing emission is 0 for standard material so this doesn't matter (but will matter for other material model) + hit.normalW = rcVertexSd.frontFacing ? rcVertexSd.faceN : -rcVertexSd.faceN; + + const IMaterialInstance rcPrevMI = gScene.materials.getMaterialInstance( rcPrevSd, + ExplicitLodTextureSampler(gPathTracer.params.lodBias), getMaterialInstanceHints(rcVertexSd.curveRadius == 0.f, false) ); + uint rcPrevLobes = rcPrevMI.getLobeTypes(rcPrevSd); + + const bool isTriangleHit = path.hit.getType() == HitType::Triangle; + const bool isCurveHit = gPathTracer.kUseCurves && (path.hit.getType() == HitType::Curve); + const bool isHairMaterial = gPathTracer.kUseHairMaterial && (rcVertexSd.mtl.getMaterialType() == MaterialType::Hair); + const bool isCurvePolyTubeHit = isTriangleHit && isHairMaterial; + + bool sampleUpperHemisphere = isCurveHit || isCurvePolyTubeHit || ((rcPrevLobes & (uint)LobeType::NonDeltaReflection) != 0); + // we assume that the emissive surface is outside any dielectric volumes. so, simply having a transmission type of last bounce we know that + // the previous vertex is inside a dielectric volume + if (reservoir.pathFlags.decodeIsTransmissionEvent(true) && !kUseLightsInDielectricVolumes) sampleUpperHemisphere = false; + bool sampleLowerHemisphere = isCurveHit || isCurvePolyTubeHit || ((rcPrevLobes & (uint)LobeType::NonDeltaTransmission) != 0); + bool upperHemisphere = sampleUpperHemisphere && !sampleUpperHemisphere; + reservoir.lightPdf = gPathTracer.getEmissiveSelectionProbability() * + gPathTracer.emissiveSampler.evalPdf(rcPrevSd.posW, rcPrevSd.frame.N, upperHemisphere, hit); + // convert to area measure + float3 disp = rcPrevSd.posW - hit.posW; + float dist2 = dot(disp, disp); + float3 dir = disp / sqrt(dist2); + float G = abs(dot(dir, hit.normalW)) / dist2; + reservoir.lightPdf *= G; +} + +[ForceInline] +void traceTemporalUpdateScatterReplayAtRc(inout PathReservoir reservoir, ShadingData rcVertexSd, const IMaterialInstance mi, inout PathState path, bool isPrevFrame) +{ + bool valid = generateScatterRayGivenDirection(rcVertexSd, mi, path); + path.restirPTData.pathRcInfo.rcThroughput = float3(1.f); // reset throughput + + // trace stuff to update stuff + if (!valid) path.terminate(); + + while (path.isActive()) + { + ClosestHitQuery chq; + nextHit(path, chq, isPrevFrame); + + if (path.isHit()) + { + VisibilityQuery vq; + handleHitReplayPath(path, vq); + } + else + { + handleMissReplayPath(path); + } + } + + // Now we don't have NEE in handleHitReplayPath, so we have to do it here + if (reservoir.pathFlags.lastVertexNEE()) + { + sampleNext1D(path.sg); // random number for RIS + ShadingData NEESd = loadShadingData(path.hit, path.origin, path.dir, isPrevFrame); + const IMaterialInstance NEEmi = gScene.materials.getMaterialInstance( NEESd, ExplicitLodTextureSampler(gPathTracer.params.lodBias), getMaterialInstanceHints(path.hit, false)); + VisibilityQuery vq; + updateDirectLightingForRcVertex(path, NEESd, NEEmi, vq, false, isPrevFrame); + } + + reservoir.reconnectionIncidentRadiance = any(path.L > 0) ? path.restirPTData.pathRcInfo.rcThroughput : 0.f; + reservoir.lightPdf = path.restirPTData.pathRcInfo.reconnectionJacobian; + reservoir.pathFlags.insertLightType(path.restirPTData.pathRcInfo.pathFlags.lightType()); +} + +void TraceTemporalUpdate(ShadingData rcPrevSd, inout PathReservoir reservoir, bool isPrevFrame) +{ + if (!gPathTracer.restirpt.kUsePrevFrameSceneData) isPrevFrame = false; + + HitInfo reconnectionHit = unpackReservoirHitInfo(reservoir.reconnectionHit); + + int rcVertexLength = reservoir.pathFlags.rcVertexLength(); + + // Branch 1: rc-vertex is the environment light. No path setup needed; just rotate + re-eval. + if (!reconnectionHit.isValid() && reservoir.pathFlags.lightType() == (uint)LightType::EnvMap) + { + assert(reservoir.pathFlags.pathLength() + 1 == rcVertexLength); + reservoir.reconnectionWi = gScene.envMap.toWorld(gScene.envMap.toLocal(reservoir.reconnectionWi, !isPrevFrame), isPrevFrame); + reservoir.reconnectionIncidentRadiance = gPathTracer.envMapSampler.eval(reservoir.reconnectionWi, isPrevFrame); + reservoir.lightPdf = gPathTracer.getEnvMapSelectionProbability() * gPathTracer.envMapSampler.evalPdf(reservoir.reconnectionWi, isPrevFrame); + return; + } + + ShadingData rcVertexSd = loadShadingData(reconnectionHit, rcPrevSd.posW, float3(0.f), isPrevFrame); + const IMaterialInstance mi = gScene.materials.getMaterialInstance( rcVertexSd, ExplicitLodTextureSampler(gPathTracer.params.lodBias), getMaterialInstanceHints(reconnectionHit, false) ); + + PathState path; + generatePathForTemporalUpdate( reconnectionHit, rcVertexSd, path, reservoir.reconnectionWi, reservoir.rcNextVertexBounceSeed, reservoir.initRandomSeed, reservoir.pathFlags); + path.setPrevFrame(isPrevFrame); + + // NEE replay not yet supported if light tile RIS was used to generate the NEE light sample. + if (reservoir.pathFlags.lastVertexNEE() && reservoir.pathFlags.pathLength() == rcVertexLength && !kUseLightTileRIS) + { + // Branch 2: NEE at rc-vertex. + traceTemporalUpdateNeeAtRc(reservoir, path, rcVertexSd, mi, isPrevFrame); + } + else if (reservoir.pathFlags.pathLength() + 1 == rcVertexLength) + { + // Branch 3: rc-vertex is emissive (env-map and emissive-triangle sub-cases). + if (reservoir.pathFlags.lightType() == (uint)LightType::EnvMap) + { + reservoir.reconnectionWi = gScene.envMap.toWorld(gScene.envMap.toLocal(reservoir.reconnectionWi, !isPrevFrame), isPrevFrame); + reservoir.reconnectionIncidentRadiance = gPathTracer.envMapSampler.eval(reservoir.reconnectionWi, 0.f, isPrevFrame); + reservoir.lightPdf = gPathTracer.envMapSampler.evalPdf(reservoir.reconnectionWi, isPrevFrame); + } + else if (reservoir.pathFlags.lightType() == (uint)LightType::Emissive) + { + traceTemporalUpdateEmissiveTriangleAtRc(reservoir, rcVertexSd, rcPrevSd, mi, path); + } + // analytic light update not supported! + } + else if (!kUseLightTileRIS) + { + // Branch 4: general case, re-trace the scatter ray and the remaining path. + traceTemporalUpdateScatterReplayAtRc(reservoir, rcVertexSd, mi, path, isPrevFrame); + } +} diff --git a/Source/Modules/ReSTIRPathTracing/LoadShadingDataDynamic.slang b/Source/Modules/ReSTIRPathTracing/LoadShadingDataDynamic.slang new file mode 100644 index 0000000000..b253d754a8 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/LoadShadingDataDynamic.slang @@ -0,0 +1,147 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "Scene/SceneDefines.slangh" + +__exported import Scene.Shading; +import Scene.Material.ShadingUtils; +import Rendering.Materials.TexLODHelpers; +import Utils.Math.MathHelpers; +import Utils.Geometry.GeometryHelpers; +import RenderPasses.PathTracer.StaticParams; + + +/** Setup vertex data based on hit point. + \param[in] hit Hit information. + \param[in] rayOrigin Ray origin. + \param[in] rayDir Normalized ray direction. + \param[out] materialID Material ID. + \param[out] normalCurvature Normal curvature. + \return VertexData struct. +*/ +VertexData loadVertexData(const HitInfo hit, const float3 rayOrigin, const float3 rayDir, out uint materialID, bool isPrevFrame) +{ + VertexData v = {}; + materialID = {}; + +#if SCENE_HAS_GEOMETRY_TYPE(GEOMETRY_TYPE_TRIANGLE_MESH) + if (hit.getType() == HitType::Triangle) + { + const TriangleHit triangleHit = hit.getTriangleHit(); + StaticVertexData vertices[3]; + v = gScene.getVertexData(triangleHit, vertices, isPrevFrame); + materialID = gScene.getMaterialID(triangleHit.instanceID); + + v.curveRadius = 0; + } +#endif +#if SCENE_HAS_GEOMETRY_TYPE(GEOMETRY_TYPE_DISPLACED_TRIANGLE_MESH) + if (hit.getType() == HitType::DisplacedTriangle) + { + const DisplacedTriangleHit displacedTriangleHit = hit.getDisplacedTriangleHit(); + v = gScene.getVertexData(displacedTriangleHit, -rayDir, isPrevFrame); + materialID = gScene.getMaterialID(displacedTriangleHit.instanceID); + v.curveRadius = 0; + } +#endif +#if SCENE_HAS_GEOMETRY_TYPE(GEOMETRY_TYPE_CURVE) + if (hit.getType() == HitType::Curve) + { + const CurveHit curveHit = hit.getCurveHit(); + v = gScene.getVertexDataFromCurve(curveHit); + materialID = gScene.getMaterialID(curveHit.instanceID); + } +#endif +#if SCENE_HAS_GEOMETRY_TYPE(GEOMETRY_TYPE_SDF_GRID) + if (hit.getType() == HitType::SDFGrid) + { + const SDFGridHit sdfGridHit = hit.getSDFGridHit(); + v = gScene.getVertexDataFromSDFGrid(sdfGridHit, rayOrigin, rayDir); + materialID = gScene.getMaterialID(sdfGridHit.instanceID); + v.curveRadius = -1; + } +#endif + return v; +} + + +uint getMaterialInstanceHints(const HitInfo hit, const bool isPrimary) +{ + uint hints = 0; + if (hit.getType() == HitType::Triangle || hit.getType() == HitType::DisplacedTriangle) + { +#if GBUFFER_ADJUST_SHADING_NORMALS + if (kAdjustShadingNormals || isPrimary) hints |= (uint)MaterialInstanceHints::AdjustShadingNormal; +#else + if (kAdjustShadingNormals && !isPrimary) hints |= (uint)MaterialInstanceHints::AdjustShadingNormal; +#endif + } + return hints; +} + +uint getMaterialInstanceHints(const bool isTriangleHit, const bool isPrimary) +{ + uint hints = 0; +#if GBUFFER_ADJUST_SHADING_NORMALS + if (isTriangleHit && (kAdjustShadingNormals || isPrimary)) hints |= (uint)MaterialInstanceHints::AdjustShadingNormal; +#else + if (isTriangleHit && (kAdjustShadingNormals && !isPrimary)) hints |= (uint)MaterialInstanceHints::AdjustShadingNormal; +#endif + return hints; +} + +/** Setup ShadingData based on loaded vertex/material attributes for a hit point. + \param[in] hit Hit information. + \param[in] rayOrigin Ray origin. + \param[in] rayDir Normalized ray direction. + \param[in] isPrimary True if this is the primary hit point. + \param[in] lod Method for computing texture level-of-detail. + \return ShadingData struct. +*/ +ShadingData loadShadingData(const HitInfo hit, const float3 rayOrigin, const float3 rayDir, bool isPrevFrame) +{ +#if !USE_PREV_FRAME_SCENE_DATA + isPrevFrame = false; +#endif + + uint materialID = {}; + bool modifyNormals = true; + + // if rayDir == 0.f, compute viewing direction from rayOrigin (might break displaced triangle) + VertexData v = loadVertexData(hit, rayOrigin, rayDir, materialID, isPrevFrame); + float3 viewDir = all(rayDir == 0.f) ? normalize(rayOrigin - v.posW) : -rayDir; + ShadingData sd = gScene.materials.prepareShadingData(v, materialID, viewDir); + return sd; +} + +float3 loadVertexPosition(const HitInfo hit, bool isPrevFrame = false, float3 rayOrigin = 0.f, float3 rayDir = 0.f) +{ + uint materialID = {}; + // if rayDir == 0.f, compute viewing direction from rayOrigin (might break displaced triangle) + VertexData v = loadVertexData(hit, rayOrigin, rayDir, materialID, isPrevFrame); + return v.posW; +} diff --git a/Source/Modules/ReSTIRPathTracing/PairedNeighborTransform.slang b/Source/Modules/ReSTIRPathTracing/PairedNeighborTransform.slang new file mode 100644 index 0000000000..ca0c866d42 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/PairedNeighborTransform.slang @@ -0,0 +1,121 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Utils.Sampling.SampleGenerator; + +StructuredBuffer pairingDeltaTextureSizes; +StructuredBuffer pairingDeltaTextures; + +struct PairedNeighborTransform { + int offsetX; + int offsetY; + int imageWidth; + int imageHeight; + bool flipX; + bool flipY; + bool transpose; + + __init(inout SampleGenerator sg, int imageWidth, int imageHeight) + { + uint r = sg.next(); + + this.imageHeight = imageHeight; + this.imageWidth = imageWidth; + + offsetX = (r & 0b00000000000000000111111111111111u) % imageWidth; + r >>= 15; + + offsetY = (r & 0b00000000000000000011111111111111u) % imageHeight; + r >>= 14; + + flipX = bool(r & 1); + r >>= 1; + + flipY = bool(r & 1); + r >>= 1; + + transpose = bool(r & 1); + } + + void transformCoordinates(int neighborIndex, int x, int y, int imageWidth, int imageHeight, inout int out_x, inout int out_y) { + int textureSize = pairingDeltaTextureSizes[neighborIndex]; + + x = (x + offsetX) % textureSize; + y = (y + offsetY) % textureSize; + + if (flipX) x = textureSize - 1 - x; + if (flipY) y = textureSize - 1 - y; + + if (transpose) + { + int temp = x; + x = y; + y = temp; + } + + out_x = x; + out_y = y; + } + + void transformDelta(int dx, int dy, inout int out_dx, inout int out_dy) { + if (transpose) + { + int temp = dx; + dx = dy; + dy = temp; + } + if (flipX) dx = -dx; + if (flipY) dy = -dy; + out_dx = dx; + out_dy = dy; + } + + void unpackDelta(uint packed, inout int dx, inout int dy) { + dx = (int)(packed & 0xFF) - 128; + dy = (int)(packed >> 8) - 128; + } + + uint readReuseTexture(int index, int x, int y) { + int textureSize = pairingDeltaTextureSizes[index]; + return pairingDeltaTextures[256 * 256 * index + y * textureSize + x]; + } + + void readTransformedDelta( int neighborIndex, int x, int y, + inout int out_dx, inout int out_dy ) { + int tx, ty, dx, dy; + transformCoordinates(neighborIndex, x, y, imageWidth, imageHeight, tx, ty); + uint packedDelta = readReuseTexture(neighborIndex, tx, ty); + unpackDelta(packedDelta, dx, dy); + transformDelta(dx, dy, out_dx, out_dy); + } + + int applyDeltaPeriodic(int x, int dx, int width) { + int r = (x + dx) % width; + if (r < 0) r += width; + return r; + } +}; diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev10.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev10.6/neighbor0.png new file mode 100644 index 0000000000..fa29664cdf Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev10.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev13.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev13.3/neighbor0.png new file mode 100644 index 0000000000..7ba42fd292 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev13.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev16.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev16.0/neighbor0.png new file mode 100644 index 0000000000..99b8971aba Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev16.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev18.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev18.6/neighbor0.png new file mode 100644 index 0000000000..2321a87995 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev18.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev2.7/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev2.7/neighbor0.png new file mode 100644 index 0000000000..923754ee90 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev2.7/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev21.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev21.3/neighbor0.png new file mode 100644 index 0000000000..04150cb518 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev21.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev23.9/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev23.9/neighbor0.png new file mode 100644 index 0000000000..1ca781ac73 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev23.9/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev26.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev26.6/neighbor0.png new file mode 100644 index 0000000000..1faa917aab Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev26.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev5.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev5.3/neighbor0.png new file mode 100644 index 0000000000..78ca8f4189 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev5.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev8.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev8.0/neighbor0.png new file mode 100644 index 0000000000..b837b1b3ed Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount1-stdev8.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev10.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev10.6/neighbor0.png new file mode 100644 index 0000000000..766139a382 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev10.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev10.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev10.6/neighbor1.png new file mode 100644 index 0000000000..96daac1c45 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev10.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev13.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev13.3/neighbor0.png new file mode 100644 index 0000000000..6e8d837fcf Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev13.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev13.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev13.3/neighbor1.png new file mode 100644 index 0000000000..9e4dcae7cd Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev13.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev16.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev16.0/neighbor0.png new file mode 100644 index 0000000000..b9b2a7f7d7 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev16.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev16.0/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev16.0/neighbor1.png new file mode 100644 index 0000000000..4bb355f4a8 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev16.0/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev18.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev18.6/neighbor0.png new file mode 100644 index 0000000000..bce92ae529 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev18.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev18.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev18.6/neighbor1.png new file mode 100644 index 0000000000..ae72e20775 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev18.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev2.7/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev2.7/neighbor0.png new file mode 100644 index 0000000000..ca00d0740c Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev2.7/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev2.7/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev2.7/neighbor1.png new file mode 100644 index 0000000000..ce1c5329cb Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev2.7/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev21.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev21.3/neighbor0.png new file mode 100644 index 0000000000..061ad8f5f2 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev21.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev21.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev21.3/neighbor1.png new file mode 100644 index 0000000000..235ed36a25 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev21.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev23.9/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev23.9/neighbor0.png new file mode 100644 index 0000000000..e06732555d Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev23.9/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev23.9/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev23.9/neighbor1.png new file mode 100644 index 0000000000..d35d665c26 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev23.9/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev26.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev26.6/neighbor0.png new file mode 100644 index 0000000000..964e507437 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev26.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev26.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev26.6/neighbor1.png new file mode 100644 index 0000000000..edd444a655 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev26.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev5.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev5.3/neighbor0.png new file mode 100644 index 0000000000..1eb00e8cd0 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev5.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev5.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev5.3/neighbor1.png new file mode 100644 index 0000000000..dbb56613c8 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev5.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev8.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev8.0/neighbor0.png new file mode 100644 index 0000000000..a2d7c7233a Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev8.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev8.0/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev8.0/neighbor1.png new file mode 100644 index 0000000000..8a9c5b38f9 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount2-stdev8.0/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor0.png new file mode 100644 index 0000000000..fa29664cdf Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor1.png new file mode 100644 index 0000000000..757b8ec238 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor2.png new file mode 100644 index 0000000000..4ade8043a9 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev10.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor0.png new file mode 100644 index 0000000000..7ba42fd292 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor1.png new file mode 100644 index 0000000000..e2fccc7133 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor2.png new file mode 100644 index 0000000000..8febc0dfe1 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev13.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor0.png new file mode 100644 index 0000000000..99b8971aba Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor1.png new file mode 100644 index 0000000000..c005f846e3 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor2.png new file mode 100644 index 0000000000..2fe3237314 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev16.0/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor0.png new file mode 100644 index 0000000000..2321a87995 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor1.png new file mode 100644 index 0000000000..87dbf5a429 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor2.png new file mode 100644 index 0000000000..bac4c84053 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev18.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor0.png new file mode 100644 index 0000000000..923754ee90 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor1.png new file mode 100644 index 0000000000..436fbd8758 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor2.png new file mode 100644 index 0000000000..48020ff10b Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev2.7/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor0.png new file mode 100644 index 0000000000..04150cb518 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor1.png new file mode 100644 index 0000000000..25d87bfc70 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor2.png new file mode 100644 index 0000000000..3741ba586e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev21.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor0.png new file mode 100644 index 0000000000..1ca781ac73 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor1.png new file mode 100644 index 0000000000..25526d7776 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor2.png new file mode 100644 index 0000000000..191d78245a Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev23.9/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor0.png new file mode 100644 index 0000000000..1faa917aab Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor1.png new file mode 100644 index 0000000000..c89909cdc2 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor2.png new file mode 100644 index 0000000000..8879b517df Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev26.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor0.png new file mode 100644 index 0000000000..78ca8f4189 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor1.png new file mode 100644 index 0000000000..1071022780 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor2.png new file mode 100644 index 0000000000..76622954fb Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev5.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor0.png new file mode 100644 index 0000000000..b837b1b3ed Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor1.png new file mode 100644 index 0000000000..e3f8f73efe Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor2.png new file mode 100644 index 0000000000..fbb315a661 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount3-stdev8.0/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor0.png new file mode 100644 index 0000000000..fa29664cdf Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor1.png new file mode 100644 index 0000000000..655c902ffe Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor2.png new file mode 100644 index 0000000000..09dd9511fa Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor3.png new file mode 100644 index 0000000000..3ab60ffa80 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev10.6/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor0.png new file mode 100644 index 0000000000..7ba42fd292 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor1.png new file mode 100644 index 0000000000..e25d45c0e3 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor2.png new file mode 100644 index 0000000000..09c48d1683 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor3.png new file mode 100644 index 0000000000..92994eadf5 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev13.3/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor0.png new file mode 100644 index 0000000000..99b8971aba Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor1.png new file mode 100644 index 0000000000..e9638ed2bf Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor2.png new file mode 100644 index 0000000000..1cc86fd9fd Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor3.png new file mode 100644 index 0000000000..474efe1b67 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev16.0/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor0.png new file mode 100644 index 0000000000..2321a87995 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor1.png new file mode 100644 index 0000000000..af423072e9 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor2.png new file mode 100644 index 0000000000..26aa47da37 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor3.png new file mode 100644 index 0000000000..60cf5f6021 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev18.6/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor0.png new file mode 100644 index 0000000000..923754ee90 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor1.png new file mode 100644 index 0000000000..a259b11a6e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor2.png new file mode 100644 index 0000000000..5ca8999e17 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor3.png new file mode 100644 index 0000000000..38aec88535 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev2.7/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor0.png new file mode 100644 index 0000000000..04150cb518 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor1.png new file mode 100644 index 0000000000..f897fb6499 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor2.png new file mode 100644 index 0000000000..b8c59ec151 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor3.png new file mode 100644 index 0000000000..37224c0061 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev21.3/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor0.png new file mode 100644 index 0000000000..1ca781ac73 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor1.png new file mode 100644 index 0000000000..c6756c2efd Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor2.png new file mode 100644 index 0000000000..d54c6eb00e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor3.png new file mode 100644 index 0000000000..e2ff3f9454 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev23.9/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor0.png new file mode 100644 index 0000000000..1faa917aab Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor1.png new file mode 100644 index 0000000000..065ebce7a6 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor2.png new file mode 100644 index 0000000000..63cea4f7e4 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor3.png new file mode 100644 index 0000000000..84b7b3d0cb Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev26.6/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor0.png new file mode 100644 index 0000000000..78ca8f4189 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor1.png new file mode 100644 index 0000000000..abb7ea2406 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor2.png new file mode 100644 index 0000000000..94f9c852ab Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor3.png new file mode 100644 index 0000000000..0c1abc89a4 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev5.3/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor0.png new file mode 100644 index 0000000000..b837b1b3ed Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor1.png new file mode 100644 index 0000000000..30ed71038e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor2.png new file mode 100644 index 0000000000..04a68144c3 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor3.png new file mode 100644 index 0000000000..76242b617c Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount4-stdev8.0/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor0.png new file mode 100644 index 0000000000..fa29664cdf Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor1.png new file mode 100644 index 0000000000..438c3db554 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor2.png new file mode 100644 index 0000000000..12eb741f2e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor3.png new file mode 100644 index 0000000000..7568e624ee Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor4.png new file mode 100644 index 0000000000..987268a59c Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev10.6/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor0.png new file mode 100644 index 0000000000..7ba42fd292 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor1.png new file mode 100644 index 0000000000..f49b470023 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor2.png new file mode 100644 index 0000000000..a6c6c23529 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor3.png new file mode 100644 index 0000000000..9c0f0363c6 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor4.png new file mode 100644 index 0000000000..b312e955ed Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev13.3/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor0.png new file mode 100644 index 0000000000..99b8971aba Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor1.png new file mode 100644 index 0000000000..b40880f71c Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor2.png new file mode 100644 index 0000000000..5f34c6873b Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor3.png new file mode 100644 index 0000000000..fe6549905f Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor4.png new file mode 100644 index 0000000000..4f5c7c2536 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev16.0/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor0.png new file mode 100644 index 0000000000..2321a87995 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor1.png new file mode 100644 index 0000000000..4d41e264a3 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor2.png new file mode 100644 index 0000000000..0dc229d5e8 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor3.png new file mode 100644 index 0000000000..ac9a6daf46 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor4.png new file mode 100644 index 0000000000..762988208d Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev18.6/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor0.png new file mode 100644 index 0000000000..923754ee90 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor1.png new file mode 100644 index 0000000000..e16633bccb Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor2.png new file mode 100644 index 0000000000..e73efe3a6e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor3.png new file mode 100644 index 0000000000..e027a0a1ca Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor4.png new file mode 100644 index 0000000000..f73560ffc2 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev2.7/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor0.png new file mode 100644 index 0000000000..04150cb518 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor1.png new file mode 100644 index 0000000000..9b3da4d597 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor2.png new file mode 100644 index 0000000000..2705952cd0 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor3.png new file mode 100644 index 0000000000..4cf8934792 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor4.png new file mode 100644 index 0000000000..2e0729d65e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev21.3/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor0.png new file mode 100644 index 0000000000..1ca781ac73 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor1.png new file mode 100644 index 0000000000..6017a708bd Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor2.png new file mode 100644 index 0000000000..acf1d3a1de Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor3.png new file mode 100644 index 0000000000..798a36570e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor4.png new file mode 100644 index 0000000000..614f0fc98d Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev23.9/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor0.png new file mode 100644 index 0000000000..1faa917aab Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor1.png new file mode 100644 index 0000000000..48516d70ed Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor2.png new file mode 100644 index 0000000000..5bddd655e0 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor3.png new file mode 100644 index 0000000000..612969244d Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor4.png new file mode 100644 index 0000000000..d25e917913 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev26.6/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor0.png new file mode 100644 index 0000000000..78ca8f4189 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor1.png new file mode 100644 index 0000000000..47d635266c Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor2.png new file mode 100644 index 0000000000..d341e3c88d Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor3.png new file mode 100644 index 0000000000..0a87a8093b Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor4.png new file mode 100644 index 0000000000..02b5ff62d5 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev5.3/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor0.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor0.png new file mode 100644 index 0000000000..b837b1b3ed Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor0.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor1.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor1.png new file mode 100644 index 0000000000..0a3e27685d Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor1.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor2.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor2.png new file mode 100644 index 0000000000..10cfd4737e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor2.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor3.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor3.png new file mode 100644 index 0000000000..c6f197032e Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor3.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor4.png b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor4.png new file mode 100644 index 0000000000..7c2ad5cfe0 Binary files /dev/null and b/Source/Modules/ReSTIRPathTracing/PairedReusePattern/neighborCount5-stdev8.0/neighbor4.png differ diff --git a/Source/Modules/ReSTIRPathTracing/PathReservoir.slang b/Source/Modules/ReSTIRPathTracing/PathReservoir.slang new file mode 100644 index 0000000000..0f5fd735f9 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/PathReservoir.slang @@ -0,0 +1,732 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Utils.Sampling.SampleGenerator; +import Scene.HitInfo; +import Utils.Debug.PixelDebug; + +import Utils.Math.PackedFormats; +import Utils.Math.FormatConversion; +#include "Utils/Math/MathConstants.slangh" +#include "Scene/SceneDefines.slangh" + +/** Packed path flags carried by every reservoir. + Bit layout (LSB to MSB), all named below as `kXxxShift` / `kXxxMask`: + + bits 0..7 : path length (8 bits, max 255) + bits 8..15 : rc-vertex length (8 bits, max 255) + bit 16 : last-vertex was NEE + bit 17 : "very direct light" flag (primary-hit emissive contribution) + bits 18..19 : light type (2 bits) + bits 20..25 : "before rc-vertex" sub-bits (6 bits, see below) + bits 26..30 : "after rc-vertex" sub-bits (5 bits, see below; no BSDF-indexing bit) + + The 6-bit "before-rc" packed payload (`kBouncePackedBeforeRcMask`) is laid out as: + bit 0 (= 20) : isDelta + bit 1 (= 21) : isTransmission + bits 2..4 : BSDFComponent (3 bits) + bit 5 (= 25): supportBSDFIndexing + The 5-bit "after-rc" packed payload (`kBouncePackedAfterRcMask`) is laid out the same way + *except* it does not carry the supportBSDFIndexing bit (so 5 bits, not 6). +*/ +struct ReSTIRPathFlags +{ + // ---- Path-level fields ---- + static const uint kPathLengthMask = 0xFF; + static const uint kPathLengthShift = 0; + static const uint kRcVertexLengthMask = 0xFF; + static const uint kRcVertexLengthShift = 8; + static const uint kLastVertexNEEShift = 16; + static const uint kIsVeryDirectLightShift = 17; + static const uint kLightTypeMask = 0x3; + static const uint kLightTypeShift = 18; + + // ---- Before-rc-vertex sub-bits (bits 20..25, 6 bits) ---- + static const uint kBouncePackedBeforeRcMask = 0x3f; + static const uint kBouncePackedBeforeRcShift = 20; + static const uint kIsDeltaBeforeRcShift = 20; + static const uint kIsTransmissionBeforeRcShift = 21; + static const uint kBSDFComponentBeforeRcShift = 22; + static const uint kSupportBSDFIndexingShift = 25; + + // ---- After-rc-vertex sub-bits (bits 26..30, 5 bits, no BSDF-indexing) ---- + static const uint kBouncePackedAfterRcMask = 0x1f; + static const uint kBouncePackedAfterRcShift = 26; + static const uint kIsDeltaAfterRcShift = 26; + static const uint kIsTransmissionAfterRcShift = 27; + static const uint kBSDFComponentAfterRcShift = 28; + + static const uint kBSDFComponentMask = 0x7; // 3 bits, used by both before/after. + + uint flags = 0; + + + [mutating] void tryInsertBounceTypesBeforeRcVertex(uint data, bool doInsert) + { + flags &= doInsert ? ~(kBouncePackedBeforeRcMask << kBouncePackedBeforeRcShift) : 0xffffffff; + flags |= doInsert ? (data << kBouncePackedBeforeRcShift) : 0; + } + + [mutating] void tryInsertBounceTypesAfterRcVertex(uint data, bool doInsert) + { + flags &= doInsert ? ~(kBouncePackedAfterRcMask << kBouncePackedAfterRcShift) : 0xffffffff; + flags |= doInsert ? (data << kBouncePackedAfterRcShift) : 0; + } + + [mutating] + void insertIsDeltaEvent(bool isDeltaEvent, bool beforeRcVertex) + { + const uint shift = beforeRcVertex ? kIsDeltaBeforeRcShift : kIsDeltaAfterRcShift; + flags &= ~(1 << shift); + flags |= uint(isDeltaEvent) << shift; + } + + [mutating] + void insertIsTransmissionEvent(bool isTransmissionEvent, bool beforeRcVertex) + { + const uint shift = beforeRcVertex ? kIsTransmissionBeforeRcShift : kIsTransmissionAfterRcShift; + flags &= ~(1 << shift); + flags |= uint(isTransmissionEvent) << shift; + } + + // component uses 3 bits + [mutating] + void insertBSDFComponentType(uint component, bool beforeRcVertex) + { + const uint shift = beforeRcVertex ? kBSDFComponentBeforeRcShift : kBSDFComponentAfterRcShift; + flags &= ~(kBSDFComponentMask << shift); + flags |= component << shift; + } + + [mutating] + void insertSupportBSDFIndexingBeforeRcVertex(bool enabled) + { + flags &= ~(1 << kSupportBSDFIndexingShift); + flags |= uint(enabled) << kSupportBSDFIndexingShift; + } + + bool decodeIsDeltaEvent(bool beforeRcVertex) + { + return (flags >> (beforeRcVertex ? kIsDeltaBeforeRcShift : kIsDeltaAfterRcShift)) & 1; + } + + bool decodeIsTransmissionEvent(bool beforeRcVertex) + { + return (flags >> (beforeRcVertex ? kIsTransmissionBeforeRcShift : kIsTransmissionAfterRcShift)) & 1; + } + + uint decodeBSDFComponent(bool beforeRcVertex) + { + return (flags >> (beforeRcVertex ? kBSDFComponentBeforeRcShift : kBSDFComponentAfterRcShift)) & kBSDFComponentMask; + } + + bool decodeSupportBSDFIndexingBeforeRcVertex() + { + return (flags >> kSupportBSDFIndexingShift) & 1; + } + + // maximum length: 255 + [mutating] + void insertPathLength(int pathLength) + { + flags &= ~kPathLengthMask; + flags |= pathLength & kPathLengthMask; + } + + // maximum length: 255 + [mutating] + void insertRcVertexLength(int rcVertexLength) + { + flags &= ~(kRcVertexLengthMask << kRcVertexLengthShift); + flags |= (rcVertexLength & kRcVertexLengthMask) << kRcVertexLengthShift; + } + + [mutating] + void resetRcVertexLength() + { + flags |= kRcVertexLengthMask << kRcVertexLengthShift; + } + + int pathLength() + { + return flags & kPathLengthMask; + } + + int rcVertexLength() + { + return (flags >> kRcVertexLengthShift) & kRcVertexLengthMask; + } + + [mutating] + void insertLastVertexNEE(bool isNEE) + { + flags &= ~(1 << kLastVertexNEEShift); + flags |= (int(isNEE) & 1) << kLastVertexNEEShift; + } + + bool lastVertexNEE() + { + return (flags >> kLastVertexNEEShift) & 1; + } + + [mutating] + void insertIsVeryDirectLight(bool isVeryDirectLight) + { + flags &= ~(1 << kIsVeryDirectLightShift); + flags |= (int(isVeryDirectLight) & 1) << kIsVeryDirectLightShift; + } + + bool isVeryDirectLight() + { + return (flags >> kIsVeryDirectLightShift) & 1; + } + + [mutating] + void insertLightType(uint lightType) + { + flags &= ~(kLightTypeMask << kLightTypeShift); + flags |= ((int(lightType) & kLightTypeMask) << kLightTypeShift); + } + + uint lightType() + { + return (flags >> kLightTypeShift) & kLightTypeMask; + } +}; + +struct LastVertexState +{ + static const uint kIsDeltaShift = 0; + static const uint kIsTransmissionShift = 1; + static const uint kBSDFComponentShift = 2; + static const uint kBSDFComponentMask = 0x7; // 3 bits + static const uint kSupportBSDFIndexingShift = 5; + static const uint kIsRoughShift = 6; + static const uint kIsFarFromPrevShift = 7; + + uint data = 0; + + [mutating] void Init( bool isLastVertexDelta, bool isLastVertexTransmission, uint lastBSDFComponent, + bool lastVertexSupportBSDFIndexing, bool isLastVertexRough, bool isCurrentVertexFarFromPrev ) + { + data = 0; + data |= (uint)isLastVertexDelta << kIsDeltaShift; + data |= (uint)isLastVertexTransmission << kIsTransmissionShift; + data |= (uint)lastBSDFComponent << kBSDFComponentShift; + data |= (uint)lastVertexSupportBSDFIndexing << kSupportBSDFIndexingShift; + data |= (uint)isLastVertexRough << kIsRoughShift; + data |= (uint)isCurrentVertexFarFromPrev << kIsFarFromPrevShift; + } + + bool isLastVertexDelta() { return (data >> kIsDeltaShift) & 1; } + bool isLastVertexTransmission() { return (data >> kIsTransmissionShift) & 1; } + uint lastBSDFComponent() { return (data >> kBSDFComponentShift) & kBSDFComponentMask; } + bool lastVertexSupportBSDFIndexing() { return (data >> kSupportBSDFIndexingShift) & 1; } + bool isLastVertexRough() { return (data >> kIsRoughShift) & 1; } + bool isCurrentVertexFarFromPrev() { return (data >> kIsFarFromPrevShift) & 1; } +} + + +// fall back to HitInfo if compression is not possible (ReSTIR PT doesn't support volume hit yet) +#if !USE_RESERVOIR_COMPRESSION || HIT_INFO_USE_COMPRESSION || 3 + HIT_INFO_INSTANCE_ID_BITS + HIT_INFO_PRIMITIVE_INDEX_BITS > 32 || SCENE_GEOMETRY_TYPES != (1 << GEOMETRY_TYPE_TRIANGLE_MESH) +typedef HitInfo HitInfoPacked; + +HitInfo unpackReservoirHitInfo(HitInfo input) +{ + return input; +} + +HitInfo packReservoirHitInfo(HitInfo input) +{ + return input; +} +#else +struct HitInfoPacked +{ + uint2 data; + + // assume traingle hit + HitInfo unpack() + { + float2 barycentrics = unpackUnorm2x16(data[1]); + HitInfo hit; + hit.data = { data[0], 0, asuint(barycentrics[0]), asuint(barycentrics[1]) }; + return hit; + } + + __init(const HitInfo hitInfo) + { + data[0] = hitInfo.data[0]; + data[1] = packUnorm2x16_unsafe(float2(asfloat(hitInfo.data[2]), asfloat(hitInfo.data[3]))); + } +} + +HitInfo unpackReservoirHitInfo(HitInfoPacked input) +{ + return input.unpack(); +} + +HitInfoPacked packReservoirHitInfo(HitInfo input) +{ + return HitInfoPacked(input); +} +#endif + +// We use PSS-solid angle mixed parameterization in the implementation +// Original PSS integrand in the GRIS paper (f_i and p_i are BSDF and the sampling PDF at each bounce) : +// (assuming the reconnection vertex is x_k, and omitting the MIS weight) +// f_1 / p_1 * f_2 / p_2 * ... * f_{ k - 1 } / p_{ k - 1 } * f_k / p_k * f_{ k + 1 } / p_{ k + 1 } * ... +// For reconnection , we will need to re-evaluate f_{ k - 1 } / p_{ k - 1 } and f_k / p_k , the Jacobian contains the new p_{ k - 1 } and p_k , besides geometry terms. +// The new mixed integrand : (switching to solid angle parameterization locally around the reconnection vertex) +// f_1 / p_1* f_2 / p_2*...* f_{ k - 1 }  * f_k   * (f_{ k + 1 } / p_{ k + 1 } * ...). The bracketed terms are cached as reconnectionIncidentRadiance. +// For reconnection, we only need to re-evaluate f_{ k - 1 } and f_k.The Jacobian only contains the geometry terms. +// This simplifies implementation, speeds up computation, and gives mathematically equivalent results. + +struct PathReservoir +{ + float M; + float weight; + float3 integrand; // the integrand value f/p in PSS + ReSTIRPathFlags pathFlags; + uint initRandomSeed; + uint rcNextVertexBounceSeed; + float lightPdf; // NEE pdf of the sampled light in the path +#if USE_AREA_RESERVOIRS + float4 filmLensSample; +#endif +#if !USE_RESERVOIR_COMPRESSION + HitInfo reconnectionHit; +#else + HitInfoPacked reconnectionHit; +#endif + float reconnectionJacobian; + float3 reconnectionIncidentRadiance; +#if !USE_RESERVOIR_COMPRESSION + float3 reconnectionWi; +#else + uint packedRcWi; + + property float3 reconnectionWi + { + get { return decodeNormal2x16(packedRcWi); } + set { packedRcWi = encodeNormal2x16(newValue); } + } +#endif + + property float2 filmSample + { +#if USE_AREA_RESERVOIRS + get { return filmLensSample.xy; } +#else + get { return float2(0.f); } +#endif + } + + property float2 lensSample + { +#if USE_AREA_RESERVOIRS + get { return filmLensSample.zw; } +#else + get { return float2(0.f); } +#endif + } + + [mutating] + void setFilmSample(float2 xy) + { +#if USE_AREA_RESERVOIRS + filmLensSample.xy = xy; +#endif + } + + [mutating] + void setLensSample(float2 zw) + { +#if USE_AREA_RESERVOIRS + filmLensSample.zw = zw; +#endif + } + + [mutating] + void init() + { + this.M = 0; + this.weight = 0.; +#if USE_AREA_RESERVOIRS + this.filmLensSample = 0.f; +#endif + this.reconnectionHit = packReservoirHitInfo(HitInfo()); + } + + [mutating] + void init(const PathRISState state) + { + this.M = 1; + this.weight = state.weight; + this.integrand = state.integrand; + this.lightPdf = state.lightPdf; + this.rcNextVertexBounceSeed = state.rcNextVertexBounceSeed; +#if USE_AREA_RESERVOIRS + this.filmLensSample = 0.f; +#endif + this.reconnectionHit = packReservoirHitInfo(state.reconnectionHit); + this.pathFlags = state.pathFlags; + this.reconnectionJacobian = state.reconnectionJacobian; + this.reconnectionIncidentRadiance = state.reconnectionIncidentRadiance; + this.reconnectionWi = state.reconnectionWi; + } + + static PathReservoir createFromRisState( const PathRISState state, uint initRandomSeed, float4 filmLensSample) + { + PathReservoir r; + r.M = 1; + r.weight = state.weight; + r.integrand = state.integrand; + r.lightPdf = state.lightPdf; + r.rcNextVertexBounceSeed = state.rcNextVertexBounceSeed; +#if USE_AREA_RESERVOIRS + r.filmLensSample = filmLensSample; +#endif + r.initRandomSeed = initRandomSeed; + r.reconnectionHit = packReservoirHitInfo(state.reconnectionHit); + r.pathFlags = state.pathFlags; + r.reconnectionJacobian = state.reconnectionJacobian; + r.reconnectionIncidentRadiance = state.reconnectionIncidentRadiance; + r.reconnectionWi = state.reconnectionWi; + return r; + } + + [mutating] + bool add( const PathRISState state, uint initRandomSeed, float4 filmLensSample, inout SampleGenerator sg) + { + float w = state.weight; + w = (isnan(w) || isinf(w)) ? 0.0f : w; + this.M += 1; + this.weight += w; + + bool sampleSelected = (sampleNext1D(sg) * this.weight < w); + + this.pathFlags.flags = sampleSelected ? state.pathFlags.flags : this.pathFlags.flags; + this.rcNextVertexBounceSeed = sampleSelected ? state.rcNextVertexBounceSeed : this.rcNextVertexBounceSeed; + this.initRandomSeed = sampleSelected ? initRandomSeed : this.initRandomSeed; +#if USE_AREA_RESERVOIRS + this.filmLensSample = sampleSelected ? filmLensSample : this.filmLensSample; +#endif + this.lightPdf = sampleSelected ? state.lightPdf : this.lightPdf; + + if (sampleSelected) + { + this.reconnectionHit = packReservoirHitInfo(state.reconnectionHit); + } + this.integrand = sampleSelected ? state.integrand : this.integrand; + this.reconnectionJacobian = sampleSelected ? state.reconnectionJacobian : this.reconnectionJacobian; + this.reconnectionIncidentRadiance = sampleSelected ? state.reconnectionIncidentRadiance : this.reconnectionIncidentRadiance; + this.reconnectionWi = sampleSelected ? state.reconnectionWi : this.reconnectionWi; + + return sampleSelected; + } + + [mutating] + bool merge(float3 integrand, float in_Jacobian, PathReservoir inReservoir, inout SampleGenerator sg, float misWeight) + { + float w = PathRISState::toScalar(integrand) * in_Jacobian * inReservoir.weight * misWeight; + w = (isnan(w) || isinf(w)) ? 0.0f : w; + + this.M += inReservoir.M; + this.weight += w; + + bool sampleSelected = (sampleNext1D(sg) * this.weight < w); + + this.pathFlags.flags = sampleSelected ? inReservoir.pathFlags.flags : this.pathFlags.flags; + this.rcNextVertexBounceSeed = sampleSelected ? inReservoir.rcNextVertexBounceSeed : this.rcNextVertexBounceSeed; + this.initRandomSeed = sampleSelected ? inReservoir.initRandomSeed : this.initRandomSeed; + this.lightPdf = sampleSelected ? inReservoir.lightPdf : this.lightPdf; +#if USE_AREA_RESERVOIRS + this.filmLensSample = sampleSelected ? inReservoir.filmLensSample : this.filmLensSample; +#endif + this.reconnectionHit.data = sampleSelected ? inReservoir.reconnectionHit.data : this.reconnectionHit.data; + this.integrand = sampleSelected ? integrand : this.integrand; + this.reconnectionJacobian = sampleSelected ? inReservoir.reconnectionJacobian : this.reconnectionJacobian; + this.reconnectionIncidentRadiance = sampleSelected ? inReservoir.reconnectionIncidentRadiance : this.reconnectionIncidentRadiance; +#if !USE_RESERVOIR_COMPRESSION + this.reconnectionWi = sampleSelected ? inReservoir.reconnectionWi : this.reconnectionWi; +#else + this.packedRcWi = sampleSelected ? inReservoir.packedRcWi : this.packedRcWi; +#endif + + return sampleSelected; + } + + [mutating] + void mergeDirectOverwrite(float3 integrand, PathReservoir inReservoir) + { + this.pathFlags.flags = inReservoir.pathFlags.flags; + this.rcNextVertexBounceSeed = inReservoir.rcNextVertexBounceSeed; + this.initRandomSeed = inReservoir.initRandomSeed; + this.lightPdf = inReservoir.lightPdf; +#if USE_AREA_RESERVOIRS + this.filmLensSample = inReservoir.filmLensSample; +#endif + this.reconnectionHit.data = inReservoir.reconnectionHit.data; + this.integrand = integrand; + this.reconnectionJacobian = inReservoir.reconnectionJacobian; + this.reconnectionIncidentRadiance = inReservoir.reconnectionIncidentRadiance; +#if !USE_RESERVOIR_COMPRESSION + this.reconnectionWi = inReservoir.reconnectionWi; +#else + this.packedRcWi = inReservoir.packedRcWi; +#endif + } + + [mutating] + bool mergeWithoutWriting(float pHatWithJacobian, PathReservoir inReservoir, inout SampleGenerator sg, float misWeight) + { + float w = pHatWithJacobian * inReservoir.weight * misWeight; + w = (isnan(w) || isinf(w)) ? 0.0f : w; + this.M += inReservoir.M; + this.weight += w; + bool sampleSelected = (sampleNext1D(sg) * this.weight < w); +#if USE_AREA_RESERVOIRS + this.filmLensSample = sampleSelected ? inReservoir.filmLensSample : this.filmLensSample; +#endif + return sampleSelected; + } + + [mutating] + void finalizeReservoirWeight() + { + float p_hat = PathRISState::toScalar(integrand); + this.weight = (p_hat == 0.f) ? 0.f : (this.weight / p_hat); + } +} + + +// stores information about reconnection vertex (that can be reused by later vertices) +struct PathReconnectionInfo +{ + ReSTIRPathFlags pathFlags = {}; + uint rcNextVertexBounceSeed = 0; + uint initRandomSeed = 0; + float3 rcThroughput = 0; // path throughput after rcVertex + HitInfo reconnectionHit = {}; + float reconnectionJacobian = 0; + float3 reconnectionWi = 0; + float footprintFactor = 0; +} + +// this is basically a reservoir without M and initRandomSeed +struct PathRISState +{ + float weight = 0; + float3 integrand = 0; + ReSTIRPathFlags pathFlags = {}; + float lightPdf = 0; + uint rcNextVertexBounceSeed = 0; + HitInfo reconnectionHit = {}; + float reconnectionJacobian = 0; + float3 reconnectionIncidentRadiance = 0; + float3 reconnectionWi = 0; + + [mutating] void init() + { + weight = 0.f; + } + + static float gaussrand(float mean, float stddev, inout SampleGenerator sg) + { + // Sampling from the normal distribution (Box-Muller method) + const float2 rand = sampleNext2D(sg); + float Z = sqrt(-2.0 * log(rand.x)) * cos(2.0 * M_PI * rand.y); + float X = Z * stddev * mean + mean; // want the standard deviation to scale with mean to provide a soft threshold + return X; + } + + [mutating] + bool addNEECandidate(float u, PathReconnectionInfo pathRcInfo, bool isRcVertex, uint pathLength, + float3 pathContribution, float3 wi, float3 Le, float3 Lr, float sourcePdf, float lightPdf, uint lightType) + { + bool selected = addPSSSample(u, pathContribution); + uint rcVertexLength = pathRcInfo.pathFlags.rcVertexLength(); + rcVertexLength = isRcVertex ? pathLength : rcVertexLength; + this.pathFlags.flags = selected ? pathRcInfo.pathFlags.flags : this.pathFlags.flags; + this.pathFlags.insertPathLength(selected ? pathLength : this.pathFlags.pathLength()); + this.pathFlags.insertRcVertexLength(selected ? rcVertexLength : this.pathFlags.rcVertexLength()); + this.pathFlags.insertLastVertexNEE(selected || this.pathFlags.lastVertexNEE()); + this.reconnectionIncidentRadiance = selected ? (isRcVertex ? Le : pathRcInfo.rcThroughput * Lr) : this.reconnectionIncidentRadiance; + this.reconnectionWi = selected ? (isRcVertex ? wi : pathRcInfo.reconnectionWi) : this.reconnectionWi; + this.reconnectionHit.data = selected ? pathRcInfo.reconnectionHit.data : this.reconnectionHit.data; + this.reconnectionJacobian = selected ? pathRcInfo.reconnectionJacobian : this.reconnectionJacobian; + this.rcNextVertexBounceSeed = selected ? pathRcInfo.rcNextVertexBounceSeed : this.rcNextVertexBounceSeed; + // integrand in our mixed PSS-solid angle measure + this.integrand = selected ? pathContribution * sourcePdf : this.integrand; + this.lightPdf = selected ? lightPdf : this.lightPdf; + this.pathFlags.insertLightType(selected ? lightType : this.pathFlags.lightType()); + + return selected; + } + + [mutating] + bool addBSDFHitCandidate(float u, PathReconnectionInfo pathRcInfo, uint pathLength, + float3 pathContribution, float3 wi, float3 Le, float3 Lr, float sourcePdf, float lightPdf, uint lightType) + { + + bool selected = addPSSSample(u, pathContribution); + uint rcVertexLength = pathRcInfo.pathFlags.rcVertexLength(); + bool isRcVertex = pathLength == rcVertexLength; + + this.pathFlags.flags = selected ? pathRcInfo.pathFlags.flags : this.pathFlags.flags; + this.pathFlags.insertPathLength(selected ? pathLength : this.pathFlags.pathLength()); + this.pathFlags.insertLastVertexNEE(!selected && this.pathFlags.lastVertexNEE()); + this.reconnectionIncidentRadiance = selected ? (isRcVertex ? Le : (pathRcInfo.rcThroughput * Lr)) : this.reconnectionIncidentRadiance; + + this.reconnectionWi = selected ? pathRcInfo.reconnectionWi : this.reconnectionWi; + this.reconnectionHit.data = selected ? pathRcInfo.reconnectionHit.data : this.reconnectionHit.data; + this.reconnectionJacobian = selected ? pathRcInfo.reconnectionJacobian : this.reconnectionJacobian; + this.rcNextVertexBounceSeed = selected ? pathRcInfo.rcNextVertexBounceSeed : this.rcNextVertexBounceSeed; + + // integrand in our mixed PSS-solid angle measure + this.integrand = selected ? pathContribution * sourcePdf : this.integrand; + this.lightPdf = selected ? lightPdf : this.lightPdf; + this.pathFlags.insertLightType(selected ? lightType : this.pathFlags.lightType()); + + return selected; + } + + [mutating] + bool addPrimaryVisibleEmitterCandidate(float u, float3 pathContribution, float sourcePdf) + { + bool selected = addPSSSample(u, pathContribution); + this.pathFlags.flags = selected ? 0 : this.pathFlags.flags; + this.pathFlags.insertIsVeryDirectLight(selected ? true : this.pathFlags.isVeryDirectLight()); + // integrand in our mixed PSS-solid angle measure + this.integrand = selected ? pathContribution * sourcePdf : this.integrand; + return selected; + } + + + [mutating] + void markAsRcVertex(uint pathLength, HitInfo hit, LastVertexState lastVertexState, + float lightPdf, uint lightType, float3 Le, float3 wi, float reconnectionJacobian) + { + this.pathFlags.insertRcVertexLength(pathLength); + + this.reconnectionHit = hit; + this.pathFlags.insertIsDeltaEvent(lastVertexState.isLastVertexDelta(), true); + this.pathFlags.insertIsTransmissionEvent(lastVertexState.isLastVertexTransmission(), true); + this.pathFlags.insertBSDFComponentType(lastVertexState.lastBSDFComponent(), true); + this.pathFlags.insertSupportBSDFIndexingBeforeRcVertex(lastVertexState.lastVertexSupportBSDFIndexing()); + this.lightPdf = lightPdf; + + this.reconnectionJacobian = reconnectionJacobian; + + this.pathFlags.insertLightType(lightType); + this.reconnectionIncidentRadiance = Le; + this.reconnectionWi = wi; + } + + [mutating] + bool addPSSSample(float u, float3 pathContribution) + { + float w = toScalar(pathContribution); + + w = isnan(w) ? 0.0f : w; + this.weight += w; + + return (u * this.weight < w); + } + + static float toScalar(float3 color) + { + return dot(color, float3(0.299, 0.587, 0.114)); // luminance + } +} + +struct ReconnectionData +{ + HitInfo rcPrevHit; + float3 pathThroughput; +#if !USE_RESERVOIR_COMPRESSION + float3 rcPrevWo; +#else + uint packedRcPrevWo; + property float3 rcPrevWo + { + get { return decodeNormal2x16(packedRcPrevWo); } + set { packedRcPrevWo = encodeNormal2x16(newValue); } + } + +#endif + float rcFootprintFactor; // for checking invertibility for footprint thresholds + + __init(HitInfo rcPrevHit, float3 rcPrevWo, float3 pathThroughput, float rcFootprintFactor) + { + this.rcPrevHit = rcPrevHit; + this.rcPrevWo = rcPrevWo; + this.pathThroughput = pathThroughput; + this.rcFootprintFactor = rcFootprintFactor; + } + + [mutating] + void init() + { + rcPrevHit.data = {}; + rcPrevHit.data[0] = uint(HitType::Triangle) << HitInfo::kTypeOffset; + rcPrevWo = float3(0.f); + pathThroughput = float3(1.f); + rcFootprintFactor = float(0.f); + } +} + +struct NeighborValidMask +{ + uint data; + float cSum; + bool isValid(int i) + { + return data >> i & 1; + } + + [mutating] + void setValid(int i, bool val) + { + data &= ~(1 << i); + data |= int(val) << i; + } + + [mutating] + void setCSum(float val) + { + cSum = val; + } +} + +struct PrecomputedShiftData +{ + float3 pathThroughput; + float Jacobian; + float reconnectionJacobian; +} diff --git a/Source/Modules/ReSTIRPathTracing/PathTracer.slang b/Source/Modules/ReSTIRPathTracing/PathTracer.slang new file mode 100644 index 0000000000..f8f42d5833 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/PathTracer.slang @@ -0,0 +1,847 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Rendering.Lights.EnvMapSampler; +import Rendering.Lights.EmissiveLightSampler; +import Rendering.Lights.EmissiveLightSamplerHelpers; +import Rendering.Volumes.HomogeneousVolumeSampler; +import Rendering.Utils.PixelStats; +import Utils.Geometry.GeometryHelpers; +import Utils.Debug.PixelDebug; +import RenderPasses.Shared.Denoising.NRDHelpers; + +import RenderPasses.PathTracer.PathTracer; +import LoadShadingDataDynamic; + +__exported import RenderPasses.PathTracer.PathTracerUtils; +__exported import RenderPasses.PathTracer.PathState; +__exported import Params; +#include "Rendering/Lights/EmissiveLightSamplerType.slangh" + +ParameterBlock gPathTracer; +static const bool kFetchNRDDataPass = FETCH_NRD_DATA_PASS; + +/** Interface for querying the closest hit in the scene. + This is used in `nextHit` to allow using different raytracing APIs. +*/ +interface IClosestHitQueryDynamic +{ + /** Trace a ray against the scene and return the closest hit point. + Note: The content of the path state must not be changed in the implementation of this interface. + However, for the ReorderTraceRay scheduler it is necessary to pass the state through to + TraceRayHitObject as ray payload and then re-materialize it after ray traversal. + \param[in,out] path Path state. + \param[in] ray Ray. + \param[out] hitT Hit distance (only valid if function returns true). + \return Returns hit info, `isValid() == true` indicates valid hit. + */ + [mutating] HitInfo traceRay(inout PathState path, const Ray ray, out float hitT, bool isPrevFrame); +}; + +/** Interface for querying visibility in the scene. + This is used in `handleHit` (and other variants) to allow using different raytracing APIs. +*/ +interface IVisibilityQueryDynamic +{ + /** Trace a visibility ray against the scene. + \param[in] ray Ray. + \return Returns true if the ray endpoints are mutually visible (i.e. the ray does NOT intersect the scene). + */ + [mutating] bool traceVisibilityRay(const Ray ray, bool isPrevFrame); +}; + +struct GeneratePathClosestHitQuery : IClosestHitQueryDynamic +{ + HitInfo primaryHit = {}; + float primaryHitT = {}; + + HitInfo traceRay(inout PathState path, const Ray ray, out float hitT, bool isPrevFrame) + { + hitT = {}; + + if (primaryHit.isValid() && primaryHitT < ray.tMax) + { + hitT = primaryHitT; + return primaryHit; + } + + return makeInvalidHit(); + } +}; + +/** Finds the next surface or volume hit. + If there are volumes in the scene, this function first samples a free flight distance. + Then a (shortened) ray is traced against the scene to find any surface hits. + This is done using the provided IClosestHitQuery adaptor to allow using different tracing APIs (TraceRay, TracerRayInline). + Finally, using both the free flight distance and the closest surface hit, the final hit is determined and stored in the path state. + \param[in,out] path The path state. + \param[in,out] chq Closest hit query. +*/ +void nextHit(inout PathState path, inout ClosestHitQuery chq, bool isPrevFrame) +{ + Ray ray = path.getScatterRay(); + + path.incrementVertexIndex(); + + logTraceRay(PixelStatsRayType::ClosestHit); + float hitT; + + HitInfo hit = chq.traceRay(path, ray, hitT, isPrevFrame); + + if (hit.isValid()) + { + path.setHit(hit); + + if (!path.interiorList.isEmpty()) + { + const uint materialID = path.interiorList.getTopMaterialID(); + let material = gScene.materials.getMaterial(materialID); + let vp = material.getHomogeneousVolumeProperties(gScene.materials, materialID); + gPathTracer.updatePathThroughput(path, HomogeneousVolumeSampler::evalTransmittance(vp, hitT)); + } + } + else + { + path.clearHit(); + } + + // Accumulate path length. + if (gPathTracer.kOutputGuideData) + { + if (path.isHit()) + { + path.sceneLength += float16_t(hitT); + } + } +} + +/** Generates a light sample on the environment map. + \param[in] vertex Path vertex. + \param[in,out] sg Sample generator. + \param[out] ls Struct describing valid samples. + \return True if the sample is valid and has nonzero contribution, false otherwise. +*/ +bool generateEnvMapSample(const PathVertex vertex, inout SampleGenerator sg, out LightSample ls, bool isPrevFrame) +{ + ls = {}; // Default initialization to avoid divergence at returns. + + if (!gPathTracer.kUseEnvLight) return false; + + // Sample environment map. + EnvMapSample lightSample; + if (!gPathTracer.envMapSampler.sample(sampleNext2D(sg), lightSample, 0.f, isPrevFrame)) return false; + + // Setup returned sample. + ls.Li = lightSample.pdf > 0.f ? lightSample.Le / lightSample.pdf : float3(0); + ls.pdf = lightSample.pdf; + ls.origin = vertex.getRayOrigin(lightSample.dir); + ls.distance = kRayTMax; + ls.dir = lightSample.dir; + + return any(ls.Li > 0.f); +} + +/** Generates a light sample on the emissive geometry. + \param[in] vertex Path vertex. + \param[in] upperHemisphere True if only upper hemisphere (w.r.t. shading normal) should be considered. + \param[in,out] sg Sample generator. + \param[out] ls Struct describing valid samples. + \return True if the sample is valid and has nonzero contribution, false otherwise. +*/ +bool generateEmissiveSample( const PathVertex vertex, const bool upperHemisphere, inout SampleGenerator sg, out LightSample ls, bool isPrevFrame ) +{ + ls = {}; // Default initialization to avoid divergence at returns. + if (!gPathTracer.kUseEmissiveLights) return false; + + TriangleLightSample tls; + if (!gPathTracer.emissiveSampler.sampleLight(vertex.pos, vertex.getOrientedFaceNormal(), upperHemisphere, sg, tls, isPrevFrame)) return false; + + // Setup returned sample. + ls.Li = tls.pdf > 0.f ? tls.Le / tls.pdf : float3(0); + ls.pdf = tls.pdf; + // Offset shading and light position to avoid self-intersection. + float3 lightPos = computeRayOrigin(tls.posW, tls.normalW); + ls.origin = vertex.getRayOrigin(lightPos - vertex.pos); + float3 toLight = lightPos - ls.origin; + ls.distance = length(toLight); + ls.dir = normalize(toLight); + + ls.barycentrics = tls.uv; + ls.triangleId = tls.triangleIndex; + ls.geometryTerm = tls.geometryTerm; + + return any(ls.Li > 0.f); +} + +/** Samples a light source in the scene. + This function first stochastically selects a type of light source to sample, + and then calls that the sampling function for the chosen light type. + The upper/lower hemisphere is defined as the union of the hemispheres w.r.t. to the shading and face normals. + \param[in] vertex Path vertex. + \param[in] sampleUpperHemisphere True if the upper hemisphere should be sampled. + \param[in] sampleLowerHemisphere True if the lower hemisphere should be sampled. + \param[in,out] sg Sample generator. + \param[out] ls Struct describing valid samples. + \return True if the sample is valid and has nonzero contribution, false otherwise. +*/ +bool generateLightSample( const PathVertex vertex, const bool sampleUpperHemisphere, const bool sampleLowerHemisphere, inout SampleGenerator sg, out LightSample ls, bool isPrevFrame ) +{ + ls = {}; + + uint lightType; + float selectionPdf; + if (!gPathTracer.selectLightType(lightType, selectionPdf, sg)) return false; + + bool valid = false; + if (gPathTracer.kUseEnvLight && lightType == (uint)LightType::EnvMap) valid = generateEnvMapSample(vertex, sg, ls, isPrevFrame); + if (gPathTracer.kUseEmissiveLights && lightType == (uint)LightType::Emissive) + { + // Emissive light samplers have an option to exclusively sample the upper hemisphere. + bool upperHemisphere = sampleUpperHemisphere && !sampleLowerHemisphere; + valid = generateEmissiveSample(vertex, upperHemisphere, sg, ls, isPrevFrame); + } + if (gPathTracer.kUseAnalyticLights && lightType == (uint)LightType::Analytic) + { + valid = gPathTracer.generateAnalyticLightSample(vertex, sg, ls); + } + if (!valid) return false; + + // Reject samples in non-requested hemispheres. + float NdotL = dot(vertex.getOrientedFaceNormal(), ls.dir); + if ((!sampleUpperHemisphere && NdotL >= -kMinCosTheta) || (!sampleLowerHemisphere && NdotL <= kMinCosTheta)) + return false; + + // Account for light type selection. + ls.lightType = lightType; + ls.pdf *= selectionPdf; + ls.Li /= selectionPdf; + + return true; +} + +[ForceInline] +void initReplayThresholds(inout SampleGenerator sg, inout PathState path) +{ + if (gPathTracer.restirpt.settings.nearFieldDistanceSigma == 0.f) + path.restirPTData.distanceThreshold = gPathTracer.restirpt.settings.nearFieldDistanceThreshold; + else + path.restirPTData.distanceThreshold = PathRISState::gaussrand(gPathTracer.restirpt.settings.nearFieldDistanceThreshold, gPathTracer.restirpt.settings.nearFieldDistanceSigma, sg); + + if (gPathTracer.restirpt.settings.specularRoughnessSigma == 0.f) + path.restirPTData.roughnessThreshold = gPathTracer.restirpt.settings.specularRoughnessThreshold; + else + path.restirPTData.roughnessThreshold = PathRISState::gaussrand(gPathTracer.restirpt.settings.specularRoughnessThreshold, gPathTracer.restirpt.settings.specularRoughnessSigma, sg); +} + +void generatePathForRandomReplay( const HitInfo hit, const ShadingData sd, out PathState path, uint initRandomSeed, ReSTIRPathFlags pathFlags ) +{ + path = {}; + path.setActive(); + path.thp = float3(1.f); + + + // Create primary ray. + if (sd.curveRadius > 0.f) + path.origin = sd.posW - sd.frame.N * sd.curveRadius; + else + path.origin = sd.computeRayOrigin(); + + path.dir = float3(0.f, 0.f, -1.f); + + path.sg = SampleGenerator(initRandomSeed); + + initReplayThresholds(path.sg, path); + + path.restirPTData.pathRcInfo.pathFlags = pathFlags; + + // Load the primary hit info from the V-buffer. + + // Grid volumes are currently not part of the scene geometry. + // In order to check for volume scattering events *before* the primary hit, + // we perform a vertex query and use the primary hit as the result of a "simulated" ray query. + + { + // If invalid, the path is still active and treated as a miss. + if (hit.isValid()) + { + path.setHit(hit); + path.setVertexIndex(1); + } + // !hit.isValid() case shouldn't occur in random replay + } +} + +void generatePathForTemporalUpdate( const HitInfo reconnectionHit, const ShadingData rcSd, out PathState path, + float3 reconnectionWi, uint rcNextVertexBounceSeed, uint initRandomSeed, ReSTIRPathFlags pathFlags ) +{ + path = {}; + path.setActive(); + path.thp = float3(1.f); + + bool isTransmission = pathFlags.decodeIsTransmissionEvent(false); + path.origin = rcSd.computeRayOrigin(!isTransmission); + path.dir = reconnectionWi; + + path.sg = SampleGenerator(rcNextVertexBounceSeed); + + // Use a separate generator for thresholds so `path.sg` (consumed by the rc-vertex scatter ray) + // stays bit-aligned with the original trace. + SampleGenerator thresholdSg = SampleGenerator(initRandomSeed); + initReplayThresholds(thresholdSg, path); + + path.restirPTData.pathRcInfo.pathFlags = pathFlags; + path.restirPTData.pathRcInfo.rcThroughput = float3(1.f); + + // Grid volumes are currently not part of the scene geometry. + // In order to check for volume scattering events *before* the primary hit, + // we perform a vertex query and use the primary hit as the result of a "simulated" ray query. + { + // If invalid, the path is still active and treated as a miss. + if (reconnectionHit.isValid()) + { + path.setHit(reconnectionHit); + path.setVertexIndex(pathFlags.rcVertexLength() + 1); + } + // !reconnectionHit.isValid() case shouldn't occur in random replay + } +} + +// return if has non-zero contribution to path +void updateDirectLightingForRcVertex( inout PathState path, ShadingData sd, const IMaterialInstance mi, inout VisibilityQuery vq, + bool isRcVertex, bool isPrevFrame ) +{ + const bool isTriangleHit = path.hit.getType() == HitType::Triangle; + const bool isCurveHit = gPathTracer.kUseCurves && (path.hit.getType() == HitType::Curve); + const bool isHairMaterial = gPathTracer.kUseHairMaterial && (sd.mtl.getMaterialType() == MaterialType::Hair); + const bool isCurvePolyTubeHit = isTriangleHit && isHairMaterial; + + LightSample ls = {}; + + const uint lobes = mi.getLobeTypes(sd); + // Setup path vertex. + PathVertex vertex = PathVertex( path.getVertexIndex(), (isCurveHit ? sd.posW - sd.frame.N * sd.curveRadius : sd.posW), sd.faceN, sd.frontFacing, isCurveHit ); + + // Determine if upper/lower hemispheres need to be sampled. + bool sampleUpperHemisphere = isCurveHit || isCurvePolyTubeHit || ((lobes & (uint)LobeType::NonDeltaReflection) != 0); + sampleUpperHemisphere = (!kUseLightsInDielectricVolumes && path.isInsideDielectricVolume()) ? false : sampleUpperHemisphere; + + bool sampleLowerHemisphere = isCurveHit || isCurvePolyTubeHit || ((lobes & (uint)LobeType::NonDeltaTransmission) != 0); + + // Sample a light. + bool validSample = generateLightSample(vertex, sampleUpperHemisphere, sampleLowerHemisphere, path.sg, ls, isPrevFrame); + + path.setLightSampled(sampleUpperHemisphere, sampleLowerHemisphere); + + path.dir = ls.dir; + + if (validSample) + { + // for ReSTIR PT + if (ls.lightType == (uint)LightType::Analytic && ls.pdf == 0.f) + ls.pdf = gPathTracer.getAnalyicSelectionProbability(); // analytic light doesn't have a solid angle PDF. + float3 Le = ls.Li * ls.pdf; + + // Apply MIS weight. (we don't care here) + if (kUseMIS && ls.lightType != (uint)LightType::Analytic && !isRcVertex) + { + float scatterPdf = mi.evalPdf(sd, ls.dir, kUseBSDFSampling); + ls.Li *= evalMIS(1, ls.pdf, 1, scatterPdf); + } + + float3 weight = mi.eval(sd, ls.dir, path.sg); + float3 Lr = weight * ls.Li; + if (any(Lr > 0.f)) + { + Ray ray = ls.getVisibilityRay(); + bool visible = true; + if (isCurvePolyTubeHit) + { + // For curves tessellated into poly-tubes, we make sure that the origin is on the same side as light + // so there is no self-shadowing (transmission lobe of hair BSDF takes care of that). + if (dot(sd.frame.N, ray.dir) < 0.f) + { + ray.origin = ray.origin - sd.frame.N * sd.curveRadius * 2.1f; + } + } + + logTraceRay(PixelStatsRayType::Visibility); + visible = vq.traceVisibilityRay(ray, isPrevFrame); + + if (visible) + { + gPathTracer.addToPathContribution(path, Lr); + float3 reconnectionIncidentRadiance = isRcVertex ? Le : path.restirPTData.pathRcInfo.rcThroughput * Lr; + gPathTracer.restirpt.WriteTemporalRetraceData(path, ls.lightType, ls.pdf, reconnectionIncidentRadiance); + } + } + } +} + +bool generateScatterRayGivenDirection(ShadingData sd, const IMaterialInstance mi, inout PathState path) +{ + uint componentType = path.restirPTData.pathRcInfo.pathFlags.decodeBSDFComponent(false); + bool isDeltaEvent = path.restirPTData.pathRcInfo.pathFlags.decodeIsDeltaEvent(false); + bool isTransmission = path.restirPTData.pathRcInfo.pathFlags.decodeIsTransmissionEvent(false); + if (isDeltaEvent) return false; + + BSDFSample result = {}; + + // hack the material system for sampled lobe + sd.mtl.setBSDFIndex(componentType); // if this is non-zero, result.pdf will only return the pdf of the sampled lobe + float3 f = mi.eval(sd, path.dir, path.sg); + float pdf = mi.evalPdf(sd, path.dir); + + result.weight = pdf <= 0.f ? 0.f : f / pdf; + + if (all(f == 0.f) || pdf == 0.f) return false; + + result.wo = path.dir; + + + // we should only compute this pdf if next bounce we are going to terminate with bsdf hit + if ((path.getVertexIndex() - 1) == path.restirPTData.pathRcInfo.pathFlags.pathLength() && + !path.restirPTData.pathRcInfo.pathFlags.lastVertexNEE()) + { + sd.mtl.setBSDFIndex(-1); // if this is non-zero, result.pdf will only return the pdf of the sampled lobe + result.pdf = mi.evalPdf(sd, path.dir); + // we need to query pdf of unsampled components + } + path.setSampledBSDFComponent(componentType); + + result.lobeType = componentType == 0 ? uint(LobeType::DiffuseReflection) : + (componentType == 1 ? uint(LobeType::DiffuseTransmission) : ((componentType == 2 || !isTransmission) ? + uint(LobeType::SpecularReflection) : uint(LobeType::SpecularTransmission))); + + // ReSTIR PT + path.setRough(mi.isSampleRough(sd, componentType, result.pdf, path.restirPTData.roughnessThreshold)); + path.setSupportBSDFIndexing(mi.supportBSDFIndexing()); + path.setSupportSimpleRoughnessThreshold(mi.supportSimpleRoughnessThreshold()); + + path.setLightSampled(true, true); + + return gPathTracer.generateScatterRay(result, sd, mi, path); +} + +bool generateScatterRayRandomReplay(const ShadingData sd, const IMaterialInstance mi, inout PathState path) +{ + bool needToQueryAllLobes = (path.getVertexIndex() - 1) == path.restirPTData.pathRcInfo.pathFlags.pathLength() && + !path.restirPTData.pathRcInfo.pathFlags.lastVertexNEE(); + BSDFSample result = {}; + // hack the material system for sampled lobe + bool valid = mi.sample(sd, path.sg, result, kUseBSDFSampling); + + // set result.pdf to a large value for delta + if (result.isLobe(LobeType::Delta)) result.pdf = 1e20f; + + valid &= !isinf(result.pdf) && all(!isinf(result.weight)); + if (!valid) return false; + + + // get total pdf in a separate call + uint componentType = result.BSDFIndex; + + path.setSampledBSDFComponent(componentType); + + // ReSTIR PT + path.setRough(mi.isSampleRough(sd, componentType, result.pdf, path.restirPTData.roughnessThreshold)); + path.setSupportBSDFIndexing(mi.supportBSDFIndexing()); + path.setSupportSimpleRoughnessThreshold(mi.supportSimpleRoughnessThreshold()); + + + // Ignore valid on purpose for now. + if (kFetchNRDDataPass) + { + const uint lobeTypes = mi.getLobeTypes(sd); + const bool hasDeltaTransmissionLobe = (lobeTypes & (uint)LobeType::DeltaTransmission) != 0; + const bool hasNonDeltaLobes = (lobeTypes & (uint)LobeType::NonDelta) != 0; + + if (path.getVertexIndex() == 1) + { + // Mark path as delta-only if it followed delta lobe on the primary hit, even though there might have been non-delta lobes. + path.setDeltaOnlyPath(result.isLobe(LobeType::DeltaReflection) || result.isLobe(LobeType::DeltaTransmission)); + + path.setDeltaReflectionPrimaryHit(result.isLobe(LobeType::DeltaReflection)); + path.setDeltaTransmissionPath(result.isLobe(LobeType::DeltaTransmission)); + } + + if (path.getVertexIndex() > 1) + { + if (hasNonDeltaLobes) path.setDeltaOnlyPath(false); + + if (path.isDeltaTransmissionPath() && path.isDeltaOnlyPath() && hasDeltaTransmissionLobe) + { + if (result.isLobe(LobeType::DeltaReflection) && !isDeltaReflectionAllowedAlongDeltaTransmissionPath(sd)) + { + path.setDeltaTransmissionPath(false); + } + } + } + } + + return gPathTracer.generateScatterRay(result, sd, mi, path); +} + +[ForceInline] +float3 computeReplayPathOriginForHit(ShadingData sd, bool isCurveHit, bool isCurvePolyTubeHit +) +{ + if (isCurveHit) + { + // For curves, set the new origin at the sphere center. + return sd.posW - sd.frame.N * sd.curveRadius; + } + else if (isCurvePolyTubeHit) + { + // For curves tessellated into poly-tubes, offset slightly away from the curve center. + return sd.posW + sd.frame.N * sd.curveRadius * 0.1f; + } + return sd.computeRayOrigin(); +} + + +void handleSurfaceHitReplayPath(inout PathState path, inout VisibilityQuery vq) +{ + const HitType hitType = path.hit.getType(); + const bool isTriangleHit = hitType == HitType::Triangle; + const bool isCurveHit = gPathTracer.kUseCurves && (hitType == HitType::Curve); + let lod = gPathTracer.createTextureSampler(path, false); + + // Load shading data. This is a long latency operation. + ShadingData sd = loadShadingData(path.hit, path.origin, path.dir, path.isPrevFrame()); + + const bool isHairMaterial = gPathTracer.kUseHairMaterial && (sd.mtl.getMaterialType() == MaterialType::Hair); + const bool isCurvePolyTubeHit = isTriangleHit && isHairMaterial; + + + // Reject false hits in nested dielectrics. + if (!gPathTracer.handleNestedDielectrics(sd, path)) return; + + int currentPathLength = path.getVertexIndex() - 1; + int rcVertexLength = path.restirPTData.pathRcInfo.pathFlags.rcVertexLength(); + + // Create BSDF instance and query its properties. + const IMaterialInstance mi = gScene.materials.getMaterialInstance(sd, lod); + BSDFProperties bsdfProperties = mi.getProperties(sd); + + gPathTracer.evaluateLightProfile(bsdfProperties, sd, path); + + // Disable specular lobes if caustics are disabled and path already contains a diffuse vertex. + bool isSpecular = bsdfProperties.roughness <= gPathTracer.params.specularRoughnessThreshold; + if (kDisableCaustics && path.getBounces(BounceType::Diffuse) > 0 && isSpecular) + { + sd.mtl.setActiveLobes((uint)LobeType::Diffuse); + } + + // Optionally disable emission inside volumes. + if (!kUseLightsInDielectricVolumes && path.isInsideDielectricVolume()) + { + bsdfProperties.emission = float3(0.f); + } + + // Check if the scatter event is samplable by the light sampling technique. + const bool isLightSamplable = path.isLightSamplable(); + + // Add emitted radiance. + // The primary hit is always included, secondary hits only if emissive lights are enabled and the full light contribution hasn't been sampled elsewhere. + bool computeEmissive = gPathTracer.kUseEmissiveLights && (!kUseNEE || kUseMIS || !path.isLightSampled() || !isLightSamplable); + + // kUseReSTIRPT stuff + float distanceToPrevVertex = length(sd.posW - path.origin); + float geometryFactor = abs(dot(sd.faceN, sd.V)) / (distanceToPrevVertex * distanceToPrevVertex); + LastVertexState lastVertexState; + float inverseGeometryFactor = distanceToPrevVertex * distanceToPrevVertex / abs(dot(path.normal, sd.V)); + float rayFootprint = 1.f / (geometryFactor * path.pdf); + + lastVertexState.Init( path.isDelta(), path.isTransmission(), path.getSampledBSDFComponent(), path.supportBSDFIndexing(), path.isRough(), + rayFootprint >= path.restirPTData.primaryRayFootprint * path.restirPTData.distanceThreshold ); + + bool terminateByHittingLight = currentPathLength - 1 == path.restirPTData.pathRcInfo.pathFlags.pathLength() && + !path.restirPTData.pathRcInfo.pathFlags.lastVertexNEE(); + // terminateByNEE is currently impossible because of the forced NEE reconnection + bool terminateByNEE = currentPathLength == path.restirPTData.pathRcInfo.pathFlags.pathLength() && + path.restirPTData.pathRcInfo.pathFlags.lastVertexNEE(); + // skip random number to align between PSS sequences when reuse + sampleNext1D(path.sg); + + if (computeEmissive && terminateByHittingLight && any(bsdfProperties.emission > 0.f)) + { + float misWeight = 1.f; + float lightPdf = 0.f; + + if (gPathTracer.kUseEmissiveLights && kUseNEE && kUseMIS && isTriangleHit && path.isLightSampled() && isLightSamplable) + { + // If NEE and MIS are enabled, and we've already sampled emissive lights, + // then we need to evaluate the MIS weight here to account for the remaining contribution. + // Note that MIS is only applied for hits on emissive triangles (other emissive geometry is not supported). + + // Prepare hit point struct with data needed for emissive light PDF evaluation. + TriangleHit triangleHit = path.hit.getTriangleHit(); + TriangleLightHit hit; + hit.triangleIndex = gScene.lightCollection.getTriangleIndex(triangleHit.instanceID, triangleHit.primitiveIndex); + hit.posW = sd.posW; + hit.normalW = sd.frontFacing ? sd.faceN : -sd.faceN; + + // Evaluate PDF at the hit, had it been generated with light sampling. + // Emissive light samplers have an option to exclusively sample the upper hemisphere. + bool upperHemisphere = path.isLightSampledUpper() && !path.isLightSampledLower(); + lightPdf = gPathTracer.getEmissiveSelectionProbability() * gPathTracer.emissiveSampler.evalPdf(path.origin, path.normal, upperHemisphere, hit); + + // Compute MIS weight by combining this with BSDF sampling. + // Note we can assume path.pdf > 0.f since we shouldn't have got here otherwise. + + int initialLightSampleCount = max(1, gPathTracer.restirpt.computeInitialLightSampleCount( gPathTracer.restirpt.initialLightSampleCount, currentPathLength)); + misWeight = evalMIS(1, path.pdf, (kUseLightTileRIS ? initialLightSampleCount : 1), lightPdf); + } + + // Accumulate emitted radiance weighted by path throughput and MIS weight. + gPathTracer.addToPathContribution(path, misWeight * bsdfProperties.emission); + + if (gPathTracer.restirpt.kTemporalUpdateForDynamicScene) + { + float3 reconnectionIncidentRadiance = currentPathLength - 1 == rcVertexLength ? + bsdfProperties.emission : path.restirPTData.pathRcInfo.rcThroughput * misWeight * bsdfProperties.emission; + gPathTracer.restirpt.WriteTemporalRetraceData(path, (uint)LightType::Emissive, lightPdf, reconnectionIncidentRadiance); + } + if (!kFetchNRDDataPass) + path.L = currentPathLength < rcVertexLength && ( + min(rayFootprint, inverseGeometryFactor / gPathTracer.restirpt.getDiffusePseudoPdf()) >= path.restirPTData.primaryRayFootprint * path.restirPTData.distanceThreshold + && (!path.supportSimpleRoughnessThreshold() || lastVertexState.isLastVertexRough())) ? 0.f : path.L; + } + + // Terminate after scatter ray on last vertex has been processed. + bool lastVertex = false; + if (!(kMaxSurfaceBounces == kMaxDiffuseBounces && kMaxSurfaceBounces == kMaxSpecularBounces && kMaxSurfaceBounces == kMaxTransmissionBounces)) + lastVertex = gPathTracer.hasFinishedSurfaceBounces(path); + + if (lastVertex || terminateByHittingLight || gPathTracer.restirpt.kTemporalUpdateForDynamicScene && currentPathLength >= rcVertexLength && terminateByNEE) + { + if (kFetchNRDDataPass) path.thp = 0.f; + path.terminate(); + return; + } + + if (kFetchNRDDataPass) + { + // if we are about to terminate due to length limit or reconnection, also write NRD data + if (currentPathLength == rcVertexLength - 1) // reached Vertex Before reconnection + { + if (path.isDeltaReflectionPrimaryHit() || path.isDeltaTransmissionPath()) + { + const MaterialType materialType = sd.mtl.getMaterialType(); + float3 deltaReflectance = getMaterialReflectanceForDeltaPaths(materialType, true, sd, bsdfProperties); + path.thp = deltaReflectance; + } + else path.thp = 0.f; + + path.terminate(); + return; + } + } + + path.origin = computeReplayPathOriginForHit(sd, isCurveHit, isCurvePolyTubeHit + ); + // Determine if BSDF has non-delta lobes. + const uint lobes = mi.getLobeTypes(sd); + const bool hasNonDeltaLobes = (lobes & (uint)LobeType::NonDelta) != 0; + + // this is always true if we are not doing temporal update + if (!kFetchNRDDataPass && (!gPathTracer.restirpt.kTemporalUpdateForDynamicScene || currentPathLength < rcVertexLength)) + { + bool shouldTerminate = currentPathLength == rcVertexLength - 1; + bool nonInvertible = shouldTerminate && !hasNonDeltaLobes; + + if (shouldTerminate && !nonInvertible) + { + gPathTracer.restirpt.WriteReconnectionData( path, sd.V, lastVertexState, rayFootprint, inverseGeometryFactor ); + } + + path.setVertexIndex(nonInvertible ? 0 : path.getVertexIndex()); + path.setFlag(PathFlags::active, !shouldTerminate); + + if (!path.isActive()) return; + } + + if (terminateByNEE) + { + path.terminate(); + return; + } + + // we can use stored light vertex if we use a position-independent light sampler + + { +#if _EMISSIVE_LIGHT_SAMPLER_TYPE == EMISSIVE_LIGHT_SAMPLER_LIGHT_BVH + // Check if we should apply NEE. + const bool sampleLights = kUseNEE && hasNonDeltaLobes; + if (sampleLights) + { + bool sampleUpperHemisphere = isCurveHit || isCurvePolyTubeHit || ((lobes & (uint)LobeType::NonDeltaReflection) != 0); + bool sampleLowerHemisphere = isCurveHit || isCurvePolyTubeHit || ((lobes & (uint)LobeType::NonDeltaTransmission) != 0); + if (!kUseLightsInDielectricVolumes && path.isInsideDielectricVolume()) sampleUpperHemisphere = false; + path.setLightSampled(sampleUpperHemisphere, sampleLowerHemisphere); + } +#endif + + // make sure that random numbers match + sampleNext1D(path.sg); + gPathTracer.skipLightSampleRandomNumbers(path.sg); + } + + if (kUseRussianRoulette) sampleNext1D(path.sg); + + // Russian roulette to terminate paths early. + + bool wasDeltaOnlyPathBeforeScattering = path.isDeltaOnlyPath(); + bool wasDeltaTransmissionPathBeforeScattering = path.isDeltaTransmissionPath(); + + // Generate the next path segment or terminate. + bool valid = generateScatterRayRandomReplay(sd, mi, path); + + if (kFetchNRDDataPass) + { + // write NRD data when non-delta scattering is sampled + if (!path.isDelta()) + { + if (path.isDeltaReflectionPrimaryHit() || wasDeltaTransmissionPathBeforeScattering) + { + const MaterialType materialType = sd.mtl.getMaterialType(); + const bool hasDeltaLobes = (lobes & (uint)LobeType::Delta) != 0; + float3 deltaReflectance = getMaterialReflectanceForDeltaPaths(materialType, hasDeltaLobes, sd, bsdfProperties); + path.thp = deltaReflectance; + } + else path.thp = 0.f; + path.terminate(); + return; + } + } + + // non-invertible (current vertex being rough before the replay ends) + if (!kFetchNRDDataPass && ((!gPathTracer.restirpt.kTemporalUpdateForDynamicScene || currentPathLength < rcVertexLength) && + (min(rayFootprint, inverseGeometryFactor / path.pdf) >= path.restirPTData.primaryRayFootprint * path.restirPTData.distanceThreshold && lastVertexState.isLastVertexRough() + ))) + { + path.setVertexIndex(0); + valid = false; + } + + if (!(kMaxSurfaceBounces == kMaxDiffuseBounces && kMaxSurfaceBounces == kMaxSpecularBounces && kMaxSurfaceBounces == kMaxTransmissionBounces)) + { + // Check if this is the last path vertex. Don't terminate at delta vertices as it messes with NRC predictions). + bool isLastVertex = gPathTracer.hasFinishedSurfaceBounces(path); + // Terminate if this is the last path vertex and light sampling already completely sampled incident radiance. + if (kUseNEE && !kUseMIS && isLastVertex && path.isLightSamplable()) valid = false; + } + + // Terminate caustics paths. + if (kDisableCaustics && path.getBounces(BounceType::Diffuse) > 0 && path.isSpecular()) valid = false; + + // Terminate the ray if we've generated an invalid segment + path.setFlag(PathFlags::active, valid); +} + +void handleHitReplayPath(inout PathState path, inout VisibilityQuery vq) +{ + // doesn't support volume hit + if (path.hit.getType() == HitType::Volume) + { + } + else + { + handleSurfaceHitReplayPath(path, vq); + } +} +/** Handle the case when a scatter ray hits a volume. + After handling the hit, a new scatter ray is generated or the path is terminated. + \param[in,out] path The path state. + \param[in,out] vq Visibility query. +*/ + +/** Handle hit on specular materials. + After handling the hit, a new scatter ray is generated or the path is terminated. + \param[in,out] path The path state. +*/ + +void handleMissReplayPath(inout PathState path) +{ + // Upon miss: + // - Compute MIS weight if previous path vertex sampled a light + // - Evaluate environment map + // - Write guiding data + // - Terminate the path + + // Check if the scatter event is samplable by the light sampling technique. + const bool isLightSamplable = path.isLightSamplable(); + + // Add env radiance. + bool computeEnv = gPathTracer.kUseEnvLight && (!kUseNEE || kUseMIS || !path.isLightSampled() || !isLightSamplable); + + // Disable all constributions to direct illumination if requested. + if (/*kDisableDirectIllumination &&*/ path.getVertexIndex() == 2 && !gPathTracer.restirpt.sampleDI) computeEnv = false; + + float3 emitterRadiance = 0.f; + + int currentPathLength = path.getVertexIndex() - 1; + int rcVertexLength = path.restirPTData.pathRcInfo.pathFlags.rcVertexLength(); + + bool terminateByHittingLight = currentPathLength - 1 == path.restirPTData.pathRcInfo.pathFlags.pathLength() && + !path.restirPTData.pathRcInfo.pathFlags.lastVertexNEE(); // && !isRcVertexEmissive; + + if (computeEnv && terminateByHittingLight) + { + + float misWeight = 1.f; + float lightPdf = 0.f; + if (kUseNEE && kUseMIS && path.isLightSampled() && isLightSamplable) + { + // If NEE and MIS are enabled, and we've already sampled the env map, + // then we need to evaluate the MIS weight here to account for the remaining contribution. + + // Evaluate PDF, had it been generated with light sampling. + lightPdf = gPathTracer.getEnvMapSelectionProbability() * gPathTracer.envMapSampler.evalPdf(path.dir); + + // Compute MIS weight by combining this with BSDF sampling. + // Note we can assume path.pdf > 0.f since we shouldn't have got here otherwise. + int initialLightSampleCount = gPathTracer.restirpt.computeInitialLightSampleCount( gPathTracer.restirpt.initialLightSampleCount, currentPathLength); + misWeight = evalMIS(1, path.pdf, (kUseLightTileRIS ? initialLightSampleCount : 1), lightPdf); + } + + float3 Le = gPathTracer.envMapSampler.eval(path.dir, 0.f, path.isPrevFrame()); + emitterRadiance = misWeight * Le; + gPathTracer.addToPathContribution(path, emitterRadiance); + // for temporal update + float3 reconnectionIncidentRadiance = currentPathLength - 1 == rcVertexLength ? Le : path.restirPTData.pathRcInfo.rcThroughput * emitterRadiance; + gPathTracer.restirpt.WriteTemporalRetraceData(path, (uint)LightType::EnvMap, lightPdf, reconnectionIncidentRadiance); + + // differentiate between hybrid shift replay and temporal update + // we are suppose to terminate at this vertex, so this shouldn't be connectible. otherwise it is non-invertible + if (!kFetchNRDDataPass) + path.L = currentPathLength < rcVertexLength && path.isRough() ? 0.f : path.L; + } + + path.terminate(); +} + diff --git a/Source/Modules/ReSTIRPathTracing/ReSTIRPT.slang b/Source/Modules/ReSTIRPathTracing/ReSTIRPT.slang new file mode 100644 index 0000000000..4fbbd6e588 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/ReSTIRPT.slang @@ -0,0 +1,573 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#pragma once +#include "RetraceScheduleDefinition.slangh" +#include "Utils/HostDeviceShared.slangh" +#include "Utils/Sampling/SampleGeneratorType.slangh" +BEGIN_NAMESPACE_FALCOR + +#ifndef HOST_CODE +import Utils.Math.FormatConversion; +import Utils.Math.MathHelpers; +import Utils.Math.PackedFormats; +import RenderPasses.PathTracer.PathState; +import RenderPasses.PathTracer.PathTracerUtils; +__exported import RenderPasses.PathTracer.Params; +__exported import ReSTIRPathTracingParams; +import Rendering.Lights.LightHelpers; +import Scene.Material.MaterialData; +import Scene.Material.MaterialTypes; +import PathReservoir; +import Scene.Scene; +import Scene.ShadingData; +import Utils.Debug.PixelDebug; +import Rendering.Materials.IMaterialInstance; +#include "Utils/Math/MathConstants.slangh" + +#ifndef RESTIRPT_SHIFT_MAPPING +#define RESTIRPT_SHIFT_MAPPING 0 +#endif + +#ifndef TEMPORAL_UPDATE_FOR_DYNAMIC_SCENE +#define TEMPORAL_UPDATE_FOR_DYNAMIC_SCENE 0 +#endif + +#ifndef USE_RESERVOIR_COMPRESSION +#define USE_RESERVOIR_COMPRESSION 0 +#endif + +#ifndef USE_AREA_RESERVOIRS +#define USE_AREA_RESERVOIRS 0 +#endif + +#ifndef USE_DECOUPLED_SHADING +#define USE_DECOUPLED_SHADING 0 +#endif + +#ifndef NUM_SPATIAL_NEIGHBORS +#define NUM_SPATIAL_NEIGHBORS 3 +#endif + +#ifndef RETRACE_SCHEDULE_TYPE +#define RETRACE_SCHEDULE_TYPE RETRACE_SCHEDULE_NAIVE +#endif + +#ifndef USE_PREV_FRAME_SCENE_DATA +#define USE_PREV_FRAME_SCENE_DATA 0 +#endif + +#endif + +struct ReSTIRPT +{ + enum class LocalStrategy + { + None = 0x0, + RoughnessCondition = 0x1, + DistanceCondition = 0x2, + }; + + enum class RetraceScheduleType + { + Naive = RETRACE_SCHEDULE_NAIVE, + Compact = RETRACE_SCHEDULE_COMPACT + }; + + enum class TemporalAreaReuseType + { + Point = 0, + Fast = 1, + Robust = 2, + Splat = 3 + }; + + FALCOR_ENUM_INFO(TemporalAreaReuseType, { + { TemporalAreaReuseType::Point, "Point" }, + { TemporalAreaReuseType::Fast, "Fast" }, + { TemporalAreaReuseType::Robust, "Robust" }, + { TemporalAreaReuseType::Splat, "Splat" } + }); + + enum class PointReprojectionMode + { + Nearest = 0, + Stochastic = 1, + }; + + FALCOR_ENUM_INFO(PointReprojectionMode, { + { PointReprojectionMode::Nearest, "Nearest" }, + { PointReprojectionMode::Stochastic, "Stochastic" }, + }); + + enum class ShiftMapping + { + Reconnection = 0, + Hybrid = 1, + }; + + FALCOR_ENUM_INFO(ShiftMapping, { + { ShiftMapping::Reconnection, "Reconnection" }, + { ShiftMapping::Hybrid, "Hybrid" }, + }); + + struct ShiftMappingSettings + { + uint localStrategyType = 0x3; + float specularRoughnessThreshold = 0.2f; + float nearFieldDistanceThreshold = 0.02f; + float specularRoughnessSigma = 0.f; // gaussian stdev for soft threshold + float nearFieldDistanceSigma = 0.2f; + bool additionalShadowRayOffset = false; // for reconnection shadow ray + +#ifdef HOST_CODE + template + void serialize(Archive& ar) + { + ar("localStrategyType", localStrategyType); + ar("specularRoughnessThreshold", specularRoughnessThreshold); + ar("nearFieldDistanceThreshold", nearFieldDistanceThreshold); + ar("specularRoughnessSigma", specularRoughnessSigma); + ar("nearFieldDistanceSigma", nearFieldDistanceSigma); + ar("additionalShadowRayOffset", additionalShadowRayOffset); + } +#endif + }; + + ShiftMappingSettings settings; + uint numSpatialRounds = 1; + uint initialLightSampleCount = 1; + bool sampleDI = true; + int samplesPerPixel = 1; + bool useAreaReSTIRBackprojection = true; + bool useDoFReconnectionShift = true; + float doFReconnectionShiftProbability = 0.3f; + bool deriveDoFReconnectionShiftProbabilityFromCoC = true; + bool temporalRobustShift = false; + bool useDoFReconnectionForTemporalReuse = true; + float specularMotionVectorRoughnessThreshold = 0.f; + bool enableSpecularMotionVectors = false; + bool enableDisocclusionMotionVectors = false; + bool useReservoirSplatting = false; + +#ifndef HOST_CODE + + uint sgCount() + { + return samplesPerPixel + 2 * int(enableSpecularMotionVectors) + int(useAreaReSTIRBackprojection) + (useDoFReconnectionForTemporalReuse ? 2 : 1) + numSpatialRounds; + } + + uint spatialSgOffset(int spatialRoundId) + { + return samplesPerPixel + 2 * int(enableSpecularMotionVectors) + int(useAreaReSTIRBackprojection) + (useDoFReconnectionForTemporalReuse ? 2 : 1) + spatialRoundId; + } + + uint temporalPopulateFloatingSgOffset() + { + return samplesPerPixel + 2 * int(enableSpecularMotionVectors); + } + + uint specMVecSgOffset() + { + return samplesPerPixel + 1; + } + + uint temporalSgOffset(int temporalPassId) + { + return samplesPerPixel + 2 * int(enableSpecularMotionVectors) + int(useAreaReSTIRBackprojection) + temporalPassId; + } + + uint spatialOrTemporalSgOffset(bool isSpatialPass, int spatialRoundId, int temporalPassId) + { + return samplesPerPixel + 2 * int(enableSpecularMotionVectors) + int(useAreaReSTIRBackprojection) + (isSpatialPass ? (useDoFReconnectionForTemporalReuse ? 2 : 1) + spatialRoundId : temporalPassId); + } + + int computeInitialLightSampleCount(int initialLightSampleCount, int vertexIndex) + { + return max(1, initialLightSampleCount / (vertexIndex * vertexIndex)); + } + // static params goes here + static const uint kShiftMapping = RESTIRPT_SHIFT_MAPPING; + static const bool kUseReservoirCompression = USE_RESERVOIR_COMPRESSION; + static const bool kUseAreaReservoirs = USE_AREA_RESERVOIRS; + static const bool kTemporalUpdateForDynamicScene = TEMPORAL_UPDATE_FOR_DYNAMIC_SCENE; + static const uint kNumSpatialNeighbors = NUM_SPATIAL_NEIGHBORS; + static const uint kRetraceScheduleType = RETRACE_SCHEDULE_TYPE; + static const uint kMaximumRcLength = 255; + static const bool kUsePrevFrameSceneData = USE_PREV_FRAME_SCENE_DATA; + RWStructuredBuffer pathReservoirs; + + float computePrimaryRayFootprint(float3 faceN, float3 rayOrg, float3 rayDir, float3 hitPos) + { + float3 disp = rayOrg - hitPos; + return dot(disp, disp) / abs(M_1_4PI * dot(rayDir, faceN)); // M_4PI should actually be M_1_4PI + } + + float getDiffusePseudoPdf() + { + return M_1_2PI; + } + + uint getCurrentSeed(SampleGenerator sg) + { +#if defined(SAMPLE_GENERATOR_TYPE) && SAMPLE_GENERATOR_TYPE == SAMPLE_GENERATOR_TINY_UNIFORM + return sg.rng.state; +#else + return 0; +#endif + } + // kReSTIRPT + + struct PathSegmentData + { + LastVertexState lastVertexState = {}; + float geometryFactor = 0; + float lastScatterPdf = 0; + float rayFootprint = 0; + float inverseGeometryFactor = 0; + } + + void PopulatePathSegmentData(inout PathSegmentData data, inout PathState path, ShadingData sd) + { + const bool isPrimaryHit = path.getVertexIndex() == 1; + if (isPrimaryHit) path.restirPTData.primaryRayFootprint = computePrimaryRayFootprint(sd.faceN, path.origin, path.dir, sd.posW); + float distanceToPrevVertex = length(sd.posW - path.origin); + data.geometryFactor = abs(dot(sd.faceN, sd.V)) / (distanceToPrevVertex * distanceToPrevVertex); + data.lastScatterPdf = path.restirPTData.lastScatterPdf; + data.inverseGeometryFactor = distanceToPrevVertex * distanceToPrevVertex / abs(dot(path.normal, sd.V)); + data.rayFootprint = 1.f / (data.geometryFactor * path.pdf); + data.lastVertexState.Init( path.isDelta(), path.isTransmission(), path.getSampledBSDFComponent(), path.supportBSDFIndexing(), path.isRough(), + data.rayFootprint >= path.restirPTData.primaryRayFootprint * path.restirPTData.distanceThreshold ); + } + + void setRcVertex(inout PathState path, PathSegmentData ps) + { + // test connectability + bool canConnect = kShiftMapping == (uint)ReSTIRPT::ShiftMapping::Reconnection && path.getVertexIndex() == 2 || + kShiftMapping == (uint)ReSTIRPT::ShiftMapping::Hybrid && + path.restirPTData.pathRcInfo.pathFlags.rcVertexLength() == kMaximumRcLength && + (ps.lastVertexState.isLastVertexRough() && min(ps.rayFootprint, + ps.inverseGeometryFactor / path.pdf) + >= path.restirPTData.primaryRayFootprint * path.restirPTData.distanceThreshold); + + path.restirPTData.pathRcInfo.rcNextVertexBounceSeed = canConnect ? getCurrentSeed(path.sg) : path.restirPTData.pathRcInfo.rcNextVertexBounceSeed; + path.restirPTData.pathRcInfo.reconnectionWi = canConnect ? path.dir : path.restirPTData.pathRcInfo.reconnectionWi; + path.restirPTData.pathRcInfo.pathFlags.insertRcVertexLength( canConnect ? path.getVertexIndex() - 1 : path.restirPTData.pathRcInfo.pathFlags.rcVertexLength() ); + path.restirPTData.pathRcInfo.reconnectionHit.data = canConnect ? path.hit.data : path.restirPTData.pathRcInfo.reconnectionHit.data; + + path.restirPTData.pathRcInfo.pathFlags.tryInsertBounceTypesBeforeRcVertex(ps.lastVertexState.data & 0x3f, canConnect); + + path.restirPTData.pathRcInfo.reconnectionJacobian = canConnect ? ps.geometryFactor : path.restirPTData.pathRcInfo.reconnectionJacobian; + + path.restirPTData.sourcePdf *= canConnect ? ps.lastScatterPdf * path.restirPTData.lastScatterPdf : 1.f; + + path.restirPTData.pathRcInfo.pathFlags.tryInsertBounceTypesAfterRcVertex( (uint(path.isDelta()) | uint(path.isTransmission()) << 1 | uint(path.getSampledBSDFComponent()) << 2), canConnect ); + + path.restirPTData.pathRcInfo.rcThroughput = canConnect ? float3(1.f) : path.restirPTData.pathRcInfo.rcThroughput; + } + + + [ForceInline] + void convertNeeSampledVertexToRcVertex(inout PathState path, ShadingData sd, LightSample ls, + float3 Le, bool supportBSDFIndexing, bool isPunctualLight) + { +#if USE_RESTIRPT + path.restirPTData.pathRis.reconnectionJacobian = 1.f; + path.restirPTData.pathRis.integrand *= ls.pdf; + path.restirPTData.pathRis.reconnectionIncidentRadiance = Le; + path.restirPTData.pathRis.reconnectionWi = ls.dir; + path.restirPTData.pathRis.pathFlags.insertRcVertexLength(path.restirPTData.pathRis.pathFlags.pathLength() + 1); + + bool isTransmission = dot(sd.faceN, sd.V) * dot(sd.faceN, ls.dir) < 0.f; + path.restirPTData.pathRis.pathFlags.insertIsTransmissionEvent(isTransmission, true); + path.restirPTData.pathRis.pathFlags.insertSupportBSDFIndexingBeforeRcVertex(supportBSDFIndexing); + + // not directional light + if (ls.lightType == (uint)LightType::Analytic) + { + if (ls.distance == kMaxLightDistance) // dir + { + path.restirPTData.pathRis.reconnectionHit.data.x = 0; + } + else + { + path.restirPTData.pathRis.reconnectionWi = ls.origin + ls.dir * ls.distance; // store position + if (isPunctualLight || kUseLightTileRIS) + { + path.restirPTData.pathRis.reconnectionIncidentRadiance /= ls.geometryTerm; // ls.geometry term is 1/d^2 + path.restirPTData.pathRis.reconnectionHit.data.x = 1; // use this as type identifier + } + else // area, sphere + { + path.restirPTData.pathRis.reconnectionHit.data.x = 2; +#if !HIT_INFO_USE_COMPRESSION + path.restirPTData.pathRis.reconnectionHit.data.yzw = asuint(ls.normal); +#endif + path.restirPTData.pathRis.reconnectionJacobian *= ls.geometryTerm; + path.restirPTData.pathRis.lightPdf *= ls.geometryTerm; // stored in area measure + } + } + } + else if (ls.lightType == (uint)LightType::Emissive) + { + TriangleHit neeTriangleHit; + // convert light collection triangle id to scene mesh triangle id + uint2 meshPrimID = gScene.lightCollection.getSceneMeshPrimID(ls.triangleId); + GeometryInstanceID neeGeoInstanceId; + neeGeoInstanceId.index = meshPrimID.x; + neeTriangleHit.instanceID = neeGeoInstanceId; + neeTriangleHit.primitiveIndex = meshPrimID.y; + neeTriangleHit.barycentrics = ls.barycentrics; + HitInfo neeHitInfo = HitInfo(neeTriangleHit); + path.restirPTData.pathRis.reconnectionHit = neeHitInfo; + path.restirPTData.pathRis.reconnectionJacobian *= ls.geometryTerm; + path.restirPTData.pathRis.lightPdf *= ls.geometryTerm; // stored in area measure + } +#endif + } + + void HandleNEECase( inout PathState path, ShadingData sd, LightSample ls, float uNEERIS, float scatterPdf, float pdfWarpFactor, uint neeSeed, PathSegmentData ps, + float3 Le, float3 Lr, bool supportBSDFIndexing, bool isPunctualLight) + { + const bool isPrimaryHit = path.getVertexIndex() == 1; + bool isPrimaryHitAndNoReSTIRPTSample = isPrimaryHit && !sampleDI; + if (isPrimaryHitAndNoReSTIRPTSample) + { + path.restirPTData.pathL0 += path.thp * Lr; // residual radiance not integrated by ReSTIR PT + return; + } + + bool canConnect = + kShiftMapping == (uint)ReSTIRPT::ShiftMapping::Reconnection && path.getVertexIndex() == 2 || + kShiftMapping == (uint)ReSTIRPT::ShiftMapping::Hybrid && + path.restirPTData.pathRcInfo.pathFlags.rcVertexLength() == kMaximumRcLength && + path.getVertexIndex() >= 2 && + (min(ps.rayFootprint, ps.inverseGeometryFactor / scatterPdf) >= path.restirPTData.primaryRayFootprint * path.restirPTData.distanceThreshold + && ps.lastVertexState.isLastVertexRough()); + + bool selected = path.restirPTData.pathRis.addNEECandidate( uNEERIS, path.restirPTData.pathRcInfo, canConnect, path.getVertexIndex() - 1, + path.thp * Lr / pdfWarpFactor, ls.dir, Le, Lr, path.restirPTData.sourcePdf * pdfWarpFactor, ls.pdf, ls.lightType ); + + bool shouldOverwrite = selected && canConnect; + + path.restirPTData.pathRis.reconnectionHit.data = shouldOverwrite ? path.hit.data : path.restirPTData.pathRis.reconnectionHit.data; + path.restirPTData.pathRis.reconnectionJacobian = shouldOverwrite ? ps.geometryFactor : path.restirPTData.pathRis.reconnectionJacobian; + if (shouldOverwrite) path.restirPTData.pathRis.integrand *= ps.lastScatterPdf * ls.pdf; + + path.restirPTData.pathRis.rcNextVertexBounceSeed = shouldOverwrite ? neeSeed : path.restirPTData.pathRis.rcNextVertexBounceSeed; + path.restirPTData.pathRis.pathFlags.tryInsertBounceTypesBeforeRcVertex(ps.lastVertexState.data & 0x3f, shouldOverwrite); + // bouncetypes after rcVertex in the shouldOverwrite case doesn't matter + + // NEE was selected but couldn't use the regular reconnection shift -> store the light sample + // directly as the reservoir's reconnection vertex. + if (selected && !canConnect && path.restirPTData.pathRcInfo.pathFlags.rcVertexLength() == kMaximumRcLength) + { + convertNeeSampledVertexToRcVertex(path, sd, ls, Le, supportBSDFIndexing, isPunctualLight); + } + } + + void populateReservoir(inout PathState path, Texture2D sampleCountTex, uint2 pixel, uint reservoirIdx, uint frameSeed) + { + uint totalSamples = kSamplesPerPixel; + if (kSamplesPerPixel == 0) + { + totalSamples = sampleCountTex[pixel]; + } + + SampleGenerator sgForFilmLens = SampleGenerator(pixel, frameSeed * sgCount()); // all samples must use the same uv + + PathReservoir pathReservoir; + if (path.getSampleIdx() == totalSamples - 1) // first sample (sampleIdx is actually the remaining samples) + { + pathReservoir = PathReservoir::createFromRisState( path.restirPTData.pathRis, path.restirPTData.pathRcInfo.initRandomSeed + , float4(sampleNext2D(sgForFilmLens), sampleNext2D(sgForFilmLens)) + ); + } + else + { + pathReservoir = pathReservoirs[reservoirIdx]; + pathReservoir.add( path.restirPTData.pathRis, path.restirPTData.pathRcInfo.initRandomSeed, + float4(sampleNext2D(sgForFilmLens), sampleNext2D(sgForFilmLens)), + path.sg ); + } + + // converting from w_sum to W + if (path.getSampleIdx() == 0) // last sample + { + float pHat = PathRISState::toScalar(pathReservoir.integrand); + pathReservoir.weight = pHat == 0.f ? 0.f : pathReservoir.weight / (pathReservoir.M * pHat); + pathReservoir.M = 1; + } + pathReservoirs[reservoirIdx] = pathReservoir; + } + + void HandleBSDFCase( inout PathState path, float uBRDFRIS, PathSegmentData ps, float lightPdf, + float3 attenuatedEmission, float misWeight, float3 emission) + { + bool lightHitHandledByReSTIRPT = path.getVertexIndex() > (sampleDI ? 1 : 2); + const bool isPrimaryHit = path.getVertexIndex() == 1; + + if (kUseAreaReservoirs && isPrimaryHit) + { + path.restirPTData.pathRis.addPrimaryVisibleEmitterCandidate(uBRDFRIS, attenuatedEmission, path.restirPTData.sourcePdf); + } + + if (lightHitHandledByReSTIRPT) + { + bool selected = path.restirPTData.pathRis.addBSDFHitCandidate( uBRDFRIS, path.restirPTData.pathRcInfo, (path.getVertexIndex() - 1) - 1, attenuatedEmission, + path.dir, emission, misWeight * emission, path.restirPTData.sourcePdf, lightPdf, (uint)LightType::Emissive ); + + // daqi: if current vertex has path.length >= 2, then it can be used as a rcVertex + if (selected && (kShiftMapping == (uint)ReSTIRPT::ShiftMapping::Hybrid || path.getVertexIndex() == 2) + && path.restirPTData.pathRcInfo.pathFlags.rcVertexLength() == kMaximumRcLength) // we don't yet have an rcVertex + { + // emissive vertex is always seen as Lambertian + if (kShiftMapping == (uint)ReSTIRPT::ShiftMapping::Reconnection || + (min(ps.rayFootprint, ps.inverseGeometryFactor / getDiffusePseudoPdf()) >= path.restirPTData.primaryRayFootprint * path.restirPTData.distanceThreshold + // we use mi instead of last mi because all material must be the same in terms of supporting BSDF indexing + && (!path.supportSimpleRoughnessThreshold() || ps.lastVertexState.isLastVertexRough()))) + { + // we found an RC vertex! + // set rcVertexLength to current length (this will make rcVertexLength = reseroivr.pathLength + 1) + path.restirPTData.pathRis.integrand *= ps.lastScatterPdf; + path.restirPTData.pathRis.markAsRcVertex( path.getVertexIndex() - 1, path.hit, ps.lastVertexState, + lightPdf * ps.geometryFactor, (uint)LightType::Emissive, emission, float3(0.f), + ps.geometryFactor ); + } + } + } + + path.restirPTData.pathL0 += (!lightHitHandledByReSTIRPT && !(kUseAreaReservoirs && isPrimaryHit)) ? attenuatedEmission : 0.f; + } + + void HandleBSDFCaseHitEnv(inout PathState path, float uBRDFRIS, float lightPdf, float3 emitterRadiance, float3 Le) + { + bool lightHitHandledByReSTIRPT = path.getVertexIndex() > (sampleDI ? 1 : 2); + bool isPrimaryHit = path.getVertexIndex() == 1; + + path.restirPTData.pathL0 += (!lightHitHandledByReSTIRPT && !(kUseAreaReservoirs && isPrimaryHit)) ? path.thp * emitterRadiance : 0.f; + + if (kUseAreaReservoirs && isPrimaryHit) + { + path.restirPTData.pathRis.addPrimaryVisibleEmitterCandidate(uBRDFRIS, emitterRadiance, path.restirPTData.sourcePdf); + } + + if (lightHitHandledByReSTIRPT) + { + LastVertexState lastVertexState; + lastVertexState.Init( path.isDelta(), path.isTransmission(), path.getSampledBSDFComponent(), path.supportBSDFIndexing(), path.isRough(), true ); + + bool selected = path.restirPTData.pathRis.addBSDFHitCandidate( uBRDFRIS, path.restirPTData.pathRcInfo, (path.getVertexIndex() - 1) - 1, + path.thp * emitterRadiance, path.dir, Le, emitterRadiance, path.restirPTData.sourcePdf, lightPdf, (uint)LightType::EnvMap ); + + if (selected && (kShiftMapping == (uint)ReSTIRPT::ShiftMapping::Hybrid || path.getVertexIndex() == 2) + && path.restirPTData.pathRcInfo.pathFlags.rcVertexLength() == kMaximumRcLength) + { + if (path.isRough() || kShiftMapping == (uint)ReSTIRPT::ShiftMapping::Reconnection) + { + path.restirPTData.pathRis.integrand *= path.restirPTData.lastScatterPdf; + // we found an RC vertex! + // set rcVertexLength to current length (this will make rcVertexLength = reseroivr.pathLength + 1) + HitInfo dummyHit = HitInfo(); + path.restirPTData.pathRis.markAsRcVertex( path.getVertexIndex() - 1, dummyHit, lastVertexState, + lightPdf, (uint)LightType::EnvMap, Le, path.dir, 1.f ); + } + } + } + } + + void ProcessScatterData(inout PathState path, inout bool valid, inout BSDFSample result, ShadingData sd, const IMaterialInstance mi) + { + // set result.pdf to a large value for delta + if (result.isLobe(LobeType::Delta)) result.pdf = 1e20f; + + + valid &= !isinf(result.pdf) && all(!isinf(result.weight)); + // result.pdf is the single component PDF + + uint componentType = result.BSDFIndex; + path.setSampledBSDFComponent(componentType); + + if (mi.supportBSDFIndexing()) + { + sd.mtl.setBSDFIndex(componentType); + path.restirPTData.lastScatterPdf = mi.evalPdf(sd, result.wo); + } + else path.restirPTData.lastScatterPdf = result.pdf; + + // set lastScatterPdf to a non-zero value for delta + if (result.isLobe(LobeType::Delta)) path.restirPTData.lastScatterPdf = 1.f; + + // compute augmented path footprint + path.setRough(mi.isSampleRough(sd, componentType, result.pdf, path.restirPTData.roughnessThreshold)); + path.setSupportBSDFIndexing(mi.supportBSDFIndexing()); + path.setSupportSimpleRoughnessThreshold(mi.supportSimpleRoughnessThreshold()); + } + + void InitializePathData(inout PathState path) + { + path.restirPTData.pathL0 = 0; + path.restirPTData.sourcePdf = 1.f; + path.restirPTData.pathRis.init(); + + path.restirPTData.pathRcInfo.initRandomSeed = getCurrentSeed(path.sg); + path.restirPTData.pathRcInfo.rcThroughput = 1.f; + + if (settings.nearFieldDistanceSigma == 0.f) path.restirPTData.distanceThreshold = settings.nearFieldDistanceThreshold; + else path.restirPTData.distanceThreshold = PathRISState::gaussrand(settings.nearFieldDistanceThreshold, settings.nearFieldDistanceSigma, path.sg); + + if (settings.specularRoughnessSigma == 0.f) path.restirPTData.roughnessThreshold = settings.specularRoughnessThreshold; + else path.restirPTData.roughnessThreshold = PathRISState::gaussrand(settings.specularRoughnessThreshold, settings.specularRoughnessSigma, path.sg); + + path.restirPTData.pathRcInfo.reconnectionHit = HitInfo(); // initialize invalid reconnectionHit + path.restirPTData.pathRcInfo.pathFlags.insertRcVertexLength(kMaximumRcLength); // insert an invalid rcvertexlength + } + + void WriteReconnectionData(inout PathState path, float3 reconnectionWi, LastVertexState lastVertexState, float rayFootprint, float inverseGeometryFactor) + { + path.restirPTData.pathRcInfo.reconnectionHit = path.hit; + path.restirPTData.pathRcInfo.reconnectionWi = reconnectionWi; + // if last vertex isn't rough, current vertex can't be a rcVertex, so we are safe + bool currentVertexCannotBeReconnected = !lastVertexState.isLastVertexRough() || rayFootprint < path.restirPTData.primaryRayFootprint * path.restirPTData.distanceThreshold; + path.restirPTData.pathRcInfo.footprintFactor = currentVertexCannotBeReconnected ? -1.f : inverseGeometryFactor; + } + + void WriteTemporalRetraceData(inout PathState path, uint lightType, float lightPdf, float3 reconnectionIncidentRadiance) + { + path.restirPTData.pathRcInfo.pathFlags.insertLightType(lightType); + path.restirPTData.pathRcInfo.reconnectionJacobian = lightPdf; + path.restirPTData.pathRcInfo.rcThroughput = reconnectionIncidentRadiance; + } + +#endif +}; + +FALCOR_ENUM_REGISTER(ReSTIRPT::PointReprojectionMode); +FALCOR_ENUM_REGISTER(ReSTIRPT::ShiftMapping); +FALCOR_ENUM_REGISTER(ReSTIRPT::TemporalAreaReuseType); + +END_NAMESPACE_FALCOR diff --git a/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracing.cpp b/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracing.cpp new file mode 100644 index 0000000000..b3e7d88080 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracing.cpp @@ -0,0 +1,1265 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "ReSTIRPathTracing.h" +#include "Core/API/RenderContext.h" +#include "Utils/Logger.h" +#include "Utils/Timing/Profiler.h" +#include "Utils/Color/ColorHelpers.slang" +#include +namespace Falcor +{ + namespace + { + const char kReflectTypesFile[] = "ReSTIRPathTracing/ReflectTypes.cs.slang"; + const char kTemporalResamplingFile[] = "ReSTIRPathTracing/TemporalResampling.cs.slang"; + const char kSpatialPrecomputeShiftBufferFile[] = "ReSTIRPathTracing/SpatialPrecomputeShiftBuffer.cs.slang"; + const char kSpatialResamplingFile[] = "ReSTIRPathTracing/SpatialResampling.cs.slang"; + const char kTemporalRetraceFile[] = "ReSTIRPathTracing/TemporalPathRetrace.cs.slang"; + const char kSpatialRetraceFile[] = "ReSTIRPathTracing/SpatialPathRetrace.cs.slang"; + const char kSpatialGenerateRetraceWorkload[] = "ReSTIRPathTracing/GenerateRetraceWorkload.cs.slang"; + const char kFetchDenoiserData[] = "ReSTIRPathTracing/FetchDenoiserData.cs.slang"; + const char kFillSampleID[] = "ReSTIRPathTracing/FillSampleID.cs.slang"; + const char kComputeDuplicationMap[] = "ReSTIRPathTracing/ComputeDuplicationMap.cs.slang"; + const char kTemporalTraceShiftedPrimaryHits[] = "ReSTIRPathTracing/TemporalTraceShiftedPrimaryHits.cs.slang"; + const char kSpatialTraceShiftedPrimaryHits[] = "ReSTIRPathTracing/SpatialTraceShiftedPrimaryHits.cs.slang"; + const char kTemporalPopulateFloatingReservoir[] = "ReSTIRPathTracing/TemporalPopulateFloatingReservoir.cs.slang"; + const char kTemporalRobustResampling[] = "ReSTIRPathTracing/TemporalRobustResampling.cs.slang"; + const char kTemporalRobustPathRetrace[] = "ReSTIRPathTracing/TemporalRobustPathRetrace.cs.slang"; + const char kTemporalRobustCreateShiftVBuffer[] = "ReSTIRPathTracing/TemporalRobustCreateShiftVBuffer.cs.slang"; + const char kTemporalRobustProduceWorkload[] = "ReSTIRPathTracing/TemporalRobustProduceWorkload.cs.slang"; + const char kComputeReuseMotionVector[] = "ReSTIRPathTracing/ComputeReuseMotionVector.cs.slang"; + const char kTemporalReservoirSplatting[] = "ReSTIRPathTracing/TemporalReservoirSplatting.cs.slang"; // 3 in 1 + + const ShaderModel kShaderModel = ShaderModel::SM6_5; + + const Gui::DropdownList kShiftMappingList = + { + { (uint32_t)ReSTIRPT::ShiftMapping::Reconnection, "Reconnection" }, + { (uint32_t)ReSTIRPT::ShiftMapping::Hybrid, "Hybrid" }, + }; + + const Gui::DropdownList kRetraceScheduleType = + { + { (uint32_t)ReSTIRPT::RetraceScheduleType::Naive, "Naive" }, + { (uint32_t)ReSTIRPT::RetraceScheduleType::Compact, "Compact" }, + }; + + const Gui::DropdownList kTemporalAreaReuseType = + { + {(uint32_t)ReSTIRPT::TemporalAreaReuseType::Point, "Point"}, + {(uint32_t)ReSTIRPT::TemporalAreaReuseType::Fast, "Fast"}, + {(uint32_t)ReSTIRPT::TemporalAreaReuseType::Robust, "Robust"}, + {(uint32_t)ReSTIRPT::TemporalAreaReuseType::Splat, "Splat"}, + }; + + const Gui::DropdownList kPointReprojectionMode = + { + {(uint32_t)ReSTIRPT::PointReprojectionMode::Nearest, "Nearest"}, + {(uint32_t)ReSTIRPT::PointReprojectionMode::Stochastic, "Stochastic"}, + }; + + const uint32_t kNeighborOffsetCount = 8192; + + constexpr uint32_t kTemporalNeighborMultiplier = 2; + constexpr uint32_t kTemporalRobustNeighborMultiplier = 8; + constexpr uint32_t kSpatialNeighborMultiplier = 2; + + constexpr uint32_t kPairedNeighborTextureExtent = 256; + constexpr uint32_t kPairedNeighborStdevLevels = 10; + constexpr int kPairedNeighborMinRadius = 5; + constexpr int kPairedNeighborMaxRadius = 50; + constexpr int kPairedNeighborRadiusStep = 5; + constexpr uint32_t kPairedNeighborMaxCount = 5; + + constexpr uint32_t kDefaultMaxSamplesPerPixel = 16; + } + + ReSTIRPathTracing::ReSTIRPathTracing(const ref& pScene, const DefineList& ownerDefines, const Options& options) + : mpScene(pScene), + mpDevice(pScene->getDevice()), + mOptions(options) + { + FALCOR_ASSERT(mpScene); + + mpPixelDebug = std::make_unique(mpDevice); + + // Create compute pass for reflecting data types. + ProgramDesc desc; + DefineList defines; + defines.add(ownerDefines); + defines.add(getDefines()); + desc.addShaderLibrary(kReflectTypesFile).csEntry("main").setShaderModel(kShaderModel); + mpReflectTypes = ComputePass::create(mpDevice, desc, defines); + + // Create neighbor offset texture. + mpNeighborOffsets = createNeighborOffsetTexture(kNeighborOffsetCount); + mpPairedNeighborDeltas = createPairedNeighborDeltasTexture(mpDevice, mOptions.spatialNeighborCount, mOptions.spatialPairingStdevLevel); + } + + DefineList ReSTIRPathTracing::getDefines() const + { + DefineList defines; + defines.add("RESTIRPT_SHIFT_MAPPING", std::to_string((uint32_t)mOptions.shiftMapping)); + defines.add("TEMPORAL_UPDATE_FOR_DYNAMIC_SCENE", mOptions.temporalUpdateForDynamicScene ? "1": "0"); + defines.add("USE_PREV_FRAME_SCENE_DATA", mOptions.temporalUpdateForDynamicScene ? "1" : "0"); + defines.add("USE_RESERVOIR_COMPRESSION", mOptions.useReservoirCompression ? "1" : "0"); + defines.add("RETRACE_SCHEDULE_TYPE", std::to_string((uint32_t)mOptions.retraceScheduleType)); + defines.add("USE_AREA_RESERVOIRS", mOptions.useAreaReservoirs ? "1" : "0"); + defines.add("USE_DECOUPLED_SHADING", mOptions.useDecoupledShading ? "1" : "0"); + defines.add("PAD_RANDOM_NUMBERS", "1"); + defines.add("FETCH_NRD_DATA_PASS", "0"); + + return defines; + } + + void ReSTIRPathTracing::setShaderData(const ShaderVar& var) const + { + var["settings"]["localStrategyType"] = mOptions.shiftMappingSettings.localStrategyType; + var["settings"]["specularRoughnessThreshold"] = mOptions.shiftMappingSettings.specularRoughnessThreshold; + var["settings"]["nearFieldDistanceThreshold"] = mOptions.shiftMappingSettings.nearFieldDistanceThreshold / 100.f; + var["settings"]["specularRoughnessSigma"] = mOptions.shiftMappingSettings.specularRoughnessSigma; + var["settings"]["nearFieldDistanceSigma"] = mOptions.shiftMappingSettings.nearFieldDistanceSigma; + var["settings"]["additionalShadowRayOffset"] = mOptions.shiftMappingSettings.additionalShadowRayOffset; + var["numSpatialRounds"] = mOptions.spatialIterations; + var["initialLightSampleCount"] = exInitialLightSampleCount; + var["sampleDI"] = mOptions.sampleDI; + var["useAreaReSTIRBackprojection"] = mOptions.useAreaReSTIRBackprojection(); + var["temporalRobustShift"] = mOptions.useAreaReservoirs && mOptions.temporalRobustShift(); + var["useReservoirSplatting"] = mOptions.useAreaReservoirs && mOptions.useReservoirSplatting(); + + var["samplesPerPixel"] = mStaticParams.samplesPerPixel != 0 ? mStaticParams.samplesPerPixel : kDefaultMaxSamplesPerPixel; + var["specularMotionVectorRoughnessThreshold"] = mOptions.specularMotionVectorRoughnessThreshold; + var["pathReservoirs"] = mpReservoirs; + var["enableSpecularMotionVectors"] = mOptions.enableSpecularMotionVectors; + var["enableDisocclusionMotionVectors"] = mOptions.enableDisocclusionMotionVectors; + var["useDoFReconnectionShift"] = mOptions.useDoFReconnectionShift; + var["useDoFReconnectionForTemporalReuse"] = useDoFReconnectionForTemporalReuseEffective(); + var["doFReconnectionShiftProbability"] = mOptions.doFReconnectionShiftProbability; + var["deriveDoFReconnectionShiftProbabilityFromCoC"] = mOptions.deriveDoFReconnectionShiftProbabilityFromCoC; + } + + void ReSTIRPathTracing::setPathTracerParams(ReSTIRPathTracingParams params) + { + mPathTracerParams = params; + } + + void ReSTIRPathTracing::setOwnerDefines(DefineList defines) + { + mOwnerDefines = defines; + } + + void ReSTIRPathTracing::setSharedStaticParams(uint32_t samplesPerPixel, uint32_t maxSurfaceBounces, bool useNEE) + { + mStaticParams.maxSurfaceBounces = maxSurfaceBounces; + mStaticParams.useNEE = useNEE; + mStaticParams.samplesPerPixel = samplesPerPixel; + } + + void ReSTIRPathTracing::createPathTracerBlock() + { + auto reflector = mpReflectTypes->getProgram()->getReflector()->getParameterBlock("pathTracer"); + mpPathTracerBlock = ParameterBlock::create(mpDevice, reflector); + } + + ref ReSTIRPathTracing::getPathTracerBlock() + { + return mpPathTracerBlock; + } + + void ReSTIRPathTracing::setReservoirData(const ShaderVar& var) const + { + var["pathReservoirs"] = mpReservoirs; + var["useAreaReservoirs"] = mOptions.useAreaReservoirs; + var["useDecoupledShading"] = mOptions.useDecoupledShading && mOptions.useSpatialResampling; + } + + bool ReSTIRPathTracing::renderUI(Gui::Widgets& widget) + { + bool dirty = false; + + dirty |= widget.checkbox("Include direct lighting in reservoirs", mOptions.sampleDI); + + mRecompile |= widget.checkbox("Use Decoupled Shading", mOptions.useDecoupledShading); + + mReallocate |= widget.checkbox("Use area reservoirs", mOptions.useAreaReservoirs); + + if (auto group = widget.group("Performance settings", true)) + { + mReallocate |= group.checkbox("Use reservoir compression", mOptions.useReservoirCompression); + mReallocate |= group.dropdown("Retrace Schedule Type", kRetraceScheduleType, reinterpret_cast(mOptions.retraceScheduleType)); + } + + if (auto group = widget.group("Shift mapping options", true)) + { + mRecompile |= group.dropdown("Shift Mapping", kShiftMappingList, reinterpret_cast(mOptions.shiftMapping)); + + if (mOptions.shiftMapping == ReSTIRPT::ShiftMapping::Hybrid) + { + dirty |= group.var("Distance Threshold", mOptions.shiftMappingSettings.nearFieldDistanceThreshold, 0.f, 10000.f); + dirty |= group.var("Distance Sigma", mOptions.shiftMappingSettings.nearFieldDistanceSigma, 0.f, 1.f); + dirty |= group.var("Roughness Threshold", mOptions.shiftMappingSettings.specularRoughnessThreshold, 0.f, 1.f); + dirty |= group.var("Roughness Sigma", mOptions.shiftMappingSettings.specularRoughnessSigma, 0.f, 1.f); + } + + dirty |= group.checkbox("Additional Shadow Ray Offset", mOptions.shiftMappingSettings.additionalShadowRayOffset); + + mReallocate |= group.checkbox("Use primary hit reconnection shift for DoF", mOptions.useDoFReconnectionShift); + if (mOptions.useDoFReconnectionShift) + { + dirty |= group.checkbox("Use DoF reconnection for temporal reuse", mOptions.useDoFReconnectionForTemporalReuse); + dirty |= group.checkbox("Derive DoF reconnection shift probability from CoC", mOptions.deriveDoFReconnectionShiftProbabilityFromCoC); + dirty |= group.var("DoF reconnection shift probability", mOptions.doFReconnectionShiftProbability, 0.f, 1.f); + } + } + + if (auto group = widget.group("Temporal resampling", true)) + { + dirty |= group.checkbox("Use temporal resampling", mOptions.useTemporalResampling); + + dirty |= group.var("Max history length", mOptions.maxHistoryLength, 0u, 100u); + group.tooltip("Maximum temporal history length."); + + + dirty |= group.dropdown("Point Reprojection Mode", kPointReprojectionMode, reinterpret_cast(mOptions.pointReprojectionMode)); + + mReallocate |= group.checkbox("Update RcVertex Radiance for Dynamic Scene", mOptions.temporalUpdateForDynamicScene); + dirty |= group.checkbox("Enable Specular Motion Vectors", mOptions.enableSpecularMotionVectors); + if (mOptions.enableSpecularMotionVectors) + { + dirty |= group.checkbox("Handle Refraction in Specular Motion Vectors", mOptions.handleTransmissionInSpecMVec); + dirty |= group.var("Specular Motion Vectors Roughness Threshold", mOptions.specularMotionVectorRoughnessThreshold, 0.f, 1.f); + } + dirty |= group.checkbox("Enable Disocclusion MVec", mOptions.enableDisocclusionMotionVectors); + + + ReSTIRPT::TemporalAreaReuseType displayed = mOptions.useAreaReservoirs + ? mOptions.temporalAreaReuseType + : ReSTIRPT::TemporalAreaReuseType::Point; + if (group.dropdown("Temporal Area Reuse Type", kTemporalAreaReuseType, reinterpret_cast(displayed)) + && mOptions.useAreaReservoirs) + { + mRecompile = true; + mOptions.temporalAreaReuseType = displayed; + } + dirty |= group.checkbox("Duplication-based MCap downweighting", mOptions.enableDupMapMCapDownweighting); + + dirty |= group.var("Duplication-based M-cap downweighting power", mOptions.duplicationMCapPower, 0.f, 10.f); + } + + if (auto group = widget.group("Spatial resampling", true)) + { + dirty |= group.checkbox("Use spatial resampling", mOptions.useSpatialResampling); + + dirty |= group.var("Iterations", mOptions.spatialIterations, 0u, 8u); + group.tooltip("Number of spatial resampling iterations."); + + dirty |= group.checkbox("Use reciprocal neighbor selection", mOptions.usePairedNeighbors); + + bool neighborCountChanged = group.var("Neighbor count", mOptions.spatialNeighborCount, 1u, (mOptions.usePairedNeighbors ? 5u : 16u)); + group.tooltip("Number of neighbor samples to resample per pixel and iteration."); + + mReallocate |= neighborCountChanged; + + if (mOptions.usePairedNeighbors) + { + bool stdevLevelChanged = false; + int avgRadius = kPairedNeighborMinRadius + mOptions.spatialPairingStdevLevel * kPairedNeighborRadiusStep; + stdevLevelChanged = group.var("Gather radius", avgRadius, kPairedNeighborMinRadius, kPairedNeighborMaxRadius, kPairedNeighborRadiusStep); + dirty |= stdevLevelChanged; + if (neighborCountChanged || stdevLevelChanged) + { + mOptions.spatialPairingStdevLevel = avgRadius / kPairedNeighborRadiusStep - 1; + mpPairedNeighborDeltas = + createPairedNeighborDeltasTexture(mpDevice, mOptions.spatialNeighborCount, mOptions.spatialPairingStdevLevel); + } + group.tooltip("Average spatial neighbor distance of the pairing texture"); + } + else + { + dirty |= group.var("Gather radius", mOptions.spatialGatherRadius, 0u, 200u); + group.tooltip("Radius to gather samples from."); + } + dirty |= group.checkbox("Area Reservoir use Neighbor Rejection", mOptions.areaReservoirUseNeighborRejection); + } + + if (auto group = widget.group("Debugging")) + { + mpPixelDebug->renderUI(group); + } + + mRecompile |= mReallocate; + dirty |= mRecompile; + + if (dirty) + { + mResetTemporalReservoirs = true; + } + + return dirty; + } + + void ReSTIRPathTracing::setOptions(const Options& options) + { + if (std::memcmp(&options, &mOptions, sizeof(Options)) != 0) + { + mOptions = options; + mRecompile = true; + } + } + + void ReSTIRPathTracing::beginFrame(RenderContext* pRenderContext, const uint2& frameDim, const uint2& screenTiles, bool needRecompile) + { + mRecompile |= needRecompile; + + mFrameDim = frameDim; + + prepareResources(pRenderContext, frameDim, screenTiles); + + if (mResetTemporalReservoirs) mFrameIndex = 0; + + + mpPixelDebug->beginFrame(pRenderContext, mFrameDim); + } + + void ReSTIRPathTracing::endFrame(RenderContext* pRenderContext) + { + mFrameIndex++; + + // Swap reservoirs. + std::swap(mpReservoirs, mpPrevReservoirs); + if (mOptions.computeTemporalDuplication) + std::swap(mpTemporalCorrelationMap, mpPrevTemporalCorrelationMap); + if (mOptions.useAreaReservoirs) + std::swap(mpRandomVBuffer, mpTemporalRandomVBuffer); + + mpPixelDebug->endFrame(pRenderContext); + } + + + void ReSTIRPathTracing::updateReSTIRPT(RenderContext* pRenderContext, const ref& pMotionVectors, const ref& pVBuffer, const ref& pColorBuffer) + { + FALCOR_PROFILE(pRenderContext, "ReSTIRPathTracing::updateReSTIRPT"); + + temporalResampling(pRenderContext, pMotionVectors, pVBuffer); + spatialResampling(pRenderContext, pVBuffer, pColorBuffer); + + // prepare temporal data + pRenderContext->copyResource(mpTemporalVBuffer.get(), pVBuffer.get()); + + if (mOptions.enableDupMapMCapDownweighting || mOptions.computeTemporalDuplication) + fillSampleID(pRenderContext); + + return; + } + + void ReSTIRPathTracing::createOrDestroyBuffer(ref& pBuffer, std::string_view reflectVarName, int requiredElementCount, bool keepCondition) + { + if (!keepCondition) + { + pBuffer = nullptr; + return; + } + if (mReallocate || !pBuffer || pBuffer->getElementCount() != requiredElementCount) + { + pBuffer = mpDevice->createStructuredBuffer( + mpReflectTypes->getRootVar()[std::string(reflectVarName)], + requiredElementCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false); + } + } + + void ReSTIRPathTracing::createOrDestroyRawBuffer(ref& pBuffer, size_t requiredSize, bool keepCondition) + { + if (keepCondition && (mReallocate || !pBuffer || pBuffer->getSize() != requiredSize)) + pBuffer = mpDevice->createBuffer(requiredSize, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal); + if (!keepCondition) pBuffer = nullptr; + } + + void ReSTIRPathTracing::createTexture2D(ref& pTexture, uint32_t width, uint32_t height, ResourceFormat format, bool keepCondition) + { + if (keepCondition && (mReallocate || !pTexture || pTexture->getWidth() != width || pTexture->getHeight() != height)) + pTexture = mpDevice->createTexture2D(width, height, format, 1, 1, nullptr, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + if (!keepCondition) pTexture = nullptr; + } + + + void ReSTIRPathTracing::createComputePass(ref& pPass, std::string shaderFile, DefineList defines, ProgramDesc baseDesc, std::string entryFunction) + { + if (!pPass) + { + ProgramDesc desc = baseDesc; + desc.addShaderLibrary(shaderFile).csEntry(entryFunction); + pPass = ComputePass::create(mpDevice, desc, defines, false); + } + pPass->getProgram()->addDefines(defines); + pPass->setVars(nullptr); + } + + void ReSTIRPathTracing::prepareResources(RenderContext* pRenderContext, const uint2& frameDim, const uint2& screenTiles) + { + // Create screen sized buffers. + uint32_t tileCount = screenTiles.x * screenTiles.y; + const uint32_t elementCount = tileCount * mPathTracerParams.kScreenTileDim.x * mPathTracerParams.kScreenTileDim.y; + + if (mReallocate && mpReservoirs) updatePrograms(); + + // The retrace queue must be sized for the worst case across temporal/spatial passes. + const uint32_t temporalPerPixel = mOptions.temporalRobustShift() ? kTemporalRobustNeighborMultiplier : kTemporalNeighborMultiplier; + const uint32_t spatialPerPixel = kSpatialNeighborMultiplier * mOptions.spatialNeighborCount; + const uint32_t shiftCount = elementCount * std::max(temporalPerPixel, spatialPerPixel); + const uint32_t splatShiftCount = elementCount * kTemporalNeighborMultiplier; + + // Floating reservoirs need to exist when any temporal mode that consumes them is enabled, + // even if the current frame routes through the splatting path (which doesn't read them). + const bool needFloatingReservoirs = + mOptions.useAreaReSTIRBackprojection() + ; + + prepareReservoirAndPathBuffers(elementCount, shiftCount, needFloatingReservoirs); + prepareDuplicationMapResources(frameDim); + prepareVBufferTextures(frameDim); + prepareReservoirSplattingResources(frameDim, splatShiftCount); + prepareMotionVectorTextures(frameDim); + + if (mReallocate) + mRecompile = true; + mReallocate = false; + } + + void ReSTIRPathTracing::prepareReservoirAndPathBuffers(uint32_t elementCount, uint32_t pathCount, bool needFloatingReservoirs) + { + // Per-reservoir buffers. + createOrDestroyBuffer(mpReservoirs, "pathReservoirs", elementCount); + createOrDestroyBuffer(mpPrevReservoirs, "pathReservoirs", elementCount); + createOrDestroyBuffer(mpFloatingReservoirs, "pathReservoirs", elementCount, needFloatingReservoirs); + createOrDestroyBuffer(mpNeighborValidMaskBuffer, "neighborValidMask", elementCount); + + // retrace work queues. + createOrDestroyRawBuffer(mpRetraceWorkItems, pathCount * sizeof(uint32_t), useCompactRetraceSchedule()); + createOrDestroyRawBuffer(mpCounters, sizeof(uint32_t), useCompactRetraceSchedule()); + + // Reconnection / shifted-hit / paired-shift buffers. + createOrDestroyBuffer(mpReconnectionDataBuffer, "reconnectionDataBuffer", pathCount, useReconnectionDataBuffers()); + createOrDestroyBuffer(mpReconnectionDataOffsets, "reconnectionDataOffsets", pathCount, useReconnectionDataBuffers()); + createOrDestroyBuffer(mpShiftedPrimaryHitBuffer, "shiftedPrimaryHitBuffer", pathCount, mOptions.useAreaReservoirs); + createOrDestroyBuffer(mpShiftedPrimaryHitBufferRobust, "shiftedPrimaryHitBuffer", pathCount, useRobustTemporalAreaReuse()); + createOrDestroyBuffer(mpPrecomputedShiftDataBuffer, "precomputedShiftBuffer", elementCount * mOptions.spatialNeighborCount, mOptions.usePairedNeighbors); + + // DoF / robust temporal MIS scratch buffers + createOrDestroyBuffer(mpDoFReconnectionShiftBuffer, "doFReconnectionShiftBuffer", pathCount, mOptions.useAreaReservoirs && mOptions.useDoFReconnectionShift); + createOrDestroyBuffer(mpTemporalMISPDFs, "temporalMISPDFs", pathCount, useRobustTemporalAreaReuse()); + } + + void ReSTIRPathTracing::prepareDuplicationMapResources(const uint2& frameDim) + { + createTexture2D(mpPrevTemporalCorrelationMap, frameDim.x, frameDim.y, ResourceFormat::R32Uint); + createTexture2D(mpTemporalCorrelationMap, frameDim.x, frameDim.y, ResourceFormat::R32Uint); + + const bool dupMapEnabled = useDuplicationMap(); + createTexture2D(mpSampleIDTexture, frameDim.x, frameDim.y, ResourceFormat::R32Uint, dupMapEnabled); + createTexture2D(mpDuplicationMap, frameDim.x, frameDim.y, ResourceFormat::R32Uint, dupMapEnabled); + + } + + void ReSTIRPathTracing::prepareVBufferTextures(const uint2& frameDim) + { + const auto& pScene = dynamic_ref_cast(mpScene); + FALCOR_ASSERT(pScene); + const HitInfo hitInfo = pScene->getHitInfo(); + createTexture2D(mpTemporalVBuffer, frameDim.x, frameDim.y, hitInfo.getFormat()); + createTexture2D(mpRandomVBuffer, frameDim.x, frameDim.y, hitInfo.getFormat(), mOptions.useAreaReservoirs); + createTexture2D(mpTemporalRandomVBuffer, frameDim.x, frameDim.y, hitInfo.getFormat(), mOptions.useAreaReservoirs); + } + + void ReSTIRPathTracing::prepareReservoirSplattingResources(const uint2& frameDim, uint32_t temporalPathCount) + { + createOrDestroyBuffer(mpReservoirSplatInfo, "splatSampleInfo", temporalPathCount, mOptions.useReservoirSplatting()); + createOrDestroyBuffer(mpReservoirSplatPixel, "splatTargetPixel", temporalPathCount, mOptions.useReservoirSplatting()); + createOrDestroyBuffer(mpSortedScatteringPixels, "sortedSplatSourcePixels", frameDim.x * frameDim.y, mOptions.useReservoirSplatting()); + createTexture2D(mpMappingIndices, frameDim.x, frameDim.y, ResourceFormat::RG32Uint, mOptions.useReservoirSplatting()); + createTexture2D(mpCellCounters, frameDim.x, frameDim.y, ResourceFormat::R32Uint, mOptions.useReservoirSplatting()); + createTexture2D(mpCellOffsets, frameDim.x, frameDim.y, ResourceFormat::R32Uint, mOptions.useReservoirSplatting()); + createOrDestroyBuffer(mpCellOffsetCounter, "splatCellOffsetCounter", 1, mOptions.useReservoirSplatting()); + } + + void ReSTIRPathTracing::prepareMotionVectorTextures(const uint2& frameDim) + { + // internal motion vector buffer used for specular/disocclusion motion vectors + createTexture2D(mpMVecBuffer, frameDim.x, frameDim.y, ResourceFormat::RG32Float); + // Delta reflection / transmission motion vectors written by the path tracer's delta guide passes. + createTexture2D(mpDeltaReflectionMotionVectors, frameDim.x, frameDim.y, ResourceFormat::RG16Float, mOptions.enableSpecularMotionVectors); + createTexture2D(mpDeltaTransmissionMotionVectors, frameDim.x, frameDim.y, ResourceFormat::RG16Float, mOptions.enableSpecularMotionVectors); + } + + void ReSTIRPathTracing::updatePrograms() + { + if (!mRecompile || !mpReservoirs) + return; + + DefineList commonDefines; + commonDefines.add(getDefines()); + commonDefines.add(mOwnerDefines); + + TypeConformanceList typeConformances; + mpScene->getTypeConformances(typeConformances); + + ProgramDesc baseDesc; + mpScene->getShaderModules(baseDesc.shaderModules); + baseDesc.addTypeConformances(typeConformances); + + DefineList defines = commonDefines; + defines.add("NEIGHBOR_OFFSET_COUNT", std::to_string(mpNeighborOffsets->getWidth())); + + createComputePass(mpReflectTypes, kReflectTypesFile, defines, baseDesc); + + compileTemporalPasses(defines, baseDesc); + compileSpatialPasses(defines, baseDesc); + + if (mOptions.useTemporalResampling || mOptions.useSpatialResampling) + createComputePass(mpGenerateRetraceWorkload, kSpatialGenerateRetraceWorkload, defines, baseDesc); + + createComputePass(mpFetchDenoiserData, kFetchDenoiserData, defines, baseDesc); + createComputePass(mpFillSampleId, kFillSampleID, defines, baseDesc); + createComputePass(mpComputeDuplicationMap, kComputeDuplicationMap, defines, baseDesc); + + if (mOptions.useAreaReservoirs) + compileAreaReservoirPasses(defines, baseDesc); + + createComputePass(mpComputeReuseMotionVector, kComputeReuseMotionVector, defines, baseDesc); + + mRecompile = false; + mResetTemporalReservoirs = true; + } + + void ReSTIRPathTracing::compileTemporalPasses(const DefineList& defines, const ProgramDesc& baseDesc) + { + if (!mOptions.useTemporalResampling) return; + createComputePass(mpTemporalRetrace, kTemporalRetraceFile, defines, baseDesc); + createComputePass(mpTemporalResampling, kTemporalResamplingFile, defines, baseDesc); + } + + void ReSTIRPathTracing::compileSpatialPasses(const DefineList& defines, const ProgramDesc& baseDesc) + { + if (!mOptions.useSpatialResampling) return; + createComputePass(mpSpatialRetrace, kSpatialRetraceFile, defines, baseDesc); + createComputePass(mpSpatialResampling, kSpatialResamplingFile, defines, baseDesc); + + if (mOptions.usePairedNeighbors) + createComputePass(mpSpatialPrecomputeShiftBufferPass, kSpatialPrecomputeShiftBufferFile, defines, baseDesc); + } + + void ReSTIRPathTracing::compileAreaReservoirPasses(const DefineList& defines, const ProgramDesc& baseDesc) + { + createComputePass(mpSpatialTraceShiftedPrimaryHits, kSpatialTraceShiftedPrimaryHits, defines, baseDesc); + createComputePass(mpTemporalTraceShiftedPrimaryHits, kTemporalTraceShiftedPrimaryHits, defines, baseDesc); + createComputePass(mpTemporalPopulateFloatingReservoir, kTemporalPopulateFloatingReservoir, defines, baseDesc); + + if (mOptions.temporalRobustShift()) + compileRobustShiftPasses(defines, baseDesc); + + if (mOptions.useReservoirSplatting()) + compileReservoirSplattingPasses(defines, baseDesc); + } + + void ReSTIRPathTracing::compileRobustShiftPasses(const DefineList& defines, const ProgramDesc& baseDesc) + { + createComputePass(mpTemporalRobustCreateShiftVBuffer, kTemporalRobustCreateShiftVBuffer, defines, baseDesc); + createComputePass(mpTemporalRobustProduceWorkload, kTemporalRobustProduceWorkload, defines, baseDesc); + createComputePass(mpTemporalRobustPathRetrace, kTemporalRobustPathRetrace, defines, baseDesc); + createComputePass(mpTemporalRobustResampling, kTemporalRobustResampling, defines, baseDesc); + } + + void ReSTIRPathTracing::compileReservoirSplattingPasses(const DefineList& defines, const ProgramDesc& baseDesc) + { + createComputePass(mpTemporalSplatSamples, kTemporalReservoirSplatting, defines, baseDesc, "splatSamples"); + createComputePass(mpTemporalComputeCellOffsets, kTemporalReservoirSplatting, defines, baseDesc, "computeSplatCellOffsets"); + createComputePass(mpTemporalSortCellData, kTemporalReservoirSplatting, defines, baseDesc, "sortSplatSourcesByTargetPixel"); + } + + ShaderVar ReSTIRPathTracing::bindCommonPassVars( + RenderContext* pRenderContext, + ref pPass, + const std::string& cbName, + bool bindPathTracer) + { + auto rootVar = pPass->getRootVar(); + mpScene->bindShaderData(rootVar["gScene"]); + mpScene->bindShaderDataForRaytracing(pRenderContext, rootVar["gScene"]); + mpPixelDebug->prepareProgram(pPass->getProgram(), rootVar); + + auto var = rootVar["CB"][cbName]; + var["params"].setBlob(mPathTracerParams); + setShaderData(var["restirpt"]); + + if (bindPathTracer) + rootVar["gPathTracer"] = mpPathTracerBlock; + + return var; + } + + ShaderVar ReSTIRPathTracing::bindTemporalRobustVars( + RenderContext* pRenderContext, + ref pPass, + std::string cbName, + bool bindPathTracer + ) + { + ShaderVar var = bindCommonPassVars(pRenderContext, pPass, cbName, bindPathTracer); + var["shiftedPrimaryHitBuffer"] = mpShiftedPrimaryHitBufferRobust; + var["prevReservoirs"] = mpPrevReservoirs; + return var; + } + + ShaderVar ReSTIRPathTracing::bindSpatialResamplingVars( + RenderContext* pRenderContext, + ref pPass, + std::string cbName, + const ref& pVBuffer, + bool bindPathTracer, + bool isShiftVBufferPass + ) + { + // Paired-neighbor delta tables live at the root scope, not inside the constant buffer. + auto rootVar = pPass->getRootVar(); + rootVar["pairingDeltaTextures"] = mpPairedNeighborDeltas; + rootVar["pairingDeltaTextureSizes"] = mpPairedNeighborTextureSizes; + + ShaderVar var = bindCommonPassVars(pRenderContext, pPass, cbName, bindPathTracer); + var["neighborOffsets"] = mpNeighborOffsets; + var["gNeighborCount"] = mOptions.spatialNeighborCount; + var["gGatherRadius"] = (float)mOptions.spatialGatherRadius; + var["vbuffer"] = pVBuffer; + var["resetTemporalReservoirs"] = mResetTemporalReservoirs; + var["shiftedPrimaryHitBuffer"] = mpShiftedPrimaryHitBuffer; + var["areaReservoirUseNeighborRejection"] = mOptions.areaReservoirUseNeighborRejection; + var["gUsePairedNeighbors"] = mOptions.usePairedNeighbors; + return var; + } + + void ReSTIRPathTracing::spatialResampling(RenderContext* pRenderContext, const ref& pVBuffer, const ref& pColorBuffer) + { + FALCOR_PROFILE(pRenderContext, "spatialResampling"); + + if (!mOptions.useSpatialResampling) return; + + // Bind the per-pass shader-var sets once outside the loop. Each iteration only writes the + // few fields that change between rounds (reservoirs, gSpatialRoundId, ...). + ShaderVar var = bindSpatialResamplingVars(pRenderContext, mpSpatialResampling, "gSpatialResampling", pVBuffer, true); + var["neighborValidMask"] = mpNeighborValidMaskBuffer; + var["reconnectionDataBuffer"] = mpReconnectionDataBuffer; + var["reconnectionDataOffsets"] = mpReconnectionDataOffsets; + + ShaderVar precomputeVar; + if (mOptions.usePairedNeighbors) + { + precomputeVar = bindSpatialResamplingVars(pRenderContext, mpSpatialPrecomputeShiftBufferPass, "gPass", pVBuffer, true); + precomputeVar["neighborValidMask"] = mpNeighborValidMaskBuffer; + precomputeVar["reconnectionDataBuffer"] = mpReconnectionDataBuffer; + precomputeVar["reconnectionDataOffsets"] = mpReconnectionDataOffsets; + } + + ShaderVar retraceVar = bindSpatialResamplingVars(pRenderContext, mpSpatialRetrace, "gSpatialPathRetrace", pVBuffer, true); + retraceVar["neighborValidMask"] = mpNeighborValidMaskBuffer; + retraceVar["reconnectionDataBuffer"] = mpReconnectionDataBuffer; + retraceVar["reconnectionDataOffsets"] = mpReconnectionDataOffsets; + retraceVar["numKeysBuffer"] = mpNumKeysBuffer; + retraceVar["queues"]["counters"] = mpCounters; + retraceVar["queues"]["retraceWorkItems"] = mpRetraceWorkItems; + + ShaderVar workLoadVar; + if (useCompactRetraceSchedule()) + { + workLoadVar = bindSpatialResamplingVars(pRenderContext, mpGenerateRetraceWorkload, "gRetraceWorkloadGenerator", pVBuffer, false); + workLoadVar["queues"]["counters"] = mpCounters; + workLoadVar["queues"]["retraceWorkItems"] = mpRetraceWorkItems; + workLoadVar["neighborValidMask"] = mpNeighborValidMaskBuffer; + workLoadVar["isSpatialPass"] = true; + } + + ShaderVar shiftVar; + if (mOptions.useAreaReservoirs) + shiftVar = bindSpatialResamplingVars(pRenderContext, mpSpatialTraceShiftedPrimaryHits, "gPass", pVBuffer, true, true); + + mResetTemporalReservoirs = false; + + for (uint32_t iteration = 0; iteration < mOptions.spatialIterations; ++iteration) + { + std::swap(mpReservoirs, mpPrevReservoirs); + + if (mOptions.useAreaReservoirs) + spatialDispatchTraceShiftedPrimaryHits(pRenderContext, shiftVar, iteration); + + if (useCompactRetraceSchedule()) + spatialDispatchGenerateRetraceWorkload(pRenderContext, workLoadVar, iteration); + + spatialDispatchRetrace(pRenderContext, retraceVar, iteration); + + if (mOptions.usePairedNeighbors) + spatialDispatchPrecomputeShiftBuffer(pRenderContext, precomputeVar, iteration); + + spatialDispatchResamplingPass(pRenderContext, var, iteration, pColorBuffer); + } + } + + void ReSTIRPathTracing::spatialDispatchTraceShiftedPrimaryHits(RenderContext* pRenderContext, ShaderVar shiftVar, uint32_t iteration) + { + FALCOR_PROFILE(pRenderContext, "spatialCreateShiftVBuffer"); + shiftVar["prevReservoirs"] = mpPrevReservoirs; + shiftVar["gSpatialRoundId"] = iteration; + shiftVar["sampleVBuffer"] = mpRandomVBuffer; + shiftVar["doFReconnectionShiftBuffer"] = mpDoFReconnectionShiftBuffer; + mpSpatialTraceShiftedPrimaryHits->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::spatialDispatchGenerateRetraceWorkload(RenderContext* pRenderContext, ShaderVar workLoadVar, uint32_t iteration) + { + FALCOR_PROFILE(pRenderContext, "produceRetraceWorkload"); + + if (mpCounters) + pRenderContext->clearUAV(mpCounters->getUAV().get(), uint4(0)); + + workLoadVar["prevReservoirs"] = mpPrevReservoirs; + workLoadVar["gSpatialRoundId"] = iteration; + const uint32_t tileSize = mPathTracerParams.kScreenTileDim.x * mPathTracerParams.kScreenTileDim.y; + mpGenerateRetraceWorkload->execute(pRenderContext, mPathTracerParams.screenTiles.x * tileSize, mPathTracerParams.screenTiles.y, 1); + } + + void ReSTIRPathTracing::spatialDispatchRetrace(RenderContext* pRenderContext, ShaderVar retraceVar, uint32_t iteration) + { + FALCOR_PROFILE(pRenderContext, "spatialRetrace"); + retraceVar["prevReservoirs"] = mpPrevReservoirs; + retraceVar["gSpatialRoundId"] = iteration; + retraceVar["doFReconnectionShiftBuffer"] = mpDoFReconnectionShiftBuffer; + if (useCompactRetraceSchedule()) + mpSpatialRetrace->execute(pRenderContext, kSpatialNeighborMultiplier * mOptions.spatialNeighborCount * mFrameDim.x * mFrameDim.y, 1, 1); + else + mpSpatialRetrace->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::spatialDispatchPrecomputeShiftBuffer(RenderContext* pRenderContext, ShaderVar precomputeVar, uint32_t iteration) + { + FALCOR_PROFILE(pRenderContext, "spatialPrecomputeShiftBuffer"); + precomputeVar["reservoirs"] = mpReservoirs; + precomputeVar["prevReservoirs"] = mpPrevReservoirs; + precomputeVar["sampleSurvivedFrames"] = mpTemporalCorrelationMap; + precomputeVar["gSpatialRoundId"] = iteration; + precomputeVar["doFReconnectionShiftBuffer"] = mpDoFReconnectionShiftBuffer; + precomputeVar["sampleVBuffer"] = mpRandomVBuffer; + precomputeVar["precomputedShiftBuffer"] = mpPrecomputedShiftDataBuffer; + mpSpatialPrecomputeShiftBufferPass->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::spatialDispatchResamplingPass(RenderContext* pRenderContext, ShaderVar var, uint32_t iteration, const ref& pColorBuffer) + { + FALCOR_PROFILE(pRenderContext, "spatialResampling"); + var["reservoirs"] = mpReservoirs; + var["prevReservoirs"] = mpPrevReservoirs; + var["sampleSurvivedFrames"] = mpTemporalCorrelationMap; + var["gSpatialRoundId"] = iteration; + var["doFReconnectionShiftBuffer"] = mpDoFReconnectionShiftBuffer; + var["sampleVBuffer"] = mpRandomVBuffer; + var["precomputedShiftBuffer"] = mpPrecomputedShiftDataBuffer; + + if (mOptions.useDecoupledShading && pColorBuffer) + var["outputColor"] = pColorBuffer; + + mpSpatialResampling->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + + + ShaderVar ReSTIRPathTracing::bindReservoirSplattingVars( + RenderContext* pRenderContext, + ref pPass, + std::string cbName + ) + { + // Reservoir splatting passes don't bind the path tracer block or the `restirpt` settings struct, + // so the common helper isn't a clean fit; just inline the small preamble. + auto rootVar = pPass->getRootVar(); + mpScene->bindShaderData(rootVar["gScene"]); + mpScene->bindShaderDataForRaytracing(pRenderContext, rootVar["gScene"]); + mpPixelDebug->prepareProgram(pPass->getProgram(), rootVar); + + auto var = rootVar["CB"][cbName]; + var["params"].setBlob(mPathTracerParams); + var["sampleVBuffer"] = mpRandomVBuffer; + var["temporalSampleVbuffer"] = mpTemporalRandomVBuffer; + var["prevReservoirs"] = mpPrevReservoirs; + var["reservoirs"] = mpReservoirs; + var["splatCellOffsetCounter"] = mpCellOffsetCounter; + var["splatCellCounts"] = mpCellCounters; + var["splatCellOffsets"] = mpCellOffsets; + var["sortedSplatSourcePixels"] = mpSortedScatteringPixels; + var["splatCellIndices"] = mpMappingIndices; + var["splatSampleInfo"] = mpReservoirSplatInfo; + var["splatTargetPixel"] = mpReservoirSplatPixel; + var["shiftedPrimaryHitBuffer"] = mpShiftedPrimaryHitBuffer; + return var; + } + + ShaderVar ReSTIRPathTracing::bindTemporalResamplingVars( + RenderContext* pRenderContext, + ref pPass, + std::string cbName, + const ref& pVBuffer, + const ref& pMotionVectors, + bool bindPathTracer, + bool isShiftVBufferPass + ) + { + ShaderVar var = bindCommonPassVars(pRenderContext, pPass, cbName, bindPathTracer); + + if (!isShiftVBufferPass) + { + var["vbuffer"] = (mOptions.useAreaReservoirs && bindPathTracer) ? mpRandomVBuffer : pVBuffer; + var["temporalVbuffer"] = mpTemporalVBuffer; + var["gTemporalHistoryLength"] = mOptions.maxHistoryLength; + } + else + { + var["vbuffer"] = pVBuffer; + } + + var["motionVectors"] = useCustomMotionVectorPass() ? mpMVecBuffer : pMotionVectors; + var["gEnableTemporalReprojection"] = true; + var["gStochasticReprojection"] = mOptions.pointReprojectionMode == ReSTIRPT::PointReprojectionMode::Stochastic; + + var["shiftedPrimaryHitBuffer"] = mpShiftedPrimaryHitBuffer; + var["reservoirs"] = mpReservoirs; + var["prevReservoirs"] = useFloatingReservoirs() ? mpFloatingReservoirs : mpPrevReservoirs; + return var; + } + + void ReSTIRPathTracing::temporalResampling(RenderContext* pRenderContext, const ref& pMotionVectors, const ref& pVBuffer) + { + FALCOR_PROFILE(pRenderContext, "temporalResampling"); + + if (mResetTemporalReservoirs) + { + pRenderContext->clearUAV(mpTemporalCorrelationMap->getUAV().get(), uint4(0)); + if (!mOptions.useSpatialResampling) mResetTemporalReservoirs = false; + return; + } + + if (!mOptions.useTemporalResampling) return; + + // A second temporal pass is required when DoF reconnection produces a separate proposal stream. + const int numPasses = mOptions.useAreaReservoirs + && useDoFReconnectionForTemporalReuseEffective() + && mOptions.useDoFReconnectionShift ? 2 : 1; + + for (int i = 0; i < numPasses; i++) + { + if (useCustomMotionVectorPass()) + temporalDispatchComputeReuseMotionVector(pRenderContext, pVBuffer, pMotionVectors); + + if (useRobustTemporalAreaReuse() && i == 0) + temporalDispatchRobustChain(pRenderContext); + + // The populate-floating-reservoir pass writes into mpFloatingReservoirs whenever the temporal + // path will read from it. The standard backprojection path only runs on the first pass (i==0), + // but the multi-motion-vectors path runs on every temporal pass. + bool executePopulatePass = !mOptions.useReservoirSplatting() && mOptions.useAreaReSTIRBackprojection() && i == 0; + if (executePopulatePass) + temporalDispatchPopulateFloatingReservoir(pRenderContext, pVBuffer, pMotionVectors); + + if (mOptions.useReservoirSplatting()) + temporalDispatchSplattingChain(pRenderContext); + + if (mOptions.useAreaReservoirs && !mOptions.useReservoirSplatting()) + temporalDispatchTraceShiftedPrimaryHits(pRenderContext, pVBuffer, pMotionVectors, i); + + if (useCompactRetraceSchedule()) + temporalDispatchGenerateRetraceWorkload(pRenderContext, pVBuffer, pMotionVectors, i); + + temporalDispatchRetrace(pRenderContext, pVBuffer, pMotionVectors, i); + temporalDispatchResamplingPass(pRenderContext, pVBuffer, pMotionVectors, i); + } + } + + void ReSTIRPathTracing::temporalDispatchComputeReuseMotionVector( + RenderContext* pRenderContext, + const ref& pVBuffer, + const ref& pMotionVectors) + { + FALCOR_PROFILE(pRenderContext, "ComputeReuseMotionVector"); + ShaderVar var = bindCommonPassVars(pRenderContext, mpComputeReuseMotionVector, "gPass", false); + var["originalMotionVectors"] = pMotionVectors; + var["deltaReflectionMotionVectors"] = mpDeltaReflectionMotionVectors; + var["deltaTransmissionMotionVectors"] = mOptions.handleTransmissionInSpecMVec ? mpDeltaTransmissionMotionVectors : pMotionVectors; + var["motionVectors"] = mpMVecBuffer; + var["vbuffer"] = pVBuffer; + var["temporalVBuffer"] = mpTemporalVBuffer; + + if (mOptions.enableSpecularMotionVectors && (!mpDeltaReflectionMotionVectors || !mpDeltaTransmissionMotionVectors)) + { + logWarning("Specular motion vector textures are not allocated; ComputeReuseMotionVector will read from unbound buffers."); + } + + mpComputeReuseMotionVector->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::temporalDispatchRobustChain(RenderContext* pRenderContext) + { + const uint32_t tileSize = mPathTracerParams.kScreenTileDim.x * mPathTracerParams.kScreenTileDim.y; + + { + FALCOR_PROFILE(pRenderContext, "TemporalRobustCreateShiftVBuffer"); + ShaderVar var = bindTemporalRobustVars(pRenderContext, mpTemporalRobustCreateShiftVBuffer, "gPass", true); + mpTemporalRobustCreateShiftVBuffer->execute(pRenderContext, mFrameDim.x, mFrameDim.y, kTemporalRobustNeighborMultiplier); + } + + { + FALCOR_PROFILE(pRenderContext, "TemporalRobustProduceWorkload"); + ShaderVar var = bindTemporalRobustVars(pRenderContext, mpTemporalRobustProduceWorkload, "gPass", false); + if (mpCounters) + pRenderContext->clearUAV(mpCounters->getUAV().get(), uint4(0)); + var["queues"]["counters"] = mpCounters; + var["queues"]["retraceWorkItems"] = mpRetraceWorkItems; + mpTemporalRobustProduceWorkload->execute(pRenderContext, mPathTracerParams.screenTiles.x * tileSize, mPathTracerParams.screenTiles.y, 1); + } + + { + FALCOR_PROFILE(pRenderContext, "TemporalRobustPathRetrace"); + ShaderVar var = bindTemporalRobustVars(pRenderContext, mpTemporalRobustPathRetrace, "gPass", true); + var["reconnectionDataBuffer"] = mpReconnectionDataBuffer; + var["reconnectionDataOffsets"] = mpReconnectionDataOffsets; + var["queues"]["counters"] = mpCounters; + var["queues"]["retraceWorkItems"] = mpRetraceWorkItems; + mpTemporalRobustPathRetrace->execute(pRenderContext, kTemporalRobustNeighborMultiplier * mFrameDim.x * mFrameDim.y, 1, 1); + } + + { + FALCOR_PROFILE(pRenderContext, "TemporalRobustResampling"); + ShaderVar var = bindTemporalRobustVars(pRenderContext, mpTemporalRobustResampling, "gPass", true); + var["reconnectionDataBuffer"] = mpReconnectionDataBuffer; + var["reconnectionDataOffsets"] = mpReconnectionDataOffsets; + var["temporalMISPDFs"] = mpTemporalMISPDFs; + mpTemporalRobustResampling->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + } + + void ReSTIRPathTracing::temporalDispatchPopulateFloatingReservoir( + RenderContext* pRenderContext, + const ref& pVBuffer, + const ref& pMotionVectors) + { + FALCOR_PROFILE(pRenderContext, "TemporalPopulateFloatingReservoir"); + ShaderVar var = bindCommonPassVars(pRenderContext, mpTemporalPopulateFloatingReservoir, "gPass", false); + + bool useCustomMVecBuffer = useCustomMotionVectorPass(); + var["motionVectors"] = useCustomMVecBuffer ? mpMVecBuffer : pMotionVectors; + var["deltaTransmissionMotionVectors"] = mOptions.handleTransmissionInSpecMVec ? mpDeltaTransmissionMotionVectors : pMotionVectors; + var["deltaReflectionMotionVectors"] = mpDeltaReflectionMotionVectors; + var["temporaryReservoirs"] = mpFloatingReservoirs; + var["prevReservoirs"] = mpPrevReservoirs; + var["temporalMISPDFs"] = mpTemporalMISPDFs; + var["reconnectionDataBuffer"] = mpReconnectionDataBuffer; + var["reconnectionDataOffsets"] = mpReconnectionDataOffsets; + var["shiftedPrimaryHitBufferRobust"] = mpShiftedPrimaryHitBufferRobust; + var["shiftedPrimaryHitBuffer"] = mpShiftedPrimaryHitBuffer; + var["reservoirs"] = mpReservoirs; + + var["sampleVBuffer"] = mpRandomVBuffer; + var["doFReconnectionShiftBuffer"] = mpDoFReconnectionShiftBuffer; + var["vbuffer"] = pVBuffer; + mpTemporalPopulateFloatingReservoir->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::temporalDispatchSplattingChain(RenderContext* pRenderContext) + { + pRenderContext->clearUAV(mpCellOffsetCounter->getUAV().get(), uint4(0)); + pRenderContext->clearUAV(mpCellCounters->getUAV().get(), uint4(0)); + + { + FALCOR_PROFILE(pRenderContext, "TemporalSplatSamples"); + ShaderVar var = bindReservoirSplattingVars(pRenderContext, mpTemporalSplatSamples, "gPass"); + mpTemporalSplatSamples->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + { + FALCOR_PROFILE(pRenderContext, "TemporalComputeCellOffsets"); + ShaderVar var = bindReservoirSplattingVars(pRenderContext, mpTemporalComputeCellOffsets, "gPass"); + mpTemporalComputeCellOffsets->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + { + FALCOR_PROFILE(pRenderContext, "TemporalSortCellData"); + ShaderVar var = bindReservoirSplattingVars(pRenderContext, mpTemporalSortCellData, "gPass"); + mpTemporalSortCellData->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + } + + void ReSTIRPathTracing::temporalDispatchTraceShiftedPrimaryHits( + RenderContext* pRenderContext, + const ref& pVBuffer, + const ref& pMotionVectors, + int temporalPassId) + { + FALCOR_PROFILE(pRenderContext, "TemporalTraceShiftedPrimaryHits"); + ShaderVar var = bindTemporalResamplingVars( + pRenderContext, mpTemporalTraceShiftedPrimaryHits, "gPass", pVBuffer, pMotionVectors, true, true + ); + var["sampleVBuffer"] = mpRandomVBuffer; + var["doFReconnectionShiftBuffer"] = mpDoFReconnectionShiftBuffer; + var["temporalPassId"] = temporalPassId; + mpTemporalTraceShiftedPrimaryHits->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::temporalDispatchGenerateRetraceWorkload( + RenderContext* pRenderContext, + const ref& pVBuffer, + const ref& pMotionVectors, + int temporalPassId) + { + FALCOR_PROFILE(pRenderContext, "GenerateRetraceWorkload"); + + if (mpCounters) + pRenderContext->clearUAV(mpCounters->getUAV().get(), uint4(0)); + + ShaderVar var = bindTemporalResamplingVars(pRenderContext, mpGenerateRetraceWorkload, "gRetraceWorkloadGenerator", pVBuffer, pMotionVectors, false); + var["queues"]["counters"] = mpCounters; + var["queues"]["retraceWorkItems"] = mpRetraceWorkItems; + var["neighborValidMask"] = mpNeighborValidMaskBuffer; + var["isSpatialPass"] = false; + var["temporalPassId"] = temporalPassId; + var["splatSampleInfo"] = mpReservoirSplatInfo; + + const uint32_t tileSize = mPathTracerParams.kScreenTileDim.x * mPathTracerParams.kScreenTileDim.y; + mpGenerateRetraceWorkload->execute(pRenderContext, mPathTracerParams.screenTiles.x * tileSize, mPathTracerParams.screenTiles.y, 1); + } + + void ReSTIRPathTracing::temporalDispatchRetrace( + RenderContext* pRenderContext, + const ref& pVBuffer, + const ref& pMotionVectors, + int temporalPassId) + { + FALCOR_PROFILE(pRenderContext, "temporalRetrace"); + + ShaderVar var = bindTemporalResamplingVars(pRenderContext, mpTemporalRetrace, "gTemporalPathRetrace", pVBuffer, pMotionVectors, true); + var["numKeysBuffer"] = mpNumKeysBuffer; + var["neighborValidMask"] = mpNeighborValidMaskBuffer; + var["reconnectionDataBuffer"] = mpReconnectionDataBuffer; + var["reconnectionDataOffsets"] = mpReconnectionDataOffsets; + var["queues"]["counters"] = mpCounters; + var["queues"]["retraceWorkItems"] = mpRetraceWorkItems; + var["doFReconnectionShiftBuffer"] = mpDoFReconnectionShiftBuffer; + var["splatSampleInfo"] = mpReservoirSplatInfo; + var["splatTargetPixel"] = mpReservoirSplatPixel; + var["temporalPassId"] = temporalPassId; + + if (useCompactRetraceSchedule()) + mpTemporalRetrace->execute(pRenderContext, kTemporalNeighborMultiplier * mFrameDim.x * mFrameDim.y, 1, 1); + else + mpTemporalRetrace->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::temporalDispatchResamplingPass( + RenderContext* pRenderContext, + const ref& pVBuffer, + const ref& pMotionVectors, + int temporalPassId) + { + FALCOR_PROFILE(pRenderContext, "temporalResampling"); + + ShaderVar var = bindTemporalResamplingVars(pRenderContext, mpTemporalResampling, "gTemporalResampling", pVBuffer, pMotionVectors, true); + var["neighborValidMask"] = mpNeighborValidMaskBuffer; + var["reconnectionDataBuffer"] = mpReconnectionDataBuffer; + var["reconnectionDataOffsets"] = mpReconnectionDataOffsets; + var["enableDupMapMCapDownweighting"] = mOptions.enableDupMapMCapDownweighting; + var["computeTemporalDuplication"] = mOptions.computeTemporalDuplication; + var["duplicationMCapPower"] = mOptions.duplicationMCapPower; + var["duplicationMap"] = mpDuplicationMap; + var["sampleSurvivedFrames"] = mpTemporalCorrelationMap; + var["prevSampleSurvivedFrames"] = mpPrevTemporalCorrelationMap; + var["gGatherRadius"] = (float)mOptions.spatialGatherRadius; + var["doFReconnectionShiftBuffer"] = mpDoFReconnectionShiftBuffer; + var["splatSampleInfo"] = mpReservoirSplatInfo; + var["splatTargetPixel"] = mpReservoirSplatPixel; + var["splatCellCounts"] = mpCellCounters; + var["splatCellOffsets"] = mpCellOffsets; + var["sortedSplatSourcePixels"] = mpSortedScatteringPixels; + var["temporalPassId"] = temporalPassId; + + mpTemporalResampling->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::fetchDenoiserData( + RenderContext* pRenderContext, + const ref& pVBuffer, + const ref& pSampleGuideData, + const ref& pEmission, + const ref& pDiffuseRadianceHitDist, + const ref& pSpecularRadianceHitDist, + const ref& pDeltaReflectionRadianceHitDist, + const ref& pDeltaTransmissionRadianceHitDist, + const ref& pResidualRadianceHitDist, + const ref& pOutputColor + ) + { + FALCOR_PROFILE(pRenderContext, "fetchDenoiserData"); + + mpFetchDenoiserData->addDefine("OUTPUT_GUIDE_DATA", (pSampleGuideData != nullptr ? "1" : "0")); + mpFetchDenoiserData->addDefine("FETCH_NRD_DATA_PASS", (pDiffuseRadianceHitDist != nullptr ? "1" : "0")); + + ShaderVar var = bindCommonPassVars(pRenderContext, mpFetchDenoiserData, "gFetchDenoiserData", true); + var["vbuffer"] = pVBuffer; + var["reservoirs"] = mpReservoirs; + var["sampleGuideData"] = pSampleGuideData; + + var["outputEmission"] = pEmission; + var["outputDiffuseRadianceHitDist"] = pDiffuseRadianceHitDist; + var["outputSpecularRadianceHitDist"] = pSpecularRadianceHitDist; + + var["outputDeltaReflectionRadianceHitDist"] = pDeltaReflectionRadianceHitDist; + var["outputDeltaTransmissionRadianceHitDist"] = pDeltaTransmissionRadianceHitDist; + var["outputResidualRadianceHitDist"] = pResidualRadianceHitDist; + var["outputColor"] = pOutputColor; + + mpFetchDenoiserData->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + void ReSTIRPathTracing::fillSampleID(RenderContext* pRenderContext) + { + if (!mpReservoirs) return; + + { + FALCOR_PROFILE(pRenderContext, "fillSampleID"); + + auto rootVar = mpFillSampleId->getRootVar(); + mpPixelDebug->prepareProgram(mpFillSampleId->getProgram(), rootVar); + auto var = rootVar["CB"]["gPass"]; + var["params"].setBlob(mPathTracerParams); + var["reservoirs"] = mpReservoirs; + var["sampleIDTexture"] = mpSampleIDTexture; + mpFillSampleId->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + + { + FALCOR_PROFILE(pRenderContext, "computeDuplicationMap"); + + auto rootVar = mpComputeDuplicationMap->getRootVar(); + mpPixelDebug->prepareProgram(mpComputeDuplicationMap->getProgram(), rootVar); + auto var = rootVar["CB"]["gPass"]; + var["params"].setBlob(mPathTracerParams); + var["sampleIDTexture"] = mpSampleIDTexture; + var["duplicationMap"] = mpDuplicationMap; + mpComputeDuplicationMap->execute(pRenderContext, mFrameDim.x, mFrameDim.y, 1); + } + } + + ref ReSTIRPathTracing::createNeighborOffsetTexture(uint32_t sampleCount) + { + std::unique_ptr offsets(new int8_t[sampleCount * 2]); + const int R = 254; + const float phi2 = 1.f / 1.3247179572447f; + float u = 0.5f; + float v = 0.5f; + for (uint32_t index = 0; index < sampleCount * 2;) + { + u += phi2; + v += phi2 * phi2; + if (u >= 1.f) u -= 1.f; + if (v >= 1.f) v -= 1.f; + + float rSq = (u - 0.5f) * (u - 0.5f) + (v - 0.5f) * (v - 0.5f); + if (rSq > 0.25f) continue; + + offsets[index++] = int8_t((u - 0.5f) * R); + offsets[index++] = int8_t((v - 0.5f) * R); + } + + return mpDevice->createTexture1D(sampleCount, ResourceFormat::RG8Snorm, 1, 1, offsets.get()); + } + + ref ReSTIRPathTracing::createPairedNeighborDeltasTexture(ref pDevice, uint32_t neighborCount, int stdevLevel) + { + // Avg radii: kPairedNeighborMinRadius, +step, +step, ... (kPairedNeighborStdevLevels entries). + static const auto avg_radii = []() { + std::array r{}; + for (size_t i = 0; i < r.size(); ++i) + r[i] = kPairedNeighborMinRadius + int(i) * kPairedNeighborRadiusStep; + return r; + }(); + //allocate space + std::vector arr(kPairedNeighborTextureExtent * kPairedNeighborTextureExtent * neighborCount); + std::vector sizes(kPairedNeighborMaxCount); + + for (uint32_t i = 0; i < neighborCount; i++) + { + float stdev = sqrt(8 / (9 * M_PI)) * float(avg_radii[stdevLevel]); + std::ostringstream oss; + oss << std::fixed << std::setprecision(1) << stdev; + std::string stdevname = oss.str(); + + std::filesystem::path filename; + findFileInShaderDirectories( + "ReSTIRPathTracing/PairedReusePattern/neighborCount" + std::to_string(neighborCount) + "-" + "stdev" + stdevname + "/neighbor" + std::to_string(i) + ".png", + filename + ); + + // read texture + Bitmap::UniqueConstPtr bitmap = Bitmap::createFromFile(filename, true, Bitmap::ImportFlags::None); + + uint32_t height = bitmap->getHeight(); + uint32_t width = bitmap->getWidth(); + uint16_t* data = (uint16_t*)bitmap->getData(); + + // assume height == width + + sizes[i] = height; + + uint32_t counter = 0; + + for (uint32_t y = 0; y < height; y++) + { + for (uint32_t x = 0; x < width; x++) + { + arr[kPairedNeighborTextureExtent * kPairedNeighborTextureExtent * i + width * y + x] = data[width * y + x]; + counter++; + } + } + } + + mpPairedNeighborTextureSizes = mpDevice->createStructuredBuffer(4, 5, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, sizes.data(), false); + + return mpDevice->createStructuredBuffer( + 4, 256 * 256 * neighborCount, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, arr.data(), false + ); + } + + void ReSTIRPathTracing::scriptBindings(pybind11::module& m) + { + + } +} diff --git a/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracing.h b/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracing.h new file mode 100644 index 0000000000..64ee71abd7 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracing.h @@ -0,0 +1,496 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#pragma once +#include "Utils/Sampling/AliasTable.h" +#include "Utils/Debug/PixelDebug.h" +#include "Utils/Scripting/ScriptBindings.h" +#include "Scene/Scene.h" +#include "Scene/Lights/LightCollection.h" +#include "Scene/Lights/Light.h" +#include "ReSTIRPT.slang" +#include "ReSTIRPathTracingParams.slang" +#include +#include +#include +#include +#include +#include "Utils/Algorithm/ParallelReduction.h" + +namespace Falcor +{ + /** Implementation of ReSTIR PT Enhanced, with area reservoir support. + * reference: "Generalized Resampled Importance Sampling: Foundations of ReSTIR" + * "Area ReSTIR: Resampling for Real-Time Defocus and Antialiasing" + * "Reservoir Splatting for Temporal Path Resampling and Motion Blur" + * "ReSTIR PT Enhanced: Algorithmic Advances for Faster and More Robust ReSTIR Path Tracing" + */ + class ReSTIRPathTracing + { + public: + using SharedPtr = std::shared_ptr; + + /** Enumeration of available debug outputs. + Note: Keep in sync with definition in Params.slang + */ + enum class DebugOutput + { + Disabled, + Position, + Depth, + Normal, + FaceNormal, + DiffuseWeight, + SpecularWeight, + SpecularRoughness, + PackedNormal, + PackedDepth, + InitialWeight, + TemporalReuse, + SpatialReuse, + FinalSampleDir, + FinalSampleDistance, + FinalSampleLi, + }; + + /** Configuration options. + */ + struct Options + { + bool sampleDI = true; // RTXDI results will be overwritten if this is set to true + + // Temporal resampling options. + + bool useTemporalResampling = true; + bool temporalUpdateForDynamicScene = false; + ReSTIRPT::PointReprojectionMode pointReprojectionMode = ReSTIRPT::PointReprojectionMode::Nearest; + ReSTIRPT::TemporalAreaReuseType temporalAreaReuseType = ReSTIRPT::TemporalAreaReuseType::Fast; + + bool enableSpecularMotionVectors = false; + bool handleTransmissionInSpecMVec = false; + float specularMotionVectorRoughnessThreshold = 0.1f; + bool enableDisocclusionMotionVectors = false; + uint32_t maxHistoryLength = 20; + bool enableDupMapMCapDownweighting = false; + bool computeTemporalDuplication = false; + float duplicationMCapPower = 0.1f; + bool useDoFReconnectionShift = true; + bool useDoFReconnectionForTemporalReuse = true; + float doFReconnectionShiftProbability = 0.3f; + bool deriveDoFReconnectionShiftProbabilityFromCoC = true; + + bool useAreaReSTIRBackprojection() const + { + return useAreaReservoirs && temporalAreaReuseType != ReSTIRPT::TemporalAreaReuseType::Point; + } + bool temporalRobustShift() const { return temporalAreaReuseType == ReSTIRPT::TemporalAreaReuseType::Robust; } + bool useReservoirSplatting() const { return temporalAreaReuseType == ReSTIRPT::TemporalAreaReuseType::Splat; } + + // Spatial resampling options. + bool useSpatialResampling = true; ///< Enable spatial resampling. + uint32_t spatialIterations = 1; ///< Number of spatial resampling iterations. + uint32_t spatialNeighborCount = 3; ///< Number of neighbor samples to resample per pixel and iteration. + uint32_t spatialGatherRadius = 30; ///< Radius to gather samples from. + bool usePairedNeighbors = true; + int spatialPairingStdevLevel = 5; + bool areaReservoirUseNeighborRejection = false; + + + // Options for ReSTIR PT. + ReSTIRPT::ShiftMappingSettings shiftMappingSettings; + + uint32_t reservoirCountPerPixel = 1; ///< Number of reservoirs per pixel. + + // static params + ReSTIRPT::ShiftMapping shiftMapping = ReSTIRPT::ShiftMapping::Hybrid; + + bool useReservoirCompression = false; + + bool useAreaReservoirs = false; + + ReSTIRPT::RetraceScheduleType retraceScheduleType = ReSTIRPT::RetraceScheduleType::Compact; + + bool useDecoupledShading = false; + + // Note: Empty constructor needed for clang due to the use of the nested struct constructor in the parent constructor. + Options() {} + + + template + void serialize(Archive& ar) + { + ar("sampleDI", sampleDI); + ar("useAreaReservoirs", useAreaReservoirs); + ar("useTemporalResampling", useTemporalResampling); + ar("maxHistoryLength", maxHistoryLength); + ar("temporalAreaReuseType", temporalAreaReuseType); + ar("pointReprojectionMode", pointReprojectionMode); + ar("enableSpecularMotionVectors", enableSpecularMotionVectors); + ar("handleTransmissionInSpecMVec", handleTransmissionInSpecMVec); + ar("enableDisocclusionMotionVectors", enableDisocclusionMotionVectors); + ar("useSpatialResampling", useSpatialResampling); + ar("spatialIterations", spatialIterations); + ar("spatialNeighborCount", spatialNeighborCount); + ar("spatialGatherRadius", spatialGatherRadius); + ar("shiftMappingSettings", shiftMappingSettings); + ar("shiftMapping", shiftMapping); + ar("useDoFReconnectionShift", useDoFReconnectionShift); + ar("useDoFReconnectionForTemporalReuse", useDoFReconnectionForTemporalReuse); + ar("deriveDoFReconnectionShiftProbabilityFromCoC", deriveDoFReconnectionShiftProbabilityFromCoC); + ar("useDecoupledShading", useDecoupledShading); + ar("usePairedNeighbors", usePairedNeighbors); + } + }; + + // static params shared with path tracer + struct SharedStaticParams + { + uint32_t samplesPerPixel; ///< Number of samples (paths) per pixel, unless a sample density map is used. + uint32_t maxSurfaceBounces; ///< Max number of surface bounces (diffuse + specular + transmission), up to kMaxPathLenth. This will be initialized at startup. + bool useNEE; ///< Use next-event estimation (NEE). This enables shadow ray(s) from each path vertex. + }; + + /** Create a new instance of the ReSTIR sampler. + \param[in] pScene Scene. + \param[in] options Configuration options. + */ + ReSTIRPathTracing(const ref& pScene, const DefineList& ownerDefines, const Options& options); + + /** Get a list of shader defines for using the ReSTIR sampler. + \return Returns a list of defines. + */ + DefineList getDefines() const; + + /** Bind the ReSTIR sampler to a given shader var. + \param[in] var The shader variable to set the data into. + */ + void setShaderData(const ShaderVar& var) const; + + void setReservoirData(const ShaderVar& var) const; + + /** Render the GUI. + \return True if options were changed, false otherwise. + */ + bool renderUI(Gui::Widgets& widget); + + /** Returns the current configuration. + */ + const Options& getOptions() const { return mOptions; } + + /** Set the configuration. + */ + void setOptions(const Options& options); + + /** Begin a frame. + Must be called once at the beginning of each frame. + \param[in] pRenderContext Render context. + \param[in] frameDim Current frame dimension. + */ + void beginFrame(RenderContext* pRenderContext, const uint2& frameDim, const uint2& screenTiles, bool needRecompile); + + /** End a frame. + Must be called one at the end of each frame. + \param[in] pRenderContext Render context. + */ + void endFrame(RenderContext* pRenderContext); + + + /** Run the ReSTIR PT algorithm. + Must be called once between beginFrame() and endFrame(). + \param[in] pRenderContext Render context. + \param[in] pMotionVectors Surface motion vectors for temporal reprojection. + \param[in] vBuffer V-buffer texture. + \param[in] pColorBuffer Output color texture. + */ + void updateReSTIRPT(RenderContext* pRenderContext, const ref& pMotionVectors, const ref& vBuffer, const ref& pColorBuffer); + + void fetchDenoiserData( + RenderContext* pRenderContext, + const ref& pVBuffer, + const ref& pSampleGuideData, + const ref& pEmission, + const ref& pDiffuseRadianceHitDist, + const ref& pSpecularRadianceHitDist, + const ref& pDeltaReflectionRadianceHitDist, + const ref& pDeltaTransmissionRadianceHitDist, + const ref& pResidualRadianceHitDist, + const ref& pOutputColor + ); + + void fillSampleID(RenderContext* pRenderContext); + + /** Get the debug output texture. + \return Returns the debug output texture. + */ + const ref& getDebugOutputTexture() const { return mpDebugOutputTexture; } + + /** Get the pixel debug component. + \return Returns the pixel debug component. + */ + const std::unique_ptr& getPixelDebug() const { return mpPixelDebug; } + + /** Delta-reflection screen-space motion vector texture consumed by `ComputeReuseMotionVector`. + Allocated when `enableSpecularMotionVectors` is enabled; the path tracer writes to it from + the delta reflection guide trace pass. Returns nullptr when not allocated. + */ + const ref& getDeltaReflectionMotionVectors() const { return mpDeltaReflectionMotionVectors; } + + /** Delta-transmission screen-space motion vector texture consumed by `ComputeReuseMotionVector`. + Allocated when `enableSpecularMotionVectors` is enabled; the path tracer writes to it from + the delta transmission guide trace pass. Returns nullptr when not allocated. + */ + const ref& getDeltaTransmissionMotionVectors() const { return mpDeltaTransmissionMotionVectors; } + + /** Register script bindings. + */ + static void scriptBindings(pybind11::module& m); + + void setPathTracerParams(ReSTIRPathTracingParams params); + + void setInitialLightSampleCount(uint32_t initialLightSampleCount) + { + exInitialLightSampleCount = initialLightSampleCount; + } + + void setOwnerDefines(DefineList defines); + + void setSharedStaticParams(uint32_t samplesPerPixel, uint32_t maxSurfaceBounces, bool useNEE); + + void createPathTracerBlock(); + + ref getPathTracerBlock(); + + void updatePrograms(); + + void setShaderVarForDuplicationMap(const ShaderVar& var) + { + var["duplicationMap"] = mpDuplicationMap; + var["sampleSurvivedFrames"] = mpTemporalCorrelationMap; + } + + const ref& getVBuffer(const ref& pInputVBuffer, bool usePredefinedJitterPattern = false) const + { + return (mOptions.useAreaReservoirs && !usePredefinedJitterPattern) ? mpRandomVBuffer : pInputVBuffer; + } + + private: + void createComputePass(ref& pPass, std::string shaderFile, DefineList defines, ProgramDesc desc, std::string entryFunction = "main"); + + void prepareResources(RenderContext* pRenderContext, const uint2& frameDim, const uint2& screenTiles); + void prepareReservoirAndPathBuffers(uint32_t elementCount, uint32_t pathCount, bool needFloatingReservoirs); + void prepareDuplicationMapResources(const uint2& frameDim); + void prepareVBufferTextures(const uint2& frameDim); + void prepareReservoirSplattingResources(const uint2& frameDim, uint32_t temporalPathCount); + void prepareMotionVectorTextures(const uint2& frameDim); + + void compileTemporalPasses(const DefineList& defines, const ProgramDesc& baseDesc); + void compileSpatialPasses(const DefineList& defines, const ProgramDesc& baseDesc); + void compileAreaReservoirPasses(const DefineList& defines, const ProgramDesc& baseDesc); + void compileRobustShiftPasses(const DefineList& defines, const ProgramDesc& baseDesc); + void compileReservoirSplattingPasses(const DefineList& defines, const ProgramDesc& baseDesc); + + void temporalResampling(RenderContext* pRenderContext, const ref& pMotionVectors, const ref& pVBuffer); + void temporalDispatchComputeReuseMotionVector(RenderContext* pRenderContext, const ref& pVBuffer, const ref& pMotionVectors); + void temporalDispatchRobustChain(RenderContext* pRenderContext); + void temporalDispatchPopulateFloatingReservoir(RenderContext* pRenderContext, const ref& pVBuffer, const ref& pMotionVectors); + void temporalDispatchSplattingChain(RenderContext* pRenderContext); + void temporalDispatchTraceShiftedPrimaryHits(RenderContext* pRenderContext, const ref& pVBuffer, const ref& pMotionVectors, int temporalPassId); + void temporalDispatchGenerateRetraceWorkload(RenderContext* pRenderContext, const ref& pVBuffer, const ref& pMotionVectors, int temporalPassId); + void temporalDispatchRetrace(RenderContext* pRenderContext, const ref& pVBuffer, const ref& pMotionVectors, int temporalPassId); + void temporalDispatchResamplingPass(RenderContext* pRenderContext, const ref& pVBuffer, const ref& pMotionVectors, int temporalPassId); + + void spatialResampling(RenderContext* pRenderContext, const ref& pVBuffer, const ref& pColorBuffer); + void spatialDispatchTraceShiftedPrimaryHits(RenderContext* pRenderContext, Falcor::ShaderVar shiftVar, uint32_t iteration); + void spatialDispatchGenerateRetraceWorkload(RenderContext* pRenderContext, Falcor::ShaderVar workLoadVar, uint32_t iteration); + void spatialDispatchRetrace(RenderContext* pRenderContext, Falcor::ShaderVar retraceVar, uint32_t iteration); + void spatialDispatchPrecomputeShiftBuffer(RenderContext* pRenderContext, Falcor::ShaderVar precomputeVar, uint32_t iteration); + void spatialDispatchResamplingPass(RenderContext* pRenderContext, Falcor::ShaderVar var, uint32_t iteration, const ref& pColorBuffer); + + Falcor::ShaderVar bindCommonPassVars(RenderContext* pRenderContext, ref pPass, const std::string& cbName, bool bindPathTracer); + Falcor::ShaderVar bindTemporalRobustVars(RenderContext* pRenderContext, ref pPass, std::string cbName, bool bindPathTracer); + Falcor::ShaderVar bindSpatialResamplingVars(RenderContext* pRenderContext, ref pPass, std::string cbName, const ref& pVBuffer, bool bindPathTracer, bool isShiftVBufferPass=false); + Falcor::ShaderVar bindTemporalResamplingVars(RenderContext* pRenderContext, ref pPass, std::string cbName, const ref& pVBuffer, const ref& pMotionVectors, bool bindPathTracer, bool isShiftVBufferPass=false); + Falcor::ShaderVar bindReservoirSplattingVars(RenderContext* pRenderContext, ref pPass, std::string cbName); + + /** Create or release a structured buffer that mirrors `reflectVarName` from the reflect shader. + Honors `mReallocate` (rebuilds the buffer when sizes change). When `keepCondition` is + false, the buffer is released and set to nullptr. + */ + void createOrDestroyBuffer(ref& pBuffer, std::string_view reflectVarName, int requiredElementCount, bool keepCondition = true); + + void createOrDestroyRawBuffer(ref& pBuffer, size_t requiredSize, bool keepCondition = true); + void createTexture2D(ref& pTexture, uint32_t width, uint32_t height, ResourceFormat format, bool keepCondition = true); + + /** Create a 1D texture with random offsets within a unit circle around (0,0). + The texture is RG8Snorm for compactness and has no mip maps. + \param[in] sampleCount Number of samples in the offset texture. + */ + ref createNeighborOffsetTexture(uint32_t sampleCount); + ref createPairedNeighborDeltasTexture(ref pDevice, uint32_t neighborCount, int stdevLevel); + + // -------- Predicate helpers -------- + + bool useCompactRetraceSchedule() const { return mOptions.retraceScheduleType == ReSTIRPT::RetraceScheduleType::Compact; } + + /// True when a custom `mpMVecBuffer` motion vector pass needs to run before temporal reuse. + bool useCustomMotionVectorPass() const + { + return mOptions.enableSpecularMotionVectors + || mOptions.enableDisocclusionMotionVectors; + } + + bool useRobustTemporalAreaReuse() const + { + return mOptions.useAreaReSTIRBackprojection() && mOptions.temporalRobustShift(); + } + + bool useFloatingReservoirs() const + { + if (mOptions.useReservoirSplatting() || !mOptions.useAreaReservoirs) return false; + if (mOptions.useAreaReSTIRBackprojection()) return true; + return false; + } + + bool useReconnectionDataBuffers() const + { + return mOptions.temporalRobustShift() || mOptions.shiftMapping == ReSTIRPT::ShiftMapping::Hybrid; + } + + bool useDuplicationMap() const + { + return mOptions.enableDupMapMCapDownweighting || mOptions.computeTemporalDuplication; + } + + bool useDoFReconnectionForTemporalReuseEffective() const + { + return mOptions.useDoFReconnectionForTemporalReuse + && mpScene->getCamera()->getApertureRadius() > 0.f + && !mOptions.useReservoirSplatting(); + } + + + + ref mpScene; ///< Scene. + ref mpDevice; ///< GPU device. + Options mOptions; ///< Configuration options. + SharedStaticParams mStaticParams; + + ReSTIRPathTracingParams mPathTracerParams; + + DefineList mOwnerDefines; ///< Share defines with inline path tracer + + std::unique_ptr mpPixelDebug; ///< Pixel debug component. + + uint2 mFrameDim = uint2(0); ///< Current frame dimensions. + uint32_t mFrameIndex = 0; ///< Current frame index. + + ref mpReflectTypes; ///< Pass for reflecting types. + + // ReSTIR PT passes. + + ref mpTemporalResampling; + ref mpTemporalRetrace; + + ref mpSpatialResampling; + ref mpSpatialPrecomputeShiftBufferPass; + ref mpSpatialRetrace; + ref mpGenerateRetraceWorkload; + + ref mpFetchDenoiserData; + ref mpFillSampleId; + ref mpComputeDuplicationMap; + + ref mpTemporalTraceShiftedPrimaryHits; + ref mpSpatialTraceShiftedPrimaryHits; + ref mpTemporalPopulateFloatingReservoir; + + ref mpTemporalRobustCreateShiftVBuffer; + ref mpTemporalRobustPathRetrace; + ref mpTemporalRobustProduceWorkload; + ref mpTemporalRobustResampling; + + ref mpComputeReuseMotionVector; + + // reservoir splatting + ref mpTemporalSplatSamples; + ref mpTemporalComputeCellOffsets; + ref mpTemporalSortCellData; + + ref mpPathTracerBlock; ///< Parameter block for the path tracer. + + ref mpReservoirs; ///< Buffer containing the current reservoirs. + ref mpPrevReservoirs; ///< Buffer containing the previous reservoirs. + ref mpFloatingReservoirs; + ref mpReconnectionDataBuffer; ///< Buffer containing the reconnection data for retrace result. + ref mpReconnectionDataOffsets; ///< Buffer containing the reconnection data for retrace result. + ref mpPrecomputedShiftDataBuffer; + ref mpNumKeysBuffer; + ref mpNeighborValidMaskBuffer; + ref mpRetraceWorkItems; ///< Paths starting from primary hits on general materials (all types). + ref mpCounters; ///< Atomic counters (32-bit). + + ref mpRandomVBuffer; ///< Randomized V-buffer for area reservoirs. + ref mpTemporalRandomVBuffer; + // for reservoir splatting + ref mpReservoirSplatInfo; + ref mpReservoirSplatPixel; + ref mpSortedScatteringPixels; + ref mpMappingIndices; + ref mpCellCounters; + ref mpCellOffsets; + ref mpCellOffsetCounter; + + ref mpDebugOutputTexture; ///< Debug output texture. + ref mpNeighborOffsets; ///< 1D texture containing neighbor offsets within a unit circle. + ref mpPairedNeighborDeltas; + ref mpPairedNeighborTextureSizes; + + + // related to sample duplication map + ref mpSampleIDTexture; + ref mpDuplicationMap; + ref mpTemporalCorrelationMap; + ref mpPrevTemporalCorrelationMap; + + ref mpTemporalVBuffer; + + ref mpShiftedPrimaryHitBuffer; + ref mpShiftedPrimaryHitBufferRobust; + ref mpDoFReconnectionShiftBuffer; + ref mpTemporalMISPDFs; + ref mpMVecBuffer; + /// Screen-space delta reflection/transmission motion vectors consumed by `ComputeReuseMotionVector`. + /// Allocated when `enableSpecularMotionVectors` is enabled; the consumer's delta-path guide trace passes write to them. + ref mpDeltaReflectionMotionVectors; + ref mpDeltaTransmissionMotionVectors; + + bool mRecompile = true; ///< Recompile programs on next frame if set to true. + bool mReallocate = true; ///< Reallocate the reservoirs since sizes change + bool mResetTemporalReservoirs = true; ///< Reset temporal reservoir buffer on next frame if set to true. + uint32_t exInitialLightSampleCount = 1; + }; +} diff --git a/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracingParams.slang b/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracingParams.slang new file mode 100644 index 0000000000..29ba93ec34 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/ReSTIRPathTracingParams.slang @@ -0,0 +1,64 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#pragma once +#include "Utils/HostDeviceShared.slangh" + +BEGIN_NAMESPACE_FALCOR + +#ifndef HOST_CODE +import Utils.Math.BitTricks; +#endif + +struct ReSTIRPathTracingParams +{ + inline static const uint2 kScreenTileDim = { 16, 16 }; + inline static const uint2 kScreenTileBits = { 4, 4 }; + + uint2 frameDim; + uint2 screenTiles; + + uint frameCount; + uint seed; + float lodBias; + float specularRoughnessThreshold; + +#ifndef HOST_CODE + uint getReservoirOffset(const uint2 pixel) + { + uint2 tileID = pixel >> kScreenTileBits; + uint stride = kScreenTileDim.x * kScreenTileDim.y; + uint tileIdx = tileID.y * screenTiles.x + tileID.x; + uint tileOffset = tileIdx * stride; + uint tileBits = kScreenTileBits.x + kScreenTileBits.y; + uint pixelIdx = interleave_16bit(pixel) & ((1 << tileBits) - 1); + return tileOffset + pixelIdx; + } +#endif +}; + +END_NAMESPACE_FALCOR diff --git a/Source/Modules/ReSTIRPathTracing/ReflectTypes.cs.slang b/Source/Modules/ReSTIRPathTracing/ReflectTypes.cs.slang new file mode 100644 index 0000000000..c1ccf0bf78 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/ReflectTypes.cs.slang @@ -0,0 +1,53 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ + +/** Dummy compute program for reflection of the structured buffer types. + + Falcor requires reflection data to be able to create a structured buffer, + but the programs are not yet ready to be used when we create the buffers. + Therefore we create this dummy program to make it easier. + */ + +import PathTracer; +import RenderPasses.PathTracer.PathTracer; + +StructuredBuffer pathReservoirs; +StructuredBuffer reconnectionDataBuffer; +ParameterBlock pathTracer; +StructuredBuffer neighborValidMask; +StructuredBuffer reconnectionDataOffsets; +StructuredBuffer shiftedPrimaryHitBuffer; +StructuredBuffer doFReconnectionShiftBuffer; +StructuredBuffer temporalMISPDFs; +StructuredBuffer precomputedShiftBuffer; +StructuredBuffer splatCellOffsetCounter; +StructuredBuffer splatSampleInfo; +StructuredBuffer splatTargetPixel; +StructuredBuffer sortedSplatSourcePixels; + +void main() {} diff --git a/Source/Modules/ReSTIRPathTracing/ResamplingCommon.slang b/Source/Modules/ReSTIRPathTracing/ResamplingCommon.slang new file mode 100644 index 0000000000..79d0fa27a4 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/ResamplingCommon.slang @@ -0,0 +1,769 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Scene.HitInfo; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import ReSTIRPT; +import PathReservoir; +import Scene.RaytracingInline; +import Scene.Camera.CameraData; +import Utils.Sampling.SampleGenerator; + +static const float kDuplicationMaxValue = 288.f; + +[ForceInline] +float2 reprojectPixelForReuse( + uint2 pixel, + float2 motionVector, + uint2 frameDim, + bool useAreaReSTIRBackprojection, + bool useStochasticReprojection, + inout SampleGenerator sg) +{ + float2 prevPixel = pixel + motionVector * frameDim + + (useAreaReSTIRBackprojection + ? 0.f + : (useStochasticReprojection + ? sampleNext2D(sg) + : 0.5)); + if (!useAreaReSTIRBackprojection) + prevPixel = int2(prevPixel); + return prevPixel; +} + +/// Reproject `pixel` for duplication-map index lookup. Always rounds to integer coordinates. +[ForceInline] +int2 reprojectPixelForDupMap( + uint2 pixel, + float2 motionVector, + uint2 frameDim, + bool useAreaReSTIRBackprojection, + bool useStochasticReprojection, + inout SampleGenerator sg) +{ + return int2(pixel + motionVector * frameDim + + (useStochasticReprojection && !useAreaReSTIRBackprojection + ? sampleNext2D(sg) + : 0.5)); +} + +Ray computeRayPredefinedThinLens(uint2 pixel, uint2 frameDim, uint frameCount, bool applyJitter = true) +{ + SampleGenerator sg = SampleGenerator(pixel, frameCount); + float2 u = sampleNext2D(sg); + return gScene.camera.computeRayThinlens(pixel, frameDim, u, applyJitter); +} + +Ray computeRayPredefinedThinLensPrevFrame(uint2 pixel, uint2 frameDim, uint frameCount, bool applyJitter = true) +{ + SampleGenerator sg = SampleGenerator(pixel, frameCount - 1); + float2 u = sampleNext2D(sg); + return gScene.camera.computeRayThinlensPrevFrame(pixel, frameDim, u, applyJitter); +} + +ShadingData getPixelShadingData(ReSTIRPathTracingParams params, Texture2D vbuffer, int2 pixel, out HitInfo hit) +{ + ShadingData sd = {}; + Ray ray = computeRayPredefinedThinLens(pixel, params.frameDim, params.frameCount); + hit = HitInfo(vbuffer[pixel]); + if (hit.isValid()) + { + sd = loadShadingData(hit, ray.origin, ray.dir, false); + } + return sd; +} + +ShadingData getPixelShadingData( ReSTIRPathTracingParams params, StructuredBuffer shiftedPrimaryHitBuffer, float2 pixel, uint shiftedPrimaryHitBufferOffset, float2 subPixelSampleUV, float2 lensSampleUV, out HitInfo hit ) +{ + ShadingData sd = {}; + Ray ray = gScene.camera.computeRayThinlensSubPixelRandom(pixel, params.frameDim, subPixelSampleUV, lensSampleUV); + hit = HitInfo(shiftedPrimaryHitBuffer[shiftedPrimaryHitBufferOffset]); + if (hit.isValid()) + { + sd = loadShadingData(hit, ray.origin, ray.dir, false); + } + return sd; +} + +ShadingData getPixelTemporalShadingData( ReSTIRPathTracingParams params, Texture2D temporalVbuffer, int2 pixel, out HitInfo hit ) +{ + ShadingData sd = {}; + Ray ray = computeRayPredefinedThinLensPrevFrame(pixel, params.frameDim, params.frameCount); + hit = HitInfo(temporalVbuffer[pixel]); + if (hit.isValid()) + { + sd = loadShadingData(hit, ray.origin, ray.dir, true); + } + return sd; +} + +ShadingData getPixelTemporalShadingData( ReSTIRPathTracingParams params, StructuredBuffer shiftedPrimaryHitBuffer, float2 pixel, uint shiftedPrimaryHitBufferOffset, float2 subPixelSampleUV, float2 lensSampleUV, out HitInfo hit ) +{ + ShadingData sd = {}; + // Note: it seems there will be a strange precison error when using previous camera data to compute ray direction + Ray ray = gScene.camera.computeRayThinlensSubPixelRandomPrevFrame(pixel, params.frameDim, subPixelSampleUV, lensSampleUV); + hit = HitInfo(shiftedPrimaryHitBuffer[shiftedPrimaryHitBufferOffset]); + if (hit.isValid()) + { + sd = loadShadingData(hit, ray.origin, ray.dir, true); + } + return sd; +} + +HitInfo getPixelHitInfo(ReSTIRPathTracingParams params, Texture2D vbuffer, int2 pixel, out float3 V) +{ + Ray ray = computeRayPredefinedThinLens(pixel, params.frameDim, params.frameCount); + V = -ray.dir; + return HitInfo(vbuffer[pixel]); +} + +HitInfo getPixelHitInfo(ReSTIRPathTracingParams params, RWTexture2D vbuffer, int2 pixel, out float3 V) +{ + Ray ray = computeRayPredefinedThinLens(pixel, params.frameDim, params.frameCount); + V = -ray.dir; + return HitInfo(vbuffer[pixel]); +} + +HitInfo getPixelHitInfo( ReSTIRPathTracingParams params, StructuredBuffer shiftedPrimaryHitBuffer, float2 pixel, uint shiftedPrimaryHitBufferOffset, float2 subPixelSampleUV, float2 lensSampleUV, out float3 V ) +{ + Ray ray = gScene.camera.computeRayThinlensSubPixelRandom(pixel, params.frameDim, subPixelSampleUV, lensSampleUV); + V = -ray.dir; + return HitInfo(shiftedPrimaryHitBuffer[shiftedPrimaryHitBufferOffset]); +} + +HitInfo getPixelTemporalHitInfo( ReSTIRPathTracingParams params, Texture2D temporalVbuffer, int2 pixel, out float3 V) +{ + Ray ray = computeRayPredefinedThinLensPrevFrame(pixel, params.frameDim, params.frameCount); + V = -ray.dir; + return HitInfo(temporalVbuffer[pixel]); +} + +HitInfo getPixelTemporalHitInfo( ReSTIRPathTracingParams params, StructuredBuffer shiftedPrimaryHitBuffer, float2 pixel, uint shiftedPrimaryHitBufferOffset, float2 subPixelSampleUV, float2 lensSampleUV, out float3 V ) +{ + // Note: it seems there will be a strange precison error when using previous camera data to compute ray direction + Ray ray = gScene.camera.computeRayThinlensSubPixelRandomPrevFrame(pixel, params.frameDim, subPixelSampleUV, lensSampleUV); + V = -ray.dir; + return HitInfo(shiftedPrimaryHitBuffer[shiftedPrimaryHitBufferOffset]); +} + +ShadingData getPixelShadingDataGeneral( ReSTIRPathTracingParams params, Texture2D vbuffer, Texture2D temporalVbuffer, int2 pixel, out HitInfo hit, bool isTemporal) +{ + ShadingData sd = {}; + Ray ray; + if (isTemporal) + ray = computeRayPredefinedThinLensPrevFrame(pixel, params.frameDim, params.frameCount); + else + ray = computeRayPredefinedThinLens(pixel, params.frameDim, params.frameCount); + + if (isTemporal) + hit = HitInfo(temporalVbuffer[pixel]); + else + hit = HitInfo(vbuffer[pixel]); + + if (hit.isValid()) + { + sd = loadShadingData(hit, ray.origin, ray.dir, isTemporal); + } + + return sd; +} + +ShadingData getPixelShadingDataGeneral( ReSTIRPathTracingParams params, StructuredBuffer shiftedPrimaryHitBuffer, + float2 pixel, uint shiftedPrimaryHitBufferOffset, float2 subPixelSampleUV, float2 lensSampleUV, out HitInfo hit, bool isPrevFrame ) +{ + ShadingData sd = {}; + // Note: it seems there will be a strange precison error when using previous camera data to compute ray direction + // If we are retracing in previous frame, we first need to generate the ray using previous camera data + Ray ray; + if (isPrevFrame) + { + ray = gScene.camera.computeRayThinlensSubPixelRandomPrevFrame(pixel, params.frameDim, subPixelSampleUV, lensSampleUV); + } + else + { + ray = gScene.camera.computeRayThinlensSubPixelRandom(pixel, params.frameDim, subPixelSampleUV, lensSampleUV); + } + + hit = HitInfo(shiftedPrimaryHitBuffer[shiftedPrimaryHitBufferOffset]); + + if (hit.isValid()) + { + sd = loadShadingData(hit, ray.origin, ray.dir, isPrevFrame); + } + + return sd; +} + +bool isBorderPixel(ReSTIRPathTracingParams params, int2 pixel) { return any(pixel == 0 || pixel == params.frameDim - 1); } + +bool isValidScreenRegion(ReSTIRPathTracingParams params, int2 pixel) { return all(pixel >= 0 && pixel < params.frameDim); } + +bool isValidScreenRegionFloat(ReSTIRPathTracingParams params, float2 pixel) { return all(pixel >= -1.f && pixel < float2(params.frameDim) + 1.f); } + +HitInfo getPixelTemporalHitInfo( ReSTIRPathTracingParams params, Texture2D temporalVbuffer, int2 pixel) +{ + if (!isValidScreenRegion(params, pixel)) return HitInfo(); + Ray ray = computeRayPredefinedThinLensPrevFrame(pixel, params.frameDim, params.frameCount); + return HitInfo(temporalVbuffer[pixel]); +} + +bool isReprojectedToOccluder(ShadingData centralSd, ShadingData neighborSd) +{ + float3 camPos = gScene.camera.data.posW; + float centralDist = distance(camPos, centralSd.posW); + float neighborDist = distance(camPos, neighborSd.posW); + return centralDist - neighborDist > 0.1f * centralDist; +} + +bool isValidGeometry(ShadingData centralSd, ShadingData neighborSd, bool useSymmetricCondition=false) +{ + float3 camPos = gScene.camera.data.posW; + float centralDist = distance(camPos, centralSd.posW); + float neighborDist = distance(camPos, neighborSd.posW); + return dot(centralSd.frame.N, neighborSd.frame.N) >= 0.5f && abs(centralDist - neighborDist) < 0.1f * max(centralDist, (useSymmetricCondition ? neighborDist : 0.f)); +} + +VertexData getVertexData(HitInfo hit, float3 rayOrigin, float3 rayDir, bool isPrevFrame=false) +{ + uint materialID; + return loadVertexData(hit, rayOrigin, rayDir, materialID, isPrevFrame); +} + +bool isValidPackedHitInfo(PackedHitInfo packed) +{ + return packed.x != 0; +} + +static const uint kNeighborOffsetCount = NEIGHBOR_OFFSET_COUNT; +static const uint kNeighborOffsetMask = kNeighborOffsetCount - 1; + +int2 getNextNeighborPixel(const uint startIndex, Texture1D neighborOffsets, int2 pixel, int i, float gatherRadius) +{ + uint neighborIndex = (startIndex + i) & kNeighborOffsetMask; + int2 neighborPixel = pixel + int2(neighborOffsets[neighborIndex] * gatherRadius); + + return neighborPixel; +} + +// Retrace the primary ray for reused sample at different pixel (domain) +HitInfo retracePrimaryRay( float2 pixel, uint2 frameDim, float2 subPixelSampleUV, float2 lensSampleUV, const bool isPrevFrame) +{ + // Note: it seems there will be a strange precison error when using previous camera data to compute ray direction + + // If we are retracing in previous frame, we first need to generate the ray using previous camera data + Ray ray; + if (isPrevFrame) + { + ray = gScene.camera.computeRayThinlensSubPixelRandomPrevFrame(pixel, frameDim, subPixelSampleUV, lensSampleUV); + } + else + { + ray = gScene.camera.computeRayThinlensSubPixelRandom(pixel, frameDim, subPixelSampleUV, lensSampleUV); + } + + SceneRayQuery sceneRayQuery; + float hitT; + HitInfo hit = sceneRayQuery.traceRay(ray, hitT, RAY_FLAG_NONE, 0xff, isPrevFrame); + + return hit; +} + +float computePrimaryReconnectionJacobian( float2 pixel, uint2 frameDim, HitInfo primaryHit, float3 primaryDir, float2 subPixelSampleUV, const bool isPrevFrame ) +{ + bool isEnvMap = !primaryHit.isValid(); + + // Note: it seems there will be a strange precison error when using previous camera data to compute ray direction + + // If we are retracing in previous frame, we first need to generate the ray using previous camera data + float3 u_World; + if (isPrevFrame) + { + u_World = computeRayThinlensGetPointOnFocalPlane(gScene.camera.prevData, pixel, frameDim, subPixelSampleUV); + } + else + { + u_World = computeRayThinlensGetPointOnFocalPlane(gScene.camera.data, pixel, frameDim, subPixelSampleUV); + } + + uint dummy; + VertexData v = loadVertexData(primaryHit, 0.f, 0.f, dummy, isPrevFrame); + // fetch + float3 x1 = v.posW; + float3 n1 = v.faceNormalW; + + if (isEnvMap) + { + primaryDir = gScene.envMap.toWorld(gScene.envMap.toLocal(primaryDir, !isPrevFrame), isPrevFrame); + x1 = u_World + primaryDir; // arbitrary point along primary dir + n1 = 0.f; + } + + float3 rayDir = normalize(x1 - u_World); + + float3 cameraW = isPrevFrame ? gScene.camera.prevData.cameraW : gScene.camera.data.cameraW; + float3 cameraU = isPrevFrame ? gScene.camera.prevData.cameraU : gScene.camera.data.cameraU; + float3 cameraV = isPrevFrame ? gScene.camera.prevData.cameraV : gScene.camera.data.cameraV; + + // center of lens + float3 x_cam = isPrevFrame ? gScene.camera.data.prevPosW : gScene.camera.data.posW; + float3 cam_dir = normalize(cameraW); + + // Z in camera space (assuming z axis is forward) + float x1_camZ = dot(x1 - x_cam, cam_dir); + + float rayT = x1_camZ / dot(rayDir, cam_dir); + + float focalPlaneZ = length(cameraW); + + // compute Jacobian + float d0Sqr = focalPlaneZ / abs(x1_camZ) * rayT; + float d1Sqr = rayT - d0Sqr; + d0Sqr *= d0Sqr; + d1Sqr *= d1Sqr; + + float cos1 = abs(dot(rayDir, n1)); + float cos0 = abs(dot(rayDir, cam_dir)); + + float JacobianTerm = d0Sqr / cos0; + if (!isEnvMap) JacobianTerm *= cos1 / d1Sqr; + + // inverse disk sampling + return JacobianTerm; +} + +float3 computeRayThinlensGetPointOnFocalPlane(CameraData camData, float2 pixel, uint2 frameDim, float2 u0) +{ + // Sample position in screen space in [0,1] with origin at the top-left corner. + // The camera jitter offsets the sample by +-0.5 pixels from the pixel center. + float2 p = float2(pixel + u0) / frameDim; + float2 ndc = float2(2, -2) * p + float2(-1, 1); + + // Compute the normalized ray direction assuming a thin-lens camera. + float3 ray_origin = camData.posW; + float3 ray_dir = ndc.x * camData.cameraU + ndc.y * camData.cameraV + camData.cameraW; + float3 rayTarget = ray_origin + ray_dir; + + return rayTarget; +} + +float3 solveLensSampleForPrimaryHit( float2 pixel, uint2 frameDim, HitInfo primaryHit, float3 primaryDir, float2 subPixelSampleUV, const bool isPrevFrame ) +{ + bool isEnvMap = !primaryHit.isValid(); + + // Note: it seems there will be a strange precison error when using previous camera data to compute ray direction + + // If we are retracing in previous frame, we first need to generate the ray using previous camera data + float3 u_World; + if (isPrevFrame) + { + u_World = computeRayThinlensGetPointOnFocalPlane(gScene.camera.prevData, pixel, frameDim, subPixelSampleUV); + } + else + { + u_World = computeRayThinlensGetPointOnFocalPlane(gScene.camera.data, pixel, frameDim, subPixelSampleUV); + } + + + float3 x1; + float3 n1; + if (isEnvMap) + { + primaryDir = gScene.envMap.toWorld(gScene.envMap.toLocal(primaryDir, !isPrevFrame), isPrevFrame); + x1 = u_World + primaryDir;// arbitrary point along primary dir + n1 = 0.f; + } + else + { + uint dummy; + VertexData v = loadVertexData(primaryHit, 0.f, 0.f, dummy, isPrevFrame); + // fetch + x1 = v.posW; + n1 = v.faceNormalW; + } + + float3 rayDir = isEnvMap ? primaryDir : normalize(x1 - u_World); + + float3 cameraW = isPrevFrame ? gScene.camera.prevData.cameraW : gScene.camera.data.cameraW; + float3 cameraU = isPrevFrame ? gScene.camera.prevData.cameraU : gScene.camera.data.cameraU; + float3 cameraV = isPrevFrame ? gScene.camera.prevData.cameraV : gScene.camera.data.cameraV; + + // center of lens + float3 x_cam = isPrevFrame ? gScene.camera.data.prevPosW : gScene.camera.data.posW; + float3 cam_dir = normalize(cameraW); + + // Z in camera space (assuming z axis is forward) + float x1_camZ = dot(x1 - x_cam, cam_dir); + + float rayT = x1_camZ / dot(rayDir, cam_dir); + + // ray-lens plane intersection + float3 x0 = x1 - rayT * rayDir; + + // use lens center as (0,0,0) + float3 x0_local_temp = x0 - x_cam; + float x0_local_x = dot(x0_local_temp, normalize(cameraU)) / gScene.camera.data.apertureRadius; + float x0_local_y = dot(x0_local_temp, normalize(cameraV)) / gScene.camera.data.apertureRadius; + + // outside disk + if (x0_local_x * x0_local_x + x0_local_y * x0_local_y >= 1.f) return float3(0.f, 0.f, 0.f); + + // test shadow ray + + float3 shadowRayDir = x1 - x0; + float shadowRayLength = isEnvMap ? 1e10 : length(shadowRayDir); + shadowRayDir = isEnvMap ? shadowRayDir : shadowRayDir / shadowRayLength; + + Ray ray = Ray(x0, shadowRayDir, 0.f, 0.999 * shadowRayLength); + SceneRayQuery sceneRayQuery; + if (!sceneRayQuery.traceVisibilityRay(ray, RAY_FLAG_NONE, 0xff, isPrevFrame)) return float3(0.f, 0.f, 0.f); + + float JacobianTerm = 1.f; + + float focalPlaneZ = length(cameraW); + // compute Jacobian + float d0 = focalPlaneZ / abs(x1_camZ) * rayT; + float cos0 = abs(dot(rayDir, cam_dir)); + JacobianTerm = d0 * d0 / cos0; + + if (!isEnvMap) + { + float d1Sqr = rayT - d0; + d1Sqr *= d1Sqr; + float cos1 = abs(dot(rayDir, n1)); + JacobianTerm *= cos1 / d1Sqr; + } + + // inverse disk sampling + return float3(saturate(inverse_sample_disk(float2(x0_local_x, x0_local_y))), JacobianTerm); +} + +float computeOnePixelDistW(float2 frameDim) +{ + // Pick two neighbor pixels on screen and project to focal plane in world space + float2 p1 = (float2(uint2(frameDim / 2)) + float2(0.5f, 0.5f)) / frameDim; + float2 p2 = (float2(uint2(frameDim / 2 + 1)) + float2(0.5f, 0.5f)) / frameDim; + float2 ndc1 = float2(2, -2) * p1 + float2(-1, 1); + float2 ndc2 = float2(2, -2) * p2 + float2(-1, 1); + float3 p1PosW = gScene.camera.data.posW + ndc1.x * gScene.camera.data.cameraU + ndc1.y * gScene.camera.data.cameraV + gScene.camera.data.cameraW; + float3 p2PosW = gScene.camera.data.posW + ndc2.x * gScene.camera.data.cameraU + ndc2.y * gScene.camera.data.cameraV + gScene.camera.data.cameraW; + float onePixeldistW = distance(p1PosW, p2PosW); + return onePixeldistW; +} + +float computeScreenSpaceCircleOfConfusion(float2 frameDim, float3 primaryHitPosW, bool isEnvMap) +{ + float onePixeldistW = computeOnePixelDistW(frameDim); + // Compute lens diameter on focal plane + float dLens = gScene.camera.data.apertureRadius * 2.0f; + float3 primaryHitPosCamera = mul(gScene.camera.data.viewMat, float4(primaryHitPosW, 1.0f)).xyz; + float dFocal = dLens * (isEnvMap ? 1.f : abs(abs(primaryHitPosCamera.z) - gScene.camera.data.focalDistance) / abs(primaryHitPosCamera.z)); + + // Scale the diameter to screen space ([-1, 1] -> [0, 1]) + return dFocal / onePixeldistW; +} + +inline float fitted_function(float x) +{ + static const float3 coefficents = float3(6.2,5.6,0.2); + return coefficents.x / (x + coefficents.y) + coefficents.z; +} + +float computeConfidenceWeightForTwoShifts(float2 frameDim, float3 primaryPos, bool isEnvMap) +{ + // Note: use sample's primay hit to compute CoC here will cause bias, so we need to use pixel center's hit + float coc = computeScreenSpaceCircleOfConfusion(frameDim, primaryPos, isEnvMap); + + // Scale the random replay confidence weight + float confidenceWeightScaler = coc <= 2.0f ? 1.0f : fitted_function(coc); + confidenceWeightScaler = clamp(confidenceWeightScaler, 0.2f, 1.0f); + return confidenceWeightScaler; +} + +float computeImageSplatJacobian(float3 x0, float3 x1, float3 n1, const bool isPrevFrame) +{ + float3 disp = x1 - x0; + float dist2 = dot(disp, disp); + float3 dir = disp / sqrt(dist2); + float3 cameraForward = normalize(isPrevFrame ? gScene.camera.prevData.cameraW : gScene.camera.data.cameraW); + float cosTheta = dot(cameraForward, dir); + float G = all(n1 == 0.f) ? 1.f : abs(dot(-dir, n1)) / dist2; + return G / (cosTheta * cosTheta * cosTheta); +} + +float3 splatHitToImagePlane(uint2 frameDim, HitInfo primaryHit, float3 primaryDir, float2 lensSampleUV, const bool isPrevFrame, + bool usePackedPosNormal = false, bool isPackedPosNormalValid = true) +{ + bool isEnvMap = !primaryHit.isValid(); + + float3 x_cam = isPrevFrame ? gScene.camera.data.prevPosW : gScene.camera.data.posW; + + // these are unnormalized + float3 cameraU = isPrevFrame ? gScene.camera.prevData.cameraU : gScene.camera.data.cameraU; + float3 cameraV = isPrevFrame ? gScene.camera.prevData.cameraV : gScene.camera.data.cameraV; + float3 cameraW = isPrevFrame ? gScene.camera.prevData.cameraW : gScene.camera.data.cameraW; + float apertureRadius = gScene.camera.data.apertureRadius; // assume aperture radius doesn't change + ShadingFrame camSf; + camSf.T = normalize(cameraU); + camSf.B = normalize(cameraV); + camSf.N = normalize(cameraW); + float2 apertureSample = sample_disk(lensSampleUV); // Sample lies in the unit disk [-1,1]^2 + float2 a = apertureRadius * float2(apertureSample.x, apertureSample.y); // 2D location of lens vertex in camera space + float3 x0 = x_cam + camSf.fromLocal(float3(a, 0)); + + float3 x1, n1; + float3 world_dir; + + if (isEnvMap) + { + world_dir = gScene.envMap.toWorld(gScene.envMap.toLocal(primaryDir, !isPrevFrame), isPrevFrame); + x1 = x0 + world_dir; + n1 = 0.f; + } + else + { + uint dummy; + VertexData v = loadVertexData(primaryHit, 0.f, 0.f, dummy, isPrevFrame); + // fetch + x1 = v.posW; + n1 = v.faceNormalW; + world_dir = normalize(x1 - x0); + } + + // for curve, we need to check intersection + if (!isEnvMap && primaryHit.getType() == HitType::Curve && dot(world_dir, -n1) < 0.f) return float3(0.f); + + // transform to camera space direction + float3 cam_dir = camSf.toLocal(world_dir); + + // reject if dir.z <= 0 + if (cam_dir.z <= 0) return float3(0.f); + + // intersect with the image plane + float d = length(cameraW); // distance to image plane + float2 t = a + d / cam_dir.z * cam_dir.xy; + float2 ndc = float2(t.x / length(cameraU), t.y / length(cameraV)); + + // reject if outside bound + if (any(ndc < -1.f) || any(ndc >= 1.f)) return float3(0.f); + + float2 imageCoord = float2(0.5 * ndc.x + 0.5, -0.5 * ndc.y + 0.5) * frameDim; + + // test shadow ray + float3 shadowRayDir = x1 - x0; + float shadowRayLength = isEnvMap ? 1e10 : length(shadowRayDir); + shadowRayDir = isEnvMap ? shadowRayDir : shadowRayDir / shadowRayLength; + + Ray ray = Ray( x0, shadowRayDir, 0.f, 0.999 * shadowRayLength ); // alternatively, compute shadowRayDir with surface-offseted x1 position + SceneRayQuery sceneRayQuery; + + if (!sceneRayQuery.traceVisibilityRay(ray, RAY_FLAG_NONE, 0xff, isPrevFrame)) return float3(0.f); + + float dstJacobian = computeImageSplatJacobian(x0, x1, n1, isPrevFrame); + float Jacobian; + { + float3 x_cam = !isPrevFrame ? gScene.camera.data.prevPosW : gScene.camera.data.posW; + + // these are unnormalized + float3 cameraU = !isPrevFrame ? gScene.camera.prevData.cameraU : gScene.camera.data.cameraU; + float3 cameraV = !isPrevFrame ? gScene.camera.prevData.cameraV : gScene.camera.data.cameraV; + float3 cameraW = !isPrevFrame ? gScene.camera.prevData.cameraW : gScene.camera.data.cameraW; + // assume aperture radius doesn't change + ShadingFrame camSf; + camSf.T = normalize(cameraU); + camSf.B = normalize(cameraV); + camSf.N = normalize(cameraW); + float3 x0 = x_cam + camSf.fromLocal(float3(a, 0)); + + float3 x1, n1; + + if (isEnvMap) + { + x1 = x0 + world_dir; + n1 = 0.f; + } + else + { + uint dummy; + VertexData v = loadVertexData(primaryHit, 0.f, 0.f, dummy, !isPrevFrame); + // fetch + x1 = v.posW; + n1 = v.faceNormalW; + } + + float sreconnectionJacobian = computeImageSplatJacobian(x0, x1, n1, !isPrevFrame); + Jacobian = dstJacobian / sreconnectionJacobian; + } + + return float3(imageCoord, Jacobian); +} + +bool hasCameraChanged() +{ + return any(gScene.camera.data.prevPosW != gScene.camera.data.posW) || any(gScene.camera.prevData.cameraU != gScene.camera.data.cameraU) || + any(gScene.camera.prevData.cameraV != gScene.camera.data.cameraV) || any(gScene.camera.prevData.cameraW != gScene.camera.data.cameraW) + || gScene.camera.prevData.apertureRadius != gScene.camera.data.apertureRadius; +} + +bool shouldGetSamePath(HitInfo temporalShiftedPrimaryHit, bool isSameSample) +{ + if (!temporalShiftedPrimaryHit.isValid()) return false; + bool cameraChanged = hasCameraChanged(); + const GeometryInstanceData instance = gScene.getGeometryInstance(temporalShiftedPrimaryHit.getInstanceID()); + const float4x4 prevWorldMat = gScene.loadPrevWorldMatrix(instance.globalMatrixID); + const float4x4 curWorldMat = gScene.loadWorldMatrix(instance.globalMatrixID); + bool hasGeometryMoved = any(curWorldMat[0] != prevWorldMat[0]) || any(curWorldMat[1] != prevWorldMat[1]) || + any(curWorldMat[2] != prevWorldMat[2]) || any(curWorldMat[3] != prevWorldMat[3]) || instance.isDynamic(); + return !cameraChanged && !hasGeometryMoved && isSameSample; +} + +ReconnectionData loadReconnectionData( StructuredBuffer reconnectionDataBuffer, StructuredBuffer reconnectionDataOffsets, uint shiftedPrimaryHitAddress, + PathReservoir srcReservoir, HitInfo dstPrimaryHit, float3 dstPrimaryWo ) +{ + ReconnectionData rcData; + if (srcReservoir.pathFlags.rcVertexLength() > 1 && dstPrimaryHit.isValid()) + { + rcData = reconnectionDataBuffer[reconnectionDataOffsets[shiftedPrimaryHitAddress]]; + } + else + { + rcData.pathThroughput = dstPrimaryHit.isValid() ? 1.f : 0.f; + rcData.rcPrevHit = dstPrimaryHit; + rcData.rcPrevWo = dstPrimaryWo; + rcData.rcFootprintFactor = 0.f; // not used + } + return rcData; +} + +HitInfo LoadPixelCenterPrimaryHit( ReSTIRPathTracingParams params, ReSTIRPT restirpt,Texture2D vbuffer, int2 dstPixel, inout float3 dstPrimaryWo, inout float dstPrimaryRayFootprint, inout float3 dstPrimaryHitPosition ) +{ + HitInfo centralPrimaryHit = getPixelHitInfo(params, vbuffer, dstPixel, dstPrimaryWo); + VertexData centralPrimaryVertexData = getVertexData(centralPrimaryHit, gScene.camera.data.posW, -dstPrimaryWo); + dstPrimaryRayFootprint = restirpt.computePrimaryRayFootprint( centralPrimaryVertexData.faceNormalW, gScene.camera.data.posW, + -dstPrimaryWo, centralPrimaryVertexData.posW ); + dstPrimaryHitPosition = centralPrimaryVertexData.posW; + return centralPrimaryHit; +} + +HitInfo LoadShiftedPrimaryHit( ReSTIRPathTracingParams params, ReSTIRPT restirpt, StructuredBuffer shiftedPrimaryHitBuffer, Texture2D vbuffer, + StructuredBuffer doFReconnectionShiftBuffer, bool isPrimaryHitReconnection, + float2 dstPixel, uint shiftedPrimaryHitAddress, PathReservoir srcReservoir, inout float3 dstPrimaryWo, inout float dstPrimaryRayFootprint) +{ + HitInfo dstPrimaryHit; + if (restirpt.kUseAreaReservoirs) + { + float2 lensSample = srcReservoir.lensSample; + if (isPrimaryHitReconnection) + { + lensSample = doFReconnectionShiftBuffer[shiftedPrimaryHitAddress].xy; + } + dstPrimaryHit = getPixelHitInfo( params, shiftedPrimaryHitBuffer, dstPixel, shiftedPrimaryHitAddress, srcReservoir.filmSample, lensSample, dstPrimaryWo ); + } + else + dstPrimaryHit = getPixelHitInfo(params, vbuffer, dstPixel, dstPrimaryWo); + + VertexData dstPrimaryVertexData = getVertexData(dstPrimaryHit, gScene.camera.data.posW, -dstPrimaryWo); + dstPrimaryRayFootprint = restirpt.computePrimaryRayFootprint( dstPrimaryVertexData.faceNormalW, gScene.camera.data.posW, + -dstPrimaryWo, dstPrimaryVertexData.posW ); + return dstPrimaryHit; +} + +// this function is identical as the previous one, except that vbuffer is RWTexture2D +HitInfo LoadShiftedPrimaryHit( ReSTIRPathTracingParams params, ReSTIRPT restirpt, StructuredBuffer shiftedPrimaryHitBuffer, RWTexture2D vbuffer, + StructuredBuffer doFReconnectionShiftBuffer, bool isPrimaryHitReconnection, + float2 dstPixel, uint shiftedPrimaryHitAddress, PathReservoir srcReservoir, inout float3 dstPrimaryWo, inout float dstPrimaryRayFootprint ) +{ + HitInfo dstPrimaryHit; + if (restirpt.kUseAreaReservoirs) + { + float2 lensSample = srcReservoir.lensSample; + if (isPrimaryHitReconnection) + { + lensSample = doFReconnectionShiftBuffer[shiftedPrimaryHitAddress].xy; + } + dstPrimaryHit = getPixelHitInfo(params, shiftedPrimaryHitBuffer, dstPixel, shiftedPrimaryHitAddress, srcReservoir.filmSample, lensSample, dstPrimaryWo); + } + else + dstPrimaryHit = getPixelHitInfo(params, vbuffer, dstPixel, dstPrimaryWo); + + VertexData dstPrimaryVertexData = getVertexData(dstPrimaryHit, gScene.camera.data.posW, -dstPrimaryWo); + dstPrimaryRayFootprint = restirpt.computePrimaryRayFootprint( dstPrimaryVertexData.faceNormalW, gScene.camera.data.posW, + -dstPrimaryWo, dstPrimaryVertexData.posW ); + return dstPrimaryHit; +} + +HitInfo LoadShiftedPrimaryHitPrevFrame( ReSTIRPathTracingParams params, ReSTIRPT restirpt, StructuredBuffer shiftedPrimaryHitBuffer, Texture2D vbuffer, + StructuredBuffer doFReconnectionShiftBuffer, bool isPrimaryHitReconnection, + float2 dstPixel, uint shiftedPrimaryHitAddress, PathReservoir srcReservoir, float2 lensSample, inout float3 dstPrimaryWo, inout float dstPrimaryRayFootprint ) +{ + HitInfo dstPrimaryHit; + if (restirpt.kUseAreaReservoirs) + { + if (isPrimaryHitReconnection) + { + lensSample = doFReconnectionShiftBuffer[shiftedPrimaryHitAddress].xy; + } + dstPrimaryHit = getPixelTemporalHitInfo(params, shiftedPrimaryHitBuffer, dstPixel, shiftedPrimaryHitAddress, srcReservoir.filmSample, lensSample, dstPrimaryWo); + } + else + dstPrimaryHit = getPixelTemporalHitInfo(params, vbuffer, dstPixel, dstPrimaryWo); + + VertexData dstPrimaryVertexData = getVertexData(dstPrimaryHit, gScene.camera.data.prevPosW, -dstPrimaryWo, true); + dstPrimaryRayFootprint = restirpt.computePrimaryRayFootprint( dstPrimaryVertexData.faceNormalW, gScene.camera.data.prevPosW, + -dstPrimaryWo, dstPrimaryVertexData.posW ); + return dstPrimaryHit; +} + +HitInfo LoadShiftedPrimaryHitAssumeAreaReSTIRAndLoadedLensSample( ReSTIRPathTracingParams params, ReSTIRPT restirpt, StructuredBuffer shiftedPrimaryHitBuffer, + int2 dstPixel, uint shiftedPrimaryHitAddress, PathReservoir srcReservoir, inout float3 dstPrimaryWo, inout float dstPrimaryRayFootprint) +{ + HitInfo dstPrimaryHit; + float2 lensSample = srcReservoir.lensSample; + dstPrimaryHit = getPixelHitInfo(params, shiftedPrimaryHitBuffer, dstPixel, shiftedPrimaryHitAddress, srcReservoir.filmSample, lensSample, dstPrimaryWo); + + VertexData dstPrimaryVertexData = getVertexData(dstPrimaryHit, gScene.camera.data.posW, -dstPrimaryWo); + dstPrimaryRayFootprint = restirpt.computePrimaryRayFootprint( dstPrimaryVertexData.faceNormalW, gScene.camera.data.posW, + -dstPrimaryWo, dstPrimaryVertexData.posW ); + return dstPrimaryHit; +} + +void DetermineMCap( inout float MCap, + Texture2D duplicationMap, + int2 prevPixelForDupMap, + bool enableDupMapMCapDownweighting, + float duplicationMCapPower +) +{ + float duplicationRate = 0.f; + if (enableDupMapMCapDownweighting) + duplicationRate = duplicationMap[prevPixelForDupMap] / kDuplicationMaxValue; + + MCap = enableDupMapMCapDownweighting ? lerp(MCap, 1.f, pow(duplicationRate, duplicationMCapPower)) : MCap; +} + diff --git a/Source/Tools/FalcorTest/Tests/Sampling/PointSetsTests.cpp b/Source/Modules/ReSTIRPathTracing/RetraceScheduleDefinition.slangh similarity index 92% rename from Source/Tools/FalcorTest/Tests/Sampling/PointSetsTests.cpp rename to Source/Modules/ReSTIRPathTracing/RetraceScheduleDefinition.slangh index 5057d2fe86..a92adb026f 100644 --- a/Source/Tools/FalcorTest/Tests/Sampling/PointSetsTests.cpp +++ b/Source/Modules/ReSTIRPathTracing/RetraceScheduleDefinition.slangh @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -25,8 +25,5 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -#include "Testing/UnitTest.h" - -namespace Falcor -{ -} // namespace Falcor +#define RETRACE_SCHEDULE_NAIVE 0 +#define RETRACE_SCHEDULE_COMPACT 1 diff --git a/Source/Modules/ReSTIRPathTracing/RetraceWorkQueue.slang b/Source/Modules/ReSTIRPathTracing/RetraceWorkQueue.slang new file mode 100644 index 0000000000..c7a77f4b5c --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/RetraceWorkQueue.slang @@ -0,0 +1,52 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Utils.Attributes; + +/** Queueing resources and helper functions. +*/ +struct RetraceWorkQueue +{ + [root] RWByteAddressBuffer counters; ///< Atomic counters used for queueing operations. + RWByteAddressBuffer retraceWorkItems; ///< List of work items + + /** + \return The unique counter value assigned to each active thread. + */ + uint addCounter(uint value) + { + uint sum = WaveActiveSum(value); + uint offset = WavePrefixSum(value); + uint originalValue; + if (WaveIsFirstLane()) + { + counters.InterlockedAdd(0, sum, originalValue); + } + originalValue = WaveReadLaneFirst(originalValue); // Broadcast to all active threads + return originalValue + offset; + } +} diff --git a/Source/Modules/ReSTIRPathTracing/Shift.slang b/Source/Modules/ReSTIRPathTracing/Shift.slang new file mode 100644 index 0000000000..4d4b15736f --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/Shift.slang @@ -0,0 +1,407 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +__exported import PathReservoir; +__exported import ReSTIRPT; +import Scene.ShadingData; +import LoadShadingDataDynamic; // currently using a copy from InlinePathtracer +import PathTracer; +import Utils.Sampling.TinyUniformSampleGenerator; +import Scene.RaytracingInline; +import Utils.Debug.PixelDebug; +__exported import HybridShiftTraceRays; +import Rendering.Lights.EmissiveLightSampler; +#include "Rendering/Lights/EmissiveLightSamplerType.slangh" + +static const float kShadowRayTMaxRatio = 0.999f; +static const float kShadowOriginAdditionalOffsetRatio = 0.005f; + +bool isJacobianInvalid(float Jacobian) +{ + return Jacobian <= 0.f || isnan(Jacobian) || isinf(Jacobian); +} + +bool isIntegrandInvalid(float3 Integrand) +{ + return all(Integrand == 0.f) || any(isnan(Integrand) || isinf(Integrand) || Integrand < 0.f); +} + +struct PairwiseFactor +{ + float c_sum; + float c_c; +} + +bool shiftAndMergeReservoirTemporal( const ReSTIRPT restirpt, const ReSTIRPathTracingParams params, + inout PathReservoir dstReservoir, PathReservoir srcReservoir, ReconnectionData rcData, float primaryRayFootprint, inout SampleGenerator sg, + float c_c, float splatOrLensShiftJacobian = 1.f, bool temporalUpdateForDynamicScene = false ) +{ + float Jacobian = 1.f; + float3 dstIntegrand = shiftPath(restirpt, params, Jacobian, + srcReservoir, rcData, primaryRayFootprint, false, temporalUpdateForDynamicScene); + + Jacobian *= splatOrLensShiftJacobian; + float srcTerm = PathRISState::toScalar(srcReservoir.integrand) * srcReservoir.M; + float misWeight = srcTerm / (srcTerm + Jacobian * PathRISState::toScalar(dstIntegrand) * c_c); + + bool selected = dstReservoir.merge(dstIntegrand, Jacobian, srcReservoir, sg, misWeight); + + return selected; +} + +// shouldn't use this for canonical samples +bool shiftAndMergeReservoirSpatial( const ReSTIRPT restirpt, const ReSTIRPathTracingParams params, + inout PathReservoir dstReservoir, PathReservoir srcReservoir, ReconnectionData rcData, float primaryRayFootprint, inout SampleGenerator sg, + PairwiseFactor C, float splatOrLensShiftJacobian +#if USE_DECOUPLED_SHADING + , inout float3 outColor +#endif + +) +{ + float Jacobian = 1.f; + float3 dstIntegrand = shiftPath(restirpt, params, Jacobian, + srcReservoir, rcData, primaryRayFootprint, false); + + Jacobian *= splatOrLensShiftJacobian; + float srcTerm = PathRISState::toScalar(srcReservoir.integrand) * (C.c_sum - C.c_c); + float misWeight = srcReservoir.M / C.c_sum * srcTerm / (srcTerm + Jacobian * PathRISState::toScalar(dstIntegrand) * C.c_c); + + bool selected = dstReservoir.merge(dstIntegrand, Jacobian, srcReservoir, sg, misWeight); + +#if USE_DECOUPLED_SHADING + float3 wvec = dstIntegrand * Jacobian * srcReservoir.weight * (isnan(misWeight) ? 0.f : misWeight); + outColor += wvec; +#endif + + return selected; +} + +bool shiftAndMergeReservoirSpatialWithPrecomputedInfo( + inout PathReservoir dstReservoir, PathReservoir srcReservoir, PrecomputedShiftData spData, inout SampleGenerator sg, + PairwiseFactor C, float splatOrLensShiftJacobian +#if USE_DECOUPLED_SHADING + , inout float3 outColor +#endif +) +{ + float Jacobian = 1.f; + float3 dstIntegrand = shiftPathWithPrecomputedInfo(Jacobian, + srcReservoir, spData); + Jacobian *= splatOrLensShiftJacobian; + float srcTerm = PathRISState::toScalar(srcReservoir.integrand) * (C.c_sum - C.c_c); + float misWeight = srcReservoir.M / C.c_sum * srcTerm / (srcTerm + Jacobian * PathRISState::toScalar(dstIntegrand) * C.c_c); + + bool selected = dstReservoir.merge(dstIntegrand, Jacobian, srcReservoir, sg, misWeight); + +#if USE_DECOUPLED_SHADING + float3 wvec = dstIntegrand * Jacobian * srcReservoir.weight * (isnan(misWeight) ? 0.f : misWeight); + outColor += wvec; +#endif + + return selected; +} + +// this function doesn't overwrite external srcReservoir +float3 computeShiftedIntegrand( + const ReSTIRPT restirpt, const ReSTIRPathTracingParams params, inout float Jacobian, + PathReservoir srcReservoir, ReconnectionData rcData, float primaryRayFootprint, bool isPrevFrame = false, bool temporalUpdateForDynamicScene = false) +{ + return shiftPath(restirpt, params, Jacobian, + srcReservoir, rcData, primaryRayFootprint, isPrevFrame, temporalUpdateForDynamicScene); +} + +float3 computeShiftedIntegrandWithPrecomputedInfo( + inout float Jacobian, + PathReservoir srcReservoir, PrecomputedShiftData spData ) +{ + return shiftPathWithPrecomputedInfo(Jacobian, + srcReservoir, spData); +} + +float3 shiftPathWithPrecomputedInfo( inout float Jacobian, + inout PathReservoir srcReservoir, PrecomputedShiftData spData) +{ + // 4+4+12 = 20 bytes + srcReservoir.reconnectionJacobian = spData.reconnectionJacobian; + Jacobian = spData.Jacobian; + return spData.pathThroughput; +} + +float3 shiftPath(const ReSTIRPT restirpt, const ReSTIRPathTracingParams params, inout float Jacobian, + inout PathReservoir srcReservoir, ReconnectionData rcData, float primaryRayFootprint, + bool isPrevFrame = false, bool temporalUpdateForDynamicScene = false) +{ + float3 pathThroughput = (srcReservoir.weight != 0.f) ? rcData.pathThroughput : float3(0.f); + bool isRcVertexLengthOne = srcReservoir.pathFlags.rcVertexLength() == 1; + + // the reconnection vertex exists + if (restirpt.kUseAreaReservoirs && !rcData.rcPrevHit.isValid() && srcReservoir.pathFlags.rcVertexLength() < restirpt.kMaximumRcLength) // return emissive value + { + if (!srcReservoir.pathFlags.isVeryDirectLight()) pathThroughput = 0.f; + else pathThroughput = gPathTracer.envMapSampler.eval(-rcData.rcPrevWo, 0.f, isPrevFrame); + Jacobian = 1.f; + } + + if (!restirpt.kUsePrevFrameSceneData) isPrevFrame = false; + + if (any(pathThroughput > 0.f) && rcData.rcPrevHit.isValid()) + { + ShadingData dstRcPrevSd = loadShadingData(rcData.rcPrevHit, float3(0.f), -rcData.rcPrevWo, isPrevFrame); + + if (temporalUpdateForDynamicScene && restirpt.kTemporalUpdateForDynamicScene && !srcReservoir.pathFlags.isVeryDirectLight()) + { + TraceTemporalUpdate(dstRcPrevSd, srcReservoir, isPrevFrame); + if (all(srcReservoir.reconnectionIncidentRadiance == 0.f)) pathThroughput = 0.f; + } + + float reconnectionJacobian = 1.f; + pathThroughput *= shiftPathReconnection( restirpt, params, primaryRayFootprint, rcData.rcFootprintFactor, reconnectionJacobian, dstRcPrevSd, srcReservoir, isPrevFrame); + Jacobian *= reconnectionJacobian; + } + + return pathThroughput; +} + +[ForceInline] +void sampleHybridShiftThresholds(const ReSTIRPT restirpt, uint initRandomSeed, out float distanceThreshold, out float roughnessThreshold) +{ + SampleGenerator thresholdSg = SampleGenerator(initRandomSeed); + distanceThreshold = (restirpt.settings.nearFieldDistanceSigma == 0.f) + ? restirpt.settings.nearFieldDistanceThreshold + : PathRISState::gaussrand(restirpt.settings.nearFieldDistanceThreshold, restirpt.settings.nearFieldDistanceSigma, thresholdSg); + roughnessThreshold = (restirpt.settings.specularRoughnessSigma == 0.f) + ? restirpt.settings.specularRoughnessThreshold + : PathRISState::gaussrand(restirpt.settings.specularRoughnessThreshold, restirpt.settings.specularRoughnessSigma, thresholdSg); +} + +[ForceInline] +void recomputeLightPdfForEmissiveRcVertex(inout float lightPdf, HitInfo rcVertexHit, ShadingData rcVertexSd, ShadingData dstPrimarySd, const IMaterialInstance dstMI, bool isCurveHit) +{ +#if _EMISSIVE_LIGHT_SAMPLER_TYPE == EMISSIVE_LIGHT_SAMPLER_LIGHT_BVH + TriangleLightHit hit; + TriangleHit triangleHit = rcVertexHit.getTriangleHit(); + hit.triangleIndex = gScene.lightCollection.getTriangleIndex(triangleHit.instanceID, triangleHit.primitiveIndex); + hit.posW = rcVertexSd.posW; + hit.normalW = rcVertexSd.getOrientedFaceNormal(); + + uint lobeTypes = dstMI.getLobeTypes(dstPrimarySd); + bool sampleUpperHemisphere = isCurveHit || (lobeTypes & (uint)LobeType::NonDeltaReflection) != 0; + bool sampleLowerHemisphere = isCurveHit || (lobeTypes & (uint)LobeType::NonDeltaTransmission) != 0; + lightPdf = gPathTracer.getEmissiveSelectionProbability() * gPathTracer.emissiveSampler.evalPdf( dstPrimarySd.posW, dstPrimarySd.frame.N, sampleUpperHemisphere && !sampleLowerHemisphere, hit ); +#endif +} + +/** + Algorithmic references: + - Mixed PSS / solid-angle Jacobian (See the comments in Line 333 PathReservoir.slang for more details): + The reservoir stores `reconnectionJacobian = G(x_{k-1} -> x_k)` (geometry factor at the source pixel) + and we accumulate `Jacobian *= dstGeometryTerm / srcReservoir.reconnectionJacobian` + at the end. No scatter-PDF ratios appear because the integrand around the reconnection vertex + is parameterized in solid-angle. + - Dual ray-footprint reconnection criteria: Lin et al. 2026 (ReSTIR PT Enhanced, Eq. 5). + min( 1/(p_scatter_{k-1} * G(x_{k-1}->x_k)), 1/(p_scatter_k * G(x_k->x_{k-1})) ) + >= (c/100) * primaryRayFootprint + The ratio `(c/100)` is rolled into `restirpt.settings.nearFieldDistanceThreshold` + on the host (see `ReSTIRPathTracing.cpp`, `setShaderData`). + - Single-vertex roughness threshold at x_{k-1}: See ReSTIR PT Enhanced Section 4.2 + + Returns the shifted integrand at the destination, scales `Jacobian` by the shift Jacobian, and + overwrites `srcReservoir.reconnectionJacobian` with the destination geometry factor (used by + later GRIS rounds to chain shifts). +*/ +float3 shiftPathReconnection(const ReSTIRPT restirpt, const ReSTIRPathTracingParams params, float primaryRayFootprint, float rcFootprintFactor, + inout float Jacobian, ShadingData dstPrimarySd, inout PathReservoir srcReservoir, bool isPrevFrame) +{ + // dummy sg (stochastic BSDF is not supported yet) + var sg = TinyUniformSampleGenerator(1); + + // ---- Unpack reservoir + flag decoding ---- + const int rcVertexLength = srcReservoir.pathFlags.rcVertexLength(); + const HitInfo rcVertexHit = unpackReservoirHitInfo(srcReservoir.reconnectionHit); + const float3 rcVertexIrradiance = srcReservoir.reconnectionIncidentRadiance; + const float3 rcVertexWi = srcReservoir.reconnectionWi; + bool rcVertexHitExists = rcVertexHit.isValid(); + const float3 reconnectionJacobian = srcReservoir.reconnectionJacobian; + + const bool isTransmission1 = srcReservoir.pathFlags.decodeIsTransmissionEvent(true); + const uint componentType1 = srcReservoir.pathFlags.decodeBSDFComponent(true); + + const bool isDelta1 = srcReservoir.pathFlags.decodeIsDeltaEvent(true); + const bool isDelta2 = srcReservoir.pathFlags.decodeIsDeltaEvent(false); + // delta bounce before/after rcVertex (if isRcVertexNEE, deltaAfterRc won't be set) + if (isDelta1 || isDelta2) return 0.f; + + const bool isRcVertexConnectedtoLight = srcReservoir.pathFlags.pathLength() == rcVertexLength; + const bool isRcVertexEmissive = srcReservoir.pathFlags.pathLength() + 1 == rcVertexLength; + const bool isLightPathNEE = srcReservoir.pathFlags.lastVertexNEE(); + const bool isRcVertexNEE = isRcVertexConnectedtoLight && isLightPathNEE; + + bool isCurveHit = dstPrimarySd.curveRadius > 0.f; + if (isCurveHit) dstPrimarySd.posW = dstPrimarySd.posW - dstPrimarySd.frame.N * dstPrimarySd.curveRadius; + else dstPrimarySd.posW = dstPrimarySd.computeRayOrigin(!isTransmission1); // a simple way to set ray offset. + //Note that this will disallow SpecularReflectionTransmission to switch side (reflection<->refraction) + //during reuse as otherwise self-shadowing will happen + + const IMaterialInstance dstMI = gScene.materials.getMaterialInstance( dstPrimarySd, ExplicitLodTextureSampler(rcVertexLength == 1 ? 0.f : params.lodBias), + getMaterialInstanceHints(dstPrimarySd.curveRadius == 0.f, rcVertexLength == 1)); + + if (restirpt.kUseAreaReservoirs && srcReservoir.pathFlags.isVeryDirectLight()) + { + Jacobian = 1.f; + return dstMI.getProperties(dstPrimarySd).emission; + } + // cannot support reuse between these too cases + if (dstMI.supportBSDFIndexing() != srcReservoir.pathFlags.decodeSupportBSDFIndexingBeforeRcVertex()) return 0.f; + + // ---- Resolve the rc-vertex shading data + connection vector ---- + ShadingData rcVertexSd = {}; + float3 shiftedDisp = 0.f; + float shifted_dist2 = 0.f; + + // direction point from dst primary hit point to reconnection vertex + float3 dstConnectionV; + rcVertexSd = loadShadingData( rcVertexHit, dstPrimarySd.posW, float3(0.f), isPrevFrame ); + bool isPointLight = false; + if (!rcVertexHitExists && isRcVertexEmissive && isLightPathNEE && (rcVertexHit.data.x == 1 || rcVertexHit.data.x == 2)) + { + rcVertexSd.posW = rcVertexWi; +#if !HIT_INFO_USE_COMPRESSION + rcVertexSd.faceN = asfloat(rcVertexHit.data.yzw); +#endif + rcVertexHitExists = true; + isPointLight = rcVertexHit.data.x == 1; + } + shiftedDisp = rcVertexSd.posW - dstPrimarySd.posW; + shifted_dist2 = dot(shiftedDisp, shiftedDisp); + + // ---- Determine hybrid-shift thresholds ---- + float distanceThreshold, roughnessThreshold; + sampleHybridShiftThresholds(restirpt, srcReservoir.initRandomSeed, distanceThreshold, roughnessThreshold); + + dstConnectionV = rcVertexHitExists ? shiftedDisp / sqrt(shifted_dist2) : rcVertexWi; + float shifted_cosine = isPointLight ? 1.f : abs(dot(rcVertexSd.faceN, -dstConnectionV)); + + // Destination geometry factor G(y_{k-1} -> x_k). Used both as the numerator of the shift Jacobian + // (committed at the bottom of this function) and inside the footprint tests below. + float dstGeometryTerm = shifted_cosine / shifted_dist2; + float dstJacobian = rcVertexHitExists ? dstGeometryTerm : 1.f; + + float3 dstIntegrand = rcVertexIrradiance; + + // assuming bsdf sampling + // setup lobe mask to query only sampled components + // does not matter for non-BSDF indexing material + dstPrimarySd.mtl.setBSDFIndex(isRcVertexEmissive && isLightPathNEE ? -1 : componentType1); + float3 dstF1 = dstMI.eval(dstPrimarySd, dstConnectionV, sg); + dstPrimarySd.mtl.setBSDFIndex(-1); + float dstPDF1BSDF = dstMI.evalPdf(dstPrimarySd, dstConnectionV); + + // lightPdf stored in area measure if rcVertexHitExists + float lightPDF1 = srcReservoir.lightPdf / dstJacobian; + + // If we use a position-dependent NEE sampler, the NEE PDF must be re-evaluated at the dst point. + if (rcVertexHit.isValid() && isRcVertexEmissive) + recomputeLightPdfForEmissiveRcVertex(lightPDF1, rcVertexHit, rcVertexSd, dstPrimarySd, dstMI, isCurveHit); + dstIntegrand *= dstF1; // f_{k-1} + + float inverseGeometryTerm = shifted_dist2 / abs(dot(dstPrimarySd.frame.N, -dstConnectionV)); + float rayFootprint = 1.f / (dstPDF1BSDF * dstGeometryTerm); + + // Invertibility test: `rcFootprintFactor` which stores G(x_{k-1}->x_{k-2})) / p_scatter_{k-1} cannot satisfy inverse footprint threshold + // otherwise, x_{k-1} could have been a rcVertex. + Jacobian *= rcVertexLength > 1 && rcFootprintFactor > 0.f && rcFootprintFactor / dstPDF1BSDF >= primaryRayFootprint * distanceThreshold ? 0.f : 1.f; + + // Single-vertex roughness threshold at x_{k-1} + // Skipped for emissive-BSDF-sampled rc vertices on materials without a simple roughness proxy. + bool violateRoughnessCondition = !(isRcVertexEmissive && !isLightPathNEE && !dstMI.supportSimpleRoughnessThreshold()) && + !dstMI.isSampleRough(dstPrimarySd, componentType1, dstPDF1BSDF, roughnessThreshold); + // Ray footprint threshold for x_k + bool violateDistanceCondition = rcVertexHitExists && rayFootprint < primaryRayFootprint * distanceThreshold; + + dstIntegrand *= uint(!rcVertexHitExists || !isRcVertexEmissive || rcVertexSd.frontFacing); + + int initialLightSampleCount = restirpt.computeInitialLightSampleCount(restirpt.initialLightSampleCount, rcVertexLength); + int NEELightSampleCount = kUseLightTileRIS ? initialLightSampleCount : 1; + float MISForEmissive = (!kUseMIS || srcReservoir.pathFlags.lightType() == (uint)LightType::Analytic) ? 1.f : evalMIS( (isLightPathNEE ? NEELightSampleCount : 1), (isLightPathNEE ? lightPDF1 : dstPDF1BSDF), + (isLightPathNEE ? 1 : NEELightSampleCount), (isLightPathNEE ? dstPDF1BSDF : lightPDF1) ); + dstIntegrand *= isRcVertexEmissive ? MISForEmissive : 1.f; // MIS weight to sample emissive x_k + + const bool needSecondPart = rcVertexHitExists && !isRcVertexEmissive; + const IMaterialInstance rcMI = gScene.materials.getMaterialInstance( rcVertexSd, ExplicitLodTextureSampler(params.lodBias), getMaterialInstanceHints(rcVertexHit, false)); + const uint componentType2 = srcReservoir.pathFlags.decodeBSDFComponent(false); + + // does not matter for non-BSDF indexing material + rcVertexSd.mtl.setBSDFIndex(isRcVertexNEE ? -1 : componentType2); + float3 dstF2 = rcMI.eval(rcVertexSd, rcVertexWi, sg); + rcVertexSd.mtl.setBSDFIndex(-1); + float dstPDF2BSDF = rcMI.evalPdf(rcVertexSd, rcVertexWi); + + initialLightSampleCount = restirpt.computeInitialLightSampleCount(restirpt.initialLightSampleCount, rcVertexLength+1); + NEELightSampleCount = kUseLightTileRIS ? initialLightSampleCount : 1; + + float MISForLightVertex = evalMIS( (isRcVertexNEE ? NEELightSampleCount : 1), (isRcVertexNEE ? srcReservoir.lightPdf : dstPDF2BSDF), + (isRcVertexNEE ? 1 : NEELightSampleCount), (isRcVertexNEE ? dstPDF2BSDF : srcReservoir.lightPdf) ); + + // Inverse Ray footprint threshold for x_k + // For emissive rc vertices reached by BSDF sampling (if it's NEE sampled it's a force reconnection) we use a diffuse-pseudo-PDF as a proxy. + // When the test fails the Jacobian is zeroed -- the shift would not be invertible from the destination's side. + Jacobian *= ReSTIRPT::ShiftMapping(restirpt.kShiftMapping) == ReSTIRPT::ShiftMapping::Hybrid && + !(isRcVertexEmissive && isLightPathNEE) && ((violateRoughnessCondition || violateDistanceCondition) || !(isRcVertexEmissive && !rcVertexHitExists) && + (inverseGeometryTerm / (isRcVertexEmissive && !isLightPathNEE ? restirpt.getDiffusePseudoPdf() : dstPDF2BSDF) < primaryRayFootprint * distanceThreshold)) ? 0.f : 1.f; + + // MIS weight to sample emissive x_{k+1} + dstIntegrand *= (kUseMIS && isRcVertexConnectedtoLight && srcReservoir.pathFlags.lightType() != (uint)LightType::Analytic) ? MISForLightVertex : 1.f; + dstIntegrand *= needSecondPart ? dstF2 : 1.f; // f_k + + // Mixed-PSS Jacobian: G(y_{k-1} -> x_k) / G(x_{k-1} -> x_k) for the shift. + Jacobian *= dstJacobian / srcReservoir.reconnectionJacobian; + + float3 shadowDestination = rcVertexHitExists ? rcVertexSd.posW : rcVertexWi; + bool isVisible = evalSegmentVisibility(isPrevFrame, dstPrimarySd.posW, shadowDestination, !rcVertexHitExists, restirpt.settings.additionalShadowRayOffset); + bool isInvalidJacobianOrIntegrand = isJacobianInvalid(Jacobian) || isIntegrandInvalid(dstIntegrand); + bool invalidShift = !isVisible || isInvalidJacobianOrIntegrand; + + Jacobian = invalidShift ? 0.f : Jacobian; + dstIntegrand = invalidShift ? 0.f : dstIntegrand; + + srcReservoir.reconnectionJacobian = dstJacobian; + + return dstIntegrand; +} + +/// Evaluates the visibility term to the reconnection vertex from the previous vertex. +bool evalSegmentVisibility(bool isPrevFrame,const float3 sourceVertex, const float3 targetVertex, bool isDirection, bool additionalOffset) +{ + float3 dir = isDirection ? targetVertex : targetVertex - sourceVertex; + float shadowRayLength = length(dir); + float tMax = isDirection ? kRayTMax : kShadowRayTMaxRatio * shadowRayLength; // alternatively, compute tMax based on offseted rcVertex position + Ray ray = Ray(sourceVertex, dir / shadowRayLength, (additionalOffset ? kShadowOriginAdditionalOffsetRatio : 0.f) * shadowRayLength, tMax); + + SceneRayQuery sceneRayQuery; + return sceneRayQuery.traceVisibilityRay(ray, RAY_FLAG_NONE, 0xff, isPrevFrame); +} diff --git a/Source/Modules/ReSTIRPathTracing/SpatialPathRetrace.cs.slang b/Source/Modules/ReSTIRPathTracing/SpatialPathRetrace.cs.slang new file mode 100644 index 0000000000..2783318206 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/SpatialPathRetrace.cs.slang @@ -0,0 +1,349 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import RetraceWorkQueue; +import ResamplingCommon; +import PairedNeighborTransform; +#include "RetraceScheduleDefinition.slangh" + + +struct SpatialPathRetrace +{ + static const uint kNeighborOffsetCount = NEIGHBOR_OFFSET_COUNT; + static const uint kNeighborOffsetMask = kNeighborOffsetCount - 1; + + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + Texture1D neighborOffsets; + + StructuredBuffer shiftedPrimaryHitBuffer; + + + StructuredBuffer prevReservoirs; + RWStructuredBuffer reconnectionDataBuffer; + RWStructuredBuffer reconnectionDataOffsets; + StructuredBuffer doFReconnectionShiftBuffer; + + StructuredBuffer keyValueBuffer; + StructuredBuffer numKeysBuffer; + RWStructuredBuffer neighborValidMask; + + int gSpatialRoundId; + int gNeighborCount; + float gGatherRadius; + bool resetTemporalReservoirs; + + bool gUsePairedNeighbors; + + bool areaReservoirUseNeighborRejection; + + RetraceWorkQueue queues; + + int getNeighborCount() + { + return gNeighborCount; + } + + // compaction +#if RETRACE_SCHEDULE_TYPE == RETRACE_SCHEDULE_COMPACT + void DoPass(const uint linearIndex) + { + // read number of paths + uint totalPathCount = queues.counters.Load(0); + if (linearIndex >= totalPathCount) return; + uint encodedRetraceWorkItem = queues.retraceWorkItems.Load(linearIndex * 4); + int2 pixel = int2(encodedRetraceWorkItem & 0xfff, encodedRetraceWorkItem >> 12 & 0xfff); + uint retraceIndex = encodedRetraceWorkItem >> 24 & 0xff; + + printSetPixel(pixel); + logSetPixel(pixel); + + + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + const uint centralOffset = params.getReservoirOffset(pixel); + + const uint startIndex = sampleNext1D(sg) * kNeighborOffsetCount; + float spatialRadius = gGatherRadius; + + int2 neighborPixel; + var sharedSg = TinyUniformSampleGenerator(params.frameDim + 1, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + PairedNeighborTransform transform = PairedNeighborTransform(sharedSg, params.frameDim.x, params.frameDim.y); + + if (gUsePairedNeighbors) + { + int dx, dy; + transform.readTransformedDelta(retraceIndex / 2, pixel.x, pixel.y, dx, dy); + neighborPixel = pixel + int2(dx, dy); + } + else + { + neighborPixel = getNextNeighborPixel(startIndex, neighborOffsets, pixel, retraceIndex / 2, spatialRadius); + } + const uint neighborOffset = params.getReservoirOffset(neighborPixel); + + PathReservoir reservoir = prevReservoirs[retraceIndex%2 ? neighborOffset : centralOffset]; + + uint neighborCount = getNeighborCount(); + + float doFReconnectionShiftProbability = restirpt.doFReconnectionShiftProbability; + if (restirpt.deriveDoFReconnectionShiftProbabilityFromCoC) + { + float3 centralPrimaryWo; + HitInfo centralPrimaryHit = getPixelHitInfo(params, vbuffer, pixel, centralPrimaryWo); + VertexData centralPrimaryVertexData = getVertexData(centralPrimaryHit, gScene.camera.data.posW, -centralPrimaryWo); + doFReconnectionShiftProbability = + 1.f - computeConfidenceWeightForTwoShifts(params.frameDim, centralPrimaryVertexData.posW, !centralPrimaryHit.isValid()); + } + + uint address = gUsePairedNeighbors ? neighborCount * centralOffset + (retraceIndex / 2) : 2 * neighborCount * centralOffset + retraceIndex; + + int doFReconnectionShiftCount = restirpt.useDoFReconnectionShift ? round(doFReconnectionShiftProbability * neighborCount) : 0; + if (restirpt.kUseAreaReservoirs && int(retraceIndex / 2) < doFReconnectionShiftCount) reservoir.setLensSample(doFReconnectionShiftBuffer[address].xy); + + HitInfo primaryHit; + ShadingData primarySd; + if (restirpt.kUseAreaReservoirs) + primarySd = getPixelShadingData( params, shiftedPrimaryHitBuffer, retraceIndex % 2 ? pixel : neighborPixel, + address, reservoir.filmSample, reservoir.lensSample, primaryHit); + else + primarySd = getPixelShadingData(params, vbuffer, retraceIndex % 2 ? pixel : neighborPixel, primaryHit); + + HitInfo rcPrevHit = HitInfo(); + float3 rcPrevWo = float3(0); + float rcFootprintFactor = 0.f; + float3 pathThroughput = TraceReplayedPath( primaryHit, primarySd, reservoir.pathFlags, + reservoir.initRandomSeed, rcPrevHit, rcPrevWo, rcFootprintFactor, false ); + + reconnectionDataOffsets[address] = linearIndex; + reconnectionDataBuffer[linearIndex] = ReconnectionData(rcPrevHit, rcPrevWo, pathThroughput, rcFootprintFactor); + } +#else + void DoPass(const uint2 pixel) + { + + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = prevReservoirs[centralOffset]; + + HitInfo centralPrimaryHit; + ShadingData centralPrimarySd = getPixelShadingData(params, vbuffer, pixel, centralPrimaryHit); + if (!centralPrimaryHit.isValid() && !restirpt.kUseAreaReservoirs) return; + + int neighborCount = getNeighborCount(); + + const uint startIndex = sampleNext1D(sg) * kNeighborOffsetCount; + float spatialRadius = gGatherRadius; + + float doFReconnectionShiftProbability = restirpt.doFReconnectionShiftProbability; + if (restirpt.deriveDoFReconnectionShiftProbabilityFromCoC) + { + doFReconnectionShiftProbability = + 1.f - computeConfidenceWeightForTwoShifts(params.frameDim, centralPrimarySd.posW, !centralPrimaryHit.isValid()); + } + + int doFReconnectionShiftCount = restirpt.useDoFReconnectionShift ? round(doFReconnectionShiftProbability * neighborCount) : 0; + var sharedSg = TinyUniformSampleGenerator(params.frameDim + 1, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + PairedNeighborTransform transform = PairedNeighborTransform(sharedSg, params.frameDim.x, params.frameDim.y); + + float cSum = centralReservoir.M; + + for (int i = 0; i < neighborCount; ++i) + { + int2 neighborPixel; + + if (gUsePairedNeighbors) + { + int dx, dy; + transform.readTransformedDelta(i, pixel.x, pixel.y, dx, dy); + neighborPixel = pixel + int2(dx, dy); + } + else + { + neighborPixel = getNextNeighborPixel(startIndex, neighborOffsets, pixel, i, spatialRadius); + } + + if (!isValidScreenRegion(params, neighborPixel)) + { + neighborValidMask[centralOffset].setValid(i, false); + continue; + } + + PathReservoir neighborReservoir = prevReservoirs[params.getReservoirOffset(neighborPixel)]; + + HitInfo neighborPrimaryHit; + ShadingData neighborPrimarySd = getPixelShadingData(params, vbuffer, neighborPixel, neighborPrimaryHit); + + if (restirpt.kUseAreaReservoirs && areaReservoirUseNeighborRejection) + { + if (centralPrimaryHit.isValid() != neighborPrimaryHit.isValid() || + centralPrimaryHit.isValid() && !isValidGeometry(centralPrimarySd, neighborPrimarySd, gUsePairedNeighbors)) + neighborValidMask[centralOffset].setValid(i, false); + continue; + } + + if (!restirpt.kUseAreaReservoirs && + (!neighborPrimaryHit.isValid() || !isValidGeometry(centralPrimarySd, neighborPrimarySd, gUsePairedNeighbors))) + { + neighborValidMask[centralOffset].setValid(i, false); + continue; + } + + if (restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && restirpt.deriveDoFReconnectionShiftProbabilityFromCoC && gUsePairedNeighbors) + { + // check if neighbor also uses the same shift + float neighborDoFReconnectionShiftProbability = + 1.f - computeConfidenceWeightForTwoShifts(params.frameDim, neighborPrimarySd.posW, !neighborPrimaryHit.isValid()); + int neighborDoFReconnectionShiftCount = round(neighborDoFReconnectionShiftProbability * neighborCount); + if ((i < doFReconnectionShiftCount) != (i < neighborDoFReconnectionShiftCount)) + { + neighborValidMask[centralOffset].setValid(i, false); + continue; + } + } + + cSum += neighborReservoir.M; + + neighborValidMask[centralOffset].setValid(i, true); + neighborValidMask[centralOffset].setCSum(cSum); + + if (ReSTIRPT::ShiftMapping(restirpt.kShiftMapping) == ReSTIRPT::ShiftMapping::Hybrid) + { + if (!gUsePairedNeighbors && centralReservoir.pathFlags.rcVertexLength() > 1) + { + HitInfo rcPrevHit = HitInfo(); + float3 rcPrevWo = float3(0.f); + float3 pathThroughput = 1.f; + float tempFootprint = 0.f; + + if (restirpt.kUseAreaReservoirs) + { + float2 lensSample = centralReservoir.lensSample; + if (restirpt.useDoFReconnectionShift && i < doFReconnectionShiftCount) + { + lensSample = doFReconnectionShiftBuffer[2 * neighborCount * centralOffset + 2 * i].xy; + } + + // this is neighborShiftedPrimarySd + neighborPrimarySd = getPixelShadingData( params, shiftedPrimaryHitBuffer, neighborPixel, 2 * neighborCount * centralOffset + 2 * i, + centralReservoir.filmSample, lensSample, neighborPrimaryHit); + } + + pathThroughput = TraceReplayedPath( neighborPrimaryHit, neighborPrimarySd, centralReservoir.pathFlags, + centralReservoir.initRandomSeed, rcPrevHit, rcPrevWo, tempFootprint, false ); + reconnectionDataOffsets[2 * neighborCount * centralOffset + 2 * i] = 2 * neighborCount * centralOffset + 2 * i; + reconnectionDataBuffer[2 * neighborCount * centralOffset + 2 * i] = ReconnectionData(rcPrevHit, rcPrevWo, pathThroughput, tempFootprint); + } + + if (neighborReservoir.pathFlags.rcVertexLength() > 1) + { + uint address = gUsePairedNeighbors ? neighborCount * centralOffset + i : 2 * neighborCount * centralOffset + 2 * i + 1; + + HitInfo rcPrevHit = HitInfo(); + float3 rcPrevWo = float3(0.f); + float3 pathThroughput = 1.f; + float tempFootprint = 0.f; + + if (restirpt.kUseAreaReservoirs) + { + float2 lensSample = neighborReservoir.lensSample; + if (restirpt.useDoFReconnectionShift && i < doFReconnectionShiftCount) + { + lensSample = doFReconnectionShiftBuffer[address].xy; + } + + HitInfo centralShiftedPrimaryHit; + ShadingData centralShiftedPrimarySd = getPixelShadingData( params, shiftedPrimaryHitBuffer, pixel, address, + neighborReservoir.filmSample, lensSample, centralShiftedPrimaryHit ); + pathThroughput = TraceReplayedPath( centralShiftedPrimaryHit, centralShiftedPrimarySd, neighborReservoir.pathFlags, + neighborReservoir.initRandomSeed, rcPrevHit, rcPrevWo, tempFootprint, false ); + } + else + { + pathThroughput = TraceReplayedPath( centralPrimaryHit, centralPrimarySd, neighborReservoir.pathFlags, + neighborReservoir.initRandomSeed, rcPrevHit, rcPrevWo, tempFootprint, false ); + } + + reconnectionDataOffsets[address] = address; + reconnectionDataBuffer[address] = ReconnectionData(rcPrevHit, rcPrevWo, pathThroughput, tempFootprint); + } + } + } + } +#endif +#if RETRACE_SCHEDULE_TYPE != RETRACE_SCHEDULE_NAIVE + void execute(const uint index) + { + if (index >= 2 * gNeighborCount * params.frameDim.x * params.frameDim.y) return; + DoPass(index); + } +#else + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + } +#endif +}; + +cbuffer CB +{ + SpatialPathRetrace gSpatialPathRetrace; +} + +#if RETRACE_SCHEDULE_TYPE != RETRACE_SCHEDULE_NAIVE +[numthreads(256, 1, 1)] +#else +[numthreads(16, 16, 1)] +#endif +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ +#if RETRACE_SCHEDULE_TYPE != RETRACE_SCHEDULE_NAIVE + gSpatialPathRetrace.execute(dispatchThreadId.x); +#else + gSpatialPathRetrace.execute(dispatchThreadId.xy); +#endif +} diff --git a/Source/Modules/ReSTIRPathTracing/SpatialPrecomputeShiftBuffer.cs.slang b/Source/Modules/ReSTIRPathTracing/SpatialPrecomputeShiftBuffer.cs.slang new file mode 100644 index 0000000000..5f4fea453d --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/SpatialPrecomputeShiftBuffer.cs.slang @@ -0,0 +1,179 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import ResamplingCommon; +import PairedNeighborTransform; + +struct Pass +{ + static const uint kNeighborOffsetCount = NEIGHBOR_OFFSET_COUNT; + static const uint kNeighborOffsetMask = kNeighborOffsetCount - 1; + + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + RWTexture2D sampleVBuffer; + Texture1D neighborOffsets; + + StructuredBuffer shiftedPrimaryHitBuffer; + + RWStructuredBuffer reservoirs; + StructuredBuffer prevReservoirs; + StructuredBuffer reconnectionDataBuffer; + StructuredBuffer reconnectionDataOffsets; + StructuredBuffer doFReconnectionShiftBuffer; + + StructuredBuffer neighborValidMask; + RWTexture2D sampleSurvivedFrames; + + RWStructuredBuffer precomputedShiftBuffer; + + int gSpatialRoundId; + // for default pattern + int gNeighborCount; + float gGatherRadius; + bool gUsePairedNeighbors; + + bool resetTemporalReservoirs; + + bool areaReservoirUseNeighborRejection; + + int getNeighborCount() + { + return gNeighborCount; + } + + void DoPass(const uint2 pixel) + { + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = prevReservoirs[centralOffset]; + + float3 centralPrimaryWo; + HitInfo centralPrimaryHit = getPixelHitInfo(params, vbuffer, pixel, centralPrimaryWo); + VertexData centralPrimaryVertexData = getVertexData(centralPrimaryHit, gScene.camera.data.posW, -centralPrimaryWo); + float centralPrimaryRayFootprint = restirpt.computePrimaryRayFootprint(centralPrimaryVertexData.faceNormalW, gScene.camera.data.posW, + -centralPrimaryWo, centralPrimaryVertexData.posW); + + if (!centralPrimaryHit.isValid() && !restirpt.kUseAreaReservoirs) return; + + + ReconnectionData dummyRcData; + dummyRcData.init(); + + int neighborCount = getNeighborCount(); + const uint startIndex = sampleNext1D(sg) * kNeighborOffsetCount; + float spatialRadius = gGatherRadius; + + float canonicalWeight = 1; + + float doFReconnectionShiftProbability = restirpt.doFReconnectionShiftProbability; + if (restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && restirpt.deriveDoFReconnectionShiftProbabilityFromCoC) + { + doFReconnectionShiftProbability = + 1.f - computeConfidenceWeightForTwoShifts(params.frameDim, centralPrimaryVertexData.posW, !centralPrimaryHit.isValid()); + } + + int doFReconnectionShiftCount = restirpt.useDoFReconnectionShift ? round(doFReconnectionShiftProbability * neighborCount) : 0; + + int selectedId = -1; + + var sharedSg = TinyUniformSampleGenerator(params.frameDim + 1, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + PairedNeighborTransform transform = PairedNeighborTransform(sharedSg, params.frameDim.x, params.frameDim.y); + + for (int i = 0; i < neighborCount; ++i) + { + if (!neighborValidMask[centralOffset].isValid(i)) continue; + + int dx, dy; + transform.readTransformedDelta(i, pixel.x, pixel.y, dx, dy); + int2 neighborPixel = pixel + int2(dx, dy); + + uint shiftedPrimaryHitAddress = neighborCount * centralOffset + i; + + uint neighborOffset = params.getReservoirOffset(neighborPixel); + PathReservoir neighborReservoir = prevReservoirs[neighborOffset]; + + if (restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && i < doFReconnectionShiftCount) + { + neighborReservoir.setLensSample(doFReconnectionShiftBuffer[shiftedPrimaryHitAddress].xy); + } + + // overwrite as centralShiftedPrimaryHit + if (restirpt.kUseAreaReservoirs) + { + centralPrimaryHit = LoadShiftedPrimaryHitAssumeAreaReSTIRAndLoadedLensSample( params, restirpt, shiftedPrimaryHitBuffer, + pixel, shiftedPrimaryHitAddress, neighborReservoir, centralPrimaryWo, centralPrimaryRayFootprint ); + } + + ReconnectionData rcData = loadReconnectionData(reconnectionDataBuffer, reconnectionDataOffsets, shiftedPrimaryHitAddress, neighborReservoir, centralPrimaryHit, centralPrimaryWo ); + + float Jacobian = 1.f; + float3 dstIntegrand = shiftPath( restirpt, params, Jacobian, + neighborReservoir, rcData, centralPrimaryRayFootprint); + + PrecomputedShiftData spData = { dstIntegrand, Jacobian, neighborReservoir.reconnectionJacobian }; + precomputedShiftBuffer[shiftedPrimaryHitAddress] = spData; + } + } + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gPass.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/SpatialResampling.cs.slang b/Source/Modules/ReSTIRPathTracing/SpatialResampling.cs.slang new file mode 100644 index 0000000000..d99867e54e --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/SpatialResampling.cs.slang @@ -0,0 +1,289 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import ResamplingCommon; +import PairedNeighborTransform; + +#if USE_DECOUPLED_SHADING +#define EXTRA_SHIFT_ARGS outColor +#else +#define EXTRA_SHIFT_ARGS +#endif + +struct SpatialResampling +{ + static const uint kNeighborOffsetCount = NEIGHBOR_OFFSET_COUNT; + static const uint kNeighborOffsetMask = kNeighborOffsetCount - 1; + + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + RWTexture2D sampleVBuffer; + Texture1D neighborOffsets; + + StructuredBuffer shiftedPrimaryHitBuffer; + + RWStructuredBuffer reservoirs; + StructuredBuffer prevReservoirs; + StructuredBuffer reconnectionDataBuffer; + StructuredBuffer reconnectionDataOffsets; + StructuredBuffer doFReconnectionShiftBuffer; + + StructuredBuffer neighborValidMask; + RWTexture2D sampleSurvivedFrames; + + StructuredBuffer precomputedShiftBuffer; + +#if USE_DECOUPLED_SHADING + RWTexture2D outputColor; +#endif + + int gSpatialRoundId; + // for default pattern + int gNeighborCount; + float gGatherRadius; + bool gUsePairedNeighbors; + + bool resetTemporalReservoirs; + + bool areaReservoirUseNeighborRejection; + + int getNeighborCount() + { + return gNeighborCount; + } + + /** Per-pixel spatial resampling pass, one spatial round. + */ + void DoPass(const uint2 pixel) + { + // ---- Phase 1: RNG + central reservoir + primary hit ---- + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = prevReservoirs[centralOffset]; + + float3 centralPrimaryWo; + float centralPrimaryRayFootprint; + float3 centralPrimaryHitPosition; + HitInfo centralPrimaryHit = LoadPixelCenterPrimaryHit( params, restirpt, vbuffer, pixel, centralPrimaryWo, centralPrimaryRayFootprint, centralPrimaryHitPosition); + + if (!centralPrimaryHit.isValid() && !restirpt.kUseAreaReservoirs) return; + + PathReservoir dstReservoir; + dstReservoir.init(); + + // ---- Phase 2: Spatial radius + DoF reconnection setup ---- + int neighborCount = getNeighborCount(); + const uint startIndex = sampleNext1D(sg) * kNeighborOffsetCount; + float spatialRadius = gGatherRadius; + + float doFReconnectionShiftProbability = restirpt.doFReconnectionShiftProbability; + if (restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && restirpt.deriveDoFReconnectionShiftProbabilityFromCoC) + { + doFReconnectionShiftProbability = + 1.f - computeConfidenceWeightForTwoShifts(params.frameDim, centralPrimaryHitPosition, !centralPrimaryHit.isValid()); + } + + int doFReconnectionShiftCount = restirpt.useDoFReconnectionShift ? round(doFReconnectionShiftProbability * neighborCount) : 0; + + int selectedId = -1; + + // ---- Phase 3: Paired-neighbor transform (reciprocal-neighbor pattern) ---- + var sharedSg = TinyUniformSampleGenerator(params.frameDim + 1, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + PairedNeighborTransform transform = PairedNeighborTransform(sharedSg, params.frameDim.x, params.frameDim.y); + + // ---- Phase 4: Pairwise canonical weight init ---- + PairwiseFactor C = { neighborValidMask[centralOffset].cSum, centralReservoir.M }; + float canonicalWeight = C.c_c / C.c_sum; + +#if USE_DECOUPLED_SHADING + float3 outColor = 0.f; +#endif + + // ---- Phase 5: Per-neighbor loop ---- + for (int i = 0; i < neighborCount; ++i) + { + if (!neighborValidMask[centralOffset].isValid(i)) continue; + + int2 neighborPixel; + if (gUsePairedNeighbors) + { + int dx, dy; + transform.readTransformedDelta(i, pixel.x, pixel.y, dx, dy); + neighborPixel = pixel + int2(dx, dy); + } + else + { + neighborPixel = getNextNeighborPixel(startIndex, neighborOffsets, pixel, i, spatialRadius); + } + uint neighborOffset = params.getReservoirOffset(neighborPixel); + + uint shiftedPrimaryHitAddress = gUsePairedNeighbors ? neighborCount * centralOffset + i : 2 * neighborCount * centralOffset + 2 * i + 1; + uint inverseShiftedPrimaryHitAddress = gUsePairedNeighbors ? neighborCount * neighborOffset + i : 2 * neighborCount * centralOffset + 2 * i; + + // Load this neighbor's reservoir and the primary hit (shifted from the canonical sample in area ReSTIR ) + float3 neighborShiftedPrimaryWo; + float neighborShiftedPrimaryRayFootprint; + HitInfo neighborShiftedPrimaryHit = LoadShiftedPrimaryHit( params, restirpt, shiftedPrimaryHitBuffer, vbuffer, doFReconnectionShiftBuffer, restirpt.useDoFReconnectionShift && i < doFReconnectionShiftCount, neighborPixel, + inverseShiftedPrimaryHitAddress, centralReservoir, neighborShiftedPrimaryWo, neighborShiftedPrimaryRayFootprint); + PathReservoir neighborReservoir = prevReservoirs[neighborOffset]; + + // Inverse shift T_{c->i}: evaluate p_hat of the canonical sample at the neighbor pixel. + float3 canonicalAtOtherIntegrand; + float canonicalAtOtherJacobian = 1.f; + + if (!gUsePairedNeighbors) + { + ReconnectionData rcData = loadReconnectionData( reconnectionDataBuffer, reconnectionDataOffsets, inverseShiftedPrimaryHitAddress, centralReservoir, neighborShiftedPrimaryHit, neighborShiftedPrimaryWo ); + canonicalAtOtherIntegrand = computeShiftedIntegrand( restirpt, params, canonicalAtOtherJacobian, centralReservoir, rcData, neighborShiftedPrimaryRayFootprint); + } + else + { + PrecomputedShiftData spData = precomputedShiftBuffer[inverseShiftedPrimaryHitAddress]; + canonicalAtOtherIntegrand = computeShiftedIntegrandWithPrecomputedInfo(canonicalAtOtherJacobian, centralReservoir, spData); + } + + // accumulate a new term for canonical's pairwise MIS weight + float canonicalAtOtherPHat = PathRISState::toScalar(canonicalAtOtherIntegrand) * canonicalAtOtherJacobian; + if (restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && i < doFReconnectionShiftCount) + { + canonicalAtOtherPHat *= doFReconnectionShiftBuffer[inverseShiftedPrimaryHitAddress].z; // Jacobian + } + + float currentWeightTerm = 1; + currentWeightTerm -= (canonicalAtOtherPHat > 0.f) + ? canonicalAtOtherPHat * (C.c_sum - C.c_c) / + (canonicalAtOtherPHat * (C.c_sum - C.c_c) + PathRISState::toScalar(centralReservoir.integrand) * C.c_c) + : 0; + + canonicalWeight += neighborReservoir.M / C.c_sum * currentWeightTerm; + + // Shift T_{i->c}: shift the neighbor sample into the canonical's domain + + // read shifted lens coordinates in the DoF case + float splatOrLensShiftJacobian = 1.f; + + if (restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && i < doFReconnectionShiftCount) + { + splatOrLensShiftJacobian = doFReconnectionShiftBuffer[shiftedPrimaryHitAddress].z; + neighborReservoir.setLensSample(doFReconnectionShiftBuffer[shiftedPrimaryHitAddress].xy); + } + + // load primary hit shifted from the neighbor sample in the area ReSTIR case + if (restirpt.kUseAreaReservoirs) + { + centralPrimaryHit = LoadShiftedPrimaryHitAssumeAreaReSTIRAndLoadedLensSample( params, restirpt, shiftedPrimaryHitBuffer, pixel, shiftedPrimaryHitAddress, neighborReservoir, centralPrimaryWo, centralPrimaryRayFootprint); + } + + bool selected; + if (!gUsePairedNeighbors) + { + ReconnectionData rcData = loadReconnectionData( reconnectionDataBuffer, reconnectionDataOffsets, shiftedPrimaryHitAddress, neighborReservoir, centralPrimaryHit, centralPrimaryWo ); + selected = shiftAndMergeReservoirSpatial( restirpt, params, dstReservoir, neighborReservoir, rcData, centralPrimaryRayFootprint, sg, C, splatOrLensShiftJacobian, EXTRA_SHIFT_ARGS ); + } + else + { + PrecomputedShiftData spData = precomputedShiftBuffer[shiftedPrimaryHitAddress]; + selected = shiftAndMergeReservoirSpatialWithPrecomputedInfo(dstReservoir, neighborReservoir, spData, sg, C, splatOrLensShiftJacobian, EXTRA_SHIFT_ARGS); + } + + if (selected) selectedId = i; + } + + // ---- Phase 6: Merge canonical sample with accumulated MIS weight ---- + bool selectOwnSample = dstReservoir.merge(centralReservoir.integrand, 1.f, centralReservoir, sg, canonicalWeight); +#if USE_DECOUPLED_SHADING + outColor += centralReservoir.integrand * centralReservoir.weight * canonicalWeight; +#endif + + // ---- Phase 7: Finalize, write back, optional decoupled shading output ---- + dstReservoir.finalizeReservoirWeight(); +#if USE_DECOUPLED_SHADING + outColor = any(isnan(outColor) || isinf(outColor)) ? 0.f : outColor; +#endif + dstReservoir.weight = (isnan(dstReservoir.weight) || isinf(dstReservoir.weight)) + ? 0.f + : max(0.f, dstReservoir.weight); + + if (restirpt.kUseAreaReservoirs && (restirpt.useDoFReconnectionShift || restirpt.useReservoirSplatting) && !selectOwnSample) + sampleVBuffer[pixel] = shiftedPrimaryHitBuffer[(gUsePairedNeighbors ? 1 : 2) * neighborCount * centralOffset + (gUsePairedNeighbors ? selectedId : 2 * selectedId + 1)]; + if (dstReservoir.initRandomSeed != centralReservoir.initRandomSeed) + sampleSurvivedFrames[pixel] = 0; + + reservoirs[centralOffset] = dstReservoir; + +#if USE_DECOUPLED_SHADING + if (gSpatialRoundId == restirpt.numSpatialRounds - 1) + { + if (kSamplesPerPixel == 1) + { + outputColor[pixel].rgb += outColor; + } + else + { + outputColor[pixel].rgb = outColor; + } + } +#endif + } + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + + } +}; + +cbuffer CB +{ + SpatialResampling gSpatialResampling; +} + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gSpatialResampling.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/SpatialTraceShiftedPrimaryHits.cs.slang b/Source/Modules/ReSTIRPathTracing/SpatialTraceShiftedPrimaryHits.cs.slang new file mode 100644 index 0000000000..7d03ce5bf3 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/SpatialTraceShiftedPrimaryHits.cs.slang @@ -0,0 +1,182 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import ResamplingCommon; +import PairedNeighborTransform; + +struct Pass +{ + static const uint kNeighborOffsetCount = NEIGHBOR_OFFSET_COUNT; + static const uint kNeighborOffsetMask = kNeighborOffsetCount - 1; + + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + Texture2D sampleVBuffer; + Texture1D neighborOffsets; + + RWStructuredBuffer reservoirs; + StructuredBuffer prevReservoirs; + + RWStructuredBuffer shiftedPrimaryHitBuffer; + RWStructuredBuffer doFReconnectionShiftBuffer; + + int gSpatialRoundId; + // for default pattern + int gNeighborCount; + float gGatherRadius; + bool gUsePairedNeighbors; + + bool resetTemporalReservoirs; + + bool areaReservoirUseNeighborRejection; + + int getNeighborCount() + { + return gNeighborCount; + } + + void DoPass(const uint2 pixel) + { + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = prevReservoirs[centralOffset]; + + HitInfo centralPrimaryCenterHit; + ShadingData centralPrimaryCenterSd = getPixelShadingData(params, vbuffer, pixel, centralPrimaryCenterHit); + + int neighborCount = getNeighborCount(); + const uint startIndex = sampleNext1D(sg) * kNeighborOffsetCount; + float spatialRadius = gGatherRadius; + + // compute self Jacobian term + PackedHitInfo centerPixelX1Hit = sampleVBuffer[pixel]; + Ray centerRay = gScene.camera.computeRayThinlensSubPixelRandom( pixel, params.frameDim, centralReservoir.filmSample, centralReservoir.lensSample ); + float centerJ = 0.f; + if (restirpt.useDoFReconnectionShift) + centerJ = computePrimaryReconnectionJacobian( pixel, params.frameDim, HitInfo(centerPixelX1Hit), centerRay.dir, centralReservoir.filmSample, false); + + float doFReconnectionShiftProbability = restirpt.deriveDoFReconnectionShiftProbabilityFromCoC ? + 1.f - computeConfidenceWeightForTwoShifts(params.frameDim, centralPrimaryCenterSd.posW, !centralPrimaryCenterHit.isValid()) : + restirpt.doFReconnectionShiftProbability; + + int doFReconnectionShiftCount = round(doFReconnectionShiftProbability * neighborCount); + var sharedSg = TinyUniformSampleGenerator(params.frameDim + 1, restirpt.sgCount() * params.seed + restirpt.spatialSgOffset(gSpatialRoundId)); + PairedNeighborTransform transform = PairedNeighborTransform(sharedSg, params.frameDim.x, params.frameDim.y); + + for (int i = 0; i < neighborCount; ++i) + { + int2 neighborPixel; + if (gUsePairedNeighbors) + { + int dx, dy; + transform.readTransformedDelta(i, pixel.x, pixel.y, dx, dy); + neighborPixel = pixel + int2(dx, dy); + } + else + { + neighborPixel = getNextNeighborPixel(startIndex, neighborOffsets, pixel, i, spatialRadius); + } + + if (!isValidScreenRegion(params, neighborPixel)) continue; + + PathReservoir neighborReservoir = prevReservoirs[params.getReservoirOffset(neighborPixel)]; + + if (restirpt.useDoFReconnectionShift && i < doFReconnectionShiftCount) + { + PackedHitInfo neighborPixelX1Hit = sampleVBuffer[neighborPixel]; + Ray neighborRay = gScene.camera.computeRayThinlensSubPixelRandom( neighborPixel, params.frameDim, neighborReservoir.filmSample, neighborReservoir.lensSample ); + float3 X0_dstJ = solveLensSampleForPrimaryHit( pixel, params.frameDim, HitInfo(neighborPixelX1Hit), neighborRay.dir, neighborReservoir.filmSample, false ); + float srcJ = computePrimaryReconnectionJacobian( neighborPixel, params.frameDim, HitInfo(neighborPixelX1Hit), neighborRay.dir, neighborReservoir.filmSample, false ); + X0_dstJ.z /= srcJ; + if (isinf(X0_dstJ.z) || isnan(X0_dstJ.z)) X0_dstJ.z = 0.f; + doFReconnectionShiftBuffer[(gUsePairedNeighbors ? 1 : 2) * neighborCount * centralOffset + (gUsePairedNeighbors ? i : 2 * i + 1)] = float4(X0_dstJ, 0.f); + shiftedPrimaryHitBuffer[(gUsePairedNeighbors ? 1 : 2) * neighborCount * centralOffset + (gUsePairedNeighbors ? i : 2 * i + 1)] = neighborPixelX1Hit; + } + else + { + // shift neighbor to center + HitInfo hit = retracePrimaryRay(pixel, params.frameDim, neighborReservoir.filmSample, neighborReservoir.lensSample, false); + shiftedPrimaryHitBuffer[(gUsePairedNeighbors ? 1 : 2) * neighborCount * centralOffset + (gUsePairedNeighbors ? i : 2 * i + 1)] = hit.data; + } + + if (!gUsePairedNeighbors) + { + if (restirpt.useDoFReconnectionShift && i < doFReconnectionShiftCount) + { + float3 X0_dstJ = solveLensSampleForPrimaryHit( neighborPixel, params.frameDim, HitInfo(centerPixelX1Hit), centerRay.dir, centralReservoir.filmSample, false ); + X0_dstJ.z /= centerJ; + if (isinf(X0_dstJ.z) || isnan(X0_dstJ.z)) X0_dstJ.z = 0.f; + doFReconnectionShiftBuffer[2 * neighborCount * centralOffset + 2 * i] = float4(X0_dstJ, 0.f); + shiftedPrimaryHitBuffer[2 * neighborCount * centralOffset + 2 * i] = centerPixelX1Hit; + } + else + { + // shift center to neighbor + HitInfo hit = retracePrimaryRay(neighborPixel, params.frameDim, centralReservoir.filmSample, centralReservoir.lensSample, false); + shiftedPrimaryHitBuffer[2 * neighborCount * centralOffset + 2 * i] = hit.data; + } + } + } + + } + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gPass.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalPathRetrace.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalPathRetrace.cs.slang new file mode 100644 index 0000000000..ccda55e088 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalPathRetrace.cs.slang @@ -0,0 +1,248 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import RetraceWorkQueue; +import ResamplingCommon; +#include "RetraceScheduleDefinition.slangh" +struct TemporalPathRetrace +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + Texture2D temporalVbuffer; + Texture2D motionVectors; + + StructuredBuffer shiftedPrimaryHitBuffer; + + StructuredBuffer reservoirs; + StructuredBuffer prevReservoirs; + RWStructuredBuffer reconnectionDataBuffer; + StructuredBuffer keyValueBuffer; + StructuredBuffer numKeysBuffer; + RWStructuredBuffer neighborValidMask; + RWStructuredBuffer reconnectionDataOffsets; + StructuredBuffer doFReconnectionShiftBuffer; + StructuredBuffer splatSampleInfo; + StructuredBuffer splatTargetPixel; + + bool gEnableTemporalReprojection; + uint gTemporalHistoryLength; + bool gStochasticReprojection; + int temporalPassId; + + RetraceWorkQueue queues; + +#if RETRACE_SCHEDULE_TYPE == RETRACE_SCHEDULE_COMPACT + void DoPass(const uint linearIndex) + { + // read number of paths + uint totalPathCount = queues.counters.Load(0); + if (linearIndex >= totalPathCount) return; + uint encodedRetraceWorkItem = queues.retraceWorkItems.Load(linearIndex * 4); + int2 pixel = int2(encodedRetraceWorkItem & 0xfff, encodedRetraceWorkItem >> 12 & 0xfff); + uint retraceIndex = encodedRetraceWorkItem >> 24 & 0xff; + + printSetPixel(pixel); + logSetPixel(pixel); + + + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.temporalSgOffset(temporalPassId)); + const uint centralOffset = params.getReservoirOffset(pixel); + float2 motionVector = gEnableTemporalReprojection ? motionVectors[pixel] : 0.f; + float2 prevPixel = reprojectPixelForReuse(pixel, motionVector, params.frameDim, restirpt.useAreaReSTIRBackprojection, gStochasticReprojection, sg); + const uint temporalOffset = params.getReservoirOffset(restirpt.useAreaReSTIRBackprojection ? pixel : int2(prevPixel)); + + + + PathReservoir reservoir; + if (retraceIndex % 2) + { + reservoir = prevReservoirs[temporalOffset]; + if (restirpt.useReservoirSplatting) + { + reservoir.setFilmSample(splatSampleInfo[2 * temporalOffset + 1].xy); + pixel = splatTargetPixel[2 * temporalOffset + 1]; + } + } + else + { + reservoir = reservoirs[centralOffset]; + if (restirpt.useReservoirSplatting) + { + reservoir.setFilmSample(splatSampleInfo[2 * centralOffset].xy); + prevPixel = splatTargetPixel[2 * centralOffset]; + } + } + + if (restirpt.kUseAreaReservoirs && temporalPassId == 1) reservoir.setLensSample(doFReconnectionShiftBuffer[2 * centralOffset + retraceIndex].xy); + + HitInfo primaryHit; + ShadingData primarySd; + if (restirpt.kUseAreaReservoirs) + primarySd = getPixelShadingDataGeneral( params, shiftedPrimaryHitBuffer, retraceIndex % 2 ? pixel : prevPixel, 2 * centralOffset + retraceIndex, + reservoir.filmSample, reservoir.lensSample, primaryHit, retraceIndex % 2 == 0 ); + else + primarySd = getPixelShadingDataGeneral(params, vbuffer, temporalVbuffer, retraceIndex % 2 ? pixel : prevPixel, primaryHit, retraceIndex % 2 == 0); + + HitInfo rcPrevHit = HitInfo(); + float3 rcPrevWo = float3(0); + float tempFootprint = 0.f; + float3 pathThroughput = TraceReplayedPath( primaryHit, primarySd, reservoir.pathFlags, + reservoir.initRandomSeed, rcPrevHit, rcPrevWo, tempFootprint, retraceIndex % 2 == 0 ); + + reconnectionDataOffsets[2 * centralOffset + retraceIndex] = linearIndex; + reconnectionDataBuffer[linearIndex] = ReconnectionData(rcPrevHit, rcPrevWo, pathThroughput, tempFootprint); + } +#else + bool DoPass(uint2 pixel) + { + + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.temporalSgOffset(temporalPassId)); + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = reservoirs[centralOffset]; + if (restirpt.useReservoirSplatting) centralReservoir.setFilmSample(splatSampleInfo[2 * centralOffset].xy); + float2 motionVector = gEnableTemporalReprojection ? motionVectors[pixel] : 0.f; + float2 prevPixel = reprojectPixelForReuse(pixel, motionVector, params.frameDim, restirpt.useAreaReSTIRBackprojection, gStochasticReprojection, sg); + + bool shouldReturnEarly = !restirpt.useReservoirSplatting && ( + !restirpt.useAreaReSTIRBackprojection && !isValidScreenRegion(params, int2(prevPixel)) || + restirpt.useAreaReSTIRBackprojection && !isValidScreenRegionFloat(params, prevPixel)); + if (shouldReturnEarly) return false; + + int2 fetchPixel = restirpt.useAreaReSTIRBackprojection ? pixel : int2(prevPixel); + uint prevOffset = params.getReservoirOffset(fetchPixel); + PathReservoir temporalReservoir = prevReservoirs[prevOffset]; + if (restirpt.useReservoirSplatting) temporalReservoir.setFilmSample(splatSampleInfo[2 * prevOffset + 1].xy); + + if (restirpt.useReservoirSplatting) + { + pixel = splatTargetPixel[2 * prevOffset + 1]; + prevPixel = splatTargetPixel[2 * centralOffset]; + } + + HitInfo centralShiftedPrimaryHit; + ShadingData centralShiftedPrimarySd; + if (restirpt.kUseAreaReservoirs) + centralShiftedPrimarySd = getPixelShadingData( params, shiftedPrimaryHitBuffer, pixel, 2 * centralOffset + 1, temporalReservoir.filmSample, temporalReservoir.lensSample, centralShiftedPrimaryHit ); + else + centralShiftedPrimarySd = getPixelShadingData(params, vbuffer, pixel, centralShiftedPrimaryHit); + if (!centralShiftedPrimaryHit.isValid() && !restirpt.kUseAreaReservoirs) return false; + + HitInfo temporalShiftedPrimaryHit; + ShadingData temporalShiftedPrimarySd; + if (restirpt.kUseAreaReservoirs) + temporalShiftedPrimarySd = getPixelTemporalShadingData( params, shiftedPrimaryHitBuffer, prevPixel, 2 * centralOffset, centralReservoir.filmSample, centralReservoir.lensSample, temporalShiftedPrimaryHit); + else + temporalShiftedPrimarySd = getPixelTemporalShadingData(params, temporalVbuffer, prevPixel, temporalShiftedPrimaryHit); + if (!restirpt.useAreaReSTIRBackprojection && !temporalShiftedPrimaryHit.isValid() && !restirpt.kUseAreaReservoirs) return false; + + if (ReSTIRPT::ShiftMapping(restirpt.kShiftMapping) == ReSTIRPT::ShiftMapping::Hybrid) + { + // talbot MIS + // compute mis weight for current pixel + + // write to memory + if (centralReservoir.pathFlags.rcVertexLength() > 1 && temporalShiftedPrimaryHit.isValid()) + { + HitInfo rcPrevHit = HitInfo(); + float3 rcPrevWo = float3(0.f); + float3 pathThroughput = 1.f; + float tempFootprint = 0.f; + pathThroughput = TraceReplayedPath( temporalShiftedPrimaryHit, temporalShiftedPrimarySd, centralReservoir.pathFlags, + centralReservoir.initRandomSeed, rcPrevHit, rcPrevWo, tempFootprint, true ); + reconnectionDataOffsets[2 * centralOffset] = 2 * centralOffset; + reconnectionDataBuffer[2 *centralOffset] = ReconnectionData(rcPrevHit, rcPrevWo, pathThroughput, tempFootprint); + } + + if (temporalReservoir.pathFlags.rcVertexLength() > 1 && centralShiftedPrimaryHit.isValid()) + { + HitInfo rcPrevHit = HitInfo(); + float3 rcPrevWo = float3(0.f); + float3 pathThroughput = 1.f; + float tempFootprint = 0.f; + pathThroughput = TraceReplayedPath( centralShiftedPrimaryHit, centralShiftedPrimarySd, temporalReservoir.pathFlags, + temporalReservoir.initRandomSeed, rcPrevHit, rcPrevWo, tempFootprint, false ); + reconnectionDataOffsets[2 * centralOffset + 1] = 2 * centralOffset + 1; + reconnectionDataBuffer[2 * centralOffset + 1] = ReconnectionData(rcPrevHit, rcPrevWo, pathThroughput, tempFootprint); + } + } + return true; + } +#endif +#if RETRACE_SCHEDULE_TYPE != RETRACE_SCHEDULE_NAIVE + void execute(const uint index) + { + if (index >= 2 * params.frameDim.x * params.frameDim.y) return; + DoPass(index); + } +#else + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + bool isValid = DoPass(pixel); + const uint centralOffset = params.getReservoirOffset(pixel); + neighborValidMask[centralOffset].setValid(0, isValid); + } +#endif +}; + +cbuffer CB +{ + TemporalPathRetrace gTemporalPathRetrace; +} + +#if RETRACE_SCHEDULE_TYPE != RETRACE_SCHEDULE_NAIVE +[numthreads(256, 1, 1)] +#else +[numthreads(16, 16, 1)] +#endif +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ +#if RETRACE_SCHEDULE_TYPE != RETRACE_SCHEDULE_NAIVE + gTemporalPathRetrace.execute(dispatchThreadId.x); +#else + gTemporalPathRetrace.execute(dispatchThreadId.xy); +#endif +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalPopulateFloatingReservoir.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalPopulateFloatingReservoir.cs.slang new file mode 100644 index 0000000000..ea825e1235 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalPopulateFloatingReservoir.cs.slang @@ -0,0 +1,252 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import LoadShadingDataDynamic; +import ResamplingCommon; +struct Pass +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + Texture2D sampleVBuffer; + RWStructuredBuffer doFReconnectionShiftBuffer; + + Texture2D motionVectors; + Texture2D deltaReflectionMotionVectors; + Texture2D deltaTransmissionMotionVectors; + + RWStructuredBuffer temporaryReservoirs; + StructuredBuffer prevReservoirs; + StructuredBuffer reservoirs; + StructuredBuffer temporalMISPDFs; + StructuredBuffer reconnectionDataBuffer; + StructuredBuffer reconnectionDataOffsets; + StructuredBuffer shiftedPrimaryHitBufferRobust; + RWStructuredBuffer shiftedPrimaryHitBuffer; + + static const uint2 kPrevNeighborOffsets[4] = { + uint2(0, 0), uint2(1, 0), uint2(0, 1), uint2(1, 1) + }; + + + void DoPass(const uint2 pixel) + { + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.temporalPopulateFloatingSgOffset()); + const uint centralOffset = params.getReservoirOffset(pixel); + + PathReservoir dstReservoir; + dstReservoir.init(); + + + float totalM = 0.f; + + { + float2 motionVector = motionVectors[pixel]; + float mvecWeight = 1.f; + + float2 floatingPixel = pixel + motionVector * params.frameDim; + + if (!isValidScreenRegionFloat(params, floatingPixel)) + return; + + int2 quadTopLeft = int2(floor(floatingPixel)); + float2 areaCoordInQuad = saturate(float2(int2(pixel) - quadTopLeft) + (motionVector * params.frameDim)); + // has to be in [(0,0),(1,1)) + + int2 selectedPixel = -1; + int selected_relative_i = -1; + + // what's my four neighbors? + for (int i = 0; i < 2; i++) + { + for (int j = 0; j < 2; j++) + { + int2 neighborPixel = quadTopLeft + int2(j, i); + if (!isValidScreenRegion(params, neighborPixel)) continue; + // is the neighbor's sample in the floating area? + PathReservoir neighborReservoir = prevReservoirs[params.getReservoirOffset(neighborPixel)]; + float2 filmSampleNew = float2(j, i) + neighborReservoir.filmSample - areaCoordInQuad; + float bilinearWeight = lerp(1 - j, j, areaCoordInQuad.x) * lerp(1 - i, i, areaCoordInQuad.y); + totalM += bilinearWeight * neighborReservoir.M * mvecWeight; + + // option 1 + + if (!restirpt.temporalRobustShift) + { + if (all(filmSampleNew >= 0.f && filmSampleNew < 1.f)) + { + neighborReservoir.setFilmSample(filmSampleNew); + dstReservoir.merge(neighborReservoir.integrand, 1, neighborReservoir, sg, mvecWeight); + } + } + // option 2 + // read from the reconnection buffer (we have already computed pHat times Jacobian) + else + { + uint pHatMemoryOffset = 8 * params.getReservoirOffset(neighborPixel); + float pHatSource = PathRISState::toScalar(neighborReservoir.integrand); + float pHatTarget = pHatSource; + // uv is in ([0,2),[0,2)) range + int2 neighborRelativeOffsets = int2(j, i); + int2 targetPixel = neighborRelativeOffsets; + float2 uv = targetPixel + neighborReservoir.filmSample; + if (uv.y < areaCoordInQuad.y) targetPixel.y += 1; + if (uv.y > 1 + areaCoordInQuad.y) targetPixel.y -= 1; + if (uv.x < areaCoordInQuad.x) targetPixel.x += 1; + if (uv.x > 1 + areaCoordInQuad.x) targetPixel.x -= 1; + // fetch the MIS weight + + int2 temp = targetPixel - neighborRelativeOffsets + int2(1, 1); // 8 different possibilites + int memoryOffset = 3 * temp.y + temp.x; + int relative_i = 9; + if (memoryOffset != 4) // not "center" + { + memoryOffset = memoryOffset < 4 ? memoryOffset : memoryOffset - 1; + // fetch pHat from memory + pHatTarget = temporalMISPDFs[pHatMemoryOffset + memoryOffset]; + relative_i = memoryOffset; + } + + // 0,0 1,0 2,0 + // 1,0 2,1 + // 2,0 2,1 2,2 + + // sum up MIS weights + float pHatSum = bilinearWeight * pHatSource * neighborReservoir.M; + for (uint jdx = 0; jdx < 4; jdx++) + { + uint2 offsetPixel = quadTopLeft + kPrevNeighborOffsets[jdx]; + if (!isValidScreenRegion(params, offsetPixel)) continue; + int2 diff = int2(kPrevNeighborOffsets[jdx]) - neighborRelativeOffsets; + if (diff.x == 0 && diff.y == 0) continue; + int2 temp = diff + int2(1, 1); + int memoryOffset_ = 3 * temp.y + temp.x; + memoryOffset_ = memoryOffset_ < 4 ? memoryOffset_ : memoryOffset_ - 1; + // fetch pHat from memory + int _i = jdx / 2; + int _j = jdx % 2; + PathReservoir offsetReservoir = prevReservoirs[params.getReservoirOffset(offsetPixel)]; + float bilinearWeightJ = lerp(1 - _j, _j, areaCoordInQuad.x) * lerp(1 - _i, _i, areaCoordInQuad.y); + pHatSum += bilinearWeightJ * offsetReservoir.M * temporalMISPDFs[pHatMemoryOffset + memoryOffset_]; + } + + // fetch MIS weight + float misWeight = pHatSum > 0.0f ? bilinearWeight * neighborReservoir.M * pHatSource / pHatSum : 0.0f; + neighborReservoir.setFilmSample(saturate(targetPixel + neighborReservoir.filmSample - areaCoordInQuad)); + + bool selected = dstReservoir.mergeWithoutWriting(pHatTarget, neighborReservoir, sg, misWeight); + + if (selected) + { + selectedPixel = neighborPixel; + selected_relative_i = relative_i; + } + + // Start resampling + // only do reconnection with the selected neighbor + } + } + } + + // populate the reservoir with shifted quantities + if (selected_relative_i != -1 && restirpt.temporalRobustShift) // doesn't support multi motion vectors yet + { + int neighborOffset = params.getReservoirOffset(selectedPixel); + PathReservoir neighborReservoir = prevReservoirs[neighborOffset]; + float3 dstIntegrand = neighborReservoir.integrand; + neighborReservoir.setFilmSample(dstReservoir.filmSample); + neighborReservoir.setLensSample(dstReservoir.lensSample); + + if (selected_relative_i != 9) + { + float3 centralShiftedPrimaryWo; + + HitInfo centralShiftedPrimaryHit = + getPixelTemporalHitInfo( params, shiftedPrimaryHitBufferRobust, floatingPixel, 8 * neighborOffset + selected_relative_i, dstReservoir.filmSample, dstReservoir.lensSample, centralShiftedPrimaryWo ); + VertexData centralShiftedPrimaryVertexData = getVertexData(centralShiftedPrimaryHit, gScene.camera.data.prevPosW, -centralShiftedPrimaryWo, true); + float centralShiftedPrimaryRayFootprint = restirpt.computePrimaryRayFootprint( centralShiftedPrimaryVertexData.faceNormalW, gScene.camera.data.prevPosW, + -centralShiftedPrimaryWo, centralShiftedPrimaryVertexData.posW ); + + ReconnectionData rcData; + if (neighborReservoir.pathFlags.rcVertexLength() > 1) + { + rcData = reconnectionDataBuffer[reconnectionDataOffsets[8 * neighborOffset + selected_relative_i]]; + } + else + { + rcData.pathThroughput = 1.f; + rcData.rcPrevHit = centralShiftedPrimaryHit; + rcData.rcPrevWo = centralShiftedPrimaryWo; + rcData.rcFootprintFactor = 0.f; // not used + } + float Jacobian = 1.f; + dstIntegrand = shiftPath(restirpt, params, Jacobian, neighborReservoir, rcData, centralShiftedPrimaryRayFootprint, true); + } + dstReservoir.mergeDirectOverwrite(dstIntegrand, neighborReservoir); + } + } + + dstReservoir.finalizeReservoirWeight(); + dstReservoir.M = totalM; + // save dstReservoir + + temporaryReservoirs[centralOffset] = dstReservoir; + } + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + } +}; + +cbuffer CB +{ + Pass gPass; +} + + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gPass.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalResampling.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalResampling.cs.slang new file mode 100644 index 0000000000..5d1f55989f --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalResampling.cs.slang @@ -0,0 +1,325 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import LoadShadingDataDynamic; +import ResamplingCommon; + +struct TemporalResampling +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + RWTexture2D vbuffer; + Texture2D temporalVbuffer; + StructuredBuffer shiftedPrimaryHitBuffer; + Texture2D motionVectors; + Texture2D duplicationMap; + + Texture2D prevSampleSurvivedFrames; + RWTexture2D sampleSurvivedFrames; + + RWStructuredBuffer reservoirs; + StructuredBuffer prevReservoirs; + StructuredBuffer reconnectionDataBuffer; + StructuredBuffer reconnectionDataOffsets; + StructuredBuffer neighborValidMask; + StructuredBuffer doFReconnectionShiftBuffer; + + Texture2D splatCellCounts; + Texture2D splatCellOffsets; + StructuredBuffer sortedSplatSourcePixels; + StructuredBuffer splatSampleInfo; + StructuredBuffer splatTargetPixel; + + bool gEnableTemporalReprojection; + + uint gTemporalHistoryLength; + bool gStochasticReprojection; + float duplicationMCapPower; + bool enableDupMapMCapDownweighting; + bool computeTemporalDuplication; + + float gGatherRadius; + + int temporalPassId; + + + void writeTemporalOutputs(const uint2 pixel, float temporalSampleAge) + { + if (temporalPassId != 0) return; + if (computeTemporalDuplication) + sampleSurvivedFrames[pixel] = temporalSampleAge; + } + + + /** Per-pixel temporal resampling pass. + */ + bool DoPass(const uint2 pixel) + { + // ---- Phase 1: RNG + central reservoir ---- + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.temporalSgOffset(temporalPassId)); + + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = reservoirs[centralOffset]; + uint vBufferWritingOffset = centralOffset; + + PathReservoir dstReservoir; + dstReservoir.init(); + + // ---- Phase 2: Motion-vector reprojection + validity early-out ---- + float2 motionVector = gEnableTemporalReprojection ? motionVectors[pixel] : 0.f; + float2 prevPixel = reprojectPixelForReuse(pixel, motionVector, params.frameDim, + restirpt.useAreaReSTIRBackprojection, gStochasticReprojection, sg); + + bool shouldReturnEarly = !restirpt.useReservoirSplatting && ( + !restirpt.useAreaReSTIRBackprojection && !isValidScreenRegion(params, int2(prevPixel)) || + restirpt.useAreaReSTIRBackprojection && !isValidScreenRegionFloat(params, prevPixel)); + + if (shouldReturnEarly) + { + return false; + } + + const bool useTemporalDoFReconnectionShift = restirpt.kUseAreaReservoirs && restirpt.useDoFReconnectionShift && restirpt.useDoFReconnectionForTemporalReuse; + const bool isAdditionalPassForDofShift = useTemporalDoFReconnectionShift && temporalPassId == 1; + + // ---- Phase 3: Temporal reservoir lookup ---- + uint prevOffset = 0; + PathReservoir temporalReservoir; + temporalReservoir.M = 0; + + int2 prevPixelForDupMap = reprojectPixelForDupMap(pixel, motionVector, params.frameDim, + restirpt.useAreaReSTIRBackprojection, gStochasticReprojection, sg); + + if (!restirpt.useReservoirSplatting) + { + int2 fetchPixel = restirpt.useAreaReSTIRBackprojection ? pixel : int2(prevPixel); + prevOffset = params.getReservoirOffset(fetchPixel); + temporalReservoir = prevReservoirs[prevOffset]; + } + else + { + if (isValidScreenRegion(params, prevPixelForDupMap)) temporalReservoir.M = prevReservoirs[params.getReservoirOffset(prevPixelForDupMap)].M; + else temporalReservoir.M = 0; + prevPixelForDupMap = min(params.frameDim - 1, max(int2(0, 0), prevPixelForDupMap)); + } + + float2 shiftToPrevfilmSample = centralReservoir.filmSample; + if (restirpt.useReservoirSplatting) + { + prevPixel = splatTargetPixel[2 * centralOffset]; + shiftToPrevfilmSample = splatSampleInfo[2 * centralOffset].xy; + } + + static const bool doTemporalUpdate = restirpt.kTemporalUpdateForDynamicScene; + static const bool doInverseTemporalUpdate = restirpt.kUsePrevFrameSceneData ? restirpt.kTemporalUpdateForDynamicScene : false; + + // ---- Phase 4: Duplication-driven MCap ---- + float MCap = gTemporalHistoryLength; + DetermineMCap( MCap, duplicationMap, + prevPixelForDupMap, enableDupMapMCapDownweighting, + duplicationMCapPower + ); + temporalReservoir.M = min(MCap, temporalReservoir.M); + + if (!neighborValidMask[centralOffset].isValid(0) && !restirpt.kUseAreaReservoirs) return false; + + bool selectNewSample = false; + + // ---- Phase 5: DoF-reconnection MIS pre-scaling ---- + if (useTemporalDoFReconnectionShift) + { + float doFReconnectionShiftProbability = doFReconnectionShiftBuffer[2 * centralOffset].w; + float2 misScales = float2(1.f - doFReconnectionShiftProbability, doFReconnectionShiftProbability); + temporalReservoir.M *= misScales[temporalPassId]; + if (temporalReservoir.M == 0.f) return false; + } + + // ---- Phase 6: Inverse-shift T_{c->t} ---- + // We need p_hat of the canonical sample evaluated at the temporal pixel to compute canonical's MIS weight + float3 temporalShiftedPrimaryWo; + float temporalShiftedPrimaryRayFootprint; + HitInfo temporalShiftedPrimaryHit = LoadShiftedPrimaryHitPrevFrame( params, restirpt, shiftedPrimaryHitBuffer, temporalVbuffer, doFReconnectionShiftBuffer, isAdditionalPassForDofShift, + prevPixel, 2 * centralOffset, centralReservoir, shiftToPrevfilmSample, temporalShiftedPrimaryWo, temporalShiftedPrimaryRayFootprint); + + ReconnectionData rcData = loadReconnectionData( reconnectionDataBuffer, reconnectionDataOffsets, 2 * centralOffset, centralReservoir, temporalShiftedPrimaryHit, temporalShiftedPrimaryWo ); + + float canonicalAtOtherJacobian = 1.f; + if (restirpt.useReservoirSplatting) + canonicalAtOtherJacobian = splatSampleInfo[2 * centralOffset].z; // splatting's Jacobian + + float3 canonicalAtOtherIntegrand = computeShiftedIntegrand( restirpt, params, canonicalAtOtherJacobian, centralReservoir, rcData, temporalShiftedPrimaryRayFootprint, true, doInverseTemporalUpdate); + float canonicalAtOtherPHat = PathRISState::toScalar(canonicalAtOtherIntegrand) * canonicalAtOtherJacobian; + if (isAdditionalPassForDofShift) + { + canonicalAtOtherPHat *= doFReconnectionShiftBuffer[2 * centralOffset].z; + // if cameray is static and centralReservoir stored shifted temporalReservoir's path, we are getting back to the original path + // (fix numerical precision which could lead to firefly explosion) + if (shouldGetSamePath(temporalShiftedPrimaryHit, centralReservoir.initRandomSeed == temporalReservoir.initRandomSeed)) + canonicalAtOtherPHat = PathRISState::toScalar(centralReservoir.integrand); + } + + // kill p_hat if the shifted film sample falls outside frame. + if (restirpt.useAreaReSTIRBackprojection && !restirpt.useReservoirSplatting) + { + float2 canonicalFilmSampleAtFloating = prevPixel + centralReservoir.filmSample; + if (!all(canonicalFilmSampleAtFloating >= 0.f && canonicalFilmSampleAtFloating < params.frameDim)) canonicalAtOtherPHat = 0.f; + } + + // ---- Phase 7: Splatting/non-splatting merge ---- + if (restirpt.useReservoirSplatting) + { + // Splatting branch: aggregate the multiple temporal reservoirs whose splat targets fell on this pixel + float historyM = temporalReservoir.M; // this must be independent of the shift mapping (splatting) + + uint32_t numReservoirs = splatCellCounts[pixel]; + uint32_t cellOffset = splatCellOffsets[pixel]; + + for (uint32_t i = 0; i < numReservoirs; i++) + { + uint2 splatSourcePixel = sortedSplatSourcePixels[cellOffset + i]; + + uint prevOffset = params.getReservoirOffset(splatSourcePixel); + uint shiftedPrimaryHitAddress = 2 * prevOffset + 1; + + temporalReservoir = prevReservoirs[prevOffset]; + temporalReservoir.setFilmSample(splatSampleInfo[shiftedPrimaryHitAddress].xy); + temporalReservoir.M = min(MCap, temporalReservoir.M); + + float3 centralShiftedPrimaryWo; + float centralShiftedPrimaryRayFootprint; + HitInfo centralShiftedPrimaryHit = LoadShiftedPrimaryHitAssumeAreaReSTIRAndLoadedLensSample( params, restirpt, shiftedPrimaryHitBuffer, pixel, shiftedPrimaryHitAddress, + temporalReservoir, centralShiftedPrimaryWo, centralShiftedPrimaryRayFootprint ); + + rcData = loadReconnectionData( reconnectionDataBuffer, reconnectionDataOffsets, shiftedPrimaryHitAddress, temporalReservoir, centralShiftedPrimaryHit, centralShiftedPrimaryWo ); + + float splatOrLensShiftJacobian = splatSampleInfo[shiftedPrimaryHitAddress].z; + bool select = shiftAndMergeReservoirTemporal( restirpt, params, dstReservoir, + temporalReservoir, rcData, centralShiftedPrimaryRayFootprint, sg, centralReservoir.M, splatOrLensShiftJacobian, doTemporalUpdate ); + vBufferWritingOffset = select ? prevOffset : vBufferWritingOffset; + } + + float splattedPrevPixelM = min(MCap, splatSampleInfo[2 * centralOffset].w); + float canonicalWeight = (canonicalAtOtherPHat > 0.f) + ? 1.f - (canonicalAtOtherPHat * splattedPrevPixelM / + (canonicalAtOtherPHat * splattedPrevPixelM + float(centralReservoir.M) * PathRISState::toScalar(centralReservoir.integrand))) + : 1.f; + // merge canonical sample + selectNewSample = dstReservoir.merge(centralReservoir.integrand, 1.f, centralReservoir, sg, canonicalWeight); + dstReservoir.M = centralReservoir.M + historyM; + } + else + { + // Non-splatting branch: a single forward-shift T_{t->c} of the temporal sample to the central pixel. + uint inverseShiftedPrimaryHitAddress = 2 * centralOffset + 1; + float3 centralShiftedPrimaryWo; + float centralShiftedPrimaryRayFootprint; + HitInfo centralShiftedPrimaryHit = LoadShiftedPrimaryHit( params, restirpt, shiftedPrimaryHitBuffer, vbuffer, + doFReconnectionShiftBuffer, isAdditionalPassForDofShift, + pixel, inverseShiftedPrimaryHitAddress, temporalReservoir, centralShiftedPrimaryWo, centralShiftedPrimaryRayFootprint ); + + if (!centralShiftedPrimaryHit.isValid() && !restirpt.kUseAreaReservoirs) return false; + + float canonicalWeight = (canonicalAtOtherPHat > 0.f) + ? 1.f - (canonicalAtOtherPHat * temporalReservoir.M / + (canonicalAtOtherPHat * temporalReservoir.M + float(centralReservoir.M) * PathRISState::toScalar(centralReservoir.integrand))) + : 1.f; + + rcData = loadReconnectionData( reconnectionDataBuffer, reconnectionDataOffsets, inverseShiftedPrimaryHitAddress, temporalReservoir, centralShiftedPrimaryHit, centralShiftedPrimaryWo); + + float splatOrLensShiftJacobian = 1.f; + if (isAdditionalPassForDofShift) + { + splatOrLensShiftJacobian = doFReconnectionShiftBuffer[inverseShiftedPrimaryHitAddress].z; + temporalReservoir.setLensSample(doFReconnectionShiftBuffer[inverseShiftedPrimaryHitAddress].xy); + } + + bool select = shiftAndMergeReservoirTemporal( restirpt, params, dstReservoir, + temporalReservoir, rcData, centralShiftedPrimaryRayFootprint, sg, centralReservoir.M, splatOrLensShiftJacobian, doTemporalUpdate ); + + // Merge canonical sample + selectNewSample = dstReservoir.merge(centralReservoir.integrand, 1.f, centralReservoir, sg, canonicalWeight); + } + + // ---- Phase 8: Finalize reservoir, write back ---- + dstReservoir.finalizeReservoirWeight(); + dstReservoir.weight = (isnan(dstReservoir.weight) || isinf(dstReservoir.weight)) + ? 0.f + : max(0.f, dstReservoir.weight); + + reservoirs[centralOffset] = dstReservoir; + + // vbuffer stores the **selected** primary hit in this case + if (!selectNewSample && restirpt.kUseAreaReservoirs && (restirpt.useDoFReconnectionShift || restirpt.useReservoirSplatting)) + vbuffer[pixel] = shiftedPrimaryHitBuffer[2 * vBufferWritingOffset + 1]; + + float temporalSampleAge = 0; + if (temporalPassId == 0 && !selectNewSample && computeTemporalDuplication) + { + temporalSampleAge = prevSampleSurvivedFrames[prevPixelForDupMap] + 1; + } + + writeTemporalOutputs(pixel, temporalSampleAge); + return true; + } + + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + if (!DoPass(pixel)) + { + writeTemporalOutputs(pixel, 0.f); + } + } +}; + +cbuffer CB +{ + TemporalResampling gTemporalResampling; +} + + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gTemporalResampling.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalReservoirSplatting.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalReservoirSplatting.cs.slang new file mode 100644 index 0000000000..e3e68f4cc8 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalReservoirSplatting.cs.slang @@ -0,0 +1,157 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import ReSTIRPT; +import PathReservoir; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import ResamplingCommon; +import Utils.Math.Ray; +import Scene.Scene; + +groupshared uint sampleIDs[32][32]; + +struct Pass +{ + ReSTIRPathTracingParams params; + Texture2D sampleVBuffer; + Texture2D temporalSampleVbuffer; + StructuredBuffer prevReservoirs; + StructuredBuffer reservoirs; + RWStructuredBuffer splatCellOffsetCounter; + RWTexture2D splatCellCounts; + RWTexture2D splatCellOffsets; + RWStructuredBuffer sortedSplatSourcePixels; + RWTexture2D splatCellIndices; + RWStructuredBuffer splatSampleInfo; + RWStructuredBuffer splatTargetPixel; + RWStructuredBuffer shiftedPrimaryHitBuffer; + + void splatSamples(const uint2 pixel) + { + if (any(pixel >= params.frameDim)) return; + + const uint offset = params.getReservoirOffset(pixel); + + HitInfo currentPixelX1Hit = HitInfo(sampleVBuffer[pixel]); + PathReservoir currentReservoir = reservoirs[offset]; + Ray currentRay = gScene.camera.computeRayThinlensSubPixelRandom( pixel, params.frameDim, currentReservoir.filmSample, currentReservoir.lensSample ); + float3 prevImageCoordAndJacobian = splatHitToImagePlane( params.frameDim, currentPixelX1Hit, currentRay.dir, currentReservoir.lensSample, true); + + int2 splattedPrevPixel = int2(prevImageCoordAndJacobian.xy); + float M = 0; + if (prevImageCoordAndJacobian.z > 0) M = prevReservoirs[params.getReservoirOffset(splattedPrevPixel)].M; + splatSampleInfo[2 * offset] = float4(frac(prevImageCoordAndJacobian.xy), prevImageCoordAndJacobian.z, M); + splatTargetPixel[2 * offset] = splattedPrevPixel; + shiftedPrimaryHitBuffer[2 * offset] = currentPixelX1Hit.data; + + PathReservoir temporalReservoir = prevReservoirs[offset]; + HitInfo temporalPixelX1Hit = HitInfo(temporalSampleVbuffer[pixel]); + Ray temporalRay = gScene.camera.computeRayThinlensSubPixelRandomPrevFrame( pixel, params.frameDim, temporalReservoir.filmSample, temporalReservoir.lensSample ); + + float3 imageCoordAndJacobian = splatHitToImagePlane( params.frameDim, temporalPixelX1Hit, temporalRay.dir, temporalReservoir.lensSample, false ); + int2 splattedPixel = int2(imageCoordAndJacobian.xy); + + splatSampleInfo[2 * offset + 1] = float4(frac(imageCoordAndJacobian.xy), imageCoordAndJacobian.z, 0); + splatTargetPixel[2 * offset + 1] = splattedPixel; + shiftedPrimaryHitBuffer[2 * offset + 1] = temporalPixelX1Hit.data; + + if (imageCoordAndJacobian.z > 0) + { + int splattedPixel1D = splattedPixel.y * params.frameDim.x + splattedPixel.x; + uint indexInCell; + InterlockedAdd(splatCellCounts[splattedPixel], 1, indexInCell); + splatCellIndices[pixel] = uint2(splattedPixel1D, indexInCell); + } + else + { + splatCellIndices[pixel] = uint2(uint(-1), 0); + } + + } + + void computeSplatCellOffsets(const uint2 pixel) + { + if (any(pixel >= params.frameDim)) return; + + uint numElements = splatCellCounts[pixel]; + + uint offset; + InterlockedAdd(splatCellOffsetCounter[0], numElements, offset); + + splatCellOffsets[pixel] = offset; + } + + void sortSplatSourcesByTargetPixel(const uint2 pixel) + { + if (any(pixel >= params.frameDim)) return; + uint2 prevPixel = pixel; + const uint2 indices = splatCellIndices[prevPixel]; + if (indices.x == uint(-1)) return; + + const uint splattedPixel1D = indices.x; + const uint2 splattedPixel = uint2(splattedPixel1D % params.frameDim.x, splattedPixel1D / params.frameDim.x); + const uint indexInCell = indices.y; + + sortedSplatSourcePixels[splatCellOffsets[splattedPixel] + indexInCell] = prevPixel; + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(16, 16, 1)] +void splatSamples( uint3 dispatchThreadId: SV_DispatchThreadID) +{ + uint2 pixel = dispatchThreadId.xy; + if (any(pixel >= gPass.params.frameDim)) return; + printSetPixel(pixel); + logSetPixel(pixel); + gPass.splatSamples(dispatchThreadId.xy); +} + +[numthreads(16, 16, 1)] +void computeSplatCellOffsets( uint3 dispatchThreadId: SV_DispatchThreadID ) +{ + uint2 pixel = dispatchThreadId.xy; + if (any(pixel >= gPass.params.frameDim)) return; + printSetPixel(pixel); + logSetPixel(pixel); + gPass.computeSplatCellOffsets(dispatchThreadId.xy); +} + +[numthreads(16, 16, 1)] +void sortSplatSourcesByTargetPixel(uint3 dispatchThreadId: SV_DispatchThreadID) +{ + uint2 pixel = dispatchThreadId.xy; + if (any(pixel >= gPass.params.frameDim)) return; + printSetPixel(pixel); + logSetPixel(pixel); + gPass.sortSplatSourcesByTargetPixel(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalRobustCreateShiftVBuffer.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalRobustCreateShiftVBuffer.cs.slang new file mode 100644 index 0000000000..293ed814fd --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalRobustCreateShiftVBuffer.cs.slang @@ -0,0 +1,89 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import ResamplingCommon; + +struct Pass +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + StructuredBuffer prevReservoirs; + RWStructuredBuffer shiftedPrimaryHitBuffer; + // for default pattern + + static const int2 kPrevNeighborOffsets[8] = { + int2(-1, -1), int2(0, -1), int2(1, -1), int2(-1, 0), int2(1, 0), int2(-1, 1), int2(0, 1), int2(1, 1) + }; + + void DoPass(uint2 pixel, uint shiftID) + { + if (any(pixel >= params.frameDim)) return; + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = prevReservoirs[centralOffset]; + int2 offset2D = kPrevNeighborOffsets[shiftID]; + // get primary hit + int2 neighborPixel = int2(pixel) + offset2D; + if (!isValidScreenRegion(params, neighborPixel)) return; + + + // shift center to neighbor + HitInfo hit = retracePrimaryRay(neighborPixel, params.frameDim, centralReservoir.filmSample, centralReservoir.lensSample, true); + shiftedPrimaryHitBuffer[8 * centralOffset + shiftID] = hit.data; + } + void execute(const uint2 pixel, uint shiftID) + { + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel, shiftID); + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(8,8,8)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gPass.execute(dispatchThreadId.xy, dispatchThreadId.z); +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalRobustPathRetrace.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalRobustPathRetrace.cs.slang new file mode 100644 index 0000000000..ec013f1444 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalRobustPathRetrace.cs.slang @@ -0,0 +1,114 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import RetraceWorkQueue; +import ResamplingCommon; +#include "RetraceScheduleDefinition.slangh" + + +struct Pass +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + StructuredBuffer shiftedPrimaryHitBuffer; + + StructuredBuffer prevReservoirs; + RWStructuredBuffer reconnectionDataBuffer; + RWStructuredBuffer reconnectionDataOffsets; + + RetraceWorkQueue queues; + + static const int2 kPrevNeighborOffsets[8] = { + int2(-1, -1), int2(0, -1), int2(1, -1), int2(-1, 0), int2(1, 0), int2(-1, 1), int2(0, 1), int2(1, 1) + }; + + + void DoPass(const uint linearIndex) + { + // read number of paths + uint totalPathCount = queues.counters.Load(0); + if (linearIndex >= totalPathCount) return; + uint encodedRetraceWorkItem = queues.retraceWorkItems.Load(linearIndex * 4); + int2 pixel = int2(encodedRetraceWorkItem & 0xfff, encodedRetraceWorkItem >> 12 & 0xfff); + uint retraceIndex = encodedRetraceWorkItem >> 24 & 0xff; + + printSetPixel(pixel); + logSetPixel(pixel); + + + const uint centralOffset = params.getReservoirOffset(pixel); + + int2 neighborPixel = pixel + kPrevNeighborOffsets[retraceIndex]; + const uint neighborOffset = params.getReservoirOffset(neighborPixel); + + PathReservoir reservoir = prevReservoirs[centralOffset]; + + + HitInfo primaryHit; + ShadingData primarySd = getPixelTemporalShadingData( params, shiftedPrimaryHitBuffer, neighborPixel, + 8 * centralOffset + retraceIndex, reservoir.filmSample, reservoir.lensSample, primaryHit ); + + + HitInfo rcPrevHit = HitInfo(); + float3 rcPrevWo = float3(0); + float tempFootprint = 0.f; + float3 pathThroughput = TraceReplayedPath( primaryHit, primarySd, reservoir.pathFlags, + reservoir.initRandomSeed, rcPrevHit, rcPrevWo, tempFootprint, true ); + + reconnectionDataOffsets[8 * centralOffset + retraceIndex] = linearIndex; + reconnectionDataBuffer[linearIndex] = ReconnectionData(rcPrevHit, rcPrevWo, pathThroughput, tempFootprint); + } + void execute(const uint index) + { + if (index >= 8 * params.frameDim.x * params.frameDim.y) return; + DoPass(index); + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(256, 1, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gPass.execute(dispatchThreadId.x); +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalRobustProduceWorkload.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalRobustProduceWorkload.cs.slang new file mode 100644 index 0000000000..110bd23155 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalRobustProduceWorkload.cs.slang @@ -0,0 +1,144 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Utils.Attributes; +import Utils.Color.ColorHelpers; +import Utils.Math.MathHelpers; +import ReSTIRPathTracing.ReSTIRPT; +import LoadShadingDataDynamic; +import RetraceWorkQueue; +import Scene.HitInfo; +import PathReservoir; +import Utils.Sampling.TinyUniformSampleGenerator; +import ResamplingCommon; + +static const uint kWarpCount = (kScreenTileDim.x * kScreenTileDim.y) / 32; +groupshared uint gRetraceWorkOffset[8 /* kWarpCount */]; + +struct Pass +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + StructuredBuffer prevReservoirs; + StructuredBuffer shiftedPrimaryHitBuffer; + + RetraceWorkQueue queues; + + static const int2 kPrevNeighborOffsets[8] = { + int2(-1, -1), int2(0, -1), int2(1, -1), int2(-1, 0), int2(1, 0), int2(-1, 1), int2(0, 1), int2(1, 1) + }; + + void execute(const uint2 tileID, const uint threadIdx) + { + // Map thread to pixel based on Morton order within tile. + // The tiles themselves are enumerated in scanline order on screen. + const uint2 tileOffset = tileID << kScreenTileBits; // Tile offset in pixels. + const uint2 pixel = deinterleave_8bit(threadIdx) + tileOffset; // Assumes 16x16 tile or smaller. A host-side assert checks this assumption. + + uint retraceWorkItemCount = 0; + uint retraceCandidateMask = 0; + + // Note: Do not terminate threads for out-of-bounds pixels because we need all threads active for the prefix sum pass below. + if (all(pixel < params.frameDim)) + { + + const uint centralOffset = params.getReservoirOffset(pixel); + + { + PathReservoir centralReservoir = prevReservoirs[centralOffset]; + + for (int i = 0; i < 8; ++i) + { + if (ReSTIRPT::ShiftMapping(restirpt.kShiftMapping) == ReSTIRPT::ShiftMapping::Hybrid) + { + int2 neighborPixel = int2(pixel) + kPrevNeighborOffsets[i]; + if (!isValidScreenRegion(params, neighborPixel)) + { + continue; + } + int p1 = int(centralReservoir.pathFlags.rcVertexLength() > 1); + retraceWorkItemCount += p1; + retraceCandidateMask |= p1 << i; + } + } + } + } + + // Perform a reduction over the tile to determine the number of paths + // to append to each queue. This is done via wave ops and shared memory. + // The write offsets are given by prefix sums over the threads. + const uint warpIdx = threadIdx >> 5; + + // Calculate the path and sample counts over the warp. + // The first thread in each warp writes the results to shared memory. + uint retraceWorkItems = WaveActiveSum(retraceWorkItemCount); + + if (WaveIsFirstLane()) + { + gRetraceWorkOffset[warpIdx] = retraceWorkItems; + } + GroupMemoryBarrierWithGroupSync(); + + // Compute the prefix sum over the warp totals in shared memory. + // The first N threads in the thread group perform this computation. + if (threadIdx < kWarpCount) + { + uint retraceWorkItems = gRetraceWorkOffset[threadIdx]; + gRetraceWorkOffset[threadIdx] = queues.addCounter(retraceWorkItems); + } + GroupMemoryBarrierWithGroupSync(); + + if (all(pixel < params.frameDim)) + { + uint encodedRetraceWorkItem = (pixel.y << 12) | pixel.x; + uint dstIdx = gRetraceWorkOffset[warpIdx] + WavePrefixSum(retraceWorkItemCount); + int j = 0; + for (uint i = 0; i < 8; i++) + { + if (retraceCandidateMask >> i & 1) + { + queues.retraceWorkItems.Store((dstIdx + j) * 4, encodedRetraceWorkItem + (i << 24)); + j++; + } + } + } + } +} + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(256 /* kScreenTileDim.x * kScreenTileDim.y */, 1, 1)] +void main( + uint3 groupID: SV_GroupID, + uint3 groupThreadID: SV_GroupThreadID) +{ + gPass.execute(groupID.xy, groupThreadID.x); +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalRobustResampling.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalRobustResampling.cs.slang new file mode 100644 index 0000000000..adcdd8b575 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalRobustResampling.cs.slang @@ -0,0 +1,121 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import Utils.Math.MathHelpers; +import LoadShadingDataDynamic; +import HybridShiftTraceRays; +import ResamplingCommon; + +struct Pass +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + StructuredBuffer shiftedPrimaryHitBuffer; + + StructuredBuffer prevReservoirs; + StructuredBuffer reconnectionDataBuffer; + StructuredBuffer reconnectionDataOffsets; + + RWStructuredBuffer temporalMISPDFs; + + static const int2 kPrevNeighborOffsets[8] = { + int2(-1, -1), int2(0, -1), int2(1, -1), int2(-1, 0), int2(1, 0), int2(-1, 1), int2(0, 1), int2(1, 1) + }; + + void DoPass(const uint2 pixel) + { + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = prevReservoirs[centralOffset]; + + for (int i = 0; i < 8; ++i) + { + int2 neighborPixel = pixel + kPrevNeighborOffsets[i]; + if (!isValidScreenRegion(params, neighborPixel)) + { + continue; + } + + float3 neighborPrimaryWo; + HitInfo neighborPrimaryHit = getPixelTemporalHitInfo( params, shiftedPrimaryHitBuffer, neighborPixel, 8 * centralOffset + i, centralReservoir.filmSample, centralReservoir.lensSample, neighborPrimaryWo ); + VertexData neighborPrimaryVertexData = getVertexData(neighborPrimaryHit, gScene.camera.data.prevPosW, -neighborPrimaryWo, true); + float neighborPrimaryRayFootprint = restirpt.computePrimaryRayFootprint( neighborPrimaryVertexData.faceNormalW, gScene.camera.data.prevPosW, + -neighborPrimaryWo, neighborPrimaryVertexData.posW); + + ReconnectionData rcData; + if (centralReservoir.pathFlags.rcVertexLength() > 1) + { + rcData = reconnectionDataBuffer[reconnectionDataOffsets[8 * centralOffset + i]]; + } + else + { + rcData.pathThroughput = 1.f; + rcData.rcPrevHit = neighborPrimaryHit; + rcData.rcPrevWo = neighborPrimaryWo; + rcData.rcFootprintFactor = 0.f; // not used + } + + float canonicalAtOtherJacobian = 1.f; + float3 canonicalAtOtherIntegrand = computeShiftedIntegrand(restirpt, params, canonicalAtOtherJacobian, centralReservoir, rcData, neighborPrimaryRayFootprint, true); + float canonicalAtOtherPHat = PathRISState::toScalar(canonicalAtOtherIntegrand) * canonicalAtOtherJacobian; + // we just want to get canonical At other pHat + + temporalMISPDFs[8 * centralOffset + i] = canonicalAtOtherPHat; + } + } + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + } +}; + +cbuffer CB +{ + Pass gPass; +} + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gPass.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/TemporalTraceShiftedPrimaryHits.cs.slang b/Source/Modules/ReSTIRPathTracing/TemporalTraceShiftedPrimaryHits.cs.slang new file mode 100644 index 0000000000..a84085adf2 --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/TemporalTraceShiftedPrimaryHits.cs.slang @@ -0,0 +1,162 @@ +/*************************************************************************** + # Copyright (c) 2015-26, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Shift; +import Scene.HitInfo; +import PathReservoir; +import PathTracer; +import Scene.Scene; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Sampling.TinyUniformSampleGenerator; +import Utils.Math.Ray; +import LoadShadingDataDynamic; +import ResamplingCommon; + +struct Pass +{ + ReSTIRPathTracingParams params; + ReSTIRPT restirpt; + + Texture2D vbuffer; + Texture2D motionVectors; + RWStructuredBuffer shiftedPrimaryHitBuffer; + + RWStructuredBuffer reservoirs; + StructuredBuffer prevReservoirs; + Texture2D sampleVBuffer; + RWStructuredBuffer doFReconnectionShiftBuffer; + + bool gEnableTemporalReprojection; + bool gStochasticReprojection; + int temporalPassId; + + void DoPass(const uint2 pixel) + { + var sg = TinyUniformSampleGenerator(pixel, restirpt.sgCount() * params.seed + restirpt.temporalSgOffset(temporalPassId)); + + const uint centralOffset = params.getReservoirOffset(pixel); + PathReservoir centralReservoir = reservoirs[centralOffset]; + + PathReservoir dstReservoir; + dstReservoir.init(); + + int numNeighbors = 1; + + float2 motionVector = gEnableTemporalReprojection ? motionVectors[pixel] : 0.f; + float2 prevPixel = reprojectPixelForReuse(pixel, motionVector, params.frameDim, restirpt.useAreaReSTIRBackprojection, gStochasticReprojection, sg); + + bool shouldReturnEarly = + !restirpt.useAreaReSTIRBackprojection && !isValidScreenRegion(params, int2(prevPixel)) || + restirpt.useAreaReSTIRBackprojection && !isValidScreenRegionFloat(params, prevPixel); + if (shouldReturnEarly) return; + + int2 fetchPixel = restirpt.useAreaReSTIRBackprojection ? pixel : int2(prevPixel); + PathReservoir temporalReservoir = prevReservoirs[params.getReservoirOffset(fetchPixel)]; + + if (temporalPassId == 0) + { + // trace primary ray (shift neighbor to center) + HitInfo hit = retracePrimaryRay(pixel, params.frameDim, temporalReservoir.filmSample, temporalReservoir.lensSample, false); + shiftedPrimaryHitBuffer[2 * numNeighbors * centralOffset + 1] = hit.data; + + // trace primary ray (shift center to neighbor) + hit = retracePrimaryRay(prevPixel, params.frameDim, centralReservoir.filmSample, centralReservoir.lensSample, true); + shiftedPrimaryHitBuffer[2 * numNeighbors * centralOffset] = hit.data; + + if (restirpt.useDoFReconnectionShift) + { + float3 centralCenterPrimaryWo; + HitInfo centralPrimaryCenterHit = getPixelHitInfo(params, vbuffer, pixel, centralCenterPrimaryWo); + VertexData centralPrimaryCenterVertexData = getVertexData(centralPrimaryCenterHit, gScene.camera.data.posW, -centralCenterPrimaryWo, false); + float doFReconnectionShiftProbability = restirpt.deriveDoFReconnectionShiftProbabilityFromCoC ? + 1.f - computeConfidenceWeightForTwoShifts( params.frameDim, centralPrimaryCenterVertexData.posW, !centralPrimaryCenterHit.isValid() ) : + restirpt.doFReconnectionShiftProbability; + doFReconnectionShiftProbability = restirpt.useDoFReconnectionForTemporalReuse ? doFReconnectionShiftProbability : 0.f; + bool cameraChanged = hasCameraChanged(); + if (isBorderPixel(params, pixel) && !cameraChanged) doFReconnectionShiftProbability = 0.f; + doFReconnectionShiftBuffer[2 * centralOffset].w = doFReconnectionShiftProbability; + } + } + else + { + if (restirpt.useDoFReconnectionShift) + { + float doFReconnectionShiftProbability = doFReconnectionShiftBuffer[2 * centralOffset].w; + + if (doFReconnectionShiftProbability > 0.f) + { + { + HitInfo neighborPixelX1Hit = retracePrimaryRay( prevPixel, params.frameDim, temporalReservoir.filmSample, temporalReservoir.lensSample, true ); + Ray neighborRay = gScene.camera.computeRayThinlensSubPixelRandomPrevFrame(prevPixel, params.frameDim, temporalReservoir.filmSample, temporalReservoir.lensSample); + float3 X0_dstJ = solveLensSampleForPrimaryHit( pixel, params.frameDim, neighborPixelX1Hit, neighborRay.dir, temporalReservoir.filmSample, false ); + float srcJ = computePrimaryReconnectionJacobian( prevPixel, params.frameDim, neighborPixelX1Hit, neighborRay.dir, temporalReservoir.filmSample, true ); + X0_dstJ.z /= srcJ; + if (isinf(X0_dstJ.z) || isnan(X0_dstJ.z)) X0_dstJ.z = 0.f; + doFReconnectionShiftBuffer[2 * centralOffset + 1] = float4(X0_dstJ, 0.f); + shiftedPrimaryHitBuffer[2 * centralOffset + 1] = neighborPixelX1Hit.data; + } + + { + HitInfo centerPixelX1Hit = retracePrimaryRay( pixel, params.frameDim, centralReservoir.filmSample, centralReservoir.lensSample, false ); + Ray centerRay = gScene.camera.computeRayThinlensSubPixelRandom( pixel, params.frameDim, centralReservoir.filmSample, centralReservoir.lensSample ); + float3 X0_dstJ = solveLensSampleForPrimaryHit( prevPixel, params.frameDim, centerPixelX1Hit, centerRay.dir, centralReservoir.filmSample, true ); + float centerJ = computePrimaryReconnectionJacobian( pixel, params.frameDim, centerPixelX1Hit, centerRay.dir, centralReservoir.filmSample, false ); + X0_dstJ.z /= centerJ; + if (isinf(X0_dstJ.z) || isnan(X0_dstJ.z)) X0_dstJ.z = 0.f; + doFReconnectionShiftBuffer[2 * centralOffset].xyz = X0_dstJ; + shiftedPrimaryHitBuffer[2 * centralOffset] = centerPixelX1Hit.data; + } + } + } + } + } + + void execute(const uint2 _pixel) + { + const uint2 pixel = _pixel; + + if (any(pixel >= params.frameDim)) return; + + printSetPixel(pixel); + logSetPixel(pixel); + + DoPass(pixel); + } +}; + +cbuffer CB +{ + Pass gPass; +} + + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + gPass.execute(dispatchThreadId.xy); +} diff --git a/Source/Modules/ReSTIRPathTracing/copy_luts.cmake b/Source/Modules/ReSTIRPathTracing/copy_luts.cmake new file mode 100644 index 0000000000..705dad3f9f --- /dev/null +++ b/Source/Modules/ReSTIRPathTracing/copy_luts.cmake @@ -0,0 +1,29 @@ +# copy_luts.cmake (called by the rule) +cmake_path(NORMAL_PATH SRC) +cmake_path(NORMAL_PATH DST) + +if(WIN32) + # Call via cmd so wildcards behave right + # Quiet flags: /NFL (no file list) /NDL (no dir list) /NJH (no job header) /NJS (no job summary) /NP (no progress) + execute_process( + COMMAND cmd /c robocopy + "${SRC}" "${DST}" *.png *.PNG + /S /XO /XN /XC /R:1 /W:1 /MT:32 + /NFL /NDL /NJH /NJS /NP + RESULT_VARIABLE rc + ) + # robocopy returns 0..7 for success + if(NOT rc EQUAL 0 AND NOT rc LESS_EQUAL 7) + message(FATAL_ERROR "robocopy failed, rc=${rc}") + endif() +else() + execute_process( + COMMAND rsync -a --prune-empty-dirs --quiet + --include="*/" --include="*.png" --include="*.PNG" --exclude="*" + "${SRC}/" "${DST}/" + RESULT_VARIABLE rc + ) + if(NOT rc EQUAL 0) + message(FATAL_ERROR "rsync failed, rc=${rc}") + endif() +endif() diff --git a/Source/Mogwai/Mogwai.cpp b/Source/Mogwai/Mogwai.cpp index f9d687dd44..eefe26ad33 100644 --- a/Source/Mogwai/Mogwai.cpp +++ b/Source/Mogwai/Mogwai.cpp @@ -37,6 +37,7 @@ #include "Utils/Timing/TimeReport.h" #include "Utils/Settings/Settings.h" + #include #include @@ -564,6 +565,7 @@ namespace Mogwai } } + void Renderer::unloadScene() { setScene(nullptr); @@ -571,10 +573,15 @@ namespace Mogwai void Renderer::setScene(const ref& pScene) { + mUpdateFlagsConnection = {}; + mUpdateFlags = IScene::UpdateFlags::None; + mpScene = pScene; if (mpScene) { + mUpdateFlagsConnection = mpScene->getUpdateFlagsSignal().connect([&](IScene::UpdateFlags flags) { mUpdateFlags |= flags; }); + const auto& pFbo = getTargetFbo(); float ratio = float(pFbo->getWidth()) / float(pFbo->getHeight()); mpScene->setCameraAspectRatio(ratio); @@ -598,6 +605,7 @@ namespace Mogwai getGlobalClock().setTime(0); } + ref Renderer::getScene() const { return mpScene; @@ -700,14 +708,17 @@ namespace Mogwai // Update scene and camera. if (mpScene) { - auto sceneUpdates = mpScene->update(pRenderContext, getGlobalClock().getTime()); + mUpdateFlags = IScene::UpdateFlags::None; + mpScene->update(pRenderContext, getGlobalClock().getTime()); // Accumulate scene update flags for each graph. // The update flags are passed to the active graph, or accumulated until a graph becomes active to avoid missing updates. for (auto& g : mGraphs) { - g.sceneUpdates |= sceneUpdates; + g.sceneUpdates |= mUpdateFlags; } + + mUpdateFlags = IScene::UpdateFlags::None; } executeActiveGraph(pRenderContext); @@ -820,6 +831,33 @@ namespace Mogwai return -1; } + void Renderer::addOption(std::string_view name, nlohmann::json value) + { + getSettings().addOption(name, value); + } + + void Renderer::addOptions(const nlohmann::json& value) + { + getSettings().addOptions(value); + onOptionsChange(); + } + + void Renderer::addFilteredAttributes(const nlohmann::json& attributes) + { + getSettings().addFilteredAttributes(attributes); + } + + void Renderer::clearOptions() + { + getSettings().clearOptions(); + onOptionsChange(); + } + + void Renderer::clearFilteredAttributes() + { + getSettings().clearFilteredAttributes(); + } + std::string Renderer::getVersionString() { return "Mogwai " + std::to_string(kMajorVersion) + "." + std::to_string(kMinorVersion); diff --git a/Source/Mogwai/Mogwai.h b/Source/Mogwai/Mogwai.h index ee2d2df8a3..98e2009e20 100644 --- a/Source/Mogwai/Mogwai.h +++ b/Source/Mogwai/Mogwai.h @@ -158,6 +158,10 @@ namespace Mogwai }; ref mpScene; + sigs::Connection mUpdateFlagsConnection; ///< Connection to the UpdateFlags signal. + /// SceneUpdateFlags accumulated since last `beginFrame()` + IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; + void addGraph(const ref& pGraph); void setActiveGraph(const ref& pGraph); @@ -186,6 +190,12 @@ namespace Mogwai void markOutput(const std::string& name); size_t findGraph(const std::string_view name); + void addOption(std::string_view name, nlohmann::json value); + void addOptions(const nlohmann::json& value); + void addFilteredAttributes(const nlohmann::json& attributes); + void clearOptions(); + void clearFilteredAttributes(); + AppData mAppData; std::vector mGraphs; diff --git a/Source/Mogwai/MogwaiScripting.cpp b/Source/Mogwai/MogwaiScripting.cpp index 799e116b13..9eb174f6f2 100644 --- a/Source/Mogwai/MogwaiScripting.cpp +++ b/Source/Mogwai/MogwaiScripting.cpp @@ -32,8 +32,10 @@ #include "Utils/Scripting/Scripting.h" #include "Utils/Scripting/ScriptWriter.h" #include "Utils/Settings/Settings.h" + #include #include +#include namespace Mogwai { @@ -205,25 +207,24 @@ namespace Mogwai renderer.def("addOptions", [](Renderer* r, pybind11::dict d = {}) { - r->getSettings().addOptions(d); - r->onOptionsChange(); + r->addOptions(pyjson::to_json(d)); }, "dict"_a = pybind11::dict()); renderer.def("addFilteredAttributes", [](Renderer* r, pybind11::dict d = {}) { - r->getSettings().addFilteredAttributes(d); + r->addFilteredAttributes(pyjson::to_json(d)); }, "dict"_a = pybind11::dict()); renderer.def("addFilteredAttributes", [](Renderer* r, pybind11::list l = pybind11::list{0}) { - r->getSettings().addFilteredAttributes(l); + r->addFilteredAttributes(pyjson::to_json(l)); }, "list"_a = pybind11::list()); renderer.def("clearOptions", [](Renderer* r) { - r->getSettings().clearOptions(); - r->onOptionsChange(); + r->clearOptions(); }); renderer.def("clearFilteredAttributes", [](Renderer* r) { - r->getSettings().clearFilteredAttributes(); + r->clearFilteredAttributes(); }); + } } diff --git a/Source/RenderPasses/AccumulatePass/AccumulatePass.cpp b/Source/RenderPasses/AccumulatePass/AccumulatePass.cpp index 34f569d63c..b6db62ca48 100644 --- a/Source/RenderPasses/AccumulatePass/AccumulatePass.cpp +++ b/Source/RenderPasses/AccumulatePass/AccumulatePass.cpp @@ -138,14 +138,13 @@ void AccumulatePass::execute(RenderContext* pRenderContext, const RenderData& re // Reset accumulation upon all scene changes, except camera jitter and history changes. // TODO: Add UI options to select which changes should trigger reset - if (mpScene) + if (mUpdateFlags != IScene::UpdateFlags::None) { - auto sceneUpdates = mpScene->getUpdates(); - if ((sceneUpdates & ~IScene::UpdateFlags::CameraPropertiesChanged) != IScene::UpdateFlags::None) + if ((mUpdateFlags & ~IScene::UpdateFlags::CameraPropertiesChanged) != IScene::UpdateFlags::None) { reset(); } - if (is_set(sceneUpdates, IScene::UpdateFlags::CameraPropertiesChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::CameraPropertiesChanged)) { auto excluded = Camera::Changes::Jitter | Camera::Changes::History; auto cameraChanges = mpScene->getCamera()->getChanges(); @@ -215,6 +214,8 @@ void AccumulatePass::execute(RenderContext* pRenderContext, const RenderData& re logWarning("AccumulatePass unsupported I/O configuration. The output will be cleared."); pRenderContext->clearUAV(pDst->getUAV().get(), uint4(0)); } + + mUpdateFlags = IScene::UpdateFlags::None; } void AccumulatePass::accumulate(RenderContext* pRenderContext, const ref& pSrc, const ref& pDst) @@ -348,14 +349,23 @@ void AccumulatePass::renderUI(Gui::Widgets& widget) } } -void AccumulatePass::setScene(RenderContext* pRenderContext, const ref& pScene) +void AccumulatePass::setIScene(RenderContext* pRenderContext, const ref& pScene) { - mpScene = pScene; + mUpdateFlagsConnection = {}; + mUpdateFlags = IScene::UpdateFlags::None; + + mpScene = std::move(pScene); + mUpdateFlagsConnection = mpScene->getUpdateFlagsSignal().connect([&](IScene::UpdateFlags flags) { mUpdateFlags |= flags; }); // Reset accumulation when the scene changes. reset(); } +void AccumulatePass::setScene(RenderContext* pRenderContext, const ref& pScene) +{ + setIScene(pRenderContext, ref(pScene)); +} + void AccumulatePass::onHotReload(HotReloadFlags reloaded) { // Reset accumulation if programs changed. diff --git a/Source/RenderPasses/AccumulatePass/AccumulatePass.h b/Source/RenderPasses/AccumulatePass/AccumulatePass.h index aa751addc0..e589aecc13 100644 --- a/Source/RenderPasses/AccumulatePass/AccumulatePass.h +++ b/Source/RenderPasses/AccumulatePass/AccumulatePass.h @@ -100,13 +100,17 @@ class AccumulatePass : public RenderPass ); protected: + void setIScene(RenderContext* pRenderContext, const ref& pScene); void prepareAccumulation(RenderContext* pRenderContext, uint32_t width, uint32_t height); void accumulate(RenderContext* pRenderContext, const ref& pSrc, const ref& pDst); // Internal state /// The current scene (or nullptr if no scene). - ref mpScene; + ref mpScene; + sigs::Connection mUpdateFlagsConnection; ///< Connection to the UpdateFlags signal. + /// IScene::UpdateFlags accumulated since last `beginFrame()` + IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; /// Accumulation programs, one per mode. std::map> mpProgram; diff --git a/Source/RenderPasses/BSDFViewer/BSDFViewer.cs.slang b/Source/RenderPasses/BSDFViewer/BSDFViewer.cs.slang index 7bdfda38cc..d516a53fd2 100644 --- a/Source/RenderPasses/BSDFViewer/BSDFViewer.cs.slang +++ b/Source/RenderPasses/BSDFViewer/BSDFViewer.cs.slang @@ -47,9 +47,9 @@ struct BSDFViewer struct SurfaceData { - ShadingData sd; - BSDFProperties bsdfProperties; - float3 wo; + ShadingData sd = {}; + BSDFProperties bsdfProperties = {}; + float3 wo = {}; }; /** @@ -275,6 +275,7 @@ struct BSDFViewer */ bool generateBSDFSample(const ShadingData sd, const IMaterialInstance mi, inout SampleGenerator sg, out BSDFSample s) { + s = {}; if (params.useDirectionalLight) { // With directional light, disable BSDF sampling and just return a sample in the light's direction. diff --git a/Source/RenderPasses/DLSSPass/NGXWrapper.cpp b/Source/RenderPasses/DLSSPass/NGXWrapper.cpp index 4df1af474a..ba4965638b 100644 --- a/Source/RenderPasses/DLSSPass/NGXWrapper.cpp +++ b/Source/RenderPasses/DLSSPass/NGXWrapper.cpp @@ -344,7 +344,9 @@ bool NGXWrapper::evaluateDLSS( return false; // In DLSS v2, the target is already upsampled (while in v1, the upsampling is handled in a later pass) - FALCOR_ASSERT(pResolvedColor->getWidth() > pUnresolvedColor->getWidth() && pResolvedColor->getHeight() > pUnresolvedColor->getHeight()); + FALCOR_ASSERT( + pResolvedColor->getWidth() >= pUnresolvedColor->getWidth() && pResolvedColor->getHeight() >= pUnresolvedColor->getHeight() + ); bool success = true; diff --git a/Source/RenderPasses/DebugPasses/ColorMapPass/ColorMapPass.ps.slang b/Source/RenderPasses/DebugPasses/ColorMapPass/ColorMapPass.ps.slang index a985fbd35e..7a9548a83a 100644 --- a/Source/RenderPasses/DebugPasses/ColorMapPass/ColorMapPass.ps.slang +++ b/Source/RenderPasses/DebugPasses/ColorMapPass/ColorMapPass.ps.slang @@ -42,7 +42,7 @@ Texture2D gTexture; #error "Invalid format" #endif -static const uint kColorMap = _COLOR_MAP; +static const ColorMap kColorMap = ColorMap(_COLOR_MAP); static const uint kChannel = _CHANNEL; cbuffer StaticCB diff --git a/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cpp b/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cpp index a761504102..daa6b95d72 100644 --- a/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cpp +++ b/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cpp @@ -137,6 +137,10 @@ void GBufferRT::execute(RenderContext* pRenderContext, const RenderData& renderD // Configure depth-of-field. // When DOF is enabled, two PRNG dimensions are used. Pass this info to subsequent passes via the dictionary. mComputeDOF = mUseDOF && mpScene->getCamera()->getApertureRadius() > 0.f; + // some other renderpasses may need to disable DoF in the G/VBuffer + if (renderData.getDictionary().keyExists("disableGBufferDoF") && renderData.getDictionary()["disableGBufferDoF"]) + mComputeDOF = false; + if (mUseDOF) { renderData.getDictionary()[Falcor::kRenderPassPRNGDimension] = mComputeDOF ? 2u : 0u; @@ -237,6 +241,10 @@ void GBufferRT::executeRaytrace(RenderContext* pRenderContext, const RenderData& desc.setMaxPayloadSize(kMaxPayloadSizeBytes); desc.setMaxAttributeSize(mpScene->getRaytracingMaxAttributeSize()); desc.setMaxTraceRecursionDepth(kMaxRecursionDepth); + if (mpDevice->getType() == Device::Type::D3D12) + desc.addCompilerArguments({"-Xdxc", "-disable-payload-qualifiers"}); // TODO WAR: Dxc requires that all fields carry payload access qualifiers. + if (mpDevice->getType() == Device::Type::D3D12 && mpDevice->isFeatureSupported(Device::SupportedFeatures::CoopVector)) + desc.setShaderModel(ShaderModel::SM6_9); // Required for CoopVector on D3D12 devices ref sbt = RtBindingTable::create(1, 1, mpScene->getGeometryCount()); sbt->setRayGen(desc.addRayGen("rayGen")); @@ -297,6 +305,8 @@ void GBufferRT::executeCompute(RenderContext* pRenderContext, const RenderData& desc.addShaderModules(mpScene->getShaderModules()); desc.addShaderLibrary(kProgramComputeFile).csEntry("main"); desc.addTypeConformances(mpScene->getTypeConformances()); + if (mpDevice->getType() == Device::Type::D3D12 && mpDevice->isFeatureSupported(Device::SupportedFeatures::CoopVector)) + desc.setShaderModel(ShaderModel::SM6_9); // Required for CoopVector on D3D12 devices DefineList defines; defines.add(mpScene->getSceneDefines()); diff --git a/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.cpp b/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.cpp index 1b8a89e868..581a892839 100644 --- a/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.cpp +++ b/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.cpp @@ -203,6 +203,10 @@ void GBufferRaster::execute(RenderContext* pRenderContext, const RenderData& ren desc.addShaderModules(mpScene->getShaderModules()); desc.addShaderLibrary(kGBufferPassProgramFile).vsEntry("vsMain").psEntry("psMain"); desc.addTypeConformances(mpScene->getTypeConformances()); + if (mpDevice->getType() == Device::Type::D3D12) + desc.addCompilerArguments({"-Xdxc", "-disable-payload-qualifiers"}); // TODO WAR: Dxc requires that all fields carry payload access qualifiers. + if (mpDevice->getType() == Device::Type::D3D12 && mpDevice->isFeatureSupported(Device::SupportedFeatures::CoopVector)) + desc.setShaderModel(ShaderModel::SM6_9); // Required for CoopVector on D3D12 devices mGBufferPass.pProgram = Program::create(mpDevice, desc, mpScene->getSceneDefines()); mGBufferPass.pState->setProgram(mGBufferPass.pProgram); diff --git a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cpp b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cpp index 72f53bb68a..afe9c6091d 100644 --- a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cpp +++ b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cpp @@ -110,6 +110,10 @@ void VBufferRT::execute(RenderContext* pRenderContext, const RenderData& renderD // Configure depth-of-field. // When DOF is enabled, two PRNG dimensions are used. Pass this info to subsequent passes via the dictionary. mComputeDOF = mUseDOF && mpScene->getCamera()->getApertureRadius() > 0.f; + // some other renderpasses may need to disable DoF in the G/VBuffer + if (renderData.getDictionary().keyExists("disableGBufferDoF") && renderData.getDictionary()["disableGBufferDoF"]) + mComputeDOF = false; + if (mUseDOF) { renderData.getDictionary()[Falcor::kRenderPassPRNGDimension] = mComputeDOF ? 2u : 0u; diff --git a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.slang b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.slang index e7ef80ad96..e4dfd34dbd 100644 --- a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.slang +++ b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.slang @@ -73,6 +73,7 @@ struct VBufferRT VertexData v; float depth = 1.f; float3 prevPosW = float3(0.f); + GeometryInstanceID instanceID = GeometryInstanceID(0); float2 motionVector = float2(0.f); if (hit.getType() == HitType::Triangle) @@ -80,27 +81,35 @@ struct VBufferRT const TriangleHit triangleHit = hit.getTriangleHit(); v = gScene.getVertexData(triangleHit); prevPosW = gScene.getPrevPosW(triangleHit); + instanceID = triangleHit.instanceID; } else if (hit.getType() == HitType::DisplacedTriangle) { const DisplacedTriangleHit displacedTriangleHit = hit.getDisplacedTriangleHit(); v = gScene.getVertexData(displacedTriangleHit, -rayDir); prevPosW = gScene.getPrevPosW(displacedTriangleHit); + instanceID = displacedTriangleHit.instanceID; } else if (hit.getType() == HitType::Curve) { const CurveHit curveHit = hit.getCurveHit(); v = gScene.getVertexDataFromCurve(curveHit); - prevPosW = gScene.getPrevPosWFromCurve(curveHit); + prevPosW = v.posW;//gScene.getPrevPosWFromCurve(curveHit); <- has GPU crash + instanceID = curveHit.instanceID; } else if (hit.getType() == HitType::SDFGrid) { const SDFGridHit sdfGridHit = hit.getSDFGridHit(); v = gScene.getVertexDataFromSDFGrid(sdfGridHit, rayOrigin, rayDir); - prevPosW = gScene.getPrevPosWFromSDFGrid(sdfGridHit, v.posW); + instanceID = sdfGridHit.instanceID; } + const GeometryInstanceData instance = gScene.getGeometryInstance(instanceID); + const float4x4 prevWorldMat = gScene.loadPrevWorldMatrix(instance.globalMatrixID); + const float4x4 curWorldMat = gScene.loadWorldMatrix(instance.globalMatrixID); + bool hasMoved = any(prevWorldMat != curWorldMat) || instance.isDynamic(); + if (hit.getType() == HitType::Triangle || hit.getType() == HitType::DisplacedTriangle || hit.getType() == HitType::Curve || hit.getType() == HitType::SDFGrid) { @@ -129,8 +138,20 @@ struct VBufferRT if (is_valid(gDepth)) gDepth[pixel] = 1.f; - if (is_valid(gMotionVector)) - gMotionVector[pixel] = {}; + if (is_valid(gMotionVector)) // also need to calculate motion vector + { + // Compute motion vector. + float2 pixelPos = pixel + float2(0.5f, 0.5f); + // view direction? + // the scale is arbitrary, just to avoid adding a small number to a large number + float scale = max(1, max(max(gScene.camera.data.prevPosW.x, gScene.camera.data.prevPosW.y), gScene.camera.data.prevPosW.z)); + // project to the previous env dir + float3 prevRayDir = gScene.envMap.toWorld(gScene.envMap.toLocal(rayDir, false), true); + float3 virtualEnvMapPosition = gScene.camera.data.prevPosW + scale * prevRayDir; + float4 prevPosH = mul(gScene.camera.data.prevViewProjMatNoJitter, float4(virtualEnvMapPosition, 1.f)); + float2 motionVector = calcMotionVector(pixelPos, prevPosH, frameDim) + float2(gScene.camera.data.jitterX, -gScene.camera.data.jitterY); + gMotionVector[pixel] = motionVector; // value will be wrong if no envmap exists + } if (is_valid(gMask)) gMask[pixel] = 0.0f; } diff --git a/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.rt.slang b/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.rt.slang index df2b53f243..d8d77461ba 100644 --- a/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.rt.slang +++ b/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.rt.slang @@ -297,7 +297,7 @@ float3 evalDirectAnalytic(const ShadingData sd, const IMaterialInstance mi, inou bool generateScatterRay(const ShadingData sd, const IMaterialInstance mi, bool isCurveHit, float3 rayOrigin, inout ScatterRayData rayData) { // Sample material. - BSDFSample bsdfSample; + BSDFSample bsdfSample = {}; if (mi.sample(sd, rayData.sg, bsdfSample, kUseImportanceSampling)) { rayData.origin = rayOrigin; diff --git a/Source/RenderPasses/OptixDenoiser/ConvertNormalsToBuf.cs.slang b/Source/RenderPasses/OptixDenoiser/ConvertNormalsToBuf.cs.slang index 14b317054d..5b368630ad 100644 --- a/Source/RenderPasses/OptixDenoiser/ConvertNormalsToBuf.cs.slang +++ b/Source/RenderPasses/OptixDenoiser/ConvertNormalsToBuf.cs.slang @@ -39,7 +39,7 @@ void main(uint3 thrdId: SV_DispatchThreadID) { uint bufIdx = thrdId.x + thrdId.y * gStride; - float3 normal = (gInTex[thrdId.xy].xyz - 0.5f) * 2.0f; + float3 normal = gInTex[thrdId.xy].xyz; if (length(normal) > 0.01) { normal = mul(gViewIT, float4(normal, 0.0f)).xyz; diff --git a/Source/RenderPasses/OptixDenoiser/OptixDenoiser.cpp b/Source/RenderPasses/OptixDenoiser/OptixDenoiser.cpp index 7436e88bc7..2cf4e97992 100644 --- a/Source/RenderPasses/OptixDenoiser/OptixDenoiser.cpp +++ b/Source/RenderPasses/OptixDenoiser/OptixDenoiser.cpp @@ -86,7 +86,9 @@ OptixDenoiser_::OptixDenoiser_(ref pDevice, const Properties& props) : R else if (key == kBlend) mDenoiser.params.blendFactor = value; else if (key == kDenoiseAlpha) - mDenoiser.params.denoiseAlpha = (value ? 1u : 0u); + mDenoiser.options.denoiseAlpha = + (value ? OptixDenoiserAlphaMode::OPTIX_DENOISER_ALPHA_MODE_DENOISE : OptixDenoiserAlphaMode::OPTIX_DENOISER_ALPHA_MODE_COPY + ); else logWarning("Unknown property '{}' in a OptixDenoiser properties.", key); } @@ -105,7 +107,7 @@ Properties OptixDenoiser_::getProperties() const props[kEnabled] = mEnabled; props[kBlend] = mDenoiser.params.blendFactor; props[kModel] = mDenoiser.modelKind; - props[kDenoiseAlpha] = bool(mDenoiser.params.denoiseAlpha > 0); + props[kDenoiseAlpha] = bool(mDenoiser.options.denoiseAlpha > 0); return props; } @@ -207,7 +209,7 @@ void OptixDenoiser_::reallocateStagingBuffers(RenderContext* pRenderContext) // Allocate a guide buffer for our normals (if necessary) if (mDenoiser.options.guideNormal > 0) - allocateStagingBuffer(pRenderContext, mDenoiser.interop.normal, mDenoiser.guideLayer.normal, OPTIX_PIXEL_FORMAT_FLOAT3); + allocateStagingBuffer(pRenderContext, mDenoiser.interop.normal, mDenoiser.guideLayer.normal, OPTIX_PIXEL_FORMAT_FLOAT4); else freeStagingBuffer(mDenoiser.interop.normal, mDenoiser.guideLayer.normal); @@ -439,10 +441,12 @@ void OptixDenoiser_::renderUI(Gui::Widgets& widget) } { - bool denoiseAlpha = mDenoiser.params.denoiseAlpha != 0; + bool denoiseAlpha = mDenoiser.options.denoiseAlpha == OptixDenoiserAlphaMode::OPTIX_DENOISER_ALPHA_MODE_DENOISE; if (widget.checkbox("Denoise Alpha?", denoiseAlpha)) { - mDenoiser.params.denoiseAlpha = denoiseAlpha ? 1u : 0u; + mDenoiser.options.denoiseAlpha = + (denoiseAlpha ? OptixDenoiserAlphaMode::OPTIX_DENOISER_ALPHA_MODE_DENOISE + : OptixDenoiserAlphaMode::OPTIX_DENOISER_ALPHA_MODE_COPY); } widget.tooltip("Denoise the alpha channel, not just RGB."); } @@ -524,7 +528,7 @@ void OptixDenoiser_::convertNormalsToBuf( var["GlobalCB"]["gViewIT"] = viewIT; var["gInTex"] = tex; var["gOutBuf"] = buf; - mpConvertTexToBuf->execute(pRenderContext, size.x, size.y); + mpConvertNormalsToBuf->execute(pRenderContext, size.x, size.y); } void OptixDenoiser_::convertBufToTex(RenderContext* pRenderContext, const ref& buf, const ref& tex, const uint2& size) diff --git a/Source/RenderPasses/OptixDenoiser/OptixDenoiser.h b/Source/RenderPasses/OptixDenoiser/OptixDenoiser.h index 35d7b58140..559526c875 100644 --- a/Source/RenderPasses/OptixDenoiser/OptixDenoiser.h +++ b/Source/RenderPasses/OptixDenoiser/OptixDenoiser.h @@ -167,7 +167,7 @@ class OptixDenoiser_ : public RenderPass OptixDenoiserOptions options = {0u, 0u}; OptixDenoiserModelKind modelKind = OptixDenoiserModelKind::OPTIX_DENOISER_MODEL_KIND_HDR; OptixDenoiser denoiser = nullptr; - OptixDenoiserParams params = {0u, static_cast(0), 0.0f, static_cast(0)}; + OptixDenoiserParams params = {static_cast(0), 0.0f, static_cast(0), 0u}; OptixDenoiserSizes sizes = {}; // TODO: Parameters currently set to false and not exposed to the user. These parameters are here to diff --git a/Source/RenderPasses/PathTracer/CMakeLists.txt b/Source/RenderPasses/PathTracer/CMakeLists.txt index 0da33d54b5..a5b7355f1d 100644 --- a/Source/RenderPasses/PathTracer/CMakeLists.txt +++ b/Source/RenderPasses/PathTracer/CMakeLists.txt @@ -12,12 +12,23 @@ target_sources(PathTracer PRIVATE PathTracer.cpp PathTracer.h PathTracerNRD.slang + PathTracerUtils.slang ReflectTypes.cs.slang ResolvePass.cs.slang + ResolveReSTIRPTPass.cs.slang + ReSTIRPTData.slang + LightTileRISHelper.slang StaticParams.slang TracePass.rt.slang + TracePassMinimalPayload.rt.slang ) target_copy_shaders(PathTracer RenderPasses/PathTracer) target_source_group(PathTracer "RenderPasses") + +target_link_libraries(PathTracer + PRIVATE + ReSTIRPathTracing + LightTileResampling +) diff --git a/Source/RenderPasses/PathTracer/ColorType.slang b/Source/RenderPasses/PathTracer/ColorType.slang index b2c29b8834..6ae64df51b 100644 --- a/Source/RenderPasses/PathTracer/ColorType.slang +++ b/Source/RenderPasses/PathTracer/ColorType.slang @@ -33,28 +33,18 @@ struct ColorType #if COLOR_FORMAT == 0 /* ColorFormat::RGBA32F */ float4 data; - float3 get() - { - return data.rgb; - } + float3 get() { return data.rgb; } - [mutating] void set(float3 color) - { - data = float4(color, 1.f); - } + [mutating] + void set(float3 color) { data = float4(color, 1.f); } #elif COLOR_FORMAT == 1 /* ColorFormat::LogLuvHDR */ uint data; - float3 get() - { - return decodeLogLuvHDR(data); - } + float3 get() { return decodeLogLuvHDR(data); } - [mutating] void set(float3 color) - { - data = encodeLogLuvHDR(color); - } + [mutating] + void set(float3 color) { data = encodeLogLuvHDR(color); } #else #error Invalid COLOR_FORMAT diff --git a/Source/RenderPasses/PathTracer/GeneratePaths.cs.slang b/Source/RenderPasses/PathTracer/GeneratePaths.cs.slang index a5d6fdb18a..830de0d711 100644 --- a/Source/RenderPasses/PathTracer/GeneratePaths.cs.slang +++ b/Source/RenderPasses/PathTracer/GeneratePaths.cs.slang @@ -28,14 +28,19 @@ import Utils.Attributes; import Utils.Color.ColorHelpers; import Rendering.RTXDI.RTXDI; +import Scene.RaytracingInline; import RenderPasses.Shared.Denoising.NRDBuffers; import RenderPasses.Shared.Denoising.NRDConstants; import LoadShadingData; import NRDHelpers; import PathTracer; +import PathTracerUtils; import PathState; import ColorType; import Params; +#if USE_RESTIRPT +import ReSTIRPathTracing.ReSTIRPT; +#endif // Shared memory variables. // TODO: Can we declare these inside PathGenerator? @@ -65,18 +70,21 @@ groupshared uint gSamplesOffset[8 /* kWarpCount */]; struct PathGenerator { // Resources - PathTracerParams params; ///< Runtime parameters. + PathTracerParams params; ///< Runtime parameters. +#if USE_RESTIRPT + ReSTIRPT restirpt; +#endif - Texture2D vbuffer; ///< Fullscreen V-buffer for the primary hits. - Texture2D viewDir; ///< Optional view direction. Only valid when kUseViewDir == true. - Texture2D sampleCount; ///< Optional input sample count buffer. Only valid when kSamplesPerPixel == 0. - RWTexture2D sampleOffset; ///< Output offset into per-sample buffers. Only valid when kSamplesPerPixel == 0. + RWTexture2D vbuffer; ///< Fullscreen V-buffer for the primary hits (RW for film/lens retracing). + Texture2D viewDir; ///< Optional view direction. Only valid when kUseViewDir == true. + Texture2D sampleCount; ///< Optional input sample count buffer. Only valid when kSamplesPerPixel == 0. + RWTexture2D sampleOffset; ///< Output offset into per-sample buffers. Only valid when kSamplesPerPixel == 0. - RWStructuredBuffer sampleColor; ///< Output per-sample color if kSamplesPerPixel != 1. - RWStructuredBuffer sampleGuideData; ///< Output per-sample guide data. - NRDBuffers outputNRD; ///< Output NRD data. + RWStructuredBuffer sampleColor; ///< Output per-sample color if kSamplesPerPixel != 1. + RWStructuredBuffer sampleGuideData; ///< Output per-sample guide data. + NRDBuffers outputNRD; ///< Output NRD data. - RWTexture2D outputColor; ///< Output color buffer if kSamplesPerPixel == 1. + RWTexture2D outputColor; ///< Output color buffer if kSamplesPerPixel == 1. // Render settings that depend on the scene. // TODO: Move into scene defines. @@ -94,8 +102,9 @@ struct PathGenerator { // Map thread to pixel based on Morton order within tile. // The tiles themselves are enumerated in scanline order on screen. - const uint2 tileOffset = tileID << kScreenTileBits; // Tile offset in pixels. - const uint2 pixel = deinterleave_8bit(threadIdx) + tileOffset; // Assumes 16x16 tile or smaller. A host-side assert checks this assumption. + const uint2 tileOffset = tileID << kScreenTileBits; // Tile offset in pixels. + const uint2 pixel = deinterleave_8bit(threadIdx) + tileOffset; // Assumes 16x16 tile or smaller. A host-side assert checks this + // assumption. // Process each pixel. // If we don't hit any surface then the background will be evaluated and written out directly. @@ -111,12 +120,32 @@ struct PathGenerator // TODO: We may want to use a nearest sampler to allow the texture to be of arbitrary dimension. spp = kSamplesPerPixel > 0 ? kSamplesPerPixel : min(sampleCount[pixel], kMaxSamplesPerPixel); - // Compute the primary ray. - cameraRay = gScene.camera.computeRayPinhole(pixel, params.frameDim); - if (kUseViewDir) cameraRay.dir = -viewDir[pixel]; + HitInfo hit = {}; - // Load the primary hit from the V-buffer. - const HitInfo hit = unpackHitInfo(vbuffer[pixel]); +#if USE_RESTIRPT + if (kUseReSTIRPT && restirpt.kUseAreaReservoirs) + { + uint maxSpp = kSamplesPerPixel > 0 ? kSamplesPerPixel : kMaxSamplesPerPixel; + maxSpp = restirpt.sgCount(); + SampleGenerator sg = SampleGenerator(pixel, params.seed * maxSpp); + float4 filmLensSample = float4(sampleNext2D(sg), sampleNext2D(sg)); + cameraRay = gScene.camera.computeRayThinlensSubPixelRandom(pixel, params.frameDim, filmLensSample.xy, filmLensSample.zw); + SceneRayQuery sceneRayQuery; + float hitT; + hit = sceneRayQuery.traceRay(cameraRay, hitT); + vbuffer[pixel] = hit.data; + } + else +#endif + { + // Compute the primary ray. + cameraRay = gScene.camera.computeRayPinhole(pixel, params.frameDim); + if (kUseViewDir) + cameraRay.dir = -viewDir[pixel]; + + // Load the primary hit from the V-buffer. + hit = unpackHitInfo(vbuffer[pixel]); + } hitSurface = hit.isValid(); // Prepare per-pixel surface data for RTXDI. @@ -141,10 +170,18 @@ struct PathGenerator { // RTXDI uses a simple material model with only diffuse and specular reflection lobes. // We query the BSDF for the diffuse albedo and specular reflectance. - gRTXDI.setSurfaceData(pixel, sd.computeRayOrigin(), bsdfProperties.guideNormal, bsdfProperties.diffuseReflectionAlbedo, bsdfProperties.specularReflectance, bsdfProperties.roughness); + gRTXDI.setSurfaceData( + pixel, + sd.computeRayOrigin(), + bsdfProperties.guideNormal, + bsdfProperties.diffuseReflectionAlbedo, + bsdfProperties.specularReflectance, + bsdfProperties.roughness + ); } } - if (!validSurface) gRTXDI.setInvalidSurfaceData(pixel); + if (!validSurface) + gRTXDI.setInvalidSurfaceData(pixel); } } @@ -215,17 +252,23 @@ struct PathGenerator // Write color and denoising guide data for all samples in pixel. // For the special case of fixed 1 spp we write the color directly to the output texture. - if (kSamplesPerPixel == 1) - { - outputColor[pixel] = float4(color, 1.f); - } +#if USE_RESTIRPT + if (!kUseReSTIRPT || !restirpt.kUseAreaReservoirs) +#endif + if (kSamplesPerPixel == 1) + { + outputColor[pixel] = float4(color, 1.f); + } for (uint i = 0; i < spp; i++) { - if (kSamplesPerPixel != 1) - { - sampleColor[outIdx + i].set(color); - } +#if USE_RESTIRPT + if (!kUseReSTIRPT || !restirpt.kUseAreaReservoirs) +#endif + if (kSamplesPerPixel != 1) + { + sampleColor[outIdx + i].set(color); + } if (kOutputGuideData) { @@ -250,9 +293,19 @@ struct PathGenerator if (kOutputNRDAdditionalData) { - writeNRDDeltaReflectionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, -dir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength); + writeNRDDeltaReflectionGuideBuffers( + outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, -dir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength + ); writeNRDDeltaTransmissionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, -dir, 0.f, kNRDInvalidPathLength, 0.f); } + +#if USE_RESTIRPT + if (kUseReSTIRPT && !restirpt.kUseAreaReservoirs) + { + restirpt.pathReservoirs[outIdx].weight = 0; + restirpt.pathReservoirs[outIdx].integrand = 0; + } +#endif } }; @@ -263,9 +316,7 @@ cbuffer CB // TODO: Replace by compile-time uint2 constant when it works in Slang. [numthreads(256 /* kScreenTileDim.x * kScreenTileDim.y */, 1, 1)] -void main( - uint3 groupID : SV_GroupID, - uint3 groupThreadID : SV_GroupThreadID) +void main(uint3 groupID: SV_GroupID, uint3 groupThreadID: SV_GroupThreadID) { gPathGenerator.execute(groupID.xy, groupThreadID.x); } diff --git a/Source/RenderPasses/PathTracer/GuideData.slang b/Source/RenderPasses/PathTracer/GuideData.slang index f839dbeafe..7831883f48 100644 --- a/Source/RenderPasses/PathTracer/GuideData.slang +++ b/Source/RenderPasses/PathTracer/GuideData.slang @@ -31,60 +31,35 @@ import Utils.Math.FormatConversion; static const float kEnvMapDepth = 100000000.0f; // Same constant as defined in GBufferRT.slang /** Struct holding guide data for denoising. -*/ + */ struct GuideData { - uint4 data; - float3 reflectionPos; - float dummy; + uint4 data = {}; + float3 reflectionPos = {}; + float dummy = {}; - float3 getAlbedo() - { - return unpackR11G11B10(data[0]); - } + float3 getAlbedo() { return unpackR11G11B10(data[0]); } - [mutating] void setAlbedo(float3 albedo) - { - data[0] = packR11G11B10(albedo); - } + [mutating] + void setAlbedo(float3 albedo) { data[0] = packR11G11B10(albedo); } - float3 getGuideNormal() - { - return decodeNormal2x16(data[1]); - } + float3 getGuideNormal() { return decodeNormal2x16(data[1]); } - [mutating] void setGuideNormal(float3 normal) - { - data[1] = encodeNormal2x16(normal); - } + [mutating] + void setGuideNormal(float3 normal) { data[1] = encodeNormal2x16(normal); } - float3 getSpecularAlbedo() - { - return unpackR11G11B10(data[2]); - } + float3 getSpecularAlbedo() { return unpackR11G11B10(data[2]); } - [mutating] void setSpecularAlbedo(float3 specular) - { - data[2] = packR11G11B10(specular); - } + [mutating] + void setSpecularAlbedo(float3 specular) { data[2] = packR11G11B10(specular); } - float3 getIndirectAlbedo() - { - return unpackR11G11B10(data[3]); - } + float3 getIndirectAlbedo() { return unpackR11G11B10(data[3]); } - [mutating] void setIndirectAlbedo(float3 albedo) - { - data[3] = packR11G11B10(albedo); - } + [mutating] + void setIndirectAlbedo(float3 albedo) { data[3] = packR11G11B10(albedo); } - float3 getReflectionPos() - { - return reflectionPos; - } + float3 getReflectionPos() { return reflectionPos; } - [mutating] void setReflectionPos(float3 pos) - { - reflectionPos = pos; - } + [mutating] + void setReflectionPos(float3 pos) { reflectionPos = pos; } }; diff --git a/Source/RenderPasses/PathTracer/LightTileRISHelper.slang b/Source/RenderPasses/PathTracer/LightTileRISHelper.slang new file mode 100644 index 0000000000..20824cee20 --- /dev/null +++ b/Source/RenderPasses/PathTracer/LightTileRISHelper.slang @@ -0,0 +1,135 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import PathState; +import Scene.Shading; +import LightTileResampling.Lights; +import LightTileResampling.StreamingRIS; +import PathTracerUtils; +import Params; +import Utils.Geometry.GeometryHelpers; +import Utils.Color.ColorHelpers; + +struct LightTileRISData +{ + float3 selectedBrdf = {}; + float selectedPdf = {}; + float pdfWarpFactor = 1.f; +} + +struct LightTileRIS +{ + static const uint kLightTileCount = LIGHT_TILE_COUNT; + static const uint kLightTileSize = LIGHT_TILE_SIZE; + static const uint kScreenTileSize = SCREEN_TILE_SIZE; + static const uint kInitialLightSampleCount = INITIAL_LIGHT_SAMPLE_COUNT; + + StructuredBuffer lightTileData; + Lights lights; + + int computeInitialLightSampleCount(int vertexIndex) + { + return max(1, kInitialLightSampleCount / (vertexIndex * vertexIndex)); + } + + bool GenerateSample( inout PathState path, ShadingData sd, const IMaterialInstance mi, inout LightSample ls, bool isCurveHit, bool applyScreenSpaceReSTIR, uint neeSeed, PathTracerParams params, inout LightTileRISData risData) + { + int initialLightSampleCount = computeInitialLightSampleCount(path.getVertexIndex()); + + uint2 screenTile = path.getPixel() / kScreenTileSize; + + SampleGenerator tileSg = SampleGenerator(screenTile, params.seed); + uint tileIndex = min(uint(sampleNext1D(tileSg) * kLightTileCount), kLightTileCount - 1); + uint lightTileOffset = tileIndex * kLightTileSize; + + SampleGenerator initialLightSampleSg = SampleGenerator(neeSeed); + RisState risState = RisState::createEmpty(); + + uint stride = (kLightTileSize + initialLightSampleCount - 1) / initialLightSampleCount; + uint offset = min(uint(sampleNext1D(initialLightSampleSg) * stride), stride - 1); + + for (uint i = 0; i < initialLightSampleCount; ++i) + { + uint index = lightTileOffset + (offset + i * stride) % kLightTileSize; + const LoadedLightSample loadedLightSample = LoadedLightSample::createFromPacked(lightTileData[index]); + const EvaluatedLightSample evaluatedLightSample = loadedLightSample.eval(sd.posW); + + float3 brdf = mi.eval(sd, evaluatedLightSample.dir, initialLightSampleSg); + + float sourcePdf = evaluatedLightSample.geomFactor == 0.f ? 0.f : evaluatedLightSample.pdf / evaluatedLightSample.geomFactor; + + float misWeight = 1.f; + float brdfPdf = 0.f; + if (kUseMIS && !applyScreenSpaceReSTIR) + { + brdfPdf = mi.evalPdf(sd, evaluatedLightSample.dir, kUseBSDFSampling); + if (loadedLightSample.lightSample.getKind() == LightKind::Analytic) brdfPdf = 0.f; + misWeight = sourcePdf + brdfPdf == 0.f ? 0.f : evalMIS(initialLightSampleCount, sourcePdf, 1, brdfPdf); + } + + float targetPdf = sourcePdf == 0.f ? 0.f : luminance(brdf * evaluatedLightSample.emission) * misWeight; + sourcePdf = sourcePdf == 0.f ? 1.f : sourcePdf; + + bool selected = streamingResampleStep(risState, loadedLightSample.lightSample, targetPdf, sourcePdf, initialLightSampleSg); + if (selected) + { + risData.selectedBrdf = brdf; + risData.selectedPdf = brdfPdf; + } + } + + risState.weight = risState.weight > 0.f ? (risState.weightSum / risState.M) / risState.weight : 0.f; + + ls.origin = isCurveHit ? path.origin : sd.posW; + ls.origin = isCurveHit ? ls.origin : sd.computeRayOrigin(); + + EvaluatedLightSample evaluatedLightSample = lights.evalLightSample(risState.lightSample, ls.origin); + + ls.dir = evaluatedLightSample.dir; + ls.distance = evaluatedLightSample.distance - Lights::kRayEpsilon; + + ls.lightType = ((uint)risState.lightSample.getKind()) >> 30; + ls.pdf = evaluatedLightSample.geomFactor == 0.f ? 0.f : evaluatedLightSample.pdf / evaluatedLightSample.geomFactor; +#if USE_RESTIRPT + ls.geometryTerm = evaluatedLightSample.geomFactor; +#endif + risData.pdfWarpFactor = 1.f / (risState.weight * ls.pdf); + + ls.Li = lights.evalEmission(risState.lightSample, -ls.dir) / ls.pdf; + +#if USE_RESTIRPT + if (risState.lightSample.getKind() == LightKind::Emissive) + { + ls.barycentrics = risState.lightSample.getEmissiveBarycentrics().yz; + ls.triangleId = risState.lightSample.getEmissiveIndex(); + } +#endif + + bool validSample = risState.weight > 0.f && ls.pdf > 0.f && risState.lightSample.isValid(); + return validSample; + } +} diff --git a/Source/RenderPasses/PathTracer/LoadShadingData.slang b/Source/RenderPasses/PathTracer/LoadShadingData.slang index 90592ff6f5..015872d339 100644 --- a/Source/RenderPasses/PathTracer/LoadShadingData.slang +++ b/Source/RenderPasses/PathTracer/LoadShadingData.slang @@ -33,7 +33,7 @@ import Utils.Math.MathHelpers; import Params; /** Compute derivatives at primary triangle hit. -*/ + */ void computeDerivativesAtPrimaryTriangleHit(const TriangleHit hit, uint2 pixel, uint2 frameDim, out float2 ddx, out float2 ddy) { // Get world space position @@ -52,11 +52,27 @@ void computeDerivativesAtPrimaryTriangleHit(const TriangleHit hit, uint2 pixel, const float4x4 worldMat = gScene.getWorldMatrix(hit.instanceID); const float3x3 worldInvTransposeMat = gScene.getInverseTransposeWorldMatrix(hit.instanceID); const uint3 vertexIndices = gScene.getIndices(hit.instanceID, hit.primitiveIndex); - StaticVertexData vertices[3] = { gScene.getVertex(vertexIndices[0]), gScene.getVertex(vertexIndices[1]), gScene.getVertex(vertexIndices[2]) }; + StaticVertexData vertices[3] = { + gScene.getVertex(vertexIndices[0]), gScene.getVertex(vertexIndices[1]), gScene.getVertex(vertexIndices[2]) + }; float2 dBarydx, dBarydy; float3 unnormalizedN, normals[3]; - prepareRayDiffAtHitPoint(v, vertices, hit.getBarycentricWeights(), rayDir, worldMat, worldInvTransposeMat, rd, unnormalizedN, normals, dBarydx, dBarydy, ddx, ddy); + prepareRayDiffAtHitPoint( + v, + vertices, + hit.getBarycentricWeights(), + rayDir, + worldMat, + worldInvTransposeMat, + rd, + unnormalizedN, + normals, + dBarydx, + dBarydy, + ddx, + ddy + ); } /** Setup vertex data based on hit point. @@ -108,16 +124,18 @@ VertexData loadVertexData(const HitInfo hit, const float3 rayOrigin, const float } /** Determine hints to use when creating the material instance. -*/ + */ uint getMaterialInstanceHints(const HitInfo hit, const bool isPrimary) { uint hints = 0; if (hit.getType() == HitType::Triangle || hit.getType() == HitType::DisplacedTriangle) { #if GBUFFER_ADJUST_SHADING_NORMALS - if (kAdjustShadingNormals || isPrimary) hints |= (uint)MaterialInstanceHints::AdjustShadingNormal; + if (kAdjustShadingNormals || isPrimary) + hints |= (uint)MaterialInstanceHints::AdjustShadingNormal; #else - if (kAdjustShadingNormals && !isPrimary) hints |= (uint)MaterialInstanceHints::AdjustShadingNormal; + if (kAdjustShadingNormals && !isPrimary) + hints |= (uint)MaterialInstanceHints::AdjustShadingNormal; #endif } return hints; diff --git a/Source/RenderPasses/PathTracer/NRDHelpers.slang b/Source/RenderPasses/PathTracer/NRDHelpers.slang index edbfcceb05..1a42297c54 100644 --- a/Source/RenderPasses/PathTracer/NRDHelpers.slang +++ b/Source/RenderPasses/PathTracer/NRDHelpers.slang @@ -35,7 +35,136 @@ import Scene.ShadingData; import Utils.Math.MathHelpers; import PathState; -void setNRDPrimaryHitEmission(NRDBuffers outputNRD, const bool useNRDDemodulation, const PathState path, const uint2 pixel, const bool isPrimaryHit, const float3 emission) +/** Generate an orthonormal basis that tries to align tangent with the view vector. + \param[in] v Unit view vector. + \param[in] n Unit normal vector. + \param[out] t Unit tangent vector. + \param[out] b Unit bitangent vector. +*/ +void buildOrthonormalBasis(float3 v, float3 n, out float3 t, out float3 b) +{ + static const float kEpsilon = 1.e-6f; + + t = abs(1.f + dot(n, v)) < kEpsilon ? float3(n.z, -n.x, n.y) : v; + t -= n * dot(n, t); + t = normalize(t); + b = cross(n, t); +} + +/** Wrap the xform so it operates in local coordinate system aligned with the view vector and normal. + \param[in] p World position of a point on a plane. + \param[in] v Unit view vector. + \param[in] n Unit normal vector. + \param[in] L Local-space xform. + \return T Wrapped world-space xform. +*/ +float4x4 wrapInLocalXform(float3 p, float3 v, float3 n, float4x4 L) +{ + float3 t1, t2; + buildOrthonormalBasis(v, n, t1, t2); + + float3x3 R = float3x3( + t1.x, t2.x, n.x, + t1.y, t2.y, n.y, + t1.z, t2.z, n.z + ); + + // LocalToWorld xform. + float4x4 P = float4x4( + float4(R[0], p.x), + float4(R[1], p.y), + float4(R[2], p.z), + float4(0.f, 0.f, 0.f, 1.f) + ); + + float3x3 invR = transpose(R); + float3 t = -mul(invR, p); + // WorldToLocal xform. + float4x4 invP = float4x4( + float4(invR[0], t.x), + float4(invR[1], t.y), + float4(invR[2], t.z), + float4(0.f, 0.f, 0.f, 1.f) + ); + + float4x4 T = mul(L, invP); + T = mul(P, T); + return T; +} + +/** Generate a reflection xform assuming a flat (zero curvature) reflector. + \param[in] p World position of a point on the reflecting plane. + \param[in] v Unit view vector. + \param[in] n Unit normal vector. + \param[in] originOffset Offset applied to the ray origin after reflection. + \return Reflection xform in world space. +*/ +float4x4 getReflectionXform(float3 p, float3 v, float3 n, float3 originOffset) +{ + // Planar reflection xform (mirror along local z). + float4x4 L = float4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, -1.f, 0.f, + 0.f, 0.f, 0.f, 1.f + ); + + float4x4 originOffsetMat = float4x4( + 1.f, 0.f, 0.f, originOffset.x, + 0.f, 1.f, 0.f, originOffset.y, + 0.f, 0.f, 1.f, originOffset.z, + 0.f, 0.f, 0.f, 1.f + ); + + return mul(originOffsetMat, wrapInLocalXform(p, v, n, L)); +} + +/** Generate a refraction xform assuming a flat (zero curvature) refractor. + \param[in] p World position of a point on the refracting plane. + \param[in] v Unit view vector. + \param[in] n Unit normal vector (assuming that dot(v, n) >= 0). + \param[in] ior Relative index of refraction (transmissive IoR / incident IoR). + \param[in] originOffset Offset applied to the ray origin after refraction. + \return Refraction xform in world space. +*/ +float4x4 getRefractionXform(float3 p, float3 v, float3 n, float ior, float3 originOffset) +{ + const float cos_i = dot(v, n); + // sint = sini / ior + // 1 - cost^2 = (1 - cosi^2) / ior^2 + // 1 - (1 - cosi^2) / ior^2 = cost^2 + const float tmp = mad(mad(cos_i, -cos_i, 1.f), -1.f / (ior * ior), 1.f); + const float cos_t = sqrt(max(0.f, tmp)); + + const float l1 = cos_t == 0.f ? 1.f : cos_i; + const float l2 = cos_t == 0.f ? -1.f : (cos_t * ior); + + // Planar refraction xform. + float4x4 L = float4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, l1 / l2, 0.f, + 0.f, 0.f, 0.f, 1.f + ); + + float4x4 originOffsetMat = float4x4( + 1.f, 0.f, 0.f, originOffset.x, + 0.f, 1.f, 0.f, originOffset.y, + 0.f, 0.f, 1.f, originOffset.z, + 0.f, 0.f, 0.f, 1.f + ); + + return mul(originOffsetMat, wrapInLocalXform(p, v, n, L)); +} + +void setNRDPrimaryHitEmission( + NRDBuffers outputNRD, + const bool useNRDDemodulation, + const PathState path, + const uint2 pixel, + const bool isPrimaryHit, + const float3 emission +) { if (isPrimaryHit && path.getSampleIdx() == 0) { @@ -52,7 +181,15 @@ void setNRDPrimaryHitEmission(NRDBuffers outputNRD, const bool useNRDDemodulatio } } -void setNRDPrimaryHitReflectance(NRDBuffers outputNRD, const bool useNRDDemodulation, const PathState path, const uint2 pixel, const bool isPrimaryHit, const ShadingData sd, const BSDFProperties bsdfProperties) +void setNRDPrimaryHitReflectance( + NRDBuffers outputNRD, + const bool useNRDDemodulation, + const PathState path, + const uint2 pixel, + const bool isPrimaryHit, + const ShadingData sd, + const BSDFProperties bsdfProperties +) { if (isPrimaryHit && path.getSampleIdx() == 0) { @@ -60,7 +197,8 @@ void setNRDPrimaryHitReflectance(NRDBuffers outputNRD, const bool useNRDDemodula if (useNRDDemodulation) { // Use sum of reflection/transmission albedo as they are denoised together. - const float3 diffuseReflectance = max(kNRDMinReflectance, bsdfProperties.diffuseReflectionAlbedo + bsdfProperties.diffuseTransmissionAlbedo); + const float3 diffuseReflectance = + max(kNRDMinReflectance, bsdfProperties.diffuseReflectionAlbedo + bsdfProperties.diffuseTransmissionAlbedo); outputNRD.primaryHitDiffuseReflectance[pixel] = float4(diffuseReflectance, 1.f); // We use an approximate shading model with a single specular lobe. @@ -87,7 +225,13 @@ void setNRDSampleHitDist(NRDBuffers outputNRD, const PathState path, const uint } } -void setNRDPrimaryHitSeparatedRadiance(NRDBuffers outputNRD, const bool useNRDDemodulation, const PathState path, const bool isPrimaryHit, const uint outSampleIdx) +void setNRDPrimaryHitSeparatedRadiance( + NRDBuffers outputNRD, + const bool useNRDDemodulation, + const PathState path, + const bool isPrimaryHit, + const uint outSampleIdx +) { if (isPrimaryHit) { @@ -102,7 +246,15 @@ void setNRDPrimaryHitSeparatedRadiance(NRDBuffers outputNRD, const bool useNRDDe } } -void setNRDSampleEmission(NRDBuffers outputNRD, const bool useNRDDemodulation, const PathState path, const uint outSampleIdx, const bool isPrimaryHit, const float3 emission, const bool wasDeltaOnlyPathBeforeScattering) +void setNRDSampleEmission( + NRDBuffers outputNRD, + const bool useNRDDemodulation, + const PathState path, + const uint outSampleIdx, + const bool isPrimaryHit, + const float3 emission, + const bool wasDeltaOnlyPathBeforeScattering +) { if (useNRDDemodulation) { @@ -129,7 +281,17 @@ void setNRDSampleEmission(NRDBuffers outputNRD, const bool useNRDDemodulation, c } } -void setNRDSampleReflectance(NRDBuffers outputNRD, const bool useNRDDemodulation, const PathState path, const uint outSampleIdx, const bool isPrimaryHit, const ShadingData sd, const BSDFProperties bsdfProperties, const uint lobeTypes, const bool wasDeltaOnlyPathBeforeScattering) +void setNRDSampleReflectance( + NRDBuffers outputNRD, + const bool useNRDDemodulation, + const PathState path, + const uint outSampleIdx, + const bool isPrimaryHit, + const ShadingData sd, + const BSDFProperties bsdfProperties, + const uint lobeTypes, + const bool wasDeltaOnlyPathBeforeScattering +) { // Demodulate reflectance. if (useNRDDemodulation) @@ -143,7 +305,8 @@ void setNRDSampleReflectance(NRDBuffers outputNRD, const bool useNRDDemodulation if (path.isDiffusePrimaryHit()) { // Use sum of reflection/transmission albedo as they are denoised together. - const float3 diffuseReflectance = max(kNRDMinReflectance, bsdfProperties.diffuseReflectionAlbedo + bsdfProperties.diffuseTransmissionAlbedo); + const float3 diffuseReflectance = + max(kNRDMinReflectance, bsdfProperties.diffuseReflectionAlbedo + bsdfProperties.diffuseTransmissionAlbedo); outputNRD.sampleReflectance[outSampleIdx] = float4(diffuseReflectance, 1.f); } else if (path.isSpecularPrimaryHit()) @@ -188,7 +351,17 @@ void setNRDSampleReflectance(NRDBuffers outputNRD, const bool useNRDDemodulation /** Write out delta reflection guide buffers. Executed only for guide paths. */ -void writeNRDDeltaReflectionGuideBuffers(NRDBuffers outputNRD, const bool useNRDDemodulation, const uint2 pixelPos, float3 reflectance, float3 emission, float3 normal, float roughness, float pathLength, float hitDist) +void writeNRDDeltaReflectionGuideBuffers( + NRDBuffers outputNRD, + const bool useNRDDemodulation, + const uint2 pixelPos, + float3 reflectance, + float3 emission, + float3 normal, + float roughness, + float pathLength, + float hitDist +) { emission = useNRDDemodulation ? emission : 0.f; reflectance = useNRDDemodulation ? max(kNRDMinReflectance, reflectance) : 1.f; @@ -208,7 +381,17 @@ void writeNRDDeltaReflectionGuideBuffers(NRDBuffers outputNRD, const bool useNRD /** Write out delta transmission guide buffers. Executed only for guide paths. */ -void writeNRDDeltaTransmissionGuideBuffers(NRDBuffers outputNRD, const bool useNRDDemodulation, const uint2 pixelPos, float3 reflectance, float3 emission, float3 normal, float roughness, float pathLength, float3 posW) +void writeNRDDeltaTransmissionGuideBuffers( + NRDBuffers outputNRD, + const bool useNRDDemodulation, + const uint2 pixelPos, + float3 reflectance, + float3 emission, + float3 normal, + float roughness, + float pathLength, + float3 posW +) { emission = useNRDDemodulation ? emission : 0.f; reflectance = useNRDDemodulation ? max(kNRDMinReflectance, reflectance) : 1.f; @@ -224,3 +407,36 @@ void writeNRDDeltaTransmissionGuideBuffers(NRDBuffers outputNRD, const bool useN outputNRD.deltaTransmissionPathLength[pixelPos] = pathLength; outputNRD.deltaTransmissionPosW[pixelPos] = float4(posW, 0.f); } + +float2 calcImageMotionVector(const PathState path, const uint2 frameDim, const HitInfo primaryHit) +{ + // Transform the last vertex's previous-frame world position into the virtual-image space of the + // primary hit using the accumulated reflection/refraction transforms along the delta path. + float3 prevPosW = gScene.getPrevPosW(path.hit); + float4 prevImagePosW = mul(path.imageXform, float4(prevPosW, 1.f)); + prevImagePosW /= prevImagePosW.w; + + // Do the same for the current position. + float3 currPosW = path.origin; + float4 currImagePosW = mul(path.imageXform, float4(currPosW, 1.f)); + currImagePosW /= currImagePosW.w; + + float4 currPosH = mul(gScene.camera.data.viewProjMatNoJitter, float4(currImagePosW.xyz, 1.f)); + float2 currCrd = currPosH.xy / currPosH.w; +#ifdef FALCOR_FLIP_Y + currCrd *= float2(0.5, 0.5); +#else + currCrd *= float2(0.5, -0.5); +#endif + currCrd += 0.5f; + + float2 pixelPos = currCrd * frameDim; + + // Compute motion vector in screen space. + float4 prevPosH = mul(gScene.camera.data.prevViewProjMatNoJitter, float4(prevImagePosW.xyz, 1.f)); + // The motion vector already contains no jitter with the current computation. + float2 imageMotionVector = calcMotionVector(pixelPos, prevPosH, frameDim); + + return imageMotionVector; +} + diff --git a/Source/RenderPasses/PathTracer/Params.slang b/Source/RenderPasses/PathTracer/Params.slang index d4607818db..eb0e59c97b 100644 --- a/Source/RenderPasses/PathTracer/Params.slang +++ b/Source/RenderPasses/PathTracer/Params.slang @@ -31,43 +31,51 @@ BEGIN_NAMESPACE_FALCOR /** Per-sample color format. -*/ + */ enum class ColorFormat : uint32_t { - RGBA32F = 0, - LogLuvHDR = 1, + RGBA32F = 0, + LogLuvHDR = 1, }; -FALCOR_ENUM_INFO(ColorFormat, { - { ColorFormat::RGBA32F, "RGBA32F" }, - { ColorFormat::LogLuvHDR, "LogLuvHDR" }, -}); +FALCOR_ENUM_INFO( + ColorFormat, + { + { ColorFormat::RGBA32F, "RGBA32F" }, + { ColorFormat::LogLuvHDR, "LogLuvHDR" }, + } +); FALCOR_ENUM_REGISTER(ColorFormat); enum class MISHeuristic : uint32_t { - Balance = 0, ///< Balance heuristic. - PowerTwo = 1, ///< Power heuristic (exponent = 2.0). - PowerExp = 2, ///< Power heuristic (variable exponent). + Balance = 0, ///< Balance heuristic. + PowerTwo = 1, ///< Power heuristic (exponent = 2.0). + PowerExp = 2, ///< Power heuristic (variable exponent). }; -FALCOR_ENUM_INFO(MISHeuristic, { - { MISHeuristic::Balance, "Balance" }, - { MISHeuristic::PowerTwo, "PowerTwo" }, - { MISHeuristic::PowerExp, "PowerExp" }, -}); +FALCOR_ENUM_INFO( + MISHeuristic, + { + { MISHeuristic::Balance, "Balance" }, + { MISHeuristic::PowerTwo, "PowerTwo" }, + { MISHeuristic::PowerExp, "PowerExp" }, + } +); FALCOR_ENUM_REGISTER(MISHeuristic); // Define tile sizes in pixels. // The frame is divided into tiles stored in scanline order, with pixels in tiles enumerated in Morton order. -static const uint2 kScreenTileDim = { 16, 16 }; ///< Screen-tile dimension in pixels. -static const uint2 kScreenTileBits = { 4, 4 }; ///< Bits needed to describe pixel position within a screen-tile. +static const uint2 kScreenTileDim = { 16, 16 }; ///< Screen-tile dimension in pixels. +static const uint2 kScreenTileBits = { 4, 4 }; ///< Bits needed to describe pixel position within a screen-tile. // Define path configuration limits. -static const uint kMaxSamplesPerPixel = 16; ///< Maximum supported sample count. We can use tiling to support large sample counts if needed. -static const uint kMaxFrameDimension = 4096; ///< Maximum supported frame dimension in pixels along x or y. We can increase the bit allocation if needed. -static const uint kMaxBounces = 254; ///< Maximum supported number of bounces per bounce category (value 255 is reserved for internal use). The resulting path length may be longer than this. -static const uint kMaxLightSamplesPerVertex = 8; ///< Maximum number of shadow rays per path vertex for next-event estimation. +static const uint kMaxSamplesPerPixel = 16; ///< Maximum supported sample count. We can use tiling to support large sample counts if needed. +static const uint kMaxFrameDimension = 4096; ///< Maximum supported frame dimension in pixels along x or y. We can increase the bit + ///< allocation if needed. +static const uint kMaxBounces = 254; ///< Maximum supported number of bounces per bounce category (value 255 is reserved for internal use). + ///< The resulting path length may be longer than this. +static const uint kMaxLightSamplesPerVertex = 8; ///< Maximum number of shadow rays per path vertex for next-event estimation. // Import static specialization constants. #ifndef HOST_CODE @@ -76,21 +84,22 @@ __exported import Utils.Math.BitTricks; #endif /** Path tracer parameters shared between host/device code. -*/ + */ struct PathTracerParams { - int useFixedSeed = false; ///< Use fixed random seed. This is useful for debugging. - uint fixedSeed = 1; ///< The seed to use when 'useFixedSeed' is enabled. - float lodBias = 0.f; ///< LOD bias applied to secondary hits. - float specularRoughnessThreshold = 0.25f; ///< Specular reflection events are only classified as specular if the material's roughness value is equal or smaller than this threshold. Otherwise they are classified diffuse. + int useFixedSeed = false; ///< Use fixed random seed. This is useful for debugging. + uint fixedSeed = 1; ///< The seed to use when 'useFixedSeed' is enabled. + float lodBias = 0.f; ///< LOD bias applied to secondary hits. + float specularRoughnessThreshold = 0.25f; ///< Specular reflection events are only classified as specular if the material's roughness + ///< value is equal or smaller than this threshold. Otherwise they are classified diffuse. // Runtime values - uint2 frameDim = { 0, 0 }; ///< Frame dimension in pixels. - uint2 screenTiles = { 0, 0 }; ///< Number of screen-tiles. Screen tiles may extend outside the frame. + uint2 frameDim = { 0, 0 }; ///< Frame dimension in pixels. + uint2 screenTiles = { 0, 0 }; ///< Number of screen-tiles. Screen tiles may extend outside the frame. - uint frameCount = 0; ///< Frames rendered. This is used as random seed. - uint seed = 0; ///< Random seed. This will get updated from the host depending on settings. - uint2 _pad0; + uint frameCount = 0; ///< Frames rendered. This is used as random seed. + uint seed = 0; ///< Random seed. This will get updated from the host depending on settings. + uint2 _pad0; #ifndef HOST_CODE /** Computes the offset into the tiled sample buffer for a given tile. @@ -106,6 +115,21 @@ struct PathTracerParams return tileIdx * stride; } + /** Computes the reservoir index for a given pixel (used by ReSTIR PT). + \param[in] pixel Pixel on screen. + \return Reservoir index. + */ + uint getReservoirOffset(const uint2 pixel) + { + uint2 tileID = pixel >> kScreenTileBits; + uint stride = kScreenTileDim.x * kScreenTileDim.y; + uint tileIdx = tileID.y * screenTiles.x + tileID.x; + uint tileOffset = tileIdx * stride; + uint tileBits = kScreenTileBits.x + kScreenTileBits.y; + uint pixelIdx = interleave_16bit(pixel) & ((1 << tileBits) - 1); + return tileOffset + pixelIdx; + } + /** Computes the offset into the tiled sample buffer for a given pixel. \param[in] pixel Pixel on screen. \param[in] sampleOffset Per-pixel sample offset within tiles. Only used if kSamplesPerPixel == 0. diff --git a/Source/RenderPasses/PathTracer/PathState.slang b/Source/RenderPasses/PathTracer/PathState.slang index 678c93ec8f..c2e10c5246 100644 --- a/Source/RenderPasses/PathTracer/PathState.slang +++ b/Source/RenderPasses/PathTracer/PathState.slang @@ -25,83 +25,111 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ +#pragma once +#include "Utils/HostDeviceShared.slangh" + +#ifndef HOST_CODE import Utils.Math.PackedFormats; __exported import Scene.HitInfo; __exported import Utils.Math.Ray; __exported import Utils.Sampling.SampleGenerator; __exported import Rendering.Materials.InteriorList; __exported import GuideData; +__exported import ReSTIRPTData; +#endif + +BEGIN_NAMESPACE_FALCOR -static const uint kMaxRejectedHits = 16; // Maximum number of rejected hits along a path. The path is terminated if the limit is reached to avoid getting stuck in pathological cases. +static const uint kMaxRejectedHits = 16; // Maximum number of rejected hits along a path. The path is terminated if the limit is reached to + // avoid getting stuck in pathological cases. static const float kRayTMax = 1e30f; // Be careful with changing these. PathFlags share 32-bit uint with vertexIndex. For now, we keep 10 bits for vertexIndex. // PathFlags take higher bits, VertexIndex takes lower bits. +// The highest 3 bits are reserved for BSDF component index (used by ReSTIR PT). static const uint kVertexIndexBitCount = 10u; static const uint kVertexIndexBitMask = (1u << kVertexIndexBitCount) - 1u; -static const uint kPathFlagsBitCount = 32u - kVertexIndexBitCount; +static const uint kBSDFComponentBitCount = 3u; +static const uint kBSDFComponentBitOffset = 32u - kBSDFComponentBitCount; +static const uint kPathFlagsBitCount = 32u - kBSDFComponentBitCount - kVertexIndexBitCount; static const uint kPathFlagsBitMask = ((1u << kPathFlagsBitCount) - 1u) << kVertexIndexBitCount; +static const uint kPathFlagsLastFlag = 0x10000u; ///< PathFlags::isPrevFrame. + +#ifdef HOST_CODE +static_assert(kPathFlagsLastFlag < (1u << kPathFlagsBitCount), "PathFlags::isPrevFrame must fit in kPathFlagsBitCount bits"); +#endif + +#ifndef HOST_CODE /** Path flags. The path flags are currently stored in kPathFlagsBitCount bits. -*/ + */ enum class PathFlags { - active = 0x0001, ///< Path is active/terminated. - hit = 0x0002, ///< Result of the scatter ray (0 = miss, 1 = hit). - - transmission = 0x0004, ///< Scatter ray went through a transmission event. - specular = 0x0008, ///< Scatter ray went through a specular event. - delta = 0x0010, ///< Scatter ray went through a delta event. - - insideDielectricVolume = 0x0020, ///< Path vertex is inside a dielectric volume. - lightSampledUpper = 0x0040, ///< Last path vertex sampled lights using NEE (in upper hemisphere). - lightSampledLower = 0x0080, ///< Last path vertex sampled lights using NEE (in lower hemisphere). - - diffusePrimaryHit = 0x0100, ///< Scatter ray went through a diffuse event on primary hit. - specularPrimaryHit = 0x0200, ///< Scatter ray went through a specular event on primary hit. - deltaReflectionPrimaryHit = 0x0400, ///< Primary hit was sampled as the delta reflection. - deltaTransmissionPath = 0x0800, ///< Path started with and followed delta transmission events (whenever possible - TIR could be an exception) until it hit the first non-delta event. - deltaOnlyPath = 0x1000, ///< There was no non-delta events along the path so far. - - // Bits 14 to kPathFlagsBitCount are still unused. + active = 0x0001, ///< Path is active/terminated. + hit = 0x0002, ///< Result of the scatter ray (0 = miss, 1 = hit). + + transmission = 0x0004, ///< Scatter ray went through a transmission event. + specular = 0x0008, ///< Scatter ray went through a specular event. + delta = 0x0010, ///< Scatter ray went through a delta event. + + insideDielectricVolume = 0x0020, ///< Path vertex is inside a dielectric volume. + lightSampledUpper = 0x0040, ///< Last path vertex sampled lights using NEE (in upper hemisphere). + lightSampledLower = 0x0080, ///< Last path vertex sampled lights using NEE (in lower hemisphere). + + diffusePrimaryHit = 0x0100, ///< Scatter ray went through a diffuse event on primary hit. + specularPrimaryHit = 0x0200, ///< Scatter ray went through a specular event on primary hit. + deltaReflectionPrimaryHit = 0x0400, ///< Primary hit was sampled as the delta reflection. + deltaTransmissionPath = 0x0800, ///< Path started with and followed delta transmission events (whenever possible - TIR could be an + ///< exception) until it hit the first non-delta event. + deltaOnlyPath = 0x1000, ///< There was no non-delta events along the path so far. + + // ReSTIR PT flags. + rough = 0x2000, ///< Scatter ray went through a "rough" vertex. + supportBSDFIndexing = 0x4000, ///< Material instance supports BSDF indexing. + supportSimpleRoughnessThreshold = 0x8000, ///< Material instance supports simple roughness threshold. + isPrevFrame = kPathFlagsLastFlag, ///< Path is being traced in previous frame data. }; /** Bounce types. We keep separate counters for all of these. -*/ + */ enum class BounceType { - Diffuse = 0, ///< Diffuse reflection. - Specular = 1, ///< Specular reflection (including delta). - Transmission = 2, ///< Transmission (all kinds). + Diffuse = 0, ///< Diffuse reflection. + Specular = 1, ///< Specular reflection (including delta). + Transmission = 2, ///< Transmission (all kinds). }; // TODO: Compact encoding to reduce live registers, e.g. packed HitInfo, packed normals. /** Live state for the path tracer. -*/ + */ struct PathState { - uint id; ///< Path ID encodes (pixel, sampleIdx) with 12 bits each for pixel x|y and 8 bits for sample index. + uint id = {}; ///< Path ID encodes (pixel, sampleIdx) with 12 bits each for pixel x|y and 8 bits for sample index. - uint flagsAndVertexIndex; ///< Higher kPathFlagsBitCount bits: Flags indicating the current status. This can be multiple PathFlags flags OR'ed together. - ///< Lower kVertexIndexBitCount bits: Current vertex index (0 = camera, 1 = primary hit, 2 = secondary hit, etc.). - uint16_t rejectedHits; ///< Number of false intersections rejected along the path. This is used as a safeguard to avoid deadlock in pathological cases. - float16_t sceneLength; ///< Path length in scene units (0.f at primary hit). - uint bounceCounters; ///< Packed counters for different types of bounces (see BounceType). + uint flagsAndVertexIndex = {}; ///< Higher kPathFlagsBitCount bits: Flags indicating the current status. This can be multiple PathFlags + ///< flags OR'ed together. Lower kVertexIndexBitCount bits: Current vertex index (0 = camera, 1 = primary + ///< hit, 2 = secondary hit, etc.). + uint16_t rejectedHits = {}; ///< Number of false intersections rejected along the path. This is used as a safeguard to avoid deadlock in + ///< pathological cases. + float16_t sceneLength = {}; ///< Path length in scene units (0.f at primary hit). + uint bounceCounters = {}; ///< Packed counters for different types of bounces (see BounceType). // Scatter ray - float3 origin; ///< Origin of the scatter ray. - float3 dir; ///< Scatter ray normalized direction. - float pdf; ///< Pdf for generating the scatter ray. - float3 normal; ///< Shading normal at the scatter ray origin. - HitInfo hit; ///< Hit information for the scatter ray. This is populated at committed triangle hits. + float3 origin = {}; ///< Origin of the scatter ray. + float3 dir = {}; ///< Scatter ray normalized direction. + float pdf = {}; ///< Pdf for generating the scatter ray. + float3 normal = {}; ///< Shading normal at the scatter ray origin. + HitInfo hit = {}; ///< Hit information for the scatter ray. This is populated at committed triangle hits. - float3 thp; ///< Path throughput. - float3 L; ///< Accumulated path contribution. + float3 thp = {}; ///< Path throughput. + float3 L = {}; ///< Accumulated path contribution. - GuideData guideData; ///< Denoiser guide data. - InteriorList interiorList; ///< Interior list. Keeping track of a stack of materials with medium properties. - SampleGenerator sg; ///< Sample generator state. Typically 4-16B. + GuideData guideData = {}; ///< Denoiser guide data. + float4x4 imageXform = {}; ///< Accumulated image transform along the path + InteriorList interiorList = {}; ///< Interior list. Keeping track of a stack of materials with medium properties. + SampleGenerator sg = {}; ///< Sample generator state. Typically 4-16B. + ReSTIRPTData restirPTData = {}; ///< ReSTIR PT data. // Accessors @@ -128,30 +156,73 @@ struct PathState bool isDeltaTransmissionPath() { return hasFlag(PathFlags::deltaTransmissionPath); } bool isDeltaOnlyPath() { return hasFlag(PathFlags::deltaOnlyPath); } + bool isRough() { return hasFlag(PathFlags::rough); } + bool supportBSDFIndexing() { return hasFlag(PathFlags::supportBSDFIndexing); } + bool supportSimpleRoughnessThreshold() { return hasFlag(PathFlags::supportSimpleRoughnessThreshold); } + uint getSampledBSDFComponent() { return (flagsAndVertexIndex >> kBSDFComponentBitOffset) & 7; } + bool isPrevFrame() { return hasFlag(PathFlags::isPrevFrame); } + // Check if the scatter event is samplable by the light sampling technique. bool isLightSamplable() { return !isDelta(); } - [mutating] void terminate() { setFlag(PathFlags::active, false); } - [mutating] void setActive() { setFlag(PathFlags::active); } - [mutating] void setHit(HitInfo hitInfo) { hit = hitInfo; setFlag(PathFlags::hit); } - [mutating] void clearHit() { setFlag(PathFlags::hit, false); } + [mutating] + void terminate() { setFlag(PathFlags::active, false); } + [mutating] + void setActive() { setFlag(PathFlags::active); } + [mutating] + void setHit(HitInfo hitInfo) + { + hit = hitInfo; + setFlag(PathFlags::hit); + } + [mutating] + void clearHit() { setFlag(PathFlags::hit, false); } - [mutating] void clearEventFlags() + [mutating] + void clearEventFlags() { const uint bits = (uint(PathFlags::transmission) | uint(PathFlags::specular) | uint(PathFlags::delta)) << kVertexIndexBitCount; flagsAndVertexIndex &= ~bits; } - [mutating] void setTransmission(bool value = true) { setFlag(PathFlags::transmission, value); } - [mutating] void setSpecular(bool value = true) { setFlag(PathFlags::specular, value); } - [mutating] void setDelta(bool value = true) { setFlag(PathFlags::delta, value); } - [mutating] void setInsideDielectricVolume(bool value = true) { setFlag(PathFlags::insideDielectricVolume, value); } - [mutating] void setLightSampled(bool upper, bool lower) { setFlag(PathFlags::lightSampledUpper, upper); setFlag(PathFlags::lightSampledLower, lower); } - [mutating] void setDiffusePrimaryHit(bool value = true) { setFlag(PathFlags::diffusePrimaryHit, value); } - [mutating] void setSpecularPrimaryHit(bool value = true) { setFlag(PathFlags::specularPrimaryHit, value); } - [mutating] void setDeltaReflectionPrimaryHit(bool value = true) { setFlag(PathFlags::deltaReflectionPrimaryHit, value); } - [mutating] void setDeltaTransmissionPath(bool value = true) { setFlag(PathFlags::deltaTransmissionPath, value); } - [mutating] void setDeltaOnlyPath(bool value = true) { setFlag(PathFlags::deltaOnlyPath, value); } + [mutating] + void setTransmission(bool value = true) { setFlag(PathFlags::transmission, value); } + [mutating] + void setSpecular(bool value = true) { setFlag(PathFlags::specular, value); } + [mutating] + void setDelta(bool value = true) { setFlag(PathFlags::delta, value); } + [mutating] + void setInsideDielectricVolume(bool value = true) { setFlag(PathFlags::insideDielectricVolume, value); } + [mutating] + void setLightSampled(bool upper, bool lower) + { + setFlag(PathFlags::lightSampledUpper, upper); + setFlag(PathFlags::lightSampledLower, lower); + } + [mutating] + void setDiffusePrimaryHit(bool value = true) { setFlag(PathFlags::diffusePrimaryHit, value); } + [mutating] + void setSpecularPrimaryHit(bool value = true) { setFlag(PathFlags::specularPrimaryHit, value); } + [mutating] + void setDeltaReflectionPrimaryHit(bool value = true) { setFlag(PathFlags::deltaReflectionPrimaryHit, value); } + [mutating] + void setDeltaTransmissionPath(bool value = true) { setFlag(PathFlags::deltaTransmissionPath, value); } + [mutating] + void setDeltaOnlyPath(bool value = true) { setFlag(PathFlags::deltaOnlyPath, value); } + [mutating] + void setRough(bool value = true) { setFlag(PathFlags::rough, value); } + [mutating] + void setSupportBSDFIndexing(bool value = true) { setFlag(PathFlags::supportBSDFIndexing, value); } + [mutating] + void setSupportSimpleRoughnessThreshold(bool value = true) { setFlag(PathFlags::supportSimpleRoughnessThreshold, value); } + [mutating] + void setSampledBSDFComponent(uint component) + { + flagsAndVertexIndex &= ~(7u << kBSDFComponentBitOffset); + flagsAndVertexIndex |= component << kBSDFComponentBitOffset; + } + [mutating] + void setPrevFrame(bool value = true) { setFlag(PathFlags::isPrevFrame, value); } bool hasFlag(PathFlags flag) { @@ -159,11 +230,14 @@ struct PathState return (flagsAndVertexIndex & bit) != 0; } - [mutating] void setFlag(PathFlags flag, bool value = true) + [mutating] + void setFlag(PathFlags flag, bool value = true) { const uint bit = uint(flag) << kVertexIndexBitCount; - if (value) flagsAndVertexIndex |= bit; - else flagsAndVertexIndex &= ~bit; + if (value) + flagsAndVertexIndex |= bit; + else + flagsAndVertexIndex &= ~bit; } uint getBounces(BounceType type) @@ -172,13 +246,15 @@ struct PathState return (bounceCounters >> shift) & 0xff; } - [mutating] void setBounces(BounceType type, uint bounces) + [mutating] + void setBounces(BounceType type, uint bounces) { const uint shift = (uint)type << 3; bounceCounters = (bounceCounters & ~((uint)0xff << shift)) | ((bounces & 0xff) << shift); } - [mutating] void incrementBounces(BounceType type) + [mutating] + void incrementBounces(BounceType type) { const uint shift = (uint)type << 3; // We assume that bounce counters cannot overflow. @@ -189,7 +265,8 @@ struct PathState uint getSampleIdx() { return id >> 24; } // Unsafe - assumes that index is small enough. - [mutating] void setVertexIndex(uint index) + [mutating] + void setVertexIndex(uint index) { // Clear old vertex index. flagsAndVertexIndex &= kPathFlagsBitMask; @@ -200,12 +277,15 @@ struct PathState uint getVertexIndex() { return flagsAndVertexIndex & kVertexIndexBitMask; } // Unsafe - assumes that vertex index never overflows. - [mutating] void incrementVertexIndex() { flagsAndVertexIndex += 1; } + [mutating] + void incrementVertexIndex() { flagsAndVertexIndex += 1; } // Unsafe - assumes that vertex index will never be decremented below zero. - [mutating] void decrementVertexIndex() { flagsAndVertexIndex -= 1; } + [mutating] + void decrementVertexIndex() { flagsAndVertexIndex -= 1; } - Ray getScatterRay() - { - return Ray(origin, dir, 0.f, kRayTMax); - } + Ray getScatterRay() { return Ray(origin, dir, 0.f, kRayTMax); } }; + +#endif // !HOST_CODE + +END_NAMESPACE_FALCOR diff --git a/Source/RenderPasses/PathTracer/PathTracer.cpp b/Source/RenderPasses/PathTracer/PathTracer.cpp index 2211662647..85ffd05a88 100644 --- a/Source/RenderPasses/PathTracer/PathTracer.cpp +++ b/Source/RenderPasses/PathTracer/PathTracer.cpp @@ -33,125 +33,210 @@ namespace { - const std::string kGeneratePathsFilename = "RenderPasses/PathTracer/GeneratePaths.cs.slang"; - const std::string kTracePassFilename = "RenderPasses/PathTracer/TracePass.rt.slang"; - const std::string kResolvePassFilename = "RenderPasses/PathTracer/ResolvePass.cs.slang"; - const std::string kReflectTypesFile = "RenderPasses/PathTracer/ReflectTypes.cs.slang"; - - // Render pass inputs and outputs. - const std::string kInputVBuffer = "vbuffer"; - const std::string kInputMotionVectors = "mvec"; - const std::string kInputViewDir = "viewW"; - const std::string kInputSampleCount = "sampleCount"; - - const Falcor::ChannelList kInputChannels = - { - { kInputVBuffer, "gVBuffer", "Visibility buffer in packed format" }, - { kInputMotionVectors, "gMotionVectors", "Motion vector buffer (float format)", true /* optional */ }, - { kInputViewDir, "gViewW", "World-space view direction (xyz float format)", true /* optional */ }, - { kInputSampleCount, "gSampleCount", "Sample count buffer (integer format)", true /* optional */, ResourceFormat::R8Uint }, - }; +const std::string kGeneratePathsFilename = "RenderPasses/PathTracer/GeneratePaths.cs.slang"; +const std::string kTracePassFilename = "RenderPasses/PathTracer/TracePass.rt.slang"; +const std::string kTracePassMinimalPayloadFilename = "RenderPasses/PathTracer/TracePassMinimalPayload.rt.slang"; +const std::string kResolvePassFilename = "RenderPasses/PathTracer/ResolvePass.cs.slang"; +const std::string kReflectTypesFile = "RenderPasses/PathTracer/ReflectTypes.cs.slang"; +const std::string kResolveReSTIRPTPassFilename = "RenderPasses/PathTracer/ResolveReSTIRPTPass.cs.slang"; + +// Render pass inputs and outputs. +const std::string kInputVBuffer = "vbuffer"; +const std::string kInputMotionVectors = "mvec"; +const std::string kInputViewDir = "viewW"; +const std::string kInputSampleCount = "sampleCount"; + +const Falcor::ChannelList kInputChannels = { + {kInputVBuffer, "gVBuffer", "Visibility buffer in packed format"}, + {kInputMotionVectors, "gMotionVectors", "Motion vector buffer (float format)", true /* optional */}, + {kInputViewDir, "gViewW", "World-space view direction (xyz float format)", true /* optional */}, + {kInputSampleCount, "gSampleCount", "Sample count buffer (integer format)", true /* optional */, ResourceFormat::R8Uint}, +}; + +const std::string kOutputColor = "color"; +const std::string kOutputAlbedo = "albedo"; +const std::string kOutputSpecularAlbedo = "specularAlbedo"; +const std::string kOutputIndirectAlbedo = "indirectAlbedo"; +const std::string kOutputGuideNormal = "guideNormal"; +const std::string kOutputReflectionPosW = "reflectionPosW"; +const std::string kOutputRayCount = "rayCount"; +const std::string kOutputPathLength = "pathLength"; +const std::string kOutputNRDDiffuseRadianceHitDist = "nrdDiffuseRadianceHitDist"; +const std::string kOutputNRDSpecularRadianceHitDist = "nrdSpecularRadianceHitDist"; +const std::string kOutputNRDEmission = "nrdEmission"; +const std::string kOutputNRDDiffuseReflectance = "nrdDiffuseReflectance"; +const std::string kOutputNRDSpecularReflectance = "nrdSpecularReflectance"; +const std::string kOutputNRDDeltaReflectionRadianceHitDist = "nrdDeltaReflectionRadianceHitDist"; +const std::string kOutputNRDDeltaReflectionReflectance = "nrdDeltaReflectionReflectance"; +const std::string kOutputNRDDeltaReflectionEmission = "nrdDeltaReflectionEmission"; +const std::string kOutputNRDDeltaReflectionNormWRoughMaterialID = "nrdDeltaReflectionNormWRoughMaterialID"; +const std::string kOutputNRDDeltaReflectionPathLength = "nrdDeltaReflectionPathLength"; +const std::string kOutputNRDDeltaReflectionHitDist = "nrdDeltaReflectionHitDist"; +const std::string kOutputNRDDeltaTransmissionRadianceHitDist = "nrdDeltaTransmissionRadianceHitDist"; +const std::string kOutputNRDDeltaTransmissionReflectance = "nrdDeltaTransmissionReflectance"; +const std::string kOutputNRDDeltaTransmissionEmission = "nrdDeltaTransmissionEmission"; +const std::string kOutputNRDDeltaTransmissionNormWRoughMaterialID = "nrdDeltaTransmissionNormWRoughMaterialID"; +const std::string kOutputNRDDeltaTransmissionPathLength = "nrdDeltaTransmissionPathLength"; +const std::string kOutputNRDDeltaTransmissionPosW = "nrdDeltaTransmissionPosW"; +const std::string kOutputNRDResidualRadianceHitDist = "nrdResidualRadianceHitDist"; +const std::string kOutputDuplicationMap = "dupMap"; + +const Falcor::ChannelList kOutputChannels = { + {kOutputColor, "", "Output color (linear)", true /* optional */, ResourceFormat::RGBA32Float}, + {kOutputAlbedo, "", "Output albedo (linear)", true /* optional */, ResourceFormat::RGBA8Unorm}, + {kOutputSpecularAlbedo, "", "Output specular albedo (linear)", true /* optional */, ResourceFormat::RGBA8Unorm}, + {kOutputIndirectAlbedo, "", "Output indirect albedo (linear)", true /* optional */, ResourceFormat::RGBA8Unorm}, + {kOutputGuideNormal, "", "Output guide normal (linear)", true /* optional */, ResourceFormat::RGBA16Float}, + {kOutputReflectionPosW, "", "Output reflection pos (world space)", true /* optional */, ResourceFormat::RGBA32Float}, + {kOutputRayCount, "", "Per-pixel ray count", true /* optional */, ResourceFormat::R32Uint}, + {kOutputPathLength, "", "Per-pixel path length", true /* optional */, ResourceFormat::R32Uint}, + // NRD outputs + {kOutputNRDDiffuseRadianceHitDist, + "", + "Output demodulated diffuse color (linear) and hit distance", + true /* optional */, + ResourceFormat::RGBA32Float}, + {kOutputNRDSpecularRadianceHitDist, + "", + "Output demodulated specular color (linear) and hit distance", + true /* optional */, + ResourceFormat::RGBA32Float}, + {kOutputNRDEmission, "", "Output primary surface emission", true /* optional */, ResourceFormat::RGBA32Float}, + {kOutputNRDDiffuseReflectance, "", "Output primary surface diffuse reflectance", true /* optional */, ResourceFormat::RGBA16Float}, + {kOutputNRDSpecularReflectance, "", "Output primary surface specular reflectance", true /* optional */, ResourceFormat::RGBA16Float}, + {kOutputNRDDeltaReflectionRadianceHitDist, + "", + "Output demodulated delta reflection color (linear)", + true /* optional */, + ResourceFormat::RGBA32Float}, + {kOutputNRDDeltaReflectionReflectance, + "", + "Output delta reflection reflectance color (linear)", + true /* optional */, + ResourceFormat::RGBA16Float}, + {kOutputNRDDeltaReflectionEmission, + "", + "Output delta reflection emission color (linear)", + true /* optional */, + ResourceFormat::RGBA32Float}, + {kOutputNRDDeltaReflectionNormWRoughMaterialID, + "", + "Output delta reflection world normal, roughness, and material ID", + true /* optional */, + ResourceFormat::RGB10A2Unorm}, + {kOutputNRDDeltaReflectionPathLength, "", "Output delta reflection path length", true /* optional */, ResourceFormat::R16Float}, + {kOutputNRDDeltaReflectionHitDist, "", "Output delta reflection hit distance", true /* optional */, ResourceFormat::R16Float}, + {kOutputNRDDeltaTransmissionRadianceHitDist, + "", + "Output demodulated delta transmission color (linear)", + true /* optional */, + ResourceFormat::RGBA32Float}, + {kOutputNRDDeltaTransmissionReflectance, + "", + "Output delta transmission reflectance color (linear)", + true /* optional */, + ResourceFormat::RGBA16Float}, + {kOutputNRDDeltaTransmissionEmission, + "", + "Output delta transmission emission color (linear)", + true /* optional */, + ResourceFormat::RGBA32Float}, + {kOutputNRDDeltaTransmissionNormWRoughMaterialID, + "", + "Output delta transmission world normal, roughness, and material ID", + true /* optional */, + ResourceFormat::RGB10A2Unorm}, + {kOutputNRDDeltaTransmissionPathLength, "", "Output delta transmission path length", true /* optional */, ResourceFormat::R16Float}, + {kOutputNRDDeltaTransmissionPosW, "", "Output delta transmission position", true /* optional */, ResourceFormat::RGBA32Float}, + {kOutputNRDResidualRadianceHitDist, + "", + "Output residual color (linear) and hit distance", + true /* optional */, + ResourceFormat::RGBA32Float}, + {kOutputDuplicationMap, "", "Duplication Map", true /* optional */, ResourceFormat::RG32Float}, +}; + +// Scripting options. +const std::string kSamplesPerPixel = "samplesPerPixel"; +const std::string kMaxSurfaceBounces = "maxSurfaceBounces"; +const std::string kMaxDiffuseBounces = "maxDiffuseBounces"; +const std::string kMaxSpecularBounces = "maxSpecularBounces"; +const std::string kMaxTransmissionBounces = "maxTransmissionBounces"; + +const std::string kSampleGenerator = "sampleGenerator"; +const std::string kFixedSeed = "fixedSeed"; +const std::string kUseBSDFSampling = "useBSDFSampling"; +const std::string kUseRussianRoulette = "useRussianRoulette"; +const std::string kUseNEE = "useNEE"; +const std::string kUseMIS = "useMIS"; +const std::string kMISHeuristic = "misHeuristic"; +const std::string kMISPowerExponent = "misPowerExponent"; +const std::string kEmissiveSampler = "emissiveSampler"; +const std::string kLightBVHOptions = "lightBVHOptions"; +const std::string kUseRTXDI = "useRTXDI"; +const std::string kRTXDIOptions = "RTXDIOptions"; +const std::string kUseReSTIRPT = "useReSTIRPT"; +const std::string kReSTIRPathTracingOptions = "reSTIRPathTracingOptions"; + +const std::string kUseAlphaTest = "useAlphaTest"; +const std::string kAdjustShadingNormals = "adjustShadingNormals"; +const std::string kMaxNestedMaterials = "maxNestedMaterials"; +const std::string kUseLightsInDielectricVolumes = "useLightsInDielectricVolumes"; +const std::string kDisableCaustics = "disableCaustics"; +const std::string kSpecularRoughnessThreshold = "specularRoughnessThreshold"; +const std::string kPrimaryLodMode = "primaryLodMode"; +const std::string kLODBias = "lodBias"; + +const std::string kUseNRDDemodulation = "useNRDDemodulation"; + +const std::string kUseSER = "useSER"; +const std::string kTracePassMode = "tracePassMode"; + +const std::string kOutputSize = "outputSize"; +const std::string kFixedOutputSize = "fixedOutputSize"; +const std::string kColorFormat = "colorFormat"; + +std::string getTracePassFilename(PathTracer::TracePassMode mode) +{ + switch (mode) + { + case PathTracer::TracePassMode::Default: + return kTracePassFilename; + case PathTracer::TracePassMode::MinimalPayload: + return kTracePassMinimalPayloadFilename; + default: + FALCOR_UNREACHABLE(); + } +} - const std::string kOutputColor = "color"; - const std::string kOutputAlbedo = "albedo"; - const std::string kOutputSpecularAlbedo = "specularAlbedo"; - const std::string kOutputIndirectAlbedo = "indirectAlbedo"; - const std::string kOutputGuideNormal = "guideNormal"; - const std::string kOutputReflectionPosW = "reflectionPosW"; - const std::string kOutputRayCount = "rayCount"; - const std::string kOutputPathLength = "pathLength"; - const std::string kOutputNRDDiffuseRadianceHitDist = "nrdDiffuseRadianceHitDist"; - const std::string kOutputNRDSpecularRadianceHitDist = "nrdSpecularRadianceHitDist"; - const std::string kOutputNRDEmission = "nrdEmission"; - const std::string kOutputNRDDiffuseReflectance = "nrdDiffuseReflectance"; - const std::string kOutputNRDSpecularReflectance = "nrdSpecularReflectance"; - const std::string kOutputNRDDeltaReflectionRadianceHitDist = "nrdDeltaReflectionRadianceHitDist"; - const std::string kOutputNRDDeltaReflectionReflectance = "nrdDeltaReflectionReflectance"; - const std::string kOutputNRDDeltaReflectionEmission = "nrdDeltaReflectionEmission"; - const std::string kOutputNRDDeltaReflectionNormWRoughMaterialID = "nrdDeltaReflectionNormWRoughMaterialID"; - const std::string kOutputNRDDeltaReflectionPathLength = "nrdDeltaReflectionPathLength"; - const std::string kOutputNRDDeltaReflectionHitDist = "nrdDeltaReflectionHitDist"; - const std::string kOutputNRDDeltaTransmissionRadianceHitDist = "nrdDeltaTransmissionRadianceHitDist"; - const std::string kOutputNRDDeltaTransmissionReflectance = "nrdDeltaTransmissionReflectance"; - const std::string kOutputNRDDeltaTransmissionEmission = "nrdDeltaTransmissionEmission"; - const std::string kOutputNRDDeltaTransmissionNormWRoughMaterialID = "nrdDeltaTransmissionNormWRoughMaterialID"; - const std::string kOutputNRDDeltaTransmissionPathLength = "nrdDeltaTransmissionPathLength"; - const std::string kOutputNRDDeltaTransmissionPosW = "nrdDeltaTransmissionPosW"; - const std::string kOutputNRDResidualRadianceHitDist = "nrdResidualRadianceHitDist"; - - const Falcor::ChannelList kOutputChannels = - { - { kOutputColor, "", "Output color (linear)", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputAlbedo, "", "Output albedo (linear)", true /* optional */, ResourceFormat::RGBA8Unorm }, - { kOutputSpecularAlbedo, "", "Output specular albedo (linear)", true /* optional */, ResourceFormat::RGBA8Unorm }, - { kOutputIndirectAlbedo, "", "Output indirect albedo (linear)", true /* optional */, ResourceFormat::RGBA8Unorm }, - { kOutputGuideNormal, "", "Output guide normal (linear)", true /* optional */, ResourceFormat::RGBA16Float }, - { kOutputReflectionPosW, "", "Output reflection pos (world space)", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputRayCount, "", "Per-pixel ray count", true /* optional */, ResourceFormat::R32Uint }, - { kOutputPathLength, "", "Per-pixel path length", true /* optional */, ResourceFormat::R32Uint }, - // NRD outputs - { kOutputNRDDiffuseRadianceHitDist, "", "Output demodulated diffuse color (linear) and hit distance", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputNRDSpecularRadianceHitDist, "", "Output demodulated specular color (linear) and hit distance", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputNRDEmission, "", "Output primary surface emission", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputNRDDiffuseReflectance, "", "Output primary surface diffuse reflectance", true /* optional */, ResourceFormat::RGBA16Float }, - { kOutputNRDSpecularReflectance, "", "Output primary surface specular reflectance", true /* optional */, ResourceFormat::RGBA16Float }, - { kOutputNRDDeltaReflectionRadianceHitDist, "", "Output demodulated delta reflection color (linear)", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputNRDDeltaReflectionReflectance, "", "Output delta reflection reflectance color (linear)", true /* optional */, ResourceFormat::RGBA16Float }, - { kOutputNRDDeltaReflectionEmission, "", "Output delta reflection emission color (linear)", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputNRDDeltaReflectionNormWRoughMaterialID, "", "Output delta reflection world normal, roughness, and material ID", true /* optional */, ResourceFormat::RGB10A2Unorm }, - { kOutputNRDDeltaReflectionPathLength, "", "Output delta reflection path length", true /* optional */, ResourceFormat::R16Float }, - { kOutputNRDDeltaReflectionHitDist, "", "Output delta reflection hit distance", true /* optional */, ResourceFormat::R16Float }, - { kOutputNRDDeltaTransmissionRadianceHitDist, "", "Output demodulated delta transmission color (linear)", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputNRDDeltaTransmissionReflectance, "", "Output delta transmission reflectance color (linear)", true /* optional */, ResourceFormat::RGBA16Float }, - { kOutputNRDDeltaTransmissionEmission, "", "Output delta transmission emission color (linear)", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputNRDDeltaTransmissionNormWRoughMaterialID, "", "Output delta transmission world normal, roughness, and material ID", true /* optional */, ResourceFormat::RGB10A2Unorm }, - { kOutputNRDDeltaTransmissionPathLength, "", "Output delta transmission path length", true /* optional */, ResourceFormat::R16Float }, - { kOutputNRDDeltaTransmissionPosW, "", "Output delta transmission position", true /* optional */, ResourceFormat::RGBA32Float }, - { kOutputNRDResidualRadianceHitDist, "", "Output residual color (linear) and hit distance", true /* optional */, ResourceFormat::RGBA32Float }, - }; +PathTracer::TracePassMode getTracePassModeFromFilename(const std::string& shaderLibrary) +{ + if (shaderLibrary == kTracePassMinimalPayloadFilename) + return PathTracer::TracePassMode::MinimalPayload; + return PathTracer::TracePassMode::Default; +} - // Scripting options. - const std::string kSamplesPerPixel = "samplesPerPixel"; - const std::string kMaxSurfaceBounces = "maxSurfaceBounces"; - const std::string kMaxDiffuseBounces = "maxDiffuseBounces"; - const std::string kMaxSpecularBounces = "maxSpecularBounces"; - const std::string kMaxTransmissionBounces = "maxTransmissionBounces"; - - const std::string kSampleGenerator = "sampleGenerator"; - const std::string kFixedSeed = "fixedSeed"; - const std::string kUseBSDFSampling = "useBSDFSampling"; - const std::string kUseRussianRoulette = "useRussianRoulette"; - const std::string kUseNEE = "useNEE"; - const std::string kUseMIS = "useMIS"; - const std::string kMISHeuristic = "misHeuristic"; - const std::string kMISPowerExponent = "misPowerExponent"; - const std::string kEmissiveSampler = "emissiveSampler"; - const std::string kLightBVHOptions = "lightBVHOptions"; - const std::string kUseRTXDI = "useRTXDI"; - const std::string kRTXDIOptions = "RTXDIOptions"; - - const std::string kUseAlphaTest = "useAlphaTest"; - const std::string kAdjustShadingNormals = "adjustShadingNormals"; - const std::string kMaxNestedMaterials = "maxNestedMaterials"; - const std::string kUseLightsInDielectricVolumes = "useLightsInDielectricVolumes"; - const std::string kDisableCaustics = "disableCaustics"; - const std::string kSpecularRoughnessThreshold = "specularRoughnessThreshold"; - const std::string kPrimaryLodMode = "primaryLodMode"; - const std::string kLODBias = "lodBias"; - - const std::string kUseNRDDemodulation = "useNRDDemodulation"; - - const std::string kUseSER = "useSER"; - - const std::string kOutputSize = "outputSize"; - const std::string kFixedOutputSize = "fixedOutputSize"; - const std::string kColorFormat = "colorFormat"; +uint32_t getTracePassMaxPayloadSize(PathTracer::TracePassMode mode, bool useReSTIRPT) +{ + switch (mode) + { + case PathTracer::TracePassMode::Default: + return useReSTIRPT ? 320u : 160u; + case PathTracer::TracePassMode::MinimalPayload: + // The compact pass only returns packed hit info and hit distance through the payload. + // Use a conservative size to cover both compressed and uncompressed PackedHitInfo layouts. + return 20u; + default: + FALCOR_UNREACHABLE(); + } } +} // namespace extern "C" FALCOR_API_EXPORT void registerPlugin(Falcor::PluginRegistry& registry) { registry.registerClass(); ScriptBindings::registerBinding(PathTracer::registerBindings); + ScriptBindings::registerBinding(ReSTIRPathTracing::scriptBindings); } void PathTracer::registerBindings(pybind11::module& m) @@ -160,18 +245,19 @@ void PathTracer::registerBindings(pybind11::module& m) pass.def("reset", &PathTracer::reset); pass.def_property_readonly("pixelStats", &PathTracer::getPixelStats); - pass.def_property("useFixedSeed", + pass.def_property( + "useFixedSeed", [](const PathTracer* pt) { return pt->mParams.useFixedSeed ? true : false; }, [](PathTracer* pt, bool value) { pt->mParams.useFixedSeed = value ? 1 : 0; } ); - pass.def_property("fixedSeed", + pass.def_property( + "fixedSeed", [](const PathTracer* pt) { return pt->mParams.fixedSeed; }, [](PathTracer* pt, uint32_t value) { pt->mParams.fixedSeed = value; } ); } -PathTracer::PathTracer(ref pDevice, const Properties& props) - : RenderPass(pDevice) +PathTracer::PathTracer(ref pDevice, const Properties& props) : RenderPass(pDevice) { if (!mpDevice->isShaderModelSupported(ShaderModel::SM6_5)) FALCOR_THROW("PathTracer requires Shader Model 6.5 support."); @@ -198,12 +284,15 @@ PathTracer::PathTracer(ref pDevice, const Properties& props) void PathTracer::setProperties(const Properties& props) { + const auto prevTracePassMode = mStaticParams.tracePassMode; parseProperties(props); validateOptions(); if (auto lightBVHSampler = dynamic_cast(mpEmissiveSampler.get())) lightBVHSampler->setOptions(mLightBVHOptions); if (mpRTXDI) mpRTXDI->setOptions(mRTXDIOptions); + if (mStaticParams.tracePassMode != prevTracePassMode) + resetPrograms(); mRecompile = true; mOptionsChanged = true; } @@ -213,72 +302,121 @@ void PathTracer::parseProperties(const Properties& props) for (const auto& [key, value] : props) { // Rendering parameters - if (key == kSamplesPerPixel) mStaticParams.samplesPerPixel = value; - else if (key == kMaxSurfaceBounces) mStaticParams.maxSurfaceBounces = value; - else if (key == kMaxDiffuseBounces) mStaticParams.maxDiffuseBounces = value; - else if (key == kMaxSpecularBounces) mStaticParams.maxSpecularBounces = value; - else if (key == kMaxTransmissionBounces) mStaticParams.maxTransmissionBounces = value; + if (key == kSamplesPerPixel) + mStaticParams.samplesPerPixel = value; + else if (key == kMaxSurfaceBounces) + mStaticParams.maxSurfaceBounces = value; + else if (key == kMaxDiffuseBounces) + mStaticParams.maxDiffuseBounces = value; + else if (key == kMaxSpecularBounces) + mStaticParams.maxSpecularBounces = value; + else if (key == kMaxTransmissionBounces) + mStaticParams.maxTransmissionBounces = value; // Sampling parameters - else if (key == kSampleGenerator) mStaticParams.sampleGenerator = value; - else if (key == kFixedSeed) { mParams.fixedSeed = value; mParams.useFixedSeed = true; } - else if (key == kUseBSDFSampling) mStaticParams.useBSDFSampling = value; - else if (key == kUseRussianRoulette) mStaticParams.useRussianRoulette = value; - else if (key == kUseNEE) mStaticParams.useNEE = value; - else if (key == kUseMIS) mStaticParams.useMIS = value; - else if (key == kMISHeuristic) mStaticParams.misHeuristic = value; - else if (key == kMISPowerExponent) mStaticParams.misPowerExponent = value; - else if (key == kEmissiveSampler) mStaticParams.emissiveSampler = value; - else if (key == kLightBVHOptions) mLightBVHOptions = value; - else if (key == kUseRTXDI) mStaticParams.useRTXDI = value; - else if (key == kRTXDIOptions) mRTXDIOptions = value; + else if (key == kSampleGenerator) + mStaticParams.sampleGenerator = value; + else if (key == kFixedSeed) + { + mParams.fixedSeed = value; + mParams.useFixedSeed = true; + } + else if (key == kUseBSDFSampling) + mStaticParams.useBSDFSampling = value; + else if (key == kUseRussianRoulette) + mStaticParams.useRussianRoulette = value; + else if (key == kUseNEE) + mStaticParams.useNEE = value; + else if (key == kUseMIS) + mStaticParams.useMIS = value; + else if (key == kMISHeuristic) + mStaticParams.misHeuristic = value; + else if (key == kMISPowerExponent) + mStaticParams.misPowerExponent = value; + else if (key == kEmissiveSampler) + mStaticParams.emissiveSampler = value; + else if (key == kLightBVHOptions) + mLightBVHOptions = value; + else if (key == kUseRTXDI) + mStaticParams.useRTXDI = value; + else if (key == kRTXDIOptions) + mRTXDIOptions = value; + else if (key == kUseReSTIRPT) + mStaticParams.useReSTIRPT = value; + else if (key == kReSTIRPathTracingOptions) + mReSTIRPathTracingOptions = value; // Material parameters - else if (key == kUseAlphaTest) mStaticParams.useAlphaTest = value; - else if (key == kAdjustShadingNormals) mStaticParams.adjustShadingNormals = value; - else if (key == kMaxNestedMaterials) mStaticParams.maxNestedMaterials = value; - else if (key == kUseLightsInDielectricVolumes) mStaticParams.useLightsInDielectricVolumes = value; - else if (key == kDisableCaustics) mStaticParams.disableCaustics = value; - else if (key == kSpecularRoughnessThreshold) mParams.specularRoughnessThreshold = value; - else if (key == kPrimaryLodMode) mStaticParams.primaryLodMode = value; - else if (key == kLODBias) mParams.lodBias = value; + else if (key == kUseAlphaTest) + mStaticParams.useAlphaTest = value; + else if (key == kAdjustShadingNormals) + mStaticParams.adjustShadingNormals = value; + else if (key == kMaxNestedMaterials) + mStaticParams.maxNestedMaterials = value; + else if (key == kUseLightsInDielectricVolumes) + mStaticParams.useLightsInDielectricVolumes = value; + else if (key == kDisableCaustics) + mStaticParams.disableCaustics = value; + else if (key == kSpecularRoughnessThreshold) + mParams.specularRoughnessThreshold = value; + else if (key == kPrimaryLodMode) + mStaticParams.primaryLodMode = value; + else if (key == kLODBias) + mParams.lodBias = value; // Denoising parameters - else if (key == kUseNRDDemodulation) mStaticParams.useNRDDemodulation = value; + else if (key == kUseNRDDemodulation) + mStaticParams.useNRDDemodulation = value; // Scheduling parameters - else if (key == kUseSER) mStaticParams.useSER = value; + else if (key == kUseSER) + mStaticParams.useSER = value; + else if (key == kTracePassMode) + mStaticParams.tracePassMode = value; // Output parameters - else if (key == kOutputSize) mOutputSizeSelection = value; - else if (key == kFixedOutputSize) mFixedOutputSize = value; - else if (key == kColorFormat) mStaticParams.colorFormat = value; - - else logWarning("Unknown property '{}' in PathTracer properties.", key); + else if (key == kOutputSize) + mOutputSizeSelection = value; + else if (key == kFixedOutputSize) + mFixedOutputSize = value; + else if (key == kColorFormat) + mStaticParams.colorFormat = value; + + else + logWarning("Unknown property '{}' in PathTracer properties.", key); } if (props.has(kMaxSurfaceBounces)) { // Initialize bounce counts to 'maxSurfaceBounces' if they weren't explicitly set. - if (!props.has(kMaxDiffuseBounces)) mStaticParams.maxDiffuseBounces = mStaticParams.maxSurfaceBounces; - if (!props.has(kMaxSpecularBounces)) mStaticParams.maxSpecularBounces = mStaticParams.maxSurfaceBounces; - if (!props.has(kMaxTransmissionBounces)) mStaticParams.maxTransmissionBounces = mStaticParams.maxSurfaceBounces; + if (!props.has(kMaxDiffuseBounces)) + mStaticParams.maxDiffuseBounces = mStaticParams.maxSurfaceBounces; + if (!props.has(kMaxSpecularBounces)) + mStaticParams.maxSpecularBounces = mStaticParams.maxSurfaceBounces; + if (!props.has(kMaxTransmissionBounces)) + mStaticParams.maxTransmissionBounces = mStaticParams.maxSurfaceBounces; } else { // Initialize surface bounces. - mStaticParams.maxSurfaceBounces = std::max(mStaticParams.maxDiffuseBounces, std::max(mStaticParams.maxSpecularBounces, mStaticParams.maxTransmissionBounces)); + mStaticParams.maxSurfaceBounces = + std::max(mStaticParams.maxDiffuseBounces, std::max(mStaticParams.maxSpecularBounces, mStaticParams.maxTransmissionBounces)); } - bool maxSurfaceBouncesNeedsAdjustment = - mStaticParams.maxSurfaceBounces < mStaticParams.maxDiffuseBounces || - mStaticParams.maxSurfaceBounces < mStaticParams.maxSpecularBounces || - mStaticParams.maxSurfaceBounces < mStaticParams.maxTransmissionBounces; + bool maxSurfaceBouncesNeedsAdjustment = mStaticParams.maxSurfaceBounces < mStaticParams.maxDiffuseBounces || + mStaticParams.maxSurfaceBounces < mStaticParams.maxSpecularBounces || + mStaticParams.maxSurfaceBounces < mStaticParams.maxTransmissionBounces; // Show a warning if maxSurfaceBounces will be adjusted in validateOptions(). if (props.has(kMaxSurfaceBounces) && maxSurfaceBouncesNeedsAdjustment) { - logWarning("'{}' is set lower than '{}', '{}' or '{}' and will be increased.", kMaxSurfaceBounces, kMaxDiffuseBounces, kMaxSpecularBounces, kMaxTransmissionBounces); + logWarning( + "'{}' is set lower than '{}', '{}' or '{}' and will be increased.", + kMaxSurfaceBounces, + kMaxDiffuseBounces, + kMaxSpecularBounces, + kMaxTransmissionBounces + ); } } @@ -297,7 +435,7 @@ void PathTracer::validateOptions() mStaticParams.samplesPerPixel = std::clamp(mStaticParams.samplesPerPixel, 1u, kMaxSamplesPerPixel); } - auto clampBounces = [] (uint32_t& bounces, const std::string& name) + auto clampBounces = [](uint32_t& bounces, const std::string& name) { if (bounces > kMaxBounces) { @@ -312,7 +450,8 @@ void PathTracer::validateOptions() clampBounces(mStaticParams.maxTransmissionBounces, kMaxTransmissionBounces); // Make sure maxSurfaceBounces is at least as many as any of diffuse, specular or transmission. - uint32_t minSurfaceBounces = std::max(mStaticParams.maxDiffuseBounces, std::max(mStaticParams.maxSpecularBounces, mStaticParams.maxTransmissionBounces)); + uint32_t minSurfaceBounces = + std::max(mStaticParams.maxDiffuseBounces, std::max(mStaticParams.maxSpecularBounces, mStaticParams.maxTransmissionBounces)); mStaticParams.maxSurfaceBounces = std::max(mStaticParams.maxSurfaceBounces, minSurfaceBounces); if (mStaticParams.primaryLodMode == TexLODMode::RayCones) @@ -346,7 +485,8 @@ Properties PathTracer::getProperties() const // Sampling parameters props[kSampleGenerator] = mStaticParams.sampleGenerator; - if (mParams.useFixedSeed) props[kFixedSeed] = mParams.fixedSeed; + if (mParams.useFixedSeed) + props[kFixedSeed] = mParams.fixedSeed; props[kUseBSDFSampling] = mStaticParams.useBSDFSampling; props[kUseRussianRoulette] = mStaticParams.useRussianRoulette; props[kUseNEE] = mStaticParams.useNEE; @@ -354,9 +494,12 @@ Properties PathTracer::getProperties() const props[kMISHeuristic] = mStaticParams.misHeuristic; props[kMISPowerExponent] = mStaticParams.misPowerExponent; props[kEmissiveSampler] = mStaticParams.emissiveSampler; - if (mStaticParams.emissiveSampler == EmissiveLightSamplerType::LightBVH) props[kLightBVHOptions] = mLightBVHOptions; + if (mStaticParams.emissiveSampler == EmissiveLightSamplerType::LightBVH) + props[kLightBVHOptions] = mLightBVHOptions; props[kUseRTXDI] = mStaticParams.useRTXDI; props[kRTXDIOptions] = mRTXDIOptions; + props[kUseReSTIRPT] = mStaticParams.useReSTIRPT; + props[kReSTIRPathTracingOptions] = mReSTIRPathTracingOptions; // Material parameters props[kUseAlphaTest] = mStaticParams.useAlphaTest; @@ -373,10 +516,12 @@ Properties PathTracer::getProperties() const // Scheduling parameters props[kUseSER] = mStaticParams.useSER; + props[kTracePassMode] = mStaticParams.tracePassMode; // Output parameters props[kOutputSize] = mOutputSizeSelection; - if (mOutputSizeSelection == RenderPassHelpers::IOSize::Fixed) props[kFixedOutputSize] = mFixedOutputSize; + if (mOutputSizeSelection == RenderPassHelpers::IOSize::Fixed) + props[kFixedOutputSize] = mFixedOutputSize; props[kColorFormat] = mStaticParams.colorFormat; return props; @@ -424,8 +569,9 @@ void PathTracer::setScene(RenderContext* pRenderContext, const ref& pScen mParams.frameDim = {}; mParams.screenTiles = {}; - // Need to recreate the RTXDI module when the scene changes. + // Need to recreate modules when the scene changes. mpRTXDI = nullptr; + mpReSTIRPathTracing = nullptr; resetPrograms(); resetLighting(); @@ -446,7 +592,8 @@ void PathTracer::setScene(RenderContext* pRenderContext, const ref& pScen void PathTracer::execute(RenderContext* pRenderContext, const RenderData& renderData) { - if (!beginFrame(pRenderContext, renderData)) return; + if (!beginFrame(pRenderContext, renderData)) + return; // Update shader program specialization. updatePrograms(); @@ -472,14 +619,43 @@ void PathTracer::execute(RenderContext* pRenderContext, const RenderData& render FALCOR_ASSERT(mpTracePass); tracePass(pRenderContext, renderData, *mpTracePass); - // Launch separate passes to trace delta reflection and transmission paths to generate respective guide buffers. - if (mOutputNRDAdditionalData) + // Launch separate passes to trace delta reflection and transmission paths. + // These guide passes populate both the NRD delta-path guide buffers (when requested) and the ReSTIR PT specular + // motion vector buffers (when requested), so we run them if either output is needed. + const bool specularMVecEnabled = mpReSTIRPathTracing && mStaticParams.useReSTIRPT && mpReSTIRPathTracing->getOptions().enableSpecularMotionVectors; + if (mOutputNRDAdditionalData || specularMVecEnabled) { FALCOR_ASSERT(mpTraceDeltaReflectionPass && mpTraceDeltaTransmissionPass); tracePass(pRenderContext, renderData, *mpTraceDeltaReflectionPass); tracePass(pRenderContext, renderData, *mpTraceDeltaTransmissionPass); } + // ReSTIR PT update. + if (mpReSTIRPathTracing && mStaticParams.useReSTIRPT) + { + const auto& pMotionVectors = renderData.getTexture(kInputMotionVectors); + const auto& pVBuffer = renderData.getTexture(kInputVBuffer); + const auto& pColorBuffer = renderData.getTexture(kOutputColor); + mpReSTIRPathTracing->updateReSTIRPT(pRenderContext, pMotionVectors, pVBuffer, pColorBuffer); + + if (mOutputGuideData || mOutputNRDData) + { + mpReSTIRPathTracing->fetchDenoiserData( + pRenderContext, renderData.getTexture(kInputVBuffer), + mpSampleGuideData, + renderData.getTexture(kOutputNRDEmission), + renderData.getTexture(kOutputNRDDiffuseRadianceHitDist), + renderData.getTexture(kOutputNRDSpecularRadianceHitDist), + renderData.getTexture(kOutputNRDDeltaReflectionRadianceHitDist), + renderData.getTexture(kOutputNRDDeltaTransmissionRadianceHitDist), + renderData.getTexture(kOutputNRDResidualRadianceHitDist), + renderData.getTexture(kOutputColor) + ); + } + + resolveReSTIRPTPass(pRenderContext, renderData); + } + // Resolve pass. resolvePass(pRenderContext, renderData); @@ -513,9 +689,13 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) { dirty |= widget.var("Samples/pixel", mStaticParams.samplesPerPixel, 1u, kMaxSamplesPerPixel); } - else widget.text("Samples/pixel: Variable"); - widget.tooltip("Number of samples per pixel. One path is traced for each sample.\n\n" - "When the '" + kInputSampleCount + "' input is connected, the number of samples per pixel is loaded from the texture."); + else + widget.text("Samples/pixel: Variable"); + widget.tooltip( + "Number of samples per pixel. One path is traced for each sample.\n\n" + "When the '" + + kInputSampleCount + "' input is connected, the number of samples per pixel is loaded from the texture." + ); if (widget.var("Max surface bounces", mStaticParams.maxSurfaceBounces, 0u, kMaxBounces)) { @@ -525,8 +705,11 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) mStaticParams.maxTransmissionBounces = std::min(mStaticParams.maxTransmissionBounces, mStaticParams.maxSurfaceBounces); dirty = true; } - widget.tooltip("Maximum number of surface bounces (diffuse + specular + transmission).\n" - "Note that specular reflection events from a material with a roughness greater than specularRoughnessThreshold are also classified as diffuse events."); + widget.tooltip( + "Maximum number of surface bounces (diffuse + specular + transmission).\n" + "Note that specular reflection events from a material with a roughness greater than specularRoughnessThreshold are also classified " + "as diffuse events." + ); dirty |= widget.var("Max diffuse bounces", mStaticParams.maxDiffuseBounces, 0u, kMaxBounces); widget.tooltip("Maximum number of diffuse bounces.\n0 = direct only\n1 = one indirect bounce etc."); @@ -537,6 +720,16 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) dirty |= widget.var("Max transmission bounces", mStaticParams.maxTransmissionBounces, 0u, kMaxBounces); widget.tooltip("Maximum number of transmission bounces.\n0 = no transmission\n1 = one transmission bounce etc."); + if (mStaticParams.useReSTIRPT && (mStaticParams.maxSurfaceBounces != mStaticParams.maxDiffuseBounces || + mStaticParams.maxSurfaceBounces != mStaticParams.maxSpecularBounces || + mStaticParams.maxSurfaceBounces != mStaticParams.maxTransmissionBounces)) + { + widget.textWrapped( + "Warning: ReSTIR PT results can be biased when the maximum surface, diffuse, specular, and transmission bounce counts are not " + "equal." + ); + } + // Sampling options. if (widget.dropdown("Sample generator", SampleGenerator::getGuiDropdownList(), mStaticParams.sampleGenerator)) @@ -546,8 +739,10 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) } dirty |= widget.checkbox("BSDF importance sampling", mStaticParams.useBSDFSampling); - widget.tooltip("BSDF importance sampling should normally be enabled.\n\n" - "If disabled, cosine-weighted hemisphere sampling is used for debugging purposes"); + widget.tooltip( + "BSDF importance sampling should normally be enabled.\n\n" + "If disabled, cosine-weighted hemisphere sampling is used for debugging purposes" + ); dirty |= widget.checkbox("Russian roulette", mStaticParams.useRussianRoulette); widget.tooltip("Use russian roulette to terminate low throughput paths."); @@ -555,11 +750,16 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) dirty |= widget.checkbox("Next-event estimation (NEE)", mStaticParams.useNEE); widget.tooltip("Use next-event estimation.\nThis option enables direct illumination sampling at each path vertex."); + dirty |= widget.checkbox("Use light tile RIS", mStaticParams.useLightTileRIS); + widget.tooltip("Use light tile RIS for direct illumination."); + if (mStaticParams.useNEE) { dirty |= widget.checkbox("Multiple importance sampling (MIS)", mStaticParams.useMIS); - widget.tooltip("When enabled, BSDF sampling is combined with light sampling for the environment map and emissive lights.\n" - "Note that MIS has currently no effect on analytic lights."); + widget.tooltip( + "When enabled, BSDF sampling is combined with light sampling for the environment map and emissive lights.\n" + "Note that MIS has currently no effect on analytic lights." + ); if (mStaticParams.useMIS) { @@ -584,7 +784,8 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) if (mpEmissiveSampler) { - if (mpEmissiveSampler->renderUI(group)) mOptionsChanged = true; + if (mpEmissiveSampler->renderUI(group)) + mOptionsChanged = true; } } } @@ -594,7 +795,21 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) { dirty |= widget.checkbox("Enabled", mStaticParams.useRTXDI); widget.tooltip("Use RTXDI for direct illumination."); - if (mpRTXDI) dirty |= mpRTXDI->renderUI(group); + if (mpRTXDI) + dirty |= mpRTXDI->renderUI(group); + } + + if (mStaticParams.useLightTileRIS && mpLightTileResampling) + { + dirty |= mpLightTileResampling->renderUI(widget); + } + + if (auto group = widget.group("ReSTIR PT")) + { + dirty |= widget.checkbox("Enabled", mStaticParams.useReSTIRPT); + widget.tooltip("Use ReSTIR PT for indirect illumination."); + if (mpReSTIRPathTracing) + dirty |= mpReSTIRPathTracing->renderUI(group); } if (auto group = widget.group("Material controls")) @@ -603,19 +818,29 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) widget.tooltip("Use alpha testing on non-opaque triangles."); dirty |= widget.checkbox("Adjust shading normals on secondary hits", mStaticParams.adjustShadingNormals); - widget.tooltip("Enables adjustment of the shading normals to reduce the risk of black pixels due to back-facing vectors.\nDoes not apply to primary hits which is configured in GBuffer.", true); + widget.tooltip( + "Enables adjustment of the shading normals to reduce the risk of black pixels due to back-facing vectors.\nDoes not apply to " + "primary hits which is configured in GBuffer.", + true + ); dirty |= widget.var("Max nested materials", mStaticParams.maxNestedMaterials, 2u, 4u); widget.tooltip("Maximum supported number of nested materials."); dirty |= widget.checkbox("Use lights in dielectric volumes", mStaticParams.useLightsInDielectricVolumes); - widget.tooltip("Use lights inside of volumes (transmissive materials). We typically don't want this because lights are occluded by the interface."); + widget.tooltip( + "Use lights inside of volumes (transmissive materials). We typically don't want this because lights are occluded by the " + "interface." + ); dirty |= widget.checkbox("Disable caustics", mStaticParams.disableCaustics); widget.tooltip("Disable sampling of caustic light paths (i.e. specular events after diffuse events)."); runtimeDirty |= widget.var("Specular roughness threshold", mParams.specularRoughnessThreshold, 0.f, 1.f); - widget.tooltip("Specular reflection events are only classified as specular if the material's roughness value is equal or smaller than this threshold. Otherwise they are classified diffuse."); + widget.tooltip( + "Specular reflection events are only classified as specular if the material's roughness value is equal or smaller than this " + "threshold. Otherwise they are classified diffuse." + ); dirty |= widget.dropdown("Primary LOD Mode", mStaticParams.primaryLodMode); widget.tooltip("Texture LOD mode at primary hit"); @@ -631,6 +856,17 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) if (auto group = widget.group("Scheduling options")) { + if (group.dropdown("Trace pass", mStaticParams.tracePassMode)) + { + resetPrograms(); + dirty = true; + } + group.tooltip( + "Selects which ray tracing trace shader implementation to use.\n" + "Default (Full Payload) uses a larger payload that allows handling hit in the closest hit shader.\n" + "Minimal Payload keeps path state in raygen and uses a smaller ray payload." + ); + dirty |= widget.checkbox("Use SER", mStaticParams.useSER); widget.tooltip("Use Shader Execution Reordering (SER) to improve GPU utilization."); } @@ -642,17 +878,20 @@ bool PathTracer::renderRenderingUI(Gui::Widgets& widget) // Controls for output size. // When output size requirements change, we'll trigger a graph recompile to update the render pass I/O sizes. - if (widget.dropdown("Output size", mOutputSizeSelection)) requestRecompile(); + if (widget.dropdown("Output size", mOutputSizeSelection)) + requestRecompile(); if (mOutputSizeSelection == RenderPassHelpers::IOSize::Fixed) { - if (widget.var("Size in pixels", mFixedOutputSize, 32u, 16384u)) requestRecompile(); + if (widget.var("Size in pixels", mFixedOutputSize, 32u, 16384u)) + requestRecompile(); } dirty |= widget.dropdown("Color format", mStaticParams.colorFormat); widget.tooltip("Selects the color format used for internal per-sample color and denoiser buffers"); } - if (dirty) mRecompile = true; + if (dirty) + mRecompile = true; return dirty || runtimeDirty; } @@ -663,8 +902,10 @@ bool PathTracer::renderDebugUI(Gui::Widgets& widget) if (auto group = widget.group("Debugging")) { dirty |= group.checkbox("Use fixed seed", mParams.useFixedSeed); - group.tooltip("Forces a fixed random seed for each frame.\n\n" - "This should produce exactly the same image each frame, which can be useful for debugging."); + group.tooltip( + "Forces a fixed random seed for each frame.\n\n" + "This should produce exactly the same image each frame, which can be useful for debugging." + ); if (mParams.useFixedSeed) { dirty |= group.var("Seed", mParams.fixedSeed); @@ -695,9 +936,16 @@ void PathTracer::reset() mParams.frameCount = 0; } -PathTracer::TracePass::TracePass(ref pDevice, const std::string& name, const std::string& passDefine, const ref& pScene, const DefineList& defines, const TypeConformanceList& globalTypeConformances) - : name(name) - , passDefine(passDefine) +PathTracer::TracePass::TracePass( + ref pDevice, + const std::string& name, + const std::string& passDefine, + const std::string& shaderLibrary, + const ref& pScene, + const DefineList& defines, + const TypeConformanceList& globalTypeConformances +) + : name(name), passDefine(passDefine) { const uint32_t kRayTypeScatter = 0; const uint32_t kMissScatter = 0; @@ -706,13 +954,19 @@ PathTracer::TracePass::TracePass(ref pDevice, const std::string& name, c ProgramDesc desc; desc.addShaderModules(pScene->getShaderModules()); - desc.addShaderLibrary(kTracePassFilename); + desc.addShaderLibrary(shaderLibrary); + if (pDevice->getType() == Device::Type::D3D12) + desc.addCompilerArguments({"-Xdxc", "-disable-payload-qualifiers"}); // TODO WAR: Dxc requires that all fields carry payload access qualifiers. if (pDevice->getType() == Device::Type::D3D12 && useSER) - desc.addCompilerArguments({ "-Xdxc", "-enable-lifetime-markers" }); - desc.setMaxPayloadSize(160); // This is conservative but the required minimum is 140 bytes. + desc.addCompilerArguments({"-Xdxc", "-enable-lifetime-markers"}); + if (pDevice->getType() == Device::Type::D3D12 && pDevice->isFeatureSupported(Device::SupportedFeatures::CoopVector)) + desc.setShaderModel(ShaderModel::SM6_9); // Required for CoopVector on D3D12 devices + desc.setMaxPayloadSize(getTracePassMaxPayloadSize(getTracePassModeFromFilename(shaderLibrary), defines.at("USE_RESTIRPT") == "1")); + desc.setMaxAttributeSize(pScene->getRaytracingMaxAttributeSize()); desc.setMaxTraceRecursionDepth(1); - if (!pScene->hasProceduralGeometry()) desc.setRtPipelineFlags(RtPipelineFlags::SkipProceduralPrimitives); + if (!pScene->hasProceduralGeometry()) + desc.setRtPipelineFlags(RtPipelineFlags::SkipProceduralPrimitives); // Create ray tracing binding table. pBindingTable = RtBindingTable::create(1, 1, pScene->getGeometryCount()); @@ -735,14 +989,17 @@ PathTracer::TracePass::TracePass(ref pDevice, const std::string& name, c // Add hit groups for triangles. if (auto geometryIDs = pScene->getGeometryIDs(Scene::GeometryType::TriangleMesh, materialType); !geometryIDs.empty()) { - auto shaderID = desc.addHitGroup("scatterTriangleClosestHit", "scatterTriangleAnyHit", "", typeConformances, to_string(materialType)); + auto shaderID = + desc.addHitGroup("scatterTriangleClosestHit", "scatterTriangleAnyHit", "", typeConformances, to_string(materialType)); pBindingTable->setHitGroup(kRayTypeScatter, geometryIDs, shaderID); } // Add hit groups for displaced triangle meshes. if (auto geometryIDs = pScene->getGeometryIDs(Scene::GeometryType::DisplacedTriangleMesh, materialType); !geometryIDs.empty()) { - auto shaderID = desc.addHitGroup("scatterDisplacedTriangleMeshClosestHit", "", "displacedTriangleMeshIntersection", typeConformances, to_string(materialType)); + auto shaderID = desc.addHitGroup( + "scatterDisplacedTriangleMeshClosestHit", "", "displacedTriangleMeshIntersection", typeConformances, to_string(materialType) + ); pBindingTable->setHitGroup(kRayTypeScatter, geometryIDs, shaderID); } @@ -756,7 +1013,8 @@ PathTracer::TracePass::TracePass(ref pDevice, const std::string& name, c // Add hit groups for SDF grids. if (auto geometryIDs = pScene->getGeometryIDs(Scene::GeometryType::SDFGrid, materialType); !geometryIDs.empty()) { - auto shaderID = desc.addHitGroup("scatterSdfGridClosestHit", "", "sdfGridIntersection", typeConformances, to_string(materialType)); + auto shaderID = + desc.addHitGroup("scatterSdfGridClosestHit", "", "sdfGridIntersection", typeConformances, to_string(materialType)); pBindingTable->setHitGroup(kRayTypeScatter, geometryIDs, shaderID); } } @@ -769,7 +1027,8 @@ void PathTracer::TracePass::prepareProgram(ref pDevice, const DefineList { FALCOR_ASSERT(pProgram != nullptr && pBindingTable != nullptr); pProgram->setDefines(defines); - if (!passDefine.empty()) pProgram->addDefine(passDefine); + if (!passDefine.empty()) + pProgram->addDefine(passDefine); pVars = RtProgramVars::create(pDevice, pProgram, pBindingTable); } @@ -780,6 +1039,7 @@ void PathTracer::resetPrograms() mpTraceDeltaTransmissionPass = nullptr; mpGeneratePaths = nullptr; mpReflectTypes = nullptr; + mpResolveReSTIRPTPass = nullptr; mRecompile = true; } @@ -788,30 +1048,38 @@ void PathTracer::updatePrograms() { FALCOR_ASSERT(mpScene); - if (mRecompile == false) return; + if (mRecompile == false) + return; // If we get here, a change that require recompilation of shader programs has occurred. // This may be due to change of scene defines, type conformances, shader modules, or other changes that require recompilation. - // When type conformances and/or shader modules change, the programs need to be recreated. We assume programs have been reset upon such changes. - // When only defines have changed, it is sufficient to update the existing programs and recreate the program vars. + // When type conformances and/or shader modules change, the programs need to be recreated. We assume programs have been reset upon such + // changes. When only defines have changed, it is sufficient to update the existing programs and recreate the program vars. auto defines = mStaticParams.getDefines(*this); + const auto tracePassFilename = getTracePassFilename(mStaticParams.tracePassMode); TypeConformanceList globalTypeConformances; mpScene->getTypeConformances(globalTypeConformances); // Create trace pass. if (!mpTracePass) - mpTracePass = TracePass::create(mpDevice, "tracePass", "", mpScene, defines, globalTypeConformances); + mpTracePass = TracePass::create(mpDevice, "tracePass", "", tracePassFilename, mpScene, defines, globalTypeConformances); mpTracePass->prepareProgram(mpDevice, defines); // Create specialized trace passes. - if (mOutputNRDAdditionalData) + // Run delta guide trace passes when NRD additional data is requested, or when ReSTIR PT's specular motion vector + // path is enabled (the guide passes are also responsible for writing the specular delta motion vectors). + const bool specularMVecEnabled = mpReSTIRPathTracing && mStaticParams.useReSTIRPT && mpReSTIRPathTracing->getOptions().enableSpecularMotionVectors; + if (mOutputNRDAdditionalData || specularMVecEnabled) { if (!mpTraceDeltaReflectionPass) - mpTraceDeltaReflectionPass = TracePass::create(mpDevice, "traceDeltaReflectionPass", "DELTA_REFLECTION_PASS", mpScene, defines, globalTypeConformances); + mpTraceDeltaReflectionPass = + TracePass::create(mpDevice, "traceDeltaReflectionPass", "DELTA_REFLECTION_PASS", tracePassFilename, mpScene, defines, globalTypeConformances); if (!mpTraceDeltaTransmissionPass) - mpTraceDeltaTransmissionPass = TracePass::create(mpDevice, "traceDeltaTransmissionPass", "DELTA_TRANSMISSION_PASS", mpScene, defines, globalTypeConformances); + mpTraceDeltaTransmissionPass = TracePass::create( + mpDevice, "traceDeltaTransmissionPass", "DELTA_TRANSMISSION_PASS", tracePassFilename, mpScene, defines, globalTypeConformances + ); mpTraceDeltaReflectionPass->prepareProgram(mpDevice, defines); mpTraceDeltaTransmissionPass->prepareProgram(mpDevice, defines); @@ -848,6 +1116,18 @@ void PathTracer::updatePrograms() preparePass(mpResolvePass); preparePass(mpReflectTypes); + if (mpReSTIRPathTracing) + { + if (!mpResolveReSTIRPTPass) + { + ProgramDesc desc = baseDesc; + desc.addShaderLibrary(kResolveReSTIRPTPassFilename).csEntry("main"); + mpResolveReSTIRPTPass = ComputePass::create(mpDevice, desc, defines, false); + } + preparePass(mpResolveReSTIRPTPass); + + mpReSTIRPathTracing->updatePrograms(); + } mVarsChanged = true; mRecompile = false; } @@ -871,7 +1151,15 @@ void PathTracer::prepareResources(RenderContext* pRenderContext, const RenderDat if (!mpSampleOffset || mpSampleOffset->getWidth() != mParams.frameDim.x || mpSampleOffset->getHeight() != mParams.frameDim.y) { FALCOR_ASSERT(kScreenTileDim.x * kScreenTileDim.y * kMaxSamplesPerPixel <= (1u << 16)); - mpSampleOffset = mpDevice->createTexture2D(mParams.frameDim.x, mParams.frameDim.y, ResourceFormat::R16Uint, 1, 1, nullptr, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + mpSampleOffset = mpDevice->createTexture2D( + mParams.frameDim.x, + mParams.frameDim.y, + ResourceFormat::R16Uint, + 1, + 1, + nullptr, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess + ); mVarsChanged = true; } } @@ -884,24 +1172,73 @@ void PathTracer::prepareResources(RenderContext* pRenderContext, const RenderDat { if (!mpSampleColor || mpSampleColor->getElementCount() < sampleCount || mVarsChanged) { - mpSampleColor = mpDevice->createStructuredBuffer(var["sampleColor"], sampleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); + mpSampleColor = mpDevice->createStructuredBuffer( + var["sampleColor"], + sampleCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); mVarsChanged = true; } } if (mOutputGuideData && (!mpSampleGuideData || mpSampleGuideData->getElementCount() < sampleCount || mVarsChanged)) { - mpSampleGuideData = mpDevice->createStructuredBuffer(var["sampleGuideData"], sampleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); + mpSampleGuideData = mpDevice->createStructuredBuffer( + var["sampleGuideData"], + sampleCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); mVarsChanged = true; } if (mOutputNRDData && (!mpSampleNRDRadiance || mpSampleNRDRadiance->getElementCount() < sampleCount || mVarsChanged)) { - mpSampleNRDRadiance = mpDevice->createStructuredBuffer(var["sampleNRDRadiance"], sampleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); - mpSampleNRDHitDist = mpDevice->createStructuredBuffer(var["sampleNRDHitDist"], sampleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); - mpSampleNRDPrimaryHitNeeOnDelta = mpDevice->createStructuredBuffer(var["sampleNRDPrimaryHitNeeOnDelta"], sampleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); - mpSampleNRDEmission = mpDevice->createStructuredBuffer(var["sampleNRDEmission"], sampleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); - mpSampleNRDReflectance = mpDevice->createStructuredBuffer(var["sampleNRDReflectance"], sampleCount, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, nullptr, false); + mpSampleNRDRadiance = mpDevice->createStructuredBuffer( + var["sampleNRDRadiance"], + sampleCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); + mpSampleNRDHitDist = mpDevice->createStructuredBuffer( + var["sampleNRDHitDist"], + sampleCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); + mpSampleNRDPrimaryHitNeeOnDelta = mpDevice->createStructuredBuffer( + var["sampleNRDPrimaryHitNeeOnDelta"], + sampleCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); + mpSampleNRDEmission = mpDevice->createStructuredBuffer( + var["sampleNRDEmission"], + sampleCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); + mpSampleNRDReflectance = mpDevice->createStructuredBuffer( + var["sampleNRDReflectance"], + sampleCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + false + ); mVarsChanged = true; } } @@ -920,6 +1257,14 @@ void PathTracer::preparePathTracer(const RenderData& renderData) // Bind resources. auto var = mpPathTracerBlock->getRootVar(); bindShaderData(var, renderData); + + // Create and bind path tracer block for ReSTIR PT. + if (mStaticParams.useReSTIRPT && mpReSTIRPathTracing) + { + if (mVarsChanged || !mpReSTIRPathTracing->getPathTracerBlock()) + mpReSTIRPathTracing->createPathTracerBlock(); + bindShaderData(mpReSTIRPathTracing->getPathTracerBlock()->getRootVar(), renderData); + } } void PathTracer::resetLighting() @@ -941,8 +1286,7 @@ void PathTracer::prepareMaterials(RenderContext* pRenderContext) // Whenever materials or geometry is added/removed to the scene, we reset the shader programs to trigger // recompilation with the correct defines, type conformances, shader modules, and binding table. - if (is_set(mUpdateFlags, IScene::UpdateFlags::RecompileNeeded) || - is_set(mUpdateFlags, IScene::UpdateFlags::GeometryChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::RecompileNeeded) || is_set(mUpdateFlags, IScene::UpdateFlags::GeometryChanged)) { resetPrograms(); } @@ -1009,7 +1353,8 @@ bool PathTracer::prepareLighting(RenderContext* pRenderContext) mpEmissiveSampler = std::make_unique(pRenderContext, mpScene->getILightCollection(pRenderContext)); break; case EmissiveLightSamplerType::LightBVH: - mpEmissiveSampler = std::make_unique(pRenderContext, mpScene->getILightCollection(pRenderContext), mLightBVHOptions); + mpEmissiveSampler = + std::make_unique(pRenderContext, mpScene->getILightCollection(pRenderContext), mLightBVHOptions); break; case EmissiveLightSamplerType::Power: mpEmissiveSampler = std::make_unique(pRenderContext, mpScene->getILightCollection(pRenderContext)); @@ -1041,7 +1386,8 @@ bool PathTracer::prepareLighting(RenderContext* pRenderContext) { lightingChanged |= mpEmissiveSampler->update(pRenderContext, mpScene->getILightCollection(pRenderContext)); auto defines = mpEmissiveSampler->getDefines(); - if (mpTracePass && mpTracePass->pProgram->addDefines(defines)) mRecompile = true; + if (mpTracePass && mpTracePass->pProgram->addDefines(defines)) + mRecompile = true; } return lightingChanged; @@ -1051,7 +1397,8 @@ void PathTracer::prepareRTXDI(RenderContext* pRenderContext) { if (mStaticParams.useRTXDI) { - if (!mpRTXDI) mpRTXDI = std::make_unique(mpScene, mRTXDIOptions); + if (!mpRTXDI) + mpRTXDI = std::make_unique(mpScene, mRTXDIOptions); // Emit warning if enabled while using spp != 1. if (!mFixedSampleCount || mStaticParams.samplesPerPixel != 1) @@ -1092,7 +1439,8 @@ void PathTracer::bindShaderData(const ShaderVar& var, const RenderData& renderDa // Bind static resources that don't change per frame. if (mVarsChanged) { - if (useLightSampling && mpEnvMapSampler) mpEnvMapSampler->bindShaderData(var["envMapSampler"]); + if (useLightSampling && mpEnvMapSampler) + mpEnvMapSampler->bindShaderData(var["envMapSampler"]); var["sampleOffset"] = mpSampleOffset; // Can be nullptr var["sampleColor"] = mpSampleColor; @@ -1106,19 +1454,26 @@ void PathTracer::bindShaderData(const ShaderVar& var, const RenderData& renderDa if (mpScene && mpScene->getCamera()->getApertureRadius() > 0.f) { pViewDir = renderData.getTexture(kInputViewDir); - if (!pViewDir) logWarning("Depth-of-field requires the '{}' input. Expect incorrect rendering.", kInputViewDir); + if (!pViewDir) + logWarning("Depth-of-field requires the '{}' input. Expect incorrect rendering.", kInputViewDir); } ref pSampleCount; if (!mFixedSampleCount) { pSampleCount = renderData.getTexture(kInputSampleCount); - if (!pSampleCount) FALCOR_THROW("PathTracer: Missing sample count input texture"); + if (!pSampleCount) + FALCOR_THROW("PathTracer: Missing sample count input texture"); } var["params"].setBlob(mParams); - var["vbuffer"] = renderData.getTexture(kInputVBuffer); - var["viewDir"] = pViewDir; // Can be nullptr + + const ref& pInputVBuffer = renderData.getTexture(kInputVBuffer); + var["vbuffer"] = (mpReSTIRPathTracing && mStaticParams.useReSTIRPT) + ? mpReSTIRPathTracing->getVBuffer(pInputVBuffer) + : pInputVBuffer; + + var["viewDir"] = pViewDir; // Can be nullptr var["sampleCount"] = pSampleCount; // Can be nullptr var["outputColor"] = renderData.getTexture(kOutputColor); @@ -1127,6 +1482,19 @@ void PathTracer::bindShaderData(const ShaderVar& var, const RenderData& renderDa // TODO: Do we have to bind this every frame? mpEmissiveSampler->bindShaderData(var["emissiveSampler"]); } + + if (mStaticParams.useReSTIRPT && mpReSTIRPathTracing) + { + auto restirptVar = var.findMember("restirpt"); + if (restirptVar.isValid()) + mpReSTIRPathTracing->setShaderData(restirptVar); + } + + if (mStaticParams.useLightTileRIS && mpLightTileResampling && useLightSampling) + { + mpLightTileResampling->setLightsShaderData(var["lightTileRIS"]["lights"]); + var["lightTileRIS"]["lightTileData"] = mpLightTileResampling->getLightTileData(); + } } bool PathTracer::beginFrame(RenderContext* pRenderContext, const RenderData& renderData) @@ -1140,11 +1508,13 @@ bool PathTracer::beginFrame(RenderContext* pRenderContext, const RenderData& ren // Validate all I/O sizes match the expected size. // If not, we'll disable the path tracer to give the user a chance to fix the configuration before re-enabling it. bool resolutionMismatch = false; - auto validateChannels = [&](const auto& channels) { + auto validateChannels = [&](const auto& channels) + { for (const auto& channel : channels) { auto pTexture = renderData.getTexture(channel.name); - if (pTexture && (pTexture->getWidth() != mParams.frameDim.x || pTexture->getHeight() != mParams.frameDim.y)) resolutionMismatch = true; + if (pTexture && (pTexture->getWidth() != mParams.frameDim.x || pTexture->getHeight() != mParams.frameDim.y)) + resolutionMismatch = true; } }; validateChannels(kInputChannels); @@ -1166,7 +1536,8 @@ bool PathTracer::beginFrame(RenderContext* pRenderContext, const RenderData& ren { auto& dict = renderData.getDictionary(); auto flags = dict.getValue(kRenderPassRefreshFlags, Falcor::RenderPassRefreshFlags::None); - if (mOptionsChanged) flags |= Falcor::RenderPassRefreshFlags::RenderOptionsChanged; + if (mOptionsChanged) + flags |= Falcor::RenderPassRefreshFlags::RenderOptionsChanged; dict[Falcor::kRenderPassRefreshFlags] = flags; } @@ -1181,15 +1552,50 @@ bool PathTracer::beginFrame(RenderContext* pRenderContext, const RenderData& ren // Prepare RTXDI. prepareRTXDI(pRenderContext); - if (mpRTXDI) mpRTXDI->beginFrame(pRenderContext, mParams.frameDim); + if (mpRTXDI) + mpRTXDI->beginFrame(pRenderContext, mParams.frameDim); + + // Prepare ReSTIR PT. + if (mStaticParams.useReSTIRPT) + { + if (!mpReSTIRPathTracing) + { + resetPrograms(); + auto defines = mStaticParams.getDefines(*this); + if (renderData[kOutputDuplicationMap] != nullptr) mReSTIRPathTracingOptions.computeTemporalDuplication = true; + mpReSTIRPathTracing = std::make_unique(mpScene, defines, mReSTIRPathTracingOptions); + } + } + + if (mpReSTIRPathTracing) + { + mpReSTIRPathTracing->setPathTracerParams({mParams.frameDim, mParams.screenTiles, mParams.frameCount, mParams.seed, mParams.lodBias, mParams.specularRoughnessThreshold}); + mpReSTIRPathTracing->setSharedStaticParams(mStaticParams.samplesPerPixel, mStaticParams.maxSurfaceBounces, mStaticParams.useNEE); + mpReSTIRPathTracing->beginFrame(pRenderContext, mParams.frameDim, mParams.screenTiles, mRecompile); + } + + if (mStaticParams.useLightTileRIS) + { + if (!mpLightTileResampling) + { + mpLightTileResampling = std::make_unique(mpScene); + } + mpLightTileResampling->beginFrame(pRenderContext); + mpLightTileResampling->generateLightTiles(pRenderContext); + if (mpReSTIRPathTracing) + mpReSTIRPathTracing->setInitialLightSampleCount(mpLightTileResampling->getOptions().initialLightSampleCount); + } // Update refresh flag if changes that affect the output have occured. auto& dict = renderData.getDictionary(); + dict["disableGBufferDoF"] = mpReSTIRPathTracing && mStaticParams.useReSTIRPT && mpReSTIRPathTracing->getOptions().useAreaReservoirs; if (mOptionsChanged || lightingChanged) { auto flags = dict.getValue(kRenderPassRefreshFlags, Falcor::RenderPassRefreshFlags::None); - if (mOptionsChanged) flags |= Falcor::RenderPassRefreshFlags::RenderOptionsChanged; - if (lightingChanged) flags |= Falcor::RenderPassRefreshFlags::LightingChanged; + if (mOptionsChanged) + flags |= Falcor::RenderPassRefreshFlags::RenderOptionsChanged; + if (lightingChanged) + flags |= Falcor::RenderPassRefreshFlags::LightingChanged; dict[Falcor::kRenderPassRefreshFlags] = flags; mOptionsChanged = false; } @@ -1206,35 +1612,42 @@ bool PathTracer::beginFrame(RenderContext* pRenderContext, const RenderData& ren mFixedSampleCount = renderData[kInputSampleCount] == nullptr; // Check if guide data should be generated. - mOutputGuideData = renderData[kOutputAlbedo] != nullptr || renderData[kOutputSpecularAlbedo] != nullptr - || renderData[kOutputIndirectAlbedo] != nullptr || renderData[kOutputGuideNormal] != nullptr - || renderData[kOutputReflectionPosW] != nullptr; + mOutputGuideData = renderData[kOutputAlbedo] != nullptr || renderData[kOutputSpecularAlbedo] != nullptr || + renderData[kOutputIndirectAlbedo] != nullptr || renderData[kOutputGuideNormal] != nullptr || + renderData[kOutputReflectionPosW] != nullptr; // Check if NRD data should be generated. - mOutputNRDData = - renderData[kOutputNRDDiffuseRadianceHitDist] != nullptr - || renderData[kOutputNRDSpecularRadianceHitDist] != nullptr - || renderData[kOutputNRDResidualRadianceHitDist] != nullptr - || renderData[kOutputNRDEmission] != nullptr - || renderData[kOutputNRDDiffuseReflectance] != nullptr - || renderData[kOutputNRDSpecularReflectance] != nullptr; + mOutputNRDData = renderData[kOutputNRDDiffuseRadianceHitDist] != nullptr || renderData[kOutputNRDSpecularRadianceHitDist] != nullptr || + renderData[kOutputNRDResidualRadianceHitDist] != nullptr || renderData[kOutputNRDEmission] != nullptr || + renderData[kOutputNRDDiffuseReflectance] != nullptr || renderData[kOutputNRDSpecularReflectance] != nullptr; // Check if additional NRD data should be generated. bool prevOutputNRDAdditionalData = mOutputNRDAdditionalData; mOutputNRDAdditionalData = - renderData[kOutputNRDDeltaReflectionRadianceHitDist] != nullptr - || renderData[kOutputNRDDeltaTransmissionRadianceHitDist] != nullptr - || renderData[kOutputNRDDeltaReflectionReflectance] != nullptr - || renderData[kOutputNRDDeltaReflectionEmission] != nullptr - || renderData[kOutputNRDDeltaReflectionNormWRoughMaterialID] != nullptr - || renderData[kOutputNRDDeltaReflectionPathLength] != nullptr - || renderData[kOutputNRDDeltaReflectionHitDist] != nullptr - || renderData[kOutputNRDDeltaTransmissionReflectance] != nullptr - || renderData[kOutputNRDDeltaTransmissionEmission] != nullptr - || renderData[kOutputNRDDeltaTransmissionNormWRoughMaterialID] != nullptr - || renderData[kOutputNRDDeltaTransmissionPathLength] != nullptr - || renderData[kOutputNRDDeltaTransmissionPosW] != nullptr; - if (mOutputNRDAdditionalData != prevOutputNRDAdditionalData) mRecompile = true; + renderData[kOutputNRDDeltaReflectionRadianceHitDist] != nullptr || + renderData[kOutputNRDDeltaTransmissionRadianceHitDist] != nullptr || renderData[kOutputNRDDeltaReflectionReflectance] != nullptr || + renderData[kOutputNRDDeltaReflectionEmission] != nullptr || renderData[kOutputNRDDeltaReflectionNormWRoughMaterialID] != nullptr || + renderData[kOutputNRDDeltaReflectionPathLength] != nullptr || renderData[kOutputNRDDeltaReflectionHitDist] != nullptr || + renderData[kOutputNRDDeltaTransmissionReflectance] != nullptr || renderData[kOutputNRDDeltaTransmissionEmission] != nullptr || + renderData[kOutputNRDDeltaTransmissionNormWRoughMaterialID] != nullptr || + renderData[kOutputNRDDeltaTransmissionPathLength] != nullptr || renderData[kOutputNRDDeltaTransmissionPosW] != nullptr; + if (mOutputNRDAdditionalData != prevOutputNRDAdditionalData) + mRecompile = true; + + // Track ReSTIR PT's specular motion vector option; toggling it changes both the set of trace passes we need + // to create (delta reflection/transmission) and the OUTPUT_DELTA_MOTION_VECTORS macro. + const bool specularMVecEnabled = mpReSTIRPathTracing && mStaticParams.useReSTIRPT && mpReSTIRPathTracing->getOptions().enableSpecularMotionVectors; + if (specularMVecEnabled != mSpecularMVecEnabled) + { + mSpecularMVecEnabled = specularMVecEnabled; + mRecompile = true; + } + + if (mSpecularMVecEnabled && mStaticParams.tracePassMode != TracePassMode::MinimalPayload) + { + mStaticParams.tracePassMode = TracePassMode::MinimalPayload; + resetPrograms(); + } // Enable pixel stats if rayCount or pathLength outputs are connected. if (renderData[kOutputRayCount] != nullptr || renderData[kOutputPathLength] != nullptr) @@ -1277,7 +1690,10 @@ void PathTracer::endFrame(RenderContext* pRenderContext, const RenderData& rende copyTexture(renderData.getTexture(kOutputRayCount).get(), mpPixelStats->getRayCountTexture(pRenderContext).get()); copyTexture(renderData.getTexture(kOutputPathLength).get(), mpPixelStats->getPathLengthTexture().get()); - if (mpRTXDI) mpRTXDI->endFrame(pRenderContext); + if (mpRTXDI) + mpRTXDI->endFrame(pRenderContext); + if (mpReSTIRPathTracing) + mpReSTIRPathTracing->endFrame(pRenderContext); mVarsChanged = false; mParams.frameCount++; @@ -1290,13 +1706,16 @@ void PathTracer::generatePaths(RenderContext* pRenderContext, const RenderData& // Check shader assumptions. // We launch one thread group per screen tile, with threads linearly indexed. const uint32_t tileSize = kScreenTileDim.x * kScreenTileDim.y; - FALCOR_ASSERT(kScreenTileDim.x == 16 && kScreenTileDim.y == 16); // TODO: Remove this temporary limitation when Slang bug has been fixed, see comments in shader. + FALCOR_ASSERT(kScreenTileDim.x == 16 && kScreenTileDim.y == 16); // TODO: Remove this temporary limitation when Slang bug has been + // fixed, see comments in shader. FALCOR_ASSERT(kScreenTileBits.x <= 4 && kScreenTileBits.y <= 4); // Since we use 8-bit deinterleave. FALCOR_ASSERT(mpGeneratePaths->getThreadGroupSize().x == tileSize); FALCOR_ASSERT(mpGeneratePaths->getThreadGroupSize().y == 1 && mpGeneratePaths->getThreadGroupSize().z == 1); // Additional specialization. This shouldn't change resource declarations. - mpGeneratePaths->addDefine("USE_VIEW_DIR", (mpScene->getCamera()->getApertureRadius() > 0 && renderData[kInputViewDir] != nullptr) ? "1" : "0"); + mpGeneratePaths->addDefine( + "USE_VIEW_DIR", (mpScene->getCamera()->getApertureRadius() > 0 && renderData[kInputViewDir] != nullptr) ? "1" : "0" + ); mpGeneratePaths->addDefine("OUTPUT_GUIDE_DATA", mOutputGuideData ? "1" : "0"); mpGeneratePaths->addDefine("OUTPUT_NRD_DATA", mOutputNRDData ? "1" : "0"); mpGeneratePaths->addDefine("OUTPUT_NRD_ADDITIONAL_DATA", mOutputNRDAdditionalData ? "1" : "0"); @@ -1307,11 +1726,12 @@ void PathTracer::generatePaths(RenderContext* pRenderContext, const RenderData& mpScene->bindShaderData(mpGeneratePaths->getRootVar()["gScene"]); - if (mpRTXDI) mpRTXDI->bindShaderData(mpGeneratePaths->getRootVar()); + if (mpRTXDI) + mpRTXDI->bindShaderData(mpGeneratePaths->getRootVar()); // Launch one thread per pixel. // The dimensions are padded to whole tiles to allow re-indexing the threads in the shader. - mpGeneratePaths->execute(pRenderContext, { mParams.screenTiles.x * tileSize, mParams.screenTiles.y, 1u }); + mpGeneratePaths->execute(pRenderContext, {mParams.screenTiles.x * tileSize, mParams.screenTiles.y, 1u}); } void PathTracer::tracePass(RenderContext* pRenderContext, const RenderData& renderData, TracePass& tracePass) @@ -1321,16 +1741,28 @@ void PathTracer::tracePass(RenderContext* pRenderContext, const RenderData& rend FALCOR_ASSERT(tracePass.pProgram != nullptr && tracePass.pBindingTable != nullptr && tracePass.pVars != nullptr); // Additional specialization. This shouldn't change resource declarations. - tracePass.pProgram->addDefine("USE_VIEW_DIR", (mpScene->getCamera()->getApertureRadius() > 0 && renderData[kInputViewDir] != nullptr) ? "1" : "0"); + const bool specularMVecEnabled = mpReSTIRPathTracing && mStaticParams.useReSTIRPT && mpReSTIRPathTracing->getOptions().enableSpecularMotionVectors; + tracePass.pProgram->addDefine( + "USE_VIEW_DIR", (mpScene->getCamera()->getApertureRadius() > 0 && renderData[kInputViewDir] != nullptr) ? "1" : "0" + ); tracePass.pProgram->addDefine("OUTPUT_GUIDE_DATA", mOutputGuideData ? "1" : "0"); tracePass.pProgram->addDefine("OUTPUT_NRD_DATA", mOutputNRDData ? "1" : "0"); tracePass.pProgram->addDefine("OUTPUT_NRD_ADDITIONAL_DATA", mOutputNRDAdditionalData ? "1" : "0"); + tracePass.pProgram->addDefine("OUTPUT_DELTA_MOTION_VECTORS", specularMVecEnabled ? "1" : "0"); // Bind global resources. auto var = tracePass.pVars->getRootVar(); - if (mVarsChanged) mpSampleGenerator->bindShaderData(var); - if (mpRTXDI) mpRTXDI->bindShaderData(var); + if (mVarsChanged) + mpSampleGenerator->bindShaderData(var); + if (mpRTXDI) + mpRTXDI->bindShaderData(var); + + if (specularMVecEnabled) + { + var["deltaReflectionMotionVectors"] = mpReSTIRPathTracing->getDeltaReflectionMotionVectors(); + var["deltaTransmissionMotionVectors"] = mpReSTIRPathTracing->getDeltaTransmissionMotionVectors(); + } mpPixelStats->prepareProgram(tracePass.pProgram, var); mpPixelDebug->prepareProgram(tracePass.pProgram, var); @@ -1338,13 +1770,45 @@ void PathTracer::tracePass(RenderContext* pRenderContext, const RenderData& rend // Bind the path tracer. var["gPathTracer"] = mpPathTracerBlock; + // Override vbuffer for ReSTIR PT film/lens sample (use randomized VBuffer for non-delta passes). + if (mpReSTIRPathTracing && mStaticParams.useReSTIRPT) + { + const bool isDeltaPass = tracePass.name.find("Delta") != std::string::npos; + var["gPathTracer"]["vbuffer"] = mpReSTIRPathTracing->getVBuffer(renderData.getTexture(kInputVBuffer), isDeltaPass); + } + // Full screen dispatch. mpScene->raytrace(pRenderContext, tracePass.pProgram.get(), tracePass.pVars, uint3(mParams.frameDim, 1)); } +void PathTracer::resolveReSTIRPTPass(RenderContext* pRenderContext, const RenderData& renderData) +{ + if (!mStaticParams.useReSTIRPT) + return; + + FALCOR_PROFILE(pRenderContext, "resolveReSTIRPTPass"); + + auto var = mpResolveReSTIRPTPass->getRootVar()["CB"]["gResolveReSTIRPTPass"]; + mpScene->bindShaderData(mpResolveReSTIRPTPass->getRootVar()["gScene"]); + mpPixelStats->prepareProgram(mpResolveReSTIRPTPass->getProgram(), mpResolveReSTIRPTPass->getRootVar()); + mpPixelDebug->prepareProgram(mpResolveReSTIRPTPass->getProgram(), mpResolveReSTIRPTPass->getRootVar()); + + var["params"].setBlob(mParams); + var["outputColor"] = renderData.getTexture(kOutputColor); + var["vbuffer"] = renderData.getTexture(kInputVBuffer); + var["motionVectors"] = renderData.getTexture(kInputMotionVectors); + + mpReSTIRPathTracing->setShaderVarForDuplicationMap(var); + var["outDuplicationMap"] = renderData.getTexture(kOutputDuplicationMap); + mpReSTIRPathTracing->setReservoirData(var); + + mpResolveReSTIRPTPass->execute(pRenderContext, {mParams.frameDim, 1u}); +} + void PathTracer::resolvePass(RenderContext* pRenderContext, const RenderData& renderData) { - if (!mOutputGuideData && !mOutputNRDData && mFixedSampleCount && mStaticParams.samplesPerPixel == 1) return; + if (!mOutputGuideData && !mOutputNRDData && mFixedSampleCount && mStaticParams.samplesPerPixel == 1) + return; FALCOR_PROFILE(pRenderContext, "resolvePass"); @@ -1389,7 +1853,7 @@ void PathTracer::resolvePass(RenderContext* pRenderContext, const RenderData& re } // Launch one thread per pixel. - mpResolvePass->execute(pRenderContext, { mParams.frameDim, 1u }); + mpResolvePass->execute(pRenderContext, {mParams.frameDim, 1u}); } DefineList PathTracer::StaticParams::getDefines(const PathTracer& owner) const @@ -1397,7 +1861,8 @@ DefineList PathTracer::StaticParams::getDefines(const PathTracer& owner) const DefineList defines; // Path tracer configuration. - defines.add("SAMPLES_PER_PIXEL", (owner.mFixedSampleCount ? std::to_string(samplesPerPixel) : "0")); // 0 indicates a variable sample count + defines.add("SAMPLES_PER_PIXEL", (owner.mFixedSampleCount ? std::to_string(samplesPerPixel) : "0")); // 0 indicates a variable sample + // count defines.add("MAX_SURFACE_BOUNCES", std::to_string(maxSurfaceBounces)); defines.add("MAX_DIFFUSE_BOUNCES", std::to_string(maxDiffuseBounces)); defines.add("MAX_SPECULAR_BOUNCES", std::to_string(maxSpecularBounces)); @@ -1414,6 +1879,8 @@ DefineList PathTracer::StaticParams::getDefines(const PathTracer& owner) const defines.add("PRIMARY_LOD_MODE", std::to_string((uint32_t)primaryLodMode)); defines.add("USE_NRD_DEMODULATION", useNRDDemodulation ? "1" : "0"); defines.add("USE_SER", useSER ? "1" : "0"); + defines.add("USE_RESTIRPT", useReSTIRPT ? "1" : "0"); + defines.add("USE_LIGHT_TILE_RIS", useLightTileRIS ? "1" : "0"); defines.add("COLOR_FORMAT", std::to_string((uint32_t)colorFormat)); defines.add("MIS_HEURISTIC", std::to_string((uint32_t)misHeuristic)); defines.add("MIS_POWER_EXPONENT", std::to_string(misPowerExponent)); @@ -1422,8 +1889,10 @@ DefineList PathTracer::StaticParams::getDefines(const PathTracer& owner) const FALCOR_ASSERT(owner.mpSampleGenerator); defines.add(owner.mpSampleGenerator->getDefines()); - if (owner.mpEmissiveSampler) defines.add(owner.mpEmissiveSampler->getDefines()); - if (owner.mpRTXDI) defines.add(owner.mpRTXDI->getDefines()); + if (owner.mpEmissiveSampler) + defines.add(owner.mpEmissiveSampler->getDefines()); + if (owner.mpRTXDI) + defines.add(owner.mpRTXDI->getDefines()); defines.add("INTERIOR_LIST_SLOT_COUNT", std::to_string(maxNestedMaterials)); @@ -1454,6 +1923,57 @@ DefineList PathTracer::StaticParams::getDefines(const PathTracer& owner) const defines.add("OUTPUT_GUIDE_DATA", "0"); defines.add("OUTPUT_NRD_DATA", "0"); defines.add("OUTPUT_NRD_ADDITIONAL_DATA", "0"); + // Gates writes to ReSTIR PT's specular delta reflection/transmission motion vector textures in PathTracerNRD.slang + // and GeneratePaths.cs.slang. Toggled per-frame based on ReSTIRPathTracing's enableSpecularMotionVectors option. + defines.add("OUTPUT_DELTA_MOTION_VECTORS", "0"); + + // Add light sampling defines using a lambda + auto addDefaultLightDefines = [](DefineList& defines) + { + // Light sampling configuration + defines.add("LIGHT_TILE_COUNT", "1"); + defines.add("LIGHT_TILE_SIZE", "1"); + defines.add("SCREEN_TILE_SIZE", "1"); + defines.add("INITIAL_LIGHT_SAMPLE_COUNT", "1"); + + // Light defines (default values) + defines.add("LIGHT_SAMPLE_ENV_INDEX_BITS", "0"); + defines.add("LIGHT_SAMPLE_ENV_POSITION_BITS", "0"); + defines.add("LIGHT_SAMPLE_EMISSIVE_INDEX_BITS", "0"); + defines.add("LIGHT_SAMPLE_EMISSIVE_POSITION_BITS", "0"); + defines.add("LIGHT_SAMPLE_ANALYTIC_INDEX_BITS", "0"); + defines.add("LIGHT_SAMPLE_ANALYTIC_POSITION_BITS", "0"); + + defines.add("USE_EMISSIVE_TEXTURE_FOR_SAMPLING", "0"); + defines.add("USE_EMISSIVE_TEXTURE_FOR_SHADING", "0"); + defines.add("USE_LOCAL_EMISSIVE_TRIANGLES", "0"); + }; + + addDefaultLightDefines(defines); + + // Light tile resampling defines. + if (owner.mpLightTileResampling && useLightTileRIS) + defines.add(owner.mpLightTileResampling->getDefines()); + + // ReSTIR PT defines. + if (owner.mpReSTIRPathTracing && useReSTIRPT) + { + DefineList restirPTDefines = defines; + addDefaultLightDefines(restirPTDefines); + owner.mpReSTIRPathTracing->setOwnerDefines(restirPTDefines); + defines.add(owner.mpReSTIRPathTracing->getDefines()); + } + else + { + defines.add("RESTIRPT_SHIFT_MAPPING", "0"); + defines.add("TEMPORAL_UPDATE_FOR_DYNAMIC_SCENE", "0"); + defines.add("USE_RESERVOIR_COMPRESSION", "0"); + defines.add("RETRACE_SCHEDULE_TYPE", "0"); + defines.add("USE_PREV_FRAME_SCENE_DATA", "0"); + defines.add("USE_AREA_RESERVOIRS", "0"); + defines.add("PAD_RANDOM_NUMBERS", "0"); + defines.add("FETCH_NRD_DATA_PASS", "0"); + } return defines; } diff --git a/Source/RenderPasses/PathTracer/PathTracer.h b/Source/RenderPasses/PathTracer/PathTracer.h index 779913d1cd..37623dbd4f 100644 --- a/Source/RenderPasses/PathTracer/PathTracer.h +++ b/Source/RenderPasses/PathTracer/PathTracer.h @@ -27,6 +27,7 @@ **************************************************************************/ #pragma once #include "Falcor.h" +#include "Core/Enum.h" #include "RenderGraph/RenderPass.h" #include "RenderGraph/RenderPassHelpers.h" #include "Utils/Debug/PixelDebug.h" @@ -37,18 +38,35 @@ #include "Rendering/Materials/TexLODTypes.slang" #include "Rendering/Utils/PixelStats.h" #include "Rendering/RTXDI/RTXDI.h" +#include "ReSTIRPathTracing/ReSTIRPathTracing.h" +#include "LightTileResampling/LightTileResampling.h" #include "Params.slang" +#include "PathState.slang" using namespace Falcor; /** Fast path tracer. -*/ + */ class PathTracer : public RenderPass { public: FALCOR_PLUGIN_CLASS(PathTracer, "PathTracer", "Reference path tracer."); + enum class TracePassMode + { + Default, + MinimalPayload, + }; + + FALCOR_ENUM_INFO( + TracePassMode, + { + {TracePassMode::Default, "Default"}, + {TracePassMode::MinimalPayload, "MinimalPayload"}, + } + ); + static ref create(ref pDevice, const Properties& props) { return make_ref(pDevice, props); } PathTracer(ref pDevice, const Properties& props); @@ -77,11 +95,27 @@ class PathTracer : public RenderPass ref pBindingTable; ref pVars; - TracePass(ref pDevice, const std::string& name, const std::string& passDefine, const ref& pScene, const DefineList& defines, const TypeConformanceList& globalTypeConformances); - static std::unique_ptr create(ref pDevice, const std::string& name, const std::string& passDefine, const ref& pScene, const DefineList& defines, const TypeConformanceList& globalTypeConformances) + TracePass( + ref pDevice, + const std::string& name, + const std::string& passDefine, + const std::string& shaderLibrary, + const ref& pScene, + const DefineList& defines, + const TypeConformanceList& globalTypeConformances + ); + static std::unique_ptr create( + ref pDevice, + const std::string& name, + const std::string& passDefine, + const std::string& shaderLibrary, + const ref& pScene, + const DefineList& defines, + const TypeConformanceList& globalTypeConformances + ) { if (auto scene = dynamic_ref_cast(pScene)) - return std::make_unique(std::move(pDevice), name, passDefine, std::move(scene), defines, globalTypeConformances); + return std::make_unique(std::move(pDevice), name, passDefine, shaderLibrary, std::move(scene), defines, globalTypeConformances); return {}; } @@ -108,100 +142,120 @@ class PathTracer : public RenderPass void endFrame(RenderContext* pRenderContext, const RenderData& renderData); void generatePaths(RenderContext* pRenderContext, const RenderData& renderData); void tracePass(RenderContext* pRenderContext, const RenderData& renderData, TracePass& tracePass); + void resolveReSTIRPTPass(RenderContext* pRenderContext, const RenderData& renderData); void resolvePass(RenderContext* pRenderContext, const RenderData& renderData); /** Static configuration. Changing any of these options require shader recompilation. - */ + */ struct StaticParams { // Rendering parameters - uint32_t samplesPerPixel = 1; ///< Number of samples (paths) per pixel, unless a sample density map is used. - uint32_t maxSurfaceBounces = 0; ///< Max number of surface bounces (diffuse + specular + transmission), up to kMaxPathLenth. This will be initialized at startup. - uint32_t maxDiffuseBounces = 3; ///< Max number of diffuse bounces (0 = direct only), up to kMaxBounces. - uint32_t maxSpecularBounces = 3; ///< Max number of specular bounces (0 = direct only), up to kMaxBounces. - uint32_t maxTransmissionBounces = 10; ///< Max number of transmission bounces (0 = none), up to kMaxBounces. + uint32_t samplesPerPixel = 1; ///< Number of samples (paths) per pixel, unless a sample density map is used. + uint32_t maxSurfaceBounces = 0; ///< Max number of surface bounces (diffuse + specular + transmission), up to kMaxPathLenth. This + ///< will be initialized at startup. + uint32_t maxDiffuseBounces = 3; ///< Max number of diffuse bounces (0 = direct only), up to kMaxBounces. + uint32_t maxSpecularBounces = 3; ///< Max number of specular bounces (0 = direct only), up to kMaxBounces. + uint32_t maxTransmissionBounces = 10; ///< Max number of transmission bounces (0 = none), up to kMaxBounces. // Sampling parameters - uint32_t sampleGenerator = SAMPLE_GENERATOR_TINY_UNIFORM; ///< Pseudorandom sample generator type. - bool useBSDFSampling = true; ///< Use BRDF importance sampling, otherwise cosine-weighted hemisphere sampling. - bool useRussianRoulette = false; ///< Use russian roulette to terminate low throughput paths. - bool useNEE = true; ///< Use next-event estimation (NEE). This enables shadow ray(s) from each path vertex. - bool useMIS = true; ///< Use multiple importance sampling (MIS) when NEE is enabled. - MISHeuristic misHeuristic = MISHeuristic::Balance; ///< MIS heuristic. - float misPowerExponent = 2.f; ///< MIS exponent for the power heuristic. This is only used when 'PowerExp' is chosen. - EmissiveLightSamplerType emissiveSampler = EmissiveLightSamplerType::LightBVH; ///< Emissive light sampler to use for NEE. - bool useRTXDI = false; ///< Use RTXDI for direct illumination. + uint32_t sampleGenerator = SAMPLE_GENERATOR_TINY_UNIFORM; ///< Pseudorandom sample generator type. + bool useBSDFSampling = true; ///< Use BRDF importance sampling, otherwise cosine-weighted hemisphere sampling. + bool useRussianRoulette = false; ///< Use russian roulette to terminate low throughput paths. + bool useNEE = true; ///< Use next-event estimation (NEE). This enables shadow ray(s) from each path vertex. + bool useMIS = true; ///< Use multiple importance sampling (MIS) when NEE is enabled. + MISHeuristic misHeuristic = MISHeuristic::Balance; ///< MIS heuristic. + float misPowerExponent = 2.f; ///< MIS exponent for the power heuristic. This is only used when 'PowerExp' is chosen. + EmissiveLightSamplerType emissiveSampler = EmissiveLightSamplerType::LightBVH; ///< Emissive light sampler to use for NEE. + bool useRTXDI = false; ///< Use RTXDI for direct illumination. // Material parameters - bool useAlphaTest = true; ///< Use alpha testing on non-opaque triangles. - bool adjustShadingNormals = false; ///< Adjust shading normals on secondary hits. - uint32_t maxNestedMaterials = 2; ///< Maximum supported number of nested materials. - bool useLightsInDielectricVolumes = false; ///< Use lights inside of volumes (transmissive materials). We typically don't want this because lights are occluded by the interface. - bool disableCaustics = false; ///< Disable sampling of caustics. - TexLODMode primaryLodMode = TexLODMode::Mip0; ///< Use filtered texture lookups at the primary hit. + bool useAlphaTest = true; ///< Use alpha testing on non-opaque triangles. + bool adjustShadingNormals = false; ///< Adjust shading normals on secondary hits. + uint32_t maxNestedMaterials = 2; ///< Maximum supported number of nested materials. + bool useLightsInDielectricVolumes = false; ///< Use lights inside of volumes (transmissive materials). We typically don't want this + ///< because lights are occluded by the interface. + bool disableCaustics = false; ///< Disable sampling of caustics. + TexLODMode primaryLodMode = TexLODMode::Mip0; ///< Use filtered texture lookups at the primary hit. // Scheduling parameters - bool useSER = true; ///< Enable SER (Shader Execution Reordering). + bool useSER = true; ///< Enable SER (Shader Execution Reordering). + TracePassMode tracePassMode = TracePassMode::Default; ///< Trace pass implementation to use. + + // ReSTIR PT + bool useReSTIRPT = false; ///< Enable ReSTIR Path Tracing for indirect illumination. + + // Light Tile RIS + bool useLightTileRIS = false; ///< Use tile-based light resampling for NEE. // Output parameters - ColorFormat colorFormat = ColorFormat::LogLuvHDR; ///< Color format used for internal per-sample color and denoiser buffers. + ColorFormat colorFormat = ColorFormat::LogLuvHDR; ///< Color format used for internal per-sample color and denoiser buffers. // Denoising parameters - bool useNRDDemodulation = true; ///< Global switch for NRD demodulation. + bool useNRDDemodulation = true; ///< Global switch for NRD demodulation. DefineList getDefines(const PathTracer& owner) const; }; // Configuration - PathTracerParams mParams; ///< Runtime path tracer parameters. - StaticParams mStaticParams; ///< Static parameters. These are set as compile-time constants in the shaders. - mutable LightBVHSampler::Options mLightBVHOptions; ///< Current options for the light BVH sampler. - RTXDI::Options mRTXDIOptions; ///< Current options for the RTXDI sampler. + PathTracerParams mParams; ///< Runtime path tracer parameters. + StaticParams mStaticParams; ///< Static parameters. These are set as compile-time constants in the shaders. + mutable LightBVHSampler::Options mLightBVHOptions; ///< Current options for the light BVH sampler. + RTXDI::Options mRTXDIOptions; ///< Current options for the RTXDI sampler. + ReSTIRPathTracing::Options mReSTIRPathTracingOptions; ///< Current options for the ReSTIR PT sampler. - bool mEnabled = true; ///< Switch to enable/disable the path tracer. When disabled the pass outputs are cleared. - RenderPassHelpers::IOSize mOutputSizeSelection = RenderPassHelpers::IOSize::Default; ///< Selected output size. - uint2 mFixedOutputSize = { 512, 512 }; ///< Output size in pixels when 'Fixed' size is selected. + bool mEnabled = true; ///< Switch to enable/disable the path tracer. When disabled the pass outputs are cleared. + RenderPassHelpers::IOSize mOutputSizeSelection = RenderPassHelpers::IOSize::Default; ///< Selected output size. + uint2 mFixedOutputSize = {512, 512}; ///< Output size in pixels when 'Fixed' size is selected. - bool mSERSupported = false; ///< True if the device supports SER. + bool mSERSupported = false; ///< True if the device supports SER. // Internal state - ref mpScene; ///< The current scene, or nullptr if no scene loaded. - ref mpSampleGenerator; ///< GPU pseudo-random sample generator. - std::unique_ptr mpEnvMapSampler; ///< Environment map sampler or nullptr if not used. - std::unique_ptr mpEmissiveSampler; ///< Emissive light sampler or nullptr if not used. - std::unique_ptr mpRTXDI; ///< RTXDI sampler for direct illumination or nullptr if not used. - std::unique_ptr mpPixelStats; ///< Utility class for collecting pixel stats. - std::unique_ptr mpPixelDebug; ///< Utility class for pixel debugging (print in shaders). - - sigs::Connection mUpdateFlagsConnection; ///< Connection to the UpdateFlags signal. + ref mpScene; ///< The current scene, or nullptr if no scene loaded. + ref mpSampleGenerator; ///< GPU pseudo-random sample generator. + std::unique_ptr mpEnvMapSampler; ///< Environment map sampler or nullptr if not used. + std::unique_ptr mpEmissiveSampler; ///< Emissive light sampler or nullptr if not used. + std::unique_ptr mpRTXDI; ///< RTXDI sampler for direct illumination or nullptr if not used. + std::unique_ptr mpReSTIRPathTracing; ///< ReSTIR PT module or nullptr if not used. + std::unique_ptr mpLightTileResampling; ///< Light tile resampling or nullptr if not used. + std::unique_ptr mpPixelStats; ///< Utility class for collecting pixel stats. + std::unique_ptr mpPixelDebug; ///< Utility class for pixel debugging (print in shaders). + + sigs::Connection mUpdateFlagsConnection; ///< Connection to the UpdateFlags signal. /// SceneUpdateFlags accumulated since last `beginFrame()` - IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; - - ref mpPathTracerBlock; ///< Parameter block for the path tracer. - - bool mRecompile = false; ///< Set to true when program specialization has changed. - bool mVarsChanged = true; ///< This is set to true whenever the program vars have changed and resources need to be rebound. - bool mOptionsChanged = false; ///< True if the config has changed since last frame. - bool mGBufferAdjustShadingNormals = false; ///< True if GBuffer/VBuffer has adjusted shading normals enabled. - bool mFixedSampleCount = true; ///< True if a fixed sample count per pixel is used. Otherwise load it from the pass sample count input. - bool mOutputGuideData = false; ///< True if guide data should be generated as outputs. - bool mOutputNRDData = false; ///< True if NRD diffuse/specular data should be generated as outputs. - bool mOutputNRDAdditionalData = false; ///< True if NRD data from delta and residual paths should be generated as designated outputs rather than being included in specular NRD outputs. - - ref mpGeneratePaths; ///< Fullscreen compute pass generating paths starting at primary hits. - ref mpResolvePass; ///< Sample resolve pass. - ref mpReflectTypes; ///< Helper for reflecting structured buffer types. - - std::unique_ptr mpTracePass; ///< Main trace pass. - std::unique_ptr mpTraceDeltaReflectionPass; ///< Delta reflection trace pass (for NRD). - std::unique_ptr mpTraceDeltaTransmissionPass; ///< Delta transmission trace pass (for NRD). - - ref mpSampleOffset; ///< Output offset into per-sample buffers to where the samples for each pixel are stored (the offset is relative the start of the tile). Only used with non-fixed sample count. - ref mpSampleColor; ///< Compact per-sample color buffer. This is used only if spp > 1. - ref mpSampleGuideData; ///< Compact per-sample denoiser guide data. - ref mpSampleNRDRadiance; ///< Compact per-sample NRD radiance data. - ref mpSampleNRDHitDist; ///< Compact per-sample NRD hit distance data. - ref mpSampleNRDPrimaryHitNeeOnDelta;///< Compact per-sample NEE on delta primary vertices data. - ref mpSampleNRDEmission; ///< Compact per-sample NRD emission data. - ref mpSampleNRDReflectance; ///< Compact per-sample NRD reflectance data. + IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; + + ref mpPathTracerBlock; ///< Parameter block for the path tracer. + + bool mRecompile = false; ///< Set to true when program specialization has changed. + bool mVarsChanged = true; ///< This is set to true whenever the program vars have changed and resources need to be rebound. + bool mOptionsChanged = false; ///< True if the config has changed since last frame. + bool mGBufferAdjustShadingNormals = false; ///< True if GBuffer/VBuffer has adjusted shading normals enabled. + bool mFixedSampleCount = true; ///< True if a fixed sample count per pixel is used. Otherwise load it from the pass sample count input. + bool mOutputGuideData = false; ///< True if guide data should be generated as outputs. + bool mOutputNRDData = false; ///< True if NRD diffuse/specular data should be generated as outputs. + bool mOutputNRDAdditionalData = false; ///< True if NRD data from delta and residual paths should be generated as designated outputs + ///< rather than being included in specular NRD outputs. + bool mSpecularMVecEnabled = false; ///< Last observed state of ReSTIRPathTracing::Options::enableSpecularMotionVectors. Tracked so a toggle + ///< triggers recompilation (delta trace passes and OUTPUT_DELTA_MOTION_VECTORS specialization). + + ref mpGeneratePaths; ///< Fullscreen compute pass generating paths starting at primary hits. + ref mpResolvePass; ///< Sample resolve pass. + ref mpResolveReSTIRPTPass; ///< ReSTIR PT resolve pass. + ref mpReflectTypes; ///< Helper for reflecting structured buffer types. + + std::unique_ptr mpTracePass; ///< Main trace pass. + std::unique_ptr mpTraceDeltaReflectionPass; ///< Delta reflection trace pass (for NRD). + std::unique_ptr mpTraceDeltaTransmissionPass; ///< Delta transmission trace pass (for NRD). + + ref mpSampleOffset; ///< Output offset into per-sample buffers to where the samples for each pixel are stored (the offset is + ///< relative the start of the tile). Only used with non-fixed sample count. + ref mpSampleColor; ///< Compact per-sample color buffer. This is used only if spp > 1. + ref mpSampleGuideData; ///< Compact per-sample denoiser guide data. + ref mpSampleNRDRadiance; ///< Compact per-sample NRD radiance data. + ref mpSampleNRDHitDist; ///< Compact per-sample NRD hit distance data. + ref mpSampleNRDPrimaryHitNeeOnDelta; ///< Compact per-sample NEE on delta primary vertices data. + ref mpSampleNRDEmission; ///< Compact per-sample NRD emission data. + ref mpSampleNRDReflectance; ///< Compact per-sample NRD reflectance data. }; + +FALCOR_ENUM_REGISTER(PathTracer::TracePassMode); diff --git a/Source/RenderPasses/PathTracer/PathTracer.slang b/Source/RenderPasses/PathTracer/PathTracer.slang index 9813be1432..f458069f0d 100644 --- a/Source/RenderPasses/PathTracer/PathTracer.slang +++ b/Source/RenderPasses/PathTracer/PathTracer.slang @@ -43,9 +43,14 @@ import Utils.Debug.PixelDebug; import LoadShadingData; import ColorType; import NRDHelpers; +__exported import PathTracerUtils; __exported import PathState; __exported import Params; +#if USE_RESTIRPT +import ReSTIRPathTracing.ReSTIRPT; +#endif import Rendering.RTXDI.RTXDI; +import LightTileRISHelper; /** Interface for querying visibility in the scene. This is used in `handleHit`. @@ -56,7 +61,8 @@ interface IVisibilityQuery \param[in] ray Ray. \return Returns true if the ray endpoints are mutually visible (i.e. the ray does NOT intersect the scene). */ - [mutating] bool traceVisibilityRay(const Ray ray); + [mutating] + bool traceVisibilityRay(const Ray ray); }; /** Path tracer. @@ -66,25 +72,29 @@ interface IVisibilityQuery */ struct PathTracer { - PathTracerParams params; ///< Runtime parameters. + PathTracerParams params; ///< Runtime parameters. // Samplers - EnvMapSampler envMapSampler; ///< Environment map sampler. Only valid when kUseEnvLight == true. - EmissiveLightSampler emissiveSampler; ///< Emissive light sampler. Only valid when kUseEmissiveLights == true. + EnvMapSampler envMapSampler; ///< Environment map sampler. Only valid when kUseEnvLight == true. + EmissiveLightSampler emissiveSampler; ///< Emissive light sampler. Only valid when kUseEmissiveLights == true. // Inputs - Texture2D vbuffer; ///< Fullscreen V-buffer for the primary hits. - Texture2D viewDir; ///< Optional view direction. Only valid when kUseViewDir == true. - Texture2D sampleCount; ///< Optional input sample count buffer. Only valid when kSamplesPerPixel == 0. - Texture2D sampleOffset; ///< Output offset into per-sample buffers. Only valid when kSamplesPerPixel == 0. + Texture2D vbuffer; ///< Fullscreen V-buffer for the primary hits. + Texture2D viewDir; ///< Optional view direction. Only valid when kUseViewDir == true. + Texture2D sampleCount; ///< Optional input sample count buffer. Only valid when kSamplesPerPixel == 0. + Texture2D sampleOffset; ///< Output offset into per-sample buffers. Only valid when kSamplesPerPixel == 0. // Outputs - RWStructuredBuffer sampleColor; ///< Output per-sample color if kSamplesPerPixel != 1. - RWStructuredBuffer sampleGuideData; ///< Output per-sample guide data. - NRDBuffers outputNRD; ///< Output NRD data. + RWStructuredBuffer sampleColor; ///< Output per-sample color if kSamplesPerPixel != 1. + RWStructuredBuffer sampleGuideData; ///< Output per-sample guide data. + NRDBuffers outputNRD; ///< Output NRD data. - RWTexture2D outputColor; ///< Output color buffer if kSamplesPerPixel == 1. + RWTexture2D outputColor; ///< Output color buffer if kSamplesPerPixel == 1. +#if USE_RESTIRPT + ReSTIRPT restirpt; ///< ReSTIR PT module data. +#endif + LightTileRIS lightTileRIS; /******************************************************************* Static members *******************************************************************/ @@ -96,77 +106,13 @@ struct PathTracer static const bool kUseAnalyticLights = USE_ANALYTIC_LIGHTS; static const bool kUseCurves = USE_CURVES; static const bool kUseHairMaterial = USE_HAIR_MATERIAL; - // Additional specialization. static const bool kOutputGuideData = OUTPUT_GUIDE_DATA; - /** Types of samplable lights. - */ - enum class LightType - { - EnvMap, - Emissive, - Analytic - }; - - /** Describes a light sample. - */ - struct LightSample - { - float3 Li; ///< Incident radiance at the shading point (unshadowed). This is already divided by the pdf. - float pdf; ///< Pdf with respect to solid angle at the shading point. - float3 origin; ///< Ray origin for visibility evaluation (offseted to avoid self-intersection). - float distance; ///< Ray distance for visibility evaluation (shortened to avoid self-intersection). - float3 dir; ///< Ray direction for visibility evaluation (normalized). - uint lightType; ///< Light type this sample comes from (LightType casted to uint). - - Ray getVisibilityRay() { return Ray(origin, dir, 0.f, distance); } - }; - - /** Describes a path vertex. - */ - struct PathVertex - { - uint index; ///< Vertex index (0 = camera, 1 = primary hit, 2 = secondary hit, etc.). - float3 pos; ///< Vertex position. - float3 faceNormal; ///< Geometry normal at the vertex (zero if not on a surface). - bool frontFacing; ///< True if path vertex is on the front-facing side (if on a surface). - - /** Initializes a path vertex. - \param[in] index Vertex index. - \param[in] pos Vertex position. - \param[in] faceNormal Geometry normal. - \param[in] frontFacing Front-facing flag. - */ - __init(uint index, float3 pos, float3 faceNormal = float3(0.f), bool frontFacing = true) - { - this.index = index; - this.pos = pos; - this.faceNormal = faceNormal; - this.frontFacing = frontFacing; - } - - /** Get position with offset applied in direction of the geometry normal to avoid self-intersection - for visibility rays. - \param[in] rayDir Direction of the visibility ray (does not need to be normalized). - \return Returns the offseted position. - */ - float3 getRayOrigin(float3 rayDir) - { - return computeRayOrigin(pos, dot(faceNormal, rayDir) >= 0 ? faceNormal : -faceNormal); - } - - /** Returns the oriented face normal. - \return Face normal flipped to the same side as the view vector. - */ - float3 getOrientedFaceNormal() - { - return frontFacing ? faceNormal : -faceNormal; - } - }; + // LightType, LightSample, PathVertex, and evalMIS are defined in PathTracerUtils.slang as standalone types. /** Set guiding data when background is hit. - */ + */ static void setBackgroundGuideData(inout GuideData guideData, const float3 dir, const float3 Le) { if (kOutputGuideData) @@ -182,7 +128,7 @@ struct PathTracer } /** Set guiding data when primary surface is hit. - */ + */ static void setPrimarySurfaceGuideData(inout GuideData guideData, const ShadingData sd, const BSDFProperties bsdfProperties) { if (kOutputGuideData) @@ -213,7 +159,7 @@ struct PathTracer } /** Set guiding data when indirect surface is hit. - */ + */ static void setIndirectSurfaceGuideData(inout GuideData guideData, const ShadingData sd, const BSDFProperties bsdfProperties) { if (any(bsdfProperties.emission > 0.f)) @@ -244,11 +190,8 @@ struct PathTracer const uint specularBounces = path.getBounces(BounceType::Specular); const uint transmissionBounces = path.getBounces(BounceType::Transmission); const uint surfaceBounces = diffuseBounces + specularBounces + transmissionBounces; - return - (surfaceBounces > kMaxSurfaceBounces) || - (diffuseBounces > kMaxDiffuseBounces) || - (specularBounces > kMaxSpecularBounces) || - (transmissionBounces > kMaxTransmissionBounces); + return (surfaceBounces > kMaxSurfaceBounces) || (diffuseBounces > kMaxDiffuseBounces) || (specularBounces > kMaxSpecularBounces) || + (transmissionBounces > kMaxTransmissionBounces); } /** Compute the total length of a terminated path. @@ -276,19 +219,44 @@ struct PathTracer path.setActive(); path.id = pathID; path.thp = float3(1.f); + path.imageXform = float4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f + ); const uint2 pixel = path.getPixel(); + // Create sample generator. + uint maxSpp = kSamplesPerPixel > 0 ? kSamplesPerPixel : kMaxSamplesPerPixel; +#if USE_RESTIRPT + maxSpp = restirpt.sgCount(); +#endif + path.sg = SampleGenerator(pixel, params.seed * maxSpp + path.getSampleIdx()); + // Create primary ray. - Ray cameraRay = gScene.camera.computeRayPinhole(pixel, params.frameDim); - if (kUseViewDir) cameraRay.dir = -viewDir[pixel]; + Ray cameraRay; +#if USE_RESTIRPT + if (restirpt.kUseAreaReservoirs) + { + float4 filmLensSample = float4(sampleNext2D(path.sg), sampleNext2D(path.sg)); + if (kSamplesPerPixel != 1) + { + SampleGenerator sgForFilmLens = SampleGenerator(pixel, params.seed * restirpt.sgCount()); + filmLensSample = float4(sampleNext2D(sgForFilmLens), sampleNext2D(sgForFilmLens)); + } + cameraRay = gScene.camera.computeRayThinlensSubPixelRandom(pixel, params.frameDim, filmLensSample.xy, filmLensSample.zw); + } + else +#endif + { + cameraRay = gScene.camera.computeRayPinhole(pixel, params.frameDim); + if (kUseViewDir) cameraRay.dir = -viewDir[pixel]; + } path.origin = cameraRay.origin; path.dir = cameraRay.dir; - // Create sample generator. - const uint maxSpp = kSamplesPerPixel > 0 ? kSamplesPerPixel : kMaxSamplesPerPixel; - path.sg = SampleGenerator(pixel, params.seed * maxSpp + path.getSampleIdx()); - // Load the primary hit info from the V-buffer. const HitInfo hit = unpackHitInfo(vbuffer[pixel]); @@ -298,6 +266,10 @@ struct PathTracer path.setHit(hit); path.setVertexIndex(1); } + +#if USE_RESTIRPT + restirpt.InitializePathData(path); +#endif } /** Set up path for logging and debugging. @@ -351,11 +323,8 @@ struct PathTracer computesEmissive = false; // MSB = highest priority hint. - return - select(computesEmissive, 0x1, 0x0) | - select(samplesLight, 0x2, 0x0) | - select(terminated, 0x4, 0x0) | - select(terminatedByRussianRoulette, 0x8, 0x0); + return select(computesEmissive, 0x1, 0x0) | select(samplesLight, 0x2, 0x0) | select(terminated, 0x4, 0x0) | + select(terminatedByRussianRoulette, 0x8, 0x0); } /** Update the path throughouput. @@ -365,15 +334,39 @@ struct PathTracer void updatePathThroughput(inout PathState path, const float3 weight) { path.thp *= weight; +#if USE_RESTIRPT + path.restirPTData.pathRcInfo.rcThroughput *= weight; +#endif } /** Add radiance to the path contribution. \param[in,out] path Path state. \param[in] radiance Vertex radiance. */ - void addToPathContribution(inout PathState path, const float3 radiance) + void addToPathContribution(inout PathState path, const float3 radiance) { path.L += path.thp * radiance; } + + /** Evaluate global light profile for materials that have it enabled. + */ + void evaluateLightProfile(inout BSDFProperties bsdfProperties, const ShadingData sd, const PathState path) + { + if (gScene.materials.hasLightProfile(sd.materialID)) + { + // Do not apply light profile on primary hits in order to have full emission in all directions. + if (path.getVertexIndex() > 0) + { + bsdfProperties.emission *= gScene.materials.evalLightProfile(dot(sd.faceN, -path.dir)); + } + } + } + + /** Skip random numbers consumed by light sampling to keep RNG alignment for ReSTIR replay. + Must match the actual consumption pattern of generateLightSample(). + */ + void skipLightSampleRandomNumbers(inout SampleGenerator sg) { - path.L += path.thp * radiance; + sampleNext1D(sg); // light type selection or light RIS sample seed + if (kUseLightTileRIS) return; + sampleNext4D(sg); // light sample (padded to 4D when PAD_RANDOM_NUMBERS is enabled) } /** Generates a new scatter ray using BSDF importance sampling. @@ -384,9 +377,16 @@ struct PathTracer */ bool generateScatterRay(const ShadingData sd, const IMaterialInstance mi, inout PathState path) { - BSDFSample result; + BSDFSample result = {}; + bool valid = mi.sample(sd, path.sg, result, kUseBSDFSampling); - if (valid) valid = generateScatterRay(result, sd, mi, path); + +#if USE_RESTIRPT + restirpt.ProcessScatterData(path, valid, result, sd, mi); +#endif + + if (valid) + valid = generateScatterRay(result, sd, mi, path); // Ignore valid on purpose for now. if (kOutputNRDData) @@ -402,7 +402,8 @@ struct PathTracer if (kOutputNRDAdditionalData) { - // Mark path as delta-only if it followed delta lobe on the primary hit, even though there might have been non-delta lobes. + // Mark path as delta-only if it followed delta lobe on the primary hit, even though there might have been non-delta + // lobes. path.setDeltaOnlyPath(result.isLobe(LobeType::DeltaReflection) || result.isLobe(LobeType::DeltaTransmission)); path.setDeltaReflectionPrimaryHit(result.isLobe(LobeType::DeltaReflection)); @@ -412,7 +413,8 @@ struct PathTracer if (path.getVertexIndex() > 1) { - if (hasNonDeltaLobes) path.setDeltaOnlyPath(false); + if (hasNonDeltaLobes) + path.setDeltaOnlyPath(false); if (kOutputNRDAdditionalData && path.isDeltaTransmissionPath() && path.isDeltaOnlyPath() && hasDeltaTransmissionLobe) { @@ -512,42 +514,7 @@ struct PathTracer return valid; } - /** Evaluates the currently configured heuristic for multiple importance sampling (MIS). - \param[in] n0 Number of samples taken from the first sampling strategy. - \param[in] p0 Pdf for the first sampling strategy. - \param[in] n1 Number of samples taken from the second sampling strategy. - \param[in] p1 Pdf for the second sampling strategy. - \return Weight for the contribution from the first strategy (p0). - */ - float evalMIS(float n0, float p0, float n1, float p1) - { - switch (MISHeuristic(kMISHeuristic)) - { - case MISHeuristic::Balance: - { - // Balance heuristic - float q0 = n0 * p0; - float q1 = n1 * p1; - return q0 / (q0 + q1); - } - case MISHeuristic::PowerTwo: - { - // Power two heuristic - float q0 = (n0 * p0) * (n0 * p0); - float q1 = (n1 * p1) * (n1 * p1); - return q0 / (q0 + q1); - } - case MISHeuristic::PowerExp: - { - // Power exp heuristic - float q0 = pow(n0 * p0, kMISPowerExponent); - float q1 = pow(n1 * p1, kMISPowerExponent); - return q0 / (q0 + q1); - } - default: - return 0.f; - } - } + // evalMIS() is now a standalone function in PathTracerUtils.slang /** Generates a light sample on the environment map. \param[in] vertex Path vertex. @@ -559,11 +526,13 @@ struct PathTracer { ls = {}; // Default initialization to avoid divergence at returns. - if (!kUseEnvLight) return false; + if (!kUseEnvLight) + return false; // Sample environment map. EnvMapSample lightSample; - if (!envMapSampler.sample(sampleNext2D(sg), lightSample)) return false; + if (!envMapSampler.sample(sampleNext2D(sg), lightSample)) + return false; // Setup returned sample. ls.Li = lightSample.pdf > 0.f ? lightSample.Le / lightSample.pdf : float3(0); @@ -571,7 +540,9 @@ struct PathTracer ls.origin = vertex.getRayOrigin(lightSample.dir); ls.distance = kRayTMax; ls.dir = lightSample.dir; - +#if PAD_RANDOM_NUMBERS + sampleNext2D(sg); +#endif return any(ls.Li > 0.f); } @@ -585,10 +556,12 @@ struct PathTracer bool generateEmissiveSample(const PathVertex vertex, const bool upperHemisphere, inout SampleGenerator sg, out LightSample ls) { ls = {}; // Default initialization to avoid divergence at returns. - if (!kUseEmissiveLights) return false; + if (!kUseEmissiveLights) + return false; TriangleLightSample tls; - if (!emissiveSampler.sampleLight(vertex.pos, vertex.getOrientedFaceNormal(), upperHemisphere, sg, tls)) return false; + if (!emissiveSampler.sampleLight(vertex.pos, vertex.getOrientedFaceNormal(), upperHemisphere, sg, tls)) + return false; // Setup returned sample. ls.Li = tls.pdf > 0.f ? tls.Le / tls.pdf : float3(0); @@ -599,7 +572,11 @@ struct PathTracer float3 toLight = lightPos - ls.origin; ls.distance = length(toLight); ls.dir = normalize(toLight); - +#if USE_RESTIRPT + ls.barycentrics = tls.uv; + ls.triangleId = tls.triangleIndex; + ls.geometryTerm = tls.geometryTerm; +#endif return any(ls.Li > 0.f); } @@ -614,7 +591,8 @@ struct PathTracer ls = {}; // Default initialization to avoid divergence at returns. uint lightCount = gScene.getLightCount(); - if (!kUseAnalyticLights || lightCount == 0) return false; + if (!kUseAnalyticLights || lightCount == 0) + return false; // Sample analytic light source selected uniformly from the light list. // TODO: Sample based on estimated contributions as pdf. @@ -622,7 +600,8 @@ struct PathTracer // Sample local light source. AnalyticLightSample lightSample; - if (!sampleLight(vertex.pos, gScene.getLight(lightIndex), sg, lightSample)) return false; + if (!sampleLight(vertex.pos, gScene.getLight(lightIndex), sg, lightSample)) + return false; // Setup returned sample. ls.pdf = lightSample.pdf / lightCount; @@ -633,7 +612,10 @@ struct PathTracer // Do not worry about adjusting the ray length to avoid self-intersections at the light. ls.distance = lightSample.distance; ls.dir = lightSample.dir; - +#if USE_RESTIRPT + ls.normal = lightSample.normalW; + ls.geometryTerm = ls.pdf == 0.f ? 1.f / (ls.distance * ls.distance) : abs(dot(ls.normal, ls.dir)) / (ls.distance * ls.distance); +#endif return any(ls.Li > 0.f); } @@ -650,7 +632,8 @@ struct PathTracer // Normalize probabilities. Early out if zero. float sum = p[0] + p[1] + p[2]; - if (sum == 0.f) return; + if (sum == 0.f) + return; float invSum = 1.f / sum; p[0] *= invSum; @@ -658,9 +641,24 @@ struct PathTracer p[2] *= invSum; } - float getEnvMapSelectionProbability() { float p[3]; getLightTypeSelectionProbabilities(p); return p[0]; } - float getEmissiveSelectionProbability() { float p[3]; getLightTypeSelectionProbabilities(p); return p[1]; } - float getAnalyicSelectionProbability() { float p[3]; getLightTypeSelectionProbabilities(p); return p[2]; } + float getEnvMapSelectionProbability() + { + float p[3]; + getLightTypeSelectionProbabilities(p); + return p[0]; + } + float getEmissiveSelectionProbability() + { + float p[3]; + getLightTypeSelectionProbabilities(p); + return p[1]; + } + float getAnalyicSelectionProbability() + { + float p[3]; + getLightTypeSelectionProbabilities(p); + return p[2]; + } /** Select a light type for sampling. \param[out] lightType Selected light type. @@ -702,16 +700,24 @@ struct PathTracer \param[out] ls Struct describing valid samples. \return True if the sample is valid and has nonzero contribution, false otherwise. */ - bool generateLightSample(const PathVertex vertex, const bool sampleUpperHemisphere, const bool sampleLowerHemisphere, inout SampleGenerator sg, out LightSample ls) + bool generateLightSample( + const PathVertex vertex, + const bool sampleUpperHemisphere, + const bool sampleLowerHemisphere, + inout SampleGenerator sg, + out LightSample ls + ) { ls = {}; uint lightType; float selectionPdf; - if (!selectLightType(lightType, selectionPdf, sg)) return false; + if (!selectLightType(lightType, selectionPdf, sg)) + return false; bool valid = false; - if (kUseEnvLight && lightType == (uint)LightType::EnvMap) valid = generateEnvMapSample(vertex, sg, ls); + if (kUseEnvLight && lightType == (uint)LightType::EnvMap) + valid = generateEnvMapSample(vertex, sg, ls); if (kUseEmissiveLights && lightType == (uint)LightType::Emissive) { // Emissive light samplers have an option to exclusively sample the upper hemisphere. @@ -722,7 +728,8 @@ struct PathTracer { valid = generateAnalyticLightSample(vertex, sg, ls); } - if (!valid) return false; + if (!valid) + return false; // Reject samples in non-requested hemispheres. float NdotL = dot(vertex.getOrientedFaceNormal(), ls.dir); @@ -787,6 +794,9 @@ struct PathTracer return true; } path.thp /= 1.f - prob; +#if USE_RESTIRPT + path.restirPTData.sourcePdf *= 1.f - prob; +#endif return false; } @@ -849,7 +859,8 @@ struct PathTracer } // Reject false hits in nested dielectrics. - if (!handleNestedDielectrics(sd, path)) return; + if (!handleNestedDielectrics(sd, path)) + return; logPathVertex(); @@ -857,10 +868,14 @@ struct PathTracer let lod = createTextureSampler(path, isPrimaryHit); // Create BSDF instance and query its properties. + // resolved. let hints = getMaterialInstanceHints(path.hit, isPrimaryHit); const IMaterialInstance mi = gScene.materials.getMaterialInstance(sd, lod, hints); BSDFProperties bsdfProperties = mi.getProperties(sd); + // Modify the emission on geometry with applied IES profile + evaluateLightProfile(bsdfProperties, sd, path); + // Disable specular lobes if caustics are disabled and path already contains a diffuse vertex. bool isSpecular = bsdfProperties.roughness <= params.specularRoughnessThreshold; if (kDisableCaustics && path.getBounces(BounceType::Diffuse) > 0 && isSpecular) @@ -878,19 +893,29 @@ struct PathTracer const bool isLightSamplable = path.isLightSamplable(); // Add emitted radiance. - // The primary hit is always included, secondary hits only if emissive lights are enabled and the full light contribution hasn't been sampled elsewhere. + // The primary hit is always included, secondary hits only if emissive lights are enabled and the full light contribution hasn't + // been sampled elsewhere. bool computeEmissive = isPrimaryHit || kUseEmissiveLights && (!kUseNEE || kUseMIS || !path.isLightSampled() || !isLightSamplable); // With RTXDI enabled, we sample the full direct illumination contribution on the primary hit. // Skip any additional contribution on the secondary hit unless it comes from a scatter event // that RTXDI cannot handle, such as transmission or delta scattering events. - if (kUseRTXDI && path.getVertexIndex() == 2 && !path.isTransmission() && !path.isDelta()) computeEmissive = false; + if (kUseRTXDI && path.getVertexIndex() == 2 && !path.isTransmission() && !path.isDelta()) + computeEmissive = false; float3 attenuatedEmission = 0.f; + // ReSTIR PT: populate path segment data and draw RIS random numbers. +#if USE_RESTIRPT + ReSTIRPT::PathSegmentData ps = {}; + restirpt.PopulatePathSegmentData(ps, path, sd); + float uBRDFRIS = sampleNext1D(path.sg); +#endif + if (computeEmissive && any(bsdfProperties.emission > 0.f)) { float misWeight = 1.f; + float lightPdf = 0.f; if (kUseEmissiveLights && kUseNEE && kUseMIS && isTriangleHit && !isPrimaryHit && path.isLightSampled() && isLightSamplable) { // If NEE and MIS are enabled, and we've already sampled emissive lights, @@ -907,17 +932,22 @@ struct PathTracer // Evaluate PDF at the hit, had it been generated with light sampling. // Emissive light samplers have an option to exclusively sample the upper hemisphere. bool upperHemisphere = path.isLightSampledUpper() && !path.isLightSampledLower(); - float lightPdf = getEmissiveSelectionProbability() * emissiveSampler.evalPdf(path.origin, path.normal, upperHemisphere, hit); + lightPdf = + getEmissiveSelectionProbability() * emissiveSampler.evalPdf(path.origin, path.normal, upperHemisphere, hit); // Compute MIS weight by combining this with BSDF sampling. // Note we can assume path.pdf > 0.f since we shouldn't have got here otherwise. - misWeight = evalMIS(1, path.pdf, 1, lightPdf); + misWeight = evalMIS(1, path.pdf, (kUseLightTileRIS ? lightTileRIS.computeInitialLightSampleCount(path.getVertexIndex() - 1) : 1), lightPdf ); } // Accumulate emitted radiance weighted by path throughput and MIS weight. addToPathContribution(path, misWeight * bsdfProperties.emission); attenuatedEmission = path.thp * misWeight * bsdfProperties.emission; + +#if USE_RESTIRPT + restirpt.HandleBSDFCase(path, uBRDFRIS, ps, lightPdf, attenuatedEmission, misWeight, bsdfProperties.emission); +#endif } // Terminate after scatter ray on last vertex has been processed. @@ -955,10 +985,21 @@ struct PathTracer // TODO: Support multiple shadow rays. path.setLightSampled(false, false); + +#if USE_RESTIRPT + float uNEERIS = sampleNext1D(path.sg); +#endif + if (applyNEE || applyRTXDI) { LightSample ls = {}; bool validSample = false; +#if USE_LIGHT_TILE_RIS + uint neeSeed = path.sg.next(); + LightTileRISData lightTileRISData = {}; +#elif USE_RESTIRPT + uint neeSeed = restirpt.getCurrentSeed(path.sg); +#endif if (applyRTXDI) { @@ -972,26 +1013,47 @@ struct PathTracer PathVertex vertex = PathVertex(path.getVertexIndex(), sd.posW, sd.faceN, sd.frontFacing); const bool canNeeSampleSphere = isCurveHit || isCurvePolyTubeHit; - // Determine if upper/lower hemispheres need to be sampled. + // Determine if upper/lower hemispheres need to be sampled. bool sampleUpperHemisphere = canNeeSampleSphere || ((lobeTypes & (uint)LobeType::NonDeltaReflection) != 0); bool sampleLowerHemisphere = canNeeSampleSphere || ((lobeTypes & (uint)LobeType::NonDeltaTransmission) != 0); - if (!kUseLightsInDielectricVolumes && path.isInsideDielectricVolume()) sampleUpperHemisphere = false; + if (!kUseLightsInDielectricVolumes && path.isInsideDielectricVolume()) + sampleUpperHemisphere = false; + // Capture RNG seed before light sampling for ReSTIR PT replay. +#if USE_LIGHT_TILE_RIS + validSample = lightTileRIS.GenerateSample(path, sd, mi, ls, isCurveHit, false, neeSeed, params, lightTileRISData); +#else // Sample a light. validSample = generateLightSample(vertex, sampleUpperHemisphere, sampleLowerHemisphere, path.sg, ls); +#endif path.setLightSampled(sampleUpperHemisphere, sampleLowerHemisphere); } if (validSample) { +#if USE_RESTIRPT + bool isPunctualLight = ls.pdf == 0.f; + if (!kUseLightTileRIS && ls.lightType == (uint)LightType::Analytic && ls.pdf == 0.f) + ls.pdf = getAnalyicSelectionProbability(); + float3 Le = ls.Li * ls.pdf; +#endif + // Apply MIS weight. + float scatterPdf = 0.f; + float3 weight = 0.f; +#if USE_LIGHT_TILE_RIS + scatterPdf = lightTileRISData.selectedPdf; + weight = lightTileRISData.selectedBrdf; +#else + scatterPdf = mi.evalPdf(sd, ls.dir, kUseBSDFSampling); + weight = mi.eval(sd, ls.dir, path.sg); +#endif if (kUseMIS && !applyRTXDI && ls.lightType != (uint)LightType::Analytic) { - float scatterPdf = mi.evalPdf(sd, ls.dir, kUseBSDFSampling); - ls.Li *= evalMIS(1, ls.pdf, 1, scatterPdf); + int initialLightSampleCount = kUseLightTileRIS ? lightTileRIS.computeInitialLightSampleCount(path.getVertexIndex()) : 1; + ls.Li *= evalMIS(initialLightSampleCount, ls.pdf, 1, scatterPdf); } - float3 weight = mi.eval(sd, ls.dir, path.sg); float3 Lr = weight * ls.Li; if (any(Lr > 0.f)) { @@ -1010,15 +1072,33 @@ struct PathTracer logTraceRay(PixelStatsRayType::Visibility); bool visible = vq.traceVisibilityRay(ray); - if (visible) addToPathContribution(path, Lr); + if (visible) + { + float pdfWarpFactor = 1.f; +#if USE_LIGHT_TILE_RIS + pdfWarpFactor = lightTileRISData.pdfWarpFactor; +#endif + addToPathContribution(path, Lr / pdfWarpFactor); +#if USE_RESTIRPT + restirpt.HandleNEECase( path, sd, ls, uNEERIS, scatterPdf, pdfWarpFactor, neeSeed, ps, + Le, Lr, mi.supportBSDFIndexing(), isPunctualLight); +#endif + } } } } +#if USE_RESTIRPT + else + { + skipLightSampleRandomNumbers(path.sg); + } +#endif // Russian roulette to terminate paths early. if (kUseRussianRoulette) { - if (terminatePathByRussianRoulette(path, sampleNext1D(path.sg))) return; + if (terminatePathByRussianRoulette(path, sampleNext1D(path.sg))) + return; } const bool wasDeltaOnlyPathBeforeScattering = path.isDeltaOnlyPath(); @@ -1026,6 +1106,13 @@ struct PathTracer // Generate the next path segment or terminate. bool valid = generateScatterRay(sd, mi, path); +#if USE_RESTIRPT + if (path.getVertexIndex() > 1) + { + restirpt.setRcVertex(path, ps); + } +#endif + // Output guide data. if (path.getVertexIndex() == 1) { @@ -1045,8 +1132,20 @@ struct PathTracer setNRDPrimaryHitReflectance(outputNRD, kUseNRDDemodulation, path, pixel, isPrimaryHit, sd, bsdfProperties); setNRDSampleHitDist(outputNRD, path, outSampleIdx); - setNRDSampleEmission(outputNRD, kUseNRDDemodulation, path, outSampleIdx, isPrimaryHit, attenuatedEmission, wasDeltaOnlyPathBeforeScattering); - setNRDSampleReflectance(outputNRD, kUseNRDDemodulation, path, outSampleIdx, isPrimaryHit, sd, bsdfProperties, lobeTypes, wasDeltaOnlyPathBeforeScattering); + setNRDSampleEmission( + outputNRD, kUseNRDDemodulation, path, outSampleIdx, isPrimaryHit, attenuatedEmission, wasDeltaOnlyPathBeforeScattering + ); + setNRDSampleReflectance( + outputNRD, + kUseNRDDemodulation, + path, + outSampleIdx, + isPrimaryHit, + sd, + bsdfProperties, + lobeTypes, + wasDeltaOnlyPathBeforeScattering + ); setNRDPrimaryHitSeparatedRadiance(outputNRD, kUseNRDDemodulation, path, isPrimaryHit, outSampleIdx); } @@ -1054,10 +1153,12 @@ struct PathTracer const bool isLastVertex = hasFinishedSurfaceBounces(path); // Terminate if this is the last path vertex and light sampling already completely sampled incident radiance. - if (kUseNEE && !kUseMIS && isLastVertex && path.isLightSamplable()) valid = false; + if (kUseNEE && !kUseMIS && isLastVertex && path.isLightSamplable()) + valid = false; // Terminate caustics paths. - if (kDisableCaustics && path.getBounces(BounceType::Diffuse) > 0 && path.isSpecular()) valid = false; + if (kDisableCaustics && path.getBounces(BounceType::Diffuse) > 0 && path.isSpecular()) + valid = false; if (!valid) { @@ -1085,7 +1186,12 @@ struct PathTracer // With RTXDI enabled, we sample the full direct illumination contribution on the primary hit. // Skip any additional contribution on the secondary hit unless it comes from a scatter event // that RTXDI cannot handle, such as transmission, delta or volume scattering events. - if (kUseRTXDI && path.getVertexIndex() == 2 && !path.isTransmission() && !path.isDelta()) computeEnv = false; + if (kUseRTXDI && path.getVertexIndex() == 2 && !path.isTransmission() && !path.isDelta()) + computeEnv = false; + +#if USE_RESTIRPT + float uBRDFRIS = sampleNext1D(path.sg); +#endif float3 emitterRadiance = 0.f; @@ -1094,26 +1200,31 @@ struct PathTracer logPathVertex(); float misWeight = 1.f; + float lightPdf = 0.f; if (kUseNEE && kUseMIS && path.isLightSampled() && isLightSamplable) { // If NEE and MIS are enabled, and we've already sampled the env map, // then we need to evaluate the MIS weight here to account for the remaining contribution. // Evaluate PDF, had it been generated with light sampling. - float lightPdf = getEnvMapSelectionProbability() * envMapSampler.evalPdf(path.dir); + lightPdf = getEnvMapSelectionProbability() * envMapSampler.evalPdf(path.dir); // Compute MIS weight by combining this with BSDF sampling. // Note we can assume path.pdf > 0.f since we shouldn't have got here otherwise. - misWeight = evalMIS(1, path.pdf, 1, lightPdf); + + misWeight = evalMIS( 1, path.pdf, (kUseLightTileRIS ? lightTileRIS.computeInitialLightSampleCount(path.getVertexIndex() - 1) : 1), lightPdf); } float3 Le = envMapSampler.eval(path.dir); emitterRadiance = misWeight * Le; addToPathContribution(path, emitterRadiance); - if (kOutputGuideData && path.getVertexIndex() == 2 - && (path.getBounces(BounceType::Specular) == 1 - || path.getBounces(BounceType::Transmission) == 1)) +#if USE_RESTIRPT + restirpt.HandleBSDFCaseHitEnv(path, uBRDFRIS, lightPdf, emitterRadiance, Le); +#endif + + if (kOutputGuideData && path.getVertexIndex() == 2 && + (path.getBounces(BounceType::Specular) == 1 || path.getBounces(BounceType::Transmission) == 1)) { // Compress dynamic range similar to UE4. float3 compressedColor = pow(Le / (Le + 1.0f), 0.454545f); @@ -1136,20 +1247,44 @@ struct PathTracer #if defined(DELTA_REFLECTION_PASS) if (path.isDeltaReflectionPrimaryHit()) { - writeNRDDeltaReflectionGuideBuffers(outputNRD, kUseNRDDemodulation, path.getPixel(), 0.f, path.thp * emitterRadiance, -path.dir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength); + writeNRDDeltaReflectionGuideBuffers( + outputNRD, + kUseNRDDemodulation, + path.getPixel(), + 0.f, + path.thp * emitterRadiance, + -path.dir, + 0.f, + kNRDInvalidPathLength, + kNRDInvalidPathLength + ); } else { - writeNRDDeltaReflectionGuideBuffers(outputNRD, kUseNRDDemodulation, path.getPixel(), 0.f, 0.f, -path.dir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength); + writeNRDDeltaReflectionGuideBuffers( + outputNRD, kUseNRDDemodulation, path.getPixel(), 0.f, 0.f, -path.dir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength + ); } #elif defined(DELTA_TRANSMISSION_PASS) if (path.isDeltaTransmissionPath()) { - writeNRDDeltaTransmissionGuideBuffers(outputNRD, kUseNRDDemodulation, path.getPixel(), 0.f, path.thp * emitterRadiance, -path.dir, 0.f, kNRDInvalidPathLength, path.origin + path.dir * kNRDInvalidPathLength); + writeNRDDeltaTransmissionGuideBuffers( + outputNRD, + kUseNRDDemodulation, + path.getPixel(), + 0.f, + path.thp * emitterRadiance, + -path.dir, + 0.f, + kNRDInvalidPathLength, + path.origin + path.dir * kNRDInvalidPathLength + ); } else { - writeNRDDeltaTransmissionGuideBuffers(outputNRD, kUseNRDDemodulation, path.getPixel(), 0.f, 0.f, -path.dir, 0.f, kNRDInvalidPathLength, 0.f); + writeNRDDeltaTransmissionGuideBuffers( + outputNRD, kUseNRDDemodulation, path.getPixel(), 0.f, 0.f, -path.dir, 0.f, kNRDInvalidPathLength, 0.f + ); } #endif @@ -1157,26 +1292,31 @@ struct PathTracer } /** Write path contribution to output buffer. - */ - void writeOutput(const PathState path) + */ + void writeOutput(inout PathState path) { - assert(!any(isnan(path.L))); - // Log path length. logPathLength(getTerminatedPathLength(path)); const uint2 pixel = path.getPixel(); const uint outIdx = params.getSampleOffset(pixel, sampleOffset) + path.getSampleIdx(); +#if USE_RESTIRPT + float3 color = path.restirPTData.pathL0; + restirpt.populateReservoir(path, sampleCount, pixel, params.getReservoirOffset(pixel), params.seed); + + if (any(isnan(color) || isinf(color))) color = 0.f; +#else + float3 color = path.L; +#endif + if (kSamplesPerPixel == 1) { - // Write color directly to frame buffer. - outputColor[pixel] = float4(path.L, 1.f); + outputColor[pixel] = float4(color, 1.f); } else { - // Write color to per-sample buffer. - sampleColor[outIdx].set(path.L); + sampleColor[outIdx].set(color); } if (kOutputGuideData) @@ -1189,11 +1329,16 @@ struct PathTracer // TODO: Optimize this for 1 SPP. It doesn't have to go through resolve pass like the color above. NRDRadiance data = {}; - if (path.isDiffusePrimaryHit()) data.setPathType(NRDPathType::Diffuse); - else if (path.isSpecularPrimaryHit()) data.setPathType(NRDPathType::Specular); - else if (path.isDeltaReflectionPrimaryHit()) data.setPathType(NRDPathType::DeltaReflection); - else if (path.isDeltaTransmissionPath()) data.setPathType(NRDPathType::DeltaTransmission); - else data.setPathType(NRDPathType::Residual); + if (path.isDiffusePrimaryHit()) + data.setPathType(NRDPathType::Diffuse); + else if (path.isSpecularPrimaryHit()) + data.setPathType(NRDPathType::Specular); + else if (path.isDeltaReflectionPrimaryHit()) + data.setPathType(NRDPathType::DeltaReflection); + else if (path.isDeltaTransmissionPath()) + data.setPathType(NRDPathType::DeltaTransmission); + else + data.setPathType(NRDPathType::Residual); data.setRadiance(path.L); diff --git a/Source/RenderPasses/PathTracer/PathTracerNRD.slang b/Source/RenderPasses/PathTracer/PathTracerNRD.slang index 1e4e7982aa..a2d65c0505 100644 --- a/Source/RenderPasses/PathTracer/PathTracerNRD.slang +++ b/Source/RenderPasses/PathTracer/PathTracerNRD.slang @@ -32,8 +32,14 @@ import LoadShadingData; import PathTracer; import NRDHelpers; +// Screen-space motion vector buffers owned by ReSTIR PT (`mpDeltaReflectionMotionVectors` / +// `mpDeltaTransmissionMotionVectors`). Bound at the trace pass root-var so they can be written +// independently of the NRD guide buffers; +RWTexture2D deltaReflectionMotionVectors; +RWTexture2D deltaTransmissionMotionVectors; + /** Extends the PathTracer with variants of handleHit() to trace NRD guide paths. -*/ + */ extension PathTracer { /** Handle hit on delta reflection materials. @@ -56,7 +62,21 @@ extension PathTracer ShadingData sd = loadShadingData(path.hit, path.origin, path.dir); // Reject false hits in nested dielectrics. - if (!handleNestedDielectrics(sd, path)) return; + if (!handleNestedDielectrics(sd, path)) + { + float3 originOffset = sd.posW - path.origin; + + float4x4 originOffsetMat = float4x4( + 1.f, 0.f, 0.f, originOffset.x, + 0.f, 1.f, 0.f, originOffset.y, + 0.f, 0.f, 1.f, originOffset.z, + 0.f, 0.f, 0.f, 1.f + ); + + path.imageXform = mul(path.imageXform, originOffsetMat); + + return; + } // Create texture sampler. let lod = createTextureSampler(path, isPrimaryHit); @@ -65,6 +85,12 @@ extension PathTracer let hints = getMaterialInstanceHints(path.hit, isPrimaryHit); let mi = gScene.materials.getMaterialInstance(sd, lod, hints); let bsdfProperties = mi.getProperties(sd); +#if USE_RESTIRPT // allow computing specmvec for non-delta surface for ReSTIR PT tempora reuse + bool isSmooth = bsdfProperties.roughness <= restirpt.specularMotionVectorRoughnessThreshold; +#else + bool isSmooth = bsdfProperties.roughness == 0.f; +#endif + uint smoothMask = uint(isSmooth) * uint(LobeType::All); // Query BSDF lobes. const uint lobeTypes = mi.getLobeTypes(sd); @@ -75,10 +101,19 @@ extension PathTracer if (isPrimaryHit) { // Terminate without the write-out if the path doesn't start as delta reflection. - bool hasDeltaReflectionLobe = ((lobeTypes & (uint)LobeType::DeltaReflection) != 0); + bool hasDeltaReflectionLobe = ((lobeTypes & (uint)LobeType::DeltaReflection) != 0) || ((lobeTypes & (uint)LobeType::SpecularReflection) != 0) && isSmooth; if (!hasDeltaReflectionLobe) { - writeNRDDeltaReflectionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, viewDir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength); + if (kOutputNRDAdditionalData) + { + writeNRDDeltaReflectionGuideBuffers( + outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, viewDir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength + ); + } + if (kOutputDeltaMotionVectors) + { + deltaReflectionMotionVectors[pixel] = 0.f; + } path.terminate(); return; } @@ -90,17 +125,20 @@ extension PathTracer path.pdf += primaryHitDist; // Set the active lobes only to delta reflection on the first bounce. - sd.mtl.setActiveLobes((uint)LobeType::DeltaReflection); + sd.mtl.setActiveLobes((uint)LobeType::DeltaReflection | smoothMask & (uint)LobeType::SpecularReflection); } else { + // disable soft roughness for delta for ReSTIR specmvec because it conflicts with NRD denoiser output + if (kOutputNRDData && path.isDeltaReflectionPrimaryHit()) isSmooth = bsdfProperties.roughness == 0.f; + // Use path's radiance field to accumulate emission along the path since the radiance is not used for denoiser guide paths. // No need for accumulating emission at the primary hit since the primary hit emission is coming from GBuffer. path.L += path.thp * bsdfProperties.emission; // Terminate after scatter ray on last vertex has been processed or non-delta lobe exists. const bool lastVertex = hasFinishedSurfaceBounces(path); - const bool hasNonDeltaLobes = (lobeTypes & (uint)LobeType::NonDelta) != 0; + const bool hasNonDeltaLobes = (lobeTypes & (uint)LobeType::NonDelta) != 0 && !isSmooth || (lobeTypes & (uint)LobeType::Diffuse); const bool isEmissive = any(bsdfProperties.emission > 0.f); if (lastVertex || hasNonDeltaLobes || isEmissive) @@ -109,7 +147,28 @@ extension PathTracer const float3 reflectance = getMaterialReflectanceForDeltaPaths(materialType, hasDeltaLobes, sd, bsdfProperties); const float primaryHitDist = path.pdf; const float hitDist = float(path.sceneLength) - primaryHitDist; - writeNRDDeltaReflectionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, reflectance, emission, bsdfProperties.guideNormal, bsdfProperties.roughness, float(path.sceneLength), hitDist); + if (kOutputNRDAdditionalData) + { + writeNRDDeltaReflectionGuideBuffers( + outputNRD, + kUseNRDDemodulation, + pixel, + reflectance, + emission, + bsdfProperties.guideNormal, + bsdfProperties.roughness, + float(path.sceneLength), + hitDist + ); + } + if (kOutputDeltaMotionVectors) + { + const HitInfo primaryHit = unpackHitInfo(vbuffer[pixel]); + // Place the current path origin at the terminal hit so calcImageMotionVector can project it. + path.origin = sd.posW; + float2 imageMotionVector = calcImageMotionVector(path, params.frameDim, primaryHit); + deltaReflectionMotionVectors[pixel] = imageMotionVector; + } path.terminate(); return; @@ -120,11 +179,14 @@ extension PathTracer // Stable guide buffers are a necessity. if (bsdfProperties.isTransmissive && all(bsdfProperties.specularReflectionAlbedo <= bsdfProperties.specularTransmissionAlbedo)) { - sd.mtl.setActiveLobes((uint)LobeType::DeltaTransmission); + // if total internal reflection happens just choose reflection + if (isDeltaReflectionAllowedAlongDeltaTransmissionPath(sd)) + sd.mtl.setActiveLobes((uint)LobeType::DeltaReflection | smoothMask & (uint)LobeType::SpecularReflection); + else sd.mtl.setActiveLobes((uint)LobeType::DeltaTransmission | smoothMask & (uint)LobeType::SpecularTransmission); } else { - sd.mtl.setActiveLobes((uint)LobeType::DeltaReflection); + sd.mtl.setActiveLobes((uint)LobeType::DeltaReflection | smoothMask & (uint)LobeType::SpecularReflection); } } @@ -141,7 +203,16 @@ extension PathTracer if (!valid) { - writeNRDDeltaReflectionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, viewDir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength); + if (kOutputNRDAdditionalData) + { + writeNRDDeltaReflectionGuideBuffers( + outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, viewDir, 0.f, kNRDInvalidPathLength, kNRDInvalidPathLength + ); + } + if (kOutputDeltaMotionVectors) + { + deltaReflectionMotionVectors[pixel] = 0.f; + } path.terminate(); return; } @@ -165,12 +236,51 @@ extension PathTracer const float3 reflectance = getMaterialReflectanceForDeltaPaths(materialType, hasDeltaLobes, sd, bsdfProperties); const float primaryHitDist = path.pdf; const float hitDist = float(path.sceneLength) - primaryHitDist; - writeNRDDeltaReflectionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, reflectance, emission, bsdfProperties.guideNormal, bsdfProperties.roughness, float(path.sceneLength), hitDist); + if (kOutputNRDAdditionalData) + { + writeNRDDeltaReflectionGuideBuffers( + outputNRD, + kUseNRDDemodulation, + pixel, + reflectance, + emission, + bsdfProperties.guideNormal, + bsdfProperties.roughness, + float(path.sceneLength), + hitDist + ); + } + if (kOutputDeltaMotionVectors) + { + const HitInfo primaryHit = unpackHitInfo(vbuffer[pixel]); + // Place the current path origin at the terminal hit so calcImageMotionVector can project it. + path.origin = sd.posW; + float2 imageMotionVector = calcImageMotionVector(path, params.frameDim, primaryHit); + deltaReflectionMotionVectors[pixel] = imageMotionVector; + } path.terminate(); return; } } + + // Accumulate the image-space xform for this bounce. + // This is used later to compute a virtual-image motion vector. + // We assume flat reflectors/refractors + float3 originOffset = sd.posW - path.origin; + float4x4 T; + if (path.isTransmission()) + { + float insideIoR = gScene.materials.evalIoR(sd.materialID); + float rcpEta = sd.frontFacing ? (insideIoR / sd.IoR) : (sd.IoR / insideIoR); + // For refractive (double sided) materials bsdfProperties.guideNormal always points in the same direction as the view vector. + T = getRefractionXform(path.origin, viewDir, bsdfProperties.guideNormal, rcpEta, originOffset); + } + else + { + T = getReflectionXform(path.origin, viewDir, bsdfProperties.guideNormal, originOffset); + } + path.imageXform = mul(path.imageXform, T); } /** Handle hit on delta transmission materials. @@ -193,7 +303,21 @@ extension PathTracer ShadingData sd = loadShadingData(path.hit, path.origin, path.dir); // Reject false hits in nested dielectrics. - if (!handleNestedDielectrics(sd, path)) return; + if (!handleNestedDielectrics(sd, path)) + { + float3 originOffset = sd.posW - path.origin; + + float4x4 originOffsetMat = float4x4( + 1.f, 0.f, 0.f, originOffset.x, + 0.f, 1.f, 0.f, originOffset.y, + 0.f, 0.f, 1.f, originOffset.z, + 0.f, 0.f, 0.f, 1.f + ); + + path.imageXform = mul(path.imageXform, originOffsetMat); + + return; + } // Create texture sampler. let lod = createTextureSampler(path, isPrimaryHit); @@ -205,11 +329,30 @@ extension PathTracer const uint lobeTypes = mi.getLobeTypes(sd); +#if USE_RESTIRPT // allow computing specmvec for non-delta surface for ReSTIR PT tempora reuse + bool isSmooth = bsdfProperties.roughness <= restirpt.specularMotionVectorRoughnessThreshold; + // disable soft roughness for delta for ReSTIR specmvec because it conflicts with NRD denoiser output + if (kOutputNRDData && path.isDeltaOnlyPath()) isSmooth = bsdfProperties.roughness == 0.f; +#else + bool isSmooth = bsdfProperties.roughness == 0.f; +#endif + + uint smoothMask = uint(isSmooth) * uint(LobeType::All); + // Terminate without the write-out if the path doesn't start as delta transmission. const bool hasDeltaTransmissionLobe = ((lobeTypes & (uint)LobeType::DeltaTransmission) != 0); if (isPrimaryHit && !hasDeltaTransmissionLobe) { - writeNRDDeltaTransmissionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, viewDir, 0.f, kNRDInvalidPathLength, 0.f); + if (kOutputNRDAdditionalData) + { + writeNRDDeltaTransmissionGuideBuffers( + outputNRD, kUseNRDDemodulation, pixel, 0.f, 0.f, viewDir, 0.f, kNRDInvalidPathLength, 0.f + ); + } + if (kOutputDeltaMotionVectors) + { + deltaTransmissionMotionVectors[pixel] = 0.f; + } path.terminate(); return; } @@ -245,7 +388,28 @@ extension PathTracer if (lastVertex || semiOpaque) { float3 emission = path.L; - writeNRDDeltaTransmissionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, getMaterialReflectanceForDeltaPaths(materialType, hasDeltaLobes, sd, bsdfProperties), emission, bsdfProperties.guideNormal, bsdfProperties.roughness, float(path.sceneLength), sd.posW); + if (kOutputNRDAdditionalData) + { + writeNRDDeltaTransmissionGuideBuffers( + outputNRD, + kUseNRDDemodulation, + pixel, + getMaterialReflectanceForDeltaPaths(materialType, hasDeltaLobes, sd, bsdfProperties), + emission, + bsdfProperties.guideNormal, + bsdfProperties.roughness, + float(path.sceneLength), + sd.posW + ); + } + if (kOutputDeltaMotionVectors) + { + const HitInfo primaryHit = unpackHitInfo(vbuffer[pixel]); + // Place the current path origin at the terminal hit so calcImageMotionVector can project it. + path.origin = sd.posW; + float2 imageMotionVector = calcImageMotionVector(path, params.frameDim, primaryHit); + deltaTransmissionMotionVectors[pixel] = imageMotionVector; + } path.terminate(); return; @@ -255,7 +419,7 @@ extension PathTracer path.origin = sd.computeRayOrigin(); // Set the active lobes only to delta transmission. - sd.mtl.setActiveLobes((uint)LobeType::DeltaTransmission); + sd.mtl.setActiveLobes((uint)LobeType::DeltaTransmission | smoothMask & (uint)LobeType::SpecularTransmission); // Generate the next path segment or terminate. bool valid = generateScatterRay(sd, mi, path); @@ -263,17 +427,56 @@ extension PathTracer // Delta transmission was not possible, fallback to delta reflection if it's allowed. if (!valid && isDeltaReflectionAllowedAlongDeltaTransmissionPath(sd)) { - sd.mtl.setActiveLobes((uint)LobeType::DeltaTransmission | (uint)LobeType::DeltaReflection); + sd.mtl.setActiveLobes( (uint)LobeType::DeltaTransmission | (uint)LobeType::DeltaReflection | smoothMask & (uint)LobeType::SpecularTransmission | smoothMask & (uint)LobeType::SpecularReflection ); valid = generateScatterRay(sd, mi, path); } if (!valid) { float3 emission = path.L; - writeNRDDeltaTransmissionGuideBuffers(outputNRD, kUseNRDDemodulation, pixel, getMaterialReflectanceForDeltaPaths(materialType, hasDeltaLobes, sd, bsdfProperties), emission, bsdfProperties.guideNormal, bsdfProperties.roughness, float(path.sceneLength), sd.posW); + if (kOutputNRDAdditionalData) + { + writeNRDDeltaTransmissionGuideBuffers( + outputNRD, + kUseNRDDemodulation, + pixel, + getMaterialReflectanceForDeltaPaths(materialType, hasDeltaLobes, sd, bsdfProperties), + emission, + bsdfProperties.guideNormal, + bsdfProperties.roughness, + float(path.sceneLength), + sd.posW + ); + } + if (kOutputDeltaMotionVectors) + { + const HitInfo primaryHit = unpackHitInfo(vbuffer[pixel]); + // Place the current path origin at the terminal hit so calcImageMotionVector can project it. + path.origin = sd.posW; + float2 imageMotionVector = calcImageMotionVector(path, params.frameDim, primaryHit); + deltaTransmissionMotionVectors[pixel] = imageMotionVector; + } path.terminate(); return; } + + // Accumulate the image-space xform for this bounce. + // This is used later to compute a virtual-image motion vector for the denoiser. + // We assume flat reflectors/refractors + float3 originOffset = sd.posW - path.origin; + float4x4 T; + if (path.isTransmission()) + { + float insideIoR = gScene.materials.evalIoR(sd.materialID); + float rcpEta = sd.frontFacing ? (insideIoR / sd.IoR) : (sd.IoR / insideIoR); + // For refractive (double sided) materials bsdfProperties.guideNormal always points in the same direction as the view vector. + T = getRefractionXform(path.origin, viewDir, bsdfProperties.guideNormal, rcpEta, originOffset); + } + else + { + T = getReflectionXform(path.origin, viewDir, bsdfProperties.guideNormal, originOffset); + } + path.imageXform = mul(path.imageXform, T); } } diff --git a/Source/RenderPasses/PathTracer/PathTracerUtils.slang b/Source/RenderPasses/PathTracer/PathTracerUtils.slang new file mode 100644 index 0000000000..4d446cae54 --- /dev/null +++ b/Source/RenderPasses/PathTracer/PathTracerUtils.slang @@ -0,0 +1,123 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Utils.Math.Ray; +import Utils.Geometry.GeometryHelpers; +import StaticParams; +import Params; + +/** Types of samplable lights. +*/ +enum class LightType +{ + EnvMap, + Emissive, + Analytic +}; + +/** Describes a light sample. +*/ +struct LightSample +{ + float3 Li; ///< Incident radiance at the shading point (unshadowed). This is already divided by the pdf. + float pdf; ///< Pdf with respect to solid angle at the shading point. + float3 origin; ///< Ray origin for visibility evaluation (offseted to avoid self-intersection). + float distance; ///< Ray distance for visibility evaluation (shortened to avoid self-intersection). + float3 dir; ///< Ray direction for visibility evaluation (normalized). + uint lightType; ///< Light type this sample comes from (LightType casted to uint). +#if USE_RESTIRPT + uint triangleId; ///< For reconstructing hitInfo for sampled emissive lights. + float2 barycentrics; ///< For reconstructing hitInfo for sampled emissive lights. + float geometryTerm; ///< For computing jacobian in GRIS. + float3 normal; ///< Normal for area (including sphere) lights. +#endif + Ray getVisibilityRay() { return Ray(origin, dir, 0.f, distance); } +}; + +/** Describes a path vertex. +*/ +struct PathVertex +{ + uint index; ///< Vertex index (0 = camera, 1 = primary hit, 2 = secondary hit, etc.). + float3 pos; ///< Vertex position. + float3 faceNormal; ///< Geometry normal at the vertex (zero if not on a surface). + bool frontFacing; ///< True if path vertex is on the front-facing side (if on a surface). + bool isCurveHit; + + __init(uint index, float3 pos, float3 faceNormal = float3(0.f), bool frontFacing = true, bool isCurveHit = false) + { + this.index = index; + this.pos = pos; + this.faceNormal = faceNormal; + this.frontFacing = frontFacing; + this.isCurveHit = isCurveHit; + } + + float3 getRayOrigin(float3 rayDir) + { + return isCurveHit ? pos : computeRayOrigin(pos, dot(faceNormal, rayDir) >= 0 ? faceNormal : -faceNormal); + } + + float3 getOrientedFaceNormal() + { + return frontFacing ? faceNormal : -faceNormal; + } +}; + +/** Evaluates the currently configured heuristic for multiple importance sampling (MIS). + \param[in] n0 Number of samples taken from the first sampling strategy. + \param[in] p0 Pdf for the first sampling strategy. + \param[in] n1 Number of samples taken from the second sampling strategy. + \param[in] p1 Pdf for the second sampling strategy. + \return Weight for the contribution from the first strategy (p0). +*/ +float evalMIS(float n0, float p0, float n1, float p1) +{ + switch (MISHeuristic(kMISHeuristic)) + { + case MISHeuristic::Balance: + { + float q0 = n0 * p0; + float q1 = n1 * p1; + return q0 / (q0 + q1); + } + case MISHeuristic::PowerTwo: + { + float q0 = (n0 * p0) * (n0 * p0); + float q1 = (n1 * p1) * (n1 * p1); + return q0 / (q0 + q1); + } + case MISHeuristic::PowerExp: + { + float q0 = pow(n0 * p0, kMISPowerExponent); + float q1 = pow(n1 * p1, kMISPowerExponent); + return q0 / (q0 + q1); + } + default: + return 0.f; + } +} diff --git a/Source/RenderPasses/PathTracer/ReSTIRPTData.slang b/Source/RenderPasses/PathTracer/ReSTIRPTData.slang new file mode 100644 index 0000000000..3d156dbae3 --- /dev/null +++ b/Source/RenderPasses/PathTracer/ReSTIRPTData.slang @@ -0,0 +1,41 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "Utils/Math/MathConstants.slangh" +__exported import ReSTIRPathTracing.PathReservoir; + +struct ReSTIRPTData +{ + PathReconnectionInfo pathRcInfo = {}; + PathRISState pathRis = {}; + float3 pathL0 = {}; + float lastScatterPdf = 0; + float primaryRayFootprint = 0; + float sourcePdf = 0; + float distanceThreshold = 0; + float roughnessThreshold = 0; +}; diff --git a/Source/RenderPasses/PathTracer/ResolvePass.cs.slang b/Source/RenderPasses/PathTracer/ResolvePass.cs.slang index 670f07898c..c0d10d54dd 100644 --- a/Source/RenderPasses/PathTracer/ResolvePass.cs.slang +++ b/Source/RenderPasses/PathTracer/ResolvePass.cs.slang @@ -40,33 +40,40 @@ import Params; struct ResolvePass { // Resources - PathTracerParams params; ///< Runtime parameters. - - StructuredBuffer sampleColor; ///< Input per-sample color if kSamplesPerPixel != 1. - StructuredBuffer sampleGuideData; ///< Input per-sample guide data. Only valid if kOutputGuideData == true. - StructuredBuffer sampleNRDRadiance; ///< Input per-sample NRD radiance data. Only valid if kOutputNRDData == true. - StructuredBuffer sampleNRDHitDist; ///< Input per-sample NRD hit distance data. Only valid if kOutputNRDData == true. - StructuredBuffer sampleNRDEmission; ///< Input per-sample NRD emission data. Only valid if kOutputNRDData == true. - StructuredBuffer sampleNRDReflectance; ///< Input per-sample NRD reflectance data. Only valid if kOutputNRDData == true. - - StructuredBuffer sampleNRDPrimaryHitNeeOnDelta; ///< Output per-sample NEE on paths classified as delta, from delta+diffuse materials. Only valid if kOutputNRDData == true. - Texture2D primaryHitDiffuseReflectance; ///< Output per-pixel primary hit diffuse reflectance. Only valid if kOutputNRDData == true. - - Texture2D sampleCount; ///< Optional input sample count buffer. Only valid when kSamplesPerPixel == 0. - Texture2D sampleOffset; ///< Output offset into per-sample buffers. Only valid when kSamplesPerPixel == 0. - - RWTexture2D outputColor; ///< Output resolved color. - RWTexture2D outputAlbedo; ///< Output resolved albedo. Only valid if kOutputGuideData == true. - RWTexture2D outputSpecularAlbedo; ///< Output resolved specular albedo. Only valid if kOutputGuideData == true. - RWTexture2D outputIndirectAlbedo; ///< Output resolved indirect albedo. Only valid if kOutputGuideData == true. - RWTexture2D outputGuideNormal; ///< Output resolved guide normal. Only valid if kOutputGuideData == true. - RWTexture2D outputReflectionPosW; ///< Output resolved reflection pos. Only valid if kOutputGuideData == true. - - RWTexture2D outputNRDDiffuseRadianceHitDist; ///< Output resolved diffuse radiance in .rgb and hit distance in .a for NRD. Only valid if kOutputNRDData == true. - RWTexture2D outputNRDSpecularRadianceHitDist; ///< Output resolved specular radiance in .rgb and hit distance in .a for NRD. Only valid if kOutputNRDData == true. - RWTexture2D outputNRDDeltaReflectionRadianceHitDist; ///< Output resolved delta reflection radiance in .rgb and hit distance in .a for NRD. Only valid if kOutputNRDData == true. - RWTexture2D outputNRDDeltaTransmissionRadianceHitDist; ///< Output resolved delta transmission radiance in .rgb and hit distance in .a for NRD. Only valid if kOutputNRDData == true. - RWTexture2D outputNRDResidualRadianceHitDist; ///< Output resolved residual radiance in .rgb and hit distance in .a for NRD. Only valid if kOutputNRDData == true. + PathTracerParams params; ///< Runtime parameters. + + StructuredBuffer sampleColor; ///< Input per-sample color if kSamplesPerPixel != 1. + StructuredBuffer sampleGuideData; ///< Input per-sample guide data. Only valid if kOutputGuideData == true. + StructuredBuffer sampleNRDRadiance; ///< Input per-sample NRD radiance data. Only valid if kOutputNRDData == true. + StructuredBuffer sampleNRDHitDist; ///< Input per-sample NRD hit distance data. Only valid if kOutputNRDData == true. + StructuredBuffer sampleNRDEmission; ///< Input per-sample NRD emission data. Only valid if kOutputNRDData == true. + StructuredBuffer sampleNRDReflectance; ///< Input per-sample NRD reflectance data. Only valid if kOutputNRDData == true. + + StructuredBuffer sampleNRDPrimaryHitNeeOnDelta; ///< Output per-sample NEE on paths classified as delta, from delta+diffuse + ///< materials. Only valid if kOutputNRDData == true. + Texture2D primaryHitDiffuseReflectance; ///< Output per-pixel primary hit diffuse reflectance. Only valid if kOutputNRDData == + ///< true. + + Texture2D sampleCount; ///< Optional input sample count buffer. Only valid when kSamplesPerPixel == 0. + Texture2D sampleOffset; ///< Output offset into per-sample buffers. Only valid when kSamplesPerPixel == 0. + + RWTexture2D outputColor; ///< Output resolved color. + RWTexture2D outputAlbedo; ///< Output resolved albedo. Only valid if kOutputGuideData == true. + RWTexture2D outputSpecularAlbedo; ///< Output resolved specular albedo. Only valid if kOutputGuideData == true. + RWTexture2D outputIndirectAlbedo; ///< Output resolved indirect albedo. Only valid if kOutputGuideData == true. + RWTexture2D outputGuideNormal; ///< Output resolved guide normal. Only valid if kOutputGuideData == true. + RWTexture2D outputReflectionPosW; ///< Output resolved reflection pos. Only valid if kOutputGuideData == true. + + RWTexture2D outputNRDDiffuseRadianceHitDist; ///< Output resolved diffuse radiance in .rgb and hit distance in .a for NRD. Only + ///< valid if kOutputNRDData == true. + RWTexture2D outputNRDSpecularRadianceHitDist; ///< Output resolved specular radiance in .rgb and hit distance in .a for NRD. + ///< Only valid if kOutputNRDData == true. + RWTexture2D outputNRDDeltaReflectionRadianceHitDist; ///< Output resolved delta reflection radiance in .rgb and hit distance in + ///< .a for NRD. Only valid if kOutputNRDData == true. + RWTexture2D outputNRDDeltaTransmissionRadianceHitDist; ///< Output resolved delta transmission radiance in .rgb and hit distance + ///< in .a for NRD. Only valid if kOutputNRDData == true. + RWTexture2D outputNRDResidualRadianceHitDist; ///< Output resolved residual radiance in .rgb and hit distance in .a for NRD. + ///< Only valid if kOutputNRDData == true. static const bool kOutputGuideData = OUTPUT_GUIDE_DATA; static const bool kOutputNRDData = OUTPUT_NRD_DATA; @@ -76,7 +83,8 @@ struct ResolvePass */ void execute(const uint2 pixel) { - if (any(pixel >= params.frameDim)) return; + if (any(pixel >= params.frameDim)) + return; // Compute offset into per-sample buffers. All samples are stored consecutively at this offset. const uint offset = params.getSampleOffset(pixel, sampleOffset); @@ -99,7 +107,10 @@ struct ResolvePass color += sampleColor[idx].get(); } - outputColor[pixel] = float4(invSpp * color, 1.f); + if (kUseReSTIRPT) + outputColor[pixel] += float4(invSpp * color, 1.f); + else + outputColor[pixel] = float4(invSpp * color, 1.f); } // Guide data is always written per-sample and needs to be resolved. @@ -129,7 +140,7 @@ struct ResolvePass } // NRD data is always written per-sample and needs to be resolved. - if (kOutputNRDData) + if (kOutputNRDData && !kUseReSTIRPT) { float3 diffuseRadiance = 0.f; float3 specularRadiance = 0.f; @@ -166,7 +177,9 @@ struct ResolvePass break; case NRDPathType::DeltaReflection: deltaReflectionRadiance += demodulatedRadiance; - diffuseRadiance += select(primaryHitDiffuseReflectance[pixel].rgb > 0.f, neeOnDelta / primaryHitDiffuseReflectance[pixel].rgb, float3(0.f)); + diffuseRadiance += select( + primaryHitDiffuseReflectance[pixel].rgb > 0.f, neeOnDelta / primaryHitDiffuseReflectance[pixel].rgb, float3(0.f) + ); break; case NRDPathType::DeltaTransmission: deltaTransmissionRadiance += demodulatedRadiance; @@ -195,7 +208,7 @@ cbuffer CB } [numthreads(16, 16, 1)] -void main(uint3 dispatchThreadId : SV_DispatchThreadID) +void main(uint3 dispatchThreadId: SV_DispatchThreadID) { gResolvePass.execute(dispatchThreadId.xy); } diff --git a/Source/RenderPasses/PathTracer/ResolveReSTIRPTPass.cs.slang b/Source/RenderPasses/PathTracer/ResolveReSTIRPTPass.cs.slang new file mode 100644 index 0000000000..82bf431d36 --- /dev/null +++ b/Source/RenderPasses/PathTracer/ResolveReSTIRPTPass.cs.slang @@ -0,0 +1,115 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ + +import Scene.Scene; +import ColorType; +import Params; +import ReSTIRPathTracing.PathReservoir; +import Utils.Debug.PixelDebug; +import Rendering.Utils.PixelStats; +import Utils.Math.HashUtils; +import Utils.Color.ColorMap; +import LoadShadingData; + +struct ResolveReSTIRPTPass +{ + PathTracerParams params; + StructuredBuffer pathReservoirs; + Texture2D sampleOffset; + RWTexture2D outputColor; + + Texture2D vbuffer; + Texture2D motionVectors; + + Texture2D duplicationMap; + Texture2D sampleSurvivedFrames; + + RWTexture2D outDuplicationMap; + + bool useDecoupledShading; + bool useAreaReservoirs; + + float3 pseudocolor(uint value) + { + uint h = jenkinsHash(value); + return (uint3(h, h >> 8, h >> 16) & 0xff) / 255.f; + } + + void execute(const uint2 pixel) + { + if (!useAreaReservoirs) + { + HitInfo hit; + hit = HitInfo(vbuffer[pixel]); + if (!hit.isValid()) + { + outDuplicationMap[pixel] = 0.f; + return; + } + } + uint reservoirIdx = params.getReservoirOffset(pixel); + float3 color = pathReservoirs[reservoirIdx].integrand * pathReservoirs[reservoirIdx].weight; + + if (any(isnan(color) || isinf(color) || color < 0.f)) color = 0.f; + + float temporalDuplicationValue = min(1.f, sampleSurvivedFrames[pixel] / 40.f); + + + if (temporalDuplicationValue < 0.f || isinf(temporalDuplicationValue) || isnan(temporalDuplicationValue)) temporalDuplicationValue = 0.f; + + float spatialDuplicationValue = min(1.f, duplicationMap[pixel] / 288.f); + + outDuplicationMap[pixel] = float4(spatialDuplicationValue, temporalDuplicationValue, 0.f, 0.f); + + + if (!useDecoupledShading) + { + if (kSamplesPerPixel == 1) + { + outputColor[pixel].rgb += color; + } + else + { + outputColor[pixel].rgb = color; + } + } + } +} + +cbuffer CB +{ + ResolveReSTIRPTPass gResolveReSTIRPTPass; +} + +[numthreads(16, 16, 1)] +void main(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + printSetPixel(dispatchThreadId.xy); + logSetPixel(dispatchThreadId.xy); + gResolveReSTIRPTPass.execute(dispatchThreadId.xy); +} diff --git a/Source/RenderPasses/PathTracer/StaticParams.slang b/Source/RenderPasses/PathTracer/StaticParams.slang index f255f96097..0723750bcb 100644 --- a/Source/RenderPasses/PathTracer/StaticParams.slang +++ b/Source/RenderPasses/PathTracer/StaticParams.slang @@ -56,3 +56,6 @@ static const bool kUseNRDDemodulation = USE_NRD_DEMODULATION; static const uint kColorFormat = COLOR_FORMAT; static const uint kMISHeuristic = MIS_HEURISTIC; static const float kMISPowerExponent = MIS_POWER_EXPONENT; +static const bool kUseReSTIRPT = USE_RESTIRPT; +static const bool kUseLightTileRIS = USE_LIGHT_TILE_RIS; +static const bool kOutputDeltaMotionVectors = OUTPUT_DELTA_MOTION_VECTORS; diff --git a/Source/RenderPasses/PathTracer/TracePass.rt.slang b/Source/RenderPasses/PathTracer/TracePass.rt.slang index b15b891969..780e6a368d 100644 --- a/Source/RenderPasses/PathTracer/TracePass.rt.slang +++ b/Source/RenderPasses/PathTracer/TracePass.rt.slang @@ -49,17 +49,23 @@ Scheduler gScheduler; static const uint kRayTypeScatter = 0; static const uint kMissScatter = 0; -struct DummyAttribs { uint dummy; }; +struct DummyAttribs +{ + uint dummy; +}; struct PathPayload { - uint4 packed[5]; - - PackedHitInfo hit; - GuideData guideData; - InteriorList interiorList; ///< Interior list. Keeping track of a stack of materials with medium properties. The size depends on the config (4B/item). - SampleGenerator sg; ///< Sample generator state. Typically 4-16B. - + uint4 packed[5] = {}; + + PackedHitInfo hit = {}; + GuideData guideData = {}; + InteriorList interiorList = {}; ///< Interior list. Keeping track of a stack of materials with medium properties. The size depends on + ///< the config (4B/item). + SampleGenerator sg = {}; ///< Sample generator state. Typically 4-16B. +#if USE_RESTIRPT + ReSTIRPTData restirPTData = {}; ///< ReSTIR PT data carried through ray bounces. +#endif static PathPayload pack(const PathState path) { PathPayload p = {}; @@ -83,7 +89,9 @@ struct PathPayload p.guideData = path.guideData; p.interiorList = path.interiorList; p.sg = path.sg; - +#if USE_RESTIRPT + p.restirPTData = path.restirPTData; +#endif return p; } @@ -111,7 +119,9 @@ struct PathPayload path.guideData = p.guideData; path.interiorList = p.interiorList; path.sg = p.sg; - +#if USE_RESTIRPT + path.restirPTData = p.restirPTData; +#endif return path; } }; @@ -248,6 +258,12 @@ struct ReorderingScheduler gPathTracer.generatePath(pathID, path); gPathTracer.setupPathLogging(path); +#if USE_RESTIRPT && !defined(DELTA_REFLECTION_PASS) && !defined(DELTA_TRANSMISSION_PASS) + const bool processPrimaryMiss = gPathTracer.restirpt.kUseAreaReservoirs; +#else + const bool processPrimaryMiss = false; +#endif + if (path.isHit()) { // Handle primary hits. @@ -256,6 +272,7 @@ struct ReorderingScheduler // The created HitObject does only contain the minimal required data, // most data is already passed in the path.hit object. // For volume hits, a miss object is returned(and handled in the handleMiss() function). + // TODO: Move to a utility function when Slang limitation has been resolved. See HitObject hitObject; RayDesc dummyRay = {}; DummyAttribs dummyAttribs = {}; @@ -292,6 +309,14 @@ struct ReorderingScheduler HitObject::Invoke(gScene.rtAccel, hitObject, payload); path = PathPayload::unpack(payload); } + else if (processPrimaryMiss) + { + // For ReSTIR PT area reservoirs, handle primary miss here so that the + // environment emission contributes to the area reservoir and the result + // is written via writeOutput below. + path.setVertexIndex(1); + gPathTracer.handleMiss(path); + } else { // Note the primary miss has already been handled by the separate path generation pass @@ -314,7 +339,16 @@ struct ReorderingScheduler const Ray ray = path.getScatterRay(); payload = PathPayload::pack(path); const uint rayFlags = kUseAlphaTest ? RAY_FLAG_NONE : RAY_FLAG_FORCE_OPAQUE; - hitObject = HitObject::TraceRay(gScene.rtAccel, rayFlags, 0xff /* instanceInclusionMask */, kRayTypeScatter /* hitIdx */, getRayTypeCount(), kMissScatter, ray.toRayDesc(), payload); + hitObject = HitObject::TraceRay( + gScene.rtAccel, + rayFlags, + 0xff /* instanceInclusionMask */, + kRayTypeScatter /* hitIdx */, + getRayTypeCount(), + kMissScatter, + ray.toRayDesc(), + payload + ); path = PathPayload::unpack(payload); if (hitObject.IsHit()) @@ -369,7 +403,8 @@ struct ReorderingScheduler if (kSamplesPerPixel == 0) { bool needReorder = WaveActiveAnyTrue(samplesRemaining == 0); - if (samplesRemaining == 0) break; + if (samplesRemaining == 0) + break; if (needReorder) { HitObject hitObject = HitObject::MakeNop(); @@ -381,7 +416,7 @@ struct ReorderingScheduler } /** Simple scheduler using a fullscreen raytracing dispatch. -*/ + */ struct Scheduler { /** Handle a miss. @@ -438,8 +473,18 @@ struct Scheduler const Ray ray = path.getScatterRay(); PathPayload payload = PathPayload::pack(path); uint rayFlags = RAY_FLAG_NONE; - if (!kUseAlphaTest) rayFlags |= RAY_FLAG_FORCE_OPAQUE; - TraceRay(gScene.rtAccel, rayFlags, 0xff /* instanceInclusionMask */, kRayTypeScatter /* hitIdx */, getRayTypeCount(), kMissScatter /* missIdx */, ray.toRayDesc(), payload); + if (!kUseAlphaTest) + rayFlags |= RAY_FLAG_FORCE_OPAQUE; + TraceRay( + gScene.rtAccel, + rayFlags, + 0xff /* instanceInclusionMask */, + kRayTypeScatter /* hitIdx */, + getRayTypeCount(), + kMissScatter /* missIdx */, + ray.toRayDesc(), + payload + ); path = PathPayload::unpack(payload); } @@ -454,6 +499,12 @@ struct Scheduler gPathTracer.generatePath(pathID, path); gPathTracer.setupPathLogging(path); +#if USE_RESTIRPT && !defined(DELTA_REFLECTION_PASS) && !defined(DELTA_TRANSMISSION_PASS) + const bool processPrimaryMiss = gPathTracer.restirpt.kUseAreaReservoirs; +#else + const bool processPrimaryMiss = false; +#endif + // Handle primary hit/miss. if (path.isHit()) { @@ -466,6 +517,14 @@ struct Scheduler gPathTracer.handleHit(path, vq); #endif } + else if (processPrimaryMiss) + { + // For ReSTIR PT area reservoirs, handle primary miss here so that the + // environment emission contributes to the area reservoir and the result + // is written via writeOutput below. + path.setVertexIndex(1); + gPathTracer.handleMiss(path); + } else { // Note the primary miss has already been handled by the separate path generation pass @@ -511,7 +570,7 @@ struct Scheduler // [shader("miss")] -void scatterMiss(inout PathPayload payload : SV_RayPayload) +void scatterMiss(inout PathPayload payload: SV_RayPayload) { gScheduler.handleMiss(payload); } @@ -521,19 +580,26 @@ void scatterMiss(inout PathPayload payload : SV_RayPayload) // [shader("anyhit")] -void scatterTriangleAnyHit(inout PathPayload payload : SV_RayPayload, BuiltInTriangleIntersectionAttributes attribs : SV_IntersectionAttributes) +void scatterTriangleAnyHit( + inout PathPayload payload: SV_RayPayload, + BuiltInTriangleIntersectionAttributes attribs: SV_IntersectionAttributes +) { #if USE_ALPHA_TEST // Alpha test for non-opaque geometry. GeometryInstanceID instanceID = getGeometryInstanceID(); VertexData v = getVertexData(instanceID, PrimitiveIndex(), attribs); const uint materialID = gScene.getMaterialID(instanceID); - if (gScene.materials.alphaTest(v, materialID, 0.f)) IgnoreHit(); + if (gScene.materials.alphaTest(v, materialID, 0.f)) + IgnoreHit(); #endif } [shader("closesthit")] -void scatterTriangleClosestHit(inout PathPayload payload : SV_RayPayload, BuiltInTriangleIntersectionAttributes attribs : SV_IntersectionAttributes) +void scatterTriangleClosestHit( + inout PathPayload payload: SV_RayPayload, + BuiltInTriangleIntersectionAttributes attribs: SV_IntersectionAttributes +) { #if USE_SER gScheduler.handleHit(payload); @@ -646,7 +712,8 @@ void rayGen() { uint2 pixel = DispatchRaysIndex().xy; uint2 frameDim = DispatchRaysDimensions().xy; - if (all(pixel >= frameDim)) return; + if (all(pixel >= frameDim)) + return; gScheduler.run(pixel); } diff --git a/Source/RenderPasses/PathTracer/TracePassMinimalPayload.rt.slang b/Source/RenderPasses/PathTracer/TracePassMinimalPayload.rt.slang new file mode 100644 index 0000000000..06be475309 --- /dev/null +++ b/Source/RenderPasses/PathTracer/TracePassMinimalPayload.rt.slang @@ -0,0 +1,576 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "Scene/SceneDefines.slangh" +import Scene.Raytracing; +import Scene.RaytracingInline; // For visibility queries. +import Scene.Intersection; +import Rendering.Utils.PixelStats; +import RenderPasses.Shared.Denoising.NRDConstants; +import Utils.Math.PackedFormats; +import Utils.Math.FormatConversion; +import PathTracer; +import PathTracerNRD; + +import Utils.NVAPI; // TODO(@skallweit) this is currently needed to make g_NvidiaExt visible + +ParameterBlock gPathTracer; +#if USE_SER +ReorderingScheduler gScheduler; +#else +Scheduler gScheduler; +#endif + +// Define ray indices. +static const uint kRayTypeScatter = 0; +static const uint kMissScatter = 0; + +/** Minimal ray payload carrying only hit information. + All path state (PathState, including ReSTIRPTData) lives in raygen registers. +*/ +struct PathPayload +{ + PackedHitInfo hit = {}; + float hitT = 0.f; +}; + +struct VisibilityQuery : IVisibilityQuery +{ + bool traceVisibilityRay(const Ray ray) + { + SceneRayQuery sceneRayQuery; + return sceneRayQuery.traceVisibilityRay(ray); + } +}; + +/** Helper function to create a HitInfo from a HitObject. + */ +HitInfo makeHitInfo(const HitObject hitObject) +{ + HitInfo hit = {}; + + if (hitObject.IsHit()) + { + const GeometryInstanceID instanceID = GeometryInstanceID(hitObject.GetInstanceID(), hitObject.GetGeometryIndex()); + const uint primitiveIndex = hitObject.GetPrimitiveIndex(); + + // Lookup geometry type as we cannot reconstruct that from the HitObject directly. + const GeometryType type = gScene.getGeometryInstanceType(instanceID); + + switch (type) + { + case GeometryType::None: + break; + case GeometryType::TriangleMesh: + { + TriangleHit triangleHit; + triangleHit.instanceID = instanceID; + triangleHit.primitiveIndex = primitiveIndex; + BuiltInTriangleIntersectionAttributes attribs = hitObject.GetAttributes(); + triangleHit.barycentrics = attribs.barycentrics; + hit = HitInfo(triangleHit); + break; + } + case GeometryType::DisplacedTriangleMesh: + { + DisplacedTriangleHit displacedTriangleHit; + displacedTriangleHit.instanceID = instanceID; + displacedTriangleHit.primitiveIndex = primitiveIndex; + DisplacedTriangleMeshIntersector.Attribs attribs = hitObject.GetAttributes(); + displacedTriangleHit.barycentrics = attribs.barycentrics; + displacedTriangleHit.displacement = attribs.displacement; + hit = HitInfo(displacedTriangleHit); + break; + } + case GeometryType::Curve: + { + CurveHit curveHit; + curveHit.instanceID = instanceID; + curveHit.primitiveIndex = primitiveIndex; + CurveIntersector.Attribs attribs = hitObject.GetAttributes(); + curveHit.barycentrics = attribs.barycentrics; + hit = HitInfo(curveHit); + break; + } + case GeometryType::SDFGrid: + { + SDFGridHit sdfGridHit; + sdfGridHit.instanceID = instanceID; + SDFGridHitData attribs = hitObject.GetAttributes(); + sdfGridHit.hitData = attribs; + hit = HitInfo(sdfGridHit); + break; + } + case GeometryType::Custom: + // Unsupported. + break; + } + } + + return hit; +} + +/** Reordering scheduler using SER/HitObject API. + + Uses HitObject::TraceRay() for traversal and ReorderThread() for coherence. + All path processing happens directly in raygen after reordering, avoiding + the need to pack/unpack path state through CHS/MS payloads. +*/ +struct ReorderingScheduler +{ + /** Process a path vertex (hit or miss) after thread reordering. + \param[in,out] path Path state. + */ + void processVertex(inout PathState path) + { + gPathTracer.setupPathLogging(path); + if (path.isHit()) + { +#if defined(DELTA_REFLECTION_PASS) + gPathTracer.handleDeltaReflectionHit(path); +#elif defined(DELTA_TRANSMISSION_PASS) + gPathTracer.handleDeltaTransmissionHit(path); +#else + VisibilityQuery vq; + gPathTracer.handleHit(path, vq); +#endif + } + else + { + gPathTracer.handleMiss(path); + } + } + + /** Traces a path and write result to output. + \param[in] pathID Path ID. + */ + void tracePath(uint pathID) + { + PathState path = {}; + + gPathTracer.generatePath(pathID, path); + gPathTracer.setupPathLogging(path); + +#if USE_RESTIRPT && !defined(DELTA_REFLECTION_PASS) && !defined(DELTA_TRANSMISSION_PASS) + const bool processPrimaryMiss = gPathTracer.restirpt.kUseAreaReservoirs; +#else + const bool processPrimaryMiss = false; +#endif + + if (path.isHit()) + { + // Handle primary hit directly in raygen (no HitObject::Invoke needed). +#if defined(DELTA_REFLECTION_PASS) + gPathTracer.handleDeltaReflectionHit(path); +#elif defined(DELTA_TRANSMISSION_PASS) + gPathTracer.handleDeltaTransmissionHit(path); +#else + VisibilityQuery vq; + gPathTracer.handleHit(path, vq); +#endif + } + else if (processPrimaryMiss) + { + // For ReSTIR PT area reservoirs, handle primary miss here so that the + // environment emission contributes to the area reservoir and the result + // is written via writeOutput below. + path.setVertexIndex(1); + gPathTracer.handleMiss(path); + } + else + { + // Note the primary miss has already been handled by the separate path generation pass + // the path tracer runs first. Abort execution here to avoid double work. + return; + } + + // Trace path. + while (path.isActive()) + { + HitObject hitObject; + + { + // Advance to next path vertex. + path.incrementVertexIndex(); + + // Trace ray with minimal payload (only carries hit info back). + logTraceRay(PixelStatsRayType::ClosestHit); + const Ray ray = path.getScatterRay(); + PathPayload payload = {}; + const uint rayFlags = kUseAlphaTest ? RAY_FLAG_NONE : RAY_FLAG_FORCE_OPAQUE; + hitObject = HitObject::TraceRay( + gScene.rtAccel, + rayFlags, + 0xff /* instanceInclusionMask */, + kRayTypeScatter /* hitIdx */, + getRayTypeCount(), + kMissScatter, + ray.toRayDesc(), + payload + ); + + if (hitObject.IsHit()) + { + path.setHit(makeHitInfo(hitObject)); + path.sceneLength += float16_t(hitObject.GetRayDesc().TMax); + } + else + { + path.clearHit(); + path.sceneLength = float16_t(kNRDInvalidPathLength); + } + } + + // Add path coherence hints. + const uint materialID = gScene.getMaterialID(path.hit.getInstanceID()); + const MaterialHeader materialHeader = gScene.materials.getMaterialHeader(materialID); + uint coherenceHints = gPathTracer.getCoherenceHints(path, materialHeader.isDeltaSpecular(), materialHeader.isEmissive()); + + // Reorder threads for coherence, then process hit/miss directly. + ReorderThread(hitObject, coherenceHints, 4); + processVertex(path); + } + +#if !defined(DELTA_REFLECTION_PASS) && !defined(DELTA_TRANSMISSION_PASS) + gPathTracer.writeOutput(path); +#endif + } + + /** Runs the path tracer for a single pixel. + \param[in] pixel Pixel index. + */ + void run(uint2 pixel) + { + // Determine number of samples to take. + uint samplesRemaining = kSamplesPerPixel; + if (kSamplesPerPixel == 0) + { + samplesRemaining = gPathTracer.sampleCount[pixel]; + } + + // Loop over samples. + while (samplesRemaining > 0) + { + samplesRemaining -= 1; + uint pathID = pixel.x | (pixel.y << 12) | (samplesRemaining << 24); + tracePath(pathID); + + // Use SER to compact active threads. + if (kSamplesPerPixel == 0) + { + bool needReorder = WaveActiveAnyTrue(samplesRemaining == 0); + if (samplesRemaining == 0) + break; + if (needReorder) + { + HitObject hitObject = HitObject::MakeNop(); + ReorderThread(hitObject); + } + } + } + } +} + +/** Simple scheduler using a fullscreen raytracing dispatch. + */ +struct Scheduler +{ + /** Trace a ray to find the next hit and process it. + Path state lives in raygen registers; only hit info travels through the payload. + \param[in,out] path Path state. + */ + void nextHit(inout PathState path) + { + // Advance to next path vertex. + path.incrementVertexIndex(); + + // Trace ray with minimal payload. + logTraceRay(PixelStatsRayType::ClosestHit); + const Ray ray = path.getScatterRay(); + PathPayload payload = {}; + uint rayFlags = RAY_FLAG_NONE; + if (!kUseAlphaTest) + rayFlags |= RAY_FLAG_FORCE_OPAQUE; + TraceRay( + gScene.rtAccel, + rayFlags, + 0xff /* instanceInclusionMask */, + kRayTypeScatter /* hitIdx */, + getRayTypeCount(), + kMissScatter /* missIdx */, + ray.toRayDesc(), + payload + ); + + // Recover hit info from the minimal payload. + HitInfo hit = unpackHitInfo(payload.hit); + if (hit.isValid()) + { + path.setHit(hit); + path.sceneLength += float16_t(payload.hitT); + } + else + { + path.clearHit(); + path.sceneLength = float16_t(kNRDInvalidPathLength); + } + + // Process the vertex directly in raygen. + gPathTracer.setupPathLogging(path); + if (hit.isValid()) + { +#if defined(DELTA_REFLECTION_PASS) + gPathTracer.handleDeltaReflectionHit(path); +#elif defined(DELTA_TRANSMISSION_PASS) + gPathTracer.handleDeltaTransmissionHit(path); +#else + VisibilityQuery vq; + gPathTracer.handleHit(path, vq); +#endif + } + else + { + gPathTracer.handleMiss(path); + } + } + + /** Traces a path and write result to output. + \param[in] pathID Path ID. + */ + void tracePath(uint pathID) + { + PathState path = {}; + + gPathTracer.generatePath(pathID, path); + gPathTracer.setupPathLogging(path); + +#if USE_RESTIRPT && !defined(DELTA_REFLECTION_PASS) && !defined(DELTA_TRANSMISSION_PASS) + const bool processPrimaryMiss = gPathTracer.restirpt.kUseAreaReservoirs; +#else + const bool processPrimaryMiss = false; +#endif + + // Handle primary hit/miss. + if (path.isHit()) + { +#if defined(DELTA_REFLECTION_PASS) + gPathTracer.handleDeltaReflectionHit(path); +#elif defined(DELTA_TRANSMISSION_PASS) + gPathTracer.handleDeltaTransmissionHit(path); +#else + VisibilityQuery vq; + gPathTracer.handleHit(path, vq); +#endif + } + else if (processPrimaryMiss) + { + // For ReSTIR PT area reservoirs, handle primary miss here so that the + // environment emission contributes to the area reservoir and the result + // is written via writeOutput below. + path.setVertexIndex(1); + gPathTracer.handleMiss(path); + } + else + { + // Note the primary miss has already been handled by the separate path generation pass + // the path tracer runs first. Abort execution here to avoid double work. + return; + } + + // Trace path. + while (path.isActive()) + { + nextHit(path); + } + +#if !defined(DELTA_REFLECTION_PASS) && !defined(DELTA_TRANSMISSION_PASS) + gPathTracer.writeOutput(path); +#endif + } + + /** Runs the path tracer for a single pixel. + \param[in] pixel Pixel index. + */ + void run(uint2 pixel) + { + // Determine number of samples to take. + uint samplesRemaining = kSamplesPerPixel; + if (kSamplesPerPixel == 0) + { + samplesRemaining = gPathTracer.sampleCount[pixel]; + } + + // Loop over samples. + while (samplesRemaining > 0) + { + samplesRemaining -= 1; + uint pathID = pixel.x | (pixel.y << 12) | (samplesRemaining << 24); + tracePath(pathID); + } + } +} + +// +// Shader entry point for miss shaders. +// + +[shader("miss")] +void scatterMiss(inout PathPayload payload: SV_RayPayload) +{ + // No-op: default-initialized payload (PackedHitInfo == 0) already indicates miss. +} + +// +// Shader entry points for TriangleMesh hit groups. +// + +[shader("anyhit")] +void scatterTriangleAnyHit( + inout PathPayload payload: SV_RayPayload, + BuiltInTriangleIntersectionAttributes attribs: SV_IntersectionAttributes +) +{ +#if USE_ALPHA_TEST + // Alpha test for non-opaque geometry. + GeometryInstanceID instanceID = getGeometryInstanceID(); + VertexData v = getVertexData(instanceID, PrimitiveIndex(), attribs); + const uint materialID = gScene.getMaterialID(instanceID); + if (gScene.materials.alphaTest(v, materialID, 0.f)) + IgnoreHit(); +#endif +} + +[shader("closesthit")] +void scatterTriangleClosestHit( + inout PathPayload payload: SV_RayPayload, + BuiltInTriangleIntersectionAttributes attribs: SV_IntersectionAttributes +) +{ + TriangleHit triangleHit; + triangleHit.instanceID = getGeometryInstanceID(); + triangleHit.primitiveIndex = PrimitiveIndex(); + triangleHit.barycentrics = attribs.barycentrics; + payload.hit = HitInfo(triangleHit).pack(); + payload.hitT = RayTCurrent(); +} + +// +// Shader entry points for DisplacedTriangleMesh hit groups. +// + +[shader("intersection")] +void displacedTriangleMeshIntersection() +{ + const Ray ray = Ray(WorldRayOrigin(), WorldRayDirection(), RayTMin(), RayTCurrent()); + DisplacedTriangleMeshIntersector::Attribs attribs; + float t; + if (DisplacedTriangleMeshIntersector::intersect(ray, getGeometryInstanceID(), PrimitiveIndex(), attribs, t)) + { + ReportHit(t, 0, attribs); + } +} + +[shader("closesthit")] +void scatterDisplacedTriangleMeshClosestHit(inout PathPayload payload, DisplacedTriangleMeshIntersector::Attribs attribs) +{ + DisplacedTriangleHit displacedTriangleHit; + displacedTriangleHit.instanceID = getGeometryInstanceID(); + displacedTriangleHit.primitiveIndex = PrimitiveIndex(); + displacedTriangleHit.barycentrics = attribs.barycentrics; + displacedTriangleHit.displacement = attribs.displacement; + payload.hit = HitInfo(displacedTriangleHit).pack(); + payload.hitT = RayTCurrent(); +} + +// +// Shader entry points for Curve hit groups. +// + +[shader("intersection")] +void curveIntersection() +{ + const Ray ray = Ray(WorldRayOrigin(), WorldRayDirection(), RayTMin(), RayTCurrent()); + CurveIntersector::Attribs attribs; + float t; + if (CurveIntersector::intersect(ray, getGeometryInstanceID(), PrimitiveIndex(), attribs, t)) + { + ReportHit(t, 0, attribs); + } +} + +[shader("closesthit")] +void scatterCurveClosestHit(inout PathPayload payload, CurveIntersector::Attribs attribs) +{ + CurveHit curveHit; + curveHit.instanceID = getGeometryInstanceID(); + curveHit.primitiveIndex = PrimitiveIndex(); + curveHit.barycentrics = attribs.barycentrics; + payload.hit = HitInfo(curveHit).pack(); + payload.hitT = RayTCurrent(); +} + + +// +// Shader entry points for SDFGrid hit groups. +// + +[shader("intersection")] +void sdfGridIntersection() +{ + const Ray ray = Ray(WorldRayOrigin(), WorldRayDirection(), RayTMin(), RayTCurrent()); + SDFGridHitData sdfGridHitData; + float t; + if (SDFGridIntersector::intersect(ray, getGeometryInstanceID(), PrimitiveIndex(), sdfGridHitData, t)) + { + ReportHit(t, 0, sdfGridHitData); + } +} + +[shader("closesthit")] +void scatterSdfGridClosestHit(inout PathPayload payload, SDFGridHitData sdfGridHitData) +{ + SDFGridHit sdfGridHit; + sdfGridHit.instanceID = getGeometryInstanceID(); + sdfGridHit.hitData = sdfGridHitData; + payload.hit = HitInfo(sdfGridHit).pack(); + payload.hitT = RayTCurrent(); +} + +// +// Shader entry point for ray generation shader. +// + +[shader("raygeneration")] +void rayGen() +{ + uint2 pixel = DispatchRaysIndex().xy; + uint2 frameDim = DispatchRaysDimensions().xy; + if (all(pixel >= frameDim)) + return; + + gScheduler.run(pixel); +} diff --git a/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp b/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp index e754cecc68..e18fbf0919 100644 --- a/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp +++ b/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp @@ -118,17 +118,7 @@ SceneDebugger::SceneDebugger(ref pDevice, const Properties& props) : Ren FALCOR_THROW("SceneDebugger requires Raytracing Tier 1.1 support."); // Parse dictionary. - for (const auto& [key, value] : props) - { - if (key == kMode) - mParams.mode = (uint32_t)value.operator SceneDebuggerMode(); - else if (key == kShowVolumes) - mParams.showVolumes = value; - else if (key == kUseVBuffer) - mParams.useVBuffer = static_cast(value); - else - logWarning("Unknown property '{}' in a SceneDebugger properties.", key); - } + setProperties(props); mpFence = mpDevice->createFence(); @@ -145,6 +135,21 @@ Properties SceneDebugger::getProperties() const return props; } +void SceneDebugger::setProperties(const Properties& props) +{ + for (const auto& [key, value] : props) + { + if (key == kMode) + mParams.mode = (uint32_t)value.operator SceneDebuggerMode(); + else if (key == kShowVolumes) + mParams.showVolumes = value; + else if (key == kUseVBuffer) + mParams.useVBuffer = static_cast(value); + else + logWarning("Unknown property '{}' in a SceneDebugger properties.", key); + } +} + RenderPassReflection SceneDebugger::reflect(const CompileData& compileData) { RenderPassReflection reflector; diff --git a/Source/RenderPasses/SceneDebugger/SceneDebugger.h b/Source/RenderPasses/SceneDebugger/SceneDebugger.h index fd3b04002b..0bb21ddb45 100644 --- a/Source/RenderPasses/SceneDebugger/SceneDebugger.h +++ b/Source/RenderPasses/SceneDebugger/SceneDebugger.h @@ -50,6 +50,7 @@ class SceneDebugger : public RenderPass SceneDebugger(ref pDevice, const Properties& props); Properties getProperties() const override; + void setProperties(const Properties& props) override; RenderPassReflection reflect(const CompileData& compileData) override; void compile(RenderContext* pRenderContext, const CompileData& compileData) override; void setScene(RenderContext* pRenderContext, const ref& pScene) override; diff --git a/Source/RenderPasses/ToneMapper/ToneMapping.ps.slang b/Source/RenderPasses/ToneMapper/ToneMapping.ps.slang index aa62307333..de3721f673 100644 --- a/Source/RenderPasses/ToneMapper/ToneMapping.ps.slang +++ b/Source/RenderPasses/ToneMapper/ToneMapping.ps.slang @@ -33,7 +33,7 @@ SamplerState gColorSampler; Texture2D gColorTex; Texture2D gLuminanceTex; -static const uint kOperator = _TONE_MAPPER_OPERATOR; +static const ToneMapperOperator kOperator = ToneMapperOperator(_TONE_MAPPER_OPERATOR); static const float kExposureKey = 0.042; static const float kLuminanceLod = 16.0; // Lookup highest mip level to get average luminance diff --git a/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang b/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang index 1a624b27c1..9c854c1148 100644 --- a/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang +++ b/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang @@ -94,6 +94,7 @@ float evalMIS(float n0, float p0, float n1, float p1) [Differentiable] [PreferRecompute] +[__NoSideEffect] ShadingData loadShadingData(SceneQueryAD sceneQuery, HitInfo hit, IntersectionAD isect, const float3 rayOrigin, const float3 rayDir) { VertexData v = {}; @@ -124,6 +125,7 @@ ShadingData loadShadingData(SceneQueryAD sceneQuery, HitInfo hit, IntersectionAD [Differentiable] [PreferRecompute] +[__NoSideEffect] IMaterialInstance getDiffMaterialInstance(out DiffMaterialData diffData, const ShadingData sd, const ITextureSampler lod) { let material = gScene.materials.getMaterial(sd.materialID); @@ -133,6 +135,7 @@ IMaterialInstance getDiffMaterialInstance(out DiffMaterialData diffData, const S [Differentiable] [PreferRecompute] +[__NoSideEffect] bool generateScatterRay( SceneQueryAD sceneQuery, ShadingData sd, @@ -174,6 +177,7 @@ bool generateScatterRay( // Generate a light sample on the emissive geometry. // Assume the area lights are static (we don't allow differentiation of dynamic area lights for now). [PreferRecompute] +[__NoSideEffect] bool generateEmissiveSample( IntersectionAD isect, EmissiveLightSampler emissiveSampler, @@ -208,6 +212,7 @@ bool generateEmissiveSample( [Differentiable] [PreferRecompute] +[__NoSideEffect] void computeNEE( SceneQueryAD sceneQuery, ShadingData sd, @@ -250,6 +255,7 @@ void computeNEE( [Differentiable] [PreferRecompute] +[__NoSideEffect] void handleHit( SceneQueryAD sceneQuery, IntersectionAD isect, @@ -262,7 +268,11 @@ void handleHit( { let lod = ExplicitLodTextureSampler(0.f); - TriangleHit triHit = { isect.instanceID, isect.triangleID, isect.barycentrics.y, isect.barycentrics.z }; + TriangleHit triHit; + triHit.instanceID = isect.instanceID; + triHit.primitiveIndex = isect.triangleID; + triHit.barycentrics = detach(float2(isect.barycentrics.y, isect.barycentrics.z)); + HitInfo hit = HitInfo(triHit); // Load shading data. diff --git a/Source/RenderPasses/WARDiffPathTracer/WarpedAreaReparam.slang b/Source/RenderPasses/WARDiffPathTracer/WarpedAreaReparam.slang index a1183de2f4..df5f389469 100644 --- a/Source/RenderPasses/WARDiffPathTracer/WarpedAreaReparam.slang +++ b/Source/RenderPasses/WARDiffPathTracer/WarpedAreaReparam.slang @@ -71,6 +71,7 @@ float computeHarmonicWeight( [Differentiable] [PreferRecompute] +[__NoSideEffect] DifferentialPair fwd_computeHarmonicWeight( IntersectionAD isect, float3 origin, @@ -196,6 +197,7 @@ __generic bool traceAsymptoticWeightedMeanIntersection( [Differentiable] [PreferRecompute] +[__NoSideEffect] void computeWarpedRay( SceneQueryAD sceneQuery, no_diff float kappa, @@ -223,6 +225,7 @@ void computeWarpedRay( [Differentiable] [PreferRecompute] +[__NoSideEffect] float reparameterizeRay(SceneQueryAD sceneQuery, inout RayAD ray, inout SampleGenerator sg) { DifferentialPair dpDirections = DifferentialPair( @@ -266,6 +269,7 @@ float reparameterizeRay(SceneQueryAD sceneQuery, inout RayAD ray, inout SampleGe [Differentiable] [PreferRecompute] +[__NoSideEffect] void computeWarpedPrimarySample( SceneQueryAD sceneQuery, no_diff float kappa, @@ -302,6 +306,7 @@ void computeWarpedPrimarySample( [Differentiable] [PreferRecompute] +[__NoSideEffect] float reparameterizeScreenSample(SceneQueryAD sceneQuery, uint2 frameDim, SampleGenerator sg, inout float2 posS, ) { // Forward-mode gradients w.r.t. screen-space position. diff --git a/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.rt.slang b/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.rt.slang index e7b867241a..98085580ec 100644 --- a/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.rt.slang +++ b/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.rt.slang @@ -150,6 +150,8 @@ struct ScatterRayData /// Per-ray state for the sample generator (up to 16B). SampleGenerator sg; + __init() {} + /** * Initializes ray payload with default parameters. */ diff --git a/Source/Tools/FalcorTest/CMakeLists.txt b/Source/Tools/FalcorTest/CMakeLists.txt index b527bd5265..4cdb316438 100644 --- a/Source/Tools/FalcorTest/CMakeLists.txt +++ b/Source/Tools/FalcorTest/CMakeLists.txt @@ -61,8 +61,6 @@ target_sources(FalcorTest PRIVATE Tests/Sampling/AliasTableTests.cs.slang Tests/Sampling/LowDiscrepancyTests.cpp Tests/Sampling/LowDiscrepancyTests.cs.slang - Tests/Sampling/PointSetsTests.cpp - Tests/Sampling/PointSetsTests.cs.slang Tests/Sampling/PseudorandomTests.cpp Tests/Sampling/PseudorandomTests.cs.slang Tests/Sampling/SampleGeneratorTests.cpp diff --git a/Source/Tools/FalcorTest/Tests/Core/BufferTests.cs.slang b/Source/Tools/FalcorTest/Tests/Core/BufferTests.cs.slang index 1dbcdb3ade..620200c11f 100644 --- a/Source/Tools/FalcorTest/Tests/Core/BufferTests.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Core/BufferTests.cs.slang @@ -32,6 +32,10 @@ that copies the result to the output buffer. */ +#ifndef TYPE +#define TYPE 0 // Define TYPE for tests that do not use it, to silence their warnings +#endif + #if TYPE == 0 RWByteAddressBuffer buffer; #elif TYPE == 1 diff --git a/Source/Tools/FalcorTest/Tests/Core/DDSReadTests.cpp b/Source/Tools/FalcorTest/Tests/Core/DDSReadTests.cpp index 49d646bce8..9e43b2b11c 100644 --- a/Source/Tools/FalcorTest/Tests/Core/DDSReadTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Core/DDSReadTests.cpp @@ -71,7 +71,7 @@ void testDDS(GPUUnitTestContext& ctx, const std::string& testName, ResourceForma } // Read reference image. If no reference image exists, the test will fail, and a reference image will be output. - std::filesystem::path refPath = getRuntimeDirectory() / fmt::format("data/tests/{}-ref.png", testName); + std::filesystem::path refPath = getRuntimeDirectory() / fmt::format("data/tests/{}-ref.exr", testName); ref pPngTex; if (std::filesystem::exists(refPath)) @@ -109,7 +109,7 @@ void testDDS(GPUUnitTestContext& ctx, const std::string& testName, ResourceForma { // Create program to copy decompressed image so that we can save it as the reference ctx.createProgram("Tests/Core/DDSReadTests.cs.slang", "readback"); - ctx.allocateStructuredBuffer("result", 4 * sizeof(uint32_t) * pDst->getWidth() * pDst->getHeight()); + ctx.allocateStructuredBuffer("result", 4 * sizeof(float) * pDst->getWidth() * pDst->getHeight()); } const uint2 dstDim(pDst->getWidth(), pDst->getHeight()); @@ -130,7 +130,8 @@ void testDDS(GPUUnitTestContext& ctx, const std::string& testName, ResourceForma analyzer.analyze(ctx.getRenderContext(), pDiffTex, 0, 0, pResultBuffer); TextureAnalyzer::Result result = pResultBuffer->getElement(0); - // Expect difference image to be uniform 0. + // Comparing the decompressed DDS with the previously stored EXR, that contains the same data in float, + // should yield the same result every time. EXPECT(result.isConstant(TextureChannelFlags::Red)); EXPECT(result.isConstant(TextureChannelFlags::Green)); EXPECT(result.isConstant(TextureChannelFlags::Blue)); @@ -144,25 +145,25 @@ void testDDS(GPUUnitTestContext& ctx, const std::string& testName, ResourceForma { // Save newly-created reference image std::vector result = ctx.readBuffer("result"); - Bitmap::UniqueConstPtr resultBitmap(Bitmap::create(dstDim.x, dstDim.y, ResourceFormat::RGBA8Unorm, result.data())); + Bitmap::UniqueConstPtr resultBitmap(Bitmap::create(dstDim.x, dstDim.y, ResourceFormat::RGBA32Float, result.data())); Bitmap::saveImage( refPath, dstDim.x, dstDim.y, - Bitmap::FileFormat::PngFile, - Bitmap::ExportFlags::Uncompressed | Bitmap::ExportFlags::ExportAlpha, - ResourceFormat::RGBA8Unorm, - false, + Bitmap::FileFormat::ExrFile, + Bitmap::ExportFlags::ExportAlpha, + ResourceFormat::RGBA32Float, + true, result.data() ); } } } // namespace -#define DDS_TEST(x, f) \ - GPU_TEST(x) \ - { \ - testDDS(ctx, std::string(#x), f, false); \ +#define DDS_TEST(x, f) \ + GPU_TEST(x, "Disabled, no longer need to test texture compression.") \ + { \ + testDDS(ctx, std::string(#x), f, false); \ } DDS_TEST(BC1Unorm, ResourceFormat::BC1Unorm); @@ -185,7 +186,7 @@ DDS_TEST(BC7UnormOdd, ResourceFormat::BC7Unorm); DDS_TEST(BC7UnormSrgb, ResourceFormat::BC7UnormSrgb); DDS_TEST(BC7UnormTiny, ResourceFormat::BC7Unorm); -GPU_TEST(BC7UnormBroken) +GPU_TEST(BC7UnormBroken, "Disabled, no longer need to test texture compression.") { testDDS(ctx, std::string("BC7UnormBroken"), ResourceFormat::BC7Unorm, true); } diff --git a/Source/Tools/FalcorTest/Tests/Core/DDSReadTests.cs.slang b/Source/Tools/FalcorTest/Tests/Core/DDSReadTests.cs.slang index d308695f9c..095c71e027 100644 --- a/Source/Tools/FalcorTest/Tests/Core/DDSReadTests.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Core/DDSReadTests.cs.slang @@ -25,7 +25,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -RWStructuredBuffer result; +RWStructuredBuffer result; RWStructuredBuffer difference; Texture2D tex; @@ -47,10 +47,8 @@ void readback(uint3 threadId: SV_DispatchThreadID) // Convert to uint8_t, and pack into uint32_t // clang-format off - result[idx] = ((uint)(saturate(v[3]) * 255) << 24) | - ((uint)(saturate(v[2]) * 255) << 16) | - ((uint)(saturate(v[1]) * 255) << 8) | - ((uint)(saturate(v[0]) * 255)); + for (uint32_t i = 0; i < 4; ++i) + result[4 * idx + i] = v[i]; // clang-format on } @@ -62,9 +60,10 @@ void diff(uint3 threadId: SV_DispatchThreadID) uint idx = threadId.y * sz.x + threadId.x; float4 tv = tex[threadId.xy]; - float4 rv = tex[threadId.xy]; + float4 rv = ref[threadId.xy]; for (uint32_t i = 0; i < 4; ++i) { - difference[4 * idx + i] = rv[i] - tv[i]; + // We have to abs here, because the TextureAnalyzer clamps min/max values to zero, so negative values would be wrong. + difference[4 * idx + i] = abs(rv[i] - tv[i]); } } diff --git a/Source/Tools/FalcorTest/Tests/Scene/Material/MERLFileTests.cpp b/Source/Tools/FalcorTest/Tests/Scene/Material/MERLFileTests.cpp index ce8c1b1700..abcd920847 100644 --- a/Source/Tools/FalcorTest/Tests/Scene/Material/MERLFileTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Scene/Material/MERLFileTests.cpp @@ -48,7 +48,7 @@ GPU_TEST(MERLFile) EXPECT_EQ(data.size(), 90 * 90 * 360 / 2); const float3 expected = float3(0.5f); - auto lut = merlFile.prepareAlbedoLUT(ctx.getDevice()); + auto lut = merlFile.prepareAlbedoLUT(ctx.getDevice(), false); EXPECT_EQ(lut.size(), MERLMaterialData::kAlbedoLUTSize); for (auto v : lut) { diff --git a/Source/Tools/FalcorTest/Tests/Slang/Atomics.cpp b/Source/Tools/FalcorTest/Tests/Slang/Atomics.cpp index f2ddbc2df8..a0b6a743f5 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/Atomics.cpp +++ b/Source/Tools/FalcorTest/Tests/Slang/Atomics.cpp @@ -45,7 +45,6 @@ void testInterlockedAddF16(GPUUnitTestContext& ctx, std::string_view entryPoint) ProgramDesc desc; desc.addShaderLibrary(kShaderFile).csEntry("testBufferAddF16"); - desc.setUseSPIRVBackend(); // NOTE: The SPIR-V backend is required for RWByteAddressBuffer.InterlockedAddF16() on Vulkan! ctx.createProgram(desc); std::vector elems(kNumElems * 2); @@ -145,7 +144,6 @@ GPU_TEST(Atomics_Texture2D_InterlockedAddF32) ProgramDesc desc; desc.addShaderLibrary(kShaderFile).csEntry("testTextureAddF32"); - desc.setUseSPIRVBackend(); // NOTE: The SPIR-V backend is required for RWTexture2D.InterlockedAddF32() on Vulkan! ctx.createProgram(desc); std::vector elems(kNumElems); diff --git a/Source/Tools/FalcorTest/Tests/Slang/Atomics.cs.slang b/Source/Tools/FalcorTest/Tests/Slang/Atomics.cs.slang index c0025a5f35..9bcc4afde2 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/Atomics.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Slang/Atomics.cs.slang @@ -39,12 +39,12 @@ void testBufferAddF16(uint3 threadID: SV_DispatchThreadID) uint y = x ^ 0x80008000; // Flip signs // This should work on both D3D12 and Vulkan. - // NOTE: The SPIR-V backend is required for RWByteAddressBuffer.InterlockedAddF16() on Vulkan! + // NOTE: This has to be InterlockedAddF16Emulated due to changes in the Slang API. float16_t origVal; - resultBuf.InterlockedAddF16(0, asfloat16((uint16_t)x), origVal); - resultBuf.InterlockedAddF16(2, asfloat16((uint16_t)(x >> 16)), origVal); - resultBuf.InterlockedAddF16(4, asfloat16((uint16_t)y), origVal); - resultBuf.InterlockedAddF16(6, asfloat16((uint16_t)(y >> 16)), origVal); + resultBuf.InterlockedAddF16Emulated(0, asfloat16((uint16_t)x), origVal); + resultBuf.InterlockedAddF16Emulated(2, asfloat16((uint16_t)(x >> 16)), origVal); + resultBuf.InterlockedAddF16Emulated(4, asfloat16((uint16_t)y), origVal); + resultBuf.InterlockedAddF16Emulated(6, asfloat16((uint16_t)(y >> 16)), origVal); } [numthreads(256, 1, 1)] @@ -54,7 +54,6 @@ void testBufferAddF16_2(uint3 threadID: SV_DispatchThreadID) uint y = x ^ 0x80008000; // Flip signs // This should work on both D3D12 and Vulkan. - // NOTE: The SPIR-V backend is required for RWByteAddressBuffer._NvInterlockedAddFp16x2() on Vulkan! resultBuf._NvInterlockedAddFp16x2(0, x); resultBuf._NvInterlockedAddFp16x2(4, y); } @@ -75,7 +74,6 @@ void testTextureAddF32(uint3 threadID: SV_DispatchThreadID) float x = data[threadID.x]; // This should work on both D3D12 and Vulkan. - // NOTE: The SPIR-V backend is required for RWTexture2D.InterlockedAddF32() on Vulkan! resultTex.InterlockedAddF32(uint2(0, 0), x); resultTex.InterlockedAddF32(uint2(1, 0), -x); } diff --git a/Source/Tools/FalcorTest/Tests/Slang/ShaderModel.cpp b/Source/Tools/FalcorTest/Tests/Slang/ShaderModel.cpp index 8dc3b02006..a1ac4e692c 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/ShaderModel.cpp +++ b/Source/Tools/FalcorTest/Tests/Slang/ShaderModel.cpp @@ -35,6 +35,8 @@ const uint32_t kNumElems = 256; void test(GPUUnitTestContext& ctx, ShaderModel shaderModel) { + ASSERT(ctx.getDevice()->isShaderModelSupported(shaderModel)); + ctx.createProgram("Tests/Slang/ShaderModel.cs.slang", "main", DefineList(), SlangCompilerFlags::None, shaderModel); ctx.allocateStructuredBuffer("result", kNumElems); ctx.runProgram(kNumElems, 1, 1); @@ -52,6 +54,8 @@ GPU_TEST(ShaderModel6_0) test(ctx, ShaderModel::SM6_0); } +// Currently on NA branch the higher shader models aren't detected correctly +#if FALCOR_WINDOWS GPU_TEST(ShaderModel6_1) { test(ctx, ShaderModel::SM6_1); @@ -76,11 +80,28 @@ GPU_TEST(ShaderModel6_5) { test(ctx, ShaderModel::SM6_5); } +#endif #if FALCOR_HAS_D3D12_AGILITY_SDK GPU_TEST(ShaderModel6_6, Device::Type::D3D12) { test(ctx, ShaderModel::SM6_6); } + +GPU_TEST(ShaderModel6_7, Device::Type::D3D12) +{ + test(ctx, ShaderModel::SM6_7); +} + +GPU_TEST(ShaderModel6_8, Device::Type::D3D12) +{ + ctx.skip("SM6.8 is not supported on NA version of dxc"); + test(ctx, ShaderModel::SM6_8); +} + +GPU_TEST(ShaderModel6_9, Device::Type::D3D12, "Requires an updated driver.") +{ + test(ctx, ShaderModel::SM6_9); +} #endif } // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Slang/SlangInheritance.cs.slang b/Source/Tools/FalcorTest/Tests/Slang/SlangInheritance.cs.slang index d13dff40d7..13115ca571 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/SlangInheritance.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Slang/SlangInheritance.cs.slang @@ -32,7 +32,7 @@ BEGIN_NAMESPACE_FALCOR struct A { - uint scalar; + uint scalar = {}; SETTER_DECL void setScalar(uint x) { scalar = x; } uint getScalar() { return scalar; } @@ -40,7 +40,7 @@ struct A struct B : A { - float3 vector; + float3 vector = {}; SETTER_DECL void setVector(float3 v) { vector = v; } float3 getVector() { return vector; } diff --git a/Source/Tools/FalcorTest/Tests/Slang/SlangTests.cs.slang b/Source/Tools/FalcorTest/Tests/Slang/SlangTests.cs.slang index 5d4c661b4a..f41b8b8936 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/SlangTests.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Slang/SlangTests.cs.slang @@ -73,17 +73,18 @@ void testScalarTypes() } // 16/32/64-bit integer + // NOTE: The original tests had asuint(int(..)) that triggers an assert in dxc. See https://github.com/shader-slang/slang/issues/6717 { int16_t a = 30000; // 0x00007530 -> 0x7530 (30000) in 16-bit. int16_t b = -200000; // 0xfffcf2c0 -> Truncated to 0xf2c0 (-3392) in 16-bit. - result[i++] = asuint((int)a); - result[i++] = asuint((int)b); + result[i++] = (uint)a; + result[i++] = (uint)b; } { int32_t a = 291123; int32_t b = -2000000000; - result[i++] = asuint(a); - result[i++] = asuint(b); + result[i++] = (uint)a; + result[i++] = (uint)b; } { // NOTE: Must use 'LL' suffix for signed 64-bit literals, otherwise interpreted as 32-bit. diff --git a/Source/Tools/FalcorTest/Tests/Utils/PathResolvingTests.cpp b/Source/Tools/FalcorTest/Tests/Utils/PathResolvingTests.cpp index 8cd0d0bf36..2c5194cfb1 100644 --- a/Source/Tools/FalcorTest/Tests/Utils/PathResolvingTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Utils/PathResolvingTests.cpp @@ -99,11 +99,10 @@ CPU_TEST(PathResolving_ResolveEnvVar) EXPECT_EQ(test, "_Value1Value2_"); }; -// We use comparison with weakly_canonical paths to resolve all possible permissible differences in paths, +// We use comparison with lexically_normal() paths to resolve all possible permissible differences in paths, // such as capital/lowercase letters or backwards/forwards slashes. Just straight path comparison does a string // comparison, so C:\Media != c:\media, while std::filesystem::equivalent requires the target of the path to actually -// exist on the file system. weakly_canonical will resolve the existing part of the path to what exists on the filesystem, -// and the non-existing remainder of the path into a standardized path form. +// exist on the file system. lexically_normal() will resolve the path into a standardized path form. CPU_TEST(PathResolving_Basic) { @@ -116,42 +115,42 @@ CPU_TEST(PathResolving_Basic) result = resolveSearchPaths(current, update, standard); ASSERT_EQ(result.invalid.size(), 0); ASSERT_EQ(result.resolved.size(), 2); - EXPECT_EQ(result.resolved[0], std::filesystem::weakly_canonical(C_DRIVE "/update/path/one")); - EXPECT_EQ(result.resolved[1], std::filesystem::weakly_canonical(D_DRIVE "/update/path/two")); + EXPECT_EQ(result.resolved[0], std::filesystem::path(C_DRIVE "/update/path/one").lexically_normal()); + EXPECT_EQ(result.resolved[1], std::filesystem::path(D_DRIVE "/update/path/two").lexically_normal()); update = {C_DRIVE "/update/path/one;" D_DRIVE "/update/path/two"}; result = resolveSearchPaths(current, update, standard); ASSERT_EQ(result.invalid.size(), 0); ASSERT_EQ(result.resolved.size(), 2); - EXPECT_EQ(result.resolved[0], std::filesystem::weakly_canonical(C_DRIVE "/update/path/one")); - EXPECT_EQ(result.resolved[1], std::filesystem::weakly_canonical(D_DRIVE "/update/path/two")); + EXPECT_EQ(result.resolved[0], std::filesystem::path(C_DRIVE "/update/path/one").lexically_normal()); + EXPECT_EQ(result.resolved[1], std::filesystem::path(D_DRIVE "/update/path/two").lexically_normal()); update = {C_DRIVE "/update/path/one;&;" D_DRIVE "/update/path/two;@;"}; result = resolveSearchPaths(current, update, standard); ASSERT_EQ(result.invalid.size(), 0); ASSERT_EQ(result.resolved.size(), 4); - EXPECT_EQ(result.resolved[0], std::filesystem::weakly_canonical(C_DRIVE "/update/path/one")); - EXPECT_EQ(result.resolved[1], std::filesystem::weakly_canonical(C_DRIVE "/current/path")); - EXPECT_EQ(result.resolved[2], std::filesystem::weakly_canonical(D_DRIVE "/update/path/two")); - EXPECT_EQ(result.resolved[3], std::filesystem::weakly_canonical(C_DRIVE "/standard/path")); + EXPECT_EQ(result.resolved[0], std::filesystem::path(C_DRIVE "/update/path/one").lexically_normal()); + EXPECT_EQ(result.resolved[1], std::filesystem::path(C_DRIVE "/current/path").lexically_normal()); + EXPECT_EQ(result.resolved[2], std::filesystem::path(D_DRIVE "/update/path/two").lexically_normal()); + EXPECT_EQ(result.resolved[3], std::filesystem::path(C_DRIVE "/standard/path").lexically_normal()); update = {C_DRIVE "/update/path/one;&", D_DRIVE "/update/path/two;@;"}; result = resolveSearchPaths(current, update, standard); ASSERT_EQ(result.invalid.size(), 0); ASSERT_EQ(result.resolved.size(), 4); - EXPECT_EQ(result.resolved[0], std::filesystem::weakly_canonical(C_DRIVE "/update/path/one")); - EXPECT_EQ(result.resolved[1], std::filesystem::weakly_canonical(C_DRIVE "/current/path")); - EXPECT_EQ(result.resolved[2], std::filesystem::weakly_canonical(D_DRIVE "/update/path/two")); - EXPECT_EQ(result.resolved[3], std::filesystem::weakly_canonical(C_DRIVE "/standard/path")); + EXPECT_EQ(result.resolved[0], std::filesystem::path(C_DRIVE "/update/path/one").lexically_normal()); + EXPECT_EQ(result.resolved[1], std::filesystem::path(C_DRIVE "/current/path").lexically_normal()); + EXPECT_EQ(result.resolved[2], std::filesystem::path(D_DRIVE "/update/path/two").lexically_normal()); + EXPECT_EQ(result.resolved[3], std::filesystem::path(C_DRIVE "/standard/path").lexically_normal()); update = {"update/path/one;&;" D_DRIVE "/update/path/two;@;"}; result = resolveSearchPaths(current, update, standard); ASSERT_EQ(result.invalid.size(), 1); EXPECT_EQ(result.invalid[0], "update/path/one"); ASSERT_EQ(result.resolved.size(), 3); - EXPECT_EQ(result.resolved[0], std::filesystem::weakly_canonical(C_DRIVE "/current/path")); - EXPECT_EQ(result.resolved[1], std::filesystem::weakly_canonical(D_DRIVE "/update/path/two")); - EXPECT_EQ(result.resolved[2], std::filesystem::weakly_canonical(C_DRIVE "/standard/path")); + EXPECT_EQ(result.resolved[0], std::filesystem::path(C_DRIVE "/current/path").lexically_normal()); + EXPECT_EQ(result.resolved[1], std::filesystem::path(D_DRIVE "/update/path/two").lexically_normal()); + EXPECT_EQ(result.resolved[2], std::filesystem::path(C_DRIVE "/standard/path").lexically_normal()); update = {"update/path/one;&;:/update/path/two;@;"}; result = resolveSearchPaths(current, update, standard); @@ -159,8 +158,8 @@ CPU_TEST(PathResolving_Basic) EXPECT_EQ(result.invalid[0], "update/path/one"); EXPECT_EQ(result.invalid[1], ":/update/path/two"); ASSERT_EQ(result.resolved.size(), 2); - EXPECT_EQ(result.resolved[0], std::filesystem::weakly_canonical(C_DRIVE "/current/path")); - EXPECT_EQ(result.resolved[1], std::filesystem::weakly_canonical(C_DRIVE "/standard/path")); + EXPECT_EQ(result.resolved[0], std::filesystem::path(C_DRIVE "/current/path").lexically_normal()); + EXPECT_EQ(result.resolved[1], std::filesystem::path(C_DRIVE "/standard/path").lexically_normal()); } CPU_TEST(PathResolving_EnvVar) @@ -183,26 +182,26 @@ CPU_TEST(PathResolving_EnvVar) result = resolveSearchPaths(current, update, standard, proxyResolver); ASSERT_EQ(result.invalid.size(), 0); ASSERT_EQ(result.resolved.size(), 2); - EXPECT_EQ(result.resolved[0], std::filesystem::weakly_canonical(C_DRIVE "/Project/Media")); - EXPECT_EQ(result.resolved[1], std::filesystem::weakly_canonical(C_DRIVE "/Users/jdoe/.falcor/media")); + EXPECT_EQ(result.resolved[0], std::filesystem::path(C_DRIVE "/Project/Media").lexically_normal()); + EXPECT_EQ(result.resolved[1], std::filesystem::path(C_DRIVE "/Users/jdoe/.falcor/media").lexically_normal()); } CPU_TEST(PathResolving_resolvePath) { auto fileChecker = [](const std::filesystem::path& path) { - auto canonical = std::filesystem::weakly_canonical(path); - if (canonical == std::filesystem::weakly_canonical(C_DRIVE "/Users/jdoe/settings.ini")) + auto canonical = path.lexically_normal(); + if (canonical == std::filesystem::path(C_DRIVE "/Users/jdoe/settings.ini").lexically_normal()) return true; - if (canonical == std::filesystem::weakly_canonical(D_DRIVE "/Project/Media/cornellbox.obj")) + if (canonical == std::filesystem::path(D_DRIVE "/Project/Media/cornellbox.obj").lexically_normal()) return true; - if (canonical == std::filesystem::weakly_canonical(D_DRIVE "/Project/Geometry/cornellbox.usd")) + if (canonical == std::filesystem::path(D_DRIVE "/Project/Geometry/cornellbox.usd").lexically_normal()) return true; - if (canonical == std::filesystem::weakly_canonical(E_DRIVE "/Textures/checkers.exr")) + if (canonical == std::filesystem::path(E_DRIVE "/Textures/checkers.exr").lexically_normal()) return true; - if (canonical == std::filesystem::weakly_canonical(D_DRIVE "/Project/Media/test.txt")) + if (canonical == std::filesystem::path(D_DRIVE "/Project/Media/test.txt").lexically_normal()) return true; - if (canonical == std::filesystem::weakly_canonical(E_DRIVE "/Textures/test.txt")) + if (canonical == std::filesystem::path(E_DRIVE "/Textures/test.txt").lexically_normal()) return true; return false; }; @@ -212,19 +211,19 @@ CPU_TEST(PathResolving_resolvePath) std::filesystem::path result; result = resolvePath(searchPaths, cwd, "../Media/cornellbox.obj", fileChecker); - EXPECT_EQ(result, std::filesystem::weakly_canonical(D_DRIVE "/Project/Media/cornellbox.obj")); + EXPECT_EQ(result, std::filesystem::path(D_DRIVE "/Project/Media/cornellbox.obj").lexically_normal()); result = resolvePath(searchPaths, cwd, "cornellbox.obj", fileChecker); - EXPECT_EQ(result, std::filesystem::weakly_canonical(D_DRIVE "/Project/Media/cornellbox.obj")); + EXPECT_EQ(result, std::filesystem::path(D_DRIVE "/Project/Media/cornellbox.obj").lexically_normal()); result = resolvePath(searchPaths, cwd, "test.txt", fileChecker); - EXPECT_EQ(result, std::filesystem::weakly_canonical(D_DRIVE "/Project/Media/test.txt")); + EXPECT_EQ(result, std::filesystem::path(D_DRIVE "/Project/Media/test.txt").lexically_normal()); result = resolvePath(searchPaths, cwd, "checkers.exr", fileChecker); - EXPECT_EQ(result, std::filesystem::weakly_canonical(E_DRIVE "/Textures/checkers.exr")); + EXPECT_EQ(result, std::filesystem::path(E_DRIVE "/Textures/checkers.exr").lexically_normal()); result = resolvePath(searchPaths, cwd, C_DRIVE "/Users/jdoe/settings.ini", fileChecker); - EXPECT_EQ(result, std::filesystem::weakly_canonical(C_DRIVE "/Users/jdoe/settings.ini")); + EXPECT_EQ(result, std::filesystem::path(C_DRIVE "/Users/jdoe/settings.ini").lexically_normal()); result = resolvePath(searchPaths, cwd, "./checkers.exr", fileChecker); EXPECT(result.empty()); @@ -233,7 +232,7 @@ CPU_TEST(PathResolving_resolvePath) EXPECT(result.empty()); result = resolvePath(searchPaths, cwd, "./cornellbox.usd", fileChecker); - EXPECT_EQ(result, std::filesystem::weakly_canonical(D_DRIVE "/Project/Geometry/cornellbox.usd")); + EXPECT_EQ(result, std::filesystem::path(D_DRIVE "/Project/Geometry/cornellbox.usd").lexically_normal()); result = resolvePath(searchPaths, cwd, "cornellbox.usd", fileChecker); EXPECT(result.empty()); diff --git a/Source/Tools/FalcorTest/Tests/Utils/SettingsTests.cpp b/Source/Tools/FalcorTest/Tests/Utils/SettingsTests.cpp index 0db21170a6..c7f6b2bdd2 100644 --- a/Source/Tools/FalcorTest/Tests/Utils/SettingsTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Utils/SettingsTests.cpp @@ -155,9 +155,9 @@ CPU_TEST(Settings_OptionsTypes) EXPECT_EQ(result[0], validTuple[0]); EXPECT_EQ(result[1], validTuple[1]); - EXPECT_THROW_AS(options.get("string", int(3)), Falcor::Settings::TypeError); - EXPECT_THROW_AS(options.get("int", std::string("test")), Falcor::Settings::TypeError); - EXPECT_THROW_AS(options.get("int[2]", float(0.f)), Falcor::Settings::TypeError); + EXPECT_THROW_AS(options.get("string", int(3)), nlohmann::json::type_error); + EXPECT_THROW_AS(options.get("int", std::string("test")), nlohmann::json::type_error); + EXPECT_THROW_AS(options.get("int[2]", float(0.f)), nlohmann::json::type_error); } CPU_TEST(Settings_OptionsOverride) @@ -339,7 +339,7 @@ CPU_TEST(Settings_UpdatePathsColon) pyDict["standardsearchpath:media"] = C_DRIVE "/media"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 1); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media").lexically_normal()); } { @@ -347,7 +347,7 @@ CPU_TEST(Settings_UpdatePathsColon) pyDict["standardsearchpath:media"] = C_DRIVE "/media/different"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 1); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media/different")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media/different").lexically_normal()); } { @@ -355,8 +355,8 @@ CPU_TEST(Settings_UpdatePathsColon) pyDict["standardsearchpath:media"] = "&;" C_DRIVE "/media/two"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 2); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media/different")); - EXPECT_EQ(settings.getSearchDirectories("media")[1], std::filesystem::weakly_canonical(C_DRIVE "/media/two")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media/different").lexically_normal()); + EXPECT_EQ(settings.getSearchDirectories("media")[1], std::filesystem::path(C_DRIVE "/media/two").lexically_normal()); } { @@ -364,7 +364,7 @@ CPU_TEST(Settings_UpdatePathsColon) pyDict["searchpath:media"] = "&;" C_DRIVE "/media/three"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 1); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media/three")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media/three").lexically_normal()); } { @@ -372,10 +372,10 @@ CPU_TEST(Settings_UpdatePathsColon) pyDict["searchpath:media"] = "&;@;" C_DRIVE "/media/four"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 4); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media/three")); - EXPECT_EQ(settings.getSearchDirectories("media")[1], std::filesystem::weakly_canonical(C_DRIVE "/media/different")); - EXPECT_EQ(settings.getSearchDirectories("media")[2], std::filesystem::weakly_canonical(C_DRIVE "/media/two")); - EXPECT_EQ(settings.getSearchDirectories("media")[3], std::filesystem::weakly_canonical(C_DRIVE "/media/four")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media/three").lexically_normal()); + EXPECT_EQ(settings.getSearchDirectories("media")[1], std::filesystem::path(C_DRIVE "/media/different").lexically_normal()); + EXPECT_EQ(settings.getSearchDirectories("media")[2], std::filesystem::path(C_DRIVE "/media/two").lexically_normal()); + EXPECT_EQ(settings.getSearchDirectories("media")[3], std::filesystem::path(C_DRIVE "/media/four").lexically_normal()); } } @@ -388,7 +388,7 @@ CPU_TEST(Settings_UpdatePathsSeparate) pyDict["standardsearchpath"]["media"] = C_DRIVE "/media"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 1); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media").lexically_normal()); } { @@ -397,7 +397,7 @@ CPU_TEST(Settings_UpdatePathsSeparate) pyDict["standardsearchpath"]["media"] = C_DRIVE "/media/different"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 1); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media/different")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media/different").lexically_normal()); } { @@ -406,8 +406,8 @@ CPU_TEST(Settings_UpdatePathsSeparate) pyDict["standardsearchpath"]["media"] = "&;" C_DRIVE "/media/two"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 2); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media/different")); - EXPECT_EQ(settings.getSearchDirectories("media")[1], std::filesystem::weakly_canonical(C_DRIVE "/media/two")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media/different").lexically_normal()); + EXPECT_EQ(settings.getSearchDirectories("media")[1], std::filesystem::path(C_DRIVE "/media/two").lexically_normal()); } { @@ -416,7 +416,7 @@ CPU_TEST(Settings_UpdatePathsSeparate) pyDict["searchpath"]["media"] = "&;" C_DRIVE "/media/three"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 1); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media/three")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media/three").lexically_normal()); } { @@ -425,10 +425,10 @@ CPU_TEST(Settings_UpdatePathsSeparate) pyDict["searchpath"]["media"] = "&;@;" C_DRIVE "/media/four"; settings.addOptions(pyDict); ASSERT_EQ(settings.getSearchDirectories("media").size(), 4); - EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::weakly_canonical(C_DRIVE "/media/three")); - EXPECT_EQ(settings.getSearchDirectories("media")[1], std::filesystem::weakly_canonical(C_DRIVE "/media/different")); - EXPECT_EQ(settings.getSearchDirectories("media")[2], std::filesystem::weakly_canonical(C_DRIVE "/media/two")); - EXPECT_EQ(settings.getSearchDirectories("media")[3], std::filesystem::weakly_canonical(C_DRIVE "/media/four")); + EXPECT_EQ(settings.getSearchDirectories("media")[0], std::filesystem::path(C_DRIVE "/media/three").lexically_normal()); + EXPECT_EQ(settings.getSearchDirectories("media")[1], std::filesystem::path(C_DRIVE "/media/different").lexically_normal()); + EXPECT_EQ(settings.getSearchDirectories("media")[2], std::filesystem::path(C_DRIVE "/media/two").lexically_normal()); + EXPECT_EQ(settings.getSearchDirectories("media")[3], std::filesystem::path(C_DRIVE "/media/four").lexically_normal()); } } } // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cpp b/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cpp index 2836fbb254..14254e5d75 100644 --- a/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cpp @@ -172,6 +172,14 @@ RangeDesc insertEmpty(SplitBuffer& buffer, uint32_t count) GPU_TEST(SplitBuffer_ByteBuffer_Large48b) { +#if FALCOR_LINUX + if (ctx.getDevice()->getType() == Device::Type::Vulkan) + { + ctx.skip("Skipping on Linux/Vulkan due intermittent SIGKILL failures."); + return; + } +#endif + static size_t k2GB = UINT64_C(1) << UINT64_C(31); static size_t k4GB = UINT64_C(1) << UINT64_C(32); @@ -231,6 +239,14 @@ GPU_TEST(SplitBuffer_ByteBuffer_Large48b) GPU_TEST(SplitBuffer_ByteBuffer_Large96b) { +#if FALCOR_LINUX + if (ctx.getDevice()->getType() == Device::Type::Vulkan) + { + ctx.skip("Skipping on Linux/Vulkan due intermittent SIGKILL failures."); + return; + } +#endif + static size_t k2GB = UINT64_C(1) << UINT64_C(31); static size_t k4GB = UINT64_C(1) << UINT64_C(32); @@ -290,6 +306,14 @@ GPU_TEST(SplitBuffer_ByteBuffer_Large96b) GPU_TEST(SplitBuffer_StructuredBuffer_Large4B) { +#if FALCOR_LINUX + if (ctx.getDevice()->getType() == Device::Type::Vulkan) + { + ctx.skip("Skipping on Linux/Vulkan due intermittent SIGKILL failures."); + return; + } +#endif + static size_t k2GB = UINT64_C(1) << UINT64_C(31); static size_t k4GB = UINT64_C(1) << UINT64_C(32); @@ -351,6 +375,14 @@ GPU_TEST(SplitBuffer_StructuredBuffer_Large4B) GPU_TEST(SplitBuffer_StructuredBuffer_Large32B) { +#if FALCOR_LINUX + if (ctx.getDevice()->getType() == Device::Type::Vulkan) + { + ctx.skip("Skipping on Linux/Vulkan due intermittent SIGKILL failures."); + return; + } +#endif + static size_t k2GB = UINT64_C(1) << UINT64_C(31); static size_t k4GB = UINT64_C(1) << UINT64_C(32); @@ -412,6 +444,14 @@ GPU_TEST(SplitBuffer_StructuredBuffer_Large32B) GPU_TEST(SplitBuffer_ByteBuffer_Many48b) { +#if FALCOR_LINUX + if (ctx.getDevice()->getType() == Device::Type::Vulkan) + { + ctx.skip("Skipping on Linux/Vulkan due intermittent SIGKILL failures."); + return; + } +#endif + static size_t k2GB = UINT64_C(1) << UINT64_C(31); static size_t k4GB = UINT64_C(1) << UINT64_C(32); @@ -470,6 +510,14 @@ GPU_TEST(SplitBuffer_ByteBuffer_Many48b) GPU_TEST(SplitBuffer_ByteBuffer_Many96b) { +#if FALCOR_LINUX + if (ctx.getDevice()->getType() == Device::Type::Vulkan) + { + ctx.skip("Skipping on Linux/Vulkan due intermittent SIGKILL failures."); + return; + } +#endif + static size_t k2GB = UINT64_C(1) << UINT64_C(31); static size_t k4GB = UINT64_C(1) << UINT64_C(32); @@ -530,6 +578,14 @@ GPU_TEST(SplitBuffer_ByteBuffer_Many96b) GPU_TEST(SplitBuffer_StructuredBuffer_Many4B) { +#if FALCOR_LINUX + if (ctx.getDevice()->getType() == Device::Type::Vulkan) + { + ctx.skip("Skipping on Linux/Vulkan due intermittent SIGKILL failures."); + return; + } +#endif + static size_t k2GB = UINT64_C(1) << UINT64_C(31); static size_t k4GB = UINT64_C(1) << UINT64_C(32); @@ -592,6 +648,14 @@ GPU_TEST(SplitBuffer_StructuredBuffer_Many4B) GPU_TEST(SplitBuffer_StructuredBuffer_Many32B) { +#if FALCOR_LINUX + if (ctx.getDevice()->getType() == Device::Type::Vulkan) + { + ctx.skip("Skipping on Linux/Vulkan due intermittent SIGKILL failures."); + return; + } +#endif + static size_t k2GB = UINT64_C(1) << UINT64_C(31); static size_t k4GB = UINT64_C(1) << UINT64_C(32); diff --git a/Source/plugins/importers/AssimpImporter/AssimpImporter.cpp b/Source/plugins/importers/AssimpImporter/AssimpImporter.cpp index 1cf165eac9..f06cb8e068 100644 --- a/Source/plugins/importers/AssimpImporter/AssimpImporter.cpp +++ b/Source/plugins/importers/AssimpImporter/AssimpImporter.cpp @@ -827,7 +827,8 @@ ref createMaterial( ImporterData& data, const aiMaterial* pAiMaterial, const std::filesystem::path& searchPath, - ImportMode importMode + ImportMode importMode, + const std::map& materialToShortName ) { aiString name; @@ -835,6 +836,36 @@ ref createMaterial( // Parse the name std::string nameStr = std::string(name.C_Str()); + // Resolve a replacement material by name. If the mapping cannot be resolved, + // report the problem and fall back to importing the Assimp material. + if (!materialToShortName.empty() && !nameStr.empty()) + { + if (auto it = materialToShortName.find(nameStr); it != materialToShortName.end()) + { + const std::string& replacementName = it->second; + if (replacementName.empty()) + { + logWarning( + "Empty replacement material name for source material '{}'. Using the Assimp material instead.", nameStr + ); + } + else + { + ref pReplacement = data.builder.getMaterial(replacementName); + if (pReplacement) + { + return pReplacement; + } + + logError( + "Replacement material '{}' for source material '{}' was not found. Using the Assimp material instead.", + replacementName, + nameStr + ); + } + } + } + if (nameStr.empty()) { logWarning("AssimpImporter: Material with no name found -> renaming to 'unnamed'."); @@ -970,12 +1001,17 @@ ref createMaterial( return pMaterial; } -void createAllMaterials(ImporterData& data, const std::filesystem::path& searchPath, ImportMode importMode) +void createAllMaterials( + ImporterData& data, + const std::filesystem::path& searchPath, + ImportMode importMode, + const std::map& materialToShortName +) { for (uint32_t i = 0; i < data.pScene->mNumMaterials; i++) { const aiMaterial* pAiMaterial = data.pScene->mMaterials[i]; - data.materialMap[i] = createMaterial(data, pAiMaterial, searchPath, importMode); + data.materialMap[i] = createMaterial(data, pAiMaterial, searchPath, importMode, materialToShortName); } } @@ -1106,7 +1142,13 @@ void dumpAssimpData(ImporterData& data) logInfo(out); } -void importInternal(const void* buffer, size_t byteSize, const std::filesystem::path& path, SceneBuilder& builder) +void importInternal( + const void* buffer, + size_t byteSize, + const std::filesystem::path& path, + SceneBuilder& builder, + const std::map& materialToShortName +) { TimeReport timeReport; @@ -1168,7 +1210,7 @@ void importInternal(const void* buffer, size_t byteSize, const std::filesystem:: // dumpAssimpData(data); - createAllMaterials(data, searchPath, importMode); + createAllMaterials(data, searchPath, importMode, materialToShortName); timeReport.measure("Creating materials"); createSceneGraph(data); @@ -1203,7 +1245,7 @@ void AssimpImporter::importScene( const std::map& materialToShortName ) { - importInternal(nullptr, 0, path, builder); + importInternal(nullptr, 0, path, builder, materialToShortName); } void AssimpImporter::importSceneFromMemory( @@ -1214,7 +1256,7 @@ void AssimpImporter::importSceneFromMemory( const std::map& materialToShortName ) { - importInternal(buffer, byteSize, {}, builder); + importInternal(buffer, byteSize, {}, builder, materialToShortName); } extern "C" FALCOR_API_EXPORT void registerPlugin(Falcor::PluginRegistry& registry) diff --git a/Source/plugins/importers/MitsubaImporter/Parser.h b/Source/plugins/importers/MitsubaImporter/Parser.h index 2bdfd67dd8..fca7c2ff14 100644 --- a/Source/plugins/importers/MitsubaImporter/Parser.h +++ b/Source/plugins/importers/MitsubaImporter/Parser.h @@ -275,7 +275,7 @@ struct Version Version(const char* value) { - if (std::sscanf(value, "%lu.%lu.%lu", &major, &minor, &patch) != 3) + if (std::sscanf(value, "%u.%u.%u", &major, &minor, &patch) != 3) { FALCOR_THROW("Version string must have x.x.x format."); } diff --git a/Source/plugins/importers/PBRTImporter/CMakeLists.txt b/Source/plugins/importers/PBRTImporter/CMakeLists.txt index f671be31b6..8f9974924d 100644 --- a/Source/plugins/importers/PBRTImporter/CMakeLists.txt +++ b/Source/plugins/importers/PBRTImporter/CMakeLists.txt @@ -14,6 +14,9 @@ target_sources(PBRTImporter PRIVATE Parser.h PBRTImporter.cpp PBRTImporter.h + TextureAlphaMerge.3d.slang + TextureAlphaMerge.cpp + TextureAlphaMerge.h Types.h ) diff --git a/Source/plugins/importers/PBRTImporter/PBRTImporter.cpp b/Source/plugins/importers/PBRTImporter/PBRTImporter.cpp index 8681a4bac1..9d243e6b90 100644 --- a/Source/plugins/importers/PBRTImporter/PBRTImporter.cpp +++ b/Source/plugins/importers/PBRTImporter/PBRTImporter.cpp @@ -64,6 +64,7 @@ #include "Helpers.h" #include "LoopSubdivide.h" #include "EnvMapConverter.h" +#include "TextureAlphaMerge.h" #include "Core/Error.h" #include "Core/API/Device.h" #include "Utils/Settings/Settings.h" @@ -211,6 +212,11 @@ struct BuilderContext { BasicScene& scene; SceneBuilder& builder; + TextureAlphaMerge& alphaMerge; + // Maps from material and alpha texture to a material with the alpha already baked in + std::map, ref>, ref> materialAlpha2Material; + // Maps from RGB + Alpha textures to a merged RGBA texture + std::map, ref>, ref> rgbAlpha2rgba; std::map floatTextures; std::map spectrumTextures; @@ -586,6 +592,69 @@ float3 getConductorSpecularAlbedo( return fresnelDieletricConductor(eta, k, 1.f); } +/// Combine the RGB from rgbTexture with alphaTexture to create RGBA. +/// If there is no rgbTexture, use the rgbColor as a constant. +Falcor::ref addAlphaTexture(BuilderContext& ctx, Falcor::ref rgbTexture, Falcor::ref alphaTexture) +{ + FALCOR_CHECK(rgbTexture, "Solid color fallback not yet implemented."); + + if (!alphaTexture) + return rgbTexture; + + if (auto it = ctx.rgbAlpha2rgba.find({rgbTexture, alphaTexture}); it != ctx.rgbAlpha2rgba.end()) + return it->second; + + auto result = ctx.alphaMerge.merge(rgbTexture, alphaTexture); + ctx.rgbAlpha2rgba[{rgbTexture, alphaTexture}] = result; + return result; +} + +Falcor::ref addAlphaToMaterial( + BuilderContext& ctx, + const ShapeSceneEntity& entity, + Falcor::ref pMaterial, + const ParameterDictionary& params +) +{ + std::optional alpha = getFloatTextureOrNull(ctx, params, "alpha"); + // No alpha to add, we just use the same material. + if (!alpha) + return pMaterial; + + // Cannot add, only standard material supports alpha for now. + auto standardMaterial = dynamic_ref_cast(pMaterial); + if (!standardMaterial) + return pMaterial; + + ref alphaTexture; + if (const auto* pAlpha = std::get_if>(&alpha->texture)) + alphaTexture = *pAlpha; + + if (!alphaTexture) + { + logWarning(entity.loc, "Alpha is not actually a texture, we do not have support for baking solid alpha"); + return pMaterial; + } + + if (auto it = ctx.materialAlpha2Material.find({pMaterial, alphaTexture}); it != ctx.materialAlpha2Material.end()) + return it->second; + + ref rgbTexture = standardMaterial->getBaseColorTexture(); + if (!rgbTexture) + { + logWarning(entity.loc, "RGB is not actually a texture, we do not have support for baking solid RGB"); + return pMaterial; + } + + ref rgbaTexture = addAlphaTexture(ctx, rgbTexture, alphaTexture); + + ref clonedMaterial(new StandardMaterial(*standardMaterial)); + clonedMaterial->setBaseColorTexture(rgbaTexture); + ctx.materialAlpha2Material[{pMaterial, alphaTexture}] = clonedMaterial; + + return clonedMaterial; +} + Camera createCamera(BuilderContext& ctx, const CameraSceneEntity& entity) { auto warnUnsupported = [&]() { warnUnsupportedType(entity.loc, "Camera", entity.name); }; @@ -723,15 +792,26 @@ Light createLight(BuilderContext& ctx, const LightSceneEntity& entity) else if (!filename.empty()) { auto path = ctx.resolver(filename); - auto pOctTexture = Falcor::Texture::createFromFile(ctx.builder.getDevice(), path, false, false); + auto pEnvTexture = Falcor::Texture::createFromFile(ctx.builder.getDevice(), path, false, false); + bool isOctTexture = pEnvTexture->getWidth() == pEnvTexture->getHeight(); // TODO: Use equal-area octahedral parametrization when env map supports it. logWarning( entity.loc, "Environment map is converted from equal-area octahedral to lat-long parametrization. Exact results cannot be expected." ); - EnvMapConverter envMapConverter(ctx.builder.getDevice()); - auto pLatLongTexture = envMapConverter.convertEqualAreaOctToLatLong(ctx.builder.getDevice()->getRenderContext(), pOctTexture); - auto pEnvMap = Falcor::EnvMap::create(ctx.builder.getDevice(), pLatLongTexture); + + ref pEnvMap; + if (isOctTexture) + { + EnvMapConverter envMapConverter(ctx.builder.getDevice()); + auto pLatLongTexture = envMapConverter.convertEqualAreaOctToLatLong(ctx.builder.getDevice()->getRenderContext(), pEnvTexture); + pEnvMap = Falcor::EnvMap::create(ctx.builder.getDevice(), pLatLongTexture); + } + else + { + pEnvMap = Falcor::EnvMap::create(ctx.builder.getDevice(), pEnvTexture); + } + pEnvMap->setIntensity(scale); float3 rotation; @@ -1422,7 +1502,9 @@ Shape createShape(BuilderContext& ctx, const ShapeSceneEntity& entity) const auto& type = entity.name; const auto& params = entity.params; - warnUnsupportedParameters(params, {"alpha"}); + // We support alpha only when StandardMaterial is used + if (ctx.usePBRTMaterials) + warnUnsupportedParameters(params, {"alpha"}); Shape shape; @@ -1640,7 +1722,7 @@ Shape createShape(BuilderContext& ctx, const ShapeSceneEntity& entity) shape.pTriangleMesh->setFrontFaceCW(!shape.pTriangleMesh->getFrontFaceCW()); // Get the material. - shape.pMaterial = ctx.getMaterial(entity.materialRef); + shape.pMaterial = addAlphaToMaterial(ctx, entity, ctx.getMaterial(entity.materialRef), params); // Create area light. if (entity.lightIndex != -1) @@ -1928,7 +2010,9 @@ void PBRTImporter::importScene( pbrt::parseFile(pbrtBuilder, path); timeReport.measure("Parsing pbrt scene"); - pbrt::BuilderContext ctx{pbrtScene, builder}; + TextureAlphaMerge textureAlphaMerge(builder.getDevice().get()); + + pbrt::BuilderContext ctx{pbrtScene, builder, textureAlphaMerge}; ctx.usePBRTMaterials = builder.getSettings().getOption("PBRTImporter:usePBRTMaterials", false); pbrt::buildScene(ctx); timeReport.measure("Building pbrt scene"); diff --git a/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.3d.slang b/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.3d.slang new file mode 100644 index 0000000000..36f335b1d7 --- /dev/null +++ b/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.3d.slang @@ -0,0 +1,60 @@ +/*************************************************************************** + # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ + +struct Config +{ + Texture2D rgbTexture; + Texture2D alphaTexture; + int alphaIndex; + + SamplerState sampler; +}; + +ParameterBlock gConfig; + +struct VsOut +{ + float2 texC : TEXCOORD; + float4 posH : SV_POSITION; +}; + +VsOut vsMain(float4 posS: POSITION, float2 texC: TEXCOORD) +{ + VsOut vOut; + vOut.texC = texC; + vOut.posH = posS; + return vOut; +} + +float4 psMain(float2 texC: TEXCOORD) : SV_TARGET +{ + float4 srcRGB = gConfig.rgbTexture.Sample(gConfig.sampler, texC); + float4 srcA = gConfig.alphaTexture.Sample(gConfig.sampler, texC); + + return float4(srcRGB.xyz, srcA[gConfig.alphaIndex]); +} diff --git a/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.cpp b/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.cpp new file mode 100644 index 0000000000..a850d8a4d8 --- /dev/null +++ b/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.cpp @@ -0,0 +1,111 @@ +/*************************************************************************** + # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "TextureAlphaMerge.h" +#include "Core/Error.h" +#include "Core/API/Device.h" +#include "Core/Program/Program.h" +#include "Core/Pass/FullScreenPass.h" + +namespace Falcor +{ + +TextureAlphaMerge::TextureAlphaMerge(Device* pDevice) : mpDevice(pDevice) +{ + FALCOR_ASSERT(pDevice); + + // Init the merge data. + ProgramDesc d; + d.addShaderLibrary("Plugins/importers/PBRTImporter/TextureAlphaMerge.3d.slang").vsEntry("vsMain").psEntry("psMain"); + mpPass = FullScreenPass::create(ref(pDevice), d); + mpFbo = Fbo::create(ref(pDevice)); + FALCOR_ASSERT(mpPass && mpFbo); + + Sampler::Desc desc; + desc.setAddressingMode(TextureAddressingMode::Clamp, TextureAddressingMode::Clamp, TextureAddressingMode::Clamp); + desc.setReductionMode(TextureReductionMode::Standard); + desc.setFilterMode(TextureFilteringMode::Linear, TextureFilteringMode::Linear, TextureFilteringMode::Point); + mpLinearSampler = pDevice->createSampler(desc); +} + +ref TextureAlphaMerge::merge(ref pRgbTexture, ref pAlphaTexture) +{ + int alphaIndex = getFormatChannelCount(pAlphaTexture->getFormat()) - 1; + + auto rgbSRV = pRgbTexture->getSRV(); + auto alphaSRV = pAlphaTexture->getSRV(); + + // Clamp rectangles to the dimensions of the source/dest views. + const uint32_t rgbMipLevel = rgbSRV->getViewInfo().mostDetailedMip; + const uint32_t alphaMipLevel = alphaSRV->getViewInfo().mostDetailedMip; + const uint2 rgbSize(pRgbTexture->getWidth(rgbMipLevel), pRgbTexture->getHeight(rgbMipLevel)); + const uint2 alphaSize(pAlphaTexture->getWidth(alphaMipLevel), pAlphaTexture->getHeight(alphaMipLevel)); + + const uint2 resultSize = max(rgbSize, alphaSize); + const GraphicsState::Viewport dstViewport(0.0f, 0.0f, (float)resultSize.x, (float)resultSize.y, 0.0f, 1.0f); + + ResourceFormat resultFormat = pRgbTexture->getFormat(); + if (!doesFormatHaveAlpha(resultFormat)) + { + switch (resultFormat) + { + case ResourceFormat::BGRX8Unorm: + resultFormat = ResourceFormat::BGRA8Unorm; + break; + case ResourceFormat::BGRX8UnormSrgb: + resultFormat = ResourceFormat::BGRA8UnormSrgb; + break; + /// We just convert everything else into the most commonly used format + default: + resultFormat = ResourceFormat::BGRA8UnormSrgb; + } + } + + ResourceBindFlags bf = pRgbTexture->getBindFlags() | ResourceBindFlags::RenderTarget; + ref pResult = mpDevice->createTexture2D(resultSize.x, resultSize.y, resultFormat, 1, Resource::kMaxPossible, nullptr, bf); + auto resultRTV = pResult->getRTV(); + mpFbo->attachColorTarget( + pResult, 0, resultRTV->getViewInfo().mostDetailedMip, resultRTV->getViewInfo().firstArraySlice, resultRTV->getViewInfo().arraySize + ); + + auto config = mpPass->getRootVar()["gConfig"]; + config["rgbTexture"] = pRgbTexture; + config["alphaTexture"] = pAlphaTexture; + config["alphaIndex"] = alphaIndex; + config["sampler"] = mpLinearSampler; + mpPass->getState()->setViewport(0, dstViewport); + mpPass->execute(mpDevice->getRenderContext(), mpFbo, false); + + // // Release the resources we bound + mpFbo->attachColorTarget(nullptr, 0); + + pResult->generateMips(mpDevice->getRenderContext()); + + return pResult; +} + +} // namespace Falcor diff --git a/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.h b/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.h new file mode 100644 index 0000000000..652badbd85 --- /dev/null +++ b/Source/plugins/importers/PBRTImporter/TextureAlphaMerge.h @@ -0,0 +1,54 @@ +/*************************************************************************** + # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#pragma once +#include "Core/API/Sampler.h" +#include "Core/API/FBO.h" +#include "Core/API/ParameterBlock.h" +#include "Utils/Math/Vector.h" +#include + +namespace Falcor +{ +class Device; +class FullScreenPass; + +class TextureAlphaMerge +{ +public: + TextureAlphaMerge(Device* pDevice); + + ref merge(ref pRgbTexture, ref pAlphaTexture); + +private: + Device* mpDevice; + ref mpPass; + ref mpFbo; + + ref mpLinearSampler; +}; +} // namespace Falcor diff --git a/build_scripts/deploycommon.bat b/build_scripts/deploycommon.bat index 6062a4b7fe..c9ee4cbb00 100644 --- a/build_scripts/deploycommon.bat +++ b/build_scripts/deploycommon.bat @@ -29,14 +29,17 @@ robocopy %ExtDir%\python\ %OutDir% python*.dll /r:0 >nul robocopy %ExtDir%\python %OutDir%\pythondist /E /r:0 >nul robocopy %SlangDir%\bin %OutDir% *.dll /r:0 >nul robocopy %ExtDir%\pix\bin\x64 %OutDir% WinPixEventRuntime.dll /r:0 >nul -robocopy %ExtDir%\dxcompiler\bin\x64 %OutDir% dxil.dll /r:0 >nul -robocopy %ExtDir%\dxcompiler\bin\x64 %OutDir% dxcompiler.dll /r:0 >nul robocopy %ExtDir%\nvtt\ %OutDir% cudart64_110.dll /r:0 >nul robocopy %ExtDir%\nvtt\ %OutDir% nvtt30106.dll /r:0 >nul -robocopy %ExtDir%\cuda\bin\ %OutDir% cudart*.dll /r:0 >nul -robocopy %ExtDir%\cuda\bin\ %OutDir% nvrtc*.dll /r:0 >nul -robocopy %ExtDir%\cuda\bin\ %OutDir% cublas*.dll /r:0 >nul -robocopy %ExtDir%\cuda\bin\ %OutDir% curand*.dll /r:0 >nul + +rem Copy CUDA +set CUDADir=%ExtDir%\cuda +if exist %CUDADir% ( + robocopy %CUDADir%\bin\ %OutDir% cudart*.dll /r:0 >nul + robocopy %CUDADir%\bin\ %OutDir% nvrtc*.dll /r:0 >nul + robocopy %CUDADir%\bin\ %OutDir% cublas*.dll /r:0 >nul + robocopy %CUDADir%\bin\ %OutDir% curand*.dll /r:0 >nul +) rem Copy Aftermath set AftermathDir=%ExtDir%\aftermath @@ -45,16 +48,6 @@ if exist %AftermathDir% ( copy /y %AftermathDir%\lib\x64\llvm_7_0_1.dll %OutDir% >nul ) -rem Copy NVAPI -set NvApiDir=%ExtDir%\nvapi -set NvApiTargetDir=%OutDir%\shaders\nvapi -if exist %NvApiDir% ( - if not exist %NvApiTargetDir% mkdir %NvApiTargetDir% >nul - copy /y %NvApiDir%\nvHLSLExtns.h %NvApiTargetDir% >nul - copy /y %NvApiDir%\nvHLSLExtnsInternal.h %NvApiTargetDir% >nul - copy /y %NvApiDir%\nvShaderExtnEnums.h %NvApiTargetDir% >nul -) - rem Copy NRD set NrdDir=%ExtDir%\nrd set NrdTargetDir=%OutDir%\shaders\nrd\Shaders @@ -81,15 +74,6 @@ if exist %RtxdiSDKDir% ( copy /y %RtxdiSDKDir%\RtxdiTypes.h %RtxdiSDKTargetDir% >nul ) -rem Copy Agility SDK Runtime -set AgilitySDKDir=%ExtDir%\agility-sdk -set AgilitySDKTargetDir=%OutDir%\D3D12 -if exist %AgilitySDKDir% ( - if not exist %AgilitySDKTargetDir% mkdir %AgilitySDKTargetDir% >nul - copy /y %AgilitySDKDir%\build\native\bin\x64\D3D12Core.dll %AgilitySDKTargetDir% >nul - copy /y %AgilitySDKDir%\build\native\bin\x64\d3d12SDKLayers.dll %AgilitySDKTargetDir% >nul -) - rem Copy NanoVDB set NanoVDBDir=%ExtDir%\nanovdb set NanoVDBTargetDir=%OutDir%\shaders\NanoVDB diff --git a/cmake/FetchPackage.cmake b/cmake/FetchPackage.cmake new file mode 100644 index 0000000000..5820d6e098 --- /dev/null +++ b/cmake/FetchPackage.cmake @@ -0,0 +1,18 @@ +cmake_policy(SET CMP0135 NEW) + +include(FetchContent) + +# Use FetchContent to download and populate a package without calling add_subdirectory(). +# This is used for downloading prebuilt external binaries. +macro(FetchPackage name) + cmake_parse_arguments(FETCH "" "URL" "" ${ARGN}) + FetchContent_Declare( + ${name} + URL ${FETCH_URL} + ) + FetchContent_GetProperties(${name}) + if(NOT ${name}_POPULATED) + message(STATUS "Fetching ${name} ...") + FetchContent_MakeAvailable(${name}) + endif() +endmacro() diff --git a/data/tests/BC1Unorm-ref.exr b/data/tests/BC1Unorm-ref.exr new file mode 100644 index 0000000000..f90db29b9f Binary files /dev/null and b/data/tests/BC1Unorm-ref.exr differ diff --git a/data/tests/BC1Unorm-ref.png b/data/tests/BC1Unorm-ref.png deleted file mode 100644 index c49390392a..0000000000 Binary files a/data/tests/BC1Unorm-ref.png and /dev/null differ diff --git a/data/tests/BC1UnormSrgb-ref.exr b/data/tests/BC1UnormSrgb-ref.exr new file mode 100644 index 0000000000..c957570c0f Binary files /dev/null and b/data/tests/BC1UnormSrgb-ref.exr differ diff --git a/data/tests/BC1UnormSrgb-ref.png b/data/tests/BC1UnormSrgb-ref.png deleted file mode 100644 index 6d25b18a12..0000000000 Binary files a/data/tests/BC1UnormSrgb-ref.png and /dev/null differ diff --git a/data/tests/BC2Unorm-ref.exr b/data/tests/BC2Unorm-ref.exr new file mode 100644 index 0000000000..f90db29b9f Binary files /dev/null and b/data/tests/BC2Unorm-ref.exr differ diff --git a/data/tests/BC2Unorm-ref.png b/data/tests/BC2Unorm-ref.png deleted file mode 100644 index c49390392a..0000000000 Binary files a/data/tests/BC2Unorm-ref.png and /dev/null differ diff --git a/data/tests/BC2UnormSrgb-ref.exr b/data/tests/BC2UnormSrgb-ref.exr new file mode 100644 index 0000000000..c957570c0f Binary files /dev/null and b/data/tests/BC2UnormSrgb-ref.exr differ diff --git a/data/tests/BC2UnormSrgb-ref.png b/data/tests/BC2UnormSrgb-ref.png deleted file mode 100644 index 6d25b18a12..0000000000 Binary files a/data/tests/BC2UnormSrgb-ref.png and /dev/null differ diff --git a/data/tests/BC2UnormSrgbTiny-ref.exr b/data/tests/BC2UnormSrgbTiny-ref.exr new file mode 100644 index 0000000000..62cc63bbd8 Binary files /dev/null and b/data/tests/BC2UnormSrgbTiny-ref.exr differ diff --git a/data/tests/BC2UnormSrgbTiny-ref.png b/data/tests/BC2UnormSrgbTiny-ref.png deleted file mode 100644 index 08c205bf58..0000000000 Binary files a/data/tests/BC2UnormSrgbTiny-ref.png and /dev/null differ diff --git a/data/tests/BC3Unorm-ref.exr b/data/tests/BC3Unorm-ref.exr new file mode 100644 index 0000000000..27c8832944 Binary files /dev/null and b/data/tests/BC3Unorm-ref.exr differ diff --git a/data/tests/BC3Unorm-ref.png b/data/tests/BC3Unorm-ref.png deleted file mode 100644 index 3b82e60257..0000000000 Binary files a/data/tests/BC3Unorm-ref.png and /dev/null differ diff --git a/data/tests/BC3UnormAlpha-ref.exr b/data/tests/BC3UnormAlpha-ref.exr new file mode 100644 index 0000000000..1c6d4cc8bb Binary files /dev/null and b/data/tests/BC3UnormAlpha-ref.exr differ diff --git a/data/tests/BC3UnormAlpha-ref.png b/data/tests/BC3UnormAlpha-ref.png deleted file mode 100644 index 8c629149d1..0000000000 Binary files a/data/tests/BC3UnormAlpha-ref.png and /dev/null differ diff --git a/data/tests/BC3UnormAlphaTiny-ref.exr b/data/tests/BC3UnormAlphaTiny-ref.exr new file mode 100644 index 0000000000..c806ed227c Binary files /dev/null and b/data/tests/BC3UnormAlphaTiny-ref.exr differ diff --git a/data/tests/BC3UnormAlphaTiny-ref.png b/data/tests/BC3UnormAlphaTiny-ref.png deleted file mode 100644 index c430173346..0000000000 Binary files a/data/tests/BC3UnormAlphaTiny-ref.png and /dev/null differ diff --git a/data/tests/BC3UnormSrgb-ref.exr b/data/tests/BC3UnormSrgb-ref.exr new file mode 100644 index 0000000000..00abb22603 Binary files /dev/null and b/data/tests/BC3UnormSrgb-ref.exr differ diff --git a/data/tests/BC3UnormSrgb-ref.png b/data/tests/BC3UnormSrgb-ref.png deleted file mode 100644 index e5e824d824..0000000000 Binary files a/data/tests/BC3UnormSrgb-ref.png and /dev/null differ diff --git a/data/tests/BC3UnormSrgbOdd-ref.exr b/data/tests/BC3UnormSrgbOdd-ref.exr new file mode 100644 index 0000000000..df555a5fbe Binary files /dev/null and b/data/tests/BC3UnormSrgbOdd-ref.exr differ diff --git a/data/tests/BC3UnormSrgbOdd-ref.png b/data/tests/BC3UnormSrgbOdd-ref.png deleted file mode 100644 index cb84b20c85..0000000000 Binary files a/data/tests/BC3UnormSrgbOdd-ref.png and /dev/null differ diff --git a/data/tests/BC3UnormSrgbTiny-ref.exr b/data/tests/BC3UnormSrgbTiny-ref.exr new file mode 100644 index 0000000000..62cc63bbd8 Binary files /dev/null and b/data/tests/BC3UnormSrgbTiny-ref.exr differ diff --git a/data/tests/BC3UnormSrgbTiny-ref.png b/data/tests/BC3UnormSrgbTiny-ref.png deleted file mode 100644 index 08c205bf58..0000000000 Binary files a/data/tests/BC3UnormSrgbTiny-ref.png and /dev/null differ diff --git a/data/tests/BC4Unorm-ref.exr b/data/tests/BC4Unorm-ref.exr new file mode 100644 index 0000000000..b5248c9a22 Binary files /dev/null and b/data/tests/BC4Unorm-ref.exr differ diff --git a/data/tests/BC4Unorm-ref.png b/data/tests/BC4Unorm-ref.png deleted file mode 100644 index 5781999163..0000000000 Binary files a/data/tests/BC4Unorm-ref.png and /dev/null differ diff --git a/data/tests/BC5Unorm-ref.exr b/data/tests/BC5Unorm-ref.exr new file mode 100644 index 0000000000..232b7f5539 Binary files /dev/null and b/data/tests/BC5Unorm-ref.exr differ diff --git a/data/tests/BC5Unorm-ref.png b/data/tests/BC5Unorm-ref.png deleted file mode 100644 index 2b397fc073..0000000000 Binary files a/data/tests/BC5Unorm-ref.png and /dev/null differ diff --git a/data/tests/BC5UnormTiny-ref.exr b/data/tests/BC5UnormTiny-ref.exr new file mode 100644 index 0000000000..9f77fe01ae Binary files /dev/null and b/data/tests/BC5UnormTiny-ref.exr differ diff --git a/data/tests/BC5UnormTiny-ref.png b/data/tests/BC5UnormTiny-ref.png deleted file mode 100644 index d0b8be2e51..0000000000 Binary files a/data/tests/BC5UnormTiny-ref.png and /dev/null differ diff --git a/data/tests/BC6HU16-ref.exr b/data/tests/BC6HU16-ref.exr new file mode 100644 index 0000000000..6e5a248fa7 Binary files /dev/null and b/data/tests/BC6HU16-ref.exr differ diff --git a/data/tests/BC6HU16-ref.png b/data/tests/BC6HU16-ref.png deleted file mode 100644 index cc35924e0d..0000000000 Binary files a/data/tests/BC6HU16-ref.png and /dev/null differ diff --git a/data/tests/BC7Unorm-ref.exr b/data/tests/BC7Unorm-ref.exr new file mode 100644 index 0000000000..a5bd726e37 Binary files /dev/null and b/data/tests/BC7Unorm-ref.exr differ diff --git a/data/tests/BC7Unorm-ref.png b/data/tests/BC7Unorm-ref.png deleted file mode 100644 index 9ce7ac3f79..0000000000 Binary files a/data/tests/BC7Unorm-ref.png and /dev/null differ diff --git a/data/tests/BC7UnormOdd-ref.exr b/data/tests/BC7UnormOdd-ref.exr new file mode 100644 index 0000000000..05e9f54b94 Binary files /dev/null and b/data/tests/BC7UnormOdd-ref.exr differ diff --git a/data/tests/BC7UnormOdd-ref.png b/data/tests/BC7UnormOdd-ref.png deleted file mode 100644 index 0076f74844..0000000000 Binary files a/data/tests/BC7UnormOdd-ref.png and /dev/null differ diff --git a/data/tests/BC7UnormSrgb-ref.exr b/data/tests/BC7UnormSrgb-ref.exr new file mode 100644 index 0000000000..daf9394d55 Binary files /dev/null and b/data/tests/BC7UnormSrgb-ref.exr differ diff --git a/data/tests/BC7UnormSrgb-ref.png b/data/tests/BC7UnormSrgb-ref.png deleted file mode 100644 index 86447be1bf..0000000000 Binary files a/data/tests/BC7UnormSrgb-ref.png and /dev/null differ diff --git a/data/tests/BC7UnormTiny-ref.exr b/data/tests/BC7UnormTiny-ref.exr new file mode 100644 index 0000000000..e516ed93da Binary files /dev/null and b/data/tests/BC7UnormTiny-ref.exr differ diff --git a/data/tests/BC7UnormTiny-ref.png b/data/tests/BC7UnormTiny-ref.png deleted file mode 100644 index 6d223a4a84..0000000000 Binary files a/data/tests/BC7UnormTiny-ref.png and /dev/null differ diff --git a/dependencies.xml b/dependencies.xml index 9ed0665b71..86a6b118b4 100644 --- a/dependencies.xml +++ b/dependencies.xml @@ -22,7 +22,7 @@ - + @@ -33,12 +33,6 @@ - - - - - - @@ -56,7 +50,4 @@ - - - diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 96ebc6a4d2..dae9090e8c 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -2,6 +2,8 @@ # System dependencies # ----------------------------------------------------------------------------- +include(FetchPackage) + # d3d12 add_library(d3d12 INTERFACE) target_link_libraries(d3d12 INTERFACE dxgi.lib d3d12.lib) @@ -101,11 +103,11 @@ if(FALCOR_WINDOWS) ${FALCOR_DEPS_DIR}/include/Imath ) target_link_directories(OpenEXR INTERFACE - $<$:${FALCOR_DEPS_DIR}/lib> + $<$:${FALCOR_DEPS_DIR}/lib> $<$:${FALCOR_DEPS_DIR}/debug/lib> ) target_link_libraries(OpenEXR INTERFACE - $<$:OpenEXR-3_1.lib Imath-3_1.lib IlmThread-3_1.lib Iex-3_1.lib> + $<$:OpenEXR-3_1.lib Imath-3_1.lib IlmThread-3_1.lib Iex-3_1.lib> $<$:OpenEXR-3_1_d.lib Imath-3_1_d.lib IlmThread-3_1_d.lib Iex-3_1_d.lib> ) elseif(FALCOR_LINUX) @@ -115,11 +117,11 @@ elseif(FALCOR_LINUX) ${FALCOR_DEPS_DIR}/include/Imath ) target_link_directories(OpenEXR INTERFACE - $<$:${FALCOR_DEPS_DIR}/lib> + $<$:${FALCOR_DEPS_DIR}/lib> $<$:${FALCOR_DEPS_DIR}/debug/lib> ) target_link_libraries(OpenEXR INTERFACE - $<$:OpenEXR-3_1.so Imath-3_1.so IlmThread-3_1.so Iex-3_1.so> + $<$:OpenEXR-3_1.so Imath-3_1.so IlmThread-3_1.so Iex-3_1.so> $<$:OpenEXR-3_1_d.so Imath-3_1_d.so IlmThread-3_1_d.so Iex-3_1_d.so> ) endif() @@ -131,11 +133,13 @@ if(FALCOR_WINDOWS) INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_IMPLIB_RELEASE ${FALCOR_DEPS_DIR}/lib/tbb.lib IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/bin/tbb.dll + IMPORTED_IMPLIB_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/tbb.lib + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/bin/tbb.dll IMPORTED_IMPLIB_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/tbb_debug.lib IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/bin/tbb_debug.dll ) target_compile_definitions(tbb INTERFACE - $<$:TBB_USE_DEBUG=0;__TBB_NO_IMPLICIT_LINKAGE=1> + $<$:TBB_USE_DEBUG=0;__TBB_NO_IMPLICIT_LINKAGE=1> $<$:TBB_USE_DEBUG=1;__TBB_NO_IMPLICIT_LINKAGE=1> ) elseif(FALCOR_LINUX) @@ -143,10 +147,11 @@ elseif(FALCOR_LINUX) set_target_properties(tbb PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/libtbb.so + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/libtbb.so IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/libtbb_debug.so ) target_compile_definitions(tbb INTERFACE - $<$:TBB_USE_DEBUG=0> + $<$:TBB_USE_DEBUG=0> $<$:TBB_USE_DEBUG=1> ) endif() @@ -158,6 +163,7 @@ if(FALCOR_WINDOWS) set_target_properties(opensubdiv PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/osdCPU.lib + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/osdCPU.lib IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/osdCPU.lib ) elseif(FALCOR_LINUX) @@ -165,6 +171,7 @@ elseif(FALCOR_LINUX) set_target_properties(opensubdiv PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/libosdCPU.a + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/libosdCPU.a IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/libosdCPU.a ) endif() @@ -176,6 +183,8 @@ if(FALCOR_WINDOWS) INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_IMPLIB_RELEASE ${FALCOR_DEPS_DIR}/lib/openvdb.lib IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/bin/openvdb.dll + IMPORTED_IMPLIB_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/openvdb.lib + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/bin/openvdb.dll IMPORTED_IMPLIB_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/openvdb.lib IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/bin/openvdb.dll INTERFACE_LINK_LIBRARIES "tbb;blosc" @@ -185,6 +194,7 @@ elseif(FALCOR_LINUX) set_target_properties(OpenVDB PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/libopenvdb.so + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/libopenvdb.so IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/libopenvdb.so INTERFACE_LINK_LIBRARIES "tbb;blosc" ) @@ -204,6 +214,7 @@ elseif(FALCOR_LINUX) set_target_properties(assimp PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/libassimp.so + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/libassimp.so IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/libassimpd.so ) endif() @@ -215,6 +226,8 @@ if(FALCOR_WINDOWS) INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_IMPLIB_RELEASE ${FALCOR_DEPS_DIR}/lib/FreeImage.lib IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/bin/FreeImage.dll + IMPORTED_IMPLIB_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/FreeImage.lib + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/bin/FreeImage.dll IMPORTED_IMPLIB_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/FreeImaged.lib IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/bin/FreeImaged.dll ) @@ -223,6 +236,7 @@ elseif(FALCOR_LINUX) set_target_properties(FreeImage PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/libFreeImage.so + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/libFreeImage.so IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/libFreeImaged.so ) endif() @@ -234,6 +248,8 @@ if(FALCOR_WINDOWS) INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_IMPLIB_RELEASE ${FALCOR_DEPS_DIR}/lib/hdf5.lib IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/bin/hdf5.dll + IMPORTED_IMPLIB_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/hdf5.lib + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/bin/hdf5.dll IMPORTED_IMPLIB_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/hdf5_D.lib IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/bin/hdf5_D.dll INTERFACE_COMPILE_DEFINITIONS H5_BUILT_AS_DYNAMIC_LIB @@ -243,6 +259,7 @@ elseif(FALCOR_LINUX) set_target_properties(hdf5 PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/libhdf5.so + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/libhdf5.so IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/libhdf5_debug.so INTERFACE_COMPILE_DEFINITIONS H5_BUILT_AS_DYNAMIC_LIB ) @@ -286,6 +303,7 @@ if(FALCOR_WINDOWS) set_target_properties(pugixml PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/pugixml.lib + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/pugixml.lib IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/pugixml.lib ) elseif(FALCOR_LINUX) @@ -293,6 +311,7 @@ elseif(FALCOR_LINUX) set_target_properties(pugixml PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${FALCOR_DEPS_DIR}/include IMPORTED_LOCATION_RELEASE ${FALCOR_DEPS_DIR}/lib/libpugixml.a + IMPORTED_LOCATION_RELWITHDEBINFO ${FALCOR_DEPS_DIR}/lib/libpugixml.a IMPORTED_LOCATION_DEBUG ${FALCOR_DEPS_DIR}/debug/lib/libpugixml.a ) endif() @@ -323,19 +342,6 @@ else() set(FALCOR_HAS_AFTERMATH OFF PARENT_SCOPE) endif() -# nvapi -set(NVAPI_DIR ${PACKMAN_DIR}/nvapi) -if(EXISTS ${NVAPI_DIR}/nvapi.h) - add_library(nvapi STATIC IMPORTED GLOBAL) - set_target_properties(nvapi PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${NVAPI_DIR} - IMPORTED_LOCATION ${NVAPI_DIR}/amd64/nvapi64.lib - ) - set(FALCOR_HAS_NVAPI ON PARENT_SCOPE) -else() - set(FALCOR_HAS_NVAPI OFF PARENT_SCOPE) -endif() - # slang set(FALCOR_LOCAL_SLANG OFF CACHE BOOL "Use a local build of slang instead of the binary package.") @@ -408,11 +414,11 @@ if((EXISTS ${NV_USD_RELEASE_DIR}) AND (EXISTS ${NV_USD_DEBUG_DIR})) if(FALCOR_WINDOWS) add_library(nv-usd INTERFACE) target_include_directories(nv-usd INTERFACE - $<$:${NV_USD_RELEASE_DIR}/include ${NV_USD_RELEASE_DIR}/include/boost-1_76> + $<$:${NV_USD_RELEASE_DIR}/include ${NV_USD_RELEASE_DIR}/include/boost-1_76> $<$:${NV_USD_DEBUG_DIR}/include ${NV_USD_DEBUG_DIR}/include/boost-1_76> ) target_link_directories(nv-usd INTERFACE - $<$:${NV_USD_RELEASE_DIR}/lib ${NV_USD_RELEASE_DIR}/plugin/usd> + $<$:${NV_USD_RELEASE_DIR}/lib ${NV_USD_RELEASE_DIR}/plugin/usd> $<$:${NV_USD_DEBUG_DIR}/lib ${NV_USD_DEBUG_DIR}/plugin/usd> ) target_link_libraries(nv-usd INTERFACE @@ -420,7 +426,7 @@ if((EXISTS ${NV_USD_RELEASE_DIR}) AND (EXISTS ${NV_USD_DEBUG_DIR})) ) target_compile_definitions(nv-usd INTERFACE _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - $<$:TBB_USE_DEBUG=0> + $<$:TBB_USE_DEBUG=0> $<$:TBB_USE_DEBUG=1> ) elseif(FALCOR_LINUX) @@ -428,6 +434,7 @@ if((EXISTS ${NV_USD_RELEASE_DIR}) AND (EXISTS ${NV_USD_DEBUG_DIR})) set_target_properties(nv-usd PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${NV_USD_RELEASE_DIR}/include;${NV_USD_RELEASE_DIR}/include/boost" IMPORTED_LOCATION_RELEASE ${NV_USD_RELEASE_DIR}/lib/libusd_ms.so + IMPORTED_LOCATION_RELWITHDEBINFO ${NV_USD_RELEASE_DIR}/lib/libusd_ms.so IMPORTED_LOCATION_DEBUG ${NV_USD_DEBUG_DIR}/lib/libusd_ms.so INTERFACE_LINK_LIBRARIES "tbb" ) @@ -447,15 +454,6 @@ else() set(FALCOR_HAS_MDL_SDK OFF PARENT_SCOPE) endif() -# dxcompiler -set(DXCOMPILER_DIR ${PACKMAN_DIR}/dxcompiler) -add_library(dxcompiler SHARED IMPORTED GLOBAL) -set_target_properties(dxcompiler PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${DXCOMPILER_DIR}/inc - IMPORTED_IMPLIB ${DXCOMPILER_DIR}/lib/x64/dxcompiler.lib - IMPORTED_LOCATION ${DXCOMPILER_DIR}/bin/x64/dxcompiler.dll -) - # nrd set(NRD_DIR ${PACKMAN_DIR}/nrd) if(EXISTS ${NRD_DIR}/Include/NRD.h) @@ -464,6 +462,8 @@ if(EXISTS ${NRD_DIR}/Include/NRD.h) INTERFACE_INCLUDE_DIRECTORIES ${NRD_DIR}/Include IMPORTED_IMPLIB_RELEASE ${NRD_DIR}/Lib/Release/NRD.lib IMPORTED_LOCATION_RELEASE ${NRD_DIR}/Lib/Release/NRD.dll + IMPORTED_IMPLIB_RELWITHDEBINFO ${NRD_DIR}/Lib/Release/NRD.lib + IMPORTED_LOCATION_RELWITHDEBINFO ${NRD_DIR}/Lib/Release/NRD.dll IMPORTED_IMPLIB_DEBUG ${NRD_DIR}/Lib/Debug/NRD.lib IMPORTED_LOCATION_DEBUG ${NRD_DIR}/Lib/Debug/NRD.dll ) @@ -473,14 +473,16 @@ else() endif() # dlss -set(DLSS_DIR ${PACKMAN_DIR}/dlss) +FetchPackage(dlss URL "https://github.com/NVIDIA/DLSS/archive/refs/tags/v310.1.0.zip") +set(DLSS_DIR ${dlss_SOURCE_DIR}) set(FALCOR_DLSS_DIR ${DLSS_DIR} PARENT_SCOPE) if(EXISTS ${DLSS_DIR}/include/nvsdk_ngx.h) add_library(dlss STATIC IMPORTED GLOBAL) set_target_properties(dlss PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${DLSS_DIR}/include - IMPORTED_LOCATION_RELEASE ${DLSS_DIR}/lib/Windows_x86_64/x86_64/nvsdk_ngx_d.lib - IMPORTED_LOCATION_DEBUG ${DLSS_DIR}/lib/Windows_x86_64/x86_64/nvsdk_ngx_d_dbg.lib + IMPORTED_LOCATION_RELEASE ${DLSS_DIR}/lib/Windows_x86_64/x64/nvsdk_ngx_d.lib + IMPORTED_LOCATION_RELWITHDEBINFO ${DLSS_DIR}/lib/Windows_x86_64/x64/nvsdk_ngx_d.lib + IMPORTED_LOCATION_DEBUG ${DLSS_DIR}/lib/Windows_x86_64/x64/nvsdk_ngx_d_dbg.lib INTERFACE_COMPILE_DEFINITIONS NV_WINDOWS ) set(FALCOR_HAS_DLSS ON PARENT_SCOPE) @@ -516,17 +518,88 @@ elseif(FALCOR_LINUX) ) endif() -# agility-sdk -set(D3D12_AGILITY_SDK_DIR ${PACKMAN_DIR}/agility-sdk) -if(EXISTS ${D3D12_AGILITY_SDK_DIR}/build/native/include/d3d12.h) +# rtxdi +set(RTXDI_DIR ${PACKMAN_DIR}/rtxdi) +add_library(rtxdi INTERFACE) +target_include_directories(rtxdi INTERFACE ${RTXDI_DIR}/rtxdi-sdk/include) + +# ----------------------------------------------------------------------------- +# FetchPackage dependencies +# ----------------------------------------------------------------------------- + +set(FALCOR_COPY_FILES "") + +# Function to copy a file to the binary output directory +macro(copy_file IN_FILE OUT_DIR) + if(EXISTS ${IN_FILE}) + get_filename_component(FILENAME ${IN_FILE} NAME) + set(OUT_FILE_1 "${FALCOR_OUTPUT_DIRECTORY}/${OUT_DIR}/${FILENAME}") + + if (UNIX) + add_custom_command( + OUTPUT ${OUT_FILE_1} DEPENDS ${IN_FILE} ${ARGN} + COMMAND ${CMAKE_COMMAND} -E copy ${IN_FILE} ${OUT_FILE_1} + COMMENT "Copying ${FILENAME}" + ) + list(APPEND FALCOR_COPY_FILES ${OUT_FILE_1}) + else() + # add_custom_command does not support generator expressions in OUTPUT argument + # make a dummy copy and then depend on that + add_custom_command( + OUTPUT ${OUT_FILE_1} DEPENDS ${IN_FILE} ${ARGN} + COMMAND ${CMAKE_COMMAND} -E copy ${IN_FILE} ${OUT_FILE_1} + COMMENT "Copying ${FILENAME}" + ) + list(APPEND FALCOR_COPY_FILES ${OUT_FILE_1}) + endif() + endif() +endmacro() + +# Fetch agility-sdk +if(FALCOR_HAS_D3D12 AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) + set(AGILITY_SDK_VERSION "1.717.0-preview") + FetchPackage(agility_sdk URL "https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/${AGILITY_SDK_VERSION}") + copy_file(${agility_sdk_SOURCE_DIR}/build/native/bin/x64/D3D12Core.dll D3D12) + copy_file(${agility_sdk_SOURCE_DIR}/build/native/bin/x64/d3d12SDKLayers.dll D3D12) add_library(agility-sdk INTERFACE) - target_include_directories(agility-sdk INTERFACE ${D3D12_AGILITY_SDK_DIR}/build/native/include) + target_include_directories(agility-sdk INTERFACE ${agility_sdk_SOURCE_DIR}/build/native/include) set(FALCOR_HAS_D3D12_AGILITY_SDK ON PARENT_SCOPE) else() set(FALCOR_HAS_D3D12_AGILITY_SDK OFF PARENT_SCOPE) endif() -# rtxdi -set(RTXDI_DIR ${PACKMAN_DIR}/rtxdi) -add_library(rtxdi INTERFACE) -target_include_directories(rtxdi INTERFACE ${RTXDI_DIR}/rtxdi-sdk/include) +# Fetch dxcompiler +if(FALCOR_HAS_D3D12 AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) + set(DXC_VERSION "1.8.2505") + set(DXC_URL "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.8.2505/dxc_2025_05_24.zip") + FetchPackage(dxc URL ${DXC_URL}) + copy_file(${dxc_SOURCE_DIR}/bin/x64/dxcompiler.dll .) + copy_file(${dxc_SOURCE_DIR}/bin/x64/dxil.dll .) + + add_library(dxcompiler SHARED IMPORTED GLOBAL) + set_target_properties(dxcompiler PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${dxc_SOURCE_DIR}/inc + IMPORTED_IMPLIB ${dxc_SOURCE_DIR}/lib/x64/dxcompiler.lib + IMPORTED_LOCATION ${dxc_SOURCE_DIR}/bin/x64/dxcompiler.dll + ) +endif() + +# Fetch NVAPI +if(FALCOR_HAS_D3D12 AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) + # Version R580-Developer SDK + FetchPackage(nvapi URL "https://github.com/NVIDIA/nvapi/archive/3d34a4faf095996663321646ebe003539a908f89.zip") + set(NVAPI_DIR ${nvapi_SOURCE_DIR}) + add_library(nvapi STATIC IMPORTED GLOBAL) + set_target_properties(nvapi PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${NVAPI_DIR} + IMPORTED_LOCATION ${NVAPI_DIR}/amd64/nvapi64.lib + ) + copy_file(${nvapi_SOURCE_DIR}/nvHLSLExtns.h shaders/nvapi) + copy_file(${nvapi_SOURCE_DIR}/nvHLSLExtnsInternal.h shaders/nvapi) + copy_file(${nvapi_SOURCE_DIR}/nvShaderExtnEnums.h shaders/nvapi) + set(FALCOR_HAS_NVAPI ON PARENT_SCOPE) +else() + set(FALCOR_HAS_NVAPI OFF PARENT_SCOPE) +endif() + +add_custom_target(falcor-copy-files ALL DEPENDS ${FALCOR_COPY_FILES}) diff --git a/scripts/PathTracer.py b/scripts/PathTracer.py index ee11c8aa24..6110d3b98b 100644 --- a/scripts/PathTracer.py +++ b/scripts/PathTracer.py @@ -2,7 +2,7 @@ def render_graph_PathTracer(): g = RenderGraph("PathTracer") - PathTracer = createPass("PathTracer", {'samplesPerPixel': 1}) + PathTracer = createPass("PathTracer", {'samplesPerPixel': 1, 'tracePassMode': 'MinimalPayload'}) g.addPass(PathTracer, "PathTracer") VBufferRT = createPass("VBufferRT", {'samplePattern': 'Stratified', 'sampleCount': 16, 'useAlphaTest': True}) g.addPass(VBufferRT, "VBufferRT") diff --git a/scripts/PathTracerAreaReSTIR.py b/scripts/PathTracerAreaReSTIR.py new file mode 100644 index 0000000000..e3c4f82bee --- /dev/null +++ b/scripts/PathTracerAreaReSTIR.py @@ -0,0 +1,33 @@ +from falcor import * + +def render_graph_PathTracerAreaReSTIR(): + g = RenderGraph("PathTracerAreaReSTIR") + PathTracer = createPass("PathTracer", { + 'samplesPerPixel': 1, + 'tracePassMode': 'MinimalPayload', + 'useReSTIRPT': True, + 'emissiveSampler': 'Power', + 'maxDiffuseBounces': 10, + 'maxSpecularBounces': 10, + 'useRussianRoulette': True, + 'usePairedNeighbors': True, + 'reSTIRPathTracingOptions': {'useAreaReservoirs': True} + }) + g.addPass(PathTracer, "PathTracer") + VBufferRT = createPass("VBufferRT", {'samplePattern': 'Center', 'sampleCount': 1, 'useAlphaTest': True}) + g.addPass(VBufferRT, "VBufferRT") + AccumulatePass = createPass("AccumulatePass", {'enabled': True, 'precisionMode': 'Single'}) + g.addPass(AccumulatePass, "AccumulatePass") + ToneMapper = createPass("ToneMapper", {'autoExposure': False, 'exposureCompensation': 0.0}) + g.addPass(ToneMapper, "ToneMapper") + g.addEdge("VBufferRT.vbuffer", "PathTracer.vbuffer") + g.addEdge("VBufferRT.viewW", "PathTracer.viewW") + g.addEdge("VBufferRT.mvec", "PathTracer.mvec") + g.addEdge("PathTracer.color", "AccumulatePass.input") + g.addEdge("AccumulatePass.output", "ToneMapper.src") + g.markOutput("ToneMapper.dst") + return g + +PathTracerAreaReSTIR = render_graph_PathTracerAreaReSTIR() +try: m.addGraph(PathTracerAreaReSTIR) +except NameError: None diff --git a/scripts/PathTracerNRD.py b/scripts/PathTracerNRD.py index 434570efca..c0b13f0720 100644 --- a/scripts/PathTracerNRD.py +++ b/scripts/PathTracerNRD.py @@ -4,7 +4,7 @@ def render_graph_PathTracerNRD(): g = RenderGraph("PathTracerNRD") GBufferRT = createPass("GBufferRT", {'samplePattern': 'Halton', 'sampleCount': 32, 'useAlphaTest': True}) g.addPass(GBufferRT, "GBufferRT") - PathTracer = createPass("PathTracer", {'samplesPerPixel': 1, 'maxSurfaceBounces': 10, 'useRussianRoulette': True}) + PathTracer = createPass("PathTracer", {'samplesPerPixel': 1, 'tracePassMode': 'MinimalPayload', 'maxSurfaceBounces': 10, 'useRussianRoulette': True}) g.addPass(PathTracer, "PathTracer") # Reference path passes @@ -35,6 +35,7 @@ def render_graph_PathTracerNRD(): g.addEdge("GBufferRT.vbuffer", "PathTracer.vbuffer") g.addEdge("GBufferRT.viewW", "PathTracer.viewW") + g.addEdge("GBufferRT.mvec", "PathTracer.mvec") # Reference path graph g.addEdge("PathTracer.color", "AccumulatePass.input") diff --git a/scripts/PathTracerReSTIRPT.py b/scripts/PathTracerReSTIRPT.py new file mode 100644 index 0000000000..cc1a88c063 --- /dev/null +++ b/scripts/PathTracerReSTIRPT.py @@ -0,0 +1,33 @@ +from falcor import * + +def render_graph_PathTracerReSTIRPT(): + g = RenderGraph("PathTracerReSTIRPT") + PathTracer = createPass("PathTracer", { + 'samplesPerPixel': 1, + 'tracePassMode': 'MinimalPayload', + 'useReSTIRPT': True, + 'emissiveSampler': 'Power', + 'maxDiffuseBounces': 10, + 'maxSpecularBounces': 10, + 'useRussianRoulette': True, + 'usePairedNeighbors': True, + 'reSTIRPathTracingOptions': {'useAreaReservoirs': False} + }) + g.addPass(PathTracer, "PathTracer") + VBufferRT = createPass("VBufferRT", {'samplePattern': 'Center', 'sampleCount': 1, 'useAlphaTest': True}) + g.addPass(VBufferRT, "VBufferRT") + AccumulatePass = createPass("AccumulatePass", {'enabled': True, 'precisionMode': 'Single'}) + g.addPass(AccumulatePass, "AccumulatePass") + ToneMapper = createPass("ToneMapper", {'autoExposure': False, 'exposureCompensation': 0.0}) + g.addPass(ToneMapper, "ToneMapper") + g.addEdge("VBufferRT.vbuffer", "PathTracer.vbuffer") + g.addEdge("VBufferRT.viewW", "PathTracer.viewW") + g.addEdge("VBufferRT.mvec", "PathTracer.mvec") + g.addEdge("PathTracer.color", "AccumulatePass.input") + g.addEdge("AccumulatePass.output", "ToneMapper.src") + g.markOutput("ToneMapper.dst") + return g + +PathTracerReSTIRPT = render_graph_PathTracerReSTIRPT() +try: m.addGraph(PathTracerReSTIRPT) +except NameError: None diff --git a/setup_vs2022.bat b/setup_vs2022.bat index 07cbf034f2..5d01812a92 100644 --- a/setup_vs2022.bat +++ b/setup_vs2022.bat @@ -16,13 +16,6 @@ call %~dp0\setup.bat set PRESET=windows-vs2022%PRESET_SUFFIX% set TOOLSET=host=x86 set CMAKE_EXE=%~dp0\tools\.packman\cmake\bin\cmake.exe -set CUSTOM_CUDA_DIR=%~dp0\external\packman\cuda - -: Check if custom CUDA directory contains a valid CUDA SDK. -: Adjust toolset string to use the custom CUDA toolkit. -if exist %CUSTOM_CUDA_DIR%\bin\nvcc.exe ( - set TOOLSET=%TOOLSET%,cuda="%CUSTOM_CUDA_DIR%" -) : Configure solution by running cmake. echo Configuring Visual Studio solution ... diff --git a/tests/environment/gitlab_linux.json b/tests/environment/gitlab_linux.json deleted file mode 100644 index 860e7f1c86..0000000000 --- a/tests/environment/gitlab_linux.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "GitLab Environment (Linux)", - "image_tests": { - "result_dir": "/home/gitlab-runner/FalcorResults/${vcs_root}/${build_id}", - "ref_dir": "/home/gitlab-runner/FalcorRefs/${vcs_root}" - } -} diff --git a/tests/environment/gitlab_windows.json b/tests/environment/gitlab_windows.json deleted file mode 100644 index 69c9d43a47..0000000000 --- a/tests/environment/gitlab_windows.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "GitLab Environment (Windows)", - "image_tests": { - "result_dir": "${project_drive}/FalcorResults/${vcs_root}/${build_id}", - "ref_dir": "${project_drive}/FalcorRefs/${vcs_root}" - } -} diff --git a/tests/image_tests/renderpasses/test_PathTracerMaterials.py b/tests/image_tests/renderpasses/test_PathTracerMaterials.py index ab4dcae60c..6146777544 100644 --- a/tests/image_tests/renderpasses/test_PathTracerMaterials.py +++ b/tests/image_tests/renderpasses/test_PathTracerMaterials.py @@ -1,5 +1,6 @@ IMAGE_TEST = { - "device_types": ["d3d12", "vulkan"] + "device_types": ["d3d12", "vulkan"], + 'tolerance': 1e-10 } import sys diff --git a/tests/image_tests/renderpasses/test_WARDiffPathTracerMaterialFwd.py b/tests/image_tests/renderpasses/test_WARDiffPathTracerMaterialFwd.py index 8999405702..70dbd526dd 100644 --- a/tests/image_tests/renderpasses/test_WARDiffPathTracerMaterialFwd.py +++ b/tests/image_tests/renderpasses/test_WARDiffPathTracerMaterialFwd.py @@ -1,4 +1,5 @@ IMAGE_TEST = { + 'skipped': 'Skipped due to incorrect codegen when used with Slang 2025.5.3.', 'tolerance': 1e-8 } diff --git a/tests/image_tests/renderpasses/test_WARDiffPathTracerTranslationBwd.py b/tests/image_tests/renderpasses/test_WARDiffPathTracerTranslationBwd.py index c925872b54..cf862fb3fe 100644 --- a/tests/image_tests/renderpasses/test_WARDiffPathTracerTranslationBwd.py +++ b/tests/image_tests/renderpasses/test_WARDiffPathTracerTranslationBwd.py @@ -1,4 +1,5 @@ IMAGE_TEST = { + 'skipped': 'Skipped due to incorrect codegen when used with Slang 2025.5.3.', 'tolerance': 1e-8 } diff --git a/tests/image_tests/renderpasses/test_WARDiffPathTracerTranslationFwd.py b/tests/image_tests/renderpasses/test_WARDiffPathTracerTranslationFwd.py index 20cc500410..0eda17a597 100644 --- a/tests/image_tests/renderpasses/test_WARDiffPathTracerTranslationFwd.py +++ b/tests/image_tests/renderpasses/test_WARDiffPathTracerTranslationFwd.py @@ -1,4 +1,5 @@ IMAGE_TEST = { + 'skipped': 'Skipped due to incorrect codegen when used with Slang 2025.5.3.', 'tolerance': 1e-8 } diff --git a/tests/image_tests/renderscripts/test_PathTracerAreaReSTIR.py b/tests/image_tests/renderscripts/test_PathTracerAreaReSTIR.py new file mode 100644 index 0000000000..8cede44853 --- /dev/null +++ b/tests/image_tests/renderscripts/test_PathTracerAreaReSTIR.py @@ -0,0 +1,15 @@ +import sys +sys.path.append('..') +from falcor import * +from helpers import render_frames + +exec(open('../../../scripts/PathTracerAreaReSTIR.py').read()) + +# default +render_frames(m, 'default', frames=[64]) + +# arcade +m.loadScene('Arcade/Arcade.pyscene') +render_frames(m, 'arcade', frames=[64]) + +exit() diff --git a/tests/image_tests/renderscripts/test_PathTracerReSTIRPT.py b/tests/image_tests/renderscripts/test_PathTracerReSTIRPT.py new file mode 100644 index 0000000000..5c495b526b --- /dev/null +++ b/tests/image_tests/renderscripts/test_PathTracerReSTIRPT.py @@ -0,0 +1,15 @@ +import sys +sys.path.append('..') +from falcor import * +from helpers import render_frames + +exec(open('../../../scripts/PathTracerReSTIRPT.py').read()) + +# default +render_frames(m, 'default', frames=[64]) + +# arcade +m.loadScene('Arcade/Arcade.pyscene') +render_frames(m, 'arcade', frames=[64]) + +exit() diff --git a/tests/image_tests/renderscripts/test_WARDiffPathTracer.py b/tests/image_tests/renderscripts/test_WARDiffPathTracer.py index ef072db743..b4751aaac5 100644 --- a/tests/image_tests/renderscripts/test_WARDiffPathTracer.py +++ b/tests/image_tests/renderscripts/test_WARDiffPathTracer.py @@ -1,4 +1,5 @@ IMAGE_TEST = { + 'skipped': 'Skipped due to incorrect codegen when used with Slang 2025.5.3.', 'tolerance': 1e-8 } diff --git a/tests/run_python_tests.sh b/tests/run_python_tests.sh index b02d6ef978..91f43e055d 100755 --- a/tests/run_python_tests.sh +++ b/tests/run_python_tests.sh @@ -2,9 +2,4 @@ DIR="$(dirname "$(realpath "$0")")" -if [ -z "${CONDA_PYTHON_EXE}" ]; then - echo "Python tests require conda environment to run." - exit 1 -fi - -python ${DIR}/testing/run_python_tests.py $@ +exec python "${DIR}/testing/run_python_tests.py" "$@" diff --git a/tests/testing/core/config.py b/tests/testing/core/config.py index 15f2a3fa6b..0461f2cd8a 100644 --- a/tests/testing/core/config.py +++ b/tests/testing/core/config.py @@ -33,6 +33,9 @@ BUILD_CONFIGS = { # Temporary build configurations combining a CMake preset and build type. # These should be replaced by parsing CMakePresets.json in the future. + 'windows-vs2022-RelWithDebInfo': { + 'build_dir': 'build/windows-vs2022/bin/RelWithDebInfo' + }, 'windows-vs2022-Release': { 'build_dir': 'build/windows-vs2022/bin/Release' }, @@ -60,6 +63,9 @@ 'linux-clang-Release': { 'build_dir': 'build/linux-clang/bin/Release' }, + 'linux-clang-RelWithDebInfo': { + 'build_dir': 'build/linux-clang/bin/RelWithDebInfo' + }, 'linux-clang-Debug': { 'build_dir': 'build/linux-clang/bin/Debug' }, @@ -72,6 +78,9 @@ 'linux-gcc-Release': { 'build_dir': 'build/linux-gcc/bin/Release' }, + 'linux-gcc-RelWithDebInfo': { + 'build_dir': 'build/linux-gcc/bin/RelWithDebInfo' + }, 'linux-gcc-Debug': { 'build_dir': 'build/linux-gcc/bin/Debug' }, diff --git a/tests/testing/core/environment.py b/tests/testing/core/environment.py index 7421ecd93e..0a34db5fde 100644 --- a/tests/testing/core/environment.py +++ b/tests/testing/core/environment.py @@ -26,7 +26,7 @@ def validate_json(data, schema, full_name=None): raise TypeError(f'Property "{name}" does not exist') -def find_most_recent_build_config(): +def find_most_recent_build_config(checked_binary=None): ''' Find the build config most recently built by checking all possible build directories and finding the most recently @@ -34,15 +34,18 @@ def find_most_recent_build_config(): ''' project_dir = Path(__file__).parents[3].resolve() + if checked_binary == None: + checked_binary = config.FALCOR_LIB + best_config_name = None best_config_time = None for config_name, config_values in config.BUILD_CONFIGS.items(): build_dir = project_dir / config_values["build_dir"] - falcor_lib = build_dir / config.FALCOR_LIB - if not os.path.exists(falcor_lib): + falcor_binary = build_dir / checked_binary + if not os.path.exists(falcor_binary): continue - stat = os.stat(falcor_lib) + stat = os.stat(falcor_binary) if not best_config_name or stat.st_mtime >= best_config_time: best_config_name = config_name best_config_time = stat.st_mtime @@ -111,6 +114,7 @@ def __init__(self, json_file, build_config): self.vcs_root = helpers.get_vcs_root(self.project_dir) self.hostname = helpers.get_hostname() + self.ipaddress = helpers.get_ip() self.build_config = build_config self.branch = helpers.get_git_head_branch(self.project_dir) diff --git a/tests/testing/core/helpers.py b/tests/testing/core/helpers.py index 13d2781c30..7d3b227c4e 100644 --- a/tests/testing/core/helpers.py +++ b/tests/testing/core/helpers.py @@ -37,6 +37,20 @@ def get_hostname(): ''' return socket.gethostname() +def get_ip(): + ''' + Returns the local IP address of the machine. + ''' + try: + # Connect to an external host; doesn't have to be reachable + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('8.8.8.8', 80)) + ip = s.getsockname()[0] + s.close() + return ip + except Exception: + return '127.0.0.1' + def get_vcs_root(path): ''' Return the git version control system root (gitlab-master or github). diff --git a/tests/testing/run_image_tests.py b/tests/testing/run_image_tests.py index 5ce86ddb6d..3d0d93196f 100644 --- a/tests/testing/run_image_tests.py +++ b/tests/testing/run_image_tests.py @@ -144,6 +144,9 @@ def __init__(self, script_file, root_dir: Path, device_type, header): # Get timeout. self.timeout = self.header.get('timeout', config.DEFAULT_TIMEOUT) + # Get extra command line args + self.extra_args = self.header.get('extra_args', []) + def __repr__(self): return f'Test(name={self.name},script_file={self.script_file})' @@ -214,6 +217,8 @@ def generate_images(self, output_dir: Path, mogwai_exe: Path, run_only: bool, te '--headless', '--precise' ] + for arg in self.extra_args: + args.append(arg) rerun_env = {} rerun_env["cwd"] = str(cwd) rerun_env["args"] = args[1:] @@ -364,30 +369,70 @@ def generate_ref(env: Environment, test: Test, ref_dir: Path, process_controller print(f' {test.name:<60} : STARTED') test.process_controller = process_controller start_time = time.time() + report = { + 'name': test.name, + 'ref_dir': str(ref_dir / test.test_dir), + } result, messages, rerun_env = test.generate_images(ref_dir, env.mogwai_exe, False, env.temp_dir) elapsed_time = time.time() - start_time + report['messages'] = messages + report['duration'] = elapsed_time + report['rerun_env'] = rerun_env + + # Write JSON report. + report_dir = ref_dir / test.test_dir + report_dir.mkdir(parents=True, exist_ok=True) + report_file = report_dir / 'report.json' + with open(report_file, 'w') as f: + json.dump(report, f, indent=4) + return {"name": test.name, "elapsed_time": elapsed_time, "result": result, "messages": messages, "rerun_env": rerun_env} +def publish_ref_dir(staging_ref_dir: Path, ref_dir: Path): + ''' + Publishes staged reference images to the active reference directory. + Existing references are kept until the staged directory is complete. + ''' + backup_ref_dir = ref_dir.with_name(f'{ref_dir.name}.bak.{os.getpid()}') + + if backup_ref_dir.exists(): + shutil.rmtree(backup_ref_dir, ignore_errors=True) + + try: + if ref_dir.exists(): + ref_dir.rename(backup_ref_dir) + staging_ref_dir.rename(ref_dir) + except Exception: + if not ref_dir.exists() and backup_ref_dir.exists(): + backup_ref_dir.rename(ref_dir) + raise + finally: + if backup_ref_dir.exists(): + shutil.rmtree(backup_ref_dir, ignore_errors=True) def generate_refs(env: Environment, tests: list[Test], ref_dir, process_controller): ''' Computes references for a set of tests and stores them into ref_dir. ''' + ref_dir = Path(ref_dir) + staging_ref_dir = ref_dir.with_name(f'{ref_dir.name}.tmp.{os.getpid()}') + print(f'Reference directory: {ref_dir}') + print(f'Staging reference directory: {staging_ref_dir}') print(f'Generating references for {len(tests)} tests on {process_controller.thread_count} processes') if process_controller.thread_count > 1: print(colored('Test timings (both indidivual and total) are unreliable when running tests in parallel.', 'red')) - # Remove existing references. - if ref_dir.exists(): - shutil.rmtree(ref_dir, ignore_errors=True) + if staging_ref_dir.exists(): + shutil.rmtree(staging_ref_dir, ignore_errors=True) + staging_ref_dir.mkdir(parents=True, exist_ok=True) success = True - total_elapsed_time = 0 + run_start_time = time.time() try: with concurrent.futures.ThreadPoolExecutor(process_controller.thread_count) as executor: - futures = {executor.submit(generate_ref, env, test, ref_dir, process_controller) for test in tests} + futures = {executor.submit(generate_ref, env, test, staging_ref_dir, process_controller) for test in tests} try: for future in concurrent.futures.as_completed(futures): run_result = future.result() @@ -411,13 +456,26 @@ def generate_refs(env: Environment, tests: list[Test], ref_dir, process_controll process_controller.interrupt_and_exit() raise except KeyboardInterrupt: + shutil.rmtree(staging_ref_dir, ignore_errors=True) return False + total_elapsed_time = time.time() - run_start_time status = colored('PASSED', 'green') if success else colored('FAILED', 'red') print(f'\nGenerating references {status} ({total_elapsed_time:.1f} s).') if process_controller.thread_count > 1: print(colored('Test timings (both indidivual and total) are unreliable when running tests in parallel.','red')) + if success: + print(f'Publishing reference images to {ref_dir}') + try: + publish_ref_dir(staging_ref_dir, ref_dir) + except Exception as e: + print(colored(f'Failed to publish reference images: {e}', 'red')) + shutil.rmtree(staging_ref_dir, ignore_errors=True) + return False + else: + shutil.rmtree(staging_ref_dir, ignore_errors=True) + return success def run_test(env: Environment, test: Test, run_only: bool, compare_only: bool, ref_dir: Path, result_dir: Path, min_tolerance, process_controller: ProcessController, build_id: str): @@ -432,7 +490,7 @@ def run_test(env: Environment, test: Test, run_only: bool, compare_only: bool, r elapsed_time = time.time() - start_time if result != Test.Result.SKIPPED: - messages.append(f'View test at: http://{env.hostname}:8080/{env.vcs_root}/{build_id}/{test.name}') + messages.append(f'View test at: http://{env.ipaddress}:8080/{env.vcs_root}/{build_id}/{test.name}') return {"name": test.name, "elapsed_time": elapsed_time, "result": result, "messages": messages} @@ -615,6 +673,7 @@ def main(): additional_group.add_argument('--pull-refs', action='store_true', help='Pull reference images from remote before running tests') additional_group.add_argument('--push-refs', action='store_true', help='Push reference images to remote after generating them') additional_group.add_argument('--build-id', action='store', help='TeamCity build ID', default='unknown') + additional_group.add_argument('--init-missing-refs', action='store_true', help='Generate reference images if the reference directory is missing before running tests') args = parser.parse_args() @@ -677,27 +736,34 @@ def main(): # Give some instructions on how to acquire reference images if not available. if not args.run_only and not ref_dir.exists(): - print(ref_dir) - print(colored(f'\n!!! Reference images for "{args.ref_branch}" branch are not available !!!', 'red')) - print('') - print(f'You have the following options:') - print('') - print(f' 1. Checkout "{args.ref_branch}" branch and generate reference images using:') - print('') - print(f' run_image_tests --gen-refs') - print('') - print(f' 2. Use references from a different branch using:') - print('') - print(f' run_image_tests --ref-branch BRANCH') - print('') - sys.exit(1) + if args.init_missing_refs and not args.compare_only: + print(colored(f'\nReference images for "{args.ref_branch}" branch are not available. Generating them now.', 'yellow')) + result = generate_refs(env, tests, ref_dir, process_controller) + if not result: + shutil.rmtree(env.temp_dir, ignore_errors=True) + sys.exit(1) + else: + print(ref_dir) + print(colored(f'\n!!! Reference images for "{args.ref_branch}" branch are not available !!!', 'red')) + print('') + print(f'You have the following options:') + print('') + print(f' 1. Checkout "{args.ref_branch}" branch and generate reference images using:') + print('') + print(f' run_image_tests --gen-refs') + print('') + print(f' 2. Use references from a different branch using:') + print('') + print(f' run_image_tests --ref-branch BRANCH') + print('') + sys.exit(1) # Run tests. result = run_tests(env, tests, args.run_only, args.compare_only, ref_dir, result_dir, args.tolerance, args.xml_report, process_controller, args.build_id) shutil.rmtree(env.temp_dir, ignore_errors=True) # Print out url to test viewer - print(f"View test results at: http://{env.hostname}:8080/{env.vcs_root}/{args.build_id}") + print(f"View test results at: http://{env.ipaddress}:8080/{env.vcs_root}/{args.build_id}") # Exit with error if failed if not result: diff --git a/tests/testing/run_python_tests.py b/tests/testing/run_python_tests.py index f4c983c40b..2fed1da01d 100644 --- a/tests/testing/run_python_tests.py +++ b/tests/testing/run_python_tests.py @@ -16,7 +16,7 @@ def run_python_tests(env: Environment, xml_report, args): """ Run python tests. """ - cmd_args = ["python"] + cmd_args = [sys.executable] if xml_report: cmd_args += ["-m", "xmlrunner", "--output-file", str(xml_report)] else: @@ -93,7 +93,7 @@ def main(): parser.print_help() if env: print(f"\nAdditional arguments consumed by Python's unit test runner:\n") - subprocess.call(["python", "-m", "unittest", "-h"]) + subprocess.call([sys.executable, "-m", "unittest", "-h"]) else: print(f"\nFailed to load environment: {env_error}") sys.exit(0) diff --git a/tests/testing/run_unit_tests.py b/tests/testing/run_unit_tests.py index 27232ebbbf..45b1d6c8f0 100644 --- a/tests/testing/run_unit_tests.py +++ b/tests/testing/run_unit_tests.py @@ -27,7 +27,7 @@ def run_unit_tests(env: Environment, args): return p.returncode == 0 def main(): - default_config = find_most_recent_build_config() + default_config = find_most_recent_build_config(config.FALCOR_TEST_EXE) parser = argparse.ArgumentParser(description=__doc__, add_help=False) parser.add_argument('-h', '--help', action='store_true', help='Show this help message and exit') @@ -63,6 +63,8 @@ def main(): print(f"\nFailed to load environment: {env_error}") sys.exit(1) + print(f"Running unit tests with config: {args.config}") + # Run tests. success = run_unit_tests(env, passthrough_args) diff --git a/tests/testing/view_image_tests.py b/tests/testing/view_image_tests.py index bc660e4454..3cdc08ca7f 100644 --- a/tests/testing/view_image_tests.py +++ b/tests/testing/view_image_tests.py @@ -417,7 +417,7 @@ def main(): url = f'http://{args.host}:{args.port}' print(f'Running server on {url}') - # Open browser window. + # Open browser window. if not args.no_browser: webbrowser.open(url)