@@ -17,6 +17,7 @@ namespace pugi = OIIO::pugi;
1717# include " raytracer.h"
1818# include " shading.h"
1919# include " simpleraytracer.h"
20+ # include " volume.h"
2021#endif
2122
2223#include < cmath>
@@ -968,11 +969,66 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
968969 int prev_id = -1 ;
969970 float bsdf_pdf = inf; // camera ray has only one possible direction
970971
972+ MediumStack medium_stack;
973+
971974 for (int b = 0 ; b <= max_bounces; b++) {
972975 ShaderGlobalsType sg;
973-
974976 // trace the ray against the scene
975977 Intersection hit = scene.intersect (r, inf, prev_id);
978+
979+ const MediumProperties* current_volume = medium_stack.current ();
980+ bool in_medium = medium_stack.in_medium ();
981+
982+ // determine whether ray is in a medium
983+ float t_volume = inf;
984+ if (in_medium && current_volume && !current_volume->is_vacuum ()) {
985+ Vec3 rand_vol = sampler.get ();
986+ t_volume = current_volume->sample_distance (rand_vol.x );
987+ }
988+
989+ // is there volume scattering
990+ bool volume_scatter = in_medium && (t_volume < hit.t );
991+ float t_event = volume_scatter ? t_volume : hit.t ;
992+
993+ // apply volumetric transmittance up to the event
994+ if (in_medium && current_volume && !current_volume->is_vacuum ()) {
995+ Color3 transmittance = current_volume->transmittance (t_event);
996+ path_weight *= transmittance;
997+
998+ // early exit if transmittance kills the path
999+ if (!(path_weight.x > 0 ) && !(path_weight.y > 0 ) && !(path_weight.z > 0 )) {
1000+ break ;
1001+ }
1002+ }
1003+
1004+ // do volume scattering
1005+ if (volume_scatter) {
1006+ r.origin = r.origin + r.direction * t_volume;
1007+
1008+ Vec3 rand_phase = sampler.get ();
1009+ HenyeyGreenstein phase_func (current_volume->medium_g );
1010+ // IsotropicPhase phase_func;
1011+ PhaseFunction::Sample phase_sample = phase_func.sample (-r.direction , rand_phase.x , rand_phase.y );
1012+
1013+ if (phase_sample.pdf <= 0 .0f ) {
1014+ break ;
1015+ }
1016+
1017+ // single scattering albedo = sigma_s / sigma_t
1018+ Color3 albedo = Color3 (
1019+ current_volume->sigma_s .x / current_volume->sigma_t .x ,
1020+ current_volume->sigma_s .y / current_volume->sigma_t .y ,
1021+ current_volume->sigma_s .z / current_volume->sigma_t .z
1022+ );
1023+
1024+ path_weight *= albedo * phase_sample.weight ;
1025+
1026+ r.direction = phase_sample.wi ;
1027+ bsdf_pdf = phase_sample.pdf ;
1028+
1029+ continue ;
1030+ }
1031+
9761032 if (hit.t == inf) {
9771033 // we hit nothing? check background shader
9781034 if (backgroundShaderID >= 0 ) {
@@ -1018,8 +1074,9 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
10181074 int shaderID = scene.shaderid (hit.id );
10191075
10201076#ifndef __CUDACC__
1021- if (shaderID < 0 || !m_shaders[shaderID].surf )
1077+ if (shaderID < 0 || !m_shaders[shaderID].surf ) {
10221078 break ; // no shader attached? done
1079+ }
10231080
10241081 // execute shader and process the resulting list of closures
10251082 shadingsys->execute (*ctx, *m_shaders[shaderID].surf , sg);
@@ -1032,7 +1089,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
10321089 bool last_bounce = b == max_bounces;
10331090 process_closure (sg, r.roughness , result, (const ClosureColor*)sg.Ci ,
10341091 last_bounce);
1035-
1092+
10361093#ifndef __CUDACC__
10371094 const size_t lightprims_size = m_lightprims.size ();
10381095#endif
@@ -1076,16 +1133,17 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
10761133 float bg_pdf = 0 ;
10771134 Vec3 bg = background.sample (xi, yi, bg_dir, bg_pdf);
10781135 BSDF::Sample b = result.bsdf .eval (-sg.I , bg_dir.val ());
1079- Color3 contrib = path_weight * b.weight * bg
1080- * MIS::power_heuristic<MIS::WEIGHT_WEIGHT>(bg_pdf,
1136+ Color3 contrib = path_weight * b.weight *
1137+ bg * MIS::power_heuristic<MIS::WEIGHT_WEIGHT>(bg_pdf,
10811138 b.pdf );
10821139 if ((contrib.x + contrib.y + contrib.z ) > 0 ) {
10831140 ShaderGlobalsType shadow_sg;
10841141 Ray shadow_ray = Ray (sg.P , bg_dir.val (), radius, 0 , 0 ,
10851142 Ray::SHADOW);
10861143 Intersection shadow_hit = scene.intersect (shadow_ray, inf,
1087- hit.id );
1088- if (shadow_hit.t == inf) // ray reached the background?
1144+ hit.id );
1145+
1146+ if (shadow_hit.t == inf) // ray reached the background?
10891147 path_radiance += contrib;
10901148 }
10911149 }
@@ -1114,27 +1172,21 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
11141172 Ray shadow_ray = Ray (sg.P , sample.dir , radius, 0 , 0 ,
11151173 Ray::SHADOW);
11161174 // trace a shadow ray and see if we actually hit the target
1117- // in this tiny renderer, tracing a ray is probably cheaper than evaluating the light shader
11181175 Intersection shadow_hit
11191176 = scene.intersect (shadow_ray, sample.dist , hit.id , lid);
11201177
11211178#ifndef __CUDACC__
11221179 const bool did_hit = shadow_hit.t == sample.dist ;
11231180#else
1124- // The hit distance on the device is not as precise as on
1125- // the CPU, so we need to allow a little wiggle room. An
1126- // epsilon of 1e-3f empirically gives results that closely
1127- // match the CPU for the test scenes, so that's what we're
1128- // using.
11291181 const bool did_hit = fabsf (shadow_hit.t - sample.dist )
11301182 < 1e-3f ;
1131- #endif
1183+ #endif
1184+
11321185 if (did_hit) {
1133- // setup a shader global for the point on the light
1186+
11341187 globals_from_hit (light_sg, shadow_ray, sample.dist , lid,
11351188 sample.u , sample.v );
11361189#ifndef __CUDACC__
1137- // execute the light shader (for emissive closures only)
11381190 shadingsys->execute (*ctx, *m_shaders[shaderID].surf ,
11391191 light_sg);
11401192#else
@@ -1143,7 +1195,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
11431195 ShadingResult light_result;
11441196 process_closure (light_sg, r.roughness , light_result,
11451197 (const ClosureColor*)light_sg.Ci , true );
1146- // accumulate contribution
1198+
11471199 path_radiance += contrib * light_result.Le ;
11481200 }
11491201 }
@@ -1160,12 +1212,22 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler,
11601212 // Just simply use roughness as spread slope
11611213 r.spread = std::max (r.spread , p.roughness );
11621214 r.roughness = p.roughness ;
1163- if (!(path_weight.x > 0 ) && !(path_weight.y > 0 )
1164- && !(path_weight.z > 0 ))
1215+
1216+ if (!result.medium_data .is_vacuum ()) {
1217+ bool entering = !sg.backfacing ;
1218+
1219+ if (entering) {
1220+ medium_stack.push (result.medium_data );
1221+ } else {
1222+ medium_stack.pop ();
1223+ }
1224+ }
1225+
1226+ if (!(path_weight.x > 0 ) && !(path_weight.y > 0 ) && !(path_weight.z > 0 ) && b > 10 )
11651227 break ; // filter out all 0's or NaNs
11661228 prev_id = hit.id ;
11671229 r.origin = sg.P ;
1168- }
1230+ }
11691231 return path_radiance;
11701232}
11711233
0 commit comments