From 899567d6728fc9405df33725836a560c1450947e Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 7 Jul 2021 20:53:24 +0200 Subject: [PATCH 1/8] Cleanup: minor renaming and adding const qualifiers --- .../integrator/integrator_shade_volume.h | 107 +++++++++++------- .../integrator/integrator_volume_stack.h | 31 ++--- 2 files changed, 85 insertions(+), 53 deletions(-) diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index 76eaa67..35cc480 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -25,11 +25,11 @@ CCL_NAMESPACE_BEGIN /* Events for probalistic scattering */ -typedef enum VolumeIntegrateResult { +typedef enum VolumeIntegrateEvent { VOLUME_PATH_SCATTERED = 0, VOLUME_PATH_ATTENUATED = 1, VOLUME_PATH_MISSED = 2 -} VolumeIntegrateResult; +} VolumeIntegrateEvent; /* Ignore paths that have volume throughput below this value, to avoid unnecessary work * and precision issues. @@ -236,19 +236,21 @@ ccl_device void volume_shadow_heterogeneous(INTEGRATOR_STATE_ARGS, /* Equi-angular sampling as in: * "Importance Sampling Techniques for Path Tracing in Participating Media" */ -ccl_device float volume_equiangular_sample(Ray *ray, float3 light_P, float xi, float *pdf) +ccl_device float volume_equiangular_sample(const Ray *ccl_restrict ray, + const float3 light_P, + const float xi, + float *pdf) { - float t = ray->t; - - float delta = dot((light_P - ray->P), ray->D); - float D = safe_sqrtf(len_squared(light_P - ray->P) - delta * delta); + const float t = ray->t; + const float delta = dot((light_P - ray->P), ray->D); + const float D = safe_sqrtf(len_squared(light_P - ray->P) - delta * delta); if (UNLIKELY(D == 0.0f)) { *pdf = 0.0f; return 0.0f; } - float theta_a = -atan2f(delta, D); - float theta_b = atan2f(t - delta, D); - float t_ = D * tanf((xi * theta_b) + (1 - xi) * theta_a); + const float theta_a = -atan2f(delta, D); + const float theta_b = atan2f(t - delta, D); + const float t_ = D * tanf((xi * theta_b) + (1 - xi) * theta_a); if (UNLIKELY(theta_b == theta_a)) { *pdf = 0.0f; return 0.0f; @@ -258,7 +260,33 @@ ccl_device float volume_equiangular_sample(Ray *ray, float3 light_P, float xi, f return min(t, delta + t_); /* min is only for float precision errors */ } -ccl_device float volume_equiangular_pdf(Ray *ray, float3 light_P, float sample_t) +ccl_device float volume_equiangular_pdf(const Ray *ccl_restrict ray, + const float3 light_P, + const float sample_t) +{ + const float delta = dot((light_P - ray->P), ray->D); + const float D = safe_sqrtf(len_squared(light_P - ray->P) - delta * delta); + if (UNLIKELY(D == 0.0f)) { + return 0.0f; + } + + const float t = ray->t; + const float t_ = sample_t - delta; + + const float theta_a = -atan2f(delta, D); + const float theta_b = atan2f(t - delta, D); + if (UNLIKELY(theta_b == theta_a)) { + return 0.0f; + } + + const float pdf = D / ((theta_b - theta_a) * (D * D + t_ * t_)); + + return pdf; +} + +ccl_device float volume_equiangular_cdf(const Ray *ccl_restrict ray, + const float3 light_P, + const float sample_t) { float delta = dot((light_P - ray->P), ray->D); float D = safe_sqrtf(len_squared(light_P - ray->P) - delta * delta); @@ -266,18 +294,19 @@ ccl_device float volume_equiangular_pdf(Ray *ray, float3 light_P, float sample_t return 0.0f; } - float t = ray->t; - float t_ = sample_t - delta; + const float t = ray->t; + const float t_ = sample_t - delta; - float theta_a = -atan2f(delta, D); - float theta_b = atan2f(t - delta, D); + const float theta_a = -atan2f(delta, D); + const float theta_b = atan2f(t - delta, D); if (UNLIKELY(theta_b == theta_a)) { return 0.0f; } - float pdf = D / ((theta_b - theta_a) * (D * D + t_ * t_)); + const float theta_sample = atan2f(t_, D); + const float cdf = (theta_sample - theta_a) / (theta_b - theta_a); - return pdf; + return cdf; } /* Distance sampling */ @@ -342,7 +371,7 @@ ccl_device float3 volume_emission_integrate(VolumeShaderCoefficients *coeff, # if 0 /* homogeneous volume: assume shader evaluation at the start gives * the volume shading coefficient for the entire line segment */ -ccl_device VolumeIntegrateResult +ccl_device VolumeIntegrateEvent volume_integrate_homogeneous(INTEGRATOR_STATE_ARGS, Ray *ccl_restrict ray, ShaderData *ccl_restrict sd, @@ -452,7 +481,7 @@ volume_integrate_homogeneous(INTEGRATOR_STATE_ARGS, * volume until we reach the end, get absorbed entirely, or run out of * iterations. this does probabilistically scatter or get transmitted through * for path tracing where we don't want to branch. */ -ccl_device VolumeIntegrateResult +ccl_device VolumeIntegrateEvent volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, Ray *ccl_restrict ray, ShaderData *ccl_restrict sd, @@ -483,7 +512,7 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, /* pick random color channel, we use the Veach one-sample * model with balance heuristic for the channels */ float xi = path_state_rng_1D(kg, rng_state, PRNG_SCATTER_DISTANCE); - float rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); + const float rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); bool has_scatter = false; for (int i = 0; i < max_steps; i++) { @@ -508,25 +537,25 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, has_scatter = true; /* Sample channel, use MIS with balance heuristic. */ - float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); + const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); float3 channel_pdf; - int channel = volume_sample_channel(albedo, tp, rphase, &channel_pdf); + const int channel = volume_sample_channel(albedo, tp, rphase, &channel_pdf); /* compute transmittance over full step */ transmittance = volume_color_transmittance(coeff.sigma_t, dt); /* decide if we will scatter or continue */ - float sample_transmittance = volume_channel_get(transmittance, channel); + const float sample_transmittance = volume_channel_get(transmittance, channel); if (1.0f - xi >= sample_transmittance) { /* compute sampling distance */ - float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); - float new_dt = -logf(1.0f - xi) / sample_sigma_t; + const float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); + const float new_dt = -logf(1.0f - xi) / sample_sigma_t; new_t = t + new_dt; /* transmittance and pdf */ - float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); - float3 pdf = coeff.sigma_t * new_transmittance; + const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); + const float3 pdf = coeff.sigma_t * new_transmittance; /* throughput */ new_tp = tp * coeff.sigma_s * new_transmittance / dot(channel_pdf, pdf); @@ -534,7 +563,7 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, } else { /* throughput */ - float pdf = dot(channel_pdf, transmittance); + const float pdf = dot(channel_pdf, transmittance); new_tp = tp * transmittance / pdf; /* remap xi so we can reuse it and keep thing stratified */ @@ -555,7 +584,7 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, /* integrate emission attenuated by absorption */ if (closure_flag & SD_EMISSION) { - float3 emission = volume_emission_integrate(&coeff, closure_flag, transmittance, dt); + const float3 emission = volume_emission_integrate(&coeff, closure_flag, transmittance, dt); kernel_accum_emission(INTEGRATOR_STATE_PASS, tp, emission, render_buffer); } @@ -735,9 +764,9 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(INTEGRATOR_STATE_ARGS * ray, with the assumption that there are no surfaces blocking light * between the endpoints. distance sampling is used to decide if we will * scatter or not. */ -ccl_device VolumeIntegrateResult volume_integrate(INTEGRATOR_STATE_ARGS, - Ray *ccl_restrict ray, - ccl_global float *ccl_restrict render_buffer) +ccl_device VolumeIntegrateEvent volume_integrate(INTEGRATOR_STATE_ARGS, + Ray *ccl_restrict ray, + ccl_global float *ccl_restrict render_buffer) { ShaderData sd; shader_setup_from_volume(kg, &sd, ray); @@ -752,7 +781,7 @@ ccl_device VolumeIntegrateResult volume_integrate(INTEGRATOR_STATE_ARGS, return integrator_state_read_volume_stack(INTEGRATOR_STATE_PASS, i); }); - VolumeIntegrateResult result = volume_integrate_heterogeneous( + VolumeIntegrateEvent event = volume_integrate_heterogeneous( INTEGRATOR_STATE_PASS, ray, &sd, &throughput, &rng_state, render_buffer, step_size); /* Perform path termination. The intersect_closest will have already marked this path @@ -766,7 +795,7 @@ ccl_device VolumeIntegrateResult volume_integrate(INTEGRATOR_STATE_ARGS, if (probability == 0.0f) { return VOLUME_PATH_MISSED; } - else if (result == VOLUME_PATH_SCATTERED) { + else if (event == VOLUME_PATH_SCATTERED) { /* Only divide throughput by probability if we scatter. For the attenuation * case the next surface will already do this division. */ if (probability != 1.0f) { @@ -776,7 +805,7 @@ ccl_device VolumeIntegrateResult volume_integrate(INTEGRATOR_STATE_ARGS, INTEGRATOR_STATE_WRITE(path, throughput) = throughput; - if (result == VOLUME_PATH_SCATTERED) { + if (event == VOLUME_PATH_SCATTERED) { /* Direct light. */ integrate_volume_direct_light(INTEGRATOR_STATE_PASS, &sd, &rng_state); @@ -786,7 +815,7 @@ ccl_device VolumeIntegrateResult volume_integrate(INTEGRATOR_STATE_ARGS, } } - return result; + return event; } #endif @@ -810,15 +839,15 @@ ccl_device void integrator_shade_volume(INTEGRATOR_STATE_ARGS, volume_stack_clean(INTEGRATOR_STATE_PASS); } - VolumeIntegrateResult result = volume_integrate(INTEGRATOR_STATE_PASS, &ray, render_buffer); + VolumeIntegrateEvent event = volume_integrate(INTEGRATOR_STATE_PASS, &ray, render_buffer); - if (result == VOLUME_PATH_SCATTERED) { + if (event == VOLUME_PATH_SCATTERED) { /* Queue intersect_closest kernel. */ INTEGRATOR_PATH_NEXT(DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); return; } - else if (result == VOLUME_PATH_MISSED) { + else if (event == VOLUME_PATH_MISSED) { /* End path. */ INTEGRATOR_PATH_TERMINATE(DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME); return; diff --git a/intern/cycles/kernel/integrator/integrator_volume_stack.h b/intern/cycles/kernel/integrator/integrator_volume_stack.h index c7078cf..00af367 100644 --- a/intern/cycles/kernel/integrator/integrator_volume_stack.h +++ b/intern/cycles/kernel/integrator/integrator_volume_stack.h @@ -318,16 +318,19 @@ ccl_device float volume_stack_step_size(INTEGRATOR_STATE_ARGS, StackReadOp stack return step_size; } -template -ccl_device int volume_stack_sampling_method(INTEGRATOR_STATE_ARGS, StackReadOp stack_read) +typedef enum VolumeSampleMethod { + VOLUME_SAMPLE_NONE = 0, + VOLUME_SAMPLE_DISTANCE = (1 << 0), + VOLUME_SAMPLE_EQUIANGULAR = (1 << 1), + VOLUME_SAMPLE_MIS = (VOLUME_SAMPLE_DISTANCE | VOLUME_SAMPLE_EQUIANGULAR), +} VolumeSampleMethod; + +ccl_device VolumeSampleMethod volume_stack_sample_method(INTEGRATOR_STATE_ARGS) { - if (kernel_data.integrator.num_all_lights == 0) - return 0; - - int method = -1; + VolumeSampleMethod method = VOLUME_SAMPLE_NONE; for (int i = 0;; i++) { - VolumeStack entry = stack_read(i); + VolumeStack entry = integrator_state_read_volume_stack(INTEGRATOR_STATE_PASS, i); if (entry.shader == SHADER_NONE) { break; } @@ -336,25 +339,25 @@ ccl_device int volume_stack_sampling_method(INTEGRATOR_STATE_ARGS, StackReadOp s if (shader_flag & SD_VOLUME_MIS) { /* Multiple importance sampling. */ - return SD_VOLUME_MIS; + return VOLUME_SAMPLE_MIS; } else if (shader_flag & SD_VOLUME_EQUIANGULAR) { /* Distance + equiangular sampling -> multiple importance sampling. */ - if (method == 0) { - return SD_VOLUME_MIS; + if (method == VOLUME_SAMPLE_DISTANCE) { + return VOLUME_SAMPLE_MIS; } /* Only equiangular sampling. */ - method = SD_VOLUME_EQUIANGULAR; + method = VOLUME_SAMPLE_EQUIANGULAR; } else { /* Distance + equiangular sampling -> multiple importance sampling. */ - if (method == SD_VOLUME_EQUIANGULAR) { - return SD_VOLUME_MIS; + if (method == VOLUME_SAMPLE_EQUIANGULAR) { + return VOLUME_SAMPLE_MIS; } /* Distance sampling only. */ - method = 0; + method = VOLUME_SAMPLE_DISTANCE; } } -- 2.25.1 From 9a593a905e6e6009681a93c726952615a5e13d6a Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 7 Jul 2021 20:56:32 +0200 Subject: [PATCH 2/8] Cycles X: tweak light functions in preparation of equiangular sampling * Add function to sample a new position on a given light * Tweak shadow ray generation to be able to override the position --- .../integrator/integrator_shade_surface.h | 2 +- .../integrator/integrator_shade_volume.h | 2 +- intern/cycles/kernel/kernel_emission.h | 78 ++++++++++++------- intern/cycles/kernel/kernel_light.h | 17 ++++ 4 files changed, 69 insertions(+), 30 deletions(-) diff --git a/intern/cycles/kernel/integrator/integrator_shade_surface.h b/intern/cycles/kernel/integrator/integrator_shade_surface.h index 0557e3a..0c81506 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_surface.h +++ b/intern/cycles/kernel/integrator/integrator_shade_surface.h @@ -155,7 +155,7 @@ ccl_device_forceinline void integrate_surface_direct_light(INTEGRATOR_STATE_ARGS /* Create shadow ray. */ Ray ray ccl_optional_struct_init; - light_sample_to_shadow_ray(kg, sd, &ls, &ray); + light_sample_to_surface_shadow_ray(kg, sd, &ls, &ray); const bool is_light = light_sample_is_light(&ls); /* Copy volume stack and enter/exit volume. */ diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index 35cc480..d7b09aa 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -684,7 +684,7 @@ ccl_device_forceinline void integrate_volume_direct_light(INTEGRATOR_STATE_ARGS, /* Create shadow ray. */ Ray ray ccl_optional_struct_init; - light_sample_to_shadow_ray(kg, sd, &ls, &ray); + light_sample_to_volume_shadow_ray(kg, sd, &ls, sd->P, &ray); const bool is_light = light_sample_is_light(&ls); /* Write shadow ray and associated state to global memory. */ diff --git a/intern/cycles/kernel/kernel_emission.h b/intern/cycles/kernel/kernel_emission.h index a8fcf4f..f54d356 100644 --- a/intern/cycles/kernel/kernel_emission.h +++ b/intern/cycles/kernel/kernel_emission.h @@ -25,8 +25,8 @@ CCL_NAMESPACE_BEGIN /* Evaluate shader on light. */ ccl_device_noinline_cpu float3 light_sample_shader_eval(INTEGRATOR_STATE_ARGS, - ShaderData *emission_sd, - LightSample *ls, + ShaderData *ccl_restrict emission_sd, + LightSample *ccl_restrict ls, float time) { /* setup shading at emitter */ @@ -93,7 +93,7 @@ ccl_device_noinline_cpu float3 light_sample_shader_eval(INTEGRATOR_STATE_ARGS, } /* Test if light sample is from a light or emission from geometry. */ -ccl_device_inline bool light_sample_is_light(const LightSample *ls) +ccl_device_inline bool light_sample_is_light(const LightSample *ccl_restrict ls) { /* return if it's a lamp for shadow pass */ return (ls->prim == PRIM_NONE && ls->type != LIGHT_BACKGROUND); @@ -101,8 +101,8 @@ ccl_device_inline bool light_sample_is_light(const LightSample *ls) /* Early path termination of shadow rays. */ ccl_device_inline bool light_sample_terminate(const KernelGlobals *ccl_restrict kg, - const LightSample *ls, - BsdfEval *eval, + const LightSample *ccl_restrict ls, + BsdfEval *ccl_restrict eval, const float rand_terminate) { if (bsdf_eval_is_zero(eval)) { @@ -207,35 +207,57 @@ ccl_device_inline float3 shadow_ray_offset(const KernelGlobals *ccl_restrict kg, return P; } +ccl_device_inline void shadow_ray_setup(const ShaderData *ccl_restrict sd, + const LightSample *ccl_restrict ls, + const float3 P, + Ray *ray) +{ + ray->P = P; + + if (ls->t == FLT_MAX) { + /* distant light */ + ray->D = ls->D; + ray->t = ls->t; + } + else { + /* other lights, avoid self-intersection */ + ray->D = ray_offset(ls->P, ls->Ng) - P; + ray->D = normalize_len(ray->D, &ray->t); + } + + ray->dP = differential_make_compact(sd->dP); + ray->dD = differential_zero_compact(); +} + /* Create shadow ray towards light sample. */ -template -ccl_device_inline void light_sample_to_shadow_ray(const KernelGlobals *ccl_restrict kg, - const ShaderData *sd, - const LightSample *ls, - Ray *ray) +ccl_device_inline void light_sample_to_surface_shadow_ray(const KernelGlobals *ccl_restrict kg, + const ShaderData *ccl_restrict sd, + const LightSample *ccl_restrict ls, + Ray *ray) { if (ls->shader & SHADER_CAST_SHADOW) { /* setup ray */ - if (is_volume) { - ray->P = sd->P; - } - else { - ray->P = shadow_ray_offset(kg, sd, ls->D); - } + const float3 P = shadow_ray_offset(kg, sd, ls->D); + shadow_ray_setup(sd, ls, P, ray); + } + else { + /* signal to not cast shadow ray */ + ray->t = 0.0f; + } - if (ls->t == FLT_MAX) { - /* distant light */ - ray->D = ls->D; - ray->t = ls->t; - } - else { - /* other lights, avoid self-intersection */ - ray->D = ray_offset(ls->P, ls->Ng) - ray->P; - ray->D = normalize_len(ray->D, &ray->t); - } + ray->time = sd->time; +} - ray->dP = differential_make_compact(sd->dP); - ray->dD = differential_zero_compact(); +/* Create shadow ray towards light sample. */ +ccl_device_inline void light_sample_to_volume_shadow_ray(const KernelGlobals *ccl_restrict kg, + const ShaderData *ccl_restrict sd, + const LightSample *ccl_restrict ls, + const float3 P, + Ray *ray) +{ + if (ls->shader & SHADER_CAST_SHADOW) { + /* setup ray */ + shadow_ray_setup(sd, ls, P, ray); } else { /* signal to not cast shadow ray */ diff --git a/intern/cycles/kernel/kernel_light.h b/intern/cycles/kernel/kernel_light.h index 9ce9fe5..ad83f79 100644 --- a/intern/cycles/kernel/kernel_light.h +++ b/intern/cycles/kernel/kernel_light.h @@ -790,4 +790,21 @@ ccl_device_noinline bool light_sample(const KernelGlobals *kg, return light_sample_from_position(kg, lamp, randu, randv, P, ls); } +ccl_device_inline bool light_sample_new_position(const KernelGlobals *kg, + const float randu, + const float randv, + const float time, + const float3 P, + LightSample *ls) +{ + /* Sample a new position on the same light, for volume sampling. */ + if (ls->type == LIGHT_TRIANGLE) { + triangle_light_sample(kg, ls->prim, ls->object, randu, randv, time, ls, P); + return (ls->pdf > 0.0f); + } + else { + return light_sample_from_position(kg, ls->lamp, randu, randv, P, ls); + } +} + CCL_NAMESPACE_END -- 2.25.1 From 9b3b6689f73196c76a496493710916e75d58ffca Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 7 Jul 2021 21:00:07 +0200 Subject: [PATCH 3/8] Cleanup: remove unused homogeneous volume code and volume scatter define --- .../integrator/integrator_shade_volume.h | 114 +----------------- intern/cycles/kernel/kernel_types.h | 2 - 2 files changed, 1 insertion(+), 115 deletions(-) diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index d7b09aa..f45cb75 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -368,115 +368,6 @@ ccl_device float3 volume_emission_integrate(VolumeShaderCoefficients *coeff, /* Volume Path */ -# if 0 -/* homogeneous volume: assume shader evaluation at the start gives - * the volume shading coefficient for the entire line segment */ -ccl_device VolumeIntegrateEvent -volume_integrate_homogeneous(INTEGRATOR_STATE_ARGS, - Ray *ccl_restrict ray, - ShaderData *ccl_restrict sd, - ccl_addr_space float3 *ccl_restrict throughput, - const RNGState *rng_state, - const bool probalistic_scatter, - ccl_global float *ccl_restrict render_buffer) -{ - /* Evaluate shader. */ - VolumeShaderCoefficients coeff ccl_optional_struct_init; - - if (!volume_shader_sample(INTEGRATOR_STATE_PASS, sd, &coeff)) { - return VOLUME_PATH_MISSED; - } - - const int closure_flag = sd->flag; - float t = ray->t; - float3 new_tp; - -# ifdef __VOLUME_SCATTER__ - /* randomly scatter, and if we do t is shortened */ - if (closure_flag & SD_SCATTER) { - /* Sample channel, use MIS with balance heuristic. */ - const float rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); - const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); - float3 channel_pdf; - const int channel = volume_sample_channel(albedo, *throughput, rphase, &channel_pdf); - - /* decide if we will hit or miss */ - bool scatter = true; - float xi = path_state_rng_1D(kg, rng_state, PRNG_SCATTER_DISTANCE); - - if (probalistic_scatter) { - float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); - float sample_transmittance = expf(-sample_sigma_t * t); - - if (1.0f - xi >= sample_transmittance) { - scatter = true; - - /* rescale random number so we can reuse it */ - xi = 1.0f - (1.0f - xi - sample_transmittance) / (1.0f - sample_transmittance); - } - else - scatter = false; - } - - if (scatter) { - /* scattering */ - float3 pdf; - float3 transmittance; - float sample_t; - - /* distance sampling */ - sample_t = volume_distance_sample(ray->t, coeff.sigma_t, channel, xi, &transmittance, &pdf); - - /* modify pdf for hit/miss decision */ - if (probalistic_scatter) - pdf *= one_float3() - volume_color_transmittance(coeff.sigma_t, t); - - new_tp = *throughput * coeff.sigma_s * transmittance / dot(channel_pdf, pdf); - t = sample_t; - } - else { - /* no scattering */ - float3 transmittance = volume_color_transmittance(coeff.sigma_t, t); - float pdf = dot(channel_pdf, transmittance); - new_tp = *throughput * transmittance / pdf; - } - } - else -# endif - if (closure_flag & SD_EXTINCTION) { - /* absorption only, no sampling needed */ - float3 transmittance = volume_color_transmittance(coeff.sigma_t, t); - new_tp = *throughput * transmittance; - } - else { - new_tp = *throughput; - } - - /* integrate emission attenuated by extinction */ - if (closure_flag & SD_EMISSION) { - float3 transmittance = volume_color_transmittance(coeff.sigma_t, ray->t); - float3 emission = volume_emission_integrate(&coeff, closure_flag, transmittance, ray->t); - - kernel_accum_emission(INTEGRATOR_STATE_PASS, *throughput, emission, render_buffer); - } - - /* modify throughput */ - if (closure_flag & SD_EXTINCTION) { - *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; -} -# endif - /* heterogeneous volume distance sampling: integrate stepping through the * volume until we reach the end, get absorbed entirely, or run out of * iterations. this does probabilistically scatter or get transmitted through @@ -532,7 +423,6 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, bool scatter = false; /* distance sampling */ -# ifdef __VOLUME_SCATTER__ if ((closure_flag & SD_SCATTER) || (has_scatter && (closure_flag & SD_EXTINCTION))) { has_scatter = true; @@ -570,9 +460,7 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, xi = 1.0f - (1.0f - xi) / sample_transmittance; } } - else -# endif - if (closure_flag & SD_EXTINCTION) { + else if (closure_flag & SD_EXTINCTION) { /* absorption only, no sampling needed */ transmittance = volume_color_transmittance(coeff.sigma_t, dt); new_tp = tp * transmittance; diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index 81d18d6..72dd9d8 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -112,7 +112,6 @@ CCL_NAMESPACE_BEGIN #define __PRINCIPLED__ #define __SUBSURFACE__ #define __VOLUME__ -#define __VOLUME_SCATTER__ #define __CMJ__ #define __SHADOW_RECORD_ALL__ #define __BRANCHED_PATH__ @@ -145,7 +144,6 @@ CCL_NAMESPACE_BEGIN #endif #ifdef __NO_VOLUME__ # undef __VOLUME__ -# undef __VOLUME_SCATTER__ #endif #ifdef __NO_SUBSURFACE__ # undef __SUBSURFACE__ -- 2.25.1 From 5bff8670fd4261748509ae6bec720bd806b5cacf Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 7 Jul 2021 21:01:54 +0200 Subject: [PATCH 4/8] Cycles X: sample position on light before stepping through volume In preparation of equiangular sampling. --- .../integrator/integrator_shade_volume.h | 74 +++++++++++++------ 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index f45cb75..2f41bef 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -516,34 +516,57 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, # ifdef __EMISSION__ /* Path tracing: sample point on light and evaluate light shader, then * queue shadow ray to be traced. */ -ccl_device_forceinline void integrate_volume_direct_light(INTEGRATOR_STATE_ARGS, - ShaderData *sd, - const RNGState *rng_state) +ccl_device_forceinline bool integrate_volume_sample_light(INTEGRATOR_STATE_ARGS, + const ShaderData *ccl_restrict sd, + const RNGState *ccl_restrict rng_state, + LightSample *ccl_restrict ls) { /* Test if there is a light or BSDF that needs direct light. */ if (!kernel_data.integrator.use_direct_light) { - return; + return false; } /* Sample position on a light. */ - LightSample ls ccl_optional_struct_init; + const int path_flag = INTEGRATOR_STATE(path, flag); + const uint bounce = INTEGRATOR_STATE(path, bounce); + float light_u, light_v; + path_state_rng_2D(kg, rng_state, PRNG_LIGHT_U, &light_u, &light_v); + + if (!light_sample(kg, light_u, light_v, sd->time, sd->P, bounce, path_flag, ls)) { + return false; + } + + if (ls->shader & SHADER_EXCLUDE_SCATTER) { + return false; + } + + kernel_assert(ls->pdf != 0.0f); + return true; +} + +/* Path tracing: sample point on light and evaluate light shader, then + * queue shadow ray to be traced. */ +ccl_device_forceinline void integrate_volume_direct_light(INTEGRATOR_STATE_ARGS, + const ShaderData *ccl_restrict sd, + const RNGState *ccl_restrict rng_state, + LightSample *ccl_restrict ls) +{ + /* Sample position on the same light again, now from the shading + * point where we scattered. + * + * TODO: decorrelate random numbers and use light_sample_new_position to + * avoid resampling the CDF. */ { const int path_flag = INTEGRATOR_STATE(path, flag); const uint bounce = INTEGRATOR_STATE(path, bounce); float light_u, light_v; path_state_rng_2D(kg, rng_state, PRNG_LIGHT_U, &light_u, &light_v); - if (!light_sample(kg, light_u, light_v, sd->time, sd->P, bounce, path_flag, &ls)) { + if (!light_sample(kg, light_u, light_v, sd->time, sd->P, bounce, path_flag, ls)) { return; } } - if (ls.shader & SHADER_EXCLUDE_SCATTER) { - return; - } - - kernel_assert(ls.pdf != 0.0f); - /* Evaluate light shader. * * TODO: can we reuse sd memory? In theory we can move this after @@ -553,27 +576,27 @@ ccl_device_forceinline void integrate_volume_direct_light(INTEGRATOR_STATE_ARGS, ShaderDataTinyStorage emission_sd_storage; ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage); const float3 light_eval = light_sample_shader_eval( - INTEGRATOR_STATE_PASS, emission_sd, &ls, sd->time); + INTEGRATOR_STATE_PASS, emission_sd, ls, sd->time); if (is_zero(light_eval)) { return; } /* Evaluate BSDF. */ BsdfEval phase_eval ccl_optional_struct_init; - shader_volume_phase_eval(kg, sd, ls.D, &phase_eval, ls.pdf, ls.shader); + shader_volume_phase_eval(kg, sd, ls->D, &phase_eval, ls->pdf, ls->shader); - bsdf_eval_mul3(&phase_eval, light_eval / ls.pdf); + bsdf_eval_mul3(&phase_eval, light_eval / ls->pdf); /* Path termination. */ const float terminate = path_state_rng_light_termination(kg, rng_state); - if (light_sample_terminate(kg, &ls, &phase_eval, terminate)) { + if (light_sample_terminate(kg, ls, &phase_eval, terminate)) { return; } /* Create shadow ray. */ Ray ray ccl_optional_struct_init; - light_sample_to_volume_shadow_ray(kg, sd, &ls, sd->P, &ray); - const bool is_light = light_sample_is_light(&ls); + light_sample_to_volume_shadow_ray(kg, sd, ls, sd->P, &ray); + const bool is_light = light_sample_is_light(ls); /* Write shadow ray and associated state to global memory. */ integrator_state_write_shadow_ray(INTEGRATOR_STATE_PASS, &ray); @@ -659,16 +682,23 @@ ccl_device VolumeIntegrateEvent volume_integrate(INTEGRATOR_STATE_ARGS, ShaderData sd; shader_setup_from_volume(kg, &sd, ray); - float3 throughput = INTEGRATOR_STATE(path, throughput); - /* Load random number state. */ RNGState rng_state; path_state_rng_load(INTEGRATOR_STATE_PASS, &rng_state); + /* Sample light ahead of volume stepping. */ + LightSample ls ccl_optional_struct_init; + const bool need_light_sample = !(INTEGRATOR_STATE(path, flag) & PATH_RAY_TERMINATE); + if (need_light_sample) { + integrate_volume_sample_light(INTEGRATOR_STATE_PASS, &sd, &rng_state, &ls); + } + + /* Step through volume. */ + float3 throughput = INTEGRATOR_STATE(path, throughput); + const float step_size = volume_stack_step_size(INTEGRATOR_STATE_PASS, [=](const int i) { return integrator_state_read_volume_stack(INTEGRATOR_STATE_PASS, i); }); - VolumeIntegrateEvent event = volume_integrate_heterogeneous( INTEGRATOR_STATE_PASS, ray, &sd, &throughput, &rng_state, render_buffer, step_size); @@ -695,7 +725,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(INTEGRATOR_STATE_ARGS, if (event == VOLUME_PATH_SCATTERED) { /* Direct light. */ - integrate_volume_direct_light(INTEGRATOR_STATE_PASS, &sd, &rng_state); + integrate_volume_direct_light(INTEGRATOR_STATE_PASS, &sd, &rng_state, &ls); /* Scatter. */ if (!integrate_volume_phase_scatter(INTEGRATOR_STATE_PASS, &sd, &rng_state)) { -- 2.25.1 From 1f613d80d9b34beadeec7fe2ef5418df6ea947cf Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 30 Jun 2021 16:35:02 +0200 Subject: [PATCH 5/8] Cycles X: refactor volume scattering logic into separate function And move some state into a struct. --- .../integrator/integrator_shade_volume.h | 199 ++++++++++-------- 1 file changed, 106 insertions(+), 93 deletions(-) diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index 2f41bef..3d75cd7 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -366,23 +366,80 @@ ccl_device float3 volume_emission_integrate(VolumeShaderCoefficients *coeff, return emission; } -/* Volume Path */ +/* Volume Integration */ + +typedef struct VolumeIntegrateState { + /* Volume segment extents. */ + float start_t; + float end_t; + + /* Current throughput. */ + float3 throughput; + + /* If volume is absorption-only up to this point, and no probabilistic + * scattering or termination has been used yet. */ + bool absorption_only; + + /* Random numbers for scattering. */ + float rscatter; + float rphase; +} VolumeIntegrateState; + +ccl_device_forceinline VolumeIntegrateEvent +volume_integrate_step_scattering(const VolumeShaderCoefficients &ccl_restrict coeff, + const float3 transmittance, + VolumeIntegrateState &ccl_restrict vstate) +{ + /* Distance sampling */ + + /* Pick random color channel, we use the Veach one-sample + * model with balance heuristic for the channels. */ + const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); + float3 channel_pdf; + const int channel = volume_sample_channel( + albedo, vstate.throughput, vstate.rphase, &channel_pdf); + + /* decide if we will scatter or continue */ + const float sample_transmittance = volume_channel_get(transmittance, channel); + + if (1.0f - vstate.rscatter >= sample_transmittance) { + /* compute sampling distance */ + const float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); + const float new_dt = -logf(1.0f - vstate.rscatter) / sample_sigma_t; + vstate.end_t = vstate.start_t + new_dt; + + /* transmittance and pdf */ + const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); + const float3 pdf = coeff.sigma_t * new_transmittance; + + /* throughput */ + vstate.throughput *= coeff.sigma_s * new_transmittance / dot(channel_pdf, pdf); + return VOLUME_PATH_SCATTERED; + } + else { + /* throughput */ + const float pdf = dot(channel_pdf, transmittance); + vstate.throughput *= transmittance / pdf; + + /* remap rscatter so we can reuse it and keep thing stratified */ + vstate.rscatter = 1.0f - (1.0f - vstate.rscatter) / sample_transmittance; + return VOLUME_PATH_ATTENUATED; + } +} /* heterogeneous volume distance sampling: integrate stepping through the * volume until we reach the end, get absorbed entirely, or run out of * iterations. this does probabilistically scatter or get transmitted through * for path tracing where we don't want to branch. */ -ccl_device VolumeIntegrateEvent +ccl_device_forceinline VolumeIntegrateEvent volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, Ray *ccl_restrict ray, ShaderData *ccl_restrict sd, - ccl_addr_space float3 *ccl_restrict throughput, + float3 *ccl_restrict throughput, const RNGState *rng_state, ccl_global float *ccl_restrict render_buffer, const float object_step_size) { - float3 tp = *throughput; - /* Prepare for stepping. * Using a different step offset for the first step avoids banding artifacts. */ int max_steps; @@ -396,119 +453,75 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, &steps_offset, &max_steps); - /* compute coefficients at the start */ - float t = 0.0f; - float3 accum_transmittance = one_float3(); - - /* pick random color channel, we use the Veach one-sample - * model with balance heuristic for the channels */ - float xi = path_state_rng_1D(kg, rng_state, PRNG_SCATTER_DISTANCE); - const float rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); - bool has_scatter = false; + /* Initialize volume integration state. */ + VolumeIntegrateState vstate ccl_optional_struct_init; + vstate.start_t = 0.0f; + vstate.end_t = 0.0f; + vstate.throughput = *throughput; + vstate.absorption_only = true; + vstate.rscatter = path_state_rng_1D(kg, rng_state, PRNG_SCATTER_DISTANCE); + vstate.rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); for (int i = 0; i < max_steps; i++) { - /* advance to new position */ - float new_t = min(ray->t, (i + steps_offset) * step_size); - float dt = new_t - t; - - float3 new_P = ray->P + ray->D * (t + dt * step_shade_offset); - VolumeShaderCoefficients coeff ccl_optional_struct_init; + /* Advance to new position */ + vstate.end_t = min(ray->t, (i + steps_offset) * step_size); + const float shade_t = vstate.start_t + (vstate.end_t - vstate.start_t) * step_shade_offset; + sd->P = ray->P + ray->D * shade_t; /* compute segment */ - sd->P = new_P; + VolumeShaderCoefficients coeff ccl_optional_struct_init; if (volume_shader_sample(INTEGRATOR_STATE_PASS, sd, &coeff)) { - int closure_flag = sd->flag; - float3 new_tp; - float3 transmittance; - bool scatter = false; - - /* distance sampling */ - if ((closure_flag & SD_SCATTER) || (has_scatter && (closure_flag & SD_EXTINCTION))) { - has_scatter = true; - - /* Sample channel, use MIS with balance heuristic. */ - const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); - float3 channel_pdf; - const int channel = volume_sample_channel(albedo, tp, rphase, &channel_pdf); - - /* compute transmittance over full step */ - transmittance = volume_color_transmittance(coeff.sigma_t, dt); - - /* decide if we will scatter or continue */ - const float sample_transmittance = volume_channel_get(transmittance, channel); - - if (1.0f - xi >= sample_transmittance) { - /* compute sampling distance */ - const float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); - const float new_dt = -logf(1.0f - xi) / sample_sigma_t; - new_t = t + new_dt; - - /* transmittance and pdf */ - const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); - const float3 pdf = coeff.sigma_t * new_transmittance; - - /* throughput */ - new_tp = tp * coeff.sigma_s * new_transmittance / dot(channel_pdf, pdf); - scatter = true; - } - else { - /* throughput */ - const float pdf = dot(channel_pdf, transmittance); - new_tp = tp * transmittance / pdf; + const int closure_flag = sd->flag; - /* remap xi so we can reuse it and keep thing stratified */ - xi = 1.0f - (1.0f - xi) / sample_transmittance; - } - } - else if (closure_flag & SD_EXTINCTION) { - /* absorption only, no sampling needed */ - transmittance = volume_color_transmittance(coeff.sigma_t, dt); - new_tp = tp * transmittance; - } - else { - transmittance = zero_float3(); - new_tp = tp; - } + /* Evaluate transmittance over segment. */ + const float dt = (vstate.end_t - vstate.start_t); + const float3 transmittance = (closure_flag & SD_EXTINCTION) ? + volume_color_transmittance(coeff.sigma_t, dt) : + one_float3(); - /* integrate emission attenuated by absorption */ + /* Emission. */ if (closure_flag & SD_EMISSION) { + /* TODO: write only once to avoid overhead of atomics? */ const float3 emission = volume_emission_integrate(&coeff, closure_flag, transmittance, dt); - kernel_accum_emission(INTEGRATOR_STATE_PASS, tp, emission, render_buffer); + kernel_accum_emission(INTEGRATOR_STATE_PASS, *throughput, emission, render_buffer); } - /* modify throughput */ + VolumeIntegrateEvent step_event = VOLUME_PATH_ATTENUATED; if (closure_flag & SD_EXTINCTION) { - tp = new_tp; + if ((closure_flag & SD_SCATTER) || !vstate.absorption_only) { + /* Absorption and scattering. */ + step_event = volume_integrate_step_scattering(coeff, transmittance, vstate); + } + else { + /* Absorption only volume. */ + vstate.throughput *= transmittance; + } - /* stop if nearly all light blocked */ - if (tp.x < VOLUME_THROUGHPUT_EPSILON && tp.y < VOLUME_THROUGHPUT_EPSILON && - tp.z < VOLUME_THROUGHPUT_EPSILON) { - tp = zero_float3(); + /* Stop if nearly all light blocked. */ + if (max3(*throughput) < VOLUME_THROUGHPUT_EPSILON) { + vstate.throughput = zero_float3(); 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; + /* Prepare to scatter to new direction. */ + if (step_event == VOLUME_PATH_SCATTERED) { + /* Adjust throughput and move to new location for indirect light. */ + sd->P = ray->P + vstate.end_t * ray->D; + *throughput = vstate.throughput; return VOLUME_PATH_SCATTERED; } - else { - /* accumulate transmittance */ - accum_transmittance *= transmittance; - } } - /* stop if at the end of the volume */ - t = new_t; - if (t == ray->t) + /* Stop if at the end of the volume. */ + vstate.start_t = vstate.end_t; + if (vstate.start_t == ray->t) { break; + } } - *throughput = tp; + *throughput = vstate.throughput; return VOLUME_PATH_ATTENUATED; } -- 2.25.1 From 57c8e4145cdc6ce5deb14ac0051579ddfd411cb0 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 7 Jul 2021 19:50:03 +0200 Subject: [PATCH 6/8] Cycles X: refactor to separately track direct and indirect in volumes So that we can scatter at two different positions. --- .../integrator/integrator_shade_volume.h | 158 +++++++++++------- 1 file changed, 97 insertions(+), 61 deletions(-) diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index 3d75cd7..f0c4837 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -31,6 +31,19 @@ typedef enum VolumeIntegrateEvent { VOLUME_PATH_MISSED = 2 } VolumeIntegrateEvent; +typedef struct VolumeIntegrateResult { + /* Throughput and offset for direct light scattering. */ + VolumeSampleMethod direct_sample_method; + bool direct_scatter; + float3 direct_throughput; + float direct_t; + + /* Throughput and offset for indirect light scattering. */ + bool indirect_scatter; + float3 indirect_throughput; + float indirect_t; +} VolumeIntegrateResult; + /* Ignore paths that have volume throughput below this value, to avoid unnecessary work * and precision issues. * todo: this value could be tweaked or turned into a probability to avoid unnecessary @@ -385,10 +398,11 @@ typedef struct VolumeIntegrateState { float rphase; } VolumeIntegrateState; -ccl_device_forceinline VolumeIntegrateEvent -volume_integrate_step_scattering(const VolumeShaderCoefficients &ccl_restrict coeff, - const float3 transmittance, - VolumeIntegrateState &ccl_restrict vstate) +ccl_device_forceinline void volume_integrate_step_scattering( + const VolumeShaderCoefficients &ccl_restrict coeff, + const float3 transmittance, + VolumeIntegrateState &ccl_restrict vstate, + VolumeIntegrateResult &ccl_restrict result) { /* Distance sampling */ @@ -397,7 +411,7 @@ volume_integrate_step_scattering(const VolumeShaderCoefficients &ccl_restrict co const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); float3 channel_pdf; const int channel = volume_sample_channel( - albedo, vstate.throughput, vstate.rphase, &channel_pdf); + albedo, result.indirect_throughput, vstate.rphase, &channel_pdf); /* decide if we will scatter or continue */ const float sample_transmittance = volume_channel_get(transmittance, channel); @@ -406,24 +420,28 @@ volume_integrate_step_scattering(const VolumeShaderCoefficients &ccl_restrict co /* compute sampling distance */ const float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); const float new_dt = -logf(1.0f - vstate.rscatter) / sample_sigma_t; - vstate.end_t = vstate.start_t + new_dt; + const float new_t = vstate.start_t + new_dt; /* transmittance and pdf */ const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); const float3 pdf = coeff.sigma_t * new_transmittance; /* throughput */ - vstate.throughput *= coeff.sigma_s * new_transmittance / dot(channel_pdf, pdf); - return VOLUME_PATH_SCATTERED; + result.indirect_scatter = true; + result.indirect_t = new_t; + result.indirect_throughput *= coeff.sigma_s * new_transmittance / dot(channel_pdf, pdf); + + result.direct_scatter = true; + result.direct_t = result.indirect_t; + result.direct_throughput = result.indirect_throughput; } else { /* throughput */ const float pdf = dot(channel_pdf, transmittance); - vstate.throughput *= transmittance / pdf; + result.indirect_throughput *= transmittance / pdf; /* remap rscatter so we can reuse it and keep thing stratified */ vstate.rscatter = 1.0f - (1.0f - vstate.rscatter) / sample_transmittance; - return VOLUME_PATH_ATTENUATED; } } @@ -431,14 +449,14 @@ volume_integrate_step_scattering(const VolumeShaderCoefficients &ccl_restrict co * volume until we reach the end, get absorbed entirely, or run out of * iterations. this does probabilistically scatter or get transmitted through * for path tracing where we don't want to branch. */ -ccl_device_forceinline VolumeIntegrateEvent -volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, - Ray *ccl_restrict ray, - ShaderData *ccl_restrict sd, - float3 *ccl_restrict throughput, - const RNGState *rng_state, - ccl_global float *ccl_restrict render_buffer, - const float object_step_size) +ccl_device_forceinline void volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, + Ray *ccl_restrict ray, + ShaderData *ccl_restrict sd, + const RNGState *rng_state, + ccl_global float *ccl_restrict + render_buffer, + const float object_step_size, + VolumeIntegrateResult &result) { /* Prepare for stepping. * Using a different step offset for the first step avoids banding artifacts. */ @@ -457,11 +475,15 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, VolumeIntegrateState vstate ccl_optional_struct_init; vstate.start_t = 0.0f; vstate.end_t = 0.0f; - vstate.throughput = *throughput; vstate.absorption_only = true; vstate.rscatter = path_state_rng_1D(kg, rng_state, PRNG_SCATTER_DISTANCE); vstate.rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); + /* Initialize volume integration result. */ + const float3 throughput = INTEGRATOR_STATE(path, throughput); + result.direct_throughput = throughput; + result.indirect_throughput = throughput; + for (int i = 0; i < max_steps; i++) { /* Advance to new position */ vstate.end_t = min(ray->t, (i + steps_offset) * step_size); @@ -481,36 +503,42 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, /* Emission. */ if (closure_flag & SD_EMISSION) { - /* TODO: write only once to avoid overhead of atomics? */ - const float3 emission = volume_emission_integrate(&coeff, closure_flag, transmittance, dt); - kernel_accum_emission(INTEGRATOR_STATE_PASS, *throughput, emission, render_buffer); + /* Only write emission before indirect light scatter position, since we terminate + * stepping at that point if we have already found a direct light scatter position. */ + if (!result.indirect_scatter) { + /* TODO: write only once to avoid overhead of atomics? */ + const float3 emission = volume_emission_integrate( + &coeff, closure_flag, transmittance, dt); + kernel_accum_emission( + INTEGRATOR_STATE_PASS, result.indirect_throughput, emission, render_buffer); + } } - VolumeIntegrateEvent step_event = VOLUME_PATH_ATTENUATED; if (closure_flag & SD_EXTINCTION) { if ((closure_flag & SD_SCATTER) || !vstate.absorption_only) { - /* Absorption and scattering. */ - step_event = volume_integrate_step_scattering(coeff, transmittance, vstate); + /* Scattering and absorption. */ + volume_integrate_step_scattering(coeff, transmittance, vstate, result); } else { - /* Absorption only volume. */ - vstate.throughput *= transmittance; + /* Absorption only. */ + result.indirect_throughput *= transmittance; + + if (!result.direct_scatter) { + /* Only update throughput before direct light scatter position. */ + result.direct_throughput *= transmittance; + } } /* Stop if nearly all light blocked. */ - if (max3(*throughput) < VOLUME_THROUGHPUT_EPSILON) { - vstate.throughput = zero_float3(); + if (max3(result.indirect_throughput) < VOLUME_THROUGHPUT_EPSILON) { + result.indirect_throughput = zero_float3(); break; } } - /* Prepare to scatter to new direction. */ - if (step_event == VOLUME_PATH_SCATTERED) { - /* Adjust throughput and move to new location for indirect light. */ - sd->P = ray->P + vstate.end_t * ray->D; - *throughput = vstate.throughput; - - return VOLUME_PATH_SCATTERED; + /* If we have scattering data for both direct and indirect, we're done. */ + if (result.direct_scatter && result.indirect_scatter) { + return; } } @@ -520,10 +548,6 @@ volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, break; } } - - *throughput = vstate.throughput; - - return VOLUME_PATH_ATTENUATED; } # ifdef __EMISSION__ @@ -562,6 +586,9 @@ ccl_device_forceinline bool integrate_volume_sample_light(INTEGRATOR_STATE_ARGS, ccl_device_forceinline void integrate_volume_direct_light(INTEGRATOR_STATE_ARGS, const ShaderData *ccl_restrict sd, const RNGState *ccl_restrict rng_state, + const VolumeIntegrateResult &ccl_restrict + result, + const float3 P, LightSample *ccl_restrict ls) { /* Sample position on the same light again, now from the shading @@ -575,7 +602,7 @@ ccl_device_forceinline void integrate_volume_direct_light(INTEGRATOR_STATE_ARGS, float light_u, light_v; path_state_rng_2D(kg, rng_state, PRNG_LIGHT_U, &light_u, &light_v); - if (!light_sample(kg, light_u, light_v, sd->time, sd->P, bounce, path_flag, ls)) { + if (!light_sample(kg, light_u, light_v, sd->time, P, bounce, path_flag, ls)) { return; } } @@ -608,7 +635,7 @@ ccl_device_forceinline void integrate_volume_direct_light(INTEGRATOR_STATE_ARGS, /* Create shadow ray. */ Ray ray ccl_optional_struct_init; - light_sample_to_volume_shadow_ray(kg, sd, ls, sd->P, &ray); + light_sample_to_volume_shadow_ray(kg, sd, ls, P, &ray); const bool is_light = light_sample_is_light(ls); /* Write shadow ray and associated state to global memory. */ @@ -622,7 +649,7 @@ ccl_device_forceinline void integrate_volume_direct_light(INTEGRATOR_STATE_ARGS, shadow_flag |= PATH_RAY_VOLUME_PASS; const float3 diffuse_glossy_ratio = (bounce == 0) ? one_float3() : INTEGRATOR_STATE(path, diffuse_glossy_ratio); - const float3 throughput = INTEGRATOR_STATE(path, throughput) * bsdf_eval_sum(&phase_eval); + const float3 throughput = result.direct_throughput * bsdf_eval_sum(&phase_eval); INTEGRATOR_STATE_WRITE(shadow_path, flag) = shadow_flag; INTEGRATOR_STATE_WRITE(shadow_path, bounce) = bounce; @@ -706,14 +733,14 @@ ccl_device VolumeIntegrateEvent volume_integrate(INTEGRATOR_STATE_ARGS, integrate_volume_sample_light(INTEGRATOR_STATE_PASS, &sd, &rng_state, &ls); } - /* Step through volume. */ - float3 throughput = INTEGRATOR_STATE(path, throughput); + VolumeIntegrateResult result = {}; + /* Step through volume. */ const float step_size = volume_stack_step_size(INTEGRATOR_STATE_PASS, [=](const int i) { return integrator_state_read_volume_stack(INTEGRATOR_STATE_PASS, i); }); - VolumeIntegrateEvent event = volume_integrate_heterogeneous( - INTEGRATOR_STATE_PASS, ray, &sd, &throughput, &rng_state, render_buffer, step_size); + volume_integrate_heterogeneous( + INTEGRATOR_STATE_PASS, ray, &sd, &rng_state, render_buffer, step_size, result); /* Perform path termination. The intersect_closest will have already marked this path * to be terminated. That will shading evaluating to leave out any scattering closures, @@ -726,27 +753,36 @@ ccl_device VolumeIntegrateEvent volume_integrate(INTEGRATOR_STATE_ARGS, if (probability == 0.0f) { return VOLUME_PATH_MISSED; } - else if (event == VOLUME_PATH_SCATTERED) { - /* Only divide throughput by probability if we scatter. For the attenuation - * case the next surface will already do this division. */ - if (probability != 1.0f) { - throughput /= probability; - } + + /* Direct light. */ + if (result.direct_scatter) { + const float3 direct_P = ray->P + result.direct_t * ray->D; + result.direct_throughput /= probability; + integrate_volume_direct_light(INTEGRATOR_STATE_PASS, &sd, &rng_state, result, direct_P, &ls); } - INTEGRATOR_STATE_WRITE(path, throughput) = throughput; + /* Indirect light. + * + * Only divide throughput by probability if we scatter. For the attenuation + * case the next surface will already do this division. */ + if (result.indirect_scatter) { + result.indirect_throughput /= probability; + } + INTEGRATOR_STATE_WRITE(path, throughput) = result.indirect_throughput; - if (event == VOLUME_PATH_SCATTERED) { - /* Direct light. */ - integrate_volume_direct_light(INTEGRATOR_STATE_PASS, &sd, &rng_state, &ls); + if (result.indirect_scatter) { + sd.P = ray->P + result.indirect_t * ray->D; - /* Scatter. */ - if (!integrate_volume_phase_scatter(INTEGRATOR_STATE_PASS, &sd, &rng_state)) { + if (integrate_volume_phase_scatter(INTEGRATOR_STATE_PASS, &sd, &rng_state)) { + return VOLUME_PATH_SCATTERED; + } + else { return VOLUME_PATH_MISSED; } } - - return event; + else { + return VOLUME_PATH_ATTENUATED; + } } #endif -- 2.25.1 From e631ad037eefb45c3e0e750a87ef0855178efa98 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 7 Jul 2021 18:33:22 +0200 Subject: [PATCH 7/8] Cycles X: add equiangular volume sampling for direct light Indirect always uses distance sampling. --- intern/cycles/blender/addon/ui.py | 6 +- .../integrator/integrator_shade_volume.h | 152 +++++++++++------- 2 files changed, 99 insertions(+), 59 deletions(-) diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 4ce090d..fdce354 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -1456,8 +1456,7 @@ class CYCLES_WORLD_PT_settings_volume(CyclesButtonsPanel, Panel): col = layout.column() sub = col.column() - sub.active = use_cpu(context) - sub.prop(cworld, "volume_sampling", text="Sampling") + col.prop(cworld, "volume_sampling", text="Sampling") col.prop(cworld, "volume_interpolation", text="Interpolation") col.prop(cworld, "homogeneous_volume", text="Homogeneous") sub = col.column() @@ -1596,8 +1595,7 @@ class CYCLES_MATERIAL_PT_settings_volume(CyclesButtonsPanel, Panel): col = layout.column() sub = col.column() - sub.active = use_cpu(context) - sub.prop(cmat, "volume_sampling", text="Sampling") + col.prop(cmat, "volume_sampling", text="Sampling") col.prop(cmat, "volume_interpolation", text="Interpolation") col.prop(cmat, "homogeneous_volume", text="Homogeneous") sub = col.column() diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index f0c4837..ff8f743 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -33,7 +33,6 @@ typedef enum VolumeIntegrateEvent { typedef struct VolumeIntegrateResult { /* Throughput and offset for direct light scattering. */ - VolumeSampleMethod direct_sample_method; bool direct_scatter; float3 direct_throughput; float direct_t; @@ -386,9 +385,6 @@ typedef struct VolumeIntegrateState { float start_t; float end_t; - /* Current throughput. */ - float3 throughput; - /* If volume is absorption-only up to this point, and no probabilistic * scattering or termination has been used yet. */ bool absorption_only; @@ -396,6 +392,9 @@ typedef struct VolumeIntegrateState { /* Random numbers for scattering. */ float rscatter; float rphase; + + /* Sampling. */ + VolumeSampleMethod direct_sample_method; } VolumeIntegrateState; ccl_device_forceinline void volume_integrate_step_scattering( @@ -404,44 +403,63 @@ ccl_device_forceinline void volume_integrate_step_scattering( VolumeIntegrateState &ccl_restrict vstate, VolumeIntegrateResult &ccl_restrict result) { - /* Distance sampling */ - - /* Pick random color channel, we use the Veach one-sample - * model with balance heuristic for the channels. */ - const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); - float3 channel_pdf; - const int channel = volume_sample_channel( - albedo, result.indirect_throughput, vstate.rphase, &channel_pdf); - - /* decide if we will scatter or continue */ - const float sample_transmittance = volume_channel_get(transmittance, channel); - - if (1.0f - vstate.rscatter >= sample_transmittance) { - /* compute sampling distance */ - const float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); - const float new_dt = -logf(1.0f - vstate.rscatter) / sample_sigma_t; - const float new_t = vstate.start_t + new_dt; - - /* transmittance and pdf */ - const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); - const float3 pdf = coeff.sigma_t * new_transmittance; - - /* throughput */ - result.indirect_scatter = true; - result.indirect_t = new_t; - result.indirect_throughput *= coeff.sigma_s * new_transmittance / dot(channel_pdf, pdf); - - result.direct_scatter = true; - result.direct_t = result.indirect_t; - result.direct_throughput = result.indirect_throughput; + /* Equiangular sampling for direct lighting. */ + if (vstate.direct_sample_method == VOLUME_SAMPLE_EQUIANGULAR && !result.direct_scatter) { + if (result.direct_t >= vstate.start_t && result.direct_t <= vstate.end_t) { + const float new_dt = result.direct_t - vstate.start_t; + const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); + + result.direct_scatter = true; + result.direct_throughput *= coeff.sigma_s * new_transmittance; + } + else { + result.direct_throughput *= transmittance; + } } - else { - /* throughput */ - const float pdf = dot(channel_pdf, transmittance); - result.indirect_throughput *= transmittance / pdf; - /* remap rscatter so we can reuse it and keep thing stratified */ - vstate.rscatter = 1.0f - (1.0f - vstate.rscatter) / sample_transmittance; + /* Distance sampling for indirect and optional direct lighting. */ + if (!result.indirect_scatter) { + /* Pick random color channel, we use the Veach one-sample + * model with balance heuristic for the channels. */ + const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); + float3 channel_pdf; + const int channel = volume_sample_channel( + albedo, result.indirect_throughput, vstate.rphase, &channel_pdf); + + /* decide if we will scatter or continue */ + const float sample_transmittance = volume_channel_get(transmittance, channel); + + if (1.0f - vstate.rscatter >= sample_transmittance) { + /* compute sampling distance */ + const float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); + const float new_dt = -logf(1.0f - vstate.rscatter) / sample_sigma_t; + const float new_t = vstate.start_t + new_dt; + + /* transmittance and pdf */ + const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); + const float distance_pdf = dot(channel_pdf, coeff.sigma_t * new_transmittance); + + /* throughput */ + result.indirect_scatter = true; + result.indirect_t = new_t; + result.indirect_throughput *= coeff.sigma_s * new_transmittance / distance_pdf; + + if (vstate.direct_sample_method != VOLUME_SAMPLE_EQUIANGULAR) { + /* If using distance sampling for direct light, just copy parameters + * of indirect light since we scatter at the same point then. */ + result.direct_scatter = true; + result.direct_t = result.indirect_t; + result.direct_throughput = result.indirect_throughput; + } + } + else { + /* throughput */ + const float pdf = dot(channel_pdf, transmittance); + result.indirect_throughput *= transmittance / pdf; + + /* remap rscatter so we can reuse it and keep thing stratified */ + vstate.rscatter = 1.0f - (1.0f - vstate.rscatter) / sample_transmittance; + } } } @@ -449,14 +467,16 @@ ccl_device_forceinline void volume_integrate_step_scattering( * volume until we reach the end, get absorbed entirely, or run out of * iterations. this does probabilistically scatter or get transmitted through * for path tracing where we don't want to branch. */ -ccl_device_forceinline void volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS, - Ray *ccl_restrict ray, - ShaderData *ccl_restrict sd, - const RNGState *rng_state, - ccl_global float *ccl_restrict - render_buffer, - const float object_step_size, - VolumeIntegrateResult &result) +ccl_device_forceinline void volume_integrate_heterogeneous( + INTEGRATOR_STATE_ARGS, + Ray *ccl_restrict ray, + ShaderData *ccl_restrict sd, + const RNGState *rng_state, + ccl_global float *ccl_restrict render_buffer, + const float object_step_size, + const VolumeSampleMethod direct_sample_method, + const float3 equiangular_light_P, + VolumeIntegrateResult &result) { /* Prepare for stepping. * Using a different step offset for the first step avoids banding artifacts. */ @@ -478,12 +498,21 @@ ccl_device_forceinline void volume_integrate_heterogeneous(INTEGRATOR_STATE_ARGS vstate.absorption_only = true; vstate.rscatter = path_state_rng_1D(kg, rng_state, PRNG_SCATTER_DISTANCE); vstate.rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); + vstate.direct_sample_method = direct_sample_method; /* Initialize volume integration result. */ const float3 throughput = INTEGRATOR_STATE(path, throughput); result.direct_throughput = throughput; result.indirect_throughput = throughput; + /* Equiangular sampling: compute distance and PDF in advance. */ + if (vstate.direct_sample_method == VOLUME_SAMPLE_EQUIANGULAR) { + float equiangular_pdf; + result.direct_t = volume_equiangular_sample( + ray, equiangular_light_P, vstate.rscatter, &equiangular_pdf); + result.direct_throughput /= equiangular_pdf; + } + for (int i = 0; i < max_steps; i++) { /* Advance to new position */ vstate.end_t = min(ray->t, (i + steps_offset) * step_size); @@ -726,21 +755,34 @@ ccl_device VolumeIntegrateEvent volume_integrate(INTEGRATOR_STATE_ARGS, RNGState rng_state; path_state_rng_load(INTEGRATOR_STATE_PASS, &rng_state); - /* Sample light ahead of volume stepping. */ + /* Sample light ahead of volume stepping, for equiangular sampling. */ + /* TODO: distant lights are ignored now, but could instead use even distribution. */ LightSample ls ccl_optional_struct_init; const bool need_light_sample = !(INTEGRATOR_STATE(path, flag) & PATH_RAY_TERMINATE); - if (need_light_sample) { - integrate_volume_sample_light(INTEGRATOR_STATE_PASS, &sd, &rng_state, &ls); - } + const bool have_equiangular_sample = need_light_sample && + integrate_volume_sample_light( + INTEGRATOR_STATE_PASS, &sd, &rng_state, &ls) && + (ls.t != FLT_MAX); - VolumeIntegrateResult result = {}; + VolumeSampleMethod direct_sample_method = (have_equiangular_sample) ? + volume_stack_sample_method(INTEGRATOR_STATE_PASS) : + VOLUME_SAMPLE_DISTANCE; /* Step through volume. */ const float step_size = volume_stack_step_size(INTEGRATOR_STATE_PASS, [=](const int i) { return integrator_state_read_volume_stack(INTEGRATOR_STATE_PASS, i); }); - volume_integrate_heterogeneous( - INTEGRATOR_STATE_PASS, ray, &sd, &rng_state, render_buffer, step_size, result); + + VolumeIntegrateResult result = {}; + volume_integrate_heterogeneous(INTEGRATOR_STATE_PASS, + ray, + &sd, + &rng_state, + render_buffer, + step_size, + direct_sample_method, + ls.P, + result); /* Perform path termination. The intersect_closest will have already marked this path * to be terminated. That will shading evaluating to leave out any scattering closures, -- 2.25.1 From 6f2e78ea8fb2622556c6d1ad7e6e0d779a69b4ca Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 7 Jul 2021 20:25:23 +0200 Subject: [PATCH 8/8] Cycles X: add volume multiple importance between equiangular and distance --- .../integrator/integrator_shade_volume.h | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index ff8f743..97a84e5 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -393,16 +393,28 @@ typedef struct VolumeIntegrateState { float rscatter; float rphase; - /* Sampling. */ + /* Multiple importance sampling. */ VolumeSampleMethod direct_sample_method; + bool use_mis; + float distance_pdf; + float equiangular_pdf; } VolumeIntegrateState; ccl_device_forceinline void volume_integrate_step_scattering( + const Ray *ray, + const float3 equiangular_light_P, const VolumeShaderCoefficients &ccl_restrict coeff, const float3 transmittance, VolumeIntegrateState &ccl_restrict vstate, VolumeIntegrateResult &ccl_restrict result) { + /* Pick random color channel, we use the Veach one-sample + * model with balance heuristic for the channels. */ + const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); + float3 channel_pdf; + const int channel = volume_sample_channel( + albedo, result.indirect_throughput, vstate.rphase, &channel_pdf); + /* Equiangular sampling for direct lighting. */ if (vstate.direct_sample_method == VOLUME_SAMPLE_EQUIANGULAR && !result.direct_scatter) { if (result.direct_t >= vstate.start_t && result.direct_t <= vstate.end_t) { @@ -410,22 +422,23 @@ ccl_device_forceinline void volume_integrate_step_scattering( const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); result.direct_scatter = true; - result.direct_throughput *= coeff.sigma_s * new_transmittance; + result.direct_throughput *= coeff.sigma_s * new_transmittance / vstate.equiangular_pdf; + + /* Multiple importance sampling. */ + if (vstate.use_mis) { + const float distance_pdf = vstate.distance_pdf * + dot(channel_pdf, coeff.sigma_t * new_transmittance); + result.direct_throughput *= 2.0f * power_heuristic(vstate.equiangular_pdf, distance_pdf); + } } else { result.direct_throughput *= transmittance; + vstate.distance_pdf *= dot(channel_pdf, transmittance); } } /* Distance sampling for indirect and optional direct lighting. */ if (!result.indirect_scatter) { - /* Pick random color channel, we use the Veach one-sample - * model with balance heuristic for the channels. */ - const float3 albedo = safe_divide_color(coeff.sigma_s, coeff.sigma_t); - float3 channel_pdf; - const int channel = volume_sample_channel( - albedo, result.indirect_throughput, vstate.rphase, &channel_pdf); - /* decide if we will scatter or continue */ const float sample_transmittance = volume_channel_get(transmittance, channel); @@ -450,6 +463,12 @@ ccl_device_forceinline void volume_integrate_step_scattering( result.direct_scatter = true; result.direct_t = result.indirect_t; result.direct_throughput = result.indirect_throughput; + + /* Multiple importance sampling. */ + if (vstate.use_mis) { + const float equiangular_pdf = volume_equiangular_pdf(ray, equiangular_light_P, new_t); + result.direct_throughput *= 2.0f * power_heuristic(distance_pdf, equiangular_pdf); + } } } else { @@ -498,7 +517,22 @@ ccl_device_forceinline void volume_integrate_heterogeneous( vstate.absorption_only = true; vstate.rscatter = path_state_rng_1D(kg, rng_state, PRNG_SCATTER_DISTANCE); vstate.rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); + + /* Multiple importance sampling: pick between equiangular and distance sampling strategy. */ vstate.direct_sample_method = direct_sample_method; + vstate.use_mis = (direct_sample_method == VOLUME_SAMPLE_MIS); + if (vstate.use_mis) { + if (vstate.rscatter < 0.5f) { + vstate.rscatter *= 2.0f; + vstate.direct_sample_method = VOLUME_SAMPLE_DISTANCE; + } + else { + vstate.rscatter = (vstate.rscatter - 0.5f) * 2.0f; + vstate.direct_sample_method = VOLUME_SAMPLE_EQUIANGULAR; + } + } + vstate.equiangular_pdf = 0.0f; + vstate.distance_pdf = 1.0f; /* Initialize volume integration result. */ const float3 throughput = INTEGRATOR_STATE(path, throughput); @@ -507,10 +541,8 @@ ccl_device_forceinline void volume_integrate_heterogeneous( /* Equiangular sampling: compute distance and PDF in advance. */ if (vstate.direct_sample_method == VOLUME_SAMPLE_EQUIANGULAR) { - float equiangular_pdf; result.direct_t = volume_equiangular_sample( - ray, equiangular_light_P, vstate.rscatter, &equiangular_pdf); - result.direct_throughput /= equiangular_pdf; + ray, equiangular_light_P, vstate.rscatter, &vstate.equiangular_pdf); } for (int i = 0; i < max_steps; i++) { @@ -546,7 +578,8 @@ ccl_device_forceinline void volume_integrate_heterogeneous( if (closure_flag & SD_EXTINCTION) { if ((closure_flag & SD_SCATTER) || !vstate.absorption_only) { /* Scattering and absorption. */ - volume_integrate_step_scattering(coeff, transmittance, vstate, result); + volume_integrate_step_scattering( + ray, equiangular_light_P, coeff, transmittance, vstate, result); } else { /* Absorption only. */ -- 2.25.1