commit 58cd16d3d89bda2ab3e2f45c89075bdc45dc3bdf
parent ef1442447e44956317b98aa4d1fb5df702c42edb
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Fri, 30 Sep 2016 14:21:28 +0200
Test the Blinn microfacet distribution
Push further the tests on the Beckmann microfacet distribution.
Diffstat:
7 files changed, 165 insertions(+), 41 deletions(-)
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
@@ -98,6 +98,7 @@ if(NOT NO_TEST)
endfunction()
new_test(test_ssf_beckmann_distribution)
+ new_test(test_ssf_blinn_distribution)
new_test(test_ssf_bsdf)
new_test(test_ssf_bxdf)
new_test(test_ssf_fresnel)
diff --git a/src/ssf.h b/src/ssf.h
@@ -35,6 +35,10 @@
#define SSF(Func) ssf_##Func
#endif
+/* Maximum value of the exponent parameter in the the Blinn microfacet
+ * distribution */
+#define SSF_BLINN_DISTRIBUTION_MAX_EXPONENT 10000.0
+
/* Forward declaration */
struct mem_allocator;
@@ -403,7 +407,7 @@ ssf_beckmann_distribution_setup
SSF_API res_T
ssf_blinn_distribution_setup
(struct ssf_microfacet_distribution* distrib,
- const double exponent); /* in [0, 10000] */
+ const double exponent); /* in [0, SSF_BLINN_DISTRIBUTION_MAX_EXPONENT] */
END_DECLS
diff --git a/src/ssf_beckmann_distribution.c b/src/ssf_beckmann_distribution.c
@@ -134,7 +134,7 @@ ssf_beckmann_distribution_setup
(struct ssf_microfacet_distribution* distrib,
const double roughness)
{
- if(!distrib || roughness <= 0) return RES_BAD_ARG;
+ if(!distrib || roughness <= 0 || roughness > 1) return RES_BAD_ARG;
if(!MICROFACET_DISTRIBUTION_TYPE_EQ(&distrib->type, &ssf_beckmann_distribution))
return RES_BAD_ARG;
((struct beckmann_distribution*)distrib->data)->roughness = roughness;
diff --git a/src/ssf_blinn_distribution.c b/src/ssf_blinn_distribution.c
@@ -19,8 +19,6 @@
#include <rsys/double3.h>
#include <rsys/double33.h>
-#define EXPONENT_MAX 10000
-
struct blinn_distribution {
double exponent;
};
@@ -49,7 +47,8 @@ blinn_distribution_eval
double cos_wh_N;
ASSERT(distrib && wo && N && wh);
ASSERT(d3_is_normalized(wo) && d3_is_normalized(N) && d3_is_normalized(wh));
- ASSERT(blinn->exponent >= 0.0 && blinn->exponent <= EXPONENT_MAX);
+ ASSERT(blinn->exponent >= 0.0);
+ ASSERT(blinn->exponent <= SSF_BLINN_DISTRIBUTION_MAX_EXPONENT);
(void)wo;
cos_wh_N = d3_dot(wh, N);
return (blinn->exponent + 2) / (2*PI) * pow(cos_wh_N, blinn->exponent);
@@ -122,7 +121,7 @@ ssf_blinn_distribution_setup
(struct ssf_microfacet_distribution* distrib,
const double exponent)
{
- if(!distrib || exponent < 0 || exponent > EXPONENT_MAX)
+ if(!distrib || exponent < 0 || exponent > SSF_BLINN_DISTRIBUTION_MAX_EXPONENT)
return RES_BAD_ARG;
if(!MICROFACET_DISTRIBUTION_TYPE_EQ(&distrib->type, &ssf_blinn_distribution))
return RES_BAD_ARG;
diff --git a/src/test_ssf_beckmann_distribution.c b/src/test_ssf_beckmann_distribution.c
@@ -13,20 +13,21 @@
* 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 /* nextafter support */
+
#include "ssf.h"
#include "test_ssf_utils.h"
+#include <math.h>
+
int
main(int argc, char** argv)
{
struct mem_allocator allocator;
struct ssf_microfacet_distribution* distrib;
struct ssf_microfacet_distribution* dummy;
- double N[3], wo[3];
- double sum, sum_sqr;
- double E, V, SE;
- const size_t NSTEPS = 10000;
- size_t i;
+ const size_t NTESTS = 10;
+ size_t itest;
(void)argc, (void)argv;
mem_init_proxy_allocator(&allocator, &mem_default_allocator);
@@ -42,37 +43,15 @@ main(int argc, char** argv)
CHECK(ssf_beckmann_distribution_setup(distrib, 0.5), RES_OK);
CHECK(ssf_beckmann_distribution_setup(dummy, 0.5), RES_BAD_ARG);
- /* Check that D(wh) is normalized wrt \int_{2PI} D(wh) |wh.n| dwh */
- d3(N, 0, 0, 1);
- ran_hemisphere_cos(N, wo, NULL);
- sum = sum_sqr = 0;
- FOR_EACH(i, 0, NSTEPS) {
- double wh[3], pdf;
- double D, weight;
- ran_hemisphere_cos(N, wh, &pdf);
- D = ssf_microfacet_distribution_eval(distrib, wo, N, wh);
- weight = D * d3_dot(wh, N) / pdf;
- sum += weight;
- sum_sqr += weight*weight;
-
- }
- E = sum / (double)NSTEPS;
- V = sum_sqr / (double)NSTEPS - E*E;
- SE = sqrt(V/(double)NSTEPS);
- CHECK(eq_eps(E, 1.0, SE), 1);
-
- /* Check the sampling of a direction wh and the returned pdf */
- sum = sum_sqr = 0;
- FOR_EACH(i, 0, NSTEPS) {
- const double u = rand_canonic();
- const double v = rand_canonic();
- double wh[3], pdf;
- double D, weight;
+ CHECK(ssf_beckmann_distribution_setup(distrib, 1), RES_OK);
+ CHECK(ssf_beckmann_distribution_setup(distrib, nextafter(0, 1)), RES_OK);
+ CHECK(ssf_beckmann_distribution_setup(distrib, nextafter(1, 2)), RES_BAD_ARG);
+ CHECK(ssf_beckmann_distribution_setup(distrib, 0), RES_BAD_ARG);
- ssf_microfacet_distribution_sample(distrib, u, v, wo, N, wh, &pdf);
- D = ssf_microfacet_distribution_eval(distrib, wo, N, wh);
- weight = D * d3_dot(wh, N) / pdf;
- CHECK(eq_eps(weight, 1, 1.e-6), 1);
+ FOR_EACH(itest, 0, NTESTS) {
+ const double roughness = nextafter(rand_canonic(), 2); /* in ]0, 1] */
+ CHECK(ssf_beckmann_distribution_setup(distrib, roughness), RES_OK);
+ check_microfacet_distribution(distrib);
}
CHECK(ssf_microfacet_distribution_ref_put(distrib), RES_OK);
diff --git a/src/test_ssf_blinn_distribution.c b/src/test_ssf_blinn_distribution.c
@@ -0,0 +1,66 @@
+/* Copyright (C) |Meso|Star> 2016 (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 /* nextafter support */
+
+#include "ssf.h"
+#include "test_ssf_utils.h"
+
+int
+main(int argc, char** argv)
+{
+ struct mem_allocator allocator;
+ struct ssf_microfacet_distribution* distrib;
+ struct ssf_microfacet_distribution* dummy;
+ const size_t NTESTS = 10;
+ size_t itest;
+ (void)argc, (void)argv;
+
+ mem_init_proxy_allocator(&allocator, &mem_default_allocator);
+
+ CHECK(ssf_microfacet_distribution_create
+ (&allocator, &ssf_blinn_distribution, &distrib), RES_OK);
+ CHECK(ssf_microfacet_distribution_create
+ (&allocator, µfacet_dummy, &dummy), RES_OK);
+
+ CHECK(ssf_blinn_distribution_setup(NULL, -1), RES_BAD_ARG);
+ CHECK(ssf_blinn_distribution_setup(distrib, -1), RES_BAD_ARG);
+ CHECK(ssf_blinn_distribution_setup(NULL, 8), RES_BAD_ARG);
+ CHECK(ssf_blinn_distribution_setup(distrib, 8), RES_OK);
+ CHECK(ssf_blinn_distribution_setup(distrib, 0), RES_OK);
+ CHECK(ssf_blinn_distribution_setup(dummy, 0), RES_BAD_ARG);
+
+ CHECK(ssf_blinn_distribution_setup
+ (distrib, SSF_BLINN_DISTRIBUTION_MAX_EXPONENT), RES_OK);
+ CHECK(ssf_blinn_distribution_setup
+ (distrib, nextafter(SSF_BLINN_DISTRIBUTION_MAX_EXPONENT, DBL_MAX)),
+ RES_BAD_ARG);
+ CHECK(ssf_blinn_distribution_setup(distrib, nextafter(0,-1)), RES_BAD_ARG);
+ CHECK(ssf_blinn_distribution_setup(distrib, 32.32), RES_OK);
+
+ FOR_EACH(itest, 0, NTESTS) {
+ const double exponent = rand_canonic()*SSF_BLINN_DISTRIBUTION_MAX_EXPONENT;
+ CHECK(ssf_blinn_distribution_setup(distrib, exponent), RES_OK);
+ check_microfacet_distribution(distrib);
+ }
+
+ CHECK(ssf_microfacet_distribution_ref_put(distrib), RES_OK);
+ CHECK(ssf_microfacet_distribution_ref_put(dummy), RES_OK);
+
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHECK(mem_allocated_size(), 0);
+ return 0;
+}
diff --git a/src/test_ssf_utils.h b/src/test_ssf_utils.h
@@ -212,6 +212,81 @@ ran_hemisphere_cos
return dir;
}
+static INLINE double*
+ran_hemisphere(const double N[3], double dir[3], double *pdf)
+{
+ const double u = rand_canonic();
+ const double v = rand_canonic();
+ const double phi = 2.0 * PI * u;
+ const double cos_theta = v;
+ const double sin_theta = cos2sin(cos_theta);
+ double basis[9];
+ double tmp[3];
+ tmp[0] = cos(phi) * sin_theta;
+ tmp[1] = sin(phi) * sin_theta;
+ tmp[2] = cos_theta;
+ d33_muld3(dir, d33_basis(basis, N), tmp);
+ if(pdf) *pdf = 1/(2*PI);
+ return dir;
+}
+
+static INLINE void
+check_microfacet_distribution
+ (struct ssf_microfacet_distribution* distrib)
+{
+ double N[3];
+ const size_t NSTEPS = 10000;
+ size_t i;
+ size_t n;
+ double wo[3];
+ double E, V, SE;
+ double sum, sum2;
+
+ N[0] = rand_canonic()*2 - 1;
+ N[1] = rand_canonic()*2 - 1;
+ N[2] = rand_canonic()*2 - 1;
+ d3_normalize(N, N);
+
+ ran_hemisphere(N, wo, NULL);
+
+ /* Check that D(wh) is normalized wrt \int_{2PI} D(wh) |wh.n| dwh */
+ sum = sum2 = 0;
+ n = 0;
+ do {
+ FOR_EACH(i, 0, NSTEPS) {
+ double wh[3], pdf, D, weight;
+ ran_hemisphere_cos(N, wh, &pdf);
+ D = ssf_microfacet_distribution_eval(distrib, wo, N, wh);
+ weight = D * d3_dot(wh, N) / pdf;
+ sum += weight;
+ sum2 += weight*weight;
+ }
+ n += NSTEPS;
+ E = sum / (double)n;
+ V = MMAX(sum2 / (double)n - E*E, 0);
+ SE = sqrt(V/(double)n);
+ } while(SE > E * 0.05);
+ CHECK(eq_eps(E, 1.0, 3*SE), 1);
+
+ /* Check the sampling of a direction wh and the returned pdf */
+ FOR_EACH(i, 0, NSTEPS) {
+ const double u = rand_canonic();
+ const double v = rand_canonic();
+ double wh[3], pdf, D, weight;
+
+ ssf_microfacet_distribution_sample(distrib, u, v, wo, N, wh, &pdf);
+ D = ssf_microfacet_distribution_eval(distrib, wo, N, wh);
+ weight = D * d3_dot(wh, N) / pdf;
+ sum += weight;
+ sum2 += weight*weight;
+ }
+ n += NSTEPS;
+ E = sum / (double)n;
+ V = MMAX(sum2 / (double)n - E*E, 0);
+ SE = sqrt(V/(double)n);
+ CHECK(eq_eps(E, 1.0, 3*SE), 1);
+}
+
static INLINE void
check_memory_allocator(struct mem_allocator* allocator)
{