Skip to content

Conversation

devshgraphicsprogramming
Copy link
Member

@devshgraphicsprogramming devshgraphicsprogramming commented Sep 16, 2025

Description

Continues #899 , #916 and #919

Testing

TODO list:

}
scalar_type getNdotL2() NBL_CONST_MEMBER_FUNC { return NdotL2; }

bool isValid() NBL_CONST_MEMBER_FUNC { return !hlsl::all<vector<bool, 3> >(hlsl::glsl::equal(L.getDirection(), hlsl::promote<vector3_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.

why not use glsl::notEqual and not-unary-not it? I mean SPIR-V OpFOrdNotEqual exists so that intrinsic should probably get made

template<typename T>
vector<T, 2> cartesianToPolar(vector<T, 3> coords)
{
return vector<T, 2>(hlsl::acos(clamp<float>(coords.z, -1, 1)), hlsl::atan2(coords.y, coords.x));
Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Sep 16, 2025

Choose a reason for hiding this comment

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

should be asin for the coord.z cause you get it from a sin in polarToCartesian.

Also there should be no need to clamp, assert that input is already clamped

btw since we're in HLSL land, we should make a struct polar<T> {T theta; T phi}; with specializations for _static_cast_helper and a header for it for semantic clarity and not being suspect to accidental casts betweeen cartesian and polar.

P.S. what uses it so far ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Used to bucket generated sample angles in the bxdf test

Copy link
Member Author

Choose a reason for hiding this comment

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

in GLSL they were in the sampling folder

vec2 nbl_glsl_sampling_envmap_uvCoordFromDirection(vec3 v)

Comment on lines +97 to +122
#ifndef __HLSL_VERSION
namespace std
{
template<typename T, uint16_t N>
struct hash<nbl::hlsl::vector<T,N> >
{
size_t operator()(const nbl::hlsl::vector<T,N>& v) const noexcept
{
size_t seed = 0;
NBL_UNROLL for (uint16_t i = 0; i < N; i++)
nbl::core::hash_combine(seed, v[i]);
return seed;
}
};

template<typename T>
struct hash<nbl::hlsl::vector<T,1> >
{
size_t operator()(const nbl::hlsl::vector<T,1>& v) const noexcept
{
std::hash<T> hasher;
return hasher(v.x);
}
};
}
#endif
Copy link
Member Author

Choose a reason for hiding this comment

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

are you sure having this specialization globally is a good idea ?

What do you use it for ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also used in the bucket bxdf test, for hash map buckets

Comment on lines -386 to +388
T eta;
T etak;
T etak2;
T ior;
T iork;
T iork2;
Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Sep 17, 2025

Choose a reason for hiding this comment

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

#916 (comment)

it seems the thing you really want to precompute would be etaLen2

and store eta

so 2 not 3 variables

Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Sep 17, 2025

Choose a reason for hiding this comment

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

you only changed the naming, why ior and not eta ?

was it because of this ? #916 (comment)

Comment on lines +167 to +174
N ndf;
F fresnel;
};

template<class Config, class N, class F>
NBL_PARTIAL_REQ_TOP(config_concepts::MicrofacetConfiguration<Config> && ndf::NDF<N> && fresnel::Fresnel<F>)
struct SCookTorrance<Config, N, F, true NBL_PARTIAL_REQ_BOT(config_concepts::MicrofacetConfiguration<Config> && ndf::NDF<N> && fresnel::Fresnel<F>) >
{
Copy link
Member Author

Choose a reason for hiding this comment

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

any chance at inheriting from a common base for both BRDF and BSDF, so that we don't have so much duplicate code (BRDF and BSDF only differ in how they factor the reflectance into the generation and pdf)

Comment on lines +66 to +78
using dg1_query_type = SBeckmannDG1Query<scalar_type>;
using g2g1_query_type = SBeckmannG2overG1Query<scalar_type>;

template<class MicrofacetCache NBL_FUNC_REQUIRES(ReadableIsotropicMicrofacetCache<MicrofacetCache>)
scalar_type D(NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
scalar_type nom = exp2<scalar_type>((cache.getNdotH2() - scalar_type(1.0)) / (log<scalar_type>(2.0) * a2 * cache.getNdotH2()));
scalar_type denom = a2 * cache.getNdotH2() * cache.getNdotH2();
scalar_type NdotH2 = cache.getNdotH2();
scalar_type nom = exp2<scalar_type>((NdotH2 - scalar_type(1.0)) / (log<scalar_type>(2.0) * a2 * NdotH2));
scalar_type denom = a2 * NdotH2 * NdotH2;
return numbers::inv_pi<scalar_type> * nom / denom;
}

// brdf
template<class Query NBL_FUNC_REQUIRES(beckmann_concepts::DG1Query<Query>)
scalar_type DG1(NBL_CONST_REF_ARG(Query) query)
scalar_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query)
Copy link
Member Author

Choose a reason for hiding this comment

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

there should be concepts for the queries, I'm not against some defined structs to use, but if you don't template the queries I love the polymorphism which is why we got rid of the overloads and complex function set ups

Copy link
Member Author

Choose a reason for hiding this comment

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

alos it seems that your DG1 doesn't depend on member variables so can be static #916 (comment)

return 2.0 / a2 - 2.0;
}

scalar_type C2(scalar_type NdotX2)
Copy link
Member Author

Choose a reason for hiding this comment

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

clampedNdotX2 also control other places #916 (comment)

You could slap an assert(clampedNdotX2>=scalar_type(0));

Comment on lines +293 to +302
using dg1_query_type = typename base_type::dg1_query_type;
using g2g1_query_type = typename base_type::g2g1_query_type;
using quant_query_type = impl::SBeckmannQuantQuery<scalar_type>;

template<class MicrofacetCache NBL_FUNC_REQUIRES(ReadableIsotropicMicrofacetCache<MicrofacetCache>)
quant_query_type createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
{
quant_query_type dummy; // brdfs don't make use of this
return dummy;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

does anyone make use of 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.

or you mean that bsdfs do ?

Comment on lines 392 to 424
vector<T, 3> generateH(const vector3_type localV, const vector2_type u)
{
return impl::BeckmannGenerateH<scalar_type>::__call(__base.A, localV, u);
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
quant_type D(NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
scalar_type d = __base.template D<MicrofacetCache>(cache);
return createDualMeasureQuantity<T>(d, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type dg1 = __base.DG1(query);
return createDualMeasureQuantity<T>(dg1, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
quant_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type g = __base.template correlated<LS, Interaction>(query, _sample, interaction);
return createDualMeasureQuantity<T>(g, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
return __base.template G2_over_G1<LS, Interaction, MicrofacetCache>(query, _sample, interaction, cache);
}

base_type __base;
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 kinda looks like this part of the Isotropic and Anisotropic code doesn't need to be specialized, you just need to make a concept alias to use in your requires clauses

   template<class Interaction>
   NBL_CONSTEXPR_STATIC_INLINE bool RequiredInteraction = IsAnisotropic ? ...

Example
https://godbolt.devsh.eu/z/zKjKa1

its only the query creation which needs specialization

Comment on lines +442 to +470
template<class MicrofacetCache NBL_FUNC_REQUIRES(ReadableIsotropicMicrofacetCache<MicrofacetCache>)
quant_query_type createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
{
quant_query_type quant_query;
quant_query.VdotHLdotH = cache.getVdotHLdotH();
quant_query.VdotH_etaLdotH = cache.getVdotH() + orientedEta * cache.getLdotH();
return quant_query;
}
template<class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
dg1_query_type createDG1Query(NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
dg1_query_type dg1_query;
dg1_query.ndf = __base.template D<MicrofacetCache>(cache);
dg1_query.lambda_V = __base.LambdaC2(interaction.getNdotV2());
return dg1_query;
}
template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
g2g1_query_type createG2G1Query(NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
g2g1_query_type g2_query;
g2_query.lambda_L = __base.LambdaC2(_sample.getNdotL2());
g2_query.lambda_V = __base.LambdaC2(interaction.getNdotV2());
return g2_query;
}

vector<T, 3> generateH(const vector3_type localV, const vector2_type u)
{
return impl::BeckmannGenerateH<scalar_type>::__call(__base.A, localV, u);
}
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 part seems identical to anisotropic BRDF

Comment on lines 472 to 497
template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
quant_type D(NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
scalar_type d = __base.template D<MicrofacetCache>(cache);
return createDualMeasureQuantity<T, reflect_refract>(d, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type dg1 = __base.DG1(query);
return createDualMeasureQuantity<T, reflect_refract>(dg1, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
quant_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type g = __base.template correlated<LS, Interaction>(query, _sample, interaction);
return createDualMeasureQuantity<T, reflect_refract>(g, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
return __base.template G2_over_G1<LS, Interaction, MicrofacetCache>(query, _sample, interaction, 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.

tbf you could skip the specialization and use NBL_IF_CONSTEXPR to choose which createDualMeasureQuantity is needed

Comment on lines 560 to 565
template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
quant_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type g = __base.template correlated<LS, Interaction>(query, _sample, interaction);
return createDualMeasureQuantity<T, reflect_refract>(g, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_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.

yeah I think that correlated should be measureless, because we'd expec to get DG2 when you multiply D with G2 (correlated) by yourself, and if the code does Dual Measure transform on bothm, then you end up with the differential reflection/refraction factors squared.

Comment on lines +22 to +32
template<typename T>
struct SGGXDG1Query
{
using scalar_type = T;

scalar_type getNdf() NBL_CONST_MEMBER_FUNC { return ndf; }
scalar_type getG1() NBL_CONST_MEMBER_FUNC { return G1; }

scalar_type ndf;
scalar_type G1;
};
Copy link
Member Author

Choose a reason for hiding this comment

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

could be defined in ndf header, its common to all

Comment on lines +41 to +45
BxDFClampMode getClampMode() NBL_CONST_MEMBER_FUNC { return _clamp; }

scalar_type devsh_v;
scalar_type devsh_l;
BxDFClampMode _clamp;
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 clamp mode is not something that can be just set willy nilly if you're specializing your NDFs based on things like MicrofacetTransformTypes

Either deduce it somewhere else, or make this an NBL_CONSTEXPR_STATIC_INLINE

Comment on lines +48 to +58
template<typename T>
struct SGGXQuantQuery
{
using scalar_type = T;

scalar_type getVdotHLdotH() NBL_CONST_MEMBER_FUNC { return VdotHLdotH; }
scalar_type getVdotH_etaLdotH() NBL_CONST_MEMBER_FUNC { return VdotH_etaLdotH; }

scalar_type VdotHLdotH;
scalar_type VdotH_etaLdotH;
};
Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Sep 19, 2025

Choose a reason for hiding this comment

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

this also looks quite common to both NDFs

Comment on lines -78 to 82

template<class Query NBL_FUNC_REQUIRES(ggx_concepts::DG1BrdfQuery<Query>)
scalar_type DG1(NBL_CONST_REF_ARG(Query) query)
scalar_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query)
{
return scalar_type(0.5) * query.getNdf() * query.getG1over2NdotV();
}

template<class Query, class MicrofacetCache NBL_FUNC_REQUIRES(ggx_concepts::DG1BsdfQuery<Query> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
scalar_type DG1(NBL_CONST_REF_ARG(Query) query, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
scalar_type NG = query.getNdf() * query.getG1over2NdotV();
scalar_type factor = scalar_type(0.5);
if (cache.isTransmission())
{
const scalar_type VdotH_etaLdotH = (cache.getVdotH() + query.getOrientedEta() * cache.getLdotH());
// VdotHLdotH is negative under transmission, so this factor is negative
factor *= -scalar_type(2.0) * cache.getVdotHLdotH() / (VdotH_etaLdotH * VdotH_etaLdotH);
}
return NG * factor;
return query.getNdf() * query.getG1();
}
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 seems silly to keep around

Copy link
Member Author

Choose a reason for hiding this comment

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

your query its just multiplying its two members

static scalar_type G1_wo_numerator_devsh_part(scalar_type absNdotX, scalar_type devsh_part)
{
// numerator is 2 * NdotX
return scalar_type(1.0) / (absNdotX + devsh_part);
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 are the two methods needed?

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'd think you only need G1_wo_numerator_devsh_part(scalar_type absNdotX, scalar_type devsh_part)

Comment on lines +39 to +40
scalar_type getDevshV() NBL_CONST_MEMBER_FUNC { return devsh_v; }
scalar_type getDevshL() NBL_CONST_MEMBER_FUNC { return devsh_l; }
Copy link
Member Author

Choose a reason for hiding this comment

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

its fine to have a query like this but we need a concept and template everything on it

}

vector<scalar_type, 2> A;
vector<scalar_type, 2> A; // TODO: remove?
Copy link
Member Author

Choose a reason for hiding this comment

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

yes!

Comment on lines 109 to 119
scalar_type pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) cache)
{
using quant_query_type = typename N::quant_query_type;
using dg1_query_type = typename N::dg1_query_type;

scalar_type dummy;
quant_query_type qq = ndf.template createQuantQuery<isocache_type>(cache, dummy);
dg1_query_type dq = ndf.template createDG1Query<isotropic_interaction_type, isocache_type>(interaction, cache);
quant_type DG1 = ndf.template DG1<sample_type, isotropic_interaction_type>(dq, qq, _sample, interaction);
return DG1.projectedLightMeasure;
}
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 isotropic methods need enable_if because you need to check if the NDF used is isotropic or not

Comment on lines +113 to +122
template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
scalar_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
BxDFClampMode _clamp = query.getClampMode();
assert(_clamp != BxDFClampMode::BCM_NONE);
return scalar_type(4.0) * interaction.getNdotV(_clamp) * _sample.getNdotL(_clamp) * correlated_wo_numerator<LS, Interaction>(query, _sample, interaction);
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) 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.

the cook_torrance_base::Eval calling D and correlated separately misses an optimization opportunity fo GGX to not put a 4 NdotV NdotL factor in the lightProjectedMeasure of the return value

We need to figure out how to leverage it on

Comment on lines 124 to 125
BxDFClampMode _clamp = query.getClampMode();
assert(_clamp != BxDFClampMode::BCM_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.

its weird for the query to tell us about clamping dynamically, I'd possibly prefer constexpr

Comment on lines 177 to 178
return w2 * w2 * atab * numbers::inv_pi<scalar_type>;
}
Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Sep 19, 2025

Choose a reason for hiding this comment

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

kill the burley method, I have some ideas to make GGX spin by employing a covariance matrix of sorts(don't implement yet, just throw the Desmos link in the comment)

https://www.desmos.com/3d/weq2ginq9o

Comment on lines +214 to 224
template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
scalar_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
BxDFClampMode _clamp = query.getClampMode();
assert(_clamp != BxDFClampMode::BCM_NONE);
return scalar_type(4.0) * interaction.getNdotV(_clamp) * _sample.getNdotL(_clamp) * correlated_wo_numerator<LS, Interaction>(query, _sample, interaction);
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) 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.

same problem as burley, these are identical for isotropic and anisotropic once you have the query that provides you with the anisotropic terms.

Even correlated_Wo_numerator is identical

Comment on lines +278 to +281
//reprojection onto hemisphere
//TODO try it wothout the max(), not sure if -t1*t1-t2*t2>-1.0
vector3_type H = t1*T1 + t2*T2 + sqrt<scalar_type>(max<scalar_type>(0.0, 1.0-t1*t1-t2*t2))*V;
//unstretch
Copy link
Member Author

Choose a reason for hiding this comment

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

find out, you might have to be careful about catastropic cancellation making small values near 0 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.

if it was in the original code, thats probably why it was there

Comment on lines 287 to 591
template<typename T, bool IsAnisotropic, MicrofacetTransformTypes reflect_refract NBL_STRUCT_CONSTRAINABLE>
struct GGX;

// partial spec for brdf
template<typename T>
NBL_PARTIAL_REQ_TOP(concepts::FloatingPointScalar<T>)
struct GGX<T,false,MTT_REFLECT NBL_PARTIAL_REQ_BOT(concepts::FloatingPointScalar<T>) >
{
using scalar_type = T;
using base_type = impl::GGXCommon<T,false>;
using quant_type = SDualMeasureQuant<scalar_type>;
using vector2_type = vector<T, 2>;
using vector3_type = vector<T, 3>;

using dg1_query_type = typename base_type::dg1_query_type;
using g2g1_query_type = typename base_type::g2g1_query_type;
using quant_query_type = impl::SGGXQuantQuery<scalar_type>;

template<class MicrofacetCache NBL_FUNC_REQUIRES(ReadableIsotropicMicrofacetCache<MicrofacetCache>)
quant_query_type createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
{
quant_query_type dummy; // brdfs don't make use of this
return dummy;
}
template<class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
dg1_query_type createDG1Query(NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
dg1_query_type dg1_query;
dg1_query.ndf = __base.template D<MicrofacetCache>(cache);
scalar_type clampedNdotV = interaction.getNdotV(BxDFClampMode::BCM_MAX);
dg1_query.G1 = scalar_type(2.0) * clampedNdotV * __base.G1_wo_numerator(clampedNdotV, interaction.getNdotV2());
return dg1_query;
}
template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
g2g1_query_type createG2G1Query(NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
g2g1_query_type g2_query;
g2_query.devsh_l = __base.devsh_part(_sample.getNdotL2());
g2_query.devsh_v = __base.devsh_part(interaction.getNdotV2());
g2_query._clamp = BxDFClampMode::BCM_MAX;
return g2_query;
}

vector<T, 3> generateH(const vector3_type localV, const vector2_type u)
{
return impl::GGXGenerateH<scalar_type>::__call(__base.A, localV, u);
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
quant_type D(NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
scalar_type d = __base.template D<MicrofacetCache>(cache);
return createDualMeasureQuantity<T>(d, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type dg1 = __base.DG1(query);
return createDualMeasureQuantity<T>(dg1, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
quant_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type g = __base.template correlated<LS, Interaction>(query, _sample, interaction);
return createDualMeasureQuantity<T>(g, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
return __base.template G2_over_G1<LS, Interaction, MicrofacetCache>(query, _sample, interaction, cache);
}

base_type __base;
};

template<typename T>
NBL_PARTIAL_REQ_TOP(concepts::FloatingPointScalar<T>)
struct GGX<T,true,MTT_REFLECT NBL_PARTIAL_REQ_BOT(concepts::FloatingPointScalar<T>) >
{
using scalar_type = T;
using base_type = impl::GGXCommon<T,true>;
using quant_type = SDualMeasureQuant<scalar_type>;
using vector2_type = vector<T, 2>;
using vector3_type = vector<T, 3>;

using dg1_query_type = typename base_type::dg1_query_type;
using g2g1_query_type = typename base_type::g2g1_query_type;
using quant_query_type = impl::SGGXQuantQuery<scalar_type>;

template<class MicrofacetCache NBL_FUNC_REQUIRES(AnisotropicMicrofacetCache<MicrofacetCache>)
quant_query_type createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
{
quant_query_type dummy; // brdfs don't make use of this
return dummy;
}
template<class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
dg1_query_type createDG1Query(NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
dg1_query_type dg1_query;
dg1_query.ndf = __base.template D<MicrofacetCache>(cache);
scalar_type clampedNdotV = interaction.getNdotV(BxDFClampMode::BCM_MAX);
dg1_query.G1 = scalar_type(2.0) * clampedNdotV * __base.G1_wo_numerator(clampedNdotV, interaction.getTdotV2(), interaction.getBdotV2(), interaction.getNdotV2());
return dg1_query;
}
template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
g2g1_query_type createG2G1Query(NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
g2g1_query_type g2_query;
g2_query.devsh_l = __base.devsh_part(_sample.getTdotL2(), _sample.getBdotL2(), _sample.getNdotL2());
g2_query.devsh_v = __base.devsh_part(interaction.getTdotV2(), interaction.getBdotV2(), interaction.getNdotV2());
g2_query._clamp = BxDFClampMode::BCM_MAX;
return g2_query;
}

vector<T, 3> generateH(const vector3_type localV, const vector2_type u)
{
return impl::GGXGenerateH<scalar_type>::__call(__base.A, localV, u);
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
quant_type D(NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
scalar_type d = __base.template D<MicrofacetCache>(cache);
return createDualMeasureQuantity<T>(d, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type dg1 = __base.DG1(query);
return createDualMeasureQuantity<T>(dg1, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
quant_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type g = __base.template correlated<LS, Interaction>(query, _sample, interaction);
return createDualMeasureQuantity<T>(g, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
return __base.template G2_over_G1<LS, Interaction, MicrofacetCache>(query, _sample, interaction, cache);
}

base_type __base;
};

// partial for bsdf
template<typename T, MicrofacetTransformTypes reflect_refract>
NBL_PARTIAL_REQ_TOP(concepts::FloatingPointScalar<T>)
struct GGX<T,false,reflect_refract NBL_PARTIAL_REQ_BOT(concepts::FloatingPointScalar<T>) >
{
using scalar_type = T;
using base_type = impl::GGXCommon<T,false>;
using quant_type = SDualMeasureQuant<scalar_type>;
using vector2_type = vector<T, 2>;
using vector3_type = vector<T, 3>;

using dg1_query_type = typename base_type::dg1_query_type;
using g2g1_query_type = typename base_type::g2g1_query_type;
using quant_query_type = impl::SGGXQuantQuery<scalar_type>;

template<class MicrofacetCache NBL_FUNC_REQUIRES(ReadableIsotropicMicrofacetCache<MicrofacetCache>)
quant_query_type createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
{
quant_query_type quant_query;
quant_query.VdotHLdotH = cache.getVdotHLdotH();
quant_query.VdotH_etaLdotH = cache.getVdotH() + orientedEta * cache.getLdotH();
return quant_query;
}
template<class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
dg1_query_type createDG1Query(NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
dg1_query_type dg1_query;
dg1_query.ndf = __base.template D<MicrofacetCache>(cache);
scalar_type clampedNdotV = interaction.getNdotV(BxDFClampMode::BCM_ABS);
dg1_query.G1 = scalar_type(2.0) * clampedNdotV * __base.G1_wo_numerator(clampedNdotV, interaction.getNdotV2());
return dg1_query;
}
template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
g2g1_query_type createG2G1Query(NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
g2g1_query_type g2_query;
g2_query.devsh_l = __base.devsh_part(_sample.getNdotL2());
g2_query.devsh_v = __base.devsh_part(interaction.getNdotV2());
g2_query._clamp = BxDFClampMode::BCM_ABS;
return g2_query;
}

vector<T, 3> generateH(const vector3_type localV, const vector2_type u)
{
return impl::GGXGenerateH<scalar_type>::__call(__base.A, localV, u);
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
quant_type D(NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
scalar_type d = __base.template D<MicrofacetCache>(cache);
return createDualMeasureQuantity<T, reflect_refract>(d, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type dg1 = __base.DG1(query);
return createDualMeasureQuantity<T, reflect_refract>(dg1, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
quant_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type g = __base.template correlated<LS, Interaction>(query, _sample, interaction);
return createDualMeasureQuantity<T, reflect_refract>(g, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction> && ReadableIsotropicMicrofacetCache<MicrofacetCache>)
scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
return __base.template G2_over_G1<LS, Interaction, MicrofacetCache>(query, _sample, interaction, cache);
}

base_type __base;
};

template<typename T, MicrofacetTransformTypes reflect_refract>
NBL_PARTIAL_REQ_TOP(concepts::FloatingPointScalar<T>)
struct GGX<T,true,reflect_refract NBL_PARTIAL_REQ_BOT(concepts::FloatingPointScalar<T>) >
{
using scalar_type = T;
using base_type = impl::GGXCommon<T,true>;
using quant_type = SDualMeasureQuant<scalar_type>;
using vector2_type = vector<T, 2>;
using vector3_type = vector<T, 3>;

using dg1_query_type = typename base_type::dg1_query_type;
using g2g1_query_type = typename base_type::g2g1_query_type;
using quant_query_type = impl::SGGXQuantQuery<scalar_type>;

template<class MicrofacetCache NBL_FUNC_REQUIRES(AnisotropicMicrofacetCache<MicrofacetCache>)
quant_query_type createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
{
quant_query_type quant_query;
quant_query.VdotHLdotH = cache.getVdotHLdotH();
quant_query.VdotH_etaLdotH = cache.getVdotH() + orientedEta * cache.getLdotH();
return quant_query;
}
template<class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
dg1_query_type createDG1Query(NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
dg1_query_type dg1_query;
dg1_query.ndf = __base.template D<MicrofacetCache>(cache);
scalar_type clampedNdotV = interaction.getNdotV(BxDFClampMode::BCM_ABS);
dg1_query.G1 = scalar_type(2.0) * clampedNdotV * __base.G1_wo_numerator(clampedNdotV, interaction.getTdotV2(), interaction.getBdotV2(), interaction.getNdotV2());
return dg1_query;
}
template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
g2g1_query_type createG2G1Query(NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
g2g1_query_type g2_query;
g2_query.devsh_l = __base.devsh_part(_sample.getTdotL2(), _sample.getBdotL2(), _sample.getNdotL2());
g2_query.devsh_v = __base.devsh_part(interaction.getTdotV2(), interaction.getBdotV2(), interaction.getNdotV2());
g2_query._clamp = BxDFClampMode::BCM_ABS;
return g2_query;
}

vector<T, 3> generateH(const vector3_type localV, const vector2_type u)
{
return impl::GGXGenerateH<scalar_type>::__call(__base.A, localV, u);
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
quant_type D(NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
scalar_type d = __base.template D<MicrofacetCache>(cache);
return createDualMeasureQuantity<T, reflect_refract>(d, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type dg1 = __base.DG1(query);
return createDualMeasureQuantity<T, reflect_refract>(dg1, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction>)
quant_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type g = __base.template correlated<LS, Interaction>(query, _sample, interaction);
return createDualMeasureQuantity<T, reflect_refract>(g, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH());
}

template<class LS, class Interaction, class MicrofacetCache NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Anisotropic<Interaction> && AnisotropicMicrofacetCache<MicrofacetCache>)
scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache)
{
return __base.template G2_over_G1<LS, Interaction, MicrofacetCache>(query, _sample, interaction, cache);
}

base_type __base;
};

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 burley, I think all 4 could be rolled into one class, cause IsBSDF is just a matter of a few enable_if and NBL_IF_CONSTEXPR and you can detect anisotropy from the NDF

Comment on lines +339 to +346
return createDualMeasureQuantity<T>(d, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
}

template<class LS, class Interaction NBL_FUNC_REQUIRES(LightSample<LS> && surface_interactions::Isotropic<Interaction>)
quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction)
{
scalar_type dg1 = __base.DG1(query);
return createDualMeasureQuantity<T>(dg1, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX));
Copy link
Member Author

Choose a reason for hiding this comment

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

we should somehow use our knowledge of factors in GGX that cancel out to make the dual measure quantity faster.

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.

2 participants