Compositor: Unify plane anti-aliasing between CPU and GPU
This patch unifies the anti-aliasing of plane deforms between the CPU and GPU compositors. The CPU used a multi-sample approach, where the mask was computed 8 times with a jitter, then averaged to get smooth edges. The GPU relied on the anisotropic filtering with zero boundaries to smooth the edges. Furthermore, the CPU implementation ignored the anti-aliasing for the deformed image and also relied anisotropic filtering like the GPU, so its outputs were different. To unify both implementation, we use the existing SMAA anti-aliasing algorithm instead, and use the anti-aliased mask for the image output as well. This affects both the Corner Pin and Plane Deform nodes. A consequence of this change for the Plane Deform node is that motion blur will appear to have less samples, that's because it was sampled 8-times more in the previous implementation. But users can just increase the samples in the node to account for that. Pull Request: https://projects.blender.org/blender/blender/pulls/118853
This commit is contained in:
parent
30ef07867e
commit
1bff17cc99
@ -412,14 +412,20 @@ void MemoryBuffer::add_pixel(int x, int y, const float color[4])
|
||||
}
|
||||
}
|
||||
|
||||
static void read_ewa_elem(void *userdata, int x, int y, float result[4])
|
||||
static void read_ewa_elem_checked(void *userdata, int x, int y, float result[4])
|
||||
{
|
||||
const MemoryBuffer *buffer = static_cast<const MemoryBuffer *>(userdata);
|
||||
buffer->read_elem_checked(x, y, result);
|
||||
}
|
||||
|
||||
static void read_ewa_elem_clamped(void *userdata, int x, int y, float result[4])
|
||||
{
|
||||
const MemoryBuffer *buffer = static_cast<const MemoryBuffer *>(userdata);
|
||||
buffer->read_elem_clamped(x, y, result);
|
||||
}
|
||||
|
||||
void MemoryBuffer::read_elem_filtered(
|
||||
const float x, const float y, float dx[2], float dy[2], float *out) const
|
||||
const float x, const float y, float dx[2], float dy[2], bool extend_boundary, float *out) const
|
||||
{
|
||||
BLI_assert(datatype_ == DataType::Color);
|
||||
|
||||
@ -441,7 +447,7 @@ void MemoryBuffer::read_elem_filtered(
|
||||
uv_normal,
|
||||
du_normal,
|
||||
dv_normal,
|
||||
read_ewa_elem,
|
||||
extend_boundary ? read_ewa_elem_clamped : read_ewa_elem_checked,
|
||||
const_cast<MemoryBuffer *>(this),
|
||||
out);
|
||||
}
|
||||
|
@ -263,7 +263,8 @@ class MemoryBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
void read_elem_filtered(float x, float y, float dx[2], float dy[2], float *out) const;
|
||||
void read_elem_filtered(
|
||||
float x, float y, float dx[2], float dy[2], bool extend_boundary, float *out) const;
|
||||
|
||||
/**
|
||||
* Get channel value at given coordinates.
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "COM_CornerPinNode.h"
|
||||
|
||||
#include "COM_PlaneCornerPinOperation.h"
|
||||
#include "COM_SMAAOperation.h"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
@ -13,7 +14,36 @@ CornerPinNode::CornerPinNode(bNode *editor_node) : Node(editor_node) {}
|
||||
void CornerPinNode::convert_to_operations(NodeConverter &converter,
|
||||
const CompositorContext & /*context*/) const
|
||||
{
|
||||
NodeInput *input_image = this->get_input_socket(0);
|
||||
PlaneCornerPinMaskOperation *plane_mask_operation = new PlaneCornerPinMaskOperation();
|
||||
converter.add_operation(plane_mask_operation);
|
||||
|
||||
SMAAEdgeDetectionOperation *smaa_edge_detection = new SMAAEdgeDetectionOperation();
|
||||
converter.add_operation(smaa_edge_detection);
|
||||
|
||||
converter.add_link(plane_mask_operation->get_output_socket(),
|
||||
smaa_edge_detection->get_input_socket(0));
|
||||
|
||||
SMAABlendingWeightCalculationOperation *smaa_blending_weights =
|
||||
new SMAABlendingWeightCalculationOperation();
|
||||
converter.add_operation(smaa_blending_weights);
|
||||
|
||||
converter.add_link(smaa_edge_detection->get_output_socket(),
|
||||
smaa_blending_weights->get_input_socket(0));
|
||||
|
||||
SMAANeighborhoodBlendingOperation *smaa_neighborhood = new SMAANeighborhoodBlendingOperation();
|
||||
converter.add_operation(smaa_neighborhood);
|
||||
|
||||
converter.add_link(plane_mask_operation->get_output_socket(),
|
||||
smaa_neighborhood->get_input_socket(0));
|
||||
converter.add_link(smaa_blending_weights->get_output_socket(),
|
||||
smaa_neighborhood->get_input_socket(1));
|
||||
|
||||
converter.map_output_socket(this->get_output_socket(1), smaa_neighborhood->get_output_socket());
|
||||
|
||||
PlaneCornerPinWarpImageOperation *warp_image_operation = new PlaneCornerPinWarpImageOperation();
|
||||
converter.add_operation(warp_image_operation);
|
||||
converter.map_input_socket(this->get_input_socket(0), warp_image_operation->get_input_socket(0));
|
||||
|
||||
/* NOTE: socket order differs between UI node and operations:
|
||||
* bNode uses intuitive order following top-down layout:
|
||||
* upper-left, upper-right, lower-left, lower-right
|
||||
@ -21,23 +51,20 @@ void CornerPinNode::convert_to_operations(NodeConverter &converter,
|
||||
* lower-left, lower-right, upper-right, upper-left
|
||||
*/
|
||||
const int node_corner_index[4] = {3, 4, 2, 1};
|
||||
|
||||
NodeOutput *output_warped_image = this->get_output_socket(0);
|
||||
NodeOutput *output_plane = this->get_output_socket(1);
|
||||
|
||||
PlaneCornerPinWarpImageOperation *warp_image_operation = new PlaneCornerPinWarpImageOperation();
|
||||
converter.add_operation(warp_image_operation);
|
||||
PlaneCornerPinMaskOperation *plane_mask_operation = new PlaneCornerPinMaskOperation();
|
||||
converter.add_operation(plane_mask_operation);
|
||||
|
||||
converter.map_input_socket(input_image, warp_image_operation->get_input_socket(0));
|
||||
for (int i = 0; i < 4; i++) {
|
||||
NodeInput *corner_input = get_input_socket(node_corner_index[i]);
|
||||
converter.map_input_socket(corner_input, warp_image_operation->get_input_socket(i + 1));
|
||||
converter.map_input_socket(corner_input, plane_mask_operation->get_input_socket(i));
|
||||
}
|
||||
converter.map_output_socket(output_warped_image, warp_image_operation->get_output_socket());
|
||||
converter.map_output_socket(output_plane, plane_mask_operation->get_output_socket());
|
||||
|
||||
SetAlphaMultiplyOperation *set_alpha_operation = new SetAlphaMultiplyOperation();
|
||||
converter.add_operation(set_alpha_operation);
|
||||
converter.add_link(warp_image_operation->get_output_socket(),
|
||||
set_alpha_operation->get_input_socket(0));
|
||||
converter.add_link(smaa_neighborhood->get_output_socket(),
|
||||
set_alpha_operation->get_input_socket(1));
|
||||
converter.map_output_socket(this->get_output_socket(0),
|
||||
set_alpha_operation->get_output_socket());
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "COM_PlaneTrackDeformNode.h"
|
||||
|
||||
#include "COM_PlaneTrackOperation.h"
|
||||
#include "COM_SMAAOperation.h"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
@ -22,9 +23,39 @@ void PlaneTrackDeformNode::convert_to_operations(NodeConverter &converter,
|
||||
|
||||
int frame_number = context.get_framenumber();
|
||||
|
||||
NodeInput *input_image = this->get_input_socket(0);
|
||||
NodeOutput *output_warped_image = this->get_output_socket(0);
|
||||
NodeOutput *output_plane = this->get_output_socket(1);
|
||||
PlaneTrackMaskOperation *plane_mask_operation = new PlaneTrackMaskOperation();
|
||||
plane_mask_operation->set_movie_clip(clip);
|
||||
plane_mask_operation->set_tracking_object(data->tracking_object);
|
||||
plane_mask_operation->set_plane_track_name(data->plane_track_name);
|
||||
plane_mask_operation->set_framenumber(frame_number);
|
||||
if (data->flag & CMP_NODE_PLANE_TRACK_DEFORM_FLAG_MOTION_BLUR) {
|
||||
plane_mask_operation->set_motion_blur_samples(data->motion_blur_samples);
|
||||
plane_mask_operation->set_motion_blur_shutter(data->motion_blur_shutter);
|
||||
}
|
||||
converter.add_operation(plane_mask_operation);
|
||||
|
||||
SMAAEdgeDetectionOperation *smaa_edge_detection = new SMAAEdgeDetectionOperation();
|
||||
converter.add_operation(smaa_edge_detection);
|
||||
|
||||
converter.add_link(plane_mask_operation->get_output_socket(),
|
||||
smaa_edge_detection->get_input_socket(0));
|
||||
|
||||
SMAABlendingWeightCalculationOperation *smaa_blending_weights =
|
||||
new SMAABlendingWeightCalculationOperation();
|
||||
converter.add_operation(smaa_blending_weights);
|
||||
|
||||
converter.add_link(smaa_edge_detection->get_output_socket(),
|
||||
smaa_blending_weights->get_input_socket(0));
|
||||
|
||||
SMAANeighborhoodBlendingOperation *smaa_neighborhood = new SMAANeighborhoodBlendingOperation();
|
||||
converter.add_operation(smaa_neighborhood);
|
||||
|
||||
converter.add_link(plane_mask_operation->get_output_socket(),
|
||||
smaa_neighborhood->get_input_socket(0));
|
||||
converter.add_link(smaa_blending_weights->get_output_socket(),
|
||||
smaa_neighborhood->get_input_socket(1));
|
||||
|
||||
converter.map_output_socket(this->get_output_socket(1), smaa_neighborhood->get_output_socket());
|
||||
|
||||
PlaneTrackWarpImageOperation *warp_image_operation = new PlaneTrackWarpImageOperation();
|
||||
warp_image_operation->set_movie_clip(clip);
|
||||
@ -37,21 +68,16 @@ void PlaneTrackDeformNode::convert_to_operations(NodeConverter &converter,
|
||||
}
|
||||
converter.add_operation(warp_image_operation);
|
||||
|
||||
converter.map_input_socket(input_image, warp_image_operation->get_input_socket(0));
|
||||
converter.map_output_socket(output_warped_image, warp_image_operation->get_output_socket());
|
||||
converter.map_input_socket(this->get_input_socket(0), warp_image_operation->get_input_socket(0));
|
||||
|
||||
PlaneTrackMaskOperation *plane_mask_operation = new PlaneTrackMaskOperation();
|
||||
plane_mask_operation->set_movie_clip(clip);
|
||||
plane_mask_operation->set_tracking_object(data->tracking_object);
|
||||
plane_mask_operation->set_plane_track_name(data->plane_track_name);
|
||||
plane_mask_operation->set_framenumber(frame_number);
|
||||
if (data->flag & CMP_NODE_PLANE_TRACK_DEFORM_FLAG_MOTION_BLUR) {
|
||||
plane_mask_operation->set_motion_blur_samples(data->motion_blur_samples);
|
||||
plane_mask_operation->set_motion_blur_shutter(data->motion_blur_shutter);
|
||||
}
|
||||
converter.add_operation(plane_mask_operation);
|
||||
|
||||
converter.map_output_socket(output_plane, plane_mask_operation->get_output_socket());
|
||||
SetAlphaMultiplyOperation *set_alpha_operation = new SetAlphaMultiplyOperation();
|
||||
converter.add_operation(set_alpha_operation);
|
||||
converter.add_link(warp_image_operation->get_output_socket(),
|
||||
set_alpha_operation->get_input_socket(0));
|
||||
converter.add_link(smaa_neighborhood->get_output_socket(),
|
||||
set_alpha_operation->get_input_socket(1));
|
||||
converter.map_output_socket(this->get_output_socket(0),
|
||||
set_alpha_operation->get_output_socket());
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
|
@ -152,7 +152,7 @@ void DisplaceOperation::update_memory_buffer_partial(MemoryBuffer *output,
|
||||
}
|
||||
else {
|
||||
/* EWA filtering (without nearest it gets blurry with NO distortion). */
|
||||
input_color->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], it.out);
|
||||
input_color->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], false, it.out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ void MapUVOperation::update_memory_buffer_partial(MemoryBuffer *output,
|
||||
}
|
||||
else {
|
||||
/* EWA filtering. */
|
||||
input_image->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], it.out);
|
||||
input_image->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], false, it.out);
|
||||
|
||||
/* UV to alpha threshold. */
|
||||
const float threshold = alpha_ * 0.05f;
|
||||
|
@ -99,7 +99,7 @@ void PlaneDistortWarpImageOperation::update_memory_buffer_partial(MemoryBuffer *
|
||||
if (motion_blur_samples_ == 1) {
|
||||
for (; !it.is_end(); ++it) {
|
||||
warp_coord(it.x, it.y, samples_[0].perspective_matrix, uv, deriv);
|
||||
input_img->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], it.out);
|
||||
input_img->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], true, it.out);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -108,7 +108,7 @@ void PlaneDistortWarpImageOperation::update_memory_buffer_partial(MemoryBuffer *
|
||||
for (const int sample : IndexRange(motion_blur_samples_)) {
|
||||
float color[4];
|
||||
warp_coord(it.x, it.y, samples_[sample].perspective_matrix, uv, deriv);
|
||||
input_img->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], color);
|
||||
input_img->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], true, color);
|
||||
add_v4_v4(it.out, color);
|
||||
}
|
||||
mul_v4_fl(it.out, 1.0f / float(motion_blur_samples_));
|
||||
@ -175,14 +175,6 @@ void PlaneDistortWarpImageOperation::get_area_of_interest(const int input_idx,
|
||||
PlaneDistortMaskOperation::PlaneDistortMaskOperation() : PlaneDistortBaseOperation()
|
||||
{
|
||||
add_output_socket(DataType::Value);
|
||||
|
||||
/* Currently hardcoded to 8 samples. */
|
||||
osa_ = 8;
|
||||
}
|
||||
|
||||
void PlaneDistortMaskOperation::init_execution()
|
||||
{
|
||||
BLI_jitter_init(jitter_, osa_);
|
||||
}
|
||||
|
||||
void PlaneDistortMaskOperation::update_memory_buffer_partial(MemoryBuffer *output,
|
||||
@ -190,37 +182,22 @@ void PlaneDistortMaskOperation::update_memory_buffer_partial(MemoryBuffer *outpu
|
||||
Span<MemoryBuffer *> /*inputs*/)
|
||||
{
|
||||
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
|
||||
int inside_count = 0;
|
||||
float accumulated_mask = 0.0f;
|
||||
const float2 point = float2(it.x, it.y);
|
||||
for (const int motion_sample : IndexRange(motion_blur_samples_)) {
|
||||
MotionSample &sample = samples_[motion_sample];
|
||||
inside_count += get_jitter_samples_inside_count(it.x, it.y, sample);
|
||||
const bool is_inside_plane = isect_point_tri_v2(point,
|
||||
sample.frame_space_corners[0],
|
||||
sample.frame_space_corners[1],
|
||||
sample.frame_space_corners[2]) ||
|
||||
isect_point_tri_v2(point,
|
||||
sample.frame_space_corners[0],
|
||||
sample.frame_space_corners[2],
|
||||
sample.frame_space_corners[3]);
|
||||
accumulated_mask += is_inside_plane ? 1.0f : 0.0f;
|
||||
}
|
||||
*it.out = float(inside_count) / (osa_ * motion_blur_samples_);
|
||||
*it.out = accumulated_mask / motion_blur_samples_;
|
||||
}
|
||||
}
|
||||
|
||||
int PlaneDistortMaskOperation::get_jitter_samples_inside_count(int x,
|
||||
int y,
|
||||
MotionSample &sample_data)
|
||||
{
|
||||
float point[2];
|
||||
int inside_count = 0;
|
||||
for (int sample = 0; sample < osa_; sample++) {
|
||||
point[0] = x + jitter_[sample][0];
|
||||
point[1] = y + jitter_[sample][1];
|
||||
if (isect_point_tri_v2(point,
|
||||
sample_data.frame_space_corners[0],
|
||||
sample_data.frame_space_corners[1],
|
||||
sample_data.frame_space_corners[2]) ||
|
||||
isect_point_tri_v2(point,
|
||||
sample_data.frame_space_corners[0],
|
||||
sample_data.frame_space_corners[2],
|
||||
sample_data.frame_space_corners[3]))
|
||||
{
|
||||
inside_count++;
|
||||
}
|
||||
}
|
||||
return inside_count;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
|
@ -60,21 +60,12 @@ class PlaneDistortWarpImageOperation : public PlaneDistortBaseOperation {
|
||||
};
|
||||
|
||||
class PlaneDistortMaskOperation : public PlaneDistortBaseOperation {
|
||||
protected:
|
||||
int osa_;
|
||||
float jitter_[32][2];
|
||||
|
||||
public:
|
||||
PlaneDistortMaskOperation();
|
||||
|
||||
void init_execution() override;
|
||||
|
||||
void update_memory_buffer_partial(MemoryBuffer *output,
|
||||
const rcti &area,
|
||||
Span<MemoryBuffer *> inputs) override;
|
||||
|
||||
private:
|
||||
int get_jitter_samples_inside_count(int x, int y, MotionSample &sample_data);
|
||||
};
|
||||
|
||||
} // namespace blender::compositor
|
||||
|
@ -191,7 +191,9 @@ set(GLSL_SRC
|
||||
shaders/compositor_parallel_reduction.glsl
|
||||
shaders/compositor_pixelate.glsl
|
||||
shaders/compositor_plane_deform.glsl
|
||||
shaders/compositor_plane_deform_mask.glsl
|
||||
shaders/compositor_plane_deform_motion_blur.glsl
|
||||
shaders/compositor_plane_deform_motion_blur_mask.glsl
|
||||
shaders/compositor_premultiply_alpha.glsl
|
||||
shaders/compositor_projector_lens_distortion.glsl
|
||||
shaders/compositor_read_input.glsl
|
||||
@ -315,7 +317,6 @@ set(SRC_SHADER_CREATE_INFOS
|
||||
shaders/infos/compositor_parallel_reduction_info.hh
|
||||
shaders/infos/compositor_pixelate_info.hh
|
||||
shaders/infos/compositor_plane_deform_info.hh
|
||||
shaders/infos/compositor_plane_deform_motion_blur_info.hh
|
||||
shaders/infos/compositor_premultiply_alpha_info.hh
|
||||
shaders/infos/compositor_projector_lens_distortion_info.hh
|
||||
shaders/infos/compositor_read_input_info.hh
|
||||
|
@ -8,8 +8,6 @@ void main()
|
||||
{
|
||||
ivec2 texel = ivec2(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. */
|
||||
vec2 coordinates = (vec2(texel) + vec2(0.5)) / vec2(texture_size(input_tx));
|
||||
|
||||
vec3 transformed_coordinates = mat3(homography_matrix) * vec3(coordinates, 1.0);
|
||||
@ -23,14 +21,8 @@ void main()
|
||||
|
||||
vec4 sampled_color = textureGrad(input_tx, projected_coordinates, x_gradient, y_gradient);
|
||||
|
||||
/* The plane mask is 1 if it is inside the plane and 0 otherwise. However, we use the alpha value
|
||||
* of the sampled color for pixels outside of the plane to utilize the anti-aliasing effect of
|
||||
* the anisotropic filtering. Therefore, the input_tx sampler should use anisotropic filtering
|
||||
* and be clamped to zero border color. */
|
||||
bool is_inside_plane = all(greaterThanEqual(projected_coordinates, vec2(0.0))) &&
|
||||
all(lessThanEqual(projected_coordinates, vec2(1.0)));
|
||||
float mask_value = is_inside_plane ? 1.0 : sampled_color.a;
|
||||
/* Premultiply the mask value as an alpha. */
|
||||
vec4 plane_color = sampled_color * texture_load(mask_tx, texel).x;
|
||||
|
||||
imageStore(output_img, texel, sampled_color);
|
||||
imageStore(mask_img, texel, vec4(mask_value));
|
||||
imageStore(output_img, texel, plane_color);
|
||||
}
|
||||
|
19
source/blender/compositor/realtime_compositor/shaders/compositor_plane_deform_mask.glsl
Normal file
19
source/blender/compositor/realtime_compositor/shaders/compositor_plane_deform_mask.glsl
Normal file
@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
vec2 coordinates = (vec2(texel) + vec2(0.5)) / vec2(imageSize(mask_img));
|
||||
|
||||
vec3 transformed_coordinates = mat3(homography_matrix) * vec3(coordinates, 1.0);
|
||||
vec2 projected_coordinates = transformed_coordinates.xy / transformed_coordinates.z;
|
||||
|
||||
bool is_inside_plane = all(greaterThanEqual(projected_coordinates, vec2(0.0))) &&
|
||||
all(lessThanEqual(projected_coordinates, vec2(1.0)));
|
||||
float mask_value = is_inside_plane ? 1.0 : 0.0;
|
||||
|
||||
imageStore(mask_img, texel, vec4(mask_value));
|
||||
}
|
@ -2,17 +2,14 @@
|
||||
*
|
||||
* 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);
|
||||
|
||||
/* Add 0.5 to evaluate the sampler at the center of the pixel and divide by the size to get the
|
||||
* coordinates into the sampler's expected [0, 1] range. We choose the maximum between both
|
||||
* output sizes because one of the outputs might be a dummy 1x1 image. */
|
||||
ivec2 output_size = max(imageSize(output_img), imageSize(mask_img));
|
||||
vec2 coordinates = (vec2(texel) + vec2(0.5)) / vec2(output_size);
|
||||
vec2 coordinates = (vec2(texel) + vec2(0.5)) / vec2(imageSize(output_img));
|
||||
|
||||
float accumulated_mask = 0.0;
|
||||
vec4 accumulated_color = vec4(0.0);
|
||||
for (int i = 0; i < number_of_motion_blur_samples; i++) {
|
||||
mat3 homography_matrix = mat3(homography_matrices[i]);
|
||||
@ -28,19 +25,12 @@ void main()
|
||||
|
||||
vec4 sampled_color = textureGrad(input_tx, projected_coordinates, x_gradient, y_gradient);
|
||||
accumulated_color += sampled_color;
|
||||
|
||||
/* The plane mask is 1 if it is inside the plane and 0 otherwise. However, we use the alpha
|
||||
* value of the sampled color for pixels outside of the plane to utilize the anti-aliasing
|
||||
* effect of the anisotropic filtering. Therefore, the input_tx sampler should use anisotropic
|
||||
* filtering and be clamped to zero border color. */
|
||||
bool is_inside_plane = all(greaterThanEqual(projected_coordinates, vec2(0.0))) &&
|
||||
all(lessThanEqual(projected_coordinates, vec2(1.0)));
|
||||
accumulated_mask += is_inside_plane ? 1.0 : sampled_color.a;
|
||||
}
|
||||
|
||||
accumulated_mask /= number_of_motion_blur_samples;
|
||||
accumulated_color /= number_of_motion_blur_samples;
|
||||
|
||||
imageStore(output_img, texel, accumulated_color);
|
||||
imageStore(mask_img, texel, vec4(accumulated_mask));
|
||||
/* Premultiply the mask value as an alpha. */
|
||||
vec4 plane_color = accumulated_color * texture_load(mask_tx, texel).x;
|
||||
|
||||
imageStore(output_img, texel, plane_color);
|
||||
}
|
||||
|
26
source/blender/compositor/realtime_compositor/shaders/compositor_plane_deform_motion_blur_mask.glsl
Normal file
26
source/blender/compositor/realtime_compositor/shaders/compositor_plane_deform_motion_blur_mask.glsl
Normal file
@ -0,0 +1,26 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
vec2 coordinates = (vec2(texel) + vec2(0.5)) / vec2(imageSize(mask_img));
|
||||
|
||||
float accumulated_mask = 0.0;
|
||||
for (int i = 0; i < number_of_motion_blur_samples; i++) {
|
||||
mat3 homography_matrix = mat3(homography_matrices[i]);
|
||||
|
||||
vec3 transformed_coordinates = homography_matrix * vec3(coordinates, 1.0);
|
||||
vec2 projected_coordinates = transformed_coordinates.xy / transformed_coordinates.z;
|
||||
|
||||
bool is_inside_plane = all(greaterThanEqual(projected_coordinates, vec2(0.0))) &&
|
||||
all(lessThanEqual(projected_coordinates, vec2(1.0)));
|
||||
accumulated_mask += is_inside_plane ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
accumulated_mask /= number_of_motion_blur_samples;
|
||||
|
||||
imageStore(mask_img, texel, vec4(accumulated_mask));
|
||||
}
|
@ -4,11 +4,36 @@
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_plane_deform_mask)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::MAT4, "homography_matrix")
|
||||
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "mask_img")
|
||||
.compute_source("compositor_plane_deform_mask.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_plane_deform)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::MAT4, "homography_matrix")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.sampler(1, ImageType::FLOAT_2D, "mask_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
|
||||
.image(1, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "mask_img")
|
||||
.compute_source("compositor_plane_deform.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_plane_deform_motion_blur_mask)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::INT, "number_of_motion_blur_samples")
|
||||
.uniform_buf(0, "mat4", "homography_matrices[64]")
|
||||
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "mask_img")
|
||||
.compute_source("compositor_plane_deform_motion_blur_mask.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_plane_deform_motion_blur)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::INT, "number_of_motion_blur_samples")
|
||||
.uniform_buf(0, "mat4", "homography_matrices[64]")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.sampler(1, ImageType::FLOAT_2D, "mask_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
|
||||
.compute_source("compositor_plane_deform_motion_blur.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
@ -1,15 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_plane_deform_motion_blur)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::INT, "number_of_motion_blur_samples")
|
||||
.uniform_buf(0, "mat4", "homography_matrices[64]")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
|
||||
.image(1, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "mask_img")
|
||||
.compute_source("compositor_plane_deform_motion_blur.glsl")
|
||||
.do_static_compilation(true);
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "BKE_tracking.h"
|
||||
|
||||
#include "COM_algorithm_smaa.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
@ -75,31 +76,71 @@ class CornerPinOperation : public NodeOperation {
|
||||
return;
|
||||
}
|
||||
|
||||
Result plane_mask = compute_plane_mask(homography_matrix);
|
||||
Result anti_aliased_plane_mask = context().create_temporary_result(ResultType::Float);
|
||||
smaa(context(), plane_mask, anti_aliased_plane_mask);
|
||||
plane_mask.release();
|
||||
|
||||
if (output_image.should_compute()) {
|
||||
compute_plane(homography_matrix, anti_aliased_plane_mask);
|
||||
}
|
||||
|
||||
if (output_mask.should_compute()) {
|
||||
output_mask.steal_data(anti_aliased_plane_mask);
|
||||
}
|
||||
else {
|
||||
anti_aliased_plane_mask.release();
|
||||
}
|
||||
}
|
||||
|
||||
void compute_plane(const float3x3 &homography_matrix, Result &plane_mask)
|
||||
{
|
||||
GPUShader *shader = context().get_shader("compositor_plane_deform");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_mat3_as_mat4(shader, "homography_matrix", homography_matrix.ptr());
|
||||
|
||||
Result &input_image = get_input("Image");
|
||||
GPU_texture_mipmap_mode(input_image.texture(), true, true);
|
||||
GPU_texture_anisotropic_filter(input_image.texture(), true);
|
||||
GPU_texture_extend_mode(input_image.texture(), GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
|
||||
GPU_texture_extend_mode(input_image.texture(), GPU_SAMPLER_EXTEND_MODE_EXTEND);
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
plane_mask.bind_as_texture(shader, "mask_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
output_mask.allocate_texture(domain);
|
||||
output_mask.bind_as_image(shader, "mask_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
plane_mask.unbind_as_texture();
|
||||
output_image.unbind_as_image();
|
||||
output_mask.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
Result compute_plane_mask(const float3x3 &homography_matrix)
|
||||
{
|
||||
GPUShader *shader = context().get_shader("compositor_plane_deform_mask");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_mat3_as_mat4(shader, "homography_matrix", homography_matrix.ptr());
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result plane_mask = context().create_temporary_result(ResultType::Float);
|
||||
plane_mask.allocate_texture(domain);
|
||||
plane_mask.bind_as_image(shader, "mask_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
plane_mask.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
|
||||
return plane_mask;
|
||||
}
|
||||
|
||||
float3x3 compute_homography_matrix()
|
||||
{
|
||||
float2 lower_left = get_input("Lower Left").get_vector_value_default(float4(0.0f)).xy();
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "GPU_texture.h"
|
||||
#include "GPU_uniform_buffer.h"
|
||||
|
||||
#include "COM_algorithm_smaa.hh"
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
@ -144,40 +145,87 @@ class PlaneTrackDeformOperation : public NodeOperation {
|
||||
}
|
||||
|
||||
const Array<float4x4> homography_matrices = compute_homography_matrices(plane_track);
|
||||
GPUUniformBuf *homography_matrices_buffer = GPU_uniformbuf_create_ex(
|
||||
homography_matrices.size() * sizeof(float4x4),
|
||||
homography_matrices.data(),
|
||||
"Plane Track Deform Homography Matrices");
|
||||
|
||||
Result plane_mask = compute_plane_mask(homography_matrices, homography_matrices_buffer);
|
||||
Result anti_aliased_plane_mask = context().create_temporary_result(ResultType::Float);
|
||||
smaa(context(), plane_mask, anti_aliased_plane_mask);
|
||||
plane_mask.release();
|
||||
|
||||
if (output_image.should_compute()) {
|
||||
compute_plane(homography_matrices, homography_matrices_buffer, anti_aliased_plane_mask);
|
||||
}
|
||||
|
||||
if (output_mask.should_compute()) {
|
||||
output_mask.steal_data(anti_aliased_plane_mask);
|
||||
}
|
||||
else {
|
||||
anti_aliased_plane_mask.release();
|
||||
}
|
||||
|
||||
GPU_uniformbuf_free(homography_matrices_buffer);
|
||||
}
|
||||
|
||||
void compute_plane(const Array<float4x4> &homography_matrices,
|
||||
GPUUniformBuf *homography_matrices_buffer,
|
||||
Result &plane_mask)
|
||||
{
|
||||
GPUShader *shader = context().get_shader("compositor_plane_deform_motion_blur");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "number_of_motion_blur_samples", homography_matrices.size());
|
||||
|
||||
GPUUniformBuf *matrices_buffer = GPU_uniformbuf_create_ex(
|
||||
homography_matrices.size() * sizeof(float4x4),
|
||||
homography_matrices.data(),
|
||||
"Plane Track Deform Homography Matrices");
|
||||
const int ubo_location = GPU_shader_get_ubo_binding(shader, "homography_matrices");
|
||||
GPU_uniformbuf_bind(matrices_buffer, ubo_location);
|
||||
GPU_uniformbuf_bind(homography_matrices_buffer, ubo_location);
|
||||
|
||||
Result &input_image = get_input("Image");
|
||||
GPU_texture_mipmap_mode(input_image.texture(), true, true);
|
||||
GPU_texture_anisotropic_filter(input_image.texture(), true);
|
||||
GPU_texture_extend_mode(input_image.texture(), GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
|
||||
GPU_texture_extend_mode(input_image.texture(), GPU_SAMPLER_EXTEND_MODE_EXTEND);
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
plane_mask.bind_as_texture(shader, "mask_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
output_mask.allocate_texture(domain);
|
||||
output_mask.bind_as_image(shader, "mask_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
plane_mask.unbind_as_texture();
|
||||
output_image.unbind_as_image();
|
||||
output_mask.unbind_as_image();
|
||||
GPU_uniformbuf_unbind(homography_matrices_buffer);
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
Result compute_plane_mask(const Array<float4x4> &homography_matrices,
|
||||
GPUUniformBuf *homography_matrices_buffer)
|
||||
{
|
||||
GPUShader *shader = context().get_shader("compositor_plane_deform_motion_blur_mask");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "number_of_motion_blur_samples", homography_matrices.size());
|
||||
|
||||
const int ubo_location = GPU_shader_get_ubo_binding(shader, "homography_matrices");
|
||||
GPU_uniformbuf_bind(homography_matrices_buffer, ubo_location);
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result plane_mask = context().create_temporary_result(ResultType::Float);
|
||||
plane_mask.allocate_texture(domain);
|
||||
plane_mask.bind_as_image(shader, "mask_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
plane_mask.unbind_as_image();
|
||||
GPU_uniformbuf_unbind(homography_matrices_buffer);
|
||||
GPU_shader_unbind();
|
||||
|
||||
GPU_uniformbuf_unbind(matrices_buffer);
|
||||
GPU_uniformbuf_free(matrices_buffer);
|
||||
return plane_mask;
|
||||
}
|
||||
|
||||
Domain compute_domain() override
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 3a0fc9f3bae1f1a838ed9e2aac0648fdad291817
|
||||
Subproject commit 15c20a4b5d40dfbb1f64c65d898ea22ab00b1f1a
|
Loading…
Reference in New Issue
Block a user