htrdr

Solving radiative transfer in heterogeneous media
git clone git://git.meso-star.fr/htrdr.git
Log | Files | Refs | README | LICENSE

commit 90b51beaeb6807ef7958ec656c08b808e61dd633
parent 3f539eecb55a386a6de92a68178d7009fba9d01d
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Mon, 20 Apr 2020 14:54:47 +0200

Add support of continue LW/SW sampling

Diffstat:
Dsrc/:w | 457-------------------------------------------------------------------------------
Msrc/htrdr_cie_xyz.c | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/htrdr_cie_xyz.h | 6+++---
Msrc/htrdr_draw_radiance.c | 22+++++++++++++---------
Msrc/htrdr_ran_lw.c | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/htrdr_ran_lw.h | 14+++++++++++---
6 files changed, 313 insertions(+), 553 deletions(-)

diff --git a/src/:w b/src/:w @@ -1,457 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * 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 "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_interface.h" -#include "htrdr_ground.h" -#include "htrdr_solve.h" -#include "htrdr_sun.h" - -#include <high_tune/htsky.h> - -#include <star/s3d.h> -#include <star/ssf.h> -#include <star/ssp.h> -#include <star/svx.h> - -#include <rsys/double2.h> -#include <rsys/float2.h> -#include <rsys/float3.h> - -struct scattering_context { - struct ssp_rng* rng; - const struct htsky* sky; - size_t iband; /* Index of the spectral band */ - size_t iquad; /* Index of the quadrature point into the band */ - - double Ts; /* Sampled optical thickness */ - double traversal_dst; /* Distance traversed along the ray */ -}; -static const struct scattering_context SCATTERING_CONTEXT_NULL = { - NULL, NULL, 0, 0, 0, 0 -}; - -struct transmissivity_context { - struct ssp_rng* rng; - const struct htsky* sky; - size_t iband; /* Index of the spectrald */ - size_t iquad; /* Index of the quadrature point into the band */ - - double Ts; /* Sampled optical thickness */ - double Tmin; /* Minimal optical thickness */ - double traversal_dst; /* Distance traversed along the ray */ - - enum htsky_property prop; -}; -static const struct transmissivity_context TRANSMISSION_CONTEXT_NULL = { - NULL, NULL, 0, 0, 0, 0, 0, 0 -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static int -scattering_hit_filter - (const struct svx_hit* hit, - const double org[3], - const double dir[3], - const double range[2], - void* context) -{ - struct scattering_context* ctx = context; - double ks_max; - int pursue_traversal = 1; - ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); - (void)range; - - ks_max = htsky_fetch_svx_voxel_property(ctx->sky, HTSKY_Ks, - HTSKY_SVX_MAX, HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, &hit->voxel); - - ctx->traversal_dst = hit->distance[0]; - - /* Iterate until a collision occurs into the voxel or until the ray - * does not collide the voxel */ - for(;;) { - /* Compute tau for the current leaf */ - const double vox_dst = hit->distance[1] - ctx->traversal_dst; - const double T = vox_dst * ks_max; - - /* A collision occurs behind `vox_dst' */ - if(ctx->Ts > T) { - ctx->Ts -= T; - ctx->traversal_dst = hit->distance[1]; - pursue_traversal = 1; - break; - - /* A real/null collision occurs before `vox_dst' */ - } else { - double pos[3]; - double proba; - double ks; - const double collision_dst = ctx->Ts / ks_max; - - /* Compute the traversed distance up to the challenged collision */ - ctx->traversal_dst += collision_dst; - ASSERT(ctx->traversal_dst >= hit->distance[0]); - ASSERT(ctx->traversal_dst <= hit->distance[1]); - - /* Compute the world space position where a collision may occur */ - pos[0] = org[0] + ctx->traversal_dst * dir[0]; - pos[1] = org[1] + ctx->traversal_dst * dir[1]; - pos[2] = org[2] + ctx->traversal_dst * dir[2]; - - ks = htsky_fetch_raw_property(ctx->sky, HTSKY_Ks, - HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); - - /* Handle the case that ks_max is not *really* the max */ - proba = ks / ks_max; - - if(ssp_rng_canonical(ctx->rng) < proba) {/* Collide <=> real scattering */ - pursue_traversal = 0; - break; - } else { /* Null collision */ - ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ - } - } - } - return pursue_traversal; -} - -static int -transmissivity_hit_filter - (const struct svx_hit* hit, - const double org[3], - const double dir[3], - const double range[2], - void* context) -{ - struct transmissivity_context* ctx = context; - int comp_mask = HTSKY_CPNT_MASK_ALL; - double k_max; - double k_min; - int pursue_traversal = 1; - ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); - (void)range; - - k_min = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop, - HTSKY_SVX_MIN, comp_mask, ctx->iband, ctx->iquad, &hit->voxel); - k_max = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop, - HTSKY_SVX_MAX, comp_mask, ctx->iband, ctx->iquad, &hit->voxel); - ASSERT(k_min <= k_max); - - ctx->Tmin += (hit->distance[1] - hit->distance[0]) * k_min; - ctx->traversal_dst = hit->distance[0]; - - /* Iterate until a collision occurs into the voxel or until the ray - * does not collide the voxel */ - for(;;) { - const double vox_dst = hit->distance[1] - ctx->traversal_dst; - const double Tdif = vox_dst * (k_max-k_min); - - /* A collision occurs behind `vox_dst' */ - if(ctx->Ts > Tdif) { - ctx->Ts -= Tdif; - ctx->traversal_dst = hit->distance[1]; - pursue_traversal = 1; - break; - - /* A real/null collision occurs before `vox_dst' */ - } else { - double x[3]; - double k; - double proba; - double collision_dst = ctx->Ts / (k_max - k_min); - - /* Compute the traversed distance up to the challenged collision */ - ctx->traversal_dst += collision_dst; - ASSERT(ctx->traversal_dst >= hit->distance[0]); - ASSERT(ctx->traversal_dst <= hit->distance[1]); - - /* Compute the world space position where a collision may occur */ - x[0] = org[0] + ctx->traversal_dst * dir[0]; - x[1] = org[1] + ctx->traversal_dst * dir[1]; - x[2] = org[2] + ctx->traversal_dst * dir[2]; - - k = htsky_fetch_raw_property(ctx->sky, ctx->prop, - comp_mask, ctx->iband, ctx->iquad, x, k_min, k_max); - ASSERT(k >= k_min && k <= k_max); - - proba = (k - k_min) / (k_max - k_min); - - if(ssp_rng_canonical(ctx->rng) < proba) { /* Collide */ - pursue_traversal = 0; - break; - } else { /* Null collision */ - ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ - } - } - } - return pursue_traversal; -} - -static double -transmissivity - (struct htrdr* htrdr, - struct ssp_rng* rng, - const enum htsky_property prop, - const size_t iband, - const size_t iquad, - const double pos[3], - const double dir[3], - const double range[2]) -{ - struct svx_hit svx_hit; - struct transmissivity_context transmissivity_ctx = TRANSMISSION_CONTEXT_NULL; - - ASSERT(htrdr && rng && pos && dir && range); - - transmissivity_ctx.rng = rng; - transmissivity_ctx.sky = htrdr->sky; - transmissivity_ctx.iband = iband; - transmissivity_ctx.iquad = iquad; - transmissivity_ctx.Ts = ssp_ran_exp(rng, 1); /* Sample an optical thickness */ - transmissivity_ctx.prop = prop; - - /* Compute the transmissivity */ - HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL, - transmissivity_hit_filter, &transmissivity_ctx, iband, iquad, &svx_hit)); - - if(SVX_HIT_NONE(&svx_hit)) { - return transmissivity_ctx.Tmin ? exp(-transmissivity_ctx.Tmin) : 1.0; - } else { - return 0; - } -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -double -htrdr_compute_radiance_sw - (struct htrdr* htrdr, - const size_t ithread, - struct ssp_rng* rng, - const double pos_in[3], - const double dir_in[3], - const double wlen, /* In nanometer */ - const size_t iband, - const size_t iquad) -{ - struct s3d_hit s3d_hit = S3D_HIT_NULL; - struct s3d_hit s3d_hit_tmp = S3D_HIT_NULL; - struct s3d_hit s3d_hit_prev = S3D_HIT_NULL; - struct svx_hit svx_hit = SVX_HIT_NULL; - struct ssf_phase* phase_hg = NULL; - struct ssf_phase* phase_rayleigh = NULL; - - double pos[3]; - double dir[3]; - double range[2]; - double pos_next[3]; - double dir_next[3]; - double band_bounds[2]; /* In nanometers */ - - double wlen; - double R; - double r; /* Random number */ - double wo[3]; /* -dir */ - double pdf; - double sun_solid_angle; - double Tr; /* Overall transmissivity */ - double Tr_abs; /* Absorption transmissivity */ - double L_sun; /* Sun radiance in W.m^-2.sr^-1 */ - double sun_dir[3]; - double ksi = 1; /* Throughput */ - double w = 0; /* MC weight */ - double g = 0; /* Asymmetry parameter of the HG phase function */ - - ASSERT(htrdr && rng && pos_in && dir_in && ithread < htrdr->nthreads); - ASSERT(!htsky_is_long_wave(htrdr->sky)); - - CHK(RES_OK == ssf_phase_create - (&htrdr->lifo_allocators[ithread], &ssf_phase_hg, &phase_hg)); - CHK(RES_OK == ssf_phase_create - (&htrdr->lifo_allocators[ithread], &ssf_phase_rayleigh, &phase_rayleigh)); - - /* Setup the phase function for this spectral band & quadrature point */ - g = htsky_fetch_particle_phase_function_asymmetry_parameter - (htrdr->sky, iband, iquad); - SSF(phase_hg_setup(phase_hg, g)); - - /* Fetch sun properties. Note that the sun spectral data are defined by bands - * that, actually are the same of the SW spectral bands defined in the - * default "ecrad_opt_prot.txt" file provided by the HTGOP project. */ - htsky_get_spectral_band_bounds(htrdr->sky, iband, band_bounds); - ASSERT(band_bounds[0] <= wlen && wlen <= band_bounds[1]); - sun_solid_angle = htrdr_sun_get_solid_angle(htrdr->sun); - L_sun = htrdr_sun_get_radiance(htrdr->sun, wlen); - - d3_set(pos, pos_in); - d3_set(dir, dir_in); - - /* Add the directly contribution of the sun */ - if(htrdr_sun_is_dir_in_solar_cone(htrdr->sun, dir)) { - /* Add the direct contribution of the sun */ - d2(range, 0, FLT_MAX); - - /* Check that the ray is not occlude along the submitted range */ - HTRDR(ground_trace_ray(htrdr->ground, pos, dir, range, NULL, &s3d_hit_tmp)); - if(!S3D_HIT_NONE(&s3d_hit_tmp)) { - Tr = 0; - } else { - Tr = transmissivity - (htrdr, rng, HTSKY_Kext, iband, iquad , pos, dir, range); - w = L_sun * Tr; - } - } - - /* Radiative random walk */ - for(;;) { - struct scattering_context scattering_ctx = SCATTERING_CONTEXT_NULL; - double bounce_reflectivity = 1; - - /* Find the first intersection with a surface */ - d2(range, 0, DBL_MAX); - HTRDR(ground_trace_ray - (htrdr->ground, pos, dir, range, &s3d_hit_prev, &s3d_hit)); - - /* Sample an optical thickness */ - scattering_ctx.Ts = ssp_ran_exp(rng, 1); - - /* Setup the remaining scattering context fields */ - scattering_ctx.rng = rng; - scattering_ctx.sky = htrdr->sky; - scattering_ctx.iband = iband; - scattering_ctx.iquad = iquad; - - /* Define if a scattering event occurs */ - d2(range, 0, s3d_hit.distance); - HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL, - scattering_hit_filter, &scattering_ctx, iband, iquad, &svx_hit)); - - /* No scattering and no surface reflection. Stop the radiative random walk */ - if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) { - break; - } - ASSERT(SVX_HIT_NONE(&svx_hit) - || ( svx_hit.distance[0] <= scattering_ctx.traversal_dst - && svx_hit.distance[1] >= scattering_ctx.traversal_dst)); - - /* Negate the incoming dir to match the convention of the SSF library */ - d3_minus(wo, dir); - - /* Compute the new position */ - pos_next[0] = pos[0] + dir[0]*scattering_ctx.traversal_dst; - pos_next[1] = pos[1] + dir[1]*scattering_ctx.traversal_dst; - pos_next[2] = pos[2] + dir[2]*scattering_ctx.traversal_dst; - - /* Define the absorption transmissivity from the current position to the - * next position */ - d2(range, 0, scattering_ctx.traversal_dst); - Tr_abs = transmissivity - (htrdr, rng, HTSKY_Ka, iband, iquad, pos, dir, range); - if(Tr_abs <= 0) break; - - /* Sample a sun direction */ - htrdr_sun_sample_direction(htrdr->sun, rng, sun_dir); - d2(range, 0, FLT_MAX); - - s3d_hit_prev = SVX_HIT_NONE(&svx_hit) ? s3d_hit : S3D_HIT_NULL; - - /* Check that the sun is visible from the new position */ - HTRDR(ground_trace_ray - (htrdr->ground, pos_next, sun_dir, range, &s3d_hit_prev, &s3d_hit_tmp)); - - /* Compute the sun transmissivity */ - if(!S3D_HIT_NONE(&s3d_hit_tmp)) { - Tr = 0; - } else { - Tr = transmissivity - (htrdr, rng, HTSKY_Kext, iband, iquad, pos_next, sun_dir, range); - } - - /* Scattering at a surface */ - if(SVX_HIT_NONE(&svx_hit)) { - struct htrdr_interface interf = HTRDR_INTERFACE_NULL; - struct ssf_bsdf* bsdf = NULL; - double N[3]; - int type; - - /* Fetch the hit interface and build its BSDF */ - htrdr_ground_get_interface(htrdr->ground, &s3d_hit, &interf); - HTRDR(interface_create_bsdf - (htrdr, &interf, ithread, wlen, pos_next, dir, rng, &s3d_hit, &bsdf)); - - d3_normalize(N, d3_set_f3(N, s3d_hit.normal)); - if(d3_dot(N, wo) < 0) d3_minus(N, N); - - bounce_reflectivity = ssf_bsdf_sample - (bsdf, rng, wo, N, dir_next, &type, &pdf); - if(!(type & SSF_REFLECTION)) { /* Handle only reflections */ - bounce_reflectivity = 0; - } - - if(d3_dot(N, sun_dir) < 0) { /* Below the ground */ - R = 0; - } else { - R = ssf_bsdf_eval(bsdf, wo, N, sun_dir) * d3_dot(N, sun_dir); - } - - /* Release the BSDF */ - SSF(bsdf_ref_put(bsdf)); - - /* Scattering in the medium */ - } else { - struct ssf_phase* phase; - double ks_particle; /* Scattering coefficient of the particles */ - double ks_gas; /* Scattering coefficient of the gaz */ - double ks; /* Overall scattering coefficient */ - - ks_gas = htsky_fetch_raw_property(htrdr->sky, HTSKY_Ks, - HTSKY_CPNT_FLAG_GAS, iband, iquad, pos_next, -DBL_MAX, DBL_MAX); - ks_particle = htsky_fetch_raw_property(htrdr->sky, HTSKY_Ks, - HTSKY_CPNT_FLAG_PARTICLES, iband, iquad, pos_next, -DBL_MAX, DBL_MAX); - ks = ks_particle + ks_gas; - - r = ssp_rng_canonical(rng); - if(r < ks_gas / ks) { /* Gas scattering */ - phase = phase_rayleigh; - } else { /* Cloud scattering */ - phase = phase_hg; - } - - ssf_phase_sample(phase, rng, wo, dir_next, NULL); - R = ssf_phase_eval(phase, wo, sun_dir); - } - - /* Update the MC weight */ - ksi *= Tr_abs; - w += ksi * L_sun * sun_solid_angle * Tr * R; - - /* Russian roulette wrt surface scattering */ - if(SVX_HIT_NONE(&svx_hit) && ssp_rng_canonical(rng) >= bounce_reflectivity) - break; - - /* Setup the next random walk state */ - d3_set(pos, pos_next); - d3_set(dir, dir_next); - } - SSF(phase_ref_put(phase_hg)); - SSF(phase_ref_put(phase_rayleigh)); - return w; -} - diff --git a/src/htrdr_cie_xyz.c b/src/htrdr_cie_xyz.c @@ -100,26 +100,66 @@ sample_cie_xyz (const struct htrdr_cie_xyz* cie, const double* cdf, const size_t cdf_length, - const double r) /* Canonical number in [0, 1[ */ + double (*f_bar)(const double lambda), /* Function to integrate */ + const double r0, /* Canonical number in [0, 1[ */ + const double r1) /* Canonical number in [0, 1[ */ { - double r_next = nextafter(r, DBL_MAX); + double r0_next = nextafter(r0, DBL_MAX); double* find; - double wlen; - size_t i; - ASSERT(cie && cdf && cdf_length && r >= 0 && r < 1); + double f_min, f_max; /* CIE 1931 value for the band boundaries */ + double lambda; /* Sampled wavelength */ + double lambda_min, lambda_max; /* Boundaries of the sampled band */ + double lambda_1, lambda_2; /* Solutions if the equation to solve */ + double a, b, c, d; /* Equation parameters */ + double delta, sqrt_delta; + size_t iband; /* Index of the sampled band */ + ASSERT(cie && cdf && cdf_length); + ASSERT(0 <= r0 && r0 < 1); + ASSERT(0 <= r1 && r1 < 1); /* Use r_next rather than r in order to find the first entry that is not less * than *or equal* to r */ - find = search_lower_bound(&r_next, cdf, cdf_length, sizeof(double), cmp_dbl); + find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); ASSERT(find); - i = (size_t)(find - cdf); - ASSERT(i < cdf_length && cdf[i] > r && (!i || cdf[i-1] <= r)); + /* Define and check the sampled band */ + iband = (size_t)(find - cdf); + ASSERT(iband < cdf_length); + ASSERT(cdf[iband] > r0 && (!iband || cdf[iband-1] <= r0)); + + /* Define the boundaries of the sampled band */ + lambda_min = cie->range[0] + cie->band_len * (double)iband; + lambda_max = lambda_min + cie->band_len; + + /* Define the value of the CIE 1931 function for the boudaries of the sampled + * band */ + f_min = f_bar(lambda_min); + f_max = f_bar(lambda_max); + + /* Compute the equation constants */ + a = 0.5 * (f_max - f_min) / cie->band_len; + b = (lambda_max * f_min - lambda_min * f_max) / cie->band_len; + c = -lambda_min * f_min + lambda_min*lambda_min * a; + d = 0.5 * (f_max + f_min) * cie->band_len; + + delta = b*b - 4*a*(c-d*r1); + ASSERT(delta > 0); + sqrt_delta = sqrt(delta); + + /* Compute the roots that solve the equation */ + lambda_1 = (-b - sqrt_delta) / (2*a); + lambda_2 = (-b + sqrt_delta) / (2*a); + + /* Select the solution */ + if(lambda_min <= lambda_1 && lambda_1 < lambda_max) { + lambda = lambda_1; + } else if(lambda_min <= lambda_2 && lambda_2 < lambda_max) { + lambda = lambda_2; + } else { + FATAL("Unexpected error.\n"); + } - /* Return the wavelength at the center of the sampled band */ - wlen = cie->range[0] + cie->band_len * ((double)i + 0.5); - ASSERT(cie->range[0] < wlen && wlen < cie->range[1]); - return wlen; + return lambda; } static res_T @@ -278,23 +318,32 @@ htrdr_cie_xyz_ref_put(struct htrdr_cie_xyz* cie) } double -htrdr_cie_xyz_sample_X(struct htrdr_cie_xyz* cie, const double r) +htrdr_cie_xyz_sample_X + (struct htrdr_cie_xyz* cie, + const double r0, + const double r1) { return sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_X), - darray_double_size_get(&cie->cdf_X), r); + darray_double_size_get(&cie->cdf_X), fit_x_bar_1931, r0, r1); } double -htrdr_cie_xyz_sample_Y(struct htrdr_cie_xyz* cie, const double r) +htrdr_cie_xyz_sample_Y + (struct htrdr_cie_xyz* cie, + const double r0, + const double r1) { return sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Y), - darray_double_size_get(&cie->cdf_Y), r); + darray_double_size_get(&cie->cdf_Y), fit_y_bar_1931, r0, r1); } double -htrdr_cie_xyz_sample_Z(struct htrdr_cie_xyz* cie, const double r) +htrdr_cie_xyz_sample_Z + (struct htrdr_cie_xyz* cie, + const double r0, + const double r1) { return sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Z), - darray_double_size_get(&cie->cdf_Z), r); + darray_double_size_get(&cie->cdf_Z), fit_z_bar_1931, r0, r1); } diff --git a/src/htrdr_cie_xyz.h b/src/htrdr_cie_xyz.h @@ -43,19 +43,19 @@ htrdr_cie_xyz_ref_put extern LOCAL_SYM double htrdr_cie_xyz_sample_X (struct htrdr_cie_xyz* cie, - const double r); /* Canonical number in [0, 1[ */ + const double r0, const double r1); /* Canonical numbers in [0, 1[ */ /* Return a wavelength in nanometer */ extern LOCAL_SYM double htrdr_cie_xyz_sample_Y (struct htrdr_cie_xyz* cie, - const double r); /* Canonical number in [0, 1[ */ + const double r0, const double r1); /* Canonical number in [0, 1[ */ /* Return a wavelength in nanometer */ extern LOCAL_SYM double htrdr_cie_xyz_sample_Z (struct htrdr_cie_xyz* cie, - const double r); /* Canonical number in [0, 1[ */ + const double r0, const double r1); /* Canonical number in [0, 1[ */ #endif /* HTRDR_cie_xyz_H */ diff --git a/src/htrdr_draw_radiance.c b/src/htrdr_draw_radiance.c @@ -512,7 +512,7 @@ draw_pixel_sw double ray_org[3]; double ray_dir[3]; double weight; - double r0, r1; + double r0, r1, r2; double wlen; /* Sampled wavelength into the spectral band */ size_t iband; /* Sampled spectral band */ size_t iquad; /* Sampled quadrature point into the spectral band */ @@ -531,17 +531,18 @@ draw_pixel_sw r0 = ssp_rng_canonical(rng); r1 = ssp_rng_canonical(rng); + r2 = ssp_rng_canonical(rng); /* Sample a spectral band and a quadrature point */ switch(ichannel) { - case 0: wlen = htrdr_cie_xyz_sample_X(htrdr->cie, r0); break; - case 1: wlen = htrdr_cie_xyz_sample_Y(htrdr->cie, r0); break; - case 2: wlen = htrdr_cie_xyz_sample_Z(htrdr->cie, r0); break; + case 0: wlen = htrdr_cie_xyz_sample_X(htrdr->cie, r0, r1); break; + case 1: wlen = htrdr_cie_xyz_sample_Y(htrdr->cie, r0, r1); break; + case 2: wlen = htrdr_cie_xyz_sample_Z(htrdr->cie, r0, r1); break; default: FATAL("Unreachable code.\n"); break; } iband = htsky_find_spectral_band(htrdr->sky, wlen); - iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r1, iband); + iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r2, iband); /* Compute the luminance */ weight = htrdr_compute_radiance_sw @@ -604,9 +605,9 @@ draw_pixel_lw size_t iband; size_t iquad; double usec; - double pdf; + double band_pdf; - /* Begin the registration of the time spent to in the realisation */ + /* Begin the registration of the time spent in the realisation */ time_current(&t0); /* Sample a position into the pixel, in the normalized image plane */ @@ -621,16 +622,19 @@ draw_pixel_lw r1 = ssp_rng_canonical(rng); /* Sample a wavelength */ - wlen = htrdr_ran_lw_sample(htrdr->ran_lw, r0, &pdf); + wlen = htrdr_ran_lw_sample(htrdr->ran_lw, r0); /* Select the associated band and sample a quadrature point */ iband = htsky_find_spectral_band(htrdr->sky, wlen); iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r1, iband); + /* Retrieve the PDF to sample this sky band */ + band_pdf = htrdr_ran_lw_get_sky_band_pdf(htrdr->ran_lw, iband); + /* Compute the luminance */ weight = htrdr_compute_radiance_lw (htrdr, ithread, rng, ray_org, ray_dir, wlen, iband, iquad); - weight /= pdf; + weight /= band_pdf; ASSERT(weight >= 0); /* End the registration of the per realisation time */ diff --git a/src/htrdr_ran_lw.c b/src/htrdr_ran_lw.c @@ -19,6 +19,8 @@ #include "htrdr_c.h" #include "htrdr_ran_lw.h" +#include <high_tune/htsky.h> + #include <rsys/algorithm.h> #include <rsys/cstr.h> #include <rsys/dynamic_array_double.h> @@ -29,9 +31,12 @@ struct htrdr_ran_lw { struct darray_double cdf; - struct darray_double pdf; + /* Probas to sample a sky band overlapped by the range */ + struct darray_double sky_bands_pdf; double range[2]; /* Boundaries of the handled CIE XYZ color space */ double band_len; /* Length in nanometers of a band */ + double ref_temperature; /* In Kelvin */ + size_t nbands; /* # bands */ struct htrdr* htrdr; ref_T ref; @@ -41,24 +46,18 @@ struct htrdr_ran_lw { * Helper functions ******************************************************************************/ static res_T -setup_ran_lw +setup_ran_lw_cdf (struct htrdr_ran_lw* ran_lw, - const char* func_name, - const double range[2], - const size_t nbands, - const double ref_temperature) + const char* func_name) { double* pdf = NULL; double* cdf = NULL; double sum = 0; size_t i; res_T res = RES_OK; - ASSERT(ran_lw && func_name && range && range && ref_temperature); - ASSERT(range[0] >= 1000); - ASSERT(range[1] <= 100000); - ASSERT(range[0] < range[1]); + ASSERT(ran_lw && func_name && ran_lw->nbands != HTRDR_RAN_LW_CONTINUE); - res = darray_double_resize(&ran_lw->cdf, nbands); + res = darray_double_resize(&ran_lw->cdf, ran_lw->nbands); if(res != RES_OK) { htrdr_log_err(ran_lw->htrdr, "%s: Error allocating the CDF of the long wave spectral bands -- %s.\n", @@ -66,23 +65,12 @@ setup_ran_lw goto error; } - res = darray_double_resize(&ran_lw->pdf, nbands); - if(res != RES_OK) { - htrdr_log_err(ran_lw->htrdr, - "%s: Error allocating the PDF of the long wave spectral bands -- %s.\n", - func_name, res_to_cstr(res)); - goto error; - } - cdf = darray_double_data_get(&ran_lw->cdf); - pdf = darray_double_data_get(&ran_lw->pdf); + pdf = cdf; /* Alias the CDF by the PDF since only one array is necessary */ /* Compute the *unormalized* probability to sample a long wave band */ - ran_lw->range[0] = range[0]; - ran_lw->range[1] = range[1]; - ran_lw->band_len = (range[1] - range[0]) / (double)nbands; - FOR_EACH(i, 0, nbands) { - double lambda_lo = range[0] + (double)i * ran_lw->band_len; + FOR_EACH(i, 0, ran_lw->nbands) { + double lambda_lo = ran_lw->range[0] + (double)i * ran_lw->band_len; double lambda_hi = lambda_lo + ran_lw->band_len; ASSERT(lambda_lo <= lambda_hi); @@ -91,14 +79,14 @@ setup_ran_lw lambda_hi *= 1.e-9; /* Compute the probability of the current band */ - pdf[i] = planck(lambda_lo, lambda_hi, ref_temperature); + pdf[i] = planck(lambda_lo, lambda_hi, ran_lw->ref_temperature); /* Update the norm */ sum += pdf[i]; } /* Compute the cumulative of the previously computed probabilities */ - FOR_EACH(i, 0, nbands) { + FOR_EACH(i, 0, ran_lw->nbands) { /* Normalize the probability */ pdf[i] /= sum; @@ -110,17 +98,170 @@ setup_ran_lw ASSERT(cdf[i] >= cdf[i-1]); } } - ASSERT(eq_eps(cdf[nbands-1], 1, 1.e-6)); - cdf[nbands - 1] = 1.0; /* Handle numerical issue */ + ASSERT(eq_eps(cdf[ran_lw->nbands-1], 1, 1.e-6)); + cdf[ran_lw->nbands - 1] = 1.0; /* Handle numerical issue */ exit: return res; error: darray_double_clear(&ran_lw->cdf); - darray_double_clear(&ran_lw->pdf); goto exit; } +static res_T +compute_sky_bands_pdf(struct htrdr_ran_lw* ran_lw, const char* func_name) +{ + double* pdf = NULL; + double sum = 0; + size_t i; + size_t nbands; + res_T res = RES_OK; + ASSERT(ran_lw && htsky_is_long_wave(ran_lw->htrdr->sky) && func_name); + + nbands = htsky_get_spectral_bands_count(ran_lw->htrdr->sky); + + res = darray_double_resize(&ran_lw->sky_bands_pdf, nbands); + if(res != RES_OK) { + htrdr_log_err(ran_lw->htrdr, + "%s: error allocating the PDF of the long wave spectral bands " + "of the sky -- %s.\n", + func_name, res_to_cstr(res)); + goto error; + } + + pdf = darray_double_data_get(&ran_lw->sky_bands_pdf); + + /* Compute the *unormalized* probability to sample a long wave band */ + sum = 0; + FOR_EACH(i, 0, nbands) { + const size_t iband = htsky_get_spectral_band_id(ran_lw->htrdr->sky, i); + double wlens[2]; + HTSKY(get_spectral_band_bounds(ran_lw->htrdr->sky, iband, wlens)); + + /* Convert from nanometer to meter */ + wlens[0] = wlens[0] * 1.e-9; + wlens[1] = wlens[1] * 1.e-9; + + /* Compute the probability of the current band */ + pdf[i] = planck(wlens[0], wlens[1], ran_lw->ref_temperature); + + /* Update the norm */ + sum += pdf[i]; + } + + /* Normalize the probabilities */ + FOR_EACH(i, 0, nbands) pdf[i] /= sum; + +exit: + return res; +error: + darray_double_clear(&ran_lw->sky_bands_pdf); + goto exit; + +} + +static double +ran_lw_sample_discreet + (const struct htrdr_ran_lw* ran_lw, + const double r, + const char* func_name) +{ + const double* cdf = NULL; + const double* find = NULL; + double r_next = nextafter(r, DBL_MAX); + double wlen = 0; + size_t cdf_length = 0; + size_t i; + ASSERT(ran_lw && ran_lw->nbands != HTRDR_RAN_LW_CONTINUE); + ASSERT(0 <= r && r < 1); + (void)func_name; + + cdf = darray_double_cdata_get(&ran_lw->cdf); + cdf_length = darray_double_size_get(&ran_lw->cdf); + ASSERT(cdf_length > 0); + + /* Use r_next rather than r in order to find the first entry that is not less + * than *or equal* to r */ + find = search_lower_bound(&r_next, cdf, cdf_length, sizeof(double), cmp_dbl); + ASSERT(find); + + i = (size_t)(find - cdf); + ASSERT(i < cdf_length && cdf[i] > r && (!i || cdf[i-1] <= r)); + + /* Return the wavelength at the center of the sampled band */ + wlen = ran_lw->range[0] + ran_lw->band_len * ((double)i + 0.5); + ASSERT(ran_lw->range[0] < wlen && wlen < ran_lw->range[1]); + + return wlen; +} + +static double +ran_lw_sample_continue + (const struct htrdr_ran_lw* ran_lw, + const double r, + const char* func_name) +{ + /* Numerical parameters of the dichotomy search */ + const size_t MAX_ITER = 100; + const double EPSILON_LAMBDA = 1e-6; + const double EPSILON_BF = 1e-6; + + /* Local variables */ + double bf = 0; + double bf_prev = 0; + double bf_min_max = 0; + double lambda_m = 0; + double lambda_m_prev = 0; + double lambda_m_min = 0; + double lambda_m_max = 0; + double range_m[2] = {0, 0}; + size_t i; + + /* Check precondition */ + ASSERT(ran_lw && ran_lw->nbands == HTRDR_RAN_LW_CONTINUE && func_name); + ASSERT(0 <= r && r < 1); + + /* Convert the wavelength range in meters */ + range_m[0] = ran_lw->range[0] / 1.0e9; + range_m[1] = ran_lw->range[1] / 1.0e9; + + /* Setup the dichotomy search */ + lambda_m_min = range_m[0]; + lambda_m_max = range_m[1]; + bf_min_max = blackbody_fraction + (range_m[0], range_m[1], ran_lw->ref_temperature); + + /* Numerically search the lambda corresponding to the submitted canonical + * number */ + FOR_EACH(i, 0, MAX_ITER) { + double r_test; + lambda_m = (lambda_m_min + lambda_m_max) * 0.5; + bf = blackbody_fraction(range_m[0], lambda_m, ran_lw->ref_temperature); + + r_test = bf / bf_min_max; + if(r_test < r) { + lambda_m_min = lambda_m; + } else { + lambda_m_max = lambda_m; + } + + if(fabs(lambda_m_prev - lambda_m) < EPSILON_LAMBDA + || fabs(bf_prev - bf) < EPSILON_BF) + break; + + lambda_m_prev = lambda_m; + bf_prev = bf; + } + if(i >= MAX_ITER) { + htrdr_log_warn(ran_lw->htrdr, + "%s: could not sample a wavelength in the range [%g, %g] nanometers " + "for the reference temperature %g Kelvin.\n", + func_name, SPLIT2(ran_lw->range), ran_lw->ref_temperature); + } + + return lambda_m * 1.0e9; /* Convert in nanometers */ +} + static void release_ran_lw(ref_T* ref) { @@ -128,7 +269,7 @@ release_ran_lw(ref_T* ref) ASSERT(ref); ran_lw = CONTAINER_OF(ref, struct htrdr_ran_lw, ref); darray_double_release(&ran_lw->cdf); - darray_double_release(&ran_lw->pdf); + darray_double_release(&ran_lw->sky_bands_pdf); MEM_RM(ran_lw->htrdr->allocator, ran_lw); } @@ -139,13 +280,17 @@ res_T htrdr_ran_lw_create (struct htrdr* htrdr, const double range[2], /* Must be included in [1000, 100000] nanometers */ - const size_t nbands, /* # bands used to discretisze the CIE tristimulus */ + const size_t nbands, /* # bands used to discretized the CIE tristimulus */ const double ref_temperature, struct htrdr_ran_lw** out_ran_lw) { struct htrdr_ran_lw* ran_lw = NULL; res_T res = RES_OK; - ASSERT(htrdr && range && nbands && out_ran_lw); + ASSERT(htrdr && range && out_ran_lw && ref_temperature > 0); + ASSERT(ref_temperature > 0); + ASSERT(range[0] >= 1000); + ASSERT(range[1] <= 100000); + ASSERT(range[0] < range[1]); ran_lw = MEM_CALLOC(htrdr->allocator, 1, sizeof(*ran_lw)); if(!ran_lw) { @@ -158,9 +303,22 @@ htrdr_ran_lw_create ref_init(&ran_lw->ref); ran_lw->htrdr = htrdr; darray_double_init(htrdr->allocator, &ran_lw->cdf); - darray_double_init(htrdr->allocator, &ran_lw->pdf); + darray_double_init(htrdr->allocator, &ran_lw->sky_bands_pdf); - res = setup_ran_lw(ran_lw, FUNC_NAME, range, nbands, ref_temperature); + ran_lw->range[0] = range[0]; + ran_lw->range[1] = range[1]; + ran_lw->ref_temperature = ref_temperature; + ran_lw->nbands = nbands; + + if(nbands == HTRDR_RAN_LW_CONTINUE) { + ran_lw->band_len = 0; + } else { + ran_lw->band_len = (range[1] - range[0]) / (double)nbands; + res = setup_ran_lw_cdf(ran_lw, FUNC_NAME); + if(res != RES_OK) goto error; + } + + res = compute_sky_bands_pdf(ran_lw, FUNC_NAME); if(res != RES_OK) goto error; exit: @@ -188,38 +346,36 @@ htrdr_ran_lw_ref_put(struct htrdr_ran_lw* ran_lw) double htrdr_ran_lw_sample (const struct htrdr_ran_lw* ran_lw, - const double r, - double* pdf) + const double r) { - const double* cdf = NULL; - const double* find = NULL; - double r_next = nextafter(r, DBL_MAX); - double wlen = 0; - size_t cdf_length = 0; - size_t i; - ASSERT(ran_lw && 0 <= r && r < 1); - ASSERT(darray_double_size_get(&ran_lw->cdf) - == darray_double_size_get(&ran_lw->pdf)); - - cdf = darray_double_cdata_get(&ran_lw->cdf); - cdf_length = darray_double_size_get(&ran_lw->cdf); - - /* Use r_next rather than r in order to find the first entry that is not less - * than *or equal* to r */ - find = search_lower_bound(&r_next, cdf, cdf_length, sizeof(double), cmp_dbl); - ASSERT(find); + ASSERT(ran_lw); + if(ran_lw->band_len == 0) { + return ran_lw_sample_continue(ran_lw, r, FUNC_NAME); + } else { + return ran_lw_sample_discreet(ran_lw, r, FUNC_NAME); + } +} - i = (size_t)(find - cdf); - ASSERT(i < cdf_length && cdf[i] > r && (!i || cdf[i-1] <= r)); +double +htrdr_ran_lw_get_sky_band_pdf + (const struct htrdr_ran_lw* ran_lw, + const size_t iband) +{ + size_t i; + size_t nbands; + (void)nbands; + ASSERT(ran_lw); - /* Return the wavelength at the center of the sampled band */ - wlen = ran_lw->range[0] + ran_lw->band_len * ((double)i + 0.5); - ASSERT(ran_lw->range[0] < wlen && wlen < ran_lw->range[1]); + nbands = htsky_get_spectral_bands_count(ran_lw->htrdr->sky); + ASSERT(nbands); + ASSERT(iband >= htsky_get_spectral_band_id(ran_lw->htrdr->sky, 0)); + ASSERT(iband <= htsky_get_spectral_band_id(ran_lw->htrdr->sky, nbands-1)); - if(pdf) { - *pdf = darray_double_cdata_get(&ran_lw->pdf)[i]; - } + /* Compute the index of the spectral band */ + i = iband - htsky_get_spectral_band_id(ran_lw->htrdr->sky, 0); - return wlen; + /* Fetch its PDF */ + ASSERT(i < darray_double_size_get(&ran_lw->sky_bands_pdf)); + return darray_double_cdata_get(&ran_lw->sky_bands_pdf)[i]; } diff --git a/src/htrdr_ran_lw.h b/src/htrdr_ran_lw.h @@ -18,6 +18,8 @@ #include <rsys/rsys.h> +#define HTRDR_RAN_LW_CONTINUE 0 + struct htrdr; struct htrdr_ran_lw; @@ -25,7 +27,9 @@ extern LOCAL_SYM res_T htrdr_ran_lw_create (struct htrdr* htrdr, const double range[2], /* Must be included in [1000, 100000] nanometers */ - const size_t nbands, /* # bands used to discretisze the LW domain */ + /* # bands used to discretisze the LW domain. HTRDR_RAN_LW_CONTINUE <=> no + * discretisation */ + const size_t nbands, const double ref_temperature, /* Reference temperature */ struct htrdr_ran_lw** ran_lw); @@ -41,8 +45,12 @@ htrdr_ran_lw_ref_put extern LOCAL_SYM double htrdr_ran_lw_sample (const struct htrdr_ran_lw* ran_lw, - const double r, - double* pdf); /* May be NULL */ + const double r); /* Canonical number in [0, 1[ */ + +extern LOCAL_SYM double +htrdr_ran_lw_get_sky_band_pdf + (const struct htrdr_ran_lw* ran_lw, + const size_t iband); #endif /* HTRDR_RAN_LW_H */