-
Notifications
You must be signed in to change notification settings - Fork 13
BxDF tests #165
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?
BxDF tests #165
Conversation
why do you have random things from sorakrit here instead of your own branch against his or I literally can't see whats yours and what's @keptsecret's. |
SBxDFTestResources retval; | ||
|
||
retval.rng = nbl::hlsl::Xoroshiro64Star::construct(seed); | ||
retval.u = float32_t3(rngFloat01(retval.rng), rngFloat01(retval.rng), 0.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.
the third random number is sometimes necessary to discern discrete BxDF branches:
- reflection vs refraction in e.g. glass
- diffuse vs specular in e.g. plastic
- choosing a leaf BxDF in a mixture/tree of BxDFs
ray_dir_info_t dV(int axis) | ||
{ | ||
float32_t3 d = (float32_t3)0.0; | ||
d[axis] += h; | ||
ray_dir_info_t retval; | ||
retval.direction = V.direction + d; | ||
return retval; | ||
} | ||
|
||
float h = 0.001; |
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.
- what do you need
dV
for? - this is not the way to vary
V
parameter
struct STestMeta | ||
{ | ||
float32_t4 result; | ||
#ifndef __HLSL_VERSION | ||
std::string bxdfName; | ||
std::string testName; | ||
#endif | ||
}; |
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.
returning a result
as a vec4 is quite a bit meh, I'd actually provide a callback (via static polymorphism) which is only called upon a failure
struct FailureCallbackConceptExample
{
void __call(NBL_CONST_REF_ARG(SBxDFTestResources) failedFor, NBL_CONST_REF_ARG(sample_t) failedAt);
};
then you can print in C++, mark booleans, append to vector, break & repeat call or whatever
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.
perfect C++ callback
printf(...your error message...);
for (volatile bool repeat=true; gDebuggerAttached&&repeat; )
{
repeat = false; // I can set to true with debugger and repeat as many times as I want till I find the cause
__debugbreak();
failedFor.test(failedAt);
}
@@ -78,6 +80,9 @@ struct SBxDFTestResources | |||
float32_t2 alpha; | |||
float eta; | |||
float32_t3x2 ior; | |||
|
|||
float32_t3 eta2; // what is this? |
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.
eta
squared, compute on the fly, don't leave stuff like this here
t.meta.result = t.test(); | ||
t.meta.testName = "u offset"; | ||
return t.meta; | ||
} |
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.
setting those thousands of times will be expensive, function should be void
and not return anything, callback instead of meta
static STestMeta run(uint32_t2 seed) | ||
{ | ||
this_t t; | ||
t.init(seed); |
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'd make the function take uint32_t
as an argument, then run a hash function (wide not deep)
https://rene.ruhr/gfx/gpuhash/
This will make it easy to invoke multiple run(uint32_t invocationID)
in parallel independently, be it on CPU with for
loop or GPU with gl_GlobalInvocationID.x
template<class BxDF, bool aniso = false> | ||
struct TestVOffset : TestBxDF<BxDF> | ||
{ | ||
using base_t = TestBase<BxDF>; | ||
using this_t = TestVOffset<BxDF, aniso>; | ||
|
||
float32_t4 test() | ||
{ | ||
sample_t s, sx, sy, sz; | ||
quotient_pdf_t pdf; | ||
float32_t3 brdf; | ||
aniso_cache cache, dummy; | ||
|
||
iso_interaction isointerx = iso_interaction::create(base_t::rc.dV(0), base_t::rc.N); | ||
aniso_interaction anisointerx = aniso_interaction::create(isointerx, base_t::rc.T, base_t::rc.B); | ||
iso_interaction isointery = iso_interaction::create(base_t::rc.dV(1), base_t::rc.N); | ||
aniso_interaction anisointery = aniso_interaction::create(isointery, base_t::rc.T, base_t::rc.B); | ||
iso_interaction isointerz = iso_interaction::create(base_t::rc.dV(2), base_t::rc.N); | ||
aniso_interaction anisointerz = aniso_interaction::create(isointerz, base_t::rc.T, base_t::rc.B); |
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 no point to this test existing, the test I've outlined ONLY makes sense for varying (differentiating) the 2D random number xi
, because thats the uniform distribution that must be stretched and squeezed to form the PDF of the importance sampler
66_HLSLBxDFTests/main.cpp
Outdated
|
||
#include "app_resources/tests.hlsl" | ||
|
||
#define ASSERT_ZERO(x) (assert(all<bool32_t4>((x.result) < float32_t4(1e-3)))); |
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 a nice callback
66_HLSLBxDFTests/main.cpp
Outdated
const uint32_t2 state = uint32_t2(12u, 69u); // (12u, 69u) | ||
|
||
// test u offset, 2 axis | ||
ASSERT_ZERO((TestUOffset<bxdf::reflection::SLambertianBxDF<sample_t, iso_interaction, aniso_interaction>>::run(state))); | ||
ASSERT_ZERO((TestUOffset<bxdf::reflection::SOrenNayarBxDF<sample_t, iso_interaction, aniso_interaction>>::run(state))); | ||
printResult(TestUOffset<bxdf::reflection::SBeckmannBxDF<sample_t, iso_cache, aniso_cache>,false>::run(state)); | ||
ASSERT_ZERO((TestUOffset<bxdf::reflection::SBeckmannBxDF<sample_t, iso_cache, aniso_cache>,true>::run(state))); | ||
printResult(TestUOffset<bxdf::reflection::SGGXBxDF<sample_t, iso_cache, aniso_cache>,false>::run(state)); | ||
ASSERT_ZERO((TestUOffset<bxdf::reflection::SGGXBxDF<sample_t, iso_cache, aniso_cache>,true>::run(state))); | ||
|
||
ASSERT_ZERO((TestUOffset<bxdf::transmission::SLambertianBxDF<sample_t, iso_interaction, aniso_interaction>>::run(state))); | ||
printResult(TestUOffset<bxdf::transmission::SBeckmannDielectricBxDF<sample_t, iso_cache, aniso_cache>,false>::run(state)); | ||
ASSERT_ZERO((TestUOffset<bxdf::transmission::SBeckmannDielectricBxDF<sample_t, iso_cache, aniso_cache>,true>::run(state))); | ||
printResult(TestUOffset<bxdf::transmission::SGGXDielectricBxDF<sample_t, iso_cache, aniso_cache>,false>::run(state)); | ||
printResult(TestUOffset<bxdf::transmission::SGGXDielectricBxDF<sample_t, iso_cache, aniso_cache>,true>::run(state)); |
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 to run the tests thousands of times with different seeds, I'd use for_each
over a ranges::iota_view
with par_unseq
policy to perform these tests on all CPU cores at once.
https://godbolt.org/z/xvxE7jzE3
this connects with https://github.com/Devsh-Graphics-Programming/Nabla-Examples-and-Tests/pull/165/files#r1906914911
if NBL_CONSTEXPR_FUNC (is_basic_brdf_v<BxDF>) | ||
{ | ||
s = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u.xy); | ||
sx = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u.xy + float32_t2(base_t::rc.h,0)); | ||
sy = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u.xy + float32_t2(0,base_t::rc.h)); | ||
} | ||
if NBL_CONSTEXPR_FUNC (is_microfacet_brdf_v<BxDF>) | ||
{ | ||
s = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u.xy, cache); | ||
sx = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u.xy + float32_t2(base_t::rc.h,0), dummy); | ||
sy = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u.xy + float32_t2(0,base_t::rc.h), dummy); | ||
} | ||
if NBL_CONSTEXPR_FUNC (is_basic_bsdf_v<BxDF>) | ||
{ | ||
s = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u); | ||
sx = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u + float32_t3(base_t::rc.h,0,0)); | ||
sy = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u + float32_t3(0,base_t::rc.h,0)); | ||
} | ||
if NBL_CONSTEXPR_FUNC (is_microfacet_bsdf_v<BxDF>) | ||
{ | ||
s = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u, cache); | ||
float32_t3 ux = base_t::rc.u + float32_t3(base_t::rc.h,0,0); | ||
sx = base_t::bxdf.generate(base_t::anisointer, ux, dummy); | ||
float32_t3 uy = base_t::rc.u + float32_t3(0,base_t::rc.h,0); | ||
sy = base_t::bxdf.generate(base_t::anisointer, uy, dummy); | ||
} |
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.
h
should be a parameter of each individual specialized BxDF test, because its specific to the differential test and also may need to change depending on the BxDF getting tested
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.
also maybe compute the perturbed u
above for readability
float32_t2x2 m = float32_t2x2(sx.TdotL - s.TdotL, sy.TdotL - s.TdotL, sx.BdotL - s.BdotL, sy.BdotL - s.BdotL); | ||
float det = nbl::hlsl::determinant<float32_t2x2>(m); | ||
|
||
return float32_t4(nbl::hlsl::abs<float32_t3>(pdf.value() - brdf), nbl::hlsl::abs<float>(det*pdf.pdf/s.NdotL) * 0.5); |
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 needs to be done better semantically with a callback, one thing for each individually.
Alos pdf.value()
does not equal brdf
in general!!!!!
P.s. the *0.5
thing on the PDF to Jacobian ratio was a thing for visualization, you should really have a callback which gets triggered if the value is too far from 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.
you need to skip checking anything that produces an "impossible" s
, so if eval
or quotient
is 0, you can skip the rest of the function (but not before you check pdf and quotient for 0 and INF respectively - the checks also check for NaN)
add more simple tests for:
pdf>0
because something you generated cannot have 0 probability of getting generatedquotient<INF
always because our importance sampler's job is to prevent that!- positivity, all
pdf
,quotient
, andeval
need to be>=0
- recprocity (eval must be equal if we swap L and V around, make a method/function to swap L and V around in
s
, the interaction, thebxdf
itself and thecache
)
Leave lots of comments.
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.
Also when pdf
is close to INF
(above some super high threshold) you need to back off the last test you have here
retval.V.direction = nbl::hlsl::normalize<float32_t3>(projected_hemisphere_generate<float>(rngUniformDist<float32_t2>(retval.rng))); | ||
retval.N = nbl::hlsl::normalize<float32_t3>(projected_hemisphere_generate<float>(rngUniformDist<float32_t2>(retval.rng))); |
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.
not projected hemisphere, just hemisphere.
Also not hemisphere but full sphere, because its good to test that BRDFs handle invalid (below hemisphere) correctly
…ng/Nabla-Examples-and-Tests into bxdf_unit_tests
…ng/Nabla-Examples-and-Tests into bxdf_unit_tests
Found it #188 |
|
||
// custom hash for float32_t2 | ||
template<> | ||
struct std::hash<float32_t2> | ||
{ | ||
size_t operator()(const float32_t2& v) const noexcept | ||
{ | ||
size_t v1 = std::hash<float32_t>()(v.x); | ||
size_t v2 = std::hash<float32_t>()(v.y); | ||
return v1 ^ (v2 << 1); | ||
} | ||
}; |
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.
make one in cpp_compat
under __HLSL_VERSION
note that we have our own hash_combine
which is very similar to GLM's https://github.com/g-truc/glm/blob/master/glm/gtx/hash.inl
You can fairly easily make hash_helper
which you can partial specialize for vectors and matrices of all types and dimensions
@@ -82,6 +105,46 @@ T rngUniformDist(NBL_REF_ARG(nbl::hlsl::Xoroshiro64Star) rng) | |||
return impl::RNGUniformDist<T>::__call(rng); |
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 already have an adapter for turning scalar rng into recursive, why are you using RNGUniformDist
?
return nbl::hlsl::all<vector<bool, vector_traits<T>::Dimension> >(nbl::hlsl::max<T>(a / b, b / a) <= (T)(1 + eps)); | ||
} | ||
|
||
template<typename T> | ||
bool checkLt(T a, T b) | ||
{ | ||
return nbl::hlsl::all<vector<bool, vector_traits<T>::Dimension> >(a < b); | ||
} | ||
|
||
template<typename T> | ||
bool checkZero(T a, float32_t eps) | ||
{ | ||
return nbl::hlsl::all<vector<bool, vector_traits<T>::Dimension> >(nbl::hlsl::abs<T>(a) < (T)eps); |
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 not a C-cast check it everywhere
#ifndef __HLSL_VERSION | ||
// because atan2 is not in tgmath.hlsl yet |
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.
atan2 should be there now
// takes in normalized vectors | ||
inline float32_t3 polarToCartesian(float32_t2 theta_phi) | ||
{ | ||
return float32_t3(std::cos(theta_phi.y) * std::cos(theta_phi.x), | ||
std::sin(theta_phi.y) * std::cos(theta_phi.x), | ||
std::sin(theta_phi.x)); | ||
} | ||
|
||
inline float32_t2 cartesianToPolar(float32_t3 coords) | ||
{ | ||
return float32_t2(std::acos(clamp<float>(coords.z, -1, 1)), std::atan2(coords.y, coords.x)); | ||
} |
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.
make these into general utilities in nbl/builtin/hlsl/math
template<typename T> | ||
bool checkEq(T a, T b, float32_t eps) | ||
{ | ||
return nbl::hlsl::all<vector<bool, vector_traits<T>::Dimension> >(nbl::hlsl::max<T>(a / b, b / a) <= (T)(1 + eps)); |
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 to take abs
of a and b, otherwise quotient of a and b of differing signs will always be negative, no matter the max
the result will be negative and always pass your inequality
float32_t3 tangent, bitangent; | ||
math::frisvad<float32_t3>(retval.N, tangent, bitangent); | ||
tangent = nbl::hlsl::normalize<float32_t3>(tangent); | ||
bitangent = nbl::hlsl::normalize<float32_t3>(bitangent); | ||
#ifndef __HLSL_VERSION | ||
const float angle = 2 * numbers::pi<float> * rngUniformDist<float>(retval.rng); |
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 2.f instead of 2 as the literal
@@ -113,8 +179,8 @@ struct SBxDFTestResources | |||
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.
eta is just a quotient of IoR, so it only makes sense to have one
furthermore you should generate a random eta between 0.4 and 2.5, but not too near 1.0
// epsilon | ||
float eps = 1e-3; | ||
float eps = 1e-3; // epsilon | ||
uint32_t state; // init state seed, for debugging |
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.
isn't the seed for xoroshiro 64bit ?
is_same<T, bxdf::transmission::SBeckmannDielectricBxDF<sample_t, iso_cache, aniso_cache, spectral_t>>::value || | ||
is_same<T, bxdf::transmission::SGGXDielectricBxDF<sample_t, iso_cache, aniso_cache, spectral_t>>::value | ||
is_same<T, bxdf::transmission::SBeckmannDielectricBxDF<sample_t, iso_interaction, aniso_interaction, iso_cache, aniso_cache, spectral_t>>::value || | ||
is_same<T, bxdf::transmission::SGGXDielectricBxDF<sample_t, iso_interaction, aniso_interaction, iso_cache, aniso_cache, spectral_t>>::value | ||
> {}; | ||
|
||
template<class 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.
there should be a general trait class in the nbl/builtin/hlsl/bxdf
which tells us these things
btw what do you mean by basic ? I think you're checking just NOT microfacet.
No description provided.