From f755c0b9fe46696ffb2bbec4179a1348f42c1bee Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Sun, 6 Jul 2025 21:30:16 +0100 Subject: [PATCH 1/9] Added colour interpolation path variants for hsv and hsl --- crates/bevy_ui/src/gradients.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bevy_ui/src/gradients.rs b/crates/bevy_ui/src/gradients.rs index eb1d255cc72d2..7086b374167cf 100644 --- a/crates/bevy_ui/src/gradients.rs +++ b/crates/bevy_ui/src/gradients.rs @@ -631,6 +631,14 @@ pub enum InterpolationColorSpace { Srgb, /// Interpolates in linear sRGB space. LinearRgb, + /// Interpolates in HSL space, taking the shortest hue path. + Hsl, + /// Interpolates in HSL space, taking the longest hue path. + HslLong, + /// Interpolates in HSV space, taking the shortest hue path. + Hsv, + /// Interpolates in HSV space, taking the longest hue path. + HsvLong, } /// Set the color space used for interpolation. From 263948efa2976997efd4d79b573cc774b6b50b91 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Sun, 6 Jul 2025 22:03:45 +0100 Subject: [PATCH 2/9] Added shader specializations for hsl and hsv colour interpolation --- crates/bevy_ui_render/src/gradient.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_ui_render/src/gradient.rs b/crates/bevy_ui_render/src/gradient.rs index 9bef5340cb9df..12cfcbeb68cb8 100644 --- a/crates/bevy_ui_render/src/gradient.rs +++ b/crates/bevy_ui_render/src/gradient.rs @@ -186,6 +186,10 @@ impl SpecializedRenderPipeline for GradientPipeline { InterpolationColorSpace::OkLchLong => "IN_OKLCH_LONG", InterpolationColorSpace::Srgb => "IN_SRGB", InterpolationColorSpace::LinearRgb => "IN_LINEAR_RGB", + InterpolationColorSpace::Hsl => "IN_HSL", + InterpolationColorSpace::HslLong => "IN_HSL_LONG", + InterpolationColorSpace::Hsv => "IN_HSV", + InterpolationColorSpace::HsvLong => "IN_HSV_LONG", }; let shader_defs = if key.anti_alias { From 1b3b56354537f6048c4731f7c7e3709c7af86e98 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Sun, 6 Jul 2025 22:06:44 +0100 Subject: [PATCH 3/9] Added hsv and hsl interpolation to gradients shader --- crates/bevy_ui_render/src/gradient.wgsl | 155 +++++++++++++++++++++++- 1 file changed, 152 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui_render/src/gradient.wgsl b/crates/bevy_ui_render/src/gradient.wgsl index 074cf35a35f03..c39a6d3244863 100644 --- a/crates/bevy_ui_render/src/gradient.wgsl +++ b/crates/bevy_ui_render/src/gradient.wgsl @@ -31,7 +31,7 @@ struct GradientVertexOutput { @location(0) uv: vec2, @location(1) @interpolate(flat) size: vec2, @location(2) @interpolate(flat) flags: u32, - @location(3) @interpolate(flat) radius: vec4, + @location(3) @interpolate(flat) radius: vec4, @location(4) @interpolate(flat) border: vec4, // Position relative to the center of the rectangle. @@ -129,7 +129,7 @@ fn linear_rgb_to_oklab(c: vec4) -> vec4 { return vec4( 0.21045426 * l + 0.7936178 * m - 0.004072047 * s, 1.9779985 * l - 2.4285922 * m + 0.4505937 * s, - 0.025904037 * l + 0.78277177 * m - 0.80867577 * s, + 0.025904037 * l + 0.78277177 * m - 0.80867577 * s, c.w ); } @@ -149,7 +149,108 @@ fn oklab_to_linear_rgba(c: vec4) -> vec4 { ); } -fn mix_linear_rgb_in_oklab_space(a: vec4, b: vec4, t: f32) -> vec4 { +fn linear_rgb_to_hsl(c: vec4) -> vec4 { + let maxc = max(max(c.r, c.g), b); + let minc = min(min(c.r, c.g), b); + let delta = maxc - minc; + let l = (maxc + minc) * 0.5; + var h: f32 = 0.0; + var s: f32 = 0.0; + if delta != 0.0 { + s = delta / (1.0 - abs(2.0 * l - 1.0)); + if maxc == c.r { + h = ((c.g - c.b) / delta) % 6.0; + } else if maxc == g { + h = ((c.b - c.r) / delta) + 2.0; + } else { + h = ((c.r - c.g) / delta) + 4.0; + } + h = h / 6.0; + if h < 0.0 { + h = h + 1.0; + } + } + return vec4(h, s, l, c.a); +} + +fn hsl_to_linear_rgb(hsl: vec4) -> vec4 { + let h = hsl.x; + let s = hsl.y; + let l = hsl.z; + let c = (1.0 - abs(2.0 * l - 1.0)) * s; + let hp = h * 6.0; + let x = c * (1.0 - abs(hp % 2.0 - 1.0)); + var r: f32 = 0.0; + var g: f32 = 0.0; + var b: f32 = 0.0; + if 0.0 <= hp && hp < 1.0 { + r = c; g = x; b = 0.0; + } else if 1.0 <= hp && hp < 2.0 { + r = x; g = c; b = 0.0; + } else if 2.0 <= hp && hp < 3.0 { + r = 0.0; g = c; b = x; + } else if 3.0 <= hp && hp < 4.0 { + r = 0.0; g = x; b = c; + } else if 4.0 <= hp && hp < 5.0 { + r = x; g = 0.0; b = c; + } else if 5.0 <= hp && hp < 6.0 { + r = c; g = 0.0; b = x; + } + let m = l - 0.5 * c; + return vec4(r + m, g + m, b + m, hsl.a); +} + +fn linear_rgba_to_hsva(c: vec4) -> vec4 { + let maxc = max(max(c.r, c.g), c.b); + let minc = min(min(c.r, c.g), c.b); + let delta = maxc - minc; + var h: f32 = 0.0; + var s: f32 = 0.0; + let v: f32 = maxc; + if delta != 0.0 { + s = delta / maxc; + if maxc == r { + h = ((c.g - c.b) / delta) % 6.0; + } else if maxc == c.g { + h = ((c.b - c.r) / delta) + 2.0; + } else { + h = ((c.r - c.g) / delta) + 4.0; + } + h = h / 6.0; + if h < 0.0 { + h = h + 1.0; + } + } + return vec4(h, s, v, c.a); +} + +fn hsva_to_linear_rgba(hsva: vec4) -> vec4 { + let h = hsva.x * 6.0; + let s = hsva.y; + let v = hsva.z; + let c = v * s; + let x = c * (1.0 - abs(h % 2.0 - 1.0)); + let m = v - c; + var r: f32 = 0.0; + var g: f32 = 0.0; + var b: f32 = 0.0; + if 0.0 <= h && h < 1.0 { + r = c; g = x; b = 0.0; + } else if 1.0 <= h && h < 2.0 { + r = x; g = c; b = 0.0; + } else if 2.0 <= h && h < 3.0 { + r = 0.0; g = c; b = x; + } else if 3.0 <= h && h < 4.0 { + r = 0.0; g = x; b = c; + } else if 4.0 <= h && h < 5.0 { + r = x; g = 0.0; b = c; + } else if 5.0 <= h && h < 6.0 { + r = c; g = 0.0; b = x; + } + return vec4(r + m, g + m, b + m, hsva.a); +} + +fn mix_linear_rgb_in_oklab_space(a: vec4, b: vec4, t: f32) -> vec4 { return oklab_to_linear_rgba(mix(linear_rgb_to_oklab(a), linear_rgb_to_oklab(b), t)); } @@ -205,6 +306,46 @@ fn mix_linear_rgb_in_oklch_space_long(a: vec4, b: vec4, t: f32) -> vec return oklch_to_linear_rgb(mix_oklch_long(linear_rgb_to_oklch(a), linear_rgb_to_oklch(b), t)); } +fn mix_linear_rgb_in_hsv_space(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgba_to_hsva(a); + let hb = linear_rgba_to_hsva(b); + let h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU; + let s = mix(ha.y, hb.y, t); + let v = mix(ha.z, hb.z, t); + let a_alpha = mix(ha.w, hb.w, t); + return hsva_to_linear_rgba(vec4(h, s, v, a_alpha)); +} + +fn mix_linear_rgb_in_hsv_space_long(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgba_to_hsva(a); + let hb = linear_rgba_to_hsva(b); + let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU; + let s = mix(ha.y, hb.y, t); + let v = mix(ha.z, hb.z, t); + let a_alpha = mix(ha.w, hb.w, t); + return hsva_to_linear_rgba(vec4(h, s, v, a_alpha)); +} + +fn mix_linear_rgb_in_hsl_space(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgb_to_hsl(a); + let hb = linear_rgb_to_hsl(b); + let h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU; + let s = mix(ha.y, hb.y, t); + let l = mix(ha.z, hb.z, t); + let a_alpha = mix(ha.w, hb.w, t); + return hsl_to_linear_rgb(vec4(h, s, l, a_alpha)); +} + +fn mix_linear_rgb_in_hsl_space_long(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgb_to_hsl(a); + let hb = linear_rgb_to_hsl(b); + let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU; + let s = mix(ha.y, hb.y, t); + let l = mix(ha.z, hb.z, t); + let a_alpha = mix(ha.w, hb.w, t); + return hsl_to_linear_rgb(vec4(h, s, l, a_alpha)); +} + // These functions are used to calculate the distance in gradient space from the start of the gradient to the point. // The distance in gradient space is then used to interpolate between the start and end colors. @@ -284,6 +425,14 @@ fn interpolate_gradient( return mix_linear_rgb_in_oklch_space(start_color, end_color, t); #else ifdef IN_OKLCH_LONG return mix_linear_rgb_in_oklch_space_long(start_color, end_color, t); +#else ifdef IN_HSV + return mix_linear_rgb_in_hsv_space(start_color, end_color, t); +#else ifdef IN_HSV_LONG + return mix_linear_rgb_in_hsv_space_long(start_color, end_color, t); +#else ifdef IN_HSL + return mix_linear_rgb_in_hsl_space(start_color, end_color, t); +#else ifdef IN_HSL_LONG + return mix_linear_rgb_in_hsl_space_long(start_color, end_color, t); #else return mix(start_color, end_color, t); #endif From ffcf323f98b19e70fd80941872c74b3d89df81f2 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Sun, 6 Jul 2025 22:06:59 +0100 Subject: [PATCH 4/9] updated gradients example --- examples/ui/gradients.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/ui/gradients.rs b/examples/ui/gradients.rs index 72b915813c673..82c69d4f8ecbb 100644 --- a/examples/ui/gradients.rs +++ b/examples/ui/gradients.rs @@ -245,6 +245,18 @@ fn setup(mut commands: Commands) { InterpolationColorSpace::LinearRgb } InterpolationColorSpace::LinearRgb => { + InterpolationColorSpace::Hsl + } + InterpolationColorSpace::Hsl => { + InterpolationColorSpace::HslLong + } + InterpolationColorSpace::HslLong => { + InterpolationColorSpace::Hsv + } + InterpolationColorSpace::Hsv => { + InterpolationColorSpace::HsvLong + } + InterpolationColorSpace::HsvLong => { InterpolationColorSpace::OkLab } }; From c2f5fa4bf71bb9c1ebc7229f40208feb46219649 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Sun, 6 Jul 2025 22:09:39 +0100 Subject: [PATCH 5/9] fixes for a few syntax errors --- crates/bevy_ui_render/src/gradient.wgsl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ui_render/src/gradient.wgsl b/crates/bevy_ui_render/src/gradient.wgsl index c39a6d3244863..d272907b52223 100644 --- a/crates/bevy_ui_render/src/gradient.wgsl +++ b/crates/bevy_ui_render/src/gradient.wgsl @@ -150,8 +150,8 @@ fn oklab_to_linear_rgba(c: vec4) -> vec4 { } fn linear_rgb_to_hsl(c: vec4) -> vec4 { - let maxc = max(max(c.r, c.g), b); - let minc = min(min(c.r, c.g), b); + let maxc = max(max(c.r, c.g), c.b); + let minc = min(min(c.r, c.g), c.b); let delta = maxc - minc; let l = (maxc + minc) * 0.5; var h: f32 = 0.0; @@ -160,7 +160,7 @@ fn linear_rgb_to_hsl(c: vec4) -> vec4 { s = delta / (1.0 - abs(2.0 * l - 1.0)); if maxc == c.r { h = ((c.g - c.b) / delta) % 6.0; - } else if maxc == g { + } else if maxc == c.g { h = ((c.b - c.r) / delta) + 2.0; } else { h = ((c.r - c.g) / delta) + 4.0; @@ -209,7 +209,7 @@ fn linear_rgba_to_hsva(c: vec4) -> vec4 { let v: f32 = maxc; if delta != 0.0 { s = delta / maxc; - if maxc == r { + if maxc == c.r { h = ((c.g - c.b) / delta) % 6.0; } else if maxc == c.g { h = ((c.b - c.r) / delta) + 2.0; From a497ebcc957a6e806cd4f19bfb40d3356d6cdc0e Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 7 Jul 2025 10:57:19 +0100 Subject: [PATCH 6/9] shader cleanup, use -a suffix for colour spaces consistantly --- crates/bevy_ui_render/src/gradient.wgsl | 94 ++++++++++++------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/crates/bevy_ui_render/src/gradient.wgsl b/crates/bevy_ui_render/src/gradient.wgsl index d272907b52223..bec0300e6b89a 100644 --- a/crates/bevy_ui_render/src/gradient.wgsl +++ b/crates/bevy_ui_render/src/gradient.wgsl @@ -114,15 +114,15 @@ fn fragment(in: GradientVertexOutput) -> @location(0) vec4 { } } -// This function converts two linear rgb colors to srgb space, mixes them, and then converts the result back to linear rgb space. -fn mix_linear_rgb_in_srgb_space(a: vec4, b: vec4, t: f32) -> vec4 { +// This function converts two linear rgba colors to srgba space, mixes them, and then converts the result back to linear rgb space. +fn mix_linear_rgba_in_srgba_space(a: vec4, b: vec4, t: f32) -> vec4 { let a_srgb = pow(a.rgb, vec3(1. / 2.2)); let b_srgb = pow(b.rgb, vec3(1. / 2.2)); let mixed_srgb = mix(a_srgb, b_srgb, t); return vec4(pow(mixed_srgb, vec3(2.2)), mix(a.a, b.a, t)); } -fn linear_rgb_to_oklab(c: vec4) -> vec4 { +fn linear_rgba_to_oklaba(c: vec4) -> vec4 { let l = pow(0.41222146 * c.x + 0.53633255 * c.y + 0.051445995 * c.z, 1. / 3.); let m = pow(0.2119035 * c.x + 0.6806995 * c.y + 0.10739696 * c.z, 1. / 3.); let s = pow(0.08830246 * c.x + 0.28171885 * c.y + 0.6299787 * c.z, 1. / 3.); @@ -130,11 +130,11 @@ fn linear_rgb_to_oklab(c: vec4) -> vec4 { 0.21045426 * l + 0.7936178 * m - 0.004072047 * s, 1.9779985 * l - 2.4285922 * m + 0.4505937 * s, 0.025904037 * l + 0.78277177 * m - 0.80867577 * s, - c.w + c.a ); } -fn oklab_to_linear_rgba(c: vec4) -> vec4 { +fn oklaba_to_linear_rgba(c: vec4) -> vec4 { let l_ = c.x + 0.39633778 * c.y + 0.21580376 * c.z; let m_ = c.x - 0.105561346 * c.y - 0.06385417 * c.z; let s_ = c.x - 0.08948418 * c.y - 1.2914855 * c.z; @@ -145,11 +145,15 @@ fn oklab_to_linear_rgba(c: vec4) -> vec4 { 4.0767417 * l - 3.3077116 * m + 0.23096994 * s, -1.268438 * l + 2.6097574 * m - 0.34131938 * s, -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s, - c.w + c.a ); } -fn linear_rgb_to_hsl(c: vec4) -> vec4 { +fn mix_linear_rgba_in_oklaba_space(a: vec4, b: vec4, t: f32) -> vec4 { + return oklaba_to_linear_rgba(mix(linear_rgba_to_oklaba(a), linear_rgba_to_oklaba(b), t)); +} + +fn linear_rgba_to_hsla(c: vec4) -> vec4 { let maxc = max(max(c.r, c.g), c.b); let minc = min(min(c.r, c.g), c.b); let delta = maxc - minc; @@ -173,7 +177,7 @@ fn linear_rgb_to_hsl(c: vec4) -> vec4 { return vec4(h, s, l, c.a); } -fn hsl_to_linear_rgb(hsl: vec4) -> vec4 { +fn hsla_to_linear_rgba(hsl: vec4) -> vec4 { let h = hsl.x; let s = hsl.y; let l = hsl.z; @@ -250,22 +254,18 @@ fn hsva_to_linear_rgba(hsva: vec4) -> vec4 { return vec4(r + m, g + m, b + m, hsva.a); } -fn mix_linear_rgb_in_oklab_space(a: vec4, b: vec4, t: f32) -> vec4 { - return oklab_to_linear_rgba(mix(linear_rgb_to_oklab(a), linear_rgb_to_oklab(b), t)); -} - /// hue is left in radians and not converted to degrees -fn linear_rgb_to_oklch(c: vec4) -> vec4 { - let o = linear_rgb_to_oklab(c); +fn linear_rgba_to_oklcha(c: vec4) -> vec4 { + let o = linear_rgba_to_oklaba(c); let chroma = sqrt(o.y * o.y + o.z * o.z); let hue = atan2(o.z, o.y); - return vec4(o.x, chroma, select(hue + TAU, hue, hue < 0.0), o.w); + return vec4(o.x, chroma, select(hue + TAU, hue, hue < 0.0), o.a); } -fn oklch_to_linear_rgb(c: vec4) -> vec4 { +fn oklcha_to_linear_rgba(c: vec4) -> vec4 { let a = c.y * cos(c.z); let b = c.y * sin(c.z); - return oklab_to_linear_rgba(vec4(c.x, a, b, c.w)); + return oklaba_to_linear_rgba(vec4(c.x, a, b, c.a)); } fn rem_euclid(a: f32, b: f32) -> f32 { @@ -282,68 +282,68 @@ fn lerp_hue_long(a: f32, b: f32, t: f32) -> f32 { return rem_euclid(a + select(diff - TAU, diff + TAU, 0. < diff) * t, TAU); } -fn mix_oklch(a: vec4, b: vec4, t: f32) -> vec4 { +fn mix_oklcha(a: vec4, b: vec4, t: f32) -> vec4 { return vec4( mix(a.xy, b.xy, t), lerp_hue(a.z, b.z, t), - mix(a.w, b.w, t) + mix(a.a, b.a, t) ); } -fn mix_oklch_long(a: vec4, b: vec4, t: f32) -> vec4 { +fn mix_oklcha_long(a: vec4, b: vec4, t: f32) -> vec4 { return vec4( mix(a.xy, b.xy, t), lerp_hue_long(a.z, b.z, t), - mix(a.w, b.w, t) + mix(a.a, b.a, t) ); } -fn mix_linear_rgb_in_oklch_space(a: vec4, b: vec4, t: f32) -> vec4 { - return oklch_to_linear_rgb(mix_oklch(linear_rgb_to_oklch(a), linear_rgb_to_oklch(b), t)); +fn mix_linear_rgba_in_oklcha_space(a: vec4, b: vec4, t: f32) -> vec4 { + return oklcha_to_linear_rgba(mix_oklcha(linear_rgba_to_oklcha(a), linear_rgba_to_oklcha(b), t)); } -fn mix_linear_rgb_in_oklch_space_long(a: vec4, b: vec4, t: f32) -> vec4 { - return oklch_to_linear_rgb(mix_oklch_long(linear_rgb_to_oklch(a), linear_rgb_to_oklch(b), t)); +fn mix_linear_rgba_in_oklcha_space_long(a: vec4, b: vec4, t: f32) -> vec4 { + return oklcha_to_linear_rgba(mix_oklcha_long(linear_rgba_to_oklcha(a), linear_rgba_to_oklcha(b), t)); } -fn mix_linear_rgb_in_hsv_space(a: vec4, b: vec4, t: f32) -> vec4 { +fn mix_linear_rgba_in_hsva_space(a: vec4, b: vec4, t: f32) -> vec4 { let ha = linear_rgba_to_hsva(a); let hb = linear_rgba_to_hsva(b); let h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU; let s = mix(ha.y, hb.y, t); let v = mix(ha.z, hb.z, t); - let a_alpha = mix(ha.w, hb.w, t); + let a_alpha = mix(ha.a, hb.a, t); return hsva_to_linear_rgba(vec4(h, s, v, a_alpha)); } -fn mix_linear_rgb_in_hsv_space_long(a: vec4, b: vec4, t: f32) -> vec4 { +fn mix_linear_rgba_in_hsva_space_long(a: vec4, b: vec4, t: f32) -> vec4 { let ha = linear_rgba_to_hsva(a); let hb = linear_rgba_to_hsva(b); let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU; let s = mix(ha.y, hb.y, t); let v = mix(ha.z, hb.z, t); - let a_alpha = mix(ha.w, hb.w, t); + let a_alpha = mix(ha.a, hb.a, t); return hsva_to_linear_rgba(vec4(h, s, v, a_alpha)); } -fn mix_linear_rgb_in_hsl_space(a: vec4, b: vec4, t: f32) -> vec4 { - let ha = linear_rgb_to_hsl(a); - let hb = linear_rgb_to_hsl(b); +fn mix_linear_rgba_in_hsla_space(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgba_to_hsla(a); + let hb = linear_rgba_to_hsla(b); let h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU; let s = mix(ha.y, hb.y, t); let l = mix(ha.z, hb.z, t); - let a_alpha = mix(ha.w, hb.w, t); - return hsl_to_linear_rgb(vec4(h, s, l, a_alpha)); + let a_alpha = mix(ha.a, hb.a, t); + return hsla_to_linear_rgba(vec4(h, s, l, a_alpha)); } -fn mix_linear_rgb_in_hsl_space_long(a: vec4, b: vec4, t: f32) -> vec4 { - let ha = linear_rgb_to_hsl(a); - let hb = linear_rgb_to_hsl(b); +fn mix_linear_rgba_in_hsla_space_long(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgba_to_hsla(a); + let hb = linear_rgba_to_hsla(b); let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU; let s = mix(ha.y, hb.y, t); let l = mix(ha.z, hb.z, t); - let a_alpha = mix(ha.w, hb.w, t); - return hsl_to_linear_rgb(vec4(h, s, l, a_alpha)); + let a_alpha = mix(ha.a, hb.a, t); + return hsla_to_linear_rgba(vec4(h, s, l, a_alpha)); } // These functions are used to calculate the distance in gradient space from the start of the gradient to the point. @@ -418,21 +418,21 @@ fn interpolate_gradient( } #ifdef IN_SRGB - return mix_linear_rgb_in_srgb_space(start_color, end_color, t); + return mix_linear_rgba_in_srgba_space(start_color, end_color, t); #else ifdef IN_OKLAB - return mix_linear_rgb_in_oklab_space(start_color, end_color, t); + return mix_linear_rgba_in_oklaba_space(start_color, end_color, t); #else ifdef IN_OKLCH - return mix_linear_rgb_in_oklch_space(start_color, end_color, t); + return mix_linear_rgba_in_oklcha_space(start_color, end_color, t); #else ifdef IN_OKLCH_LONG - return mix_linear_rgb_in_oklch_space_long(start_color, end_color, t); + return mix_linear_rgba_in_oklcha_space_long(start_color, end_color, t); #else ifdef IN_HSV - return mix_linear_rgb_in_hsv_space(start_color, end_color, t); + return mix_linear_rgba_in_hsva_space(start_color, end_color, t); #else ifdef IN_HSV_LONG - return mix_linear_rgb_in_hsv_space_long(start_color, end_color, t); + return mix_linear_rgba_in_hsva_space_long(start_color, end_color, t); #else ifdef IN_HSL - return mix_linear_rgb_in_hsl_space(start_color, end_color, t); + return mix_linear_rgba_in_hsla_space(start_color, end_color, t); #else ifdef IN_HSL_LONG - return mix_linear_rgb_in_hsl_space_long(start_color, end_color, t); + return mix_linear_rgba_in_hsla_space_long(start_color, end_color, t); #else return mix(start_color, end_color, t); #endif From f0a2b9543b179095f7280df210d98f839568eb45 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 7 Jul 2025 12:41:34 +0100 Subject: [PATCH 7/9] Updated gradients release note --- release-content/release-notes/ui_gradients.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release-content/release-notes/ui_gradients.md b/release-content/release-notes/ui_gradients.md index 15ea3bf5edb0f..147377087cd63 100644 --- a/release-content/release-notes/ui_gradients.md +++ b/release-content/release-notes/ui_gradients.md @@ -1,7 +1,7 @@ --- title: UI Gradients authors: ["@Ickshonpe"] -pull_requests: [18139, 19330] +pull_requests: [18139, 19330, 19992] --- Support for UI node's that display a gradient that transitions smoothly between two or more colors. @@ -19,7 +19,7 @@ vec![vec![ColorStop::new(RED, Val::Percent(90.), ColorStop::new(Color::GREEN, Va the colors will be reordered and the gradient will transition from green at 10% to red at 90%. -Colors can be interpolated between the stops in OKLab, OKLCH, SRGB and linear RGB color spaces. The hint is a normalized value that can be used to shift the mid-point where the colors are mixed 50-50 between the stop with the hint and the following stop. +Colors can be interpolated between the stops in OKLab, OKLCH, SRGB, HSL, HSV and linear RGB color spaces. The hint is a normalized value that can be used to shift the mid-point where the colors are mixed 50-50 between the stop with the hint and the following stop. Cylindrical color spaces support interpolation along both short and long hue paths. For sharp stops with no interpolated transition, place two stops at the same point. From 6381b9df25ecfac24be57f22fa10cd87c1fd2a62 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 7 Jul 2025 12:48:46 +0100 Subject: [PATCH 8/9] Fixed the syntax of color stops example in the UI Gradients release note --- release-content/release-notes/ui_gradients.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/release-notes/ui_gradients.md b/release-content/release-notes/ui_gradients.md index 147377087cd63..155e394892aac 100644 --- a/release-content/release-notes/ui_gradients.md +++ b/release-content/release-notes/ui_gradients.md @@ -14,7 +14,7 @@ Each gradient type consists of the geometric properties for that gradient, a lis Color stops consist of a color, a position or angle and an optional hint. If no position is specified for a stop, it's evenly spaced between the previous and following stops. Color stop positions are absolute. With the list of stops: ```rust -vec![vec![ColorStop::new(RED, Val::Percent(90.), ColorStop::new(Color::GREEN, Val::Percent(10.)) +vec![ColorStop::new(RED, Val::Percent(90.), ColorStop::new(GREEN), Val::Percent(10.))] ``` the colors will be reordered and the gradient will transition from green at 10% to red at 90%. From 756c9f7eecf4617cc8251e792d626d24ea936312 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 7 Jul 2025 15:44:44 +0100 Subject: [PATCH 9/9] Don't interpolate the hue when the saturation is 0. --- crates/bevy_ui_render/src/gradient.wgsl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui_render/src/gradient.wgsl b/crates/bevy_ui_render/src/gradient.wgsl index bec0300e6b89a..7ee4ce886272a 100644 --- a/crates/bevy_ui_render/src/gradient.wgsl +++ b/crates/bevy_ui_render/src/gradient.wgsl @@ -309,7 +309,14 @@ fn mix_linear_rgba_in_oklcha_space_long(a: vec4, b: vec4, t: f32) -> v fn mix_linear_rgba_in_hsva_space(a: vec4, b: vec4, t: f32) -> vec4 { let ha = linear_rgba_to_hsva(a); let hb = linear_rgba_to_hsva(b); - let h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU; + var h: f32; + if ha.y == 0. { + h = hb.x; + } else if hb.y == 0. { + h = ha.x; + } else { + h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU; + } let s = mix(ha.y, hb.y, t); let v = mix(ha.z, hb.z, t); let a_alpha = mix(ha.a, hb.a, t);