commit 63698d1426453abe9010c9cc90e86ddf34f383bd
parent 5ff5de7bf7fff376191e2cec70e5d2c077a656f0
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Wed, 9 Nov 2022 08:37:52 +0100
htrdr-planeto: complete write radiance calculation function
This commit is the first attempt to sample a multi-scattering radiative
path taking into account the entire scene data i.e. BSDFs and spatially
varying phase functions, a heterogeneous semi-transparent mixture and a
ground geometry.
Diffstat:
1 file changed, 464 insertions(+), 54 deletions(-)
diff --git a/src/planeto/htrdr_planeto_compute_radiance.c b/src/planeto/htrdr_planeto_compute_radiance.c
@@ -24,10 +24,48 @@
#include <star/s3d.h>
#include <star/ssf.h>
#include <star/ssp.h>
+#include <star/suvm.h>
+#include <star/svx.h>
#include <rsys/double2.h>
#include <rsys/double3.h>
+/* Syntactic sugar */
+#define DISTANCE_NONE(Dst) ((Dst) >= FLT_MAX)
+#define SURFACE_SCATTERING(Scattering) (!S3D_HIT_NONE(&(Scattering)->hit))
+
+struct scattering {
+ /* Set to S3D_HIT_NULL if the scattering occurs in volume.*/
+ struct s3d_hit hit;
+
+ /* The surface normal defines only if scattering is on the surface. It is
+ * normalized and looks towards the incoming direction */
+ double normal[3];
+
+ double distance;
+};
+#define SCATTERING_NULL__ {S3D_HIT_NULL__, {0,0,0}, DBL_MAX}
+static const struct scattering SCATTERING_NULL = SCATTERING_NULL__;
+
+/* Arguments of the filtering function used to sample a position */
+struct sample_distance_context {
+ struct ssp_rng* rng;
+ struct rnatm* atmosphere;
+ size_t iband;
+ size_t iquad;
+ double wavelength; /* In nm */
+ enum rnatm_radcoef radcoef;
+ double Ts; /* Sample optical thickness */
+
+ /* Output data */
+ double distance;
+};
+#define SAMPLE_DISTANCE_CONTEXT_NULL__ { \
+ NULL, NULL, 0, 0, 0, RNATM_RADCOEFS_COUNT__, 0, DBL_MAX \
+}
+static const struct sample_distance_context SAMPLE_DISTANCE_CONTEXT_NULL =
+ SAMPLE_DISTANCE_CONTEXT_NULL__;
+
/*******************************************************************************
* Helper functions
******************************************************************************/
@@ -67,6 +105,384 @@ check_planeto_compute_radiance_args
return RES_OK;
}
+static int
+sample_position_hit_filter
+ (const struct svx_hit* hit,
+ const double org[3],
+ const double dir[3],
+ const double range[2],
+ void* context)
+{
+ struct rnatm_get_radcoef_args get_k_args = RNATM_GET_RADCOEF_ARGS_NULL;
+ struct sample_distance_context* ctx = context;
+ double k_min = 0;
+ double k_max = 0;
+ double dst_travelled = 0;
+ int pursue_traversal = 1;
+ ASSERT(hit && org && range && context);
+ (void)range;
+
+ dst_travelled = hit->distance[0];
+
+ /* Get the k_min, k_max of the voxel */
+ k_min = rnatm_get_k_svx_voxel
+ (ctx->atmosphere, &hit->voxel, ctx->radcoef, RNATM_SVX_OP_MIN);
+ k_max = rnatm_get_k_svx_voxel
+ (ctx->atmosphere, &hit->voxel, ctx->radcoef, RNATM_SVX_OP_MAX);
+
+ /* Configure the common input arguments to retrieve the radiative coefficient
+ * of a given position */
+ get_k_args.iband = ctx->iband;
+ get_k_args.iquad = ctx->iquad;
+ get_k_args.radcoef = ctx->radcoef;
+ get_k_args.k_min = k_min;
+ get_k_args.k_max = k_max;
+
+ for(;;) {
+ /* Compute the optical thickness of the voxel */
+ const double vox_dst = hit->distance[1] - dst_travelled;
+ const double T = vox_dst * k_max;
+
+ /* A collision occurs behind the voxel */
+ if(ctx->Ts > T) {
+ ctx->Ts -= T;
+ pursue_traversal = 1;
+ break;
+
+ /* A collision occurs in the voxel */
+ } else {
+ const double vox_dst_collision = ctx->Ts / k_max;
+ double k, r;
+
+ /* Calculate the distance travelled to the collision to be queried */
+ dst_travelled += vox_dst_collision;
+
+ /* Retrieve the radiative coefficient at the collision position */
+ get_k_args.pos[0] = org[0] + dst_travelled * dir[0];
+ get_k_args.pos[1] = org[1] + dst_travelled * dir[1];
+ get_k_args.pos[2] = org[2] + dst_travelled * dir[2];
+ RNATM(get_radcoef(ctx->atmosphere, &get_k_args, &k));
+
+ r = ssp_rng_canonical(ctx->rng);
+
+ /* Null collision */
+ if(r > k/k_max) {
+ ctx->Ts = ssp_ran_exp(ctx->rng, 1);
+
+ /* Real collision */
+ } else {
+ ctx->distance = dst_travelled;
+ pursue_traversal = 0;
+ break;
+ }
+ }
+ }
+
+ return pursue_traversal;
+}
+
+static double
+sample_distance
+ (struct htrdr_planeto* cmd,
+ const struct planeto_compute_radiance_args* args,
+ const enum rnatm_radcoef radcoef,
+ const double pos[3],
+ const double dir[3],
+ const double range[2])
+{
+ struct rnatm_trace_ray_args rt = RNATM_TRACE_RAY_ARGS_NULL;
+ struct sample_distance_context ctx = SAMPLE_DISTANCE_CONTEXT_NULL;
+ struct svx_hit hit;
+ ASSERT(cmd && args && pos && dir && d3_is_normalized(dir) && range);
+ ASSERT((unsigned)radcoef < RNATM_RADCOEFS_COUNT__);
+ ASSERT(range[0] < range[1]);
+
+ /* Sample an optical thickness */
+ ctx.Ts = ssp_ran_exp(args->rng, 1);
+
+ /* Setup the remaining arguments of the RT context */
+ ctx.rng = args->rng;
+ ctx.atmosphere = cmd->atmosphere;
+ ctx.iband = args->iband;
+ ctx.iquad = args->iquad;
+ ctx.wavelength = args->wlen;
+ ctx.radcoef = radcoef;
+
+ /* Trace the ray into the atmosphere */
+ d3_set(rt.ray_org, pos);
+ d3_set(rt.ray_dir, dir);
+ rt.ray_range[0] = range[0];
+ rt.ray_range[1] = range[1];
+ rt.filter = sample_position_hit_filter;
+ rt.context = &ctx;
+ rt.iband = args->iband;
+ rt.iquad = args->iquad;
+ RNATM(trace_ray(cmd->atmosphere, &rt, &hit));
+
+ if(SVX_HIT_NONE(&hit)) { /* No collision found */
+ return INF;
+ } else { /* A (real) collision occured */
+ return ctx.distance;
+ }
+}
+
+static INLINE double
+transmissivity
+ (struct htrdr_planeto* cmd,
+ const struct planeto_compute_radiance_args* args,
+ const enum rnatm_radcoef radcoef,
+ const double pos[3],
+ const double dir[3],
+ const double range_max)
+{
+ double range[2];
+ double dst = 0;
+ ASSERT(range_max >= 0);
+
+ range[0] = 0;
+ range[1] = range_max;
+ dst = sample_distance(cmd, args, radcoef, pos, dir, range);
+
+ if(DISTANCE_NONE(dst)) {
+ return 1.0; /* No collision in the medium */
+ } else {
+ return 0.0; /* A (real) collision occurs */
+ }
+}
+
+static double
+direct_contribution
+ (struct htrdr_planeto* cmd,
+ const struct planeto_compute_radiance_args* args,
+ const double pos[3],
+ const double dir[3],
+ const struct s3d_hit* hit_from)
+{
+ struct rngrd_trace_ray_args rt = RNGRD_TRACE_RAY_ARGS_DEFAULT;
+ struct s3d_hit hit;
+ double Tr;
+ double Ld;
+ ASSERT(cmd && args && pos && dir);
+
+ /* Is the source hidden? */
+ d3_set(rt.ray_org, pos);
+ d3_set(rt.ray_dir, dir);
+ if(hit_from) rt.hit_from = *hit_from;
+ RNGRD(trace_ray(cmd->ground, &rt, &hit));
+ if(!S3D_HIT_NONE(&hit)) return 0;
+
+ Tr = transmissivity(cmd, args, RNATM_RADCOEF_Kext, pos, dir, INF);
+ Ld = htrdr_planeto_source_get_radiance(cmd->source, args->wlen);
+ return Ld * Tr;
+}
+
+static void
+sample_scattering
+ (struct htrdr_planeto* cmd,
+ const struct planeto_compute_radiance_args* args,
+ const double pos[3],
+ const double dir[3],
+ struct scattering* sc)
+{
+ struct rngrd_trace_ray_args rt = RNGRD_TRACE_RAY_ARGS_DEFAULT;
+ struct s3d_hit hit;
+ double range[2];
+ double dst;
+ ASSERT(cmd && args && pos && dir && sc);
+
+ *sc = SCATTERING_NULL;
+
+ /* Look for a surface intersection */
+ d3_set(rt.ray_org, pos);
+ d3_set(rt.ray_dir, dir);
+ d2(rt.ray_range, 0, INF);
+ RNGRD(trace_ray(cmd->ground, &rt, &hit));
+
+ /* Look for an atmospheric collision */
+ range[0] = 0;
+ range[1] = hit.distance;
+ dst = sample_distance(cmd, args, RNATM_RADCOEF_Ks, pos, dir, range);
+
+ /* Scattering in volume */
+ if(!DISTANCE_NONE(dst)) {
+ sc->distance = dst;
+ sc->hit = S3D_HIT_NULL;
+
+ /* Surface scattering */
+ } else if(!S3D_HIT_NONE(&hit)) {
+ /* Normalize the normal and ensure that it points to `dir' */
+ d3_normalize(sc->normal, d3_set_f3(sc->normal, hit.normal));
+ if(d3_dot(sc->normal, dir) > 0) d3_minus(sc->normal, sc->normal);
+
+ sc->distance = hit.distance;
+ sc->hit = hit;
+
+ /* No scattering */
+ } else {
+ sc->distance = INF;
+ sc->hit = S3D_HIT_NULL;
+ }
+}
+
+static INLINE struct ssf_bsdf*
+create_bsdf
+ (struct htrdr_planeto* cmd,
+ const struct planeto_compute_radiance_args* args,
+ const struct s3d_hit* hit)
+{
+ struct rngrd_create_bsdf_args bsdf_args = RNGRD_CREATE_BSDF_ARGS_NULL;
+ struct ssf_bsdf* bsdf = NULL;
+ ASSERT(!S3D_HIT_NONE(hit));
+
+ /* Retrieve the BSDF at the intersected surface position */
+ bsdf_args.prim = hit->prim;
+ bsdf_args.barycentric_coords[0] = hit->uv[0];
+ bsdf_args.barycentric_coords[1] = hit->uv[1];
+ bsdf_args.barycentric_coords[2] = 1 - hit->uv[0] - hit->uv[1];
+ bsdf_args.wavelength = args->wlen;
+ bsdf_args.r = ssp_rng_canonical(args->rng);
+ RNGRD(create_bsdf(cmd->ground, &bsdf_args, &bsdf));
+
+ return bsdf;
+}
+
+static INLINE struct ssf_phase*
+create_phase_fn
+ (struct htrdr_planeto* cmd,
+ const struct planeto_compute_radiance_args* args,
+ const double pos[3]) /* Scattering position */
+{
+ struct rnatm_sample_component_args sample_args =
+ RNATM_SAMPLE_COMPONENT_ARGS_NULL;
+ struct rnatm_cell_create_phase_fn_args phase_fn_args =
+ RNATM_CELL_CREATE_PHASE_FN_ARGS_NULL;
+ struct ssf_phase* phase = NULL;
+ size_t cpnt;
+ ASSERT(cmd && args && pos);
+
+ /* Sample the atmospheric scattering component */
+ d3_set(sample_args.pos, pos);
+ sample_args.iband = args->iband;
+ sample_args.iquad = args->iquad;
+ sample_args.radcoef = RNATM_RADCOEF_Ks;
+ sample_args.r = ssp_rng_canonical(args->rng);
+ RNATM(sample_component(cmd->atmosphere, &sample_args, &cpnt));
+
+ /* Retrieve the component cell in which the scattering position is located */
+ RNATM(fetch_cell(cmd->atmosphere, pos, cpnt, &phase_fn_args.cell,
+ phase_fn_args.barycentric_coords));
+ ASSERT(!SUVM_PRIMITIVE_NONE(&phase_fn_args.cell.prim));
+
+ /* Retrieve the component phase function */
+ phase_fn_args.wavelength = args->wlen;
+ phase_fn_args.r[0] = ssp_rng_canonical(args->rng);
+ phase_fn_args.r[1] = ssp_rng_canonical(args->rng);
+ RNATM(cell_create_phase_fn(cmd->atmosphere, &phase_fn_args, &phase));
+
+ return phase;
+}
+
+/* Return the direct contribution at the scattering position */
+static double
+surface_scattering
+ (struct htrdr_planeto* cmd,
+ const struct planeto_compute_radiance_args* args,
+ const struct scattering* sc,
+ const double sc_pos[3], /* Scattering position */
+ const double in_dir[3], /* Incident direction */
+ double sc_dir[3], /* Sampled scattering direction */
+ double *out_refl) /* Surface reflectivity */
+{
+ struct ssf_bsdf* bsdf = NULL;
+ const double* N = NULL;
+ double wo[3] = {0,0,0};
+ double reflectivity = 0; /* Surface reflectivity */
+ double L = 0;
+ int mask = 0;
+ ASSERT(cmd && args && sc && sc_pos && in_dir && sc_dir && out_refl);
+ ASSERT(d3_dot(sc->normal, in_dir) < 0 && d3_is_normalized(sc->normal));
+
+ bsdf = create_bsdf(cmd, args, &sc->hit);
+ N = sc->normal;
+ d3_minus(wo, in_dir); /* Match StarSF convention */
+ ASSERT(d3_dot(wo, N) > 0);
+
+ /* Sample the scattering direction */
+ reflectivity = ssf_bsdf_sample(bsdf, args->rng, wo, N, sc_dir, &mask, NULL);
+
+ /* Fully absorbs transmissions */
+ if(mask & SSF_TRANSMISSION) reflectivity = 0;
+
+ /* Calculate direct contribution for specular reflection */
+ if((mask & SSF_SPECULAR)
+ && (mask & SSF_REFLECTION)
+ && htrdr_planeto_source_is_targeted(cmd->source, sc_pos, sc_dir)) {
+ const double Ld = direct_contribution(cmd, args, sc_pos, sc_dir, &sc->hit);
+ L = Ld * reflectivity;
+
+ /* Calculate direct contribution in general case */
+ } else if(!(mask & SSF_SPECULAR)) {
+ double wi[3], pdf;
+
+ /* Sample a direction toward the source */
+ pdf = htrdr_planeto_source_sample_direction(cmd->source, args->rng, sc_pos, wi);
+
+ /* The source is behind the surface */
+ if(d3_dot(wi, N) <= 0) {
+ L = 0;
+
+ /* The source is above the surface */
+ } else {
+ const double Ld = direct_contribution(cmd, args, sc_pos, wi, &sc->hit);
+ const double rho = ssf_bsdf_eval(bsdf, wo, N, wi);
+ const double cos_N_wi = d3_dot(N, wi);
+ ASSERT(cos_N_wi > 0);
+
+ L = Ld * rho * cos_N_wi / pdf;
+ }
+ }
+
+ SSF(bsdf_ref_put(bsdf));
+
+ *out_refl = reflectivity;
+ return L;
+}
+
+/* Return the direct contribution at the scattering position */
+static INLINE double
+volume_scattering
+ (struct htrdr_planeto* cmd,
+ const struct planeto_compute_radiance_args* args,
+ const double sc_pos[3], /* Scattering position */
+ const double in_dir[3], /* Incident direction */
+ double sc_dir[3]) /* Sampled scattering direction */
+{
+ struct ssf_phase* phase = NULL;
+ double wo[3] = {0,0,0};
+ double wi[3] = {0,0,0};
+ double L = 0;
+ double pdf = 0;
+ double rho = 0;
+ double Ld = 0;
+ ASSERT(cmd && args && sc_pos && in_dir && sc_dir);
+
+ phase = create_phase_fn(cmd, args, sc_pos);
+ d3_minus(wo, in_dir); /* Match StarSF convention */
+
+ ssf_phase_sample(phase, args->rng, wo, sc_dir, NULL);
+
+ /* Sample a direction toward the source */
+ pdf = htrdr_planeto_source_sample_direction(cmd->source, args->rng, sc_pos, wi);
+
+ /* Calculate the direct contribution at the scattering position */
+ Ld = direct_contribution(cmd, args, sc_pos, wi, NULL);
+ rho = ssf_phase_eval(phase, wo, wi);
+ L = Ld * rho / pdf;
+
+ SSF(phase_ref_put(phase));
+ return L;
+}
+
/*******************************************************************************
* Local functions
******************************************************************************/
@@ -75,68 +491,62 @@ planeto_compute_radiance
(struct htrdr_planeto* cmd,
const struct planeto_compute_radiance_args* args)
{
- struct rngrd_trace_ray_args rt_args = RNGRD_TRACE_RAY_ARGS_DEFAULT;
- struct rngrd_create_bsdf_args bsdf_args = RNGRD_CREATE_BSDF_ARGS_NULL;
- struct s3d_hit s3d_hit0 = S3D_HIT_NULL;
- struct s3d_hit s3d_hit1 = S3D_HIT_NULL;
- struct ssf_bsdf* bsdf = NULL;
double pos[3];
- double N[3];
- double wo[3];
- double wi[3];
- double L = 0;
- double Li = 0;
- double cos_wi_N = 0;
- double rho = 0;
+ double dir[3];
+ double L = 0; /* Radiance */
+ double Tr_abs = 1; /* Absorption transmissivity */
+ size_t nsc = 0; /* For debug */
ASSERT(cmd && check_planeto_compute_radiance_args(cmd, args) == RES_OK);
- /* Look for a surface intersection */
- d3_set(rt_args.ray_org, args->path_org);
- d3_set(rt_args.ray_dir, args->path_dir);
- d2(rt_args.ray_range, 0, INF);
- RNGRD(trace_ray(cmd->ground, &rt_args, &s3d_hit0));
+ d3_set(pos, args->path_org);
+ d3_set(dir, args->path_dir);
- /* No surface intersection */
- if(S3D_HIT_NONE(&s3d_hit0)) goto exit; /* Nothing more to do */
+ if(htrdr_planeto_source_is_targeted(cmd->source, pos, dir)) {
+ L = direct_contribution(cmd, args, pos, dir, NULL);
+ }
- /* Calculate the position of intersection */
- pos[0] = s3d_hit0.distance * rt_args.ray_dir[0] + rt_args.ray_org[0];
- pos[1] = s3d_hit0.distance * rt_args.ray_dir[1] + rt_args.ray_org[1];
- pos[2] = s3d_hit0.distance * rt_args.ray_dir[2] + rt_args.ray_org[2];
+ for(;;) {
+ struct scattering sc;
+ double sc_pos[3];
+ double sc_dir[3];
- /* Sample a direction toward the source */
- htrdr_planeto_source_sample_direction(cmd->source, args->rng, pos, wi);
- Li = htrdr_planeto_source_get_radiance(cmd->source, args->wlen);
-
- /* The source is not facing the current position */
- d3_normalize(N, d3_set_f3(N, s3d_hit0.normal));
- if(d3_dot(N, rt_args.ray_dir) > 0) d3_minus(N, N);
- cos_wi_N = d3_dot(wi, N);
- if(cos_wi_N < 0) goto exit; /* Nothing more to do */
-
- /* Check the source visibility */
- d3_set(rt_args.ray_org, pos);
- d3_set(rt_args.ray_dir, wi);
- d2(rt_args.ray_range, 0, INF);
- rt_args.hit_from = s3d_hit0;
- RNGRD(trace_ray(cmd->ground, &rt_args, &s3d_hit1));
- if(!S3D_HIT_NONE(&s3d_hit1)) goto exit; /* Nothing more to do */
-
- /* Fetch the surface BSDF */
- bsdf_args.prim = s3d_hit0.prim;
- bsdf_args.barycentric_coords[0] = s3d_hit0.uv[0];
- bsdf_args.barycentric_coords[1] = s3d_hit0.uv[1];
- bsdf_args.barycentric_coords[2] = 1 - s3d_hit0.uv[0] - s3d_hit0.uv[1];
- bsdf_args.wavelength = args->wlen;
- bsdf_args.r = ssp_rng_canonical(args->rng);
- RNGRD(create_bsdf(cmd->ground, &bsdf_args, &bsdf));
+ sample_scattering(cmd, args, pos, dir, &sc);
- d3_minus(wo, rt_args.ray_org);
- rho = ssf_bsdf_eval(bsdf, wo, N, wi);
- SSF(bsdf_ref_put(bsdf));
+ /* No scattering. Stop the path */
+ if(DISTANCE_NONE(sc.distance)) break;
+
+ Tr_abs *= transmissivity(cmd, args, RNATM_RADCOEF_Ka, pos, dir, sc.distance);
+
+ /* Full absorption. Stop the path */
+ if(Tr_abs == 0) break;
+
+ /* Compute the scattering position */
+ sc_pos[0] = pos[0] + dir[0] * sc.distance;
+ sc_pos[1] = pos[1] + dir[1] * sc.distance;
+ sc_pos[2] = pos[2] + dir[2] * sc.distance;
+
+ /* Scattering in volume */
+ if(!SURFACE_SCATTERING(&sc)) {
+ double Ls; /* Direct contribution at the scattering position */
+ Ls = volume_scattering(cmd, args, sc_pos, dir, sc_dir);
+ L += Tr_abs * Ls;
+
+ /* Surface scattering */
+ } else {
+ double Ls; /* Direct contribution at the scattering position */
+ double refl; /* Surface reflectivity */
+ Ls = surface_scattering(cmd, args, &sc, sc_pos, dir, sc_dir, &refl);
+ L += Tr_abs * Ls;
+
+ /* Russian roulette wrt surface reflectivity */
+ if(ssp_rng_canonical(args->rng) >= refl) break;
+ }
+
+ d3_set(pos, sc_pos);
+ d3_set(dir, sc_dir);
- L = rho * Li * cos_wi_N;
+ ++nsc;
+ }
-exit:
return L;
}