Switch testrender to cone tracing (#1543)

The idea is to stop propagating derivs through BSDFs, which we
never did properly. Instead we trace rays with radius + spread,
which we turn into OSL derivs in SG when we hit a surface. Then
when bouncing off a surface we use the BSDF roughness to set the
spread. In addition we also:

  * Return BSDF samples/eval as a small struct Sample(). This
    avoids output parameters in the methods.
  * Stop passing a full ShaderGlobals to the BSDF methods. Pass
    a viewing direction wo instead so they could run in a simpler
    context.

Signed-off-by: Alejandro Conty <aconty@imageworks.com>
This commit is contained in:
Alejandro Conty
2022-08-03 23:25:09 -07:00
committed by Larry Gritz
parent 162b355bc8
commit d42c5d3e1d
17 changed files with 380 additions and 350 deletions
+12 -12
View File
@@ -31,19 +31,18 @@ fresnel_dielectric(float cosi, float eta)
}
inline float
fresnel_refraction(const Dual2<Vec3>& I, const Vec3& N, float eta,
Dual2<Vec3>& T)
fresnel_refraction(const Vec3& I, const Vec3& N, float eta, Vec3& T)
{
// compute refracted direction and fresnel term
// return value will be 0 if TIR occurs
// NOTE: I is the incoming ray direction (points toward the surface, normalized)
// N is the surface normal (points toward the incoming ray origin, normalized)
// T is the outgoing refracted direction (points away from the surface)
Dual2<float> cosi = -dot(I, N);
float cosi = -dot(I, N);
// check which side of the surface we are on
Vec3 Nn;
float neta;
if (cosi.val() > 0) {
if (cosi > 0) {
// we are on the outside of the surface, going in
neta = 1 / eta;
Nn = N;
@@ -53,12 +52,12 @@ fresnel_refraction(const Dual2<Vec3>& I, const Vec3& N, float eta,
neta = eta;
Nn = -N;
}
Dual2<float> arg = 1.0f - (neta * neta * (1.0f - cosi * cosi));
if (arg.val() >= 0) {
Dual2<float> dnp = sqrt(arg);
Dual2<float> nK = (neta * cosi) - dnp;
T = I * neta + Nn * nK;
return 1 - fresnel_dielectric(cosi.val(), eta);
float arg = 1.0f - (neta * neta * (1.0f - cosi * cosi));
if (arg >= 0) {
float dnp = sqrtf(arg);
float nK = (neta * cosi) - dnp;
T = I * neta + Nn * nK;
return 1 - fresnel_dielectric(cosi, eta);
}
T = make_Vec3(0, 0, 0);
return 0;
@@ -93,9 +92,10 @@ Color3 fresnel_conductor(float cos_theta, Color3 n, Color3 k)
return 0.5f * (rp + rs);
}
inline float fresnel_schlick(float cos_theta, float F0, float F90)
inline float
fresnel_schlick(float cos_theta, float F0, float F90)
{
float x = OIIO::clamp(1.0f - cos_theta, 0.0f, 1.0f);
float x = Imath::clamp(1.0f - cos_theta, 0.0f, 1.0f);
float x2 = x * x;
float x4 = x2 * x2;
float x5 = x4 * x;
+40 -16
View File
@@ -40,19 +40,6 @@ OSL_NAMESPACE_ENTER
class OptixRenderer; // FIXME -- should not be here
// Note: not used in OptiX mode
struct Ray {
Ray(const Dual2<Vec3>& o, const Dual2<Vec3>& d) : origin(o), direction(d) {}
Vec3 point(float t) const { return origin.val() + direction.val() * t; }
Dual2<Vec3> point(Dual2<float> t) const { return origin + direction * t; }
Dual2<Vec3> origin, direction;
};
// build two vectors orthogonal to the first, assumes n is normalized
inline void
ortho(const Vec3& n, Vec3& x, Vec3& y)
@@ -63,6 +50,40 @@ ortho(const Vec3& n, Vec3& x, Vec3& y)
}
// Note: not used in OptiX mode
struct Ray {
Ray(const Vec3& o, const Vec3& d, float radius, float spread)
: origin(o), direction(d), radius(radius), spread(spread)
{
}
Vec3 point(float t) const { return origin + direction * t; }
Dual2<Vec3> dual_direction() const
{
Dual2<Vec3> v;
v.val() = direction;
ortho(direction, v.dx(), v.dy());
v.dx() *= spread;
v.dy() *= spread;
return v;
}
Dual2<Vec3> point(Dual2<float> t) const
{
const float r = radius + spread * t.val();
Dual2<Vec3> p;
p.val() = point(t.val());
ortho(direction, p.dx(), p.dy());
p.dx() *= r;
p.dy() *= r;
return p;
}
Vec3 origin, direction;
float radius, spread;
};
struct Camera {
Camera() {}
@@ -99,9 +120,12 @@ struct Camera {
// Get a ray for the given screen coordinates.
Ray get(float x, float y) const
{
Dual2<Vec3> v = cx * (Dual2<float>(x, 1, 0) * invw - 0.5f)
+ cy * (0.5f - Dual2<float>(y, 0, -1) * invh) + dir;
return Ray(eye, normalize(v));
const Vec3 v = (cx * (x * invw - 0.5f) + cy * (0.5f - y * invh) + dir)
.normalize();
const float cos_a = dir.dot(v);
const float spread
= sqrtf(invw * invh * cx.length() * cy.length() * cos_a) * cos_a;
return Ray(eye, v, 0, spread);
}
// Specified by user:
+17 -11
View File
@@ -81,9 +81,10 @@ struct Sampling {
pdf = cos_theta * float(M_1_PI);
}
static void sample_uniform_hemisphere(const Vec3& N, float rndx, float rndy, Vec3& out, float& pdf)
static void sample_uniform_hemisphere(const Vec3& N, float rndx, float rndy,
Vec3& out, float& pdf)
{
float phi = float(2 * M_PI) * rndx;
float phi = float(2 * M_PI) * rndx;
float cos_theta = rndy;
float sin_theta = sqrtf(1 - cos_theta * cos_theta);
TangentFrame f(N);
@@ -182,7 +183,8 @@ struct MIS {
// https://jcgt.org/published/0009/04/01/
struct Sampler {
Sampler(int px, int py, int si)
: seed(((px & 2047) << 22) | ((py & 2047) << 11)), index(reversebits(si))
: seed(((px & 2047) << 22) | ((py & 2047) << 11))
, index(reversebits(si))
{
assert(si < (1 << 24));
}
@@ -197,21 +199,25 @@ struct Sampler {
0x060671u, 0x0909a3u, 0x171616u, 0x3a3939u, 0x717777u, 0xa3aaaau
};
seed += 4; // advance depth for next call
uint32_t scrambled_index = owen_scramble(index, hash(seed - 4)) & 0xFFFFFF;
uint32_t result_x = scrambled_index; // already reversed
uint32_t scrambled_index = owen_scramble(index, hash(seed - 4))
& 0xFFFFFF;
uint32_t result_x = scrambled_index; // already reversed
uint32_t result_y = 0;
uint32_t result_z = 0;
uint32_t ymatrix = 1;
uint32_t ymatrix = 1;
for (int c = 0; c < 24; c++) {
uint32_t bit = (scrambled_index >> c) & 1;
result_y ^= bit * ymatrix;
result_z ^= bit * zmatrix[c];
ymatrix ^= ymatrix << 1; // generate procedurally instead of storing this
ymatrix ^= ymatrix
<< 1; // generate procedurally instead of storing this
}
// scramble results and scale by 2^-24 to guarantee equally spaced values in [0,1)
return { (owen_scramble(result_x, hash(seed - 3)) >> 8) * 5.96046448e-8f,
(owen_scramble(result_y, hash(seed - 2)) >> 8) * 5.96046448e-8f,
(owen_scramble(result_z, hash(seed - 1)) >> 8) * 5.96046448e-8f };
return {
(owen_scramble(result_x, hash(seed - 3)) >> 8) * 5.96046448e-8f,
(owen_scramble(result_y, hash(seed - 2)) >> 8) * 5.96046448e-8f,
(owen_scramble(result_z, hash(seed - 1)) >> 8) * 5.96046448e-8f
};
}
private:
@@ -248,7 +254,7 @@ private:
// assumes reversed input
p ^= p * 0x3d20adea;
p += s;
p *= (s>> 16) | 1;
p *= (s >> 16) | 1;
p ^= p * 0x05526c56;
p ^= p * 0x53a22864;
return reversebits(p);
+219 -236
View File
@@ -14,14 +14,20 @@ namespace { // anonymous namespace
// define a few handy utility functions
Color3 clamp(const Color3& c, float min, float max)
float
clamp(float v, float min, float max)
{
return Color3(
c.x < min ? min : max < c.x ? max : c.x,
c.y < min ? min : max < c.y ? max : c.y,
c.z < min ? min : max < c.z ? max : c.z);
return v < min ? min : max < v ? max : v;
}
Color3
clamp(const Color3& c, float min, float max)
{
return Color3(clamp(c.x, min, max), clamp(c.y, min, max),
clamp(c.z, min, max));
}
bool is_black(const Color3& c)
{
return c.x == 0 && c.y == 0 && c.z == 0;
@@ -63,8 +69,7 @@ enum ClosureIDs {
// these structures hold the parameters of each closure type
// they will be contained inside ClosureComponent
struct EmptyParams {
};
struct EmptyParams {};
struct DiffuseParams {
Vec3 N;
};
@@ -303,14 +308,14 @@ register_closures(OSL::ShadingSystem* shadingsys)
CLOSURE_FLOAT_PARAM(RefractionParams, eta),
CLOSURE_FINISH_PARAM(RefractionParams) } },
{ "transparent", TRANSPARENT_ID, { CLOSURE_FINISH_PARAM(EmptyParams) } },
// See MATERIALX_CLOSURES in stdosl.h
// See MATERIALX_CLOSURES in stdosl.h
{ "oren_nayar_diffuse_bsdf",
MX_OREN_NAYAR_DIFFUSE_ID,
{ CLOSURE_VECTOR_PARAM(MxOrenNayarDiffuseParams, N),
CLOSURE_COLOR_PARAM(MxOrenNayarDiffuseParams, albedo),
CLOSURE_FLOAT_PARAM(MxOrenNayarDiffuseParams, roughness),
CLOSURE_STRING_KEYPARAM(MxOrenNayarDiffuseParams, label, "label"),
CLOSURE_FINISH_PARAM(MxOrenNayarDiffuseParams) } },
MX_OREN_NAYAR_DIFFUSE_ID,
{ CLOSURE_VECTOR_PARAM(MxOrenNayarDiffuseParams, N),
CLOSURE_COLOR_PARAM(MxOrenNayarDiffuseParams, albedo),
CLOSURE_FLOAT_PARAM(MxOrenNayarDiffuseParams, roughness),
CLOSURE_STRING_KEYPARAM(MxOrenNayarDiffuseParams, label, "label"),
CLOSURE_FINISH_PARAM(MxOrenNayarDiffuseParams) } },
{ "burley_diffuse_bsdf",
MX_BURLEY_DIFFUSE_ID,
{ CLOSURE_VECTOR_PARAM(MxBurleyDiffuseParams, N),
@@ -328,8 +333,10 @@ register_closures(OSL::ShadingSystem* shadingsys)
CLOSURE_FLOAT_PARAM(MxDielectricParams, roughness_y),
CLOSURE_FLOAT_PARAM(MxDielectricParams, ior),
CLOSURE_STRING_PARAM(MxDielectricParams, distribution),
CLOSURE_FLOAT_KEYPARAM(MxDielectricParams, thinfilm_thickness, "thinfilm_thickness"),
CLOSURE_FLOAT_KEYPARAM(MxDielectricParams, thinfilm_ior, "thinfilm_ior"),
CLOSURE_FLOAT_KEYPARAM(MxDielectricParams, thinfilm_thickness,
"thinfilm_thickness"),
CLOSURE_FLOAT_KEYPARAM(MxDielectricParams, thinfilm_ior,
"thinfilm_ior"),
CLOSURE_STRING_KEYPARAM(MxDielectricParams, label, "label"),
CLOSURE_FINISH_PARAM(MxDielectricParams) } },
{ "conductor_bsdf",
@@ -341,8 +348,10 @@ register_closures(OSL::ShadingSystem* shadingsys)
CLOSURE_COLOR_PARAM(MxConductorParams, ior),
CLOSURE_COLOR_PARAM(MxConductorParams, extinction),
CLOSURE_STRING_PARAM(MxConductorParams, distribution),
CLOSURE_FLOAT_KEYPARAM(MxConductorParams, thinfilm_thickness, "thinfilm_thickness"),
CLOSURE_FLOAT_KEYPARAM(MxConductorParams, thinfilm_ior, "thinfilm_ior"),
CLOSURE_FLOAT_KEYPARAM(MxConductorParams, thinfilm_thickness,
"thinfilm_thickness"),
CLOSURE_FLOAT_KEYPARAM(MxConductorParams, thinfilm_ior,
"thinfilm_ior"),
CLOSURE_STRING_KEYPARAM(MxConductorParams, label, "label"),
CLOSURE_FINISH_PARAM(MxConductorParams) } },
{ "generalized_schlick_bsdf",
@@ -357,8 +366,10 @@ register_closures(OSL::ShadingSystem* shadingsys)
CLOSURE_COLOR_PARAM(MxGeneralizedSchlickParams, f90),
CLOSURE_FLOAT_PARAM(MxGeneralizedSchlickParams, exponent),
CLOSURE_STRING_PARAM(MxGeneralizedSchlickParams, distribution),
CLOSURE_FLOAT_KEYPARAM(MxGeneralizedSchlickParams, thinfilm_thickness, "thinfilm_thickness"),
CLOSURE_FLOAT_KEYPARAM(MxGeneralizedSchlickParams, thinfilm_ior, "thinfilm_ior"),
CLOSURE_FLOAT_KEYPARAM(MxGeneralizedSchlickParams,
thinfilm_thickness, "thinfilm_thickness"),
CLOSURE_FLOAT_KEYPARAM(MxGeneralizedSchlickParams, thinfilm_ior,
"thinfilm_ior"),
CLOSURE_STRING_KEYPARAM(MxGeneralizedSchlickParams, label, "label"),
CLOSURE_FINISH_PARAM(MxGeneralizedSchlickParams) } },
{ "translucent_bsdf",
@@ -368,8 +379,8 @@ register_closures(OSL::ShadingSystem* shadingsys)
CLOSURE_STRING_KEYPARAM(MxTranslucentParams, label, "label"),
CLOSURE_FINISH_PARAM(MxTranslucentParams) } },
{ "transparent_bsdf",
MX_TRANSPARENT_ID,
{ CLOSURE_FINISH_PARAM(EmptyParams) } },
MX_TRANSPARENT_ID,
{ CLOSURE_FINISH_PARAM(EmptyParams) } },
{ "subsurface_bssrdf",
MX_SUBSURFACE_ID,
{ CLOSURE_VECTOR_PARAM(MxSubsurfaceParams, N),
@@ -431,20 +442,18 @@ template<int trans> struct Diffuse final : public BSDF, DiffuseParams {
if (trans)
N = -N;
}
Color3 eval(const OSL::ShaderGlobals& /*sg*/, const OSL::Vec3& wi,
float& pdf) const override
Sample eval(const Vec3& /*wo*/, const OSL::Vec3& wi) const override
{
pdf = std::max(N.dot(wi), 0.0f) * float(M_1_PI);
return Color3(1.0f);
const float pdf = std::max(N.dot(wi), 0.0f) * float(M_1_PI);
return { wi, Color3(1.0f), pdf, 1.0f };
}
Color3 sample(const OSL::ShaderGlobals& /*sg*/, float rx, float ry,
float /*rz*/, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& /*wo*/, float rx, float ry,
float /*rz*/) const override
{
Vec3 out_dir;
float pdf;
Sampling::sample_cosine_hemisphere(N, rx, ry, out_dir, pdf);
wi = out_dir; // FIXME: leave derivs 0?
return Color3(1.0f);
return { out_dir, Color3(1.0f), pdf, 1.0f };
}
};
@@ -456,42 +465,30 @@ struct OrenNayar final : public BSDF, OrenNayarParams {
A = 1 - 0.50f * s2 / (s2 + 0.33f);
B = 0.45f * s2 / (s2 + 0.09f);
}
Color3 eval(const OSL::ShaderGlobals& sg, const OSL::Vec3& wi,
float& pdf) const override
Sample eval(const Vec3& wo, const OSL::Vec3& wi) const override
{
float NL = N.dot(wi);
float NV = -N.dot(sg.I);
float NV = N.dot(wo);
if (NL > 0 && NV > 0) {
pdf = NL * float(M_1_PI);
// Simplified math from: "A tiny improvement of Oren-Nayar reflectance model"
// by Yasuhiro Fujii
// http://mimosa-pudica.net/improved-oren-nayar.html
// NOTE: This is using the math to match the original ON model, not the tweak
// proposed in the text which is a slightly different BRDF
float LV = -sg.I.dot(wi);
float LV = wo.dot(wi);
float s = LV - NL * NV;
float stinv = s > 0 ? s / std::max(NL, NV) : 0.0f;
return Color3(A + B * stinv);
return { wi, Color3(A + B * stinv), NL * float(M_1_PI), 1.0f };
}
return Color3(pdf = 0);
return {};
}
Color3 sample(const OSL::ShaderGlobals& sg, float rx, float ry,
float /*rz*/, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& wo, float rx, float ry,
float /*rz*/) const override
{
Vec3 out_dir;
float pdf;
Sampling::sample_cosine_hemisphere(N, rx, ry, out_dir, pdf);
wi = out_dir; // leave derivs 0?
const float NL = N.dot(wi.val());
const float NV = -N.dot(sg.I);
if (NL > 0 && NV > 0) {
float LV = -sg.I.dot(wi.val());
float s = LV - NL * NV;
float stinv = s > 0 ? s / std::max(NL, NV) : 0.0f;
return Color3(A + B * stinv);
}
return Color3(0.0f);
return eval(wo, out_dir);
}
private:
@@ -500,31 +497,30 @@ private:
struct Phong final : public BSDF, PhongParams {
Phong(const PhongParams& params) : BSDF(), PhongParams(params) {}
Color3 eval(const OSL::ShaderGlobals& sg, const OSL::Vec3& wi,
float& pdf) const override
Sample eval(const Vec3& wo, const Vec3& wi) const override
{
float cosNI = N.dot(wi);
float cosNO = -N.dot(sg.I);
float cosNO = N.dot(wo);
if (cosNI > 0 && cosNO > 0) {
// reflect the view vector
Vec3 R = (2 * cosNO) * N + sg.I;
Vec3 R = (2 * cosNO) * N - wo;
float cosRI = R.dot(wi);
if (cosRI > 0) {
pdf = (exponent + 1) * float(M_1_PI / 2)
* OIIO::fast_safe_pow(cosRI, exponent);
return Color3(cosNI * (exponent + 2) / (exponent + 1));
const float pdf = (exponent + 1) * float(M_1_PI / 2)
* OIIO::fast_safe_pow(cosRI, exponent);
return { wi, Color3(cosNI * (exponent + 2) / (exponent + 1)),
pdf, 1 / (1 + exponent) };
}
}
return Color3(pdf = 0);
return {};
}
Color3 sample(const OSL::ShaderGlobals& sg, float rx, float ry,
float /*rz*/, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& wo, float rx, float ry,
float /*rz*/) const override
{
float cosNO = -N.dot(sg.I);
float cosNO = N.dot(wo);
if (cosNO > 0) {
// reflect the view vector
Vec3 R = (2 * cosNO) * N + sg.I;
Vec3 R = (2 * cosNO) * N - wo;
TangentFrame tf(R);
float phi = 2 * float(M_PI) * rx;
float sp, cp;
@@ -532,29 +528,22 @@ struct Phong final : public BSDF, PhongParams {
float cosTheta = OIIO::fast_safe_pow(ry, 1 / (exponent + 1));
float sinTheta2 = 1 - cosTheta * cosTheta;
float sinTheta = sinTheta2 > 0 ? sqrtf(sinTheta2) : 0;
wi = tf.get(cp * sinTheta, sp * sinTheta,
cosTheta); // leave derivs 0?
float cosNI = N.dot(wi.val());
if (cosNI > 0) {
pdf = (exponent + 1) * float(M_1_PI / 2)
* OIIO::fast_safe_pow(cosTheta, exponent);
return Color3(cosNI * (exponent + 2) / (exponent + 1));
}
Vec3 wi = tf.get(cp * sinTheta, sp * sinTheta, cosTheta);
return eval(wo, wi);
}
return Color3(pdf = 0);
return {};
}
};
struct Ward final : public BSDF, WardParams {
Ward(const WardParams& params) : BSDF(), WardParams(params) {}
Color3 eval(const OSL::ShaderGlobals& sg, const OSL::Vec3& wi,
float& pdf) const override
Sample eval(const Vec3& wo, const OSL::Vec3& wi) const override
{
float cosNO = -N.dot(sg.I);
float cosNO = N.dot(wo);
float cosNI = N.dot(wi);
if (cosNI > 0 && cosNO > 0) {
// get half vector and get x,y basis on the surface for anisotropy
Vec3 H = wi - sg.I;
Vec3 H = wi + wo;
H.normalize(); // normalize needed for pdf
TangentFrame tf(N, T);
// eq. 4
@@ -566,16 +555,16 @@ struct Ward final : public BSDF, WardParams {
/ (dotn * dotn));
float c = float(4 * M_PI) * ax * ay;
float k = oh * dotn * dotn * dotn;
pdf = e / (c * k);
return Color3(k * sqrtf(cosNI / cosNO));
float pdf = e / (c * k);
return { wi, Color3(k * sqrtf(cosNI / cosNO)), pdf,
std::max(ax, ay) };
}
return Color3(0.0f);
return {};
}
Color3 sample(const OSL::ShaderGlobals& sg, float rx, float ry,
float /*rz*/, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& wo, float rx, float ry,
float /*rz*/) const override
{
float cosNO = -N.dot(sg.I);
float cosNO = N.dot(wo);
if (cosNO > 0) {
// get x,y basis on the surface for anisotropy
TangentFrame tf(N, T);
@@ -609,22 +598,21 @@ struct Ward final : public BSDF, WardParams {
// transform to world space
h = tf.get(h.x, h.y, h.z);
// generate the final sample
float oh = -h.dot(sg.I);
wi = 2 * oh * h + sg.I; // TODO: leave derivs 0?
if (sg.Ng.dot(wi.val()) > 0) {
float cosNI = N.dot(wi.val());
if (cosNI > 0) {
// eq. 9
float e = OIIO::fast_exp(-(dotx * dotx + doty * doty)
/ (dotn * dotn));
float c = float(4 * M_PI) * ax * ay;
float k = oh * dotn * dotn * dotn;
pdf = e / (c * k);
return Color3(k * sqrtf(cosNI / cosNO));
}
float oh = h.dot(wo);
Vec3 wi = 2 * oh * h - wo;
float cosNI = N.dot(wi);
if (cosNI > 0) {
// eq. 9
float e = OIIO::fast_exp(-(dotx * dotx + doty * doty)
/ (dotn * dotn));
float c = float(4 * M_PI) * ax * ay;
float k = oh * dotn * dotn * dotn;
float pdf = e / (c * k);
return { wi, Color3(k * sqrtf(cosNI / cosNO)), pdf,
std::max(ax, ay) };
}
}
return Color3(0.0f);
return {};
}
};
@@ -756,19 +744,17 @@ struct Microfacet final : public BSDF, MicrofacetParams {
: TangentFrame(N, U))
{
}
Color3 get_albedo(const ShaderGlobals& sg) const override
Color3 get_albedo(const Vec3& wo) const override
{
if (Refract == 2)
return Color3(1.0f);
// FIXME: this heuristic is not particularly good, and looses energy
// compared to the reference solution
float fr = fresnel_dielectric(-N.dot(sg.I), eta);
float fr = fresnel_dielectric(N.dot(wo), eta);
return Color3(Refract ? 1 - fr : fr);
}
Color3 eval(const OSL::ShaderGlobals& sg, const OSL::Vec3& wi,
float& pdf) const override
Sample eval(const Vec3& wo, const OSL::Vec3& wi) const override
{
Vec3 wo = -sg.I;
const Vec3 wo_l = tf.tolocal(wo);
const Vec3 wi_l = tf.tolocal(wi);
if (Refract == 0 || Refract == 2) {
@@ -781,13 +767,14 @@ struct Microfacet final : public BSDF, MicrofacetParams {
const float G1 = evalG1(Lambda_o);
const float Fr = fresnel_dielectric(m.dot(wo_l), eta);
pdf = (G1 * D * 0.25f) / wo_l.z;
float pdf = (G1 * D * 0.25f) / wo_l.z;
float out = G2 / G1;
if (Refract == 2) {
pdf *= Fr;
return Color3(out);
return { wi, Color3(out), pdf, std::max(xalpha, yalpha) };
} else {
return Color3(out * Fr);
return { wi, Color3(out * Fr), pdf,
std::max(xalpha, yalpha) };
}
}
}
@@ -806,7 +793,7 @@ struct Microfacet final : public BSDF, MicrofacetParams {
// eq. 33: first we calculate D(m) with m=Ht:
const float cosThetaM = Ht.z;
if (cosThetaM <= 0.0f)
return Color3(0.0f);
return {};
const float Dt = evalD(Ht);
const float Lambda_o = evalLambda(wo_l);
const float Lambda_i = evalLambda(wi_l);
@@ -815,29 +802,30 @@ struct Microfacet final : public BSDF, MicrofacetParams {
// probability
float invHt2 = 1 / ht.dot(ht);
pdf = (fabsf(cosHI * cosHO) * (eta * eta) * (G1 * Dt)
* invHt2)
/ wo_l.z;
float pdf = (fabsf(cosHI * cosHO) * (eta * eta) * (G1 * Dt)
* invHt2)
/ wo_l.z;
float out = G2 / G1;
if (Refract == 2) {
pdf *= Ft;
return Color3(out);
return { wi, Color3(out), pdf,
std::max(xalpha, yalpha) };
} else {
return Color3(out * Ft);
return { wi, Color3(out * Ft), pdf,
std::max(xalpha, yalpha) };
}
}
}
}
return Color3(pdf = 0);
return {};
}
Color3 sample(const OSL::ShaderGlobals& sg, float rx, float ry,
float rz, OSL::Dual2<OSL::Vec3>& wi, float& pdf) const override
Sample sample(const Vec3& wo, float rx, float ry, float rz) const override
{
const Vec3 wo_l = tf.tolocal(-sg.I);
const Vec3 wo_l = tf.tolocal(wo);
const float cosNO = wo_l.z;
if (!(cosNO > 0))
return Color3(pdf = 0);
return {};
const Vec3 m = sampleMicronormal(wo_l, rx, ry);
const float cosMO = m.dot(wo_l);
const float F = fresnel_dielectric(cosMO, eta);
@@ -851,19 +839,20 @@ struct Microfacet final : public BSDF, MicrofacetParams {
const float G2 = evalG2(Lambda_o, Lambda_i);
const float G1 = evalG1(Lambda_o);
wi = tf.toworld(wi_l);
Vec3 wi = tf.toworld(wi_l);
pdf = (G1 * D * 0.25f) / cosNO;
float pdf = (G1 * D * 0.25f) / cosNO;
float out = G2 / G1;
if (Refract == 2) {
pdf *= F;
return Color3(out);
return { wi, Color3(out), pdf, std::max(xalpha, yalpha) };
} else
return Color3(F * out);
return { wi, Color3(F * out), pdf, std::max(xalpha, yalpha) };
} else {
const Vec3 M = tf.toworld(m);
float Ft = fresnel_refraction(sg.I, M, eta, wi);
const Vec3 wi_l = tf.tolocal(wi.val());
const Vec3 M = tf.toworld(m);
Vec3 wi;
float Ft = fresnel_refraction(-wo, M, eta, wi);
const Vec3 wi_l = tf.tolocal(wi);
const float cosHO = m.dot(wo_l);
const float cosHI = m.dot(wi_l);
const float D = evalD(m);
@@ -876,16 +865,16 @@ struct Microfacet final : public BSDF, MicrofacetParams {
const Vec3 ht = -(eta * wi_l + wo_l);
const float invHt2 = 1.0f / ht.dot(ht);
pdf = (fabsf(cosHI * cosHO) * (eta * eta) * (G1 * D) * invHt2)
/ fabsf(wo_l.z);
float pdf = (fabsf(cosHI * cosHO) * (eta * eta) * (G1 * D) * invHt2)
/ fabsf(wo_l.z);
float out = G2 / G1;
if (Refract == 2) {
pdf *= Ft;
return Color3(out);
return { wi, Color3(out), pdf, std::max(xalpha, yalpha) };
} else
return Color3(Ft * out);
return { wi, Color3(Ft * out), pdf, std::max(xalpha, yalpha) };
}
return Color3(pdf = 0);
return {};
}
private:
@@ -992,7 +981,7 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
return cos_theta;
}
Color3 get_albedo(const ShaderGlobals& sg) const override
Color3 get_albedo(const Vec3& wo) const override
{
// if transmission is enabled, punt on
if (EnableTransmissionLobe)
@@ -1000,13 +989,11 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
// FIXME: this heuristic is not particularly good, and looses energy
// compared to the reference solution
return MxMicrofacetParams::evalR(get_fresnel_angle(-MxMicrofacetParams::N.dot(sg.I)));
return MxMicrofacetParams::evalR(get_fresnel_angle(MxMicrofacetParams::N.dot(wo)));
}
Color3 eval(const OSL::ShaderGlobals& sg, const OSL::Vec3& wi,
float& pdf) const override
Sample eval(const Vec3& wo, const OSL::Vec3& wi) const override
{
Vec3 wo = -sg.I;
const Vec3 wo_l = tf.tolocal(wo);
const Vec3 wi_l = tf.tolocal(wi);
@@ -1023,7 +1010,7 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
const float cosHO = m.dot(wo_l);
const float cosHOf = get_fresnel_angle(cosHO);
const Color3 Fr = MxMicrofacetParams::evalR(cosHOf);
pdf = (G1 * D * 0.25f) / wo_l.z;
float pdf = (G1 * D * 0.25f) / wo_l.z;
float out = G2 / G1;
if (EnableTransmissionLobe)
{
@@ -1035,7 +1022,7 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
out /= probR;
pdf *= probR;
}
return Fr * out;
return { wi, Fr * out, pdf, std::max(MxMicrofacetParams::roughness_x, MxMicrofacetParams::roughness_y) };
}
// handle refraction lobe
@@ -1054,7 +1041,7 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
// eq. 33: first we calculate D(m) with m=Ht:
const float cosThetaM = Ht.z;
if (cosThetaM <= 0.0f)
return Color3(0.0f);
return {};
const float Dt = evalD(Ht);
const float Lambda_o = evalLambda(wo_l);
const float Lambda_i = evalLambda(wi_l);
@@ -1063,7 +1050,7 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
// probability
float invHt2 = 1 / ht.dot(ht);
pdf = (fabsf(cosHI * cosHO) * (refraction_ior * refraction_ior) * (G1 * Dt)
float pdf = (fabsf(cosHI * cosHO) * (refraction_ior * refraction_ior) * (G1 * Dt)
* invHt2)
/ wo_l.z;
float out = G2 / G1;
@@ -1073,21 +1060,21 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
const float weight_r = Fr.x + Fr.y + Fr.z;
const float probT = weight_t / (weight_t + weight_r + 1e-6f);
pdf *= probT;
return Ft * out / probT;
return { wi, Ft * out / probT, pdf, std::max(MxMicrofacetParams::roughness_x, MxMicrofacetParams::roughness_y) };
}
}
return Color3(pdf = 0);
return {};
}
Color3 sample(const OSL::ShaderGlobals& sg, float rx, float ry,
float rz, OSL::Dual2<OSL::Vec3>& wi, float& pdf) const override
Sample sample(const Vec3& wo, float rx, float ry,
float rz) const override
{
const Vec3 wo_l = tf.tolocal(-sg.I);
const Vec3 wo_l = tf.tolocal(wo);
const float cosNO = wo_l.z;
if (!(cosNO > 0))
return Color3(pdf = 0);
return {};
const Vec3 m = sampleMicronormal(wo_l, rx, ry);
const float cosMO = m.dot(wo_l);
const float cosMOf = get_fresnel_angle(cosMO);
@@ -1103,9 +1090,9 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
const Color3 Fr = MxMicrofacetParams::evalR(cosMOf);
wi = tf.toworld(wi_l);
Vec3 wi = tf.toworld(wi_l);
pdf = (G1 * D * 0.25f) / cosNO;
float pdf = (G1 * D * 0.25f) / cosNO;
float out = G2 / G1;
if (EnableTransmissionLobe)
@@ -1118,8 +1105,8 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
{
// switch to transmitted vector instead
const Vec3 M = tf.toworld(m);
fresnel_refraction(sg.I, M, refraction_ior, wi);
const Vec3 wi_l = tf.tolocal(wi.val());
fresnel_refraction(-wo, M, refraction_ior, wi);
const Vec3 wi_l = tf.tolocal(wi);
const float cosHO = m.dot(wo_l);
const float cosHI = m.dot(wi_l);
const float D = evalD(m);
@@ -1138,17 +1125,17 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
float out = G2 / G1;
pdf *= probT;
return Ft * out / probT;
return { wi, Ft * out / probT, pdf, std::max(MxMicrofacetParams::roughness_x, MxMicrofacetParams::roughness_y) };
}
else
{
pdf *= probT;
return Fr * out / (1.0f - probT);
return { wi, Fr * out / (1.0f - probT), pdf, std::max(MxMicrofacetParams::roughness_x, MxMicrofacetParams::roughness_y) };
}
}
//
return Fr * out;
return { wi, Fr * out, pdf, std::max(MxMicrofacetParams::roughness_x, MxMicrofacetParams::roughness_y) };
}
}
@@ -1231,31 +1218,28 @@ struct Reflection final : public BSDF, ReflectionParams {
: BSDF(), ReflectionParams(params)
{
}
Color3 get_albedo(const ShaderGlobals& sg) const override
Color3 get_albedo(const Vec3& wo) const override
{
float cosNO = -N.dot(sg.I);
float cosNO = N.dot(wo);
if (cosNO > 0)
return Color3(fresnel_dielectric(cosNO, eta));
return Color3(1);
}
Color3 eval(const OSL::ShaderGlobals& /*sg*/,
const OSL::Vec3& /*wi*/, float& pdf) const override
Sample eval(const Vec3& /*wo*/, const OSL::Vec3& /*wi*/) const override
{
return Color3(pdf = 0);
return {};
}
Color3 sample(const OSL::ShaderGlobals& sg, float /*rx*/,
float /*ry*/, float /*rz*/, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& wo, float /*rx*/, float /*ry*/,
float /*rz*/) const override
{
// only one direction is possible
OSL::Dual2<OSL::Vec3> I = OSL::Dual2<OSL::Vec3>(sg.I, sg.dIdx, sg.dIdy);
OSL::Dual2<float> cosNO = -dot(N, I);
if (cosNO.val() > 0) {
wi = (2 * cosNO) * N + I;
pdf = std::numeric_limits<float>::infinity();
return Color3(fresnel_dielectric(cosNO.val(), eta));
float cosNO = dot(N, wo);
if (cosNO > 0) {
Vec3 wi = (2 * cosNO) * N - wo;
float pdf = std::numeric_limits<float>::infinity();
return { wi, Color3(fresnel_dielectric(cosNO, eta)), pdf, 0 };
}
return Color3(pdf = 0);
return {};
}
};
@@ -1264,40 +1248,37 @@ struct Refraction final : public BSDF, RefractionParams {
: BSDF(), RefractionParams(params)
{
}
Color3 get_albedo(const ShaderGlobals& sg) const override
Color3 get_albedo(const Vec3& wo) const override
{
float cosNO = -N.dot(sg.I);
float cosNO = N.dot(wo);
return Color3(1 - fresnel_dielectric(cosNO, eta));
}
Color3 eval(const OSL::ShaderGlobals& /*sg*/,
const OSL::Vec3& /*wi*/, float& pdf) const override
Sample eval(const Vec3& /*wo*/, const OSL::Vec3& /*wi*/) const override
{
return Color3(pdf = 0);
return {};
}
Color3 sample(const OSL::ShaderGlobals& sg, float /*rx*/,
float /*ry*/, float /*rz*/, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& wo, float /*rx*/, float /*ry*/,
float /*rz*/) const override
{
OSL::Dual2<OSL::Vec3> I = OSL::Dual2<OSL::Vec3>(sg.I, sg.dIdx, sg.dIdy);
pdf = std::numeric_limits<float>::infinity();
return Color3(fresnel_refraction(I, N, eta, wi));
float pdf = std::numeric_limits<float>::infinity();
Vec3 wi;
float Ft = fresnel_refraction(-wo, N, eta, wi);
return { wi, Color3(Ft), pdf, 0 };
}
};
struct Transparent final : public BSDF {
Transparent() : BSDF() {}
Color3 eval(const OSL::ShaderGlobals& /*sg*/,
const OSL::Vec3& /*wi*/, float& pdf) const override
Sample eval(const Vec3& /*wo*/, const Vec3& /*wi*/) const override
{
return Color3(pdf = 0);
return {};
}
Color3 sample(const OSL::ShaderGlobals& sg, float /*rx*/,
float /*ry*/, float /*rz*/, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& wo, float /*rx*/, float /*ry*/,
float /*rz*/) const override
{
wi = OSL::Dual2<OSL::Vec3>(sg.I, sg.dIdx, sg.dIdy);
pdf = std::numeric_limits<float>::infinity();
return Color3(1.0f);
Vec3 wi = -wo;
float pdf = std::numeric_limits<float>::infinity();
return { wi, Color3(1.0f), pdf, 0 };
}
};
@@ -1307,44 +1288,37 @@ struct MxBurleyDiffuse final : public BSDF, MxBurleyDiffuseParams {
{
}
Color3 get_albedo(const ShaderGlobals& sg) const override
{
return albedo;
}
Color3 get_albedo(const Vec3& wo) const override { return albedo; }
Color3 eval(const ShaderGlobals& sg, const Vec3& wi, float& pdf) const override
Sample eval(const Vec3& wo, const Vec3& wi) const override
{
const Vec3 L = wi, V = -sg.I;
const Vec3 L = wi, V = wo;
const Vec3 H = (L + V).normalize();
float LdotH = Imath::clamp(dot(L, H), 0.0f, 1.0f);
float NdotV = Imath::clamp(dot(N, V), 0.0f, 1.0f);
float NdotL = Imath::clamp(dot(N, L), 0.0f, 1.0f);
float F90 = 0.5f + (2.0f * roughness * LdotH * LdotH);
float refL = fresnel_schlick(NdotL, 1.0f, F90);
float refV = fresnel_schlick(NdotV, 1.0f, F90);
pdf = NdotL * float(M_1_PI);
return albedo * refL * refV;
float LdotH = Imath::clamp(dot(L, H), 0.0f, 1.0f);
float NdotV = Imath::clamp(dot(N, V), 0.0f, 1.0f);
float NdotL = Imath::clamp(dot(N, L), 0.0f, 1.0f);
float F90 = 0.5f + (2.0f * roughness * LdotH * LdotH);
float refL = fresnel_schlick(NdotL, 1.0f, F90);
float refV = fresnel_schlick(NdotV, 1.0f, F90);
float pdf = NdotL * float(M_1_PI);
return { wi, albedo * refL * refV, pdf, 1.0f };
}
Color3 sample(const ShaderGlobals& sg, float rx,
float ry, float rz, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& wo, float rx, float ry, float rz) const override
{
Vec3 out_dir;
float pdf;
Sampling::sample_cosine_hemisphere(N, rx, ry, out_dir, pdf);
wi = out_dir; // FIXME: leave derivs 0?
return eval(sg, out_dir, pdf);
return eval(wo, out_dir);
}
};
struct MxSheen final : public BSDF, MxSheenParams {
MxSheen(const MxSheenParams& params) : BSDF(), MxSheenParams(params)
{
}
MxSheen(const MxSheenParams& params) : BSDF(), MxSheenParams(params) {}
Color3 get_albedo(const ShaderGlobals& sg) const override
Color3 get_albedo(const Vec3& wo) const override
{
const float NdotV = OIIO::clamp(-N.dot(sg.I), 0.0f, 1.0f);
const float NdotV = OIIO::clamp(N.dot(wo), 0.0f, 1.0f);
// Rational fit from the Material X project
// Ref: https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl
const Vec2 r = Vec2(13.67300f, 1.0f) +
@@ -1356,34 +1330,38 @@ struct MxSheen final : public BSDF, MxSheenParams {
return clamp(albedo * (r.x / r.y), 0.0f, 1.0f);
}
Color3 eval(const ShaderGlobals& sg, const Vec3& wi, float& pdf) const override
Sample eval(const Vec3& wo, const Vec3& wi) const override
{
const Vec3 L = wi, V = -sg.I;
const Vec3 H = (L + V).normalize();
float NdotV = Imath::clamp(dot(N, V), 0.0f, 1.0f);
float NdotL = Imath::clamp(dot(N, L), 0.0f, 1.0f);
float NdotH = Imath::clamp(dot(N, H), 0.0f, 1.0f);
const Vec3 L = wi, V = wo;
const Vec3 H = (L + V).normalize();
float NdotV = Imath::clamp(dot(N, V), 0.0f, 1.0f);
float NdotL = Imath::clamp(dot(N, L), 0.0f, 1.0f);
float NdotH = Imath::clamp(dot(N, H), 0.0f, 1.0f);
float invRoughness = 1.0f / std::max(roughness, 0.005f);
float D = (2.0f + invRoughness) * powf(1.0f - NdotH * NdotH, invRoughness * 0.5f) / float(2 * M_PI);
pdf = float(0.5 * M_1_PI);
float D = (2.0f + invRoughness)
* powf(1.0f - NdotH * NdotH, invRoughness * 0.5f)
/ float(2 * M_PI);
float pdf = float(0.5 * M_1_PI);
// NOTE: sheen closure has no fresnel/masking
return float(2 * M_PI) * NdotL * albedo * D / (4.0f * (NdotL + NdotV - NdotL * NdotV));
return { wi,
Color3(float(2 * M_PI) * NdotL * albedo * D
/ (4.0f * (NdotL + NdotV - NdotL * NdotV))),
pdf, 1.0f };
}
Color3 sample(const ShaderGlobals& sg, float rx,
float ry, float rz, OSL::Dual2<OSL::Vec3>& wi,
float& pdf) const override
Sample sample(const Vec3& wo, float rx, float ry, float rz) const override
{
Vec3 out_dir;
float pdf;
Sampling::sample_uniform_hemisphere(N, rx, ry, out_dir, pdf);
wi = out_dir; // FIXME: leave derivs 0?
return eval(sg, out_dir, pdf);
return eval(wo, out_dir);
}
};
Color3
evaluate_layer_opacity(const OSL::ShaderGlobals& sg, const ClosureColor* closure)
evaluate_layer_opacity(const OSL::ShaderGlobals& sg,
const ClosureColor* closure)
{
// Null closure, the layer is fully transparent
if (closure == nullptr)
@@ -1391,7 +1369,8 @@ evaluate_layer_opacity(const OSL::ShaderGlobals& sg, const ClosureColor* closure
switch (closure->id) {
case ClosureColor::MUL:
return closure->as_mul()->weight * evaluate_layer_opacity(sg, closure->as_mul()->closure);
return closure->as_mul()->weight
* evaluate_layer_opacity(sg, closure->as_mul()->closure);
case ClosureColor::ADD:
return evaluate_layer_opacity(sg, closure->as_add()->closureA) +
evaluate_layer_opacity(sg, closure->as_add()->closureB);
@@ -1407,7 +1386,7 @@ evaluate_layer_opacity(const OSL::ShaderGlobals& sg, const ClosureColor* closure
case REFLECTION_ID:
case FRESNEL_REFLECTION_ID: {
Reflection bsdf(*comp->as<ReflectionParams>());
return w * bsdf.get_albedo(sg);
return w * bsdf.get_albedo(-sg.I);
}
case MX_DIELECTRIC_ID : {
const MxDielectricParams& params = *comp->as<MxDielectricParams>();
@@ -1415,7 +1394,7 @@ evaluate_layer_opacity(const OSL::ShaderGlobals& sg, const ClosureColor* closure
if (!is_black(params.transmission_tint))
return Color3(1);
MxMicrofacet<MxDielectricParams, GGXDist, false> mf(params, 1.0f);
return w * mf.get_albedo(sg);
return w * mf.get_albedo(-sg.I);
}
case MX_GENERALIZED_SCHLICK_ID: {
const MxGeneralizedSchlickParams& params = *comp->as<MxGeneralizedSchlickParams>();
@@ -1423,11 +1402,11 @@ evaluate_layer_opacity(const OSL::ShaderGlobals& sg, const ClosureColor* closure
if (!is_black(params.transmission_tint))
return Color3(1);
MxMicrofacet<MxGeneralizedSchlickParams, GGXDist, false> mf(params, 1.0f);
return w * mf.get_albedo(sg);
return w * mf.get_albedo(-sg.I);
}
case MX_SHEEN_ID: {
MxSheen bsdf(*comp->as<MxSheenParams>());
return w * bsdf.get_albedo(sg);
return w * bsdf.get_albedo(-sg.I);
}
default : // Assume unhandled BSDFs are opaque
return Color3(1);
@@ -1573,7 +1552,8 @@ process_bsdf_closure(const OSL::ShaderGlobals& sg, ShadingResult& result, const
break;
case MX_OREN_NAYAR_DIFFUSE_ID: {
// translate MaterialX parameters into existing closure
const MxOrenNayarDiffuseParams* srcparams = comp->as<MxOrenNayarDiffuseParams>();
const MxOrenNayarDiffuseParams* srcparams
= comp->as<MxOrenNayarDiffuseParams>();
OrenNayarParams params = {};
params.N = srcparams->N;
params.sigma = srcparams->roughness;
@@ -1607,7 +1587,8 @@ process_bsdf_closure(const OSL::ShaderGlobals& sg, ShadingResult& result, const
break;
};
case MX_TRANSLUCENT_ID: {
const MxTranslucentParams* srcparams = comp->as<MxTranslucentParams>();
const MxTranslucentParams* srcparams
= comp->as<MxTranslucentParams>();
DiffuseParams params = {};
params.N = srcparams->N;
ok = result.bsdf.add_bsdf<Diffuse<1> >(cw * srcparams->albedo, params);
@@ -1619,7 +1600,8 @@ process_bsdf_closure(const OSL::ShaderGlobals& sg, ShadingResult& result, const
}
case MX_SUBSURFACE_ID: {
// TODO: implement BSSRDF support?
const MxSubsurfaceParams* srcparams = comp->as<MxSubsurfaceParams>();
const MxSubsurfaceParams* srcparams
= comp->as<MxSubsurfaceParams>();
DiffuseParams params = {};
params.N = srcparams->N;
ok = result.bsdf.add_bsdf<Diffuse<0> >(cw * srcparams->albedo, params);
@@ -1658,7 +1640,8 @@ process_bsdf_closure(const OSL::ShaderGlobals& sg, ShadingResult& result, const
OSL_NAMESPACE_ENTER
void
process_closure(const OSL::ShaderGlobals& sg, ShadingResult& result, const ClosureColor* Ci, bool light_only)
process_closure(const OSL::ShaderGlobals& sg, ShadingResult& result,
const ClosureColor* Ci, bool light_only)
{
if (!light_only)
process_medium_closure(sg, result, Ci, Color3(1, 1, 1));
+46 -33
View File
@@ -17,12 +17,26 @@ OSL_NAMESPACE_ENTER
/// Individual BSDF (diffuse, phong, refraction, etc ...)
/// Actual implementations of this class are private
struct BSDF {
struct Sample {
Sample() : wi(0.0f), weight(0.0f), pdf(0.0f), roughness(0.0f) {}
Sample(const Sample& o)
: wi(o.wi), weight(o.weight), pdf(o.pdf), roughness(o.roughness)
{
}
Sample(Vec3 wi, Color3 w, float pdf, float r)
: wi(wi), weight(w), pdf(pdf), roughness(r)
{
}
Vec3 wi;
Color3 weight;
float pdf;
float roughness;
};
BSDF() {}
virtual Color3 get_albedo(const ShaderGlobals& /*sg*/) const { return Color3(1); }
virtual Color3 eval(const ShaderGlobals& sg, const Vec3& wi,
float& pdf) const = 0;
virtual Color3 sample(const ShaderGlobals& sg, float rx, float ry, float rz,
Dual2<Vec3>& wi, float& pdf) const = 0;
virtual Color3 get_albedo(const Vec3& /*wo*/) const { return Color3(1); }
virtual Sample eval(const Vec3& wo, const Vec3& wi) const = 0;
virtual Sample sample(const Vec3& wo, float rx, float ry,
float rz) const = 0;
};
/// Represents a weighted sum of BSDFS
@@ -31,12 +45,11 @@ struct BSDF {
struct CompositeBSDF {
CompositeBSDF() : num_bsdfs(0), num_bytes(0) {}
void prepare(const ShaderGlobals& sg, const Color3& path_weight,
bool absorb)
void prepare(const Vec3& wo, const Color3& path_weight, bool absorb)
{
float total = 0;
for (int i = 0; i < num_bsdfs; i++) {
pdfs[i] = weights[i].dot(path_weight * bsdfs[i]->get_albedo(sg)) / (path_weight.x + path_weight.y + path_weight.z);
pdfs[i] = weights[i].dot(path_weight * bsdfs[i]->get_albedo(wo)) / (path_weight.x + path_weight.y + path_weight.z);
assert(pdfs[i] >= 0);
assert(pdfs[i] <= 1);
total += pdfs[i];
@@ -47,53 +60,52 @@ struct CompositeBSDF {
}
}
Color3 get_albedo(const ShaderGlobals& sg) const
Color3 get_albedo(const Vec3& wo) const
{
Color3 result(0, 0, 0);
for (int i = 0; i < num_bsdfs; i++)
result += weights[i] * bsdfs[i]->get_albedo(sg);
result += weights[i] * bsdfs[i]->get_albedo(wo);
return result;
}
Color3 eval(const ShaderGlobals& sg, const Vec3& wi, float& pdf) const
BSDF::Sample eval(const Vec3& wo, const Vec3& wi) const
{
Color3 result(0, 0, 0);
pdf = 0;
BSDF::Sample s = {};
for (int i = 0; i < num_bsdfs; i++) {
float bsdf_pdf = 0;
Color3 bsdf_weight = weights[i] * bsdfs[i]->eval(sg, wi, bsdf_pdf);
MIS::update_eval(&result, &pdf, bsdf_weight, bsdf_pdf, pdfs[i]);
BSDF::Sample b = bsdfs[i]->eval(wo, wi);
b.weight *= weights[i];
MIS::update_eval(&s.weight, &s.pdf, b.weight, b.pdf, pdfs[i]);
s.roughness += b.roughness * pdfs[i];
}
return result;
return s;
}
Color3 sample(const ShaderGlobals& sg, float rx, float ry, float rz,
Dual2<Vec3>& wi, float& pdf) const
BSDF::Sample sample(const Vec3& wo, float rx, float ry, float rz) const
{
float accum = 0;
for (int i = 0; i < num_bsdfs; i++) {
if (rx < (pdfs[i] + accum)) {
rx = (rx - accum) / pdfs[i];
rx = std::min(rx, 0.99999994f); // keep result in [0,1)
Color3 result = weights[i]
* (bsdfs[i]->sample(sg, rx, ry, rz, wi, pdf)
/ pdfs[i]);
pdf *= pdfs[i];
BSDF::Sample s = bsdfs[i]->sample(wo, rx, ry, rz);
s.weight *= weights[i] * (1 / pdfs[i]);
s.pdf *= pdfs[i];
if (s.pdf == 0.0f)
return {};
// we sampled PDF i, now figure out how much the other bsdfs contribute to the chosen direction
for (int j = 0; j < num_bsdfs; j++) {
if (i == j)
continue;
float bsdf_pdf = 0;
Color3 bsdf_weight
= weights[j] * bsdfs[j]->eval(sg, wi.val(), bsdf_pdf);
MIS::update_eval(&result, &pdf, bsdf_weight, bsdf_pdf,
pdfs[j]);
if (i != j) {
BSDF::Sample b = bsdfs[j]->eval(wo, s.wi);
b.weight *= weights[j];
MIS::update_eval(&s.weight, &s.pdf, b.weight, b.pdf,
pdfs[j]);
}
}
return result;
return s;
}
accum += pdfs[i];
}
return Color3(0, 0, 0);
return {};
}
template<typename BSDF_Type, typename... BSDF_Args>
@@ -140,7 +152,8 @@ struct ShadingResult {
void
register_closures(ShadingSystem* shadingsys);
void
process_closure(const OSL::ShaderGlobals& sg, ShadingResult& result, const ClosureColor* Ci, bool light_only);
process_closure(const OSL::ShaderGlobals& sg, ShadingResult& result,
const ClosureColor* Ci, bool light_only);
Vec3
process_background_closure(const ClosureColor* Ci);
+45 -41
View File
@@ -796,32 +796,34 @@ SimpleRaytracer::get_camera_screen_window(ShaderGlobals* /*sg*/, bool derivs,
void
SimpleRaytracer::globals_from_hit(ShaderGlobals& sg, const Ray& r,
const Dual2<float>& t, int id, bool flip)
const Dual2<float>& t, int id)
{
memset((char*)&sg, 0, sizeof(ShaderGlobals));
Dual2<Vec3> P = r.point(t);
// We are missing the projection onto the surface here
sg.P = P.val();
sg.dPdx = P.dx();
sg.dPdy = P.dy();
Dual2<Vec3> N = scene.normal(P, id);
sg.Ng = sg.N = N.val();
Dual2<Vec2> uv = scene.uv(P, N, sg.dPdu, sg.dPdv, id);
sg.u = uv.val().x;
sg.dudx = uv.dx().x;
sg.dudy = uv.dy().x;
sg.v = uv.val().y;
sg.dvdx = uv.dx().y;
sg.dvdy = uv.dy().y;
sg.surfacearea = scene.surfacearea(id);
sg.I = r.direction.val();
sg.dIdx = r.direction.dx();
sg.dIdy = r.direction.dy();
sg.backfacing = sg.N.dot(sg.I) > 0;
sg.Ng = sg.N = N.val();
Dual2<Vec2> uv = scene.uv(P, N, sg.dPdu, sg.dPdv, id);
sg.u = uv.val().x;
sg.dudx = uv.dx().x;
sg.dudy = uv.dy().x;
sg.v = uv.val().y;
sg.dvdx = uv.dx().y;
sg.dvdy = uv.dy().y;
sg.surfacearea = scene.surfacearea(id);
Dual2<Vec3> direction = r.dual_direction();
sg.I = direction.val();
sg.dIdx = direction.dx();
sg.dIdy = direction.dy();
sg.backfacing = sg.N.dot(sg.I) > 0;
if (sg.backfacing) {
sg.N = -sg.N;
sg.Ng = -sg.Ng;
}
sg.flipHandedness = flip;
sg.flipHandedness = sg.dPdx.cross(sg.dPdy).dot(sg.N) < 0;
// In our SimpleRaytracer, the "renderstate" itself just a pointer to
// the ShaderGlobals.
@@ -850,7 +852,6 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
int prev_id = -1;
float bsdf_pdf = std::numeric_limits<
float>::infinity(); // camera ray has only one possible direction
bool flip = false;
for (int b = 0; b <= max_bounces; b++) {
// trace the ray against the scene
Dual2<float> t;
@@ -860,7 +861,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
if (backgroundShaderID >= 0) {
if (backgroundResolution > 0) {
float bg_pdf = 0;
Vec3 bg = background.eval(r.direction.val(), bg_pdf);
Vec3 bg = background.eval(r.direction, bg_pdf);
path_radiance
+= path_weight * bg
* MIS::power_heuristic<MIS::WEIGHT_WEIGHT>(bsdf_pdf,
@@ -876,8 +877,9 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
// construct a shader globals for the hit point
ShaderGlobals sg;
globals_from_hit(sg, r, t, id, flip);
int shaderID = scene.shaderid(id);
globals_from_hit(sg, r, t, id);
const float radius = r.radius + r.spread * t.val();
int shaderID = scene.shaderid(id);
if (shaderID < 0 || !m_shaders[shaderID])
break; // no shader attached? done
@@ -891,7 +893,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
float k = 1;
if (scene.islight(id)) {
// figure out the probability of reaching this point
float light_pdf = scene.shapepdf(id, r.origin.val(), sg.P);
float light_pdf = scene.shapepdf(id, r.origin, sg.P);
k = MIS::power_heuristic<MIS::WEIGHT_EVAL>(bsdf_pdf, light_pdf);
}
path_radiance += path_weight * k * result.Le;
@@ -901,14 +903,14 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
break;
// build internal pdf for sampling between bsdf closures
result.bsdf.prepare(sg, path_weight, b >= rr_depth);
result.bsdf.prepare(-sg.I, path_weight, b >= rr_depth);
if (show_albedo_scale > 0)
{
// Instead of path tracing, just visualize the albedo
// of the bsdf. This can be used to validate the accuracy of
// the get_albedo method for a particular bsdf.
path_radiance += path_weight * result.bsdf.get_albedo(sg) * show_albedo_scale;
path_radiance += path_weight * result.bsdf.get_albedo(-sg.I) * show_albedo_scale;
break;
}
@@ -921,15 +923,15 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
// trace one ray to the background
if (backgroundResolution > 0) {
Dual2<Vec3> bg_dir;
float bg_pdf = 0, bsdf_pdf = 0;
Vec3 bg = background.sample(xi, yi, bg_dir, bg_pdf);
Color3 bsdf_weight = result.bsdf.eval(sg, bg_dir.val(), bsdf_pdf);
Color3 contrib
= path_weight * bsdf_weight * bg
* MIS::power_heuristic<MIS::WEIGHT_WEIGHT>(bg_pdf, bsdf_pdf);
float bg_pdf = 0;
Vec3 bg = background.sample(xi, yi, bg_dir, bg_pdf);
BSDF::Sample b = result.bsdf.eval(-sg.I, bg_dir.val());
Color3 contrib = path_weight * b.weight * bg
* MIS::power_heuristic<MIS::WEIGHT_WEIGHT>(bg_pdf,
b.pdf);
if ((contrib.x + contrib.y + contrib.z) > 0) {
int shadow_id = id;
Ray shadow_ray = Ray(sg.P, bg_dir);
Ray shadow_ray = Ray(sg.P, bg_dir.val(), radius, 0);
Dual2<float> shadow_dist;
if (!scene.intersect(shadow_ray, shadow_dist,
shadow_id)) // ray reached the background?
@@ -948,14 +950,13 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
continue; // no shader attached to this light
// sample a random direction towards the object
float light_pdf;
Vec3 ldir = scene.sample(lid, sg.P, xi, yi, light_pdf);
float bsdf_pdf = 0;
Color3 bsdf_weight = result.bsdf.eval(sg, ldir, bsdf_pdf);
Color3 contrib = path_weight * bsdf_weight
Vec3 ldir = scene.sample(lid, sg.P, xi, yi, light_pdf);
BSDF::Sample b = result.bsdf.eval(-sg.I, ldir);
Color3 contrib = path_weight * b.weight
* MIS::power_heuristic<MIS::EVAL_WEIGHT>(light_pdf,
bsdf_pdf);
b.pdf);
if ((contrib.x + contrib.y + contrib.z) > 0) {
Ray shadow_ray = Ray(sg.P, ldir);
Ray shadow_ray = Ray(sg.P, ldir, radius, 0);
// trace a shadow ray and see if we actually hit the target
// in this tiny renderer, tracing a ray is probably cheaper than evaluating the light shader
int shadow_id = id; // ignore self hit
@@ -964,8 +965,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
&& shadow_id == lid) {
// setup a shader global for the point on the light
ShaderGlobals light_sg;
globals_from_hit(light_sg, shadow_ray, shadow_dist, lid,
false);
globals_from_hit(light_sg, shadow_ray, shadow_dist, lid);
// execute the light shader (for emissive closures only)
shadingsys->execute(*ctx, *m_shaders[shaderID], light_sg);
ShadingResult light_result;
@@ -977,14 +977,18 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
}
// trace indirect ray and continue
path_weight *= result.bsdf.sample(sg, xi, yi, zi, r.direction,
bsdf_pdf);
BSDF::Sample p = result.bsdf.sample(-sg.I, xi, yi, zi);
path_weight *= p.weight;
bsdf_pdf = p.pdf;
r.direction = p.wi;
r.radius = radius;
// Just simply use roughness as spread slope
r.spread = std::max(r.spread, p.roughness);
if (!(path_weight.x > 0) && !(path_weight.y > 0)
&& !(path_weight.z > 0))
break; // filter out all 0's or NaNs
prev_id = id;
r.origin = Dual2<Vec3>(sg.P, sg.dPdx, sg.dPdy);
flip ^= sg.Ng.dot(r.direction.val()) > 0;
r.origin = sg.P;
}
return path_radiance;
}
+1 -1
View File
@@ -165,7 +165,7 @@ private:
// CPU renderer helpers
void globals_from_hit(ShaderGlobals& sg, const Ray& r,
const Dual2<float>& t, int id, bool flip);
const Dual2<float>& t, int id);
Vec3 eval_background(const Dual2<Vec3>& dir, ShadingContext* ctx);
Color3 subpixel_radiance(float x, float y, Sampler& sampler,
ShadingContext* ctx);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.