|
| 1 | +#import bevy_render::view::View |
| 2 | +#import bevy_ui::ui_node::{ |
| 3 | + draw_uinode_background, |
| 4 | + draw_uinode_border, |
| 5 | +} |
| 6 | + |
| 7 | +const PI: f32 = 3.14159265358979323846; |
| 8 | +const TAU: f32 = 2. * PI; |
| 9 | + |
| 10 | +const TEXTURED = 1u; |
| 11 | +const RIGHT_VERTEX = 2u; |
| 12 | +const BOTTOM_VERTEX = 4u; |
| 13 | +// must align with BORDER_* shader_flags from bevy_ui/render/mod.rs |
| 14 | +const RADIAL: u32 = 16u; |
| 15 | +const FILL_START: u32 = 32u; |
| 16 | +const FILL_END: u32 = 64u; |
| 17 | +const CONIC: u32 = 128u; |
| 18 | +const BORDER_LEFT: u32 = 256u; |
| 19 | +const BORDER_TOP: u32 = 512u; |
| 20 | +const BORDER_RIGHT: u32 = 1024u; |
| 21 | +const BORDER_BOTTOM: u32 = 2048u; |
| 22 | +const BORDER_ANY: u32 = BORDER_LEFT + BORDER_TOP + BORDER_RIGHT + BORDER_BOTTOM; |
| 23 | + |
| 24 | +fn enabled(flags: u32, mask: u32) -> bool { |
| 25 | + return (flags & mask) != 0u; |
| 26 | +} |
| 27 | + |
| 28 | +@group(0) @binding(0) var<uniform> view: View; |
| 29 | + |
| 30 | +struct GradientVertexOutput { |
| 31 | + @location(0) uv: vec2<f32>, |
| 32 | + @location(1) @interpolate(flat) size: vec2<f32>, |
| 33 | + @location(2) @interpolate(flat) flags: u32, |
| 34 | + @location(3) @interpolate(flat) radius: vec4<f32>, |
| 35 | + @location(4) @interpolate(flat) border: vec4<f32>, |
| 36 | + |
| 37 | + // Position relative to the center of the rectangle. |
| 38 | + @location(5) point: vec2<f32>, |
| 39 | + @location(6) @interpolate(flat) g_start: vec2<f32>, |
| 40 | + @location(7) @interpolate(flat) dir: vec2<f32>, |
| 41 | + @location(8) @interpolate(flat) start_color: vec4<f32>, |
| 42 | + @location(9) @interpolate(flat) start_len: f32, |
| 43 | + @location(10) @interpolate(flat) end_len: f32, |
| 44 | + @location(11) @interpolate(flat) end_color: vec4<f32>, |
| 45 | + @location(12) @interpolate(flat) hint: f32, |
| 46 | + @builtin(position) position: vec4<f32>, |
| 47 | +}; |
| 48 | + |
| 49 | +@vertex |
| 50 | +fn vertex( |
| 51 | + @location(0) vertex_position: vec3<f32>, |
| 52 | + @location(1) vertex_uv: vec2<f32>, |
| 53 | + @location(2) flags: u32, |
| 54 | + |
| 55 | + // x: top left, y: top right, z: bottom right, w: bottom left. |
| 56 | + @location(3) radius: vec4<f32>, |
| 57 | + |
| 58 | + // x: left, y: top, z: right, w: bottom. |
| 59 | + @location(4) border: vec4<f32>, |
| 60 | + @location(5) size: vec2<f32>, |
| 61 | + @location(6) point: vec2<f32>, |
| 62 | + @location(7) @interpolate(flat) g_start: vec2<f32>, |
| 63 | + @location(8) @interpolate(flat) dir: vec2<f32>, |
| 64 | + @location(9) @interpolate(flat) start_color: vec4<f32>, |
| 65 | + @location(10) @interpolate(flat) start_len: f32, |
| 66 | + @location(11) @interpolate(flat) end_len: f32, |
| 67 | + @location(12) @interpolate(flat) end_color: vec4<f32>, |
| 68 | + @location(13) @interpolate(flat) hint: f32 |
| 69 | +) -> GradientVertexOutput { |
| 70 | + var out: GradientVertexOutput; |
| 71 | + out.position = view.clip_from_world * vec4(vertex_position, 1.0); |
| 72 | + out.uv = vertex_uv; |
| 73 | + out.size = size; |
| 74 | + out.flags = flags; |
| 75 | + out.radius = radius; |
| 76 | + out.border = border; |
| 77 | + out.point = point; |
| 78 | + out.dir = dir; |
| 79 | + out.start_color = start_color; |
| 80 | + out.start_len = start_len; |
| 81 | + out.end_len = end_len; |
| 82 | + out.end_color = end_color; |
| 83 | + out.g_start = g_start; |
| 84 | + out.hint = hint; |
| 85 | + |
| 86 | + return out; |
| 87 | +} |
| 88 | + |
| 89 | +@fragment |
| 90 | +fn fragment(in: GradientVertexOutput) -> @location(0) vec4<f32> { |
| 91 | + var g_distance: f32; |
| 92 | + if enabled(in.flags, RADIAL) { |
| 93 | + g_distance = radial_distance(in.point, in.g_start, in.dir.x); |
| 94 | + } else if enabled(in.flags, CONIC) { |
| 95 | + g_distance = conic_distance(in.dir.x, in.point, in.g_start); |
| 96 | + } else { |
| 97 | + g_distance = linear_distance(in.point, in.g_start, in.dir); |
| 98 | + } |
| 99 | + |
| 100 | + let gradient_color = interpolate_gradient( |
| 101 | + g_distance, |
| 102 | + in.start_color, |
| 103 | + in.start_len, |
| 104 | + in.end_color, |
| 105 | + in.end_len, |
| 106 | + in.hint, |
| 107 | + in.flags |
| 108 | + ); |
| 109 | + |
| 110 | + if enabled(in.flags, BORDER_ANY) { |
| 111 | + return draw_uinode_border(gradient_color, in.point, in.size, in.radius, in.border, in.flags); |
| 112 | + } else { |
| 113 | + return draw_uinode_background(gradient_color, in.point, in.size, in.radius, in.border); |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +// Mix colors in sRGB space and convert back to linear for GPU output |
| 118 | +fn mix_srgb_colors(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> { |
| 119 | + let mixed_srgb = mix(a, b, t); |
| 120 | + // Convert from sRGB back to linear RGB for GPU output |
| 121 | + return vec4(pow(mixed_srgb.rgb, vec3(2.2)), mixed_srgb.a); |
| 122 | +} |
| 123 | + |
| 124 | +// These functions are used to calculate the distance in gradient space from the start of the gradient to the point. |
| 125 | +// The distance in gradient space is then used to interpolate between the start and end colors. |
| 126 | + |
| 127 | +fn linear_distance( |
| 128 | + point: vec2<f32>, |
| 129 | + g_start: vec2<f32>, |
| 130 | + g_dir: vec2<f32>, |
| 131 | +) -> f32 { |
| 132 | + return dot(point - g_start, g_dir); |
| 133 | +} |
| 134 | + |
| 135 | +fn radial_distance( |
| 136 | + point: vec2<f32>, |
| 137 | + center: vec2<f32>, |
| 138 | + ratio: f32, |
| 139 | +) -> f32 { |
| 140 | + let d = point - center; |
| 141 | + return length(vec2(d.x, d.y * ratio)); |
| 142 | +} |
| 143 | + |
| 144 | +fn conic_distance( |
| 145 | + start: f32, |
| 146 | + point: vec2<f32>, |
| 147 | + center: vec2<f32>, |
| 148 | +) -> f32 { |
| 149 | + let d = point - center; |
| 150 | + let angle = atan2(-d.x, d.y) + PI; |
| 151 | + return (((angle - start) % TAU) + TAU) % TAU; |
| 152 | +} |
| 153 | + |
| 154 | +fn interpolate_gradient( |
| 155 | + distance: f32, |
| 156 | + start_color: vec4<f32>, |
| 157 | + start_distance: f32, |
| 158 | + end_color: vec4<f32>, |
| 159 | + end_distance: f32, |
| 160 | + hint: f32, |
| 161 | + flags: u32, |
| 162 | +) -> vec4<f32> { |
| 163 | + if start_distance == end_distance { |
| 164 | + if distance <= start_distance && enabled(flags, FILL_START) { |
| 165 | + return start_color; |
| 166 | + } |
| 167 | + if start_distance <= distance && enabled(flags, FILL_END) { |
| 168 | + return end_color; |
| 169 | + } |
| 170 | + return vec4(0.); |
| 171 | + } |
| 172 | + |
| 173 | + var t = (distance - start_distance) / (end_distance - start_distance); |
| 174 | + |
| 175 | + if t < 0.0 { |
| 176 | + if enabled(flags, FILL_START) { |
| 177 | + return start_color; |
| 178 | + } |
| 179 | + return vec4(0.0); |
| 180 | + } |
| 181 | + |
| 182 | + if 1. < t { |
| 183 | + if enabled(flags, FILL_END) { |
| 184 | + return end_color; |
| 185 | + } |
| 186 | + return vec4(0.0); |
| 187 | + } |
| 188 | + |
| 189 | + if t < hint { |
| 190 | + t = 0.5 * t / hint; |
| 191 | + } else { |
| 192 | + t = 0.5 * (1 + (t - hint) / (1.0 - hint)); |
| 193 | + } |
| 194 | + |
| 195 | + // Only color interpolation in SRGB space is supported atm. |
| 196 | + return mix_linear_rgb_in_srgb_space(start_color, end_color, t); |
| 197 | +} |
0 commit comments