Skip to content

Conversation

@devshgraphicsprogramming
Copy link
Member

@devshgraphicsprogramming devshgraphicsprogramming commented Sep 16, 2025

Description

Continues #899 , #916 and #919

Testing

TODO list:

Comment on lines +181 to +182
if (isInfinity)
return hlsl::promote<spectral_type>(0.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.

it needs to be commented and documented why you don't hoist above line 176 (my previous review comment)

isInfinity = true;
return bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity);
}
isInfinity = false;
Copy link
Member Author

Choose a reason for hiding this comment

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

not that easy!

your exp2 will spit out inf waay before a2<Threshold it also depends on NdotH2

so you need to replace isInfinity = false with isInfinity = isinf(retval) after the multiplication by pi and /denom (or you compute nom/denom and check that value is <Max/Pi)

isInfinity = true;
return bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity);
}
isInfinity = false;
Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +281 to +285
const scalar_type VdotH = cache.getVdotH();
const scalar_type VdotH_etaLdotH = hlsl::mix(VdotH + orientedEta * cache.getLdotH(),
VdotH / orientedEta + cache.getLdotH(),
interaction.getPathOrigin() == PathOrigin::PO_SENSOR);
quant_query.neg_rcp2_refractionDenom = scalar_type(-1.0) / (VdotH_etaLdotH * VdotH_etaLdotH);
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 have we got code for this and not using somethign from microfacet_to_light_transform.hlsl ?

{
scalar_type D = query.getNdf();
scalar_type dg1 = query.getNdf() / (scalar_type(1.0) + query.getLambdaV());
isInfinity = D == bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity);
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 isInfinity is supposed to be an "early out" , it should even be called isNDFInfinity to make things clear.

You want to know if the NDF is infinite, sicne G2 and G2 can only be in [0,1]

isInfinity = true;
return bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity);
}
isInfinity = false;
Copy link
Member Author

Choose a reason for hiding this comment

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

same as beckmann

isInfinity = true;
return bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity);
}
isInfinity = false;
Copy link
Member Author

Choose a reason for hiding this comment

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

same as beckmann comment

Comment on lines 215 to 228
const scalar_type VdotH_etaLdotH = cache.getVdotH() + orientedEta * cache.getLdotH();
quant_query.neg_rcp2_VdotH_etaLdotH = scalar_type(-1.0) / (VdotH_etaLdotH * VdotH_etaLdotH);
const scalar_type VdotH = cache.getVdotH();
const scalar_type VdotH_etaLdotH = hlsl::mix(VdotH + orientedEta * cache.getLdotH(),
VdotH / orientedEta + cache.getLdotH(),
interaction.getPathOrigin() == PathOrigin::PO_SENSOR);
quant_query.neg_rcp2_refractionDenom = scalar_type(-1.0) / (VdotH_etaLdotH * VdotH_etaLdotH);
Copy link
Member Author

Choose a reason for hiding this comment

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

again, why duplicate code with Beckmann ?

{
scalar_type D = query.getNdfwoNumerator();
scalar_type dg1_over_2NdotV = query.getNdfwoNumerator() * query.getG1over2NdotV();
isInfinity = D == bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity);
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 got the right thing this time, but I'd hoist to be right after D and use isinf or <Max for setting the bool

Comment on lines +38 to +40
void addCosine(T cosA, T biasA)
{
const bool AltminusB = tmp0 < (-tmp1);
const float cosSumAB = tmp0 * tmp1 - tmp3 * tmp4;
const bool ABltminusC = cosSumAB < (-tmp2);
const bool ABltC = cosSumAB < tmp2;
// apply triple angle formula
const float absArccosSumABC = acos<float>(clamp<float>(cosSumAB * tmp2 - (tmp0 * tmp4 + tmp3 * tmp1) * tmp5, -1.f, 1.f));
return ((AltminusB ? ABltC : ABltminusC) ? (-absArccosSumABC) : absArccosSumABC) + ((AltminusB || ABltminusC) ? numbers::pi<float> : (-numbers::pi<float>));
const T bias = biasA + runningSum.imag();
Copy link
Member Author

Choose a reason for hiding this comment

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

what's biasA ? how does it work ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me recap some assumptions:

  • you initialize to an angle thats between 0 and 2pi (you need to handle the case where the initialization is with sin<0 so you get >pi when querying for the arccos)
  • each time you add an angle you add a positive angle (so again, handle case where new angle sine is negative)

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually you can assume the initial and each added angle is between 0 and pi because a larger angle would mean a concave (not convex) spherical polygon

}

float getArccosSumofABC_minus_PI()
void addCosine(T cosA, T biasA)
Copy link
Member Author

Choose a reason for hiding this comment

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

where's a addAngle(const complex_t<T>) if I don't want to reconstruct the sine of the angle I'm adding from the cosine?

Comment on lines +59 to 68
static T getArccosSumofABC_minus_PI(T cosA, T cosB, T cosC, T sinA, T sinB, T sinC)
{
const bool AltminusB = cosA < (-cosB);
const T cosSumAB = cosA * cosB - sinA * sinB;
const bool ABltminusC = cosSumAB < (-cosC);
const bool ABltC = cosSumAB < cosC;
// apply triple angle formula
const T absArccosSumABC = acos<T>(clamp<T>(cosSumAB * cosC - (cosA * sinB + sinA * cosB) * sinC, T(-1.0), T(1.0)));
return ((AltminusB ? ABltC : ABltminusC) ? (-absArccosSumABC) : absArccosSumABC) + ((AltminusB || ABltminusC) ? numbers::pi<T> : (-numbers::pi<T>));
}
Copy link
Member Author

Choose a reason for hiding this comment

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

this should disappear and everything that uses it, should just do

accumulator = sincos_accumulator::create(cosA,sinA)
accumulator.addAngle(cosB,sinB);
accumulator.addAngle(cosC,sinC);
accumulator.getSumofArccos();

const T a = cosA;
const T b = runningSum.real();
const bool reverse = abs<T>(min<T>(a, b)) > max<T>(a, b);
const T c = a * b - sqrt<T>((T(1.0) - a * a) * (T(1.0) - b * b));
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 shouldn't need to reconstruct the sine of the angle I've accumulated

retval.tmp3 = sinA;
retval.tmp4 = sinB;
retval.tmp5 = sinC;
retval.runningSum = complex_t<T>::create(cosA, T(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.

if you're using complex for storage, then you should initialize to cosA,sinA

Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Nov 3, 2025

Choose a reason for hiding this comment

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

what I meant by #899 (comment)

store the wraparound in integer part of sine

Is that a sine is [-1,1] and therefore you can store the wraparound in int(abs(complex.y)) or a similar encoding which retains the info about the fractional part of the sine or a separate uint16_t (if you see its faster despite using more regs- test with spherical triangle and rectangle sampling)

Comment on lines +285 to +286
const vector3_type H = hlsl::mul(interaction.getFromTangentSpace(), localH);
assert(hlsl::abs(hlsl::length(H) - scalar_type(1.0)) < scalar_type(1e-4));
Copy link
Member Author

Choose a reason for hiding this comment

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

btw assertion for orthonormality of matrix is just checking the dot products between all pairs of rows are 1.0

You could have a general utility function bool dotIsUnity(A,B); then this becomes

// tangent frame orthonormality
assert(dotIsUnity(fromTangent[0],fromTangent[1]));
assert(dotIsUnity(fromTangent[1],fromTangent[2]));
assert(dotIsUnity(fromTangent[2],fromTangent[0]));
// NDF sampling produced a unit length direction
assert(dotIsUnity(localH,localH));

you don't want to assert stuff on the output H

@devshgraphicsprogramming devshgraphicsprogramming merged commit ba500ab into master Nov 3, 2025
19 of 20 checks passed
@devshgraphicsprogramming devshgraphicsprogramming deleted the bxdf_fixes_cook_torrance branch November 3, 2025 13:24
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.

3 participants