Add Chromium-only Blender WebEngine parity work
This commit is contained in:
144
blender-5.2.0/source/blender/compositor/shaders/CMakeLists.txt
Normal file
144
blender-5.2.0/source/blender/compositor/shaders/CMakeLists.txt
Normal file
@@ -0,0 +1,144 @@
|
||||
# SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC_GLSL
|
||||
.
|
||||
..
|
||||
./library
|
||||
../../gpu
|
||||
../../gpu/intern
|
||||
../../gpu/shaders
|
||||
../../gpu/shaders/common
|
||||
../../gpu/shaders/infos
|
||||
)
|
||||
|
||||
set(SRC_GLSL_COMP
|
||||
compositor_alpha_crop.glsl
|
||||
compositor_bilateral_blur.glsl
|
||||
compositor_bokeh_blur_variable_size.glsl
|
||||
compositor_bokeh_blur.glsl
|
||||
compositor_bokeh_image.glsl
|
||||
compositor_box_mask.glsl
|
||||
compositor_compute_preview.glsl
|
||||
compositor_convert.glsl
|
||||
compositor_cryptomatte_image.glsl
|
||||
compositor_cryptomatte_matte.glsl
|
||||
compositor_cryptomatte_pick.glsl
|
||||
compositor_defocus_blur.glsl
|
||||
compositor_defocus_radius_from_depth.glsl
|
||||
compositor_defocus_radius_from_scale.glsl
|
||||
compositor_deriche_gaussian_blur_sum.glsl
|
||||
compositor_deriche_gaussian_blur.glsl
|
||||
compositor_despeckle.glsl
|
||||
compositor_directional_blur.glsl
|
||||
compositor_displace_anisotropic.glsl
|
||||
compositor_displace.glsl
|
||||
compositor_double_edge_mask_compute_boundary.glsl
|
||||
compositor_double_edge_mask_compute_gradient.glsl
|
||||
compositor_edge_filter.glsl
|
||||
compositor_ellipse_mask.glsl
|
||||
compositor_filter.glsl
|
||||
compositor_flip.glsl
|
||||
compositor_gamma_correct.glsl
|
||||
compositor_glare_bloom_downsample.glsl
|
||||
compositor_glare_bloom_upsample.glsl
|
||||
compositor_glare_ghost_accumulate.glsl
|
||||
compositor_glare_ghost_base.glsl
|
||||
compositor_glare_highlights.glsl
|
||||
compositor_glare_kernel_downsample.glsl
|
||||
compositor_glare_mix.glsl
|
||||
compositor_glare_simple_star_anti_diagonal_pass.glsl
|
||||
compositor_glare_simple_star_diagonal_pass.glsl
|
||||
compositor_glare_simple_star_horizontal_pass.glsl
|
||||
compositor_glare_simple_star_vertical_pass.glsl
|
||||
compositor_glare_streaks_accumulate.glsl
|
||||
compositor_glare_streaks_filter.glsl
|
||||
compositor_glare_sun_beams.glsl
|
||||
compositor_glare_write_glare_output.glsl
|
||||
compositor_glare_write_highlights_output.glsl
|
||||
compositor_horizontal_lens_distortion.glsl
|
||||
compositor_id_mask.glsl
|
||||
compositor_image_coordinates_normalized.glsl
|
||||
compositor_image_coordinates_pixel.glsl
|
||||
compositor_image_coordinates_uniform.glsl
|
||||
compositor_image_crop.glsl
|
||||
compositor_inpaint_compute_boundary.glsl
|
||||
compositor_inpaint_compute_region.glsl
|
||||
compositor_inpaint_fill_region.glsl
|
||||
compositor_jump_flooding.glsl
|
||||
compositor_keying_compute_image.glsl
|
||||
compositor_keying_compute_matte.glsl
|
||||
compositor_keying_extract_chroma.glsl
|
||||
compositor_keying_replace_chroma.glsl
|
||||
compositor_keying_screen.glsl
|
||||
compositor_keying_tweak_matte.glsl
|
||||
compositor_kuwahara_anisotropic_compute_structure_tensor.glsl
|
||||
compositor_kuwahara_anisotropic.glsl
|
||||
compositor_kuwahara_classic.glsl
|
||||
compositor_map_uv_anisotropic.glsl
|
||||
compositor_map_uv.glsl
|
||||
compositor_mask_to_sdf_compute_boundary.glsl
|
||||
compositor_mask_to_sdf_compute_distance.glsl
|
||||
compositor_morphological_blur.glsl
|
||||
compositor_morphological_distance_feather.glsl
|
||||
compositor_morphological_distance_threshold.glsl
|
||||
compositor_morphological_distance_threshold_seeds.glsl
|
||||
compositor_morphological_distance.glsl
|
||||
compositor_morphological_step.glsl
|
||||
compositor_motion_blur_max_velocity_dilate.glsl
|
||||
compositor_motion_blur.glsl
|
||||
compositor_movie_distortion.glsl
|
||||
compositor_normalize.glsl
|
||||
compositor_pad.glsl
|
||||
compositor_parallel_reduction.glsl
|
||||
compositor_pixelate.glsl
|
||||
compositor_plane_deform_anisotropic.glsl
|
||||
compositor_plane_deform_mask.glsl
|
||||
compositor_plane_deform_motion_blur_mask.glsl
|
||||
compositor_plane_deform_motion_blur.glsl
|
||||
compositor_plane_deform.glsl
|
||||
compositor_premultiply_alpha.glsl
|
||||
compositor_radial_lens_distortion.glsl
|
||||
compositor_realize_on_domain.glsl
|
||||
compositor_relative_to_pixel_float.glsl
|
||||
compositor_relative_to_pixel_float_per_dimension.glsl
|
||||
compositor_relative_to_pixel_vector.glsl
|
||||
compositor_sample_pixel.glsl
|
||||
compositor_scale_variable.glsl
|
||||
compositor_smaa_blending_weight_calculation.glsl
|
||||
compositor_smaa_edge_detection.glsl
|
||||
compositor_smaa_neighborhood_blending.glsl
|
||||
compositor_split.glsl
|
||||
compositor_summed_area_table_compute_complete_blocks.glsl
|
||||
compositor_summed_area_table_compute_complete_x_prologues.glsl
|
||||
compositor_summed_area_table_compute_complete_y_prologues.glsl
|
||||
compositor_summed_area_table_compute_incomplete_prologues.glsl
|
||||
compositor_symmetric_blur_variable_size.glsl
|
||||
compositor_symmetric_blur.glsl
|
||||
compositor_symmetric_separable_blur_variable_size.glsl
|
||||
compositor_symmetric_separable_blur.glsl
|
||||
compositor_tone_map_photoreceptor.glsl
|
||||
compositor_tone_map_simple.glsl
|
||||
compositor_translate_wrapped.glsl
|
||||
compositor_van_vliet_gaussian_blur_sum.glsl
|
||||
compositor_van_vliet_gaussian_blur.glsl
|
||||
compositor_write_output.glsl
|
||||
compositor_z_combine_compute_mask.glsl
|
||||
compositor_z_combine_from_mask_depth.glsl
|
||||
compositor_z_combine_from_mask_image.glsl
|
||||
compositor_z_combine_simple_depth.glsl
|
||||
compositor_z_combine_simple_image.glsl
|
||||
)
|
||||
|
||||
set(SRC_GLSL_LIB
|
||||
library/gpu_shader_compositor_texture_utilities.glsl
|
||||
)
|
||||
|
||||
# Compile shaders with shader code.
|
||||
if(WITH_GPU_SHADER_CPP_COMPILATION)
|
||||
compile_sources_as_cpp(compositor_cpp_shaders_comp "${SRC_GLSL_COMP}" "GPU_COMPUTE_SHADER")
|
||||
# Only enable to make sure they compile on their own.
|
||||
# Otherwise it creates a warning about `pragma once`.
|
||||
# compile_sources_as_cpp(compositor_cpp_shaders_lib "${SRC_GLSL_LIB}" "GPU_LIBRARY_SHADER")
|
||||
endif()
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_alpha_crop_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_alpha_crop)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
/* The lower bound is inclusive and upper bound is exclusive. */
|
||||
bool is_inside = all(greaterThanEqual(texel, lower_bound)) && all(lessThan(texel, upper_bound));
|
||||
/* Write the pixel color if it is inside the cropping region, otherwise, write zero. */
|
||||
float4 color = is_inside ? texture_load(input_tx, texel) : float4(0.0f);
|
||||
imageStore(output_img, texel, color);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_bilateral_blur_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_bilateral_blur)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 center_determinator = texture_load(determinator_tx, texel);
|
||||
|
||||
/* Go over the pixels in the blur window of the specified radius around the center pixel, and for
|
||||
* pixels whose determinator is close enough to the determinator of the center pixel, accumulate
|
||||
* their color as well as their weights. */
|
||||
float accumulated_weight = 0.0f;
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
for (int y = -radius; y <= radius; y++) {
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
float4 determinator = texture_load(determinator_tx, texel + int2(x, y));
|
||||
float difference = dot(abs(center_determinator - determinator).rgb, float3(1.0f)) / 3.0f;
|
||||
|
||||
if (difference < threshold) {
|
||||
accumulated_weight += 1.0f;
|
||||
accumulated_color += texture_load(input_tx, texel + int2(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the accumulated color divided by the accumulated weight if any pixel in the window was
|
||||
* accumulated, otherwise, write a fallback black color. */
|
||||
float4 fallback = float4(float3(0.0f), 1.0f);
|
||||
float4 color = (accumulated_weight != 0.0f) ? (accumulated_color / accumulated_weight) :
|
||||
fallback;
|
||||
imageStore(output_img, texel, color);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_bokeh_blur_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_bokeh_blur)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_vector_safe_lib.glsl"
|
||||
|
||||
/* Given the texel in the range [-radius, radius] in both axis, load the appropriate weight from
|
||||
* the weights texture, where the given texel (0, 0) corresponds the center of weights texture.
|
||||
* Note that we load the weights texture inverted along both directions to maintain the shape of
|
||||
* the weights if it was not symmetrical. To understand why inversion makes sense, consider a 1D
|
||||
* weights texture whose right half is all ones and whose left half is all zeros. Further, consider
|
||||
* that we are blurring a single white pixel on a black background. When computing the value of a
|
||||
* pixel that is to the right of the white pixel, the white pixel will be in the left region of the
|
||||
* search window, and consequently, without inversion, a zero will be sampled from the left side of
|
||||
* the weights texture and result will be zero. However, what we expect is that pixels to the right
|
||||
* of the white pixel will be white, that is, they should sample a weight of 1 from the right side
|
||||
* of the weights texture, hence the need for inversion. */
|
||||
float4 load_weight(int2 texel)
|
||||
{
|
||||
/* Add the radius to transform the texel into the range [0, radius * 2], with an additional 0.5f
|
||||
* to sample at the center of the pixels, then divide by the upper bound plus one to transform
|
||||
* the texel into the normalized range [0, 1] needed to sample the weights sampler. Finally,
|
||||
* invert the textures coordinates by subtracting from 1 to maintain the shape of the weights as
|
||||
* mentioned in the function description. */
|
||||
return texture(weights_tx,
|
||||
1.0f - ((float2(texel) + float2(radius + 0.5f)) / (radius * 2.0f + 1.0f)));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* The mask input is treated as a boolean. If it is zero, then no blurring happens for this
|
||||
* pixel. Otherwise, the pixel is blurred normally and the mask value is irrelevant. */
|
||||
float mask = texture_load(mask_tx, texel).x;
|
||||
if (mask == 0.0f) {
|
||||
imageStore(output_img, texel, texture_load(input_tx, texel));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Go over the window of the given radius and accumulate the colors multiplied by their
|
||||
* respective weights as well as the weights themselves. */
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
float4 accumulated_weight = float4(0.0f);
|
||||
for (int y = -radius; y <= radius; y++) {
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
float4 weight = load_weight(int2(x, y));
|
||||
accumulated_color += texture_load(input_tx, texel + int2(x, y)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, safe_divide(accumulated_color, accumulated_weight));
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_bokeh_blur_variable_size_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_bokeh_blur_variable_size)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_vector_lib.glsl"
|
||||
#include "gpu_shader_math_vector_safe_lib.glsl"
|
||||
|
||||
/* Given the texel in the range [-radius, radius] in both axis, load the appropriate weight from
|
||||
* the weights texture, where the given texel (0, 0) corresponds the center of weights texture.
|
||||
* Note that we load the weights texture inverted along both directions to maintain the shape of
|
||||
* the weights if it was not symmetrical. To understand why inversion makes sense, consider a 1D
|
||||
* weights texture whose right half is all ones and whose left half is all zeros. Further, consider
|
||||
* that we are blurring a single white pixel on a black background. When computing the value of a
|
||||
* pixel that is to the right of the white pixel, the white pixel will be in the left region of the
|
||||
* search window, and consequently, without inversion, a zero will be sampled from the left side of
|
||||
* the weights texture and result will be zero. However, what we expect is that pixels to the right
|
||||
* of the white pixel will be white, that is, they should sample a weight of 1 from the right side
|
||||
* of the weights texture, hence the need for inversion. */
|
||||
float4 load_weight(int2 texel, float radius)
|
||||
{
|
||||
/* The center zero texel is always assigned a unit weight regardless of the corresponding weight
|
||||
* in the weights texture. That's to guarantee that at last the center pixel will be accumulated
|
||||
* even if the weights texture is zero at its center. */
|
||||
if (texel.x == 0 && texel.y == 0) {
|
||||
return float4(1.0f);
|
||||
}
|
||||
|
||||
/* Add the radius to transform the texel into the range [0, radius * 2], with an additional 0.5f
|
||||
* to sample at the center of the pixels, then divide by the upper bound plus one to transform
|
||||
* the texel into the normalized range [0, 1] needed to sample the weights sampler. Finally,
|
||||
* invert the textures coordinates by subtracting from 1 to maintain the shape of the weights as
|
||||
* mentioned in the function description. */
|
||||
return texture(weights_tx,
|
||||
1.0f - ((float2(texel) + float2(radius + 0.5f)) / (radius * 2.0f + 1.0f)));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* The mask input is treated as a boolean. If it is zero, then no blurring happens for this
|
||||
* pixel. Otherwise, the pixel is blurred normally and the mask value is irrelevant. */
|
||||
float mask = texture_load(mask_tx, texel).x;
|
||||
if (mask == 0.0f) {
|
||||
imageStore(output_img, texel, texture_load(input_tx, texel));
|
||||
return;
|
||||
}
|
||||
|
||||
float center_size = max(0.0f, texture_load(size_tx, texel).x);
|
||||
|
||||
/* Go over the window of the given search radius and accumulate the colors multiplied by their
|
||||
* respective weights as well as the weights themselves, but only if both the size of the center
|
||||
* pixel and the size of the candidate pixel are less than both the x and y distances of the
|
||||
* candidate pixel. */
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
float4 accumulated_weight = float4(0.0f);
|
||||
for (int y = -search_radius; y <= search_radius; y++) {
|
||||
for (int x = -search_radius; x <= search_radius; x++) {
|
||||
float candidate_size = max(0.0f, texture_load(size_tx, texel + int2(x, y)).x);
|
||||
|
||||
/* Skip accumulation if either the x or y distances of the candidate pixel are larger than
|
||||
* either the center or candidate pixel size. Note that the max and min functions here denote
|
||||
* "either" in the aforementioned description. */
|
||||
float size = min(center_size, candidate_size);
|
||||
if (max(abs(x), abs(y)) > size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float4 weight = load_weight(int2(x, y), size);
|
||||
accumulated_color += texture_load(input_tx, texel + int2(x, y)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, safe_divide(accumulated_color, accumulated_weight));
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_bokeh_image_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_bokeh_image)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
#include "gpu_shader_math_constants_lib.glsl"
|
||||
|
||||
/* Get the 2D vertex position of the vertex with the given index in the regular polygon
|
||||
* representing this bokeh. The polygon is rotated by the rotation amount and have a unit
|
||||
* circumradius. The regular polygon is one whose vertices' exterior angles are given by
|
||||
* exterior_angle. See the bokeh function for more information. */
|
||||
float2 get_regular_polygon_vertex_position(int vertex_index)
|
||||
{
|
||||
float angle = exterior_angle * vertex_index - rotation;
|
||||
return float2(cos(angle), sin(angle));
|
||||
}
|
||||
|
||||
/* Find the closest point to the given point on the given line. This assumes the length of the
|
||||
* given line is not zero. */
|
||||
float2 closest_point_on_line(float2 point, float2 line_start, float2 line_end)
|
||||
{
|
||||
float2 line_vector = line_end - line_start;
|
||||
float2 point_vector = point - line_start;
|
||||
float line_length_squared = dot(line_vector, line_vector);
|
||||
float parameter = dot(point_vector, line_vector) / line_length_squared;
|
||||
return line_start + line_vector * parameter;
|
||||
}
|
||||
|
||||
/* Compute the value of the bokeh at the given point. The computed bokeh is essentially a regular
|
||||
* polygon centered in space having the given circumradius. The regular polygon is one whose
|
||||
* vertices' exterior angles are given by "exterior_angle", which relates to the number of vertices
|
||||
* n through the equation "exterior angle = 2 pi / n". The regular polygon may additionally morph
|
||||
* into a shape with the given properties:
|
||||
*
|
||||
* - The regular polygon may have a circular hole in its center whose radius is controlled by the
|
||||
* "catadioptric" value.
|
||||
* - The regular polygon is rotated by the "rotation" value.
|
||||
* - The regular polygon can morph into a circle controlled by the "roundness" value, such that it
|
||||
* becomes a full circle at unit roundness.
|
||||
*
|
||||
* The function returns 0 when the point lies inside the regular polygon and 1 otherwise. However,
|
||||
* at the edges, it returns a narrow band gradient as a form of anti-aliasing. */
|
||||
float bokeh(float2 point, float circumradius)
|
||||
{
|
||||
if (circumradius == 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* Get the index of the vertex of the regular polygon whose polar angle is maximum but less than
|
||||
* the polar angle of the given point, taking rotation into account. This essentially finds the
|
||||
* vertex closest to the given point in the clock-wise direction. */
|
||||
float angle = mod(atan(point.y, point.x) + rotation, 2.0f * M_PI);
|
||||
int vertex_index = int(angle / exterior_angle);
|
||||
|
||||
/* Compute the shortest distance between the origin and the polygon edge composed from the
|
||||
* previously selected vertex and the one following it. */
|
||||
float2 first_vertex = get_regular_polygon_vertex_position(vertex_index) * circumradius;
|
||||
float2 second_vertex = get_regular_polygon_vertex_position(vertex_index + 1) * circumradius;
|
||||
float2 closest_point = closest_point_on_line(point, first_vertex, second_vertex);
|
||||
float distance_to_edge = length(closest_point);
|
||||
|
||||
/* Mix the distance to the edge with the circumradius, making it tend to the distance to a
|
||||
* circle when roundness tends to 1. */
|
||||
float distance_to_edge_round = mix(distance_to_edge, circumradius, roundness);
|
||||
|
||||
/* The point is outside of the bokeh, so we return 0. */
|
||||
float distance = length(point);
|
||||
if (distance > distance_to_edge_round) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* The point is inside the catadioptric hole and is not part of the bokeh, so we return 0. */
|
||||
float catadioptric_distance = distance_to_edge_round * catadioptric;
|
||||
if (distance < catadioptric_distance) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* The point is very close to the edge of the bokeh, so we return the difference between the
|
||||
* distance to the edge and the distance as a form of anti-aliasing. */
|
||||
if (distance_to_edge_round - distance < 1.0f) {
|
||||
return distance_to_edge_round - distance;
|
||||
}
|
||||
|
||||
/* The point is very close to the edge of the catadioptric hole, so we return the difference
|
||||
* between the distance to the hole and the distance as a form of anti-aliasing. */
|
||||
if (catadioptric != 0.0f && distance - catadioptric_distance < 1.0f) {
|
||||
return distance - catadioptric_distance;
|
||||
}
|
||||
|
||||
/* Otherwise, the point is part of the bokeh and we return 1. */
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Since we need the regular polygon to occupy the entirety of the output image, the circumradius
|
||||
* of the regular polygon is half the width of the output image. */
|
||||
float circumradius = float(imageSize(output_img).x) / 2.0f;
|
||||
|
||||
/* Move the texel coordinates such that the regular polygon is centered. */
|
||||
float2 point = float2(texel) + float2(0.5f) - circumradius;
|
||||
|
||||
/* Each of the color channels of the output image contains a bokeh with a different circumradius.
|
||||
* The largest one occupies the whole image as stated above, while the other two have circumradii
|
||||
* that are shifted by an amount that is proportional to the "lens_shift" value. The alpha
|
||||
* channel of the output is the average of all three values. */
|
||||
float min_shift = abs(lens_shift * circumradius);
|
||||
float min = bokeh(point, circumradius - min_shift);
|
||||
|
||||
float median_shift = min_shift / 2.0f;
|
||||
float median = bokeh(point, circumradius - median_shift);
|
||||
|
||||
float max = bokeh(point, circumradius);
|
||||
float4 bokeh = float4(min, median, max, (max + median + min) / 3.0f);
|
||||
|
||||
/* If the lens shift is negative, swap the min and max bokeh values, which are stored in the red
|
||||
* and blue channels respectively. Note that we take the absolute value of the lens shift above,
|
||||
* so the sign of the lens shift only controls this swap. */
|
||||
if (lens_shift < 0.0f) {
|
||||
bokeh = bokeh.zyxw;
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, bokeh);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_box_mask_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_box_mask_add)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
/* TODO(fclem): deduplicate. */
|
||||
#define CMP_NODE_MASKTYPE_ADD 0
|
||||
#define CMP_NODE_MASKTYPE_SUBTRACT 1
|
||||
#define CMP_NODE_MASKTYPE_MULTIPLY 2
|
||||
#define CMP_NODE_MASKTYPE_NOT 3
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float2 uv = float2(texel + data_offset) / float2(display_size - int2(1));
|
||||
uv -= location;
|
||||
uv.y *= float(display_size.y) / float(display_size.x);
|
||||
uv = float2x2(cos_angle, -sin_angle, sin_angle, cos_angle) * uv;
|
||||
bool is_inside = all(lessThan(abs(uv), size));
|
||||
|
||||
float base_mask_value = texture_load(base_mask_tx, texel).x;
|
||||
float value = texture_load(mask_value_tx, texel).x;
|
||||
|
||||
float output_mask_value;
|
||||
if (node_type == CMP_NODE_MASKTYPE_ADD) {
|
||||
output_mask_value = is_inside ? max(base_mask_value, value) : base_mask_value;
|
||||
}
|
||||
else if (node_type == CMP_NODE_MASKTYPE_SUBTRACT) {
|
||||
output_mask_value = is_inside ? clamp(base_mask_value - value, 0.0f, 1.0f) : base_mask_value;
|
||||
}
|
||||
else if (node_type == CMP_NODE_MASKTYPE_MULTIPLY) {
|
||||
output_mask_value = is_inside ? base_mask_value * value : 0.0f;
|
||||
}
|
||||
else if (node_type == CMP_NODE_MASKTYPE_NOT) {
|
||||
output_mask_value = is_inside ? (base_mask_value > 0.0f ? 0.0f : value) : base_mask_value;
|
||||
}
|
||||
|
||||
imageStore(output_mask_img, texel, float4(output_mask_value));
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_compute_preview_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_compute_preview)
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(imageSize(preview_img));
|
||||
imageStore(preview_img, texel, texture(input_tx, coordinates));
|
||||
}
|
||||
@@ -0,0 +1,800 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_convert_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_convert_float2_to_color)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_compositor_type_conversion.glsl"
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Float to other.
|
||||
*/
|
||||
|
||||
void convert_float_to_int()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_int, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_int, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float_to_int(value.x), int3(0)));
|
||||
}
|
||||
|
||||
void convert_float_to_int2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_int2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_int2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float_to_int2(value.x), int2(0)));
|
||||
}
|
||||
|
||||
void convert_float_to_int3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_int3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_int3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float_to_int3(value.x), 0));
|
||||
}
|
||||
|
||||
void convert_float_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_float2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_float2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float_to_float2(value.x), float2(0.0f)));
|
||||
}
|
||||
|
||||
void convert_float_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_float3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_float3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float_to_float3(value.x), 0.0f));
|
||||
}
|
||||
|
||||
void convert_float_to_color()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_color, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_color, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float_to_color(value.x)));
|
||||
}
|
||||
|
||||
void convert_float_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_float4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_float4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float_to_float4(value.x)));
|
||||
}
|
||||
|
||||
void convert_float_to_bool()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_bool, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_bool, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float_to_bool(value.x)));
|
||||
}
|
||||
|
||||
void convert_float_to_quaternion()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float_to_quaternion, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float_to_quaternion, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float_to_quaternion(value.x));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Float2 to other.
|
||||
*/
|
||||
|
||||
void convert_float2_to_float()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_float, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_float, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float2_to_float(value.xy), float3(0.0f)));
|
||||
}
|
||||
|
||||
void convert_float2_to_int()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_int, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_int, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float2_to_int(value.xy), int3(0)));
|
||||
}
|
||||
|
||||
void convert_float2_to_int2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_int2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_int2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float2_to_int2(value.xy), int2(0)));
|
||||
}
|
||||
|
||||
void convert_float2_to_int3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_int3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_int3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float2_to_int3(value.xy), 0));
|
||||
}
|
||||
|
||||
void convert_float2_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_float3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_float3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float2_to_float3(value.xy), 0.0f));
|
||||
}
|
||||
|
||||
void convert_float2_to_color()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_color, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_color, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float2_to_color(value.xy)));
|
||||
}
|
||||
|
||||
void convert_float2_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_float4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_float4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float2_to_float4(value.xy)));
|
||||
}
|
||||
|
||||
void convert_float2_to_bool()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_bool, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_bool, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float2_to_bool(value.xy)));
|
||||
}
|
||||
|
||||
void convert_float2_to_quaternion()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float2_to_quaternion, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float2_to_quaternion, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float2_to_quaternion(value.xy));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Float3 to other.
|
||||
*/
|
||||
|
||||
void convert_float3_to_float()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_float, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_float, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float3_to_float(value.xyz), float3(0.0f)));
|
||||
}
|
||||
|
||||
void convert_float3_to_int()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_int, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_int, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float3_to_int(value.xyz), int3(0)));
|
||||
}
|
||||
|
||||
void convert_float3_to_int2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_int2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_int2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float3_to_int2(value.xyz), int2(0)));
|
||||
}
|
||||
|
||||
void convert_float3_to_int3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_int3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_int3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float3_to_int3(value.xyz), 0));
|
||||
}
|
||||
|
||||
void convert_float3_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_float2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_float2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float3_to_float2(value.xyz), float2(0.0f)));
|
||||
}
|
||||
|
||||
void convert_float3_to_color()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_color, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_color, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float3_to_color(value.xyz)));
|
||||
}
|
||||
|
||||
void convert_float3_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_float4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_float4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float3_to_float4(value.xyz)));
|
||||
}
|
||||
|
||||
void convert_float3_to_bool()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_bool, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_bool, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float3_to_bool(value.xyz)));
|
||||
}
|
||||
|
||||
void convert_float3_to_quaternion()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float3_to_quaternion, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float3_to_quaternion, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float3_to_quaternion(value.xyz));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Float4 to other.
|
||||
*/
|
||||
|
||||
void convert_float4_to_float()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_float, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_float, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float4_to_float(value), float3(0.0f)));
|
||||
}
|
||||
|
||||
void convert_float4_to_int()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_int, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_int, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float4_to_int(value), int3(0)));
|
||||
}
|
||||
|
||||
void convert_float4_to_int2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_int2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_int2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float4_to_int2(value), int2(0)));
|
||||
}
|
||||
|
||||
void convert_float4_to_int3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_int3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_int3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float4_to_int3(value), 0));
|
||||
}
|
||||
|
||||
void convert_float4_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_float2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_float2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float4_to_float2(value), float2(0.0f)));
|
||||
}
|
||||
|
||||
void convert_float4_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_float3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_float3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float4_to_float3(value), 0.0f));
|
||||
}
|
||||
|
||||
void convert_float4_to_color()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_color, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_color, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(float4_to_color(value)));
|
||||
}
|
||||
|
||||
void convert_float4_to_bool()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_bool, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_bool, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(float4_to_bool(value)));
|
||||
}
|
||||
|
||||
void convert_float4_to_quaternion()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4_to_quaternion, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4_to_quaternion, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4_to_quaternion(value));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Color to other.
|
||||
*/
|
||||
|
||||
void convert_color_to_float()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_float, input_tx);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
auto &image_out = image_get(compositor_convert_color_to_float, output_img);
|
||||
auto &luma_coefs = push_constant_get(compositor_convert_color_to_float,
|
||||
luminance_coefficients_u);
|
||||
imageStore(image_out, texel, float4(color_to_float(value, luma_coefs)));
|
||||
}
|
||||
|
||||
void convert_color_to_int()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_int, input_tx);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
auto &image_out = image_get(compositor_convert_color_to_int, output_img);
|
||||
auto &luma_coefs = push_constant_get(compositor_convert_color_to_int, luminance_coefficients_u);
|
||||
imageStore(image_out, texel, int4(color_to_int(value, luma_coefs)));
|
||||
}
|
||||
|
||||
void convert_color_to_int2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_int2, input_tx);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
auto &image_out = image_get(compositor_convert_color_to_int2, output_img);
|
||||
imageStore(image_out, texel, int4(color_to_int2(value), int2(0)));
|
||||
}
|
||||
|
||||
void convert_color_to_int3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_int3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_color_to_int3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(color_to_int3(value), 0));
|
||||
}
|
||||
|
||||
void convert_color_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_float2, input_tx);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
auto &image_out = image_get(compositor_convert_color_to_float2, output_img);
|
||||
imageStore(image_out, texel, float4(color_to_float2(value), float2(0.0f)));
|
||||
}
|
||||
|
||||
void convert_color_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_float3, input_tx);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
auto &image_out = image_get(compositor_convert_color_to_float3, output_img);
|
||||
imageStore(image_out, texel, float4(color_to_float3(value), 0.0f));
|
||||
}
|
||||
|
||||
void convert_color_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_float4, input_tx);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
auto &image_out = image_get(compositor_convert_color_to_float4, output_img);
|
||||
imageStore(image_out, texel, float4(color_to_float4(value)));
|
||||
}
|
||||
|
||||
void convert_color_to_bool()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_bool, input_tx);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
auto &image_out = image_get(compositor_convert_color_to_bool, output_img);
|
||||
auto &luma_coefs = push_constant_get(compositor_convert_color_to_bool, luminance_coefficients_u);
|
||||
imageStore(image_out, texel, int4(color_to_bool(value, luma_coefs)));
|
||||
}
|
||||
|
||||
void convert_color_to_alpha()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_color_to_alpha, input_tx);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
auto &image_out = image_get(compositor_convert_color_to_alpha, output_img);
|
||||
imageStore(image_out, texel, float4(value.a));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Int to other.
|
||||
*/
|
||||
|
||||
void convert_int_to_int2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int_to_int2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int_to_int2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int_to_int2(value.x), int2(0)));
|
||||
}
|
||||
|
||||
void convert_int_to_int3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int_to_int3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int_to_int3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int_to_int3(value.x), 0));
|
||||
}
|
||||
|
||||
void convert_int_to_float()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int_to_float, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int_to_float, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int_to_float(value.x), float3(0.0f)));
|
||||
}
|
||||
|
||||
void convert_int_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int_to_float2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int_to_float2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int_to_float2(value.x), float2(0.0f)));
|
||||
}
|
||||
|
||||
void convert_int_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int_to_float3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int_to_float3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int_to_float3(value.x), 0.0f));
|
||||
}
|
||||
|
||||
void convert_int_to_color()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int_to_color, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int_to_color, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int_to_color(value.x)));
|
||||
}
|
||||
|
||||
void convert_int_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int_to_float4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int_to_float4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int_to_float4(value.x)));
|
||||
}
|
||||
|
||||
void convert_int_to_bool()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int_to_bool, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int_to_bool, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int_to_bool(value.x)));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Int2 to other.
|
||||
*/
|
||||
|
||||
void convert_int2_to_int()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int2_to_int, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int2_to_int, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int2_to_int(value.xy), int3(0)));
|
||||
}
|
||||
|
||||
void convert_int2_to_int3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int2_to_int3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int2_to_int3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int2_to_int3(value.xy), 0));
|
||||
}
|
||||
|
||||
void convert_int2_to_float()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int2_to_float, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int2_to_float, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int2_to_float(value.xy), float3(0.0f)));
|
||||
}
|
||||
|
||||
void convert_int2_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int2_to_float2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int2_to_float2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int2_to_float2(value.xy), float2(0.0f)));
|
||||
}
|
||||
|
||||
void convert_int2_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int2_to_float3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int2_to_float3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int2_to_float3(value.xy), 0.0f));
|
||||
}
|
||||
|
||||
void convert_int2_to_color()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int2_to_color, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int2_to_color, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int2_to_color(value.xy)));
|
||||
}
|
||||
|
||||
void convert_int2_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int2_to_float4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int2_to_float4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int2_to_float4(value.xy)));
|
||||
}
|
||||
|
||||
void convert_int2_to_bool()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int2_to_bool, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int2_to_bool, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int2_to_bool(value.xy)));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Int3 to other.
|
||||
*/
|
||||
|
||||
void convert_int3_to_int()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int3_to_int, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int3_to_int, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int3_to_int(value.xyz), int3(0)));
|
||||
}
|
||||
|
||||
void convert_int3_to_int2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int3_to_int2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int3_to_int2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int3_to_int2(value.xyz), int2(0)));
|
||||
}
|
||||
|
||||
void convert_int3_to_float()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int3_to_float, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int3_to_float, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int3_to_float(value.xyz), float3(0.0f)));
|
||||
}
|
||||
|
||||
void convert_int3_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int3_to_float2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int3_to_float2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int3_to_float2(value.xyz), float2(0.0f)));
|
||||
}
|
||||
|
||||
void convert_int3_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int3_to_float3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int3_to_float3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int3_to_float3(value.xyz), 0.0f));
|
||||
}
|
||||
|
||||
void convert_int3_to_color()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int3_to_color, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int3_to_color, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int3_to_color(value.xyz)));
|
||||
}
|
||||
|
||||
void convert_int3_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int3_to_float4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int3_to_float4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(int3_to_float4(value.xyz)));
|
||||
}
|
||||
|
||||
void convert_int3_to_bool()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_int3_to_bool, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_int3_to_bool, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(int3_to_bool(value.xyz)));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Bool to other.
|
||||
*/
|
||||
|
||||
void convert_bool_to_float()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_bool_to_float, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_bool_to_float, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(bool_to_float(bool(value.x)), float3(0.0f)));
|
||||
}
|
||||
|
||||
void convert_bool_to_int()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_bool_to_int, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_bool_to_int, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(bool_to_int(bool(value.x)), int3(0)));
|
||||
}
|
||||
|
||||
void convert_bool_to_int2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_bool_to_int2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_bool_to_int2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(bool_to_int2(bool(value.x)), int2(0)));
|
||||
}
|
||||
|
||||
void convert_bool_to_int3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_bool_to_int3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_bool_to_int3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, int4(bool_to_int3(bool(value.x)), 0));
|
||||
}
|
||||
|
||||
void convert_bool_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_bool_to_float2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_bool_to_float2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(bool_to_float2(bool(value.x)), float2(0.0f)));
|
||||
}
|
||||
|
||||
void convert_bool_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_bool_to_float3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_bool_to_float3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(bool_to_float3(bool(value.x)), 0.0f));
|
||||
}
|
||||
|
||||
void convert_bool_to_color()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_bool_to_color, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_bool_to_color, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(bool_to_color(bool(value.x))));
|
||||
}
|
||||
|
||||
void convert_bool_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_bool_to_float4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_bool_to_float4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(bool_to_float4(bool(value.x))));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Float4x4 to other.
|
||||
*/
|
||||
|
||||
void convert_float4x4_to_quaternion()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_float4x4_to_quaternion, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_float4x4_to_quaternion, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4x4 mat = texture_load_float4x4(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4x4_to_quaternion(mat));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Quaternion to other.
|
||||
*/
|
||||
|
||||
void convert_quaternion_to_float2()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_quaternion_to_float2, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_quaternion_to_float2, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(quaternion_to_float2(value), 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
void convert_quaternion_to_float3()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_quaternion_to_float3, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_quaternion_to_float3, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(quaternion_to_float3(value), 0.0f));
|
||||
}
|
||||
|
||||
void convert_quaternion_to_float4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_quaternion_to_float4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_quaternion_to_float4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
imageStore(image_out, texel, float4(quaternion_to_float4(value)));
|
||||
}
|
||||
|
||||
void convert_quaternion_to_float4x4()
|
||||
{
|
||||
auto &sampler_in = sampler_get(compositor_convert_quaternion_to_float4x4, input_tx);
|
||||
auto &image_out = image_get(compositor_convert_quaternion_to_float4x4, output_img);
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 value = texture_load(sampler_in, texel);
|
||||
float4x4 mat = quaternion_to_float4x4(value);
|
||||
imageStore(image_out, int3(texel, 0), mat[0]);
|
||||
imageStore(image_out, int3(texel, 1), mat[1]);
|
||||
imageStore(image_out, int3(texel, 2), mat[2]);
|
||||
imageStore(image_out, int3(texel, 3), mat[3]);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_cryptomatte_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_cryptomatte_image)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 input_color = texture_load(input_tx, texel);
|
||||
float input_matte = texture_load(matte_tx, texel).x;
|
||||
|
||||
/* Premultiply the alpha to the image. */
|
||||
imageStore(output_img, texel, input_color * float4(input_matte));
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Loops over all identifiers selected by the user, and accumulate the coverage of ranks whose
|
||||
* identifiers match that of the user selected identifiers.
|
||||
*
|
||||
* This is described in section "Matte Extraction: Implementation Details" in the original
|
||||
* Cryptomatte publication:
|
||||
*
|
||||
* Friedman, Jonah, and Andrew C. Jones. "Fully automatic id mattes with support for motion blur
|
||||
* and transparency." ACM SIGGRAPH 2015 Posters. 2015. 1-1.
|
||||
*/
|
||||
|
||||
#include "infos/compositor_cryptomatte_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_cryptomatte_matte)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 layer = texture_load(layer_tx, texel);
|
||||
|
||||
/* Each Cryptomatte layer stores two ranks. */
|
||||
float2 first_rank = layer.xy;
|
||||
float2 second_rank = layer.zw;
|
||||
|
||||
/* Each Cryptomatte rank stores a pair of an identifier and the coverage of the entity identified
|
||||
* by that identifier. */
|
||||
float identifier_of_first_rank = first_rank.x;
|
||||
float coverage_of_first_rank = first_rank.y;
|
||||
float identifier_of_second_rank = second_rank.x;
|
||||
float coverage_of_second_rank = second_rank.y;
|
||||
|
||||
/* Loop over all identifiers selected by the user, if the identifier of either of the ranks match
|
||||
* it, accumulate its coverage. */
|
||||
float total_coverage = 0.0f;
|
||||
for (int i = 0; i < identifiers_count; i++) {
|
||||
float identifier = identifiers[i];
|
||||
if (identifier_of_first_rank == identifier) {
|
||||
total_coverage += coverage_of_first_rank;
|
||||
}
|
||||
if (identifier_of_second_rank == identifier) {
|
||||
total_coverage += coverage_of_second_rank;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add the total coverage to the coverage accumulated by previous layers. */
|
||||
imageStore(matte_img, texel, imageLoad(matte_img, texel) + float4(total_coverage));
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Blender provides a Cryptomatte picker operator (UI_OT_eyedropper_color) that can pick a
|
||||
* Cryptomatte entity from an image. That image is a specially encoded image that the picker
|
||||
* operator can understand. In particular, its red channel is the identifier of the entity in the
|
||||
* first rank, while the green and blue channels are arbitrary [0, 1] compressed versions of the
|
||||
* identifier to make the image more humane-viewable, but they are actually ignored by the picker
|
||||
* operator, as can be seen in functions like eyedropper_color_sample_text_update, where only the
|
||||
* red channel is considered.
|
||||
*
|
||||
* This shader just computes this special image given the first Cryptomatte layer. The output needs
|
||||
* to be in full precision since the identifier is a 32-bit float.
|
||||
*
|
||||
* This is the same concept as the "keyable" image described in section "Matte Extraction:
|
||||
* Implementation Details" in the original Cryptomatte publication:
|
||||
*
|
||||
* Friedman, Jonah, and Andrew C. Jones. "Fully automatic id mattes with support for motion blur
|
||||
* and transparency." ACM SIGGRAPH 2015 Posters. 2015. 1-1.
|
||||
*
|
||||
* Except we put the identifier in the red channel by convention instead of the suggested blue
|
||||
* channel. */
|
||||
|
||||
#include "infos/compositor_cryptomatte_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_cryptomatte_pick)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Each layer stores two ranks, each rank contains a pair, the identifier and the coverage of
|
||||
* the entity identified by the identifier. */
|
||||
float2 first_rank = texture_load(first_layer_tx, texel).xy;
|
||||
float id_of_first_rank = first_rank.x;
|
||||
|
||||
/* There is no logic to this, we just compute arbitrary compressed versions of the identifier in
|
||||
* the [0, 1] range to make the image more human-viewable. */
|
||||
uint hash_value = floatBitsToUint(id_of_first_rank);
|
||||
float green = float(hash_value << 8) / float(0xFFFFFFFFu);
|
||||
float blue = float(hash_value << 16) / float(0xFFFFFFFFu);
|
||||
|
||||
imageStore(output_img, texel, float4(id_of_first_rank, green, blue, 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_defocus_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_defocus_blur)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_vector_lib.glsl"
|
||||
#include "gpu_shader_math_vector_safe_lib.glsl"
|
||||
|
||||
/* Given the texel in the range [-radius, radius] in both axis, load the appropriate weight from
|
||||
* the weights texture, where the given texel (0, 0) corresponds the center of weights texture.
|
||||
* Note that we load the weights texture inverted along both directions to maintain the shape of
|
||||
* the weights if it was not symmetrical. To understand why inversion makes sense, consider a 1D
|
||||
* weights texture whose right half is all ones and whose left half is all zeros. Further, consider
|
||||
* that we are blurring a single white pixel on a black background. When computing the value of a
|
||||
* pixel that is to the right of the white pixel, the white pixel will be in the left region of the
|
||||
* search window, and consequently, without inversion, a zero will be sampled from the left side of
|
||||
* the weights texture and result will be zero. However, what we expect is that pixels to the right
|
||||
* of the white pixel will be white, that is, they should sample a weight of 1 from the right side
|
||||
* of the weights texture, hence the need for inversion. */
|
||||
float4 load_weight(int2 texel, float radius)
|
||||
{
|
||||
/* Add the radius to transform the texel into the range [0, radius * 2], with an additional 0.5f
|
||||
* to sample at the center of the pixels, then divide by the upper bound plus one to transform
|
||||
* the texel into the normalized range [0, 1] needed to sample the weights sampler. Finally,
|
||||
* invert the textures coordinates by subtracting from 1 to maintain the shape of the weights as
|
||||
* mentioned in the function description. */
|
||||
return texture(weights_tx,
|
||||
1.0f - ((float2(texel) + float2(radius + 0.5f)) / (radius * 2.0f + 1.0f)));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float center_radius = max(0.0f, texture_load(radius_tx, texel).x);
|
||||
|
||||
/* Go over the window of the given search radius and accumulate the colors multiplied by their
|
||||
* respective weights as well as the weights themselves, but only if both the radius of the
|
||||
* center pixel and the radius of the candidate pixel are less than both the x and y distances of
|
||||
* the candidate pixel. */
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
float4 accumulated_weight = float4(0.0f);
|
||||
for (int y = -search_radius; y <= search_radius; y++) {
|
||||
for (int x = -search_radius; x <= search_radius; x++) {
|
||||
float candidate_radius = max(0.0f, texture_load(radius_tx, texel + int2(x, y)).x);
|
||||
|
||||
/* Skip accumulation if either the x or y distances of the candidate pixel are larger than
|
||||
* either the center or candidate pixel radius. Note that the max and min functions here
|
||||
* denote "either" in the aforementioned description. */
|
||||
float radius = min(center_radius, candidate_radius);
|
||||
if (max(abs(x), abs(y)) > radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float4 weight = load_weight(int2(x, y), radius);
|
||||
float4 input_color = texture_load(input_tx, texel + int2(x, y));
|
||||
|
||||
accumulated_color += input_color * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
accumulated_color = safe_divide(accumulated_color, accumulated_weight);
|
||||
|
||||
imageStore(output_img, texel, accumulated_color);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Given a depth texture, compute the radius of the circle of confusion in pixels based on equation
|
||||
* (8) of the paper:
|
||||
*
|
||||
* Potmesil, Michael, and Indranil Chakravarty. "A lens and aperture camera model for synthetic
|
||||
* image generation." ACM SIGGRAPH Computer Graphics 15.3 (1981): 297-305. */
|
||||
|
||||
#include "infos/compositor_defocus_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_defocus_radius_from_depth)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float depth = texture_load(depth_tx, texel).x;
|
||||
|
||||
/* Compute `Vu` in equation (7). */
|
||||
const float distance_to_image_of_object = (focal_length * depth) / (depth - focal_length);
|
||||
|
||||
/* Compute C in equation (8). Notice that the last multiplier was included in the absolute since
|
||||
* it is negative when the object distance is less than the focal length, as noted in equation
|
||||
* (7). */
|
||||
float diameter = abs((distance_to_image_of_object - distance_to_image_of_focus) *
|
||||
(focal_length / (f_stop * distance_to_image_of_object)));
|
||||
|
||||
/* The diameter is in meters, so multiply by the pixels per meter. */
|
||||
float radius = (diameter / 2.0f) * pixels_per_meter;
|
||||
|
||||
imageStore(radius_img, texel, float4(min(max_radius, radius)));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_defocus_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_defocus_radius_from_scale)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float radius = texture_load(radius_tx, texel).x;
|
||||
imageStore(radius_img, texel, float4(clamp(radius * scale, 0.0f, max_radius)));
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Blur the input horizontally by applying a fourth order IIR filter approximating a Gaussian
|
||||
* filter using Deriche's design method. This is based on the following paper:
|
||||
*
|
||||
* Deriche, Rachid. Recursively implementating the Gaussian and its derivatives. Diss. INRIA,
|
||||
* 1993.
|
||||
*
|
||||
* We run two filters per row in parallel, one for the causal filter and one for the non causal
|
||||
* filter, storing the result of each separately. See the DericheGaussianCoefficients class and the
|
||||
* implementation for more information. */
|
||||
|
||||
#include "infos/compositor_deriche_gaussian_blur_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_deriche_gaussian_blur)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
#define FILTER_ORDER 4
|
||||
|
||||
void main()
|
||||
{
|
||||
/* The shader runs parallel across rows but serially across columns. */
|
||||
int y = int(gl_GlobalInvocationID.x);
|
||||
int width = texture_size(input_tx).x;
|
||||
|
||||
/* The second dispatch dimension is two dispatches, one for the causal filter and one for the non
|
||||
* causal one. */
|
||||
bool is_causal = gl_GlobalInvocationID.y == 0;
|
||||
float4 feedforward_coefficients = is_causal ? causal_feedforward_coefficients :
|
||||
non_causal_feedforward_coefficients;
|
||||
float boundary_coefficient = is_causal ? causal_boundary_coefficient :
|
||||
non_causal_boundary_coefficient;
|
||||
|
||||
/* Create an array that holds the last FILTER_ORDER inputs along with the current input. The
|
||||
* current input is at index 0 and the oldest input is at index FILTER_ORDER. We assume Neumann
|
||||
* boundary condition, so we initialize all inputs by the boundary pixel. */
|
||||
int2 boundary_texel = is_causal ? int2(0, y) : int2(width - 1, y);
|
||||
float4 input_boundary = texture_load(input_tx, boundary_texel);
|
||||
float4 inputs[FILTER_ORDER + 1] = float4_array(
|
||||
input_boundary, input_boundary, input_boundary, input_boundary, input_boundary);
|
||||
|
||||
/* Create an array that holds the last FILTER_ORDER outputs along with the current output. The
|
||||
* current output is at index 0 and the oldest output is at index FILTER_ORDER. We assume Neumann
|
||||
* boundary condition, so we initialize all outputs by the boundary pixel multiplied by the
|
||||
* boundary coefficient. See the DericheGaussianCoefficients class for more information on the
|
||||
* boundary handing. */
|
||||
float4 output_boundary = input_boundary * boundary_coefficient;
|
||||
float4 outputs[FILTER_ORDER + 1] = float4_array(
|
||||
output_boundary, output_boundary, output_boundary, output_boundary, output_boundary);
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
/* Run forward across rows for the causal filter and backward for the non causal filter. */
|
||||
int2 texel = is_causal ? int2(x, y) : int2(width - 1 - x, y);
|
||||
inputs[0] = texture_load(input_tx, texel);
|
||||
|
||||
/* Compute Equation (28) for the causal filter or Equation (29) for the non causal filter. The
|
||||
* only difference is that the non causal filter ignores the current value and starts from the
|
||||
* previous input, as can be seen in the subscript of the first input term in both equations.
|
||||
* So add one while indexing the non causal inputs. */
|
||||
outputs[0] = float4(0.0f);
|
||||
int first_input_index = is_causal ? 0 : 1;
|
||||
for (int i = 0; i < FILTER_ORDER; i++) {
|
||||
outputs[0] += feedforward_coefficients[i] * inputs[first_input_index + i];
|
||||
outputs[0] -= feedback_coefficients[i] * outputs[i + 1];
|
||||
}
|
||||
|
||||
/* Store the causal and non causal outputs independently, then sum them in a separate shader
|
||||
* dispatch for better parallelism. */
|
||||
if (is_causal) {
|
||||
imageStore(causal_output_img, texel, outputs[0]);
|
||||
}
|
||||
else {
|
||||
imageStore(non_causal_output_img, texel, outputs[0]);
|
||||
}
|
||||
|
||||
/* Shift the inputs temporally by one. The oldest input is discarded, while the current input
|
||||
* will retain its value but will be overwritten with the new current value in the next
|
||||
* iteration. */
|
||||
for (int i = FILTER_ORDER; i >= 1; i--) {
|
||||
inputs[i] = inputs[i - 1];
|
||||
}
|
||||
|
||||
/* Shift the outputs temporally by one. The oldest output is discarded, while the current
|
||||
* output will retain its value but will be overwritten with the new current value in the next
|
||||
* iteration. */
|
||||
for (int i = FILTER_ORDER; i >= 1; i--) {
|
||||
outputs[i] = outputs[i - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_deriche_gaussian_blur_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_deriche_gaussian_blur_sum)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* The Deriche filter is a parallel interconnection filter, meaning its output is the sum of its
|
||||
* causal and non causal filters. */
|
||||
float4 filter_output = texture_load(causal_input_tx, texel) +
|
||||
texture_load(non_causal_input_tx, texel);
|
||||
|
||||
/* Write the color using the transposed texel. See the sum_causal_and_non_causal_results method
|
||||
* in the deriche_gaussian_blur.cc file for more information on the rational behind this. */
|
||||
imageStore(output_img, texel.yx, filter_output);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_despeckle_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_despeckle)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_vector_compare_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* A 3x3 weights kernel whose weights are the inverse of the distance to the center of the
|
||||
* kernel. So the center weight is zero, the corners weights are (1 / sqrt(2)), and the rest
|
||||
* of the weights are 1. The total sum of weights is 4 plus quadruple the corner weight. */
|
||||
float corner_weight = 1.0f / sqrt(2.0f);
|
||||
float sum_of_weights = 4.0f + corner_weight * 4.0f;
|
||||
float3x3 weights = float3x3(float3(corner_weight, 1.0f, corner_weight),
|
||||
float3(1.0f, 0.0f, 1.0f),
|
||||
float3(corner_weight, 1.0f, corner_weight));
|
||||
|
||||
float4 center_color = texture_load(input_tx, texel);
|
||||
|
||||
/* Go over the pixels in the 3x3 window around the center pixel and compute the total sum of
|
||||
* their colors multiplied by their weights. Additionally, for pixels whose colors are not close
|
||||
* enough to the color of the center pixel, accumulate their color as well as their weights. */
|
||||
float4 sum_of_colors = float4(0);
|
||||
float accumulated_weight = 0.0f;
|
||||
float4 accumulated_color = float4(0);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float weight = weights[j][i];
|
||||
float4 color = texture_load(input_tx, texel + int2(i - 1, j - 1)) * weight;
|
||||
sum_of_colors += color;
|
||||
if (!is_equal(center_color.rgb, color.rgb, color_threshold)) {
|
||||
accumulated_color += color;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the accumulated weight is zero, that means all pixels in the 3x3 window are similar and no
|
||||
* need to despeckle anything, so write the original center color and return. */
|
||||
if (accumulated_weight == 0.0f) {
|
||||
imageStore(output_img, texel, center_color);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the ratio between the accumulated weights and the total sum of weights is not larger than
|
||||
* the user specified neighbor threshold, then the number of pixels in the neighborhood that are
|
||||
* not close enough to the center pixel is low, and no need to despeckle anything, so write the
|
||||
* original center color and return. */
|
||||
if (accumulated_weight / sum_of_weights < neighbor_threshold) {
|
||||
imageStore(output_img, texel, center_color);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the weighted average color of the neighborhood is close enough to the center pixel, then no
|
||||
* need to despeckle anything, so write the original center color and return. */
|
||||
if (is_equal(center_color.rgb, (sum_of_colors / sum_of_weights).rgb, color_threshold)) {
|
||||
imageStore(output_img, texel, center_color);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to despeckle, so write the mean accumulated color. */
|
||||
float factor = texture_load(factor_tx, texel).x;
|
||||
float4 mean_color = accumulated_color / accumulated_weight;
|
||||
imageStore(output_img, texel, mix(center_color, mean_color, factor));
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_directional_blur_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_directional_blur)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float2 input_size = float2(texture_size(input_tx));
|
||||
|
||||
float2 coordinates = float2(texel) + float2(0.5f);
|
||||
|
||||
float current_sin = 0.0f;
|
||||
float current_cos = 1.0f;
|
||||
float current_scale = 1.0f;
|
||||
float2 current_translation = float2(0.0f);
|
||||
|
||||
/* For each iteration, accumulate the input at the transformed coordinates, then increment the
|
||||
* transformations for the next iteration. */
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
/* Transform the coordinates by first offsetting the origin, scaling, translating, rotating,
|
||||
* then finally restoring the origin. Notice that we do the inverse of each of the transforms,
|
||||
* since we are transforming the coordinates, not the image. */
|
||||
float2 transformed_coordinates = coordinates;
|
||||
transformed_coordinates -= origin;
|
||||
transformed_coordinates /= current_scale;
|
||||
transformed_coordinates -= current_translation;
|
||||
transformed_coordinates = transformed_coordinates *
|
||||
float2x2(current_cos, current_sin, -current_sin, current_cos);
|
||||
transformed_coordinates += origin;
|
||||
|
||||
accumulated_color += texture(input_tx, transformed_coordinates / input_size);
|
||||
|
||||
current_scale += delta_scale;
|
||||
current_translation += delta_translation;
|
||||
|
||||
/* Those are the sine and cosine addition identities. Used to avoid computing sine and cosine
|
||||
* at each iteration. */
|
||||
float new_sin = current_sin * delta_rotation_cos + current_cos * delta_rotation_sin;
|
||||
current_cos = current_cos * delta_rotation_cos - current_sin * delta_rotation_sin;
|
||||
current_sin = new_sin;
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, accumulated_color / iterations);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_displace_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_displace)
|
||||
|
||||
#include "gpu_shader_bicubic_sampler_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int2 input_size = texture_size(input_tx);
|
||||
|
||||
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
|
||||
* to get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(input_size);
|
||||
|
||||
/* Note that the input displacement is in pixel space, so divide by the input size to transform
|
||||
* it into the normalized sampler space. */
|
||||
float2 displacement = texture_load(displacement_tx, texel).xy / float2(input_size);
|
||||
float2 displaced_coordinates = coordinates - displacement;
|
||||
|
||||
imageStore(output_img, texel, SAMPLER_FUNCTION(input_tx, displaced_coordinates));
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_displace_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_displace_anisotropic)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
/* A shared table that stores the displaced coordinates of all pixels in the work group. This is
|
||||
* necessary to avoid recomputing displaced coordinates when computing the gradients necessary for
|
||||
* anisotropic filtering, see the implementation for more information. */
|
||||
shared float2 displaced_coordinates_table[gl_WorkGroupSize.x][gl_WorkGroupSize.y];
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int2 input_size = texture_size(input_tx);
|
||||
|
||||
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
|
||||
* to get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(input_size);
|
||||
|
||||
/* Note that the input displacement is in pixel space, so divide by the input size to transform
|
||||
* it into the normalized sampler space. */
|
||||
float2 displacement = texture_load(displacement_tx, texel).xy / float2(input_size);
|
||||
float2 displaced_coordinates = coordinates - displacement;
|
||||
|
||||
/* Store the displaced coordinates into the shared table and issue a barrier to later compute the
|
||||
* gradients from the table. */
|
||||
int2 table_index = int2(gl_LocalInvocationID.xy);
|
||||
displaced_coordinates_table[table_index.x][table_index.y] = displaced_coordinates;
|
||||
barrier();
|
||||
|
||||
/* Compute the partial derivative of the displaced coordinates along the x direction using a
|
||||
* finite difference approximation. Odd invocations use a forward finite difference equation
|
||||
* while even invocations use a backward finite difference equation. This is done such that
|
||||
* invocations at the edges of the work group wouldn't need access to pixels that are outside of
|
||||
* the work group.
|
||||
*
|
||||
* The x_step value is 1 for even invocations and when added to the x table index and multiplied
|
||||
* by the result yields a standard forward finite difference equation. The x_step value is -1 for
|
||||
* odd invocations and when added to the x table index and multiplied by the result yields a
|
||||
* standard backward finite difference equation, because multiplication by -1 flips the order of
|
||||
* subtraction.
|
||||
*
|
||||
* Divide by the input size since textureGrad assumes derivatives with respect to texel
|
||||
* coordinates. */
|
||||
int x_step = (table_index.x % 2) * -2 + 1;
|
||||
float2 x_neighbor = displaced_coordinates_table[table_index.x + x_step][table_index.y];
|
||||
float2 x_gradient = (x_neighbor - displaced_coordinates) * x_step / input_size.x;
|
||||
|
||||
/* Compute the partial derivative of the displaced coordinates along the y direction using a
|
||||
* finite difference approximation. See the previous code section for more information. */
|
||||
int y_step = (table_index.y % 2) * -2 + 1;
|
||||
float2 y_neighbor = displaced_coordinates_table[table_index.x][table_index.y + y_step];
|
||||
float2 y_gradient = (y_neighbor - displaced_coordinates) * y_step / input_size.y;
|
||||
|
||||
/* Sample the input using the displaced coordinates passing in the computed gradients in order to
|
||||
* utilize the anisotropic filtering capabilities of the sampler. */
|
||||
float4 displaced_color = textureGrad(input_tx, displaced_coordinates, x_gradient, y_gradient);
|
||||
|
||||
imageStore(output_img, texel, displaced_color);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* The Double Edge Mask operation uses a jump flood algorithm to compute a distance transform to
|
||||
* the boundary of the inner and outer masks. The algorithm expects an input image whose values are
|
||||
* those returned by the initialize_jump_flooding_value function, given the texel location and a
|
||||
* boolean specifying if the pixel is a boundary one.
|
||||
*
|
||||
* Technically, we needn't restrict the output to just the boundary pixels, since the algorithm can
|
||||
* still operate if the interior of the masks was also included. However, the algorithm operates
|
||||
* more accurately when the number of pixels to be flooded is minimum. */
|
||||
|
||||
#include "infos/compositor_double_edge_mask_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_double_edge_mask_compute_boundary)
|
||||
|
||||
#include "gpu_shader_compositor_jump_flooding_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Identify if any of the 8 neighbors around the center pixel are not masked. */
|
||||
bool has_inner_non_masked_neighbors = false;
|
||||
bool has_outer_non_masked_neighbors = false;
|
||||
for (int j = -1; j <= 1; j++) {
|
||||
for (int i = -1; i <= 1; i++) {
|
||||
int2 offset = int2(i, j);
|
||||
|
||||
/* Exempt the center pixel. */
|
||||
if (all(equal(offset, int2(0)))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (texture_load(inner_mask_tx, texel + offset).x == 0.0f) {
|
||||
has_inner_non_masked_neighbors = true;
|
||||
}
|
||||
|
||||
/* If the user specified include_edges_of_image to be true, then we assume the outer mask is
|
||||
* bounded by the image boundary, otherwise, we assume the outer mask is open-ended. This is
|
||||
* practically implemented by falling back to 0.0 or 1.0 for out of bound pixels. */
|
||||
float4 boundary_fallback = include_edges_of_image ? float4(0.0f) : float4(1.0f);
|
||||
if (texture_load(outer_mask_tx, texel + offset, boundary_fallback).x == 0.0f) {
|
||||
has_outer_non_masked_neighbors = true;
|
||||
}
|
||||
|
||||
/* Both are true, no need to continue. */
|
||||
if (has_inner_non_masked_neighbors && has_outer_non_masked_neighbors) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_inner_masked = texture_load(inner_mask_tx, texel).x > 0.0f;
|
||||
bool is_outer_masked = texture_load(outer_mask_tx, texel).x > 0.0f;
|
||||
|
||||
/* The pixels at the boundary are those that are masked and have non masked neighbors. The inner
|
||||
* boundary has a specialization, if include_all_inner_edges is false, only inner boundaries that
|
||||
* lie inside the outer mask will be considered a boundary. The outer boundary is only considered
|
||||
* if it is not inside the inner mask. */
|
||||
bool is_inner_boundary = is_inner_masked && has_inner_non_masked_neighbors &&
|
||||
(is_outer_masked || include_all_inner_edges);
|
||||
bool is_outer_boundary = is_outer_masked && !is_inner_masked && has_outer_non_masked_neighbors;
|
||||
|
||||
/* Encode the boundary information in the format expected by the jump flooding algorithm. */
|
||||
int2 inner_jump_flooding_value = initialize_jump_flooding_value(texel, is_inner_boundary);
|
||||
int2 outer_jump_flooding_value = initialize_jump_flooding_value(texel, is_outer_boundary);
|
||||
|
||||
imageStore(inner_boundary_img, texel, int4(inner_jump_flooding_value, int2(0)));
|
||||
imageStore(outer_boundary_img, texel, int4(outer_jump_flooding_value, int2(0)));
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Computes a linear gradient from the outer mask boundary to the inner mask boundary, starting
|
||||
* from 0 and ending at 1. This is computed using the equation:
|
||||
*
|
||||
* Gradient = O / (O + I)
|
||||
*
|
||||
* Where O is the distance to the outer boundary and I is the distance to the inner boundary.
|
||||
* This can be viewed as computing the ratio between the distance to the outer boundary to the
|
||||
* distance between the outer and inner boundaries as can be seen in the following illustration
|
||||
* where the $ sign designates a pixel between both boundaries.
|
||||
*
|
||||
* | O I |
|
||||
* Outer Boundary |---------$---------| Inner Boundary
|
||||
* | |
|
||||
*/
|
||||
|
||||
#include "infos/compositor_double_edge_mask_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_double_edge_mask_compute_gradient)
|
||||
|
||||
#include "gpu_shader_compositor_jump_flooding_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Pixels inside the inner mask are always 1.0. */
|
||||
float inner_mask = texture_load(inner_mask_tx, texel).x;
|
||||
if (inner_mask != 0.0f) {
|
||||
imageStore(output_img, texel, float4(1.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Pixels outside the outer mask are always 0.0. */
|
||||
float outer_mask = texture_load(outer_mask_tx, texel).x;
|
||||
if (outer_mask == 0.0f) {
|
||||
imageStore(output_img, texel, float4(0.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute the distances to the inner and outer boundaries from the jump flooding tables. */
|
||||
int2 inner_boundary_texel = texture_load(flooded_inner_boundary_tx, texel).xy;
|
||||
int2 outer_boundary_texel = texture_load(flooded_outer_boundary_tx, texel).xy;
|
||||
float distance_to_inner = distance(float2(texel), float2(inner_boundary_texel));
|
||||
float distance_to_outer = distance(float2(texel), float2(outer_boundary_texel));
|
||||
|
||||
float gradient = distance_to_outer / (distance_to_outer + distance_to_inner);
|
||||
|
||||
imageStore(output_img, texel, float4(gradient));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_edge_filter_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_edge_filter)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Compute the dot product between the 3x3 window around the pixel and the edge detection kernel
|
||||
* in the X direction and Y direction. The Y direction kernel is computed by transposing the
|
||||
* given X direction kernel. */
|
||||
float3 color_x = float3(0);
|
||||
float3 color_y = float3(0);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float3 color = texture_load(input_tx, texel + int2(i - 1, j - 1)).rgb;
|
||||
color_x += color * ukernel[j][i];
|
||||
color_y += color * ukernel[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute the channel-wise magnitude of the 2D vector composed from the X and Y edge detection
|
||||
* filter results. */
|
||||
float3 magnitude = sqrt(color_x * color_x + color_y * color_y);
|
||||
|
||||
/* Mix the channel-wise magnitude with the original color at the center of the kernel using the
|
||||
* input factor. */
|
||||
float4 color = texture_load(input_tx, texel);
|
||||
magnitude = mix(color.rgb, magnitude, texture_load(factor_tx, texel).x);
|
||||
|
||||
/* Store the channel-wise magnitude with the original alpha of the input. */
|
||||
imageStore(output_img, texel, float4(magnitude, color.a));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_ellipse_mask_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_ellipse_mask_add)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
/* TODO(fclem): deduplicate. */
|
||||
#define CMP_NODE_MASKTYPE_ADD 0
|
||||
#define CMP_NODE_MASKTYPE_SUBTRACT 1
|
||||
#define CMP_NODE_MASKTYPE_MULTIPLY 2
|
||||
#define CMP_NODE_MASKTYPE_NOT 3
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float2 uv = float2(texel + data_offset) / float2(display_size - int2(1));
|
||||
uv -= location;
|
||||
uv.y *= float(display_size.y) / float(display_size.x);
|
||||
uv = float2x2(cos_angle, -sin_angle, sin_angle, cos_angle) * uv;
|
||||
bool is_inside = length(uv / radius) < 1.0f;
|
||||
|
||||
float base_mask_value = texture_load(base_mask_tx, texel).x;
|
||||
float value = texture_load(mask_value_tx, texel).x;
|
||||
|
||||
float output_mask_value;
|
||||
if (node_type == CMP_NODE_MASKTYPE_ADD) {
|
||||
output_mask_value = is_inside ? max(base_mask_value, value) : base_mask_value;
|
||||
}
|
||||
else if (node_type == CMP_NODE_MASKTYPE_SUBTRACT) {
|
||||
output_mask_value = is_inside ? clamp(base_mask_value - value, 0.0f, 1.0f) : base_mask_value;
|
||||
}
|
||||
else if (node_type == CMP_NODE_MASKTYPE_MULTIPLY) {
|
||||
output_mask_value = is_inside ? base_mask_value * value : 0.0f;
|
||||
}
|
||||
else if (node_type == CMP_NODE_MASKTYPE_NOT) {
|
||||
output_mask_value = is_inside ? (base_mask_value > 0.0f ? 0.0f : value) : base_mask_value;
|
||||
}
|
||||
|
||||
imageStore(output_mask_img, texel, float4(output_mask_value));
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_filter_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_filter)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Compute the dot product between the 3x3 window around the pixel and the filter kernel. */
|
||||
float4 color = float4(0);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
color += texture_load(input_tx, texel + int2(i - 1, j - 1)) * ukernel[j][i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Mix with the original color at the center of the kernel using the input factor. */
|
||||
color = mix(texture_load(input_tx, texel), color, texture_load(factor_tx, texel).x);
|
||||
|
||||
/* Store the color making sure it is not negative. */
|
||||
imageStore(output_img, texel, max(color, 0.0f));
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_flip_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_flip)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int2 size = texture_size(input_tx);
|
||||
int2 flipped_texel = texel;
|
||||
if (flip_x) {
|
||||
flipped_texel.x = size.x - texel.x - 1;
|
||||
}
|
||||
if (flip_y) {
|
||||
flipped_texel.y = size.y - texel.y - 1;
|
||||
}
|
||||
imageStore(output_img, texel, texture_load(input_tx, flipped_texel));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_gamma_correct_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_gamma_correct)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 color = texture_load(input_tx, texel);
|
||||
float alpha = color.a > 0.0f ? color.a : 1.0f;
|
||||
float3 corrected_color = FUNCTION(max(color.rgb / alpha, float3(0.0f))) * alpha;
|
||||
imageStore(output_img, texel, float4(corrected_color, color.a));
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_bloom_downsample_shared)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_safe_lib.glsl"
|
||||
#include "gpu_shader_math_vector_lib.glsl"
|
||||
#include "gpu_shader_math_vector_reduce_lib.glsl"
|
||||
#include "gpu_shader_utildefines_lib.glsl"
|
||||
|
||||
#if defined(KARIS_AVERAGE)
|
||||
/* Computes the weighted average of the given four colors, which are assumed to the colors of
|
||||
* spatially neighboring pixels. The weights are computed so as to reduce the contributions of
|
||||
* fireflies on the result by applying a form of local tone mapping as described by Brian Karis in
|
||||
* the article "Graphic Rants: Tone Mapping".
|
||||
*
|
||||
* https://graphicrants.blogspot.com/2013/12/tone-mapping.html */
|
||||
float4 karis_brightness_weighted_sum(float4 color1, float4 color2, float4 color3, float4 color4)
|
||||
{
|
||||
float4 brightness = float4(
|
||||
reduce_max(color1), reduce_max(color2), reduce_max(color3), reduce_max(color4));
|
||||
float4 weights = 1.0f / (brightness + 1.0f);
|
||||
return weighted_sum(color1, color2, color3, color4, weights);
|
||||
}
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
/* Each invocation corresponds to one output pixel, where the output has half the size of the
|
||||
* input. */
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Add 0.5 to evaluate the sampler at the center of the pixel and divide by the image size to
|
||||
* get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(imageSize(output_img));
|
||||
|
||||
/* All the offsets in the following code section are in the normalized pixel space of the input
|
||||
* texture, so compute its normalized pixel size. */
|
||||
float2 pixel_size = 1.0f / float2(texture_size(input_tx));
|
||||
|
||||
/* Each invocation downsamples a 6x6 area of pixels around the center of the corresponding output
|
||||
* pixel, but instead of sampling each of the 36 pixels in the area, we only sample 13 positions
|
||||
* using bilinear fetches at the center of a number of overlapping square 4-pixel groups. This
|
||||
* downsampling strategy is described in the talk:
|
||||
*
|
||||
* Next Generation Post Processing in Call of Duty: Advanced Warfare
|
||||
* https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
|
||||
*
|
||||
* In particular, the downsampling strategy is described and illustrated in slide 153 titled
|
||||
* "Downsampling - Our Solution". This is employed as it significantly improves the stability of
|
||||
* the glare as can be seen in the videos in the talk. */
|
||||
float4 center = texture(input_tx, coordinates);
|
||||
float4 upper_left_near = texture(input_tx, coordinates + pixel_size * float2(-1.0f, 1.0f));
|
||||
float4 upper_right_near = texture(input_tx, coordinates + pixel_size * float2(1.0f, 1.0f));
|
||||
float4 lower_left_near = texture(input_tx, coordinates + pixel_size * float2(-1.0f, -1.0f));
|
||||
float4 lower_right_near = texture(input_tx, coordinates + pixel_size * float2(1.0f, -1.0f));
|
||||
float4 left_far = texture(input_tx, coordinates + pixel_size * float2(-2.0f, 0.0f));
|
||||
float4 right_far = texture(input_tx, coordinates + pixel_size * float2(2.0f, 0.0f));
|
||||
float4 upper_far = texture(input_tx, coordinates + pixel_size * float2(0.0f, 2.0f));
|
||||
float4 lower_far = texture(input_tx, coordinates + pixel_size * float2(0.0f, -2.0f));
|
||||
float4 upper_left_far = texture(input_tx, coordinates + pixel_size * float2(-2.0f, 2.0f));
|
||||
float4 upper_right_far = texture(input_tx, coordinates + pixel_size * float2(2.0f, 2.0f));
|
||||
float4 lower_left_far = texture(input_tx, coordinates + pixel_size * float2(-2.0f, -2.0f));
|
||||
float4 lower_right_far = texture(input_tx, coordinates + pixel_size * float2(2.0f, -2.0f));
|
||||
|
||||
#if defined(SIMPLE_AVERAGE)
|
||||
/* The original weights equation mentioned in slide 153 is:
|
||||
* 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1
|
||||
* The 0.5 corresponds to the center group of pixels and the 0.125f corresponds to the other
|
||||
* groups of pixels. The center is sampled 4 times, the far non corner pixels are sampled 2
|
||||
* times, the near corner pixels are sampled only once; but their weight is quadruple the weights
|
||||
* of other groups; so they count as sampled 4 times, finally the far corner pixels are sampled
|
||||
* only once, essentially totaling 32 samples. So the weights are as used in the following code
|
||||
* section. */
|
||||
float4 result = (4.0f / 32.0f) * center +
|
||||
(4.0f / 32.0f) *
|
||||
(upper_left_near + upper_right_near + lower_left_near + lower_right_near) +
|
||||
(2.0f / 32.0f) * (left_far + right_far + upper_far + lower_far) +
|
||||
(1.0f / 32.0f) *
|
||||
(upper_left_far + upper_right_far + lower_left_far + lower_right_far);
|
||||
#elif defined(KARIS_AVERAGE)
|
||||
/* Reduce the contributions of fireflies on the result by reducing each group of pixels using a
|
||||
* Karis brightness weighted sum. This is described in slide 168 titled "Fireflies - Partial
|
||||
* Karis Average".
|
||||
*
|
||||
* This needn't be done on all downsampling passes, but only the first one, since fireflies
|
||||
* will not survive the first pass, later passes can use the weighted average. */
|
||||
float4 center_weighted_sum = karis_brightness_weighted_sum(
|
||||
upper_left_near, upper_right_near, lower_right_near, lower_left_near);
|
||||
float4 upper_left_weighted_sum = karis_brightness_weighted_sum(
|
||||
upper_left_far, upper_far, center, left_far);
|
||||
float4 upper_right_weighted_sum = karis_brightness_weighted_sum(
|
||||
upper_far, upper_right_far, right_far, center);
|
||||
float4 lower_right_weighted_sum = karis_brightness_weighted_sum(
|
||||
center, right_far, lower_right_far, lower_far);
|
||||
float4 lower_left_weighted_sum = karis_brightness_weighted_sum(
|
||||
left_far, center, lower_far, lower_left_far);
|
||||
|
||||
/* The original weights equation mentioned in slide 153 is:
|
||||
* 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1
|
||||
* Multiply both sides by 8 and you get:
|
||||
* 4 + 1 + 1 + 1 + 1 = 8
|
||||
* So the weights are as used in the following code section. */
|
||||
float4 result = (4.0f / 8.0f) * center_weighted_sum +
|
||||
(1.0f / 8.0f) * (upper_left_weighted_sum + upper_right_weighted_sum +
|
||||
lower_left_weighted_sum + lower_right_weighted_sum);
|
||||
#endif
|
||||
|
||||
imageStore(output_img, texel, result);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_bloom_upsample)
|
||||
|
||||
void main()
|
||||
{
|
||||
/* Each invocation corresponds to one output pixel, where the output has twice the size of the
|
||||
* input. */
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Add 0.5 to evaluate the sampler at the center of the pixel and divide by the image size to
|
||||
* get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(imageSize(output_img));
|
||||
|
||||
/* All the offsets in the following code section are in the normalized pixel space of the output
|
||||
* image, so compute its normalized pixel size. */
|
||||
float2 pixel_size = 1.0f / float2(imageSize(output_img));
|
||||
|
||||
/* Upsample by applying a 3x3 tent filter on the bi-linearly interpolated values evaluated at
|
||||
* the center of neighboring output pixels. As more tent filter upsampling passes are applied,
|
||||
* the result approximates a large sized Gaussian filter. This upsampling strategy is described
|
||||
* in the talk:
|
||||
*
|
||||
* Next Generation Post Processing in Call of Duty: Advanced Warfare
|
||||
* https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
|
||||
*
|
||||
* In particular, the upsampling strategy is described and illustrated in slide 162 titled
|
||||
* "Upsampling - Our Solution". */
|
||||
float4 upsampled = float4(0.0f);
|
||||
upsampled += (4.0f / 16.0f) * texture(input_tx, coordinates);
|
||||
upsampled += (2.0f / 16.0f) * texture(input_tx, coordinates + pixel_size * float2(-1.0f, 0.0f));
|
||||
upsampled += (2.0f / 16.0f) * texture(input_tx, coordinates + pixel_size * float2(0.0f, 1.0f));
|
||||
upsampled += (2.0f / 16.0f) * texture(input_tx, coordinates + pixel_size * float2(1.0f, 0.0f));
|
||||
upsampled += (2.0f / 16.0f) * texture(input_tx, coordinates + pixel_size * float2(0.0f, -1.0f));
|
||||
upsampled += (1.0f / 16.0f) * texture(input_tx, coordinates + pixel_size * float2(-1.0f, -1.0f));
|
||||
upsampled += (1.0f / 16.0f) * texture(input_tx, coordinates + pixel_size * float2(-1.0f, 1.0f));
|
||||
upsampled += (1.0f / 16.0f) * texture(input_tx, coordinates + pixel_size * float2(1.0f, -1.0f));
|
||||
upsampled += (1.0f / 16.0f) * texture(input_tx, coordinates + pixel_size * float2(1.0f, 1.0f));
|
||||
|
||||
float4 combined = imageLoad(output_img, texel) + upsampled;
|
||||
imageStore(output_img, texel, float4(combined.rgb, 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_ghost_accumulate)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int2 input_size = texture_size(input_ghost_tx);
|
||||
|
||||
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
|
||||
* to get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(input_size);
|
||||
|
||||
/* We accumulate four variants of the input ghost texture, each is scaled by some amount and
|
||||
* possibly multiplied by some color as a form of color modulation. */
|
||||
float4 accumulated_ghost = float4(0.0f);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float scale = scales[i];
|
||||
float4 color_modulator = color_modulators[i];
|
||||
|
||||
/* Scale the coordinates for the ghost, pre subtract 0.5 and post add 0.5 to use 0.5 as the
|
||||
* origin of the scaling. */
|
||||
float2 scaled_coordinates = (coordinates - 0.5f) * scale + 0.5f;
|
||||
|
||||
/* The value of the ghost is attenuated by a scalar multiple of the inverse distance to the
|
||||
* center, such that it is maximum at the center and become zero further from the center,
|
||||
* making sure to take the scale into account. The scalar multiple of 1 / 4 is chosen using
|
||||
* visual judgment. */
|
||||
float distance_to_center = distance(coordinates, float2(0.5f)) * 2.0f;
|
||||
float attenuator = max(0.0f, 1.0f - distance_to_center * abs(scale)) / 4.0f;
|
||||
|
||||
/* Accumulate the scaled ghost after attenuating and color modulating its value. */
|
||||
float4 multiplier = attenuator * color_modulator;
|
||||
accumulated_ghost += texture(input_ghost_tx, scaled_coordinates) * multiplier;
|
||||
}
|
||||
|
||||
float4 current_accumulated_ghost = imageLoad(accumulated_ghost_img, texel);
|
||||
float4 combined_ghost = current_accumulated_ghost + accumulated_ghost;
|
||||
imageStore(accumulated_ghost_img, texel, float4(combined_ghost.rgb, 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_ghost_base)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int2 input_size = texture_size(small_ghost_tx);
|
||||
|
||||
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
|
||||
* to get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(input_size);
|
||||
|
||||
/* The small ghost is scaled down with the origin as the center of the image by a factor
|
||||
* of 2.13, while the big ghost is flipped and scaled up with the origin as the center of the
|
||||
* image by a factor of 0.97. Note that 1) The negative scale implements the flipping. 2)
|
||||
* Factors larger than 1 actually scales down the image since the factor multiplies the
|
||||
* coordinates and not the images itself. 3) The values are arbitrarily chosen using visual
|
||||
* judgment. */
|
||||
float small_ghost_scale = 2.13f;
|
||||
float big_ghost_scale = -0.97f;
|
||||
|
||||
/* Scale the coordinates for the small and big ghosts, pre subtract 0.5 and post add 0.5 to use
|
||||
* 0.5 as the origin of the scaling. Notice that the big ghost is flipped due to the negative
|
||||
* scale. */
|
||||
float2 small_ghost_coordinates = (coordinates - 0.5f) * small_ghost_scale + 0.5f;
|
||||
float2 big_ghost_coordinates = (coordinates - 0.5f) * big_ghost_scale + 0.5f;
|
||||
|
||||
/* The values of the ghosts are attenuated by the inverse distance to the center, such that they
|
||||
* are maximum at the center and become zero further from the center, making sure to take the
|
||||
* aforementioned scale into account. */
|
||||
float distance_to_center = distance(coordinates, float2(0.5f)) * 2.0f;
|
||||
float small_ghost_attenuator = max(0.0f, 1.0f - distance_to_center * small_ghost_scale);
|
||||
float big_ghost_attenuator = max(0.0f, 1.0f - distance_to_center * abs(big_ghost_scale));
|
||||
|
||||
float4 small_ghost = texture(small_ghost_tx, small_ghost_coordinates) * small_ghost_attenuator;
|
||||
float4 big_ghost = texture(big_ghost_tx, big_ghost_coordinates) * big_ghost_attenuator;
|
||||
|
||||
imageStore(combined_ghost_img, texel, small_ghost + big_ghost);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_highlights)
|
||||
|
||||
#include "gpu_shader_common_color_utils.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
#define CMP_NODE_GLARE_QUALITY_HIGH 0
|
||||
#define CMP_NODE_GLARE_QUALITY_MEDIUM 1
|
||||
#define CMP_NODE_GLARE_QUALITY_LOW 2
|
||||
|
||||
/* A Quadratic Polynomial smooth minimum function *without* normalization, based on:
|
||||
*
|
||||
* https://iquilezles.org/articles/smin/
|
||||
*
|
||||
* This should not be converted into a common utility function because the glare code is
|
||||
* specifically designed for it as can be seen in the adaptive_smooth_clamp method, and it is
|
||||
* intentionally not normalized. */
|
||||
float smooth_min(float a, float b, float smoothness)
|
||||
{
|
||||
if (smoothness == 0.0f) {
|
||||
return min(a, b);
|
||||
}
|
||||
float h = max(smoothness - abs(a - b), 0.0f) / smoothness;
|
||||
return min(a, b) - h * h * smoothness * (1.0f / 4.0f);
|
||||
}
|
||||
|
||||
float smooth_max(float a, float b, float smoothness)
|
||||
{
|
||||
return -smooth_min(-a, -b, smoothness);
|
||||
}
|
||||
|
||||
/* Clamps the input x within min_value and max_value using a quadratic polynomial smooth minimum
|
||||
* and maximum functions, with individual control over their smoothness. */
|
||||
float smooth_clamp(
|
||||
float x, float min_value, float max_value, float min_smoothness, float max_smoothness)
|
||||
{
|
||||
return smooth_min(max_value, smooth_max(min_value, x, min_smoothness), max_smoothness);
|
||||
}
|
||||
|
||||
/* A variant of smooth_clamp that limits the smoothness such that the function evaluates to the
|
||||
* given min for 0 <= min <= max and x >= 0. The aforementioned guarantee holds for the standard
|
||||
* clamp function by definition, but since the smooth clamp function gradually increases before
|
||||
* the specified min/max, if min/max are sufficiently close together or to zero, they will not
|
||||
* evaluate to min at zero or at min, since zero or min will be at the region of the gradual
|
||||
* increase.
|
||||
*
|
||||
* It can be shown that the width of the gradual increase region is equivalent to the smoothness
|
||||
* parameter, so smoothness can't be larger than the difference between the min/max and zero, or
|
||||
* larger than the difference between min and max themselves. Otherwise, zero or min will lie
|
||||
* inside the gradual increase region of min/max. So we limit the smoothness of min/max by taking
|
||||
* the minimum with the distances to zero and to the distance to the other bound. */
|
||||
float adaptive_smooth_clamp(float x, float min_value, float max_value, float smoothness)
|
||||
{
|
||||
float range_distance = distance(min_value, max_value);
|
||||
float distance_from_min_to_zero = distance(min_value, 0.0f);
|
||||
float distance_from_max_to_zero = distance(max_value, 0.0f);
|
||||
|
||||
float max_safe_smoothness_for_min = min(distance_from_min_to_zero, range_distance);
|
||||
float max_safe_smoothness_for_max = min(distance_from_max_to_zero, range_distance);
|
||||
|
||||
float min_smoothness = min(smoothness, max_safe_smoothness_for_min);
|
||||
float max_smoothness = min(smoothness, max_safe_smoothness_for_max);
|
||||
|
||||
return smooth_clamp(x, min_value, max_value, min_smoothness, max_smoothness);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 color = float4(0.0f);
|
||||
|
||||
switch (quality) {
|
||||
case CMP_NODE_GLARE_QUALITY_HIGH: {
|
||||
color = texture_load(input_tx, texel);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Down-sample the image 2 times to match the output size by averaging the 2x2 block of
|
||||
* pixels into a single output pixel. This is done due to the bilinear interpolation at the
|
||||
* center of the 2x2 block of pixels. */
|
||||
case CMP_NODE_GLARE_QUALITY_MEDIUM: {
|
||||
float2 normalized_coordinates = (float2(texel) * 2.0f + float2(1.0f)) /
|
||||
float2(texture_size(input_tx));
|
||||
color = texture(input_tx, normalized_coordinates);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Down-sample the image 4 times to match the output size by averaging each 4x4 block of
|
||||
* pixels into a single output pixel. This is done by averaging 4 bilinear taps at the
|
||||
* center of each of the corner 2x2 pixel blocks, which are themselves the average of the
|
||||
* 2x2 block due to the bilinear interpolation at the center. */
|
||||
case CMP_NODE_GLARE_QUALITY_LOW: {
|
||||
float2 lower_left_coordinates = (float2(texel) * 4.0f + float2(1.0f)) /
|
||||
float2(texture_size(input_tx));
|
||||
float4 lower_left_color = texture(input_tx, lower_left_coordinates);
|
||||
|
||||
float2 lower_right_coordinates = (float2(texel) * 4.0f + float2(3.0f, 1.0f)) /
|
||||
float2(texture_size(input_tx));
|
||||
float4 lower_right_color = texture(input_tx, lower_right_coordinates);
|
||||
|
||||
float2 upper_left_coordinates = (float2(texel) * 4.0f + float2(1.0f, 3.0f)) /
|
||||
float2(texture_size(input_tx));
|
||||
float4 upper_left_color = texture(input_tx, upper_left_coordinates);
|
||||
|
||||
float2 upper_right_coordinates = (float2(texel) * 4.0f + float2(3.0f)) /
|
||||
float2(texture_size(input_tx));
|
||||
float4 upper_right_color = texture(input_tx, upper_right_coordinates);
|
||||
|
||||
color = (upper_left_color + upper_right_color + lower_left_color + lower_right_color) / 4.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float4 hsva;
|
||||
rgb_to_hsv(color, hsva);
|
||||
|
||||
/* Clamp the brightness of the highlights such that pixels whose brightness are less than the
|
||||
* threshold will be equal to the threshold and will become zero once threshold is subtracted
|
||||
* later. We also clamp by the specified max brightness to suppress very bright highlights.
|
||||
*
|
||||
* We use a smooth clamping function such that highlights do not become very sharp but use
|
||||
* the adaptive variant such that we guarantee that zero highlights remain zero even after
|
||||
* smoothing. Notice that when we mention zero, we mean zero after subtracting the threshold,
|
||||
* so we actually mean the minimum bound, the threshold. See the adaptive_smooth_clamp
|
||||
* function for more information. */
|
||||
float clamped_brightness = adaptive_smooth_clamp(
|
||||
hsva.z, threshold, max_brightness, highlights_smoothness);
|
||||
|
||||
/* The final brightness is relative to the threshold. */
|
||||
hsva.z = clamped_brightness - threshold;
|
||||
|
||||
float4 rgba;
|
||||
hsv_to_rgb(hsva, rgba);
|
||||
|
||||
imageStore(output_img, texel, float4(rgba.rgb, 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_kernel_downsample_color)
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
const float2 normalized_coordinates = (float2(texel) + float2(0.5f)) /
|
||||
float2(imageSize(output_img));
|
||||
imageStore(output_img, texel, texture(input_tx, normalized_coordinates));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_mix)
|
||||
|
||||
#include "gpu_shader_common_color_utils.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Make sure the input is not negative to avoid a subtractive effect when mixing the glare. */
|
||||
float4 input_color = max(float4(0.0f), texture_load(input_tx, texel));
|
||||
|
||||
float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(texture_size(input_tx));
|
||||
float4 glare_color = texture(glare_tx, normalized_coordinates);
|
||||
|
||||
/* Adjust saturation of glare. */
|
||||
float4 glare_hsva;
|
||||
rgb_to_hsv(glare_color, glare_hsva);
|
||||
glare_hsva.y = clamp(glare_hsva.y * saturation, 0.0f, 1.0f);
|
||||
float4 glare_rgba;
|
||||
hsv_to_rgb(glare_hsva, glare_rgba);
|
||||
|
||||
float3 combined_color = input_color.rgb + glare_rgba.rgb * tint;
|
||||
|
||||
imageStore(output_img, texel, float4(combined_color, input_color.a));
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_simple_star_anti_diagonal_pass)
|
||||
|
||||
#include "gpu_shader_compositor_image_diagonals.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 size = imageSize(anti_diagonal_img);
|
||||
int index = int(gl_GlobalInvocationID.x);
|
||||
int anti_diagonal_length = compute_anti_diagonal_length(size, index);
|
||||
int2 start = compute_anti_diagonal_start(size, index);
|
||||
int2 direction = get_anti_diagonal_direction();
|
||||
int2 end = start + (anti_diagonal_length - 1) * direction;
|
||||
|
||||
/* For each iteration, apply a causal filter followed by a non causal filters along the anti
|
||||
* diagonal mapped to the current thread invocation. */
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
/* Causal Pass:
|
||||
* Sequentially apply a causal filter running from the start of the anti diagonal to its end by
|
||||
* mixing the value of the pixel in the anti diagonal with the average value of the previous
|
||||
* output and next input in the same anti diagonal. */
|
||||
for (int j = 0; j < anti_diagonal_length; j++) {
|
||||
int2 texel = start + j * direction;
|
||||
float4 previous_output = imageLoad(anti_diagonal_img, texel - i * direction);
|
||||
float4 current_input = imageLoad(anti_diagonal_img, texel);
|
||||
float4 next_input = imageLoad(anti_diagonal_img, texel + i * direction);
|
||||
|
||||
float4 neighbor_average = (previous_output + next_input) / 2.0f;
|
||||
float4 causal_output = mix(current_input, neighbor_average, fade_factor);
|
||||
imageStore(anti_diagonal_img, texel, causal_output);
|
||||
imageFence(anti_diagonal_img);
|
||||
}
|
||||
|
||||
/* Non Causal Pass:
|
||||
* Sequentially apply a non causal filter running from the end of the diagonal to its start by
|
||||
* mixing the value of the pixel in the diagonal with the average value of the previous output
|
||||
* and next input in the same diagonal. */
|
||||
for (int j = 0; j < anti_diagonal_length; j++) {
|
||||
int2 texel = end - j * direction;
|
||||
float4 previous_output = imageLoad(anti_diagonal_img, texel + i * direction);
|
||||
float4 current_input = imageLoad(anti_diagonal_img, texel);
|
||||
float4 next_input = imageLoad(anti_diagonal_img, texel - i * direction);
|
||||
|
||||
float4 neighbor_average = (previous_output + next_input) / 2.0f;
|
||||
float4 non_causal_output = mix(current_input, neighbor_average, fade_factor);
|
||||
imageStore(anti_diagonal_img, texel, non_causal_output);
|
||||
imageFence(anti_diagonal_img);
|
||||
}
|
||||
}
|
||||
|
||||
/* For each pixel in the anti diagonal mapped to the current invocation thread, add the result of
|
||||
* the diagonal pass to the vertical pass. */
|
||||
for (int j = 0; j < anti_diagonal_length; j++) {
|
||||
int2 texel = start + j * direction;
|
||||
float4 horizontal = texture_load(diagonal_tx, texel);
|
||||
float4 vertical = imageLoad(anti_diagonal_img, texel);
|
||||
float4 combined = horizontal + vertical;
|
||||
imageStore(anti_diagonal_img, texel, float4(combined.rgb, 1.0f));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_simple_star_diagonal_pass)
|
||||
|
||||
#include "gpu_shader_compositor_image_diagonals.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 size = imageSize(diagonal_img);
|
||||
int index = int(gl_GlobalInvocationID.x);
|
||||
int diagonal_length = compute_diagonal_length(size, index);
|
||||
int2 start = compute_diagonal_start(size, index);
|
||||
int2 direction = get_diagonal_direction();
|
||||
int2 end = start + (diagonal_length - 1) * direction;
|
||||
|
||||
/* For each iteration, apply a causal filter followed by a non causal filters along the diagonal
|
||||
* mapped to the current thread invocation. */
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
/* Causal Pass:
|
||||
* Sequentially apply a causal filter running from the start of the diagonal to its end by
|
||||
* mixing the value of the pixel in the diagonal with the average value of the previous output
|
||||
* and next input in the same diagonal. */
|
||||
for (int j = 0; j < diagonal_length; j++) {
|
||||
int2 texel = start + j * direction;
|
||||
float4 previous_output = imageLoad(diagonal_img, texel - i * direction);
|
||||
float4 current_input = imageLoad(diagonal_img, texel);
|
||||
float4 next_input = imageLoad(diagonal_img, texel + i * direction);
|
||||
|
||||
float4 neighbor_average = (previous_output + next_input) / 2.0f;
|
||||
float4 causal_output = mix(current_input, neighbor_average, fade_factor);
|
||||
imageStore(diagonal_img, texel, causal_output);
|
||||
imageFence(diagonal_img);
|
||||
}
|
||||
|
||||
/* Non Causal Pass:
|
||||
* Sequentially apply a non causal filter running from the end of the diagonal to its start by
|
||||
* mixing the value of the pixel in the diagonal with the average value of the previous output
|
||||
* and next input in the same diagonal. */
|
||||
for (int j = 0; j < diagonal_length; j++) {
|
||||
int2 texel = end - j * direction;
|
||||
float4 previous_output = imageLoad(diagonal_img, texel + i * direction);
|
||||
float4 current_input = imageLoad(diagonal_img, texel);
|
||||
float4 next_input = imageLoad(diagonal_img, texel - i * direction);
|
||||
|
||||
float4 neighbor_average = (previous_output + next_input) / 2.0f;
|
||||
float4 non_causal_output = mix(current_input, neighbor_average, fade_factor);
|
||||
imageStore(diagonal_img, texel, non_causal_output);
|
||||
imageFence(diagonal_img);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_simple_star_horizontal_pass)
|
||||
|
||||
void main()
|
||||
{
|
||||
int width = imageSize(horizontal_img).x;
|
||||
|
||||
/* For each iteration, apply a causal filter followed by a non causal filters along the row
|
||||
* mapped to the current thread invocation. */
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
/* Causal Pass:
|
||||
* Sequentially apply a causal filter running from left to right by mixing the value of the
|
||||
* pixel in the row with the average value of the previous output and next input in the same
|
||||
* row. */
|
||||
for (int x = 0; x < width; x++) {
|
||||
int2 texel = int2(x, gl_GlobalInvocationID.x);
|
||||
float4 previous_output = imageLoad(horizontal_img, texel - int2(i, 0));
|
||||
float4 current_input = imageLoad(horizontal_img, texel);
|
||||
float4 next_input = imageLoad(horizontal_img, texel + int2(i, 0));
|
||||
|
||||
float4 neighbor_average = (previous_output + next_input) / 2.0f;
|
||||
float4 causal_output = mix(current_input, neighbor_average, fade_factor);
|
||||
imageStore(horizontal_img, texel, causal_output);
|
||||
imageFence(horizontal_img);
|
||||
}
|
||||
|
||||
/* Non Causal Pass:
|
||||
* Sequentially apply a non causal filter running from right to left by mixing the value of the
|
||||
* pixel in the row with the average value of the previous output and next input in the same
|
||||
* row. */
|
||||
for (int x = width - 1; x >= 0; x--) {
|
||||
int2 texel = int2(x, gl_GlobalInvocationID.x);
|
||||
float4 previous_output = imageLoad(horizontal_img, texel + int2(i, 0));
|
||||
float4 current_input = imageLoad(horizontal_img, texel);
|
||||
float4 next_input = imageLoad(horizontal_img, texel - int2(i, 0));
|
||||
|
||||
float4 neighbor_average = (previous_output + next_input) / 2.0f;
|
||||
float4 non_causal_output = mix(current_input, neighbor_average, fade_factor);
|
||||
imageStore(horizontal_img, texel, non_causal_output);
|
||||
imageFence(horizontal_img);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_simple_star_vertical_pass)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int height = imageSize(vertical_img).y;
|
||||
|
||||
/* For each iteration, apply a causal filter followed by a non causal filters along the column
|
||||
* mapped to the current thread invocation. */
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
/* Causal Pass:
|
||||
* Sequentially apply a causal filter running from bottom to top by mixing the value of the
|
||||
* pixel in the column with the average value of the previous output and next input in the same
|
||||
* column. */
|
||||
for (int y = 0; y < height; y++) {
|
||||
int2 texel = int2(gl_GlobalInvocationID.x, y);
|
||||
float4 previous_output = imageLoad(vertical_img, texel - int2(0, i));
|
||||
float4 current_input = imageLoad(vertical_img, texel);
|
||||
float4 next_input = imageLoad(vertical_img, texel + int2(0, i));
|
||||
|
||||
float4 neighbor_average = (previous_output + next_input) / 2.0f;
|
||||
float4 causal_output = mix(current_input, neighbor_average, fade_factor);
|
||||
imageStore(vertical_img, texel, causal_output);
|
||||
imageFence(vertical_img);
|
||||
}
|
||||
|
||||
/* Non Causal Pass:
|
||||
* Sequentially apply a non causal filter running from top to bottom by mixing the value of the
|
||||
* pixel in the column with the average value of the previous output and next input in the same
|
||||
* column. */
|
||||
for (int y = height - 1; y >= 0; y--) {
|
||||
int2 texel = int2(gl_GlobalInvocationID.x, y);
|
||||
float4 previous_output = imageLoad(vertical_img, texel + int2(0, i));
|
||||
float4 current_input = imageLoad(vertical_img, texel);
|
||||
float4 next_input = imageLoad(vertical_img, texel - int2(0, i));
|
||||
|
||||
float4 neighbor_average = (previous_output + next_input) / 2.0f;
|
||||
float4 non_causal_output = mix(current_input, neighbor_average, fade_factor);
|
||||
imageStore(vertical_img, texel, non_causal_output);
|
||||
imageFence(vertical_img);
|
||||
}
|
||||
}
|
||||
|
||||
/* For each pixel in the column mapped to the current invocation thread, add the result of the
|
||||
* horizontal pass to the vertical pass. */
|
||||
for (int y = 0; y < height; y++) {
|
||||
int2 texel = int2(gl_GlobalInvocationID.x, y);
|
||||
float4 horizontal = texture_load(horizontal_tx, texel);
|
||||
float4 vertical = imageLoad(vertical_img, texel);
|
||||
float4 combined = horizontal + vertical;
|
||||
imageStore(vertical_img, texel, float4(combined.rgb, 1.0f));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_streaks_accumulate)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 attenuated_streak = texture_load(streak_tx, texel) * attenuation_factor;
|
||||
float4 current_accumulated_streaks = imageLoad(accumulated_streaks_img, texel);
|
||||
float4 combined_streaks = current_accumulated_streaks + attenuated_streak;
|
||||
imageStore(accumulated_streaks_img, texel, float4(combined_streaks.rgb, 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_streaks_filter)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int2 input_size = texture_size(input_streak_tx);
|
||||
|
||||
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
|
||||
* to get the coordinates into the sampler's expected [0, 1] range. Similarly, transform the
|
||||
* vector into the sampler's space by dividing by the input size. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(input_size);
|
||||
float2 vector = streak_vector / float2(input_size);
|
||||
|
||||
/* Load three equally spaced neighbors to the current pixel in the direction of the streak
|
||||
* vector. */
|
||||
float4 neighbors[3];
|
||||
neighbors[0] = texture(input_streak_tx, coordinates + vector);
|
||||
neighbors[1] = texture(input_streak_tx, coordinates + vector * 2.0f);
|
||||
neighbors[2] = texture(input_streak_tx, coordinates + vector * 3.0f);
|
||||
|
||||
/* Attenuate the value of two of the channels for each of the neighbors by multiplying by the
|
||||
* color modulator. The particular channels for each neighbor were chosen to be visually similar
|
||||
* to the modulation pattern of chromatic aberration. */
|
||||
neighbors[0].gb *= color_modulator;
|
||||
neighbors[1].rg *= color_modulator;
|
||||
neighbors[2].rb *= color_modulator;
|
||||
|
||||
/* Compute the weighted sum of all neighbors using the given fade factors as weights. The
|
||||
* weights are expected to be lower for neighbors that are further away. */
|
||||
float4 weighted_neighbors_sum = float4(0.0f);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
weighted_neighbors_sum += fade_factors[i] * neighbors[i];
|
||||
}
|
||||
|
||||
/* The output is the average between the center color and the weighted sum of the neighbors.
|
||||
* Which intuitively mean that highlights will spread in the direction of the streak, which is
|
||||
* the desired result. */
|
||||
float4 center_color = texture(input_streak_tx, coordinates);
|
||||
float4 output_color = (center_color + weighted_neighbors_sum) / 2.0f;
|
||||
imageStore(output_streak_img, texel, output_color);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_sun_beams_shared)
|
||||
|
||||
#include "gpu_shader_common_hash.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
#include "gpu_shader_math_safe_lib.glsl"
|
||||
|
||||
/* Returns an index for a position along the path between the texel and the source.
|
||||
*
|
||||
* When jitter is enabled, the position index is computed using the Global Shift
|
||||
* sampling technique: a hash-based global shift is applied to the indices which is then
|
||||
* factored to cover the range [0, steps].
|
||||
* Without jitter, the integer index `i` is returned
|
||||
* directly.
|
||||
*/
|
||||
float get_sample_position(int i, [[maybe_unused]] float random_offset)
|
||||
{
|
||||
#if defined(JITTER)
|
||||
return safe_divide(i + random_offset, 1.0f - jitter_factor);
|
||||
#else
|
||||
return i;
|
||||
#endif
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float2 input_size = float2(texture_size(input_tx));
|
||||
|
||||
/* The number of steps is the distance in pixels from the source to the current texel. With at
|
||||
* least a single step and at most the user specified maximum ray length, which is proportional
|
||||
* to the diagonal pixel count. */
|
||||
float unbounded_steps = max(1.0f, distance(float2(texel), source * input_size));
|
||||
int steps = min(max_steps, int(unbounded_steps));
|
||||
|
||||
/* We integrate from the current pixel to the source pixel, so compute the start coordinates and
|
||||
* step vector in the direction to source. Notice that the step vector is still computed from the
|
||||
* unbounded steps, such that the total integration length becomes limited by the bounded steps,
|
||||
* and thus by the maximum ray length. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / input_size;
|
||||
float2 vector_to_source = source - coordinates;
|
||||
float2 step_vector = vector_to_source / unbounded_steps;
|
||||
|
||||
float accumulated_weight = 0.0f;
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
|
||||
#if defined(JITTER)
|
||||
int number_of_steps = int((1.0f - jitter_factor) * steps);
|
||||
#else
|
||||
int number_of_steps = steps;
|
||||
#endif
|
||||
float random_offset = hash_uint2_to_float(uint(texel.x), uint(texel.y));
|
||||
|
||||
for (int i = 0; i <= number_of_steps; i++) {
|
||||
float position_index = get_sample_position(i, random_offset);
|
||||
float2 position = coordinates + position_index * step_vector;
|
||||
|
||||
/* We are already past the image boundaries, and any future steps are also past the image
|
||||
* boundaries, so break. */
|
||||
if (any(lessThan(position, float2(0.0f))) || any(greaterThan(position, float2(1.0f)))) {
|
||||
break;
|
||||
}
|
||||
|
||||
float4 sample_color = texture(input_tx, position);
|
||||
|
||||
/* Attenuate the contributions of pixels that are further away from the source using a
|
||||
* quadratic falloff. */
|
||||
float weight = square(1.0f - position_index / float(steps));
|
||||
|
||||
accumulated_weight += weight;
|
||||
accumulated_color += sample_color * weight;
|
||||
}
|
||||
|
||||
if (accumulated_weight != 0.0f) {
|
||||
accumulated_color /= accumulated_weight;
|
||||
}
|
||||
else {
|
||||
accumulated_color = texture(input_tx, coordinates);
|
||||
}
|
||||
imageStore(output_img, texel, accumulated_color);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_write_glare_output)
|
||||
|
||||
#include "gpu_shader_common_color_utils.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(imageSize(output_img));
|
||||
float4 glare_color = texture(input_tx, normalized_coordinates);
|
||||
|
||||
/* Adjust saturation of glare. */
|
||||
float4 glare_hsva;
|
||||
rgb_to_hsv(glare_color, glare_hsva);
|
||||
glare_hsva.y = clamp(glare_hsva.y * saturation, 0.0f, 1.0f);
|
||||
float4 glare_rgba;
|
||||
hsv_to_rgb(glare_hsva, glare_rgba);
|
||||
|
||||
float3 adjusted_glare_value = glare_rgba.rgb * tint;
|
||||
imageStore(output_img, texel, float4(adjusted_glare_value, 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_glare_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_glare_write_highlights_output)
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(imageSize(output_img));
|
||||
imageStore(output_img, texel, texture(input_tx, normalized_coordinates));
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_lens_distortion_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_horizontal_lens_distortion)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Get the normalized coordinates of the pixel centers. */
|
||||
float2 normalized_texel = (float2(texel) + float2(0.5f)) / float2(texture_size(input_tx));
|
||||
|
||||
/* Sample the red and blue channels shifted by the dispersion amount. */
|
||||
const float4 red = texture(input_tx, normalized_texel + float2(dispersion, 0.0f));
|
||||
const float4 green = texture_load(input_tx, texel);
|
||||
const float4 blue = texture(input_tx, normalized_texel - float2(dispersion, 0.0f));
|
||||
const float alpha = (red.a + green.a + blue.a) / 3.0f;
|
||||
|
||||
imageStore(output_img, texel, float4(red.r, green.g, blue.b, alpha));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_id_mask_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_id_mask)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float input_mask_value = texture_load(input_mask_tx, texel).x;
|
||||
float mask = int(round(input_mask_value)) == index ? 1.0f : 0.0f;
|
||||
|
||||
imageStore(output_mask_img, texel, float4(mask));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_image_coordinates_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_image_coordinates_normalized)
|
||||
|
||||
void main()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
const float2 coordinates = float2(data_offset + texel) + 0.5f;
|
||||
const float2 normalized_coordinates = coordinates / float2(display_size);
|
||||
|
||||
imageStore(output_img, texel, float4(normalized_coordinates, float2(0.0f)));
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_image_coordinates_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_image_coordinates_pixel)
|
||||
|
||||
void main()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
imageStore(output_img, texel, int4(data_offset + texel, int2(0)));
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_image_coordinates_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_image_coordinates_uniform)
|
||||
|
||||
#include "gpu_shader_math_vector_reduce_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
const int max_display_size = reduce_max(display_size);
|
||||
|
||||
const float2 coordinates = float2(data_offset + texel) + 0.5f;
|
||||
const float2 centered_coordinates = coordinates - float2(display_size) / 2.0f;
|
||||
const float2 normalized_coordinates = (centered_coordinates / max_display_size) * 2.0f;
|
||||
|
||||
imageStore(output_img, texel, float4(normalized_coordinates, float2(0.0f)));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_image_crop_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_image_crop_float4)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
imageStore(output_img, texel, texture_load(input_tx, texel + lower_bound));
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* The in-paint operation uses a jump flood algorithm to flood the region to be in-painted with the
|
||||
* pixels at its boundary. The algorithms expects an input image whose values are those returned by
|
||||
* the initialize_jump_flooding_value function, given the texel location and a boolean specifying
|
||||
* if the pixel is a boundary one.
|
||||
*
|
||||
* Technically, we needn't restrict the output to just the boundary pixels, since the algorithm can
|
||||
* still operate if the interior of the region was also included. However, the algorithm operates
|
||||
* more accurately when the number of pixels to be flooded is minimum. */
|
||||
|
||||
#include "infos/compositor_inpaint_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_inpaint_compute_boundary)
|
||||
|
||||
#include "gpu_shader_compositor_jump_flooding_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Identify if any of the 8 neighbors around the center pixel are transparent. */
|
||||
bool has_transparent_neighbors = false;
|
||||
for (int j = -1; j <= 1; j++) {
|
||||
for (int i = -1; i <= 1; i++) {
|
||||
int2 offset = int2(i, j);
|
||||
|
||||
/* Exempt the center pixel. */
|
||||
if (all(notEqual(offset, int2(0)))) {
|
||||
if (texture_load(input_tx, texel + offset).a < 1.0f) {
|
||||
has_transparent_neighbors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The pixels at the boundary are those that are opaque and have transparent neighbors. */
|
||||
bool is_opaque = texture_load(input_tx, texel).a == 1.0f;
|
||||
bool is_boundary_pixel = is_opaque && has_transparent_neighbors;
|
||||
|
||||
/* Encode the boundary information in the format expected by the jump flooding algorithm. */
|
||||
int2 jump_flooding_value = initialize_jump_flooding_value(texel, is_boundary_pixel);
|
||||
|
||||
imageStore(boundary_img, texel, int4(jump_flooding_value, int2(0)));
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_inpaint_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_inpaint_compute_region)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 color = texture_load(input_tx, texel);
|
||||
|
||||
/* An opaque pixel, not part of the inpainting region, write the original color. */
|
||||
if (color.a == 1.0f) {
|
||||
imageStore(output_img, texel, color);
|
||||
return;
|
||||
}
|
||||
|
||||
float distance_to_boundary = texture_load(distance_to_boundary_tx, texel).x;
|
||||
|
||||
/* Further than the inpainting distance, not part of the inpainting region, write the original
|
||||
* color. */
|
||||
if (distance_to_boundary > max_distance) {
|
||||
imageStore(output_img, texel, color);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mix the inpainted color with the original color using its alpha because semi-transparent areas
|
||||
* are considered to be partially inpainted. */
|
||||
float4 inpainted_color = texture_load(inpainted_region_tx, texel);
|
||||
imageStore(output_img, texel, float4(mix(inpainted_color.rgb, color.rgb, color.a), 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Fill the inpainting region by sampling the color of the nearest boundary pixel. Additionally,
|
||||
* compute some information about the inpainting region, like the distance to the boundary, as well
|
||||
* as the blur radius to use to smooth out that region. */
|
||||
|
||||
#include "infos/compositor_inpaint_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_inpaint_fill_region)
|
||||
|
||||
#include "gpu_shader_compositor_jump_flooding_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
#include "gpu_shader_math_constants_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 color = texture_load(input_tx, texel);
|
||||
|
||||
/* An opaque pixel, not part of the inpainting region. */
|
||||
if (color.a == 1.0f) {
|
||||
imageStore(filled_region_img, texel, color);
|
||||
imageStore(smoothing_radius_img, texel, float4(0.0f));
|
||||
imageStore(distance_to_boundary_img, texel, float4(0.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
int2 closest_boundary_texel = texture_load(flooded_boundary_tx, texel).xy;
|
||||
float distance_to_boundary = distance(float2(texel), float2(closest_boundary_texel));
|
||||
imageStore(distance_to_boundary_img, texel, float4(distance_to_boundary));
|
||||
|
||||
/* We follow this shader by a blur shader that smooths out the inpainting region, where the blur
|
||||
* radius is the radius of the circle that touches the boundary. We can imagine the blur window
|
||||
* to be inscribed in that circle and thus the blur radius is the distance to the boundary
|
||||
* divided by square root two. As a performance optimization, we limit the blurring to areas that
|
||||
* will affect the inpainting region, that is, whose distance to boundary is less than double the
|
||||
* inpainting distance. Additionally, we clamp to the distance to the inpainting distance since
|
||||
* areas outside of the clamp range only indirectly affect the inpainting region due to blurring
|
||||
* and thus needn't use higher blur radii. */
|
||||
float blur_window_size = min(float(max_distance), distance_to_boundary) / M_SQRT2;
|
||||
bool skip_smoothing = distance_to_boundary > (max_distance * 2.0f);
|
||||
float smoothing_radius = skip_smoothing ? 0.0f : blur_window_size;
|
||||
imageStore(smoothing_radius_img, texel, float4(smoothing_radius));
|
||||
|
||||
/* Mix the boundary color with the original color using its alpha because semi-transparent areas
|
||||
* are considered to be partially inpainted. */
|
||||
float4 boundary_color = texture_load(input_tx, closest_boundary_texel);
|
||||
imageStore(filled_region_img, texel, mix(boundary_color, color, color.a));
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* This shader implements a single pass of the Jump Flooding algorithm described in sections 3.1
|
||||
* and 3.2 of the paper:
|
||||
*
|
||||
* Rong, Guodong, and Tiow-Seng Tan. "Jump flooding in GPU with applications to Voronoi diagram
|
||||
* and distance transform." Proceedings of the 2006 symposium on Interactive 3D graphics and
|
||||
* games. 2006.
|
||||
*
|
||||
* The shader is a straightforward implementation of the aforementioned sections of the paper,
|
||||
* noting that the nil special value in the paper is equivalent to JUMP_FLOODING_NON_FLOODED_VALUE.
|
||||
*
|
||||
* The `gpu_shader_compositor_jump_flooding_lib.glsl` library contains the necessary utility
|
||||
* functions to initialize and encode the jump flooding values. */
|
||||
|
||||
#include "infos/compositor_jump_flooding_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_jump_flooding)
|
||||
|
||||
#include "gpu_shader_compositor_jump_flooding_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_vector_lib.glsl"
|
||||
#include "gpu_shader_utildefines_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* For each of the previously flooded pixels in the 3x3 window of the given step size around the
|
||||
* center pixel, find the position of the closest seed pixel that is closest to the current
|
||||
* center pixel. */
|
||||
int2 closest_seed_texel = int2(0.0f);
|
||||
float minimum_squared_distance = FLT_MAX;
|
||||
for (int j = -1; j <= 1; j++) {
|
||||
for (int i = -1; i <= 1; i++) {
|
||||
int2 offset = int2(i, j) * step_size;
|
||||
|
||||
/* Use #JUMP_FLOODING_NON_FLOODED_VALUE as a fallback value to exempt out of bound pixels
|
||||
* from the loop as can be seen in the following continue condition. */
|
||||
int4 fallback = int4(JUMP_FLOODING_NON_FLOODED_VALUE, int2(0));
|
||||
int2 jump_flooding_value = texture_load(input_tx, texel + offset, fallback).xy;
|
||||
|
||||
/* The pixel is either not flooded yet or is out of bound, so skip it. */
|
||||
if (all(equal(jump_flooding_value, JUMP_FLOODING_NON_FLOODED_VALUE))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The neighboring pixel is flooded, so its flooding value is the texel of the closest seed
|
||||
* pixel to this neighboring pixel. */
|
||||
int2 closest_seed_texel_to_neighbor = jump_flooding_value;
|
||||
|
||||
/* Compute the squared distance to the neighbor's closest seed pixel. */
|
||||
float squared_distance = distance_squared(float2(closest_seed_texel_to_neighbor),
|
||||
float2(texel));
|
||||
|
||||
if (squared_distance < minimum_squared_distance) {
|
||||
minimum_squared_distance = squared_distance;
|
||||
closest_seed_texel = closest_seed_texel_to_neighbor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the minimum squared distance is still #FLT_MAX, that means the loop never got past the
|
||||
* continue condition and thus no flooding happened. If flooding happened, we encode the closest
|
||||
* seed texel in the format expected by the algorithm. */
|
||||
bool flooding_happened = minimum_squared_distance != FLT_MAX;
|
||||
int2 jump_flooding_value = encode_jump_flooding_value(closest_seed_texel, flooding_happened);
|
||||
|
||||
imageStore(output_img, texel, int4(jump_flooding_value, int2(0)));
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_keying_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_keying_compute_image)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
int3 compute_saturation_indices(float3 v)
|
||||
{
|
||||
int index_of_max = ((v.x > v.y) ? ((v.x > v.z) ? 0 : 2) : ((v.y > v.z) ? 1 : 2));
|
||||
int2 other_indices = (int2(index_of_max) + int2(1, 2)) % 3;
|
||||
int min_index = min(other_indices.x, other_indices.y);
|
||||
int max_index = max(other_indices.x, other_indices.y);
|
||||
return int3(index_of_max, max_index, min_index);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 key = texture_load(key_tx, texel);
|
||||
float4 color = texture_load(input_tx, texel);
|
||||
float matte = texture_load(matte_tx, texel).x;
|
||||
|
||||
/* Alpha multiply the matte to the image. */
|
||||
color *= matte;
|
||||
|
||||
/* Color despill. */
|
||||
int3 indices = compute_saturation_indices(key.rgb);
|
||||
float weighted_average = mix(color[indices.y], color[indices.z], despill_balance);
|
||||
color[indices.x] -= max(0.0f, (color[indices.x] - weighted_average) * despill_factor);
|
||||
|
||||
imageStore(output_img, texel, color);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_keying_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_keying_compute_matte)
|
||||
|
||||
#include "gpu_shader_common_color_utils.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_vector_lib.glsl"
|
||||
#include "gpu_shader_math_vector_reduce_lib.glsl"
|
||||
|
||||
int3 compute_saturation_indices(float3 v)
|
||||
{
|
||||
int index_of_max = ((v.x > v.y) ? ((v.x > v.z) ? 0 : 2) : ((v.y > v.z) ? 1 : 2));
|
||||
int2 other_indices = (int2(index_of_max) + int2(1, 2)) % 3;
|
||||
int min_index = min(other_indices.x, other_indices.y);
|
||||
int max_index = max(other_indices.x, other_indices.y);
|
||||
return int3(index_of_max, max_index, min_index);
|
||||
}
|
||||
|
||||
float compute_saturation(float4 color, int3 indices)
|
||||
{
|
||||
float weighted_average = mix(color[indices.y], color[indices.z], key_balance);
|
||||
return (color[indices.x] - weighted_average) * abs(1.0f - weighted_average);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 input_color = texture_load(input_tx, texel);
|
||||
|
||||
/* We assume that the keying screen will not be overexposed in the image, so if the input
|
||||
* brightness is high, we assume the pixel is opaque. */
|
||||
if (reduce_min(input_color) > 1.0f) {
|
||||
imageStore(output_img, texel, float4(1.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
float4 key_color = texture_load(key_tx, texel);
|
||||
int3 key_saturation_indices = compute_saturation_indices(key_color.rgb);
|
||||
float input_saturation = compute_saturation(input_color, key_saturation_indices);
|
||||
float key_saturation = compute_saturation(key_color, key_saturation_indices);
|
||||
|
||||
float matte;
|
||||
if (input_saturation < 0) {
|
||||
/* Means main channel of pixel is different from screen, assume this is completely a
|
||||
* foreground. */
|
||||
matte = 1.0f;
|
||||
}
|
||||
else if (input_saturation >= key_saturation) {
|
||||
/* Matched main channels and higher saturation on pixel is treated as completely background. */
|
||||
matte = 0.0f;
|
||||
}
|
||||
else {
|
||||
matte = 1.0f - clamp(input_saturation / key_saturation, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, float4(matte));
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_keying_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_keying_extract_chroma)
|
||||
|
||||
#include "gpu_shader_common_color_utils.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 color_ycca;
|
||||
rgba_to_ycca_itu_709(texture_load(input_tx, texel), color_ycca);
|
||||
|
||||
imageStore(output_img, texel, color_ycca);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_keying_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_keying_replace_chroma)
|
||||
|
||||
#include "gpu_shader_common_color_utils.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 color_ycca;
|
||||
rgba_to_ycca_itu_709(texture_load(input_tx, texel), color_ycca);
|
||||
|
||||
color_ycca.yz = texture_load(new_chroma_tx, texel).yz;
|
||||
|
||||
float4 color_rgba;
|
||||
ycca_to_rgba_itu_709(color_ycca, color_rgba);
|
||||
|
||||
imageStore(output_img, texel, color_rgba);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_keying_screen_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_keying_screen)
|
||||
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
|
||||
shared float2 cached_marker_positions[gl_WorkGroupSize.x * gl_WorkGroupSize.y];
|
||||
shared float4 cached_marker_colors[gl_WorkGroupSize.x * gl_WorkGroupSize.y];
|
||||
#define CACHE_SIZE (gl_WorkGroupSize.x * gl_WorkGroupSize.y)
|
||||
|
||||
/* Cache the initial part of the marker SSBOs in shared memory to make the interpolation loop
|
||||
* faster. */
|
||||
void populate_cache()
|
||||
{
|
||||
if (int(gl_LocalInvocationIndex) < number_of_markers) {
|
||||
cached_marker_positions[gl_LocalInvocationIndex] = marker_positions[gl_LocalInvocationIndex];
|
||||
cached_marker_colors[gl_LocalInvocationIndex] = marker_colors[gl_LocalInvocationIndex];
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
int2 size = imageSize(output_img);
|
||||
float2 normalized_pixel_location = (float2(texel) + float2(0.5f)) / float2(size);
|
||||
float squared_shape_parameter = square(1.0f / smoothness);
|
||||
|
||||
populate_cache();
|
||||
|
||||
/* Interpolate the markers using a Gaussian Radial Basis Function Interpolation with the
|
||||
* reciprocal of the smoothness as the shaping parameter. Equal weights are assigned to all
|
||||
* markers, so no RBF fitting is required. */
|
||||
float sum_of_weights = 0.0f;
|
||||
float4 weighted_sum = float4(0.0f);
|
||||
for (int i = 0; i < number_of_markers; i++) {
|
||||
bool use_cache = i < int(CACHE_SIZE);
|
||||
|
||||
float2 marker_position = use_cache ? cached_marker_positions[i] : marker_positions[i];
|
||||
float2 difference = normalized_pixel_location - marker_position;
|
||||
float squared_distance = dot(difference, difference);
|
||||
float gaussian = exp(-squared_distance * squared_shape_parameter);
|
||||
|
||||
float4 marker_color = use_cache ? cached_marker_colors[i] : marker_colors[i];
|
||||
weighted_sum += marker_color * gaussian;
|
||||
sum_of_weights += gaussian;
|
||||
}
|
||||
weighted_sum /= sum_of_weights;
|
||||
|
||||
imageStore(output_img, texel, weighted_sum);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_keying_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_keying_tweak_matte_with_edges)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float matte = texture_load(input_matte_tx, texel).x;
|
||||
|
||||
/* Search the neighborhood around the current matte value and identify if it lies along the
|
||||
* edges of the matte. This is needs to be computed only when we need to compute the edges output
|
||||
* or tweak the levels of the matte. */
|
||||
bool is_edge = false;
|
||||
#if defined(COMPUTE_EDGES)
|
||||
bool compute_edges = true;
|
||||
#else
|
||||
bool compute_edges = black_level != 0.0f || white_level != 1.0f;
|
||||
#endif
|
||||
if (compute_edges) {
|
||||
/* Count the number of neighbors whose matte is sufficiently similar to the current matte,
|
||||
* as controlled by the edge_tolerance factor. */
|
||||
int count = 0;
|
||||
for (int j = -edge_search_radius; j <= edge_search_radius; j++) {
|
||||
for (int i = -edge_search_radius; i <= edge_search_radius; i++) {
|
||||
float neighbor_matte = texture_load(input_matte_tx, texel + int2(i, j)).x;
|
||||
count += int(distance(matte, neighbor_matte) < edge_tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the number of neighbors that are sufficiently similar to the center matte is less that
|
||||
* 90% of the total number of neighbors, then that means the variance is high in that areas
|
||||
* and it is considered an edge. */
|
||||
is_edge = count < ((edge_search_radius * 2 + 1) * (edge_search_radius * 2 + 1)) * 0.9f;
|
||||
}
|
||||
|
||||
float tweaked_matte = matte;
|
||||
|
||||
/* Remap the matte using the black and white levels, but only for areas that are not on the edge
|
||||
* of the matte to preserve details. Also check for equality between levels to avoid zero
|
||||
* division. */
|
||||
if (!is_edge && white_level != black_level) {
|
||||
tweaked_matte = clamp((matte - black_level) / (white_level - black_level), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
/* Exclude unwanted areas using the provided garbage matte, 1 means unwanted, so invert the
|
||||
* garbage matte and take the minimum. */
|
||||
float garbage_matte = texture_load(garbage_matte_tx, texel).x;
|
||||
tweaked_matte = min(tweaked_matte, 1.0f - garbage_matte);
|
||||
|
||||
/* Include wanted areas that were incorrectly keyed using the provided core matte. */
|
||||
float core_matte = texture_load(core_matte_tx, texel).x;
|
||||
tweaked_matte = max(tweaked_matte, core_matte);
|
||||
|
||||
imageStore(output_matte_img, texel, float4(tweaked_matte));
|
||||
#if defined(COMPUTE_EDGES)
|
||||
imageStore(output_edges_img, texel, float4(is_edge ? 1.0f : 0.0f));
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_kuwahara_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_variable_size)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
|
||||
/* An implementation of the Anisotropic Kuwahara filter described in the paper:
|
||||
*
|
||||
* Kyprianidis, Jan Eric, Henry Kang, and Jurgen Dollner. "Image and video abstraction by
|
||||
* anisotropic Kuwahara filtering." 2009.
|
||||
*
|
||||
* But with the polynomial weighting functions described in the paper:
|
||||
*
|
||||
* Kyprianidis, Jan Eric, et al. "Anisotropic Kuwahara Filtering with Polynomial Weighting
|
||||
* Functions." 2010.
|
||||
*
|
||||
* And the sector weight function described in the paper:
|
||||
*
|
||||
* Kyprianidis, Jan Eric. "Image and video abstraction by multi-scale anisotropic Kuwahara
|
||||
* filtering." 2011.
|
||||
*/
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
#include "gpu_shader_math_constants_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* The structure tensor is encoded in a vec4 using a column major storage order, as can be seen
|
||||
* in the compositor_kuwahara_anisotropic_compute_structure_tensor.glsl shader. */
|
||||
float4 encoded_structure_tensor = texture_load(structure_tensor_tx, texel);
|
||||
float dxdx = encoded_structure_tensor.x;
|
||||
float dxdy = encoded_structure_tensor.y;
|
||||
float dydy = encoded_structure_tensor.w;
|
||||
|
||||
/* Compute the first and second eigenvalues of the structure tensor using the equations in
|
||||
* section "3.1 Orientation and Anisotropy Estimation" of the paper. */
|
||||
float eigenvalue_first_term = (dxdx + dydy) / 2.0f;
|
||||
float eigenvalue_square_root_term = sqrt(square(dxdx - dydy) + 4.0f * square(dxdy)) / 2.0f;
|
||||
float first_eigenvalue = eigenvalue_first_term + eigenvalue_square_root_term;
|
||||
float second_eigenvalue = eigenvalue_first_term - eigenvalue_square_root_term;
|
||||
|
||||
/* Compute the normalized eigenvector of the structure tensor oriented in direction of the
|
||||
* minimum rate of change using the equations in section "3.1 Orientation and Anisotropy
|
||||
* Estimation" of the paper. */
|
||||
float2 eigenvector = float2(first_eigenvalue - dxdx, -dxdy);
|
||||
float eigenvector_length = length(eigenvector);
|
||||
float2 unit_eigenvector = eigenvector_length != 0.0f ? eigenvector / eigenvector_length :
|
||||
float2(1.0f);
|
||||
|
||||
/* Compute the amount of anisotropy using equations in section "3.1 Orientation and Anisotropy
|
||||
* Estimation" of the paper. The anisotropy ranges from 0 to 1, where 0 corresponds to isotropic
|
||||
* and 1 corresponds to entirely anisotropic regions. */
|
||||
float eigenvalue_sum = first_eigenvalue + second_eigenvalue;
|
||||
float eigenvalue_difference = first_eigenvalue - second_eigenvalue;
|
||||
float anisotropy = eigenvalue_sum > 0.0f ? eigenvalue_difference / eigenvalue_sum : 0.0f;
|
||||
|
||||
#if defined(VARIABLE_SIZE)
|
||||
float radius = max(0.0f, texture_load(size_tx, texel).x);
|
||||
#elif defined(CONSTANT_SIZE)
|
||||
float radius = max(0.0f, size);
|
||||
#endif
|
||||
if (radius == 0) {
|
||||
imageStore(output_img, texel, texture_load(input_tx, texel));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute the width and height of an ellipse that is more width-elongated for high anisotropy
|
||||
* and more circular for low anisotropy, controlled using the eccentricity factor. Since the
|
||||
* anisotropy is in the [0, 1] range, the width factor tends to 1 as the eccentricity tends to
|
||||
* infinity and tends to infinity when the eccentricity tends to zero. This is based on the
|
||||
* equations in section "3.2. Anisotropic Kuwahara Filtering" of the paper. */
|
||||
float ellipse_width_factor = (eccentricity + anisotropy) / eccentricity;
|
||||
float ellipse_width = ellipse_width_factor * radius;
|
||||
float ellipse_height = radius / ellipse_width_factor;
|
||||
|
||||
/* Compute the cosine and sine of the angle that the eigenvector makes with the x axis. Since the
|
||||
* eigenvector is normalized, its x and y components are the cosine and sine of the angle it
|
||||
* makes with the x axis. */
|
||||
float cosine = unit_eigenvector.x;
|
||||
float sine = unit_eigenvector.y;
|
||||
|
||||
/* Compute an inverse transformation matrix that represents an ellipse of the given width and
|
||||
* height and makes and an angle with the x axis of the given cosine and sine. This is an inverse
|
||||
* matrix, so it transforms the ellipse into a disk of unit radius. */
|
||||
float2x2 inverse_ellipse_matrix = float2x2(cosine / ellipse_width,
|
||||
-sine / ellipse_height,
|
||||
sine / ellipse_width,
|
||||
cosine / ellipse_height);
|
||||
|
||||
/* Compute the bounding box of a zero centered ellipse whose major axis is aligned with the
|
||||
* eigenvector and has the given width and height. This is based on the equations described in:
|
||||
*
|
||||
* https://iquilezles.org/articles/ellipses/
|
||||
*
|
||||
* Notice that we only compute the upper bound, the lower bound is just negative that since the
|
||||
* ellipse is zero centered. Also notice that we take the ceiling of the bounding box, just to
|
||||
* ensure the filter window is at least 1x1. */
|
||||
float2 ellipse_major_axis = ellipse_width * unit_eigenvector;
|
||||
float2 ellipse_minor_axis = ellipse_height * unit_eigenvector.yx * float2(-1, 1);
|
||||
int2 ellipse_bounds = int2(ceil(sqrt(square(ellipse_major_axis) + square(ellipse_minor_axis))));
|
||||
|
||||
/* Compute the overlap polynomial parameters for 8-sector ellipse based on the equations in
|
||||
* section "3 Alternative Weighting Functions" of the polynomial weights paper. More on this
|
||||
* later in the code. */
|
||||
constexpr int number_of_sectors = 8;
|
||||
float sector_center_overlap_parameter = 2.0f / radius;
|
||||
float sector_envelope_angle = ((3.0f / 2.0f) * M_PI) / number_of_sectors;
|
||||
float cross_sector_overlap_parameter = (sector_center_overlap_parameter +
|
||||
cos(sector_envelope_angle)) /
|
||||
square(sin(sector_envelope_angle));
|
||||
|
||||
/* We need to compute the weighted mean of color and squared color of each of the 8 sectors of
|
||||
* the ellipse, so we declare arrays for accumulating those and initialize them in the next code
|
||||
* section. */
|
||||
float4 weighted_mean_of_squared_color_of_sectors[8];
|
||||
float4 weighted_mean_of_color_of_sectors[8];
|
||||
float sum_of_weights_of_sectors[8];
|
||||
|
||||
/* The center pixel (0, 0) is exempt from the main loop below for reasons that are explained in
|
||||
* the first if statement in the loop, so we need to accumulate its color, squared color, and
|
||||
* weight separately first. Luckily, the zero coordinates of the center pixel zeros out most of
|
||||
* the complex computations below, and it can easily be shown that the weight for the center
|
||||
* pixel in all sectors is simply (1 / number_of_sectors). */
|
||||
float4 center_color = texture_load(input_tx, texel);
|
||||
float4 center_color_squared = center_color * center_color;
|
||||
float center_weight = 1.0f / number_of_sectors;
|
||||
float4 weighted_center_color = center_color * center_weight;
|
||||
float4 weighted_center_color_squared = center_color_squared * center_weight;
|
||||
for (int i = 0; i < number_of_sectors; i++) {
|
||||
weighted_mean_of_squared_color_of_sectors[i] = weighted_center_color_squared;
|
||||
weighted_mean_of_color_of_sectors[i] = weighted_center_color;
|
||||
sum_of_weights_of_sectors[i] = center_weight;
|
||||
}
|
||||
|
||||
/* Loop over the window of pixels inside the bounding box of the ellipse. However, we utilize the
|
||||
* fact that ellipses are mirror symmetric along the horizontal axis, so we reduce the window to
|
||||
* only the upper two quadrants, and compute each two mirrored pixels at the same time using the
|
||||
* same weight as an optimization. */
|
||||
for (int j = 0; j <= ellipse_bounds.y; j++) {
|
||||
for (int i = -ellipse_bounds.x; i <= ellipse_bounds.x; i++) {
|
||||
/* Since we compute each two mirrored pixels at the same time, we need to also exempt the
|
||||
* pixels whose x coordinates are negative and their y coordinates are zero, that's because
|
||||
* those are mirrored versions of the pixels whose x coordinates are positive and their y
|
||||
* coordinates are zero, and we don't want to compute and accumulate them twice. Moreover, we
|
||||
* also need to exempt the center pixel with zero coordinates for the same reason, however,
|
||||
* since the mirror of the center pixel is itself, it need to be accumulated separately,
|
||||
* hence why we did that in the code section just before this loop. */
|
||||
if (j == 0 && i <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Map the pixels of the ellipse into a unit disk, exempting any points that are not part of
|
||||
* the ellipse or disk. */
|
||||
float2 disk_point = inverse_ellipse_matrix * float2(i, j);
|
||||
float disk_point_length_squared = dot(disk_point, disk_point);
|
||||
if (disk_point_length_squared > 1.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* While each pixel belongs to a single sector in the ellipse, we expand the definition of
|
||||
* a sector a bit to also overlap with other sectors as illustrated in Figure 8 of the
|
||||
* polynomial weights paper. So each pixel may contribute to multiple sectors, and thus we
|
||||
* compute its weight in each of the 8 sectors. */
|
||||
float sector_weights[8];
|
||||
|
||||
/* We evaluate the weighting polynomial at each of the 8 sectors by rotating the disk point
|
||||
* by 45 degrees and evaluating the weighting polynomial at each incremental rotation. To
|
||||
* avoid potentially expensive rotations, we utilize the fact that rotations by 90 degrees
|
||||
* are simply swapping of the coordinates and negating the x component. We also note that
|
||||
* since the y term of the weighting polynomial is squared, it is not affected by the sign
|
||||
* and can be computed once for the x and once for the y coordinates. So we compute every
|
||||
* other even-indexed 4 weights by successive 90 degree rotations as discussed. */
|
||||
float2 polynomial = sector_center_overlap_parameter -
|
||||
cross_sector_overlap_parameter * square(disk_point);
|
||||
sector_weights[0] = square(max(0.0f, disk_point.y + polynomial.x));
|
||||
sector_weights[2] = square(max(0.0f, -disk_point.x + polynomial.y));
|
||||
sector_weights[4] = square(max(0.0f, -disk_point.y + polynomial.x));
|
||||
sector_weights[6] = square(max(0.0f, disk_point.x + polynomial.y));
|
||||
|
||||
/* Then we rotate the disk point by 45 degrees, which is a simple expression involving a
|
||||
* constant as can be demonstrated by applying a 45 degree rotation matrix. */
|
||||
float2 rotated_disk_point = M_SQRT1_2 *
|
||||
float2(disk_point.x - disk_point.y, disk_point.x + disk_point.y);
|
||||
|
||||
/* Finally, we compute every other odd-index 4 weights starting from the 45 degrees rotated
|
||||
* disk point. */
|
||||
float2 rotated_polynomial = sector_center_overlap_parameter -
|
||||
cross_sector_overlap_parameter * square(rotated_disk_point);
|
||||
sector_weights[1] = square(max(0.0f, rotated_disk_point.y + rotated_polynomial.x));
|
||||
sector_weights[3] = square(max(0.0f, -rotated_disk_point.x + rotated_polynomial.y));
|
||||
sector_weights[5] = square(max(0.0f, -rotated_disk_point.y + rotated_polynomial.x));
|
||||
sector_weights[7] = square(max(0.0f, rotated_disk_point.x + rotated_polynomial.y));
|
||||
|
||||
/* We compute a radial Gaussian weighting component such that pixels further away from the
|
||||
* sector center gets attenuated, and we also divide by the sum of sector weights to
|
||||
* normalize them, since the radial weight will eventually be multiplied to the sector weight
|
||||
* below. */
|
||||
float sector_weights_sum = sector_weights[0] + sector_weights[1] + sector_weights[2] +
|
||||
sector_weights[3] + sector_weights[4] + sector_weights[5] +
|
||||
sector_weights[6] + sector_weights[7];
|
||||
float radial_gaussian_weight = exp(-M_PI * disk_point_length_squared) / sector_weights_sum;
|
||||
|
||||
/* Load the color of the pixel and its mirrored pixel and compute their square. */
|
||||
float4 upper_color = texture_load(input_tx, texel + int2(i, j));
|
||||
float4 lower_color = texture_load(input_tx, texel - int2(i, j));
|
||||
float4 upper_color_squared = upper_color * upper_color;
|
||||
float4 lower_color_squared = lower_color * lower_color;
|
||||
|
||||
for (int k = 0; k < number_of_sectors; k++) {
|
||||
float weight = sector_weights[k] * radial_gaussian_weight;
|
||||
|
||||
/* Accumulate the pixel to each of the sectors multiplied by the sector weight. */
|
||||
int upper_index = k;
|
||||
sum_of_weights_of_sectors[upper_index] += weight;
|
||||
weighted_mean_of_color_of_sectors[upper_index] += upper_color * weight;
|
||||
weighted_mean_of_squared_color_of_sectors[upper_index] += upper_color_squared * weight;
|
||||
|
||||
/* Accumulate the mirrored pixel to each of the sectors multiplied by the sector weight. */
|
||||
int lower_index = (k + number_of_sectors / 2) % number_of_sectors;
|
||||
sum_of_weights_of_sectors[lower_index] += weight;
|
||||
weighted_mean_of_color_of_sectors[lower_index] += lower_color * weight;
|
||||
weighted_mean_of_squared_color_of_sectors[lower_index] += lower_color_squared * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute the weighted sum of mean of sectors, such that sectors with lower standard deviation
|
||||
* gets more significant weight than sectors with higher standard deviation. */
|
||||
float sum_of_weights = 0.0f;
|
||||
float4 weighted_sum = float4(0.0f);
|
||||
for (int i = 0; i < number_of_sectors; i++) {
|
||||
weighted_mean_of_color_of_sectors[i] /= sum_of_weights_of_sectors[i];
|
||||
weighted_mean_of_squared_color_of_sectors[i] /= sum_of_weights_of_sectors[i];
|
||||
|
||||
float4 color_mean = weighted_mean_of_color_of_sectors[i];
|
||||
float4 squared_color_mean = weighted_mean_of_squared_color_of_sectors[i];
|
||||
float4 color_variance = abs(squared_color_mean - color_mean * color_mean);
|
||||
|
||||
float standard_deviation = dot(sqrt(color_variance.rgb), float3(1.0f));
|
||||
|
||||
/* Compute the sector weight based on the weight function introduced in section "3.3.1
|
||||
* Single-scale Filtering" of the multi-scale paper. Use a threshold of 0.02 to avoid zero
|
||||
* division and avoid artifacts in homogeneous regions as demonstrated in the paper. */
|
||||
float weight = 1.0f / pow(max(0.02f, standard_deviation), sharpness);
|
||||
|
||||
sum_of_weights += weight;
|
||||
weighted_sum += color_mean * weight;
|
||||
}
|
||||
|
||||
/* Fallback to the original color if all sector weights are zero due to very high standard
|
||||
* deviation and sharpness. */
|
||||
if (sum_of_weights == 0.0f) {
|
||||
weighted_sum = center_color;
|
||||
}
|
||||
else {
|
||||
weighted_sum /= sum_of_weights;
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, weighted_sum);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Computes the structure tensor of the image using a Dirac delta window function as described in
|
||||
* section "3.2 Local Structure Estimation" of the paper:
|
||||
*
|
||||
* Kyprianidis, Jan Eric. "Image and video abstraction by multi-scale anisotropic Kuwahara
|
||||
* filtering." 2011.
|
||||
*
|
||||
* The structure tensor should then be smoothed using a Gaussian function to eliminate high
|
||||
* frequency details. */
|
||||
|
||||
#include "infos/compositor_kuwahara_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_compute_structure_tensor)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* The weight kernels of the filter optimized for rotational symmetry described in section
|
||||
* "3.2.1 Gradient Calculation". */
|
||||
constexpr float corner_weight = 0.182f;
|
||||
constexpr float center_weight = 1.0f - 2.0f * corner_weight;
|
||||
|
||||
float3 x_partial_derivative = texture_load(input_tx, texel + int2(-1, 1)).rgb * -corner_weight +
|
||||
texture_load(input_tx, texel + int2(-1, 0)).rgb * -center_weight +
|
||||
texture_load(input_tx, texel + int2(-1, -1)).rgb * -corner_weight +
|
||||
texture_load(input_tx, texel + int2(1, 1)).rgb * corner_weight +
|
||||
texture_load(input_tx, texel + int2(1, 0)).rgb * center_weight +
|
||||
texture_load(input_tx, texel + int2(1, -1)).rgb * corner_weight;
|
||||
|
||||
float3 y_partial_derivative = texture_load(input_tx, texel + int2(-1, 1)).rgb * corner_weight +
|
||||
texture_load(input_tx, texel + int2(0, 1)).rgb * center_weight +
|
||||
texture_load(input_tx, texel + int2(1, 1)).rgb * corner_weight +
|
||||
texture_load(input_tx, texel + int2(-1, -1)).rgb * -corner_weight +
|
||||
texture_load(input_tx, texel + int2(0, -1)).rgb * -center_weight +
|
||||
texture_load(input_tx, texel + int2(1, -1)).rgb * -corner_weight;
|
||||
|
||||
float dxdx = dot(x_partial_derivative, x_partial_derivative);
|
||||
float dxdy = dot(x_partial_derivative, y_partial_derivative);
|
||||
float dydy = dot(y_partial_derivative, y_partial_derivative);
|
||||
|
||||
/* We encode the structure tensor in a vec4 using a column major storage order. */
|
||||
float4 structure_tensor = float4(dxdx, dxdy, dxdy, dydy);
|
||||
|
||||
imageStore(structure_tensor_img, texel, structure_tensor);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_kuwahara_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_kuwahara_classic_convolution_variable_size)
|
||||
|
||||
#include "gpu_shader_compositor_summed_area_table_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_utildefines_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
#if defined(VARIABLE_SIZE)
|
||||
int radius = max(0, int(texture_load(size_tx, texel).x));
|
||||
#elif defined(CONSTANT_SIZE)
|
||||
int radius = max(0, size);
|
||||
#endif
|
||||
|
||||
float4 mean_of_squared_color_of_quadrants[4] = float4_array(
|
||||
float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f));
|
||||
float4 mean_of_color_of_quadrants[4] = float4_array(
|
||||
float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f));
|
||||
|
||||
/* Compute the above statistics for each of the quadrants around the current pixel. */
|
||||
for (int q = 0; q < 4; q++) {
|
||||
/* A fancy expression to compute the sign of the quadrant q. */
|
||||
int2 sign = int2((q % 2) * 2 - 1, ((q / 2) * 2 - 1));
|
||||
|
||||
int2 lower_bound = texel - int2(sign.x > 0 ? 0 : radius, sign.y > 0 ? 0 : radius);
|
||||
int2 upper_bound = texel + int2(sign.x < 0 ? 0 : radius, sign.y < 0 ? 0 : radius);
|
||||
|
||||
/* Limit the quadrants to the image bounds. */
|
||||
int2 image_bound = imageSize(output_img) - int2(1);
|
||||
int2 corrected_lower_bound = min(image_bound, max(int2(0), lower_bound));
|
||||
int2 corrected_upper_bound = min(image_bound, max(int2(0), upper_bound));
|
||||
int2 region_size = corrected_upper_bound - corrected_lower_bound + int2(1);
|
||||
int quadrant_pixel_count = region_size.x * region_size.y;
|
||||
|
||||
#if defined(SUMMED_AREA_TABLE)
|
||||
mean_of_color_of_quadrants[q] = summed_area_table_sum(table_tx, lower_bound, upper_bound);
|
||||
mean_of_squared_color_of_quadrants[q] = summed_area_table_sum(
|
||||
squared_table_tx, lower_bound, upper_bound);
|
||||
#else
|
||||
for (int j = 0; j <= radius; j++) {
|
||||
for (int i = 0; i <= radius; i++) {
|
||||
float4 color = texture_load(input_tx, texel + int2(i, j) * sign, float4(0.0f));
|
||||
mean_of_color_of_quadrants[q] += color;
|
||||
mean_of_squared_color_of_quadrants[q] += color * color;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
mean_of_color_of_quadrants[q] /= quadrant_pixel_count;
|
||||
mean_of_squared_color_of_quadrants[q] /= quadrant_pixel_count;
|
||||
}
|
||||
|
||||
/* Find the quadrant which has the minimum variance. */
|
||||
float minimum_variance = FLT_MAX;
|
||||
float4 mean_color_of_chosen_quadrant = mean_of_color_of_quadrants[0];
|
||||
for (int q = 0; q < 4; q++) {
|
||||
float4 color_mean = mean_of_color_of_quadrants[q];
|
||||
float4 squared_color_mean = mean_of_squared_color_of_quadrants[q];
|
||||
float4 color_variance = squared_color_mean - color_mean * color_mean;
|
||||
|
||||
float variance = dot(color_variance.rgb, float3(1.0f));
|
||||
if (variance < minimum_variance) {
|
||||
minimum_variance = variance;
|
||||
mean_color_of_chosen_quadrant = color_mean;
|
||||
}
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, mean_color_of_chosen_quadrant);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_map_uv_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_map_uv)
|
||||
|
||||
#include "gpu_shader_bicubic_sampler_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float2 uv_coordinates = texture_load(uv_tx, texel).xy;
|
||||
|
||||
float4 sampled_color = SAMPLER_FUNCTION(input_tx, uv_coordinates);
|
||||
|
||||
/* The UV texture is assumed to contain an alpha channel as its third channel, since the UV
|
||||
* coordinates might be defined in only a subset area of the UV texture as mentioned. In that
|
||||
* case, the alpha is typically opaque at the subset area and transparent everywhere else, and
|
||||
* alpha pre-multiplication is then performed. This format of having an alpha channel in the UV
|
||||
* coordinates is the format used by UV passes in render engines, hence the mentioned logic. */
|
||||
float alpha = texture_load(uv_tx, texel).z;
|
||||
|
||||
float4 result = sampled_color * alpha;
|
||||
|
||||
imageStore(output_img, texel, result);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_map_uv_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_map_uv_anisotropic)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
/* A shared table that stores the UV coordinates of all pixels in the work group. This is necessary
|
||||
* to avoid recomputing UV coordinates when computing the gradients necessary for anisotropic
|
||||
* filtering, see the implementation for more information. */
|
||||
shared float2 uv_coordinates_table[gl_WorkGroupSize.x][gl_WorkGroupSize.y];
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float2 uv_coordinates = texture_load(uv_tx, texel).xy;
|
||||
float2 uv_size = float2(texture_size(uv_tx));
|
||||
|
||||
/* Store the UV coordinates into the shared table and issue a barrier to later compute the
|
||||
* gradients from the table. */
|
||||
int2 table_index = int2(gl_LocalInvocationID.xy);
|
||||
uv_coordinates_table[table_index.x][table_index.y] = uv_coordinates;
|
||||
barrier();
|
||||
|
||||
/* Compute the partial derivative of the UV coordinates along the x direction using a finite
|
||||
* difference approximation. Odd invocations use a forward finite difference equation while even
|
||||
* invocations use a backward finite difference equation. This is done such that invocations at
|
||||
* the edges of the work group wouldn't need access to pixels that are outside of the work group.
|
||||
*
|
||||
* The x_step value is 1 for even invocations and when added to the x table index and multiplied
|
||||
* by the result yields a standard forward finite difference equation. The x_step value is -1 for
|
||||
* odd invocations and when added to the x table index and multiplied by the result yields a
|
||||
* standard backward finite difference equation, because multiplication by -1 flips the order of
|
||||
* subtraction.
|
||||
*
|
||||
* Divide by the input size since textureGrad assumes derivatives with respect to texel
|
||||
* coordinates. */
|
||||
int x_step = (table_index.x % 2) * -2 + 1;
|
||||
float2 x_neighbor = uv_coordinates_table[table_index.x + x_step][table_index.y];
|
||||
float2 x_gradient = (x_neighbor - uv_coordinates) * x_step / uv_size.x;
|
||||
|
||||
/* Compute the partial derivative of the UV coordinates along the y direction using a
|
||||
* finite difference approximation. See the previous code section for more information. */
|
||||
int y_step = (table_index.y % 2) * -2 + 1;
|
||||
float2 y_neighbor = uv_coordinates_table[table_index.x][table_index.y + y_step];
|
||||
float2 y_gradient = (y_neighbor - uv_coordinates) * y_step / uv_size.y;
|
||||
|
||||
/* Sample the input using the UV coordinates passing in the computed gradients in order to
|
||||
* utilize the anisotropic filtering capabilities of the sampler. */
|
||||
float4 sampled_color = textureGrad(input_tx, uv_coordinates, x_gradient, y_gradient);
|
||||
|
||||
/* The UV texture is assumed to contain an alpha channel as its third channel, since the UV
|
||||
* coordinates might be defined in only a subset area of the UV texture as mentioned. In that
|
||||
* case, the alpha is typically opaque at the subset area and transparent everywhere else, and
|
||||
* alpha pre-multiplication is then performed. This format of having an alpha channel in the UV
|
||||
* coordinates is the format used by UV passes in render engines, hence the mentioned logic. */
|
||||
float alpha = texture_load(uv_tx, texel).z;
|
||||
|
||||
float4 result = sampled_color * alpha;
|
||||
|
||||
imageStore(output_img, texel, result);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* The mask to SDF operation uses a jump flood algorithm to flood the region to be distance
|
||||
* transformed with the pixels at its boundary. The algorithms expects an input image whose values
|
||||
* are those returned by the initialize_jump_flooding_value function, given the texel location and
|
||||
* a boolean specifying if the pixel is a boundary one.
|
||||
*
|
||||
* Technically, we needn't restrict the output to just the boundary pixels, since the algorithm can
|
||||
* still operate if the interior of the region was also included. However, the algorithm operates
|
||||
* more accurately when the number of pixels to be flooded is minimum. */
|
||||
|
||||
#include "infos/compositor_mask_to_sdf_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_mask_to_sdf_compute_boundary)
|
||||
|
||||
#include "gpu_shader_compositor_jump_flooding_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Identify if any of the 8 neighbors around the center pixel are unmasked. */
|
||||
bool has_unmasked_neighbors = false;
|
||||
for (int j = -1; j <= 1; j++) {
|
||||
for (int i = -1; i <= 1; i++) {
|
||||
const int2 offset = int2(i, j);
|
||||
|
||||
/* Exempt the center pixel. */
|
||||
if (all(equal(offset, int2(0)))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!bool(texture_load(mask_tx, texel + offset).x)) {
|
||||
has_unmasked_neighbors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The pixels at the boundary are those that are masked and have unmasked neighbors. */
|
||||
const bool is_masked = bool(texture_load(mask_tx, texel).x);
|
||||
const bool is_boundary_pixel = is_masked && has_unmasked_neighbors;
|
||||
|
||||
/* Encode the boundary information in the format expected by the jump flooding algorithm. */
|
||||
const int2 jump_flooding_value = initialize_jump_flooding_value(texel, is_boundary_pixel);
|
||||
|
||||
imageStore(boundary_img, texel, int4(jump_flooding_value, int2(0)));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_mask_to_sdf_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_mask_to_sdf_compute_distance)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
const bool is_inside_mask = bool(texture_load(mask_tx, texel).x);
|
||||
const int2 closest_boundary_texel = texture_load(flooded_boundary_tx, texel).xy;
|
||||
const float distance_to_boundary = distance(float2(texel), float2(closest_boundary_texel));
|
||||
const float signed_distance = is_inside_mask ? -distance_to_boundary : distance_to_boundary;
|
||||
|
||||
imageStore(distance_img, texel, float4(signed_distance));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_morphological_blur_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_morphological_blur_dilate)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float input_value = texture_load(input_tx, texel).x;
|
||||
float blurred_value = imageLoad(blurred_input_img, texel).x;
|
||||
|
||||
imageStore(blurred_input_img, texel, float4(OPERATOR(input_value, blurred_value)));
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_morphological_distance_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_morphological_distance_dilate)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_utildefines_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int2 image_size = texture_size(input_tx);
|
||||
|
||||
int radius_squared = radius * radius;
|
||||
|
||||
/* Compute the start and end bounds of the window such that no out-of-bounds processing happen
|
||||
* in the loops. */
|
||||
int2 start = max(texel - radius, int2(0)) - texel;
|
||||
int2 end = min(texel + radius + 1, image_size) - texel;
|
||||
|
||||
/* Find the minimum/maximum value in the circular window of the given radius around the pixel. By
|
||||
* circular window, we mean that pixels in the window whose distance to the center of window is
|
||||
* larger than the given radius are skipped and not considered. Consequently, the dilation or
|
||||
* erosion that take place produces round results as opposed to squarish ones. This is
|
||||
* essentially a morphological operator with a circular structuring element. The LIMIT value
|
||||
* should be FLT_MAX if OPERATOR is min and -FLT_MAX if OPERATOR is max. */
|
||||
float value = LIMIT;
|
||||
for (int y = start.y; y < end.y; y++) {
|
||||
int yy = y * y;
|
||||
for (int x = start.x; x < end.y; x++) {
|
||||
if (x * x + yy > radius_squared) {
|
||||
continue;
|
||||
}
|
||||
value = OPERATOR(value, texture_load(input_tx, texel + int2(x, y)).x);
|
||||
}
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, float4(value));
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* The Morphological Distance Feather operation is a linear combination between the result of two
|
||||
* operations. The first operation is a Gaussian blur with a radius equivalent to the dilate/erode
|
||||
* distance, which is straightforward and implemented as a separable filter similar to the blur
|
||||
* operation.
|
||||
*
|
||||
* The second operation is an approximation of a morphological inverse distance operation evaluated
|
||||
* at a distance falloff function. The result of a morphological inverse distance operation is a
|
||||
* narrow band distance field that starts at its maximum value at boundaries where a difference in
|
||||
* values took place and linearly deceases until it reaches zero in the span of a number of pixels
|
||||
* equivalent to the erode/dilate distance. Additionally, instead of linearly decreasing, the user
|
||||
* may choose a different falloff which is evaluated at the computed distance. For dilation, the
|
||||
* distance field decreases outwards, and for erosion, the distance field decreased inwards.
|
||||
*
|
||||
* The reason why the result of a Gaussian blur is mixed in with the distance field is because the
|
||||
* distance field is merely approximated and not accurately computed, the defects of which is more
|
||||
* apparent away from boundaries and especially at corners where the distance field should take a
|
||||
* circular shape. That's why the Gaussian blur is mostly mixed only further from boundaries.
|
||||
*
|
||||
* The morphological inverse distance operation is approximated using a separable implementation
|
||||
* and intertwined with the Gaussian blur implementation as follows. A search window of a radius
|
||||
* equivalent to the dilate/erode distance is applied on the image to find either the minimum or
|
||||
* maximum pixel value multiplied by its corresponding falloff value in the window. For dilation,
|
||||
* we try to find the maximum, and for erosion, we try to find the minimum. The implementation uses
|
||||
* an inverse function to find the minimum, specified through the FUNCTION macro. Additionally, we
|
||||
* also save the falloff value where the minimum or maximum was found. The found value will be that
|
||||
* of the narrow band distance field and the saved falloff value will be used as the mixing factor
|
||||
* with the Gaussian blur.
|
||||
*
|
||||
* To make sense of the aforementioned algorithm, assume we are dilating a binary image by 5 pixels
|
||||
* whose half has a value of 1 and the other half has a value of zero. Consider the following:
|
||||
*
|
||||
* - A pixel of value 1 already has the maximum possible value, so its value will remain unchanged
|
||||
* regardless of its position.
|
||||
* - A pixel of value 0 that is right at the boundary of the 1's region will have a maximum value
|
||||
* of around 0.8 depending on the falloff. That's because the search window intersects the 1's
|
||||
* region, which when multiplied by the falloff gives the first value of the falloff, which is
|
||||
* larger than the initially zero value computed at the center of the search window.
|
||||
* - A pixel of value 0 that is 3 pixels away from the boundary will have a maximum value of around
|
||||
* 0.4 depending on the falloff. That's because the search window intersects the 1's region,
|
||||
* which when multiplied by the falloff gives the third value of the falloff, which is larger
|
||||
* than the initially zero value computed at the center of the search window.
|
||||
* - Finally, a pixel of value 0 that is 6 pixels away from the boundary will have a maximum value
|
||||
* of 0, because the search window doesn't intersects the 1's region and only spans zero values.
|
||||
*
|
||||
* The previous example demonstrates how the distance field naturally arises, and the same goes for
|
||||
* the erode case, except the minimum value is computed instead.
|
||||
*/
|
||||
|
||||
#include "infos/compositor_morphological_distance_feather_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_morphological_distance_feather_dilate)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* A value for accumulating the blur result. */
|
||||
float accumulated_value = 0.0f;
|
||||
|
||||
/* Compute the contribution of the center pixel to the blur result. */
|
||||
float center_value = FUNCTION(texture_load(input_tx, texel).x);
|
||||
accumulated_value += center_value * texture_load(weights_tx, int2(0)).x;
|
||||
|
||||
/* Start with the center value as the maximum/minimum distance and reassign to the true maximum
|
||||
* or minimum in the search loop below. Additionally, the center falloff is always 1.0, so start
|
||||
* with that. */
|
||||
float limit_distance = center_value;
|
||||
float limit_distance_falloff = 1.0f;
|
||||
|
||||
/* Compute the contributions of the pixels to the right and left, noting that the weights and
|
||||
* falloffs textures only store the weights and falloffs for the positive half, but since the
|
||||
* they are both symmetric, the same weights and falloffs are used for the negative half and we
|
||||
* compute both of their contributions. */
|
||||
for (int i = 1; i < texture_size(weights_tx).x; i++) {
|
||||
float weight = texture_load(weights_tx, int2(i, 0)).x;
|
||||
float falloff = texture_load(falloffs_tx, int2(i, 0)).x;
|
||||
|
||||
/* Loop for two iterations, where s takes the value of -1 and 1, which is used as the sign
|
||||
* needed to evaluated the positive and negative sides as explain above. */
|
||||
for (int s = -1; s < 2; s += 2) {
|
||||
/* Compute the contribution of the pixel to the blur result. */
|
||||
float value = FUNCTION(texture_load(input_tx, texel + int2(s * i, 0)).x);
|
||||
accumulated_value += value * weight;
|
||||
|
||||
/* The distance is computed such that its highest value is the pixel value itself, so
|
||||
* multiply the distance falloff by the pixel value. */
|
||||
float falloff_distance = value * falloff;
|
||||
|
||||
/* Find either the maximum or the minimum for the dilate and erode cases respectively. */
|
||||
if (falloff_distance > limit_distance) {
|
||||
limit_distance = falloff_distance;
|
||||
limit_distance_falloff = falloff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Mix between the limit distance and the blurred accumulated value such that the limit distance
|
||||
* is used for pixels closer to the boundary and the blurred value is used for pixels away from
|
||||
* the boundary. */
|
||||
float value = mix(accumulated_value, limit_distance, limit_distance_falloff);
|
||||
|
||||
/* Write the value using the transposed texel. See the execute_distance_feather_horizontal_pass
|
||||
* method for more information on the rational behind this. */
|
||||
imageStore(output_img, texel.yx, float4(FUNCTION(value)));
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_morphological_distance_threshold_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_morphological_distance_threshold)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
const bool is_masked = texture_load(mask_tx, texel).x > 0.5f;
|
||||
const int2 closest_masked_texel = texture_load(flooded_masked_pixels_tx, texel).xy;
|
||||
const int2 closest_unmasked_texel = texture_load(flooded_unmasked_pixels_tx, texel).xy;
|
||||
const int2 closest_different_texel = is_masked ? closest_unmasked_texel : closest_masked_texel;
|
||||
const float distance_to_different = distance(float2(texel), float2(closest_different_texel));
|
||||
const float signed_distance = is_masked ? distance_to_different : -distance_to_different;
|
||||
const float value = clamp((signed_distance + distance_offset) / falloff_size, 0.0f, 1.0f);
|
||||
|
||||
imageStore(output_img, texel, float4(value));
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_morphological_distance_threshold_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_morphological_distance_threshold_seeds)
|
||||
|
||||
#include "gpu_shader_compositor_jump_flooding_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
const bool is_masked = texture_load(mask_tx, texel).x > 0.5f;
|
||||
|
||||
const int2 masked_jump_flooding_value = initialize_jump_flooding_value(texel, is_masked);
|
||||
imageStore(masked_pixels_img, texel, int4(masked_jump_flooding_value, int2(0)));
|
||||
|
||||
const int2 unmasked_jump_flooding_value = initialize_jump_flooding_value(texel, !is_masked);
|
||||
imageStore(unmasked_pixels_img, texel, int4(unmasked_jump_flooding_value, int2(0)));
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_morphological_step_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_morphological_step_dilate)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_utildefines_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Find the minimum/maximum value in the window of the given radius around the pixel. This is
|
||||
* essentially a morphological operator with a square structuring element. The LIMIT value should
|
||||
* be FLT_MAX if OPERATOR is min and -FLT_MAX if OPERATOR is max. */
|
||||
float value = LIMIT;
|
||||
for (int i = -radius; i <= radius; i++) {
|
||||
value = OPERATOR(value, texture_load(input_tx, texel + int2(i, 0), float4(LIMIT)).x);
|
||||
}
|
||||
|
||||
/* Write the value using the transposed texel. See the execute_step_horizontal_pass method for
|
||||
* more information on the rational behind this. */
|
||||
imageStore(output_img, texel.yx, float4(value));
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* This is identical to the EEVEE implementation in eevee_motion_blur_gather_comp.glsl with the
|
||||
* necessary adjustments to make it work for the compositor:
|
||||
*
|
||||
* - depth_compare() uses an inverted sign since the depth texture stores linear depth.
|
||||
* - The next velocities are inverted since the velocity textures stores the previous and next
|
||||
* velocities in the same direction.
|
||||
* - The samples count is a variable uniform and not fixed to 8 samples.
|
||||
* - The depth scale is constant and set to 100.
|
||||
* - The motion scale is defined by the shutter_speed. */
|
||||
|
||||
#include "infos/compositor_motion_blur_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_motion_blur)
|
||||
|
||||
#include "gpu_shader_compositor_motion_blur_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
#define g_depth_scale 100.0f
|
||||
|
||||
/* Interleaved gradient noise by Jorge Jimenez
|
||||
* http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare. */
|
||||
float interleaved_gradient_noise(int2 p)
|
||||
{
|
||||
return fract(52.9829189f * fract(0.06711056f * p.x + 0.00583715f * p.y));
|
||||
}
|
||||
|
||||
float2 spread_compare(float center_motion_length, float sample_motion_length, float offset_length)
|
||||
{
|
||||
return clamp(
|
||||
float2(center_motion_length, sample_motion_length) - offset_length + 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float2 depth_compare(float center_depth, float sample_depth)
|
||||
{
|
||||
float2 depth_scale = float2(g_depth_scale, -g_depth_scale);
|
||||
return clamp(0.5f + depth_scale * (sample_depth - center_depth), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
/* Kill contribution if not going the same direction. */
|
||||
float dir_compare(float2 offset, float2 sample_motion, float sample_motion_length)
|
||||
{
|
||||
if (sample_motion_length < 0.5f) {
|
||||
return 1.0f;
|
||||
}
|
||||
return (dot(offset, sample_motion) > 0.0f) ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
/* Return background (x) and foreground (y) weights. */
|
||||
float2 sample_weights(float center_depth,
|
||||
float sample_depth,
|
||||
float center_motion_length,
|
||||
float sample_motion_length,
|
||||
float offset_length)
|
||||
{
|
||||
/* Classify foreground/background. */
|
||||
float2 depth_weight = depth_compare(center_depth, sample_depth);
|
||||
/* Weight if sample is overlapping or under the center pixel. */
|
||||
float2 spread_weight = spread_compare(center_motion_length, sample_motion_length, offset_length);
|
||||
return depth_weight * spread_weight;
|
||||
}
|
||||
|
||||
struct Accumulator {
|
||||
float4 fg;
|
||||
float4 bg;
|
||||
/** x: Background, y: Foreground, z: dir. */
|
||||
float3 weight;
|
||||
};
|
||||
|
||||
void gather_sample(float2 screen_uv,
|
||||
float center_depth,
|
||||
float center_motion_len,
|
||||
float2 offset,
|
||||
float offset_len,
|
||||
const bool next,
|
||||
Accumulator &accum)
|
||||
{
|
||||
float2 sample_uv = screen_uv - offset / float2(texture_size(input_tx));
|
||||
float4 sample_vectors = texture(velocity_tx, sample_uv) *
|
||||
float4(float2(shutter_speed), float2(-shutter_speed));
|
||||
float2 sample_motion = (next) ? sample_vectors.zw : sample_vectors.xy;
|
||||
float sample_motion_len = length(sample_motion);
|
||||
float sample_depth = texture(depth_tx, sample_uv).r;
|
||||
float4 sample_color = texture(input_tx, sample_uv);
|
||||
|
||||
float3 weights;
|
||||
weights.xy = sample_weights(
|
||||
center_depth, sample_depth, center_motion_len, sample_motion_len, offset_len);
|
||||
weights.z = dir_compare(offset, sample_motion, sample_motion_len);
|
||||
weights.xy *= weights.z;
|
||||
|
||||
accum.fg += sample_color * weights.y;
|
||||
accum.bg += sample_color * weights.x;
|
||||
accum.weight += weights;
|
||||
}
|
||||
|
||||
void gather_blur(float2 screen_uv,
|
||||
float2 center_motion,
|
||||
float center_depth,
|
||||
float2 max_motion,
|
||||
float ofs,
|
||||
const bool next,
|
||||
Accumulator &accum)
|
||||
{
|
||||
float center_motion_len = length(center_motion);
|
||||
float max_motion_len = length(max_motion);
|
||||
|
||||
/* Tile boundaries randomization can fetch a tile where there is less motion than this pixel.
|
||||
* Fix this by overriding the max_motion. */
|
||||
if (max_motion_len < center_motion_len) {
|
||||
max_motion_len = center_motion_len;
|
||||
max_motion = center_motion;
|
||||
}
|
||||
|
||||
if (max_motion_len < 0.5f) {
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
float t, inc = 1.0f / float(samples_count);
|
||||
for (i = 0, t = ofs * inc; i < samples_count; i++, t += inc) {
|
||||
gather_sample(screen_uv,
|
||||
center_depth,
|
||||
center_motion_len,
|
||||
max_motion * t,
|
||||
max_motion_len * t,
|
||||
next,
|
||||
accum);
|
||||
}
|
||||
|
||||
if (center_motion_len < 0.5f) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0, t = ofs * inc; i < samples_count; i++, t += inc) {
|
||||
/* Also sample in center motion direction.
|
||||
* Allow recovering motion where there is conflicting
|
||||
* motion between foreground and background. */
|
||||
gather_sample(screen_uv,
|
||||
center_depth,
|
||||
center_motion_len,
|
||||
center_motion * t,
|
||||
center_motion_len * t,
|
||||
next,
|
||||
accum);
|
||||
}
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float2 uv = (float2(texel) + 0.5f) / float2(texture_size(input_tx));
|
||||
|
||||
/* Data of the center pixel of the gather (target). */
|
||||
float center_depth = texture_load(depth_tx, texel).x;
|
||||
float4 center_motion = texture(velocity_tx, uv) *
|
||||
float4(float2(shutter_speed), float2(-shutter_speed));
|
||||
float4 center_color = textureLod(input_tx, uv, 0.0f);
|
||||
|
||||
/* Randomize tile boundary to avoid ugly discontinuities. Randomize 1/4th of the tile.
|
||||
* Note this randomize only in one direction but in practice it's enough. */
|
||||
float rand = interleaved_gradient_noise(texel);
|
||||
int2 tile = (texel + int2(rand * 2.0f - 1.0f * float(MOTION_BLUR_TILE_SIZE) * 0.25f)) /
|
||||
MOTION_BLUR_TILE_SIZE;
|
||||
|
||||
float4 max_motion;
|
||||
/* Load dilation result from the indirection table. */
|
||||
int2 tile_prev;
|
||||
motion_blur_tile_indirection_load(tile_indirection_buf, MOTION_PREV, uint2(tile), tile_prev);
|
||||
max_motion.xy = texture_load(max_velocity_tx, tile_prev).xy;
|
||||
int2 tile_next;
|
||||
motion_blur_tile_indirection_load(tile_indirection_buf, MOTION_NEXT, uint2(tile), tile_next);
|
||||
max_motion.zw = texture_load(max_velocity_tx, tile_next).zw;
|
||||
|
||||
max_motion *= float4(float2(shutter_speed), float2(-shutter_speed));
|
||||
|
||||
Accumulator accum;
|
||||
accum.weight = float3(0.0f, 0.0f, 1.0f);
|
||||
accum.bg = float4(0.0f);
|
||||
accum.fg = float4(0.0f);
|
||||
/* First linear gather. time = [T - delta, T] */
|
||||
gather_blur(uv, center_motion.xy, center_depth, max_motion.xy, rand, false, accum);
|
||||
/* Second linear gather. time = [T, T + delta] */
|
||||
gather_blur(uv, center_motion.zw, center_depth, max_motion.zw, rand, true, accum);
|
||||
|
||||
#if 1 /* Own addition. Not present in reference implementation. */
|
||||
/* Avoid division by 0.0. */
|
||||
float w = 1.0f / (50.0f * float(samples_count) * 4.0f);
|
||||
accum.bg += center_color * w;
|
||||
accum.weight.x += w;
|
||||
/* NOTE: In Jimenez's presentation, they used center sample.
|
||||
* We use background color as it contains more information for foreground
|
||||
* elements that have not enough weights.
|
||||
* Yield better blur in complex motion. */
|
||||
center_color = accum.bg / accum.weight.x;
|
||||
#endif
|
||||
/* Merge background. */
|
||||
accum.fg += accum.bg;
|
||||
accum.weight.y += accum.weight.x;
|
||||
/* Balance accumulation for failed samples.
|
||||
* We replace the missing foreground by the background. */
|
||||
float blend_fac = clamp(1.0f - accum.weight.y / accum.weight.z, 0.0f, 1.0f);
|
||||
float4 out_color = (accum.fg / accum.weight.z) + center_color * blend_fac;
|
||||
|
||||
imageStore(output_img, texel, out_color);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Identical to eevee_motion_blur_dilate_comp.glsl but with minor adjustments to work with the
|
||||
* compositor. */
|
||||
|
||||
#include "infos/compositor_motion_blur_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_motion_blur_max_velocity_dilate)
|
||||
|
||||
#include "gpu_shader_compositor_motion_blur_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
#include "gpu_shader_math_constants_lib.glsl"
|
||||
|
||||
struct MotionRect {
|
||||
int2 bottom_left;
|
||||
int2 extent;
|
||||
};
|
||||
|
||||
MotionRect compute_motion_rect(int2 tile, float2 motion)
|
||||
{
|
||||
/* `ceil()` to number of tile touched. */
|
||||
int2 point1 = tile + int2(sign(motion) * ceil(abs(motion) / float(MOTION_BLUR_TILE_SIZE)));
|
||||
int2 point2 = tile;
|
||||
|
||||
int2 max_point = max(point1, point2);
|
||||
int2 min_point = min(point1, point2);
|
||||
/* Clamp to bounds. */
|
||||
max_point = min(max_point, texture_size(input_tx) - 1);
|
||||
min_point = max(min_point, int2(0));
|
||||
|
||||
MotionRect rect;
|
||||
rect.bottom_left = min_point;
|
||||
rect.extent = 1 + max_point - min_point;
|
||||
return rect;
|
||||
}
|
||||
|
||||
struct MotionLine {
|
||||
/** Origin of the line. */
|
||||
float2 origin;
|
||||
/** Normal to the line direction. */
|
||||
float2 normal;
|
||||
};
|
||||
|
||||
MotionLine compute_motion_line(int2 tile, float2 motion)
|
||||
{
|
||||
float magnitude = length(motion);
|
||||
float2 dir = magnitude != 0.0f ? motion / magnitude : motion;
|
||||
|
||||
MotionLine line;
|
||||
line.origin = float2(tile);
|
||||
/* Rotate 90 degrees counter-clockwise. */
|
||||
line.normal = float2(-dir.y, dir.x);
|
||||
return line;
|
||||
}
|
||||
|
||||
bool is_inside_motion_line(int2 tile, MotionLine motion_line)
|
||||
{
|
||||
/* NOTE: Everything in is tile unit. */
|
||||
float distance_to_line = dot(motion_line.normal, motion_line.origin - float2(tile));
|
||||
/* In order to be conservative and for simplicity, we use the tiles bounding circles.
|
||||
* Consider that both the tile and the line have bounding radius of M_SQRT1_2. */
|
||||
return abs(distance_to_line) < M_SQRT2;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 src_tile = int2(gl_GlobalInvocationID.xy);
|
||||
if (any(greaterThanEqual(src_tile, texture_size(input_tx)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
float4 max_motion = texture_load(input_tx, src_tile) *
|
||||
float4(float2(shutter_speed), float2(-shutter_speed));
|
||||
|
||||
MotionPayload payload_prv = motion_blur_tile_indirection_pack_payload(max_motion.xy,
|
||||
uint2(src_tile));
|
||||
MotionPayload payload_nxt = motion_blur_tile_indirection_pack_payload(max_motion.zw,
|
||||
uint2(src_tile));
|
||||
if (true) {
|
||||
/* Rectangular area (in tiles) where the motion vector spreads. */
|
||||
MotionRect motion_rect = compute_motion_rect(src_tile, max_motion.xy);
|
||||
MotionLine motion_line = compute_motion_line(src_tile, max_motion.xy);
|
||||
/* Do a conservative rasterization of the line of the motion vector line. */
|
||||
for (int x = 0; x < motion_rect.extent.x; x++) {
|
||||
for (int y = 0; y < motion_rect.extent.y; y++) {
|
||||
int2 tile = motion_rect.bottom_left + int2(x, y);
|
||||
if (is_inside_motion_line(tile, motion_line)) {
|
||||
motion_blur_tile_indirection_store(
|
||||
tile_indirection_buf, MOTION_PREV, uint2(tile), payload_prv);
|
||||
/* FIXME: This is a bit weird, but for some reason, we need the store the same vector in
|
||||
* the motion next so that weighting in gather pass is better. */
|
||||
motion_blur_tile_indirection_store(
|
||||
tile_indirection_buf, MOTION_NEXT, uint2(tile), payload_nxt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (true) {
|
||||
/* Rectangular area (in tiles) where the motion vector spreads. */
|
||||
MotionRect motion_rect = compute_motion_rect(src_tile, max_motion.zw);
|
||||
MotionLine motion_line = compute_motion_line(src_tile, max_motion.zw);
|
||||
/* Do a conservative rasterization of the line of the motion vector line. */
|
||||
for (int x = 0; x < motion_rect.extent.x; x++) {
|
||||
for (int y = 0; y < motion_rect.extent.y; y++) {
|
||||
int2 tile = motion_rect.bottom_left + int2(x, y);
|
||||
if (is_inside_motion_line(tile, motion_line)) {
|
||||
motion_blur_tile_indirection_store(
|
||||
tile_indirection_buf, MOTION_NEXT, uint2(tile), payload_nxt);
|
||||
/* FIXME: This is a bit weird, but for some reason, we need the store the same vector in
|
||||
* the motion next so that weighting in gather pass is better. */
|
||||
motion_blur_tile_indirection_store(
|
||||
tile_indirection_buf, MOTION_PREV, uint2(tile), payload_prv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_movie_distortion_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_movie_distortion)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
imageStore(output_img, texel, texture(input_tx, texture_load(distortion_grid_tx, texel).xy));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_normalize_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_normalize)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float value = texture_load(input_tx, texel).x;
|
||||
float normalized_value = (value - minimum) * scale;
|
||||
float clamped_value = clamp(normalized_value, 0.0f, 1.0f);
|
||||
imageStore(output_img, texel, float4(clamped_value));
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_pad_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_pad_zero_float4)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
if (zero_pad) {
|
||||
imageStore(output_img, texel, texture_load(input_tx, texel - size, float4(0.0f)));
|
||||
}
|
||||
else {
|
||||
imageStore(output_img, texel, texture_load(input_tx, texel - size));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_parallel_reduction_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_parallel_reduction_shared)
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_parallel_reduction_output_float4)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
#include "gpu_shader_utildefines_lib.glsl"
|
||||
|
||||
/* Texture Loading Utilities.
|
||||
*
|
||||
* Utility functions to load the appropriate type from the float4 returned by the generic texture
|
||||
* loading function. This is just a swizzle in most cases. */
|
||||
template<typename T> T load(float4 value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
template<> float load<float>(float4 value)
|
||||
{
|
||||
return value.x;
|
||||
}
|
||||
|
||||
template<> float2 load<float2>(float4 value)
|
||||
{
|
||||
return value.xy;
|
||||
}
|
||||
|
||||
template<> float4 load<float4>(float4 value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Image Storing Utilities.
|
||||
*
|
||||
* Utility functions to convert the input value into a float4 value since this is what the image
|
||||
* store functions expect. */
|
||||
float4 to_storage_value(float value)
|
||||
{
|
||||
return float4(value);
|
||||
}
|
||||
|
||||
float4 to_storage_value(float2 value)
|
||||
{
|
||||
return value.xyyy;
|
||||
}
|
||||
|
||||
float4 to_storage_value(float4 value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Group Shard Data Loading Utilities.
|
||||
*
|
||||
* Utility functions to load the element with the given index from the group shared data. */
|
||||
template<typename T> T load_shared_data(uint /*index*/)
|
||||
{
|
||||
return T(0);
|
||||
}
|
||||
|
||||
template<> float load_shared_data<float>(uint index)
|
||||
{
|
||||
return shared_variable_get(compositor_parallel_reduction_float_shared, reduction_data)[index];
|
||||
}
|
||||
|
||||
template<> float2 load_shared_data<float2>(uint index)
|
||||
{
|
||||
return shared_variable_get(compositor_parallel_reduction_float2_shared, reduction_data)[index];
|
||||
}
|
||||
|
||||
template<> float4 load_shared_data<float4>(uint index)
|
||||
{
|
||||
return shared_variable_get(compositor_parallel_reduction_float4_shared, reduction_data)[index];
|
||||
}
|
||||
|
||||
/* Group Shard Data Storing Utilities.
|
||||
*
|
||||
* Utility functions to store an element in the group shared data at the given index. */
|
||||
void store_shared_data(uint index, float data)
|
||||
{
|
||||
shared_variable_get(compositor_parallel_reduction_float_shared, reduction_data)[index] = data;
|
||||
}
|
||||
|
||||
void store_shared_data(uint index, float2 data)
|
||||
{
|
||||
shared_variable_get(compositor_parallel_reduction_float2_shared, reduction_data)[index] = data;
|
||||
}
|
||||
|
||||
void store_shared_data(uint index, float4 data)
|
||||
{
|
||||
shared_variable_get(compositor_parallel_reduction_float4_shared, reduction_data)[index] = data;
|
||||
}
|
||||
|
||||
/* Identity Functions.
|
||||
*
|
||||
* This should return the value of the type that does not affect the output of the reduction. For
|
||||
* instance, sum reductions have an identity of 0, while max value reductions have an identity of
|
||||
* the minimum possible float value. */
|
||||
struct IdentityZero {};
|
||||
struct IdentityMinimumFloat {};
|
||||
struct IdentityMaximumFloat {};
|
||||
struct IdentityLowerBound {};
|
||||
struct IdentityUpperBound {};
|
||||
|
||||
template<typename T, typename IdentityValue> T identity()
|
||||
{
|
||||
return T(0);
|
||||
}
|
||||
|
||||
template<> float identity<float, IdentityZero>()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
template<> float4 identity<float4, IdentityZero>()
|
||||
{
|
||||
return float4(0.0f);
|
||||
}
|
||||
|
||||
template<> float identity<float, IdentityMinimumFloat>()
|
||||
{
|
||||
return -FLT_MAX;
|
||||
}
|
||||
|
||||
template<> float2 identity<float2, IdentityMinimumFloat>()
|
||||
{
|
||||
return float2(-FLT_MAX);
|
||||
}
|
||||
|
||||
template<> float4 identity<float4, IdentityMinimumFloat>()
|
||||
{
|
||||
return float4(-FLT_MAX);
|
||||
}
|
||||
|
||||
template<> float identity<float, IdentityMaximumFloat>()
|
||||
{
|
||||
return FLT_MAX;
|
||||
}
|
||||
|
||||
template<> float4 identity<float4, IdentityMaximumFloat>()
|
||||
{
|
||||
return float4(FLT_MAX);
|
||||
}
|
||||
|
||||
template<> float identity<float, IdentityLowerBound>()
|
||||
{
|
||||
return push_constant_get(compositor_maximum_float_in_range, lower_bound);
|
||||
}
|
||||
|
||||
template<> float identity<float, IdentityUpperBound>()
|
||||
{
|
||||
return push_constant_get(compositor_minimum_float_in_range, upper_bound);
|
||||
}
|
||||
|
||||
/* Initialize Functions.
|
||||
*
|
||||
* This should compute the value that should be reduced from the loaded value. For instance, a sum
|
||||
* reduction would simply return the value while a sum squared difference reduction would compute
|
||||
* square the difference from a subtrahend. */
|
||||
struct InitializeDefault {};
|
||||
struct InitializeSquaredDifference {};
|
||||
struct InitializeMinimumInRange {};
|
||||
struct InitializeMaximumInRange {};
|
||||
struct InitializeLuminance {};
|
||||
struct InitializeLogLuminance {};
|
||||
|
||||
template<typename T, typename InitializeFunction> T initialize(float4 value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
template<> float initialize<float, InitializeDefault>(float4 value)
|
||||
{
|
||||
return value.x;
|
||||
}
|
||||
|
||||
template<> float2 initialize<float2, InitializeDefault>(float4 value)
|
||||
{
|
||||
return value.xy;
|
||||
}
|
||||
|
||||
template<> float4 initialize<float4, InitializeDefault>(float4 value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
template<> float4 initialize<float4, InitializeSquaredDifference>(float4 value)
|
||||
{
|
||||
float4 sub = push_constant_get(compositor_sum_squared_difference_color, subtrahend);
|
||||
return square(value - sub);
|
||||
}
|
||||
|
||||
template<> float initialize<float, InitializeMinimumInRange>(float4 value)
|
||||
{
|
||||
const float max = push_constant_get(compositor_minimum_float_in_range, upper_bound);
|
||||
const float min = push_constant_get(compositor_minimum_float_in_range, lower_bound);
|
||||
return ((value.x <= max) && (value.x >= min)) ? value.x : max;
|
||||
}
|
||||
|
||||
template<> float initialize<float, InitializeMaximumInRange>(float4 value)
|
||||
{
|
||||
float max = push_constant_get(compositor_maximum_float_in_range, upper_bound);
|
||||
float min = push_constant_get(compositor_maximum_float_in_range, lower_bound);
|
||||
return ((value.x <= max) && (value.x >= min)) ? value.x : min;
|
||||
}
|
||||
|
||||
template<> float initialize<float, InitializeLuminance>(float4 value)
|
||||
{
|
||||
float3 coefficients = push_constant_get(compositor_parallel_reduction_luminance_shared,
|
||||
luminance_coefficients);
|
||||
return dot(value.rgb, coefficients);
|
||||
}
|
||||
|
||||
template<> float initialize<float, InitializeLogLuminance>(float4 value)
|
||||
{
|
||||
float3 coefficients = push_constant_get(compositor_parallel_reduction_luminance_shared,
|
||||
luminance_coefficients);
|
||||
return log(max(dot(value.rgb, coefficients), 1e-5f));
|
||||
}
|
||||
|
||||
/* Reduce Functions.
|
||||
*
|
||||
* This should be a commutative and associative binary operator suitable for parallel reduction. */
|
||||
struct ReduceSum {};
|
||||
struct ReduceMinimum {};
|
||||
struct ReduceMaximum {};
|
||||
struct ReduceMinimumInRange {};
|
||||
struct ReduceMaximumInRange {};
|
||||
struct ReduceMaximumVelocity {};
|
||||
|
||||
template<typename T, typename ReduceFunction> T reduce(T lhs, T rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template<> float4 reduce<float4, ReduceSum>(float4 lhs, float4 rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template<> float reduce<float, ReduceSum>(float lhs, float rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template<> float reduce<float, ReduceMinimum>(float lhs, float rhs)
|
||||
{
|
||||
return min(lhs, rhs);
|
||||
}
|
||||
|
||||
template<> float4 reduce<float4, ReduceMinimum>(float4 lhs, float4 rhs)
|
||||
{
|
||||
return min(lhs, rhs);
|
||||
}
|
||||
|
||||
template<> float reduce<float, ReduceMaximum>(float lhs, float rhs)
|
||||
{
|
||||
return max(lhs, rhs);
|
||||
}
|
||||
|
||||
template<> float2 reduce<float2, ReduceMaximum>(float2 lhs, float2 rhs)
|
||||
{
|
||||
return max(lhs, rhs);
|
||||
}
|
||||
|
||||
template<> float4 reduce<float4, ReduceMaximum>(float4 lhs, float4 rhs)
|
||||
{
|
||||
return max(lhs, rhs);
|
||||
}
|
||||
|
||||
template<> float reduce<float, ReduceMinimumInRange>(float lhs, float rhs)
|
||||
{
|
||||
float min = push_constant_get(compositor_minimum_float_in_range, lower_bound);
|
||||
return ((rhs < lhs) && (rhs >= min)) ? rhs : lhs;
|
||||
}
|
||||
|
||||
template<> float reduce<float, ReduceMaximumInRange>(float lhs, float rhs)
|
||||
{
|
||||
float max = push_constant_get(compositor_maximum_float_in_range, upper_bound);
|
||||
return ((rhs > lhs) && (rhs <= max)) ? rhs : lhs;
|
||||
}
|
||||
|
||||
template<> float4 reduce<float4, ReduceMaximumVelocity>(float4 lhs, float4 rhs)
|
||||
{
|
||||
return float4(dot(lhs.xy, lhs.xy) > dot(rhs.xy, rhs.xy) ? lhs.xy : rhs.xy,
|
||||
dot(lhs.zw, lhs.zw) > dot(rhs.zw, rhs.zw) ? lhs.zw : rhs.zw);
|
||||
}
|
||||
|
||||
/* This shader reduces the given texture into a smaller texture of a size equal to the number of
|
||||
* work groups. In particular, each work group reduces its contents into a single value and writes
|
||||
* that value to a single pixel in the output image. The shader can be dispatched multiple times to
|
||||
* eventually reduce the image into a single pixel.
|
||||
*
|
||||
* The shader works by loading the whole data of each work group into a linear array, then it
|
||||
* reduces the second half of the array onto the first half of the array, then it reduces the
|
||||
* second quarter of the array onto the first quarter or the array, and so on until only one
|
||||
* element remains. The following figure illustrates the process for sum reduction on 8 elements.
|
||||
*
|
||||
* .---. .---. .---. .---. .---. .---. .---. .---.
|
||||
* | 0 | | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | | 7 | Original data.
|
||||
* '---' '---' '---' '---' '---' '---' '---' '---'
|
||||
* |.____|_____|_____|_____| | | |
|
||||
* || |.____|_____|___________| | |
|
||||
* || || |.____|_________________| |
|
||||
* || || || |.______________________| <--First reduction. Stride = 4.
|
||||
* || || || ||
|
||||
* .---. .---. .---. .----.
|
||||
* | 4 | | 6 | | 8 | | 10 | <--Data after first reduction.
|
||||
* '---' '---' '---' '----'
|
||||
* |.____|_____| |
|
||||
* || |.__________| <--Second reduction. Stride = 2.
|
||||
* || ||
|
||||
* .----. .----.
|
||||
* | 12 | | 16 | <--Data after second reduction.
|
||||
* '----' '----'
|
||||
* |.____|
|
||||
* || <--Third reduction. Stride = 1.
|
||||
* .----.
|
||||
* | 28 |
|
||||
* '----' <--Data after third reduction.
|
||||
*
|
||||
*
|
||||
* The shader is generic enough to implement many types of reductions. This is done by using
|
||||
* templating as can be seen in the implementation and the above templates.
|
||||
*
|
||||
* Doing the reduction in shared memory is faster, so a shared array is used where the whole data
|
||||
* of the work group will be loaded and reduced. The 2D structure of the work group is irrelevant
|
||||
* for reduction, so we just load the data in a 1D array to simplify reduction. */
|
||||
template<typename T, typename IdentityValue, typename InitializeFunction, typename ReduceFunction>
|
||||
void reduction()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Initialize the shared array for out of bound invocations using the identity value of the
|
||||
* operation. */
|
||||
if (any(lessThan(texel, int2(0))) || any(greaterThanEqual(texel, texture_size(input_tx)))) {
|
||||
store_shared_data(gl_LocalInvocationIndex, identity<T, IdentityValue>());
|
||||
}
|
||||
else {
|
||||
float4 value = texture_load_unbound(input_tx, texel);
|
||||
|
||||
/* Initialize the shared array given the previously loaded value. This step can be different
|
||||
* depending on whether this is the initial reduction pass or a latter one. Indeed, the input
|
||||
* texture for the initial reduction is the source texture itself, while the input texture to a
|
||||
* latter reduction pass is an intermediate texture after one or more reductions have happened.
|
||||
* This is significant because the data being reduced might be computed from the original data
|
||||
* and different from it, for instance, when summing the luminance of an image, the original
|
||||
* data is a float4 color, while the reduced data is a float luminance value. So for the
|
||||
* initial reduction pass, the luminance will be computed from the color, reduced, then stored
|
||||
* into an intermediate float texture. On the other hand, for latter reduction passes, the
|
||||
* luminance will be loaded directly and reduced without extra processing. */
|
||||
T data = is_initial_reduction ? initialize<T, InitializeFunction>(value) : load<T>(value);
|
||||
store_shared_data(gl_LocalInvocationIndex, data);
|
||||
}
|
||||
|
||||
/* Reduce the reduction data by half on every iteration until only one element remains. See the
|
||||
* above figure for an intuitive understanding of the stride value. */
|
||||
constexpr uint reduction_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y;
|
||||
for (uint stride = reduction_size / 2; stride > 0; stride /= 2) {
|
||||
barrier();
|
||||
|
||||
/* All invocations read shared memory into local variables. No writes allowed between these
|
||||
* barriers. */
|
||||
T my_value = load_shared_data<T>(gl_LocalInvocationIndex);
|
||||
T neighbor_value = load_shared_data<T>(gl_LocalInvocationIndex + stride);
|
||||
|
||||
/* Barrier ensures all reads complete before any writes begin. */
|
||||
barrier();
|
||||
|
||||
/* Only active invocations write reduced values back. Reads happen from the local variables. */
|
||||
if (gl_LocalInvocationIndex < stride) {
|
||||
T result = reduce<T, ReduceFunction>(my_value, neighbor_value);
|
||||
store_shared_data(gl_LocalInvocationIndex, result);
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, the result of the reduction is available as the first element in the reduction data,
|
||||
* write it to the pixel corresponding to the work group, making sure only the one thread writes
|
||||
* it. */
|
||||
barrier();
|
||||
if (gl_LocalInvocationIndex == 0) {
|
||||
imageStore(output_img, int2(gl_WorkGroupID.xy), to_storage_value(load_shared_data<T>(0)));
|
||||
}
|
||||
}
|
||||
|
||||
template void reduction<float4, IdentityZero, InitializeDefault, ReduceSum>();
|
||||
void reduce_sum_color()
|
||||
{
|
||||
reduction<float4, IdentityZero, InitializeDefault, ReduceSum>();
|
||||
}
|
||||
|
||||
template void reduction<float, IdentityZero, InitializeLogLuminance, ReduceSum>();
|
||||
void reduce_sum_log_luminance()
|
||||
{
|
||||
reduction<float, IdentityZero, InitializeLogLuminance, ReduceSum>();
|
||||
}
|
||||
|
||||
template void reduction<float4, IdentityZero, InitializeSquaredDifference, ReduceSum>();
|
||||
void reduce_sum_squared_difference_color()
|
||||
{
|
||||
reduction<float4, IdentityZero, InitializeSquaredDifference, ReduceSum>();
|
||||
}
|
||||
|
||||
template void reduction<float, IdentityMaximumFloat, InitializeDefault, ReduceMinimum>();
|
||||
void reduce_minimum_float()
|
||||
{
|
||||
reduction<float, IdentityMaximumFloat, InitializeDefault, ReduceMinimum>();
|
||||
}
|
||||
|
||||
template void reduction<float4, IdentityMaximumFloat, InitializeDefault, ReduceMinimum>();
|
||||
void reduce_minimum_float4()
|
||||
{
|
||||
reduction<float4, IdentityMaximumFloat, InitializeDefault, ReduceMinimum>();
|
||||
}
|
||||
|
||||
template void reduction<float, IdentityMaximumFloat, InitializeLuminance, ReduceMinimum>();
|
||||
void reduce_minimum_luminance()
|
||||
{
|
||||
reduction<float, IdentityMaximumFloat, InitializeLuminance, ReduceMinimum>();
|
||||
}
|
||||
|
||||
template void reduction<float,
|
||||
IdentityUpperBound,
|
||||
InitializeMinimumInRange,
|
||||
ReduceMinimumInRange>();
|
||||
void reduce_minimum_float_in_range()
|
||||
{
|
||||
reduction<float, IdentityUpperBound, InitializeMinimumInRange, ReduceMinimumInRange>();
|
||||
}
|
||||
|
||||
template void reduction<float, IdentityMinimumFloat, InitializeDefault, ReduceMaximum>();
|
||||
void reduce_maximum_float()
|
||||
{
|
||||
reduction<float, IdentityMinimumFloat, InitializeDefault, ReduceMaximum>();
|
||||
}
|
||||
|
||||
template void reduction<float2, IdentityMinimumFloat, InitializeDefault, ReduceMaximum>();
|
||||
void reduce_maximum_float2()
|
||||
{
|
||||
reduction<float2, IdentityMinimumFloat, InitializeDefault, ReduceMaximum>();
|
||||
}
|
||||
|
||||
template void reduction<float4, IdentityMinimumFloat, InitializeDefault, ReduceMaximum>();
|
||||
void reduce_maximum_float4()
|
||||
{
|
||||
reduction<float4, IdentityMinimumFloat, InitializeDefault, ReduceMaximum>();
|
||||
}
|
||||
|
||||
template void reduction<float, IdentityMinimumFloat, InitializeLuminance, ReduceMaximum>();
|
||||
void reduce_maximum_luminance()
|
||||
{
|
||||
reduction<float, IdentityMinimumFloat, InitializeLuminance, ReduceMaximum>();
|
||||
}
|
||||
|
||||
template void reduction<float,
|
||||
IdentityLowerBound,
|
||||
InitializeMaximumInRange,
|
||||
ReduceMaximumInRange>();
|
||||
void reduce_maximum_float_in_range()
|
||||
{
|
||||
reduction<float, IdentityLowerBound, InitializeMaximumInRange, ReduceMaximumInRange>();
|
||||
}
|
||||
|
||||
template void reduction<float4, IdentityZero, InitializeDefault, ReduceMaximumVelocity>();
|
||||
void reduce_max_velocity()
|
||||
{
|
||||
reduction<float4, IdentityZero, InitializeDefault, ReduceMaximumVelocity>();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_pixelate_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_pixelate)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
int2 start = (texel / int2(pixel_size)) * int2(pixel_size);
|
||||
int2 end = min(start + int2(pixel_size), texture_size(input_tx));
|
||||
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
for (int y = start.y; y < end.y; y++) {
|
||||
for (int x = start.x; x < end.x; x++) {
|
||||
accumulated_color += texture_load_unbound(input_tx, int2(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
int2 size = end - start;
|
||||
int count = size.x * size.y;
|
||||
imageStore(output_img, texel, accumulated_color / count);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_plane_deform_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_plane_deform)
|
||||
|
||||
#include "gpu_shader_bicubic_sampler_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float2 output_size = float2(imageSize(output_img));
|
||||
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / output_size;
|
||||
|
||||
float3 transformed_coordinates = to_float3x3(homography_matrix) * float3(coordinates, 1.0f);
|
||||
/* Point is at infinity and will be zero when sampled, so early exit. */
|
||||
if (transformed_coordinates.z == 0.0f) {
|
||||
imageStore(output_img, texel, float4(0.0f));
|
||||
return;
|
||||
}
|
||||
float2 projected_coordinates = transformed_coordinates.xy / transformed_coordinates.z;
|
||||
|
||||
float4 sampled_color = SAMPLER_FUNCTION(input_tx, projected_coordinates);
|
||||
|
||||
#if defined(PREMULTIPLY_MASK)
|
||||
/* Premultiply the mask value as an alpha. */
|
||||
float4 plane_color = sampled_color * texture_load(mask_tx, texel).x;
|
||||
#else
|
||||
float4 plane_color = sampled_color;
|
||||
#endif
|
||||
|
||||
imageStore(output_img, texel, plane_color);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_plane_deform_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_plane_deform_anisotropic_masked)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float2 output_size = float2(imageSize(output_img));
|
||||
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / output_size;
|
||||
|
||||
float3 transformed_coordinates = to_float3x3(homography_matrix) * float3(coordinates, 1.0f);
|
||||
/* Point is at infinity and will be zero when sampled, so early exit. */
|
||||
if (transformed_coordinates.z == 0.0f) {
|
||||
imageStore(output_img, texel, float4(0.0f));
|
||||
return;
|
||||
}
|
||||
float2 projected_coordinates = transformed_coordinates.xy / transformed_coordinates.z;
|
||||
|
||||
/* Derivative of transformed_coordinates.xy / transformed_coordinates.z vs texel */
|
||||
float2 x_gradient = (homography_matrix[0].xy * transformed_coordinates.z -
|
||||
transformed_coordinates.xy * homography_matrix[0].z) /
|
||||
(square(transformed_coordinates.z) * output_size.x);
|
||||
float2 y_gradient = (homography_matrix[1].xy * transformed_coordinates.z -
|
||||
transformed_coordinates.xy * homography_matrix[1].z) /
|
||||
(square(transformed_coordinates.z) * output_size.y);
|
||||
|
||||
float4 sampled_color = textureGrad(input_tx, projected_coordinates, x_gradient, y_gradient);
|
||||
|
||||
/* Premultiply the mask value as an alpha. */
|
||||
float4 plane_color = sampled_color * texture_load(mask_tx, texel).x;
|
||||
|
||||
imageStore(output_img, texel, plane_color);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_plane_deform_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_plane_deform_mask)
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(imageSize(mask_img));
|
||||
|
||||
float3 transformed_coordinates = to_float3x3(homography_matrix) * float3(coordinates, 1.0f);
|
||||
/* Point is at infinity and will be zero when sampled, so early exit. */
|
||||
if (transformed_coordinates.z == 0.0f) {
|
||||
imageStore(mask_img, texel, float4(0.0f));
|
||||
return;
|
||||
}
|
||||
float2 projected_coordinates = transformed_coordinates.xy / transformed_coordinates.z;
|
||||
|
||||
bool is_inside_plane_x = projected_coordinates.x >= 0.0f && projected_coordinates.x <= 1.0f;
|
||||
bool is_inside_plane_y = projected_coordinates.y >= 0.0f && projected_coordinates.y <= 1.0f;
|
||||
|
||||
bool is_x_masked = is_inside_plane_x || !is_x_clipped;
|
||||
bool is_y_masked = is_inside_plane_y || !is_y_clipped;
|
||||
|
||||
float mask_value = is_x_masked && is_y_masked ? 1.0f : 0.0f;
|
||||
|
||||
imageStore(mask_img, texel, float4(mask_value));
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_plane_deform_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_plane_deform_motion_blur)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float2 output_size = float2(imageSize(output_img));
|
||||
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / output_size;
|
||||
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
for (int i = 0; i < number_of_motion_blur_samples; i++) {
|
||||
float3x3 homography_matrix = to_float3x3(homography_matrices[i]);
|
||||
|
||||
float3 transformed_coordinates = homography_matrix * float3(coordinates, 1.0f);
|
||||
/* Point is at infinity and will be zero when sampled, so early exit. */
|
||||
if (transformed_coordinates.z == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
float2 projected_coordinates = transformed_coordinates.xy / transformed_coordinates.z;
|
||||
|
||||
/* Derivative of transformed_coordinates.xy / transformed_coordinates.z vs texel */
|
||||
float2 x_gradient = (homography_matrix[0].xy * transformed_coordinates.z -
|
||||
transformed_coordinates.xy * homography_matrix[0].z) /
|
||||
(square(transformed_coordinates.z) * output_size.x);
|
||||
float2 y_gradient = (homography_matrix[1].xy * transformed_coordinates.z -
|
||||
transformed_coordinates.xy * homography_matrix[1].z) /
|
||||
(square(transformed_coordinates.z) * output_size.y);
|
||||
|
||||
float4 sampled_color = textureGrad(input_tx, projected_coordinates, x_gradient, y_gradient);
|
||||
accumulated_color += sampled_color;
|
||||
}
|
||||
|
||||
accumulated_color /= number_of_motion_blur_samples;
|
||||
|
||||
/* Premultiply the mask value as an alpha. */
|
||||
float4 plane_color = accumulated_color * texture_load(mask_tx, texel).x;
|
||||
|
||||
imageStore(output_img, texel, plane_color);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_plane_deform_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_plane_deform_motion_blur_mask)
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(imageSize(mask_img));
|
||||
|
||||
float accumulated_mask = 0.0f;
|
||||
for (int i = 0; i < number_of_motion_blur_samples; i++) {
|
||||
float3x3 homography_matrix = to_float3x3(homography_matrices[i]);
|
||||
|
||||
float3 transformed_coordinates = homography_matrix * float3(coordinates, 1.0f);
|
||||
/* Point is at infinity and will be zero when sampled, so early exit. */
|
||||
if (transformed_coordinates.z == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
float2 projected_coordinates = transformed_coordinates.xy / transformed_coordinates.z;
|
||||
|
||||
bool is_inside_plane = all(greaterThanEqual(projected_coordinates, float2(0.0f))) &&
|
||||
all(lessThanEqual(projected_coordinates, float2(1.0f)));
|
||||
accumulated_mask += is_inside_plane ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
accumulated_mask /= number_of_motion_blur_samples;
|
||||
|
||||
imageStore(mask_img, texel, float4(accumulated_mask));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_premultiply_alpha_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_premultiply_alpha)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
float4 input_color = texture_load(input_tx, texel);
|
||||
imageStore(output_img, texel, input_color * float4(float3(input_color.a), 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_lens_distortion_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_radial_lens_distortion)
|
||||
|
||||
#include "gpu_shader_common_hash.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
/* A model that approximates lens distortion parameterized by a distortion parameter and dependent
|
||||
* on the squared distance to the center of the image. The distorted pixel is then computed as the
|
||||
* scalar multiplication of the pixel coordinates with the value returned by this model. See the
|
||||
* compute_distorted_uv function for more details. */
|
||||
float compute_distortion_scale(float distortion, float distance_squared)
|
||||
{
|
||||
return 1.0f / (1.0f + sqrt(max(0.0f, 1.0f - distortion * distance_squared)));
|
||||
}
|
||||
|
||||
/* A vectorized version of compute_distortion_scale that is applied on the chromatic distortion
|
||||
* parameters passed to the shader. */
|
||||
float3 compute_chromatic_distortion_scale(float distance_squared)
|
||||
{
|
||||
return 1.0f / (1.0f + sqrt(max(float3(0.0f), 1.0f - chromatic_distortion * distance_squared)));
|
||||
}
|
||||
|
||||
/* Compute the image coordinates after distortion by the given distortion scale computed by the
|
||||
* compute_distortion_scale function. Note that the function expects centered normalized UV
|
||||
* coordinates but outputs non-centered image coordinates. */
|
||||
float2 compute_distorted_uv(float2 uv, float uv_scale)
|
||||
{
|
||||
return (uv * uv_scale + 0.5f) * float2(texture_size(input_tx));
|
||||
}
|
||||
|
||||
/* Compute the number of integration steps that should be used to approximate the distorted pixel
|
||||
* using a heuristic, see the compute_number_of_steps function for more details. The numbers of
|
||||
* steps is proportional to the number of pixels spanned by the distortion amount. For jitter
|
||||
* distortion, the square root of the distortion amount plus 1 is used with a minimum of 2 steps.
|
||||
* For non-jitter distortion, the distortion amount plus 1 is used as the number of steps */
|
||||
int compute_number_of_integration_steps_heuristic(float distortion)
|
||||
{
|
||||
#if defined(JITTER)
|
||||
return distortion < 4.0f ? 2 : int(sqrt(distortion + 1.0f));
|
||||
#else
|
||||
return int(distortion + 1.0f);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Compute the number of integration steps that should be used to compute each channel of the
|
||||
* distorted pixel. Each of the channels are distorted by their respective chromatic distortion
|
||||
* amount, then the amount of distortion between each two consecutive channels is computed, this
|
||||
* amount is then used to heuristically infer the number of needed integration steps, see the
|
||||
* integrate_distortion function for more information. */
|
||||
int4 compute_number_of_integration_steps(float2 uv, float distance_squared)
|
||||
{
|
||||
/* Distort each channel by its respective chromatic distortion amount. */
|
||||
float3 distortion_scale = compute_chromatic_distortion_scale(distance_squared);
|
||||
float2 distorted_uv_red = compute_distorted_uv(uv, distortion_scale.r);
|
||||
float2 distorted_uv_green = compute_distorted_uv(uv, distortion_scale.g);
|
||||
float2 distorted_uv_blue = compute_distorted_uv(uv, distortion_scale.b);
|
||||
|
||||
/* Infer the number of needed integration steps to compute the distorted red channel starting
|
||||
* from the green channel. */
|
||||
float distortion_red = distance(distorted_uv_red, distorted_uv_green);
|
||||
int steps_red = compute_number_of_integration_steps_heuristic(distortion_red);
|
||||
|
||||
/* Infer the number of needed integration steps to compute the distorted blue channel starting
|
||||
* from the green channel. */
|
||||
float distortion_blue = distance(distorted_uv_green, distorted_uv_blue);
|
||||
int steps_blue = compute_number_of_integration_steps_heuristic(distortion_blue);
|
||||
|
||||
/* The number of integration steps used to compute the green and the alpha channels is the sum of
|
||||
* both the red and the blue channel steps because they are computed once with each of them. */
|
||||
return int4(steps_red, steps_red + steps_blue, steps_blue, steps_red + steps_blue);
|
||||
}
|
||||
|
||||
/* Returns a random jitter amount, which is essentially a random value in the [0, 1] range. If
|
||||
* jitter is not enabled, return a constant 0.5 value instead. */
|
||||
float get_jitter([[maybe_unused]] int seed)
|
||||
{
|
||||
#if defined(JITTER)
|
||||
return hash_uint3_to_float(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y, seed);
|
||||
#else
|
||||
return 0.5f;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Each color channel may have a different distortion with the guarantee that the red will have the
|
||||
* lowest distortion while the blue will have the highest one. If each channel is distorted
|
||||
* independently, the image will look disintegrated, with each channel seemingly merely shifted.
|
||||
* Consequently, the distorted pixels needs to be computed by integrating along the path of change
|
||||
* of distortion starting from one channel to another. For instance, to compute the distorted red
|
||||
* from the distorted green, we accumulate the color of the distorted pixel starting from the
|
||||
* distortion of the red, taking small steps until we reach the distortion of the green. The pixel
|
||||
* color is weighted such that it is maximum at the start distortion and zero at the end distortion
|
||||
* in an arithmetic progression. The integration steps can be augmented with random values to
|
||||
* simulate lens jitter. Finally, it should be noted that this function integrates both the start
|
||||
* and end channels in reverse directions for more efficient computation. */
|
||||
float4 integrate_distortion(int start, int end, float distance_squared, float2 uv, int steps)
|
||||
{
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
float distortion_amount = chromatic_distortion[end] - chromatic_distortion[start];
|
||||
for (int i = 0; i < steps; i++) {
|
||||
/* The increment will be in the [0, 1) range across iterations. Include the start channel in
|
||||
* the jitter seed to make sure each channel gets a different jitter. */
|
||||
float increment = (i + get_jitter(start * steps + i)) / steps;
|
||||
float distortion = chromatic_distortion[start] + increment * distortion_amount;
|
||||
float distortion_scale = compute_distortion_scale(distortion, distance_squared);
|
||||
|
||||
/* Sample the color at the distorted coordinates and accumulate it weighted by the increment
|
||||
* value for both the start and end channels. */
|
||||
float2 distorted_uv = compute_distorted_uv(uv, distortion_scale);
|
||||
float4 color = texture(input_tx, distorted_uv / float2(texture_size(input_tx)));
|
||||
accumulated_color[start] += (1.0f - increment) * color[start];
|
||||
accumulated_color[end] += increment * color[end];
|
||||
accumulated_color.w += color.w;
|
||||
}
|
||||
return accumulated_color;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Compute the UV image coordinates in the range [-1, 1] as well as the squared distance to the
|
||||
* center of the image, which is at (0, 0) in the UV coordinates. */
|
||||
float2 center = float2(texture_size(input_tx)) / 2.0f;
|
||||
float2 uv = scale * (float2(texel) + float2(0.5f) - center) / center;
|
||||
float distance_squared = dot(uv, uv);
|
||||
|
||||
/* If any of the color channels will get distorted outside of the screen beyond what is possible,
|
||||
* write a zero transparent color and return. */
|
||||
if (any(greaterThan(chromatic_distortion * distance_squared, float3(1.0f)))) {
|
||||
imageStore(output_img, texel, float4(0.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute the number of integration steps that should be used to compute each channel of the
|
||||
* distorted pixel. */
|
||||
int4 number_of_steps = compute_number_of_integration_steps(uv, distance_squared);
|
||||
|
||||
/* Integrate the distortion of the red and green, then the green and blue channels. That means
|
||||
* the green will be integrated twice, but this is accounted for in the number of steps which the
|
||||
* color will later be divided by. See the compute_number_of_integration_steps function for more
|
||||
* details. */
|
||||
float4 color = float4(0.0f);
|
||||
color += integrate_distortion(0, 1, distance_squared, uv, number_of_steps.r);
|
||||
color += integrate_distortion(1, 2, distance_squared, uv, number_of_steps.b);
|
||||
|
||||
/* The integration above performed weighted accumulation, and thus the color needs to be divided
|
||||
* by the sum of the weights. Assuming no jitter, the weights are generated as an arithmetic
|
||||
* progression starting from (0.5 / n) to ((n - 0.5) / n) for n terms. The sum of an arithmetic
|
||||
* progression can be computed as (n * (start + end) / 2), which when subsisting the start and
|
||||
* end reduces to (n / 2). So the color should be multiplied by 2 / n. On the other hand alpha
|
||||
* is not weighted by the arithmetic progression, so it is multiplied by (1.0) and it is
|
||||
* normalized by averaging only (i.e. division by (n)). The jitter sequence approximately sums to
|
||||
* the same value because it is a uniform random value whose mean value is 0.5, so the expression
|
||||
* doesn't change regardless of jitter. */
|
||||
color *= float4(float3(2.0f), 1.0f) / float4(number_of_steps);
|
||||
|
||||
imageStore(output_img, texel, color);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_realize_on_domain_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_realize_on_domain_bicubic_float)
|
||||
|
||||
#include "gpu_shader_bicubic_sampler_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_math_matrix_transform_lib.glsl"
|
||||
|
||||
void realize_on_domain()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
const float2 coordinates = transform_point(to_float3x3(transformation), float2(texel));
|
||||
imageStore(domain_img, texel, texture(input_tx, coordinates));
|
||||
}
|
||||
|
||||
void realize_on_domain_float4x4()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
const float2 coordinates = transform_point(to_float3x3(transformation), float2(texel));
|
||||
/* Each column of the matrix is stored in one layer of the texture. */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
imageStore(domain_img, int3(texel, i), texture(input_tx, float3(coordinates, float(i))));
|
||||
}
|
||||
}
|
||||
|
||||
void realize_on_domain_bicubic()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
const float2 coordinates = transform_point(to_float3x3(transformation), float2(texel));
|
||||
imageStore(domain_img, texel, texture_bicubic(input_tx, coordinates));
|
||||
}
|
||||
|
||||
void realize_on_domain_anisotropic()
|
||||
{
|
||||
const int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
const float2 coordinates = transform_point(to_float3x3(transformation), float2(texel));
|
||||
imageStore(domain_img,
|
||||
texel,
|
||||
textureGrad(input_tx, coordinates, transformation[0].xy, transformation[1].xy));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_relative_to_pixel_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_relative_to_pixel_float)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
imageStore(output_img, texel, float4(texture_load(input_tx, texel).x * reference_size));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_relative_to_pixel_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_relative_to_pixel_float_per_dimension)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
imageStore(
|
||||
output_img, texel, float4(texture_load(input_tx, texel).x * reference_size, 0.0f, 0.0f));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_relative_to_pixel_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_relative_to_pixel_vector)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
imageStore(
|
||||
output_img, texel, float4(texture_load(input_tx, texel).xy() * reference_size, 0.0f, 0.0f));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_sample_pixel_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_sample_pixel)
|
||||
|
||||
#include "gpu_shader_bicubic_sampler_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float4 sampled_color = SAMPLER_FUNCTION(input_tx, coordinates_u);
|
||||
|
||||
imageStore(output_img, texel, sampled_color);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_scale_variable_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_scale_variable)
|
||||
|
||||
#include "gpu_shader_bicubic_sampler_lib.glsl"
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
int2 input_size = texture_size(input_tx);
|
||||
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(input_size);
|
||||
float2 center = float2(0.5f);
|
||||
|
||||
float2 scale = float2(texture_load(x_scale_tx, texel).x, texture_load(y_scale_tx, texel).x);
|
||||
float2 scaled_coordinates = center + (coordinates - center) / max(scale, 0.0001f);
|
||||
|
||||
imageStore(output_img, texel, SAMPLER_FUNCTION(input_tx, scaled_coordinates));
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_smaa_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_smaa_blending_weight_calculation)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_smaa_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
|
||||
* to get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(texture_size(edges_tx));
|
||||
|
||||
float4 offset[3];
|
||||
float2 pixel_coordinates;
|
||||
SMAABlendingWeightCalculationVS(coordinates, pixel_coordinates, offset);
|
||||
|
||||
float4 weights = SMAABlendingWeightCalculationPS(
|
||||
coordinates, pixel_coordinates, offset, edges_tx, area_tx, search_tx, float4(0.0f));
|
||||
imageStore(weights_img, texel, weights);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_smaa_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_smaa_edge_detection)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_smaa_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
|
||||
* to get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(texture_size(input_tx));
|
||||
|
||||
float4 offset[3];
|
||||
SMAAEdgeDetectionVS(coordinates, offset);
|
||||
|
||||
float2 edge = SMAALumaEdgeDetectionPS(coordinates, offset, input_tx);
|
||||
imageStore(edges_img, texel, float4(edge, float2(0.0f)));
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_smaa_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_smaa_neighborhood_blending_float)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
#include "gpu_shader_smaa_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image size
|
||||
* to get the coordinates into the sampler's expected [0, 1] range. */
|
||||
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(texture_size(input_tx));
|
||||
|
||||
float4 offset;
|
||||
SMAANeighborhoodBlendingVS(coordinates, offset);
|
||||
|
||||
float4 result = SMAANeighborhoodBlendingPS(coordinates, offset, input_tx, weights_tx);
|
||||
imageStore(output_img, texel, result);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_split_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_split)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
|
||||
const float2 direction_to_line_point = position - float2(texel);
|
||||
const float projection = dot(normal, direction_to_line_point);
|
||||
|
||||
bool is_below_line = projection <= 0;
|
||||
float4 color = is_below_line ? texture_load(first_image_tx, texel) :
|
||||
texture_load(second_image_tx, texel);
|
||||
imageStore(output_img, texel, color);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_summed_area_table_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_summed_area_table_compute_complete_blocks_identity)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
/* An intermediate shared memory where the result of X accumulation will be stored. */
|
||||
shared float4 block[gl_WorkGroupSize.x][gl_WorkGroupSize.y];
|
||||
|
||||
void main()
|
||||
{
|
||||
/* Accumulate the block along the horizontal direction starting from the X prologue value,
|
||||
* writing each accumulation step to the intermediate shared memory. */
|
||||
if (gl_LocalInvocationID.x == 0) {
|
||||
int2 x_prologue_texel = int2(gl_GlobalInvocationID.y, gl_WorkGroupID.x);
|
||||
float4 x_accumulated_color = texture_load(
|
||||
complete_x_prologues_tx, x_prologue_texel, float4(0.0f));
|
||||
for (uint i = 0; i < gl_WorkGroupSize.x; i++) {
|
||||
int2 texel = int2(gl_WorkGroupID.x * gl_WorkGroupSize.x + i, gl_GlobalInvocationID.y);
|
||||
x_accumulated_color += OPERATION(texture_load(input_tx, texel, float4(0.0f)));
|
||||
block[i][gl_LocalInvocationID.y] = x_accumulated_color;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure the result of X accumulation is completely done. */
|
||||
barrier();
|
||||
|
||||
/* Accumulate the block along the vertical direction starting from the Y prologue value,
|
||||
* writing each accumulation step to the output image. */
|
||||
if (gl_LocalInvocationID.y == 0) {
|
||||
int2 y_prologue_texel = int2(gl_GlobalInvocationID.x, gl_WorkGroupID.y);
|
||||
float4 y_accumulated_color = texture_load(
|
||||
complete_y_prologues_tx, y_prologue_texel, float4(0.0f));
|
||||
for (uint i = 0; i < gl_WorkGroupSize.y; i++) {
|
||||
y_accumulated_color += block[gl_LocalInvocationID.x][i];
|
||||
int2 texel = int2(gl_GlobalInvocationID.x, gl_WorkGroupID.y * gl_WorkGroupSize.y + i);
|
||||
imageStore(output_img, texel, y_accumulated_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_summed_area_table_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_summed_area_table_compute_complete_x_prologues)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
/* A shared memory to sum the prologues using parallel reduction. See the parallel reduction shader
|
||||
* `compositor_parallel_reduction.glsl` for more information. */
|
||||
shared float4 complete_prologue[gl_WorkGroupSize.x];
|
||||
|
||||
/* See the compute_complete_x_prologues function for a description of this shader. */
|
||||
void main()
|
||||
{
|
||||
/* Note that the X prologues are stored transposed, hence the horizontal dispatch domain, even
|
||||
* though, conceptually, the dispatch domain covers the vertical axis of the image. */
|
||||
int x = int(gl_GlobalInvocationID.x);
|
||||
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
for (int y = 0; y < texture_size(incomplete_x_prologues_tx).y; y++) {
|
||||
accumulated_color += texture_load(incomplete_x_prologues_tx, int2(x, y), float4(0.0f));
|
||||
imageStore(complete_x_prologues_img, int2(x, y), accumulated_color);
|
||||
|
||||
if (gl_WorkGroupID.x == 0) {
|
||||
/* Note that the first row of sums is the result of summing the prologues of a virtual block
|
||||
* that is before the first row of blocks and we assume that those prologues are all zeros,
|
||||
* so we set the sum to zero in that case. This is implemented by setting the sums of the
|
||||
* first vertical work-group to zero, white latter work-groups are summed as usual and
|
||||
* stored starting from the second row. */
|
||||
imageStore(complete_x_prologues_sum_img, int2(y, 0), float4(0.0f));
|
||||
}
|
||||
|
||||
/* Synchronize between iterations: ensure the read of complete_prologue[0] from the previous
|
||||
* iteration is visible before any invocation writes to the shared array for the next
|
||||
* iteration. */
|
||||
barrier();
|
||||
|
||||
/* A parallel reduction loop to sum the prologues. This is exactly the same as the parallel
|
||||
* reduction loop in the shader `compositor_parallel_reduction.glsl`, see that shader for
|
||||
* more information. */
|
||||
complete_prologue[gl_LocalInvocationIndex] = accumulated_color;
|
||||
for (uint stride = gl_WorkGroupSize.x / 2; stride > 0; stride /= 2) {
|
||||
barrier();
|
||||
float4 my_value = complete_prologue[gl_LocalInvocationIndex];
|
||||
float4 neighbor_value = float4(0.0f);
|
||||
if (gl_LocalInvocationIndex + stride < gl_WorkGroupSize.x) {
|
||||
neighbor_value = complete_prologue[gl_LocalInvocationIndex + stride];
|
||||
}
|
||||
barrier();
|
||||
|
||||
if (gl_LocalInvocationIndex < stride) {
|
||||
complete_prologue[gl_LocalInvocationIndex] = my_value + neighbor_value;
|
||||
}
|
||||
}
|
||||
|
||||
barrier();
|
||||
if (gl_LocalInvocationIndex == 0) {
|
||||
/* Note that we store using a transposed texel, but that is only to undo the transposition
|
||||
* mentioned above. Also note that we start from the second row because the first row is
|
||||
* set to zero as mentioned above. */
|
||||
float4 sum = complete_prologue[0];
|
||||
imageStore(complete_x_prologues_sum_img, int2(y, gl_WorkGroupID.x + 1), sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/compositor_summed_area_table_infos.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(compositor_summed_area_table_compute_complete_y_prologues)
|
||||
|
||||
#include "gpu_shader_compositor_texture_utilities.glsl"
|
||||
|
||||
/* See the compute_complete_y_prologues function for a description of this shader. */
|
||||
void main()
|
||||
{
|
||||
int x = int(gl_GlobalInvocationID.x);
|
||||
int num_rows = texture_size(incomplete_y_prologues_tx).y;
|
||||
|
||||
float4 accumulated_color = float4(0.0f);
|
||||
for (int y = 0; y < num_rows; y++) {
|
||||
accumulated_color += texture_load(incomplete_y_prologues_tx, int2(x, y));
|
||||
accumulated_color += texture_load(complete_x_prologues_sum_tx, int2(gl_WorkGroupID.x, y));
|
||||
imageStore(complete_y_prologues_img, int2(x, y), accumulated_color);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user