-
Notifications
You must be signed in to change notification settings - Fork 65
Hlsl bxdfs 3 #899
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?
Hlsl bxdfs 3 #899
Conversation
Another Question, why don't we add |
return sample_type::create(L, nbl::hlsl::dot<vector3_type>(V, L.direction), T, B, N); | ||
} | ||
|
||
sample_type generate_wo_clamps(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_REF_ARG(vector<scalar_type, 3>) u) |
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.
don't take vec3 by reference, take by copy as a const
return (spectral_type)0; | ||
} | ||
|
||
sample_type __generate_wo_clamps(NBL_CONST_REF_ARG(vector3_type) V, NBL_CONST_REF_ARG(vector3_type) T, NBL_CONST_REF_ARG(vector3_type) B, NBL_CONST_REF_ARG(vector3_type) N, scalar_type NdotV, scalar_type absNdotV, NBL_REF_ARG(vector3_type) u, NBL_CONST_REF_ARG(fresnel::OrientedEtas<scalar_type>) orientedEta, NBL_CONST_REF_ARG(fresnel::OrientedEtaRcps<scalar_type>) rcpEta, NBL_REF_ARG(bool) 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.
take a scalar u
if you collapse all 3 generate
methods into one, then keep taking a vec3 u
p.S. you could also have a __generate_wo_clamps
which takes just isotropic interaction and spits out a single L
ray_dir_info and LdotV
not a full blown Light Sample,, then that could take a scalar u
Refract<scalar_type> r = Refract<scalar_type>::create(rcpEta, V, N, NdotV); | ||
bxdf::ReflectRefract<scalar_type> rr = bxdf::ReflectRefract<scalar_type>::create(transmitted, r); | ||
L.direction = rr(transmitted); | ||
return sample_type::create(L, nbl::hlsl::dot<vector3_type>(V, L.direction), T, B, N); |
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 ReflecctRefract::operator()
could have an overload added
vector_type operator()(const bool doRefract, NBL_REF_ARG(scalar_type) out_IdotTorR)
{
const float a = hlsl::mix<scalar_type>(1.0f, rcpOrientedEta, doRefract);
const float b = NdotI * a + NdotTorR;
// assuming `I` is normalized
out_IdotTorR = NdotI * b - a;
return N * b - I * a;
}
vector_type operator()(const bool doRefract)
{
scalar_type dummy;
return operator(doRefract,dummy);
}
its slightly more efficient than doing the for proguct between L and V
Refract<scalar_type> r = Refract<scalar_type>::create(rcpEta, V, N, NdotV); | ||
bxdf::ReflectRefract<scalar_type> rr = bxdf::ReflectRefract<scalar_type>::create(transmitted, r); | ||
L.direction = rr(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.
the ray_dir_info_type
and the ray_dir_info::Basic
concept should have a method reflectRefract(NBL_CONST_REF_ARG(ReflectRefract<scalar_type>) rr, const bool tranmitted)
which should be used instead of doing L.direction = rr(transmitted)
Why?
Because when we'ss use a RayDirInfo with ray-differentials or covariance tracing metadata we need to handle those extra parameters in the ray-dir-info (its not as simple as just setting a new ray direction) and by using the ray-dir-info's method to do that, the BxDFs don't need to care about how exacly its being done .
quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(params_isotropic_t) params) | ||
{ | ||
const bool transmitted = ComputeMicrofacetNormal<vector3_type>::isTransmissionPath(params.getNdotVUnclamped(), params.getNdotLUnclamped()); | ||
|
||
fresnel::OrientedEtaRcps<scalar_type> rcpOrientedEtas = fresnel::OrientedEtaRcps<scalar_type>::create(params.getNdotV(), eta); | ||
|
||
const scalar_type _pdf = bit_cast<scalar_type, uint32_t>(numeric_limits<scalar_type>::infinity); | ||
scalar_type quo = hlsl::mix<scalar_type, bool>(1.0, rcpOrientedEtas.value, transmitted); | ||
return quotient_pdf_type::create((spectral_type)(quo), _pdf); | ||
} | ||
quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(params_anisotropic_t) params) | ||
{ | ||
const bool transmitted = ComputeMicrofacetNormal<vector3_type>::isTransmissionPath(params.getNdotVUnclamped(), params.getNdotLUnclamped()); | ||
|
||
fresnel::OrientedEtaRcps<scalar_type> rcpOrientedEtas = fresnel::OrientedEtaRcps<scalar_type>::create(params.getNdotV(), eta); | ||
|
||
const scalar_type _pdf = bit_cast<scalar_type, uint32_t>(numeric_limits<scalar_type>::infinity); | ||
scalar_type quo = hlsl::mix(1.0, rcpOrientedEtas.value, transmitted); | ||
return quotient_pdf_type::create((spectral_type)(quo), _pdf); | ||
} |
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.
nope, that is not the correct quotient calculation
you transmit with probablity (1-reflectance)*deltaDistribution(L=V.refract(N))
and reflect with probability reflectance*deltaDistributiron(L=v.reflect(N))
This means your quotient_and_pdf is always (1.f,INF)
Why? There's no absorption, the light reflected and refracted always add up to 1 and you've importance sampled it perfectly.
static this_t create(NBL_CONST_REF_ARG(spectral_type) eta2, NBL_CONST_REF_ARG(spectral_type) luminosityContributionHint) | ||
{ | ||
this_t retval; | ||
retval.eta2 = eta2; | ||
retval.luminosityContributionHint = luminosityContributionHint; | ||
return retval; | ||
} | ||
|
||
static this_t create(NBL_CONST_REF_ARG(SBxDFCreationParams<scalar_type, spectral_type>) params) | ||
{ | ||
return create(params.eta2, params.luminosityContributionHint); | ||
} | ||
|
||
void init(NBL_CONST_REF_ARG(SBxDFCreationParams<scalar_type, spectral_type>) params) | ||
{ | ||
eta2 = params.eta2; | ||
luminosityContributionHint = params.luminosityContributionHint; | ||
} |
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.
same comment as always
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 probide a single create
from just eta
which sets the luminosityContributionHint to the Rec.709 luma coefficients
sample_type __generate_wo_clamps(NBL_CONST_REF_ARG(vector3_type) V, NBL_CONST_REF_ARG(vector3_type) T, NBL_CONST_REF_ARG(vector3_type) B, NBL_CONST_REF_ARG(vector3_type) N, scalar_type NdotV, scalar_type absNdotV, NBL_REF_ARG(vector3_type) u, NBL_CONST_REF_ARG(spectral_type) eta2, NBL_CONST_REF_ARG(spectral_type) luminosityContributionHint, NBL_REF_ARG(spectral_type) remainderMetadata) | ||
{ | ||
// we will only ever intersect from the outside | ||
const spectral_type reflectance = fresnel::thinDielectricInfiniteScatter<spectral_type>(fresnel::Dielectric<spectral_type>::__call(eta2,absNdotV)); | ||
|
||
// we are only allowed one choice for the entire ray, so make the probability a weighted sum | ||
const scalar_type reflectionProb = nbl::hlsl::dot<spectral_type>(reflectance, luminosityContributionHint); | ||
|
||
scalar_type rcpChoiceProb; | ||
const bool transmitted = math::partitionRandVariable(reflectionProb, u.z, rcpChoiceProb); | ||
remainderMetadata = (transmitted ? ((spectral_type)(1.0) - reflectance) : reflectance) * rcpChoiceProb; | ||
|
||
ray_dir_info_type L; | ||
L.direction = (transmitted ? (vector3_type)(0.0) : N * 2.0f * NdotV) - V; | ||
return sample_type::create(L, nbl::hlsl::dot<vector3_type>(V, L.direction), T, B, N); | ||
} | ||
|
||
sample_type generate_wo_clamps(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_REF_ARG(vector<scalar_type, 3>) u) | ||
{ | ||
scalar_type NdotV = interaction.isotropic.getNdotV(); | ||
vector3_type dummy; | ||
return __generate_wo_clamps(interaction.isotropic.getV().getDirection(), interaction.getT(), interaction.getB(), interaction.isotropic.getN(), NdotV, NdotV, u, eta2, luminosityContributionHint, dummy); | ||
} | ||
|
||
sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_REF_ARG(vector<scalar_type, 3>) u) | ||
{ | ||
scalar_type NdotV = interaction.isotropic.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.
again, just collapse the 3 generate
together
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 do 1 __generate_wo_clamps
and 1 generate
but have them both take an interaction instead of a V,T,B,N,...
// its basically a set of weights that determine | ||
// assert(1.0==luminosityContributionHint.r+luminosityContributionHint.g+luminosityContributionHint.b); | ||
// `remainderMetadata` is a variable which the generator function returns byproducts of sample generation that would otherwise have to be redundantly calculated `quotient_and_pdf` | ||
sample_type __generate_wo_clamps(NBL_CONST_REF_ARG(vector3_type) V, NBL_CONST_REF_ARG(vector3_type) T, NBL_CONST_REF_ARG(vector3_type) B, NBL_CONST_REF_ARG(vector3_type) N, scalar_type NdotV, scalar_type absNdotV, NBL_REF_ARG(vector3_type) u, NBL_CONST_REF_ARG(spectral_type) eta2, NBL_CONST_REF_ARG(spectral_type) luminosityContributionHint, NBL_REF_ARG(spectral_type) remainderMetadata) |
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.
luminosityContributionHint
and eta2
are already member variables, why is the function taking them ?
ray_dir_info_type L; | ||
L.direction = (transmitted ? (vector3_type)(0.0) : N * 2.0f * NdotV) - V; |
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.
do it as L = transmitted ? V.transmit():V.reflect({...construct a fresnel::Reflect from N and NdotV directly...});
for same reasons of ray differentials or covariance tracing
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 also add a transmitReflect
method to the concept if you wish and use that instead
quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(params_anisotropic_t) params) | ||
{ | ||
const bool transmitted = ComputeMicrofacetNormal<vector3_type>::isTransmissionPath(params.getNdotVUnclamped(), params.getNdotLUnclamped()); | ||
const spectral_type reflectance = fresnel::thinDielectricInfiniteScatter<spectral_type>(fresnel::Dielectric<spectral_type>::__call(eta2, params.getNdotV())); | ||
const spectral_type sampleValue = hlsl::mix(reflectance, (spectral_type)(1.0) - reflectance, transmitted); | ||
|
||
const scalar_type sampleProb = nbl::hlsl::dot<spectral_type>(sampleValue,luminosityContributionHint); | ||
|
||
const scalar_type _pdf = bit_cast<scalar_type, uint32_t>(numeric_limits<scalar_type>::infinity); | ||
return quotient_pdf_type::create((spectral_type)(sampleValue / sampleProb), _pdf); | ||
} |
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 doesn't anisotropic cast down to isotropic or have a getter to get the isotropic ? So that aniso can call the iso method and return that saving us duplicate code ?
{ | ||
const bool transmitted = ComputeMicrofacetNormal<vector3_type>::isTransmissionPath(params.getNdotVUnclamped(), params.getNdotLUnclamped()); | ||
const spectral_type reflectance = fresnel::thinDielectricInfiniteScatter<spectral_type>(fresnel::Dielectric<spectral_type>::__call(eta2, params.getNdotV())); | ||
const spectral_type sampleValue = hlsl::mix(reflectance, (spectral_type)(1.0) - reflectance, 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.
use promote
instead of dum C-cast for the 1.0
const scalar_type sampleProb = nbl::hlsl::dot<spectral_type>(sampleValue,luminosityContributionHint); | ||
|
||
const scalar_type _pdf = bit_cast<scalar_type, uint32_t>(numeric_limits<scalar_type>::infinity); | ||
return quotient_pdf_type::create((spectral_type)(sampleValue / sampleProb), _pdf); |
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.
sampleValue
is a spectral_type which should already have an operator/(scalar_type)
no need to cast
|
||
ray_dir_info_type L; | ||
L.direction = (transmitted ? (vector3_type)(0.0) : N * 2.0f * NdotV) - V; | ||
return sample_type::create(L, nbl::hlsl::dot<vector3_type>(V, L.direction), T, B, N); |
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.
VdotL
can be computed much more cheaply as (transmitted ? 0.f:2.f)*NdotV*NdotV-1.f;
template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>) | ||
struct SAnisotropicParams | ||
{ | ||
using this_t = SAnisotropicParams<T>; | ||
|
||
static this_t create(T NdotH, T one_minus_NdotH2_rcp, T TdotH2, T BdotH2, T nx, T ny) // blinn-phong | ||
{ | ||
this_t retval; | ||
retval.NdotH = NdotH; | ||
retval.one_minus_NdotH2_rcp = one_minus_NdotH2_rcp; | ||
retval.TdotH2 = TdotH2; | ||
retval.BdotH2 = BdotH2; | ||
retval.nx = nx; | ||
retval.ny = ny; | ||
return retval; | ||
} | ||
|
||
static this_t create(T ax, T ay, T ax2, T ay2, T TdotH2, T BdotH2, T NdotH2) // beckmann, ggx aniso | ||
{ | ||
this_t retval; | ||
retval.ax = ax; | ||
retval.ax2 = ax2; | ||
retval.ay = ay; | ||
retval.ay2 = ay2; | ||
retval.TdotH2 = TdotH2; | ||
retval.BdotH2 = BdotH2; | ||
retval.NdotH2 = NdotH2; | ||
return retval; | ||
} | ||
|
||
static this_t create(T a2, T TdotH, T BdotH, T NdotH) // ggx burley | ||
{ | ||
this_t retval; | ||
retval.ax = a2; | ||
retval.TdotH = TdotH; | ||
retval.BdotH = BdotH; | ||
retval.NdotH = NdotH; | ||
return retval; | ||
} | ||
|
||
T ax; | ||
T ay; | ||
T ax2; | ||
T ay2; | ||
T nx; | ||
T ny; | ||
T NdotH; | ||
T TdotH; | ||
T BdotH; | ||
T NdotH2; | ||
T TdotH2; | ||
T BdotH2; | ||
T one_minus_NdotH2_rcp; | ||
}; |
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 Microfacet caches already have getters for all the dotH
Do not conflate per-ray and per-material values together.
Each NDF needs to have its own parameter struct with basic members (e.g. roughness) and expands that to more members inside itself via create
if needed
Right now you've made a frankenstein that makes GGX keep BlinnPhong nx,ny exponents and Blinn Phong keep ax,ay roughnesses
Same with one_minus_NdotH2
only certain NDFs (seems Ashikhmin-shirley only) need it
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 dotH
can be rolled into a proto-microfacet cache structs
scalar_type operator()(SAnisotropicParams<scalar_type> params) | ||
{ | ||
scalar_type n = (params.TdotH2 * params.ny + params.BdotH2 * params.nx) * params.one_minus_NdotH2_rcp; | ||
return (isinf<scalar_type>(params.nx) || isinf<scalar_type>(params.ny)) ? numeric_limits<scalar_type>::infinity : |
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 enough to check that n
is "large enough" e.g. >254 because pow
with exponent higher than 254 is bound to make a lowest non denormalized number into an inf
}; | ||
|
||
|
||
template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>) |
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 actually need FloatingPointScalar
on all the NDF, surely you don't want them to be creatable with integers and booleans, right ?
using scalar_type = T; | ||
|
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 some static functions to convert phong exponent to roughness squared and back, according to Walter et al.
https://github.com/Devsh-Graphics-Programming/Nabla/blob/1314fb0d05d85c2df8400733e4ff5602746a06a0/include/nbl/builtin/hlsl/bxdf/ndf/blinn_phong.hlsl
sqrt<scalar_type>((params.nx + 2.0) * (params.ny + 2.0)) * numbers::inv_pi<scalar_type> * 0.5 * pow<scalar_type>(params.NdotH, n); | ||
} | ||
}; | ||
|
||
template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>) | ||
struct Beckmann | ||
{ | ||
using scalar_type = T; | ||
|
||
scalar_type operator()(SIsotropicParams<scalar_type> params) | ||
{ | ||
scalar_type nom = exp<scalar_type>( (params.NdotH2 - 1.0) / (params.n_or_a2 * params.NdotH2) ); // exp(x) == exp2(x/log(2)) ? | ||
scalar_type denom = params.n_or_a2 * params.NdotH2 * params.NdotH2; | ||
return numbers::inv_pi<scalar_type> * nom / denom; | ||
} | ||
|
||
scalar_type operator()(SAnisotropicParams<scalar_type> params) | ||
{ | ||
scalar_type nom = exp<scalar_type>(-(params.TdotH2 / params.ax2 + params.BdotH2 / params.ay2) / params.NdotH2); | ||
scalar_type denom = params.ax * params.ay * params.NdotH2 * params.NdotH2; | ||
return numbers::inv_pi<scalar_type> * nom / denom; | ||
} | ||
}; | ||
|
||
|
||
template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>) | ||
struct GGX | ||
{ | ||
using scalar_type = T; | ||
|
||
// trowbridge-reitz | ||
scalar_type operator()(SIsotropicParams<scalar_type> params) | ||
{ | ||
scalar_type denom = params.NdotH2 * (params.n_or_a2 - 1.0) + 1.0; |
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.
literals need scalar_type(1.0)
otherwise when scalar_type=double
you loose precision and with scalar_type=float16_t
you accidentally end up with float32_t
promotions and truncations for parts of the expression
// burley | ||
scalar_type operator()(SAnisotropicParams<scalar_type> params, scalar_type anisotropy) |
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 should have just been done with different creation parameters or a different NDF struct
} | ||
}; | ||
|
||
template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>) | ||
struct Beckmann | ||
{ | ||
using scalar_type = T; | ||
|
||
scalar_type operator()(SIsotropicParams<scalar_type> params) | ||
{ | ||
scalar_type nom = exp<scalar_type>( (params.NdotH2 - 1.0) / (params.n_or_a2 * params.NdotH2) ); // exp(x) == exp2(x/log(2)) ? | ||
scalar_type denom = params.n_or_a2 * params.NdotH2 * params.NdotH2; | ||
return numbers::inv_pi<scalar_type> * nom / denom; | ||
} | ||
|
||
scalar_type operator()(SAnisotropicParams<scalar_type> params) | ||
{ | ||
scalar_type nom = exp<scalar_type>(-(params.TdotH2 / params.ax2 + params.BdotH2 / params.ay2) / params.NdotH2); | ||
scalar_type denom = params.ax * params.ay * params.NdotH2 * params.NdotH2; | ||
return numbers::inv_pi<scalar_type> * nom / denom; | ||
} | ||
}; | ||
|
||
|
||
template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>) | ||
struct GGX | ||
{ | ||
using scalar_type = T; |
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 of the differing number of member parameters and implementations of operator()
for each NDF you must have a different struct (add a trait ndf::is_anisotropic_v
)
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.
example, Isotropic GGX only needs a2
while anisotropic needs ax2,ay2,a2
(created from ax,ay or ax2 and ay2)
|
||
scalar_type operator()(SIsotropicParams<scalar_type> params) | ||
{ | ||
scalar_type nom = exp<scalar_type>( (params.NdotH2 - 1.0) / (params.n_or_a2 * params.NdotH2) ); // exp(x) == exp2(x/log(2)) ? |
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 slap the log<scalar_type>(2.f)
into the a2*NdotH2
expression here
template<class T, class U> | ||
struct is_ggx : bool_constant< | ||
is_same<T, GGX<U> >::value | ||
> {}; | ||
} | ||
|
||
template<class T> | ||
struct is_ggx : impl::is_ggx<T, typename T::scalar_type> {}; | ||
|
||
template<typename T> | ||
NBL_CONSTEXPR bool is_ggx_v = is_ggx<T>::value; |
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.
could be a general trait, not hidden in impl::
// Copyright (C) 2018-2023 - 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_INCLUDED_ | ||
#define _NBL_BUILTIN_HLSL_BXDF_NDF_INCLUDED_ | ||
|
||
#include "nbl/builtin/hlsl/limits.hlsl" | ||
#include "nbl/builtin/hlsl/bxdf/common.hlsl" | ||
|
||
namespace nbl | ||
{ | ||
namespace hlsl | ||
{ | ||
namespace bxdf | ||
{ | ||
namespace ndf | ||
{ |
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 NDF "fixes" the Lambda and Beta functions so it makes sense to have an ndf(this_t::query_type)
(instead of just operator(this_t::query_type)
) and lambda()
methods instead of having the lambda buried somewhere else in anothre header
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.
actualy ndf
should be called D
to be consistent with that PBR papers (Cook Torrance)
then vndf is DG1
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.
So to recap:
- D (the ndf)
- Lambda
- Beta
- DG1
using this_t = GGXParams<LS, SI, MC, Scalar>; | ||
|
||
static this_t create(NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(SI) interaction, NBL_CONST_REF_ARG(MC) cache, BxDFClampMode _clamp) | ||
{ | ||
this_t retval; | ||
retval._sample = _sample; | ||
retval.interaction = interaction; | ||
retval.cache = cache; | ||
retval._clamp = _clamp; | ||
return retval; | ||
} |
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.
again, pointless constructor, conflation of material properties with query data
// iso | ||
Scalar getNdotV() NBL_CONST_MEMBER_FUNC { return hlsl::mix(math::conditionalAbsOrMax<Scalar>(_clamp == BxDFClampMode::BCM_ABS, interaction.getNdotV(), 0.0), interaction.getNdotV(), _clamp == BxDFClampMode::BCM_NONE); } | ||
Scalar getNdotVUnclamped() NBL_CONST_MEMBER_FUNC { return interaction.getNdotV(); } | ||
Scalar getNdotV2() NBL_CONST_MEMBER_FUNC { return interaction.getNdotV2(); } | ||
Scalar getNdotL() NBL_CONST_MEMBER_FUNC { return hlsl::mix(math::conditionalAbsOrMax<Scalar>(_clamp == BxDFClampMode::BCM_ABS, _sample.getNdotL(), 0.0), _sample.getNdotL(), _clamp == BxDFClampMode::BCM_NONE); } | ||
Scalar getNdotLUnclamped() NBL_CONST_MEMBER_FUNC { return _sample.getNdotL(); } | ||
Scalar getNdotL2() NBL_CONST_MEMBER_FUNC { return _sample.getNdotL2(); } | ||
Scalar getVdotL() NBL_CONST_MEMBER_FUNC { return _sample.getVdotL(); } | ||
Scalar getNdotH() NBL_CONST_MEMBER_FUNC { return cache.getNdotH(); } | ||
Scalar getNdotH2() NBL_CONST_MEMBER_FUNC { return cache.getNdotH2(); } | ||
Scalar getVdotH() NBL_CONST_MEMBER_FUNC { return cache.getVdotH(); } | ||
Scalar getLdotH() NBL_CONST_MEMBER_FUNC { return cache.getLdotH(); } | ||
|
||
LS _sample; | ||
SI interaction; | ||
MC cache; | ||
BxDFClampMode _clamp; | ||
}; | ||
template<class LS, class SI, class MC, typename Scalar> | ||
NBL_PARTIAL_REQ_TOP(surface_interactions::Anisotropic<SI> && AnisotropicMicrofacetCache<MC>) | ||
struct GGXParams<LS, SI, MC, Scalar NBL_PARTIAL_REQ_BOT(surface_interactions::Anisotropic<SI> && AnisotropicMicrofacetCache<MC>) > | ||
{ | ||
using this_t = GGXParams<LS, SI, MC, Scalar>; | ||
|
||
static this_t create(NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(SI) interaction, NBL_CONST_REF_ARG(MC) cache, BxDFClampMode _clamp) | ||
{ | ||
this_t retval; | ||
retval._sample = _sample; | ||
retval.interaction = interaction; | ||
retval.cache = cache; | ||
retval._clamp = _clamp; | ||
return retval; | ||
} | ||
|
||
// iso | ||
Scalar getNdotV() NBL_CONST_MEMBER_FUNC { return hlsl::mix(math::conditionalAbsOrMax<Scalar>(_clamp == BxDFClampMode::BCM_ABS, interaction.getNdotV(), 0.0), interaction.getNdotV(), _clamp == BxDFClampMode::BCM_NONE); } | ||
Scalar getNdotVUnclamped() NBL_CONST_MEMBER_FUNC { return interaction.getNdotV(); } | ||
Scalar getNdotV2() NBL_CONST_MEMBER_FUNC { return interaction.getNdotV2(); } | ||
Scalar getNdotL() NBL_CONST_MEMBER_FUNC { return hlsl::mix(math::conditionalAbsOrMax<Scalar>(_clamp == BxDFClampMode::BCM_ABS, _sample.getNdotL(), 0.0), _sample.getNdotL(), _clamp == BxDFClampMode::BCM_NONE); } | ||
Scalar getNdotLUnclamped() NBL_CONST_MEMBER_FUNC { return _sample.getNdotL(); } | ||
Scalar getNdotL2() NBL_CONST_MEMBER_FUNC { return _sample.getNdotL2(); } | ||
Scalar getVdotL() NBL_CONST_MEMBER_FUNC { return _sample.getVdotL(); } | ||
Scalar getNdotH() NBL_CONST_MEMBER_FUNC { return cache.getNdotH(); } | ||
Scalar getNdotH2() NBL_CONST_MEMBER_FUNC { return cache.getNdotH2(); } | ||
Scalar getVdotH() NBL_CONST_MEMBER_FUNC { return cache.getVdotH(); } | ||
Scalar getLdotH() NBL_CONST_MEMBER_FUNC { return cache.getLdotH(); } | ||
|
||
// aniso | ||
Scalar getTdotL2() NBL_CONST_MEMBER_FUNC { return _sample.getTdotL() * _sample.getTdotL(); } | ||
Scalar getBdotL2() NBL_CONST_MEMBER_FUNC { return _sample.getBdotL() * _sample.getBdotL(); } | ||
Scalar getTdotV2() NBL_CONST_MEMBER_FUNC { return interaction.getTdotV() * interaction.getTdotV(); } | ||
Scalar getBdotV2() NBL_CONST_MEMBER_FUNC { return interaction.getBdotV() * interaction.getBdotV(); } | ||
Scalar getTdotH2() NBL_CONST_MEMBER_FUNC {return cache.getTdotH() * cache.getTdotH(); } | ||
Scalar getBdotH2() NBL_CONST_MEMBER_FUNC {return cache.getBdotH() * cache.getBdotH(); } |
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.
we definitely need getXdotY(const BxDFClampMode)
on our interaction, sample and cache concepts and skip this insanity
vector2_type A; | ||
spectral_type ior0, ior1; |
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 need different BxDF or specializations for anisotropic and isotropic, different amounts of variables to precompute.
static this_t create(scalar_type NDFcos, scalar_type maxNdotV) | ||
{ | ||
this_t retval; | ||
retval.NDFcos = NDFcos; | ||
if (is_ggx_v<NDF>) | ||
retval.maxNdotL = maxNdotV; | ||
else | ||
retval.maxNdotV = maxNdotV; |
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 makes no sense to me when I'm reading it, why do we have NdotV
thats then being assigned to NdotL
when we have GGX
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.
I'm pretty sure this is wrong for GGX, you can't just take the dot product of the view vector and geometrical normal and use it as the dot product of view and geometrical
scalar_type operator()() | ||
{ | ||
if (is_ggx_v<NDF>) | ||
return NDFcos * maxNdotL; | ||
else | ||
return 0.25 * NDFcos / maxNdotV; | ||
} |
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.
write a comment that this computes the max(NdotL,0)/(4*max(NdotV,0)*max(NdotL,0))
factor which transforms PDFs in the f
in projected microfacet f * NdotH
measure to projected light measure f * NdotL
template<typename NDF> | ||
struct microfacet_to_light_measure_transform<NDF,REFLECT_BIT> | ||
{ | ||
using this_t = microfacet_to_light_measure_transform<NDF,REFLECT_BIT>; | ||
using scalar_type = typename NDF::scalar_type; | ||
|
||
static this_t create(scalar_type NDFcos, scalar_type maxNdotV) |
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 this doesn't need to be a struct, can be a plain old function (if you need partial specs, can be like the tgmath functions calling an impl::helper
)
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 right now you're constructing the structs immediately with create
and calling operator()
on them immediately, also there's literally no input argument you could cache for multiple calls
Description
Continuing #811 due to GH UI messing up diffs again
Testing
TODO list: