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 a PxrIESFile class which mimics iesFile, with additonal functionality #11

Open
wants to merge 4 commits into
base: pr/hdEmbree-ies-cycles
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
6 changes: 6 additions & 0 deletions pxr/imaging/plugin/hdEmbree/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,14 @@ pxr_plugin(hdEmbree
implicitSurfaceSceneIndexPlugin

PRIVATE_HEADERS
pxrIES/ies.h
pxrIES/pxrIES.h
pxrPbrt/pbrtUtils.h

CPPFILES
pxrIES/ies.cpp
pxrIES/pxrIES.cpp

RESOURCE_FILES
plugInfo.json

Expand Down
4 changes: 4 additions & 0 deletions pxr/imaging/plugin/hdEmbree/pxrIES/ies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
Expand Down
22 changes: 20 additions & 2 deletions pxr/imaging/plugin/hdEmbree/pxrIES/pxr-IES.patch
Original file line number Diff line number Diff line change
@@ -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 @@
Expand Down Expand Up @@ -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();
}

Expand Down
168 changes: 168 additions & 0 deletions pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//
// 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 "pxr/base/gf/math.h"

#include <algorithm>


#define _USE_MATH_DEFINES
#include <cmath>

#if !defined(M_PI)
#define M_PI 3.14159265358979323846
#endif

namespace {

// -------------------------------------------------------------------------
// Constants
// -------------------------------------------------------------------------

template <typename T>
constexpr T _pi = static_cast<T>(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(const std::string &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)
> (_pi<float> / 2.0f + _hemisphereFudgeFactor)) {
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.0f;
float i1 =
(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.0f);
_power += dS * center_intensity;
}
}

// ...and divide by surface area of a unit sphere (or hemisphere)
// (this result matches Karma & RIS)
_power /= _pi<float> * (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<float>);
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<float>) / profileScale + _pi<float>;
theta = GfClamp(theta, 0.0f, _pi<float>);

if (theta < 0) {
// vi = 0;
// dv = 0;
return 0.0f;
} else if (theta >= _pi<float>) {
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
57 changes: 57 additions & 0 deletions pxr/imaging/plugin/hdEmbree/pxrIES/pxrIES.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// 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 <string>

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;
}

// 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();

private:

float _power = 0;
};

PXR_NAMESPACE_CLOSE_SCOPE

#endif // PXR_IMAGING_PLUGIN_HD_EMBREE_PXRIES_PXRIES_H