Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1#import bevy_render::view::View
2#import bevy_ui::ui_node::{
3 draw_uinode_background,
4 draw_uinode_border,
5}
7const PI: f32 = 3.14159265358979323846;
8const TAU: f32 = 2. * PI;
9const HUE_GUARD: f32 = 0.0001;
11const TEXTURED = 1u;
12const RIGHT_VERTEX = 2u;
13const BOTTOM_VERTEX = 4u;
14// must align with BORDER_* shader_flags from bevy_ui/render/mod.rs
15const RADIAL: u32 = 16u;
16const FILL_START: u32 = 32u;
17const FILL_END: u32 = 64u;
18const CONIC: u32 = 128u;
19const BORDER_LEFT: u32 = 256u;
20const BORDER_TOP: u32 = 512u;
21const BORDER_RIGHT: u32 = 1024u;
22const BORDER_BOTTOM: u32 = 2048u;
23const BORDER_ANY: u32 = BORDER_LEFT + BORDER_TOP + BORDER_RIGHT + BORDER_BOTTOM;
25fn enabled(flags: u32, mask: u32) -> bool {
26 return (flags & mask) != 0u;
29@group(0) @binding(0) var<uniform> view: View;
31struct GradientVertexOutput {
32 @location(0) uv: vec2<f32>,
33 @location(1) @interpolate(flat) size: vec2<f32>,
34 @location(2) @interpolate(flat) flags: u32,
35 @location(3) @interpolate(flat) radius: vec4<f32>,
36 @location(4) @interpolate(flat) border: vec4<f32>,
38 // Position relative to the center of the rectangle.
39 @location(5) point: vec2<f32>,
40 @location(6) @interpolate(flat) g_start: vec2<f32>,
41 @location(7) @interpolate(flat) dir: vec2<f32>,
42 @location(8) @interpolate(flat) start_color: vec4<f32>,
43 @location(9) @interpolate(flat) start_len: f32,
44 @location(10) @interpolate(flat) end_len: f32,
45 @location(11) @interpolate(flat) end_color: vec4<f32>,
46 @location(12) @interpolate(flat) hint: f32,
47 @builtin(position) position: vec4<f32>,
48};
50@vertex
51fn vertex(
52 @location(0) vertex_position: vec3<f32>,
53 @location(1) vertex_uv: vec2<f32>,
54 @location(2) flags: u32,
56 // x: top left, y: top right, z: bottom right, w: bottom left.
57 @location(3) radius: vec4<f32>,
59 // x: left, y: top, z: right, w: bottom.
60 @location(4) border: vec4<f32>,
61 @location(5) size: vec2<f32>,
62 @location(6) point: vec2<f32>,
63 @location(7) @interpolate(flat) g_start: vec2<f32>,
64 @location(8) @interpolate(flat) dir: vec2<f32>,
65 @location(9) @interpolate(flat) start_color: vec4<f32>,
66 @location(10) @interpolate(flat) start_len: f32,
67 @location(11) @interpolate(flat) end_len: f32,
68 @location(12) @interpolate(flat) end_color: vec4<f32>,
69 @location(13) @interpolate(flat) hint: f32
70) -> GradientVertexOutput {
71 var out: GradientVertexOutput;
72 out.position = view.clip_from_world * vec4(vertex_position, 1.0);
73 out.uv = vertex_uv;
74 out.size = size;
75 out.flags = flags;
76 out.radius = radius;
77 out.border = border;
78 out.point = point;
79 out.dir = dir;
80 out.start_color = start_color;
81 out.start_len = start_len;
82 out.end_len = end_len;
83 out.end_color = end_color;
84 out.g_start = g_start;
85 out.hint = hint;
87 return out;
90@fragment
91fn fragment(in: GradientVertexOutput) -> @location(0) vec4<f32> {
92 var g_distance: f32;
93 if enabled(in.flags, RADIAL) {
94 g_distance = radial_distance(in.point, in.g_start, in.dir.x);
95 } else if enabled(in.flags, CONIC) {
96 g_distance = conic_distance(in.dir.x, in.point, in.g_start);
97 } else {
98 g_distance = linear_distance(in.point, in.g_start, in.dir);
99 }
101 let gradient_color = interpolate_gradient(
102 g_distance,
103 in.start_color,
104 in.start_len,
105 in.end_color,
106 in.end_len,
107 in.hint,
108 in.flags
109 );
111 if enabled(in.flags, BORDER_ANY) {
112 return draw_uinode_border(gradient_color, in.point, in.size, in.radius, in.border, in.flags);
113 } else {
114 return draw_uinode_background(gradient_color, in.point, in.size, in.radius, in.border);
115 }
118// https://en.wikipedia.org/wiki/SRGB
119fn gamma(value: f32) -> f32 {
120 if value <= 0.0 {
121 return value;
122 }
123 if value <= 0.04045 {
124 return value / 12.92; // linear falloff in dark values
125 } else {
126 return pow((value + 0.055) / 1.055, 2.4); // gamma curve in other area
127 }
130// https://en.wikipedia.org/wiki/SRGB
131fn inverse_gamma(value: f32) -> f32 {
132 if value <= 0.0 {
133 return value;
134 }
136 if value <= 0.0031308 {
137 return value * 12.92; // linear falloff in dark values
138 } else {
139 return 1.055 * pow(value, 1.0 / 2.4) - 0.055; // gamma curve in other area
140 }
143fn srgb_to_linear_rgb(color: vec3<f32>) -> vec3<f32> {
144 return vec3(
145 gamma(color.x),
146 gamma(color.y),
147 gamma(color.z)
148 );
151fn linear_rgb_to_srgb(color: vec3<f32>) -> vec3<f32> {
152 return vec3(
153 inverse_gamma(color.x),
154 inverse_gamma(color.y),
155 inverse_gamma(color.z)
156 );
159fn oklab_to_linear_rgb(c: vec3<f32>) -> vec3<f32> {
160 let l_ = c.x + 0.39633778 * c.y + 0.21580376 * c.z;
161 let m_ = c.x - 0.105561346 * c.y - 0.06385417 * c.z;
162 let s_ = c.x - 0.08948418 * c.y - 1.2914855 * c.z;
163 let l = l_ * l_ * l_;
164 let m = m_ * m_ * m_;
165 let s = s_ * s_ * s_;
166 return vec3(
167 4.0767417 * l - 3.3077116 * m + 0.23096994 * s,
168 -1.268438 * l + 2.6097574 * m - 0.34131938 * s,
169 -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s,
170 );
173fn hsl_to_linear_rgb(hsl: vec3<f32>) -> vec3<f32> {
174 let h = hsl.x;
175 let s = hsl.y;
176 let l = hsl.z;
177 let c = (1.0 - abs(2.0 * l - 1.0)) * s;
178 let hp = h * 6.0;
179 let x = c * (1.0 - abs(hp % 2.0 - 1.0));
180 var r: f32 = 0.0;
181 var g: f32 = 0.0;
182 var b: f32 = 0.0;
183 if 0.0 <= hp && hp < 1.0 {
184 r = c; g = x; b = 0.0;
185 } else if 1.0 <= hp && hp < 2.0 {
186 r = x; g = c; b = 0.0;
187 } else if 2.0 <= hp && hp < 3.0 {
188 r = 0.0; g = c; b = x;
189 } else if 3.0 <= hp && hp < 4.0 {
190 r = 0.0; g = x; b = c;
191 } else if 4.0 <= hp && hp < 5.0 {
192 r = x; g = 0.0; b = c;
193 } else if 5.0 <= hp && hp < 6.0 {
194 r = c; g = 0.0; b = x;
195 }
196 let m = l - 0.5 * c;
197 return srgb_to_linear_rgb(vec3(r + m, g + m, b + m));
200fn hsv_to_linear_rgb(hsva: vec3<f32>) -> vec3<f32> {
201 let h = hsva.x * 6.0;
202 let s = hsva.y;
203 let v = hsva.z;
204 let c = v * s;
205 let x = c * (1.0 - abs(h % 2.0 - 1.0));
206 let m = v - c;
207 var r: f32 = 0.0;
208 var g: f32 = 0.0;
209 var b: f32 = 0.0;
210 if 0.0 <= h && h < 1.0 {
211 r = c; g = x; b = 0.0;
212 } else if 1.0 <= h && h < 2.0 {
213 r = x; g = c; b = 0.0;
214 } else if 2.0 <= h && h < 3.0 {
215 r = 0.0; g = c; b = x;
216 } else if 3.0 <= h && h < 4.0 {
217 r = 0.0; g = x; b = c;
218 } else if 4.0 <= h && h < 5.0 {
219 r = x; g = 0.0; b = c;
220 } else if 5.0 <= h && h < 6.0 {
221 r = c; g = 0.0; b = x;
222 }
223 return srgb_to_linear_rgb(vec3(r + m, g + m, b + m));
226fn oklch_to_linear_rgb(c: vec3<f32>) -> vec3<f32> {
227 let hue = c.z * TAU;
228 return oklab_to_linear_rgb(vec3(c.x, c.y * cos(hue), c.y * sin(hue)));
231fn rem_euclid(a: f32, b: f32) -> f32 {
232 return ((a % b) + b) % b;
236// These functions are used to calculate the distance in gradient space from the start of the gradient to the point.
237// The distance in gradient space is then used to interpolate between the start and end colors.
239fn linear_distance(
240 point: vec2<f32>,
241 g_start: vec2<f32>,
242 g_dir: vec2<f32>,
243) -> f32 {
244 return dot(point - g_start, g_dir);
247fn radial_distance(
248 point: vec2<f32>,
249 center: vec2<f32>,
250 ratio: f32,
251) -> f32 {
252 let d = point - center;
253 return length(vec2(d.x, d.y * ratio));
256fn conic_distance(
257 start: f32,
258 point: vec2<f32>,
259 center: vec2<f32>,
260) -> f32 {
261 let d = point - center;
262 let angle = atan2(-d.x, d.y) + PI;
263 return (((angle - start) % TAU) + TAU) % TAU;
266fn mix_oklch(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
267 // If the chroma is close to zero for one of the endpoints, don't interpolate
268 // the hue and instead use the hue of the other endpoint. This allows gradients that smoothly
269 // transition from black or white to a target color without passing through unrelated hues.
270 var h = a.z;
271 var g = b.z;
272 if a.y < HUE_GUARD {
273 h = g;
274 } else if b.y < HUE_GUARD {
275 g = h;
276 }
278 let hue_diff = g - h;
279 if abs(hue_diff) > 0.5 {
280 if hue_diff > 0.0 {
281 h += (hue_diff - 1.) * t;
282 } else {
283 h += (hue_diff + 1.) * t;
284 }
285 } else {
286 h += hue_diff * t;
287 }
288 return vec3(
289 mix(a.x, b.x, t),
290 mix(a.y, b.y, t),
291 fract(h),
292 );
295fn mix_oklch_long(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
296 var h = a.z;
297 var g = b.z;
298 if a.y < HUE_GUARD {
299 h = g;
300 } else if b.y < HUE_GUARD {
301 g = h;
302 }
304 let hue_diff = g - h;
305 if abs(hue_diff) < 0.5 {
306 if hue_diff >= 0.0 {
307 h += (hue_diff - 1.) * t;
308 } else {
309 h += (hue_diff + 1.) * t;
310 }
311 } else {
312 h += hue_diff * t;
313 }
314 return vec3(
315 mix(a.x, b.x, t),
316 mix(a.y, b.y, t),
317 fract(h),
318 );
321fn mix_hsl(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
322 // If the saturation is close to zero for one of the endpoints, don't interpolate
323 // the hue and instead use the hue of the other endpoint. This allows gradients that smoothly
324 // transition from black or white to a target color without passing through unrelated hues.
325 var h = a.x;
326 var g = b.x;
327 if a.y < HUE_GUARD {
328 h = g;
329 } else if b.y < HUE_GUARD {
330 g = h;
331 }
333 return vec3(
334 fract(h + (fract(g - h + 0.5) - 0.5) * t),
335 mix(a.y, b.y, t),
336 mix(a.z, b.z, t),
337 );
340fn mix_hsl_long(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
341 var h = a.x;
342 var g = b.x;
343 if a.y < HUE_GUARD {
344 h = g;
345 } else if b.y < HUE_GUARD {
346 g = h;
347 }
349 let d = fract(g - h + 0.5) - 0.5;
350 return vec3(
351 fract(h + (d + select(1., -1., 0. < d)) * t),
352 mix(a.y, b.y, t),
353 mix(a.z, b.z, t),
354 );
357fn mix_hsv(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
358 // If the saturation is close to zero for one of the endpoints, don't interpolate
359 // the hue and instead use the hue of the other endpoint. This allows gradients that smoothly
360 // transition from black or white to a target color without passing through unrelated hues.
361 var h = a.x;
362 var g = b.x;
363 if a.y < HUE_GUARD {
364 h = g;
365 } else if b.y < HUE_GUARD {
366 g = h;
367 }
369 let hue_diff = g - h;
370 if abs(hue_diff) > 0.5 {
371 if hue_diff > 0.0 {
372 h += (hue_diff - 1.0) * t;
373 } else {
374 h += (hue_diff + 1.0) * t;
375 }
376 } else {
377 h += hue_diff * t;
378 }
379 return vec3(
380 fract(h),
381 mix(a.y, b.y, t),
382 mix(a.z, b.z, t),
383 );
386fn mix_hsv_long(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
387 var h = a.x;
388 var g = b.x;
389 if a.y < HUE_GUARD {
390 h = g;
391 } else if b.y < HUE_GUARD {
392 g = h;
393 }
395 let hue_diff = g - h;
396 if abs(hue_diff) < 0.5 {
397 if hue_diff >= 0.0 {
398 h += (hue_diff - 1.0) * t;
399 } else {
400 h += (hue_diff + 1.0) * t;
401 }
402 } else {
403 h += hue_diff * t;
404 }
405 return vec3(
406 fract(h),
407 mix(a.y, b.y, t),
408 mix(a.z, b.z, t),
409 );
412fn interpolate_gradient(
413 distance: f32,
414 start_color: vec4<f32>,
415 start_distance: f32,
416 end_color: vec4<f32>,
417 end_distance: f32,
418 hint: f32,
419 flags: u32,
420) -> vec4<f32> {
421 if start_distance == end_distance {
422 if distance <= start_distance && enabled(flags, FILL_START) {
423 return convert_to_linear_rgba(start_color);
424 }
425 if start_distance <= distance && enabled(flags, FILL_END) {
426 return convert_to_linear_rgba(end_color);
427 }
428 return vec4(0.);
429 }
431 var t = (distance - start_distance) / (end_distance - start_distance);
433 if t < 0.0 {
434 if enabled(flags, FILL_START) {
435 return convert_to_linear_rgba(start_color);
436 }
437 return vec4(0.0);
438 }
440 if 1. < t {
441 if enabled(flags, FILL_END) {
442 return convert_to_linear_rgba(end_color);
443 }
444 return vec4(0.0);
445 }
447 if t < hint {
448 t = 0.5 * t / hint;
449 } else {
450 t = 0.5 * (1 + (t - hint) / (1.0 - hint));
451 }
453 return convert_to_linear_rgba(vec4(mix_colors(start_color.xyz, end_color.xyz, t), mix(start_color.a, end_color.a, t)));
456// Mix the colors, choosing the appropriate interpolation method for the given color space
457fn mix_colors(
458 start_color: vec3<f32>,
459 end_color: vec3<f32>,
460 t: f32,
461) -> vec3<f32> {
462#ifdef IN_OKLCH
463 return mix_oklch(start_color, end_color, t);
464#else ifdef IN_OKLCH_LONG
465 return mix_oklch_long(start_color, end_color, t);
466#else ifdef IN_HSV
467 return mix_hsv(start_color, end_color, t);
468#else ifdef IN_HSV_LONG
469 return mix_hsv_long(start_color, end_color, t);
470#else ifdef IN_HSL
471 return mix_hsl(start_color, end_color, t);
472#else ifdef IN_HSL_LONG
473 return mix_hsl_long(start_color, end_color, t);
474#else
475 // Just lerp in linear RGBA, OkLab and SRGBA spaces
476 return mix(start_color, end_color, t);
477#endif
480// Convert a color from the interpolation color space to linear rgba
481fn convert_to_linear_rgba(
482 color: vec4<f32>,
483) -> vec4<f32> {
484#ifdef IN_OKLCH
485 let rgb = oklch_to_linear_rgb(color.xyz);
486#else ifdef IN_OKLCH_LONG
487 let rgb = oklch_to_linear_rgb(color.xyz);
488#else ifdef IN_HSV
489 let rgb = hsv_to_linear_rgb(color.xyz);
490#else ifdef IN_HSV_LONG
491 let rgb = hsv_to_linear_rgb(color.xyz);
492#else ifdef IN_HSL
493 let rgb = hsl_to_linear_rgb(color.xyz);
494#else ifdef IN_HSL_LONG
495 let rgb = hsl_to_linear_rgb(color.xyz);
496#else ifdef IN_OKLAB
497 let rgb = oklab_to_linear_rgb(color.xyz);
498#else ifdef IN_SRGB
499 let rgb = pow(color.xyz, vec3(2.2));
500#else
501 // Color is already in linear rgba space
502 let rgb = color.rgb;
503#endif
504 return vec4(rgb, color.a);
Contents