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 75f142d18acf46f581f469379025b3e8021d3764
parent c828f4a75986cbc12924241cd4d496a289a385bb
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Wed, 28 Sep 2022 16:47:43 +0200

Add the discrete phase function

Diffstat:
Mcmake/CMakeLists.txt | 1+
Msrc/ssf.h | 29+++++++++++++++++++++++++++++
Asrc/ssf_phase_discrete.c | 377+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 407 insertions(+), 0 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -106,6 +106,7 @@ set(SSF_FILES_SRC ssf_microfacet_distribution.c ssf_microfacet_reflection.c ssf_phase.c + ssf_phase_discrete.c ssf_phase_hg.c ssf_phase_rayleigh.c ssf_phase_rdgfa.c diff --git a/src/ssf.h b/src/ssf.h @@ -235,6 +235,27 @@ struct ssf_phase_rdgfa_interval { static const struct ssf_phase_rdgfa_interval SSF_PHASE_RDGFA_INTERVAL_NULL = SSF_PHASE_RDGFA_INTERVAL_NULL__; +struct ssf_discrete_item { + double theta; /* In radian */ + double value; +}; +#define SSF_DISCRETE_ITEM_NULL__ {0,0} +static const struct ssf_discrete_item SSF_DISCRETE_ITEM_NULL = + SSF_DISCRETE_ITEM_NULL__; + +/* Discrete phase function input arguments */ +struct ssf_discrete_setup_args { + void (*get_item) /* Returns a given discrete item */ + (const size_t iitem, + struct ssf_discrete_item* item, + void* context); + void* context; /* User defined data sent to `get_item' functor */ + size_t nitems; /* #discrete items */ +}; +#define SSF_DISCRETE_SETUP_ARGS_NULL__ {NULL,NULL,0} +static const struct ssf_discrete_setup_args +SSF_DISCRETE_SETUP_ARGS_NULL = SSF_DISCRETE_SETUP_ARGS_NULL__; + BEGIN_DECLS /******************************************************************************* @@ -368,6 +389,9 @@ SSF_API const struct ssf_phase_type ssf_phase_rayleigh; * Radiative Transfer Vol 133, pp. 374-381, 2014 */ SSF_API const struct ssf_phase_type ssf_phase_rdgfa; +/* Discrete phase function normalized to 1 */ +SSF_API const struct ssf_phase_type ssf_phase_discrete; + /******************************************************************************* * SSF API ******************************************************************************/ @@ -508,6 +532,11 @@ ssf_phase_hg_setup (struct ssf_phase* phase, const double g); /* Asymmetric parameter in [-1, 1] */ +SSF_API res_T +ssf_phase_discrete_setup + (struct ssf_phase* phase, + const struct ssf_discrete_setup_args* args); + /******************************************************************************* * RDGFA phase function ******************************************************************************/ diff --git a/src/ssf_phase_discrete.c b/src/ssf_phase_discrete.c @@ -0,0 +1,377 @@ +/* Copyright (C) 2016-2018, 2021 |Meso|Star> (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 */ + +#include "ssf.h" +#include "ssf_phase_c.h" + +#include <star/ssp.h> + +#include <rsys/algorithm.h> +#include <rsys/double3.h> +#include <rsys/dynamic_array_double.h> + +#include <math.h> /* nextafter */ + +struct discrete_item { + double cos_theta; + double value; +}; + +/* Define the dynamic array of discrete items */ +#define DARRAY_NAME discrete_item +#define DARRAY_DATA struct discrete_item +#include <rsys/dynamic_array.h> + +struct discrete { + struct darray_discrete_item items; + struct darray_double cumulative; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE res_T +check_ssf_discrete_setup_args + (const struct ssf_discrete_setup_args* args) +{ + struct ssf_discrete_item item; + if(!args || args->nitems < 2 || !args->get_item) + return RES_BAD_ARG; + + /* Invalid 1st angle */ + args->get_item(0, &item, args->context); + if(item.theta != 0) + return RES_BAD_ARG; + + /* Invalid last angle */ + args->get_item(args->nitems-1, &item, args->context); + if(!eq_eps(item.theta, PI, 1.e-4)) + return RES_BAD_ARG; + + return RES_OK; +} + +static res_T +setup_discrete_items + (struct discrete* discrete, + const struct ssf_discrete_setup_args* args) +{ + struct discrete_item* items = NULL; + size_t i = 0; + res_T res = RES_OK; + ASSERT(discrete && args); + + res = darray_discrete_item_resize(&discrete->items, args->nitems); + if(res != RES_OK) goto error; + items = darray_discrete_item_data_get(&discrete->items); + + FOR_EACH(i, 0, args->nitems) { + struct ssf_discrete_item item; + args->get_item(i, &item, args->context); + items[i].cos_theta = cos(item.theta); + items[i].value = item.value; + + /* Angles must be sorted in ascending order */ + if(i > 0 && items[i].cos_theta > items[i-1].cos_theta) { + res = RES_BAD_ARG; + goto error; + } + } + +exit: + return res; +error: + goto exit; +} + +static res_T +compute_cumulative(struct discrete* discrete) +{ + struct discrete_item* items = NULL; + double* cumul = NULL; + double alpha = 0; + size_t n; + size_t i; + res_T res = RES_OK; + ASSERT(discrete); + + n = darray_discrete_item_size_get(&discrete->items); + ASSERT(n >= 2); + + res = darray_double_resize(&discrete->cumulative, n); + if(res != RES_OK) goto error; + + items = darray_discrete_item_data_get(&discrete->items); + cumul = darray_double_data_get(&discrete->cumulative); + + alpha = 0; + cumul[0] = alpha; + FOR_EACH(i, 1, n) { + alpha += PI + * (items[i-1].value + items[i].value) + * (items[i-1].cos_theta - items[i].cos_theta); + + cumul[i] = alpha; + } + + /* Ensure that the phase function is normalized */ + if(!eq_eps(alpha, 1, 1.e-6)) { + const double rcp_alpha = 1.0 / alpha; + FOR_EACH(i, 0, n) { + items[i].value *= rcp_alpha; + cumul[i] *= rcp_alpha; + } + } + +exit: + return res; +error: + goto exit; +} + +static INLINE int +cmp_discrete_item(const void* a, const void* b) +{ + const struct discrete_item* item; + double key; + ASSERT(a && b); + + key = *((double*)a); + item = b; + + /* The array is sorted in ascending order of the angles while it stores the + * cosine of the angles. In the following, we reverse the test to find the + * first entry whose _angle_ is not less than the angle whose cosine is the + * key sought. This ensures that the array is sorted in ascending order as + * expected by the search_lower_bound routine */ + if(key < item->cos_theta) { + return +1; + } else if(key > item->cos_theta) { + return -1; + } else { + return 0; + } +} + +static INLINE int +cmp_cumul(const void* a, const void* b) +{ + double key; + double cumul; + ASSERT(a && b); + + key = *((double*)a); + cumul = *((double*)b); + + if(key < cumul) { + return -1; + } else if(key > cumul) { + return +1; + } else { + return 0; + } +} + +/******************************************************************************* + * Private functions + ******************************************************************************/ +static res_T +discrete_init(struct mem_allocator* allocator, void* phase) +{ + struct discrete* discrete = phase; + ASSERT(phase); + darray_discrete_item_init(allocator, &discrete->items); + darray_double_init(allocator, &discrete->cumulative); + return RES_OK; +} + +static void +discrete_release(void* phase) +{ + struct discrete* discrete = phase; + ASSERT(phase); + darray_discrete_item_release(&discrete->items); + darray_double_release(&discrete->cumulative); +} + +static double +discrete_eval(void* phase, const double wo[3], const double wi[3]) +{ + const struct discrete* discrete = phase; + const struct discrete_item* items = NULL; + double v[3]; + double cos_theta = 0; + double value = 0; + size_t nitems = 0; + ASSERT(phase && wo && wi); + ASSERT(d3_is_normalized(wo) && d3_is_normalized(wi)); + + d3_minus(v, wo); + cos_theta = d3_dot(v, wi); + + items = darray_discrete_item_cdata_get(&discrete->items); + nitems = darray_discrete_item_size_get(&discrete->items); + ASSERT(nitems >= 2); + + if(eq_eps(cos_theta, 1, 1.e-6)) { + value = items[0].value; + } else if(eq_eps(cos_theta, 0, 1.e-6)) { + value = items[nitems-1].value; + } else { + const struct discrete_item* found_item = NULL; + size_t iitem = 0; + double u = 0; + + /* Search for the discrete item whose angle is greater or equal to the + * angle between wo and wi */ + found_item = search_lower_bound + (&cos_theta, items, nitems, sizeof(*items), cmp_discrete_item); + iitem = (size_t)(found_item - items); + ASSERT(found_item && iitem > 0 && iitem < nitems); + ASSERT(cos_theta <= items[iitem-1].cos_theta); + ASSERT(cos_theta > items[iitem].cos_theta); + + /* Linearly interpolate the phase function value */ + u = + (cos_theta - items[iitem].cos_theta) + / (items[iitem-1].cos_theta - items[iitem].cos_theta); + value = items[iitem].value + u*(items[iitem-1].value - items[iitem].value); + } + + return value; +} + +static void +discrete_sample + (void* phase, + struct ssp_rng* rng, + const double wo[3], + double wi[3], + double *pdf) +{ + const struct discrete* discrete = phase; + const struct discrete_item* items = NULL; + const double* cumul = NULL; + double frame[9]; + double w[3]; + double cos_theta = 0; + double sin_theta = 0; + double phi = 0; + size_t ncumul = 0; + size_t i = 0; + double r = 0; + ASSERT(phase && rng && wo && wi); + ASSERT(d3_is_normalized(wo) && d3_is_normalized(wi)); + + items = darray_discrete_item_cdata_get(&discrete->items); + cumul = darray_double_cdata_get(&discrete->cumulative); + ncumul = darray_double_size_get(&discrete->cumulative); + ASSERT(ncumul == darray_discrete_item_size_get(&discrete->items)); + + /* Sample r in [0, 1] */ + r = ssp_rng_uniform_double(rng, 0, nextafter(1, 2)); + if(r == 0) { + cos_theta = 1; + sin_theta = 0; + } else if(r ==1) { + cos_theta =-1; + sin_theta = 0; + } else { + double* found_cumul = NULL; + double u; + + /* Search for the first entry in the cumulative that is greater than or equal + * to the sampled number in [0, 1] */ + found_cumul = search_lower_bound(&r, cumul, ncumul, sizeof(*cumul), cmp_cumul); + + i = (size_t)(found_cumul - cumul); + ASSERT(found_cumul && i > 0 && i < ncumul); + + /* Linearly interpolate the sampled cos_theta */ + u = (r - cumul[i-1]) / (cumul[i] - cumul[i-1]); + ASSERT(r >= cumul[i-1] && r < cumul[i]); + cos_theta = items[i-1].cos_theta + u*(items[i].cos_theta - items[i-1].cos_theta); + + /* Compute the sinus of theta from its cosine */ + sin_theta = sqrt(1 - cos_theta*cos_theta); + } + + /* Uniformly sample phi in [0, 2PI[ */ + phi = ssp_rng_uniform_double(rng, 0, 2*PI); + + /* Calculate the Cartesian coordinates of the direction in the local + * coordinate system of the phase function */ + wi[0] = cos(phi) * sin_theta; + wi[1] = sin(phi) * cos_theta; + wi[2] = cos_theta; + + /* Calculate the transformation matrix from the local coordinate system of + * the phase function to the absolute coordinate system. Note that by + * convention in Star-SF the directions point outward from the scattering + * position. That's why we reverse 'wo' to point inside the scattering + * position */ + d33_basis(frame, d3_minus(w, wo)); + d33_muld3(wi, frame, wi); + ASSERT(eq_eps(d3_dot(wi, w), cos_theta, fabs(cos_theta*1.e-6))); + + if(pdf) *pdf = discrete_eval(phase, wo, wi); +} + +/******************************************************************************* + * Exported symbols + ******************************************************************************/ +const struct ssf_phase_type ssf_phase_discrete = { + discrete_init, + discrete_release, + discrete_sample, + discrete_eval, + discrete_eval, + sizeof(struct discrete), + ALIGNOF(struct discrete) +}; + +res_T +ssf_phase_discrete_setup + (struct ssf_phase* phase, + const struct ssf_discrete_setup_args* args) +{ + struct discrete* discrete = NULL; + res_T res = RES_OK; + + if(!phase || !PHASE_TYPE_EQ(&phase->type, &ssf_phase_discrete)) { + res = RES_BAD_ARG; + goto error; + } + res = check_ssf_discrete_setup_args(args); + if(res != RES_OK) goto error; + + discrete = phase->data; + + res = setup_discrete_items(discrete, args); + if(res != RES_OK) goto error; + res = compute_cumulative(discrete); + if(res != RES_OK) goto error; + +exit: + return res; +error: + if(discrete) { + darray_discrete_item_purge(&discrete->items); + darray_double_purge(&discrete->cumulative); + } + goto exit; +}