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_hanabi::vfx_common::{
3 IndirectBuffer, SimParams, Spawner,
4 seed, tau, pcg_hash, to_float01, frand, frand2, frand3, frand4,
5 rand_uniform_f, rand_uniform_vec2, rand_uniform_vec3, rand_uniform_vec4,
6 rand_normal_f, rand_normal_vec2, rand_normal_vec3, rand_normal_vec4, proj
7}
9struct Particle {
10{{ATTRIBUTES}}
13struct ParticleBuffer {
14 particles: array<Particle>,
17struct VertexOutput {
18 @builtin(position) position: vec4<f32>,
19 @location(0) color: vec4<f32>,
20#ifdef NEEDS_UV
21 @location(1) uv: vec2<f32>,
22#endif
23#ifdef NEEDS_NORMAL
24 @location(2) normal: vec3<f32>,
25#endif
26#ifdef NEEDS_PARTICLE_FRAGMENT
27 @location(3) particle_index: u32,
28#endif
31@group(0) @binding(0) var<uniform> view: View;
32@group(0) @binding(1) var<uniform> sim_params : SimParams;
34@group(1) @binding(0) var<storage, read> particle_buffer : ParticleBuffer;
35@group(1) @binding(1) var<storage, read> indirect_buffer : IndirectBuffer;
36@group(1) @binding(2) var<storage, read> spawner : Spawner;
38{{MATERIAL_BINDINGS}}
40fn get_camera_position_effect_space() -> vec3<f32> {
41 let view_pos = view.world_from_view[3].xyz;
42#ifdef LOCAL_SPACE_SIMULATION
43 let inverse_transform = transpose(
44 mat3x3(
45 spawner.inverse_transform[0].xyz,
46 spawner.inverse_transform[1].xyz,
47 spawner.inverse_transform[2].xyz,
48 )
49 );
50 return inverse_transform * view_pos;
51#else
52 return view_pos;
53#endif
56fn get_camera_rotation_effect_space() -> mat3x3<f32> {
57 let view_rot = mat3x3(view.world_from_view[0].xyz, view.world_from_view[1].xyz, view.world_from_view[2].xyz);
58#ifdef LOCAL_SPACE_SIMULATION
59 let inverse_transform = transpose(
60 mat3x3(
61 spawner.inverse_transform[0].xyz,
62 spawner.inverse_transform[1].xyz,
63 spawner.inverse_transform[2].xyz,
64 )
65 );
66 return inverse_transform * view_rot;
67#else
68 return view_rot;
69#endif
72/// Unpack a compressed transform stored in transposed row-major form.
73fn unpack_compressed_transform(compressed_transform: mat3x4<f32>) -> mat4x4<f32> {
74 return transpose(
75 mat4x4(
76 compressed_transform[0],
77 compressed_transform[1],
78 compressed_transform[2],
79 vec4<f32>(0.0, 0.0, 0.0, 1.0)
80 )
81 );
84// Unpacks a compressed transform and transposes is.
85fn unpack_compressed_transform_3x3_transpose(compressed_transform: mat3x4<f32>) -> mat3x3<f32> {
86 return mat3x3(
87 compressed_transform[0].xyz,
88 compressed_transform[1].xyz,
89 compressed_transform[2].xyz,
90 );
93/// Transform a simulation space position into a world space position.
94///
95/// The simulation space depends on the effect's SimulationSpace value, and is either
96/// the effect space (SimulationSpace::Local) or the world space (SimulationSpace::Global).
97fn transform_position_simulation_to_world(sim_position: vec3<f32>) -> vec4<f32> {
98#ifdef LOCAL_SPACE_SIMULATION
99 let transform = unpack_compressed_transform(spawner.transform);
100 return transform * vec4<f32>(sim_position, 1.0);
101#else
102 return vec4<f32>(sim_position, 1.0);
103#endif
106fn transform_normal_simulation_to_world(sim_normal: vec3<f32>) -> vec3<f32> {
107#ifdef LOCAL_SPACE_SIMULATION
108 // We use the inverse transpose transform to transform normals.
109 // The inverse transpose is the same as the transposed inverse, so we can
110 // safely use the inverse transform.
111 let transform = unpack_compressed_transform_3x3_transpose(spawner.inverse_transform);
112 return transform * sim_normal;
113#else
114 return sim_normal;
115#endif
118/// Transform a simulation space position into a clip space position.
119///
120/// The simulation space depends on the effect's SimulationSpace value, and is either
121/// the effect space (SimulationSpace::Local) or the world space (SimulationSpace::Global).
122/// The clip space is the final [-1:1]^3 space output from the vertex shader, before
123/// perspective divide and viewport transform are applied.
124fn transform_position_simulation_to_clip(sim_position: vec3<f32>) -> vec4<f32> {
125 return view.clip_from_world * transform_position_simulation_to_world(sim_position);
128fn inverse_transpose_mat3(m: mat3x3<f32>) -> mat3x3<f32> {
129 let tmp0 = cross(m[1], m[2]);
130 let tmp1 = cross(m[2], m[0]);
131 let tmp2 = cross(m[0], m[1]);
132 let inv_det = 1.0 / dot(m[2], tmp2);
133 return mat3x3<f32>(tmp0 * inv_det, tmp1 * inv_det, tmp2 * inv_det);
136{{RENDER_EXTRA}}
138@vertex
139fn vertex(
140 @builtin(instance_index) instance_index: u32,
141 @location(0) vertex_position: vec3<f32>,
142#ifdef NEEDS_UV
143 @location(1) vertex_uv: vec2<f32>,
144#endif
145#ifdef NEEDS_NORMAL
146 @location(2) vertex_normal: vec3<f32>,
147#endif
148 // @location(1) vertex_color: u32,
149 // @location(1) vertex_velocity: vec3<f32>,
150) -> VertexOutput {
151 // Fetch particle
152 let indirect_read_index = spawner.render_indirect_read_index;
153 let particle_index = indirect_buffer.rows[instance_index].particle_index[indirect_read_index];
154 var particle = particle_buffer.particles[particle_index];
156 var out: VertexOutput;
158#ifdef NEEDS_PARTICLE_FRAGMENT
159 out.particle_index = particle_index;
160#endif // NEEDS_PARTICLE_FRAGMENT
162#ifdef RIBBONS
163 // Discard first instance; we draw from second one, and link to previous one
164 if (instance_index == 0) {
165 out.position = vec4(0.0);
166 return out;
167 }
169 // Fetch previous particle
170 let prev_index = indirect_buffer.rows[instance_index - 1u].particle_index[indirect_read_index];
171 let prev_particle = particle_buffer.particles[prev_index];
173 // Discard this instance if previous one is from a different ribbon. Again,
174 // we draw from second one of each ribbon.
175 if (prev_particle.ribbon_id != particle.ribbon_id) {
176 out.position = vec4(0.0);
177 return out;
178 }
179#endif // RIBBONS
181#ifdef NEEDS_UV
182 // Compute UVs
183 var uv = vertex_uv;
184#ifdef FLIPBOOK
185 let row_count = {{FLIPBOOK_ROW_COUNT}};
186 let ij = vec2<f32>(f32(particle.sprite_index % row_count), f32(particle.sprite_index / row_count));
187 uv = (ij + uv) * {{FLIPBOOK_SCALE}};
188#endif
189 out.uv = uv;
190#endif // NEEDS_UV
192{{INPUTS}}
194{{VERTEX_MODIFIERS}}
196#ifdef RIBBONS
197 var delta = particle.position - prev_particle.position;
199 axis_x = normalize(delta);
200 axis_y = normalize(cross(axis_x, axis_z));
201 axis_z = cross(axis_x, axis_y);
203 position = mix(particle.position, prev_particle.position, 0.5);
204 size = vec3(length(delta), size.y, 1.0);
205#endif // RIBBONS
207 // Expand particle mesh vertex based on particle position ("origin"), and local
208 // orientation and size of the particle mesh.
209 let vpos = vertex_position * size;
210 let sim_position = position + axis_x * vpos.x + axis_y * vpos.y + axis_z * vpos.z;
211 out.position = transform_position_simulation_to_clip(sim_position);
213 out.color = color;
215#ifdef NEEDS_NORMAL
216 let normal = inverse_transpose_mat3(mat3x3(axis_x, axis_y, axis_z)) * vertex_normal;
217 out.normal = transform_normal_simulation_to_world(normal);
218#endif // NEEDS_NORMAL
220 return out;
223@fragment
224fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
225 // Read fragment inputs
226#ifdef USE_ALPHA_MASK
227 var alpha_cutoff: f32 = {{ALPHA_CUTOFF}};
228#endif
229 var color = in.color;
230#ifdef NEEDS_UV
231 var uv = in.uv;
232#endif
233#ifdef NEEDS_NORMAL
234 var normal = in.normal;
235#endif
236#ifdef NEEDS_PARTICLE_FRAGMENT
237 var particle = particle_buffer.particles[in.particle_index];
238#endif // NEEDS_PARTICLE_FRAGMENT
240{{FRAGMENT_MODIFIERS}}
242#ifdef USE_ALPHA_MASK
243 if color.a >= alpha_cutoff {
244 color.a = 1.0;
245 } else {
246 discard;
247 }
248#endif
250 return color;
Contents