Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[hdEmbree] add direct camera visibility support for rect lights #7

Open
wants to merge 1 commit into
base: pr/hdEmbree-dome-lights
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -117,7 +123,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 @@ -147,6 +154,13 @@ HdEmbree_Light::Sync(HdSceneDelegate *sceneDelegate,

// Get visibility
_lightData.visible = sceneDelegate->GetVisible(id);
_lightData.visible_camera = sceneDelegate->GetLightParamValue(
id, _tokens->inputsVisibilityCamera).GetWithDefault(false);
// XXX: Don't think we can get this to work in Embree unless it's built with
// masking only solution would be to use rtcIntersect instead of rtcOccluded
// for shadow rays, which maybe isn't the worst for a reference renderer
_lightData.visible_shadow = sceneDelegate->GetLightParamValue(
id, _tokens->inputsVisibilityShadow).GetWithDefault(false);

// Switch on the _lightData type and pull the relevant attributes from the scene
// delegate
Expand Down Expand Up @@ -211,12 +225,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 @@ -227,10 +300,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 @@ -88,7 +88,11 @@ struct HdEmbree_LightData
HdEmbree_LightVariant lightVariant;
bool normalize = false;
bool visible = true;
bool visible_camera = true;
bool visible_shadow = true;
HdEmbree_Shaping shaping;
unsigned rtcMeshId = RTC_INVALID_GEOMETRY_ID;
RTCGeometry rtcGeometry = nullptr;
};

class HdEmbree_Light final : public HdLight
Expand Down Expand Up @@ -118,6 +122,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 @@ -422,6 +422,20 @@ _SampleCylinder(GfMatrix4f const& xf, GfMatrix3f const& normalXform,
};
}

_ShapeSample
_IntersectAreaLight(HdEmbree_LightData const& light, RTCRayHit const& rayHit)
{
// XXX: just rect lights at the moment, need to do the others
auto const& rect = std::get<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 @@ -1275,6 +1289,43 @@ _CosineWeightedDirection(GfVec2f const& uniform_float)
return dir;
}

bool
HdEmbreeRenderer::_RayShouldContinue(RTCRayHit const& rayHit) const {
if (rayHit.hit.geomID == RTC_INVALID_GEOMETRY_ID) {
// missed, don't continue
return false;
}

if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) {
// not hit an instance, but a "raw" geometry. This should be a light
const HdEmbreeInstanceContext *instanceContext =
static_cast<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 @@ -1306,6 +1357,13 @@ HdEmbreeRenderer::_TraceRay(unsigned int x, unsigned int y,
rayHit.hit.Ng_z = -rayHit.hit.Ng_z;
}

if (_RayShouldContinue(rayHit)) {
GfVec3f hitPos = _CalculateHitPosition(rayHit);

_TraceRay(x, y, hitPos + dir * _rayHitContinueBias, dir, random);
return;
}

// Write AOVs to attachments that aren't converged.
for (size_t i = 0; i < _aovBindings.size(); ++i) {
HdEmbreeRenderBuffer *renderBuffer =
Expand Down Expand Up @@ -1361,6 +1419,11 @@ HdEmbreeRenderer::_ComputeId(RTCRayHit const& rayHit, TfToken const& idType,
return false;
}

if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) {
// not hit an instance, but a "raw" geometry. This should be a light
return false;
}

// Get the instance and prototype context structures for the hit prim.
// We don't use embree's multi-level instancing; we
// flatten everything in hydra. So instID[0] should always be correct.
Expand Down Expand Up @@ -1401,6 +1464,11 @@ HdEmbreeRenderer::_ComputeDepth(RTCRayHit const& rayHit,
return false;
}

if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) {
// not hit an instance, but a "raw" geometry. This should be a light
return false;
}

if (clip) {
GfVec3f hitPos = _CalculateHitPosition(rayHit);

Expand All @@ -1424,6 +1492,11 @@ HdEmbreeRenderer::_ComputeNormal(RTCRayHit const& rayHit,
return false;
}

if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) {
// not hit an instance, but a "raw" geometry. This should be a light
return false;
}

// We don't use embree's multi-level instancing; we
// flatten everything in hydra. So instID[0] should always be correct.
const HdEmbreeInstanceContext *instanceContext =
Expand Down Expand Up @@ -1462,6 +1535,11 @@ HdEmbreeRenderer::_ComputePrimvar(RTCRayHit const& rayHit,
return false;
}

if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) {
// not hit an instance, but a "raw" geometry. This should be a light
return false;
}

// We don't use embree's multi-level instancing; we
// flatten everything in hydra. So instID[0] should always be correct.
const HdEmbreeInstanceContext *instanceContext =
Expand Down Expand Up @@ -1548,6 +1626,30 @@ HdEmbreeRenderer::_ComputeColor(RTCRayHit const& rayHit,
return domeColor;
}

if (rayHit.hit.instID[0] == RTC_INVALID_GEOMETRY_ID) {
// if it's not an instance then it's almost certainly a light
const HdEmbreeInstanceContext *instanceContext =
static_cast<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