From e1cfe971ee5149ea5a85ab4cc0f7b2239e5e58bd Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 23 Jul 2024 09:33:58 -0700 Subject: [PATCH 01/24] [hdEmbree] default-initialize HdEmbreeConfig --- pxr/imaging/plugin/hdEmbree/config.cpp | 48 +++++++++++++++++--------- pxr/imaging/plugin/hdEmbree/config.h | 32 +++++++++++------ 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/pxr/imaging/plugin/hdEmbree/config.cpp b/pxr/imaging/plugin/hdEmbree/config.cpp index 2501a99d04..65768582cb 100644 --- a/pxr/imaging/plugin/hdEmbree/config.cpp +++ b/pxr/imaging/plugin/hdEmbree/config.cpp @@ -20,26 +20,40 @@ TF_INSTANTIATE_SINGLETON(HdEmbreeConfig); // Each configuration variable has an associated environment variable. // The environment variable macro takes the variable name, a default value, // and a description... -TF_DEFINE_ENV_SETTING(HDEMBREE_SAMPLES_TO_CONVERGENCE, 100, - "Samples per pixel before we stop rendering (must be >= 1)"); +TF_DEFINE_ENV_SETTING( + HDEMBREE_SAMPLES_TO_CONVERGENCE, + HdEmbreeDefaultSamplesToConvergence, + "Samples per pixel before we stop rendering (must be >= 1)"); -TF_DEFINE_ENV_SETTING(HDEMBREE_TILE_SIZE, 8, - "Size (per axis) of threading work units (must be >= 1)"); +TF_DEFINE_ENV_SETTING( + HDEMBREE_TILE_SIZE, + HdEmbreeDefaultTileSize, + "Size (per axis) of threading work units (must be >= 1)"); -TF_DEFINE_ENV_SETTING(HDEMBREE_AMBIENT_OCCLUSION_SAMPLES, 16, - "Ambient occlusion samples per camera ray (must be >= 0; a value of 0 disables ambient occlusion)"); +TF_DEFINE_ENV_SETTING( + HDEMBREE_AMBIENT_OCCLUSION_SAMPLES, + HdEmbreeDefaultAmbientOcclusionSamples, + "Ambient occlusion samples per camera ray (must be >= 0;" + " a value of 0 disables ambient occlusion)"); -TF_DEFINE_ENV_SETTING(HDEMBREE_JITTER_CAMERA, 1, - "Should HdEmbree jitter camera rays while rendering? (values >0 are true)"); +TF_DEFINE_ENV_SETTING( + HDEMBREE_JITTER_CAMERA, + HdEmbreeDefaultJitterCamera, + "Should HdEmbree jitter camera rays while rendering?"); -TF_DEFINE_ENV_SETTING(HDEMBREE_USE_FACE_COLORS, 1, - "Should HdEmbree use face colors while rendering? (values > 0 are true)"); +TF_DEFINE_ENV_SETTING( + HDEMBREE_USE_FACE_COLORS, + HdEmbreeDefaultUseFaceColors, + "Should HdEmbree use face colors while rendering?"); -TF_DEFINE_ENV_SETTING(HDEMBREE_CAMERA_LIGHT_INTENSITY, 300, - "Intensity of the camera light, specified as a percentage of <1,1,1>."); +TF_DEFINE_ENV_SETTING( + HDEMBREE_CAMERA_LIGHT_INTENSITY, + HdEmbreeDefaultCameraLightIntensity, + "Intensity of the camera light, specified as a percentage of <1,1,1>."); -TF_DEFINE_ENV_SETTING(HDEMBREE_PRINT_CONFIGURATION, 0, - "Should HdEmbree print configuration on startup? (values > 0 are true)"); +TF_DEFINE_ENV_SETTING(HDEMBREE_PRINT_CONFIGURATION, + false, + "Should HdEmbree print configuration on startup?"); HdEmbreeConfig::HdEmbreeConfig() { @@ -50,12 +64,12 @@ HdEmbreeConfig::HdEmbreeConfig() TfGetEnvSetting(HDEMBREE_TILE_SIZE)); ambientOcclusionSamples = std::max(0, TfGetEnvSetting(HDEMBREE_AMBIENT_OCCLUSION_SAMPLES)); - jitterCamera = (TfGetEnvSetting(HDEMBREE_JITTER_CAMERA) > 0); - useFaceColors = (TfGetEnvSetting(HDEMBREE_USE_FACE_COLORS) > 0); + jitterCamera = (TfGetEnvSetting(HDEMBREE_JITTER_CAMERA)); + useFaceColors = (TfGetEnvSetting(HDEMBREE_USE_FACE_COLORS)); cameraLightIntensity = (std::max(100, TfGetEnvSetting(HDEMBREE_CAMERA_LIGHT_INTENSITY)) / 100.0f); - if (TfGetEnvSetting(HDEMBREE_PRINT_CONFIGURATION) > 0) { + if (TfGetEnvSetting(HDEMBREE_PRINT_CONFIGURATION)) { std::cout << "HdEmbree Configuration: \n" << " samplesToConvergence = " diff --git a/pxr/imaging/plugin/hdEmbree/config.h b/pxr/imaging/plugin/hdEmbree/config.h index 708c2cd43a..593ebb794d 100644 --- a/pxr/imaging/plugin/hdEmbree/config.h +++ b/pxr/imaging/plugin/hdEmbree/config.h @@ -12,6 +12,15 @@ PXR_NAMESPACE_OPEN_SCOPE +// NOTE: types here restricted to bool/int/string, as also used for +// TF_DEFINE_ENV_SETTING +constexpr int HdEmbreeDefaultSamplesToConvergence = 100; +constexpr int HdEmbreeDefaultTileSize = 8; +constexpr int HdEmbreeDefaultAmbientOcclusionSamples = 16; +constexpr bool HdEmbreeDefaultJitterCamera = true; +constexpr bool HdEmbreeDefaultUseFaceColors = true; +constexpr int HdEmbreeDefaultCameraLightIntensity = 300; + /// \class HdEmbreeConfig /// /// This class is a singleton, holding configuration parameters for HdEmbree. @@ -27,6 +36,7 @@ PXR_NAMESPACE_OPEN_SCOPE /// class HdEmbreeConfig { public: + /// \brief Return the configuration singleton. static const HdEmbreeConfig &GetInstance(); @@ -34,38 +44,40 @@ class HdEmbreeConfig { /// converged? /// /// Override with *HDEMBREE_SAMPLES_TO_CONVERGENCE*. - unsigned int samplesToConvergence; + unsigned int samplesToConvergence = HdEmbreeDefaultSamplesToConvergence; /// How many pixels are in an atomic unit of parallel work? /// A work item is a square of size [tileSize x tileSize] pixels. /// /// Override with *HDEMBREE_TILE_SIZE*. - unsigned int tileSize; + unsigned int tileSize = HdEmbreeDefaultTileSize; /// How many ambient occlusion rays should we generate per /// camera ray? /// /// Override with *HDEMBREE_AMBIENT_OCCLUSION_SAMPLES*. - unsigned int ambientOcclusionSamples; + unsigned int ambientOcclusionSamples = HdEmbreeDefaultAmbientOcclusionSamples; /// Should the renderpass jitter camera rays for antialiasing? /// - /// Override with *HDEMBREE_JITTER_CAMERA*. Integer values greater than - /// zero are considered "true". - bool jitterCamera; + /// Override with *HDEMBREE_JITTER_CAMERA*. The case-insensitive strings + /// "true", "yes", "on", and "1" are considered true; an empty value uses + /// the default, and all other values are false. + bool jitterCamera = HdEmbreeDefaultJitterCamera; /// Should the renderpass use the color primvar, or flat white colors? /// (Flat white shows off ambient occlusion better). /// - /// Override with *HDEMBREE_USE_FACE_COLORS*. Integer values greater than - /// zero are considered "true". - bool useFaceColors; + /// Override with *HDEMBREE_USE_FACE_COLORS*. The case-insensitive strings + /// "true", "yes", "on", and "1" are considered true; an empty value uses + /// the default, and all other values are false. + bool useFaceColors = HdEmbreeDefaultUseFaceColors; /// What should the intensity of the camera light be, specified as a /// percent of <1, 1, 1>. For example, 300 would be <3, 3, 3>. /// /// Override with *HDEMBREE_CAMERA_LIGHT_INTENSITY*. - float cameraLightIntensity; + float cameraLightIntensity = HdEmbreeDefaultCameraLightIntensity; private: // The constructor initializes the config variables with their From f53d3e6dec0e6cfee14aa29835cf2889a63780ca Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 2 Jul 2024 12:20:26 -0700 Subject: [PATCH 02/24] [hdEmbree] add HDEMBREE_RANDOM_NUMBER_SEED --- pxr/imaging/plugin/hdEmbree/config.cpp | 12 +++++++++++ pxr/imaging/plugin/hdEmbree/config.h | 10 ++++++++++ .../plugin/hdEmbree/renderDelegate.cpp | 5 ++++- pxr/imaging/plugin/hdEmbree/renderDelegate.h | 3 ++- pxr/imaging/plugin/hdEmbree/renderPass.cpp | 4 ++++ pxr/imaging/plugin/hdEmbree/renderer.cpp | 20 +++++++++++++++---- pxr/imaging/plugin/hdEmbree/renderer.h | 10 +++++++++- 7 files changed, 57 insertions(+), 7 deletions(-) diff --git a/pxr/imaging/plugin/hdEmbree/config.cpp b/pxr/imaging/plugin/hdEmbree/config.cpp index 65768582cb..f0d2d4d6e8 100644 --- a/pxr/imaging/plugin/hdEmbree/config.cpp +++ b/pxr/imaging/plugin/hdEmbree/config.cpp @@ -51,6 +51,15 @@ TF_DEFINE_ENV_SETTING( HdEmbreeDefaultCameraLightIntensity, "Intensity of the camera light, specified as a percentage of <1,1,1>."); +TF_DEFINE_ENV_SETTING( + HDEMBREE_RANDOM_NUMBER_SEED, + HdEmbreeDefaultRandomNumberSeed, + "Seed to give to the random number generator. A value of anything other" + " than -1, combined with setting PXR_WORK_THREAD_LIMIT=1, should" + " give deterministic / repeatable results. A value of -1 (the" + " default) will allow the implementation to set a value that varies" + " from invocation to invocation and thread to thread."); + TF_DEFINE_ENV_SETTING(HDEMBREE_PRINT_CONFIGURATION, false, "Should HdEmbree print configuration on startup?"); @@ -68,6 +77,7 @@ HdEmbreeConfig::HdEmbreeConfig() useFaceColors = (TfGetEnvSetting(HDEMBREE_USE_FACE_COLORS)); cameraLightIntensity = (std::max(100, TfGetEnvSetting(HDEMBREE_CAMERA_LIGHT_INTENSITY)) / 100.0f); + randomNumberSeed = TfGetEnvSetting(HDEMBREE_RANDOM_NUMBER_SEED); if (TfGetEnvSetting(HDEMBREE_PRINT_CONFIGURATION)) { std::cout @@ -84,6 +94,8 @@ HdEmbreeConfig::HdEmbreeConfig() << useFaceColors << "\n" << " cameraLightIntensity = " << cameraLightIntensity << "\n" + << " randomNumberSeed = " + << randomNumberSeed << "\n" ; } } diff --git a/pxr/imaging/plugin/hdEmbree/config.h b/pxr/imaging/plugin/hdEmbree/config.h index 593ebb794d..2632cbb35f 100644 --- a/pxr/imaging/plugin/hdEmbree/config.h +++ b/pxr/imaging/plugin/hdEmbree/config.h @@ -20,6 +20,7 @@ constexpr int HdEmbreeDefaultAmbientOcclusionSamples = 16; constexpr bool HdEmbreeDefaultJitterCamera = true; constexpr bool HdEmbreeDefaultUseFaceColors = true; constexpr int HdEmbreeDefaultCameraLightIntensity = 300; +constexpr int HdEmbreeDefaultRandomNumberSeed = -1; /// \class HdEmbreeConfig /// @@ -79,6 +80,15 @@ class HdEmbreeConfig { /// Override with *HDEMBREE_CAMERA_LIGHT_INTENSITY*. float cameraLightIntensity = HdEmbreeDefaultCameraLightIntensity; + /// Seed to give to the random number generator. A value of anything other + /// than -1, combined with setting PXR_WORK_THREAD_LIMIT=1, should give + /// deterministic / repeatable results. A value of -1 (the default) will + /// allow the implementation to set a value that varies from invocation to + /// invocation and thread to thread. + /// + /// Override with *HDEMBREE_RANDOM_NUMBER_SEED*. + int randomNumberSeed = HdEmbreeDefaultRandomNumberSeed; + private: // The constructor initializes the config variables with their // default or environment-provided override, and optionally prints diff --git a/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp b/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp index fb77050b40..32ef99dbbb 100644 --- a/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp @@ -99,7 +99,7 @@ void HdEmbreeRenderDelegate::_Initialize() { // Initialize the settings and settings descriptors. - _settingDescriptors.resize(4); + _settingDescriptors.resize(5); _settingDescriptors[0] = { "Enable Scene Colors", HdEmbreeRenderSettingsTokens->enableSceneColors, VtValue(HdEmbreeConfig::GetInstance().useFaceColors) }; @@ -112,6 +112,9 @@ HdEmbreeRenderDelegate::_Initialize() _settingDescriptors[3] = { "Samples To Convergence", HdRenderSettingsTokens->convergedSamplesPerPixel, VtValue(int(HdEmbreeConfig::GetInstance().samplesToConvergence)) }; + _settingDescriptors[4] = { "Random Number Seed", + HdEmbreeRenderSettingsTokens->randomNumberSeed, + VtValue(HdEmbreeConfig::GetInstance().randomNumberSeed) }; _PopulateDefaultSettings(_settingDescriptors); // Initialize the embree library handle (_rtcDevice). diff --git a/pxr/imaging/plugin/hdEmbree/renderDelegate.h b/pxr/imaging/plugin/hdEmbree/renderDelegate.h index d4f8f78d54..1d8694daa6 100644 --- a/pxr/imaging/plugin/hdEmbree/renderDelegate.h +++ b/pxr/imaging/plugin/hdEmbree/renderDelegate.h @@ -23,7 +23,8 @@ class HdEmbreeRenderParam; #define HDEMBREE_RENDER_SETTINGS_TOKENS \ (enableAmbientOcclusion) \ (enableSceneColors) \ - (ambientOcclusionSamples) + (ambientOcclusionSamples) \ + (randomNumberSeed) // Also: HdRenderSettingsTokens->convergedSamplesPerPixel diff --git a/pxr/imaging/plugin/hdEmbree/renderPass.cpp b/pxr/imaging/plugin/hdEmbree/renderPass.cpp index 9583d8c757..4b28269cd5 100644 --- a/pxr/imaging/plugin/hdEmbree/renderPass.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderPass.cpp @@ -113,6 +113,10 @@ HdEmbreeRenderPass::_Execute(HdRenderPassStateSharedPtr const& renderPassState, renderDelegate->GetRenderSetting( HdEmbreeRenderSettingsTokens->enableSceneColors, true)); + _renderer->SetRandomNumberSeed( + renderDelegate->GetRenderSetting( + HdEmbreeRenderSettingsTokens->randomNumberSeed, (unsigned int)-1)); + needStartRender = true; } diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 13956c7b25..e216216759 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -69,6 +69,12 @@ HdEmbreeRenderer::SetEnableSceneColors(bool enableSceneColors) _enableSceneColors = enableSceneColors; } +void +HdEmbreeRenderer::SetRandomNumberSeed(int randomNumberSeed) +{ + _randomNumberSeed = randomNumberSeed; +} + void HdEmbreeRenderer::SetDataWindow(const GfRect2i &dataWindow) { @@ -432,8 +438,8 @@ HdEmbreeRenderer::Render(HdRenderThread *renderThread) // Always pass the renderThread to _RenderTiles to allow the first frame // to be interrupted. WorkParallelForN(numTilesX*numTilesY, - std::bind(&HdEmbreeRenderer::_RenderTiles, this, renderThread, - std::placeholders::_1, std::placeholders::_2)); + std::bind(&HdEmbreeRenderer::_RenderTiles, this, + renderThread, i, std::placeholders::_1, std::placeholders::_2)); // After the first pass, mark the single-sampled attachments as // converged and unmap them. If there are no multisampled attachments, @@ -472,7 +478,7 @@ HdEmbreeRenderer::Render(HdRenderThread *renderThread) } void -HdEmbreeRenderer::_RenderTiles(HdRenderThread *renderThread, +HdEmbreeRenderer::_RenderTiles(HdRenderThread *renderThread, int sampleNum, size_t tileStart, size_t tileEnd) { const unsigned int minX = _dataWindow.GetMinX(); @@ -497,8 +503,14 @@ HdEmbreeRenderer::_RenderTiles(HdRenderThread *renderThread, // Initialize the RNG for this tile (each tile creates one as // a lazy way to do thread-local RNGs). - size_t seed = std::chrono::system_clock::now().time_since_epoch().count(); + size_t seed; + if (_randomNumberSeed == -1) { + seed = std::chrono::system_clock::now().time_since_epoch().count(); + } else { + seed = static_cast(_randomNumberSeed); + } seed = TfHash::Combine(seed, tileStart); + seed = TfHash::Combine(seed, sampleNum); std::default_random_engine random(seed); // Create a uniform distribution for jitter calculations. diff --git a/pxr/imaging/plugin/hdEmbree/renderer.h b/pxr/imaging/plugin/hdEmbree/renderer.h index 8e727ecba0..2da9880848 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.h +++ b/pxr/imaging/plugin/hdEmbree/renderer.h @@ -81,6 +81,12 @@ class HdEmbreeRenderer final /// everything as white. void SetEnableSceneColors(bool enableSceneColors); + /// Sets a number to seed the random number generator with. + /// \param randomNumberSeed If -1, then the random number generator + /// is seeded in a non-deterministic way; + /// otherwise, it is seeded with this value. + void SetRandomNumberSeed(int randomNumberSeed); + /// Rendering entrypoint: add one sample per pixel to the whole sample /// buffer, and then loop until the image is converged. After each pass, /// the image will be resolved into a color buffer. @@ -115,7 +121,7 @@ class HdEmbreeRenderer final // work. For each tile, iterate over pixels in the tile, generating camera // rays, and following them/calculating color with _TraceRay. This function // renders all tiles between tileStart and tileEnd. - void _RenderTiles(HdRenderThread *renderThread, + void _RenderTiles(HdRenderThread *renderThread, int sampleNum, size_t tileStart, size_t tileEnd); // Cast a ray into the scene and if it hits an object, write to the bound @@ -184,6 +190,8 @@ class HdEmbreeRenderer final int _ambientOcclusionSamples; // Should we enable scene colors? bool _enableSceneColors; + // If other than -1, use this to seed the random number generator with. + int _randomNumberSeed; // How many samples have been completed. std::atomic _completedSamples; From 20f883e8ba5f61a706aea8bd4d995b22b61b4094 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Thu, 11 Jul 2024 02:47:09 -0700 Subject: [PATCH 03/24] [hdEmbree] fix for random number generation use of std::bind was copying the underlying random number generator before use, resulting in the exact same sequence getting reused for, ie, every AO calculation within a given tile-group --- pxr/imaging/plugin/hdEmbree/renderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index e216216759..95663f7639 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -515,7 +515,7 @@ HdEmbreeRenderer::_RenderTiles(HdRenderThread *renderThread, int sampleNum, // Create a uniform distribution for jitter calculations. std::uniform_real_distribution uniform_dist(0.0f, 1.0f); - std::function uniform_float = std::bind(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) { @@ -935,7 +935,7 @@ HdEmbreeRenderer::_ComputeAmbientOcclusion(GfVec3f const& position, { // Create a uniform random distribution for AO calculations. std::uniform_real_distribution uniform_dist(0.0f, 1.0f); - std::function uniform_float = std::bind(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) { From 0083231d8a9dd27f8bf9676d7db11b86a691daf9 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Fri, 28 Jun 2024 11:28:39 -0700 Subject: [PATCH 04/24] [hdEmbree] factor out a _CalculateHitPosition utility function --- pxr/imaging/plugin/hdEmbree/renderer.cpp | 26 ++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 95663f7639..88d5e79093 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -22,6 +22,24 @@ #include #include +namespace { + +PXR_NAMESPACE_USING_DIRECTIVE + +// ------------------------------------------------------------------------- +// General Ray Utilities +// ------------------------------------------------------------------------- + +inline GfVec3f +_CalculateHitPosition(RTCRayHit const& rayHit) +{ + return GfVec3f(rayHit.ray.org_x + rayHit.ray.tfar * rayHit.ray.dir_x, + rayHit.ray.org_y + rayHit.ray.tfar * rayHit.ray.dir_y, + rayHit.ray.org_z + rayHit.ray.tfar * rayHit.ray.dir_z); +} + +} // anonymous namespace + PXR_NAMESPACE_OPEN_SCOPE HdEmbreeRenderer::HdEmbreeRenderer() @@ -762,9 +780,7 @@ HdEmbreeRenderer::_ComputeDepth(RTCRayHit const& rayHit, } if (clip) { - GfVec3f hitPos = GfVec3f(rayHit.ray.org_x + rayHit.ray.tfar * rayHit.ray.dir_x, - rayHit.ray.org_y + rayHit.ray.tfar * rayHit.ray.dir_y, - rayHit.ray.org_z + rayHit.ray.tfar * rayHit.ray.dir_z); + GfVec3f hitPos = _CalculateHitPosition(rayHit); hitPos = GfVec3f(_viewMatrix.Transform(hitPos)); hitPos = GfVec3f(_projMatrix.Transform(hitPos)); @@ -874,9 +890,7 @@ HdEmbreeRenderer::_ComputeColor(RTCRayHit const& rayHit, rtcGetGeometryUserData(rtcGetGeometry(instanceContext->rootScene,rayHit.hit.geomID))); // Compute the worldspace location of the rayHit hit. - GfVec3f hitPos = GfVec3f(rayHit.ray.org_x + rayHit.ray.tfar * rayHit.ray.dir_x, - rayHit.ray.org_y + rayHit.ray.tfar * rayHit.ray.dir_y, - rayHit.ray.org_z + rayHit.ray.tfar * rayHit.ray.dir_z); + 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. From 4b10db71a5c79b9787bd72864b2e1ca8e2dc8163 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 00:32:15 -0700 Subject: [PATCH 05/24] [hdEmbree] minor fixes in hdEmbree/mesh.h - forward-declare some types as struct instead of class (avoids msvc compiler warning) - comment typo fix --- pxr/imaging/plugin/hdEmbree/mesh.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pxr/imaging/plugin/hdEmbree/mesh.h b/pxr/imaging/plugin/hdEmbree/mesh.h index 29b0d55029..bbb006302f 100644 --- a/pxr/imaging/plugin/hdEmbree/mesh.h +++ b/pxr/imaging/plugin/hdEmbree/mesh.h @@ -20,8 +20,8 @@ PXR_NAMESPACE_OPEN_SCOPE -class HdEmbreePrototypeContext; -class HdEmbreeInstanceContext; +struct HdEmbreePrototypeContext; +struct HdEmbreeInstanceContext; /// \class HdEmbreeMesh /// @@ -171,7 +171,7 @@ class HdEmbreeMesh final : public HdMesh { private: // Every HdEmbreeMesh is treated as instanced; if there's no instancer, - // the prototype has a single identity istance. The prototype is stored + // the prototype has a single identity instance. The prototype is stored // as _rtcMeshId, in _rtcMeshScene. unsigned _rtcMeshId; RTCScene _rtcMeshScene; From a6a913639ab96e7f1f7e2fb837a13f08ae1770f9 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Fri, 1 Sep 2023 07:34:44 +1200 Subject: [PATCH 06/24] [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 | 691 ++++++++++++++++-- pxr/imaging/plugin/hdEmbree/renderer.h | 49 +- 8 files changed, 987 insertions(+), 65 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 88d5e79093..92e5f89cb2 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -6,26 +6,145 @@ // #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 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 // ------------------------------------------------------------------------- @@ -38,6 +157,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 @@ -130,6 +571,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() { @@ -371,7 +828,7 @@ _IsContained(const GfRect2i &rect, int width, int height) } void -HdEmbreeRenderer::Render(HdRenderThread *renderThread) +HdEmbreeRenderer::_PreRenderSetup() { _completedSamples.store(0); @@ -422,9 +879,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 @@ -533,7 +995,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) { @@ -557,7 +1021,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) { @@ -571,25 +1034,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. @@ -607,7 +1070,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]; @@ -619,25 +1084,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]. @@ -648,7 +1114,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; @@ -665,7 +1131,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); @@ -746,11 +1214,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(); @@ -806,11 +1276,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); @@ -841,27 +1314,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; } @@ -869,32 +1348,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( @@ -903,12 +1402,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); } } @@ -918,38 +1417,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); + } - // XXX: We should support opacity here... + // 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; + + // Lighting gets modulated by an ambient occlusion term. + float aoLightIntensity = + _ComputeAmbientOcclusion(hitPos, normal, random); + + // XXX: We should support opacity here... + + lightingColor = GfVec3f(diffuseLight * aoLightIntensity); + } + else + { + // For lighting, default material is 100% white + if (materialColor == _invalidColor) { + materialColor = GfVec3f(1.0f); + } - // Return color * diffuseLight * aoLightIntensity. - GfVec3f finalColor = color * diffuseLight * aoLightIntensity; + lightingColor = _ComputeLighting( + hitPos, normal,random, prototypeContext); + } + const GfVec3f finalColor = GfCompMult(materialColor, lightingColor); - // Clamp colors to [0,1]. + // 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) { @@ -962,12 +1486,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()); @@ -1024,4 +1548,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 4c8c043e6f15d452aedc68b3ba122230b802de95 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 00:27:19 -0700 Subject: [PATCH 07/24] [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 082e4605d2..f8ffb4c4b8 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) @@ -112,6 +167,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 906ba185bc..9b54575bc3 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 exposure = 0.0f; float colorTemperature = 6500.0f; diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 92e5f89cb2..9863ec0a44 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -216,6 +216,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) @@ -363,6 +376,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 55ae4aadb68f0157835c7b996a52b23c15a8468a Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 00:27:48 -0700 Subject: [PATCH 08/24] [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 f8ffb4c4b8..06781f27b9 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -89,6 +89,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(); @@ -160,6 +162,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 9b54575bc3..e74190481b 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>; @@ -107,6 +112,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 9863ec0a44..4a8e7cadf5 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -411,6 +411,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, @@ -478,6 +517,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, @@ -597,6 +640,10 @@ HdEmbreeRenderer::AddLight(SdfPath const& lightPath, { ScopedLock lightsWriteLock(_lightsWriteMutex); _lightMap[lightPath] = light; + + if (light->IsDome()) { + _domes.push_back(light); + } } void @@ -604,6 +651,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 @@ -1392,6 +1442,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 ff7aecde21e79f3e14638f158f52664fd4ddf332 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 00:28:30 -0700 Subject: [PATCH 09/24] [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 06781f27b9..66c1247440 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()) { @@ -115,7 +121,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(); @@ -143,6 +150,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 @@ -207,12 +221,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 { @@ -223,10 +296,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 e74190481b..f4b8d1a762 100644 --- a/pxr/imaging/plugin/hdEmbree/light.h +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -87,7 +87,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 @@ -117,6 +121,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 4a8e7cadf5..d2b7f8b6c6 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -344,6 +344,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) { @@ -1192,6 +1206,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, @@ -1223,6 +1274,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 = @@ -1278,6 +1336,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. @@ -1318,6 +1381,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); @@ -1341,6 +1409,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 = @@ -1379,6 +1452,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 = @@ -1465,6 +1543,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 38fdeedfb757cfd1a86f1a9d1af557ca24c5bbcd Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 29 Jul 2024 23:03:30 -0700 Subject: [PATCH 10/24] [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 a2cdda092e..faad3f3f6c 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 From c90bbc496a1e44994eaf439f71874663815378b2 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 29 Jul 2024 23:05:19 -0700 Subject: [PATCH 11/24] [hdEmbree] add distant light support --- pxr/imaging/plugin/hdEmbree/CMakeLists.txt | 3 ++ pxr/imaging/plugin/hdEmbree/light.cpp | 8 +++ pxr/imaging/plugin/hdEmbree/light.h | 6 +++ .../plugin/hdEmbree/renderDelegate.cpp | 3 ++ pxr/imaging/plugin/hdEmbree/renderer.cpp | 50 +++++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt index 2efef685eb..55444663f7 100644 --- a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt +++ b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt @@ -45,6 +45,9 @@ pxr_plugin(hdEmbree context.h renderParam.h + PRIVATE_HEADERS + pxrPbrt/pbrtUtils.h + PRIVATE_CLASSES implicitSurfaceSceneIndexPlugin diff --git a/pxr/imaging/plugin/hdEmbree/light.cpp b/pxr/imaging/plugin/hdEmbree/light.cpp index 66c1247440..b3a3c8175d 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -95,6 +95,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->distantLight) { + _lightData.lightVariant = HdEmbree_Distant(); } else if (lightType == HdSprimTypeTokens->domeLight) { _lightData.lightVariant = HdEmbree_Dome(); } else if (lightType == HdSprimTypeTokens->rectLight) { @@ -176,6 +178,12 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, sceneDelegate->GetLightParamValue(id, HdLightTokens->radius) .GetWithDefault(0.5f), }; + } else if constexpr (std::is_same_v) { + typedLight = HdEmbree_Distant{ + float(GfDegreesToRadians( + sceneDelegate->GetLightParamValue(id, HdLightTokens->angle) + .GetWithDefault(0.53f) / 2.0f)), + }; } else if constexpr (std::is_same_v) { typedLight = HdEmbree_Dome{}; _SyncLightTexture(id, _lightData, sceneDelegate); diff --git a/pxr/imaging/plugin/hdEmbree/light.h b/pxr/imaging/plugin/hdEmbree/light.h index f4b8d1a762..f3f4392ae5 100644 --- a/pxr/imaging/plugin/hdEmbree/light.h +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -35,6 +35,11 @@ struct HdEmbree_Disk float radius; }; +struct HdEmbree_Distant +{ + float halfAngleRadians; +}; + // Needed for HdEmbree_LightVariant struct HdEmbree_Dome {}; @@ -54,6 +59,7 @@ using HdEmbree_LightVariant = std::variant< HdEmbree_UnknownLight, HdEmbree_Cylinder, HdEmbree_Disk, + HdEmbree_Distant, HdEmbree_Dome, HdEmbree_Rect, HdEmbree_Sphere>; diff --git a/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp b/pxr/imaging/plugin/hdEmbree/renderDelegate.cpp index 27cad002da..3050fc6d03 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->distantLight, HdPrimTypeTokens->domeLight, HdPrimTypeTokens->rectLight, HdPrimTypeTokens->sphereLight, @@ -338,6 +339,7 @@ HdEmbreeRenderDelegate::CreateSprim(TfToken const& typeId, } else if (typeId == HdPrimTypeTokens->extComputation) { return new HdExtComputation(sprimId); } else if (typeId == HdPrimTypeTokens->light || + typeId == HdPrimTypeTokens->distantLight || typeId == HdPrimTypeTokens->diskLight || typeId == HdPrimTypeTokens->domeLight || typeId == HdPrimTypeTokens->rectLight || @@ -361,6 +363,7 @@ HdEmbreeRenderDelegate::CreateFallbackSprim(TfToken const& typeId) } else if (typeId == HdPrimTypeTokens->extComputation) { return new HdExtComputation(SdfPath::EmptyPath()); } else if (typeId == HdPrimTypeTokens->light || + typeId == HdPrimTypeTokens->distantLight || typeId == HdPrimTypeTokens->diskLight || typeId == HdPrimTypeTokens->domeLight || typeId == HdPrimTypeTokens->rectLight || diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index d2b7f8b6c6..32b24331a7 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -9,6 +9,7 @@ #include "pxr/imaging/plugin/hdEmbree/config.h" #include "pxr/imaging/plugin/hdEmbree/light.h" #include "pxr/imaging/plugin/hdEmbree/mesh.h" +#include "pxr/imaging/plugin/hdEmbree/pxrPbrt/pbrtUtils.h" #include "pxr/imaging/plugin/hdEmbree/renderBuffer.h" #include "pxr/imaging/hd/perfLog.h" @@ -369,6 +370,51 @@ _EvalLightBasic(HdEmbree_LightData const& light) return Le; } +_LightSample +_EvalDistantLight(HdEmbree_LightData const& light, GfVec3f const& position, + float u1, float u2) +{ + auto const& distant = std::get(light.lightVariant); + + GfVec3f Le = _EvalLightBasic(light); + + if (distant.halfAngleRadians > 0.0f) + { + if (light.normalize) + { + float sinTheta = sinf(distant.halfAngleRadians); + Le /= _Sqr(sinTheta) * _pi; + } + + // There's an implicit double-negation of the wI direction here + GfVec3f localDir = pxr_pbrt::SampleUniformCone(GfVec2f(u1, u2), + distant.halfAngleRadians); + GfVec3f wI = light.xformLightToWorld.TransformDir(localDir); + wI.Normalize(); + + return _LightSample { + Le, + wI, + std::numeric_limits::max(), + pxr_pbrt::InvUniformConePDF(distant.halfAngleRadians) + }; + } + else + { + // delta case, infinite pdf + GfVec3f wI = light.xformLightToWorld.TransformDir( + GfVec3f(0.0f, 0.0f, 1.0f)); + wI.Normalize(); + + return _LightSample { + Le, + wI, + std::numeric_limits::max(), + 1.0f, + }; + } +} + _LightSample _EvalAreaLight(HdEmbree_LightData const& light, _ShapeSample const& ss, GfVec3f const& position) @@ -535,6 +581,10 @@ class _LightSampler { return _EvalDomeLight(_lightData, _normal, _u1, _u2); } + _LightSample operator()(HdEmbree_Distant const& distant) { + return _EvalDistantLight(_lightData, _hitPosition, _u1, _u2); + } + private: _LightSampler(HdEmbree_LightData const& lightData, GfVec3f const& hitPosition, From 0c4816df6ca6758023312f54d2f54e6aa8711810 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 24 Apr 2024 12:45:53 -0700 Subject: [PATCH 12/24] [hdEmbree] ies.h / ies.cpp: add unaltered These are taken unaltered from cycles, v4.1.1 - https://projects.blender.org/blender/cycles/src/tag/v4.1.1/src/util/ies.h - https://projects.blender.org/blender/cycles/src/tag/v4.1.1/src/util/ies.cpp These are not yet built / used, but merely introduce the files for use in the codebase later. --- pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp | 410 +++++++++++++++++++++ pxr/imaging/plugin/hdEmbree/pxrIES/ies.h | 46 +++ 2 files changed, 456 insertions(+) create mode 100644 pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp create mode 100644 pxr/imaging/plugin/hdEmbree/pxrIES/ies.h diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp new file mode 100644 index 0000000000..a6725cc049 --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp @@ -0,0 +1,410 @@ +/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation + * + * SPDX-License-Identifier: Apache-2.0 */ + +#include + +#include "util/foreach.h" +#include "util/ies.h" +#include "util/math.h" +#include "util/string.h" + +CCL_NAMESPACE_BEGIN + +// NOTE: For some reason gcc-7.2 does not instantiate this version of the +// allocator here (used in IESTextParser). Works fine for gcc-6, gcc-7.3 and gcc-8. +// +// TODO(sergey): Get to the root of this issue, or confirm this is a compiler +// issue. +template class GuardedAllocator; + +bool IESFile::load(const string &ies) +{ + clear(); + if (!parse(ies) || !process()) { + clear(); + return false; + } + return true; +} + +void IESFile::clear() +{ + intensity.clear(); + v_angles.clear(); + h_angles.clear(); +} + +int IESFile::packed_size() +{ + if (v_angles.size() && h_angles.size() > 0) { + return 2 + h_angles.size() + v_angles.size() + h_angles.size() * v_angles.size(); + } + return 0; +} + +void IESFile::pack(float *data) +{ + if (v_angles.size() && h_angles.size()) { + *(data++) = __int_as_float(h_angles.size()); + *(data++) = __int_as_float(v_angles.size()); + + memcpy(data, &h_angles[0], h_angles.size() * sizeof(float)); + data += h_angles.size(); + memcpy(data, &v_angles[0], v_angles.size() * sizeof(float)); + data += v_angles.size(); + + for (int h = 0; h < intensity.size(); h++) { + memcpy(data, &intensity[h][0], v_angles.size() * sizeof(float)); + data += v_angles.size(); + } + } +} + +class IESTextParser { + public: + string text; + char *data; + bool error; + + IESTextParser(const string &str) : text(str), error(false) + { + std::replace(text.begin(), text.end(), ',', ' '); + data = strstr(&text[0], "\nTILT="); + } + + bool eof() + { + return (data == NULL) || (data[0] == '\0'); + } + + bool has_error() + { + return error; + } + + double get_double() + { + if (eof()) { + error = true; + return 0.0; + } + char *old_data = data; + double val = strtod(data, &data); + if (data == old_data) { + data = NULL; + error = true; + return 0.0; + } + return val; + } + + long get_long() + { + if (eof()) { + error = true; + return 0; + } + char *old_data = data; + long val = strtol(data, &data, 10); + if (data == old_data) { + data = NULL; + error = true; + return 0; + } + return val; + } +}; + +bool IESFile::parse(const string &ies) +{ + if (ies.empty()) { + return false; + } + + IESTextParser parser(ies); + if (parser.eof()) { + return false; + } + + /* Handle the tilt data block. */ + if (strncmp(parser.data, "\nTILT=INCLUDE", 13) == 0) { + parser.data += 13; + parser.get_double(); /* Lamp to Luminaire geometry */ + int num_tilt = parser.get_long(); /* Amount of tilt angles and factors */ + /* Skip over angles and factors. */ + for (int i = 0; i < 2 * num_tilt; i++) { + parser.get_double(); + } + } + else { + /* Skip to next line. */ + parser.data = strstr(parser.data + 1, "\n"); + } + + if (parser.eof()) { + return false; + } + parser.data++; + + parser.get_long(); /* Number of lamps */ + parser.get_double(); /* Lumens per lamp */ + double factor = parser.get_double(); /* Candela multiplier */ + int v_angles_num = parser.get_long(); /* Number of vertical angles */ + int h_angles_num = parser.get_long(); /* Number of horizontal angles */ + type = (IESType)parser.get_long(); /* Photometric type */ + + if (type != TYPE_A && type != TYPE_B && type != TYPE_C) { + return false; + } + + parser.get_long(); /* Unit of the geometry data */ + parser.get_double(); /* Width */ + parser.get_double(); /* Length */ + parser.get_double(); /* Height */ + factor *= parser.get_double(); /* Ballast factor */ + factor *= parser.get_double(); /* Ballast-Lamp Photometric factor */ + parser.get_double(); /* Input Watts */ + + /* Intensity values in IES files are specified in candela (lumen/sr), a photometric quantity. + * Cycles expects radiometric quantities, though, which requires a conversion. + * However, the Luminous efficacy (ratio of lumens per Watt) depends on the spectral distribution + * of the light source since lumens take human perception into account. + * Since this spectral distribution is not known from the IES file, a typical one must be + * assumed. The D65 standard illuminant has a Luminous efficacy of 177.83, which is used here to + * convert to Watt/sr. A more advanced approach would be to add a Blackbody Temperature input to + * the node and numerically integrate the Luminous efficacy from the resulting spectral + * distribution. Also, the Watt/sr value must be multiplied by 4*pi to get the Watt value that + * Cycles expects for lamp strength. Therefore, the conversion here uses 4*pi/177.83 as a Candela + * to Watt factor. + */ + factor *= 0.0706650768394; + + v_angles.reserve(v_angles_num); + for (int i = 0; i < v_angles_num; i++) { + v_angles.push_back((float)parser.get_double()); + } + + h_angles.reserve(h_angles_num); + for (int i = 0; i < h_angles_num; i++) { + h_angles.push_back((float)parser.get_double()); + } + + intensity.resize(h_angles_num); + for (int i = 0; i < h_angles_num; i++) { + intensity[i].reserve(v_angles_num); + for (int j = 0; j < v_angles_num; j++) { + intensity[i].push_back((float)(factor * parser.get_double())); + } + } + + return !parser.has_error(); +} + +static bool angle_close(float a, float b) +{ + return fabsf(a - b) < 1e-4f; +} + +/* Processing functions to turn file contents into the format that Cycles expects. + * Handles type conversion (the output format is based on Type C), symmetry/mirroring, + * value shifting etc. + * Note that this code is much more forgiving than the spec. For example, in type A and B, + * the range of vertical angles officially must be either exactly 0°-90° or -90°-90°. + * However, in practice, IES files are all over the place. Therefore, the handling is as + * flexible as possible, and tries to turn any input into something useful. */ + +void IESFile::process_type_b() +{ + /* According to the standard, Type B defines a different coordinate system where the polar axis + * is horizontal, not vertical. + * To avoid over complicating the conversion logic, we just transpose the angles and use the + * regular Type A/C coordinate system. Users can just rotate the light to get the "proper" + * orientation. */ + vector> newintensity; + newintensity.resize(v_angles.size()); + for (int i = 0; i < v_angles.size(); i++) { + newintensity[i].reserve(h_angles.size()); + for (int j = 0; j < h_angles.size(); j++) { + newintensity[i].push_back(intensity[j][i]); + } + } + intensity.swap(newintensity); + h_angles.swap(v_angles); + + if (angle_close(h_angles[0], 0.0f)) { + /* File angles cover 0°-90°. Mirror that to -90°-90°, and shift to 0°-180° to match Cycles. */ + vector new_h_angles; + vector> new_intensity; + int hnum = h_angles.size(); + new_h_angles.reserve(2 * hnum - 1); + new_intensity.reserve(2 * hnum - 1); + for (int i = hnum - 1; i > 0; i--) { + new_h_angles.push_back(90.0f - h_angles[i]); + new_intensity.push_back(intensity[i]); + } + for (int i = 0; i < hnum; i++) { + new_h_angles.push_back(90.0f + h_angles[i]); + new_intensity.push_back(intensity[i]); + } + h_angles.swap(new_h_angles); + intensity.swap(new_intensity); + } + else { + /* File angles cover -90°-90°. Shift to 0°-180° to match Cycles. */ + for (int i = 0; i < h_angles.size(); i++) { + h_angles[i] += 90.0f; + } + } + + if (angle_close(v_angles[0], 0.0f)) { + /* File angles cover 0°-90°. Mirror that to -90°-90°, and shift to 0°-180° to match Cycles. */ + vector new_v_angles; + int hnum = h_angles.size(); + int vnum = v_angles.size(); + new_v_angles.reserve(2 * vnum - 1); + for (int i = vnum - 1; i > 0; i--) { + new_v_angles.push_back(90.0f - v_angles[i]); + } + for (int i = 0; i < vnum; i++) { + new_v_angles.push_back(90.0f + v_angles[i]); + } + for (int i = 0; i < hnum; i++) { + vector new_intensity; + new_intensity.reserve(2 * vnum - 1); + for (int j = vnum - 1; j > 0; j--) { + new_intensity.push_back(intensity[i][j]); + } + new_intensity.insert(new_intensity.end(), intensity[i].begin(), intensity[i].end()); + intensity[i].swap(new_intensity); + } + v_angles.swap(new_v_angles); + } + else { + /* File angles cover -90°-90°. Shift to 0°-180° to match Cycles. */ + for (int i = 0; i < v_angles.size(); i++) { + v_angles[i] += 90.0f; + } + } +} + +void IESFile::process_type_a() +{ + /* Convert vertical angles - just a simple offset. */ + for (int i = 0; i < v_angles.size(); i++) { + v_angles[i] += 90.0f; + } + + vector new_h_angles; + new_h_angles.reserve(h_angles.size()); + vector> new_intensity; + new_intensity.reserve(h_angles.size()); + + /* Type A goes from -90° to 90°, which is mapped to 270° to 90° respectively in Type C. */ + for (int i = h_angles.size() - 1; i >= 0; i--) { + new_h_angles.push_back(180.0f - h_angles[i]); + new_intensity.push_back(intensity[i]); + } + + /* If the file angles start at 0°, we need to mirror around that. + * Since the negative input range (which we generate here) maps to 180° to 270°, + * it comes after the original entries in the output. */ + if (angle_close(h_angles[0], 0.0f)) { + new_h_angles.reserve(2 * h_angles.size() - 1); + new_intensity.reserve(2 * h_angles.size() - 1); + for (int i = 1; i < h_angles.size(); i++) { + new_h_angles.push_back(180.0f + h_angles[i]); + new_intensity.push_back(intensity[i]); + } + } + + h_angles.swap(new_h_angles); + intensity.swap(new_intensity); +} + +void IESFile::process_type_c() +{ + if (angle_close(h_angles[0], 90.0f)) { + /* Some files are stored from 90° to 270°, so rotate them to the regular 0°-180° range. */ + for (int i = 0; i < h_angles.size(); i++) { + h_angles[i] -= 90.0f; + } + } + + if (h_angles.size() == 1) { + h_angles[0] = 0.0f; + h_angles.push_back(360.0f); + intensity.push_back(intensity[0]); + } + + if (angle_close(h_angles[h_angles.size() - 1], 90.0f)) { + /* Only one quadrant is defined, so we need to mirror twice (from one to two, then to four). + * Since the two->four mirroring step might also be required if we get an input of two + * quadrants, we only do the first mirror here and later do the second mirror in either case. + */ + int hnum = h_angles.size(); + for (int i = hnum - 2; i >= 0; i--) { + h_angles.push_back(180.0f - h_angles[i]); + intensity.push_back(intensity[i]); + } + } + + if (angle_close(h_angles[h_angles.size() - 1], 180.0f)) { + /* Mirror half to the full range. */ + int hnum = h_angles.size(); + for (int i = hnum - 2; i >= 0; i--) { + h_angles.push_back(360.0f - h_angles[i]); + intensity.push_back(intensity[i]); + } + } + + /* Some files skip the 360° entry (contrary to standard) because it's supposed to be identical to + * the 0° entry. If the file has a discernible order in its spacing, just fix this. */ + if (angle_close(h_angles[0], 0.0f) && !angle_close(h_angles[h_angles.size() - 1], 360.0f)) { + int hnum = h_angles.size(); + float last_step = h_angles[hnum - 1] - h_angles[hnum - 2]; + float first_step = h_angles[1] - h_angles[0]; + float gap_step = 360.0f - h_angles[hnum - 1]; + if (angle_close(last_step, gap_step) || angle_close(first_step, gap_step)) { + h_angles.push_back(360.0f); + intensity.push_back(intensity[0]); + } + } +} + +bool IESFile::process() +{ + if (h_angles.size() == 0 || v_angles.size() == 0) { + return false; + } + + if (type == TYPE_A) { + process_type_a(); + } + else if (type == TYPE_B) { + process_type_b(); + } + else if (type == TYPE_C) { + process_type_c(); + } + else { + return false; + } + + /* Convert from deg to rad. */ + for (int i = 0; i < v_angles.size(); i++) { + v_angles[i] *= M_PI_F / 180.f; + } + for (int i = 0; i < h_angles.size(); i++) { + h_angles[i] *= M_PI_F / 180.f; + } + + return true; +} + +IESFile::~IESFile() +{ + clear(); +} + +CCL_NAMESPACE_END diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h new file mode 100644 index 0000000000..8c506befdd --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h @@ -0,0 +1,46 @@ +/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation + * + * SPDX-License-Identifier: Apache-2.0 */ + +#ifndef __UTIL_IES_H__ +#define __UTIL_IES_H__ + +#include "util/string.h" +#include "util/vector.h" + +CCL_NAMESPACE_BEGIN + +class IESFile { + public: + IESFile() {} + ~IESFile(); + + int packed_size(); + void pack(float *data); + + bool load(const string &ies); + void clear(); + + protected: + bool parse(const string &ies); + bool process(); + void process_type_a(); + void process_type_b(); + void process_type_c(); + + /* The brightness distribution is stored in spherical coordinates. + * The horizontal angles correspond to theta in the regular notation + * and always span the full range from 0° to 360°. + * The vertical angles correspond to phi and always start at 0°. */ + vector v_angles, h_angles; + /* The actual values are stored here, with every entry storing the values + * of one horizontal segment. */ + vector> intensity; + + /* Types of angle representation in IES files. */ + enum IESType { TYPE_A = 3, TYPE_B = 2, TYPE_C = 1 } type; +}; + +CCL_NAMESPACE_END + +#endif /* __UTIL_IES_H__ */ From 3525a4d800e96e665fe91ce25eb3217d92007750 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 22 May 2024 12:22:37 -0700 Subject: [PATCH 13/24] [hdEmbree] ies.h / ies.cpp: patch, and add pxr-IES.patch and README.md --- pxr/imaging/plugin/hdEmbree/pxrIES/README.md | 32 +++++ pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp | 44 +++++-- pxr/imaging/plugin/hdEmbree/pxrIES/ies.h | 25 +++- .../plugin/hdEmbree/pxrIES/pxr-IES.patch | 123 ++++++++++++++++++ 4 files changed, 203 insertions(+), 21 deletions(-) create mode 100644 pxr/imaging/plugin/hdEmbree/pxrIES/README.md create mode 100644 pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/README.md b/pxr/imaging/plugin/hdEmbree/pxrIES/README.md new file mode 100644 index 0000000000..307db52715 --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/README.md @@ -0,0 +1,32 @@ +# IES utilities + +Utilities for reading and using .ies files (IESNA LM-63 Format), which are used +to describe lights. + +The files `ies.h` and `ies.cpp` are originally from +[Cycles](https://www.cycles-renderer.org/), a path-traced renderer that is a +spinoff of the larger [Blender](https://projects.blender.org/blender/blender/) +project, though available with in own repository, and via the Apache 2.0 +license: + +- https://projects.blender.org/blender/cycles +- https://projects.blender.org/blender/cycles/src/branch/main/LICENSE + +## Version + +v4.1.1 ( 234fa733d30a0e49cd10b2c92091500103a1150a ) + +## Setup + +When updating IES, the following steps should be followed: + +1. Copy `src/util/ies.h` and `src/util/ies.cpp` over the + copies in `pxr/imaging/plugin/hdEmbree/pxrIES`. +2. Apply `pxr-IES.patch` to update the source files with modifications for USD, + ie, from the USD repo root folder: + + ```sh + patch -p1 -i pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch + ``` +3. Commit your changes, noting the exact version of blender that the new ies + files were copied from. \ No newline at end of file diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp index a6725cc049..243680d918 100644 --- a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp @@ -2,21 +2,22 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#include "ies.h" #include +#include -#include "util/foreach.h" -#include "util/ies.h" -#include "util/math.h" -#include "util/string.h" +#define _USE_MATH_DEFINES +#include -CCL_NAMESPACE_BEGIN +#if !defined(M_PI) +#define M_PI 3.14159265358979323846 +#endif -// NOTE: For some reason gcc-7.2 does not instantiate this version of the -// allocator here (used in IESTextParser). Works fine for gcc-6, gcc-7.3 and gcc-8. -// -// TODO(sergey): Get to the root of this issue, or confirm this is a compiler -// issue. -template class GuardedAllocator; +#define M_PI_F M_PI + +PXR_NAMESPACE_OPEN_SCOPE + +namespace pxr_ccl { bool IESFile::load(const string &ies) { @@ -43,11 +44,23 @@ int IESFile::packed_size() return 0; } + +static float sizet_to_float(const size_t source_size_t) noexcept +{ + int intermediate_int = static_cast(source_size_t); + float dest_float; + + static_assert(sizeof(intermediate_int) == sizeof(dest_float), + "Size of source and destination for memcpy must be identical"); + std::memcpy(&dest_float, &intermediate_int, sizeof(float)); + return dest_float; +} + void IESFile::pack(float *data) { if (v_angles.size() && h_angles.size()) { - *(data++) = __int_as_float(h_angles.size()); - *(data++) = __int_as_float(v_angles.size()); + *(data++) = sizet_to_float(h_angles.size()); + *(data++) = sizet_to_float(v_angles.size()); memcpy(data, &h_angles[0], h_angles.size() * sizeof(float)); data += h_angles.size(); @@ -407,4 +420,7 @@ IESFile::~IESFile() clear(); } -CCL_NAMESPACE_END +} // namespace pxr_ccl + +PXR_NAMESPACE_CLOSE_SCOPE + diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h index 8c506befdd..0bbae712ff 100644 --- a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h @@ -2,13 +2,21 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef __UTIL_IES_H__ -#define __UTIL_IES_H__ +#ifndef PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_IES_H +#define PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_IES_H -#include "util/string.h" -#include "util/vector.h" -CCL_NAMESPACE_BEGIN +#include +#include + +#include "pxr/pxr.h" + +PXR_NAMESPACE_OPEN_SCOPE + +namespace pxr_ccl { + +using std::string; +using std::vector; class IESFile { public: @@ -24,6 +32,7 @@ class IESFile { protected: bool parse(const string &ies); bool process(); + void process_type_a(); void process_type_b(); void process_type_c(); @@ -41,6 +50,8 @@ class IESFile { enum IESType { TYPE_A = 3, TYPE_B = 2, TYPE_C = 1 } type; }; -CCL_NAMESPACE_END +} /* namespace pxr_ccl */ + +PXR_NAMESPACE_CLOSE_SCOPE -#endif /* __UTIL_IES_H__ */ +#endif /* PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_IES_H */ diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch b/pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch new file mode 100644 index 0000000000..50f8ead381 --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch @@ -0,0 +1,123 @@ +diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp +index a6725cc04..243680d91 100644 +--- a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp ++++ b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp +@@ -2,21 +2,22 @@ + * + * SPDX-License-Identifier: Apache-2.0 */ + ++#include "ies.h" + #include ++#include + +-#include "util/foreach.h" +-#include "util/ies.h" +-#include "util/math.h" +-#include "util/string.h" ++#define _USE_MATH_DEFINES ++#include + +-CCL_NAMESPACE_BEGIN ++#if !defined(M_PI) ++#define M_PI 3.14159265358979323846 ++#endif + +-// NOTE: For some reason gcc-7.2 does not instantiate this version of the +-// allocator here (used in IESTextParser). Works fine for gcc-6, gcc-7.3 and gcc-8. +-// +-// TODO(sergey): Get to the root of this issue, or confirm this is a compiler +-// issue. +-template class GuardedAllocator; ++#define M_PI_F M_PI ++ ++PXR_NAMESPACE_OPEN_SCOPE ++ ++namespace pxr_ccl { + + bool IESFile::load(const string &ies) + { +@@ -43,11 +44,23 @@ int IESFile::packed_size() + return 0; + } + ++ ++static float sizet_to_float(const size_t source_size_t) noexcept ++{ ++ int intermediate_int = static_cast(source_size_t); ++ float dest_float; ++ ++ static_assert(sizeof(intermediate_int) == sizeof(dest_float), ++ "Size of source and destination for memcpy must be identical"); ++ std::memcpy(&dest_float, &intermediate_int, sizeof(float)); ++ return dest_float; ++} ++ + void IESFile::pack(float *data) + { + if (v_angles.size() && h_angles.size()) { +- *(data++) = __int_as_float(h_angles.size()); +- *(data++) = __int_as_float(v_angles.size()); ++ *(data++) = sizet_to_float(h_angles.size()); ++ *(data++) = sizet_to_float(v_angles.size()); + + memcpy(data, &h_angles[0], h_angles.size() * sizeof(float)); + data += h_angles.size(); +@@ -407,4 +420,7 @@ IESFile::~IESFile() + clear(); + } + +-CCL_NAMESPACE_END ++} // namespace pxr_ccl ++ ++PXR_NAMESPACE_CLOSE_SCOPE ++ +diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h +index 8c506befd..0bbae712f 100644 +--- a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h ++++ b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.h +@@ -2,13 +2,21 @@ + * + * SPDX-License-Identifier: Apache-2.0 */ + +-#ifndef __UTIL_IES_H__ +-#define __UTIL_IES_H__ ++#ifndef PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_IES_H ++#define PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_IES_H + +-#include "util/string.h" +-#include "util/vector.h" + +-CCL_NAMESPACE_BEGIN ++#include ++#include ++ ++#include "pxr/pxr.h" ++ ++PXR_NAMESPACE_OPEN_SCOPE ++ ++namespace pxr_ccl { ++ ++using std::string; ++using std::vector; + + class IESFile { + public: +@@ -24,6 +32,7 @@ class IESFile { + protected: + bool parse(const string &ies); + bool process(); ++ + void process_type_a(); + void process_type_b(); + void process_type_c(); +@@ -41,6 +50,8 @@ class IESFile { + enum IESType { TYPE_A = 3, TYPE_B = 2, TYPE_C = 1 } type; + }; + +-CCL_NAMESPACE_END ++} /* namespace pxr_ccl */ ++ ++PXR_NAMESPACE_CLOSE_SCOPE + +-#endif /* __UTIL_IES_H__ */ ++#endif /* PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_IES_H */ From 13d9d8e97403efe5193adb94ceb4f858ada6dc97 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 5 Jun 2024 14:21:40 -0700 Subject: [PATCH 14/24] [hdEmbree] build ies.cpp --- pxr/imaging/plugin/hdEmbree/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt index 55444663f7..5360b4106b 100644 --- a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt +++ b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt @@ -46,8 +46,12 @@ pxr_plugin(hdEmbree renderParam.h PRIVATE_HEADERS + pxrIES/ies.h pxrPbrt/pbrtUtils.h + CPPFILES + pxrIES/ies.cpp + PRIVATE_CLASSES implicitSurfaceSceneIndexPlugin From 1bc9dab5ba73bd9ae82b6037f8f82cb9d5469530 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 5 Jun 2024 16:28:51 -0700 Subject: [PATCH 15/24] [hdEmbree] ies: disable cycles' candela-to-watt conversion factor by default ...since, unlike Cycles, we intend to work in candela --- pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp | 4 ++++ .../plugin/hdEmbree/pxrIES/pxr-IES.patch | 22 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp index 243680d918..eadcb51698 100644 --- a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp @@ -179,6 +179,8 @@ bool IESFile::parse(const string &ies) factor *= parser.get_double(); /* Ballast-Lamp Photometric factor */ parser.get_double(); /* Input Watts */ +#ifdef PXR_IES_USE_CANDELA_TO_WATT_MULTIPLIER + /* Intensity values in IES files are specified in candela (lumen/sr), a photometric quantity. * Cycles expects radiometric quantities, though, which requires a conversion. * However, the Luminous efficacy (ratio of lumens per Watt) depends on the spectral distribution @@ -193,6 +195,8 @@ bool IESFile::parse(const string &ies) */ factor *= 0.0706650768394; +#endif //PXR_IES_USE_CANDELA_TO_WATT_MULTIPLIER + v_angles.reserve(v_angles_num); for (int i = 0; i < v_angles_num; i++) { v_angles.push_back((float)parser.get_double()); diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch b/pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch index 50f8ead381..1420f27361 100644 --- a/pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch @@ -1,5 +1,5 @@ diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp -index a6725cc04..243680d91 100644 +index a6725cc04..eadcb5169 100644 --- a/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp @@ -2,21 +2,22 @@ @@ -62,7 +62,25 @@ index a6725cc04..243680d91 100644 memcpy(data, &h_angles[0], h_angles.size() * sizeof(float)); data += h_angles.size(); -@@ -407,4 +420,7 @@ IESFile::~IESFile() +@@ -166,6 +179,8 @@ bool IESFile::parse(const string &ies) + factor *= parser.get_double(); /* Ballast-Lamp Photometric factor */ + parser.get_double(); /* Input Watts */ + ++#ifdef PXR_IES_USE_CANDELA_TO_WATT_MULTIPLIER ++ + /* Intensity values in IES files are specified in candela (lumen/sr), a photometric quantity. + * Cycles expects radiometric quantities, though, which requires a conversion. + * However, the Luminous efficacy (ratio of lumens per Watt) depends on the spectral distribution +@@ -180,6 +195,8 @@ bool IESFile::parse(const string &ies) + */ + factor *= 0.0706650768394; + ++#endif //PXR_IES_USE_CANDELA_TO_WATT_MULTIPLIER ++ + v_angles.reserve(v_angles_num); + for (int i = 0; i < v_angles_num; i++) { + v_angles.push_back((float)parser.get_double()); +@@ -407,4 +424,7 @@ IESFile::~IESFile() clear(); } From 74c4607002d1e0483a0a1d9756117e2ff6f89f82 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 5 Jun 2024 17:23:34 -0700 Subject: [PATCH 16/24] [hdEmbree] ies: add PxrIESFile subclass, which calculates power via integration --- pxr/imaging/plugin/hdEmbree/CMakeLists.txt | 2 + pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp | 78 +++++++++++++++++++ pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h | 47 +++++++++++ 3 files changed, 127 insertions(+) create mode 100644 pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp create mode 100644 pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h diff --git a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt index 5360b4106b..86575eeba7 100644 --- a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt +++ b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt @@ -47,10 +47,12 @@ pxr_plugin(hdEmbree PRIVATE_HEADERS pxrIES/ies.h + pxrIES/pxrIES.h pxrPbrt/pbrtUtils.h CPPFILES pxrIES/ies.cpp + pxrIES/pxrIES.cpp PRIVATE_CLASSES implicitSurfaceSceneIndexPlugin diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp b/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp new file mode 100644 index 0000000000..407ae08e67 --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp @@ -0,0 +1,78 @@ +// +// 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/pxrIES/pxrIES.h" + +#include + + +#define _USE_MATH_DEFINES +#include + +#if !defined(M_PI) +#define M_PI 3.14159265358979323846 +#endif + + +PXR_NAMESPACE_OPEN_SCOPE + +bool +PxrIESFile::load(std::string const& ies) // non-virtual "override" +{ + clear(); + if (!Base::load(ies)) { + return false; + } + pxr_extra_process(); + return true; +} + +void +PxrIESFile::clear() // non-virtual "override" +{ + Base::clear(); + _power = 0; +} + +void +PxrIESFile::pxr_extra_process() +{ + // find max v_angle delta, as a way to estimate whether the distribution + // is over a hemisphere or sphere + const auto [v_angleMin, v_angleMax] = std::minmax_element( + v_angles.cbegin(), v_angles.cend()); + + // does the distribution cover the whole sphere? + bool is_sphere = false; + if ((*v_angleMax - *v_angleMin) > (M_PI / 2 + 0.1 /* fudge factor*/)) { + is_sphere = true; + } + + _power = 0; + + // integrate the intensity over solid angle to get power + for (size_t h = 0; h < h_angles.size() - 1; ++h) { + for (size_t v = 0; v < v_angles.size() - 1; ++v) { + // approximate dimensions of the patch + float dh = h_angles[h + 1] - h_angles[h]; + float dv = v_angles[v + 1] - v_angles[v]; + // bilinearly interpolate intensity at the patch center + float i0 = (intensity[h][v] + intensity[h][v + 1]) / 2; + float i1 = + (intensity[h + 1][v] + intensity[h + 1][v + 1]) / 2; + float center_intensity = (i0 + i1) / 2; + // solid angle of the patch + float dS = dh * dv * sinf(v_angles[v] + dv / 2); + _power += dS * center_intensity; + } + } + + // ...and divide by surface area of a unit sphere (or hemisphere) + // (this result matches Karma & RIS) + _power /= M_PI * (is_sphere ? 4 : 2); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h b/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h new file mode 100644 index 0000000000..a406b62531 --- /dev/null +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h @@ -0,0 +1,47 @@ +// +// 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_PXRIES_PXRIES_H +#define PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_PXRIES_H + +#include "pxr/pxr.h" +#include "pxr/imaging/plugin/hdEmbree/pxrIES/ies.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/// \class PxrIESFile +/// +/// Extends / overrides some functionality of standard IESFile. +/// +class PxrIESFile : public pxr_ccl::IESFile { +private: + using Base = pxr_ccl::IESFile; + +public: + + bool load(std::string const& ies); // non-virtual "override" + void clear(); // non-virtual "override" + + /// \brief The light's power, as calculated when parsing + inline float power() const + { + return _power; + } + +protected: + // Extra processing we do on-top of the "standard" process() from IESFile + void pxr_extra_process(); + +private: + + float _power = 0; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_PXRIES_H From 3b9c6a20be2059ba2bdcccfb26352d7ddec19264 Mon Sep 17 00:00:00 2001 From: Anders Langlands Date: Tue, 21 May 2024 12:22:04 -0700 Subject: [PATCH 17/24] [hdEmbree] PxrIESFile: add eval() and valid() methods --- pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp | 104 ++++++++++++++++-- pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h | 10 ++ 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp b/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp index 407ae08e67..b05dbe78af 100644 --- a/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp @@ -6,6 +6,8 @@ // #include "pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h" +#include "pxr/base/gf/math.h" + #include @@ -16,11 +18,43 @@ #define M_PI 3.14159265358979323846 #endif +namespace { + +// ------------------------------------------------------------------------- +// Constants +// ------------------------------------------------------------------------- + +template +constexpr T _pi = static_cast(M_PI); + +constexpr float _hemisphereFudgeFactor = 0.1f; + +// ------------------------------------------------------------------------- +// Utility functions +// ------------------------------------------------------------------------- + + +float +_linearstep(float x, float a, float b) +{ + if (x <= a) { + return 0.0f; + } + + if (x >= b) { + return 1.0f; + } + + return (x - a) / (b - a); +} + +} // anonymous namespace + PXR_NAMESPACE_OPEN_SCOPE bool -PxrIESFile::load(std::string const& ies) // non-virtual "override" +PxrIESFile::load(const std::string &ies) // non-virtual "override" { clear(); if (!Base::load(ies)) { @@ -47,7 +81,8 @@ PxrIESFile::pxr_extra_process() // does the distribution cover the whole sphere? bool is_sphere = false; - if ((*v_angleMax - *v_angleMin) > (M_PI / 2 + 0.1 /* fudge factor*/)) { + if ((*v_angleMax - *v_angleMin) + > (_pi / 2.0f + _hemisphereFudgeFactor)) { is_sphere = true; } @@ -60,19 +95,74 @@ PxrIESFile::pxr_extra_process() float dh = h_angles[h + 1] - h_angles[h]; float dv = v_angles[v + 1] - v_angles[v]; // bilinearly interpolate intensity at the patch center - float i0 = (intensity[h][v] + intensity[h][v + 1]) / 2; + float i0 = (intensity[h][v] + intensity[h][v + 1]) / 2.0f; float i1 = - (intensity[h + 1][v] + intensity[h + 1][v + 1]) / 2; - float center_intensity = (i0 + i1) / 2; + (intensity[h + 1][v] + intensity[h + 1][v + 1]) / 2.0f; + float center_intensity = (i0 + i1) / 2.0f; // solid angle of the patch - float dS = dh * dv * sinf(v_angles[v] + dv / 2); + float dS = dh * dv * sinf(v_angles[v] + dv / 2.0f); _power += dS * center_intensity; } } // ...and divide by surface area of a unit sphere (or hemisphere) // (this result matches Karma & RIS) - _power /= M_PI * (is_sphere ? 4 : 2); + _power /= _pi * (is_sphere ? 4.0f : 2.0f); +} + +float +PxrIESFile::eval(float theta, float phi, float angleScale) const +{ + int hi = -1; + int vi = -1; + float dh = 0.0f; + float dv = 0.0f; + + phi = GfMod(phi, 2.0f * _pi); + for (size_t i = 0; i < h_angles.size() - 1; ++i) { + if (phi >= h_angles[i] && phi < h_angles[i + 1]) { + hi = i; + dh = _linearstep(phi, h_angles[i], h_angles[i + 1]); + break; + } + } + + // This formula matches Renderman's behavior + + // Scale with origin at "top" (ie, 180 degress / pi), by a factor + // of 1 / (1 + angleScale), offset so that angleScale = 0 yields the + // identity function. + const float profileScale = 1.0f + angleScale; + theta = (theta - _pi) / profileScale + _pi; + theta = GfClamp(theta, 0.0f, _pi); + + if (theta < 0) { + // vi = 0; + // dv = 0; + return 0.0f; + } else if (theta >= _pi) { + vi = v_angles.size() - 2; + dv = 1; + } else { + for (size_t i = 0; i < v_angles.size() - 1; ++i) { + if (theta >= v_angles[i] && theta < v_angles[i + 1]) { + vi = i; + dv = _linearstep(theta, v_angles[i], v_angles[i + 1]); + break; + } + } + } + + if (hi == -1 || vi == -1) { + // XXX: need to indicate error somehow here + return 0.0f; + } + + // XXX: This should be a cubic interpolation + float i0 = GfLerp(dv, intensity[hi][vi], intensity[hi][vi + 1]); + float i1 = GfLerp(dv, intensity[hi + 1][vi], intensity[hi + 1][vi + 1]); + + return GfLerp(dh, i0, i1); } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h b/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h index a406b62531..3378146df2 100644 --- a/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h +++ b/pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h @@ -33,6 +33,16 @@ class PxrIESFile : public pxr_ccl::IESFile { return _power; } + // returns true if the IES files was successfully loaded and processed and + // is ready to evaluate + bool valid() const + { + return !intensity.empty(); + } + + // evaluate the IES file for the given spherical coordinates + float eval(float theta, float phi, float angleScale) const; + protected: // Extra processing we do on-top of the "standard" process() from IESFile void pxr_extra_process(); From 850b7dee561b195a506571ec15272796e80aa303 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 29 Jul 2024 23:27:05 -0700 Subject: [PATCH 18/24] [hdEmbree] add lighting support for IES files --- pxr/imaging/plugin/hdEmbree/light.cpp | 36 ++++++++++++++++++++++++ pxr/imaging/plugin/hdEmbree/light.h | 10 +++++++ pxr/imaging/plugin/hdEmbree/renderer.cpp | 24 ++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/pxr/imaging/plugin/hdEmbree/light.cpp b/pxr/imaging/plugin/hdEmbree/light.cpp index b3a3c8175d..dd3bae34ae 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -229,6 +229,42 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate, _lightData.shaping.coneSoftness = value.UncheckedGet(); } + if (const auto value = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingIesFile); + value.IsHolding()) { + SdfAssetPath iesAssetPath = value.UncheckedGet(); + std::string iesPath = iesAssetPath.GetResolvedPath(); + if (iesPath.empty()) { + iesPath = iesAssetPath.GetAssetPath(); + } + + if (!iesPath.empty()) { + std::ifstream in(iesPath); + if (!in.is_open()) { + TF_WARN("could not open ies file %s", iesPath.c_str()); + } else { + std::stringstream buffer; + buffer << in.rdbuf(); + + if (!_lightData.shaping.ies.iesFile.load(buffer.str())) { + TF_WARN("could not load ies file %s", iesPath.c_str()); + } + } + } + } + + if (const auto value = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingIesNormalize); + value.IsHolding()) { + _lightData.shaping.ies.normalize = value.UncheckedGet(); + } + + if (const auto value = sceneDelegate->GetLightParamValue( + id, HdLightTokens->shapingIesAngleScale); + value.IsHolding()) { + _lightData.shaping.ies.angleScale = value.UncheckedGet(); + } + _PopulateRtcLight(device, scene); HdEmbreeRenderer *renderer = embreeRenderParam->GetRenderer(); diff --git a/pxr/imaging/plugin/hdEmbree/light.h b/pxr/imaging/plugin/hdEmbree/light.h index f3f4392ae5..0394f82d8b 100644 --- a/pxr/imaging/plugin/hdEmbree/light.h +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -7,6 +7,8 @@ #ifndef PXR_IMAGING_PLUGIN_HD_EMBREE_LIGHT_H #define PXR_IMAGING_PLUGIN_HD_EMBREE_LIGHT_H +#include "pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h" + #include "pxr/base/gf/vec3f.h" #include "pxr/base/gf/matrix3f.h" #include "pxr/base/gf/matrix4f.h" @@ -71,12 +73,20 @@ struct HdEmbree_LightTexture int height = 0; }; +struct HdEmbree_IES +{ + PxrIESFile iesFile; + bool normalize = false; + float angleScale = 0.0f; +}; + struct HdEmbree_Shaping { GfVec3f focusTint; float focus = 0.0f; float coneAngle = 180.0f; float coneSoftness = 0.0f; + HdEmbree_IES ies; }; struct HdEmbree_LightData diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index 32b24331a7..7a5c9e8aaf 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -359,6 +359,27 @@ _IntersectAreaLight(HdEmbree_LightData const& light, RTCRayHit const& rayHit) }; } +float +_EvalIES(HdEmbree_LightData const& light, GfVec3f const& wI) +{ + HdEmbree_IES const& ies = light.shaping.ies; + + if (!ies.iesFile.valid()) { + // Either none specified or there was an error loading. In either case, + // just ignore + return 1.0f; + } + + // emission direction in light space + GfVec3f wE = light.xformWorldToLight.TransformDir(wI).GetNormalized(); + + float theta = _Theta(wE); + float phi = _Phi(wE); + float norm = ies.normalize ? ies.iesFile.power() : 1.0f; + + return ies.iesFile.eval(theta, phi, ies.angleScale) / norm; +} + GfVec3f _EvalLightBasic(HdEmbree_LightData const& light) { @@ -463,6 +484,9 @@ _EvalAreaLight(HdEmbree_LightData const& light, _ShapeSample const& ss, const float thetaOffZ = acosf(cosThetaOffZ); Le *= 1.0f - _Smoothstep(thetaOffZ, GfRange1f(thetaSoft, thetaCone)); + // Apply IES + Le *= _EvalIES(light, wI); + return _LightSample { Le, wI, From 23622c5fd2118b7f17c5e0e5f657458a45c44845 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 22 Jul 2024 05:16:48 -0700 Subject: [PATCH 19/24] [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 7a5c9e8aaf..ed2cde500b 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -1854,8 +1854,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 1dede61faccd501155866f3cc745d792be346996 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 29 Jul 2024 01:08:53 -0700 Subject: [PATCH 20/24] [hdEmbree] add HDEMBREE_LIGHT_CREATE debug code --- pxr/imaging/plugin/hdEmbree/CMakeLists.txt | 7 ++++--- pxr/imaging/plugin/hdEmbree/debugCodes.cpp | 20 ++++++++++++++++++++ pxr/imaging/plugin/hdEmbree/debugCodes.h | 21 +++++++++++++++++++++ pxr/imaging/plugin/hdEmbree/light.cpp | 2 ++ 4 files changed, 47 insertions(+), 3 deletions(-) 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 86575eeba7..8e4a5d68b8 100644 --- a/pxr/imaging/plugin/hdEmbree/CMakeLists.txt +++ b/pxr/imaging/plugin/hdEmbree/CMakeLists.txt @@ -45,6 +45,10 @@ pxr_plugin(hdEmbree context.h renderParam.h + PRIVATE_CLASSES + debugCodes + implicitSurfaceSceneIndexPlugin + PRIVATE_HEADERS pxrIES/ies.h pxrIES/pxrIES.h @@ -54,9 +58,6 @@ pxr_plugin(hdEmbree pxrIES/ies.cpp pxrIES/pxrIES.cpp - PRIVATE_CLASSES - implicitSurfaceSceneIndexPlugin - RESOURCE_FILES plugInfo.json 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 dd3bae34ae..d6dd0e6400 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -90,6 +90,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 a6e65e2cb3e534df4d5b04c6fc3e254013701717 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 30 Jul 2024 22:10:02 -0700 Subject: [PATCH 21/24] [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 d6dd0e6400..6885d20965 100644 --- a/pxr/imaging/plugin/hdEmbree/light.cpp +++ b/pxr/imaging/plugin/hdEmbree/light.cpp @@ -141,6 +141,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 0394f82d8b..1020de3cea 100644 --- a/pxr/imaging/plugin/hdEmbree/light.h +++ b/pxr/imaging/plugin/hdEmbree/light.h @@ -97,6 +97,7 @@ struct HdEmbree_LightData GfVec3f color; HdEmbree_LightTexture texture; 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 ed2cde500b..ae8edc67fd 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -383,7 +383,9 @@ _EvalIES(HdEmbree_LightData const& light, GfVec3f const& wI) 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 91848ca987bfdbf3c7d4e8b6b299a0e8970bb3f9 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 10 Jul 2024 09:38:24 -0700 Subject: [PATCH 22/24] [work] expose WorkGetConcurrencyLimitEnvSetting() --- pxr/base/work/threadLimits.cpp | 8 ++++---- pxr/base/work/threadLimits.h | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pxr/base/work/threadLimits.cpp b/pxr/base/work/threadLimits.cpp index a1fafdd64d..7e02263a1e 100644 --- a/pxr/base/work/threadLimits.cpp +++ b/pxr/base/work/threadLimits.cpp @@ -85,8 +85,8 @@ Work_NormalizeThreadCount(const int n) // Returns the normalized thread limit value from the environment setting. Note // that 0 means "no change", i.e. the environment setting does not apply. -static unsigned -Work_GetConcurrencyLimitSetting() +unsigned +WorkGetConcurrencyLimitEnvSetting() { return Work_NormalizeThreadCount(TfGetEnvSetting(PXR_WORK_THREAD_LIMIT)); } @@ -106,7 +106,7 @@ Work_InitializeThreading() { // Get the thread limit from the environment setting. Note that this value // can be 0, i.e. the environment setting does not apply. - const unsigned settingVal = Work_GetConcurrencyLimitSetting(); + const unsigned settingVal = WorkGetConcurrencyLimitEnvSetting(); // Threading is initialized with maximum physical concurrency. const unsigned physicalLimit = WorkGetPhysicalConcurrencyLimit(); @@ -147,7 +147,7 @@ WorkSetConcurrencyLimit(unsigned n) if (n) { // Get the thread limit from the environment setting. Note this value // may be 0 (default). - const unsigned settingVal = Work_GetConcurrencyLimitSetting(); + const unsigned settingVal = WorkGetConcurrencyLimitEnvSetting(); // Override n with the environment setting. This will make sure that the // setting always wins over the specified value n, but only if the diff --git a/pxr/base/work/threadLimits.h b/pxr/base/work/threadLimits.h index 11bd658c0c..59ef9f1c45 100644 --- a/pxr/base/work/threadLimits.h +++ b/pxr/base/work/threadLimits.h @@ -30,6 +30,13 @@ PXR_NAMESPACE_OPEN_SCOPE /// WORK_API unsigned WorkGetConcurrencyLimit(); +/// Return the value set via the PXR_WORK_THREAD_LIMIT env setting. +/// +/// The returned value is always >= 0, and it is normalized according to the +/// rules in WorkSetConcurrencyLimitArgument. +/// +WORK_API unsigned WorkGetConcurrencyLimitEnvSetting(); + /// Return true if WorkGetPhysicalConcurrencyLimit() returns a number greater /// than 1 and PXR_WORK_THREAD_LIMIT was not set in an attempt to limit the /// process to a single thread, false otherwise. From 820adf831cb6bc800daf8e20cc64de400033d190 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 10 Jul 2024 09:41:04 -0700 Subject: [PATCH 23/24] [hdEmbree] ensure we respect PXR_WORK_THREAD_LIMIT --- pxr/imaging/plugin/hdEmbree/renderer.cpp | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pxr/imaging/plugin/hdEmbree/renderer.cpp b/pxr/imaging/plugin/hdEmbree/renderer.cpp index ae8edc67fd..72b29656d3 100644 --- a/pxr/imaging/plugin/hdEmbree/renderer.cpp +++ b/pxr/imaging/plugin/hdEmbree/renderer.cpp @@ -28,9 +28,15 @@ #include #include +#include +#if TBB_INTERFACE_VERSION_MAJOR < 12 +#include +#endif + #include #include #include +#include #include #include @@ -51,6 +57,30 @@ constexpr float _minLuminanceCutoff = 1e-9f; constexpr GfVec3f _invalidColor = GfVec3f(-std::numeric_limits::infinity()); +// ------------------------------------------------------------------------- +// Old TBB workaround - can remove once OneTBB is mandatory +// ------------------------------------------------------------------------- + +#if TBB_INTERFACE_VERSION_MAJOR < 12 +// 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 = WorkGetConcurrencyLimitEnvSetting(); + if (limit != 0) { + _tbbTaskSchedInit = + std::make_unique(limit); + } + } + + std::unique_ptr _tbbTaskSchedInit; +}; +#else +class _ScopedThreadScheduler { +}; +#endif + // ------------------------------------------------------------------------- // General Math Utilities // ------------------------------------------------------------------------- @@ -1074,6 +1104,7 @@ HdEmbreeRenderer::Render(HdRenderThread *renderThread) // Render by scheduling square tiles of the sample buffer in a parallel // for loop. + _ScopedThreadScheduler scheduler; // Always pass the renderThread to _RenderTiles to allow the first frame // to be interrupted. WorkParallelForN(numTilesX*numTilesY, From 78ca9541a7f4aaf97189a1bc33ff314cd77946b7 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Thu, 15 Aug 2024 13:58:50 -0700 Subject: [PATCH 24/24] [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 f31eecd19f..aa1feab9a0 100644 --- a/build_scripts/build_usd.py +++ b/build_scripts/build_usd.py @@ -2578,6 +2578,7 @@ def _JoinVersion(v): OpenVDB support: {enableOpenVDB} OpenImageIO support: {buildOIIO} OpenColorIO support: {buildOCIO} + Embree support: {buildEmbree} PRMan support: {buildPrman} UsdImaging {buildUsdImaging} usdview: {buildUsdview} @@ -2641,6 +2642,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"),