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.