solstice-solver

Solver library of the solstice app
git clone git://git.meso-star.com/solstice-solver.git
Log | Files | Refs | README | LICENSE

commit 72c285814c28801b7540f24dd2eabf04f92b77a6
parent 2fae5d2d2c2837452bf3a182b0595d115dbcfe48
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu, 13 Oct 2016 16:53:47 +0200

Split the solver process in sub-routines

Diffstat:
Msrc/ssol.h | 2+-
Msrc/ssol_c.h | 2+-
Msrc/ssol_instance.c | 2+-
Msrc/ssol_solver.c | 388+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/test_ssol_solver1.c | 4++--
5 files changed, 244 insertions(+), 154 deletions(-)

diff --git a/src/ssol.h b/src/ssol.h @@ -502,7 +502,7 @@ ssol_instance_sample /* Retrieve the id of the shape */ SSOL_API res_T ssol_instance_get_id - (struct ssol_instance* instance, + (const struct ssol_instance* instance, uint32_t* id); /******************************************************************************* diff --git a/src/ssol_c.h b/src/ssol_c.h @@ -33,7 +33,7 @@ struct ray_data { struct ssol_scene* scn; /* The scene into which the ray is traced */ struct s3d_primitive prim_from; /* Primitive from which the ray starts */ - struct ssol_instance* inst_from; /* Instance from which the ray starts */ + const struct ssol_instance* inst_from; /* Instance from which the ray starts */ enum ssol_side_flag side_from; /* Primitive side from which the ray starts */ int discard_virtual_materials; /* Define if virtual materials are not RT */ float range_min; diff --git a/src/ssol_instance.c b/src/ssol_instance.c @@ -170,7 +170,7 @@ ssol_instance_sample } res_T -ssol_instance_get_id(struct ssol_instance* instance, uint32_t* id) +ssol_instance_get_id(const struct ssol_instance* instance, uint32_t* id) { unsigned u; STATIC_ASSERT diff --git a/src/ssol_solver.c b/src/ssol_solver.c @@ -43,9 +43,203 @@ #include <omp.h> +struct point { + const struct ssol_instance* inst; + const struct shaded_shape* sshape; + struct s3d_primitive prim; + double N[3]; + double pos[3]; + double dir[3]; /* Incoming direction */ + float uv[2]; + double wl; /* Sampled wavelength */ + double weight; + enum ssol_side_flag side; +}; + /******************************************************************************* * Helper functions ******************************************************************************/ +static void +point_init + (struct point* pt, + struct ssol_scene* scn, + const double sampled_area, + struct s3d_scene_view* view_samp, + struct ranst_sun_dir* ran_sun_dir, + struct ranst_sun_wl* ran_sun_wl, + struct ssp_rng* rng) +{ + struct s3d_attrib attr; + size_t id; + + /* Sample a point into the scene view */ + S3D(scene_view_sample + (view_samp, + ssp_rng_canonical_float(rng), + ssp_rng_canonical_float(rng), + ssp_rng_canonical_float(rng), + &pt->prim, pt->uv)); + + /* Retrieve the position of the sampled point */ + S3D(primitive_get_attrib(&pt->prim, S3D_POSITION, pt->uv, &attr)); + d3_set_f3(pt->pos, attr.value); + + /* Retrieve the normal of the sampled point */ + S3D(primitive_get_attrib(&pt->prim, S3D_GEOMETRY_NORMAL, pt->uv, &attr)); + f3_normalize(attr.value, attr.value); + d3_set_f3(pt->N, attr.value); + + /* Sample a sun direction */ + ranst_sun_dir_get(ran_sun_dir, rng, pt->dir); + + /* Initialise the Monte Carlo weight */ + pt->weight = scn->sun->dni * sampled_area * fabs(d3_dot(pt->N, pt->dir)); + + /* Retrieve the sampled instance and shaded shape */ + pt->inst = *htable_instance_find(&scn->instances_samp, &pt->prim.inst_id); + id = *htable_shaded_shape_find + (&pt->inst->object->shaded_shapes_samp, &pt->prim.geom_id); + pt->sshape = darray_shaded_shape_cdata_get + (&pt->inst->object->shaded_shapes) + id; + + /* For punched surface, retrieve the sampled position and normal onto the + * quadric surface */ + if(pt->sshape->shape->type == SHAPE_PUNCHED) { + punched_shape_project_point + (pt->sshape->shape, pt->inst->transform, pt->pos, pt->pos, pt->N); + } + + /* Sample a wavelength */ + pt->wl = ranst_sun_wl_get(ran_sun_wl, rng); + + /* Define the primitive side on which the point lies */ + if(d3_dot(pt->N, pt->dir) < 0) { + pt->side = SSOL_FRONT; + } else { + pt->side = SSOL_BACK; + d3_minus(pt->N, pt->N); + } +} + +static FINLINE void +point_update_from_hit + (struct point* pt, + struct ssol_scene* scn, /* Scene into which the hit occurs */ + const float org[3], /* Origin of the ray that generates the hit */ + const float dir[3], /* Direction of the ray that generates the hit */ + const struct s3d_hit* hit, + struct ray_data* rdata) /* Ray data used to generate the hit */ +{ + double tmp[3]; + float tmpf[3]; + size_t id; + + /* Retrieve the hit instance and shaded shape */ + pt->inst = *htable_instance_find(&scn->instances_rt, &hit->prim.inst_id); + id = *htable_shaded_shape_find + (&pt->inst->object->shaded_shapes_rt, &hit->prim.geom_id); + pt->sshape = darray_shaded_shape_cdata_get + (&pt->inst->object->shaded_shapes) + id; + + /* Fetch the current position and its associated normal */ + switch(pt->sshape->shape->type) { + case SHAPE_MESH: + d3_set_f3(pt->N, hit->normal); + d3_normalize(pt->N, pt->N); + f3_mulf(tmpf, dir, hit->distance); + f3_add(tmpf, org, tmpf); + d3_set_f3(pt->pos, tmpf); + break; + case SHAPE_PUNCHED: + d3_normalize(pt->N, rdata->N); + d3_muld(tmp, pt->dir, rdata->dst); + d3_add(pt->pos, pt->pos, tmp); + break; + default: FATAL("Unreachable code"); break; + } + + pt->prim = hit->prim; + + /* Define the primitive side on which the point lies */ + if(d3_dot(pt->dir, pt->N) < 0) { + pt->side = SSOL_FRONT; + } else { + pt->side = SSOL_BACK; + d3_minus(pt->N, pt->N); + } +} + +static FINLINE struct ssol_material* +point_get_material(const struct point* pt) +{ + return pt->side == SSOL_FRONT ? pt->sshape->mtl_front : pt->sshape->mtl_back; +} + +static FINLINE res_T +point_shade + (struct point* pt, struct ssf_bsdf* bsdf, struct ssp_rng* rng, double dir[3]) +{ + struct surface_fragment frag; + double wi[3], pdf; + res_T res; + + /* TODO ensure that if `prim' was sampled, then the surface fragment setup + * remains valid in *all* situations, i.e. even though the point primitive + * comes from a sampling operation */ + surface_fragment_setup(&frag, pt->pos, pt->dir, pt->N, &pt->prim, pt->uv); + + /* Shade the surface fragment */ + SSF(bsdf_clear(bsdf)); + res = material_shade(point_get_material(pt), &frag, pt->wl, bsdf); + if(res != RES_OK) return res; + + /* By convention, Star-SF assumes that incoming and reflected + * directions point outward the surface => negate incoming dir */ + d3_minus(wi, pt->dir); + + pt->weight *= ssf_bsdf_sample(bsdf, rng, wi, frag.Ns, dir, &pdf); + return RES_OK; +} + +static int +point_receive_sunlight + (struct point* pt, + struct ssol_scene* scn, + struct s3d_scene_view* view_rt) +{ + struct ray_data ray_data = RAY_DATA_NULL; + struct s3d_hit hit; + float dir[3], pos[3], range[2] = { 0, FLT_MAX }; + + /* Initialise the ray data to avoid self intersection */ + ray_data.scn = scn; + ray_data.prim_from = pt->prim; + ray_data.inst_from = pt->inst; + ray_data.side_from = pt->side; + ray_data.discard_virtual_materials = 1; /* Do not intersect virtual mtl */ + ray_data.dst = FLT_MAX; + + /* Trace a ray toward the sun to check if the sampled point is occluded */ + f3_minus(dir, f3_set_d3(dir, pt->dir)); + f3_set_d3(pos, pt->pos); + S3D(scene_view_trace_ray(view_rt, pos, dir, range, &ray_data, &hit)); + return S3D_HIT_NONE(&hit); +} + +static FINLINE int +point_is_receiver(const struct point* pt) +{ + return (pt->inst->receiver_mask & (int)pt->side) != 0; +} + +static FINLINE int32_t +point_get_id(const struct point* pt) +{ + uint32_t inst_id; + SSOL(instance_get_id(pt->inst, &inst_id)); + return pt->side == SSOL_FRONT ? (int32_t)inst_id : -(int32_t)inst_id; +} + static FINLINE res_T check_scene(const struct ssol_scene* scene, const char* caller) { @@ -148,25 +342,16 @@ ssol_solve #pragma omp parallel for schedule(static) for(i = 0; i < nrealisations; ++i) { - struct ssp_rng* rng; - struct s3d_attrib attr; struct s3d_hit hit; - struct s3d_primitive prim; + struct point pt; + struct ssp_rng* rng; struct ssf_bsdf* bsdf; - struct surface_fragment frag; - struct ssol_instance* inst; - struct ray_data ray_data = RAY_DATA_NULL; struct mc_data* shadow; struct mc_data* missing; - const struct shaded_shape* sshape; - double pos[3], dir[3], N[3], weight, cos_dir_N, wl; - float posf[3], dirf[3], uv[2]; - float range[2] = { 0, FLT_MAX }; - size_t id; + float org[3], dir[3], range[2] = { 0, FLT_MAX }; const int ithread = omp_get_thread_num(); - int depth = 0; + size_t depth = 0; int hit_a_receiver = 0; - res_T res_local; if(ATOMIC_GET(&res) != RES_OK) continue; /* An error occurs */ @@ -176,102 +361,37 @@ ssol_solve shadow = mc_shadows + ithread; missing = mc_missings + ithread; - /* Sample a point into the scene view */ - S3D(scene_view_sample - (view_samp, - ssp_rng_canonical_float(rng), - ssp_rng_canonical_float(rng), - ssp_rng_canonical_float(rng), - &prim, uv)); - - /* Retrieve the position of the sampled point */ - S3D(primitive_get_attrib(&prim, S3D_POSITION, uv, &attr)); - d3_set_f3(pos, attr.value); - - /* Retrieve the sampled instance and shaded shape */ - inst = *htable_instance_find(&scn->instances_samp, &prim.inst_id); - id = *htable_shaded_shape_find(&inst->object->shaded_shapes_samp, &prim.geom_id); - sshape = darray_shaded_shape_cdata_get(&inst->object->shaded_shapes)+id; - - /* Fetch the sampled position and its associated normal */ - S3D(primitive_get_attrib(&prim, S3D_GEOMETRY_NORMAL, uv, &attr)); - f3_normalize(attr.value, attr.value); - d3_set_f3(N, attr.value); - - /* Sample a sun direction */ - ranst_sun_dir_get(ran_sun_dir, rng, dir); - cos_dir_N = d3_dot(N, dir); - - /* Initialise the Monte Carlo weight */ - weight = scn->sun->dni * sampled_area * fabs(cos_dir_N); - - /* For punched surface, retrieve the sampled position and normal onto the - * quadric surface */ - if(sshape->shape->type == SHAPE_PUNCHED) { - punched_shape_project_point(sshape->shape, inst->transform, pos, pos, N); - cos_dir_N = d3_dot(N, dir); - } + /* Find a new starting point of the radiative random walk */ + point_init(&pt, scn, sampled_area, view_samp, ran_sun_dir, ran_sun_wl, rng); - /* Initialise the ray data to avoid self intersection */ - ray_data.scn = scn; - ray_data.prim_from = prim; - ray_data.inst_from = inst; - ray_data.side_from = cos_dir_N < 0 ? SSOL_FRONT : SSOL_BACK; - ray_data.discard_virtual_materials = 1; /* Do not intersect virtual mtl */ - ray_data.dst = FLT_MAX; - - /* Trace a ray toward the sun to check if the sampled point is occluded */ - f3_minus(dirf, f3_set_d3(dirf, dir)); - f3_set_d3(posf, pos); - S3D(scene_view_trace_ray(view_rt, posf, dirf, range, &ray_data, &hit)); - if(!S3D_HIT_NONE(&hit)) { /* First ray is occluded */ - shadow->weight += weight; - shadow->sqr_weight += weight*weight; + /* Check that the starting point is visible from its incoming dir */ + if(!point_receive_sunlight(&pt, scn, view_rt)) { + shadow->weight += pt.weight; + shadow->sqr_weight += pt.weight * pt.weight; continue; } - ray_data.discard_virtual_materials = 0; - /* Sample a wavelength */ - wl = ranst_sun_wl_get(ran_sun_wl, rng); + /* Setup the next ray */ + f3_set_d3(org, pt.pos); + f3_set_d3(dir, pt.dir); for(;;) { /* Here we go for the radiative random walk */ + struct ray_data ray_data = RAY_DATA_NULL; struct ssol_material* mtl; - double tmp[3]; - float tmpf[3]; - double pdf; - uint32_t inst_id; - int32_t receiver_id; - int is_receiver; - - if(cos_dir_N < 0) { /* Front face */ - mtl = sshape->mtl_front; - is_receiver = inst->receiver_mask & SSOL_FRONT; - SSOL(instance_get_id(inst, &inst_id)); - receiver_id = (int32_t)inst_id; - ray_data.side_from = SSOL_FRONT; - - } else { /* Back face */ - mtl = sshape->mtl_back; - is_receiver = inst->receiver_mask & SSOL_BACK; - SSOL(instance_get_id(inst, &inst_id)); - receiver_id = -(int32_t)inst_id; - d3_minus(N, N); - ray_data.side_from = SSOL_BACK; - } - if(is_receiver) { + if(point_is_receiver(&pt)) { struct ssol_receiver_data out; size_t n; out.realization_id = i; out.date = 0; /* TODO */ out.segment_id = (uint32_t)depth; - out.receiver_id = receiver_id; - out.wavelength = (float)wl; - f3_set_d3(out.pos, pos); - f3_set_d3(out.in_dir, dir); - f3_set_d3(out.normal, N); - f2_set(out.uv, uv); - out.weight = weight; + out.receiver_id = point_get_id(&pt); + out.wavelength = (float)pt.wl; + f3_set_d3(out.pos, pt.pos); + f3_set_d3(out.in_dir, pt.dir); + f3_set_d3(out.normal, pt.N); + f2_set(out.uv, pt.uv); + out.weight = pt.weight; n = fwrite(&out, sizeof(out), 1, output); if(n < 1) { ATOMIC_SET(&res, RES_IO_ERR); @@ -280,82 +400,52 @@ ssol_solve hit_a_receiver = 1; } + mtl = point_get_material(&pt); if(mtl->type == MATERIAL_VIRTUAL) { - /* Note that for Virtual materials, the ray parameters 'posf' & 'dirf' + /* Note that for Virtual materials, the ray parameters 'org' & 'dir' * are not updated to ensure that it pursues its traversal without any * accuracy issue */ range[0] = nextafterf(hit.distance, FLT_MAX); range[1] = FLT_MAX; } else { - /* TODO ensure that if `prim' was sampled, then the surface fragment - * setup remains valid in *all* situations */ - surface_fragment_setup(&frag, pos, dir, N, &prim, uv); - - /* Shade the surface fragment */ - SSF(bsdf_clear(bsdf)); - res_local = material_shade(mtl, &frag, wl, bsdf); - if(res_local != RES_OK) { ATOMIC_SET(&res, res_local); break; } - - /* By convention, Star-SF assumes that incoming and reflected - * directions point outward the surface => negate incoming dir */ - d3_minus(dir, dir); - - /* Sample the BSDF to find the next direction to trace */ - weight *= ssf_bsdf_sample(bsdf, rng, dir, frag.Ns, dir, &pdf); + res_T res_local = point_shade(&pt, bsdf, rng, pt.dir); + if(res_local != RES_OK) { + ATOMIC_SET(&res, res_local); + break; + } /* Setup new ray parameters */ - range[0] = 0; - range[1] = FLT_MAX; - f3_set_d3(posf, pos); - f3_set_d3(dirf, dir); + f2(range, 0, FLT_MAX); + f3_set_d3(org, pt.pos); + f3_set_d3(dir, pt.dir); } /* Trace the next ray */ - ray_data.dst = FLT_MAX; + ray_data.scn = scn, + ray_data.prim_from = pt.prim; + ray_data.inst_from = pt.inst; + ray_data.side_from = pt.side; + ray_data.discard_virtual_materials = 0; ray_data.range_min = range[0]; - S3D(scene_view_trace_ray(view_rt, posf, dirf, range, &ray_data, &hit)); + ray_data.dst = FLT_MAX; + S3D(scene_view_trace_ray(view_rt, org, dir, range, &ray_data, &hit)); if(S3D_HIT_NONE(&hit)) break; depth += mtl->type != MATERIAL_VIRTUAL; /* Take into account the atmosphere attenuation along the new ray */ if(scn->atmosphere) { - weight *= compute_atmosphere_attenuation - (scn->atmosphere, hit.distance, wl); - } - - /* Retrieve the hit instance and shaded shape */ - inst = *htable_instance_find(&scn->instances_rt, &hit.prim.inst_id); - id = *htable_shaded_shape_find(&inst->object->shaded_shapes_rt, &hit.prim.geom_id); - sshape = darray_shaded_shape_cdata_get(&inst->object->shaded_shapes)+id; - - /* Fetch the current position and its associated normal */ - switch(sshape->shape->type) { - case SHAPE_MESH: - f3_normalize(hit.normal, hit.normal); - d3_set_f3(N, hit.normal); - f3_mulf(tmpf, dirf, hit.distance); - f3_add(tmpf, posf, tmpf); - d3_set_f3(pos, tmpf); - break; - case SHAPE_PUNCHED: - d3_normalize(N, ray_data.N); - d3_muld(tmp, dir, ray_data.dst); - d3_add(pos, pos, tmp); - break; - default: FATAL("Unreachable code"); break; + pt.weight *= compute_atmosphere_attenuation + (scn->atmosphere, hit.distance, pt.wl); } - /* Setup the ray data to avoid self intersection */ - ray_data.prim_from = hit.prim; - ray_data.inst_from = inst; - - cos_dir_N = d3_dot(dir, N); + /* Update the point */ + point_update_from_hit(&pt, scn, org, dir, &hit, &ray_data); } if(!hit_a_receiver) { - missing->weight += weight; - missing->sqr_weight += weight*weight; + missing->weight += pt.weight; + missing->sqr_weight += pt.weight*pt.weight; } } diff --git a/src/test_ssol_solver1.c b/src/test_ssol_solver1.c @@ -192,7 +192,7 @@ main(int argc, char** argv) /* can sample any geometry; variance is high */ NCHECK(tmp = tmpfile(), 0); -#define N__ 10000 +#define N__ 100000 CHECK(ssol_estimator_clear(estimator), RES_OK); CHECK(ssol_solve(scene, rng, N__, tmp, estimator), RES_OK); CHECK(ssol_instance_get_id(target, &r_id), RES_OK); @@ -205,7 +205,7 @@ main(int argc, char** argv) logger_print(&logger, LOG_OUTPUT, "\nP = %g +/- %g", m, std); #define COS cos(PI / 4) #define DNI_cos (1000 * COS) - CHECK(eq_eps(m, 4 * DNI_cos, MMAX(4 * DNI_cos * 1e-2, std)), 1); + CHECK(eq_eps(m, 4 * DNI_cos, MMAX(4 * DNI_cos * 1e-2, 2*std)), 1); #define SQR(x) ((x)*(x)) dbl = sqrt((SQR(12 * DNI_cos) / 3 - SQR(4 * DNI_cos)) / (double)count); CHECK(eq_eps(std, dbl, dbl*1e-2), 1);