Add fireball and bomb particle effects

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

View File

@ -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);

View File

@ -46,67 +46,3 @@ impl Default for LightAnimation {
impl Component for LightAnimation {
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",
// 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<f32>,
inst_pos: Vec3<f32>,
) -> 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;

View File

@ -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::<Pos>(),
&ecs.read_storage::<Body>(),
&ecs.read_storage::<Pos>(),
)
.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<Light>,
shadows: &Consts<Shadow>,
) {
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);
}
}
}