Skip to main content

veloren_voxygen/render/pipelines/
particle.rs

1use super::super::{ExperimentalShader, GlobalsLayouts, PipelineModes, Vertex as VertexTrait};
2use bytemuck::{Pod, Zeroable};
3use std::mem;
4use vek::*;
5
6#[repr(C)]
7#[derive(Copy, Clone, Debug, Zeroable, Pod)]
8pub struct Vertex {
9    pub pos: [f32; 3],
10    // ____BBBBBBBBGGGGGGGGRRRRRRRR
11    // col: u32 = "v_col",
12    // ...AANNN
13    // A = AO
14    // N = Normal
15    norm_ao: u32,
16}
17
18impl Vertex {
19    pub fn new(pos: Vec3<f32>, norm: Vec3<f32>) -> Self {
20        #[expect(clippy::bool_to_int_with_if)]
21        let norm_bits = if norm.x != 0.0 {
22            if norm.x < 0.0 { 0 } else { 1 }
23        } else if norm.y != 0.0 {
24            if norm.y < 0.0 { 2 } else { 3 }
25        } else {
26            if norm.z < 0.0 { 4 } else { 5 }
27        };
28
29        Self {
30            pos: pos.into_array(),
31            norm_ao: norm_bits,
32        }
33    }
34
35    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
36        const ATTRIBUTES: [wgpu::VertexAttribute; 2] =
37            wgpu::vertex_attr_array![0 => Float32x3, 1 => Uint32];
38        wgpu::VertexBufferLayout {
39            array_stride: Self::STRIDE,
40            step_mode: wgpu::VertexStepMode::Vertex,
41            attributes: &ATTRIBUTES,
42        }
43    }
44}
45
46impl VertexTrait for Vertex {
47    const QUADS_INDEX: Option<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint16);
48    const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
49}
50
51#[derive(Copy, Clone)]
52pub enum ParticleMode {
53    CampfireSmoke = 0,
54    CampfireFire = 1,
55    GunPowderSpark = 2,
56    Shrapnel = 3,
57    FireworkBlue = 4,
58    FireworkGreen = 5,
59    FireworkPurple = 6,
60    FireworkRed = 7,
61    FireworkWhite = 8,
62    FireworkYellow = 9,
63    Leaf = 10,
64    Firefly = 11,
65    Bee = 12,
66    GroundShockwave = 13,
67    EnergyHealing = 14,
68    EnergyNature = 15,
69    FlameThrower = 16,
70    FireShockwave = 17,
71    FireBowl = 18,
72    Snow = 19,
73    Explosion = 20,
74    Ice = 21,
75    LifestealBeam = 22,
76    CultistFlame = 23,
77    StaticSmoke = 24,
78    Blood = 25,
79    Enraged = 26,
80    BigShrapnel = 27,
81    Laser = 28,
82    Bubbles = 29,
83    Water = 30,
84    IceSpikes = 31,
85    Drip = 32,
86    Tornado = 33,
87    Death = 34,
88    EnergyBuffing = 35,
89    WebStrand = 36,
90    BlackSmoke = 37,
91    Lightning = 38,
92    Steam = 39,
93    BarrelOrgan = 40,
94    PotionSickness = 41,
95    GigaSnow = 42,
96    CyclopsCharge = 43,
97    SnowStorm = 44,
98    PortalFizz = 45,
99    Ink = 46,
100    IceWhirlwind = 47,
101    FieryBurst = 48,
102    FieryBurstVortex = 49,
103    FieryBurstSparks = 50,
104    FieryBurstAsh = 51,
105    FieryTornado = 52,
106    PhoenixCloud = 53,
107    FieryDropletTrace = 54,
108    EnergyPhoenix = 55,
109    PhoenixBeam = 56,
110    PhoenixBuildUpAim = 57,
111    ClayShrapnel = 58,
112    Airflow = 59,
113    Spore = 60,
114    SurpriseEgg = 61,
115    FlameTornado = 62,
116    Poison = 63,
117    WaterFoam = 64,
118    EngineJet = 65,
119    Transformation = 66,
120    FireGigasAsh = 67,
121    FireGigasWhirlwind = 68,
122    FireGigasOverheat = 69,
123    FireGigasExplosion = 70,
124    FirePillarIndicator = 71,
125    FirePillar = 72,
126    FireLowShockwave = 73,
127    PipeSmoke = 74,
128    TrainSmoke = 75,
129    Bubble = 76,
130    ElephantVacuum = 77,
131    ElectricSparks = 78,
132    FlamethrowerBlue = 79,
133    FlameCloakOrbit = 80,
134    Dust = 81,
135    CaveDust = 82,
136    BubbleAmbient = 83,
137}
138
139impl ParticleMode {
140    pub fn into_uint(self) -> u32 { self as u32 }
141}
142
143#[repr(C)]
144#[derive(Copy, Clone, Debug, Zeroable, Pod)]
145pub struct Instance {
146    // created_at time, so we can calculate time relativity, needed for relative animation.
147    // can save 32 bits per instance, for particles that are not relatively animated.
148    inst_time: f32,
149
150    // The lifespan in seconds of the particle
151    inst_lifespan: f32,
152
153    // a seed value for randomness
154    // can save 32 bits per instance, for particles that don't need randomness/uniqueness.
155    inst_entropy: f32,
156
157    // modes should probably be seperate shaders, as a part of scaling and optimisation efforts.
158    // can save 32 bits per instance, and have cleaner tailor made code.
159    inst_mode: i32,
160
161    // A direction for particles to move in. Alternatively, a color.
162    inst_dir_color: [f32; 3],
163
164    // a triangle is: f32 x 3 x 3 x 1  = 288 bits
165    // a quad is:     f32 x 3 x 3 x 2  = 576 bits
166    // a cube is:     f32 x 3 x 3 x 12 = 3456 bits
167    // this vec is:   f32 x 3 x 1 x 1  = 96 bits (per instance!)
168    // consider using a throw-away mesh and
169    // positioning the vertex verticies instead,
170    // if we have:
171    // - a triangle mesh, and 3 or more instances.
172    // - a quad mesh, and 6 or more instances.
173    // - a cube mesh, and 36 or more instances.
174    inst_pos: [f32; 3],
175
176    inst_start_wind_vel: [f32; 2],
177
178    // The voxel lighting at the particle's expected position.
179    //
180    // First element is sunlight, second is glow light.
181    //
182    // If in doubt, use (1.0, 0.0).
183    inst_voxel_light: [f32; 2],
184}
185
186impl Instance {
187    pub fn new(
188        inst_time: f64,
189        lifespan: f32,
190        inst_mode: ParticleMode,
191        inst_pos: Vec3<f32>,
192        inst_start_wind_vel: Vec2<f32>,
193    ) -> Self {
194        use rand::RngExt;
195        Self {
196            inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
197            inst_lifespan: lifespan,
198            inst_entropy: rand::rng().random(),
199            inst_mode: inst_mode as i32,
200            inst_pos: inst_pos.into_array(),
201            inst_start_wind_vel: inst_start_wind_vel.into_array(),
202            inst_dir_color: [0.0, 0.0, 0.0],
203            inst_voxel_light: [1.0, 0.0],
204        }
205    }
206
207    pub fn new_directed(
208        inst_time: f64,
209        lifespan: f32,
210        inst_mode: ParticleMode,
211        inst_pos: Vec3<f32>,
212        inst_pos2: Vec3<f32>,
213        inst_start_wind_vel: Vec2<f32>,
214    ) -> Self {
215        use rand::RngExt;
216        Self {
217            inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
218            inst_lifespan: lifespan,
219            inst_entropy: rand::rng().random(),
220            inst_mode: inst_mode as i32,
221            inst_pos: inst_pos.into_array(),
222            inst_start_wind_vel: inst_start_wind_vel.into_array(),
223            inst_dir_color: (inst_pos2 - inst_pos).into_array(),
224            inst_voxel_light: [1.0, 0.0],
225        }
226    }
227
228    pub fn new_colored(
229        inst_time: f64,
230        lifespan: f32,
231        inst_mode: ParticleMode,
232        inst_pos: Vec3<f32>,
233        col: Rgb<f32>,
234        inst_start_wind_vel: Vec2<f32>,
235    ) -> Self {
236        use rand::RngExt;
237        Self {
238            inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
239            inst_lifespan: lifespan,
240            inst_entropy: rand::rng().random(),
241            inst_mode: inst_mode as i32,
242            inst_pos: inst_pos.into_array(),
243            inst_start_wind_vel: inst_start_wind_vel.into_array(),
244            inst_dir_color: col.into_array(),
245            inst_voxel_light: [1.0, 0.0],
246        }
247    }
248
249    pub fn with_light(self, sun_light: f32, glow_light: f32) -> Self {
250        Self {
251            inst_voxel_light: [sun_light, glow_light],
252            ..self
253        }
254    }
255
256    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
257        const ATTRIBUTES: [wgpu::VertexAttribute; 8] = wgpu::vertex_attr_array![2 => Float32, 3 => Float32, 4 => Float32, 5 => Sint32, 6 => Float32x3, 7 => Float32x3, 8 => Float32x2, 9 => Float32x2];
258        wgpu::VertexBufferLayout {
259            array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
260            step_mode: wgpu::VertexStepMode::Instance,
261            attributes: &ATTRIBUTES,
262        }
263    }
264}
265
266impl Default for Instance {
267    fn default() -> Self {
268        Self::new(
269            0.0,
270            0.0,
271            ParticleMode::CampfireSmoke,
272            Vec3::zero(),
273            Vec2::zero(),
274        )
275    }
276}
277
278pub struct ParticlePipeline {
279    pub pipeline: wgpu::RenderPipeline,
280}
281
282impl ParticlePipeline {
283    pub fn new(
284        device: &wgpu::Device,
285        vs_module: &wgpu::ShaderModule,
286        fs_module: &wgpu::ShaderModule,
287        global_layout: &GlobalsLayouts,
288        format: wgpu::TextureFormat,
289        pipeline_modes: &PipelineModes,
290    ) -> Self {
291        common_base::span!(_guard, "ParticlePipeline::new");
292        let render_pipeline_layout =
293            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
294                label: Some("Particle pipeline layout"),
295                push_constant_ranges: &[],
296                bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures],
297            });
298
299        let samples = pipeline_modes.aa.samples();
300
301        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
302            label: Some("Particle pipeline"),
303            layout: Some(&render_pipeline_layout),
304            vertex: wgpu::VertexState {
305                module: vs_module,
306                entry_point: Some("main"),
307                buffers: &[Vertex::desc(), Instance::desc()],
308                compilation_options: Default::default(),
309            },
310            primitive: wgpu::PrimitiveState {
311                topology: wgpu::PrimitiveTopology::TriangleList,
312                strip_index_format: None,
313                front_face: wgpu::FrontFace::Ccw,
314                cull_mode: Some(wgpu::Face::Back),
315                unclipped_depth: false,
316                polygon_mode: if pipeline_modes
317                    .experimental_shaders
318                    .contains(&ExperimentalShader::Wireframe)
319                {
320                    wgpu::PolygonMode::Line
321                } else {
322                    wgpu::PolygonMode::Fill
323                },
324                conservative: false,
325            },
326            depth_stencil: Some(wgpu::DepthStencilState {
327                format: wgpu::TextureFormat::Depth32Float,
328                depth_write_enabled: true,
329                depth_compare: wgpu::CompareFunction::GreaterEqual,
330                stencil: wgpu::StencilState {
331                    front: wgpu::StencilFaceState::IGNORE,
332                    back: wgpu::StencilFaceState::IGNORE,
333                    read_mask: !0,
334                    write_mask: 0,
335                },
336                bias: wgpu::DepthBiasState {
337                    constant: 0,
338                    slope_scale: 0.0,
339                    clamp: 0.0,
340                },
341            }),
342            multisample: wgpu::MultisampleState {
343                count: samples,
344                mask: !0,
345                alpha_to_coverage_enabled: false,
346            },
347            fragment: Some(wgpu::FragmentState {
348                module: fs_module,
349                entry_point: Some("main"),
350                targets: &[
351                    Some(wgpu::ColorTargetState {
352                        format,
353                        blend: Some(wgpu::BlendState {
354                            color: wgpu::BlendComponent {
355                                src_factor: wgpu::BlendFactor::SrcAlpha,
356                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
357                                operation: wgpu::BlendOperation::Add,
358                            },
359                            alpha: wgpu::BlendComponent {
360                                src_factor: wgpu::BlendFactor::One,
361                                dst_factor: wgpu::BlendFactor::One,
362                                operation: wgpu::BlendOperation::Add,
363                            },
364                        }),
365                        write_mask: wgpu::ColorWrites::ALL,
366                    }),
367                    Some(wgpu::ColorTargetState {
368                        format: wgpu::TextureFormat::Rgba8Uint,
369                        blend: None,
370                        write_mask: wgpu::ColorWrites::ALL,
371                    }),
372                ],
373                compilation_options: Default::default(),
374            }),
375            multiview: None,
376            cache: None,
377        });
378
379        Self {
380            pipeline: render_pipeline,
381        }
382    }
383}