diff options
author | Brecht Van Lommel <brechtvanlommel@gmail.com> | 2013-12-29 18:40:43 +0400 |
---|---|---|
committer | Brecht Van Lommel <brechtvanlommel@gmail.com> | 2014-01-07 18:03:41 +0400 |
commit | 01df756bd10709bb707d4f88f14c50e5680d05a5 (patch) | |
tree | 62d1374ada6e9c62bae736a7e0e62a6cb2c0a14d | |
parent | b174e7b0b89d0e03a20a11f4a754029689af56a9 (diff) |
Cycles Volume Render: scattering support.
This is done by adding a Volume Scatter node. In many cases you will want to
add together a Volume Absorption and Volume Scatter node with the same color
and density to get the expected results.
This should work with branched path tracing, mixing closures, overlapping
volumes, etc. However there's still various optimizations needed for sampling.
The main missing thing from the volume branch is the equiangular sampling for
homogeneous volumes.
The heterogeneous scattering code was arranged such that we can use a single
stratified random number for distance sampling, which gives less noise than
pseudo random numbers for each step. For volumes where the color is textured
there still seems to be something off, needs to be investigated.
-rw-r--r-- | intern/cycles/blender/addon/properties.py | 13 | ||||
-rw-r--r-- | intern/cycles/blender/addon/ui.py | 2 | ||||
-rw-r--r-- | intern/cycles/blender/blender_sync.cpp | 4 | ||||
-rw-r--r-- | intern/cycles/kernel/closure/bsdf.h | 17 | ||||
-rw-r--r-- | intern/cycles/kernel/closure/volume.h | 8 | ||||
-rw-r--r-- | intern/cycles/kernel/kernel_accumulate.h | 4 | ||||
-rw-r--r-- | intern/cycles/kernel/kernel_emission.h | 7 | ||||
-rw-r--r-- | intern/cycles/kernel/kernel_path.h | 146 | ||||
-rw-r--r-- | intern/cycles/kernel/kernel_path_state.h | 74 | ||||
-rw-r--r-- | intern/cycles/kernel/kernel_shader.h | 117 | ||||
-rw-r--r-- | intern/cycles/kernel/kernel_types.h | 18 | ||||
-rw-r--r-- | intern/cycles/kernel/kernel_volume.h | 285 | ||||
-rw-r--r-- | intern/cycles/kernel/svm/svm_types.h | 1 | ||||
-rw-r--r-- | intern/cycles/render/integrator.cpp | 11 | ||||
-rw-r--r-- | intern/cycles/render/integrator.h | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.c | 2 |
16 files changed, 580 insertions, 131 deletions
diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index aa2da00838e..74f94d6eafc 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -220,6 +220,13 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): default=1, ) + cls.volume_samples = IntProperty( + name="Volume Samples", + description="Number of volume scattering samples to render for each AA sample", + min=1, max=10000, + default=1, + ) + cls.sampling_pattern = EnumProperty( name="Sampling Pattern", description="Random sampling pattern used by the integrator", @@ -280,6 +287,12 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): min=0, max=1024, default=12, ) + cls.volume_bounces = IntProperty( + name="Volume Bounces", + description="Maximum number of volumetric scattering events", + min=0, max=1024, + default=1, + ) cls.transparent_min_bounces = IntProperty( name="Transparent Min Bounces", diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 969f13017ca..d5f14473230 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -139,6 +139,7 @@ class CyclesRender_PT_sampling(CyclesButtonsPanel, Panel): sub.prop(cscene, "ao_samples", text="AO") sub.prop(cscene, "mesh_light_samples", text="Mesh Light") sub.prop(cscene, "subsurface_samples", text="Subsurface") + sub.prop(cscene, "volume_samples", text="Volume") if cscene.feature_set == 'EXPERIMENTAL' and (device_type == 'NONE' or cscene.device == 'CPU'): layout.row().prop(cscene, "sampling_pattern", text="Pattern") @@ -208,6 +209,7 @@ class CyclesRender_PT_light_paths(CyclesButtonsPanel, Panel): sub.prop(cscene, "diffuse_bounces", text="Diffuse") sub.prop(cscene, "glossy_bounces", text="Glossy") sub.prop(cscene, "transmission_bounces", text="Transmission") + sub.prop(cscene, "volume_bounces", text="Volume") class CyclesRender_PT_motion_blur(CyclesButtonsPanel, Panel): diff --git a/intern/cycles/blender/blender_sync.cpp b/intern/cycles/blender/blender_sync.cpp index 826402c8ba6..14e9f27ee39 100644 --- a/intern/cycles/blender/blender_sync.cpp +++ b/intern/cycles/blender/blender_sync.cpp @@ -166,6 +166,7 @@ void BlenderSync::sync_integrator() integrator->max_diffuse_bounce = get_int(cscene, "diffuse_bounces"); integrator->max_glossy_bounce = get_int(cscene, "glossy_bounces"); integrator->max_transmission_bounce = get_int(cscene, "transmission_bounces"); + integrator->max_volume_bounce = get_int(cscene, "volume_bounces"); integrator->transparent_max_bounce = get_int(cscene, "transparent_max_bounces"); integrator->transparent_min_bounce = get_int(cscene, "transparent_min_bounces"); @@ -201,6 +202,7 @@ void BlenderSync::sync_integrator() int ao_samples = get_int(cscene, "ao_samples"); int mesh_light_samples = get_int(cscene, "mesh_light_samples"); int subsurface_samples = get_int(cscene, "subsurface_samples"); + int volume_samples = get_int(cscene, "volume_samples"); if(get_boolean(cscene, "use_square_samples")) { integrator->diffuse_samples = diffuse_samples * diffuse_samples; @@ -209,6 +211,7 @@ void BlenderSync::sync_integrator() integrator->ao_samples = ao_samples * ao_samples; integrator->mesh_light_samples = mesh_light_samples * mesh_light_samples; integrator->subsurface_samples = subsurface_samples * subsurface_samples; + integrator->volume_samples = volume_samples * volume_samples; } else { integrator->diffuse_samples = diffuse_samples; @@ -217,6 +220,7 @@ void BlenderSync::sync_integrator() integrator->ao_samples = ao_samples; integrator->mesh_light_samples = mesh_light_samples; integrator->subsurface_samples = subsurface_samples; + integrator->volume_samples = volume_samples; } diff --git a/intern/cycles/kernel/closure/bsdf.h b/intern/cycles/kernel/closure/bsdf.h index b3141d1154f..8d458c552ac 100644 --- a/intern/cycles/kernel/closure/bsdf.h +++ b/intern/cycles/kernel/closure/bsdf.h @@ -32,6 +32,9 @@ #ifdef __SUBSURFACE__ #include "../closure/bssrdf.h" #endif +#ifdef __VOLUME__ +#include "../closure/volume.h" +#endif CCL_NAMESPACE_BEGIN @@ -124,6 +127,9 @@ ccl_device int bsdf_sample(KernelGlobals *kg, const ShaderData *sd, const Shader eval, omega_in, &domega_in->dx, &domega_in->dy, pdf); break; #endif + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: + label = volume_henyey_greenstein_sample(sc, sd->I, sd->dI.dx, sd->dI.dy, randu, randv, eval, omega_in, &domega_in->dx, &domega_in->dy, pdf); + break; default: label = LABEL_NONE; break; @@ -204,6 +210,11 @@ ccl_device float3 bsdf_eval(KernelGlobals *kg, const ShaderData *sd, const Shade eval = bsdf_hair_transmission_eval_reflect(sc, sd->I, omega_in, pdf); break; #endif +#ifdef __VOLUME__ + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: + eval = volume_henyey_greenstein_eval_phase(sc, sd->I, omega_in, pdf); + break; +#endif default: eval = make_float3(0.0f, 0.0f, 0.0f); break; @@ -266,6 +277,11 @@ ccl_device float3 bsdf_eval(KernelGlobals *kg, const ShaderData *sd, const Shade eval = bsdf_hair_transmission_eval_transmit(sc, sd->I, omega_in, pdf); break; #endif +#ifdef __VOLUME__ + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: + eval = volume_henyey_greenstein_eval_phase(sc, sd->I, omega_in, pdf); + break; +#endif default: eval = make_float3(0.0f, 0.0f, 0.0f); break; @@ -344,6 +360,7 @@ ccl_device void bsdf_blur(KernelGlobals *kg, ShaderClosure *sc, float roughness) bsdf_hair_reflection_blur(sc, roughness); break; #endif + /* todo: do we want to blur volume closures? */ default: break; } diff --git a/intern/cycles/kernel/closure/volume.h b/intern/cycles/kernel/closure/volume.h index 951a03bce71..058c4b8408f 100644 --- a/intern/cycles/kernel/closure/volume.h +++ b/intern/cycles/kernel/closure/volume.h @@ -39,7 +39,7 @@ ccl_device int volume_henyey_greenstein_setup(ShaderClosure *sc) /* clamp anisotropy to avoid delta function */ sc->data0 = signf(sc->data0) * min(fabsf(sc->data0), 1.0f - 1e-3f); - return SD_BSDF|SD_BSDF_HAS_EVAL|SD_SCATTER; + return SD_SCATTER|SD_PHASE_HAS_EVAL; } ccl_device float3 volume_henyey_greenstein_eval_phase(const ShaderClosure *sc, const float3 I, float3 omega_in, float *pdf) @@ -103,13 +103,13 @@ ccl_device int volume_absorption_setup(ShaderClosure *sc) /* VOLUME CLOSURE */ -ccl_device float3 volume_eval_phase(const ShaderClosure *sc, const float3 I, float3 omega_in, float *pdf) +ccl_device float3 volume_phase_eval(const ShaderData *sd, const ShaderClosure *sc, float3 omega_in, float *pdf) { float3 eval; switch(sc->type) { case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: - eval = volume_henyey_greenstein_eval_phase(sc, I, omega_in, pdf); + eval = volume_henyey_greenstein_eval_phase(sc, sd->I, omega_in, pdf); break; default: eval = make_float3(0.0f, 0.0f, 0.0f); @@ -119,7 +119,7 @@ ccl_device float3 volume_eval_phase(const ShaderClosure *sc, const float3 I, flo return eval; } -ccl_device int volume_sample(const ShaderData *sd, const ShaderClosure *sc, float randu, +ccl_device int volume_phase_sample(const ShaderData *sd, const ShaderClosure *sc, float randu, float randv, float3 *eval, float3 *omega_in, differential3 *domega_in, float *pdf) { int label; diff --git a/intern/cycles/kernel/kernel_accumulate.h b/intern/cycles/kernel/kernel_accumulate.h index f4febd7cf2c..24e9d7b8858 100644 --- a/intern/cycles/kernel/kernel_accumulate.h +++ b/intern/cycles/kernel/kernel_accumulate.h @@ -35,7 +35,7 @@ ccl_device_inline void bsdf_eval_init(BsdfEval *eval, ClosureType type, float3 v if(type == CLOSURE_BSDF_TRANSPARENT_ID) eval->transparent = value; - else if(CLOSURE_IS_BSDF_DIFFUSE(type)) + else if(CLOSURE_IS_BSDF_DIFFUSE(type) || CLOSURE_IS_PHASE(type)) eval->diffuse = value; else if(CLOSURE_IS_BSDF_GLOSSY(type)) eval->glossy = value; @@ -55,7 +55,7 @@ ccl_device_inline void bsdf_eval_accum(BsdfEval *eval, ClosureType type, float3 { #ifdef __PASSES__ if(eval->use_light_pass) { - if(CLOSURE_IS_BSDF_DIFFUSE(type)) + if(CLOSURE_IS_BSDF_DIFFUSE(type) || CLOSURE_IS_PHASE(type)) eval->diffuse += value; else if(CLOSURE_IS_BSDF_GLOSSY(type)) eval->glossy += value; diff --git a/intern/cycles/kernel/kernel_emission.h b/intern/cycles/kernel/kernel_emission.h index e0db9435239..1ec116b7635 100644 --- a/intern/cycles/kernel/kernel_emission.h +++ b/intern/cycles/kernel/kernel_emission.h @@ -103,7 +103,14 @@ ccl_device_noinline bool direct_emission(KernelGlobals *kg, ShaderData *sd, int /* evaluate BSDF at shading point */ float bsdf_pdf; +#ifdef __VOLUME__ + if(sd->prim != ~0) + shader_bsdf_eval(kg, sd, ls.D, eval, &bsdf_pdf); + else + shader_volume_phase_eval(kg, sd, ls.D, eval, &bsdf_pdf); +#else shader_bsdf_eval(kg, sd, ls.D, eval, &bsdf_pdf); +#endif if(ls.shader & SHADER_USE_MIS) { /* multiple importance sampling */ diff --git a/intern/cycles/kernel/kernel_path.h b/intern/cycles/kernel/kernel_path.h index fda6b45e149..8460af85d4c 100644 --- a/intern/cycles/kernel/kernel_path.h +++ b/intern/cycles/kernel/kernel_path.h @@ -48,6 +48,89 @@ CCL_NAMESPACE_BEGIN +#ifdef __VOLUME__ + +ccl_device_inline bool kernel_path_integrate_scatter_lighting(KernelGlobals *kg, RNG *rng, + ShaderData *sd, float3 *throughput, PathState *state, PathRadiance *L, Ray *ray, + float num_samples_adjust) +{ +#ifdef __EMISSION__ + if(kernel_data.integrator.use_direct_light) { + /* sample illumination from lights to find path contribution */ + if(sd->flag & SD_BSDF_HAS_EVAL) { + float light_t = path_state_rng_1D(kg, rng, state, PRNG_LIGHT); +#ifdef __MULTI_CLOSURE__ + float light_o = 0.0f; +#else + float light_o = path_state_rng_1D(kg, rng, state, PRNG_LIGHT_F); +#endif + float light_u, light_v; + path_state_rng_2D(kg, rng, state, PRNG_LIGHT_U, &light_u, &light_v); + + Ray light_ray; + BsdfEval L_light; + bool is_lamp; + +#ifdef __OBJECT_MOTION__ + light_ray.time = sd->time; +#endif + + if(direct_emission(kg, sd, -1, light_t, light_o, light_u, light_v, &light_ray, &L_light, &is_lamp, state->bounce)) { + /* trace shadow ray */ + float3 shadow; + + if(!shadow_blocked(kg, state, &light_ray, &shadow)) { + /* accumulate */ + path_radiance_accum_light(L, *throughput * num_samples_adjust, &L_light, shadow, 1.0f, state->bounce, is_lamp); + } + } + } + } +#endif + + /* sample phase function */ + float phase_pdf; + BsdfEval phase_eval; + float3 phase_omega_in; + differential3 phase_domega_in; + float phase_u, phase_v; + path_state_rng_2D(kg, rng, state, PRNG_PHASE_U, &phase_u, &phase_v); + int label; + + label = shader_volume_phase_sample(kg, sd, phase_u, phase_v, &phase_eval, + &phase_omega_in, &phase_domega_in, &phase_pdf); + + if(phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) + return false; + + /* modify throughput */ + path_radiance_bsdf_bounce(L, throughput, &phase_eval, phase_pdf, state->bounce, label); + + /* set labels */ + state->ray_pdf = phase_pdf; +#ifdef __LAMP_MIS__ + state->ray_t = 0.0f; +#endif + state->min_ray_pdf = fminf(phase_pdf, state->min_ray_pdf); + + /* update path state */ + path_state_next(kg, state, label); + + /* setup ray */ + ray->P = sd->P; + ray->D = phase_omega_in; + ray->t = FLT_MAX; + +#ifdef __RAY_DIFFERENTIALS__ + ray->dP = sd->dP; + ray->dD = phase_domega_in; +#endif + + return true; +} + +#endif + #if defined(__BRANCHED_PATH__) || defined(__SUBSURFACE__) ccl_device void kernel_path_indirect(KernelGlobals *kg, RNG *rng, Ray ray, ccl_global float *buffer, @@ -91,7 +174,17 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, RNG *rng, Ray ray, ccl_g if(state.volume_stack[0].shader != SHADER_NO_ID) { Ray volume_ray = ray; volume_ray.t = (hit)? isect.t: FLT_MAX; - kernel_volume_integrate(kg, &state, &volume_ray, L, &throughput); + + ShaderData volume_sd; + VolumeIntegrateResult result = kernel_volume_integrate(kg, &state, + &volume_sd, &volume_ray, L, &throughput, rng); + + if(result == VOLUME_PATH_SCATTERED) { + if(kernel_path_integrate_scatter_lighting(kg, rng, &volume_sd, &throughput, &state, L, &ray, 1.0f)) + continue; + else + break; + } } #endif @@ -498,7 +591,17 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample, if(state.volume_stack[0].shader != SHADER_NO_ID) { Ray volume_ray = ray; volume_ray.t = (hit)? isect.t: FLT_MAX; - kernel_volume_integrate(kg, &state, &volume_ray, &L, &throughput); + + ShaderData volume_sd; + VolumeIntegrateResult result = kernel_volume_integrate(kg, &state, + &volume_sd, &volume_ray, &L, &throughput, rng); + + if(result == VOLUME_PATH_SCATTERED) { + if(kernel_path_integrate_scatter_lighting(kg, rng, &volume_sd, &throughput, &state, &L, &ray, 1.0f)) + continue; + else + break; + } } #endif @@ -934,7 +1037,7 @@ ccl_device_noinline void kernel_branched_path_integrate_lighting(KernelGlobals * kernel_volume_stack_enter_exit(kg, sd, ps.volume_stack); #endif - /* update RNG state */ + /* branch RNG state */ path_state_branch(&ps, j, num_samples); /* set MIS state */ @@ -996,7 +1099,42 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in if(state.volume_stack[0].shader != SHADER_NO_ID) { Ray volume_ray = ray; volume_ray.t = (hit)? isect.t: FLT_MAX; - kernel_volume_integrate(kg, &state, &volume_ray, &L, &throughput); + + int num_samples = kernel_data.integrator.volume_samples; + float num_samples_inv = 1.0f/num_samples; + float3 avg_tp = make_float3(0.0f, 0.0f, 0.0f); + + /* todo: we should cache the shader evaluations from stepping + * through the volume, for now we redo them multiple times */ + + for(int j = 0; j < num_samples; j++) { + PathState ps = state; + Ray pray = ray; + ShaderData volume_sd; + float3 tp = throughput; + + /* branch RNG state */ + path_state_branch(&ps, j, num_samples); + + VolumeIntegrateResult result = kernel_volume_integrate(kg, &ps, + &volume_sd, &volume_ray, &L, &tp, rng); + + if(result == VOLUME_PATH_SCATTERED) { + /* todo: use all-light sampling */ + if(kernel_path_integrate_scatter_lighting(kg, rng, &volume_sd, &tp, &ps, &L, &pray, num_samples_inv)) { + kernel_path_indirect(kg, rng, pray, buffer, tp*num_samples_inv, num_samples, ps, &L); + + /* for render passes, sum and reset indirect light pass variables + * for the next samples */ + path_radiance_sum_indirect(&L); + path_radiance_reset_indirect(&L); + } + } + else + avg_tp += tp; + } + + throughput = avg_tp * num_samples_inv; } #endif diff --git a/intern/cycles/kernel/kernel_path_state.h b/intern/cycles/kernel/kernel_path_state.h index fe4d38b10d6..5def19f2c42 100644 --- a/intern/cycles/kernel/kernel_path_state.h +++ b/intern/cycles/kernel/kernel_path_state.h @@ -42,6 +42,8 @@ ccl_device_inline void path_state_init(KernelGlobals *kg, PathState *state, RNG #ifdef __VOLUME__ if(kernel_data.integrator.use_volumes) { + state->volume_bounce = 0; + /* initialize volume stack with volume we are inside of */ kernel_volume_stack_init(kg, state->volume_stack); /* seed RNG for cases where we can't use stratified samples */ @@ -61,6 +63,9 @@ ccl_device_inline void path_state_next(KernelGlobals *kg, PathState *state, int state->flag |= PATH_RAY_TRANSPARENT; state->transparent_bounce++; + /* random number generator next bounce */ + state->rng_offset += PRNG_BOUNCE_NUM; + if(!kernel_data.integrator.transparent_shadows) state->flag |= PATH_RAY_MIS_SKIP; @@ -69,39 +74,51 @@ ccl_device_inline void path_state_next(KernelGlobals *kg, PathState *state, int state->bounce++; - /* reflection/transmission */ - if(label & LABEL_REFLECT) { - state->flag |= PATH_RAY_REFLECT; - state->flag &= ~(PATH_RAY_TRANSMIT|PATH_RAY_CAMERA|PATH_RAY_TRANSPARENT); +#ifdef __VOLUME__ + if(label & LABEL_VOLUME_SCATTER) { + /* volume scatter */ + state->flag |= PATH_RAY_VOLUME_SCATTER; + state->flag &= ~(PATH_RAY_REFLECT|PATH_RAY_TRANSMIT|PATH_RAY_CAMERA|PATH_RAY_TRANSPARENT|PATH_RAY_DIFFUSE|PATH_RAY_GLOSSY|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP); - if(label & LABEL_DIFFUSE) - state->diffuse_bounce++; - else - state->glossy_bounce++; + state->volume_bounce++; } - else { - kernel_assert(label & LABEL_TRANSMIT); + else +#endif + { + /* surface reflection/transmission */ + if(label & LABEL_REFLECT) { + state->flag |= PATH_RAY_REFLECT; + state->flag &= ~(PATH_RAY_TRANSMIT|PATH_RAY_VOLUME_SCATTER|PATH_RAY_CAMERA|PATH_RAY_TRANSPARENT); + + if(label & LABEL_DIFFUSE) + state->diffuse_bounce++; + else + state->glossy_bounce++; + } + else { + kernel_assert(label & LABEL_TRANSMIT); - state->flag |= PATH_RAY_TRANSMIT; - state->flag &= ~(PATH_RAY_REFLECT|PATH_RAY_CAMERA|PATH_RAY_TRANSPARENT); + state->flag |= PATH_RAY_TRANSMIT; + state->flag &= ~(PATH_RAY_REFLECT|PATH_RAY_VOLUME_SCATTER|PATH_RAY_CAMERA|PATH_RAY_TRANSPARENT); - state->transmission_bounce++; - } + state->transmission_bounce++; + } - /* diffuse/glossy/singular */ - if(label & LABEL_DIFFUSE) { - state->flag |= PATH_RAY_DIFFUSE|PATH_RAY_DIFFUSE_ANCESTOR; - state->flag &= ~(PATH_RAY_GLOSSY|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP); - } - else if(label & LABEL_GLOSSY) { - state->flag |= PATH_RAY_GLOSSY|PATH_RAY_GLOSSY_ANCESTOR; - state->flag &= ~(PATH_RAY_DIFFUSE|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP); - } - else { - kernel_assert(label & LABEL_SINGULAR); + /* diffuse/glossy/singular */ + if(label & LABEL_DIFFUSE) { + state->flag |= PATH_RAY_DIFFUSE|PATH_RAY_DIFFUSE_ANCESTOR; + state->flag &= ~(PATH_RAY_GLOSSY|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP); + } + else if(label & LABEL_GLOSSY) { + state->flag |= PATH_RAY_GLOSSY|PATH_RAY_GLOSSY_ANCESTOR; + state->flag &= ~(PATH_RAY_DIFFUSE|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP); + } + else { + kernel_assert(label & LABEL_SINGULAR); - state->flag |= PATH_RAY_GLOSSY|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP; - state->flag &= ~PATH_RAY_DIFFUSE; + state->flag |= PATH_RAY_GLOSSY|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP; + state->flag &= ~PATH_RAY_DIFFUSE; + } } /* random number generator next bounce */ @@ -136,7 +153,8 @@ ccl_device_inline float path_state_terminate_probability(KernelGlobals *kg, Path if((state->bounce >= kernel_data.integrator.max_bounce) || (state->diffuse_bounce >= kernel_data.integrator.max_diffuse_bounce) || (state->glossy_bounce >= kernel_data.integrator.max_glossy_bounce) || - (state->transmission_bounce >= kernel_data.integrator.max_transmission_bounce)) + (state->transmission_bounce >= kernel_data.integrator.max_transmission_bounce) || + (state->volume_bounce >= kernel_data.integrator.max_volume_bounce)) { return 0.0f; } diff --git a/intern/cycles/kernel/kernel_shader.h b/intern/cycles/kernel/kernel_shader.h index 7b9a4ab12b1..35844dc4f65 100644 --- a/intern/cycles/kernel/kernel_shader.h +++ b/intern/cycles/kernel/kernel_shader.h @@ -27,7 +27,6 @@ #include "closure/bsdf_util.h" #include "closure/bsdf.h" #include "closure/emissive.h" -#include "closure/volume.h" #include "svm/svm.h" @@ -958,37 +957,120 @@ ccl_device float3 shader_eval_background(KernelGlobals *kg, ShaderData *sd, int /* Volume */ #ifdef __VOLUME__ -ccl_device float3 shader_volume_eval_phase(KernelGlobals *kg, ShaderData *sd, - float3 omega_in, float3 omega_out) -{ -#ifdef __MULTI_CLOSURE__ - float3 eval = make_float3(0.0f, 0.0f, 0.0f); - float pdf; +ccl_device_inline void _shader_volume_phase_multi_eval(const ShaderData *sd, const float3 omega_in, float *pdf, + int skip_phase, BsdfEval *result_eval, float sum_pdf, float sum_sample_weight) +{ for(int i = 0; i< sd->num_closure; i++) { + if(i == skip_phase) + continue; + const ShaderClosure *sc = &sd->closure[i]; - if(CLOSURE_IS_VOLUME(sc->type)) - eval += volume_eval_phase(sc, omega_in, omega_out, &pdf); + if(CLOSURE_IS_PHASE(sc->type)) { + float phase_pdf = 0.0f; + float3 eval = volume_phase_eval(sd, sc, omega_in, &phase_pdf); + + if(phase_pdf != 0.0f) { + bsdf_eval_accum(result_eval, sc->type, eval); + sum_pdf += phase_pdf; + } + + sum_sample_weight += sc->sample_weight; + } } - return eval; -#else - float pdf; - return volume_eval_phase(&sd->closure, omega_in, omega_out, &pdf); -#endif + *pdf = (sum_sample_weight > 0.0f)? sum_pdf/sum_sample_weight: 0.0f; +} + +ccl_device void shader_volume_phase_eval(KernelGlobals *kg, const ShaderData *sd, + const float3 omega_in, BsdfEval *eval, float *pdf) +{ + bsdf_eval_init(eval, NBUILTIN_CLOSURES, make_float3(0.0f, 0.0f, 0.0f), kernel_data.film.use_light_pass); + + _shader_volume_phase_multi_eval(sd, omega_in, pdf, -1, eval, 0.0f, 0.0f); } +ccl_device int shader_volume_phase_sample(KernelGlobals *kg, const ShaderData *sd, + float randu, float randv, BsdfEval *phase_eval, + float3 *omega_in, differential3 *domega_in, float *pdf) +{ + int sampled = 0; + + if(sd->num_closure > 1) { + /* pick a phase closure based on sample weights */ + float sum = 0.0f; + + for(sampled = 0; sampled < sd->num_closure; sampled++) { + const ShaderClosure *sc = &sd->closure[sampled]; + + if(CLOSURE_IS_PHASE(sc->type)) + sum += sc->sample_weight; + } + + float r = sd->randb_closure*sum; + sum = 0.0f; + + for(sampled = 0; sampled < sd->num_closure; sampled++) { + const ShaderClosure *sc = &sd->closure[sampled]; + + if(CLOSURE_IS_PHASE(sc->type)) { + sum += sc->sample_weight; + + if(r <= sum) + break; + } + } + + if(sampled == sd->num_closure) { + *pdf = 0.0f; + return LABEL_NONE; + } + } + + /* todo: this isn't quite correct, we don't weight anisotropy properly + * depending on color channels, even if this is perhaps not a common case */ + const ShaderClosure *sc = &sd->closure[sampled]; + int label; + float3 eval; + + *pdf = 0.0f; + label = volume_phase_sample(sd, sc, randu, randv, &eval, omega_in, domega_in, pdf); + + if(*pdf != 0.0f) { + bsdf_eval_init(phase_eval, sc->type, eval, kernel_data.film.use_light_pass); + } + + return label; +} + +ccl_device int shader_phase_sample_closure(KernelGlobals *kg, const ShaderData *sd, + const ShaderClosure *sc, float randu, float randv, BsdfEval *phase_eval, + float3 *omega_in, differential3 *domega_in, float *pdf) +{ + int label; + float3 eval; + + *pdf = 0.0f; + label = volume_phase_sample(sd, sc, randu, randv, &eval, omega_in, domega_in, pdf); + + if(*pdf != 0.0f) + bsdf_eval_init(phase_eval, sc->type, eval, kernel_data.film.use_light_pass); + + return label; +} + +#endif + /* Volume Evaluation */ ccl_device void shader_eval_volume(KernelGlobals *kg, ShaderData *sd, - VolumeStack *stack, float randb, int path_flag, ShaderContext ctx) + VolumeStack *stack, int path_flag, ShaderContext ctx) { /* reset closures once at the start, we will be accumulating the closures * for all volumes in the stack into a single array of closures */ #ifdef __MULTI_CLOSURE__ sd->num_closure = 0; - sd->randb_closure = randb; #else sd->closure.type = NBUILTIN_CLOSURES; #endif @@ -1022,7 +1104,7 @@ ccl_device void shader_eval_volume(KernelGlobals *kg, ShaderData *sd, else #endif { - svm_eval_nodes(kg, sd, SHADER_TYPE_VOLUME, randb, path_flag); + svm_eval_nodes(kg, sd, SHADER_TYPE_VOLUME, 0.0f, path_flag); } #endif @@ -1031,7 +1113,6 @@ ccl_device void shader_eval_volume(KernelGlobals *kg, ShaderData *sd, shader_merge_closures(sd); } } -#endif /* Displacement Evaluation */ diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index faed1e7a71e..77ecc2b8dd8 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -190,7 +190,16 @@ enum PathTraceDimension { PRNG_LIGHT_V = 5, PRNG_LIGHT_F = 6, PRNG_TERMINATE = 7, - PRNG_BOUNCE_NUM = 8 + +#ifdef __VOLUME__ + PRNG_PHASE_U = 8, + PRNG_PHASE_V = 9, + PRNG_PHASE = 10, + PRNG_SCATTER_DISTANCE = 11, + PRNG_BOUNCE_NUM = 12, +#else + PRNG_BOUNCE_NUM = 8, +#endif }; enum SamplingPattern { @@ -503,11 +512,12 @@ enum ShaderDataFlag { SD_EMISSION = 2, /* have emissive closure? */ SD_BSDF = 4, /* have bsdf closure? */ SD_BSDF_HAS_EVAL = 8, /* have non-singular bsdf closure? */ + SD_PHASE_HAS_EVAL = 8, /* have non-singular phase closure? */ SD_BSDF_GLOSSY = 16, /* have glossy bsdf */ SD_BSSRDF = 32, /* have bssrdf */ SD_HOLDOUT = 64, /* have holdout closure? */ SD_ABSORPTION = 128, /* have volume absorption closure? */ - SD_SCATTER = 256, /* have volume scattering closure? */ + SD_SCATTER = 256, /* have volume phase closure? */ SD_AO = 512, /* have ao closure? */ SD_CLOSURE_FLAGS = (SD_EMISSION|SD_BSDF|SD_BSDF_HAS_EVAL|SD_BSDF_GLOSSY|SD_BSSRDF|SD_HOLDOUT|SD_ABSORPTION|SD_SCATTER|SD_AO), @@ -646,6 +656,7 @@ typedef struct PathState { /* volume rendering */ #ifdef __VOLUME__ + int volume_bounce; RNG rng_congruential; VolumeStack volume_stack[VOLUME_STACK_SIZE]; #endif @@ -791,6 +802,7 @@ typedef struct KernelIntegrator { int max_diffuse_bounce; int max_glossy_bounce; int max_transmission_bounce; + int max_volume_bounce; /* transparent */ int transparent_min_bounce; @@ -830,7 +842,7 @@ typedef struct KernelIntegrator { int use_volumes; int volume_max_steps; float volume_step_size; - int pad1, pad2; + int volume_samples; } KernelIntegrator; typedef struct KernelBVH { diff --git a/intern/cycles/kernel/kernel_volume.h b/intern/cycles/kernel/kernel_volume.h index 65333190350..dc2ddf1098e 100644 --- a/intern/cycles/kernel/kernel_volume.h +++ b/intern/cycles/kernel/kernel_volume.h @@ -17,10 +17,9 @@ CCL_NAMESPACE_BEGIN typedef enum VolumeIntegrateResult { - VOLUME_PATH_TERMINATED = 0, - VOLUME_PATH_SCATTERED = 1, - VOLUME_PATH_ATTENUATED = 2, - VOLUME_PATH_MISSED = 3 + VOLUME_PATH_SCATTERED = 0, + VOLUME_PATH_ATTENUATED = 1, + VOLUME_PATH_MISSED = 2 } VolumeIntegrateResult; /* Volume shader properties @@ -35,10 +34,10 @@ typedef struct VolumeShaderCoefficients { } VolumeShaderCoefficients; /* evaluate shader to get extinction coefficient at P */ -ccl_device bool volume_shader_extinction_sample(KernelGlobals *kg, ShaderData *sd, VolumeStack *stack, int path_flag, ShaderContext ctx, float3 P, float3 *extinction) +ccl_device bool volume_shader_extinction_sample(KernelGlobals *kg, ShaderData *sd, PathState *state, float3 P, float3 *extinction) { sd->P = P; - shader_eval_volume(kg, sd, stack, 0.0f, path_flag, ctx); + shader_eval_volume(kg, sd, state->volume_stack, PATH_RAY_SHADOW, SHADER_CONTEXT_SHADOW); if(!(sd->flag & (SD_ABSORPTION|SD_SCATTER))) return false; @@ -57,27 +56,37 @@ ccl_device bool volume_shader_extinction_sample(KernelGlobals *kg, ShaderData *s } /* evaluate shader to get absorption, scattering and emission at P */ -ccl_device bool volume_shader_sample(KernelGlobals *kg, ShaderData *sd, VolumeStack *stack, int path_flag, ShaderContext ctx, float3 P, VolumeShaderCoefficients *sample) +ccl_device bool volume_shader_sample(KernelGlobals *kg, ShaderData *sd, PathState *state, float3 P, VolumeShaderCoefficients *coeff) { sd->P = P; - shader_eval_volume(kg, sd, stack, 0.0f, path_flag, ctx); + shader_eval_volume(kg, sd, state->volume_stack, state->flag, SHADER_CONTEXT_VOLUME); if(!(sd->flag & (SD_ABSORPTION|SD_SCATTER|SD_EMISSION))) return false; - - sample->sigma_a = make_float3(0.0f, 0.0f, 0.0f); - sample->sigma_s = make_float3(0.0f, 0.0f, 0.0f); - sample->emission = make_float3(0.0f, 0.0f, 0.0f); + + coeff->sigma_a = make_float3(0.0f, 0.0f, 0.0f); + coeff->sigma_s = make_float3(0.0f, 0.0f, 0.0f); + coeff->emission = make_float3(0.0f, 0.0f, 0.0f); for(int i = 0; i < sd->num_closure; i++) { const ShaderClosure *sc = &sd->closure[i]; if(sc->type == CLOSURE_VOLUME_ABSORPTION_ID) - sample->sigma_a += sc->weight; + coeff->sigma_a += sc->weight; else if(sc->type == CLOSURE_EMISSION_ID) - sample->emission += sc->weight; + coeff->emission += sc->weight; else if(CLOSURE_IS_VOLUME(sc->type)) - sample->sigma_s += sc->weight; + coeff->sigma_s += sc->weight; + } + + /* when at the max number of bounces, treat scattering as absorption */ + if(sd->flag & SD_SCATTER) { + if(state->volume_bounce >= kernel_data.integrator.max_volume_bounce) { + coeff->sigma_a += coeff->sigma_s; + coeff->sigma_s = make_float3(0.0f, 0.0f, 0.0f); + sd->flag &= ~SD_SCATTER; + sd->flag |= SD_ABSORPTION; + } } return true; @@ -100,7 +109,7 @@ ccl_device bool volume_stack_is_heterogeneous(KernelGlobals *kg, VolumeStack *st return false; } -/* Volumetric Shadows +/* Volume Shadows * * These functions are used to attenuate shadow rays to lights. Both absorption * and scattering will block light, represented by the extinction coefficient. */ @@ -109,11 +118,9 @@ ccl_device bool volume_stack_is_heterogeneous(KernelGlobals *kg, VolumeStack *st * the extinction coefficient for the entire line segment */ ccl_device void kernel_volume_shadow_homogeneous(KernelGlobals *kg, PathState *state, Ray *ray, ShaderData *sd, float3 *throughput) { - ShaderContext ctx = SHADER_CONTEXT_SHADOW; - int path_flag = PATH_RAY_SHADOW; float3 sigma_t; - if(volume_shader_extinction_sample(kg, sd, state->volume_stack, path_flag, ctx, ray->P, &sigma_t)) + if(volume_shader_extinction_sample(kg, sd, state, ray->P, &sigma_t)) *throughput *= volume_color_attenuation(sigma_t, ray->t); } @@ -121,8 +128,6 @@ ccl_device void kernel_volume_shadow_homogeneous(KernelGlobals *kg, PathState *s * reach the end, get absorbed entirely, or run out of iterations */ ccl_device void kernel_volume_shadow_heterogeneous(KernelGlobals *kg, PathState *state, Ray *ray, ShaderData *sd, float3 *throughput) { - ShaderContext ctx = SHADER_CONTEXT_SHADOW; - int path_flag = PATH_RAY_SHADOW; float3 tp = *throughput; const float tp_eps = 1e-10f; /* todo: this is likely not the right value */ @@ -136,7 +141,7 @@ ccl_device void kernel_volume_shadow_heterogeneous(KernelGlobals *kg, PathState float3 P = ray->P; float3 sigma_t; - if(!volume_shader_extinction_sample(kg, sd, state->volume_stack, path_flag, ctx, P, &sigma_t)) + if(!volume_shader_extinction_sample(kg, sd, state, P, &sigma_t)) sigma_t = make_float3(0.0f, 0.0f, 0.0f); for(int i = 0; i < max_steps; i++) { @@ -146,7 +151,7 @@ ccl_device void kernel_volume_shadow_heterogeneous(KernelGlobals *kg, PathState float3 new_sigma_t; /* compute attenuation over segment */ - if(volume_shader_extinction_sample(kg, sd, state->volume_stack, path_flag, ctx, new_P, &new_sigma_t)) { + if(volume_shader_extinction_sample(kg, sd, state, new_P, &new_sigma_t)) { /* todo: we could avoid computing expf() for each step by summing, * because exp(a)*exp(b) = exp(a+b), but we still want a quick * tp_eps check too */ @@ -174,7 +179,7 @@ ccl_device void kernel_volume_shadow_heterogeneous(KernelGlobals *kg, PathState /* get the volume attenuation over line segment defined by ray, with the * assumption that there are no surfaces blocking light between the endpoints */ -ccl_device void kernel_volume_shadow(KernelGlobals *kg, PathState *state, Ray *ray, float3 *throughput) +ccl_device_noinline void kernel_volume_shadow(KernelGlobals *kg, PathState *state, Ray *ray, float3 *throughput) { ShaderData sd; shader_setup_from_volume(kg, &sd, ray, state->bounce); @@ -185,46 +190,81 @@ ccl_device void kernel_volume_shadow(KernelGlobals *kg, PathState *state, Ray *r kernel_volume_shadow_homogeneous(kg, state, ray, &sd, throughput); } -/* Volumetric Path */ +/* Volume Path */ /* homogenous volume: assume shader evaluation at the starts gives * the volume shading coefficient for the entire line segment */ -ccl_device VolumeIntegrateResult kernel_volume_integrate_homogeneous(KernelGlobals *kg, PathState *state, Ray *ray, ShaderData *sd, PathRadiance *L, float3 *throughput) +ccl_device VolumeIntegrateResult kernel_volume_integrate_homogeneous(KernelGlobals *kg, + PathState *state, Ray *ray, ShaderData *sd, PathRadiance *L, float3 *throughput, + RNG *rng) { - ShaderContext ctx = SHADER_CONTEXT_VOLUME; - int path_flag = PATH_RAY_SHADOW; VolumeShaderCoefficients coeff; - if(!volume_shader_sample(kg, sd, state->volume_stack, path_flag, ctx, ray->P, &coeff)) + if(!volume_shader_sample(kg, sd, state, ray->P, &coeff)) return VOLUME_PATH_MISSED; - /* todo: in principle the SD_EMISSION, SD_ABSORPTION and SD_SCATTER flags - * should ensure that one of the components is > 0 and so no division by - * zero occurs, however this needs to be double-checked and tested */ - int closure_flag = sd->flag; float t = ray->t; + float3 new_tp; + float3 transmittance; + + /* randomly scatter, and if we do t is shortened */ + if(closure_flag & SD_SCATTER) { + float3 sigma_t = coeff.sigma_a + coeff.sigma_s; + + /* set up variables for sampling */ + float rphase = path_state_rng_1D(kg, rng, state, PRNG_PHASE); + int channel = (int)(rphase*3.0f); + sd->randb_closure = rphase*3.0f - channel; + + /* pick random color channel, we use the Veach one-sample + * model with balance heuristic for the channels */ + float sample_sigma_t; + + if(channel == 0) + sample_sigma_t = sigma_t.x; + else if(channel == 1) + sample_sigma_t = sigma_t.y; + else + sample_sigma_t = sigma_t.z; + + /* xi is [0, 1[ so log(0) should never happen, division by zero is + * avoided because sample_sigma_t > 0 when SD_SCATTER is set */ + float xi = path_state_rng_1D(kg, rng, state, PRNG_SCATTER_DISTANCE); + float sample_t = min(t, -logf(1.0f - xi)/sample_sigma_t); - /* compute attenuation from absorption */ - float3 attenuation; + transmittance = volume_color_attenuation(sigma_t, sample_t); - if(closure_flag & SD_ABSORPTION) - attenuation = volume_color_attenuation(coeff.sigma_a, t); + if(sample_t < t) { + float pdf = dot(sigma_t, transmittance); + new_tp = *throughput * coeff.sigma_s * transmittance * (3.0f / pdf); + t = sample_t; + } + else { + float pdf = (transmittance.x + transmittance.y + transmittance.z); + new_tp = *throughput * transmittance * (3.0f / pdf); + } + } + else if(closure_flag & SD_ABSORPTION) { + /* absorption only, no sampling needed */ + transmittance = volume_color_attenuation(coeff.sigma_a, t); + new_tp = *throughput * transmittance; + } - /* integrate emission attenuated by absorption - * integral E * exp(-sigma_a * t) from 0 to t = E * (1 - exp(-sigma_a * t))/sigma_a - * this goes to E * t as sigma_a goes to zero + /* integrate emission attenuated by extinction + * integral E * exp(-sigma_t * t) from 0 to t = E * (1 - exp(-sigma_t * t))/sigma_t + * this goes to E * t as sigma_t goes to zero * - * todo: we should use an epsilon to avoid precision issues near zero sigma_a */ + * todo: we should use an epsilon to avoid precision issues near zero sigma_t */ if(closure_flag & SD_EMISSION) { float3 emission = coeff.emission; if(closure_flag & SD_ABSORPTION) { - float3 sigma_a = coeff.sigma_a; + float3 sigma_t = coeff.sigma_a + coeff.sigma_s; - emission.x *= (sigma_a.x > 0.0f)? (1.0f - attenuation.x)/sigma_a.x: t; - emission.y *= (sigma_a.y > 0.0f)? (1.0f - attenuation.y)/sigma_a.y: t; - emission.z *= (sigma_a.z > 0.0f)? (1.0f - attenuation.z)/sigma_a.z: t; + emission.x *= (sigma_t.x > 0.0f)? (1.0f - transmittance.x)/sigma_t.x: t; + emission.y *= (sigma_t.y > 0.0f)? (1.0f - transmittance.y)/sigma_t.y: t; + emission.z *= (sigma_t.z > 0.0f)? (1.0f - transmittance.z)/sigma_t.z: t; } else emission *= t; @@ -233,18 +273,26 @@ ccl_device VolumeIntegrateResult kernel_volume_integrate_homogeneous(KernelGloba } /* modify throughput */ - if(closure_flag & SD_ABSORPTION) - *throughput *= attenuation; + if(closure_flag & (SD_ABSORPTION|SD_SCATTER)) { + *throughput = new_tp; + + /* prepare to scatter to new direction */ + if(t < ray->t) { + /* adjust throughput and move to new location */ + sd->P = ray->P + t*ray->D; + + return VOLUME_PATH_SCATTERED; + } + } return VOLUME_PATH_ATTENUATED; } /* heterogeneous volume: integrate stepping through the volume until we * reach the end, get absorbed entirely, or run out of iterations */ -ccl_device VolumeIntegrateResult kernel_volume_integrate_heterogeneous(KernelGlobals *kg, PathState *state, Ray *ray, ShaderData *sd, PathRadiance *L, float3 *throughput) +ccl_device VolumeIntegrateResult kernel_volume_integrate_heterogeneous(KernelGlobals *kg, + PathState *state, Ray *ray, ShaderData *sd, PathRadiance *L, float3 *throughput, RNG *rng) { - ShaderContext ctx = SHADER_CONTEXT_VOLUME; - int path_flag = PATH_RAY_SHADOW; VolumeShaderCoefficients coeff; float3 tp = *throughput; const float tp_eps = 1e-10f; /* todo: this is likely not the right value */ @@ -258,46 +306,125 @@ ccl_device VolumeIntegrateResult kernel_volume_integrate_heterogeneous(KernelGlo float t = 0.0f; float3 P = ray->P; - if(!volume_shader_sample(kg, sd, state->volume_stack, path_flag, ctx, P, &coeff)) { + if(!volume_shader_sample(kg, sd, state, P, &coeff)) { coeff.sigma_a = make_float3(0.0f, 0.0f, 0.0f); coeff.sigma_s = make_float3(0.0f, 0.0f, 0.0f); coeff.emission = make_float3(0.0f, 0.0f, 0.0f); } + /* accumulate these values so we can use a single stratified number to sample */ + float3 accum_transmittance = make_float3(1.0f, 1.0f, 1.0f); + float3 accum_sigma_t = make_float3(0.0f, 0.0f, 0.0f); + float3 accum_sigma_s = make_float3(0.0f, 0.0f, 0.0f); + + /* cache some constant variables */ + float nlogxi; + int channel = -1; + bool has_scatter = false; + for(int i = 0; i < max_steps; i++) { /* advance to new position */ float new_t = min(ray->t, t + random_jitter_offset + i * step); float3 new_P = ray->P + ray->D * new_t; VolumeShaderCoefficients new_coeff; - /* compute attenuation over segment */ - if(volume_shader_sample(kg, sd, state->volume_stack, path_flag, ctx, new_P, &new_coeff)) { + /* compute segment */ + if(volume_shader_sample(kg, sd, state, new_P, &new_coeff)) { int closure_flag = sd->flag; float dt = new_t - t; + float3 new_tp; + float3 transmittance; + bool scatter = false; + + /* randomly scatter, and if we do dt and new_t are shortened */ + if((closure_flag & SD_SCATTER) || (has_scatter && (closure_flag & SD_ABSORPTION))) { + has_scatter = true; + + /* average sigma_t and sigma_s over segment */ + float3 last_sigma_t = coeff.sigma_a + coeff.sigma_s; + float3 new_sigma_t = new_coeff.sigma_a + new_coeff.sigma_s; + float3 sigma_t = 0.5f*(last_sigma_t + new_sigma_t); + float3 sigma_s = 0.5f*(coeff.sigma_s + new_coeff.sigma_s); + + /* lazily set up variables for sampling */ + if(channel == -1) { + float xi = path_state_rng_1D(kg, rng, state, PRNG_SCATTER_DISTANCE); + nlogxi = -logf(1.0f - xi); + + float rphase = path_state_rng_1D(kg, rng, state, PRNG_PHASE); + channel = (int)(rphase*3.0f); + sd->randb_closure = rphase*3.0f - channel; + } - /* compute attenuation from absorption */ - float3 attenuation, sigma_a; + /* pick random color channel, we use the Veach one-sample + * model with balance heuristic for the channels */ + float sample_sigma_t; + + if(channel == 0) + sample_sigma_t = accum_sigma_t.x + dt*sigma_t.x; + else if(channel == 1) + sample_sigma_t = accum_sigma_t.y + dt*sigma_t.y; + else + sample_sigma_t = accum_sigma_t.z + dt*sigma_t.z; + + if(nlogxi < sample_sigma_t) { + /* compute sampling distance */ + sample_sigma_t /= new_t; + new_t = nlogxi/sample_sigma_t; + dt = new_t - t; + + transmittance = volume_color_attenuation(sigma_t, dt); + + accum_transmittance *= transmittance; + accum_sigma_t = (accum_sigma_t + dt*sigma_t)/new_t; + accum_sigma_s = (accum_sigma_s + dt*sigma_s)/new_t; + + /* todo: it's not clear to me that this is correct if we move + * through a color volumed, needs verification */ + float pdf = dot(accum_sigma_t, accum_transmittance); + new_tp = tp * accum_sigma_s * transmittance * (3.0f / pdf); + + scatter = true; + } + else { + transmittance = volume_color_attenuation(sigma_t, dt); + + accum_transmittance *= transmittance; + accum_sigma_t += dt*sigma_t; + accum_sigma_s += dt*sigma_s; + + new_tp = tp * transmittance; + } + } + else if(closure_flag & SD_ABSORPTION) { + /* absorption only, no sampling needed */ + float3 sigma_a = 0.5f*(coeff.sigma_a + new_coeff.sigma_a); + transmittance = volume_color_attenuation(sigma_a, dt); + + accum_transmittance *= transmittance; + accum_sigma_t += dt*sigma_a; + + new_tp = tp * transmittance; - if(closure_flag & SD_ABSORPTION) { /* todo: we could avoid computing expf() for each step by summing, * because exp(a)*exp(b) = exp(a+b), but we still want a quick * tp_eps check too */ - sigma_a = 0.5f*(coeff.sigma_a + new_coeff.sigma_a); - attenuation = volume_color_attenuation(sigma_a, dt); } /* integrate emission attenuated by absorption - * integral E * exp(-sigma_a * t) from 0 to t = E * (1 - exp(-sigma_a * t))/sigma_a - * this goes to E * t as sigma_a goes to zero + * integral E * exp(-sigma_t * t) from 0 to t = E * (1 - exp(-sigma_t * t))/sigma_t + * this goes to E * t as sigma_t goes to zero * - * todo: we should use an epsilon to avoid precision issues near zero sigma_a */ + * todo: we should use an epsilon to avoid precision issues near zero sigma_t */ if(closure_flag & SD_EMISSION) { float3 emission = 0.5f*(coeff.emission + new_coeff.emission); if(closure_flag & SD_ABSORPTION) { - emission.x *= (sigma_a.x > 0.0f)? (1.0f - attenuation.x)/sigma_a.x: dt; - emission.y *= (sigma_a.y > 0.0f)? (1.0f - attenuation.y)/sigma_a.y: dt; - emission.z *= (sigma_a.z > 0.0f)? (1.0f - attenuation.z)/sigma_a.z: dt; + float3 sigma_t = 0.5f*(coeff.sigma_a + coeff.sigma_s + new_coeff.sigma_a + new_coeff.sigma_s); + + emission.x *= (sigma_t.x > 0.0f)? (1.0f - transmittance.x)/sigma_t.x: dt; + emission.y *= (sigma_t.y > 0.0f)? (1.0f - transmittance.y)/sigma_t.y: dt; + emission.z *= (sigma_t.z > 0.0f)? (1.0f - transmittance.z)/sigma_t.z: dt; } else emission *= dt; @@ -306,14 +433,23 @@ ccl_device VolumeIntegrateResult kernel_volume_integrate_heterogeneous(KernelGlo } /* modify throughput */ - if(closure_flag & SD_ABSORPTION) { - tp *= attenuation; + if(closure_flag & (SD_ABSORPTION|SD_SCATTER)) { + tp = new_tp; /* stop if nearly all light blocked */ if(tp.x < tp_eps && tp.y < tp_eps && tp.z < tp_eps) { tp = make_float3(0.0f, 0.0f, 0.0f); break; } + + /* prepare to scatter to new direction */ + if(scatter) { + /* adjust throughput and move to new location */ + sd->P = ray->P + new_t*ray->D; + *throughput = tp; + + return VOLUME_PATH_SCATTERED; + } } coeff = new_coeff; @@ -331,6 +467,13 @@ ccl_device VolumeIntegrateResult kernel_volume_integrate_heterogeneous(KernelGlo break; } + /* include pdf for volumes with scattering */ + if(has_scatter) { + float pdf = (accum_transmittance.x + accum_transmittance.y + accum_transmittance.z); + if(pdf > 0.0f) + tp *= (3.0f/pdf); + } + *throughput = tp; return VOLUME_PATH_ATTENUATED; @@ -339,15 +482,15 @@ ccl_device VolumeIntegrateResult kernel_volume_integrate_heterogeneous(KernelGlo /* get the volume attenuation and emission over line segment defined by * ray, with the assumption that there are no surfaces blocking light * between the endpoints */ -ccl_device VolumeIntegrateResult kernel_volume_integrate(KernelGlobals *kg, PathState *state, Ray *ray, PathRadiance *L, float3 *throughput) +ccl_device_noinline VolumeIntegrateResult kernel_volume_integrate(KernelGlobals *kg, + PathState *state, ShaderData *sd, Ray *ray, PathRadiance *L, float3 *throughput, RNG *rng) { - ShaderData sd; - shader_setup_from_volume(kg, &sd, ray, state->bounce); + shader_setup_from_volume(kg, sd, ray, state->bounce); if(volume_stack_is_heterogeneous(kg, state->volume_stack)) - return kernel_volume_integrate_heterogeneous(kg, state, ray, &sd, L, throughput); + return kernel_volume_integrate_heterogeneous(kg, state, ray, sd, L, throughput, rng); else - return kernel_volume_integrate_homogeneous(kg, state, ray, &sd, L, throughput); + return kernel_volume_integrate_homogeneous(kg, state, ray, sd, L, throughput, rng); } /* Volume Stack diff --git a/intern/cycles/kernel/svm/svm_types.h b/intern/cycles/kernel/svm/svm_types.h index 9850a058611..52bea153a2a 100644 --- a/intern/cycles/kernel/svm/svm_types.h +++ b/intern/cycles/kernel/svm/svm_types.h @@ -410,6 +410,7 @@ typedef enum ClosureType { #define CLOSURE_IS_HOLDOUT(type) (type == CLOSURE_HOLDOUT_ID) #define CLOSURE_IS_BACKGROUND(type) (type == CLOSURE_BACKGROUND_ID) #define CLOSURE_IS_AMBIENT_OCCLUSION(type) (type == CLOSURE_AMBIENT_OCCLUSION_ID) +#define CLOSURE_IS_PHASE(type) (type == CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID) #define CLOSURE_WEIGHT_CUTOFF 1e-5f diff --git a/intern/cycles/render/integrator.cpp b/intern/cycles/render/integrator.cpp index ed8883c52f7..1042a5c4102 100644 --- a/intern/cycles/render/integrator.cpp +++ b/intern/cycles/render/integrator.cpp @@ -33,6 +33,7 @@ Integrator::Integrator() max_diffuse_bounce = max_bounce; max_glossy_bounce = max_bounce; max_transmission_bounce = max_bounce; + max_volume_bounce = max_bounce; probalistic_termination = true; transparent_min_bounce = min_bounce; @@ -57,6 +58,7 @@ Integrator::Integrator() ao_samples = 1; mesh_light_samples = 1; subsurface_samples = 1; + volume_samples = 1; method = PATH; sampling_pattern = SAMPLING_PATTERN_SOBOL; @@ -88,6 +90,11 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene kintegrator->max_glossy_bounce = max_glossy_bounce + 1; kintegrator->max_transmission_bounce = max_transmission_bounce + 1; + if(kintegrator->use_volumes) + kintegrator->max_volume_bounce = max_volume_bounce + 1; + else + kintegrator->max_volume_bounce = 1; + kintegrator->transparent_max_bounce = transparent_max_bounce + 1; if(transparent_probalistic) kintegrator->transparent_min_bounce = transparent_min_bounce + 1; @@ -118,6 +125,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene kintegrator->ao_samples = ao_samples; kintegrator->mesh_light_samples = mesh_light_samples; kintegrator->subsurface_samples = subsurface_samples; + kintegrator->volume_samples = volume_samples; kintegrator->sampling_pattern = sampling_pattern; @@ -130,6 +138,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene max_samples = max(max_samples, max(diffuse_samples, max(glossy_samples, transmission_samples))); max_samples = max(max_samples, max(ao_samples, max(mesh_light_samples, subsurface_samples))); + max_samples = max(max_samples, volume_samples); } max_samples *= (max_bounce + transparent_max_bounce + 3); @@ -159,6 +168,7 @@ bool Integrator::modified(const Integrator& integrator) max_diffuse_bounce == integrator.max_diffuse_bounce && max_glossy_bounce == integrator.max_glossy_bounce && max_transmission_bounce == integrator.max_transmission_bounce && + max_volume_bounce == integrator.max_volume_bounce && probalistic_termination == integrator.probalistic_termination && transparent_min_bounce == integrator.transparent_min_bounce && transparent_max_bounce == integrator.transparent_max_bounce && @@ -179,6 +189,7 @@ bool Integrator::modified(const Integrator& integrator) ao_samples == integrator.ao_samples && mesh_light_samples == integrator.mesh_light_samples && subsurface_samples == integrator.subsurface_samples && + volume_samples == integrator.volume_samples && motion_blur == integrator.motion_blur && sampling_pattern == integrator.sampling_pattern); } diff --git a/intern/cycles/render/integrator.h b/intern/cycles/render/integrator.h index d9e49d5906e..cc4a28a4e54 100644 --- a/intern/cycles/render/integrator.h +++ b/intern/cycles/render/integrator.h @@ -33,6 +33,7 @@ public: int max_diffuse_bounce; int max_glossy_bounce; int max_transmission_bounce; + int max_volume_bounce; bool probalistic_termination; int transparent_min_bounce; @@ -59,6 +60,7 @@ public: int ao_samples; int mesh_light_samples; int subsurface_samples; + int volume_samples; enum Method { BRANCHED_PATH = 0, diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c index 11c27eb0334..6e04f7e0167 100644 --- a/source/blender/blenkernel/intern/node.c +++ b/source/blender/blenkernel/intern/node.c @@ -3500,7 +3500,7 @@ static void registerShaderNodes(void) register_node_type_sh_emission(); register_node_type_sh_holdout(); register_node_type_sh_volume_absorption(); - //register_node_type_sh_volume_scatter(); + register_node_type_sh_volume_scatter(); register_node_type_sh_subsurface_scattering(); register_node_type_sh_mix_shader(); register_node_type_sh_add_shader(); |