diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index c51431b5d5..54fac86749 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -6,10 +6,7 @@ in vec3 v_pos; in uint v_col; in uint v_norm_ao; -in vec4 inst_mat0; -in vec4 inst_mat1; -in vec4 inst_mat2; -in vec4 inst_mat3; +in vec3 inst_pos; in float inst_time; in float inst_entropy; in int inst_mode; @@ -22,7 +19,8 @@ out float f_light; const float SCALE = 1.0 / 11.0; -float PHI = 1.61803398874989484820459; // Φ = Golden Ratio +// Φ = Golden Ratio +float PHI = 1.61803398874989484820459; float gold_noise(in vec2 xy, in float seed){ return fract(tan(distance(xy * PHI, xy) * seed) * xy.x); @@ -31,46 +29,54 @@ float gold_noise(in vec2 xy, in float seed){ // Modes const int SMOKE = 0; const int FIRE = 1; -const int FLAMETHROWER = 2; +const int GUN_POWDER_SPARK = 2; + +// meters per second +const float earth_gravity = 9.807; + +mat4 translate(vec3 vec){ + return mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(vec.x, vec.y, vec.z, 1.0) + ); +} void main() { - mat4 inst_mat; - inst_mat[0] = inst_mat0; - inst_mat[1] = inst_mat1; - inst_mat[2] = inst_mat2; - inst_mat[3] = inst_mat3; + mat4 inst_mat = translate(inst_pos); float rand1 = gold_noise(vec2(0.0, 0.0), inst_entropy); - float rand2 = gold_noise(vec2(1.0, 1.0), inst_entropy); - float rand3 = gold_noise(vec2(2.0, 2.0), inst_entropy); - float rand4 = gold_noise(vec2(3.0, 3.0), inst_entropy); - float rand5 = gold_noise(vec2(4.0, 4.0), inst_entropy); - float rand6 = gold_noise(vec2(5.0, 5.0), inst_entropy); + float rand2 = gold_noise(vec2(10.0, 10.0), inst_entropy); + float rand3 = gold_noise(vec2(20.0, 20.0), inst_entropy); + float rand4 = gold_noise(vec2(30.0, 30.0), inst_entropy); + float rand5 = gold_noise(vec2(40.0, 40.0), inst_entropy); + float rand6 = gold_noise(vec2(50.0, 50.0), inst_entropy); vec3 inst_vel = vec3(0.0, 0.0, 0.0); - vec3 inst_pos = vec3(0.0, 0.0, 0.0); + vec3 inst_pos2 = vec3(0.0, 0.0, 0.0); vec3 inst_col = vec3(1.0, 1.0, 1.0); if (inst_mode == SMOKE) { inst_col = vec3(1.0, 1.0, 1.0); inst_vel = vec3(rand1 * 0.2 - 0.1, rand2 * 0.2 - 0.1, 1.0 + rand3); - inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + inst_pos2 = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); } else if (inst_mode == FIRE) { inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); inst_vel = vec3(rand1 * 0.2 - 0.1, rand2 * 0.2 - 0.1, 4.0 + rand3); - inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); - } else if (inst_mode == FLAMETHROWER) { - // TODO: velocity based on attack range, angle and parent orientation. - inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); - inst_vel = vec3(rand1 * 0.1, rand2 * 0.1, 3.0 + rand3); - inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + inst_pos2 = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + } else if (inst_mode == GUN_POWDER_SPARK) { + inst_col = vec3(1.0, 1.0, 0.0); + inst_vel = vec3(rand2 * 2.0 - 1.0, rand1 * 2.0 - 1.0, 5.0 + rand3); + inst_vel -= vec3(0.0, 0.0, earth_gravity * (tick.x - inst_time)); + inst_pos2 = vec3(0.0, 0.0, 0.0); } else { inst_col = vec3(rand1, rand2, rand3); inst_vel = vec3(rand4, rand5, rand6); - inst_pos = vec3(rand1, rand2, rand3); + inst_pos2 = vec3(rand1, rand2, rand3); } - f_pos = (inst_mat * vec4((v_pos + inst_pos) * SCALE, 1)).xyz; + f_pos = (inst_mat * vec4((v_pos + inst_pos2) * SCALE, 1)).xyz; f_pos += inst_vel * (tick.x - inst_time); diff --git a/common/src/comp/visual.rs b/common/src/comp/visual.rs index b8f463d593..013f27aa2c 100644 --- a/common/src/comp/visual.rs +++ b/common/src/comp/visual.rs @@ -46,67 +46,3 @@ impl Default for LightAnimation { impl Component for LightAnimation { type Storage = FlaggedStorage>; } -// #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -// pub enum ParticleEmitterMode { -// Sprinkler, -// } - -// #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // Copy -// pub struct ParticleEmitters(pub Vec); - -// impl Default for ParticleEmitters { -// fn default() -> Self { -// Self(vec![ParticleEmitter::default(), ParticleEmitter { -// mode: ParticleEmitterMode::Sprinkler, -// // model_key: "voxygen.voxel.not_found", -// count: (7, 10), -// frequency: Duration::from_millis(100), -// initial_lifespan: Duration::from_millis(500), -// initial_offset: (Vec3::broadcast(-0.2), Vec3::broadcast(0.2)), -// initial_orientation: (Vec3::broadcast(0.0), -// Vec3::broadcast(1.0)), initial_scale: (1.0, 2.5), -// initial_velocity: (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.01, -// 0.01, 3.0)), initial_col: (Rgb::new(0.999, 0.0, 0.0), -// Rgb::new(1.0, 1.0, 0.001)), }]) -// } -// } - -// #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -// pub struct ParticleEmitter { -// pub mode: ParticleEmitterMode, - -// // spawn X particles per Y, that live for Z -// // pub model_ref: &str, // can we have some kind of stack based key like -// a u8? pub count: (u8, u8), -// pub frequency: Duration, - -// // relative to Pos, Ori components? -// // can these be functions that returns a Vec3? -// pub initial_lifespan: Duration, -// pub initial_offset: (Vec3, Vec3), // fn() -> Vec3, -// pub initial_scale: (f32, f32), // fn() -> Vec3, -// pub initial_orientation: (Vec3, Vec3), // fn() -> Vec3, -// pub initial_velocity: (Vec3, Vec3), // fn() -> Vec3, -// pub initial_col: (Rgb, Rgb), // fn() -> Vec3, -// } - -// impl Default for ParticleEmitter { -// fn default() -> Self { -// Self { -// mode: ParticleEmitterMode::Sprinkler, -// // model_key: "voxygen.voxel.not_found", -// count: (2, 5), -// frequency: Duration::from_millis(100), -// initial_lifespan: Duration::from_secs(20), -// initial_offset: (Vec3::broadcast(-0.1), Vec3::broadcast(0.1)), -// initial_orientation: (Vec3::broadcast(0.0), -// Vec3::broadcast(1.0)), initial_scale: (0.1, 2.0), -// initial_velocity: (Vec3::new(0.0, 0.0, 0.2), Vec3::new(0.01, -// 0.01, 1.0)), initial_col: (Rgb::new(0.999, 0.999, 0.999), -// Rgb::new(1.0, 1.0, 1.0)), } -// } -// } - -// impl Component for ParticleEmitter { -// type Storage = FlaggedStorage>; -// } diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 9b4a94606f..1f467c638e 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -26,21 +26,24 @@ gfx_defines! { inst_time: f32 = "inst_time", // a seed value for randomness + // can save 32 bits per instance, for particles that don't need randomness/uniqueness. inst_entropy: f32 = "inst_entropy", - // modes should probably be seperate shaders, as a part of scaling and optimisation efforts + // modes should probably be seperate shaders, as a part of scaling and optimisation efforts. + // can save 32 bits per instance, and have cleaner tailor made code. inst_mode: i32 = "inst_mode", - // a triangle is: f32 x 3 x 3 x 1 = 288 bits - // a quad is: f32 x 3 x 3 x 2 = 576 bits - // a cube is: f32 x 3 x 3 x 12 = 3456 bits - // this matrix is: f32 x 4 x 4 x 1 = 512 bits (per instance!) - // consider using vertex postion & entropy instead; - // to determine initial offset, scale, orientation etc. - inst_mat0: [f32; 4] = "inst_mat0", - inst_mat1: [f32; 4] = "inst_mat1", - inst_mat2: [f32; 4] = "inst_mat2", - inst_mat3: [f32; 4] = "inst_mat3", + // a triangle is: f32 x 3 x 3 x 1 = 288 bits + // a quad is: f32 x 3 x 3 x 2 = 576 bits + // a cube is: f32 x 3 x 3 x 12 = 3456 bits + // this vec is: f32 x 3 x 1 x 1 = 96 bits (per instance!) + // consider using a throw-away mesh and + // positioning the vertex verticies instead, + // if we have: + // - a triangle mesh, and 3 or more instances. + // - a quad mesh, and 6 or more instances. + // - a cube mesh, and 36 or more instances. + inst_pos: [f32; 3] = "inst_pos", } pipeline pipe { @@ -82,6 +85,7 @@ impl Vertex { pub enum ParticleMode { CampfireSmoke, CampfireFire, + GunPowderSpark, } impl ParticleMode { @@ -93,24 +97,19 @@ impl Instance { inst_time: f64, inst_entropy: f32, inst_mode: ParticleMode, - inst_mat: Mat4, + inst_pos: Vec3, ) -> Self { - let inst_mat_col = inst_mat.into_col_arrays(); Self { inst_time: inst_time as f32, inst_entropy, inst_mode: inst_mode as i32, - - inst_mat0: inst_mat_col[0], - inst_mat1: inst_mat_col[1], - inst_mat2: inst_mat_col[2], - inst_mat3: inst_mat_col[3], + inst_pos: inst_pos.into_array(), } } } impl Default for Instance { - fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Mat4::identity()) } + fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Vec3::zero()) } } pub struct ParticlePipeline; diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 3477f52d87..5a0b299a7b 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -16,7 +16,7 @@ use hashbrown::HashMap; use rand::Rng; use specs::{Join, WorldExt}; use std::time::{Duration, Instant}; -use vek::{Mat4, Vec3}; +use vek::Vec3; struct Particles { alive_until: Instant, // created_at + lifespan @@ -66,63 +66,231 @@ impl ParticleMgr { // remove dead particles self.particles.retain(|p| p.alive_until > now); - self.maintain_waypoint_particles(renderer, scene_data); + self.maintain_body_particles(renderer, scene_data); self.maintain_boost_particles(renderer, scene_data); } - fn maintain_waypoint_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { - let state = scene_data.state; - let ecs = state.ecs(); - let time = state.get_time(); - let now = Instant::now(); - let mut rng = rand::thread_rng(); - - for (_i, (_entity, pos, body)) in ( + fn maintain_body_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + let ecs = scene_data.state.ecs(); + for (_i, (_entity, body, pos)) in ( &ecs.entities(), - &ecs.read_storage::(), &ecs.read_storage::(), + &ecs.read_storage::(), ) .join() .enumerate() { match body { Body::Object(object::Body::CampfireLit) => { - let fire_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireFire, - Mat4::identity().translated_3d(pos.0), - )]; - - self.particles.push(Particles { - alive_until: now + Duration::from_millis(250), - instances: renderer - .create_instances(&fire_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"), - }); - - let smoke_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireSmoke, - Mat4::identity().translated_3d(pos.0), - )]; - - let smoke_cpu_insts = renderer - .create_instances(&smoke_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"); - - self.particles.push(Particles { - alive_until: now + Duration::from_secs(10), - instances: smoke_cpu_insts, - }); + self.maintain_campfirelit_particles(renderer, scene_data, pos) }, + Body::Object(object::Body::BoltFire) => { + self.maintain_boltfire_particles(renderer, scene_data, pos) + }, + Body::Object(object::Body::BoltFireBig) => { + self.maintain_boltfirebig_particles(renderer, scene_data, pos) + }, + Body::Object(object::Body::Bomb) => { + self.maintain_bomb_particles(renderer, scene_data, pos) + }, + // Body::Object(object::Body::Pouch) => { + // self.maintain_pouch_particles(renderer, scene_data, pos) + // }, _ => {}, } } } + fn maintain_campfirelit_particles( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + pos: &Pos, + ) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + let fire_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireFire, + pos.0, + )]; + + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); + + let smoke_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + pos.0, + )]; + + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + self.particles.push(Particles { + alive_until: now + Duration::from_secs(10), + instances: smoke_cpu_insts, + }); + } + + fn maintain_boltfire_particles( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + pos: &Pos, + ) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + let fire_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireFire, + pos.0, + )]; + + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); + + let smoke_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + pos.0, + )]; + + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + self.particles.push(Particles { + alive_until: now + Duration::from_secs(1), + instances: smoke_cpu_insts, + }); + } + + fn maintain_boltfirebig_particles( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + pos: &Pos, + ) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + let fire_cpu_insts = vec![ + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), + ]; + + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); + + let smoke_cpu_insts = vec![ + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), + ]; + + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + self.particles.push(Particles { + alive_until: now + Duration::from_secs(2), + instances: smoke_cpu_insts, + }); + } + + fn maintain_bomb_particles( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + pos: &Pos, + ) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + let fire_cpu_insts = vec![ + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ]; + + self.particles.push(Particles { + alive_until: now + Duration::from_millis(1500), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); + + let smoke_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + pos.0, + )]; + + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + self.particles.push(Particles { + alive_until: now + Duration::from_secs(2), + instances: smoke_cpu_insts, + }); + } + + // fn maintain_pouch_particles( + // &mut self, + // renderer: &mut Renderer, + // scene_data: &SceneData, + // pos: &Pos, + // ) { + // let time = scene_data.state.get_time(); + // let now = Instant::now(); + // let mut rng = rand::thread_rng(); + + // let smoke_cpu_insts = vec![ParticleInstance::new( + // time, + // rng.gen(), + // ParticleMode::CampfireSmoke, + // pos.0, + // )]; + + // let smoke_cpu_insts = renderer + // .create_instances(&smoke_cpu_insts) + // .expect("Failed to upload particle instances to the GPU!"); + + // self.particles.push(Particles { + // alive_until: now + Duration::from_secs(1), + // instances: smoke_cpu_insts, + // }); + // } + fn maintain_boost_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { let state = scene_data.state; let ecs = state.ecs(); @@ -143,7 +311,7 @@ impl ParticleMgr { time, rng.gen(), ParticleMode::CampfireSmoke, - Mat4::identity().translated_3d(pos.0), + pos.0, )]; let gpu_insts = renderer @@ -165,17 +333,13 @@ impl ParticleMgr { lights: &Consts, shadows: &Consts, ) { + let model = &self + .model_cache + .get(MODEL_KEY) + .expect("Expected particle model in cache"); + for particle in &self.particles { - renderer.render_particles( - &self - .model_cache - .get(MODEL_KEY) - .expect("Expected particle model in cache"), - globals, - &particle.instances, - lights, - shadows, - ); + renderer.render_particles(model, globals, &particle.instances, lights, shadows); } } }