star-phor

Radiative transfer solver for photoreactors.
git clone https://www.edstar.cnrs.fr/git/star-phor.git
Log | Files | Refs | README | LICENSE

commit ec361da113dfe30dda367335e435cd7905113d6e
parent 717573681135dff7085564931f353d23e6530bd9
Author: Eduardo Fontana Lazzari <edufonlaz@gmail.com>
Date:   Fri, 27 Feb 2026 18:33:24 +0100

Implement SNELL_DIELECTRIC BTDF and update path sampling

Yet another BTDF. Always samples the Snell refraction direction,
computed using the real part of the refractive indices on each side of
the interface. Heavily inspired by what is done in ssf for dielectric
interfaces, but here we leave the possibility for the caller to
explicitly instantiate its Fresnel struct instead of having it
implicitly computed during sampling, so one can use whichever Fresnel
model they want.

The new BTDF is integrated into path tracing.

Diffstat:
Msrc/sphor_ran_bsdf.c | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 166 insertions(+), 0 deletions(-)

diff --git a/src/sphor_ran_bsdf.c b/src/sphor_ran_bsdf.c @@ -230,6 +230,146 @@ const struct ssf_bsdf_type keep_current_dir_transmission = { ALIGNOF(struct keep_current_dir_transmission) }; +/* BTDF SNELL DIELECTRIC */ +struct snell_dielectric_transmission { + struct ssf_fresnel* fresnel; + double eta_i; /* Refractive index of the incoming medium */ + double eta_t; /* Refractive index of the transmissive medium */ +}; + +/* Refract the vect V wrt the normal N using the relative refractive index eta. + * Eta is the refraction index of the outside medium (where N points into) + * devided by the refraction index of the inside medium. By convention N and V + * points on the same side of the surface. */ +static INLINE void +refract(double res[3], const double V[3], const double N[3], const double eta) +{ + double tmp0[3]; + double tmp1[3]; + double cos_theta_i; + double cos_theta_t; + double sin2_theta_i; + double sin2_theta_t; + + ASSERT(res && V && N); + ASSERT(d3_is_normalized(V) && d3_is_normalized(N)); + cos_theta_i = d3_dot(V, N); + sin2_theta_i = MMAX(0, 1.0 - cos_theta_i*cos_theta_i); + sin2_theta_t = eta * eta * sin2_theta_i; + cos_theta_t = sqrt(1 - sin2_theta_t); + + d3_muld(tmp0, V, eta); + d3_muld(tmp1, N, eta * cos_theta_i - cos_theta_t); + d3_sub(res, tmp1, tmp0); +} + +static void +snell_dielectric_transmission_release + (void* data) +{ + struct keep_current_dir_transmission* btdf = data; + ASSERT(data); + if (btdf->fresnel) SSF(fresnel_ref_put(btdf->fresnel)); +} + +static double +snell_dielectric_transmission_sample + (void* data, + struct ssp_rng* rng, + const double wo[3], + const double N[3], + double wi[3], + int* type, + double* pdf) +{ + struct snell_dielectric_transmission* btdf = data; + double wt[3]; + double cos_wo_N; + double eta; /* Ratio of eta_i / eta_t */ + + ASSERT(NULL != btdf); + ASSERT(NULL != data); + ASSERT(NULL != rng); + ASSERT(NULL != N); + ASSERT(NULL != wi); + ASSERT(d3_is_normalized(wo) && d3_is_normalized(N)); + ASSERT(d3_dot(wo, N) > -1.e-6); + (void)rng; + + eta = btdf->eta_i / btdf->eta_t; + refract(wt, wo, N, eta); + + cos_wo_N = MMAX(0.0, d3_dot(wo, N)); + + if(pdf) *pdf = INF; + d3_set(wi, wt); + if(type) *type = SSF_SPECULAR | SSF_TRANSMISSION; + return 1 - ssf_fresnel_eval(btdf->fresnel, cos_wo_N); +} + +static double +snell_dielectric_transmission_eval + (void* bsdf, + const double wo[3], + const double N[3], + const double wi[3]) +{ + (void)bsdf, (void)wo, (void)N, (void)wi; + return 0.0; +} + +static double +snell_dielectric_transmission_pdf + (void* bsdf, + const double wo[3], + const double N[3], + const double wi[3]) +{ + (void)bsdf, (void)wo, (void)N, (void)wi; + return 0.0; +} + +static res_T +snell_dielectric_transmission_setup + (struct ssf_bsdf* bsdf, + struct ssf_fresnel* fresnel, + double eta_i, + double eta_t) +{ + void* ptr = NULL; + res_T res = RES_OK; + + ASSERT(NULL != bsdf); + + ssf_bsdf_get_data(bsdf, &ptr); + struct snell_dielectric_transmission* data = ptr; + + if (data->fresnel != fresnel) { + if (NULL != data->fresnel) { + res = ssf_fresnel_ref_put(data->fresnel); + if (RES_OK != res) { return res; } + } + res = ssf_fresnel_ref_get(fresnel); + if (RES_OK != res) { return res; } + + data->fresnel = fresnel; + } + data->eta_i = eta_i; + data->eta_t = eta_t; + + return RES_OK; +} + +const struct ssf_bsdf_type snell_dielectric_transmission = { + NULL, + snell_dielectric_transmission_release, + snell_dielectric_transmission_sample, + snell_dielectric_transmission_eval, + snell_dielectric_transmission_pdf, + sizeof(struct snell_dielectric_transmission), + ALIGNOF(struct snell_dielectric_transmission) +}; + /******************************************************************************* * Helper functions ******************************************************************************/ @@ -322,6 +462,8 @@ setup_bsdf_transmission double transmissivity, struct ssf_bsdf** out_bsdf) { + double eta_i = 0, k_i = 0; + double eta_t = 0, k_t = 0; struct primitive* primitive = &intersection->position.primitive; struct sphin_btdf* btdf = NULL; struct ssf_bsdf* bsdf = NULL; @@ -375,6 +517,30 @@ setup_bsdf_transmission break; case SPHIN_BTDF_DIRECTION_SNELL_DIELECTRIC: + + res = ssf_bsdf_create + (sphor->allocator, &snell_dielectric_transmission, &bsdf); + if (RES_OK != res) { goto error; } + + /* Get refractive index of the medium in the incident side */ + res = primitive_get_in_refraction_index + (sphor, primitive, wavelength, &eta_i, &k_i); + if (RES_OK != res) { goto error; } + + /* Get refractive index of the medium in the transmitted side */ + res = primitive_get_out_refraction_index + (sphor, primitive, wavelength, &eta_t, &k_t); + + res = ssf_fresnel_create + (sphor->allocator, &ssf_fresnel_constant, &fresnel); + if (RES_OK != res) { goto error; } + + res = ssf_fresnel_constant_setup(fresnel, 1 - transmissivity); + if (RES_OK != res) { goto error; } + + res = snell_dielectric_transmission_setup(bsdf, fresnel, eta_i, eta_t); + if (RES_OK != res) { goto error; } + break; case SPHIN_BTDF_DIRECTION_NONE__: