star-sf

Set of surface and volume scattering functions
git clone git://git.meso-star.fr/star-sf.git
Log | Files | Refs | README | LICENSE

commit f0533842d772e7f0502b067086d8d8dde803a098
parent 7a23d2203659d3a9d5cabd03dc92d4c3f35617cb
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Mon, 23 Jul 2018 14:18:07 +0200

Merge branch 'feature_phase' into develop

Diffstat:
Mcmake/CMakeLists.txt | 12++++++++++--
Msrc/ssf.h | 131++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/ssf_beckmann_distribution.c | 9+++------
Msrc/ssf_blinn_distribution.c | 11++++-------
Msrc/ssf_bsdf.c | 32++++++++++++++++++--------------
Msrc/ssf_fresnel.c | 24+++++++++++++-----------
Msrc/ssf_fresnel_dielectric_conductor.c | 25+++----------------------
Msrc/ssf_fresnel_dielectric_dielectric.c | 22++--------------------
Msrc/ssf_lambertian_reflection.c | 21++++-----------------
Msrc/ssf_microfacet_distribution.c | 34++++++++++++++++++----------------
Msrc/ssf_microfacet_reflection.c | 30++++++++++--------------------
Asrc/ssf_phase.c | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ssf_phase_c.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/ssf_phase_hg.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ssf_phase_rayleigh.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ssf_pillbox_distribution.c | 3++-
Msrc/ssf_specular_dielectric_dielectric_interface.c | 12++++++------
Msrc/ssf_specular_reflection.c | 16++++------------
Msrc/ssf_thin_specular_dielectric.c | 12++++++------
Msrc/test_ssf_bsdf.c | 27+++++++++++++++------------
Msrc/test_ssf_fresnel.c | 16++++++++--------
Msrc/test_ssf_lambertian_reflection.c | 7+++++++
Msrc/test_ssf_microfacet_distribution.c | 19+++++++++++--------
Msrc/test_ssf_microfacet_reflection.c | 45++++++++++++++++++++++++++++++++++++++++++---
Asrc/test_ssf_phase.c | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_ssf_phase_hg.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_ssf_phase_rayleigh.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_ssf_specular_dielectric_dielectric_reflection.c | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_ssf_specular_reflection.c | 7+++++++
Dsrc/test_ssf_specular_transmission.c | 103-------------------------------------------------------------------------------
Msrc/test_ssf_thin_specular_dielectric.c | 9+++++++++
Msrc/test_ssf_utils.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
32 files changed, 1312 insertions(+), 311 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -39,7 +39,7 @@ rcmake_append_runtime_dirs(_runtime_dirs RSys StarSP) # Define targets ################################################################################ set(VERSION_MAJOR 0) -set(VERSION_MINOR 5) +set(VERSION_MINOR 6) set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) @@ -49,7 +49,8 @@ set(SSF_FILES_INC ssf_bsdf_c.h ssf_fresnel_c.h ssf_microfacet_distribution_c.h - ssf_optics.h) + ssf_optics.h + ssf_phase_c.h) set(SSF_FILES_SRC ssf_beckmann_distribution.c ssf_blinn_distribution.c @@ -62,6 +63,9 @@ set(SSF_FILES_SRC ssf_lambertian_reflection.c ssf_microfacet_distribution.c ssf_microfacet_reflection.c + ssf_phase.c + ssf_phase_hg.c + ssf_phase_rayleigh.c ssf_pillbox_distribution.c ssf_specular_dielectric_dielectric_interface.c ssf_specular_reflection.c @@ -112,6 +116,10 @@ if(NOT NO_TEST) new_test(test_ssf_lambertian_reflection) new_test(test_ssf_microfacet_distribution) new_test(test_ssf_microfacet_reflection) + new_test(test_ssf_phase) + new_test(test_ssf_phase_hg) + new_test(test_ssf_phase_rayleigh) + new_test(test_ssf_specular_dielectric_dielectric_reflection) new_test(test_ssf_specular_reflection) new_test(test_ssf_thin_specular_dielectric) diff --git a/src/ssf.h b/src/ssf.h @@ -46,6 +46,7 @@ struct ssp_rng; struct ssf_bsdf; /* Bidirectional Scattering Distribution Function */ struct ssf_fresnel; /* Equation of the Fresnel term */ struct ssf_microfacet_distribution; +struct ssf_phase; /* Phase function */ enum ssf_bxdf_flag { SSF_REFLECTION = BIT(0), @@ -61,8 +62,8 @@ enum ssf_bxdf_flag { * consequence the reflected or refracted direction `wi' must point on the same * side or on the opposite side of `N', respectively. */ struct ssf_bsdf_type { - res_T (*init)(struct mem_allocator* allocator, void* bsdf); - void (*release)(void* bsdf); + res_T (*init)(struct mem_allocator* allocator, void* bsdf); /* Can be NULL */ + void (*release)(void* bsdf); /* Can be NULL */ /* Sample a direction `wi' wrt `wo' whose pdf is BSDF(wo, wi) |wi.n|. Return * the value of BSDF(wo, wi) */ @@ -73,10 +74,10 @@ struct ssf_bsdf_type { const double wo[3], /* Normalized outgoing direction */ const double N[3], /* Normalized surface normal */ double wi[3], /* Sampled normalized incoming direction */ - int* type, /* Type of the sampled component. Combination of ssf_bxdf_flag */ - double* pdf); /* PDF to sample wi wrt wo */ + int* type, /* Sampled component. Combination of ssf_bxdf_flag. Can be NULL */ + double* pdf); /* PDF to sample wi wrt wo. Can be NULL */ - /* Evaluate the BxDF wrt to wo and wi */ + /* Evaluate the BxDF wrt `wo' and `wi' */ double (*eval) (void* bsdf, @@ -93,15 +94,15 @@ struct ssf_bsdf_type { const double wi[3]);/* Normalized incoming direction */ size_t sizeof_bsdf; /* In Bytes */ - size_t alignof_bsdf; /* In Bytes */ + size_t alignof_bsdf; /* In Bytes. Must be a power of 2 */ }; #define SSF_BSDF_TYPE_NULL__ {NULL, NULL, NULL, NULL, NULL, 0, 1} static const struct ssf_bsdf_type SSF_BXDF_TYPE_NULL = SSF_BSDF_TYPE_NULL__; /* Generic Fresnel term descriptor */ struct ssf_fresnel_type { - res_T (*init)(struct mem_allocator* allocator, void* fresnel); - void (*release)(void* bxdf); + res_T (*init)(struct mem_allocator* allocator, void* fresnel); /* Can be NULL */ + void (*release)(void* bxdf); /* Can be NULL */ double (*eval) @@ -109,7 +110,7 @@ struct ssf_fresnel_type { const double cos_wi_N); /* Cosine between facet normal and incoming dir */ size_t sizeof_fresnel; /* In Bytes */ - size_t alignof_fresnel; /* In Bytes */ + size_t alignof_fresnel; /* In Bytes. Must be a power of 2 */ }; #define SSF_FRESNEL_TYPE_NULL__ {NULL, NULL, NULL, 0, 1} static const struct ssf_fresnel_type SSF_FRESNEL_TYPE_NULL = @@ -117,8 +118,8 @@ static const struct ssf_fresnel_type SSF_FRESNEL_TYPE_NULL = /* Generic descriptor of a microfacet distribution */ struct ssf_microfacet_distribution_type { - res_T (*init)(struct mem_allocator* allocator, void* distrib); - void (*release)(void* distrib); + res_T (*init)(struct mem_allocator* allocator, void* distrib); /* Can be NULL */ + void (*release)(void* distrib); /* Can be NULL */ void (*sample) @@ -126,7 +127,7 @@ struct ssf_microfacet_distribution_type { struct ssp_rng* rng, /* Random number generator */ const double N[3], /* Normalized Z-direction of the distribution */ double wh[3], /* Sampled normalized half vector */ - double* pdf); /* PDF to sample wh */ + double* pdf); /* PDF to sample wh. Can be NULL */ double (*eval) @@ -141,13 +142,49 @@ struct ssf_microfacet_distribution_type { const double wh[3]); /* Normalized half vector */ size_t sizeof_distribution; /* In Bytes */ - size_t alignof_distribution; /* In Bytes */ + size_t alignof_distribution; /* In Bytes. Must be a Power of 2 */ }; #define SSF_MICROFACET_DISTRIBUTION_TYPE_NULL__ \ {NULL, NULL, NULL, NULL, NULL, 0, 1} static const struct ssf_microfacet_distribution_type SSF_MICROFACET_DISTRIBUTION_TYPE_NULL = SSF_MICROFACET_DISTRIBUTION_TYPE_NULL__; +/* Generic phase function type descriptor. Note that by convention the outgoing + * direction `wo' and the incoming direction `wi' point *OUTWARD* the scattering + * point. The scattering angle is thus acos(-wo.wi) */ +struct ssf_phase_type { + res_T (*init)(struct mem_allocator* allocator, void* bsdf); /*Can be NULL*/ + void (*release)(void* bsdf); /* Can be NULL */ + + /* Sample a direction `wi' wrt `wo'. */ + void + (*sample) + (void* phase, + struct ssp_rng* rng, /* Random number generator */ + const double wo[3], /* Normalized outgoing direction */ + double wi[3], /* Sampled normalized incoming direction */ + double* pdf); /* PDF to sample wi wrt wo. Can be NULL */ + + /* Evaluate the phase function wrt `wo' and `wi' */ + double + (*eval) + (void* phase, + const double wo[3], /* Normalized outgoing direction */ + const double wi[3]); /* Normalized incoming direction */ + + /* Probability density function */ + double + (*pdf) + (void* phase, + const double wo[3], /* Normalized outgoing direction */ + const double wi[3]); /* Normalized incoming direction */ + + size_t sizeof_phase; /* In Bytes */ + size_t alignof_phase; /* In Bytes. Must be a power of 2 */ +}; +#define SSF_PHASE_TYPE_NULL__ {NULL,NULL,NULL,NULL,NULL,0,1} +static const struct ssf_phase_type SSF_PHASE_TYPE_NULL = SSF_PHASE_TYPE_NULL__; + BEGIN_DECLS /******************************************************************************* @@ -256,10 +293,21 @@ SSF_API const struct ssf_microfacet_distribution_type ssf_beckmann_distribution; SSF_API const struct ssf_microfacet_distribution_type ssf_blinn_distribution; /* Pillbox microfacet distribution. - * D(wh) = */ + * D(wh) = TODO comment this */ SSF_API const struct ssf_microfacet_distribution_type ssf_pillbox_distribution; /******************************************************************************* + * Built-in phase functions. + ******************************************************************************/ +/* Henyey & Greenstein phase function normalized to 1; p(wo, wi) == pdf(wo, wi). + * p(wo, wi) = 1/(4*PI)* (1-g^2) / (1+g^2-2*g*(-wo.wi))^3/2 */ +SSF_API const struct ssf_phase_type ssf_phase_hg; + +/* Rayleigh phase function normalized to 1; p(wo, wi) == pdf(wo, wi). + * p(wo, wi) = 3/(16*PI) * (1+(-wo.wi)^2) */ +SSF_API const struct ssf_phase_type ssf_phase_rayleigh; + +/******************************************************************************* * BSDF API - Bidirectional Scattering Distribution Function. Describes the way * the light is scattered by a surface. Note that by convention the outgoing * direction `wo' and the incoming direction `wi' point outward the surface. @@ -282,7 +330,7 @@ ssf_bsdf_ref_put (struct ssf_bsdf* bsdf); /* Sample a direction `wi' wrt `wo' whose pdf is BSDF(wo, wi) |wi.n|. Return - * the directionnal reflectance, i.e. pdf to be reflected in *any* direction + * the directional reflectance, i.e. pdf to be reflected in *any* direction * wrt to the incident direction `wi' */ SSF_API double ssf_bsdf_sample @@ -304,9 +352,9 @@ ssf_bsdf_eval SSF_API double /* Probability density function*/ ssf_bsdf_pdf (struct ssf_bsdf* bsdf, - const double wo[3], /* Normalized incoming direction */ + const double wo[3], /* Normalized outgoing direction */ const double N[3], /* Normalized surface normal */ - const double wi[3]); /* Normalized outgoing direction */ + const double wi[3]); /* Normalized incoming direction */ SSF_API res_T ssf_bsdf_get_data @@ -344,6 +392,55 @@ ssf_specular_dielectric_dielectric_interface_setup const double eta_t); /******************************************************************************* + * Phase function API - Describes the way the light is scattered in a medium. + * Note that by convention the outgoing direction `wo' and the incoming + * direction `wi' point outward the scattering position. + ******************************************************************************/ +SSF_API res_T +ssf_phase_create + (struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */ + const struct ssf_phase_type* type, + struct ssf_phase** phase); + +SSF_API res_T +ssf_phase_ref_get + (struct ssf_phase* phase); + +SSF_API res_T +ssf_phase_ref_put + (struct ssf_phase* phase); + +SSF_API void +ssf_phase_sample + (struct ssf_phase* phase, + struct ssp_rng* rng, /* Random number generator */ + const double wo[3], /* Normalized outgoing direction */ + double wi[3], /* Sampled normalized incoming direction */ + double* pdf); /* PDF to sample wi wrt wo */ + +SSF_API double +ssf_phase_eval + (struct ssf_phase* phase, + const double wo[3], /* Normalized outgoing direction */ + const double wi[3]); /* Normalized incoming direction */ + +SSF_API double +ssf_phase_pdf + (struct ssf_phase* phase, + const double wo[3], /* Normalized outgoing direction */ + const double wi[3]); /* Normalized incoming direction */ + +SSF_API res_T +ssf_phase_get_data + (struct ssf_phase* bsdf, + void** data); + +SSF_API res_T +ssf_phase_hg_setup + (struct ssf_phase* phase, + const double g); /* Asymmetric parameter */ + +/******************************************************************************* * Fresnel API - Define the equation of the fresnel term ******************************************************************************/ SSF_API res_T diff --git a/src/ssf_beckmann_distribution.c b/src/ssf_beckmann_distribution.c @@ -37,10 +37,6 @@ beckmann_distribution_init(struct mem_allocator* allocator, void* distrib) return RES_OK; } -static void -beckmann_distribution_release(void* distrib) -{ (void)distrib; } - static double beckmann_distribution_eval (void* distrib, const double N[3], const double wh[3]) @@ -96,7 +92,8 @@ beckmann_distribution_sample dir[1] = sin(phi) * sin_theta; dir[2] = cos_theta; d33_muld3(wh, d33_basis(basis, N), dir); - *pdf = exp(-sin2_theta/(cos2_theta*m2)) / (PI*m2*cos_theta*cos2_theta); + ASSERT(d3_dot(wh, N) > 0); + if(pdf) *pdf = exp(-sin2_theta/(cos2_theta*m2))/(PI*m2*cos_theta*cos2_theta); } static double @@ -121,7 +118,7 @@ beckmann_distribution_pdf ******************************************************************************/ const struct ssf_microfacet_distribution_type ssf_beckmann_distribution = { beckmann_distribution_init, - beckmann_distribution_release, + NULL, beckmann_distribution_sample, beckmann_distribution_eval, beckmann_distribution_pdf, diff --git a/src/ssf_blinn_distribution.c b/src/ssf_blinn_distribution.c @@ -37,10 +37,6 @@ blinn_distribution_init(struct mem_allocator* allocator, void* distrib) return RES_OK; } -static void -blinn_distribution_release(void* distrib) -{ (void)distrib; } - static double blinn_distribution_eval(void* distrib, const double N[3], const double wh[3]) { @@ -68,7 +64,7 @@ blinn_distribution_sample double cos_theta, sin_theta; double phi; double u, v; - ASSERT(distrib && rng && N && wh && pdf); + ASSERT(distrib && rng && N && wh); ASSERT(d3_is_normalized(N)); u = ssp_rng_canonical(rng); @@ -82,8 +78,9 @@ blinn_distribution_sample dir[2] = cos_theta; d33_muld3(dir, d33_basis(basis, N), dir); - *pdf = (blinn->exponent+2)/(2*PI)*pow(cos_theta, blinn->exponent+1); d3_set(wh, dir); + + if(pdf) *pdf = (blinn->exponent+2)/(2*PI)*pow(cos_theta, blinn->exponent+1); } static double @@ -102,7 +99,7 @@ blinn_distribution_pdf(void* distrib, const double N[3], const double wh[3]) ******************************************************************************/ const struct ssf_microfacet_distribution_type ssf_blinn_distribution = { blinn_distribution_init, - blinn_distribution_release, + NULL, blinn_distribution_sample, blinn_distribution_eval, blinn_distribution_pdf, diff --git a/src/ssf_bsdf.c b/src/ssf_bsdf.c @@ -29,8 +29,6 @@ static INLINE int check_bsdf_type(const struct ssf_bsdf_type* type) { return type - && type->init - && type->release && type->sample && type->eval && type->pdf @@ -43,7 +41,7 @@ bsdf_release(ref_T* ref) struct ssf_bsdf* bsdf = CONTAINER_OF(ref, struct ssf_bsdf, ref); ASSERT(ref); if(bsdf->data) { - bsdf->type.release(bsdf->data); + if(bsdf->type.release) bsdf->type.release(bsdf->data); MEM_RM(bsdf->allocator, bsdf->data); } MEM_RM(bsdf->allocator, bsdf); @@ -76,15 +74,19 @@ ssf_bsdf_create bsdf->allocator = mem_allocator; bsdf->type = *type; - bsdf->data = MEM_ALLOC_ALIGNED - (bsdf->allocator, bsdf->type.sizeof_bsdf, bsdf->type.alignof_bsdf); - if(!bsdf->data) { - res = RES_MEM_ERR; - goto error; + if(bsdf->type.sizeof_bsdf) { + bsdf->data = MEM_ALLOC_ALIGNED + (bsdf->allocator, bsdf->type.sizeof_bsdf, bsdf->type.alignof_bsdf); + if(!bsdf->data) { + res = RES_MEM_ERR; + goto error; + } + memset(bsdf->data, 0, bsdf->type.sizeof_bsdf); + if(bsdf->type.init) { + res = bsdf->type.init(mem_allocator, bsdf->data); + if(res != RES_OK) goto error; + } } - memset(bsdf->data, 0, bsdf->type.sizeof_bsdf); - res = bsdf->type.init(mem_allocator, bsdf->data); - if(res != RES_OK) goto error; exit: if(out_bsdf) *out_bsdf = bsdf; @@ -121,11 +123,13 @@ ssf_bsdf_sample const double N[3], double wi[3], int* type, - double* pdf) + double* out_pdf) { - ASSERT(bsdf && rng && wo && N && wi && type && pdf); + double R; + ASSERT(bsdf && rng && wo && N && wi); ASSERT(d3_is_normalized(wo) && d3_is_normalized(N)); - return bsdf->type.sample(bsdf->data, rng, wo, N, wi, type, pdf); + R = bsdf->type.sample(bsdf->data, rng, wo, N, wi, type, out_pdf); + return R; } double diff --git a/src/ssf_fresnel.c b/src/ssf_fresnel.c @@ -27,8 +27,6 @@ static INLINE int check_fresnel_type(const struct ssf_fresnel_type* type) { return type - && type->init - && type->release && type->eval && IS_POW2(type->alignof_fresnel); } @@ -39,7 +37,7 @@ fresnel_release(ref_T* ref) struct ssf_fresnel* fresnel = CONTAINER_OF(ref, struct ssf_fresnel, ref); ASSERT(ref); if(fresnel->data) { - fresnel->type.release(fresnel->data); + if(fresnel->type.release) fresnel->type.release(fresnel->data); MEM_RM(fresnel->allocator, fresnel->data); } MEM_RM(fresnel->allocator, fresnel); @@ -73,15 +71,19 @@ ssf_fresnel_create fresnel->allocator = mem_allocator; fresnel->type = *type; - fresnel->data = MEM_ALLOC_ALIGNED(fresnel->allocator, - fresnel->type.sizeof_fresnel, fresnel->type.alignof_fresnel); - if(!fresnel->data) { - res = RES_MEM_ERR; - goto error; + if(fresnel->type.sizeof_fresnel) { + fresnel->data = MEM_ALLOC_ALIGNED(fresnel->allocator, + fresnel->type.sizeof_fresnel, fresnel->type.alignof_fresnel); + if(!fresnel->data) { + res = RES_MEM_ERR; + goto error; + } + memset(fresnel->data, 0, fresnel->type.sizeof_fresnel); + if(fresnel->type.init) { + res = fresnel->type.init(fresnel->allocator, fresnel->data); + if(res != RES_OK) goto error; + } } - memset(fresnel->data, 0, fresnel->type.sizeof_fresnel); - res = fresnel->type.init(fresnel->allocator, fresnel->data); - if(res != RES_OK) goto error; exit: if(out_fresnel) *out_fresnel = fresnel; diff --git a/src/ssf_fresnel_dielectric_conductor.c b/src/ssf_fresnel_dielectric_conductor.c @@ -27,25 +27,6 @@ struct fresnel_dielectric_conductor { /******************************************************************************* * Private functions ******************************************************************************/ -static res_T -fresnel_dielectric_conductor_init - (struct mem_allocator* allocator, void* fresnel) -{ - struct fresnel_dielectric_conductor* fc = fresnel; - (void)allocator; - ASSERT(fresnel); - fc->eta_i = 0.0; - fc->eta_t = 0.0; - fc->eta_k_t = 0.0; - return RES_OK; -} - -static void -fresnel_dielectric_conductor_release(void* fresnel) -{ - (void)fresnel; -} - /* Code from * https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ */ static double @@ -85,8 +66,8 @@ fresnel_dielectric_conductor_eval(void* fresnel, const double cos_theta_i) * Exported symbols ******************************************************************************/ const struct ssf_fresnel_type ssf_fresnel_dielectric_conductor = { - fresnel_dielectric_conductor_init, - fresnel_dielectric_conductor_release, + NULL, + NULL, fresnel_dielectric_conductor_eval, sizeof(struct fresnel_dielectric_conductor), ALIGNOF(struct fresnel_dielectric_conductor) @@ -100,7 +81,7 @@ ssf_fresnel_dielectric_conductor_setup const double eta_k_t) { struct fresnel_dielectric_conductor* fc; - if(!fresnel + if(!fresnel || !FRESNEL_TYPE_EQ(&fresnel->type, &ssf_fresnel_dielectric_conductor)) { return RES_BAD_ARG; } diff --git a/src/ssf_fresnel_dielectric_dielectric.c b/src/ssf_fresnel_dielectric_dielectric.c @@ -26,24 +26,6 @@ struct fresnel_dielectric_dielectric { /******************************************************************************* * Private functions ******************************************************************************/ -static res_T -fresnel_dielectric_dielectric_init - (struct mem_allocator* allocator, void* fresnel) -{ - struct fresnel_dielectric_dielectric* fd = fresnel; - (void)allocator; - ASSERT(fresnel); - fd->eta_i = 0.0; - fd->eta_t = 0.0; - return RES_OK; -} - -static void -fresnel_dielectric_dielectric_release(void* fresnel) -{ - (void)fresnel; -} - static double fresnel_dielectric_dielectric_eval(void* fresnel, const double cos_theta_i) { @@ -81,8 +63,8 @@ fresnel_dielectric_dielectric_eval(void* fresnel, const double cos_theta_i) * Exported symbols ******************************************************************************/ const struct ssf_fresnel_type ssf_fresnel_dielectric_dielectric = { - fresnel_dielectric_dielectric_init, - fresnel_dielectric_dielectric_release, + NULL, + NULL, fresnel_dielectric_dielectric_eval, sizeof(struct fresnel_dielectric_dielectric), ALIGNOF(struct fresnel_dielectric_dielectric) diff --git a/src/ssf_lambertian_reflection.c b/src/ssf_lambertian_reflection.c @@ -28,19 +28,6 @@ struct lambertian_reflection { /******************************************************************************* * Private functions ******************************************************************************/ -static res_T -lambertian_reflection_init(struct mem_allocator* allocator, void* bsdf) -{ - ASSERT(bsdf); - (void)allocator; - ((struct lambertian_reflection*)bsdf)->reflectivity = 0.0; - return RES_OK; -} - -static void -lambertian_reflection_release(void* bsdf) -{ (void)bsdf; } - static double lambertian_reflection_eval (void* data, const double wo[3], const double N[3], const double wi[3]) @@ -64,13 +51,13 @@ lambertian_reflection_sample double* pdf) { double sample[3]; - ASSERT(data && rng && wo && N && wi && type && pdf); + ASSERT(data && rng && wo && N && wi); ASSERT(d3_is_normalized(wo) && d3_is_normalized(N) && d3_dot(wo, N) > 0); (void)wo; ssp_ran_hemisphere_cos(rng, N, sample, pdf); d3_set(wi, sample); - *type = SSF_REFLECTION | SSF_DIFFUSE; + if(type) *type = SSF_REFLECTION | SSF_DIFFUSE; return ((struct lambertian_reflection*)data)->reflectivity; } @@ -90,8 +77,8 @@ lambertian_reflection_pdf * Exorted symbols ******************************************************************************/ const struct ssf_bsdf_type ssf_lambertian_reflection = { - lambertian_reflection_init, - lambertian_reflection_release, + NULL, + NULL, lambertian_reflection_sample, lambertian_reflection_eval, lambertian_reflection_pdf, diff --git a/src/ssf_microfacet_distribution.c b/src/ssf_microfacet_distribution.c @@ -30,8 +30,6 @@ check_microfacet_distribution_type (const struct ssf_microfacet_distribution_type* type) { return type - && type->init - && type->release && type->sample && type->eval && type->pdf @@ -45,7 +43,7 @@ microfacet_distribution_release(ref_T* ref) CONTAINER_OF(ref, struct ssf_microfacet_distribution, ref); ASSERT(ref); if(distrib->data) { - distrib->type.release(distrib->data); + if(distrib->type.release) distrib->type.release(distrib->data); MEM_RM(distrib->allocator, distrib->data); } MEM_RM(distrib->allocator, distrib); @@ -79,17 +77,21 @@ ssf_microfacet_distribution_create distrib->allocator = mem_allocator; distrib->type = *type; - distrib->data = MEM_ALLOC_ALIGNED - (distrib->allocator, - distrib->type.sizeof_distribution, - distrib->type.alignof_distribution); - if(!distrib->data) { - res = RES_MEM_ERR; - goto error; + if(distrib->type.sizeof_distribution) { + distrib->data = MEM_ALLOC_ALIGNED + (distrib->allocator, + distrib->type.sizeof_distribution, + distrib->type.alignof_distribution); + if(!distrib->data) { + res = RES_MEM_ERR; + goto error; + } + memset(distrib->data, 0, distrib->type.sizeof_distribution); + if(distrib->type.init) { + res = distrib->type.init(mem_allocator, distrib->data); + if(res != RES_OK) goto error; + } } - memset(distrib->data, 0, distrib->type.sizeof_distribution); - res = distrib->type.init(mem_allocator, distrib->data); - if(res != RES_OK) goto error; exit: if(out_distrib) *out_distrib = distrib; @@ -124,11 +126,11 @@ ssf_microfacet_distribution_sample struct ssp_rng* rng, const double N[3], double wh[3], - double* pdf) + double* out_pdf) { - ASSERT(distrib && rng && N && wh && pdf); + ASSERT(distrib && rng && N && wh); ASSERT(d3_is_normalized(N)); - distrib->type.sample(distrib->data, rng, N, wh, pdf); + distrib->type.sample(distrib->data, rng, N, wh, out_pdf); } double diff --git a/src/ssf_microfacet_reflection.c b/src/ssf_microfacet_reflection.c @@ -27,17 +27,6 @@ struct microfacet_reflection { /******************************************************************************* * Microfacet functions ******************************************************************************/ -static res_T -microfacet_reflection_init(struct mem_allocator* allocator, void* data) -{ - struct microfacet_reflection* bsdf = data; - ASSERT(bsdf); - (void)allocator; - bsdf->fresnel = NULL; - bsdf->distrib = NULL; - return RES_OK; -} - static void microfacet_reflection_release(void* data) { @@ -94,14 +83,14 @@ microfacet_reflection_sample double wh[3]; double pdf_wh; double R; - ASSERT(data && wo && N && wi && pdf && type); + ASSERT(data && wo && N && wi); ASSERT(d3_is_normalized(wo) && d3_is_normalized(N) && d3_dot(wo, N) > 0); ASSERT(bsdf->distrib && bsdf->fresnel); ssf_microfacet_distribution_sample(bsdf->distrib, rng, N, wh, &pdf_wh); reflect(dir, wo, wh); - *pdf = pdf_wh / (4.0*fabs(d3_dot(wo, N))); - *type = SSF_REFLECTION | SSF_GLOSSY; + if(pdf) *pdf = pdf_wh / (4.0*fabs(d3_dot(wo, N))); + if(type) *type = SSF_REFLECTION | SSF_GLOSSY; R = d3_dot(dir, N) > 0 ? ssf_fresnel_eval(bsdf->fresnel, d3_dot(dir, wh)) : 0; d3_set(wi, dir); return R; @@ -116,9 +105,10 @@ microfacet_reflection_pdf double pdf_wh; ASSERT(data && wo && N && wi); ASSERT(d3_is_normalized(wo) && d3_is_normalized(N) && d3_is_normalized(wi)); - ASSERT(d3_dot(wo, N) > 0 && d3_dot(wi, N) > 0); + ASSERT(d3_dot(wo, N) > 0); ASSERT(bsdf->distrib); d3_normalize(wh, d3_add(wh, wi, wo)); + if(d3_dot(wh, N) < 0) d3_minus(wh, wh); pdf_wh = ssf_microfacet_distribution_pdf(bsdf->distrib, N, wh); return pdf_wh / (4.0*fabs(d3_dot(wo, N))); } @@ -141,7 +131,7 @@ microfacet2_reflection_sample double wh[3]; double p; double troughput = 1; - ASSERT(data && wo && N && wi && pdf && type); + ASSERT(data && wo && N && wi); ASSERT(d3_is_normalized(wo) && d3_is_normalized(N) && d3_dot(N, wo) > 0.0); ASSERT(bsdf->distrib); @@ -165,8 +155,8 @@ microfacet2_reflection_sample reflect(dir, dir, wh); } - *pdf = NaN; - *type = SSF_REFLECTION | SSF_GLOSSY; + if(pdf) *pdf = NaN; + if(type) *type = SSF_REFLECTION | SSF_GLOSSY; d3_set(wi, dir); return troughput; } @@ -191,7 +181,7 @@ microfacet2_reflection_pdf * Exported symbols ******************************************************************************/ const struct ssf_bsdf_type ssf_microfacet_reflection = { - microfacet_reflection_init, + NULL, microfacet_reflection_release, microfacet_reflection_sample, microfacet_reflection_eval, @@ -201,7 +191,7 @@ const struct ssf_bsdf_type ssf_microfacet_reflection = { }; const struct ssf_bsdf_type ssf_microfacet2_reflection = { - microfacet_reflection_init, + NULL, microfacet_reflection_release, microfacet2_reflection_sample, microfacet2_reflection_eval, diff --git a/src/ssf_phase.c b/src/ssf_phase.c @@ -0,0 +1,160 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "ssf.h" +#include "ssf_phase_c.h" + +#include <rsys/double3.h> +#include <rsys/math.h> +#include <rsys/mem_allocator.h> + +#include <string.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE int +check_phase_type(const struct ssf_phase_type* type) +{ + return type + && type->sample + && type->eval + && type->pdf + && IS_POW2(type->alignof_phase); +} + +static void +phase_release(ref_T* ref) +{ + struct ssf_phase* phase = CONTAINER_OF(ref, struct ssf_phase, ref); + ASSERT(ref); + if(phase->data) { + if(phase->type.release) phase->type.release(phase->data); + MEM_RM(phase->allocator, phase->data); + } + MEM_RM(phase->allocator, phase); +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +ssf_phase_create + (struct mem_allocator* allocator, + const struct ssf_phase_type* type, + struct ssf_phase** out_phase) +{ + struct mem_allocator* mem_allocator = NULL; + struct ssf_phase* phase = NULL; + res_T res = RES_OK; + + if(!out_phase || !check_phase_type(type)) { + res = RES_BAD_ARG; + goto error; + } + mem_allocator = allocator ? allocator : &mem_default_allocator; + phase = MEM_CALLOC(mem_allocator, 1, sizeof(struct ssf_phase)); + if(!phase) { + res = RES_MEM_ERR; + goto error; + } + ref_init(&phase->ref); + phase->allocator = mem_allocator; + phase->type = *type; + + if(phase->type.sizeof_phase) { + phase->data = MEM_ALLOC_ALIGNED + (phase->allocator, phase->type.sizeof_phase, phase->type.alignof_phase); + if(!phase->data) { + res = RES_MEM_ERR; + goto error; + } + memset(phase->data, 0, phase->type.sizeof_phase); + if(phase->type.init) { + res = phase->type.init(mem_allocator, phase->data); + if(res != RES_OK) goto error; + } + } + +exit: + if(out_phase) *out_phase = phase; + return res; +error: + if(phase) { + SSF(phase_ref_put(phase)); + phase = NULL; + } + goto exit; +} + +res_T +ssf_phase_ref_get(struct ssf_phase* phase) +{ + if(!phase) return RES_BAD_ARG; + ref_get(&phase->ref); + return RES_OK; +} + +res_T +ssf_phase_ref_put(struct ssf_phase* phase) +{ + if(!phase) return RES_BAD_ARG; + ref_put(&phase->ref, phase_release); + return RES_OK; +} + +void +ssf_phase_sample + (struct ssf_phase* phase, + struct ssp_rng* rng, + const double wo[3], + double wi[3], + double* out_pdf) +{ + ASSERT(phase && rng && wo && wi); + ASSERT(d3_is_normalized(wo)); + phase->type.sample(phase->data, rng, wo, wi, out_pdf); +} + +double +ssf_phase_eval + (struct ssf_phase* phase, + const double wo[3], + const double wi[3]) +{ + ASSERT(phase && wo && wi); + ASSERT(d3_is_normalized(wo) && d3_is_normalized(wi)); + return phase->type.eval(phase->data, wo, wi); +} + +double +ssf_phase_pdf + (struct ssf_phase* phase, + const double wo[3], + const double wi[3]) +{ + ASSERT(phase && wi && wo); + ASSERT(d3_is_normalized(wo) && d3_is_normalized(wi)); + return phase->type.pdf(phase->data, wo, wi); +} + +res_T +ssf_phase_get_data(struct ssf_phase* phase, void** data) +{ + if(!phase || !data) return RES_BAD_ARG; + *data = phase->data; + return RES_OK; +} + diff --git a/src/ssf_phase_c.h b/src/ssf_phase_c.h @@ -0,0 +1,43 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef SSF_PHASE_C_H +#define SSF_PHASE_C_H + +#include "ssf.h" +#include <rsys/ref_count.h> + +struct mem_allocator; + +struct ssf_phase { + struct ssf_phase_type type; + void* data; /* Specific internal data of the pĥase function */ + + /* Private data */ + ref_T ref; + struct mem_allocator* allocator; +}; + +#define PHASE_TYPE_EQ(A, B) \ + ( (A)->init == (B)->init \ + && (A)->release == (B)->release \ + && (A)->sample == (B)->sample \ + && (A)->eval == (B)->eval \ + && (A)->pdf == (B)->pdf \ + && (A)->sizeof_phase == (B)->sizeof_phase \ + && (A)->alignof_phase == (B)->alignof_phase) + +#endif /* SSF_PHASE_C_H */ + diff --git a/src/ssf_phase_hg.c b/src/ssf_phase_hg.c @@ -0,0 +1,113 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "ssf.h" +#include "ssf_phase_c.h" + +#include <rsys/double3.h> + +#include <star/ssp.h> + +struct hg { double g; }; + +/******************************************************************************* + * Private functions + ******************************************************************************/ +static res_T +hg_init(struct mem_allocator* allocator, void* phase) +{ + ASSERT(phase); + (void)allocator; + ((struct hg*)phase)->g = 0.0; + return RES_OK; +} + +static double +hg_pdf + (void* data, + const double wo[3], + const double wi[3]) +{ + double w[3]; + const struct hg* hg = data; + ASSERT(data && wo && wi); + return ssp_ran_sphere_hg_pdf(d3_minus(w, wo), hg->g, wi); +} + +/* HG(theta) = 1/(4*PI) * (1 - g^2) / (1 + g^2 - 2*g*cos(theta))^3/2 */ +static double +hg_eval(void* data, const double wo[3], const double wi[3]) +{ + const struct hg* hg = data; + double w[3]; + double g; + double cos_theta; + double denom; + double val; + ASSERT(data && wo && wi); + ASSERT(d3_is_normalized(wo) && d3_is_normalized(wi)); + + g = hg->g; + /* By convention wo points outward the scattering point. Revert it to point + * inward the scattering point in order to compute the cosine of the + * scattering angle */ + d3_minus(w, wo); + cos_theta = d3_dot(w, wi); + denom = 1 + g*g - 2*g*cos_theta; + ASSERT(denom != 0); + val = 1.0/(4.0*PI) * (1 - g*g) / (denom*sqrt(denom)); + ASSERT(eq_eps(val, hg_pdf(data, wo, wi), 1.e-6)); + return val; +} + +static void +hg_sample + (void* data, + struct ssp_rng* rng, + const double wo[3], + double wi[3], + double* pdf) +{ + const struct hg* hg = data; + double w[3]; + ASSERT(data && wo && wi); + + /* By convention wo points outward the scattering point. Revert it to point + * inward the scattering point as expected by the SSP library */ + ssp_ran_sphere_hg(rng, d3_minus(w, wo), hg->g, wi, pdf); +} + +/******************************************************************************* + * Exported symbols + ******************************************************************************/ +const struct ssf_phase_type ssf_phase_hg = { + hg_init, + NULL, + hg_sample, + hg_eval, + hg_pdf, + sizeof(struct hg), + ALIGNOF(struct hg) +}; + +res_T +ssf_phase_hg_setup(struct ssf_phase* phase, const double g) +{ + if(!phase || g < -1 || g > 1) return RES_BAD_ARG; + if(!PHASE_TYPE_EQ(&phase->type, &ssf_phase_hg)) return RES_BAD_ARG; + ((struct hg*)phase->data)->g = g; + return RES_OK; +} + diff --git a/src/ssf_phase_rayleigh.c b/src/ssf_phase_rayleigh.c @@ -0,0 +1,111 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#define _POSIX_C_SOURCE 200112L /* cbrt support */ + +#include "ssf.h" +#include "ssf_phase_c.h" + +#include <rsys/double3.h> +#include <star/ssp.h> +#include <math.h> + +/******************************************************************************* + * Private functions + ******************************************************************************/ +static res_T +rayleigh_init(struct mem_allocator* allocator, void* phase) +{ + (void)allocator, (void)phase; + return RES_OK; +} + +static void +rayleigh_release(void* phase) +{ (void)phase; } + + +/* Rayleigh(theta) = 3/(16*PI)*(1+cos(theta)^2) */ +static double +rayleigh_eval(void* data, const double wo[3], const double wi[3]) +{ + double cos_theta; + double w[3]; + ASSERT(wo && wi); + ASSERT(d3_is_normalized(wo) && d3_is_normalized(wi)); + (void)data; + /* By convention wo points outward the scattering point. Revert it to point + * inward the scattering point in order to compute the cosine of the + * scattering angle */ + d3_minus(w, wo); + cos_theta = d3_dot(w, wi); + return 3.0/(16.0*PI)*(1.0+cos_theta*cos_theta); +} + +static void +rayleigh_sample + (void* data, + struct ssp_rng* rng, + const double wo[3], + double wi[3], + double* pdf) +{ + double frame[9]; + double sample[3]; + double w[3]; + double phi, cos_theta, sin_theta; + double u; + double s; + ASSERT(rng && wo && wi); + (void)data; + + phi = ssp_rng_uniform_double(rng, 0, 2*PI); + + u = 4*ssp_rng_canonical(rng)-2; + s = cbrt(u + sqrt(1+u*u)); + cos_theta = s - 1.0/s; + sin_theta = cos2sin(cos_theta); + + sample[0] = cos(phi) * sin_theta; + sample[1] = sin(phi) * sin_theta; + sample[2] = cos_theta; + + /* By convention wo points outward the scattering point. Revert it to point + * inward the scattering point */ + d3_minus(w, wo); + d33_muld3(wi, d33_basis(frame, w), sample); + + if(pdf) *pdf = rayleigh_eval(data, wo, wi); +} + +static double +rayleigh_pdf(void* data, const double wo[3], const double wi[3]) +{ + return rayleigh_eval(data, wo, wi); +} + +/******************************************************************************* + * Exported symbols + ******************************************************************************/ +const struct ssf_phase_type ssf_phase_rayleigh = { + rayleigh_init, + rayleigh_release, + rayleigh_sample, + rayleigh_eval, + rayleigh_pdf, + 0, + 1 +}; + diff --git a/src/ssf_pillbox_distribution.c b/src/ssf_pillbox_distribution.c @@ -82,7 +82,8 @@ pillbox_distribution_sample dir[1] = sin(phi) * sin_theta; dir[2] = cos_theta; d33_muld3(wh, d33_basis(basis, N), dir); - *pdf = cos_theta / (PI-PI*cos2_theta_max); + + if(pdf) *pdf = cos_theta / (PI-PI*cos2_theta_max); } static double diff --git a/src/ssf_specular_dielectric_dielectric_interface.c b/src/ssf_specular_dielectric_dielectric_interface.c @@ -63,7 +63,7 @@ ssf_specular_dielectric_dielectric_interface_sample double cos_wo_N; double eta; /* Ratio of eta_i / eta_t */ double R; - ASSERT(bsdf && rng && wi && N && wo && type && pdf); + ASSERT(bsdf && rng && wi && N && wo); ASSERT(d3_is_normalized(wo) && d3_is_normalized(N)); ASSERT(d3_dot(wo, N) > -1.e-6); (void)rng; @@ -72,8 +72,8 @@ ssf_specular_dielectric_dielectric_interface_sample refracted = refract(wt, wo, N, eta); if(!refracted) { /* Total reflection */ reflect(wi, wo, N); - *pdf = INF; - *type = SSF_SPECULAR | SSF_REFLECTION; + if(pdf) *pdf = INF; + if(type) *type = SSF_SPECULAR | SSF_REFLECTION; return 1; } fresnel = bsdf->fresnel; @@ -82,15 +82,15 @@ ssf_specular_dielectric_dielectric_interface_sample SSF(fresnel_dielectric_dielectric_setup(fresnel, bsdf->eta_i, bsdf->eta_t)); R = ssf_fresnel_eval(fresnel, cos_wo_N); - *pdf = INF; + if(pdf) *pdf = INF; /* Sample the output direction wrt R */ if(ssp_rng_canonical(rng) < R) { reflect(wi, wo, N); - *type = SSF_SPECULAR | SSF_REFLECTION; + if(type) *type = SSF_SPECULAR | SSF_REFLECTION; } else { d3_set(wi, refracted); - *type = SSF_SPECULAR | SSF_TRANSMISSION; + if(type) *type = SSF_SPECULAR | SSF_TRANSMISSION; } return 1; } diff --git a/src/ssf_specular_reflection.c b/src/ssf_specular_reflection.c @@ -26,14 +26,6 @@ struct specular_reflection { /******************************************************************************* * Private functions ******************************************************************************/ -static res_T -specular_reflection_init(struct mem_allocator* allocator, void* bsdf) -{ - ASSERT(bsdf); - (void)allocator, (void)bsdf; - return RES_OK; -} - static void specular_reflection_release(void* data) { @@ -55,14 +47,14 @@ specular_reflection_sample struct specular_reflection* brdf = data; double cos_wo_N; double cos_wi_N; - ASSERT(rng && wi && N && wo && type && pdf); + ASSERT(rng && wi && N && wo); ASSERT(d3_is_normalized(wo) && d3_is_normalized(N) && d3_dot(wo, N) > 0); (void)rng; /* Reflect the outgoing direction wo with respect to the surface normal N */ reflect(wi, wo, N); - *pdf = INF; - *type = SSF_REFLECTION | SSF_SPECULAR; + if(pdf) *pdf = INF; + if(type) *type = SSF_REFLECTION | SSF_SPECULAR; cos_wi_N = cos_wo_N = d3_dot(wo, N); return ssf_fresnel_eval(brdf->fresnel, cos_wi_N); @@ -88,7 +80,7 @@ specular_reflection_pdf * Exported symbols ******************************************************************************/ const struct ssf_bsdf_type ssf_specular_reflection = { - specular_reflection_init, + NULL, specular_reflection_release, specular_reflection_sample, specular_reflection_eval, diff --git a/src/ssf_thin_specular_dielectric.c b/src/ssf_thin_specular_dielectric.c @@ -66,7 +66,7 @@ thin_specular_dielectric_sample double R, T; double rho1, rho2, rho2_sqr; double tau, tau_sqr; - ASSERT(bsdf && rng && wi && N && wo && type && pdf); + ASSERT(bsdf && rng && wi && N && wo); ASSERT(d3_is_normalized(wo) && d3_is_normalized(N)); ASSERT(d3_dot(wo, N) > -1.e-6); (void)rng; @@ -74,8 +74,8 @@ thin_specular_dielectric_sample eta = bsdf->eta_i / bsdf->eta_t; if(!refract(wt, wo, N, eta)) { /* Total reflection */ reflect(wi, wo, N); - *pdf = INF; - *type = SSF_SPECULAR | SSF_REFLECTION; + if(pdf) *pdf = INF; + if(type) *type = SSF_SPECULAR | SSF_REFLECTION; return 1; } fresnel = bsdf->fresnel; @@ -103,15 +103,15 @@ thin_specular_dielectric_sample } #endif - *pdf = INF; + if(pdf) *pdf = INF; /* Importance sample the BTDF wrt R */ if(ssp_rng_uniform_double(rng, 0, R + T) < R) { reflect(wi, wo, N); - *type = SSF_SPECULAR | SSF_REFLECTION; + if(type) *type = SSF_SPECULAR | SSF_REFLECTION; } else { /* Sample the transmissive part */ d3_minus(wi, wo); - *type = SSF_SPECULAR | SSF_TRANSMISSION; + if(type) *type = SSF_SPECULAR | SSF_TRANSMISSION; } if(bsdf->absorption == 0) { /* avoid numerical loss of energy if no absorption */ diff --git a/src/test_ssf_bsdf.c b/src/test_ssf_bsdf.c @@ -67,8 +67,8 @@ bsdf_sample CHK(d3_eq(BxDF->wo, wo) == 1); CHK(d3_eq(BxDF->N, N) == 1); d3(wi, 1.0, 2.0, 3.0); - *type = 314; - *pdf = 4; + if(type) *type = 314; + if(pdf) *pdf = 4; return BxDF->reflectivity; } @@ -114,6 +114,7 @@ main(int argc, char** argv) struct bsdf* data; struct ssf_bsdf* bsdf; struct ssf_bsdf_type type = SSF_BXDF_TYPE_NULL; + struct ssf_bsdf_type type2 = bsdf_dummy; double wo[3]; double N[3]; double wi[4]; @@ -156,14 +157,14 @@ main(int argc, char** argv) CHK(ssf_bsdf_ref_put(bsdf) == RES_OK); CHK(bsdf_is_init == 0); - type.init = NULL; - CHK(ssf_bsdf_create(&allocator, &type, &bsdf) == RES_BAD_ARG); - CHK(bsdf_is_init == 0); - type.init = bsdf_init; - type.release = NULL; - CHK(ssf_bsdf_create(&allocator, &type, &bsdf) == RES_BAD_ARG); - CHK(bsdf_is_init == 0); - type.release = bsdf_release; + type2.init = NULL; + CHK(ssf_bsdf_create(&allocator, &type2, &bsdf) == RES_OK); + CHK(ssf_bsdf_ref_put(bsdf) == RES_OK); + type2.init = bsdf_dummy.init; + type2.release = NULL; + CHK(ssf_bsdf_create(&allocator, &type2, &bsdf) == RES_OK); + CHK(ssf_bsdf_ref_put(bsdf) == RES_OK); + type.sample = NULL; CHK(ssf_bsdf_create(&allocator, &type, &bsdf) == RES_BAD_ARG); CHK(bsdf_is_init == 0); @@ -189,10 +190,12 @@ main(int argc, char** argv) data->rng = rng; data->reflectivity = 0.1234; - CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, &i, &pdf) == 0.1234); - CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, &i, &pdf) == 0.1234); + i = 0, pdf = 0; + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, &i, NULL) == 0.1234); CHK(i == 314); + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, &pdf) == 0.1234); CHK(pdf == 4); + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, NULL) == 0.1234); data->reflectivity = 0.314; CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, &i, &pdf) == 0.314); diff --git a/src/test_ssf_fresnel.c b/src/test_ssf_fresnel.c @@ -60,6 +60,7 @@ main(int argc, char** argv) struct fresnel* data; struct ssf_fresnel* fresnel; struct ssf_fresnel_type type = SSF_FRESNEL_TYPE_NULL; + struct ssf_fresnel_type type2 = fresnel_dummy; (void)argc, (void)argv; mem_init_proxy_allocator(&allocator, &mem_default_allocator); @@ -96,14 +97,13 @@ main(int argc, char** argv) CHK(ssf_fresnel_ref_put(fresnel) == RES_OK); CHK(fresnel_is_init == 0); - type.init = NULL; - CHK(ssf_fresnel_create(&allocator, &type, &fresnel) == RES_BAD_ARG); - CHK(fresnel_is_init == 0); - type.init = fresnel_init; - type.release = NULL; - CHK(ssf_fresnel_create(&allocator, &type, &fresnel) == RES_BAD_ARG); - CHK(fresnel_is_init == 0); - type.release = fresnel_release; + type2.init = NULL; + CHK(ssf_fresnel_create(&allocator, &type2, &fresnel) == RES_OK); + CHK(ssf_fresnel_ref_put(fresnel) == RES_OK); + type2.init = fresnel_dummy.init; + CHK(ssf_fresnel_create(&allocator, &type2, &fresnel) == RES_OK); + CHK(ssf_fresnel_ref_put(fresnel) == RES_OK); + type.eval = NULL; CHK(ssf_fresnel_create(&allocator, &type, &fresnel) == RES_BAD_ARG); CHK(fresnel_is_init == 0); diff --git a/src/test_ssf_lambertian_reflection.c b/src/test_ssf_lambertian_reflection.c @@ -66,6 +66,13 @@ main(int argc, char** argv) CHK(eq_eps(pdf, d3_dot(wi, N)/PI, 1.e-6) == 1); CHK(type == (SSF_DIFFUSE|SSF_REFLECTION)); + pdf = 0, type = 0; + CHK(ssf_bsdf_sample(brdf, rng, wo, N, wi, NULL, &pdf) == R); + CHK(eq_eps(pdf, d3_dot(wi, N)/PI, 1.e-6) == 1); + CHK(ssf_bsdf_sample(brdf, rng, wo, N, wi, &type, NULL) == R); + CHK(type == (SSF_DIFFUSE|SSF_REFLECTION)); + CHK(ssf_bsdf_sample(brdf, rng, wo, N, wi, NULL, NULL) == R); + d3_normalize(wo, d3(wo, 1.0, 0.0, 1.0)); N[0] = ssp_rng_uniform_double(rng, -1, 1); N[1] = ssp_rng_uniform_double(rng, -1, 1); diff --git a/src/test_ssf_microfacet_distribution.c b/src/test_ssf_microfacet_distribution.c @@ -60,11 +60,10 @@ ufacet_sample CHK(ufacet != NULL); CHK(N != NULL); CHK(wh != NULL); - CHK(pdf != NULL); CHK(ufacet->rng == rng); CHK(d3_eq(ufacet->N, N) == 1); d3_normalize(wh, d3(wh, 1.0, 2.0, 3.0)); - *pdf = ufacet->pdf; + if(pdf) *pdf = ufacet->pdf; } static double @@ -103,6 +102,7 @@ main(int argc, char** argv) struct ufacet* data; struct ssf_microfacet_distribution_type type = SSF_MICROFACET_DISTRIBUTION_TYPE_NULL; + struct ssf_microfacet_distribution_type type2 = microfacet_dummy; struct ssf_microfacet_distribution* distrib; struct ssp_rng* rng; double N[3], wh[3], pdf; @@ -144,12 +144,14 @@ main(int argc, char** argv) CHK(ssf_microfacet_distribution_ref_put(distrib) == RES_OK); CHK(ufacet_is_init == 0); - type.init = NULL; - CHK(CREATE(&allocator, &type, &distrib) == RES_BAD_ARG); - type.init = ufacet_init; - type.release = NULL; - CHK(CREATE(&allocator, &type, &distrib) == RES_BAD_ARG); - type.release = ufacet_release; + type2.init = NULL; + CHK(CREATE(&allocator, &type2, &distrib) == RES_OK); + CHK(ssf_microfacet_distribution_ref_put(distrib) == RES_OK); + type2.init = microfacet_dummy.init; + type2.release = NULL; + CHK(CREATE(&allocator, &type2, &distrib) == RES_OK); + CHK(ssf_microfacet_distribution_ref_put(distrib) == RES_OK); + type.sample = NULL; CHK(CREATE(&allocator, &type, &distrib) == RES_BAD_ARG); type.sample = ufacet_sample; @@ -179,6 +181,7 @@ main(int argc, char** argv) data->pdf = 0.1234; data->value = 0.43; + ssf_microfacet_distribution_sample(distrib, rng, N, wh, NULL); ssf_microfacet_distribution_sample(distrib, rng, N, wh, &pdf); CHK(d3_eq(wh, data->wh) == 1); CHK(pdf == data->pdf); diff --git a/src/test_ssf_microfacet_reflection.c b/src/test_ssf_microfacet_reflection.c @@ -30,7 +30,11 @@ main(int argc, char** argv) struct ssp_rng* rng; double N[3]; double wo[3]; - size_t i, NSTEPS=100000; + double wi[3]; + double pdf; + double R; + size_t i, NSTEPS=10000; + int type; (void)argc, (void)argv; mem_init_proxy_allocator(&allocator, &mem_default_allocator); @@ -56,11 +60,46 @@ main(int argc, char** argv) ssp_ran_sphere_uniform(rng, N, NULL); ssp_ran_hemisphere_cos(rng, N, wo, NULL); FOR_EACH(i, 0, NSTEPS) { - double wi[3], pdf; - int type; CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, &pdf) == 1); CHK(type == (SSF_GLOSSY | SSF_REFLECTION)); + CHK(IS_NaN(pdf)); CHK(d3_dot(wi, N) > 0); + + pdf = 0, type = 0; + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, &pdf) == 1); + CHK(IS_NaN(pdf)); + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, NULL) == 1); + CHK(type == (SSF_GLOSSY | SSF_REFLECTION)); + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, NULL) == 1); + } + + CHK(ssf_bsdf_ref_put(bsdf) == RES_OK); + CHK(ssf_bsdf_create(&allocator, &ssf_microfacet_reflection, &bsdf) == RES_OK); + + CHK(ssf_microfacet_reflection_setup(NULL, NULL, NULL) == RES_BAD_ARG); + CHK(ssf_microfacet_reflection_setup(bsdf, NULL, NULL) == RES_BAD_ARG); + CHK(ssf_microfacet_reflection_setup(NULL, F, NULL) == RES_BAD_ARG); + CHK(ssf_microfacet_reflection_setup(bsdf, F, NULL) == RES_BAD_ARG); + CHK(ssf_microfacet_reflection_setup(NULL, NULL, D) == RES_BAD_ARG); + CHK(ssf_microfacet_reflection_setup(bsdf, NULL, D) == RES_BAD_ARG); + CHK(ssf_microfacet_reflection_setup(NULL, F, D) == RES_BAD_ARG); + CHK(ssf_microfacet_reflection_setup(bsdf, F, D) == RES_OK); + + FOR_EACH(i, 0, NSTEPS) { + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, &pdf); + CHK(R == (d3_dot(wi, N) > 0 ? 1 : 0)); + CHK(type == (SSF_GLOSSY | SSF_REFLECTION)); + CHK(eq_eps(pdf, ssf_bsdf_pdf(bsdf, wo, N, wi), 1.e-6)); + + pdf = 0, type = 0; + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, &pdf); + CHK(R == (d3_dot(wi, N) > 0 ? 1 : 0)); + CHK(eq_eps(pdf, ssf_bsdf_pdf(bsdf, wo, N, wi), 1.e-6)); + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, NULL); + CHK(R == (d3_dot(wi, N) > 0 ? 1 : 0)); + CHK(type == (SSF_GLOSSY | SSF_REFLECTION)); + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, NULL); + CHK(R == (d3_dot(wi, N) > 0 ? 1 : 0)); } CHK(ssf_bsdf_ref_put(bsdf) == RES_OK); diff --git a/src/test_ssf_phase.c b/src/test_ssf_phase.c @@ -0,0 +1,189 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "ssf.h" +#include "test_ssf_utils.h" + +static int phase_is_init = 0; + +struct ALIGN(64) phase { + uint32_t id; + struct ssp_rng* rng; + double wi[3]; + double wo[3]; + double pdf; + double value; +}; + +static res_T +phase_init(struct mem_allocator* allocator, void* phase) +{ + CHK(allocator != NULL); + CHK(phase != NULL); + CHK(IS_ALIGNED(phase, 64) == 1); + ((struct phase*)phase)->id = 0xDECAFBAD; + phase_is_init = 1; + return RES_OK; +} + +static void +phase_release(void* phase) +{ + CHK(phase != NULL); + CHK(((struct phase*)phase)->id == 0xDECAFBAD); + phase_is_init = 0; +} + +static void +phase_sample + (void* data, + struct ssp_rng* rng, + const double wo[3], + double wi[3], + double* pdf) +{ + struct phase* phase = data; + CHK(phase != NULL); + CHK(phase->rng == rng); + CHK(d3_eq(phase->wo, wo) == 1); + d3(wi, 1.0, 2.0, 3.0); + if(pdf) *pdf = phase->pdf; +} + +static double +phase_eval + (void* data, + const double wo[3], + const double wi[3]) +{ + struct phase* phase = data; + CHK(phase != NULL); + CHK(wi != NULL); + CHK(wo != NULL); + CHK(d3_eq(phase->wo, wo) == 1); + CHK(d3_eq(phase->wi, wi) == 1); + return phase->value; +} + +static double +phase_pdf + (void* data, + const double wo[3], + const double wi[3]) +{ + struct phase* phase = data; + CHK(phase != NULL); + CHK(wi != NULL); + CHK(wo != NULL); + CHK(d3_eq(phase->wo, wo) == 1); + CHK(d3_eq(phase->wi, wi) == 1); + return phase->pdf; +} + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct ssp_rng* rng; + struct ssf_phase* phase = NULL; + struct ssf_phase_type type = SSF_PHASE_TYPE_NULL; + struct ssf_phase_type type2 = phase_dummy; + struct phase* data = NULL; + double wi[3]; + double pdf; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + CHK(ssp_rng_create(&allocator, &ssp_rng_threefry, &rng) == RES_OK); + + type.init = phase_init; + type.release = phase_release; + type.sample = phase_sample; + type.eval = phase_eval; + type.pdf = phase_pdf; + type.sizeof_phase = sizeof(struct phase); + type.alignof_phase = ALIGNOF(struct phase); + + CHK(ssf_phase_create(NULL, NULL, NULL) == RES_BAD_ARG); + CHK(ssf_phase_create(&allocator, NULL, NULL) == RES_BAD_ARG); + CHK(ssf_phase_create(NULL, &type, NULL) == RES_BAD_ARG); + CHK(ssf_phase_create(&allocator, &type, NULL) == RES_BAD_ARG); + CHK(ssf_phase_create(NULL, NULL, &phase) == RES_BAD_ARG); + CHK(ssf_phase_create(&allocator, NULL, &phase) == RES_BAD_ARG); + + CHK(phase_is_init == 0); + CHK(ssf_phase_create(NULL, &type, &phase) == RES_OK); + CHK(phase_is_init == 1); + + CHK(ssf_phase_ref_get(NULL) == RES_BAD_ARG); + CHK(ssf_phase_ref_get(phase) == RES_OK); + CHK(ssf_phase_ref_put(NULL) == RES_BAD_ARG); + CHK(ssf_phase_ref_put(phase) == RES_OK); + CHK(phase_is_init == 1); + CHK(ssf_phase_ref_put(phase) == RES_OK); + CHK(phase_is_init == 0); + + type2.init = NULL; + CHK(ssf_phase_create(&allocator, &type2, &phase) == RES_OK); + CHK(ssf_phase_ref_put(phase) == RES_OK); + type2.init = phase_dummy.init; + type2.release = NULL; + CHK(ssf_phase_create(&allocator, &type2, &phase) == RES_OK); + CHK(ssf_phase_ref_put(phase) == RES_OK); + + type.sample = NULL; + CHK(ssf_phase_create(&allocator, &type, &phase) == RES_BAD_ARG); + CHK(phase_is_init == 0); + type.sample = phase_sample; + type.alignof_phase = 3; + CHK(ssf_phase_create(&allocator, &type, &phase) == RES_BAD_ARG); + CHK(phase_is_init == 0); + type.alignof_phase = ALIGNOF(struct phase); + CHK(ssf_phase_create(&allocator, &type, &phase) == RES_OK); + CHK(phase_is_init == 1); + + CHK(ssf_phase_get_data(NULL, NULL) == RES_BAD_ARG); + CHK(ssf_phase_get_data(phase, NULL) == RES_BAD_ARG); + CHK(ssf_phase_get_data(NULL, (void**)&data) == RES_BAD_ARG); + CHK(ssf_phase_get_data(phase, (void**)&data) == RES_OK); + + CHK(data->id == 0xDECAFBAD); + + d3_normalize(data->wo, d3(data->wo, -1, -1, 0)); + data->rng = rng; + data->value = 0.1234; + data->pdf = 4; + + pdf = 0; + ssf_phase_sample(phase, rng, data->wo, wi, NULL); + ssf_phase_sample(phase, rng, data->wo, wi, &pdf); + CHK(pdf == 4); + + d3_normalize(data->wi, wi); + data->value = 0.4567; + CHK(ssf_phase_eval(phase, data->wo, data->wi) == data->value); + data->pdf = 0.890; + CHK(ssf_phase_pdf(phase, data->wo, data->wi) == data->pdf); + + CHK(ssf_phase_ref_put(phase) == RES_OK); + + CHK(ssp_rng_ref_put(rng) == RES_OK); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} + diff --git a/src/test_ssf_phase_hg.c b/src/test_ssf_phase_hg.c @@ -0,0 +1,98 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "ssf.h" +#include "test_ssf_utils.h" + +#define NG 10 +#define NSAMPS 10000 + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct ssf_phase* phase; + struct ssf_phase* dummy; + struct ssp_rng* rng; + double wo[3]; + double wi[3]; + size_t i; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + CHK(ssp_rng_create(&allocator, &ssp_rng_mt19937_64, &rng) == RES_OK); + + CHK(ssf_phase_create(&allocator, &ssf_phase_hg, &phase) == RES_OK); + CHK(ssf_phase_create(&allocator, &phase_dummy, &dummy) == RES_OK); + + CHK(ssf_phase_hg_setup(NULL, -2) == RES_BAD_ARG); + CHK(ssf_phase_hg_setup(phase, -2) == RES_BAD_ARG); + CHK(ssf_phase_hg_setup(NULL, -1) == RES_BAD_ARG); + CHK(ssf_phase_hg_setup(phase, -1) == RES_OK); + CHK(ssf_phase_hg_setup(phase, 0) == RES_OK); + CHK(ssf_phase_hg_setup(phase, 1) == RES_OK); + CHK(ssf_phase_hg_setup(dummy, 1) == RES_BAD_ARG); + + FOR_EACH(i, 0, NG) { + const double g = ssp_rng_uniform_double(rng, -1, +1); + double sum_cos = 0; + double sum_cos_sqr = 0; + double E, V, SE; + size_t isamp; + + CHK(ssf_phase_hg_setup(phase, g) == RES_OK); + + ssp_ran_sphere_uniform(rng, wo, NULL); + FOR_EACH(isamp, 0, NSAMPS) { + double w[3]; + double weight; + double pdf; + double r; + double ref; + ssf_phase_sample(phase, rng, wo, wi, &pdf); + CHK(d3_is_normalized(wi)); + CHK(eq_eps(ssf_phase_pdf(phase, wo, wi), pdf, 1.e-6)); + d3_minus(w, wo); /* Match the convention */ + weight = d3_dot(w, wi); + sum_cos += weight; + sum_cos_sqr += weight*weight; + + /* HG(theta) = 1/(4*PI) * (1 - g^2) / (1 + g^2 - 2*g*cos(theta))^3/2 */ + r = ssf_phase_eval(phase, wo, wi); + ref = 1/(4*PI) * (1-g*g) / pow(1+g*g-2*g*d3_dot(w,wi), 1.5); + CHK(eq_eps(r, ref, 1.e-6)); + CHK(eq_eps(r, pdf, 1.e-6)); + } + /* On average cos(-wo,wi) should be g */ + E = sum_cos / NSAMPS; + V = sum_cos_sqr / NSAMPS - E*E; + SE = sqrt(V/NSAMPS); + CHK(eq_eps(E, g, 3*SE)); + } + + ssp_ran_sphere_uniform(rng, wo, NULL); + ssf_phase_sample(phase, rng, wo, wi, NULL); + CHK(d3_is_normalized(wi)); + + CHK(ssf_phase_ref_put(phase) == RES_OK); + CHK(ssf_phase_ref_put(dummy) == RES_OK); + CHK(ssp_rng_ref_put(rng) == RES_OK); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} + diff --git a/src/test_ssf_phase_rayleigh.c b/src/test_ssf_phase_rayleigh.c @@ -0,0 +1,85 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "ssf.h" +#include "test_ssf_utils.h" + +#define NSAMPS 10000 + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct ssf_phase* phase; + struct ssp_rng* rng; + double wo[3]; + double wi[3]; + double sum_cos = 0; + double sum_cos_sqr = 0; + double sum_cos_sqr2 = 0; + double E, V, SE; + size_t i; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + CHK(ssp_rng_create(&allocator, &ssp_rng_mt19937_64, &rng) == RES_OK); + + CHK(ssf_phase_create(&allocator, &ssf_phase_rayleigh, &phase) == RES_OK); + + ssp_ran_sphere_uniform(rng, wo, NULL); + FOR_EACH(i, 0, NSAMPS) { + double w[3]; + double weight; + double pdf; + double ref; + double r; + ssf_phase_sample(phase, rng, wo, wi, &pdf); + CHK(d3_is_normalized(wi)); + CHK(eq_eps(ssf_phase_pdf(phase, wo, wi), pdf, 1.e-6f)); + d3_minus(w, wo); + weight = d3_dot(wo, wi); + sum_cos += weight; + sum_cos_sqr += weight*weight; + sum_cos_sqr2 += weight*weight*weight*weight; + + r = ssf_phase_eval(phase, wo, wi); + ref = 3.0/(16.0*PI)*(1.0+weight*weight); + CHK(eq_eps(r, ref, 1.e-6)); + CHK(eq_eps(r, pdf, 1.e-6)); + } + + /* On average cos(-wo,wi) should be 0 */ + E = sum_cos / NSAMPS; + V = sum_cos_sqr / NSAMPS - E*E; + SE = sqrt(V/NSAMPS); + CHK(eq_eps(E, 0, SE*3)); + + /* On average cos(-wo,wi)^2 should be 2/5 */ + E = sum_cos_sqr / NSAMPS; + V = sum_cos_sqr2 / NSAMPS - E*E; + SE = sqrt(V/NSAMPS); + CHK(eq_eps(E, 2.0/5.0, SE*3)); + + ssf_phase_sample(phase, rng, wo, wi, NULL); + CHK(d3_is_normalized(wi)); + + CHK(ssf_phase_ref_put(phase) == RES_OK); + CHK(ssp_rng_ref_put(rng) == RES_OK); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_ssf_specular_dielectric_dielectric_reflection.c b/src/test_ssf_specular_dielectric_dielectric_reflection.c @@ -0,0 +1,138 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * ALOng with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "ssf.h" +#include "ssf_optics.h" +#include "test_ssf_utils.h" + +#include <rsys/double3.h> + +int +main(int argc, char** argv) +{ + const size_t NSTEPS = 10000; + struct mem_allocator allocator; + struct ssp_rng* rng; + struct ssf_bsdf* bsdf; + struct ssf_bsdf* dummy; + const double eta_i = 1.00027; + const double eta_t = 1.5; + const double eta = eta_i/eta_t; + double N[3]; + double wo[3]; + double wi[3]; + double tmp[3]; + double R; + double pdf; + double sum_t, sum_t2, t; + double sum_r, sum_r2, r; + size_t i; + int type; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + CHK(ssp_rng_create(&allocator, &ssp_rng_mt19937_64, &rng) == RES_OK); + CHK(ssf_bsdf_create(&allocator, + &ssf_specular_dielectric_dielectric_interface, &bsdf) == RES_OK); + CHK(ssf_bsdf_create(&allocator, &bsdf_dummy, &dummy) == RES_OK); + + #define SETUP ssf_specular_dielectric_dielectric_interface_setup + CHK(SETUP(NULL, 0, 0) == RES_BAD_ARG); + CHK(SETUP(bsdf, 0, 0) == RES_BAD_ARG); + CHK(SETUP(NULL, 1, 0) == RES_BAD_ARG); + CHK(SETUP(bsdf, 1, 0) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, 1) == RES_BAD_ARG); + CHK(SETUP(bsdf, 0, 1) == RES_BAD_ARG); + CHK(SETUP(NULL, 1, 1) == RES_BAD_ARG); + CHK(SETUP(bsdf, 1, 1) == RES_OK); + CHK(SETUP(dummy, 1, 1) == RES_BAD_ARG); + CHK(SETUP(bsdf, 1.00027, 1.5) == RES_OK); + #undef SETUP + + d3(N, 0, 1, 0); + + /* Test transmission */ + d3(wo, 0, 1, 0); + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, &pdf); + CHK(R != 0); + CHK(IS_INF(pdf)); + CHK(d3_eq_eps(wi, d3(tmp,0,-1,0), 1.e-6)); + CHK(type == (SSF_SPECULAR | SSF_TRANSMISSION)); + + /* Test reflection */ + d3(wo, 1, 0, 0); + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, &pdf); + CHK(R != 0); + CHK(IS_INF(pdf)); + CHK(d3_eq_eps(wi, d3(tmp,-1,0,0), 1.e-6)); + CHK(type == (SSF_SPECULAR | SSF_REFLECTION)); + + d3_normalize(wo, d3(wo, 1, 0.0000001, 0)); + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, &pdf); + CHK(R == 1); + CHK(IS_INF(pdf)); + CHK(d3_eq_eps(wi, d3(tmp, -wo[0], wo[1], 0), 1.e-6) == 1); + CHK(type == (SSF_SPECULAR | SSF_REFLECTION)); + + pdf = 0, type = 0; + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, &pdf) == R); + CHK(IS_INF(pdf)); + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, NULL) == R); + CHK(type == (SSF_SPECULAR | SSF_REFLECTION)); + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, NULL) == R); + + /* Check energy conservation */ + sum_t = sum_t2 = 0; + sum_r = sum_r2 = 0; + FOR_EACH(i, 0, NSTEPS) { + ssp_ran_hemisphere_cos(rng, wo, N, NULL); + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, NULL); + if(type & SSF_TRANSMISSION) { + sum_t += R; + sum_t2 += R*R; + } else { + sum_r += R; + sum_r2 += R*R; + } + } + + t = sum_t / (double)NSTEPS; + r = sum_r / (double)NSTEPS; + CHK(t > 0); + CHK(r > 0); + CHK(eq_eps(t + r, 1, 1.e-6)); + + FOR_EACH(i, 0, NSTEPS) { + d3_normalize(wo, d3(wo,1,1,0)); + R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, &pdf); + CHK(R != 0); + CHK(type & SSF_SPECULAR); + CHK(IS_INF(pdf)); + if(d3_eq_eps(wi, refract(tmp, wo, N, eta), 1.e-6)) { + CHK(type & SSF_TRANSMISSION); + } else { + CHK(d3_eq_eps(wi, reflect(tmp, wo, N), 1.e-6)); + CHK(type & SSF_REFLECTION); + } + CHK(ssf_bsdf_pdf(bsdf, wo, N, wi) == 0); + CHK(ssf_bsdf_eval(bsdf, wo, N, wi) == 0); + } + + CHK(ssp_rng_ref_put(rng) == RES_OK); + CHK(ssf_bsdf_ref_put(bsdf) == RES_OK); + CHK(ssf_bsdf_ref_put(dummy) == RES_OK); + + return 0; +} diff --git a/src/test_ssf_specular_reflection.c b/src/test_ssf_specular_reflection.c @@ -56,6 +56,13 @@ main(int argc, char** argv) CHK(IS_INF(pdf) == 1); CHK(type == (SSF_SPECULAR|SSF_REFLECTION)); + pdf = 0, type = 0; + CHK(ssf_bsdf_sample(brdf, rng, wo, N, wi, NULL, &pdf) == R); + CHK(IS_INF(pdf)); + CHK(ssf_bsdf_sample(brdf, rng, wo, N, wi, &type, NULL) == R); + CHK(type == (SSF_SPECULAR|SSF_REFLECTION)); + CHK(ssf_bsdf_sample(brdf, rng, wo, N, wi, NULL, NULL) == R); + d3_normalize(wo, d3(wo, 1.0, 1.0, 0.0)); CHK(ssf_specular_reflection_setup(brdf, fresnel) == RES_OK); R = ssf_bsdf_sample(brdf, rng, wo, N, wi, &type, &pdf); diff --git a/src/test_ssf_specular_transmission.c b/src/test_ssf_specular_transmission.c @@ -1,103 +0,0 @@ -/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "ssf.h" -#include "ssf_optics.h" -#include "test_ssf_utils.h" - -#include <rsys/double3.h> - -int -main(int argc, char** argv) -{ - struct mem_allocator allocator; - struct ssp_rng* rng; - struct ssf_bxdf* btdf; - struct ssf_bxdf* dummy; - struct ssf_fresnel* fresnel; - double wo[3], wi[3], tmp[3]; - double N[3]; - double pdf; - double R; - int type; - (void)argc, (void)argv; - - mem_init_proxy_allocator(&allocator, &mem_default_allocator); - CHK(ssp_rng_create(&allocator, &ssp_rng_threefry, &rng) == RES_OK); - CHK(ssf_bxdf_create(&allocator, &ssf_specular_transmission, &btdf) == RES_OK); - CHK(ssf_bxdf_create(&allocator, &bxdf_dummy, &dummy) == RES_OK); - CHECK(ssf_fresnel_create - (&allocator, &ssf_fresnel_dielectric_dielectric, &fresnel), RES_OK); - CHK(ssf_fresnel_dielectric_dielectric_setup(fresnel, 1.00027, 1.5) == RES_OK); - - CHK(ssf_specular_transmission_setup(NULL, -1, -1) == RES_BAD_ARG); - CHK(ssf_specular_transmission_setup(btdf, -1, -1) == RES_BAD_ARG); - CHK(ssf_specular_transmission_setup(NULL, 1.00027, -1) == RES_BAD_ARG); - CHK(ssf_specular_transmission_setup(btdf, 1.00027, -1) == RES_BAD_ARG); - CHK(ssf_specular_transmission_setup(NULL, -1, 1.5) == RES_BAD_ARG); - CHK(ssf_specular_transmission_setup(btdf, -1, 1.5) == RES_BAD_ARG); - CHK(ssf_specular_transmission_setup(NULL, 1.00027, 1.5) == RES_BAD_ARG); - CHK(ssf_specular_transmission_setup(btdf, 1.00027, 1.5) == RES_OK); - CHK(ssf_specular_transmission_setup(dummy, 1.00027, 1.5) == RES_BAD_ARG); - - d3(N, 0.0, 1.0, 0.0); - d3_normalize(wo, d3(wo, 1.0, 1.0, 0.0)); - R = ssf_bxdf_sample(btdf, rng, wo, N, wi, &type, &pdf); - CHK(eq_eps(R, ssf_fresnel_eval(fresnel, fabs(d3_dot(N, wi))), 1.e-6) == RES_OK); - CHK(d3_eq_eps(wi, d3_minus(tmp, wo), 1.e-6) == 0); - CHK(wi[1] < 0 == 1); - CHK(IS_INF(pdf) == 1); - CHK(type == SSF_SPECULAR|SSF_TRANSMISSION); - - CHK(ssf_bxdf_eval(btdf, wo, N, wi) == 0.0); - CHK(ssf_bxdf_pdf(btdf, wo, N, wi) == 0.0); - - d3(wo, 0.0, 1.0, 0.0); - R = ssf_bxdf_sample(btdf, rng, wo, N, wi, &type, &pdf); - CHK(eq_eps(R, ssf_fresnel_eval(fresnel, fabs(d3_dot(N, wi))), 1.e-6) == RES_OK); - CHK(IS_INF(pdf) == 1); - CHK(d3_eq_eps(wi, d3_minus(tmp, wo), 1.e-6) == 1); - CHK(type == SSF_SPECULAR|SSF_TRANSMISSION); - - d3_normalize(wo, d3(wo, -1.0, 1.0, 0.0)); - R = ssf_bxdf_sample(btdf, rng, wo, N, wi, &type, &pdf); - reflect(tmp, wi, N); - CHK(type == SSF_SPECULAR|SSF_TRANSMISSION); - d3_normalize(wo, d3(wo, 1.0, 1.0, 0.0)); - R = ssf_bxdf_sample(btdf, rng, wo, N, wi, &type, &pdf); - CHK(d3_eq_eps(wi, tmp, 1.e-6) == 1); - CHK(type == SSF_SPECULAR|SSF_TRANSMISSION); - - d3_normalize(wo, d3(wo, -1, 1.e-6, 0)); - R = ssf_bxdf_sample(btdf, rng, wo, N, wi, &type, &pdf); - CHK(eq_eps(R, 0, 1.e-3) == 1); - CHK(type == SSF_SPECULAR|SSF_TRANSMISSION); - - /* Check total internal reflection */ - CHK(ssf_specular_transmission_setup(btdf, 1.5, 1.00027) == RES_OK); - R = ssf_bxdf_sample(btdf, rng, wo, N, wi, &type, &pdf); - CHK(eq_eps(R, 0, 1.e-3) == 1); - CHK(type == SSF_SPECULAR|SSF_REFLECTION); - - CHK(ssf_fresnel_ref_put(fresnel) == RES_OK); - CHK(ssf_bxdf_ref_put(btdf) == RES_OK); - CHK(ssf_bxdf_ref_put(dummy) == RES_OK); - CHK(ssp_rng_ref_put(rng) == RES_OK); - - check_memory_allocator(&allocator); - mem_shutdown_proxy_allocator(&allocator); - CHK(mem_allocated_size() == 0); - return 0; -} diff --git a/src/test_ssf_thin_specular_dielectric.c b/src/test_ssf_thin_specular_dielectric.c @@ -115,6 +115,15 @@ main(int argc, char** argv) CHK(IS_INF(pdf) == 1); CHK(d3_eq_eps(wi, d3(tmp, -wo[0], wo[1], 0), 1.e-6) == 1); CHK(type == (SSF_SPECULAR | SSF_REFLECTION)); + + /* Check optional arguments */ + pdf = 0, type = 0; + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, &pdf) == R); + CHK(IS_INF(pdf)); + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, NULL) == R); + CHK(type == (SSF_SPECULAR | SSF_REFLECTION)); + CHK(ssf_bsdf_sample(bsdf, rng, wo, N, wi, NULL, NULL) == R); + FOR_EACH(i, 0, NSTEPS) { R = ssf_bsdf_sample(bsdf, rng, wo, N, wi, &type, &pdf); CHK(type & SSF_REFLECTION); diff --git a/src/test_ssf_utils.h b/src/test_ssf_utils.h @@ -178,6 +178,62 @@ static const struct ssf_microfacet_distribution_type microfacet_dummy = { }; /******************************************************************************* + * Dummy Phase function type + ******************************************************************************/ +static res_T +phase_dummy_init(struct mem_allocator* allocator, void* phase) +{ + (void)allocator, (void)phase; + return RES_OK; +} + +static void +phase_dummy_release(void* phase) +{ + (void)phase; +} + +static void +phase_dummy_sample + (void* phase, + struct ssp_rng* rng, + const double w[3], + double dir[3], + double* pdf) +{ + (void)phase, (void)rng, (void)w, (void)dir, (void)pdf; +} + +static double +phase_dummy_eval + (void* phase, + const double wo[3], + const double wi[3]) +{ + (void)phase, (void)wo, (void)wi; + return 0.0; +} + +static double +phase_dummy_pdf + (void* phase, + const double wo[3], + const double wi[3]) +{ + (void)phase, (void)wo, (void)wi; + return 0.0; +} + +static const struct ssf_phase_type phase_dummy = { + phase_dummy_init, + phase_dummy_release, + phase_dummy_sample, + phase_dummy_eval, + phase_dummy_pdf, + 0, 1 +}; + +/******************************************************************************* * Miscellaneous functions ******************************************************************************/ static INLINE void @@ -226,6 +282,9 @@ check_microfacet_distribution D = ssf_microfacet_distribution_eval(distrib, N, wh); weight = D * d3_dot(wh, N) / pdf; CHK(eq_eps(weight, 1.0, 1.e-6) == 1); + CHK(eq_eps(pdf, ssf_microfacet_distribution_pdf(distrib, N, wh), 1.e-6f)); + + ssf_microfacet_distribution_sample(distrib, rng, N, wh, NULL); } }