Skip to content

Created concepts for samplers, added quotient_and_pdf variants to satisfy the concepts#1001

Open
karimsayedre wants to merge 46 commits intomasterfrom
sampler-concepts
Open

Created concepts for samplers, added quotient_and_pdf variants to satisfy the concepts#1001
karimsayedre wants to merge 46 commits intomasterfrom
sampler-concepts

Conversation

@karimsayedre
Copy link
Copy Markdown
Contributor

@karimsayedre karimsayedre commented Feb 18, 2026

Examples PR

Notes:

  • The quotient_and_pdf() methods in UniformHemisphere, UniformSphere, ProjectedHemisphere, and ProjectedSphere shadow the struct type sampling::quotient_and_pdf<Q, P> from quotient_and_pdf.hlsl. DXC can't resolve the return type because the method name takes precedence over the struct name during lookup. Fixed by fully qualifying with ::nbl::hlsl::sampling::quotient_and_pdf<U, T>.
  • Obv. there's some refactoring to be done to satisfy all the concepts, so for not Basic (Level1) samplers are concept tested

…concepts

- Move codomain_and_*Pdf and domain_and_*Pdf structs into their own warp_and_pdf.hlsl header
- Keeping quotient_and_pdf.hlsl focused on importance sampling quotients for BxDFs
- Add SampleWithPDF, SampleWithRcpPDF, and SampleWithDensity concepts to validate sample types
- Used concept composition (NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT) to build ResamplableSampler on TractableSampler and BijectiveSampler on ResamplableSampler
Comment on lines 97 to 102
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if

const scalar_type tmp = _sample.z -  scalar_type(0.5);
retval.z *= sign(tmp);
if (tmp)
   _sample.z = tmp;
_sample.z *= 2.0;

would be faster

Comment on lines 69 to 70
if (pyramidAngles())
return 0.f;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

instead check for the solidAngle being 0 or below a threshold

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

then you can fold pyramidAngles into the create

Comment on lines 103 to 109
const scalar_type u = subTriSolidAngleRatio > numeric_limits<scalar_type>::min ? subTriSolidAngleRatio : 0.0;

const scalar_type sinBC_s_product = sinB_ * sinC_;

// 1 ULP below 1.0, ensures (1.0 - cosBC_s) is strictly positive in float
const scalar_type one_below_one = bit_cast<scalar_type>(bit_cast<uint_type>(scalar_type(1)) - uint_type(1));
const scalar_type one_below_one = ieee754::nextTowardZero(scalar_type(1.0));
const scalar_type cosBC_s = sinBC_s_product > numeric_limits<scalar_type>::min ? (cosA + cosB_ * cosC_) / sinBC_s_product : triCosC;
const scalar_type v_denom = scalar_type(1.0) - (cosBC_s < one_below_one ? cosBC_s : triCosC);
Copy link
Copy Markdown
Member

@devshgraphicsprogramming devshgraphicsprogramming Mar 25, 2026

Choose a reason for hiding this comment

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

replace the ?s with hlsl::select


struct cache_type
{
// TODO: should we cache `r`?
Copy link
Copy Markdown
Member

@devshgraphicsprogramming devshgraphicsprogramming Mar 27, 2026

Choose a reason for hiding this comment

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

no, what for?
the PDF is constant

Comment on lines +53 to +64
typename Linear<scalar_type>::cache_type linearYCache;

vector2_type p;
p.y = lineary.generate(u.y, linearCache);
p.y = lineary.generate(u.y, linearYCache);

const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]);
Linear<scalar_type> linearx = Linear<scalar_type>::create(ySliceEndPoints);
p.x = linearx.generate(u.x, linearCache);
p.x = linearx.generate(u.x, cache.linearXCache);

cache.pdf = nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], p.x) * fourOverTwiceAreasUnderXCurveSum;
// pre-multiply by normalization so forwardPdf is just addition
cache.normalizedStart = ySliceEndPoints[0] * fourOverTwiceAreasUnderXCurveSum;
cache.linearXCache.diffTimesX *= fourOverTwiceAreasUnderXCurveSum;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the reason I came up with this refactor #1001 (comment) was motivated exactly by doing less work in bilinear

const T twopi = T(2.0) * numbers::pi<T>;
phi += hlsl::mix(T(0.0), twopi, phi < T(0.0));
return vector_t2(_sample.z, phi / twopi);
return vector_t2(_sample.z, impl::phiSampleFromDirection(_sample.x, _sample.y));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this was just a polar sampling with corrected/linear r instead of square root

Btw we should use concentric because its higher quality, since the would-be-r needs to be squared, you'd need to pass dominant from the concentric mapping back to multiply the XY coordinates by it again

then you can use sqrt(1-dominant*dominant) as the z-coordinate for the hemisphere

vector_t3 retval = hemisphere_t::__generate(vector_t2(hemiX, _sample.y));
retval.z = chooseLower ? (-retval.z) : retval.z;
return retval;
return impl::directionFromZandPhi(T(1.0) - T(2.0) * _sample.x, _sample.y);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

to make it polar vs concentric mapping agnostic, you could

const float tmp = _sample.u*2.0-1.0;
_sample.u = abs(tmp);
L = callHemisphere(_sample,cache);
L.z *= sign(tmp);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

its also easy to invert

const float dir = sign(L.z)*0.5;
L.z = abs(L.z);
_sample = hemisphere.invert(L);
_sample.x = _sample.x*dir+0.5;

# Conflicts:
#	examples_tests
…() for floats, separeted SMaxError into a separate file with support to other types than float
Comment on lines 55 to 67
static density_type forwardPdf(const cache_type cache)
static density_type forwardPdf(const codomain_type v, const cache_type cache)
{
return cache.L_z * numbers::inv_pi<T>;
return v.z * numbers::inv_pi<T>;
}

static weight_type forwardWeight(const cache_type cache)
static weight_type forwardWeight(const codomain_type v, const cache_type cache)
{
return forwardPdf(cache);
return forwardPdf(v, cache);
}

static density_type backwardPdf(const codomain_type L)
{
assert(L.z > 0);

cache_type c;
c.L_z = L.z;
return forwardPdf(c);
return forwardPdf(L, c);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is all wrong it should have been domain_type !!!!

((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.generate(u, cache)), ::nbl::hlsl::is_same_v, typename T::codomain_type))
((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.forwardPdf(cache)), ::nbl::hlsl::is_same_v, typename T::density_type)));
((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.forwardPdf(v, cache)), ::nbl::hlsl::is_same_v, typename T::density_type)));
#undef cache
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nooo, it was meant to be u,cache

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