Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
/* SPDX-FileCopyrightText: 2019-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/film/write.h"
CCL_NAMESPACE_BEGIN
/* Check whether the pixel has converged and should not be sampled anymore. */
ccl_device_forceinline bool film_need_sample_pixel(KernelGlobals kg,
ConstIntegratorState state,
ccl_global float *render_buffer)
{
if (kernel_data.film.pass_adaptive_aux_buffer == PASS_UNUSED) {
return true;
}
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
const uint aux_w_offset = kernel_data.film.pass_adaptive_aux_buffer + 3;
return buffer[aux_w_offset] == 0.0f;
}
/* Determines whether to continue sampling a given pixel or if it has sufficiently converged. */
ccl_device bool film_adaptive_sampling_convergence_check(KernelGlobals kg,
ccl_global float *render_buffer,
const int x,
const int y,
const float threshold,
const int reset,
const int offset,
const int stride)
{
kernel_assert(kernel_data.film.pass_adaptive_aux_buffer != PASS_UNUSED);
kernel_assert(kernel_data.film.pass_sample_count != PASS_UNUSED);
const int render_pixel_index = offset + x + y * stride;
ccl_global float *buffer = render_buffer +
(uint64_t)render_pixel_index * kernel_data.film.pass_stride;
/* TODO(Stefan): Is this better in linear, sRGB or something else? */
const float4 A = kernel_read_pass_float4(buffer + kernel_data.film.pass_adaptive_aux_buffer);
if (!reset && A.w != 0.0f) {
/* If the pixel was considered converged, its state will not change in this kernel. Early
* output before doing any math.
*
* TODO(sergey): On a GPU it might be better to keep thread alive for better coherency? */
return true;
}
const float4 I = kernel_read_pass_float4(buffer + kernel_data.film.pass_combined);
const float sample = __float_as_uint(buffer[kernel_data.film.pass_sample_count]);
const float intensity_scale = kernel_data.film.exposure / sample;
/* The per pixel error as seen in section 2.1 of
* "A hierarchical automatic stopping condition for Monte Carlo global illumination" */
const float error_difference = (fabsf(I.x - A.x) + fabsf(I.y - A.y) + fabsf(I.z - A.z)) *
intensity_scale;
const float intensity = (I.x + I.y + I.z) * intensity_scale;
/* Anything with R+G+B > 1 is highly exposed - even in sRGB it's a range that
* some displays aren't even able to display without significant losses in
* detalization. Everything with R+G+B > 3 is overexposed and should receive
* even less samples. Filmic-like curves need maximum sampling rate at
* intensity near 0.1-0.2, so threshold of 1 for R+G+B leaves an additional
* fstop in case it is needed for compositing.
*/
float error_normalize;
if (intensity < 1.0f) {
error_normalize = sqrtf(intensity);
}
else {
error_normalize = intensity;
}
/* A small epsilon is added to the divisor to prevent division by zero. */
const float error = error_difference / (0.0001f + error_normalize);
const bool did_converge = (error < threshold);
const uint aux_w_offset = kernel_data.film.pass_adaptive_aux_buffer + 3;
buffer[aux_w_offset] = did_converge;
return did_converge;
}
/* This is a simple box filter in two passes.
* When a pixel demands more adaptive samples, let its neighboring pixels draw more samples too. */
ccl_device void film_adaptive_sampling_filter_x(KernelGlobals kg,
ccl_global float *render_buffer,
const int y,
const int start_x,
const int width,
const int offset,
const int stride)
{
kernel_assert(kernel_data.film.pass_adaptive_aux_buffer != PASS_UNUSED);
bool prev = false;
for (int x = start_x; x < start_x + width; ++x) {
int index = offset + x + y * stride;
ccl_global float *buffer = render_buffer + (uint64_t)index * kernel_data.film.pass_stride;
const uint aux_w_offset = kernel_data.film.pass_adaptive_aux_buffer + 3;
if (buffer[aux_w_offset] == 0.0f) {
if (x > start_x && !prev) {
index = index - 1;
buffer = render_buffer + (uint64_t)index * kernel_data.film.pass_stride;
buffer[aux_w_offset] = 0.0f;
}
prev = true;
}
else {
if (prev) {
buffer[aux_w_offset] = 0.0f;
}
prev = false;
}
}
}
ccl_device void film_adaptive_sampling_filter_y(KernelGlobals kg,
ccl_global float *render_buffer,
const int x,
const int start_y,
const int height,
const int offset,
const int stride)
{
kernel_assert(kernel_data.film.pass_adaptive_aux_buffer != PASS_UNUSED);
bool prev = false;
for (int y = start_y; y < start_y + height; ++y) {
int index = offset + x + y * stride;
ccl_global float *buffer = render_buffer + (uint64_t)index * kernel_data.film.pass_stride;
const uint aux_w_offset = kernel_data.film.pass_adaptive_aux_buffer + 3;
if (buffer[aux_w_offset] == 0.0f) {
if (y > start_y && !prev) {
index = index - stride;
buffer = render_buffer + (uint64_t)index * kernel_data.film.pass_stride;
buffer[aux_w_offset] = 0.0f;
}
prev = true;
}
else {
if (prev) {
buffer[aux_w_offset] = 0.0f;
}
prev = false;
}
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/film/write.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline void film_write_aov_pass_value(KernelGlobals kg,
ConstIntegratorState state,
ccl_global float *ccl_restrict render_buffer,
const int aov_id,
const float value)
{
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
film_write_pass_float(buffer + kernel_data.film.pass_aov_value + aov_id, value);
}
ccl_device_inline void film_write_aov_pass_color(KernelGlobals kg,
ConstIntegratorState state,
ccl_global float *ccl_restrict render_buffer,
const int aov_id,
const float3 color)
{
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
film_write_pass_float4(buffer + kernel_data.film.pass_aov_color + aov_id,
make_float4(color, 1.0f));
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2018-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
CCL_NAMESPACE_BEGIN
/* Element of ID pass stored in the render buffers.
* It is `float2` semantically, but it must be unaligned since the offset of ID passes in the
* render buffers might not meet expected by compiler alignment. */
struct CryptoPassBufferElement {
float x;
float y;
};
ccl_device_inline void film_write_cryptomatte_slots(ccl_global float *buffer,
const int num_slots,
const float id,
const float weight)
{
kernel_assert(id != ID_NONE);
if (weight == 0.0f) {
return;
}
for (int slot = 0; slot < num_slots; slot++) {
ccl_global CryptoPassBufferElement *id_buffer = (ccl_global CryptoPassBufferElement *)buffer;
#ifdef __ATOMIC_PASS_WRITE__
/* If the loop reaches an empty slot, the ID isn't in any slot yet - so add it! */
if (id_buffer[slot].x == ID_NONE) {
/* Use an atomic to claim this slot.
* If a different thread got here first, try again from this slot on. */
float old_id = atomic_compare_and_swap_float(buffer + slot * 2, ID_NONE, id);
if (old_id != ID_NONE && old_id != id) {
continue;
}
atomic_add_and_fetch_float(buffer + slot * 2 + 1, weight);
break;
}
/* If there already is a slot for that ID, add the weight.
* If no slot was found, add it to the last. */
else if (id_buffer[slot].x == id || slot == num_slots - 1) {
atomic_add_and_fetch_float(buffer + slot * 2 + 1, weight);
break;
}
#else /* __ATOMIC_PASS_WRITE__ */
/* If the loop reaches an empty slot, the ID isn't in any slot yet - so add it! */
if (id_buffer[slot].x == ID_NONE) {
id_buffer[slot].x = id;
id_buffer[slot].y = weight;
break;
}
/* If there already is a slot for that ID, add the weight.
* If no slot was found, add it to the last. */
if (id_buffer[slot].x == id || slot == num_slots - 1) {
id_buffer[slot].y += weight;
break;
}
#endif /* __ATOMIC_PASS_WRITE__ */
}
}
ccl_device_inline void film_sort_cryptomatte_slots(ccl_global float *buffer, const int num_slots)
{
ccl_global CryptoPassBufferElement *id_buffer = (ccl_global CryptoPassBufferElement *)buffer;
for (int slot = 1; slot < num_slots; ++slot) {
if (id_buffer[slot].x == ID_NONE) {
return;
}
/* Since we're dealing with a tiny number of elements, insertion sort should be fine. */
int i = slot;
while (i > 0 && id_buffer[i].y > id_buffer[i - 1].y) {
const CryptoPassBufferElement swap = id_buffer[i];
id_buffer[i] = id_buffer[i - 1];
id_buffer[i - 1] = swap;
--i;
}
}
}
/* post-sorting for Cryptomatte */
ccl_device_inline void film_cryptomatte_post(KernelGlobals kg,
ccl_global float *render_buffer,
const int pixel_index)
{
const int pass_stride = kernel_data.film.pass_stride;
const uint64_t render_buffer_offset = (uint64_t)pixel_index * pass_stride;
ccl_global float *cryptomatte_buffer = render_buffer + render_buffer_offset +
kernel_data.film.pass_cryptomatte;
film_sort_cryptomatte_slots(cryptomatte_buffer, 2 * kernel_data.film.cryptomatte_depth);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,224 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/surface_shader.h"
#include "kernel/camera/camera.h"
#include "kernel/geom/primitive.h"
#include "kernel/film/cryptomatte_passes.h"
#include "kernel/film/write.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline size_t film_write_cryptomatte_pass(ccl_global float *ccl_restrict buffer,
const size_t depth,
const float id,
const float matte_weight)
{
film_write_cryptomatte_slots(buffer, depth * 2, id, matte_weight);
return depth * 4;
}
ccl_device_inline void film_write_data_passes(KernelGlobals kg,
IntegratorState state,
const ccl_private ShaderData *sd,
ccl_global float *ccl_restrict render_buffer)
{
#ifdef __PASSES__
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
return;
}
/* Don't write data passes for paths that were split off for shadow catchers
* to avoid double-counting. */
if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
return;
}
const int flag = kernel_data.film.pass_flag;
if (!(flag & PASS_ANY)) {
return;
}
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
if (!(path_flag & PATH_RAY_SINGLE_PASS_DONE)) {
if (INTEGRATOR_STATE(state, path, sample) == 0) {
if (flag & PASSMASK(DEPTH)) {
const float depth = camera_z_depth(kg, sd->P);
film_overwrite_pass_float(buffer + kernel_data.film.pass_depth, depth);
}
if (flag & PASSMASK(OBJECT_ID)) {
const float id = object_pass_id(kg, sd->object);
film_overwrite_pass_float(buffer + kernel_data.film.pass_object_id, id);
}
if (flag & PASSMASK(MATERIAL_ID)) {
const float id = shader_pass_id(kg, sd);
film_overwrite_pass_float(buffer + kernel_data.film.pass_material_id, id);
}
if (flag & PASSMASK(POSITION)) {
const float3 position = sd->P;
film_overwrite_pass_float3(buffer + kernel_data.film.pass_position, position);
}
}
if (!(sd->flag & (SD_TRANSPARENT | SD_RAY_PORTAL)) ||
kernel_data.film.pass_alpha_threshold == 0.0f ||
average(surface_shader_alpha(sd)) >= kernel_data.film.pass_alpha_threshold)
{
if (flag & PASSMASK(NORMAL)) {
const float3 normal = surface_shader_average_normal(sd);
film_write_pass_float3(buffer + kernel_data.film.pass_normal, normal);
}
if (flag & PASSMASK(ROUGHNESS)) {
const float roughness = surface_shader_average_roughness(sd);
film_write_pass_float(buffer + kernel_data.film.pass_roughness, roughness);
}
if (flag & PASSMASK(UV)) {
const float3 uv = primitive_uv(kg, sd);
film_write_pass_float3(buffer + kernel_data.film.pass_uv, uv);
}
if (flag & PASSMASK(MOTION)) {
const float4 speed = primitive_motion_vector(kg, sd);
film_write_pass_float4(buffer + kernel_data.film.pass_motion, speed);
film_write_pass_float(buffer + kernel_data.film.pass_motion_weight, 1.0f);
}
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_SINGLE_PASS_DONE;
}
}
if (kernel_data.film.cryptomatte_passes) {
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
const float matte_weight = average(throughput) *
(1.0f - average(surface_shader_transparency(sd)));
if (matte_weight > 0.0f) {
ccl_global float *cryptomatte_buffer = buffer + kernel_data.film.pass_cryptomatte;
if (kernel_data.film.cryptomatte_passes & CRYPT_OBJECT) {
const float id = object_cryptomatte_id(kg, sd->object);
cryptomatte_buffer += film_write_cryptomatte_pass(
cryptomatte_buffer, kernel_data.film.cryptomatte_depth, id, matte_weight);
}
if (kernel_data.film.cryptomatte_passes & CRYPT_MATERIAL) {
const float id = kernel_data_fetch(shaders, (sd->shader & SHADER_MASK)).cryptomatte_id;
cryptomatte_buffer += film_write_cryptomatte_pass(
cryptomatte_buffer, kernel_data.film.cryptomatte_depth, id, matte_weight);
}
if (kernel_data.film.cryptomatte_passes & CRYPT_ASSET) {
const float id = object_cryptomatte_asset_id(kg, sd->object);
cryptomatte_buffer += film_write_cryptomatte_pass(
cryptomatte_buffer, kernel_data.film.cryptomatte_depth, id, matte_weight);
}
}
}
if (flag & PASSMASK(DIFFUSE_COLOR)) {
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
film_write_pass_spectrum(buffer + kernel_data.film.pass_diffuse_color,
surface_shader_diffuse(kg, sd) * throughput);
}
if (flag & PASSMASK(GLOSSY_COLOR)) {
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
film_write_pass_spectrum(buffer + kernel_data.film.pass_glossy_color,
surface_shader_glossy(kg, sd) * throughput);
}
if (flag & PASSMASK(TRANSMISSION_COLOR)) {
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
film_write_pass_spectrum(buffer + kernel_data.film.pass_transmission_color,
surface_shader_transmission(kg, sd) * throughput);
}
if (flag & PASSMASK(MIST)) {
/* Bring depth into 0..1 range. */
const float mist_start = kernel_data.film.mist_start;
const float mist_inv_depth = kernel_data.film.mist_inv_depth;
const float depth = camera_distance(kg, sd->P);
float mist = saturatef((depth - mist_start) * mist_inv_depth);
/* Falloff */
const float mist_falloff = kernel_data.film.mist_falloff;
if (mist_falloff == 1.0f) {
;
}
else if (mist_falloff == 2.0f) {
mist = mist * mist;
}
else if (mist_falloff == 0.5f) {
mist = sqrtf(mist);
}
else {
mist = powf(mist, mist_falloff);
}
/* Modulate by transparency */
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
const Spectrum alpha = surface_shader_alpha(sd);
const float mist_output = (1.0f - mist) * average(throughput * alpha);
/* Note that the final value in the render buffer we want is 1 - mist_output,
* to avoid having to tracking this in the Integrator state we do the negation
* after rendering. */
film_write_pass_float(buffer + kernel_data.film.pass_mist, mist_output);
}
#endif
}
ccl_device_inline void film_write_data_passes_background(
KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
{
#ifdef __PASSES__
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
return;
}
/* Don't write data passes for paths that were split off for shadow catchers
* to avoid double-counting. */
if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
return;
}
const int flag = kernel_data.film.pass_flag;
if (!(flag & PASS_ANY)) {
return;
}
if (!(path_flag & PATH_RAY_SINGLE_PASS_DONE)) {
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
if (INTEGRATOR_STATE(state, path, sample) == 0) {
if (flag & PASSMASK(DEPTH)) {
film_overwrite_pass_float(buffer + kernel_data.film.pass_depth, 0.0f);
}
if (flag & PASSMASK(OBJECT_ID)) {
film_overwrite_pass_float(buffer + kernel_data.film.pass_object_id, 0.0f);
}
if (flag & PASSMASK(MATERIAL_ID)) {
film_overwrite_pass_float(buffer + kernel_data.film.pass_material_id, 0.0f);
}
if (flag & PASSMASK(POSITION)) {
film_overwrite_pass_float3(buffer + kernel_data.film.pass_position, zero_float3());
}
}
if (flag & PASSMASK(MOTION)) {
const float4 speed = camera_motion_vector_direction(kg, INTEGRATOR_STATE(state, ray, D));
film_write_pass_float4(buffer + kernel_data.film.pass_motion, speed);
film_write_pass_float(buffer + kernel_data.film.pass_motion_weight, 1.0f);
}
}
#endif
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,277 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/closure/bsdf.h"
#include "kernel/film/write.h"
CCL_NAMESPACE_BEGIN
#ifdef __DENOISING_FEATURES__
ccl_device_forceinline float denoising_depth_compute(KernelGlobals kg,
IntegratorState state,
const ccl_private ShaderData *sd,
const Spectrum denoising_feature_throughput,
const bool follow_reflections)
{
float depth;
const float d = sd->ray_length - INTEGRATOR_STATE(state, ray, tmin);
if (follow_reflections) {
/* Write the ray length minus tmin. */
depth = d;
}
else {
/* Write the camera z depth. */
const float3 prev_P = sd->P + sd->wi * d;
const float prev_depth = camera_z_depth(kg, prev_P);
const float new_depth = camera_z_depth(kg, sd->P);
depth = new_depth - prev_depth;
}
return ensure_finite(depth * average(denoising_feature_throughput));
}
ccl_device_forceinline void film_write_denoising_features_surface(KernelGlobals kg,
IntegratorState state,
const ccl_private ShaderData *sd,
ccl_global float *ccl_restrict
render_buffer)
{
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!(path_flag & PATH_RAY_DENOISING_FEATURES)) {
return;
}
/* Don't write denoising passes for paths that were split off for shadow catchers
* to avoid double-counting. */
if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
return;
}
const bool use_albedo_roughness_weighting = (kernel_data.film.denoising_pass_options_flag &
DENOISING_PASS_USE_ALBEDO_ROUGHNESS_WEIGHTING) != 0;
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
float3 normal = zero_float3();
Spectrum diffuse_albedo = zero_spectrum();
Spectrum specular_albedo = zero_spectrum();
Spectrum transparent_albedo = zero_spectrum();
float specular_roughness = 0.0f;
float sum_weight = 0.0f;
float sum_nonspecular_weight = 0.0f;
for (int i = 0; i < sd->num_closure; i++) {
const ccl_private ShaderClosure *sc = &sd->closure[i];
if (!CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) {
continue;
}
/* Transparency always passes through. */
if (CLOSURE_IS_BSDF_TRANSPARENT(sc->type)) {
transparent_albedo += sc->weight;
continue;
}
const Spectrum closure_albedo = bsdf_albedo(kg, sd, sc, true, true);
const float closure_weight = average(closure_albedo);
/* All closures contribute to the normal feature, but only diffuse-like ones to the albedo. */
/* If far-field hair, use fiber tangent as feature instead of normal. */
normal += (sc->type == CLOSURE_BSDF_HAIR_HUANG_ID ? safe_normalize(sd->dPdu) : sc->N) *
closure_weight;
/* bsdf_get_specular_roughness_squared returns GGX alpha squared (alpha_x*alpha_y). Use sqrtf
* to get GGX alpha. */
const float roughness = sqrtf(bsdf_get_specular_roughness_squared(sc));
/* Transition smoothly from specular to diffuse between 0.0 and 0.15 roughness. */
const float diffuse_weight = (sc->type == CLOSURE_BSDF_HAIR_HUANG_ID) ?
1.0f :
smoothstep(0.0f, 0.15f, roughness);
if (use_albedo_roughness_weighting) {
diffuse_albedo += closure_albedo * diffuse_weight;
specular_albedo += closure_albedo * (1.0f - diffuse_weight);
}
else if (CLOSURE_IS_BSDF_DIFFUSE(sc->type) || CLOSURE_IS_BSSRDF(sc->type)) {
diffuse_albedo += closure_albedo;
}
else if (CLOSURE_IS_BSDF_GLOSSY(sc->type) || CLOSURE_IS_GLASS(sc->type)) {
specular_albedo += closure_albedo;
}
/* Apply sqrtf again to convert GGX alpha to perceptual roughness. */
specular_roughness += sqrtf(roughness) * closure_weight;
sum_weight += closure_weight;
sum_nonspecular_weight += closure_weight * diffuse_weight;
}
/* Fraction of non-transparent closures, for smooth blending at transparent surfaces. */
const float transparent_weight = average(transparent_albedo);
const float total_weight = sum_weight + transparent_weight;
/* Blend between writing features at this bounce vs. deferring to the next bounce based
* on the proportion of diffuse closures. Smoothly transition between 0.0 and 0.5 diffuse
* fraction. */
float feature_weight = 0.0f;
if (sum_weight > 0.0f) {
normal /= sum_weight;
specular_roughness /= sum_weight;
feature_weight = smoothstep(0.0f, 0.5f, sum_nonspecular_weight / sum_weight);
}
/* Whether to defer features to the next bounce for individual passes. */
const bool follow_reflections = (kernel_data.film.denoising_pass_options_flag &
DENOISING_PASS_FOLLOW_REFLECTIONS) != 0;
if (!follow_reflections) {
feature_weight = 1.0f;
}
const Spectrum denoising_feature_throughput = INTEGRATOR_STATE(
state, path, denoising_feature_throughput);
const bool is_first_bounce = INTEGRATOR_STATE(state, path, bounce) == 0;
if (kernel_data.film.pass_denoising_depth != PASS_UNUSED &&
(is_first_bounce || follow_reflections))
{
const float denoising_depth = denoising_depth_compute(
kg, state, sd, denoising_feature_throughput, follow_reflections);
film_write_pass_float(buffer + kernel_data.film.pass_denoising_depth, denoising_depth);
}
if (kernel_data.film.pass_denoising_normal != PASS_UNUSED && feature_weight > 0.0f &&
(is_first_bounce || follow_reflections))
{
/* Transform normal into camera space. */
const Transform worldtocamera = kernel_data.cam.worldtocamera;
float3 denoising_normal = transform_direction(&worldtocamera, normal);
const float opaque_fraction = (total_weight > 0.0f) ? (sum_weight / total_weight) : 1.0f;
denoising_normal = ensure_finite(denoising_normal * opaque_fraction * feature_weight *
average(denoising_feature_throughput));
film_write_pass_float3(buffer + kernel_data.film.pass_denoising_normal, denoising_normal);
}
if (kernel_data.film.pass_denoising_albedo != PASS_UNUSED && feature_weight > 0.0f &&
(is_first_bounce || follow_reflections))
{
const Spectrum denoising_albedo = ensure_finite(diffuse_albedo * feature_weight *
denoising_feature_throughput);
film_write_pass_spectrum(buffer + kernel_data.film.pass_denoising_albedo, denoising_albedo);
}
if (is_first_bounce) {
if (kernel_data.film.pass_denoising_roughness != PASS_UNUSED) {
const float denoising_roughness = ensure_finite(specular_roughness *
average(denoising_feature_throughput));
film_write_pass_float(buffer + kernel_data.film.pass_denoising_roughness,
denoising_roughness);
}
if (kernel_data.film.pass_denoising_specular_albedo != PASS_UNUSED) {
const Spectrum denoising_specular_albedo = ensure_finite(specular_albedo *
denoising_feature_throughput);
film_write_pass_spectrum(buffer + kernel_data.film.pass_denoising_specular_albedo,
denoising_specular_albedo);
}
if (kernel_data.film.pass_denoising_backward_motion != PASS_UNUSED) {
const float3 backward_motion = primitive_motion_vector_backward_depth_delta(kg, sd);
film_write_pass_float3(buffer + kernel_data.film.pass_denoising_backward_motion,
backward_motion);
}
}
/* Portion deferred to the next bounce. Specularity uses the feature weight, transparent
* always passes through. */
const Spectrum deferred_albedo = specular_albedo * (1.0f - feature_weight) + transparent_albedo;
if (reduce_max(fabs(deferred_albedo)) > 1e-4f) {
INTEGRATOR_STATE_WRITE(state, path, denoising_feature_throughput) *= deferred_albedo;
}
else {
INTEGRATOR_STATE_WRITE(state, path, flag) &= ~PATH_RAY_DENOISING_FEATURES;
}
}
ccl_device_forceinline void film_write_denoising_features_surface_volume(
KernelGlobals kg,
IntegratorState state,
const ccl_private ShaderData *sd,
ccl_global float *ccl_restrict render_buffer)
{
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
const bool follow_reflections = (kernel_data.film.denoising_pass_options_flag &
DENOISING_PASS_FOLLOW_REFLECTIONS) != 0;
const bool is_first_bounce = INTEGRATOR_STATE(state, path, bounce) == 0;
if (kernel_data.film.pass_denoising_depth != PASS_UNUSED &&
(is_first_bounce || follow_reflections))
{
const Spectrum denoising_feature_throughput = INTEGRATOR_STATE(
state, path, denoising_feature_throughput);
const float denoising_depth = denoising_depth_compute(
kg, state, sd, denoising_feature_throughput, follow_reflections);
film_write_pass_float(buffer + kernel_data.film.pass_denoising_depth, denoising_depth);
}
}
ccl_device_forceinline void film_write_denoising_features_volume(KernelGlobals kg,
IntegratorState state,
const Spectrum albedo,
const bool scatter,
ccl_global float *ccl_restrict
render_buffer)
{
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
const Spectrum denoising_feature_throughput = INTEGRATOR_STATE(
state, path, denoising_feature_throughput);
if (scatter && kernel_data.film.pass_denoising_normal != PASS_UNUSED) {
/* Assume scatter is sufficiently diffuse to stop writing denoising features. */
INTEGRATOR_STATE_WRITE(state, path, flag) &= ~PATH_RAY_DENOISING_FEATURES;
/* Write view direction as normal. */
const float3 denoising_normal = make_float3(0.0f, 0.0f, -1.0f);
film_write_pass_float3(buffer + kernel_data.film.pass_denoising_normal, denoising_normal);
}
if (kernel_data.film.pass_denoising_albedo != PASS_UNUSED) {
/* Write albedo. */
const Spectrum denoising_albedo = ensure_finite(denoising_feature_throughput * albedo);
film_write_pass_spectrum(buffer + kernel_data.film.pass_denoising_albedo, denoising_albedo);
}
}
ccl_device_forceinline void film_write_denoising_features_background(
KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
{
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!(path_flag & PATH_RAY_DENOISING_FEATURES)) {
return;
}
/* Do not write default background denoising data for secondary paths. */
if (INTEGRATOR_STATE(state, path, bounce) != 0) {
return;
}
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
if (kernel_data.film.pass_denoising_depth != PASS_UNUSED) {
film_overwrite_pass_float(buffer + kernel_data.film.pass_denoising_depth, FLT_MAX);
}
/* 'pass_denoising_albedo' is written by 'film_write_emission_or_background_pass' */
}
#endif /* __DENOISING_FEATURES__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,700 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/film/write.h"
#include "kernel/integrator/shadow_catcher.h"
#include "kernel/sample/pattern.h"
#include "util/atomic.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 ccl_private ShaderClosure *sc,
const float3 wo,
Spectrum value)
{
eval->diffuse = zero_spectrum();
eval->glossy = zero_spectrum();
if (CLOSURE_IS_BSDF_DIFFUSE(sc->type)) {
eval->diffuse = value;
}
else if (CLOSURE_IS_BSDF_GLOSSY(sc->type)) {
eval->glossy = value;
}
else if (CLOSURE_IS_GLASS(sc->type)) {
/* Glass can count as glossy or transmission, depending on which side we end up on. */
if (dot(sc->N, wo) > 0.0f) {
eval->glossy = value;
}
}
eval->sum = value;
}
ccl_device_inline void bsdf_eval_init(ccl_private BsdfEval *eval, Spectrum value)
{
eval->diffuse = zero_spectrum();
eval->glossy = zero_spectrum();
eval->sum = value;
}
ccl_device_inline void bsdf_eval_accum(ccl_private BsdfEval *eval,
const ccl_private ShaderClosure *sc,
const float3 wo,
Spectrum value)
{
if (CLOSURE_IS_BSDF_DIFFUSE(sc->type)) {
eval->diffuse += value;
}
else if (CLOSURE_IS_BSDF_GLOSSY(sc->type)) {
eval->glossy += value;
}
else if (CLOSURE_IS_GLASS(sc->type)) {
if (dot(sc->N, wo) > 0.0f) {
eval->glossy += value;
}
}
eval->sum += value;
}
ccl_device_inline void bsdf_eval_accum(ccl_private BsdfEval *eval, Spectrum 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, const 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(const ccl_private BsdfEval *eval)
{
return eval->sum;
}
ccl_device_inline Spectrum bsdf_eval_pass_diffuse_weight(const ccl_private 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(const ccl_private 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,
const 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__
const float limit = (bounce > 0) ? kernel_data.integrator.sample_clamp_indirect :
kernel_data.integrator.sample_clamp_direct;
const 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,
const int sample,
const 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));
}
}
/* Write the volume and surface contribution for volume scattering probability guiding. */
ccl_device_inline void film_write_volume_scattering_guiding_pass(
KernelGlobals kg,
ccl_global float *ccl_restrict buffer,
const PathRayVisibility path_visibility,
const uint32_t path_flag,
const Spectrum contribution)
{
int pass_offset = PASS_UNUSED;
if (path_flag & PATH_RAY_VOLUME_PRIMARY_TRANSMIT) {
pass_offset = kernel_data.film.pass_volume_transmit;
}
else if (path_visibility & PATH_RAY_VISIBILITY_VOLUME_SCATTER) {
pass_offset = kernel_data.film.pass_volume_scatter;
}
if (pass_offset != PASS_UNUSED) {
film_write_pass_spectrum(buffer + pass_offset, contribution);
}
}
/* --------------------------------------------------------------------
* 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, 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 PathRayVisibility path_visibility,
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);
film_write_volume_scattering_guiding_pass(kg, buffer, path_visibility, path_flag, contribution);
}
/* Write combined pass with transparency. */
ccl_device_inline void film_write_combined_transparent_pass(
KernelGlobals kg,
const PathRayVisibility path_visibility,
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, transparent));
}
film_write_adaptive_buffer(kg, sample, contribution, buffer);
film_write_volume_scattering_guiding_pass(kg, buffer, path_visibility, path_flag, contribution);
}
/* 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__ */
const bool is_shadowcatcher = (path_flag & PATH_RAY_SHADOW_CATCHER_HIT) != 0;
if (!is_shadowcatcher && 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 (is_shadowcatcher) {
/* Don't write any light passes for shadow catcher, for easier
* compositing back together of the combined pass. */
return;
}
else if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
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));
ccl_global float *buffer = film_pass_pixel_render_buffer_shadow(kg, state, render_buffer);
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, shadow_path, visibility);
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_visibility & PATH_RAY_VISIBILITY_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_visibility, path_flag, sample, contribution * ao_weight, buffer);
}
return;
}
/* Direct light shadow. */
film_write_combined_pass(kg, path_visibility, 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);
}
}
}
#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,
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);
}
#ifdef __SHADOW_CATCHER__
film_write_shadow_catcher_transparent_only(kg, path_flag, transparent, buffer);
#endif
if (path_flag & PATH_RAY_VOLUME_PRIMARY_TRANSMIT) {
kernel_assert(kernel_data.film.pass_volume_transmit != PASS_UNUSED);
film_write_pass_spectrum(buffer + kernel_data.film.pass_volume_transmit,
make_spectrum(transparent));
}
}
/* 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, 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 PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (is_transparent_background_ray) {
film_write_transparent(kg, path_flag, transparent, buffer);
}
else {
const int sample = INTEGRATOR_STATE(state, path, sample);
film_write_combined_transparent_pass(
kg, path_visibility, 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 PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const int sample = INTEGRATOR_STATE(state, path, sample);
film_write_combined_pass(kg, path_visibility, 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 PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const int sample = INTEGRATOR_STATE(state, path, sample);
film_write_combined_pass(kg, path_visibility, path_flag, sample, contribution, buffer);
film_write_emission_or_background_pass(
kg, state, contribution, buffer, kernel_data.film.pass_emission, lightgroup);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,599 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Functions to retrieving render passes for display or output. Reading from
* the raw render buffer and normalizing based on the number of samples,
* computing alpha, compositing shadow catchers, etc. */
#pragma once
#include "kernel/types.h"
#include "util/color.h"
CCL_NAMESPACE_BEGIN
/* --------------------------------------------------------------------
* Common utilities.
*/
/* The input buffer contains transparency = 1 - alpha, this converts it to
* alpha. Also clamp since alpha might end up outside of 0..1 due to Russian
* roulette. */
ccl_device_forceinline float film_transparency_to_alpha(const float transparency)
{
return saturatef(1.0f - transparency);
}
ccl_device_inline float film_get_scale(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer)
{
if (kfilm_convert->pass_sample_count == PASS_UNUSED) {
return kfilm_convert->scale;
}
if (kfilm_convert->pass_use_filter) {
const uint sample_count = *(
(const ccl_global uint *)(buffer + kfilm_convert->pass_sample_count));
return kfilm_convert->scale / sample_count;
}
return kfilm_convert->scale;
}
ccl_device_inline float film_get_scale_exposure(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer)
{
if (kfilm_convert->pass_sample_count == PASS_UNUSED) {
return kfilm_convert->scale_exposure;
}
const float scale = film_get_scale(kfilm_convert, buffer);
if (kfilm_convert->pass_use_exposure) {
return scale * kfilm_convert->exposure;
}
return scale;
}
ccl_device_inline bool film_get_scale_and_scale_exposure(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict scale,
ccl_private float *ccl_restrict scale_exposure)
{
if (kfilm_convert->pass_sample_count == PASS_UNUSED) {
*scale = kfilm_convert->scale;
*scale_exposure = kfilm_convert->scale_exposure;
return true;
}
if (kfilm_convert->pass_use_filter) {
const uint sample_count = *(
(const ccl_global uint *)(buffer + kfilm_convert->pass_sample_count));
if (!sample_count) {
*scale = 0.0f;
*scale_exposure = 0.0f;
return false;
}
*scale = kfilm_convert->scale / sample_count;
}
else {
*scale = kfilm_convert->scale;
}
if (kfilm_convert->pass_use_exposure) {
*scale_exposure = *scale * kfilm_convert->exposure;
}
else {
*scale_exposure = *scale;
}
return true;
}
/* --------------------------------------------------------------------
* Float (scalar) passes.
*/
ccl_device_inline void film_get_pass_pixel_depth(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components >= 1);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
const float scale_exposure = film_get_scale_exposure(kfilm_convert, buffer);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float f = *in;
pixel[0] = (f == 0.0f) ? 1e10f : f * scale_exposure;
}
ccl_device_inline void film_get_pass_pixel_mist(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components >= 1);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
const float scale_exposure = film_get_scale_exposure(kfilm_convert, buffer);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float f = *in;
/* Note that we accumulate 1 - mist in the kernel to avoid having to
* track the mist values in the integrator state. */
pixel[0] = saturatef(1.0f - f * scale_exposure);
}
ccl_device_inline void film_get_pass_pixel_sample_count(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
/* TODO(sergey): Consider normalizing into the [0..1] range, so that it is possible to see
* meaningful value when adaptive sampler stopped rendering image way before the maximum
* number of samples was reached (for examples when number of samples is set to 0 in
* viewport). */
kernel_assert(kfilm_convert->num_components >= 1);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float f = *in;
pixel[0] = __float_as_uint(f) * kfilm_convert->scale;
}
ccl_device_inline void film_get_pass_pixel_volume_majorant(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components >= 1);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
const float scale_exposure = film_get_scale_exposure(kfilm_convert, buffer);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const ccl_global float *count = buffer + kfilm_convert->pass_divide;
const float f = *in;
pixel[0] = (*count != 0.0f) ? expf(-(f * scale_exposure) / *count) : 0.0f;
}
ccl_device_inline void film_get_pass_pixel_rgbe(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components >= 1);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float3 f = rgbe_to_rgb(RGBE(*in));
pixel[0] = f.x;
pixel[1] = f.y;
pixel[2] = f.z;
}
ccl_device_inline void film_get_pass_pixel_float(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components >= 1);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
const float scale_exposure = film_get_scale_exposure(kfilm_convert, buffer);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float f = *in;
pixel[0] = f * scale_exposure;
}
/* --------------------------------------------------------------------
* Float 3 passes.
*/
ccl_device_inline void film_get_pass_pixel_light_path(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components >= 3);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
/* Read light pass. */
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
float3 f = make_float3(in[0], in[1], in[2]);
/* Optionally add indirect light pass. */
if (kfilm_convert->pass_indirect != PASS_UNUSED) {
const ccl_global float *in_indirect = buffer + kfilm_convert->pass_indirect;
const float3 f_indirect = make_float3(in_indirect[0], in_indirect[1], in_indirect[2]);
f += f_indirect;
}
/* Optionally divide out color. */
if (kfilm_convert->pass_divide != PASS_UNUSED) {
const ccl_global float *in_divide = buffer + kfilm_convert->pass_divide;
const float3 f_divide = make_float3(in_divide[0], in_divide[1], in_divide[2]);
f = safe_divide_even_color(f, f_divide);
/* Exposure only, sample scale cancels out. */
f *= kfilm_convert->exposure;
}
else {
/* Sample scale and exposure. */
f *= film_get_scale_exposure(kfilm_convert, buffer);
}
pixel[0] = f.x;
pixel[1] = f.y;
pixel[2] = f.z;
/* Optional alpha channel. */
if (kfilm_convert->num_components >= 4) {
if (kfilm_convert->pass_combined != PASS_UNUSED) {
float scale;
float scale_exposure;
film_get_scale_and_scale_exposure(kfilm_convert, buffer, &scale, &scale_exposure);
const ccl_global float *in_combined = buffer + kfilm_convert->pass_combined;
const float alpha = in_combined[3] * scale;
pixel[3] = film_transparency_to_alpha(alpha);
}
else {
pixel[3] = 1.0f;
}
}
}
ccl_device_inline void film_get_pass_pixel_float3(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components >= 3);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
const float scale_exposure = film_get_scale_exposure(kfilm_convert, buffer);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float3 f = make_float3(in[0], in[1], in[2]) * scale_exposure;
pixel[0] = f.x;
pixel[1] = f.y;
pixel[2] = f.z;
/* Optional alpha channel. */
if (kfilm_convert->num_components >= 4) {
if (kfilm_convert->pass_combined != PASS_UNUSED) {
float scale;
float scale_exposure;
film_get_scale_and_scale_exposure(kfilm_convert, buffer, &scale, &scale_exposure);
const ccl_global float *in_combined = buffer + kfilm_convert->pass_combined;
const float alpha = in_combined[3] * scale;
pixel[3] = film_transparency_to_alpha(alpha);
}
else {
pixel[3] = 1.0f;
}
}
}
/* --------------------------------------------------------------------
* Float4 passes.
*/
ccl_device_inline void film_get_pass_pixel_motion(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components == 4);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
kernel_assert(kfilm_convert->pass_motion_weight != PASS_UNUSED);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const ccl_global float *in_weight = buffer + kfilm_convert->pass_motion_weight;
const float weight = in_weight[0];
const float weight_inv = (weight > 0.0f) ? 1.0f / weight : 0.0f;
const float4 motion = make_float4(in[0], in[1], in[2], in[3]) * weight_inv;
pixel[0] = motion.x;
pixel[1] = motion.y;
pixel[2] = motion.z;
pixel[3] = motion.w;
}
ccl_device_inline void film_get_pass_pixel_cryptomatte(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components == 4);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
const float scale = film_get_scale(kfilm_convert, buffer);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float4 f = make_float4(in[0], in[1], in[2], in[3]);
/* x and z contain integer IDs, don't rescale them.
* y and w contain matte weights, they get scaled. */
pixel[0] = f.x;
pixel[1] = f.y * scale;
pixel[2] = f.z;
pixel[3] = f.w * scale;
}
ccl_device_inline void film_get_pass_pixel_float4(const ccl_global KernelFilmConvert *ccl_restrict
kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components == 4);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
float scale;
float scale_exposure;
film_get_scale_and_scale_exposure(kfilm_convert, buffer, &scale, &scale_exposure);
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float3 color = make_float3(in[0], in[1], in[2]) * scale_exposure;
const float alpha = in[3] * scale;
pixel[0] = color.x;
pixel[1] = color.y;
pixel[2] = color.z;
pixel[3] = alpha;
}
ccl_device_inline void film_get_pass_pixel_combined(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components == 4);
/* 3rd channel contains transparency = 1 - alpha for the combined pass. */
kernel_assert(kfilm_convert->num_components == 4);
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
float scale;
float scale_exposure;
if (!film_get_scale_and_scale_exposure(kfilm_convert, buffer, &scale, &scale_exposure)) {
pixel[0] = 0.0f;
pixel[1] = 0.0f;
pixel[2] = 0.0f;
pixel[3] = 0.0f;
return;
}
const ccl_global float *in = buffer + kfilm_convert->pass_offset;
const float3 color = make_float3(in[0], in[1], in[2]) * scale_exposure;
const float alpha = in[3] * scale;
pixel[0] = color.x;
pixel[1] = color.y;
pixel[2] = color.z;
pixel[3] = film_transparency_to_alpha(alpha);
}
/* --------------------------------------------------------------------
* Shadow catcher.
*/
ccl_device_inline float3 film_calculate_shadow_catcher_denoised(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer)
{
kernel_assert(kfilm_convert->pass_shadow_catcher != PASS_UNUSED);
float scale;
float scale_exposure;
film_get_scale_and_scale_exposure(kfilm_convert, buffer, &scale, &scale_exposure);
const ccl_global float *in_catcher = buffer + kfilm_convert->pass_shadow_catcher;
const float3 pixel = make_float3(in_catcher[0], in_catcher[1], in_catcher[2]) * scale_exposure;
return pixel;
}
ccl_device_inline float3 safe_divide_shadow_catcher(const float3 a, const float3 b)
{
float x;
float y;
float z;
x = (b.x != 0.0f) ? a.x / b.x : 1.0f;
y = (b.y != 0.0f) ? a.y / b.y : 1.0f;
z = (b.z != 0.0f) ? a.z / b.z : 1.0f;
return make_float3(x, y, z);
}
ccl_device_inline float3
film_calculate_shadow_catcher(const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer)
{
/* For the shadow catcher pass we divide combined pass by the shadow catcher.
* Note that denoised shadow catcher pass contains value which only needs ot be scaled (but not
* to be calculated as division). */
if (kfilm_convert->is_denoised) {
return film_calculate_shadow_catcher_denoised(kfilm_convert, buffer);
}
kernel_assert(kfilm_convert->pass_shadow_catcher_sample_count != PASS_UNUSED);
/* If there is no shadow catcher object in this pixel, there is no modification of the light
* needed, so return one. */
const ccl_global float *in_catcher_sample_count =
buffer + kfilm_convert->pass_shadow_catcher_sample_count;
const float num_samples = in_catcher_sample_count[0];
if (num_samples == 0.0f) {
return one_float3();
}
kernel_assert(kfilm_convert->pass_shadow_catcher != PASS_UNUSED);
const ccl_global float *in_catcher = buffer + kfilm_convert->pass_shadow_catcher;
/* NOTE: It is possible that the Shadow Catcher pass is requested as an output without actual
* shadow catcher objects in the scene. In this case there will be no auxiliary passes required
* for the decision (to save up memory). So delay the asserts to this point so that the number of
* samples check handles such configuration. */
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
kernel_assert(kfilm_convert->pass_combined != PASS_UNUSED);
kernel_assert(kfilm_convert->pass_shadow_catcher_matte != PASS_UNUSED);
const ccl_global float *in_combined = buffer + kfilm_convert->pass_combined;
const ccl_global float *in_matte = buffer + kfilm_convert->pass_shadow_catcher_matte;
/* No scaling needed. The integration works in way that number of samples in the combined and
* shadow catcher passes are the same, and exposure is canceled during the division. */
const float3 color_catcher = make_float3(in_catcher[0], in_catcher[1], in_catcher[2]);
const float3 color_combined = make_float3(in_combined[0], in_combined[1], in_combined[2]);
const float3 color_matte = make_float3(in_matte[0], in_matte[1], in_matte[2]);
/* Need to ignore contribution of the matte object when doing division (otherwise there will be
* artifacts caused by anti-aliasing). Since combined pass is used for adaptive sampling and need
* to contain matte objects, we subtract matte objects contribution here. This is the same as if
* the matte objects were not accumulated to the combined pass. */
const float3 combined_no_matte = color_combined - color_matte;
const float3 shadow_catcher = safe_divide_shadow_catcher(combined_no_matte, color_catcher);
const float scale = film_get_scale(kfilm_convert, buffer);
const float transparency = in_combined[3] * scale;
const float alpha = film_transparency_to_alpha(transparency);
/* Alpha-over on white using transparency of the combined pass. This allows to eliminate
* artifacts which are happening on an edge of a shadow catcher when using transparent film.
* Note that we treat shadow catcher as straight alpha here because alpha got canceled out
* during the division. */
const float3 pixel = (1.0f - alpha) * one_float3() + alpha * shadow_catcher;
return pixel;
}
ccl_device_inline float4 film_calculate_shadow_catcher_matte_with_shadow(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer)
{
/* The approximation of the shadow is 1 - average(shadow_catcher_pass). A better approximation
* is possible.
*
* The matte is alpha-overed onto the shadow (which is kind of alpha-overing shadow onto footage,
* and then alpha-overing synthetic objects on top). */
kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
kernel_assert(kfilm_convert->pass_shadow_catcher != PASS_UNUSED);
kernel_assert(kfilm_convert->pass_shadow_catcher_matte != PASS_UNUSED);
float scale;
float scale_exposure;
if (!film_get_scale_and_scale_exposure(kfilm_convert, buffer, &scale, &scale_exposure)) {
return zero_float4();
}
const ccl_global float *in_matte = buffer + kfilm_convert->pass_shadow_catcher_matte;
const float3 shadow_catcher = film_calculate_shadow_catcher(kfilm_convert, buffer);
const float3 color_matte = make_float3(in_matte[0], in_matte[1], in_matte[2]) * scale_exposure;
const float transparency = in_matte[3] * scale;
const float alpha = saturatef(1.0f - transparency);
const float alpha_matte = (1.0f - alpha) * (1.0f - saturatef(average(shadow_catcher))) + alpha;
if (kfilm_convert->use_approximate_shadow_catcher_background) {
kernel_assert(kfilm_convert->pass_background != PASS_UNUSED);
const ccl_global float *in_background = buffer + kfilm_convert->pass_background;
const float3 color_background = make_float3(
in_background[0], in_background[1], in_background[2]) *
scale_exposure;
const float3 alpha_over = color_matte + color_background * (1.0f - alpha_matte);
return make_float4(alpha_over, 1.0f);
}
return make_float4(color_matte, alpha_matte);
}
ccl_device_inline void film_get_pass_pixel_shadow_catcher(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components >= 3);
const float3 pixel_value = film_calculate_shadow_catcher(kfilm_convert, buffer);
pixel[0] = pixel_value.x;
pixel[1] = pixel_value.y;
pixel[2] = pixel_value.z;
}
ccl_device_inline void film_get_pass_pixel_shadow_catcher_matte_with_shadow(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
kernel_assert(kfilm_convert->num_components == 3 || kfilm_convert->num_components == 4);
const float4 pixel_value = film_calculate_shadow_catcher_matte_with_shadow(kfilm_convert,
buffer);
pixel[0] = pixel_value.x;
pixel[1] = pixel_value.y;
pixel[2] = pixel_value.z;
if (kfilm_convert->num_components == 4) {
pixel[3] = pixel_value.w;
}
}
/* --------------------------------------------------------------------
* Compositing and overlays.
*/
ccl_device_inline void film_apply_pass_pixel_overlays_rgba(
const ccl_global KernelFilmConvert *ccl_restrict kfilm_convert,
const ccl_global float *ccl_restrict buffer,
ccl_private float *ccl_restrict pixel)
{
if (kfilm_convert->show_active_pixels && kfilm_convert->pass_adaptive_aux_buffer != PASS_UNUSED)
{
if (buffer[kfilm_convert->pass_adaptive_aux_buffer + 3] == 0.0f) {
const float3 active_rgb = make_float3(1.0f, 0.0f, 0.0f);
const float3 mix_rgb = interp(make_float3(pixel[0], pixel[1], pixel[2]), active_rgb, 0.5f);
pixel[0] = mix_rgb.x;
pixel[1] = mix_rgb.y;
pixel[2] = mix_rgb.z;
}
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,155 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/film/write.h"
/* Denoise volume scattering probability guiding buffers. */
CCL_NAMESPACE_BEGIN
/* Two-pass Gaussian filter. */
ccl_device void volume_guiding_filter_x(KernelGlobals kg,
ccl_global float *render_buffer,
const int y,
const int center_x,
const int min_x,
const int max_x,
const int offset,
const int stride)
{
kernel_assert(kernel_data.film.pass_volume_scatter != PASS_UNUSED);
kernel_assert(kernel_data.film.pass_sample_count != PASS_UNUSED);
const int radius = 5;
const int filter_width = radius * 2 + 1;
/* sigma = 1.5 with integral according to
* https://lisyarus.github.io/blog/posts/blur-coefficients-generator.html
* https://bartwronski.com/2021/10/31/practical-gaussian-filter-binomial-filter-and-small-sigma-gaussians/
*/
const float gaussian_params[filter_width] = {0.0012273699895602f,
0.0084674212370284f,
0.0379843612914121f,
0.1108921888487800f,
0.2108379677336155f,
0.2611813817992076f,
0.2108379677336155f,
0.1108921888487800f,
0.0379843612914121f,
0.0084674212370284f,
0.0012273699895602f};
ccl_global float *buffer = film_pass_pixel_render_buffer(
kg, center_x, y, offset, stride, render_buffer);
/* Apply Gaussian filter in x direction. */
float3 scatter = zero_float3(), transmit = zero_float3();
for (int dx = 0; dx < filter_width; dx++) {
const int x = center_x + dx - radius;
if (x < min_x || x >= max_x) {
/* Ignore boundary pixels. */
continue;
}
ccl_global float *buffer = film_pass_pixel_render_buffer(
kg, x, y, offset, stride, render_buffer);
const float weight = gaussian_params[dx] /
__float_as_uint(buffer[kernel_data.film.pass_sample_count]);
scatter += kernel_read_pass_float3(buffer + kernel_data.film.pass_volume_scatter) * weight;
transmit += kernel_read_pass_float3(buffer + kernel_data.film.pass_volume_transmit) * weight;
}
/* Write to the buffer. */
film_overwrite_pass_rgbe(buffer + kernel_data.film.pass_volume_scatter_denoised, scatter);
film_overwrite_pass_rgbe(buffer + kernel_data.film.pass_volume_transmit_denoised, transmit);
}
ccl_device void volume_guiding_filter_y(KernelGlobals kg,
ccl_global float *render_buffer,
const int x,
const int min_y,
const int max_y,
const int offset,
const int stride)
{
kernel_assert(kernel_data.film.pass_volume_scatter != PASS_UNUSED);
const int radius = 5;
const int filter_width = radius * 2 + 1;
const float gaussian_params[filter_width] = {0.0012273699895602f,
0.0084674212370284f,
0.0379843612914121f,
0.1108921888487800f,
0.2108379677336155f,
0.2611813817992076f,
0.2108379677336155f,
0.1108921888487800f,
0.0379843612914121f,
0.0084674212370284f,
0.0012273699895602f};
/* Store neighboring values to avoid overwriting. */
float3 scatter_neighbors[filter_width], transmit_neighbors[filter_width];
/* Initialize neighbors. */
for (int i = 0; i < filter_width; i++) {
const int y = min_y + i;
if (i >= radius || y < min_y || y >= max_y) {
/* Out-of-boundary neighbors are initialized with zero. */
scatter_neighbors[i] = transmit_neighbors[i] = zero_float3();
}
else {
ccl_global float *buffer = film_pass_pixel_render_buffer(
kg, x, y, offset, stride, render_buffer);
scatter_neighbors[i] = kernel_read_pass_rgbe(buffer +
kernel_data.film.pass_volume_scatter_denoised);
transmit_neighbors[i] = kernel_read_pass_rgbe(
buffer + kernel_data.film.pass_volume_transmit_denoised);
}
}
/* Apply Gaussian filter in y direction. */
int index = radius;
for (int y = min_y; y < max_y; y++) {
/* Fetch the furthest neighbor to the right. */
const int next_y = y + radius;
if (next_y < min_y || next_y >= max_y) {
scatter_neighbors[index] = zero_float3();
transmit_neighbors[index] = zero_float3();
}
else {
ccl_global float *buffer = film_pass_pixel_render_buffer(
kg, x, next_y, offset, stride, render_buffer);
scatter_neighbors[index] = kernel_read_pass_rgbe(
buffer + kernel_data.film.pass_volume_scatter_denoised);
transmit_neighbors[index] = kernel_read_pass_rgbe(
buffer + kernel_data.film.pass_volume_transmit_denoised);
}
/* Slide the kernel to the right. */
index = (index + 1) % filter_width;
/* Apply convolution. */
float3 scatter = zero_float3(), transmit = zero_float3();
for (int i = 0; i < filter_width; i++) {
scatter += gaussian_params[i] * scatter_neighbors[(index + i) % filter_width];
transmit += gaussian_params[i] * transmit_neighbors[(index + i) % filter_width];
}
/* Write to the buffers. */
ccl_global float *buffer = film_pass_pixel_render_buffer(
kg, x, y, offset, stride, render_buffer);
film_overwrite_pass_rgbe(buffer + kernel_data.film.pass_volume_scatter_denoised,
fabs(scatter));
film_overwrite_pass_rgbe(buffer + kernel_data.film.pass_volume_transmit_denoised,
fabs(transmit));
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,158 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/integrator/state.h"
#include "kernel/util/colorspace.h"
#include "util/types_rgbe.h"
#ifdef __KERNEL_GPU__
# include "util/atomic.h"
# define __ATOMIC_PASS_WRITE__
#endif
CCL_NAMESPACE_BEGIN
/* Get pointer to pixel in render buffer. */
ccl_device_forceinline ccl_global float *film_pass_pixel_render_buffer(
KernelGlobals kg, ConstIntegratorState state, ccl_global float *ccl_restrict render_buffer)
{
const uint32_t render_pixel_index = INTEGRATOR_STATE(state, path, render_pixel_index);
const uint64_t render_buffer_offset = (uint64_t)render_pixel_index *
kernel_data.film.pass_stride;
return render_buffer + render_buffer_offset;
}
ccl_device_forceinline ccl_global float *film_pass_pixel_render_buffer_shadow(
KernelGlobals kg,
ConstIntegratorShadowState state,
ccl_global float *ccl_restrict render_buffer)
{
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;
return render_buffer + render_buffer_offset;
}
ccl_device_forceinline ccl_global float *film_pass_pixel_render_buffer(
KernelGlobals kg,
const int x,
const int y,
const int offset,
const int stride,
ccl_global float *ccl_restrict render_buffer)
{
const int render_pixel_index = offset + x + y * stride;
return render_buffer + (uint64_t)render_pixel_index * kernel_data.film.pass_stride;
}
/* Accumulate in passes. */
ccl_device_inline void film_write_pass_float(ccl_global float *ccl_restrict buffer,
const float value)
{
#ifdef __ATOMIC_PASS_WRITE__
atomic_add_and_fetch_float(buffer, value);
#else
*buffer += value;
#endif
}
ccl_device_inline void film_write_pass_float3(ccl_global float *ccl_restrict buffer,
const float3 value)
{
#ifdef __ATOMIC_PASS_WRITE__
ccl_global float *buf_x = buffer + 0;
ccl_global float *buf_y = buffer + 1;
ccl_global float *buf_z = buffer + 2;
atomic_add_and_fetch_float(buf_x, value.x);
atomic_add_and_fetch_float(buf_y, value.y);
atomic_add_and_fetch_float(buf_z, value.z);
#else
buffer[0] += value.x;
buffer[1] += value.y;
buffer[2] += value.z;
#endif
}
ccl_device_inline void film_write_pass_spectrum(ccl_global float *ccl_restrict buffer,
Spectrum value)
{
film_write_pass_float3(buffer, spectrum_to_rgb(value));
}
ccl_device_inline void film_write_pass_float4(ccl_global float *ccl_restrict buffer,
const float4 value)
{
#ifdef __ATOMIC_PASS_WRITE__
ccl_global float *buf_x = buffer + 0;
ccl_global float *buf_y = buffer + 1;
ccl_global float *buf_z = buffer + 2;
ccl_global float *buf_w = buffer + 3;
atomic_add_and_fetch_float(buf_x, value.x);
atomic_add_and_fetch_float(buf_y, value.y);
atomic_add_and_fetch_float(buf_z, value.z);
atomic_add_and_fetch_float(buf_w, value.w);
#else
buffer[0] += value.x;
buffer[1] += value.y;
buffer[2] += value.z;
buffer[3] += value.w;
#endif
}
ccl_device_inline void film_overwrite_pass_rgbe(ccl_global float *ccl_restrict buffer,
const float3 value)
{
*buffer = rgb_to_rgbe(value).f;
}
/* Overwrite for passes that only write on sample 0. This assumes only a single thread will write
* to this pixel and no atomics are needed. */
ccl_device_inline void film_overwrite_pass_float(ccl_global float *ccl_restrict buffer,
const float value)
{
*buffer = value;
}
ccl_device_inline void film_overwrite_pass_float3(ccl_global float *ccl_restrict buffer,
const float3 value)
{
buffer[0] = value.x;
buffer[1] = value.y;
buffer[2] = value.z;
}
/* Read back from passes. */
ccl_device_inline float kernel_read_pass_float(const ccl_global float *ccl_restrict buffer)
{
return *buffer;
}
ccl_device_inline float3 kernel_read_pass_float3(const ccl_global float *ccl_restrict buffer)
{
return make_float3(buffer[0], buffer[1], buffer[2]);
}
ccl_device_inline float4 kernel_read_pass_float4(ccl_global float *ccl_restrict buffer)
{
return make_float4(buffer[0], buffer[1], buffer[2], buffer[3]);
}
ccl_device_inline float3 kernel_read_pass_rgbe(const ccl_global float *ccl_restrict buffer)
{
return rgbe_to_rgb(RGBE(*buffer));
}
CCL_NAMESPACE_END