From 3883a88d4ef13d8fd9d85f94e2c006054dc909d7 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Tue, 10 Jun 2025 07:42:35 +0200 Subject: [PATCH] Compositor: Maintain alpha in Lens Distortion node The alpha channel value was previously hard codded to one in the lens distortion node. This patch solves that by integrating the alpha as well in the radial distortion mode and taking the average alpha in the horizontal case. This breaks backward compatibility, but is what users what in most cases, so it is acceptable. Fixes #134658. Pull Request: https://projects.blender.org/blender/blender/pulls/137994 --- ...compositor_horizontal_lens_distortion.glsl | 9 +++-- .../compositor_radial_lens_distortion.glsl | 29 ++++++++------ .../nodes/node_composite_lensdist.cc | 40 +++++++++++-------- .../node_lens_distortion_negative.png | 4 +- .../node_lens_distortion_positive.png | 4 +- .../node_lens_distortion_projector.png | 4 +- .../compositor_renders/distorted_bw_key.png | 4 +- 7 files changed, 52 insertions(+), 42 deletions(-) diff --git a/source/blender/compositor/shaders/compositor_horizontal_lens_distortion.glsl b/source/blender/compositor/shaders/compositor_horizontal_lens_distortion.glsl index c2c5ff5a0c7..8edc637d155 100644 --- a/source/blender/compositor/shaders/compositor_horizontal_lens_distortion.glsl +++ b/source/blender/compositor/shaders/compositor_horizontal_lens_distortion.glsl @@ -12,9 +12,10 @@ void main() 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 float red = texture(input_tx, normalized_texel + float2(dispersion, 0.0f)).r; - const float green = texture_load(input_tx, texel).g; - const float blue = texture(input_tx, normalized_texel - float2(dispersion, 0.0f)).b; + 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, green, blue, 1.0f)); + imageStore(output_img, texel, float4(red.r, green.g, blue.b, alpha)); } diff --git a/source/blender/compositor/shaders/compositor_radial_lens_distortion.glsl b/source/blender/compositor/shaders/compositor_radial_lens_distortion.glsl index 9b19b347836..fb328a0fdbe 100644 --- a/source/blender/compositor/shaders/compositor_radial_lens_distortion.glsl +++ b/source/blender/compositor/shaders/compositor_radial_lens_distortion.glsl @@ -48,7 +48,7 @@ int compute_number_of_integration_steps_heuristic(float 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. */ -int3 compute_number_of_integration_steps(float2 uv, float distance_squared) +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); @@ -66,9 +66,9 @@ int3 compute_number_of_integration_steps(float2 uv, float distance_squared) 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 channel is the sum of both the red - * and the blue channel steps because it is computed once with each of them. */ - return int3(steps_red, steps_red + steps_blue, steps_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 @@ -93,9 +93,9 @@ float get_jitter(int seed) * 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. */ -float3 integrate_distortion(int start, int end, float distance_squared, float2 uv, int steps) +float4 integrate_distortion(int start, int end, float distance_squared, float2 uv, int steps) { - float3 accumulated_color = float3(0.0f); + 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 @@ -110,6 +110,7 @@ float3 integrate_distortion(int start, int end, float distance_squared, float2 u 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; } @@ -133,13 +134,13 @@ void main() /* Compute the number of integration steps that should be used to compute each channel of the * distorted pixel. */ - int3 number_of_steps = compute_number_of_integration_steps(uv, distance_squared); + 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. */ - float3 color = float3(0.0f); + 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); @@ -147,10 +148,12 @@ void main() * 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. 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 *= 2.0f / float3(number_of_steps); + * 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, float4(color, 1.0f)); + imageStore(output_img, texel, color); } diff --git a/source/blender/nodes/composite/nodes/node_composite_lensdist.cc b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc index de95a491ea1..b2fd8df3629 100644 --- a/source/blender/nodes/composite/nodes/node_composite_lensdist.cc +++ b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc @@ -153,7 +153,7 @@ static int compute_number_of_integration_steps_heuristic(const float 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. */ -static int3 compute_number_of_integration_steps(const float3 &chromatic_distortion, +static int4 compute_number_of_integration_steps(const float3 &chromatic_distortion, const int2 &size, const float2 &uv, const float distance_squared, @@ -176,9 +176,10 @@ static int3 compute_number_of_integration_steps(const float3 &chromatic_distorti float distortion_blue = math::distance(distorted_uv_green, distorted_uv_blue); int steps_blue = compute_number_of_integration_steps_heuristic(distortion_blue, use_jitter); - /* The number of integration steps used to compute the green channel is the sum of both the red - * and the blue channel steps because it is computed once with each of them. */ - return int3(steps_red, steps_red + steps_blue, steps_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 channels 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 @@ -202,7 +203,7 @@ static float get_jitter(const int2 &texel, const int seed, const bool use_jitter * 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. */ -static float3 integrate_distortion(const int2 &texel, +static float4 integrate_distortion(const int2 &texel, const Result &input, const int2 &size, const float3 &chromatic_distortion, @@ -213,7 +214,7 @@ static float3 integrate_distortion(const int2 &texel, const int steps, const bool use_jitter) { - float3 accumulated_color = float3(0.0f); + 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 @@ -228,6 +229,7 @@ static float3 integrate_distortion(const int2 &texel, float4 color = input.sample_bilinear_zero(distorted_uv / float2(size)); accumulated_color[start] += (1.0f - increment) * color[start]; accumulated_color[end] += increment * color[end]; + accumulated_color.w += color.w; } return accumulated_color; } @@ -256,14 +258,14 @@ static void radial_lens_distortion(const int2 texel, /* Compute the number of integration steps that should be used to compute each channel of the * distorted pixel. */ - int3 number_of_steps = compute_number_of_integration_steps( + int4 number_of_steps = compute_number_of_integration_steps( chromatic_distortion, size, uv, distance_squared, use_jitter); /* 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. */ - float3 color = float3(0.0f); + float4 color = float4(0.0f); color += integrate_distortion(texel, input, size, @@ -289,12 +291,14 @@ static void radial_lens_distortion(const int2 texel, * 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. 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 *= 2.0f / float3(number_of_steps); + * 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); - output.store_pixel(texel, float4(color, 1.0f)); + output.store_pixel(texel, color); } class LensDistortionOperation : public NodeOperation { @@ -372,11 +376,13 @@ class LensDistortionOperation : public NodeOperation { float2 normalized_texel = (float2(texel) + float2(0.5f)) / float2(size); /* Sample the red and blue channels shifted by the dispersion amount. */ - const float red = input.sample_bilinear_zero(normalized_texel + float2(dispersion, 0.0f)).x; - const float green = input.load_pixel(texel).y; - const float blue = input.sample_bilinear_zero(normalized_texel - float2(dispersion, 0.0f)).z; + const float4 red = input.sample_bilinear_zero(normalized_texel + float2(dispersion, 0.0f)); + const float4 green = input.load_pixel(texel); + const float4 blue = input.sample_bilinear_zero(normalized_texel - float2(dispersion, 0.0f)); - output.store_pixel(texel, float4(red, green, blue, 1.0f)); + const float alpha = blender::math::dot(float3(red.w, green.w, blue.w), float3(1.0f)) / 3.0f; + + output.store_pixel(texel, float4(red.x, green.y, blue.z, alpha)); }); } diff --git a/tests/files/compositor/distort/compositor_renders/node_lens_distortion_negative.png b/tests/files/compositor/distort/compositor_renders/node_lens_distortion_negative.png index 525f016dd29..36466577706 100644 --- a/tests/files/compositor/distort/compositor_renders/node_lens_distortion_negative.png +++ b/tests/files/compositor/distort/compositor_renders/node_lens_distortion_negative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6d3e245d821c6fc694bf0e091329d0ecda898ea49b9da0a4665d556ad87e313 -size 79134 +oid sha256:e1f9fe789b1f12eba64f5e2bd982723ef214ebafe2be6a0b19a42bd9e0baaa9d +size 80920 diff --git a/tests/files/compositor/distort/compositor_renders/node_lens_distortion_positive.png b/tests/files/compositor/distort/compositor_renders/node_lens_distortion_positive.png index 427040a8344..838c539c9b1 100644 --- a/tests/files/compositor/distort/compositor_renders/node_lens_distortion_positive.png +++ b/tests/files/compositor/distort/compositor_renders/node_lens_distortion_positive.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e42c74a52d60281866d1c4472a56e9080145e9f4ec8db3c34dfb0a6b73020c36 -size 73266 +oid sha256:81ec9af0e80e855e3d087ff49a41ae6e9e0be864e93af2e12b2d830ae2b04650 +size 76683 diff --git a/tests/files/compositor/distort/compositor_renders/node_lens_distortion_projector.png b/tests/files/compositor/distort/compositor_renders/node_lens_distortion_projector.png index dd2bb6040cc..556c663e2cc 100644 --- a/tests/files/compositor/distort/compositor_renders/node_lens_distortion_projector.png +++ b/tests/files/compositor/distort/compositor_renders/node_lens_distortion_projector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:835ad4fe9a0861f2ca15fb52cb88075e3568458cccee1b790ca3106ca9a94bce -size 85355 +oid sha256:c5c83c01c5e117519bdc5b22513e061eff6c43418d2ddd5a87187fd0921e180d +size 87842 diff --git a/tests/files/compositor/multiple_node_setups/compositor_renders/distorted_bw_key.png b/tests/files/compositor/multiple_node_setups/compositor_renders/distorted_bw_key.png index 4e5b3097c8c..c8f7a436769 100644 --- a/tests/files/compositor/multiple_node_setups/compositor_renders/distorted_bw_key.png +++ b/tests/files/compositor/multiple_node_setups/compositor_renders/distorted_bw_key.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8664981b4ab545ebb4891e2a981ec90a5cac642b25bc09f13e56a9e99a23d0fd -size 45539 +oid sha256:fad1d23246c5a7c010c12732378ffa98b4dff6c8a5cc28f0379b900333cbe0a8 +size 48448