Skip to content

Conversation

@devshgraphicsprogramming
Copy link
Member

No description provided.

if NBL_CONSTEXPR_FUNC (aniso)
NBL_IF_CONSTEXPR(aniso)
{
pdf = base_t::bxdf.quotient_and_pdf(s, base_t::anisointer, cache);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pdf is a really bad variable name for this, it leads to unreadability pathologies in code like pdf.pdf or worse pdf.quotient

Comment on lines 119 to 120
if (checkZero<float32_t3>(bsdf, 1e-5) || checkZero<float32_t3>(pdf.quotient, 1e-5))
return BET_NONE; // produces an "impossible" sample
return BTR_NONE; // likely to be that a bad sample was produced, unless it's a mixture/delta BxDF
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve the comment

before we've checked if:
1. PDF is positive (you return no error if its not)
2. qutient is positive and (1) already checked

So if we must have `bsdf == quotient*pdf` , then bsdf must also be positive.

However for mixture of, or singular delta BxDF the bsdf can be less due to removal of Dirac-Delta lobes from the eval method, which is why allow `BTR_NONE` in this case

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GH UI shows outdated, but still needs to be done

Comment on lines 122 to 123
if (hlsl::isnan(pdf.pdf))
return BTR_NONE;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pdf should never return NAN (because it will get used in MIS weights), that should be a separate error on its own!

Comment on lines 132 to 134
// infinite PDF and zero jacobian are both valid behaviors
if (!checkZero<float>(det, 1e-3) && !checkZero<float>(det * pdf.pdf / s.getNdotL(), 1e-4))
return BTR_ERROR_JACOBIAN_TEST_FAIL;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just check if PDF is inf AND jacobian is 0 and return BTR_NONE

because right now you check Jacobian!=0 AND Jacobian*pdf/NdotL != 0 for the error which is a completely wrong test

actually why are you not failing here all the time!? Literally every BXDF would fail this!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically

if (isinf(pdf))
{
   // if pdf is infinite then density is infinite and no differential area inbetween samples
   if (det!=0)
      return BTR_ERROR_JACOBIAN_TEST_FAIL;
   // valid behaviour, but obviously can't check bsdf = quotient*pdf
   return BTR_NONE;
}
else if (det==0)
      return BTR_ERROR_JACOBIAN_TEST_FAIL;
      
if (!checkEq(pdf.value,bsdf))
{
  ... the usual stuff ...
  return BTR_ERROR_PDF_EVAL_DIFF;
}
return BTR_NONE;

Comment on lines 49 to 53
template<typename T>
bool checkEq(T a, T b, float32_t eps)
{
T _a = hlsl::max(hlsl::abs(a), hlsl::promote<T>(1e-5));
T _b = hlsl::max(hlsl::abs(b), hlsl::promote<T>(1e-5));
return nbl::hlsl::all<hlsl::vector<bool, vector_traits<T>::Dimension> >(nbl::hlsl::max<T>(_a / _b, _b / _a) <= hlsl::promote<T>(1 + eps));
}

template<>
bool checkEq<float32_t>(float32_t a, float32_t b, float32_t eps)
{
float32_t _a = hlsl::max(hlsl::abs(a), 1e-5f); // prevent divide by 0
float32_t _b = hlsl::max(hlsl::abs(b), 1e-5f);
return nbl::hlsl::max<float32_t>(_a / _b, _b / _a) <= float32_t(1 + eps);
return testing::relativeApproxCompare<T>(a, b, eps);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just remove checkEq and replace all usages with testing::relativeApproxCompare

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GH UI shows outdated, but still needs to be done

Comment on lines 110 to 111
if (pdf.pdf < bit_cast<float>(numeric_limits<float>::min)) // there's exceptional cases where pdf=0, so we check here to avoid adding all edge-cases, but quotient must be positive afterwards
return BTR_NONE;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should check pdf.pdf being non-negative before returning BTR_NONE for a 0 pdf

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to rename BTR_ERROR_GENERATED_SAMPLE_NON_POSITIVE_PDF to BTR_ERROR_GENERATED_SAMPLE_NEGATIVE_PDF or just remove it as an enum and just use BTR_ERROR_NEGATIVE_VAL

Comment on lines 18 to 19
float32_t3 ux = base_t::rc.u + float32_t3(base_t::rc.eps,0,0);
float32_t3 uy = base_t::rc.u + float32_t3(0,base_t::rc.eps,0);
Copy link
Member

@AnastaZIuk AnastaZIuk Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I think we have a problem here. The domain where forward finite differences are
well-defined is the interior $u \in (\varepsilon, 1-\varepsilon)^2$. This is also what the test setup implicitly assumes by clamping the input

retval.u.x = hlsl::clamp(retval.u.x, retval.eps, 1.f-retval.eps);

Tho the input generation and clamp allows the boundary value u.x = 1 − ε to occur.

return ret_t(x) / hlsl::promote<ret_t>(numeric_limits<uint32_t>::max);

Since the Jacobian test uses forward finite differences ux = u + (ε, 0)

float32_t3 ux = base_t::rc.u + float32_t3(base_t::rc.eps,0,0);

the boundary case leads to

$$ u_x = 1 - \varepsilon \Rightarrow u_x + \varepsilon = 1 $$

and for a partial derivative at $u_x$ to be well‑defined we need an open neighborhood:

$$\exists,\delta>0:\ (u_x-\delta,\ u_x+\delta)\subset[\varepsilon,1-\varepsilon].$$

At the boundary $u_x=1-\varepsilon$ such $\delta$ does not exist, and with forward diff:

$$u_x+\varepsilon=1\notin[\varepsilon,1-\varepsilon].$$

which means the Jacobian here is evaluated outside the differentiability domain and we rely on whatever generate() does (clamp/wrap/invalid value? does all of them know what to do in such situation?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and we rely on whatever generate()

even if generate() handles such inputs in a defined way, the finite difference is evaluated outside the differentiability domain. In that case we either differentiate a different function or rely on implementation specific behavior, so then the Jacobian test is mathematically invalid regardless

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the solution is to not generate [0,1]^2 inputs, but [0,1-epsilon]^2 inputs

Or better, geneerate [0,1] but when generated number is >1-epsilon we don't do a "positive difference" but "negative instead" (so we take our s_x sample by subtracting epsilon, then correct the sign diff throughout)

Copy link
Member

@AnastaZIuk AnastaZIuk Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u.xy is in [epsilon,1-epsilon]^2, the problem is what goes on FD when u.x or u.y is 1-eps, then ux or uy is 1 which is outside input domain. Next it enters each generate(). The base sample is in-domain, but the forward-FD sample can step out of the declared domain.

If the domain were [0,1]^2 the same issue would still exist at the boundary u.x=1 (or u.y) because 1+eps is outside

float32_t3 ux = base_t::rc.u + float32_t3(base_t::rc.eps,0,0);

Jacobian requires differentiability at point, even if a one sided derivative exists at 1 the function is not differentiable at this point (my previous arg about open neighborhood around the point), which discards the Jacobian test at this point

Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a choice of how to handle when we get too close to 1.0 on the point:

  1. don't do the test (discard/exit with positive non-zero code
  2. switch from Forward to Backward differentiation differences

either is valid, the jacobian test is only approximate

return BTR_NONE;
}
else
else if (checkZero<float>(det, numeric_limits<float>::min))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is incorrect, det here is det(m) which comes from forward differences so it scales with the step eps. A constant threshold like numeric_limits<float>::min is in the wrong units. You need to normalize by eps^2 (or scale your tolerance by eps^2). It should be more or less

constexpr auto tol = numeric_limits<float>::min(); // example (not best probably)
checkZero(det, tol * eps * eps);

To recap:

$$ m=\big[F(u+\varepsilon e_x)-F(u), F(u+\varepsilon e_y)-F(u)\big] $$

first-order Taylor expansion is

$$ F(u+\varepsilon e_i)=F(u)+\varepsilon \frac{\partial F}{\partial u_i}(u)+O(\varepsilon^2) $$

so

$$ m \approx \varepsilon J_F(u),\qquad \det(m)\approx \varepsilon^2\det(J_F(u)). $$

Counterexample why it matters https://godbolt.org/z/MnfMzYcE7

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense I guess

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also seems done

Comment on lines +18 to +20
float32_t3 u = hlsl::min(base_t::rc.u, hlsl::promote<float32_t3>(1.0-2.0*eps));
float32_t3 ux = u + float32_t3(eps,0,0);
float32_t3 uy = u + float32_t3(0,eps,0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I found a bug. FD can cross the Cook‑Torrance reflect/refract branch which invalidates Jacobian test for certain points.

https://github.com/Devsh-Graphics-Programming/Nabla/blob/a37f289569af93f0156e2fdbad1d656e379b6580/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl#L306-L308

Its because the reflect/refract choice is a hard threshold depending on u.xy, so FD perturbations can flip the branch and then sx-s is a jump (~2/eps), its not a derivative and the Jacobian test cannot be valid there. The mapping is not differentiable

Counterexample https://godbolt.org/z/E8KM8xE3b

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lambertian and Oren‑Nayar have similar issue and can hit FD at points where the mapping is not differentiable. Both go through ProjectedHemisphere, then ConcentricMapping which is piecewise with a hard branch on

if (std::abs(ux) > std::abs(uy)) { ... } else { ... }

FD step in u.x or u.y can cross that branch near the diagonal, then it measures a jump between two branches, its not a derivative and the Jacobian test is invalid at those samples.

There is plenty of those points in our domain $D = [\varepsilon,1-2\varepsilon]^2$.

$$ L = \lbrace (x,y)\in D : |2x-1| = |2y-1| \rbrace = \lbrace (x,y)\in D : x=y \rbrace \cup \lbrace (x,y)\in D : x+y=1 \rbrace $$

$$ S_x = \lbrace u\in D : [u,u+(\varepsilon,0)] \cap L \neq \varnothing \rbrace $$

$$ S_y = \lbrace u\in D : [u,u+(0,\varepsilon)] \cap L \neq \varnothing \rbrace $$

$S_x \cup S_y$ are exactly bad points where the forward FD step crosses $L$. Counterexample https://godbolt.org/z/nbYz1T1M3

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it can cross the Reflect/Refract branch, because the choice of reflect vs refract is done on the Z coordinate of the random variable

        scalar_type z = u.z;
...
        bool transmitted = partitionRandVariable(z, rcpChoiceProb);

Me and sorakrit thought about it when designing the tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because the choice of reflect vs refract is done on the Z coordinate of the random variable

but the threshold is not and that's why we can have the branch flip with fixed u.z. FD changes in u.xy

float32_t3 u = hlsl::min(base_t::rc.u, hlsl::promote<float32_t3>(1.0-2.0*eps));
float32_t3 ux = u + float32_t3(eps,0,0);
float32_t3 uy = u + float32_t3(0,eps,0);

u.xy is plugged into

const vector3_type localH = ndf.generateH(upperHemisphereV, u.xy);

https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl#L286

then

VdotH = dot(localV, localH)

https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl#L287

and

const scalar_type reflectance = __getScaledReflectance(_f, interaction, hlsl::abs(VdotH), false, dummy);

// ..
sampling::PartitionRandVariable<scalar_type> partitionRandVariable;
partitionRandVariable.leftProb = reflectance; // !!

https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl#L306

then we finally have

$$u.xy → localH → VdotH → reflectance (= leftProb)$$ $$branch = compare(u.z, leftProb)$$

https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/sampling/basic.hlsl#L19-L35

note that leftProb depends on u.xy, henceforth constant u.z does not save our situation

Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First thing we need to change

 float32_t3 u = hlsl::min(base_t::rc.u, float32_t3(1.0-2.0*eps,1.0-2.0*eps,1.0)); 

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I see exactly what you're getting at now....

This means we need to run the test with either u.z=0 or u.z=1 and no value in between

@keptsecret keptsecret mentioned this pull request Jan 20, 2026
Comment on lines +265 to +269
rec_cache.iso_cache.VdotH = cache.iso_cache.getLdotH();
rec_cache.iso_cache.LdotH = cache.iso_cache.getVdotH();
rec_isocache = isocache;
rec_isocache.VdotH = isocache.getLdotH();
rec_isocache.LdotH = isocache.getVdotH();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks anisotropic reciprocity. rec_cache is copied from forward and only VdotH/LdotH are swapped but TdotH/BdotH stay from the forward H. Anisotropic NDFs GGX and Beckmann use getTdotH2/getBdotH2 so reciprocal eval is fed a forward half‑vector, making reciprocity test invalid for anisotropic microfacets.

https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/bxdf/common.hlsl#L877-L880

https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/bxdf/ndf/ggx.hlsl#L126

https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/bxdf/ndf/beckmann.hlsl#L143

Counterexample https://godbolt.org/z/EnrnTxevG

);
float det = nbl::hlsl::determinant<float32_t2x2>(m) / (eps * eps);

float jacobi_dg1_ndoth = det * dg1 / hlsl::abs(s.getNdotL());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s.getNdotL() can be 0 or near, the sample is still valid since s.isValid only checks L!=0 so this division can produce inf/NaN and trigger a false Jacobian fail

note we already guard NdotV earlier but not NdotL here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm good catch

bool32_t3 diff_test = nbl::hlsl::max<float32_t3>(pdf.value() / bsdf, bsdf / pdf.value()) <= (float32_t3)(1 + base_t::rc.eps);
if (!all<bool32_t3>(diff_test))
return PDF_EVAL_DIFF;
float32_t3 a = Li / hlsl::abs(s.getNdotL());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same NdotL issue as in TestNDF, 0 or near -> false fail #236 (comment)

if (verbose)
base_t::errMsg += std::format("VdotH={}, NdotV={}, LdotH={}, NdotL={}, NdotH={}, eta={}, alpha=[{},{}] Jacobian={}, DG1={}, Jacobian*DG1={}",
aniso ? cache.getVdotH() : isocache.getVdotH(), aniso ? base_t::anisointer.getNdotV() : base_t::isointer.getNdotV(),
aniso ? cache.getLdotH() : isocache.getLdotH(), s.getNdotL(), NdotH, base_t::rc.eta.x, base_t::rc.alpha.x, base_t::rc.alpha.y,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NdotH is not assigned value, are we printing garbage?

Comment on lines +93 to +94
retval.alpha.x = ConvertToFloat01<uint32_t>::__call(retval.rng());
retval.alpha.y = ConvertToFloat01<uint32_t>::__call(retval.rng());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no clamp on RNG -> alpha can be 0. initBxDF feeds that into NDF (ax/ay/a2) and Beckmann/GGX's D divides by ax2/ay2/a2 -> inf/NaN.

base_t::bxdf.ndf = base_t::bxdf_t::ndf_type::create(_rc.alpha.x);

base_t::bxdf.ndf = base_t::bxdf_t::ndf_type::create(_rc.alpha.x, _rc.alpha.y);

base_t::bxdf.ndf = base_t::bxdf_t::ndf_type::create(_rc.alpha.x);

base_t::bxdf.ndf = base_t::bxdf_t::ndf_type::create(_rc.alpha.x, _rc.alpha.y);

TestNDF handles isInfinity with skip, ok

if (isNdfInfinity)
return BTR_INVALID_TEST_CONFIG;

however BxDF tests call quotient_and_pdf

sampledLi = base_t::bxdf.quotient_and_pdf(s, base_t::isointer);

In cook_torrance_base, pdf() masks isInfinity -> 0 but quotient_and_pdf() does not
https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl#L365
https://github.com/Devsh-Graphics-Programming/Nabla/blob/c26719ac178c943362c12b25aa53644653df6954/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl#L373

So pdf can stay inf and the test rejects it unless det≈0:

const float NdotL = s.getNdotL();
if (NdotV * NdotL < 0.f)
tmpeta = NdotV < 0.f ? 1.f / eta : eta;
float32_t3 H = hlsl::normalize(V.getDirection() + L.getDirection() * tmpeta);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this builds H manually and our sign logic diverges from ComputeMicrofacetNormal for transmission (esp NdotV<0), hence chi2 compares against a different pdf and the test is invalid

float tmpeta = 1.f;
NBL_IF_CONSTEXPR(traits_t::IsMicrofacet)
{
const float NdotV = anisointer.getNdotV();
NBL_IF_CONSTEXPR(traits_t::type == bxdf::BT_BRDF)
if (NdotV < 0.f) return 0.f;
const float NdotL = s.getNdotL();
if (NdotV * NdotL < 0.f)
tmpeta = NdotV < 0.f ? 1.f / eta : eta;
float32_t3 H = hlsl::normalize(V.getDirection() + L.getDirection() * tmpeta);
float VdotH = hlsl::dot(V.getDirection(), H);
if (NdotV * VdotH < 0.f)
{
H = -H;
VdotH = -VdotH;
}
cache.iso_cache.VdotH = VdotH;
cache.iso_cache.LdotH = hlsl::dot(L.getDirection(), H);
cache.iso_cache.VdotL = hlsl::dot(V.getDirection(), L.getDirection());
cache.iso_cache.absNdotH = hlsl::abs(hlsl::dot(N, H));
cache.iso_cache.NdotH2 = cache.iso_cache.absNdotH * cache.iso_cache.absNdotH;
if (!cache.isValid(bxdf::fresnel::OrientedEtas<hlsl::vector<float, 1> >::create(1.f, hlsl::promote<hlsl::vector<float, 1> >(tmpeta))))
return 0.f;
cache.fillTangents(T, B, H);

https://github.com/Devsh-Graphics-Programming/Nabla/blob/3ce0d89aef69ff7bb52b3b784b6b58228040a629/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl#L101-L166

counterexample: https://godbolt.org/z/5q6oe59ra

pdfSinTheta.isointer = base_t::isointer;
pdfSinTheta.anisointer = base_t::anisointer;
pdfSinTheta.eta = base_t::rc.eta.x;
integrateFreq[intidx++] = numSamples * math::quadrature::AdaptiveSimpson2D<CalculatePdfSinTheta<BxDF, aniso>, float>::__call(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expected frequencies are scaled by numSamples even though invalid samples are skipped above. Observed and expected therefore correspond to different underlying distributions which biases chi2 and makes the test invalid

$$ E_i = N_{\text{accepted}} \int_{\Omega_i} p(\omega) d\omega $$

integrateFreq[intidx++] = numSamples * math::quadrature::AdaptiveSimpson2D<CalculatePdfSinTheta<BxDF, aniso>, float>::__call(
pdfSinTheta, float32_t2(i * thetaFactor, j * phiFactor), float32_t2((i + 1) * thetaFactor, (j + 1) * phiFactor));

counterexample: https://godbolt.org/z/4YY5889rf

even if we wanted to “test with rejection” right now observed comes from $$P(ω | accepted)$$ while expected uses $$P(ω)$$, these correspond to different underlying probability measures

Comment on lines +88 to +89
if (hlsl::isinf(sampledLi.pdf))
accumulatedQuotient += sampledLi.quotient;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this accumulates only when pdf==inf, so for all continuous BxDFs with finite pdf the test never adds anything and becomes a false pass

we do run this test on continuous BxDFs (Lambert/Oren/GGX/Beckmann/etc) hence it will silently pass for all of them

RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::reflection::SLambertian<iso_config_t>>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::reflection::SOrenNayar<iso_config_t>>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::reflection::SBeckmannIsotropic<iso_microfacet_config_t>, false>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::reflection::SBeckmannAnisotropic<aniso_microfacet_config_t>, true>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::reflection::SGGXIsotropic<iso_microfacet_config_t>, false>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::reflection::SGGXAnisotropic<aniso_microfacet_config_t>, true>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::reflection::SIridescent<iso_microfacet_config_t>, false>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::transmission::SLambertian<iso_config_t>>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::transmission::SOrenNayar<iso_config_t>>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::transmission::SBeckmannDielectricIsotropic<iso_microfacet_config_t>, false>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::transmission::SBeckmannDielectricAnisotropic<aniso_microfacet_config_t>, true>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::transmission::SGGXDielectricIsotropic<iso_microfacet_config_t>, false>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::transmission::SGGXDielectricAnisotropic<aniso_microfacet_config_t>, true>), initparams);
RUN_TEST_OF_TYPE((TestModifiedWhiteFurnace<bxdf::transmission::SIridescent<iso_microfacet_config_t>, false>), initparams);

ErrorType test()
{
compute();
transmitted = aniso ? cache.isTransmission() : isocache.isTransmission();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache/isocache are uninitialized for non‑microfacet paths but we read them here (transmitted)

NBL_IF_CONSTEXPR(traits_t::type == bxdf::BT_BRDF && !traits_t::IsMicrofacet)
{
s = base_t::bxdf.generate(anisointer, base_t::rc.u.xy);
}
NBL_IF_CONSTEXPR(traits_t::type == bxdf::BT_BRDF && traits_t::IsMicrofacet)
{
NBL_IF_CONSTEXPR(aniso)
{
s = base_t::bxdf.generate(anisointer, base_t::rc.u.xy, cache);
}
else
{
s = base_t::bxdf.generate(isointer, base_t::rc.u.xy, isocache);
}
}
NBL_IF_CONSTEXPR(traits_t::type == bxdf::BT_BSDF && !traits_t::IsMicrofacet)
{
s = base_t::bxdf.generate(anisointer, base_t::rc.u);
}

we need a guard with traits_t::IsMicrofacet or init the caches

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants