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#ifdef WIREFRAME_WIDE
3#import bevy_pbr::{
4 mesh_bindings::mesh,
5 mesh_view_bindings::view,
6 view_transformations::position_world_to_clip,
7}
8#import bevy_render::maths::affine3_to_square
10struct Immediates {
11 color: vec4<f32>,
12 line_width: f32,
13 smoothing: f32,
14#ifdef SIXTEEN_BYTE_ALIGNMENT
15 _pad0: f32,
16 _pad1: f32,
17#endif
20var<immediate> immediates: Immediates;
22struct VertexPullParams {
23 index_offset: u32,
24 vertex_stride: u32,
25 position_offset: u32,
28@group(3) @binding(0) var<storage, read> vertex_data: array<u32>;
29@group(3) @binding(1) var<storage, read> index_data: array<u32>;
30@group(3) @binding(2) var<uniform> vp_params: VertexPullParams;
32struct WireframeVertexOutput {
33 @builtin(position) position: vec4<f32>,
34 @location(0) @interpolate(linear) edge_distance: vec4<f32>,
37// unsigned perpendicular distance from point p to the infinite line through a and b.
38fn point_line_distance(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>) -> f32 {
39 let edge = b - a;
40 let len = length(edge);
41 if len < 0.001 {
42 return 1e6;
43 }
44 return abs(edge.x * (p.y - a.y) - edge.y * (p.x - a.x)) / len;
47// finds the local-space vertex position for a given vertex index by reading from the vertex buffer using the provided
48// parameters. the vertex buffer is interpreted as an array of u32s, so the parameters allow us to calculate the
49// correct offset for the position attribute of the vertex at the given index.
50fn read_local_position(first_vertex: u32, vertex_index: u32) -> vec3<f32> {
51 let base = (first_vertex + vertex_index) * vp_params.vertex_stride
52 + vp_params.position_offset;
53 return vec3<f32>(
54 bitcast<f32>(vertex_data[base]),
55 bitcast<f32>(vertex_data[base + 1u]),
56 bitcast<f32>(vertex_data[base + 2u]),
57 );
60@vertex
61fn vertex(
62 @builtin(vertex_index) vertex_index: u32,
63 @builtin(instance_index) instance_index: u32,
64) -> WireframeVertexOutput {
65 var out: WireframeVertexOutput;
67 let first_vertex = mesh[instance_index].first_vertex_index;
68 let draw_id = vertex_index - first_vertex;
69 let corner = draw_id % 3u;
70 let tri_base = draw_id - corner;
72 let idx0 = index_data[vp_params.index_offset + tri_base];
73 let idx1 = index_data[vp_params.index_offset + tri_base + 1u];
74 let idx2 = index_data[vp_params.index_offset + tri_base + 2u];
76 let p0 = read_local_position(first_vertex, idx0);
77 let p1 = read_local_position(first_vertex, idx1);
78 let p2 = read_local_position(first_vertex, idx2);
80 let world_from_local = affine3_to_square(mesh[instance_index].world_from_local);
81 let clip0 = position_world_to_clip((world_from_local * vec4(p0, 1.0)).xyz);
82 let clip1 = position_world_to_clip((world_from_local * vec4(p1, 1.0)).xyz);
83 let clip2 = position_world_to_clip((world_from_local * vec4(p2, 1.0)).xyz);
85 let viewport_size = view.viewport.zw;
86 let screen0 = (clip0.xy / clip0.w) * viewport_size * 0.5;
87 let screen1 = (clip1.xy / clip1.w) * viewport_size * 0.5;
88 let screen2 = (clip2.xy / clip2.w) * viewport_size * 0.5;
90 let area2 = abs((screen1.x - screen0.x) * (screen2.y - screen0.y)
91 - (screen2.x - screen0.x) * (screen1.y - screen0.y));
93 let len01 = length(screen1 - screen0);
94 let len12 = length(screen2 - screen1);
95 let len20 = length(screen0 - screen2);
97 // altitudes: h_i = 2 * area / opposite_edge_length
98 // the altitude-based distance field follows Baerentzen et al., "Single-Pass
99 // Wireframe Rendering" (SIGGRAPH 2006) and the NVIDIA "Solid Wireframe"
100 // whitepaper (2007).
101 let h0 = area2 / max(len12, 0.001);
102 let h1 = area2 / max(len20, 0.001);
103 let h2 = area2 / max(len01, 0.001);
105#ifdef WIREFRAME_QUADS
106 // detect and suppress the shared diagonal between two triangles forming a quad.
107 let quad_base = (draw_id / 6u) * 6u;
108 let tri_in_quad = (draw_id / 3u) % 2u;
109 let other_tri_base = quad_base + (1u - tri_in_quad) * 3u;
111 let j0 = index_data[vp_params.index_offset + other_tri_base];
112 let j1 = index_data[vp_params.index_offset + other_tri_base + 1u];
113 let j2 = index_data[vp_params.index_offset + other_tri_base + 2u];
115 let in_other_0 = (idx0 == j0) || (idx0 == j1) || (idx0 == j2);
116 let in_other_1 = (idx1 == j0) || (idx1 == j1) || (idx1 == j2);
117 let in_other_2 = (idx2 == j0) || (idx2 == j1) || (idx2 == j2);
119 // edge is diagonal if both endpoints appear in the other triangle
120 let diag_01 = in_other_0 && in_other_1;
121 let diag_12 = in_other_1 && in_other_2;
122 let diag_20 = in_other_2 && in_other_0;
123 let has_diagonal = diag_01 || diag_12 || diag_20;
125 // find the extra vertex from the other triangle (the one not shared with ours)
126 // and compute distances to the other triangle's two perimeter edges.
127 // this ensures each fragment sees all 4 quad perimeter edges, not just the 2
128 // belonging to its own triangle. without this, pixels near the diagonal see the
129 // wrong triangle's edges and the wireframe line appears to thin at vertices.
130 var other_d0 = vec2<f32>(1e6);
131 var other_d1 = vec2<f32>(1e6);
132 var other_d2 = vec2<f32>(1e6);
134 if has_diagonal {
135 // identify the extra vertex index
136 let j0_shared = (j0 == idx0) || (j0 == idx1) || (j0 == idx2);
137 let j1_shared = (j1 == idx0) || (j1 == idx1) || (j1 == idx2);
138 var extra_idx = j2;
139 if !j0_shared {
140 extra_idx = j0;
141 } else if !j1_shared {
142 extra_idx = j1;
143 }
145 let p_extra = read_local_position(first_vertex, extra_idx);
146 let clip_extra = position_world_to_clip((world_from_local * vec4(p_extra, 1.0)).xyz);
147 let screen_extra = (clip_extra.xy / clip_extra.w) * viewport_size * 0.5;
149 // the two "other" perimeter edges connect the extra vertex to each diagonal
150 // endpoint. identify the diagonal endpoints from our triangle's vertices.
151 var diag_a = screen0;
152 var diag_b = screen1;
153 if diag_12 {
154 diag_a = screen1;
155 diag_b = screen2;
156 } else if diag_20 {
157 diag_a = screen2;
158 diag_b = screen0;
159 }
161 // other edge 1: extra_vertex → diag_a
162 // other edge 2: extra_vertex → diag_b
163 // compute unsigned perpendicular distance at each of our three vertices.
164 other_d0 = vec2<f32>(
165 point_line_distance(screen0, screen_extra, diag_a),
166 point_line_distance(screen0, screen_extra, diag_b),
167 );
168 other_d1 = vec2<f32>(
169 point_line_distance(screen1, screen_extra, diag_a),
170 point_line_distance(screen1, screen_extra, diag_b),
171 );
172 other_d2 = vec2<f32>(
173 point_line_distance(screen2, screen_extra, diag_a),
174 point_line_distance(screen2, screen_extra, diag_b),
175 );
176 }
178 let mask = vec3<f32>(
179 select(1.0, 0.0, diag_12),
180 select(1.0, 0.0, diag_20),
181 select(1.0, 0.0, diag_01),
182 );
183 let suppress = vec3<f32>(1.0) - mask;
185 // pack all 4 quad perimeter edge distances into edge_distance:
186 // xyz = own triangle's edges (diagonal slot replaced with other_d.x)
187 // w = other_d.y
188 // taking min(other_d.x, other_d.y) at the vertex level would be wrong
189 // because the diagonal endpoints lie ON the other perimeter edges,
190 // giving min = 0 at both endpoints and 0 along the entire diagonal
191 // after interpolation. keeping them separate ensures the interpolated
192 // values are nonzero at the diagonal's midpoint.
193 if corner == 0u {
194 let ed = vec3<f32>(h0, 0.0, 0.0) * mask + suppress * other_d0.x;
195 out.edge_distance = vec4<f32>(ed.x, ed.y, ed.z, other_d0.y);
196 out.position = clip0;
197 } else if corner == 1u {
198 let ed = vec3<f32>(0.0, h1, 0.0) * mask + suppress * other_d1.x;
199 out.edge_distance = vec4<f32>(ed.x, ed.y, ed.z, other_d1.y);
200 out.position = clip1;
201 } else {
202 let ed = vec3<f32>(0.0, 0.0, h2) * mask + suppress * other_d2.x;
203 out.edge_distance = vec4<f32>(ed.x, ed.y, ed.z, other_d2.y);
204 out.position = clip2;
205 }
206#else // WIREFRAME_QUADS
207 if corner == 0u {
208 out.edge_distance = vec4<f32>(h0, 0.0, 0.0, 1e6);
209 out.position = clip0;
210 } else if corner == 1u {
211 out.edge_distance = vec4<f32>(0.0, h1, 0.0, 1e6);
212 out.position = clip1;
213 } else {
214 out.edge_distance = vec4<f32>(0.0, 0.0, h2, 1e6);
215 out.position = clip2;
216 }
217#endif // WIREFRAME_QUADS
219 return out;
222@fragment
223fn fragment(in: WireframeVertexOutput) -> @location(0) vec4<f32> {
224 let d = min(min(in.edge_distance.x, in.edge_distance.y),
225 min(in.edge_distance.z, in.edge_distance.w));
227 let width = immediates.line_width;
228 let smoothing = immediates.smoothing;
230 let effective_width = max(width, 1.0);
231 let alpha_scale = min(width, 1.0);
233 let half_width = effective_width * 0.5;
235 let alpha = (1.0 - smoothstep(half_width, half_width + smoothing, d))
236 * alpha_scale
237 * immediates.color.a;
239 if alpha < 0.01 {
240 discard;
241 }
243 return vec4<f32>(immediates.color.rgb, alpha);
246#else // WIREFRAME_WIDE
248// the fast path for thin wireframes that render as lines
250#import bevy_pbr::forward_io::VertexOutput
252struct Immediates {
253 color: vec4<f32>,
256var<immediate> immediates: Immediates;
258@fragment
259fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
260 return immediates.color;
263#endif // WIREFRAME_WIDE
Contents