Skip to content

Commit

Permalink
[hdEmbree] add direct camera visibility support for rect lights
Browse files Browse the repository at this point in the history
  • Loading branch information
pmolodo committed Jul 31, 2024
1 parent 05c56e7 commit 0cbce3f
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 2 deletions.
4 changes: 4 additions & 0 deletions pxr/imaging/plugin/hdEmbree/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@

#include "pxr/base/gf/matrix4f.h"
#include "pxr/base/vt/array.h"
#include "pxr/base/vt/types.h"

#include <embree3/rtcore.h>

PXR_NAMESPACE_OPEN_SCOPE

class HdRprim;
class HdEmbree_Light;

/// \class HdEmbreePrototypeContext
///
Expand Down Expand Up @@ -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;
};


Expand Down
89 changes: 87 additions & 2 deletions pxr/imaging/plugin/hdEmbree/light.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "pxr/imaging/hio/image.h"

#include <embree3/rtcore_buffer.h>
#include <embree3/rtcore_device.h>
#include <embree3/rtcore_scene.h>

#include <fstream>
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -115,7 +121,8 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate,
static_cast<HdEmbreeRenderParam*>(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();

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -207,12 +221,71 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate,
_lightData.shaping.coneSoftness = value.UncheckedGet<float>();
}

_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<HdEmbree_Rect>(&_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<GfVec3f*>(
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<unsigned*>(
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<HdEmbreeInstanceContext>();
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
{
Expand All @@ -223,10 +296,22 @@ void
HdEmbree_Light::Finalize(HdRenderParam *renderParam)
{
auto* embreeParam = static_cast<HdEmbreeRenderParam*>(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<HdEmbreeInstanceContext*>(
rtcGetGeometryUserData(_lightData.rtcGeometry));

rtcDetachGeometry(scene, _lightData.rtcMeshId);
rtcReleaseGeometry(_lightData.rtcGeometry);
_lightData.rtcMeshId = RTC_INVALID_GEOMETRY_ID;
_lightData.rtcGeometry = nullptr;
}
}

PXR_NAMESPACE_CLOSE_SCOPE
6 changes: 6 additions & 0 deletions pxr/imaging/plugin/hdEmbree/light.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -117,6 +121,8 @@ class HdEmbree_Light final : public HdLight
}

private:
void _PopulateRtcLight(RTCDevice device, RTCScene scene);

HdEmbree_LightData _lightData;
};

Expand Down
1 change: 1 addition & 0 deletions pxr/imaging/plugin/hdEmbree/mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
102 changes: 102 additions & 0 deletions pxr/imaging/plugin/hdEmbree/renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<HdEmbree_Rect>(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)
{
Expand Down Expand Up @@ -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<HdEmbreeInstanceContext*>(
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,
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);

Expand All @@ -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 =
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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<HdEmbreeInstanceContext*>(
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.
Expand Down

0 comments on commit 0cbce3f

Please sign in to comment.