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 86e82071520b52433c94677b8968df20fc31f616
parent eb05706ceee0a91d2e15c760493e263616745c4f
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Fri,  5 Feb 2021 11:45:58 +0100

Add the RDGFA phase function

Diffstat:
Mcmake/CMakeLists.txt | 1+
Msrc/ssf.h | 31+++++++++++++++++++++++++++++--
Msrc/ssf_phase_hg.c | 1-
Asrc/ssf_phase_rdgfa.c | 387+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 417 insertions(+), 3 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -66,6 +66,7 @@ set(SSF_FILES_SRC ssf_phase.c ssf_phase_hg.c ssf_phase_rayleigh.c + ssf_phase_rdgfa.c ssf_pillbox_distribution.c ssf_specular_dielectric_dielectric_interface.c ssf_specular_reflection.c diff --git a/src/ssf.h b/src/ssf.h @@ -153,8 +153,8 @@ SSF_MICROFACET_DISTRIBUTION_TYPE_NULL = SSF_MICROFACET_DISTRIBUTION_TYPE_NULL__; * 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 */ + res_T (*init)(struct mem_allocator* allocator, void* phase); /*Can be NULL*/ + void (*release)(void* phase); /* Can be NULL */ /* Sample a direction `wi' wrt `wo'. */ void @@ -185,6 +185,19 @@ struct ssf_phase_type { #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__; +/* RDGFA phase function input arguments */ +struct ssf_phase_rdgfa_setup_args { + double wavelength; /* In nm */ + double fractal_dimension; /* No unit */ + double gyration_radius; /* In nm */ + + /* #intervals used to discretized the both angular domains */ + size_t nintervals[2]; +}; +#define SSF_PHASE_RDGFA_SETUP_DEFAULT_ARGS__ {0, 0, 0, {1000, 1000}} +static const struct ssf_phase_rdgfa_setup_args +SSF_PHASE_RDGFA_SETUP_DEFAULT_ARGS = SSF_PHASE_RDGFA_SETUP_DEFAULT_ARGS__; + BEGIN_DECLS /******************************************************************************* @@ -309,6 +322,15 @@ SSF_API const struct ssf_phase_type ssf_phase_hg; * p(wo, wi) = 3/(16*PI) * (1+(-wo.wi)^2) */ SSF_API const struct ssf_phase_type ssf_phase_rayleigh; +/* RDGFA phase function normalized to 1: + * p(wo, wi) = 3/(16*PI) * f(theta)/g * (1+cos(theta)^2); + * theta = acos(wo.wi) + * + * "Effects of multiple scattering on radiative properties of soot fractal + * aggregates" J. Yon et al., Journal of Quantitative Spectroscopy and + * Radiative Transfer Vol 133, pp. 374-381, 2014 */ +SSF_API const struct ssf_phase_type ssg_phase_rdgfa; + /******************************************************************************* * BSDF API - Bidirectional Scattering Distribution Function. Describes the way * the light is scattered by a surface. Note that by convention the outgoing @@ -442,6 +464,11 @@ ssf_phase_hg_setup (struct ssf_phase* phase, const double g); /* Asymmetric parameter in [-1, 1] */ +SSF_API res_T +ssf_phase_rdgfa_setup + (struct ssf_phase* phase, + const struct ssf_phase_rdgfa_setup_args* args); + /******************************************************************************* * Fresnel API - Define the equation of the fresnel term ******************************************************************************/ diff --git a/src/ssf_phase_hg.c b/src/ssf_phase_hg.c @@ -110,4 +110,3 @@ ssf_phase_hg_setup(struct ssf_phase* phase, const double g) ((struct hg*)phase->data)->g = g; return RES_OK; } - diff --git a/src/ssf_phase_rdgfa.c b/src/ssf_phase_rdgfa.c @@ -0,0 +1,387 @@ +/* Copyright (C) 2016-2018 |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/>. */ + +#include "ssf.h" +#include "ssf_phase_c.h" + +#include <star/ssp.h> + +#include <rsys/algorithm.h> +#include <rsys/dynamic_array_double.h> + +#define EXP1 2.7182818284590452354 + +struct rdgfa { + double wavelength; /* In nm */ + double fractal_dimension; /* No unit */ + double gyration_radius; /* In nm */ + + double rcp_normalize_factor; /* Reciprocal normalization factor of the CDF */ + + /* Discretized cumulative (#entries = nintervals[0] + nintervals[1]) */ + struct darray_double cdf; + + /* Precomputed values */ + double Rg2; /* gyration_radius^2 */ + double cst_4pi_div_lambda; /* (4*PI)/wavelength */ + double cst_3Df_div_2E; /* 3*Df/(2*exp(1)) */ + double g; /* g function */ + + /* #intervals used to discretized both the [0,theta_limit[ and + * [theta_limit,pi[ intervals */ + size_t nintervals[2]; + + /* Length of an angular interval for the tow angular domains */ + double dtheta[2]; /* In rad */ + + /* Limite angle that split the 2 angular domains */ + double theta_limit; /* In rad */ +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE int +check_phase_rdgfa_setup_args(const struct ssf_phase_rdgfa_setup_args* args) +{ + return args + && args->wavelength > 0 + && args->fractal_dimension > 0 + && args->gyration_radius > 0 + && args->nintervals[0] != 0 + && args->nintervals[1] != 0; +} + +static INLINE int +cmp_dbl(const void* a, const void* b) +{ + const double key = *((double*)a); + const double val = *((double*)b); + if(key < val) { + return -1; + } else if(key > val) { + return 1; + } else { + return 0; + } +} + +#if 0 +/* Not normalized */ +static INLINE double +eval__(struct rdgfa* rdgfa, const double theta) +{ + /* Input arguments */ + const double lambda = rdgfa->wavelength; + const double Df = rdgfa->fractal_dimension; + const double Rg = rdgfa->gyration_radius; + const double Rg2 = Rg*Rg; + + /* TODO precompute constants */ + const double k = 2*PI / lambda; /* [nm^-1] */ + const double k2 = k*k; + + const double g = pow(1 + 4*k2*Rg2/(3*Df), -Df*0.5); + + const double q = 4*PI / lambda * sin(theta*0.5); + const double qRg = q * Rg; + const double qRg2 = qRg*qRg; + const double f = qRg2 < 1.5*Df + ? exp(-1.0/3.0 * qRg2) + : pow(3.0*Df/(2.0*EXP1) * 1.0/qRg2, Df*0.5); + + ASSERT(d3_is_normalized(wo) && d3_is_normalized(wi)); + return f; +} +#endif + +static INLINE double +eval_f(struct rdgfa* rdgfa, const double theta) +{ + double Df, Rg2, q, q2Rg2, f; + ASSERT(rdgfa); + + /* Input arguments */ + Df = rdgfa->fractal_dimension; + + /* Fech precomputed constants */ + Rg2 = rdgfa->Rg2; + + /* Precompute values */ + q = rdgfa->cst_4pi_div_lambda * sin(theta*0.5); + q2Rg2 = q*q*Rg2; + + /* Evaluate f(theta) */ + f = q2Rg2 < 1.5*Df + ? exp(-1.0/3.0 * q2Rg2) + : pow(rdgfa->cst_3Df_div_2E * 1/q2Rg2, Df*0.5); + return f; +} + +/* Not normalized */ +static INLINE double +eval2 + (struct rdgfa* rdgfa, + const double theta, + const double cos_theta) +{ + double f, cos2_theta, phase; + ASSERT(rdgfa && eq_eps(cos_theta, cos(theta), 1.e-6)); + + /* Precompute values */ + cos2_theta = cos_theta * cos_theta; + + /* Evaluate the phase(theta) */ + f = eval_f(rdgfa, theta); + phase = 3.0/(16*PI) * f / rdgfa->g * (1 + cos2_theta); + return phase; +} + + +/* Not normalized */ +static FINLINE double +eval(struct rdgfa* rdgfa, const double theta) +{ + return eval2(rdgfa, theta, cos(theta)); +} + +static res_T +compute_cumulative(struct rdgfa* rdgfa) +{ + double* cdf = NULL; + double Df, Rg; + double f1; + double theta1; + size_t i; + size_t nintervals_total; + res_T res = RES_OK; + + /* Compute the overall number of angular intervals */ + nintervals_total = rdgfa->nintervals[0] + rdgfa->nintervals[1]; + + /* Allocate the cumulative array */ + res = darray_double_resize(&rdgfa->cdf, nintervals_total); + if(res != RES_OK) goto error; + cdf = darray_double_data_get(&rdgfa->cdf); + + /* Fetch input arguments */ + Df = rdgfa->fractal_dimension; + Rg = rdgfa->gyration_radius; + + /* Compute the limit angle between the 2 angular domains */ + rdgfa->theta_limit = 2 * asin(rdgfa->wavelength/(4*PI) * sqrt(1.5*Df) / Rg); + + /* Compute the angular step for the 2 angular domains */ + rdgfa->dtheta[0] = rdgfa->theta_limit / (double)rdgfa->nintervals[0]; + rdgfa->dtheta[1] = (PI - rdgfa->theta_limit) / (double)rdgfa->nintervals[1]; + + theta1 = 0; + f1 = eval(rdgfa, 0); + FOR_EACH(i, 0, nintervals_total) { + /* Fetch the current angular step */ + const double dtheta_curr = i < rdgfa->nintervals[0] + ? rdgfa->dtheta[0] : rdgfa->dtheta[1]; + + /* Compute the upper bound of the currant angular range */ + const double theta2 = theta1 + dtheta_curr; + + /* Compute the (unormalized) cumulative for current interval */ + const double delta_omega = 2*PI*sin((theta1+theta2)*0.5)*dtheta_curr; + const double f2 = eval(rdgfa, theta2); + const double tmp = (f1 + f2) * 0.5 * delta_omega; + cdf[i] = (i == 0 ? tmp : tmp + cdf[i-1]); + + /* Go to the next interval */ + f1 = f2; + theta1 = theta2; + } + + /* Save the normamlization factor */ + rdgfa->rcp_normalize_factor = 1.0 / cdf[nintervals_total-1]; + + /* Finally normalize the CDF */ + FOR_EACH(i, 0, nintervals_total) { + cdf[i] *= rdgfa->rcp_normalize_factor; + } + +exit: + return res; +error: + darray_double_clear(&rdgfa->cdf); + goto exit; +} + +/******************************************************************************* + * Private functions + ******************************************************************************/ +static res_T +rdgfa_init(struct mem_allocator* allocator, void* phase) +{ + struct rdgfa* rdgfa = phase; + ASSERT(phase); + memset(rdgfa, 0, sizeof(*rdgfa)); + darray_double_init(allocator, &rdgfa->cdf); + return RES_OK; +} + +static void +rdgfa_release(void* phase) +{ + struct rdgfa* rdgfa = phase; + ASSERT(phase); + darray_double_release(&rdgfa->cdf); +} + +static double +rdgfa_eval(void* data, const double wo[3], const double wi[3]) +{ + const struct rdgfa* rdgfa = data; + const double cos_theta = d3_dot(wo, wi); + const double theta = acos(cos_theta); + return eval2(data, theta, cos_theta) * rdgfa->rcp_normalize_factor; +} + +static void +rdgfa_sample + (void* data, + struct ssp_rng* rng, + const double wo[3], + double wi[3], + double* pdf) +{ + struct rdgfa* rdgfa = data; + const double* cdf = NULL; + double* find = NULL; + double frame[9]; + double w[3]; + double theta1; + double theta2; + double theta; + double sin_theta; + double phi; + double r; + double u; + size_t n; + size_t i; + ASSERT(data && rng && wo && wi); + + /* Fetch the CDF array and its number of entries */ + cdf = darray_double_cdata_get(&rdgfa->cdf); + n = darray_double_size_get(&rdgfa->cdf); + + /* Sample the CDF */ + r = ssp_rng_canonical(rng); + find = search_lower_bound(&r, cdf, n, sizeof(double), cmp_dbl); + ASSERT(find && (*find) >= r); + i = (size_t)(find - cdf); + + /* Compute the angle interval into which the sample lies */ + if(i < rdgfa->nintervals[0]) { + theta1 = rdgfa->dtheta[0] * (double)(i + 0); + theta2 = rdgfa->dtheta[0] * (double)(i + 1); + } else { + theta1 = rdgfa->dtheta[1] * (double)(i - rdgfa->nintervals[0] + 0); + theta2 = rdgfa->dtheta[1] * (double)(i - rdgfa->nintervals[0] + 1); + theta1 = theta1 + rdgfa->theta_limit; + theta2 = theta2 + rdgfa->theta_limit; + } + + /* Compute the sampled theta angle by linearly interpolate it from the + * boundaries of its interval */ + if(i == 0) { + u = r / cdf[0]; + } else { + u = (r - cdf[i-1]) / (cdf[i] - cdf[i-1]); + } + ASSERT(0 <= u && u < 1); + theta = u * (theta2 - theta1) + theta1; + + /* Uniformly sample a phi angle in [0, 2PI[ */ + phi = ssp_rng_uniform_double(rng, 0, 2*PI); + sin_theta = sin(theta); + + /* Compute the cartesian coordinates of the sampled direction in the _local_ + * phase function space */ + wi[0] = cos(phi) * sin_theta; + wi[1] = sin(phi) * sin_theta; + wi[2] = cos(theta); + + /* Compute the transformation matrix from local phase function to world + * space. Note that by convention, in Star-SF the directions point outward + * the scattering position. Revert 'wo' to match the convention used by the + * previous computations */ + d33_basis(frame, d3_minus(w, wo)); + d33_muld3(wi, frame, wi); + + if(pdf) *pdf = rdgfa_eval(rdgfa, wo, wi); +} + +/******************************************************************************* + * Exported symbols + ******************************************************************************/ +const struct ssf_phase_type ssf_phase_rdgfa = { + rdgfa_init, + rdgfa_release, + rdgfa_sample, + rdgfa_eval, + rdgfa_eval, + sizeof(struct rdgfa), + ALIGNOF(struct rdgfa) +}; + +res_T +ssf_phase_rdgfa_setup + (struct ssf_phase* phase, + const struct ssf_phase_rdgfa_setup_args* args) +{ + struct rdgfa* rdgfa = NULL; + double lambda, Df, Rg, k, k2; + res_T res = RES_OK; + + if(!phase || !check_phase_rdgfa_setup_args(args)) { + res = RES_OK; + goto error; + } + + rdgfa = phase->data; + rdgfa->wavelength = args->wavelength; + rdgfa->fractal_dimension = args->fractal_dimension; + rdgfa->gyration_radius = args->gyration_radius; + rdgfa->nintervals[0] = args->nintervals[0]; + + /* Fetch input data */ + lambda = rdgfa->wavelength; + Df = rdgfa->fractal_dimension; + Rg = rdgfa->gyration_radius; + + /* Precompute constants */ + rdgfa->Rg2 = Rg*Rg; /* gyration_radius^2 */ + rdgfa->cst_4pi_div_lambda = 4*PI / lambda; + rdgfa->cst_3Df_div_2E = 3*Df/(2.0*EXP1); + + /* Precompute the function g */ + k = (2.0*PI) / lambda; + k2 = k*k; + rdgfa->g = pow(1 + 4*k2*rdgfa->Rg2/(3*Df), -Df*0.5); + + /* Precompute the phase function cumulative */ + res = compute_cumulative(rdgfa); + if(res != RES_OK) goto error; + +exit: + return res; +error: + goto exit; +}