Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'intern/cycles/kernel/film/light_passes.h')
-rw-r--r--intern/cycles/kernel/film/light_passes.h643
1 files changed, 643 insertions, 0 deletions
diff --git a/intern/cycles/kernel/film/light_passes.h b/intern/cycles/kernel/film/light_passes.h
new file mode 100644
index 00000000000..b45b5305119
--- /dev/null
+++ b/intern/cycles/kernel/film/light_passes.h
@@ -0,0 +1,643 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 Blender Foundation */
+
+#pragma once
+
+#include "kernel/film/adaptive_sampling.h"
+#include "kernel/film/write.h"
+
+#include "kernel/integrator/shadow_catcher.h"
+
+CCL_NAMESPACE_BEGIN
+
+/* --------------------------------------------------------------------
+ * BSDF Evaluation
+ *
+ * BSDF evaluation result, split between diffuse and glossy. This is used to
+ * accumulate render passes separately. Note that reflection, transmission
+ * and volume scattering are written to different render passes, but we assume
+ * that only one of those can happen at a bounce, and so do not need to accumulate
+ * them separately. */
+
+ccl_device_inline void bsdf_eval_init(ccl_private BsdfEval *eval,
+ const ClosureType closure_type,
+ Spectrum value)
+{
+ eval->diffuse = zero_spectrum();
+ eval->glossy = zero_spectrum();
+
+ if (CLOSURE_IS_BSDF_DIFFUSE(closure_type)) {
+ eval->diffuse = value;
+ }
+ else if (CLOSURE_IS_BSDF_GLOSSY(closure_type)) {
+ eval->glossy = value;
+ }
+
+ eval->sum = value;
+}
+
+ccl_device_inline void bsdf_eval_accum(ccl_private BsdfEval *eval,
+ const ClosureType closure_type,
+ Spectrum value)
+{
+ if (CLOSURE_IS_BSDF_DIFFUSE(closure_type)) {
+ eval->diffuse += value;
+ }
+ else if (CLOSURE_IS_BSDF_GLOSSY(closure_type)) {
+ eval->glossy += value;
+ }
+
+ eval->sum += value;
+}
+
+ccl_device_inline bool bsdf_eval_is_zero(ccl_private BsdfEval *eval)
+{
+ return is_zero(eval->sum);
+}
+
+ccl_device_inline void bsdf_eval_mul(ccl_private BsdfEval *eval, float value)
+{
+ eval->diffuse *= value;
+ eval->glossy *= value;
+ eval->sum *= value;
+}
+
+ccl_device_inline void bsdf_eval_mul(ccl_private BsdfEval *eval, Spectrum value)
+{
+ eval->diffuse *= value;
+ eval->glossy *= value;
+ eval->sum *= value;
+}
+
+ccl_device_inline Spectrum bsdf_eval_sum(ccl_private const BsdfEval *eval)
+{
+ return eval->sum;
+}
+
+ccl_device_inline Spectrum bsdf_eval_pass_diffuse_weight(ccl_private const BsdfEval *eval)
+{
+ /* Ratio of diffuse weight to recover proportions for writing to render pass.
+ * We assume reflection, transmission and volume scatter to be exclusive. */
+ return safe_divide(eval->diffuse, eval->sum);
+}
+
+ccl_device_inline Spectrum bsdf_eval_pass_glossy_weight(ccl_private const BsdfEval *eval)
+{
+ /* Ratio of glossy weight to recover proportions for writing to render pass.
+ * We assume reflection, transmission and volume scatter to be exclusive. */
+ return safe_divide(eval->glossy, eval->sum);
+}
+
+/* --------------------------------------------------------------------
+ * Clamping
+ *
+ * Clamping is done on a per-contribution basis so that we can write directly
+ * to render buffers instead of using per-thread memory, and to avoid the
+ * impact of clamping on other contributions. */
+
+ccl_device_forceinline void film_clamp_light(KernelGlobals kg, ccl_private Spectrum *L, int bounce)
+{
+#ifdef __KERNEL_DEBUG_NAN__
+ if (!isfinite_safe(*L)) {
+ kernel_assert(!"Cycles sample with non-finite value detected");
+ }
+#endif
+ /* Make sure all components are finite, allowing the contribution to be usable by adaptive
+ * sampling convergence check, but also to make it so render result never causes issues with
+ * post-processing. */
+ *L = ensure_finite(*L);
+
+#ifdef __CLAMP_SAMPLE__
+ float limit = (bounce > 0) ? kernel_data.integrator.sample_clamp_indirect :
+ kernel_data.integrator.sample_clamp_direct;
+ float sum = reduce_add(fabs(*L));
+ if (sum > limit) {
+ *L *= limit / sum;
+ }
+#endif
+}
+
+/* --------------------------------------------------------------------
+ * Pass accumulation utilities.
+ */
+
+/* --------------------------------------------------------------------
+ * Adaptive sampling.
+ */
+
+ccl_device_inline int film_write_sample(KernelGlobals kg,
+ ConstIntegratorState state,
+ ccl_global float *ccl_restrict render_buffer,
+ int sample,
+ int sample_offset)
+{
+ if (kernel_data.film.pass_sample_count == PASS_UNUSED) {
+ return sample;
+ }
+
+ ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
+
+ return atomic_fetch_and_add_uint32(
+ (ccl_global uint *)(buffer) + kernel_data.film.pass_sample_count, 1) +
+ sample_offset;
+}
+
+ccl_device void film_write_adaptive_buffer(KernelGlobals kg,
+ const int sample,
+ const Spectrum contribution,
+ ccl_global float *ccl_restrict buffer)
+{
+ /* Adaptive Sampling. Fill the additional buffer with only one half of the samples and
+ * calculate our stopping criteria. This is the heuristic from "A hierarchical automatic
+ * stopping condition for Monte Carlo global illumination" except that here it is applied
+ * per pixel and not in hierarchical tiles. */
+
+ if (kernel_data.film.pass_adaptive_aux_buffer == PASS_UNUSED) {
+ return;
+ }
+
+ if (sample_is_class_A(kernel_data.integrator.sampling_pattern, sample)) {
+ const float3 contribution_rgb = spectrum_to_rgb(contribution);
+
+ film_write_pass_float4(buffer + kernel_data.film.pass_adaptive_aux_buffer,
+ make_float4(contribution_rgb.x * 2.0f,
+ contribution_rgb.y * 2.0f,
+ contribution_rgb.z * 2.0f,
+ 0.0f));
+ }
+}
+
+/* --------------------------------------------------------------------
+ * Shadow catcher.
+ */
+
+#ifdef __SHADOW_CATCHER__
+
+/* Accumulate contribution to the Shadow Catcher pass.
+ *
+ * Returns truth if the contribution is fully handled here and is not to be added to the other
+ * passes (like combined, adaptive sampling). */
+
+ccl_device bool film_write_shadow_catcher(KernelGlobals kg,
+ const uint32_t path_flag,
+ const Spectrum contribution,
+ ccl_global float *ccl_restrict buffer)
+{
+ if (!kernel_data.integrator.has_shadow_catcher) {
+ return false;
+ }
+
+ kernel_assert(kernel_data.film.pass_shadow_catcher != PASS_UNUSED);
+ kernel_assert(kernel_data.film.pass_shadow_catcher_matte != PASS_UNUSED);
+
+ /* Matte pass. */
+ if (kernel_shadow_catcher_is_matte_path(path_flag)) {
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_shadow_catcher_matte, contribution);
+ /* NOTE: Accumulate the combined pass and to the samples count pass, so that the adaptive
+ * sampling is based on how noisy the combined pass is as if there were no catchers in the
+ * scene. */
+ }
+
+ /* Shadow catcher pass. */
+ if (kernel_shadow_catcher_is_object_pass(path_flag)) {
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_shadow_catcher, contribution);
+ return true;
+ }
+
+ return false;
+}
+
+ccl_device bool film_write_shadow_catcher_transparent(KernelGlobals kg,
+ const uint32_t path_flag,
+ const Spectrum contribution,
+ const float transparent,
+ ccl_global float *ccl_restrict buffer)
+{
+ if (!kernel_data.integrator.has_shadow_catcher) {
+ return false;
+ }
+
+ kernel_assert(kernel_data.film.pass_shadow_catcher != PASS_UNUSED);
+ kernel_assert(kernel_data.film.pass_shadow_catcher_matte != PASS_UNUSED);
+
+ if (path_flag & PATH_RAY_SHADOW_CATCHER_BACKGROUND) {
+ return true;
+ }
+
+ /* Matte pass. */
+ if (kernel_shadow_catcher_is_matte_path(path_flag)) {
+ const float3 contribution_rgb = spectrum_to_rgb(contribution);
+
+ film_write_pass_float4(
+ buffer + kernel_data.film.pass_shadow_catcher_matte,
+ make_float4(contribution_rgb.x, contribution_rgb.y, contribution_rgb.z, transparent));
+ /* NOTE: Accumulate the combined pass and to the samples count pass, so that the adaptive
+ * sampling is based on how noisy the combined pass is as if there were no catchers in the
+ * scene. */
+ }
+
+ /* Shadow catcher pass. */
+ if (kernel_shadow_catcher_is_object_pass(path_flag)) {
+ /* NOTE: The transparency of the shadow catcher pass is ignored. It is not needed for the
+ * calculation and the alpha channel of the pass contains numbers of samples contributed to a
+ * pixel of the pass. */
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_shadow_catcher, contribution);
+ return true;
+ }
+
+ return false;
+}
+
+ccl_device void film_write_shadow_catcher_transparent_only(KernelGlobals kg,
+ const uint32_t path_flag,
+ const float transparent,
+ ccl_global float *ccl_restrict buffer)
+{
+ if (!kernel_data.integrator.has_shadow_catcher) {
+ return;
+ }
+
+ kernel_assert(kernel_data.film.pass_shadow_catcher_matte != PASS_UNUSED);
+
+ /* Matte pass. */
+ if (kernel_shadow_catcher_is_matte_path(path_flag)) {
+ film_write_pass_float(buffer + kernel_data.film.pass_shadow_catcher_matte + 3, transparent);
+ }
+}
+
+/* Write shadow catcher passes on a bounce from the shadow catcher object. */
+ccl_device_forceinline void film_write_shadow_catcher_bounce_data(
+ KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
+{
+ kernel_assert(kernel_data.film.pass_shadow_catcher_sample_count != PASS_UNUSED);
+ kernel_assert(kernel_data.film.pass_shadow_catcher_matte != PASS_UNUSED);
+
+ ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
+
+ /* Count sample for the shadow catcher object. */
+ film_write_pass_float(buffer + kernel_data.film.pass_shadow_catcher_sample_count, 1.0f);
+
+ /* Since the split is done, the sample does not contribute to the matte, so accumulate it as
+ * transparency to the matte. */
+ const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
+ film_write_pass_float(buffer + kernel_data.film.pass_shadow_catcher_matte + 3,
+ average(throughput));
+}
+
+#endif /* __SHADOW_CATCHER__ */
+
+/* --------------------------------------------------------------------
+ * Render passes.
+ */
+
+/* Write combined pass. */
+ccl_device_inline void film_write_combined_pass(KernelGlobals kg,
+ const uint32_t path_flag,
+ const int sample,
+ const Spectrum contribution,
+ ccl_global float *ccl_restrict buffer)
+{
+#ifdef __SHADOW_CATCHER__
+ if (film_write_shadow_catcher(kg, path_flag, contribution, buffer)) {
+ return;
+ }
+#endif
+
+ if (kernel_data.film.light_pass_flag & PASSMASK(COMBINED)) {
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_combined, contribution);
+ }
+
+ film_write_adaptive_buffer(kg, sample, contribution, buffer);
+}
+
+/* Write combined pass with transparency. */
+ccl_device_inline void film_write_combined_transparent_pass(KernelGlobals kg,
+ const uint32_t path_flag,
+ const int sample,
+ const Spectrum contribution,
+ const float transparent,
+ ccl_global float *ccl_restrict buffer)
+{
+#ifdef __SHADOW_CATCHER__
+ if (film_write_shadow_catcher_transparent(kg, path_flag, contribution, transparent, buffer)) {
+ return;
+ }
+#endif
+
+ if (kernel_data.film.light_pass_flag & PASSMASK(COMBINED)) {
+ const float3 contribution_rgb = spectrum_to_rgb(contribution);
+
+ film_write_pass_float4(
+ buffer + kernel_data.film.pass_combined,
+ make_float4(contribution_rgb.x, contribution_rgb.y, contribution_rgb.z, transparent));
+ }
+
+ film_write_adaptive_buffer(kg, sample, contribution, buffer);
+}
+
+/* Write background or emission to appropriate pass. */
+ccl_device_inline void film_write_emission_or_background_pass(
+ KernelGlobals kg,
+ ConstIntegratorState state,
+ Spectrum contribution,
+ ccl_global float *ccl_restrict buffer,
+ const int pass,
+ const int lightgroup = LIGHTGROUP_NONE)
+{
+ if (!(kernel_data.film.light_pass_flag & PASS_ANY)) {
+ return;
+ }
+
+#ifdef __PASSES__
+ const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
+ int pass_offset = PASS_UNUSED;
+
+ /* Denoising albedo. */
+# ifdef __DENOISING_FEATURES__
+ if (path_flag & PATH_RAY_DENOISING_FEATURES) {
+ if (kernel_data.film.pass_denoising_albedo != PASS_UNUSED) {
+ const Spectrum denoising_feature_throughput = INTEGRATOR_STATE(
+ state, path, denoising_feature_throughput);
+ const Spectrum denoising_albedo = denoising_feature_throughput * contribution;
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_denoising_albedo, denoising_albedo);
+ }
+ }
+# endif /* __DENOISING_FEATURES__ */
+
+ if (lightgroup != LIGHTGROUP_NONE && kernel_data.film.pass_lightgroup != PASS_UNUSED) {
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_lightgroup + 3 * lightgroup,
+ contribution);
+ }
+
+ if (!(path_flag & PATH_RAY_ANY_PASS)) {
+ /* Directly visible, write to emission or background pass. */
+ pass_offset = pass;
+ }
+ else if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
+ /* Don't write any light passes for shadow catcher, for easier
+ * compositing back together of the combined pass. */
+ if (path_flag & PATH_RAY_SHADOW_CATCHER_HIT) {
+ return;
+ }
+
+ if (path_flag & PATH_RAY_SURFACE_PASS) {
+ /* Indirectly visible through reflection. */
+ const Spectrum diffuse_weight = INTEGRATOR_STATE(state, path, pass_diffuse_weight);
+ const Spectrum glossy_weight = INTEGRATOR_STATE(state, path, pass_glossy_weight);
+
+ /* Glossy */
+ const int glossy_pass_offset = ((INTEGRATOR_STATE(state, path, bounce) == 1) ?
+ kernel_data.film.pass_glossy_direct :
+ kernel_data.film.pass_glossy_indirect);
+ if (glossy_pass_offset != PASS_UNUSED) {
+ film_write_pass_spectrum(buffer + glossy_pass_offset, glossy_weight * contribution);
+ }
+
+ /* Transmission */
+ const int transmission_pass_offset = ((INTEGRATOR_STATE(state, path, bounce) == 1) ?
+ kernel_data.film.pass_transmission_direct :
+ kernel_data.film.pass_transmission_indirect);
+
+ if (transmission_pass_offset != PASS_UNUSED) {
+ /* Transmission is what remains if not diffuse and glossy, not stored explicitly to save
+ * GPU memory. */
+ const Spectrum transmission_weight = one_spectrum() - diffuse_weight - glossy_weight;
+ film_write_pass_spectrum(buffer + transmission_pass_offset,
+ transmission_weight * contribution);
+ }
+
+ /* Reconstruct diffuse subset of throughput. */
+ pass_offset = (INTEGRATOR_STATE(state, path, bounce) == 1) ?
+ kernel_data.film.pass_diffuse_direct :
+ kernel_data.film.pass_diffuse_indirect;
+ if (pass_offset != PASS_UNUSED) {
+ contribution *= diffuse_weight;
+ }
+ }
+ else if (path_flag & PATH_RAY_VOLUME_PASS) {
+ /* Indirectly visible through volume. */
+ pass_offset = (INTEGRATOR_STATE(state, path, bounce) == 1) ?
+ kernel_data.film.pass_volume_direct :
+ kernel_data.film.pass_volume_indirect;
+ }
+ }
+
+ /* Single write call for GPU coherence. */
+ if (pass_offset != PASS_UNUSED) {
+ film_write_pass_spectrum(buffer + pass_offset, contribution);
+ }
+#endif /* __PASSES__ */
+}
+
+/* Write light contribution to render buffer. */
+ccl_device_inline void film_write_direct_light(KernelGlobals kg,
+ ConstIntegratorShadowState state,
+ ccl_global float *ccl_restrict render_buffer)
+{
+ /* The throughput for shadow paths already contains the light shader evaluation. */
+ Spectrum contribution = INTEGRATOR_STATE(state, shadow_path, throughput);
+ film_clamp_light(kg, &contribution, INTEGRATOR_STATE(state, shadow_path, bounce));
+
+ const uint32_t render_pixel_index = INTEGRATOR_STATE(state, shadow_path, render_pixel_index);
+ const uint64_t render_buffer_offset = (uint64_t)render_pixel_index *
+ kernel_data.film.pass_stride;
+ ccl_global float *buffer = render_buffer + render_buffer_offset;
+
+ const uint32_t path_flag = INTEGRATOR_STATE(state, shadow_path, flag);
+ const int sample = INTEGRATOR_STATE(state, shadow_path, sample);
+
+ /* Ambient occlusion. */
+ if (path_flag & PATH_RAY_SHADOW_FOR_AO) {
+ if ((kernel_data.kernel_features & KERNEL_FEATURE_AO_PASS) && (path_flag & PATH_RAY_CAMERA)) {
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_ao, contribution);
+ }
+ if (kernel_data.kernel_features & KERNEL_FEATURE_AO_ADDITIVE) {
+ const Spectrum ao_weight = INTEGRATOR_STATE(state, shadow_path, unshadowed_throughput);
+ film_write_combined_pass(kg, path_flag, sample, contribution * ao_weight, buffer);
+ }
+ return;
+ }
+
+ /* Direct light shadow. */
+ film_write_combined_pass(kg, path_flag, sample, contribution, buffer);
+
+#ifdef __PASSES__
+ if (kernel_data.film.light_pass_flag & PASS_ANY) {
+ const uint32_t path_flag = INTEGRATOR_STATE(state, shadow_path, flag);
+
+ /* Don't write any light passes for shadow catcher, for easier
+ * compositing back together of the combined pass. */
+ if (path_flag & PATH_RAY_SHADOW_CATCHER_HIT) {
+ return;
+ }
+
+ /* Write lightgroup pass. LIGHTGROUP_NONE is ~0 so decode from unsigned to signed */
+ const int lightgroup = (int)(INTEGRATOR_STATE(state, shadow_path, lightgroup)) - 1;
+ if (lightgroup != LIGHTGROUP_NONE && kernel_data.film.pass_lightgroup != PASS_UNUSED) {
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_lightgroup + 3 * lightgroup,
+ contribution);
+ }
+
+ if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
+ int pass_offset = PASS_UNUSED;
+
+ if (path_flag & PATH_RAY_SURFACE_PASS) {
+ /* Indirectly visible through reflection. */
+ const Spectrum diffuse_weight = INTEGRATOR_STATE(state, shadow_path, pass_diffuse_weight);
+ const Spectrum glossy_weight = INTEGRATOR_STATE(state, shadow_path, pass_glossy_weight);
+
+ /* Glossy */
+ const int glossy_pass_offset = ((INTEGRATOR_STATE(state, shadow_path, bounce) == 0) ?
+ kernel_data.film.pass_glossy_direct :
+ kernel_data.film.pass_glossy_indirect);
+ if (glossy_pass_offset != PASS_UNUSED) {
+ film_write_pass_spectrum(buffer + glossy_pass_offset, glossy_weight * contribution);
+ }
+
+ /* Transmission */
+ const int transmission_pass_offset = ((INTEGRATOR_STATE(state, shadow_path, bounce) == 0) ?
+ kernel_data.film.pass_transmission_direct :
+ kernel_data.film.pass_transmission_indirect);
+
+ if (transmission_pass_offset != PASS_UNUSED) {
+ /* Transmission is what remains if not diffuse and glossy, not stored explicitly to save
+ * GPU memory. */
+ const Spectrum transmission_weight = one_spectrum() - diffuse_weight - glossy_weight;
+ film_write_pass_spectrum(buffer + transmission_pass_offset,
+ transmission_weight * contribution);
+ }
+
+ /* Reconstruct diffuse subset of throughput. */
+ pass_offset = (INTEGRATOR_STATE(state, shadow_path, bounce) == 0) ?
+ kernel_data.film.pass_diffuse_direct :
+ kernel_data.film.pass_diffuse_indirect;
+ if (pass_offset != PASS_UNUSED) {
+ contribution *= diffuse_weight;
+ }
+ }
+ else if (path_flag & PATH_RAY_VOLUME_PASS) {
+ /* Indirectly visible through volume. */
+ pass_offset = (INTEGRATOR_STATE(state, shadow_path, bounce) == 0) ?
+ kernel_data.film.pass_volume_direct :
+ kernel_data.film.pass_volume_indirect;
+ }
+
+ /* Single write call for GPU coherence. */
+ if (pass_offset != PASS_UNUSED) {
+ film_write_pass_spectrum(buffer + pass_offset, contribution);
+ }
+ }
+
+ /* Write shadow pass. */
+ if (kernel_data.film.pass_shadow != PASS_UNUSED && (path_flag & PATH_RAY_SHADOW_FOR_LIGHT) &&
+ (path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
+ const Spectrum unshadowed_throughput = INTEGRATOR_STATE(
+ state, shadow_path, unshadowed_throughput);
+ const Spectrum shadowed_throughput = INTEGRATOR_STATE(state, shadow_path, throughput);
+ const Spectrum shadow = safe_divide(shadowed_throughput, unshadowed_throughput) *
+ kernel_data.film.pass_shadow_scale;
+ film_write_pass_spectrum(buffer + kernel_data.film.pass_shadow, shadow);
+ }
+ }
+#endif
+}
+
+/* Write transparency to render buffer.
+ *
+ * Note that we accumulate transparency = 1 - alpha in the render buffer.
+ * Otherwise we'd have to write alpha on path termination, which happens
+ * in many places. */
+ccl_device_inline void film_write_transparent(KernelGlobals kg,
+ ConstIntegratorState state,
+ const uint32_t path_flag,
+ const float transparent,
+ ccl_global float *ccl_restrict buffer)
+{
+ if (kernel_data.film.light_pass_flag & PASSMASK(COMBINED)) {
+ film_write_pass_float(buffer + kernel_data.film.pass_combined + 3, transparent);
+ }
+
+ film_write_shadow_catcher_transparent_only(kg, path_flag, transparent, buffer);
+}
+
+/* Write holdout to render buffer. */
+ccl_device_inline void film_write_holdout(KernelGlobals kg,
+ ConstIntegratorState state,
+ const uint32_t path_flag,
+ const float transparent,
+ ccl_global float *ccl_restrict render_buffer)
+{
+ ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
+ film_write_transparent(kg, state, path_flag, transparent, buffer);
+}
+
+/* Write background contribution to render buffer.
+ *
+ * Includes transparency, matching film_write_transparent. */
+ccl_device_inline void film_write_background(KernelGlobals kg,
+ ConstIntegratorState state,
+ const Spectrum L,
+ const float transparent,
+ const bool is_transparent_background_ray,
+ ccl_global float *ccl_restrict render_buffer)
+{
+ Spectrum contribution = INTEGRATOR_STATE(state, path, throughput) * L;
+ film_clamp_light(kg, &contribution, INTEGRATOR_STATE(state, path, bounce) - 1);
+
+ ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
+ const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
+
+ if (is_transparent_background_ray) {
+ film_write_transparent(kg, state, path_flag, transparent, buffer);
+ }
+ else {
+ const int sample = INTEGRATOR_STATE(state, path, sample);
+ film_write_combined_transparent_pass(kg, path_flag, sample, contribution, transparent, buffer);
+ }
+ film_write_emission_or_background_pass(kg,
+ state,
+ contribution,
+ buffer,
+ kernel_data.film.pass_background,
+ kernel_data.background.lightgroup);
+}
+
+/* Write emission to render buffer. */
+ccl_device_inline void film_write_volume_emission(KernelGlobals kg,
+ ConstIntegratorState state,
+ const Spectrum L,
+ ccl_global float *ccl_restrict render_buffer,
+ const int lightgroup = LIGHTGROUP_NONE)
+{
+ Spectrum contribution = L;
+ film_clamp_light(kg, &contribution, INTEGRATOR_STATE(state, path, bounce) - 1);
+
+ ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
+ const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
+ const int sample = INTEGRATOR_STATE(state, path, sample);
+
+ film_write_combined_pass(kg, path_flag, sample, contribution, buffer);
+ film_write_emission_or_background_pass(
+ kg, state, contribution, buffer, kernel_data.film.pass_emission, lightgroup);
+}
+
+ccl_device_inline void film_write_surface_emission(KernelGlobals kg,
+ ConstIntegratorState state,
+ const Spectrum L,
+ const float mis_weight,
+ ccl_global float *ccl_restrict render_buffer,
+ const int lightgroup = LIGHTGROUP_NONE)
+{
+ Spectrum contribution = INTEGRATOR_STATE(state, path, throughput) * L * mis_weight;
+ film_clamp_light(kg, &contribution, INTEGRATOR_STATE(state, path, bounce) - 1);
+
+ ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
+ const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
+ const int sample = INTEGRATOR_STATE(state, path, sample);
+
+ film_write_combined_pass(kg, path_flag, sample, contribution, buffer);
+ film_write_emission_or_background_pass(
+ kg, state, contribution, buffer, kernel_data.film.pass_emission, lightgroup);
+}
+
+CCL_NAMESPACE_END