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