Add fireball and bomb particle effects

This commit is contained in:
scott-c
2020-07-19 15:16:06 +08:00
parent 4c765eac9b
commit 29a78ceaa3
4 changed files with 265 additions and 160 deletions

View File

@ -6,10 +6,7 @@
in vec3 v_pos; in vec3 v_pos;
in uint v_col; in uint v_col;
in uint v_norm_ao; in uint v_norm_ao;
in vec4 inst_mat0; in vec3 inst_pos;
in vec4 inst_mat1;
in vec4 inst_mat2;
in vec4 inst_mat3;
in float inst_time; in float inst_time;
in float inst_entropy; in float inst_entropy;
in int inst_mode; in int inst_mode;
@ -22,7 +19,8 @@ out float f_light;
const float SCALE = 1.0 / 11.0; 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){ float gold_noise(in vec2 xy, in float seed){
return fract(tan(distance(xy * PHI, xy) * seed) * xy.x); return fract(tan(distance(xy * PHI, xy) * seed) * xy.x);
@ -31,46 +29,54 @@ float gold_noise(in vec2 xy, in float seed){
// Modes // Modes
const int SMOKE = 0; const int SMOKE = 0;
const int FIRE = 1; 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() { void main() {
mat4 inst_mat; mat4 inst_mat = translate(inst_pos);
inst_mat[0] = inst_mat0;
inst_mat[1] = inst_mat1;
inst_mat[2] = inst_mat2;
inst_mat[3] = inst_mat3;
float rand1 = gold_noise(vec2(0.0, 0.0), inst_entropy); float rand1 = gold_noise(vec2(0.0, 0.0), inst_entropy);
float rand2 = gold_noise(vec2(1.0, 1.0), inst_entropy); float rand2 = gold_noise(vec2(10.0, 10.0), inst_entropy);
float rand3 = gold_noise(vec2(2.0, 2.0), inst_entropy); float rand3 = gold_noise(vec2(20.0, 20.0), inst_entropy);
float rand4 = gold_noise(vec2(3.0, 3.0), inst_entropy); float rand4 = gold_noise(vec2(30.0, 30.0), inst_entropy);
float rand5 = gold_noise(vec2(4.0, 4.0), inst_entropy); float rand5 = gold_noise(vec2(40.0, 40.0), inst_entropy);
float rand6 = gold_noise(vec2(5.0, 5.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_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); vec3 inst_col = vec3(1.0, 1.0, 1.0);
if (inst_mode == SMOKE) { if (inst_mode == SMOKE) {
inst_col = vec3(1.0, 1.0, 1.0); 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_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) { } else if (inst_mode == FIRE) {
inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); 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_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); inst_pos2 = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0);
} else if (inst_mode == FLAMETHROWER) { } else if (inst_mode == GUN_POWDER_SPARK) {
// TODO: velocity based on attack range, angle and parent orientation. inst_col = vec3(1.0, 1.0, 0.0);
inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); inst_vel = vec3(rand2 * 2.0 - 1.0, rand1 * 2.0 - 1.0, 5.0 + rand3);
inst_vel = vec3(rand1 * 0.1, rand2 * 0.1, 3.0 + rand3); inst_vel -= vec3(0.0, 0.0, earth_gravity * (tick.x - inst_time));
inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); inst_pos2 = vec3(0.0, 0.0, 0.0);
} else { } else {
inst_col = vec3(rand1, rand2, rand3); inst_col = vec3(rand1, rand2, rand3);
inst_vel = vec3(rand4, rand5, rand6); 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); f_pos += inst_vel * (tick.x - inst_time);

View File

@ -46,67 +46,3 @@ impl Default for LightAnimation {
impl Component for LightAnimation { impl Component for LightAnimation {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>; type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
} }
// #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
// pub enum ParticleEmitterMode {
// Sprinkler,
// }
// #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // Copy
// pub struct ParticleEmitters(pub Vec<ParticleEmitter>);
// 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<f32>?
// pub initial_lifespan: Duration,
// pub initial_offset: (Vec3<f32>, Vec3<f32>), // fn() -> Vec3<f32>,
// pub initial_scale: (f32, f32), // fn() -> Vec3<f32>,
// pub initial_orientation: (Vec3<f32>, Vec3<f32>), // fn() -> Vec3<f32>,
// pub initial_velocity: (Vec3<f32>, Vec3<f32>), // fn() -> Vec3<f32>,
// pub initial_col: (Rgb<f32>, Rgb<f32>), // fn() -> Vec3<f32>,
// }
// 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<Self, IdvStorage<Self>>;
// }

View File

@ -26,21 +26,24 @@ gfx_defines! {
inst_time: f32 = "inst_time", inst_time: f32 = "inst_time",
// a seed value for randomness // a seed value for randomness
// can save 32 bits per instance, for particles that don't need randomness/uniqueness.
inst_entropy: f32 = "inst_entropy", 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", inst_mode: i32 = "inst_mode",
// a triangle is: f32 x 3 x 3 x 1 = 288 bits // 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 quad is: f32 x 3 x 3 x 2 = 576 bits
// a cube is: f32 x 3 x 3 x 12 = 3456 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!) // this vec is: f32 x 3 x 1 x 1 = 96 bits (per instance!)
// consider using vertex postion & entropy instead; // consider using a throw-away mesh and
// to determine initial offset, scale, orientation etc. // positioning the vertex verticies instead,
inst_mat0: [f32; 4] = "inst_mat0", // if we have:
inst_mat1: [f32; 4] = "inst_mat1", // - a triangle mesh, and 3 or more instances.
inst_mat2: [f32; 4] = "inst_mat2", // - a quad mesh, and 6 or more instances.
inst_mat3: [f32; 4] = "inst_mat3", // - a cube mesh, and 36 or more instances.
inst_pos: [f32; 3] = "inst_pos",
} }
pipeline pipe { pipeline pipe {
@ -82,6 +85,7 @@ impl Vertex {
pub enum ParticleMode { pub enum ParticleMode {
CampfireSmoke, CampfireSmoke,
CampfireFire, CampfireFire,
GunPowderSpark,
} }
impl ParticleMode { impl ParticleMode {
@ -93,24 +97,19 @@ impl Instance {
inst_time: f64, inst_time: f64,
inst_entropy: f32, inst_entropy: f32,
inst_mode: ParticleMode, inst_mode: ParticleMode,
inst_mat: Mat4<f32>, inst_pos: Vec3<f32>,
) -> Self { ) -> Self {
let inst_mat_col = inst_mat.into_col_arrays();
Self { Self {
inst_time: inst_time as f32, inst_time: inst_time as f32,
inst_entropy, inst_entropy,
inst_mode: inst_mode as i32, inst_mode: inst_mode as i32,
inst_pos: inst_pos.into_array(),
inst_mat0: inst_mat_col[0],
inst_mat1: inst_mat_col[1],
inst_mat2: inst_mat_col[2],
inst_mat3: inst_mat_col[3],
} }
} }
} }
impl Default for Instance { 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; pub struct ParticlePipeline;

View File

@ -16,7 +16,7 @@ use hashbrown::HashMap;
use rand::Rng; use rand::Rng;
use specs::{Join, WorldExt}; use specs::{Join, WorldExt};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use vek::{Mat4, Vec3}; use vek::Vec3;
struct Particles { struct Particles {
alive_until: Instant, // created_at + lifespan alive_until: Instant, // created_at + lifespan
@ -66,63 +66,231 @@ impl ParticleMgr {
// remove dead particles // remove dead particles
self.particles.retain(|p| p.alive_until > now); 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); self.maintain_boost_particles(renderer, scene_data);
} }
fn maintain_waypoint_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { fn maintain_body_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) {
let state = scene_data.state; let ecs = scene_data.state.ecs();
let ecs = state.ecs(); for (_i, (_entity, body, pos)) in (
let time = state.get_time();
let now = Instant::now();
let mut rng = rand::thread_rng();
for (_i, (_entity, pos, body)) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<Body>(), &ecs.read_storage::<Body>(),
&ecs.read_storage::<Pos>(),
) )
.join() .join()
.enumerate() .enumerate()
{ {
match body { match body {
Body::Object(object::Body::CampfireLit) => { Body::Object(object::Body::CampfireLit) => {
let fire_cpu_insts = vec![ParticleInstance::new( self.maintain_campfirelit_particles(renderer, scene_data, pos)
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,
});
}, },
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) { fn maintain_boost_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) {
let state = scene_data.state; let state = scene_data.state;
let ecs = state.ecs(); let ecs = state.ecs();
@ -143,7 +311,7 @@ impl ParticleMgr {
time, time,
rng.gen(), rng.gen(),
ParticleMode::CampfireSmoke, ParticleMode::CampfireSmoke,
Mat4::identity().translated_3d(pos.0), pos.0,
)]; )];
let gpu_insts = renderer let gpu_insts = renderer
@ -165,17 +333,13 @@ impl ParticleMgr {
lights: &Consts<Light>, lights: &Consts<Light>,
shadows: &Consts<Shadow>, shadows: &Consts<Shadow>,
) { ) {
let model = &self
.model_cache
.get(MODEL_KEY)
.expect("Expected particle model in cache");
for particle in &self.particles { for particle in &self.particles {
renderer.render_particles( renderer.render_particles(model, globals, &particle.instances, lights, shadows);
&self
.model_cache
.get(MODEL_KEY)
.expect("Expected particle model in cache"),
globals,
&particle.instances,
lights,
shadows,
);
} }
} }
} }