Compositor: Implement Mao UV node for new CPU compositor
Reference #125968.
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
* \ingroup cmpnodes
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
#include "GPU_texture.hh"
|
||||
|
||||
@@ -53,27 +57,25 @@ class MapUVOperation : public NodeOperation {
|
||||
|
||||
void execute() override
|
||||
{
|
||||
/* Not yet supported on CPU. */
|
||||
if (!context().use_gpu()) {
|
||||
for (const bNodeSocket *output : this->node()->output_sockets()) {
|
||||
Result &output_result = get_result(output->identifier);
|
||||
if (output_result.should_compute()) {
|
||||
output_result.allocate_invalid();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_input("Image").is_single_value()) {
|
||||
get_input("Image").pass_through(get_result("Image"));
|
||||
return;
|
||||
}
|
||||
bool nearest_neighbour = get_nearest_neighbour();
|
||||
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_gpu();
|
||||
}
|
||||
else {
|
||||
this->execute_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_gpu()
|
||||
{
|
||||
GPUShader *shader = context().get_shader(get_shader_name());
|
||||
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const bool nearest_neighbour = get_nearest_neighbour();
|
||||
if (!nearest_neighbour) {
|
||||
GPU_shader_uniform_1f(
|
||||
shader, "gradient_attenuation_factor", get_gradient_attenuation_factor());
|
||||
@@ -108,9 +110,144 @@ class MapUVOperation : public NodeOperation {
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
/* A factor that controls the attenuation of the result at the pixels where the gradients of the
|
||||
* UV texture are too high, see the shader for more information. The factor ranges between zero
|
||||
* and one, where it has no effect when it is zero and performs full attenuation when it is 1. */
|
||||
char const *get_shader_name()
|
||||
{
|
||||
return get_nearest_neighbour() ? "compositor_map_uv_nearest_neighbour" :
|
||||
"compositor_map_uv_anisotropic";
|
||||
}
|
||||
|
||||
void execute_cpu()
|
||||
{
|
||||
if (this->get_nearest_neighbour()) {
|
||||
this->execute_cpu_nearest();
|
||||
}
|
||||
else {
|
||||
this->execute_cpu_anisotropic();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_cpu_nearest()
|
||||
{
|
||||
const Result &input_image = get_input("Image");
|
||||
const Result &input_uv = get_input("UV");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
|
||||
parallel_for(domain.size, [&](const int2 texel) {
|
||||
float2 uv_coordinates = input_uv.load_pixel(texel).xy();
|
||||
|
||||
float4 sampled_color = input_image.sample_nearest_zero(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 = input_uv.load_pixel(texel).z;
|
||||
|
||||
float4 result = sampled_color * alpha;
|
||||
|
||||
output_image.store_pixel(texel, result);
|
||||
});
|
||||
}
|
||||
|
||||
void execute_cpu_anisotropic()
|
||||
{
|
||||
const Result &input_image = get_input("Image");
|
||||
const Result &input_uv = get_input("UV");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
const float gradient_attenuation_factor = this->get_gradient_attenuation_factor();
|
||||
|
||||
/* In order to perform EWA sampling, we need to compute the partial derivative of the UV
|
||||
* coordinates along the x and y directions using a finite difference approximation. But in
|
||||
* order to avoid loading multiple neighbouring UV coordinates for each pixel, we operate on
|
||||
* the image in 2x2 blocks of pixels, where the derivatives are computed horizontally and
|
||||
* vertically across the 2x2 block such that odd texels use a forward finite difference
|
||||
* equation while even invocations use a backward finite difference equation. */
|
||||
const int2 size = domain.size;
|
||||
const int2 uv_size = input_uv.domain().size;
|
||||
parallel_for(math::divide_ceil(size, int2(2)), [&](const int2 base_texel) {
|
||||
const int x = base_texel.x * 2;
|
||||
const int y = base_texel.y * 2;
|
||||
|
||||
const int2 lower_left_texel = int2(x, y);
|
||||
const int2 lower_right_texel = int2(x + 1, y);
|
||||
const int2 upper_left_texel = int2(x, y + 1);
|
||||
const int2 upper_right_texel = int2(x + 1, y + 1);
|
||||
|
||||
const float2 lower_left_uv = input_uv.load_pixel(lower_left_texel).xy();
|
||||
const float2 lower_right_uv = input_uv.load_pixel_extended(lower_right_texel).xy();
|
||||
const float2 upper_left_uv = input_uv.load_pixel_extended(upper_left_texel).xy();
|
||||
const float2 upper_right_uv = input_uv.load_pixel_extended(upper_right_texel).xy();
|
||||
|
||||
/* Compute the partial derivatives using finite difference. Divide by the input size since
|
||||
* sample_ewa_zero assumes derivatives with respect to texel coordinates. */
|
||||
const float2 lower_x_gradient = (lower_right_uv - lower_left_uv) / uv_size.x;
|
||||
const float2 left_y_gradient = (upper_left_uv - lower_left_uv) / uv_size.y;
|
||||
const float2 right_y_gradient = (upper_right_uv - lower_right_uv) / uv_size.y;
|
||||
const float2 upper_x_gradient = (upper_right_uv - upper_left_uv) / uv_size.x;
|
||||
|
||||
/* Computes one of the 2x2 pixels given its texel location, coordinates, and gradients. */
|
||||
auto compute_pixel = [&](const int2 &texel,
|
||||
const float2 &coordinates,
|
||||
const float2 &x_gradient,
|
||||
const float2 &y_gradient) {
|
||||
/* 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 = input_image.sample_ewa_zero(coordinates, x_gradient, y_gradient);
|
||||
|
||||
/* The UV coordinates might be defined in only a subset area of the UV textures, in which
|
||||
* case, the gradients would be infinite at the boundary of that area, which would
|
||||
* produce erroneous results due to anisotropic filtering. To workaround this, we
|
||||
* attenuate the result if its computed gradients are too high such that the result tends
|
||||
* to zero when the magnitude of the gradients tends to one, that is when their sum tends
|
||||
* to 2. One is chosen as the threshold because that's the maximum gradient magnitude
|
||||
* when the boundary is the maximum sampler value of one and the out of bound values are
|
||||
* zero. Additionally, the user supplied gradient attenuation factor can be used to
|
||||
* control this attenuation or even disable it when it is zero, ranging between zero and
|
||||
* one. */
|
||||
float gradient_magnitude = (math::length(x_gradient) + math::length(y_gradient)) / 2.0f;
|
||||
float gradient_attenuation = math::max(
|
||||
0.0f, 1.0f - gradient_attenuation_factor * gradient_magnitude);
|
||||
|
||||
/* 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 = input_uv.load_pixel(texel).z;
|
||||
|
||||
float4 result = sampled_color * gradient_attenuation * alpha;
|
||||
|
||||
output_image.store_pixel(texel, result);
|
||||
};
|
||||
|
||||
/* Compute each of the pixels in the 2x2 block, making sure to exempt out of bounds right
|
||||
* and upper pixels. */
|
||||
compute_pixel(lower_left_texel, lower_left_uv, lower_x_gradient, left_y_gradient);
|
||||
if (lower_right_texel.x != size.x) {
|
||||
compute_pixel(lower_right_texel, lower_right_uv, lower_x_gradient, right_y_gradient);
|
||||
}
|
||||
if (upper_left_texel.y != size.y) {
|
||||
compute_pixel(upper_left_texel, upper_left_uv, upper_x_gradient, left_y_gradient);
|
||||
}
|
||||
if (upper_right_texel.x != size.x && upper_right_texel.y != size.y) {
|
||||
compute_pixel(upper_right_texel, upper_right_uv, upper_x_gradient, right_y_gradient);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* A factor that controls the attenuation of the result at the pixels where the gradients of
|
||||
* the UV texture are too high, see the shader for more information. The factor ranges between
|
||||
* zero and one, where it has no effect when it is zero and performs full attenuation when it
|
||||
* is 1. */
|
||||
float get_gradient_attenuation_factor()
|
||||
{
|
||||
return bnode().custom1 / 100.0f;
|
||||
@@ -120,12 +257,6 @@ class MapUVOperation : public NodeOperation {
|
||||
{
|
||||
return bnode().custom2 == CMP_NODE_MAP_UV_FILTERING_NEAREST;
|
||||
}
|
||||
|
||||
char const *get_shader_name()
|
||||
{
|
||||
return get_nearest_neighbour() ? "compositor_map_uv_nearest_neighbour" :
|
||||
"compositor_map_uv_anisotropic";
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, DNode node)
|
||||
|
||||
Reference in New Issue
Block a user