bevy/assets/shaders/custom_ui_material.wgsl
ickshonpe c0ccc87738
UI material border radius (#15171)
# Objective

I wrote a box shadow UI material naively thinking I could use the border
widths attribute to hold the border radius but it
doesn't work as the border widths are automatically set in the
extraction function. Need to send border radius to the shader seperately
for it to be viable.

## Solution

Add a `border_radius` vertex attribute to the ui material.

This PR also removes the normalization of border widths for custom UI
materials. The regular UI shader doesn't do this so it's a bit confusing
and means you can't use the logic from `ui.wgsl` in your custom UI
materials.

## Testing / Showcase

Made a change to the `ui_material` example to display border radius:

```cargo run --example ui_material```

<img width="569" alt="corners" src="https://github.com/user-attachments/assets/36412736-a9ee-4042-aadd-68b9cafb17cb" />
2025-01-28 04:54:48 +00:00

60 lines
2.2 KiB
WebGPU Shading Language

// Draws a progress bar with properties defined in CustomUiMaterial
#import bevy_ui::ui_vertex_output::UiVertexOutput
@group(1) @binding(0) var<uniform> color: vec4<f32>;
@group(1) @binding(1) var<uniform> slider: f32;
@group(1) @binding(2) var material_color_texture: texture_2d<f32>;
@group(1) @binding(3) var material_color_sampler: sampler;
@group(1) @binding(4) var<uniform> border_color: vec4<f32>;
@fragment
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color;
// half size of the UI node
let half_size = 0.5 * in.size;
// position relative to the center of the UI node
let p = in.uv * in.size - half_size;
// thickness of the border closest to the current position
let b = vec2(
select(in.border_widths.x, in.border_widths.z, 0. < p.x),
select(in.border_widths.y, in.border_widths.w, 0. < p.y)
);
// select radius for the nearest corner
let rs = select(in.border_radius.xy, in.border_radius.wz, 0.0 < p.y);
let radius = select(rs.x, rs.y, 0.0 < p.x);
// distance along each axis from the corner
let d = half_size - abs(p);
// if the distance to the edge from the current position on any axis
// is less than the border width on that axis then the position is within
// the border and we return the border color
if d.x < b.x || d.y < b.y {
// select radius for the nearest corner
let rs = select(in.border_radius.xy, in.border_radius.wz, 0.0 < p.y);
let radius = select(rs.x, rs.y, 0.0 < p.x);
// determine if the point is inside the curved corner and return the corresponding color
let q = radius - d;
if radius < min(max(q.x, q.y), 0.0) + length(vec2(max(q.x, 0.0), max(q.y, 0.0))) {
return vec4(0.0);
} else {
return border_color;
}
}
// sample the texture at this position if it's to the left of the slider value
// otherwise return a fully transparent color
if in.uv.x < slider {
let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color;
return output_color;
} else {
return vec4(0.0);
}
}