Realtime Compositor: Implement Defocus node #116391
|
@ -64,6 +64,7 @@ set(SRC
|
|||
COM_utilities.hh
|
||||
|
||||
algorithms/intern/jump_flooding.cc
|
||||
algorithms/intern/morphological_blur.cc
|
||||
algorithms/intern/morphological_distance.cc
|
||||
algorithms/intern/morphological_distance_feather.cc
|
||||
algorithms/intern/parallel_reduction.cc
|
||||
|
@ -75,6 +76,7 @@ set(SRC
|
|||
algorithms/intern/transform.cc
|
||||
|
||||
algorithms/COM_algorithm_jump_flooding.hh
|
||||
algorithms/COM_algorithm_morphological_blur.hh
|
||||
algorithms/COM_algorithm_morphological_distance.hh
|
||||
algorithms/COM_algorithm_morphological_distance_feather.hh
|
||||
algorithms/COM_algorithm_parallel_reduction.hh
|
||||
|
@ -136,6 +138,9 @@ set(GLSL_SRC
|
|||
shaders/compositor_cryptomatte_image.glsl
|
||||
shaders/compositor_cryptomatte_matte.glsl
|
||||
shaders/compositor_cryptomatte_pick.glsl
|
||||
shaders/compositor_defocus_blur.glsl
|
||||
shaders/compositor_defocus_radius_from_depth.glsl
|
||||
shaders/compositor_defocus_radius_from_scale.glsl
|
||||
shaders/compositor_despeckle.glsl
|
||||
shaders/compositor_directional_blur.glsl
|
||||
shaders/compositor_displace.glsl
|
||||
|
@ -173,6 +178,7 @@ set(GLSL_SRC
|
|||
shaders/compositor_kuwahara_anisotropic_compute_structure_tensor.glsl
|
||||
shaders/compositor_kuwahara_classic.glsl
|
||||
shaders/compositor_map_uv.glsl
|
||||
shaders/compositor_morphological_blur.glsl
|
||||
shaders/compositor_morphological_distance.glsl
|
||||
shaders/compositor_morphological_distance_feather.glsl
|
||||
shaders/compositor_morphological_distance_threshold.glsl
|
||||
|
@ -275,6 +281,7 @@ set(SRC_SHADER_CREATE_INFOS
|
|||
shaders/infos/compositor_compute_preview_info.hh
|
||||
shaders/infos/compositor_convert_info.hh
|
||||
shaders/infos/compositor_cryptomatte_info.hh
|
||||
shaders/infos/compositor_defocus_info.hh
|
||||
shaders/infos/compositor_despeckle_info.hh
|
||||
shaders/infos/compositor_directional_blur_info.hh
|
||||
shaders/infos/compositor_displace_info.hh
|
||||
|
@ -292,6 +299,7 @@ set(SRC_SHADER_CREATE_INFOS
|
|||
shaders/infos/compositor_keying_screen_info.hh
|
||||
shaders/infos/compositor_kuwahara_info.hh
|
||||
shaders/infos/compositor_map_uv_info.hh
|
||||
shaders/infos/compositor_morphological_blur_info.hh
|
||||
shaders/infos/compositor_morphological_distance_feather_info.hh
|
||||
shaders/infos/compositor_morphological_distance_info.hh
|
||||
shaders/infos/compositor_morphological_distance_threshold_info.hh
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_result.hh"
|
||||
|
||||
namespace blender::realtime_compositor {
|
||||
|
||||
/* Possible morphological operations to apply. */
|
||||
enum class MorphologicalBlurOperation : uint8_t {
|
||||
/* Dilate by taking the maximum from the original input and the blurred input. Which means the
|
||||
* whites bleeds into the blacks while the blacks don't bleed into the whites. */
|
||||
Dilate,
|
||||
/* Erode by taking the minimum from the original input and the blurred input. Which means the
|
||||
* blacks bleeds into the whites while the whites don't bleed into the blacks. */
|
||||
Erode,
|
||||
};
|
||||
|
||||
/* Applies a morphological blur on input using the given radius and filter type. This essentially
|
||||
* applies a standard blur operation, but then takes the maximum or minimum from the original input
|
||||
* and blurred input depending on the chosen operation, see the MorphologicalBlurOperation enum for
|
||||
* more information. The output is written to the given output result, which will be allocated
|
||||
* internally and is thus expected not to be previously allocated. */
|
||||
void morphological_blur(Context &context,
|
||||
Result &input,
|
||||
Result &output,
|
||||
float2 radius,
|
||||
MorphologicalBlurOperation operation = MorphologicalBlurOperation::Erode,
|
||||
int filter_type = R_FILTER_GAUSS);
|
||||
|
||||
} // namespace blender::realtime_compositor
|
|
@ -0,0 +1,69 @@
|
|||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "GPU_shader.h"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "COM_algorithm_morphological_blur.hh"
|
||||
#include "COM_algorithm_symmetric_separable_blur.hh"
|
||||
|
||||
namespace blender::realtime_compositor {
|
||||
|
||||
static const char *get_shader(MorphologicalBlurOperation operation)
|
||||
{
|
||||
switch (operation) {
|
||||
case MorphologicalBlurOperation::Dilate:
|
||||
return "compositor_morphological_blur_dilate";
|
||||
case MorphologicalBlurOperation::Erode:
|
||||
return "compositor_morphological_blur_erode";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Apply the morphological operator (minimum or maximum) on the input and the blurred input. The
|
||||
* output is written to the blurred_input in-place. */
|
||||
static void apply_morphological_operator(Context &context,
|
||||
Result &input,
|
||||
Result &blurred_input,
|
||||
MorphologicalBlurOperation operation)
|
||||
{
|
||||
GPUShader *shader = context.get_shader(get_shader(operation));
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
|
||||
blurred_input.bind_as_image(shader, "blurred_input_img", true);
|
||||
|
||||
Domain domain = input.domain();
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
input.unbind_as_texture();
|
||||
blurred_input.unbind_as_image();
|
||||
}
|
||||
|
||||
void morphological_blur(Context &context,
|
||||
Result &input,
|
||||
Result &output,
|
||||
float2 radius,
|
||||
MorphologicalBlurOperation operation,
|
||||
int filter_type)
|
||||
{
|
||||
BLI_assert(input.type() == ResultType::Float);
|
||||
|
||||
symmetric_separable_blur(context, input, output, radius, filter_type);
|
||||
apply_morphological_operator(context, input, output, operation);
|
||||
}
|
||||
|
||||
} // namespace blender::realtime_compositor
|
|
@ -0,0 +1,74 @@
|
|||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_blur_common.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.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. */
|
||||
vec4 load_weight(ivec2 texel, float radius)
|
||||
{
|
||||
/* Add the radius to transform the texel into the range [0, radius * 2], with an additional 0.5
|
||||
* 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.0 - ((vec2(texel) + vec2(radius + 0.5)) / (radius * 2.0 + 1.0)));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float center_radius = texture_load(radius_tx, texel).x;
|
||||
vec4 center_color = texture_load(input_tx, texel);
|
||||
|
||||
/* 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. */
|
||||
vec4 accumulated_color = vec4(0.0);
|
||||
vec4 accumulated_weight = vec4(0.0);
|
||||
for (int y = -search_radius; y <= search_radius; y++) {
|
||||
for (int x = -search_radius; x <= search_radius; x++) {
|
||||
float candidate_radius = texture_load(radius_tx, texel + ivec2(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;
|
||||
}
|
||||
|
||||
vec4 weight = load_weight(ivec2(x, y), radius);
|
||||
vec4 input_color = texture_load(input_tx, texel + ivec2(x, y));
|
||||
|
||||
if (gamma_correct) {
|
||||
input_color = gamma_correct_blur_input(input_color);
|
||||
}
|
||||
|
||||
accumulated_color += input_color * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
accumulated_color = safe_divide(accumulated_color, accumulated_weight);
|
||||
|
||||
if (gamma_correct) {
|
||||
accumulated_color = gamma_uncorrect_blur_output(accumulated_color);
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, accumulated_color);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/* 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. */
|
||||
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(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.0) * pixels_per_meter;
|
||||
|
||||
imageStore(radius_img, texel, vec4(min(max_radius, radius)));
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
float radius = texture_load(radius_tx, texel).x;
|
||||
imageStore(radius_img, texel, vec4(min(max_radius, radius * scale)));
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(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, vec4(OPERATOR(input_value, blurred_value)));
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_defocus_radius_from_scale)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::FLOAT, "scale")
|
||||
.push_constant(Type::FLOAT, "max_radius")
|
||||
.sampler(0, ImageType::FLOAT_2D, "radius_tx")
|
||||
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "radius_img")
|
||||
.compute_source("compositor_defocus_radius_from_scale.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_defocus_radius_from_depth)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::FLOAT, "f_stop")
|
||||
.push_constant(Type::FLOAT, "max_radius")
|
||||
.push_constant(Type::FLOAT, "focal_length")
|
||||
.push_constant(Type::FLOAT, "pixels_per_meter")
|
||||
.push_constant(Type::FLOAT, "distance_to_image_of_focus")
|
||||
.sampler(0, ImageType::FLOAT_2D, "depth_tx")
|
||||
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "radius_img")
|
||||
.compute_source("compositor_defocus_radius_from_depth.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_defocus_blur)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::BOOL, "gamma_correct")
|
||||
.push_constant(Type::INT, "search_radius")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.sampler(1, ImageType::FLOAT_2D, "weights_tx")
|
||||
.sampler(2, ImageType::FLOAT_2D, "radius_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
|
||||
.compute_source("compositor_defocus_blur.glsl")
|
||||
.do_static_compilation(true);
|
|
@ -0,0 +1,21 @@
|
|||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_morphological_blur_shared)
|
||||
.local_group_size(16, 16)
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.image(0, GPU_R16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "blurred_input_img")
|
||||
.compute_source("compositor_morphological_blur.glsl");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_morphological_blur_dilate)
|
||||
.additional_info("compositor_morphological_blur_shared")
|
||||
.define("OPERATOR(x, y)", "max(x, y)")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_morphological_blur_erode)
|
||||
.additional_info("compositor_morphological_blur_shared")
|
||||
.define("OPERATOR(x, y)", "min(x, y)")
|
||||
.do_static_compilation(true);
|
|
@ -8,12 +8,21 @@
|
|||
|
||||
#include <climits>
|
||||
|
||||
#include "DNA_camera_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_camera.h"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "COM_algorithm_morphological_blur.hh"
|
||||
#include "COM_bokeh_kernel.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
|
@ -21,10 +30,15 @@
|
|||
|
||||
namespace blender::nodes::node_composite_defocus_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeDefocus)
|
||||
|
||||
static void cmp_node_defocus_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(1.0f);
|
||||
b.add_input<decl::Color>("Image")
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.compositor_domain_priority(0);
|
||||
b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(1.0f).compositor_domain_priority(
|
||||
1);
|
||||
b.add_output<decl::Color>("Image");
|
||||
}
|
||||
|
||||
|
@ -92,8 +106,233 @@ class DefocusOperation : public NodeOperation {
|
|||
|
||||
void execute() override
|
||||
{
|
||||
get_input("Image").pass_through(get_result("Image"));
|
||||
context().set_info_message("Viewport compositor setup not fully supported");
|
||||
Result &input = get_input("Image");
|
||||
Result &output = get_result("Image");
|
||||
if (input.is_single_value()) {
|
||||
input.pass_through(output);
|
||||
return;
|
||||
}
|
||||
|
||||
Result radius = compute_defocus_radius();
|
||||
|
||||
const int maximum_defocus_radius = compute_maximum_defocus_radius();
|
||||
|
||||
/* The special zero value indicate a circle, in which case, the roundness should be set to
|
||||
* 1, and the number of sides can be anything and is arbitrarily set to 3. */
|
||||
const bool is_circle = node_storage(bnode()).bktype == 0;
|
||||
const int2 kernel_size = int2(maximum_defocus_radius * 2);
|
||||
const int sides = is_circle ? 3 : node_storage(bnode()).bktype;
|
||||
const float rotation = node_storage(bnode()).rotation;
|
||||
const float roundness = is_circle ? 1.0f : 0.0f;
|
||||
const BokehKernel &bokeh_kernel = context().cache_manager().bokeh_kernels.get(
|
||||
context(), kernel_size, sides, rotation, roundness, 0.0f, 0.0f);
|
||||
|
||||
GPUShader *shader = context().get_shader("compositor_defocus_blur");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1b(shader, "gamma_correct", node_storage(bnode()).gamco);
|
||||
GPU_shader_uniform_1i(shader, "search_radius", maximum_defocus_radius);
|
||||
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
|
||||
radius.bind_as_texture(shader, "radius_tx");
|
||||
|
||||
bokeh_kernel.bind_as_texture(shader, "weights_tx");
|
||||
GPU_texture_filter_mode(bokeh_kernel.texture(), true);
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
output.allocate_texture(domain);
|
||||
output.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
input.unbind_as_texture();
|
||||
radius.unbind_as_texture();
|
||||
bokeh_kernel.unbind_as_texture();
|
||||
output.unbind_as_image();
|
||||
|
||||
radius.release();
|
||||
}
|
||||
|
||||
Result compute_defocus_radius()
|
||||
{
|
||||
if (node_storage(bnode()).no_zbuf) {
|
||||
return compute_defocus_radius_from_scale();
|
||||
}
|
||||
else {
|
||||
return compute_defocus_radius_from_depth();
|
||||
}
|
||||
}
|
||||
|
||||
Result compute_defocus_radius_from_scale()
|
||||
{
|
||||
GPUShader *shader = context().get_shader("compositor_defocus_radius_from_scale");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1f(shader, "scale", node_storage(bnode()).scale);
|
||||
GPU_shader_uniform_1f(shader, "max_radius", node_storage(bnode()).maxblur);
|
||||
|
||||
Result &input_radius = get_input("Z");
|
||||
input_radius.bind_as_texture(shader, "radius_tx");
|
||||
|
||||
Result output_radius = context().create_temporary_result(ResultType::Float);
|
||||
const Domain domain = input_radius.domain();
|
||||
output_radius.allocate_texture(domain);
|
||||
output_radius.bind_as_image(shader, "radius_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
input_radius.unbind_as_texture();
|
||||
output_radius.unbind_as_image();
|
||||
|
||||
return output_radius;
|
||||
}
|
||||
|
||||
Result compute_defocus_radius_from_depth()
|
||||
{
|
||||
GPUShader *shader = context().get_shader("compositor_defocus_radius_from_depth");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const float distance_to_image_of_focus = compute_distance_to_image_of_focus();
|
||||
GPU_shader_uniform_1f(shader, "f_stop", get_f_stop());
|
||||
GPU_shader_uniform_1f(shader, "focal_length", get_focal_length());
|
||||
GPU_shader_uniform_1f(shader, "max_radius", node_storage(bnode()).maxblur);
|
||||
GPU_shader_uniform_1f(shader, "pixels_per_meter", compute_pixels_per_meter());
|
||||
GPU_shader_uniform_1f(shader, "distance_to_image_of_focus", distance_to_image_of_focus);
|
||||
|
||||
Result &input_depth = get_input("Z");
|
||||
input_depth.bind_as_texture(shader, "depth_tx");
|
||||
|
||||
Result output_radius = context().create_temporary_result(ResultType::Float);
|
||||
const Domain domain = input_depth.domain();
|
||||
output_radius.allocate_texture(domain);
|
||||
output_radius.bind_as_image(shader, "radius_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
input_depth.unbind_as_texture();
|
||||
output_radius.unbind_as_image();
|
||||
|
||||
/* We apply a dilate morphological operator on the radius computed from depth, the operator
|
||||
* radius is the maximum possible defocus radius. This is done such that objects in
|
||||
* focus---that is, objects whose defocus radius is small---are not affected by nearby out of
|
||||
* focus objects, hence the use of dilation. */
|
||||
const float morphological_radius = compute_maximum_defocus_radius();
|
||||
Result eroded_radius = context().create_temporary_result(ResultType::Float);
|
||||
morphological_blur(context(), output_radius, eroded_radius, float2(morphological_radius));
|
||||
output_radius.release();
|
||||
|
||||
return eroded_radius;
|
||||
}
|
||||
|
||||
/* Computes the maximum possible defocus radius in pixels. */
|
||||
float compute_maximum_defocus_radius()
|
||||
{
|
||||
const float maximum_diameter = compute_maximum_diameter_of_circle_of_confusion();
|
||||
const float pixels_per_meter = compute_pixels_per_meter();
|
||||
const float radius = (maximum_diameter / 2.0f) * pixels_per_meter;
|
||||
return math::min(radius, node_storage(bnode()).maxblur);
|
||||
}
|
||||
|
||||
/* Computes the diameter of the circle of confusion at infinity. This computes the limit in
|
||||
* figure (5) 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.
|
||||
*
|
||||
* Notice that the diameter is asymmetric around the focus point, and we are computing the
|
||||
* limiting diameter at infinity, while another limiting diameter exist at zero distance from the
|
||||
* lens. This is a limitation of the implementation, as it assumes far defocusing only. */
|
||||
float compute_maximum_diameter_of_circle_of_confusion()
|
||||
{
|
||||
const float f_stop = get_f_stop();
|
||||
const float focal_length = get_focal_length();
|
||||
const float distance_to_image_of_focus = compute_distance_to_image_of_focus();
|
||||
return math::abs((distance_to_image_of_focus / (f_stop * focal_length)) -
|
||||
(focal_length / f_stop));
|
||||
}
|
||||
|
||||
/* Computes the distance in meters to the image of the focus point across a lens of the specified
|
||||
* focal length. This computes Vp in equation (7) 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. */
|
||||
float compute_distance_to_image_of_focus()
|
||||
{
|
||||
const float focal_length = get_focal_length();
|
||||
const float focus_distance = compute_focus_distance();
|
||||
return (focal_length * focus_distance) / (focus_distance - focal_length);
|
||||
}
|
||||
|
||||
/* Returns the focal length in meters. Fallback to 50 mm in case of an invalid camera. Ensure a
|
||||
* minimum of 1e-6. */
|
||||
float get_focal_length()
|
||||
{
|
||||
const Camera *camera = get_camera();
|
||||
return camera ? math::max(1e-6f, camera->lens / 1000.0f) : 50.0f / 1000.0f;
|
||||
}
|
||||
|
||||
/* Computes the distance to the point that is completely in focus. */
|
||||
float compute_focus_distance()
|
||||
{
|
||||
return BKE_camera_object_dof_distance(get_camera_object());
|
||||
}
|
||||
|
||||
/* Computes the number of pixels per meter of the sensor size. This is essentially the resolution
|
||||
* over the sensor size, using the sensor fit axis. Fallback to DEFAULT_SENSOR_WIDTH in case of
|
||||
* an invalid camera. Note that the stored sensor size is in millimeter, so convert to meters. */
|
||||
float compute_pixels_per_meter()
|
||||
{
|
||||
const int2 size = compute_domain().size;
|
||||
const Camera *camera = get_camera();
|
||||
const float default_value = size.x / (DEFAULT_SENSOR_WIDTH / 1000.0f);
|
||||
if (!camera) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
switch (camera->sensor_fit) {
|
||||
case CAMERA_SENSOR_FIT_HOR:
|
||||
return size.x / (camera->sensor_x / 1000.0f);
|
||||
case CAMERA_SENSOR_FIT_VERT:
|
||||
return size.y / (camera->sensor_y / 1000.0f);
|
||||
case CAMERA_SENSOR_FIT_AUTO: {
|
||||
return size.x > size.y ? size.x / (camera->sensor_x / 1000.0f) :
|
||||
size.y / (camera->sensor_y / 1000.0f);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
/* Returns the f-stop number. Fallback to 1e-3 for zero f-stop. */
|
||||
const float get_f_stop()
|
||||
{
|
||||
return math::max(1e-3f, node_storage(bnode()).fstop);
|
||||
}
|
||||
|
||||
const Camera *get_camera()
|
||||
{
|
||||
const Object *camera_object = get_camera_object();
|
||||
if (!camera_object || camera_object->type != OB_CAMERA) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<Camera *>(camera_object->data);
|
||||
}
|
||||
|
||||
const Object *get_camera_object()
|
||||
{
|
||||
return get_scene()->camera;
|
||||
}
|
||||
|
||||
const Scene *get_scene()
|
||||
{
|
||||
return bnode().id ? reinterpret_cast<Scene *>(bnode().id) : &context().get_scene();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -116,8 +355,6 @@ void register_node_type_cmp_defocus()
|
|||
ntype.initfunc = file_ns::node_composit_init_defocus;
|
||||
node_type_storage(&ntype, "NodeDefocus", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.get_compositor_operation = file_ns::get_compositor_operation;
|
||||
ntype.realtime_compositor_unsupported_message = N_(
|
||||
"Node not supported in the Viewport compositor");
|
||||
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue