Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ae39c11
draft version of microfacet normal mapping brdf
keptsecret Apr 7, 2026
5ed75b6
store shading (geometric) normal in bxdf wrapper, input interaction i…
keptsecret Apr 8, 2026
6d64218
fill in cache for generate and quotientAndWeight methods + some bug f…
keptsecret Apr 8, 2026
5c6a0c8
various bug fixes so it runs + add to cmakelist sources
keptsecret Apr 9, 2026
f9171aa
fix avoid nans
keptsecret Apr 9, 2026
cfbd1e9
fix confusion in usage of shading vs perturbed normals
keptsecret Apr 9, 2026
8ef9c3b
correct basis transforms for getting Nt
keptsecret Apr 10, 2026
428dfa7
fix wrong normals to G1 in eval
keptsecret Apr 10, 2026
4dfae88
use our own reflect because it works, custom cache type to pass condi…
keptsecret Apr 10, 2026
29d3f2a
separate cache creation by bxdf microfacet
keptsecret Apr 13, 2026
84fcf79
specialize call bxdf forwardPdf, some bug fixes
keptsecret Apr 13, 2026
b013d89
minor bug fixes and redundant typename usage in bxdfs
keptsecret Apr 14, 2026
f632eb1
change edge case to when perturbed normal is close to shading normal
keptsecret Apr 16, 2026
a7067a4
change build interaction for procedural normals
keptsecret Apr 16, 2026
8379c11
merge ris_bxdf, fix conflicts
keptsecret Apr 17, 2026
b02964d
Merge branch 'master' into microfacet_normals
keptsecret Apr 20, 2026
db2706a
forward pdf of delta distributions should return inf
keptsecret Apr 20, 2026
c1e224e
minor bug fixes to changes from master in lambertian and cook torrance
keptsecret Apr 20, 2026
a46ba65
add alternative microfacet shadowing methods, needs organizing
keptsecret Apr 20, 2026
d9e2c86
made shadowing method for microfacet normals configurable
keptsecret Apr 21, 2026
d267f69
remember to pass luminosity contribution hint to interaction
keptsecret Apr 22, 2026
ae0d6ce
removed estevez variant, simplified Nt compute for yining
keptsecret Apr 23, 2026
8e6387f
separate lambda for yining variant
keptsecret Apr 23, 2026
8c40f6d
fix wrong shadowing term in eval
keptsecret Apr 23, 2026
5b168fd
add 1st order scattering version for schussler and yining, 2nd-order …
keptsecret Apr 24, 2026
1b742e5
some minor bug fixes
keptsecret Apr 27, 2026
d11a859
split out the shadowing methods into ndf file
keptsecret Apr 27, 2026
b4a0f3f
microfacet normal bsdf, 1st order only
keptsecret Apr 27, 2026
12b9c59
latest example
keptsecret Apr 27, 2026
db2dc26
fix quotient using wrong V when sample generated from tangent facet
keptsecret Apr 28, 2026
6e84d79
fix yining masking term
keptsecret Apr 29, 2026
965e9fc
fix masking/shadowing conditions usage, especially for microfacet nor…
keptsecret Apr 29, 2026
d9d9603
merge master, fix conflicts
keptsecret Apr 30, 2026
a1d9577
fix passing wrong cache to child bxdf in tangent facet case for quotient
keptsecret Apr 30, 2026
1c20019
mix child bxdf in microfacet normals quotient properly
keptsecret May 1, 2026
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
7 changes: 4 additions & 3 deletions include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -192,19 +192,20 @@ struct SCookTorrance
NBL_FUNC_REQUIRES(RequiredInteraction<Interaction> && RequiredMicrofacetCache<MicrofacetCache> && !IsBSDF)
value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction) NBL_CONST_MEMBER_FUNC
{
const MicrofacetCache cache = MicrofacetCache::template createForReflection<isotropic_interaction_type, sample_type>(interaction, _sample);
const MicrofacetCache cache = MicrofacetCache::template createForReflection<Interaction, sample_type>(interaction, _sample);
return evalAndWeight(_sample, interaction, cache);
}

template<class Interaction=isotropic_interaction_type, class MicrofacetCache=isocache_type
template<class Interaction=conditional_t<IsAnisotropic,anisotropic_interaction_type,isotropic_interaction_type>,
class MicrofacetCache=conditional_t<IsAnisotropic,anisocache_type,isocache_type>
NBL_FUNC_REQUIRES(RequiredInteraction<Interaction> && RequiredMicrofacetCache<MicrofacetCache> && IsBSDF)
value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction) NBL_CONST_MEMBER_FUNC
{
const scalar_type eta = fresnel.getRefractionOrientedEta();
using oriented_etas_t = fresnel::OrientedEtas<monochrome_type>;
oriented_etas_t orientedEta = oriented_etas_t::create(interaction.getNdotV(), hlsl::promote<monochrome_type>(eta));
vector3_type dummyH;
MicrofacetCache cache = MicrofacetCache::template create<isotropic_interaction_type, sample_type>(interaction, _sample, orientedEta, dummyH);
MicrofacetCache cache = MicrofacetCache::template create<Interaction, sample_type>(interaction, _sample, orientedEta, dummyH);
return evalAndWeight(_sample, interaction, cache);
}

Expand Down
2 changes: 1 addition & 1 deletion include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ struct SLambertianBase
}
scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC
{
return forwardPdf(_sample, interaction.isotropic);
return forwardPdf(_sample, interaction.isotropic, _cache);
}

quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC
Expand Down
2 changes: 1 addition & 1 deletion include/nbl/builtin/hlsl/bxdf/common.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ struct SIsotropicMicrofacetCache

// not coming from the medium (reflected) OR
// exiting at the macro scale AND ( (not L outside the cone of possible directions given IoR with constraint VdotH*LdotH<0.0) OR (microfacet not facing toward the macrosurface, i.e. non heightfield profile of microsurface) )
const bool valid = ComputeMicrofacetNormal<scalar_type>::isValidMicrofacet(transmitted, VdotL, retval.absNdotH, fresnel::OrientedEtas<monochrome_type>::create(1.0, computeMicrofacetNormal.orientedEta));
const bool valid = ComputeMicrofacetNormal<scalar_type>::isValidMicrofacet(transmitted, VdotL, retval.absNdotH, fresnel::OrientedEtas<monochrome_type>::create(1.0, hlsl::promote<monochrome_type>(computeMicrofacetNormal.orientedEta)));
if (valid)
{
retval.VdotH = hlsl::dot<vector3_type>(computeMicrofacetNormal.V,H);
Expand Down
12 changes: 6 additions & 6 deletions include/nbl/builtin/hlsl/bxdf/fresnel.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ struct OrientedEtaRcps
{
using scalar_type = typename vector_traits<T>::scalar_type;

static OrientedEtaRcps<T> create(scalar_type NdotI, T eta)
static OrientedEtaRcps<T> create(const scalar_type NdotI, const T eta)
{
OrientedEtaRcps<T> retval;
const bool backside = NdotI < scalar_type(0.0);
Expand All @@ -73,7 +73,7 @@ struct OrientedEtas
{
using scalar_type = typename vector_traits<T>::scalar_type;

static OrientedEtas<T> create(scalar_type NdotI, T eta)
static OrientedEtas<T> create(const scalar_type NdotI, const T eta)
{
OrientedEtas<T> retval;
const bool backside = NdotI < scalar_type(0.0);
Expand Down Expand Up @@ -117,8 +117,8 @@ struct ComputeMicrofacetNormal
ComputeMicrofacetNormal<T> retval;
retval.V = V;
retval.L = L;
fresnel::OrientedEtas<monochrome_type> orientedEtas = fresnel::OrientedEtas<monochrome_type>::create(VdotH, eta);
retval.orientedEta = orientedEtas.value;
fresnel::OrientedEtas<monochrome_type> orientedEtas = fresnel::OrientedEtas<monochrome_type>::create(VdotH, hlsl::promote<monochrome_type>(eta));
retval.orientedEta = orientedEtas.value[0];
return retval;
}

Expand All @@ -138,7 +138,7 @@ struct ComputeMicrofacetNormal
// VdotH <= 1-orientedEta2 for orientedEta<1 -> VdotH<0
// VdotH <= 0 for orientedEta>1
// so for transmission VdotH<=0, H needs to be flipped to be consistent with oriented eta
vector_type unnormalized(const bool _refract)
vector_type unnormalized(const bool _refract) NBL_CONST_MEMBER_FUNC
{
assert(hlsl::dot(V, L) <= -hlsl::min(orientedEta, scalar_type(1.0) / orientedEta));
const scalar_type etaFactor = hlsl::mix(scalar_type(1.0), orientedEta, _refract);
Expand All @@ -148,7 +148,7 @@ struct ComputeMicrofacetNormal
}

// returns normalized vector, but NaN when result is length 0
vector_type normalized(const bool _refract)
vector_type normalized(const bool _refract) NBL_CONST_MEMBER_FUNC
{
const vector_type H = unnormalized(_refract);
return hlsl::normalize<vector_type>(H);
Expand Down
102 changes: 102 additions & 0 deletions include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (C) 2018-2026 - DevSH Graphics Programming Sp. z O.O.
// This file is part of the "Nabla Engine".
// For conditions of distribution and use, see copyright notice in nabla.h
#ifndef _NBL_BUILTIN_HLSL_BXDF_NDF_MICROFACET_NORMAL_SHADOWING_INCLUDED_
#define _NBL_BUILTIN_HLSL_BXDF_NDF_MICROFACET_NORMAL_SHADOWING_INCLUDED_

namespace nbl
{
namespace hlsl
{
namespace bxdf
{
namespace ndf
{

enum PerturbedNormalShadowing : uint16_t
{
PNS_SCHUSSLER,
PNS_YINING
};

template<typename T, PerturbedNormalShadowing P>
struct ShadowingMethod;

// based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf
template<typename T>
struct ShadowingMethod<T, PNS_SCHUSSLER>
{
using scalar_type = T;
using vector3_type = vector<scalar_type, 3>;
using matrix3x3_type = matrix<scalar_type, 3, 3>;

static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL, const bool isTangentFacet=false)
{
const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0));
return hlsl::min(scalar_type(1.0),
clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp)
/ (clampedNpdotL + clampedNtdotL * sinThetaNp)
);
}

static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV)
{
const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0));
return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp);
}

static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis)
{
const vector3_type local_Np = hlsl::mul(shadingBasis, Np);
const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0));
return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt);
}
};

// based on Taming the Shadow Terminator: https://www.yiningkarlli.com/projects/shadowterminator/shadow_terminator_v1_1.pdf
template<typename T>
struct ShadowingMethod<T, PNS_YINING>
{
using scalar_type = T;
using vector3_type = vector<scalar_type, 3>;
using matrix3x3_type = matrix<scalar_type, 3, 3>;

static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL, const bool isTangentFacet=false)
{
const scalar_type g = hlsl::min(scalar_type(1.0),
clampedNdotL / (hlsl::mix(clampedNpdotL, clampedNtdotL, isTangentFacet) * NdotNp)
);
const scalar_type g2 = g * g;
return -g2 * g + g2 + g;
}

// TODO: verify maths
// since Nt is now perpendicular to Np and not N
// total area of surface = 1 = hypotenuse of right triangle
// area of perturbed facet = cos(Np) = NdotNp
// area of tangent facet = cos(Nt) = sin(Np)
// projected area of Np onto V = area * NpdotV
static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV)
{
const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0));
const scalar_type ap = clampedNpdotV * NdotNp;
if (ap < numeric_limits<scalar_type>::min)
return scalar_type(0.0);
const scalar_type at = clampedNtdotV * sinThetaNp;
return ap / (ap + at);
}

static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis)
{
const vector3_type local_Np = hlsl::mul(shadingBasis, Np);
const vector3_type local_Nt = hlsl::normalize(vector3_type(local_Np.xy * -local_Np.z, 1.0 - local_Np.z*local_Np.z));
return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt);
}
};

}
}
}
}

#endif
1 change: 1 addition & 0 deletions include/nbl/builtin/hlsl/bxdf/reflection.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "nbl/builtin/hlsl/bxdf/reflection/beckmann.hlsl"
#include "nbl/builtin/hlsl/bxdf/reflection/ggx.hlsl"
#include "nbl/builtin/hlsl/bxdf/reflection/iridescent.hlsl"
#include "nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl"

namespace nbl
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ struct SDeltaDistribution

scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC
{
return 0;
return bit_cast<scalar_type, uint32_t>(numeric_limits<scalar_type>::infinity);
}
scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC
{
return 0;
return bit_cast<scalar_type, uint32_t>(numeric_limits<scalar_type>::infinity);
}

quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC
Expand Down
Loading
Loading