-
Notifications
You must be signed in to change notification settings - Fork 66
Bxdf fixes cook torrance #930
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…back into OrientedEta struct
retval.iso_cache.VdotL = hlsl::mix(scalar_type(2.0) * retval.iso_cache.VdotH * retval.iso_cache.VdotH - scalar_type(1.0), | ||
VdotH * (LdotH - rcpOrientedEta.value[0] + VdotH * rcpOrientedEta.value[0]), transmitted); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's a more graceful way to compute this via mix
, see my other comment
} | ||
|
||
OrientedEtas<T> getRefractionOrientedEta() NBL_CONST_MEMBER_FUNC { return orientedEta; } | ||
scalar_type getRefractionOrientedEta() NBL_CONST_MEMBER_FUNC { return orientedEta.value[0]; } // expect T to be monochrome? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its a bit of a complicated topic, its possible to have RGB fresnel without dispersion by fixing the refraction Eta to be something else than the etas used to compute RGB reflectance or some sort of interpolation of them.
This is a good default cause the Etas wont differ that much anyway
fresnel::OrientedEtas<vector_type> orientedEta = fresnel::OrientedEtas<vector_type>::create(typename F::scalar_type(1.0), hlsl::promote<vector_type>(fresnel.getRefractionOrientedEta())); | ||
return cache.isValid(orientedEta); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are you promoting to a vector!? Cache.isValid
MUST take the Eta as a scalar!!!!
template<class Interaction, class MicrofacetCache, typename C=bool_constant<!IsAnisotropic> NBL_FUNC_REQUIRES(RequiredInteraction<Interaction> && RequiredMicrofacetCache<MicrofacetCache>) | ||
enable_if_t<C::value && !IsAnisotropic, dg1_query_type> createDG1Query(NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) | ||
{ | ||
dg1_query_type dg1_query; | ||
dg1_query.ndf = __ndf_base.template D<MicrofacetCache>(cache); | ||
scalar_type clampedNdotV = interaction.getNdotV(_clamp); | ||
dg1_query.G1_over_2NdotV = base_type::G1_wo_numerator_devsh_part(clampedNdotV, __ndf_base.devsh_part(interaction.getNdotV2())); | ||
return dg1_query; | ||
} | ||
template<class LS, class Interaction, typename C=bool_constant<!IsAnisotropic> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction>) | ||
enable_if_t<C::value && !IsAnisotropic, 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 = __ndf_base.devsh_part(_sample.getNdotL2()); | ||
g2_query.devsh_v = __ndf_base.devsh_part(interaction.getNdotV2()); | ||
return g2_query; | ||
} | ||
template<class Interaction, class MicrofacetCache, typename C=bool_constant<IsAnisotropic> NBL_FUNC_REQUIRES(RequiredInteraction<Interaction> && RequiredMicrofacetCache<MicrofacetCache>) | ||
enable_if_t<C::value && IsAnisotropic, dg1_query_type> createDG1Query(NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) | ||
{ | ||
dg1_query_type dg1_query; | ||
dg1_query.ndf = __ndf_base.template D<MicrofacetCache>(cache); | ||
scalar_type clampedNdotV = interaction.getNdotV(_clamp); | ||
dg1_query.G1_over_2NdotV = base_type::G1_wo_numerator_devsh_part(clampedNdotV, __ndf_base.devsh_part(interaction.getTdotV2(), interaction.getBdotV2(), interaction.getNdotV2())); | ||
return dg1_query; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you made createDG1Query
use createG2G1Query
and grab the devsh_v
from the return value you could get away with only having one createDG1Query
method I think
dmq.microfacetMeasure = d; | ||
dmq.projectedLightMeasure = d * _sample.getNdotL(BxDFClampMode::BCM_MAX); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is wrong, the projectedLightMeasure
needs a full transform same as beckmann
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because its the PDF of V getting reflected through H which has a PDF of microfacetmeasure
{ | ||
scalar_type d = __ndf_base.template D<MicrofacetCache>(cache); | ||
quant_type dmq; | ||
dmq.microfacetMeasure = d; // note: microfacetMeasure/2NdotV |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh? then the method shouldn't be called D
but D_over_2NdotV
but the comment is wrong, cause your __ndf_base.D
is actually the NDF, not anythingelse
scalar_type NdotL_over_denominator = _sample.getNdotL(BxDFClampMode::BCM_ABS); | ||
if (transmitted) | ||
NdotL_over_denominator *= -scalar_type(4.0) * VdotHLdotH / (VdotH_etaLdotH * VdotH_etaLdotH); | ||
dmq.projectedLightMeasure = d * NdotL_over_denominator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong the denominator is 4 NdotV NdotL
, so NdotL over denominator must be 0.25 / NdotV(ABS)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for the transmission case its - VdotHLdotH / (VdotH_etaLdotH*VdotH_etaLdotH*NdotV(abs))
So its NdotL_over_denominator = mix(0.25,VdotHLdotH*neg_rcp2_VdotH_etaLdotH,transmitted)/NdotV(ABS);
dmq.microfacetMeasure = d; // note: microfacetMeasure/2NdotV | ||
|
||
const scalar_type VdotHLdotH = quant_query.getVdotHLdotH(); | ||
const scalar_type VdotH_etaLdotH = quant_query.getVdotH_etaLdotH(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should already take this a neg_rcp2_VdotH_etaLdotH // -(VdotH+eta*LdotH)^(-2)
// help avoid preprocessor splitting template declarations by comma | ||
#define SINGLE_ARG(...) __VA_ARGS__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you want a global macro, make it NDF
prefixed.
Also all macros need NBL_HLSL
prefix
template<typename C=bool_constant<!IsAnisotropic>, typename T=conditional_t<IsBSDF, vector3_type, vector2_type> > | ||
enable_if_t<C::value && !IsAnisotropic, sample_type> generate(NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, const T u, NBL_REF_ARG(isocache_type) cache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can use conditional_t
directly in a function signature, there's no need to add another template parameter T
P.S. also try to use NBL_FUNC_REQUIRES instead of the enable_if_t
fresnel_type _f = fresnel; | ||
NBL_IF_CONSTEXPR(IsBSDF) | ||
_f = impl::getOrientedFresnel<fresnel_type, IsBSDF>::__call(fresnel, interaction.getNdotV()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
either don't passIsBSDF
to impl::getOrientedFresnel
or skip the NBL_IF_CONSTEXPR
spectral_type quo = hlsl::promote<spectral_type>(0.0); | ||
const bool notTIR = impl::check_TIR_helper<fresnel_type, IsBSDF>::template __call<MicrofacetCache>(_f, cache); | ||
if (notTIR) | ||
assert(notTIR); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a comment why
template<class MicrofacetCache> | ||
NBL_CONSTEXPR_STATIC_INLINE bool RequiredMicrofacetCache = IsAnisotropic ? AnisotropicMicrofacetCache<MicrofacetCache> : ReadableIsotropicMicrofacetCache<MicrofacetCache>; | ||
NDF_CONSTEXPR_DECLS(_IsAnisotropic,reflect_refract); | ||
NDF_TYPE_ALIASES(SINGLE_ARG(Beckmann<T,IsAnisotropic,SupportedPaths>), SINGLE_ARG(impl::BeckmannCommon<T,IsAnisotropic>), impl::SBeckmannDG1Query<scalar_type>, impl::SBeckmannG2overG1Query<scalar_type>, DualMeasureQuantQuery<scalar_type>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could structure the macro to take a BOOST_PP tuple
NDF_TYPE_ALIASES((Beckmann<T,IsAnisotropic,SupportedPaths>)(impl::BeckmannCommon<T,IsAnisotropic>)(impl::SBeckmannDG1Query<scalar_type>)(impl::SBeckmannG2overG1Query<scalar_type>)(DualMeasureQuantQuery<scalar_type>))
and then get the args via BOOST_PP_TUPLE_ELEM
quant_query_type quant_query; // only has members for refraction | ||
if (SupportsTransmission) | ||
{ | ||
quant_query.VdotHLdotH = cache.getVdotHLdotH(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a comment that even though in PBRT its abs(VdotH)*abs(LdotH)
we leverage that under transmission the sign must always be negattive and rest of the code accounts for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or where the getVdotHLdotH
concept check is defined
scalar_type getVdotH_etaLdotH() NBL_CONST_MEMBER_FUNC { return VdotH_etaLdotH; } | ||
|
||
scalar_type VdotHLdotH; | ||
scalar_type VdotH_etaLdotH; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the VdotH_etaLdotH
needs to change (name and what they store) as per #930 (comment)
and a comment needs to be left here or in the concept #930 (comment)
retval.projectedLightMeasure = microfacetMeasure*mix<scalar_type>(scalar_type(0.25),VdotHLdotH,transmitted); | ||
scalar_type denominator = clampedNdotV; | ||
if (transmitted) // VdotHLdotH is negative under transmission, so thats denominator is negative | ||
denominator *= -VdotH_etaLdotH * VdotH_etaLdotH; | ||
} | ||
return pdf * (transmitted ? VdotHLdotH : scalar_type(0.25)) / denominator; | ||
} | ||
|
||
scalar_type pdf; | ||
scalar_type absNdotV; | ||
bool transmitted; | ||
scalar_type VdotH; | ||
scalar_type LdotH; | ||
scalar_type VdotHLdotH; | ||
scalar_type orientedEta; | ||
retval.projectedLightMeasure /= denominator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is correct, unlike #930 (comment)
although you may want to reformulate it same way into mix(0.25,VdotHLdotH*neg_rcp2_VdotH_etaLdotH,transmitted)/NdotV(ABS);
dmq.microfacetMeasure = dg1; | ||
dmq.projectedLightMeasure = dg1;// TODO: figure this out * _sample.getNdotL(BxDFClampMode::BCM_MAX); | ||
return dmq; | ||
} | ||
template<class LS, class Interaction, typename C=bool_constant<IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction>) | ||
enable_if_t<C::value && IsBSDF, 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_type::template DG1<dg1_query_type>(query); | ||
quant_type dmq; | ||
dmq.microfacetMeasure = dg1; // note: microfacetMeasure/2NdotV |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if your base DG1
returns something else than actual DG1 like DG1_over_4NdotV
, then it should be called DG1_over_4NdotV
then you assign straight to projectedLightMeasure
in the reflective case and properly multiply the 4*NdotV
back in for the microfacetMeasure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ideally you should really just return DG1_over_2NdotV
cause extra 0.5 mul is kinda pointless inside
const scalar_type VdotHLdotH = quant_query.getVdotHLdotH(); | ||
const scalar_type VdotH_etaLdotH = quant_query.getVdotH_etaLdotH(); | ||
const bool transmitted = reflect_refract==MTT_REFRACT || (reflect_refract!=MTT_REFLECT && VdotHLdotH < scalar_type(0.0)); | ||
scalar_type NdotL_over_denominator = _sample.getNdotL(BxDFClampMode::BCM_ABS); | ||
if (transmitted) | ||
NdotL_over_denominator *= -scalar_type(4.0) * VdotHLdotH / (VdotH_etaLdotH * VdotH_etaLdotH); | ||
dmq.projectedLightMeasure = dg1;// TODO: figure this out * NdotL_over_denominator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for the projectedLightMeasure
transmittance you'd just do mix(0.5,2.0,transmitted)*DG1_over_2NdotV
and then
if (transmitted)
dmq.projectedLightMeasure *= VdotHLdotH*neg_rcp2_VdotH_etaLdotH;
dmq.microfacetMeasure *= 2.0*clampedNdotV;
template<class LS, class Interaction, class MicrofacetCache, typename C=bool_constant<!IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction> && RequiredMicrofacetCache<MicrofacetCache>) | ||
enable_if_t<C::value && !IsBSDF, 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 = __ndf_base.template D<MicrofacetCache>(cache); | ||
quant_type dmq; | ||
dmq.microfacetMeasure = d; | ||
dmq.projectedLightMeasure = d * _sample.getNdotL(BxDFClampMode::BCM_MAX); | ||
return dmq; | ||
} | ||
template<class LS, class Interaction, class MicrofacetCache, typename C=bool_constant<IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction> && RequiredMicrofacetCache<MicrofacetCache>) | ||
enable_if_t<C::value && IsBSDF, 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 = __ndf_base.template D<MicrofacetCache>(cache); | ||
quant_type dmq; | ||
dmq.microfacetMeasure = d; // note: microfacetMeasure/2NdotV | ||
|
||
const scalar_type VdotHLdotH = quant_query.getVdotHLdotH(); | ||
const scalar_type VdotH_etaLdotH = quant_query.getVdotH_etaLdotH(); | ||
const bool transmitted = reflect_refract==MTT_REFRACT || (reflect_refract!=MTT_REFLECT && VdotHLdotH < scalar_type(0.0)); | ||
scalar_type NdotL_over_denominator = _sample.getNdotL(BxDFClampMode::BCM_ABS); | ||
if (transmitted) | ||
NdotL_over_denominator *= -scalar_type(4.0) * VdotHLdotH / (VdotH_etaLdotH * VdotH_etaLdotH); | ||
dmq.projectedLightMeasure = d * NdotL_over_denominator; | ||
return dmq; | ||
} | ||
|
||
template<class LS, class Interaction, typename C=bool_constant<!IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction>) | ||
enable_if_t<C::value && !IsBSDF, 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_type::template DG1<dg1_query_type>(query); | ||
quant_type dmq; | ||
dmq.microfacetMeasure = dg1; | ||
dmq.projectedLightMeasure = dg1;// TODO: figure this out * _sample.getNdotL(BxDFClampMode::BCM_MAX); | ||
return dmq; | ||
} | ||
template<class LS, class Interaction, typename C=bool_constant<IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction>) | ||
enable_if_t<C::value && IsBSDF, 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_type::template DG1<dg1_query_type>(query); | ||
quant_type dmq; | ||
dmq.microfacetMeasure = dg1; // note: microfacetMeasure/2NdotV | ||
|
||
const scalar_type VdotHLdotH = quant_query.getVdotHLdotH(); | ||
const scalar_type VdotH_etaLdotH = quant_query.getVdotH_etaLdotH(); | ||
const bool transmitted = reflect_refract==MTT_REFRACT || (reflect_refract!=MTT_REFLECT && VdotHLdotH < scalar_type(0.0)); | ||
scalar_type NdotL_over_denominator = _sample.getNdotL(BxDFClampMode::BCM_ABS); | ||
if (transmitted) | ||
NdotL_over_denominator *= -scalar_type(4.0) * VdotHLdotH / (VdotH_etaLdotH * VdotH_etaLdotH); | ||
dmq.projectedLightMeasure = dg1;// TODO: figure this out * NdotL_over_denominator; | ||
return dmq; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stuff maybe we can rethink, I'd be tempted to allow an NDF "skip" providing D
and correlated
if it has both the DG1
and Dcorrelated
methods (so the cook torrance can leverage that and skip multiplying D
with correlated
by itself for eval
)
NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = ndf_type::NDFSurfaceType != ndf::MTT_REFLECT; | ||
|
||
template<class Interaction, class MicrofacetCache> | ||
static bool __checkValid(NBL_CONST_REF_ARG(fresnel_type) f, NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why make static
and pass the f
if its your member?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it because the fresnel is already properly oriented? then give f
a clear name like reorientedFresnel
and since its static
you may as well make the __checkValid
into a standalone struct functor with a partial spec on IsBSDF
, that way you don't call impl::check_TIR_helper
but you can call cache.isValid(reorientedFresnel.getRefractionOrientedEta())
directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually if you use impl::checkValid<IsBSDF>::template __call()
here
Nabla/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl
Lines 350 to 351 in dcc2464
const bool notTIR = impl::check_TIR_helper<fresnel_type, IsBSDF>::template __call<MicrofacetCache>(_f, cache); | |
assert(notTIR); |
then there's no need to have an impl::check_TIR_helper
at all
|
||
// help avoid preprocessor splitting template declarations by comma | ||
#define SINGLE_ARG(...) __VA_ARGS__ | ||
#define NDF_SINGLE_ARG(...) __VA_ARGS__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imho you wont need it if you rework NDF_TYPE_ALIASES
into taking a boost PP Tuple
else | ||
dmq.projectedLightMeasure = d * _sample.getNdotL(BxDFClampMode::BCM_MAX); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw you can always use ABS, because it should be the callee's job to not call you on backfacing BRDF if it chooses to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is just the NDF
Description
Continues #899 , #916 and #919
Testing
TODO list: