From c0a497b5db83709cc034ec11e7dd48328293f823 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Thu, 15 Aug 2024 13:58:50 -0700 Subject: [PATCH 01/11] [hdEmbree][build_usd] add to build_usd.py status message --- build_scripts/build_usd.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build_scripts/build_usd.py b/build_scripts/build_usd.py index 66b5c231a5..4565e9972a 100644 --- a/build_scripts/build_usd.py +++ b/build_scripts/build_usd.py @@ -2730,6 +2730,7 @@ def _JoinVersion(v): OpenVDB support: {enableOpenVDB} OpenImageIO support: {buildOIIO} OpenColorIO support: {buildOCIO} + Embree support: {buildEmbree} PRMan support: {buildPrman} Vulkan support: {enableVulkan} UsdImaging {buildUsdImaging} @@ -2794,6 +2795,7 @@ def FormatBuildArguments(buildArgs): enableOpenVDB=("On" if context.enableOpenVDB else "Off"), buildOIIO=("On" if context.buildOIIO else "Off"), buildOCIO=("On" if context.buildOCIO else "Off"), + buildEmbree=("On" if context.buildEmbree else "Off"), buildPrman=("On" if context.buildPrman else "Off"), buildUsdImaging=("On" if context.buildUsdImaging else "Off"), buildUsdview=("On" if context.buildUsdview else "Off"), From d05ab0839339118bafe7c8c66071dfd88a3d32fe Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 22 Oct 2024 10:41:20 -0700 Subject: [PATCH 02/11] [work] fix docs for Work_NormalizeThreadCount --- pxr/base/work/threadLimits.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pxr/base/work/threadLimits.cpp b/pxr/base/work/threadLimits.cpp index a1fafdd64d..60abc34f37 100644 --- a/pxr/base/work/threadLimits.cpp +++ b/pxr/base/work/threadLimits.cpp @@ -71,14 +71,14 @@ WorkGetPhysicalConcurrencyLimit() #endif } -// This function always returns an actual thread count >= 1. +// This function always returns either 0 (meaning "no change") or >= 1 static unsigned Work_NormalizeThreadCount(const int n) { // Zero means "no change", and n >= 1 means exactly n threads, so simply // pass those values through unchanged. // For negative integers, subtract the absolute value from the total number - // of available cores (denoting all but n cores). If n == number of cores, + // of available cores (denoting all but n cores). If |n| >= number of cores, // clamp to 1 to set single-threaded mode. return n >= 0 ? n : std::max(1, n + WorkGetPhysicalConcurrencyLimit()); } From d6a0b099d012158367636904942d13862a543ec9 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 10 Jul 2024 09:41:04 -0700 Subject: [PATCH 03/11] [hdEmbree] ensure we respect PXR_WORK_THREAD_LIMIT --- pxr/imaging/plugin/hdEmbree/renderer.cpp | 84 ++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 88d5e79093..26df6d3171 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -22,6 +22,87 @@ #include #include +// ------------------------------------------------------------------------- +// Old TBB workaround - can remove once OneTBB is mandatory +// ------------------------------------------------------------------------- +#include + +#if TBB_INTERFACE_VERSION_MAJOR < 12 + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// PXR_WORK_THREAD_LIMIT isn't exported as part of it's api, and we're not +// part of the work library, so we can't use: +// extern TfEnvSetting PXR_WORK_THREAD_LIMIT; +extern std::variant const * +Tf_GetEnvSettingByName(std::string const&); + +PXR_NAMESPACE_CLOSE_SCOPE + +namespace { + +PXR_NAMESPACE_USING_DIRECTIVE + +// This function always returns either 0 (meaning "no change") or >= 1 +// +// Duplication of code from threadLimits.cpp - copied here to avoid having to +// change API of work lib. Will go away once we don't need to support tbb < 12 +// (ie, pre-OneTBB) +static unsigned +HdEmbree_NormalizeThreadCount(const int n) +{ + // Zero means "no change", and n >= 1 means exactly n threads, so simply + // pass those values through unchanged. + // For negative integers, subtract the absolute value from the total number + // of available cores (denoting all but n cores). If |n| >= number of cores, + // clamp to 1 to set single-threaded mode. + return n >= 0 ? n : std::max(1, n + WorkGetPhysicalConcurrencyLimit()); +} + + +// Returns the normalized thread limit value from the environment setting. Note +// that 0 means "no change", i.e. the environment setting does not apply. +// +// Duplication of code from threadLimits.cpp - copied here to avoid having to +// change API of work lib. Will go away once we don't need to support tbb < 12 +// (ie, pre-OneTBB) +static unsigned +HdEmbree_GetConcurrencyLimitSetting() +{ + std::variant const * + variantValue = Tf_GetEnvSettingByName("PXR_WORK_THREAD_LIMIT"); + int threadLimit = 0; + if (int const *value = std::get_if(variantValue)) { + threadLimit = *value; + } + return HdEmbree_NormalizeThreadCount(threadLimit); +} + + +// Make the calling context respect PXR_WORK_THREAD_LIMIT, if run from a thread +// other than the main thread (ie, the renderThread) +class _ScopedThreadScheduler { +public: + _ScopedThreadScheduler() { + auto limit = HdEmbree_GetConcurrencyLimitSetting(); + if (limit != 0) { + _tbbTaskSchedInit = + std::make_unique(limit); + } + } + + std::unique_ptr _tbbTaskSchedInit; +}; + +} // anonymous namespace + +#endif // TBB_INTERFACE_VERSION_MAJOR < 12 + + namespace { PXR_NAMESPACE_USING_DIRECTIVE @@ -453,6 +534,9 @@ HdEmbreeRenderer::Render(HdRenderThread *renderThread) // Render by scheduling square tiles of the sample buffer in a parallel // for loop. +#if TBB_INTERFACE_VERSION_MAJOR < 12 + _ScopedThreadScheduler scheduler; +#endif // Always pass the renderThread to _RenderTiles to allow the first frame // to be interrupted. WorkParallelForN(numTilesX*numTilesY, From 3a6afbf8173dc2c8a5789c2cbd17960eb580e470 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 1 Sep 2023 07:34:44 +1200 Subject: [PATCH 04/11] [hdEmbree] Initial UsdLux reference implementation This modifies the hdEmbree example plugin to do direct lighting so as to provide a reference implementation of the expected behaviour for UsdLux. If no lights are present in the stage, the old ambient occlusion path will be used. --- pxr/imaging/plugin/hdEmbree/CMakeLists.txt | 1 + pxr/imaging/plugin/hdEmbree/light.cpp | 171 +++++ pxr/imaging/plugin/hdEmbree/light.h | 109 +++ pxr/imaging/plugin/hdEmbree/renderBuffer.h | 2 +- .../plugin/hdEmbree/renderDelegate.cpp | 21 +- pxr/imaging/plugin/hdEmbree/renderParam.h | 8 +- pxr/imaging/plugin/hdEmbree/renderer.cpp | 694 ++++++++++++++++-- pxr/imaging/plugin/hdEmbree/renderer.h | 49 +- 8 files changed, 987 insertions(+), 68 deletions(-) create mode 100644 pxr/imaging/plugin/hdEmbree/light.cpp create mode 100644 pxr/imaging/plugin/hdEmbree/light.h diff --git a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt index 43aa0826e5..2efef685eb 100644 --- a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt +++ b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt @@ -31,6 +31,7 @@ pxr_plugin(hdEmbree PUBLIC_CLASSES config instancer + light mesh meshSamplers renderBuffer diff --git a/pxr/imaging/plugin/hdEmbree/light.cpp b/pxr/imaging/plugin/hdEmbree/light.cpp new file mode 100644 index 0000000000..082e4605d2 --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -0,0 +1,171 @@ +// +// Copyright 2024 Pixar +// +// Licensed under the terms set forth in the LICENSE.txt file available at +// https://openusd.org/license. +// +#include "pxr/imaging/plugin/hdEmbree/light.h" + +#include "light.h" +#include "pxr/imaging/plugin/hdEmbree/debugCodes.h" +#include "pxr/imaging/plugin/hdEmbree/renderParam.h" +#include "pxr/imaging/plugin/hdEmbree/renderer.h" + +#include "pxr/imaging/hd/sceneDelegate.h" +#include "pxr/imaging/hio/image.h" + +#include +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +HdEmbree_Light::HdEmbree_Light(SdfPath const& id, TfToken const& lightType) + : HdLight(id) { + if (id.IsEmpty()) { + return; + } + + // Set the variant to the right type - Sync will fill rest of data + if (lightType == HdSprimTypeTokens->cylinderLight) { + _lightData.lightVariant = HdEmbree_Cylinder(); + } else if (lightType == HdSprimTypeTokens->diskLight) { + _lightData.lightVariant = HdEmbree_Disk(); + } else if (lightType == HdSprimTypeTokens->rectLight) { + // Get shape parameters + _lightData.lightVariant = HdEmbree_Rect(); + } else if (lightType == HdSprimTypeTokens->sphereLight) { + _lightData.lightVariant = HdEmbree_Sphere(); + } else { + TF_WARN("HdEmbree - Unrecognized light type: %s", lightType.GetText()); + _lightData.lightVariant = HdEmbree_UnknownLight(); + } +} + +HdEmbree_Light::~HdEmbree_Light() = default; + +void +HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, + HdRenderParam *renderParam, HdDirtyBits *dirtyBits) +{ + HD_TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + HdEmbreeRenderParam *embreeRenderParam = + static_cast(renderParam); + + // calling this bumps the scene version and causes a re-render + embreeRenderParam->AcquireSceneForEdit(); + + SdfPath const& id = GetId(); + + // Get _lightData's transform. We'll only consider the first time sample for now + HdTimeSampleArray xformSamples; + sceneDelegate->SampleTransform(id, &xformSamples); + _lightData.xformLightToWorld = GfMatrix4f(xformSamples.values[0]); + _lightData.xformWorldToLight = _lightData.xformLightToWorld.GetInverse(); + _lightData.normalXformLightToWorld = + _lightData.xformWorldToLight.ExtractRotationMatrix().GetTranspose(); + + // Store luminance parameters + _lightData.intensity = sceneDelegate->GetLightParamValue( + id, HdLightTokens->intensity).GetWithDefault(1.0f); + _lightData.exposure = sceneDelegate->GetLightParamValue( + id, HdLightTokens->exposure).GetWithDefault(0.0f); + _lightData.color = sceneDelegate->GetLightParamValue( + id, HdLightTokens->color).GetWithDefault(GfVec3f{1.0f, 1.0f, 1.0f}); + _lightData.normalize = sceneDelegate->GetLightParamValue( + id, HdLightTokens->normalize).GetWithDefault(false); + _lightData.colorTemperature = sceneDelegate->GetLightParamValue( + id, HdLightTokens->colorTemperature).GetWithDefault(6500.0f); + _lightData.enableColorTemperature = sceneDelegate->GetLightParamValue( + id, HdLightTokens->enableColorTemperature).GetWithDefault(false); + + // Get visibility + _lightData.visible = sceneDelegate->GetVisible(id); + + // Switch on the _lightData type and pull the relevant attributes from the scene + // delegate + std::visit([this, &id, &sceneDelegate](auto& typedLight) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + // Do nothing + } else if constexpr (std::is_same_v) { + typedLight = HdEmbree_Cylinder{ + sceneDelegate->GetLightParamValue(id, HdLightTokens->radius) + .GetWithDefault(0.5f), + sceneDelegate->GetLightParamValue(id, HdLightTokens->length) + .GetWithDefault(1.0f), + }; + } else if constexpr (std::is_same_v) { + typedLight = HdEmbree_Disk{ + sceneDelegate->GetLightParamValue(id, HdLightTokens->radius) + .GetWithDefault(0.5f), + }; + } else if constexpr (std::is_same_v) { + typedLight = HdEmbree_Rect{ + sceneDelegate->GetLightParamValue(id, HdLightTokens->width) + .Get(), + sceneDelegate->GetLightParamValue(id, HdLightTokens->height) + .Get(), + }; + } else if constexpr (std::is_same_v) { + typedLight = HdEmbree_Sphere{ + sceneDelegate->GetLightParamValue(id, HdLightTokens->radius) + .GetWithDefault(0.5f), + }; + } else { + static_assert(false, "non-exhaustive _LightVariant visitor"); + } + }, _lightData.lightVariant); + + if (const auto value = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingFocus); + value.IsHolding()) { + _lightData.shaping.focus = value.UncheckedGet(); + } + + if (const auto value = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingFocusTint); + value.IsHolding()) { + _lightData.shaping.focusTint = value.UncheckedGet(); + } + + if (const auto value = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingConeAngle); + value.IsHolding()) { + _lightData.shaping.coneAngle = value.UncheckedGet(); + } + + if (const auto value = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingConeSoftness); + value.IsHolding()) { + _lightData.shaping.coneSoftness = value.UncheckedGet(); + } + + HdEmbreeRenderer *renderer = embreeRenderParam->GetRenderer(); + renderer->AddLight(id, this); + + *dirtyBits &= ~HdLight::AllDirty; +} + +HdDirtyBits +HdEmbree_Light::GetInitialDirtyBitsMask() const +{ + return HdLight::AllDirty; +} + +void +HdEmbree_Light::Finalize(HdRenderParam *renderParam) +{ + auto* embreeParam = static_cast(renderParam); + + // Remove from renderer's light map + HdEmbreeRenderer *renderer = embreeParam->GetRenderer(); + renderer->RemoveLight(GetId(), this); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/plugin/hdEmbree/light.h b/pxr/imaging/plugin/hdEmbree/light.h new file mode 100644 index 0000000000..906ba185bc --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -0,0 +1,109 @@ +// +// Copyright 2024 Pixar +// +// Licensed under the terms set forth in the LICENSE.txt file available at +// https://openusd.org/license. +// +#ifndef PXR_IMAGING_PLUGIN_HD_EMBREE_LIGHT_H +#define PXR_IMAGING_PLUGIN_HD_EMBREE_LIGHT_H + +#include "pxr/base/gf/vec3f.h" +#include "pxr/base/gf/matrix3f.h" +#include "pxr/base/gf/matrix4f.h" +#include "pxr/imaging/hd/light.h" + +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class HdEmbreeRenderer; + +struct HdEmbree_UnknownLight +{}; +struct HdEmbree_Cylinder +{ + float radius; + float length; +}; + +struct HdEmbree_Disk +{ + float radius; +}; + +struct HdEmbree_Rect +{ + float width; + float height; +}; + +struct HdEmbree_Sphere +{ + float radius; +}; + +using HdEmbree_LightVariant = std::variant< + HdEmbree_UnknownLight, + HdEmbree_Cylinder, + HdEmbree_Disk, + HdEmbree_Rect, + HdEmbree_Sphere>; + +struct HdEmbree_Shaping +{ + GfVec3f focusTint; + float focus = 0.0f; + float coneAngle = 180.0f; + float coneSoftness = 0.0f; +}; + +struct HdEmbree_LightData +{ + GfMatrix4f xformLightToWorld; + GfMatrix3f normalXformLightToWorld; + GfMatrix4f xformWorldToLight; + GfVec3f color; + float intensity = 1.0f; + float exposure = 0.0f; + float colorTemperature = 6500.0f; + bool enableColorTemperature = false; + HdEmbree_LightVariant lightVariant; + bool normalize = false; + bool visible = true; + HdEmbree_Shaping shaping; +}; + +class HdEmbree_Light final : public HdLight +{ +public: + HdEmbree_Light(SdfPath const& id, TfToken const& lightType); + ~HdEmbree_Light(); + + /// Synchronizes state from the delegate to this object. + void Sync(HdSceneDelegate* sceneDelegate, + HdRenderParam* renderParam, + HdDirtyBits* dirtyBits) override; + + /// Returns the minimal set of dirty bits to place in the + /// change tracker for use in the first sync of this prim. + /// Typically this would be all dirty bits. + HdDirtyBits GetInitialDirtyBitsMask() const override; + + void Finalize(HdRenderParam *renderParam) override; + + HdEmbree_LightData const& LightData() const { + return _lightData; + } + +private: + HdEmbree_LightData _lightData; +}; + + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif \ No newline at end of file diff --git a/pxr/imaging/plugin/hdEmbree/renderBuffer.h b/pxr/imaging/plugin/hdEmbree/renderBuffer.h index 10c9813c8d..b3f1a38ca7 100644 --- a/pxr/imaging/plugin/hdEmbree/renderBuffer.h +++ b/pxr/imaging/plugin/hdEmbree/renderBuffer.h @@ -166,7 +166,7 @@ class HdEmbreeRenderBuffer : public HdRenderBuffer // For multisampled buffers: the input write buffer. std::vector _sampleBuffer; // For multisampled buffers: the sample count buffer. - std::vector _sampleCount; + std::vector _sampleCount; // The number of callers mapping this buffer. std::atomic _mappers; diff --git a/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp b/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp index 32ef99dbbb..100e9c133a 100644 --- a/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp @@ -8,6 +8,7 @@ #include "pxr/imaging/plugin/hdEmbree/config.h" #include "pxr/imaging/plugin/hdEmbree/instancer.h" +#include "pxr/imaging/plugin/hdEmbree/light.h" #include "pxr/imaging/plugin/hdEmbree/renderParam.h" #include "pxr/imaging/plugin/hdEmbree/renderPass.h" @@ -35,6 +36,10 @@ const TfTokenVector HdEmbreeRenderDelegate::SUPPORTED_SPRIM_TYPES = { HdPrimTypeTokens->camera, HdPrimTypeTokens->extComputation, + HdPrimTypeTokens->cylinderLight, + HdPrimTypeTokens->diskLight, + HdPrimTypeTokens->rectLight, + HdPrimTypeTokens->sphereLight, }; const TfTokenVector HdEmbreeRenderDelegate::SUPPORTED_BPRIM_TYPES = @@ -147,7 +152,7 @@ HdEmbreeRenderDelegate::_Initialize() // Store top-level embree objects inside a render param that can be // passed to prims during Sync(). Also pass a handle to the render thread. _renderParam = std::make_shared( - _rtcDevice, _rtcScene, &_renderThread, &_sceneVersion); + _rtcDevice, _rtcScene, &_renderThread, &_renderer, &_sceneVersion); // Pass the scene handle to the renderer. _renderer.SetScene(_rtcScene); @@ -230,7 +235,7 @@ HdAovDescriptor HdEmbreeRenderDelegate::GetDefaultAovDescriptor(TfToken const& name) const { if (name == HdAovTokens->color) { - return HdAovDescriptor(HdFormatUNorm8Vec4, true, + return HdAovDescriptor(HdFormatFloat32Vec4, true, VtValue(GfVec4f(0.0f))); } else if (name == HdAovTokens->normal || name == HdAovTokens->Neye) { return HdAovDescriptor(HdFormatFloat32Vec3, false, @@ -331,6 +336,12 @@ HdEmbreeRenderDelegate::CreateSprim(TfToken const& typeId, return new HdCamera(sprimId); } else if (typeId == HdPrimTypeTokens->extComputation) { return new HdExtComputation(sprimId); + } else if (typeId == HdPrimTypeTokens->light || + typeId == HdPrimTypeTokens->diskLight || + typeId == HdPrimTypeTokens->rectLight || + typeId == HdPrimTypeTokens->sphereLight || + typeId == HdPrimTypeTokens->cylinderLight) { + return new HdEmbree_Light(sprimId, typeId); } else { TF_CODING_ERROR("Unknown Sprim Type %s", typeId.GetText()); } @@ -347,6 +358,12 @@ HdEmbreeRenderDelegate::CreateFallbackSprim(TfToken const& typeId) return new HdCamera(SdfPath::EmptyPath()); } else if (typeId == HdPrimTypeTokens->extComputation) { return new HdExtComputation(SdfPath::EmptyPath()); + } else if (typeId == HdPrimTypeTokens->light || + typeId == HdPrimTypeTokens->diskLight || + typeId == HdPrimTypeTokens->rectLight || + typeId == HdPrimTypeTokens->sphereLight || + typeId == HdPrimTypeTokens->cylinderLight) { + return new HdEmbree_Light(SdfPath::EmptyPath(), typeId); } else { TF_CODING_ERROR("Unknown Sprim Type %s", typeId.GetText()); } diff --git a/pxr/imaging/plugin/hdEmbree/renderParam.h b/pxr/imaging/plugin/hdEmbree/renderParam.h index 206a7458bc..e333b2dc4b 100644 --- a/pxr/imaging/plugin/hdEmbree/renderParam.h +++ b/pxr/imaging/plugin/hdEmbree/renderParam.h @@ -15,6 +15,8 @@ PXR_NAMESPACE_OPEN_SCOPE +class HdEmbreeRenderer; + /// /// \class HdEmbreeRenderParam /// @@ -27,9 +29,10 @@ class HdEmbreeRenderParam final : public HdRenderParam public: HdEmbreeRenderParam(RTCDevice device, RTCScene scene, HdRenderThread *renderThread, + HdEmbreeRenderer *renderer, std::atomic *sceneVersion) : _scene(scene), _device(device) - , _renderThread(renderThread), _sceneVersion(sceneVersion) + , _renderThread(renderThread), _renderer(renderer), _sceneVersion(sceneVersion) {} /// Accessor for the top-level embree scene. @@ -41,6 +44,8 @@ class HdEmbreeRenderParam final : public HdRenderParam /// Accessor for the top-level embree device (library handle). RTCDevice GetEmbreeDevice() { return _device; } + HdEmbreeRenderer* GetRenderer() { return _renderer; } + private: /// A handle to the top-level embree scene. RTCScene _scene; @@ -48,6 +53,7 @@ class HdEmbreeRenderParam final : public HdRenderParam RTCDevice _device; /// A handle to the global render thread. HdRenderThread *_renderThread; + HdEmbreeRenderer* _renderer; /// A version counter for edits to _scene. std::atomic *_sceneVersion; }; diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 26df6d3171..47f93cf498 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -6,20 +6,31 @@ // #include "pxr/imaging/plugin/hdEmbree/renderer.h" -#include "pxr/imaging/plugin/hdEmbree/renderBuffer.h" #include "pxr/imaging/plugin/hdEmbree/config.h" -#include "pxr/imaging/plugin/hdEmbree/context.h" +#include "pxr/imaging/plugin/hdEmbree/light.h" #include "pxr/imaging/plugin/hdEmbree/mesh.h" +#include "pxr/imaging/plugin/hdEmbree/renderBuffer.h" #include "pxr/imaging/hd/perfLog.h" +#include "pxr/base/gf/color.h" +#include "pxr/base/gf/colorSpace.h" #include "pxr/base/gf/matrix3f.h" +#include "pxr/base/gf/range1f.h" #include "pxr/base/gf/vec2f.h" +#include "pxr/base/gf/vec3f.h" #include "pxr/base/work/loops.h" #include "pxr/base/tf/hash.h" +#include +#include +#include + +#include #include +#include +#include #include // ------------------------------------------------------------------------- @@ -63,7 +74,6 @@ HdEmbree_NormalizeThreadCount(const int n) return n >= 0 ? n : std::max(1, n + WorkGetPhysicalConcurrencyLimit()); } - // Returns the normalized thread limit value from the environment setting. Note // that 0 means "no change", i.e. the environment setting does not apply. // @@ -82,7 +92,6 @@ HdEmbree_GetConcurrencyLimitSetting() return HdEmbree_NormalizeThreadCount(threadLimit); } - // Make the calling context respect PXR_WORK_THREAD_LIMIT, if run from a thread // other than the main thread (ie, the renderThread) class _ScopedThreadScheduler { @@ -102,11 +111,118 @@ class _ScopedThreadScheduler { #endif // TBB_INTERFACE_VERSION_MAJOR < 12 - namespace { PXR_NAMESPACE_USING_DIRECTIVE +// ------------------------------------------------------------------------- +// Constants +// ------------------------------------------------------------------------- + +template +constexpr T _pi = static_cast(M_PI); + +constexpr float _rayHitContinueBias = 0.001f; + +constexpr float _minLuminanceCutoff = 1e-9f; + +constexpr GfVec3f _invalidColor = GfVec3f(-std::numeric_limits::infinity()); + +// ------------------------------------------------------------------------- +// General Math Utilities +// ------------------------------------------------------------------------- + +inline float +_Sqr(float x) +{ + return x*x; +} + +// The latitudinal polar coordinate of v, in the range [0, pi] +inline float +_Theta(GfVec3f const& v) +{ + return acosf(GfClamp(v[2], -1.0f, 1.0f)); +} + +// The longitudinal polar coordinate of v, in the range [0, 2*pi) +inline float +_Phi(GfVec3f const& v) +{ + float p = atan2f(v[1], v[0]); + return p < 0.0f ? (p + 2.0f * _pi) : p; +} + +// Dot product, but set to 0 if less than 0 - ie, 0 for backward-facing rays +inline float +_DotZeroClip(GfVec3f const& a, GfVec3f const& b) +{ + return std::max(0.0f, GfDot(a, b)); +} + +float +_Smoothstep(float t, GfRange1f range) +{ + const float length = range.GetSize(); + if (length == 0) { + if (t <= range.GetMin()) { + // Note that in the case of t == range.GetMin(), we have a + // degenerate case where there's no clear answer what the "right" + // thing to do is. + + // I arbitrarily chose 0.0 to return in this case, so at least we + // have consistent / well defined behavior; could have also done 1.0 + // or 0.5... + return 0.0; + } + return 1.0; + } + t = GfClamp((t - range.GetMin())/length, 0.0f, 1.0f); + return t * t * (3.0f - 2.0f * t); +} + +float +_AreaRect(GfMatrix4f const& xf, float width, float height) +{ + const GfVec3f U = xf.TransformDir(GfVec3f{width, 0.0f, 0.0f}); + const GfVec3f V = xf.TransformDir(GfVec3f{0.0f, height, 0.0f}); + return GfCross(U, V).GetLength(); +} + +float +_AreaSphere(GfMatrix4f const& xf, float radius) +{ + // Area of the ellipsoid + const float a = xf.TransformDir(GfVec3f{radius, 0.0f, 0.0f}).GetLength(); + const float b = xf.TransformDir(GfVec3f{0.0f, radius, 0.0f}).GetLength(); + const float c = xf.TransformDir(GfVec3f{0.0f, 0.0f, radius}).GetLength(); + const float ab = powf(a*b, 1.6f); + const float ac = powf(a*c, 1.6f); + const float bc = powf(b*c, 1.6f); + return powf((ab + ac + bc) / 3.0f, 1.0f / 1.6f) * 4.0f * _pi; +} + +float +_AreaDisk(GfMatrix4f const& xf, float radius) +{ + // Calculate surface area of the ellipse + const float a = xf.TransformDir(GfVec3f{radius, 0.0f, 0.0f}).GetLength(); + const float b = xf.TransformDir(GfVec3f{0.0f, radius, 0.0f}).GetLength(); + return _pi * a * b; +} + +float +_AreaCylinder(GfMatrix4f const& xf, float radius, float length) +{ + const float c = xf.TransformDir(GfVec3f{length, 0.0f, 0.0f}).GetLength(); + const float a = xf.TransformDir(GfVec3f{0.0f, radius, 0.0f}).GetLength(); + const float b = xf.TransformDir(GfVec3f{0.0f, 0.0f, radius}).GetLength(); + // Ramanujan's approximation to perimeter of ellipse + const float e = + _pi * (3.0f * (a + b) - sqrtf((3.0f * a + b) * (a + 3.0f * b))); + return e * c; +} + // ------------------------------------------------------------------------- // General Ray Utilities // ------------------------------------------------------------------------- @@ -119,6 +235,328 @@ _CalculateHitPosition(RTCRayHit const& rayHit) rayHit.ray.org_z + rayHit.ray.tfar * rayHit.ray.dir_z); } +// ------------------------------------------------------------------------- +// Color utilities +// ------------------------------------------------------------------------- + +const GfColorSpace _linRec709(GfColorSpaceNames->LinearRec709); +const GfColorSpace _xyzColorSpace(GfColorSpaceNames->CIEXYZ); + +// Ideally, we could could move this to GfColor::GetLuminance() +inline float +_GetLuminance(GfColor const& color) +{ + GfColor xyzColor(color, _xyzColorSpace); + // The "Y" component in XYZ space is luminance + return xyzColor.GetRGB()[1]; +} + +const GfVec3f _rec709LuminanceComponents( + _GetLuminance(GfColor(GfVec3f::XAxis(), _linRec709)), + _GetLuminance(GfColor(GfVec3f::YAxis(), _linRec709)), + _GetLuminance(GfColor(GfVec3f::ZAxis(), _linRec709))); + + +// Recreates UsdLuxBlackbodyTemperatureAsRgb in "pxr/usd/usdLux/blackbody.h"... +/// But uses new GfColor functionality, since we shouldn't import usd into +// imaging + +// Perhaps UsdLuxBlackbodyTemperatureAsRgb should be deprecated, and this made +// a new utility function somewhere, for use by other HdRenderDelegates? +// (Maybe in gf/color.h?) +inline GfVec3f +_BlackbodyTemperatureAsRgb(float kelvinColorTemp) +{ + auto tempColor = GfColor(_linRec709); + // Get color in Rec709 with luminance 1.0 + tempColor.SetFromPlanckianLocus(kelvinColorTemp, 1.0f); + // We normalize to the luminance of (1,1,1) in Rec709 + GfVec3f tempColorRGB = tempColor.GetRGB(); + float rec709Luminance = GfDot(tempColorRGB, _rec709LuminanceComponents); + return tempColorRGB / rec709Luminance; +} + +// ------------------------------------------------------------------------- +// Light sampling structures / utilities +// ------------------------------------------------------------------------- + +struct _ShapeSample { + GfVec3f pWorld; + GfVec3f nWorld; + GfVec2f uv; + float invPdfA; +}; + +struct _LightSample { + GfVec3f Li; + GfVec3f wI; + float dist; + float invPdfW; +}; + +_ShapeSample +_SampleRect(GfMatrix4f const& xf, GfMatrix3f const& normalXform, float width, + float height, float u1, float u2) +{ + // Sample rectangle in object space + const GfVec3f pLight( + (u1 - 0.5f) * width, + (u2 - 0.5f) * height, + 0.0f + ); + const GfVec3f nLight(0.0f, 0.0f, -1.0f); + const GfVec2f uv(u1, u2); + + // Transform to world space + const GfVec3f pWorld = xf.Transform(pLight); + const GfVec3f nWorld = (nLight * normalXform).GetNormalized(); + + const float area = _AreaRect(xf, width, height); + + return _ShapeSample { + pWorld, + nWorld, + uv, + area + }; +} + +_ShapeSample +_SampleSphere(GfMatrix4f const& xf, GfMatrix3f const& normalXform, float radius, + float u1, float u2) +{ + // Sample sphere in light space + const float z = 1.0 - 2.0 * u1; + const float r = sqrtf(std::max(0.0f, 1.0f - z*z)); + const float phi = 2.0f * _pi * u2; + GfVec3f pLight{r * std::cos(phi), r * std::sin(phi), z}; + const GfVec3f nLight = pLight; + pLight *= radius; + const GfVec2f uv(u2, z); + + // Transform to world space + const GfVec3f pWorld = xf.Transform(pLight); + const GfVec3f nWorld = (nLight * normalXform).GetNormalized(); + + const float area = _AreaSphere(xf, radius); + + return _ShapeSample { + pWorld, + nWorld, + uv, + area + }; +} + +GfVec3f +_SampleDiskPolar(float u1, float u2) +{ + const float r = sqrtf(u1); + const float theta = 2.0f * _pi * u2; + return GfVec3f(r * cosf(theta), r * sinf(theta), 0.0f); +} + +_ShapeSample +_SampleDisk(GfMatrix4f const& xf, GfMatrix3f const& normalXform, float radius, + float u1, float u2) +{ + // Sample disk in light space + GfVec3f pLight = _SampleDiskPolar(u1, u2); + const GfVec3f nLight(0.0f, 0.0f, -1.0f); + const GfVec2f uv(pLight[0], pLight[1]); + pLight *= radius; + + // Transform to world space + const GfVec3f pWorld = xf.Transform(pLight); + const GfVec3f nWorld = (nLight * normalXform).GetNormalized(); + + const float area = _AreaDisk(xf, radius); + + return _ShapeSample { + pWorld, + nWorld, + uv, + area + }; +} + +_ShapeSample +_SampleCylinder(GfMatrix4f const& xf, GfMatrix3f const& normalXform, + float radius,float length, float u1, float u2) { + float z = GfLerp(u1, -length/2.0f, length/2.0f); + float phi = u2 * 2.0f * _pi; + // Compute cylinder sample position _pi_ and normal _n_ from $z$ and $\phi$ + GfVec3f pLight = GfVec3f(z, radius * cosf(phi), radius * sinf(phi)); + // Reproject _pObj_ to cylinder surface and compute _pObjError_ + float hitRad = sqrtf(_Sqr(pLight[1]) + _Sqr(pLight[2])); + pLight[1] *= radius / hitRad; + pLight[2] *= radius / hitRad; + + GfVec3f nLight(0.0f, pLight[1], pLight[2]); + nLight.Normalize(); + + // Transform to world space + const GfVec3f pWorld = xf.Transform(pLight); + const GfVec3f nWorld = (nLight * normalXform).GetNormalized(); + + const float area = _AreaCylinder(xf, radius, length); + + return _ShapeSample { + pWorld, + nWorld, + GfVec2f(u2, u1), + area + }; +} + +GfVec3f +_EvalLightBasic(HdEmbree_LightData const& light) +{ + GfVec3f Le = light.color * light.intensity * powf(2.0f, light.exposure); + if (light.enableColorTemperature) { + Le = GfCompMult(Le, + _BlackbodyTemperatureAsRgb(light.colorTemperature)); + } + return Le; +} + +_LightSample +_EvalAreaLight(HdEmbree_LightData const& light, _ShapeSample const& ss, + GfVec3f const& position) +{ + // Transform PDF from area measure to solid angle measure. We use the + // inverse PDF here to avoid division by zero when the surface point is + // behind the light + GfVec3f wI = ss.pWorld - position; + const float dist = wI.GetLength(); + wI /= dist; + const float cosThetaOffNormal = _DotZeroClip(-wI, ss.nWorld); + float invPdfW = cosThetaOffNormal / _Sqr(dist) * ss.invPdfA; + GfVec3f lightNegZ = -light.xformLightToWorld.GetRow3(2).GetNormalized(); + const float cosThetaOffZ = GfDot(-wI, lightNegZ); + + // Combine the brightness parameters to get initial emission luminance + // (nits) + GfVec3f Le = cosThetaOffNormal > 0.0f ? + _EvalLightBasic(light) + : GfVec3f(0.0f); + + // If normalize is enabled, we need to divide the luminance by the surface + // area of the light, which for an area light is equivalent to multiplying + // by the area pdf, which is itself the reciprocal of the surface area + if (light.normalize && ss.invPdfA != 0) { + Le /= ss.invPdfA; + } + + // Apply focus shaping + if (light.shaping.focus > 0.0f) { + const float ff = powf(GfAbs(cosThetaOffZ), light.shaping.focus); + const GfVec3f focusTint = GfLerp(ff, light.shaping.focusTint, + GfVec3f(1.0f)); + Le = GfCompMult(Le, focusTint); + } + + // Apply cone shaping + const float thetaCone = GfDegreesToRadians(light.shaping.coneAngle); + const float thetaSoft = GfLerp(light.shaping.coneSoftness, thetaCone, 0.0f); + const float thetaOffZ = acosf(cosThetaOffZ); + Le *= 1.0f - _Smoothstep(thetaOffZ, GfRange1f(thetaSoft, thetaCone)); + + return _LightSample { + Le, + wI, + dist, + invPdfW + }; +} + +class _LightSampler { +public: + static _LightSample GetLightSample(HdEmbree_LightData const& lightData, + GfVec3f const& hitPosition, + GfVec3f const& normal, + float u1, + float u2) + { + _LightSampler lightSampler(lightData, hitPosition, normal, u1, u2); + return std::visit(lightSampler, lightData.lightVariant); + } + + // callables to be used with std::visit + _LightSample operator()(HdEmbree_UnknownLight const& rect) { + // Could warn, but we should have already warned when lightVariant + // first created / set to HdEmbree_UnknownLight... and warning here + // could result in a LOT of spam + return _LightSample { + GfVec3f(0.0f), + GfVec3f(0.0f), + 0.0f, + 0.0f, + }; + } + + _LightSample operator()(HdEmbree_Rect const& rect) { + _ShapeSample shapeSample = _SampleRect( + _lightData.xformLightToWorld, + _lightData.normalXformLightToWorld, + rect.width, + rect.height, + _u1, + _u2); + return _EvalAreaLight(_lightData, shapeSample, _hitPosition); + } + + _LightSample operator()(HdEmbree_Sphere const& sphere) { + _ShapeSample shapeSample = _SampleSphere( + _lightData.xformLightToWorld, + _lightData.normalXformLightToWorld, + sphere.radius, + _u1, + _u2); + return _EvalAreaLight(_lightData, shapeSample, _hitPosition); + } + + _LightSample operator()(HdEmbree_Disk const& disk) { + _ShapeSample shapeSample = _SampleDisk( + _lightData.xformLightToWorld, + _lightData.normalXformLightToWorld, + disk.radius, + _u1, + _u2); + return _EvalAreaLight(_lightData, shapeSample, _hitPosition); + } + + _LightSample operator()(HdEmbree_Cylinder const& cylinder) { + _ShapeSample shapeSample = _SampleCylinder( + _lightData.xformLightToWorld, + _lightData.normalXformLightToWorld, + cylinder.radius, + cylinder.length, + _u1, + _u2); + return _EvalAreaLight(_lightData, shapeSample, _hitPosition); + } + +private: + _LightSampler(HdEmbree_LightData const& lightData, + GfVec3f const& hitPosition, + GfVec3f const& normal, + float u1, + float u2) : + _lightData(lightData), + _hitPosition(hitPosition), + _normal(normal), + _u1(u1), + _u2(u2) + {} + + HdEmbree_LightData const& _lightData; + GfVec3f const& _hitPosition; + GfVec3f const& _normal; + float _u1; + float _u2; +}; + } // anonymous namespace PXR_NAMESPACE_OPEN_SCOPE @@ -211,6 +649,22 @@ HdEmbreeRenderer::SetAovBindings( _aovBindingsNeedValidation = true; } + +void +HdEmbreeRenderer::AddLight(SdfPath const& lightPath, + HdEmbree_Light* light) +{ + ScopedLock lightsWriteLock(_lightsWriteMutex); + _lightMap[lightPath] = light; +} + +void +HdEmbreeRenderer::RemoveLight(SdfPath const& lightPath, HdEmbree_Light* light) +{ + ScopedLock lightsWriteLock(_lightsWriteMutex); + _lightMap.erase(lightPath); +} + bool HdEmbreeRenderer::_ValidateAovBindings() { @@ -452,7 +906,7 @@ _IsContained(const GfRect2i &rect, int width, int height) } void -HdEmbreeRenderer::Render(HdRenderThread *renderThread) +HdEmbreeRenderer::_PreRenderSetup() { _completedSamples.store(0); @@ -503,9 +957,14 @@ HdEmbreeRenderer::Render(HdRenderThread *renderThread) if (!_IsContained(_dataWindow, _width, _height)) { TF_CODING_ERROR( "dataWindow is larger than render buffer"); - } } +} + +void +HdEmbreeRenderer::Render(HdRenderThread *renderThread) +{ + _PreRenderSetup(); // Render the image. Each pass through the loop adds a sample per pixel // (with jittered ray direction); the longer the loop runs, the less noisy @@ -617,7 +1076,9 @@ HdEmbreeRenderer::_RenderTiles(HdRenderThread *renderThread, int sampleNum, // Create a uniform distribution for jitter calculations. std::uniform_real_distribution uniform_dist(0.0f, 1.0f); - auto uniform_float = [&random, &uniform_dist]() { return uniform_dist(random); }; + auto uniform_float = [&random, &uniform_dist]() { + return uniform_dist(random); + }; // _RenderTiles gets a range of tiles; iterate through them. for (unsigned int tile = tileStart; tile < tileEnd; ++tile) { @@ -641,7 +1102,6 @@ HdEmbreeRenderer::_RenderTiles(HdRenderThread *renderThread, int sampleNum, // Loop over pixels casting rays. for (unsigned int y = y0; y < y1; ++y) { for (unsigned int x = x0; x < x1; ++x) { - // Jitter the camera ray direction. GfVec2f jitter(0.0f, 0.0f); if (HdEmbreeConfig::GetInstance().jitterCamera) { @@ -655,25 +1115,25 @@ HdEmbreeRenderer::_RenderTiles(HdRenderThread *renderThread, int sampleNum, const float h(_dataWindow.GetHeight()); const GfVec3f ndc( - 2 * ((x + jitter[0] - minX) / w) - 1, - 2 * ((y + jitter[1] - minY) / h) - 1, - -1); + 2.0f * ((x + jitter[0] - minX) / w) - 1.0f, + 2.0f * ((y + jitter[1] - minY) / h) - 1.0f, + -1.0f); const GfVec3f nearPlaneTrace(_inverseProjMatrix.Transform(ndc)); GfVec3f origin; GfVec3f dir; - const bool isOrthographic = round(_projMatrix[3][3]) == 1; + const bool isOrthographic = round(_projMatrix[3][3]) == 1.0; if (isOrthographic) { // During orthographic projection: trace parallel rays // from the near plane trace. origin = nearPlaneTrace; - dir = GfVec3f(0,0,-1); + dir = GfVec3f(0.0f, 0.0f, -1.0f); } else { // Otherwise, assume this is a perspective projection; // project from the camera origin through the // near plane trace. - origin = GfVec3f(0,0,0); + origin = GfVec3f(0.0f, 0.0f, 0.0f); dir = nearPlaneTrace; } // Transform camera rays to world space. @@ -691,7 +1151,9 @@ HdEmbreeRenderer::_RenderTiles(HdRenderThread *renderThread, int sampleNum, /// Fill in an RTCRay structure from the given parameters. static void _PopulateRay(RTCRay *ray, GfVec3f const& origin, - GfVec3f const& dir, float nearest) + GfVec3f const& dir, float nearest, + float furthest = std::numeric_limits::infinity(), + HdEmbree_RayMask mask = HdEmbree_RayMask::All) { ray->org_x = origin[0]; ray->org_y = origin[1]; @@ -703,25 +1165,26 @@ _PopulateRay(RTCRay *ray, GfVec3f const& origin, ray->dir_z = dir[2]; ray->time = 0.0f; - ray->tfar = std::numeric_limits::infinity(); - ray->mask = -1; + ray->tfar = furthest; + ray->mask = static_cast(mask); } /// Fill in an RTCRayHit structure from the given parameters. // note this containts a Ray and a RayHit static void _PopulateRayHit(RTCRayHit* rayHit, GfVec3f const& origin, - GfVec3f const& dir, float nearest) + GfVec3f const& dir, float nearest, + float furthest = std::numeric_limits::infinity(), + HdEmbree_RayMask mask = HdEmbree_RayMask::All) { // Fill in defaults for the ray - _PopulateRay(&rayHit->ray, origin, dir, nearest); + _PopulateRay(&rayHit->ray, origin, dir, nearest, furthest, mask); // Fill in defaults for the hit rayHit->hit.primID = RTC_INVALID_GEOMETRY_ID; rayHit->hit.geomID = RTC_INVALID_GEOMETRY_ID; } - /// Generate a random cosine-weighted direction ray (in the hemisphere /// around <0,0,1>). The input is a pair of uniformly distributed random /// numbers in the range [0,1]. @@ -732,7 +1195,7 @@ static GfVec3f _CosineWeightedDirection(GfVec2f const& uniform_float) { GfVec3f dir; - float theta = 2.0f * M_PI * uniform_float[0]; + float theta = 2.0f * _pi * uniform_float[0]; float eta = uniform_float[1]; float sqrteta = sqrtf(eta); dir[0] = cosf(theta) * sqrteta; @@ -749,7 +1212,9 @@ HdEmbreeRenderer::_TraceRay(unsigned int x, unsigned int y, // Intersect the camera ray. RTCRayHit rayHit; // EMBREE_FIXME: use RTCRay for occlusion rays rayHit.ray.flags = 0; - _PopulateRayHit(&rayHit, origin, dir, 0.0f); + _PopulateRayHit(&rayHit, origin, dir, 0.0f, + std::numeric_limits::max(), + HdEmbree_RayMask::Camera); { RTCIntersectContext context; rtcInitIntersectContext(&context); @@ -830,11 +1295,13 @@ HdEmbreeRenderer::_ComputeId(RTCRayHit const& rayHit, TfToken const& idType, // flatten everything in hydra. So instID[0] should always be correct. const HdEmbreeInstanceContext *instanceContext = static_cast( - rtcGetGeometryUserData(rtcGetGeometry(_scene, rayHit.hit.instID[0]))); + rtcGetGeometryUserData(rtcGetGeometry(_scene, + rayHit.hit.instID[0]))); const HdEmbreePrototypeContext *prototypeContext = static_cast( - rtcGetGeometryUserData(rtcGetGeometry(instanceContext->rootScene,rayHit.hit.geomID))); + rtcGetGeometryUserData(rtcGetGeometry(instanceContext->rootScene, + rayHit.hit.geomID))); if (idType == HdAovTokens->primId) { *id = prototypeContext->rprim->GetPrimId(); @@ -890,11 +1357,14 @@ HdEmbreeRenderer::_ComputeNormal(RTCRayHit const& rayHit, // flatten everything in hydra. So instID[0] should always be correct. const HdEmbreeInstanceContext *instanceContext = static_cast( - rtcGetGeometryUserData(rtcGetGeometry(_scene,rayHit.hit.instID[0]))); + rtcGetGeometryUserData(rtcGetGeometry(_scene, + rayHit.hit.instID[0]))); const HdEmbreePrototypeContext *prototypeContext = static_cast( - rtcGetGeometryUserData(rtcGetGeometry(instanceContext->rootScene,rayHit.hit.geomID))); + rtcGetGeometryUserData( + rtcGetGeometry(instanceContext->rootScene, + rayHit.hit.geomID))); GfVec3f n = -GfVec3f(rayHit.hit.Ng_x, rayHit.hit.Ng_y, rayHit.hit.Ng_z); auto it = prototypeContext->primvarMap.find(HdTokens->normals); @@ -925,27 +1395,33 @@ HdEmbreeRenderer::_ComputePrimvar(RTCRayHit const& rayHit, // flatten everything in hydra. So instID[0] should always be correct. const HdEmbreeInstanceContext *instanceContext = static_cast( - rtcGetGeometryUserData(rtcGetGeometry(_scene,rayHit.hit.instID[0]))); + rtcGetGeometryUserData(rtcGetGeometry(_scene, + rayHit.hit.instID[0]))); const HdEmbreePrototypeContext *prototypeContext = static_cast( - rtcGetGeometryUserData(rtcGetGeometry(instanceContext->rootScene,rayHit.hit.geomID))); + rtcGetGeometryUserData( + rtcGetGeometry(instanceContext->rootScene, + rayHit.hit.geomID))); // XXX: This is a little clunky, although sample will early out if the // types don't match. auto it = prototypeContext->primvarMap.find(primvar); if (it != prototypeContext->primvarMap.end()) { const HdEmbreePrimvarSampler *sampler = it->second; - if (sampler->Sample(rayHit.hit.primID, rayHit.hit.u, rayHit.hit.v, value)) { + if (sampler->Sample(rayHit.hit.primID, rayHit.hit.u, rayHit.hit.v, + value)) { return true; } GfVec2f v2; - if (sampler->Sample(rayHit.hit.primID, rayHit.hit.u, rayHit.hit.v, &v2)) { + if (sampler->Sample(rayHit.hit.primID, rayHit.hit.u, rayHit.hit.v, + &v2)) { value->Set(v2[0], v2[1], 0.0f); return true; } float v1; - if (sampler->Sample(rayHit.hit.primID, rayHit.hit.u, rayHit.hit.v, &v1)) { + if (sampler->Sample(rayHit.hit.primID, rayHit.hit.u, rayHit.hit.v, + &v1)) { value->Set(v1, 0.0f, 0.0f); return true; } @@ -953,32 +1429,52 @@ HdEmbreeRenderer::_ComputePrimvar(RTCRayHit const& rayHit, return false; } +float +HdEmbreeRenderer::_Visibility( + GfVec3f const& position, GfVec3f const& direction, float dist) const +{ + RTCRay shadow; + shadow.flags = 0; + _PopulateRay(&shadow, position, direction, 0.001f, dist, + HdEmbree_RayMask::Shadow); + { + RTCIntersectContext context; + rtcInitIntersectContext(&context); + rtcOccluded1(_scene,&context,&shadow); + } + // XXX: what do we do about shadow visibility (continuation) here? + // probably need to use rtcIntersect instead of rtcOccluded + + // occluded sets tfar < 0 if the ray hit anything + return shadow.tfar > 0.0f; +} + GfVec4f HdEmbreeRenderer::_ComputeColor(RTCRayHit const& rayHit, std::default_random_engine &random, GfVec4f const& clearColor) { - if (rayHit.hit.geomID == RTC_INVALID_GEOMETRY_ID) { - return clearColor; - } - // Get the instance and prototype context structures for the hit prim. // We don't use embree's multi-level instancing; we // flatten everything in hydra. So instID[0] should always be correct. const HdEmbreeInstanceContext *instanceContext = static_cast( - rtcGetGeometryUserData(rtcGetGeometry(_scene,rayHit.hit.instID[0]))); + rtcGetGeometryUserData(rtcGetGeometry(_scene, + rayHit.hit.instID[0]))); const HdEmbreePrototypeContext *prototypeContext = static_cast( - rtcGetGeometryUserData(rtcGetGeometry(instanceContext->rootScene,rayHit.hit.geomID))); + rtcGetGeometryUserData( + rtcGetGeometry(instanceContext->rootScene, + rayHit.hit.geomID))); // Compute the worldspace location of the rayHit hit. GfVec3f hitPos = _CalculateHitPosition(rayHit); // If a normal primvar is present (e.g. from smooth shading), use that // for shading; otherwise use the flat face normal. - GfVec3f normal = -GfVec3f(rayHit.hit.Ng_x, rayHit.hit.Ng_y, rayHit.hit.Ng_z); + GfVec3f normal = -GfVec3f(rayHit.hit.Ng_x, rayHit.hit.Ng_y, + rayHit.hit.Ng_z); auto it = prototypeContext->primvarMap.find(HdTokens->normals); if (it != prototypeContext->primvarMap.end()) { it->second->Sample( @@ -987,12 +1483,12 @@ HdEmbreeRenderer::_ComputeColor(RTCRayHit const& rayHit, // If a color primvar is present, use that as diffuse color; otherwise, // use flat grey. - GfVec3f color = GfVec3f(0.5f, 0.5f, 0.5f); + GfVec3f materialColor = _invalidColor; if (_enableSceneColors) { auto it = prototypeContext->primvarMap.find(HdTokens->displayColor); if (it != prototypeContext->primvarMap.end()) { it->second->Sample( - rayHit.hit.primID, rayHit.hit.u, rayHit.hit.v, &color); + rayHit.hit.primID, rayHit.hit.u, rayHit.hit.v, &materialColor); } } @@ -1002,38 +1498,63 @@ HdEmbreeRenderer::_ComputeColor(RTCRayHit const& rayHit, // Make sure the normal is unit-length. normal.Normalize(); - // Lighting model: (camera dot normal), i.e. diffuse-only point light - // centered on the camera. - GfVec3f dir = GfVec3f(rayHit.ray.dir_x, rayHit.ray.dir_y, rayHit.ray.dir_z); - float diffuseLight = fabs(GfDot(-dir, normal)) * - HdEmbreeConfig::GetInstance().cameraLightIntensity; + GfVec3f lightingColor(0.0f); - // Lighting gets modulated by an ambient occlusion term. - float aoLightIntensity = - _ComputeAmbientOcclusion(hitPos, normal, random); + // If there are no lights, then keep the existing camera light + AO path to + // be able to inspect the scene + if (_lightMap.empty()) + { + // For ambient occlusion, default material is flat 50% gray + if (materialColor == _invalidColor) { + materialColor = GfVec3f(.5f); + } + + // Lighting model: (camera dot normal), i.e. diffuse-only point light + // centered on the camera. + GfVec3f dir = GfVec3f(rayHit.ray.dir_x, rayHit.ray.dir_y, + rayHit.ray.dir_z); + float diffuseLight = fabs(GfDot(-dir, normal)) * + HdEmbreeConfig::GetInstance().cameraLightIntensity; - // XXX: We should support opacity here... + // Lighting gets modulated by an ambient occlusion term. + float aoLightIntensity = + _ComputeAmbientOcclusion(hitPos, normal, random); - // Return color * diffuseLight * aoLightIntensity. - GfVec3f finalColor = color * diffuseLight * aoLightIntensity; + // XXX: We should support opacity here... - // Clamp colors to [0,1]. + lightingColor = GfVec3f(diffuseLight * aoLightIntensity); + } + else + { + // For lighting, default material is 100% white + if (materialColor == _invalidColor) { + materialColor = GfVec3f(1.0f); + } + + lightingColor = _ComputeLighting( + hitPos, normal,random, prototypeContext); + } + const GfVec3f finalColor = GfCompMult(materialColor, lightingColor); + + // Clamp colors to > 0 GfVec4f output; - output[0] = std::max(0.0f, std::min(1.0f, finalColor[0])); - output[1] = std::max(0.0f, std::min(1.0f, finalColor[1])); - output[2] = std::max(0.0f, std::min(1.0f, finalColor[2])); + output[0] = std::max(0.0f, finalColor[0]); + output[1] = std::max(0.0f, finalColor[1]); + output[2] = std::max(0.0f, finalColor[2]); output[3] = 1.0f; return output; } float HdEmbreeRenderer::_ComputeAmbientOcclusion(GfVec3f const& position, - GfVec3f const& normal, - std::default_random_engine &random) + GfVec3f const& normal, + std::default_random_engine &random) { // Create a uniform random distribution for AO calculations. std::uniform_real_distribution uniform_dist(0.0f, 1.0f); - auto uniform_float = [&random, &uniform_dist]() { return uniform_dist(random); }; + auto uniform_float = [&random, &uniform_dist]() { + return uniform_dist(random); + }; // 0 ambient occlusion samples means disable the ambient occlusion term. if (_ambientOcclusionSamples < 1) { @@ -1046,12 +1567,12 @@ HdEmbreeRenderer::_ComputeAmbientOcclusion(GfVec3f const& position, // point. For the purposes of _CosineWeightedDirection, the normal needs // to map to (0,0,1), but since the distribution is radially symmetric // we don't care about the other axes. - GfMatrix3f basis(1); + GfMatrix3f basis(1.0f); GfVec3f xAxis; - if (fabsf(GfDot(normal, GfVec3f(0,0,1))) < 0.9f) { - xAxis = GfCross(normal, GfVec3f(0,0,1)); + if (fabsf(GfDot(normal, GfVec3f(0.0f,0.0f,1.0f))) < 0.9f) { + xAxis = GfCross(normal, GfVec3f(0.0f,0.0f,1.0f)); } else { - xAxis = GfCross(normal, GfVec3f(0,1,0)); + xAxis = GfCross(normal, GfVec3f(0.0f,1.0f,0.0f)); } GfVec3f yAxis = GfCross(normal, xAxis); basis.SetColumn(0, xAxis.GetNormalized()); @@ -1108,4 +1629,51 @@ HdEmbreeRenderer::_ComputeAmbientOcclusion(GfVec3f const& position, return occlusionFactor; } +GfVec3f +HdEmbreeRenderer::_ComputeLighting( + GfVec3f const& position, + GfVec3f const& normal, + std::default_random_engine &random, + HdEmbreePrototypeContext const* prototypeContext) const +{ + std::uniform_real_distribution uniform_dist(0.0f, 1.0f); + auto uniform_float = [&random, &uniform_dist]() { + return uniform_dist(random); + }; + + GfVec3f finalColor(0.0f); + // For now just a 100% reflective diffuse BRDF + float brdf = 1.0f / _pi; + + // For now just iterate over all lights + /// XXX: simple uniform sampling may be better here + for (auto const& it : _lightMap) + { + auto const& light = it.second->LightData(); + // Skip light if it's hidden + if (!light.visible) + { + continue; + } + + // Sample the light + _LightSample ls = _LightSampler::GetLightSample( + light, position, normal, uniform_float(), uniform_float()); + if (GfIsClose(ls.Li, GfVec3f(0.0f), _minLuminanceCutoff)) { + continue; + } + + // Trace shadow + float vis = _Visibility(position, ls.wI, ls.dist * 0.99f); + + // Add exitant luminance + finalColor += ls.Li + * _DotZeroClip(ls.wI, normal) + * brdf + * vis + * ls.invPdfW; + } + return finalColor; +} + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/plugin/hdEmbree/renderer.h b/pxr/imaging/plugin/hdEmbree/renderer.h index 2da9880848..85a44ad4b2 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.h +++ b/pxr/imaging/plugin/hdEmbree/renderer.h @@ -9,20 +9,35 @@ #include "pxr/pxr.h" +#include "pxr/imaging/plugin/hdEmbree/context.h" +#include "pxr/imaging/plugin/hdEmbree/light.h" + +#include "pxr/imaging/hd/aov.h" #include "pxr/imaging/hd/renderThread.h" -#include "pxr/imaging/hd/renderPassState.h" #include "pxr/base/gf/matrix4d.h" #include "pxr/base/gf/rect2i.h" #include +#include #include #include #include +#include +#include PXR_NAMESPACE_OPEN_SCOPE +enum HdEmbree_RayMask: uint32_t { + None = 0, + + Camera = 1 << 0, + Shadow = 1 << 1, + + All = UINT_MAX, +}; + /// \class HdEmbreeRenderer /// /// HdEmbreeRenderer implements a renderer on top of Embree's raycasting @@ -37,6 +52,9 @@ PXR_NAMESPACE_OPEN_SCOPE class HdEmbreeRenderer final { public: + using WriteMutex = std::mutex; + using ScopedLock = std::scoped_lock; + /// Renderer constructor. HdEmbreeRenderer(); @@ -60,6 +78,12 @@ class HdEmbreeRenderer final /// \param aovBindings A list of aov bindings. void SetAovBindings(HdRenderPassAovBindingVector const &aovBindings); + /// Add a light + void AddLight(SdfPath const& lightPath, HdEmbree_Light* light); + + /// Remove a light + void RemoveLight(SdfPath const& lightPath, HdEmbree_Light* light); + /// Get the aov bindings being used for rendering. /// \return the current aov bindings. HdRenderPassAovBindingVector const& GetAovBindings() const { @@ -104,6 +128,9 @@ class HdEmbreeRenderer final int GetCompletedSamples() const; private: + // Perform validation and setup immediately before starting a render + void _PreRenderSetup(); + // Validate the internal consistency of aov bindings provided to // SetAovBindings. If the aov bindings are invalid, this will issue // appropriate warnings. If the function returns false, Render() will fail @@ -154,6 +181,22 @@ class HdEmbreeRenderer final GfVec3f const& normal, std::default_random_engine &random); + ///If the scene has lights, sample them to return the color at a given + ///position + GfVec3f _ComputeLighting( + GfVec3f const& position, + GfVec3f const& normal, + std::default_random_engine &random, + HdEmbreePrototypeContext const* prototypeContext) const; + + // Return the visibility from `position` along `direction` + float _Visibility(GfVec3f const& position, + GfVec3f const& direction, + float offset = 1.0e-3f) const; + + // Should the ray continue based on the possibly intersected prim's visibility settings? + bool _RayShouldContinue(RTCRayHit const& rayHit) const; + // The bound aovs for this renderer. HdRenderPassAovBindingVector _aovBindings; // Parsed AOV name tokens. @@ -195,6 +238,10 @@ class HdEmbreeRenderer final // How many samples have been completed. std::atomic _completedSamples; + + // Lights + mutable WriteMutex _lightsWriteMutex; // protects the 2 below + std::map _lightMap; }; PXR_NAMESPACE_CLOSE_SCOPE From 157b0b5f7f68acaab804c02756565c4a92bea18b Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 29 Jul 2024 01:08:53 -0700 Subject: [PATCH 05/11] [hdEmbree] add HDEMBREE_LIGHT_CREATE debug code --- pxr/imaging/plugin/hdEmbree/CMakeLists.txt | 1 + pxr/imaging/plugin/hdEmbree/debugCodes.cpp | 20 ++++++++++++++++++++ pxr/imaging/plugin/hdEmbree/debugCodes.h | 21 +++++++++++++++++++++ pxr/imaging/plugin/hdEmbree/light.cpp | 2 ++ 4 files changed, 44 insertions(+) create mode 100644 pxr/imaging/plugin/hdEmbree/debugCodes.cpp create mode 100644 pxr/imaging/plugin/hdEmbree/debugCodes.h diff --git a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt index 2efef685eb..c3c92aa552 100644 --- a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt +++ b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt @@ -46,6 +46,7 @@ pxr_plugin(hdEmbree renderParam.h PRIVATE_CLASSES + debugCodes implicitSurfaceSceneIndexPlugin RESOURCE_FILES diff --git a/pxr/imaging/plugin/hdEmbree/debugCodes.cpp b/pxr/imaging/plugin/hdEmbree/debugCodes.cpp new file mode 100644 index 0000000000..e38f776489 --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/debugCodes.cpp @@ -0,0 +1,20 @@ +// +// Copyright 2024 Pixar +// +// Licensed under the terms set forth in the LICENSE.txt file available at +// https://openusd.org/license. +// +#include "pxr/pxr.h" +#include "pxr/imaging/plugin/hdEmbree/debugCodes.h" + +#include "pxr/base/tf/debug.h" +#include "pxr/base/tf/registryManager.h" + +PXR_NAMESPACE_OPEN_SCOPE + +TF_REGISTRY_FUNCTION(TfDebug) +{ + TF_DEBUG_ENVIRONMENT_SYMBOL(HDEMBREE_LIGHT_CREATE, "Creation of HdEmbree lights"); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/plugin/hdEmbree/debugCodes.h b/pxr/imaging/plugin/hdEmbree/debugCodes.h new file mode 100644 index 0000000000..c65002452b --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/debugCodes.h @@ -0,0 +1,21 @@ +// +// Copyright 2024 Pixar +// +// Licensed under the terms set forth in the LICENSE.txt file available at +// https://openusd.org/license. +// +#ifndef PXR_IMAGING_PLUGIN_HD_EMBREE_DEBUG_CODES_H +#define PXR_IMAGING_PLUGIN_HD_EMBREE_DEBUG_CODES_H + +#include "pxr/pxr.h" +#include "pxr/base/tf/debug.h" + +PXR_NAMESPACE_OPEN_SCOPE + +TF_DEBUG_CODES( + HDEMBREE_LIGHT_CREATE +); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // PXR_IMAGING_PLUGIN_HD_EMBREE_DEBUG_CODES_H diff --git a/pxr/imaging/plugin/hdEmbree/light.cpp b/pxr/imaging/plugin/hdEmbree/light.cpp index 082e4605d2..b1555a5ba2 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -29,6 +29,8 @@ HdEmbree_Light::HdEmbree_Light(SdfPath const& id, TfToken const& lightType) return; } + TF_DEBUG(HDEMBREE_LIGHT_CREATE).Msg("Creating light %s: %s\n", id.GetText(), lightType.GetText()); + // Set the variant to the right type - Sync will fill rest of data if (lightType == HdSprimTypeTokens->cylinderLight) { _lightData.lightVariant = HdEmbree_Cylinder(); From 7923e8ca1e59810fcd3e68a2445de8d96329841a Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 22 Jul 2024 05:16:48 -0700 Subject: [PATCH 06/11] [hdEmbree] add support for lighting double-sided meshes --- pxr/imaging/plugin/hdEmbree/mesh.h | 5 +++++ pxr/imaging/plugin/hdEmbree/renderer.cpp | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pxr/imaging/plugin/hdEmbree/mesh.h b/pxr/imaging/plugin/hdEmbree/mesh.h index bbb006302f..2d1ff9a257 100644 --- a/pxr/imaging/plugin/hdEmbree/mesh.h +++ b/pxr/imaging/plugin/hdEmbree/mesh.h @@ -99,6 +99,11 @@ class HdEmbreeMesh final : public HdMesh { /// embree state. virtual void Finalize(HdRenderParam *renderParam) override; + bool EmbreeMeshIsDoubleSided() const + { + return _doubleSided; + } + protected: // Initialize the given representation of this Rprim. // This is called prior to syncing the prim, the first time the repr diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 47f93cf498..88603a7059 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -1667,8 +1667,23 @@ HdEmbreeRenderer::_ComputeLighting( float vis = _Visibility(position, ls.wI, ls.dist * 0.99f); // Add exitant luminance + float cosOffNormal = GfDot(ls.wI, normal); + if (cosOffNormal < 0.0f) { + bool doubleSided = false; + HdEmbreeMesh *mesh = + dynamic_cast(prototypeContext->rprim); + if (mesh) { + doubleSided = mesh->EmbreeMeshIsDoubleSided(); + } + + if (doubleSided) { + cosOffNormal *= -1.0f; + } else { + cosOffNormal = 0.0f; + } + } finalColor += ls.Li - * _DotZeroClip(ls.wI, normal) + * cosOffNormal * brdf * vis * ls.invPdfW; From 845eca75c6d0382b59fab97b6a0f067eba58dde4 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 22:10:02 -0700 Subject: [PATCH 07/11] [hdEmbree] add support for inputs:diffuse --- pxr/imaging/plugin/hdEmbree/light.cpp | 2 ++ pxr/imaging/plugin/hdEmbree/light.h | 1 + pxr/imaging/plugin/hdEmbree/renderer.cpp | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pxr/imaging/plugin/hdEmbree/light.cpp b/pxr/imaging/plugin/hdEmbree/light.cpp index b1555a5ba2..ee03567894 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -75,6 +75,8 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, // Store luminance parameters _lightData.intensity = sceneDelegate->GetLightParamValue( id, HdLightTokens->intensity).GetWithDefault(1.0f); + _lightData.diffuse = sceneDelegate->GetLightParamValue( + id, HdLightTokens->diffuse).GetWithDefault(1.0f); _lightData.exposure = sceneDelegate->GetLightParamValue( id, HdLightTokens->exposure).GetWithDefault(0.0f); _lightData.color = sceneDelegate->GetLightParamValue( diff --git a/pxr/imaging/plugin/hdEmbree/light.h b/pxr/imaging/plugin/hdEmbree/light.h index 906ba185bc..2dd44d0898 100644 --- a/pxr/imaging/plugin/hdEmbree/light.h +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -68,6 +68,7 @@ struct HdEmbree_LightData GfMatrix4f xformWorldToLight; GfVec3f color; float intensity = 1.0f; + float diffuse = 1.0f; float exposure = 0.0f; float colorTemperature = 6500.0f; bool enableColorTemperature = false; diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 88603a7059..fa2d8074e9 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -412,7 +412,9 @@ _SampleCylinder(GfMatrix4f const& xf, GfMatrix3f const& normalXform, GfVec3f _EvalLightBasic(HdEmbree_LightData const& light) { - GfVec3f Le = light.color * light.intensity * powf(2.0f, light.exposure); + // Our current material model is always 100% diffuse, so diffuse parameter + // is a stright multiplier + GfVec3f Le = light.color * light.intensity * light.diffuse * powf(2.0f, light.exposure); if (light.enableColorTemperature) { Le = GfCompMult(Le, _BlackbodyTemperatureAsRgb(light.colorTemperature)); From 6dbe06bc1d2dbc3c987ea9d19422e63152208f9a Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 00:27:19 -0700 Subject: [PATCH 08/11] [hdEmbree] add light texture support --- pxr/imaging/plugin/hdEmbree/light.cpp | 56 ++++++++++++++++++++++++ pxr/imaging/plugin/hdEmbree/light.h | 8 ++++ pxr/imaging/plugin/hdEmbree/renderer.cpp | 19 ++++++++ 3 files changed, 83 insertions(+) diff --git a/pxr/imaging/plugin/hdEmbree/light.cpp b/pxr/imaging/plugin/hdEmbree/light.cpp index ee03567894..e8bcbd88eb 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -21,6 +21,61 @@ #include #include +namespace { + +PXR_NAMESPACE_USING_DIRECTIVE + +HdEmbree_LightTexture +_LoadLightTexture(std::string const& path) +{ + if (path.empty()) { + return HdEmbree_LightTexture(); + } + + HioImageSharedPtr img = HioImage::OpenForReading(path); + if (!img) { + return HdEmbree_LightTexture(); + } + + int width = img->GetWidth(); + int height = img->GetHeight(); + + std::vector pixels(width * height * 3.0f); + + HioImage::StorageSpec storage; + storage.width = width; + storage.height = height; + storage.depth = 1; + storage.format = HioFormatFloat32Vec3; + storage.data = &pixels.front(); + + if (img->Read(storage)) { + return {std::move(pixels), width, height}; + } + TF_WARN("Could not read image %s", path.c_str()); + return { std::vector(), 0, 0 }; +} + + +void +_SyncLightTexture(const SdfPath& id, HdEmbree_LightData& light, HdSceneDelegate *sceneDelegate) +{ + std::string path; + if (VtValue textureValue = sceneDelegate->GetLightParamValue( + id, HdLightTokens->textureFile); + textureValue.IsHolding()) { + SdfAssetPath texturePath = + textureValue.UncheckedGet(); + path = texturePath.GetResolvedPath(); + if (path.empty()) { + path = texturePath.GetAssetPath(); + } + } + light.texture = _LoadLightTexture(path); +} + + +} // anonymous namespace PXR_NAMESPACE_OPEN_SCOPE HdEmbree_Light::HdEmbree_Light(SdfPath const& id, TfToken const& lightType) @@ -116,6 +171,7 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, sceneDelegate->GetLightParamValue(id, HdLightTokens->height) .Get(), }; + _SyncLightTexture(id, _lightData, sceneDelegate); } else if constexpr (std::is_same_v) { typedLight = HdEmbree_Sphere{ sceneDelegate->GetLightParamValue(id, HdLightTokens->radius) diff --git a/pxr/imaging/plugin/hdEmbree/light.h b/pxr/imaging/plugin/hdEmbree/light.h index 2dd44d0898..fece3056d8 100644 --- a/pxr/imaging/plugin/hdEmbree/light.h +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -53,6 +53,13 @@ using HdEmbree_LightVariant = std::variant< HdEmbree_Rect, HdEmbree_Sphere>; +struct HdEmbree_LightTexture +{ + std::vector pixels; + int width = 0; + int height = 0; +}; + struct HdEmbree_Shaping { GfVec3f focusTint; @@ -67,6 +74,7 @@ struct HdEmbree_LightData GfMatrix3f normalXformLightToWorld; GfMatrix4f xformWorldToLight; GfVec3f color; + HdEmbree_LightTexture texture; float intensity = 1.0f; float diffuse = 1.0f; float exposure = 0.0f; diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index fa2d8074e9..281d091e8c 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -294,6 +294,19 @@ struct _LightSample { float invPdfW; }; +GfVec3f +_SampleLightTexture(HdEmbree_LightTexture const& texture, float s, float t) +{ + if (texture.pixels.empty()) { + return GfVec3f(0.0f); + } + + int x = float(texture.width) * s; + int y = float(texture.height) * t; + + return texture.pixels.at(y*texture.width + x); +} + _ShapeSample _SampleRect(GfMatrix4f const& xf, GfMatrix3f const& normalXform, float width, float height, float u1, float u2) @@ -443,6 +456,12 @@ _EvalAreaLight(HdEmbree_LightData const& light, _ShapeSample const& ss, _EvalLightBasic(light) : GfVec3f(0.0f); + // Multiply by the texture, if there is one + if (!light.texture.pixels.empty()) { + Le = GfCompMult(Le, _SampleLightTexture(light.texture, ss.uv[0], + 1.0f - ss.uv[1])); + } + // If normalize is enabled, we need to divide the luminance by the surface // area of the light, which for an area light is equivalent to multiplying // by the area pdf, which is itself the reciprocal of the surface area From f6e88fa2b7d0d7a7e13d32bf1d318ff62287b1f1 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 00:27:48 -0700 Subject: [PATCH 09/11] [hdEmbree] add dome light suppport --- pxr/imaging/plugin/hdEmbree/light.cpp | 5 ++ pxr/imaging/plugin/hdEmbree/light.h | 9 +++ .../plugin/hdEmbree/renderDelegate.cpp | 3 + pxr/imaging/plugin/hdEmbree/renderer.cpp | 73 +++++++++++++++++++ pxr/imaging/plugin/hdEmbree/renderer.h | 1 + 5 files changed, 91 insertions(+) diff --git a/pxr/imaging/plugin/hdEmbree/light.cpp b/pxr/imaging/plugin/hdEmbree/light.cpp index e8bcbd88eb..a556d385d3 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -91,6 +91,8 @@ HdEmbree_Light::HdEmbree_Light(SdfPath const& id, TfToken const& lightType) _lightData.lightVariant = HdEmbree_Cylinder(); } else if (lightType == HdSprimTypeTokens->diskLight) { _lightData.lightVariant = HdEmbree_Disk(); + } else if (lightType == HdSprimTypeTokens->domeLight) { + _lightData.lightVariant = HdEmbree_Dome(); } else if (lightType == HdSprimTypeTokens->rectLight) { // Get shape parameters _lightData.lightVariant = HdEmbree_Rect(); @@ -164,6 +166,9 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, sceneDelegate->GetLightParamValue(id, HdLightTokens->radius) .GetWithDefault(0.5f), }; + } else if constexpr (std::is_same_v) { + typedLight = HdEmbree_Dome{}; + _SyncLightTexture(id, _lightData, sceneDelegate); } else if constexpr (std::is_same_v) { typedLight = HdEmbree_Rect{ sceneDelegate->GetLightParamValue(id, HdLightTokens->width) diff --git a/pxr/imaging/plugin/hdEmbree/light.h b/pxr/imaging/plugin/hdEmbree/light.h index fece3056d8..05ab262cb2 100644 --- a/pxr/imaging/plugin/hdEmbree/light.h +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -35,6 +35,10 @@ struct HdEmbree_Disk float radius; }; +// Needed for HdEmbree_LightVariant +struct HdEmbree_Dome +{}; + struct HdEmbree_Rect { float width; @@ -50,6 +54,7 @@ using HdEmbree_LightVariant = std::variant< HdEmbree_UnknownLight, HdEmbree_Cylinder, HdEmbree_Disk, + HdEmbree_Dome, HdEmbree_Rect, HdEmbree_Sphere>; @@ -108,6 +113,10 @@ class HdEmbree_Light final : public HdLight return _lightData; } + bool IsDome() const { + return std::holds_alternative(_lightData.lightVariant); + } + private: HdEmbree_LightData _lightData; }; diff --git a/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp b/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp index 100e9c133a..27cad002da 100644 --- a/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp @@ -38,6 +38,7 @@ const TfTokenVector HdEmbreeRenderDelegate::SUPPORTED_SPRIM_TYPES = HdPrimTypeTokens->extComputation, HdPrimTypeTokens->cylinderLight, HdPrimTypeTokens->diskLight, + HdPrimTypeTokens->domeLight, HdPrimTypeTokens->rectLight, HdPrimTypeTokens->sphereLight, }; @@ -338,6 +339,7 @@ HdEmbreeRenderDelegate::CreateSprim(TfToken const& typeId, return new HdExtComputation(sprimId); } else if (typeId == HdPrimTypeTokens->light || typeId == HdPrimTypeTokens->diskLight || + typeId == HdPrimTypeTokens->domeLight || typeId == HdPrimTypeTokens->rectLight || typeId == HdPrimTypeTokens->sphereLight || typeId == HdPrimTypeTokens->cylinderLight) { @@ -360,6 +362,7 @@ HdEmbreeRenderDelegate::CreateFallbackSprim(TfToken const& typeId) return new HdExtComputation(SdfPath::EmptyPath()); } else if (typeId == HdPrimTypeTokens->light || typeId == HdPrimTypeTokens->diskLight || + typeId == HdPrimTypeTokens->domeLight || typeId == HdPrimTypeTokens->rectLight || typeId == HdPrimTypeTokens->sphereLight || typeId == HdPrimTypeTokens->cylinderLight) { diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 281d091e8c..23e4c72e4b 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -491,6 +491,45 @@ _EvalAreaLight(HdEmbree_LightData const& light, _ShapeSample const& ss, }; } +_LightSample +_SampleDomeLight(HdEmbree_LightData const& light, GfVec3f const& direction) +{ + float t = acosf(direction[1]) / _pi; + float s = atan2f(direction[0], direction[2]) / (2.0f * _pi); + s = 1.0f - fmodf(s+0.5f, 1.0f); + + GfVec3f Li = light.texture.pixels.empty() ? + GfVec3f(1.0f) + : _SampleLightTexture(light.texture, s, t); + + return _LightSample { + Li, + direction, + std::numeric_limits::max(), + 4.0f * _pi + }; +} + +_LightSample +_EvalDomeLight(HdEmbree_LightData const& light, GfVec3f const& W, + float u1, float u2) +{ + GfVec3f U, V; + GfBuildOrthonormalFrame(W, &U, &V); + + float z = u1; + float r = sqrtf(std::max(0.0f, 1.0f - _Sqr(z))); + float phi = 2.0f * _pi * u2; + + const GfVec3f wI = + (W * z + r * cosf(phi) * U + r * sinf(phi) * V).GetNormalized(); + + _LightSample ls = _SampleDomeLight(light, wI); + ls.invPdfW = 2.0f * _pi; // We only picked from the hemisphere + + return ls; +} + class _LightSampler { public: static _LightSample GetLightSample(HdEmbree_LightData const& lightData, @@ -558,6 +597,10 @@ class _LightSampler { return _EvalAreaLight(_lightData, shapeSample, _hitPosition); } + _LightSample operator()(HdEmbree_Dome const& dome) { + return _EvalDomeLight(_lightData, _normal, _u1, _u2); + } + private: _LightSampler(HdEmbree_LightData const& lightData, GfVec3f const& hitPosition, @@ -677,6 +720,10 @@ HdEmbreeRenderer::AddLight(SdfPath const& lightPath, { ScopedLock lightsWriteLock(_lightsWriteMutex); _lightMap[lightPath] = light; + + if (light->IsDome()) { + _domes.push_back(light); + } } void @@ -684,6 +731,9 @@ HdEmbreeRenderer::RemoveLight(SdfPath const& lightPath, HdEmbree_Light* light) { ScopedLock lightsWriteLock(_lightsWriteMutex); _lightMap.erase(lightPath); + _domes.erase(std::remove_if(_domes.begin(), _domes.end(), + [&light](auto& l){ return l == light; }), + _domes.end()); } bool @@ -1475,6 +1525,29 @@ HdEmbreeRenderer::_ComputeColor(RTCRayHit const& rayHit, std::default_random_engine &random, GfVec4f const& clearColor) { + if (rayHit.hit.geomID == RTC_INVALID_GEOMETRY_ID) { + if (_domes.empty()) { + return clearColor; + } + + // if we missed all geometry in the scene, evaluate the infinite lights + // directly + GfVec4f domeColor(0.0f, 0.0f, 0.0f, 1.0f); + + for (auto* dome : _domes) { + _LightSample ls = _SampleDomeLight( + dome->LightData(), + GfVec3f(rayHit.ray.dir_x, + rayHit.ray.dir_y, + rayHit.ray.dir_z) + ); + domeColor[0] += ls.Li[0]; + domeColor[1] += ls.Li[1]; + domeColor[2] += ls.Li[2]; + } + return domeColor; + } + // Get the instance and prototype context structures for the hit prim. // We don't use embree's multi-level instancing; we // flatten everything in hydra. So instID[0] should always be correct. diff --git a/pxr/imaging/plugin/hdEmbree/renderer.h b/pxr/imaging/plugin/hdEmbree/renderer.h index 85a44ad4b2..e5a8426136 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.h +++ b/pxr/imaging/plugin/hdEmbree/renderer.h @@ -242,6 +242,7 @@ class HdEmbreeRenderer final // Lights mutable WriteMutex _lightsWriteMutex; // protects the 2 below std::map _lightMap; + std::vector _domes; }; PXR_NAMESPACE_CLOSE_SCOPE From 2c7c13e35e970f7f739416c17fd613b02d00e1dd Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 00:28:30 -0700 Subject: [PATCH 10/11] [hdEmbree] add direct camera visibility support for rect lights --- pxr/imaging/plugin/hdEmbree/context.h | 4 + pxr/imaging/plugin/hdEmbree/light.cpp | 89 +++++++++++++++++++- pxr/imaging/plugin/hdEmbree/light.h | 6 ++ pxr/imaging/plugin/hdEmbree/mesh.cpp | 1 + pxr/imaging/plugin/hdEmbree/renderer.cpp | 102 +++++++++++++++++++++++ 5 files changed, 200 insertions(+), 2 deletions(-) diff --git a/pxr/imaging/plugin/hdEmbree/context.h b/pxr/imaging/plugin/hdEmbree/context.h index 4165adb1e6..d0d2a96b6a 100644 --- a/pxr/imaging/plugin/hdEmbree/context.h +++ b/pxr/imaging/plugin/hdEmbree/context.h @@ -13,12 +13,14 @@ #include "pxr/base/gf/matrix4f.h" #include "pxr/base/vt/array.h" +#include "pxr/base/vt/types.h" #include PXR_NAMESPACE_OPEN_SCOPE class HdRprim; +class HdEmbree_Light; /// \class HdEmbreePrototypeContext /// @@ -51,6 +53,8 @@ struct HdEmbreeInstanceContext RTCScene rootScene; /// The instance id of this instance. int32_t instanceId; + /// The light (if this is a light) + HdEmbree_Light *light = nullptr; }; diff --git a/pxr/imaging/plugin/hdEmbree/light.cpp b/pxr/imaging/plugin/hdEmbree/light.cpp index a556d385d3..d83d07b59f 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -15,6 +15,7 @@ #include "pxr/imaging/hio/image.h" #include +#include #include #include @@ -78,6 +79,11 @@ _SyncLightTexture(const SdfPath& id, HdEmbree_LightData& light, HdSceneDelegate } // anonymous namespace PXR_NAMESPACE_OPEN_SCOPE +TF_DEFINE_PRIVATE_TOKENS(_tokens, + ((inputsVisibilityCamera, "inputs:visibility:camera")) + ((inputsVisibilityShadow, "inputs:visibility:shadow")) +); + HdEmbree_Light::HdEmbree_Light(SdfPath const& id, TfToken const& lightType) : HdLight(id) { if (id.IsEmpty()) { @@ -117,7 +123,8 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, static_cast(renderParam); // calling this bumps the scene version and causes a re-render - embreeRenderParam->AcquireSceneForEdit(); + RTCScene scene = embreeRenderParam->AcquireSceneForEdit(); + RTCDevice device = embreeRenderParam->GetEmbreeDevice(); SdfPath const& id = GetId(); @@ -147,6 +154,13 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, // Get visibility _lightData.visible = sceneDelegate->GetVisible(id); + _lightData.visible_camera = sceneDelegate->GetLightParamValue( + id, _tokens->inputsVisibilityCamera).GetWithDefault(false); + // XXX: Don't think we can get this to work in Embree unless it's built with + // masking only solution would be to use rtcIntersect instead of rtcOccluded + // for shadow rays, which maybe isn't the worst for a reference renderer + _lightData.visible_shadow = sceneDelegate->GetLightParamValue( + id, _tokens->inputsVisibilityShadow).GetWithDefault(false); // Switch on the _lightData type and pull the relevant attributes from the scene // delegate @@ -211,12 +225,71 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, _lightData.shaping.coneSoftness = value.UncheckedGet(); } + _PopulateRtcLight(device, scene); + HdEmbreeRenderer *renderer = embreeRenderParam->GetRenderer(); renderer->AddLight(id, this); *dirtyBits &= ~HdLight::AllDirty; } +void +HdEmbree_Light::_PopulateRtcLight(RTCDevice device, RTCScene scene) +{ + _lightData.rtcMeshId = RTC_INVALID_GEOMETRY_ID; + + // create the light geometry, if required + if (_lightData.visible) { + if (auto* rect = std::get_if(&_lightData.lightVariant)) + { + // create _lightData mesh + GfVec3f v0(-rect->width/2.0f, -rect->height/2.0f, 0.0f); + GfVec3f v1( rect->width/2.0f, -rect->height/2.0f, 0.0f); + GfVec3f v2( rect->width/2.0f, rect->height/2.0f, 0.0f); + GfVec3f v3(-rect->width/2.0f, rect->height/2.0f, 0.0f); + + v0 = _lightData.xformLightToWorld.Transform(v0); + v1 = _lightData.xformLightToWorld.Transform(v1); + v2 = _lightData.xformLightToWorld.Transform(v2); + v3 = _lightData.xformLightToWorld.Transform(v3); + + _lightData.rtcGeometry = rtcNewGeometry(device, + RTC_GEOMETRY_TYPE_QUAD); + GfVec3f* vertices = static_cast( + rtcSetNewGeometryBuffer(_lightData.rtcGeometry, + RTC_BUFFER_TYPE_VERTEX, + 0, + RTC_FORMAT_FLOAT3, + sizeof(GfVec3f), + 4)); + vertices[0] = v0; + vertices[1] = v1; + vertices[2] = v2; + vertices[3] = v3; + + unsigned* index = static_cast( + rtcSetNewGeometryBuffer(_lightData.rtcGeometry, + RTC_BUFFER_TYPE_INDEX, + 0, + RTC_FORMAT_UINT4, + sizeof(unsigned)*4, + 1)); + index[0] = 0; index[1] = 1; index[2] = 2; index[3] = 3; + + auto ctx = std::make_unique(); + ctx->light = this; + rtcSetGeometryTimeStepCount(_lightData.rtcGeometry, 1); + rtcCommitGeometry(_lightData.rtcGeometry); + _lightData.rtcMeshId = rtcAttachGeometry(scene, _lightData.rtcGeometry); + if (_lightData.rtcMeshId == RTC_INVALID_GEOMETRY_ID) { + TF_WARN("could not create rect mesh for %s", GetId().GetAsString().c_str()); + } else { + rtcSetGeometryUserData(_lightData.rtcGeometry, ctx.release()); + } + } + } +} + HdDirtyBits HdEmbree_Light::GetInitialDirtyBitsMask() const { @@ -227,10 +300,22 @@ void HdEmbree_Light::Finalize(HdRenderParam *renderParam) { auto* embreeParam = static_cast(renderParam); + RTCScene scene = embreeParam->AcquireSceneForEdit(); - // Remove from renderer's light map + // First, remove from renderer's light map HdEmbreeRenderer *renderer = embreeParam->GetRenderer(); renderer->RemoveLight(GetId(), this); + + // Then clean up the associated embree objects + if (_lightData.rtcMeshId != RTC_INVALID_GEOMETRY_ID) { + delete static_cast( + rtcGetGeometryUserData(_lightData.rtcGeometry)); + + rtcDetachGeometry(scene, _lightData.rtcMeshId); + rtcReleaseGeometry(_lightData.rtcGeometry); + _lightData.rtcMeshId = RTC_INVALID_GEOMETRY_ID; + _lightData.rtcGeometry = nullptr; + } } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/plugin/hdEmbree/light.h b/pxr/imaging/plugin/hdEmbree/light.h index 05ab262cb2..99d29837d6 100644 --- a/pxr/imaging/plugin/hdEmbree/light.h +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -88,7 +88,11 @@ struct HdEmbree_LightData HdEmbree_LightVariant lightVariant; bool normalize = false; bool visible = true; + bool visible_camera = true; + bool visible_shadow = true; HdEmbree_Shaping shaping; + unsigned rtcMeshId = RTC_INVALID_GEOMETRY_ID; + RTCGeometry rtcGeometry = nullptr; }; class HdEmbree_Light final : public HdLight @@ -118,6 +122,8 @@ class HdEmbree_Light final : public HdLight } private: + void _PopulateRtcLight(RTCDevice device, RTCScene scene); + HdEmbree_LightData _lightData; }; diff --git a/pxr/imaging/plugin/hdEmbree/mesh.cpp b/pxr/imaging/plugin/hdEmbree/mesh.cpp index 195f83a633..bb4515cf56 100644 --- a/pxr/imaging/plugin/hdEmbree/mesh.cpp +++ b/pxr/imaging/plugin/hdEmbree/mesh.cpp @@ -894,6 +894,7 @@ HdEmbreeMesh::_PopulateRtMesh(HdSceneDelegate* sceneDelegate, HdEmbreeInstanceContext *ctx = new HdEmbreeInstanceContext; ctx->rootScene = _rtcMeshScene; ctx->instanceId = i; + ctx->light = nullptr; rtcSetGeometryUserData(geom,ctx); _rtcInstanceGeometries[i] = geom; } diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 23e4c72e4b..356ff2450b 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -422,6 +422,20 @@ _SampleCylinder(GfMatrix4f const& xf, GfMatrix3f const& normalXform, }; } +_ShapeSample +_IntersectAreaLight(HdEmbree_LightData const& light, RTCRayHit const& rayHit) +{ + // XXX: just rect lights at the moment, need to do the others + auto const& rect = std::get(light.lightVariant); + + return _ShapeSample { + _CalculateHitPosition(rayHit), + GfVec3f(rayHit.hit.Ng_x, rayHit.hit.Ng_y, rayHit.hit.Ng_z), + GfVec2f(1.0f - rayHit.hit.u, rayHit.hit.v), + _AreaRect(light.xformLightToWorld, rect.width, rect.height) + }; +} + GfVec3f _EvalLightBasic(HdEmbree_LightData const& light) { @@ -1275,6 +1289,43 @@ _CosineWeightedDirection(GfVec2f const& uniform_float) return dir; } +bool +HdEmbreeRenderer::_RayShouldContinue(RTCRayHit const& rayHit) const { + if (rayHit.hit.geomID == RTC_INVALID_GEOMETRY_ID) { + // missed, don't continue + return false; + } + + if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) { + // not hit an instance, but a "raw" geometry. This should be a light + const HdEmbreeInstanceContext *instanceContext = + static_cast( + rtcGetGeometryUserData(rtcGetGeometry(_scene, + rayHit.hit.geomID))); + + if (instanceContext->light == nullptr) { + // if this isn't a light, don't know what this is + return false; + } + + auto const& light = instanceContext->light->LightData(); + + if ((rayHit.ray.mask & HdEmbree_RayMask::Camera) + && !light.visible_camera) { + return true; + } else if ((rayHit.ray.mask & HdEmbree_RayMask::Shadow) + && !light.visible_shadow) { + return true; + } else { + return false; + } + } + + // XXX: otherwise this is a regular geo. we should handle visibility here + // too eventually + return false; +} + void HdEmbreeRenderer::_TraceRay(unsigned int x, unsigned int y, GfVec3f const &origin, GfVec3f const &dir, @@ -1306,6 +1357,13 @@ HdEmbreeRenderer::_TraceRay(unsigned int x, unsigned int y, rayHit.hit.Ng_z = -rayHit.hit.Ng_z; } + if (_RayShouldContinue(rayHit)) { + GfVec3f hitPos = _CalculateHitPosition(rayHit); + + _TraceRay(x, y, hitPos + dir * _rayHitContinueBias, dir, random); + return; + } + // Write AOVs to attachments that aren't converged. for (size_t i = 0; i < _aovBindings.size(); ++i) { HdEmbreeRenderBuffer *renderBuffer = @@ -1361,6 +1419,11 @@ HdEmbreeRenderer::_ComputeId(RTCRayHit const& rayHit, TfToken const& idType, return false; } + if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) { + // not hit an instance, but a "raw" geometry. This should be a light + return false; + } + // Get the instance and prototype context structures for the hit prim. // We don't use embree's multi-level instancing; we // flatten everything in hydra. So instID[0] should always be correct. @@ -1401,6 +1464,11 @@ HdEmbreeRenderer::_ComputeDepth(RTCRayHit const& rayHit, return false; } + if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) { + // not hit an instance, but a "raw" geometry. This should be a light + return false; + } + if (clip) { GfVec3f hitPos = _CalculateHitPosition(rayHit); @@ -1424,6 +1492,11 @@ HdEmbreeRenderer::_ComputeNormal(RTCRayHit const& rayHit, return false; } + if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) { + // not hit an instance, but a "raw" geometry. This should be a light + return false; + } + // We don't use embree's multi-level instancing; we // flatten everything in hydra. So instID[0] should always be correct. const HdEmbreeInstanceContext *instanceContext = @@ -1462,6 +1535,11 @@ HdEmbreeRenderer::_ComputePrimvar(RTCRayHit const& rayHit, return false; } + if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) { + // not hit an instance, but a "raw" geometry. This should be a light + return false; + } + // We don't use embree's multi-level instancing; we // flatten everything in hydra. So instID[0] should always be correct. const HdEmbreeInstanceContext *instanceContext = @@ -1548,6 +1626,30 @@ HdEmbreeRenderer::_ComputeColor(RTCRayHit const& rayHit, return domeColor; } + if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) { + // if it's not an instance then it's almost certainly a light + const HdEmbreeInstanceContext *instanceContext = + static_cast( + rtcGetGeometryUserData(rtcGetGeometry(_scene, + rayHit.hit.geomID))); + + // if we hit a light, just evaluate the light directly + if (instanceContext->light != nullptr) { + auto const& light = instanceContext->light->LightData(); + _ShapeSample ss = _IntersectAreaLight(light, rayHit); + _LightSample ls = _EvalAreaLight(light, ss, + GfVec3f(rayHit.ray.org_x, rayHit.ray.org_y, rayHit.ray.org_z)); + + return GfVec4f(ls.Li[0], ls.Li[1], ls.Li[2], 1.0f); + } else { + // should never get here. magenta warning! + TF_WARN("Unexpected runtime state - hit an an embree instance " + "that wasn't a geo or light"); + return GfVec4f(1.0f, 0.0f, 1.0f, 1.0f); + } + + } + // Get the instance and prototype context structures for the hit prim. // We don't use embree's multi-level instancing; we // flatten everything in hydra. So instID[0] should always be correct. From e7e4ae94a051a9f5dd3365630f5108d200036c0b Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 29 Jul 2024 23:03:30 -0700 Subject: [PATCH 11/11] [hdEmbree] add pxrPbrt/pbrUtils.h A few small utility functions from pbrt-v4 --- LICENSE.txt | 17 ++++++ .../plugin/hdEmbree/pxrPbrt/pbrtUtils.h | 52 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 pxr/imaging/plugin/hdEmbree/pxrPbrt/pbrtUtils.h diff --git a/LICENSE.txt b/LICENSE.txt index d3bb1fa530..06a5b356ee 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -508,6 +508,23 @@ Redistributions in binary form must reproduce the above copyright notice, this l THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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. +============================================================ +pbrt (sampling functions in hdEmbree/pxrPbrt/pbrUtils.h) +============================================================ + +Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ============================================================ Draco diff --git a/pxr/imaging/plugin/hdEmbree/pxrPbrt/pbrtUtils.h b/pxr/imaging/plugin/hdEmbree/pxrPbrt/pbrtUtils.h new file mode 100644 index 0000000000..b7fcf4741d --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/pxrPbrt/pbrtUtils.h @@ -0,0 +1,52 @@ +// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys. +// The pbrt source code is licensed under the Apache License, Version 2.0. +// SPDX: Apache-2.0 + +#ifndef PXR_IMAGING_PLUGIN_HD_EMBREE_PBRT_UTILS_H +#define PXR_IMAGING_PLUGIN_HD_EMBREE_PBRT_UTILS_H + +#include "pxr/pxr.h" + +#include "pxr/base/arch/math.h" +#include "pxr/base/gf/vec2f.h" +#include "pxr/base/gf/vec3f.h" + +PXR_NAMESPACE_OPEN_SCOPE + +namespace pxr_pbrt { + +template +constexpr T pi = static_cast(M_PI); + +// Ported from PBRT +inline GfVec3f +SphericalDirection(float sinTheta, float cosTheta, float phi) +{ + return GfVec3f(GfClamp(sinTheta, -1.0f, 1.0f) * GfCos(phi), + GfClamp(sinTheta, -1.0f, 1.0f) * GfSin(phi), + GfClamp(cosTheta, -1.0f, 1.0f)); +} + +// Ported from PBRT +inline GfVec3f +SampleUniformCone(GfVec2f const& u, float angle) +{ + float cosAngle = GfCos(angle); + float cosTheta = (1.0f - u[0]) + u[0] * cosAngle; + float sinTheta = GfSqrt(GfMax(0.0f, 1.0f - cosTheta*cosTheta)); + float phi = u[1] * 2.0f * pi; + return SphericalDirection(sinTheta, cosTheta, phi); +} + +// Ported from PBRT +inline float +InvUniformConePDF(float angle) +{ + return 2.0f * pi * (1.0f - GfCos(angle)); +} + +} // namespace pxr_pbrt + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // PXR_IMAGING_PLUGIN_HD_EMBREE_PBRT_UTILS_H \ No newline at end of file