Improved sprite routines
parent
6e59d95752
commit
4abbcca1d4
|
@ -0,0 +1,76 @@
|
||||||
|
use bytemuck;
|
||||||
|
use std::mem;
|
||||||
|
use wgpu;
|
||||||
|
|
||||||
|
pub struct GlobalData {
|
||||||
|
pub buffer: wgpu::Buffer,
|
||||||
|
pub bind_group: wgpu::BindGroup,
|
||||||
|
pub bind_group_layout: wgpu::BindGroupLayout,
|
||||||
|
pub content: GlobalDataContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct GlobalDataContent {
|
||||||
|
/// Camera position, in game units
|
||||||
|
pub camera_position: [f32; 2],
|
||||||
|
|
||||||
|
/// Camera zoom value, in game units
|
||||||
|
pub camera_zoom: f32,
|
||||||
|
|
||||||
|
/// Aspect ratio of window (width / height)
|
||||||
|
pub window_aspect: f32,
|
||||||
|
|
||||||
|
/// Texture index of starfield sprites
|
||||||
|
pub starfield_texture: u32,
|
||||||
|
|
||||||
|
// Size of (square) starfield tile, in game units
|
||||||
|
pub starfield_tile_size: f32,
|
||||||
|
|
||||||
|
pub padding: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalDataContent {
|
||||||
|
const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalData {
|
||||||
|
pub fn new(device: &wgpu::Device) -> Self {
|
||||||
|
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: GlobalDataContent::SIZE,
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
label: Some("camera_bind_group_layout"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: buffer.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
label: Some("camera_bind_group"),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Self {
|
||||||
|
buffer,
|
||||||
|
bind_group,
|
||||||
|
bind_group_layout,
|
||||||
|
content: GlobalDataContent::default(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use cgmath::{EuclideanSpace, Point2, Vector2};
|
use cgmath::{EuclideanSpace, Matrix2, Point2};
|
||||||
use std::{iter, rc::Rc};
|
use std::{iter, rc::Rc};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit::{self, dpi::PhysicalSize, window::Window};
|
use winit::{self, dpi::PhysicalSize, window::Window};
|
||||||
|
@ -8,12 +8,12 @@ use winit::{self, dpi::PhysicalSize, window::Window};
|
||||||
use crate::Game;
|
use crate::Game;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
globaldata::{GlobalData, GlobalDataContent},
|
||||||
pipeline::PipelineBuilder,
|
pipeline::PipelineBuilder,
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
util::Transform,
|
|
||||||
vertexbuffer::{
|
vertexbuffer::{
|
||||||
data::{SPRITE_INDICES, SPRITE_VERTICES},
|
data::{SPRITE_INDICES, SPRITE_VERTICES},
|
||||||
types::{PlainVertex, SpriteInstance, StarfieldInstance, TexturedVertex},
|
types::{SpriteInstance, StarfieldInstance, TexturedVertex},
|
||||||
VertexBuffer,
|
VertexBuffer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -32,6 +32,7 @@ pub struct GPUState {
|
||||||
starfield_pipeline: wgpu::RenderPipeline,
|
starfield_pipeline: wgpu::RenderPipeline,
|
||||||
|
|
||||||
texture_array: TextureArray,
|
texture_array: TextureArray,
|
||||||
|
global_data: GlobalData,
|
||||||
vertex_buffers: VertexBuffers,
|
vertex_buffers: VertexBuffers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ impl GPUState {
|
||||||
// We can draw at most this many sprites on the screen.
|
// We can draw at most this many sprites on the screen.
|
||||||
// TODO: compile-time option
|
// TODO: compile-time option
|
||||||
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
|
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
|
||||||
pub const STARFIELD_INSTANCE_LIMIT: u64 = 100;
|
pub const STARFIELD_INSTANCE_LIMIT: u64 = 501 * 9;
|
||||||
|
|
||||||
pub async fn new(window: Window) -> Result<Self> {
|
pub async fn new(window: Window) -> Result<Self> {
|
||||||
let window_size = window.inner_size();
|
let window_size = window.inner_size();
|
||||||
|
@ -118,18 +119,25 @@ impl GPUState {
|
||||||
Self::SPRITE_INSTANCE_LIMIT,
|
Self::SPRITE_INSTANCE_LIMIT,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
starfield: Rc::new(VertexBuffer::new::<PlainVertex, StarfieldInstance>(
|
starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
|
||||||
"starfield",
|
"starfield",
|
||||||
&device,
|
&device,
|
||||||
None,
|
Some(SPRITE_VERTICES),
|
||||||
None,
|
Some(SPRITE_INDICES),
|
||||||
Self::STARFIELD_INSTANCE_LIMIT,
|
Self::STARFIELD_INSTANCE_LIMIT,
|
||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load textures
|
// Load uniforms
|
||||||
|
let global_data = GlobalData::new(&device);
|
||||||
let texture_array = TextureArray::new(&device, &queue)?;
|
let texture_array = TextureArray::new(&device, &queue)?;
|
||||||
|
|
||||||
|
// Make sure these match the indices in each shader
|
||||||
|
let bind_group_layouts = &[
|
||||||
|
&texture_array.bind_group_layout,
|
||||||
|
&global_data.bind_group_layout,
|
||||||
|
];
|
||||||
|
|
||||||
// Create render pipelines
|
// Create render pipelines
|
||||||
let sprite_pipeline = PipelineBuilder::new("sprite", &device)
|
let sprite_pipeline = PipelineBuilder::new("sprite", &device)
|
||||||
.set_shader(include_str!(concat!(
|
.set_shader(include_str!(concat!(
|
||||||
|
@ -140,7 +148,7 @@ impl GPUState {
|
||||||
.set_format(config.format)
|
.set_format(config.format)
|
||||||
.set_triangle(true)
|
.set_triangle(true)
|
||||||
.set_vertex_buffer(&vertex_buffers.sprite)
|
.set_vertex_buffer(&vertex_buffers.sprite)
|
||||||
.set_bind_group_layouts(&[&texture_array.bind_group_layout])
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let starfield_pipeline = PipelineBuilder::new("starfield", &device)
|
let starfield_pipeline = PipelineBuilder::new("starfield", &device)
|
||||||
|
@ -150,8 +158,9 @@ impl GPUState {
|
||||||
"starfield.wgsl"
|
"starfield.wgsl"
|
||||||
)))
|
)))
|
||||||
.set_format(config.format)
|
.set_format(config.format)
|
||||||
.set_triangle(false)
|
.set_triangle(true)
|
||||||
.set_vertex_buffer(&vertex_buffers.starfield)
|
.set_vertex_buffer(&vertex_buffers.starfield)
|
||||||
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
|
@ -168,6 +177,7 @@ impl GPUState {
|
||||||
starfield_pipeline,
|
starfield_pipeline,
|
||||||
|
|
||||||
texture_array,
|
texture_array,
|
||||||
|
global_data,
|
||||||
vertex_buffers,
|
vertex_buffers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -229,19 +239,12 @@ impl GPUState {
|
||||||
//
|
//
|
||||||
// We can't use screen_pos to exclude off-screen sprites because
|
// We can't use screen_pos to exclude off-screen sprites because
|
||||||
// it can't account for height and width.
|
// it can't account for height and width.
|
||||||
let scale = height / game.camera.zoom;
|
|
||||||
let screen_pos: Point2<f32> = pos / game.camera.zoom;
|
|
||||||
|
|
||||||
instances.push(SpriteInstance {
|
instances.push(SpriteInstance {
|
||||||
transform: Transform {
|
position: pos.into(),
|
||||||
pos: screen_pos,
|
aspect: texture.aspect,
|
||||||
sprite_aspect: texture.aspect,
|
rotation: Matrix2::from_angle(s.angle).into(),
|
||||||
window_aspect: self.window_aspect,
|
height,
|
||||||
rotate: s.angle,
|
|
||||||
scale,
|
|
||||||
}
|
|
||||||
.to_matrix()
|
|
||||||
.into(),
|
|
||||||
texture_index: texture.index,
|
texture_index: texture.index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -259,22 +262,25 @@ impl GPUState {
|
||||||
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
|
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
|
||||||
///
|
///
|
||||||
/// This is only called inside self.render()
|
/// This is only called inside self.render()
|
||||||
fn make_starfield_instances(&self, _game: &Game) -> Vec<StarfieldInstance> {
|
fn make_starfield_instances(&self, game: &Game) -> Vec<StarfieldInstance> {
|
||||||
let instances: Vec<StarfieldInstance> = (-10..10)
|
let mut instances = Vec::new();
|
||||||
.map(|x| StarfieldInstance {
|
for s in &game.system.starfield {
|
||||||
position: Vector2 {
|
// TODO: minimize operations here
|
||||||
x: x as f32 / 10.0,
|
//let pos = Sprite::post_parallax(s.parallax, s.pos * 500.0, game.camera)
|
||||||
y: 0.0,
|
// - game.camera.pos.to_vec();
|
||||||
|
instances.push(StarfieldInstance {
|
||||||
|
position: s.pos.into(),
|
||||||
|
parallax: s.parallax,
|
||||||
|
height: 2.0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
.into(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Enforce starfield limit
|
// Enforce starfield limit
|
||||||
if instances.len() as u64 > Self::STARFIELD_INSTANCE_LIMIT {
|
if instances.len() as u64 > Self::STARFIELD_INSTANCE_LIMIT {
|
||||||
// TODO: no panic, handle this better.
|
// TODO: no panic, handle this better.
|
||||||
unreachable!("Starfield limit exceeded!")
|
unreachable!("Starfield limit exceeded!")
|
||||||
}
|
}
|
||||||
|
|
||||||
return instances;
|
return instances;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,6 +317,20 @@ impl GPUState {
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update global values
|
||||||
|
self.queue.write_buffer(
|
||||||
|
&self.global_data.buffer,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[GlobalDataContent {
|
||||||
|
camera_position: game.camera.pos.into(),
|
||||||
|
camera_zoom: game.camera.zoom,
|
||||||
|
window_aspect: self.window_aspect,
|
||||||
|
starfield_texture: 1,
|
||||||
|
starfield_tile_size: 1000.0,
|
||||||
|
padding: Default::default(),
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
// Create sprite instances
|
// Create sprite instances
|
||||||
let sprite_instances = self.make_sprite_instances(game);
|
let sprite_instances = self.make_sprite_instances(game);
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
|
@ -327,12 +347,21 @@ impl GPUState {
|
||||||
bytemuck::cast_slice(&starfield_instances),
|
bytemuck::cast_slice(&starfield_instances),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// These should match the indices in each shader,
|
||||||
|
// and should each have a corresponding bind group layout.
|
||||||
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
||||||
|
render_pass.set_bind_group(1, &self.global_data.bind_group, &[]);
|
||||||
|
|
||||||
// Starfield pipeline
|
// Starfield pipeline
|
||||||
self.vertex_buffers.starfield.set_in_pass(&mut render_pass);
|
self.vertex_buffers.starfield.set_in_pass(&mut render_pass);
|
||||||
render_pass.set_pipeline(&self.starfield_pipeline);
|
render_pass.set_pipeline(&self.starfield_pipeline);
|
||||||
|
|
||||||
render_pass.draw(0..1, 0..starfield_instances.len() as _);
|
render_pass.draw(0..1, 0..starfield_instances.len() as _);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..starfield_instances.len() as _,
|
||||||
|
);
|
||||||
|
|
||||||
// Sprite pipeline
|
// Sprite pipeline
|
||||||
self.vertex_buffers.sprite.set_in_pass(&mut render_pass);
|
self.vertex_buffers.sprite.set_in_pass(&mut render_pass);
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
|
mod globaldata;
|
||||||
mod gpustate;
|
mod gpustate;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod rawtexture;
|
mod rawtexture;
|
||||||
mod texturearray;
|
mod texturearray;
|
||||||
mod util;
|
|
||||||
mod vertexbuffer;
|
mod vertexbuffer;
|
||||||
|
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::GPUState;
|
||||||
pub use texturearray::Texture;
|
pub use texturearray::Texture;
|
||||||
|
|
||||||
use cgmath::Matrix4;
|
// API correction matrix.
|
||||||
|
// cgmath uses OpenGL's matrix format, which
|
||||||
/// API correction matrix.
|
// needs to be converted to wgpu's matrix format.
|
||||||
/// cgmath uses OpenGL's matrix format, which
|
/*
|
||||||
/// needs to be converted to wgpu's matrix format.
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||||
1.0, 0.0, 0.0, 0.0,
|
1.0, 0.0, 0.0, 0.0,
|
||||||
|
@ -20,3 +19,4 @@ const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||||
0.0, 0.0, 0.5, 0.5,
|
0.0, 0.0, 0.5, 0.5,
|
||||||
0.0, 0.0, 0.0, 1.0,
|
0.0, 0.0, 0.0, 1.0,
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
// Vertex shader
|
// Vertex shader
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) transform_matrix_0: vec4<f32>,
|
@location(2) rotation_matrix_0: vec2<f32>,
|
||||||
@location(3) transform_matrix_1: vec4<f32>,
|
@location(3) rotation_matrix_1: vec2<f32>,
|
||||||
@location(4) transform_matrix_2: vec4<f32>,
|
@location(4) position: vec2<f32>,
|
||||||
@location(5) transform_matrix_3: vec4<f32>,
|
@location(5) height: f32,
|
||||||
@location(6) texture_idx: u32,
|
@location(6) aspect: f32,
|
||||||
|
@location(7) texture_idx: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -13,26 +14,57 @@ struct VertexInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
@location(0) texture_coords: vec2<f32>,
|
@location(0) texture_coords: vec2<f32>,
|
||||||
@location(1) index: u32,
|
@location(1) index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<uniform> global: GlobalUniform;
|
||||||
|
struct GlobalUniform {
|
||||||
|
camera_position: vec2<f32>,
|
||||||
|
camera_zoom: f32,
|
||||||
|
window_aspect: f32,
|
||||||
|
starfield_texture: u32,
|
||||||
|
starfield_tile_size: f32
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex_shader_main(
|
fn vertex_shader_main(
|
||||||
model: VertexInput,
|
vertex: VertexInput,
|
||||||
instance: InstanceInput,
|
instance: InstanceInput,
|
||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
let transform_matrix = mat4x4<f32>(
|
|
||||||
instance.transform_matrix_0,
|
// Apply sprite aspect ratio & scale factor
|
||||||
instance.transform_matrix_1,
|
// This must be done *before* rotation.
|
||||||
instance.transform_matrix_2,
|
let scale = instance.height / global.camera_zoom;
|
||||||
instance.transform_matrix_3,
|
var pos: vec2<f32> = vec2<f32>(
|
||||||
|
vertex.position.x * instance.aspect * scale,
|
||||||
|
vertex.position.y * scale
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rotate
|
||||||
|
pos = mat2x2<f32>(
|
||||||
|
instance.rotation_matrix_0,
|
||||||
|
instance.rotation_matrix_1,
|
||||||
|
) * pos;
|
||||||
|
|
||||||
|
// Apply screen aspect ratio, again preserving height.
|
||||||
|
// This must be done AFTER rotation... think about it!
|
||||||
|
pos = pos / vec2<f32>(global.window_aspect, 1.0);
|
||||||
|
|
||||||
|
// Translate
|
||||||
|
pos = pos + (
|
||||||
|
// Don't forget to correct distance for screen aspect ratio too!
|
||||||
|
(instance.position / global.camera_zoom)
|
||||||
|
/ vec2<f32>(global.window_aspect, 1.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.texture_coords = model.texture_coords;
|
out.position = vec4<f32>(pos, 0.0, 1.0);
|
||||||
out.clip_position = transform_matrix * vec4<f32>(model.position, 1.0);
|
out.texture_coords = vertex.texture_coords;
|
||||||
out.index = instance.texture_idx;
|
out.index = instance.texture_idx;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,94 @@
|
||||||
// Vertex shader
|
// Vertex shader
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) position: vec2<f32>,
|
@location(2) position: vec2<f32>,
|
||||||
|
@location(3) parallax: f32,
|
||||||
|
@location(4) height: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
@location(1) texture_coords: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
|
@location(0) texture_coords: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<uniform> global: GlobalUniform;
|
||||||
|
struct GlobalUniform {
|
||||||
|
camera_position: vec2<f32>,
|
||||||
|
camera_zoom: f32,
|
||||||
|
window_aspect: f32,
|
||||||
|
starfield_texture: u32,
|
||||||
|
starfield_tile_size: f32
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
fn fmod(x: vec2<f32>, m: f32) -> vec2<f32> {
|
||||||
|
return x - floor(x * (1.0 / m)) * m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex_shader_main(
|
fn vertex_shader_main(
|
||||||
|
vertex: VertexInput,
|
||||||
instance: InstanceInput,
|
instance: InstanceInput,
|
||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
// Stars consist of only one vertex, so we don't need to pass a buffer into this shader.
|
|
||||||
// We need one instance per star, and that's it!
|
// Center of the tile the camera is currently in, in game coordinates.
|
||||||
|
// x div y = x - (x mod y)
|
||||||
|
let tile_center = (
|
||||||
|
global.camera_position
|
||||||
|
- (
|
||||||
|
fmod(
|
||||||
|
global.camera_position + global.starfield_tile_size/2.0,
|
||||||
|
global.starfield_tile_size
|
||||||
|
) - global.starfield_tile_size/2.0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply sprite aspect ratio & scale factor
|
||||||
|
// also applies screen aspect ratio
|
||||||
|
let scale = instance.height / global.camera_zoom;
|
||||||
|
var pos: vec2<f32> = vec2<f32>(
|
||||||
|
vertex.position.x * scale / global.window_aspect,
|
||||||
|
vertex.position.y * scale
|
||||||
|
);
|
||||||
|
|
||||||
|
// World position relative to camera
|
||||||
|
// (Note that instance position is in a different
|
||||||
|
// coordinate system than usual)
|
||||||
|
let camera_pos = (instance.position + tile_center) - global.camera_position;
|
||||||
|
let parallax_vector = -camera_pos * instance.parallax * 0.1;
|
||||||
|
|
||||||
|
// Translate
|
||||||
|
pos = pos + (
|
||||||
|
// Don't forget to correct distance for screen aspect ratio too!
|
||||||
|
// The minus in "minus parallax_vector" is important.
|
||||||
|
((camera_pos + parallax_vector) / global.camera_zoom)
|
||||||
|
/ vec2<f32>(global.window_aspect, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.clip_position = vec4<f32>(instance.position, 0.0, 1.0);
|
out.position = vec4<f32>(pos, 0.0, 1.0);
|
||||||
|
out.texture_coords = vertex.texture_coords;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var texture_array: binding_array<texture_2d<f32>>;
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var sampler_array: binding_array<sampler>;
|
||||||
|
|
||||||
// Fragment shader
|
// Fragment shader
|
||||||
@fragment
|
@fragment
|
||||||
fn fragment_shader_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fragment_shader_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
return textureSampleLevel(
|
||||||
|
texture_array[1],
|
||||||
|
sampler_array[1],
|
||||||
|
in.texture_coords,
|
||||||
|
0.0
|
||||||
|
).rgba * vec4<f32>(0.5, 0.5, 0.5, 1.0);
|
||||||
}
|
}
|
|
@ -1,53 +0,0 @@
|
||||||
use cgmath::{Deg, Matrix4, Point2, Vector3};
|
|
||||||
|
|
||||||
use super::OPENGL_TO_WGPU_MATRIX;
|
|
||||||
|
|
||||||
// Represents a sprite tranformation.
|
|
||||||
//
|
|
||||||
// This produces a single matrix we can apply to a brand-new sprite instance
|
|
||||||
// to put it in the right place, at the right angle, with the right scale.
|
|
||||||
pub struct Transform {
|
|
||||||
pub pos: Point2<f32>, // position on screen
|
|
||||||
pub window_aspect: f32, // width / height. Screen aspect ratio.
|
|
||||||
pub sprite_aspect: f32, // width / height. Sprite aspect ratio.
|
|
||||||
pub scale: f32, // if scale = 1, this sprite will be as tall as the screen.
|
|
||||||
pub rotate: Deg<f32>, // Around this object's center, in degrees measured ccw from vertical
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transform {
|
|
||||||
/// Build the matrix that corresponds to this transformation.
|
|
||||||
pub fn to_matrix(&self) -> Matrix4<f32> {
|
|
||||||
// Note that our mesh starts centered at (0, 0).
|
|
||||||
// This is essential---we do not want scale and rotation
|
|
||||||
// changing our sprite's position!
|
|
||||||
|
|
||||||
// Apply sprite aspect ratio, preserving height.
|
|
||||||
// This must be done *before* rotation.
|
|
||||||
//
|
|
||||||
// We apply the provided scale here as well as a minor optimization
|
|
||||||
let sprite_aspect_and_scale =
|
|
||||||
Matrix4::from_nonuniform_scale(self.sprite_aspect * self.scale, self.scale, 1.0);
|
|
||||||
|
|
||||||
// Apply rotation
|
|
||||||
let rotate = Matrix4::from_angle_z(self.rotate);
|
|
||||||
|
|
||||||
// Apply screen aspect ratio, again preserving height.
|
|
||||||
// This must be done AFTER rotation... think about it!
|
|
||||||
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
|
|
||||||
|
|
||||||
// After finishing all op, translate.
|
|
||||||
// This must be done last, all other operations
|
|
||||||
// require us to be at (0, 0).
|
|
||||||
let translate = Matrix4::from_translation(Vector3 {
|
|
||||||
x: self.pos.x,
|
|
||||||
y: self.pos.y,
|
|
||||||
z: 0.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Order matters!
|
|
||||||
// The rightmost matrix is applied first.
|
|
||||||
return OPENGL_TO_WGPU_MATRIX
|
|
||||||
* translate * screen_aspect
|
|
||||||
* rotate * sprite_aspect_and_scale;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,34 +32,20 @@ impl BufferObject for TexturedVertex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents a plain vertex in WGSL
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct PlainVertex {
|
|
||||||
pub position: [f32; 3],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BufferObject for PlainVertex {
|
|
||||||
fn layout() -> wgpu::VertexBufferLayout<'static> {
|
|
||||||
wgpu::VertexBufferLayout {
|
|
||||||
array_stride: Self::SIZE,
|
|
||||||
step_mode: wgpu::VertexStepMode::Vertex,
|
|
||||||
attributes: &[wgpu::VertexAttribute {
|
|
||||||
offset: 0,
|
|
||||||
shader_location: 0,
|
|
||||||
format: wgpu::VertexFormat::Float32x3,
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Represents a point in the starfield instance in WGSL
|
// Represents a point in the starfield instance in WGSL
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct StarfieldInstance {
|
pub struct StarfieldInstance {
|
||||||
// All transformations we need to put this dot in its place
|
/// Position in origin field tile.
|
||||||
// TODO: we don't need that much data
|
/// note that this is DIFFERENT from
|
||||||
|
/// the way we provide sprite positions!
|
||||||
pub position: [f32; 2],
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
/// Parallax factor (same unit as usual)
|
||||||
|
pub parallax: f32,
|
||||||
|
|
||||||
|
/// Height of (unrotated) sprite in world units
|
||||||
|
pub height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferObject for StarfieldInstance {
|
impl BufferObject for StarfieldInstance {
|
||||||
|
@ -67,11 +53,26 @@ impl BufferObject for StarfieldInstance {
|
||||||
wgpu::VertexBufferLayout {
|
wgpu::VertexBufferLayout {
|
||||||
array_stride: Self::SIZE,
|
array_stride: Self::SIZE,
|
||||||
step_mode: wgpu::VertexStepMode::Instance,
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
attributes: &[wgpu::VertexAttribute {
|
attributes: &[
|
||||||
|
// Position
|
||||||
|
wgpu::VertexAttribute {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
shader_location: 2,
|
shader_location: 2,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
}],
|
},
|
||||||
|
// Parallax
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 3,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Height
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 4,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,11 +81,19 @@ impl BufferObject for StarfieldInstance {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct SpriteInstance {
|
pub struct SpriteInstance {
|
||||||
// All transformations we need to put this sprite in its place
|
/// Rotation matrix for this sprite
|
||||||
pub transform: [[f32; 4]; 4],
|
pub rotation: [[f32; 2]; 2],
|
||||||
|
|
||||||
// Which texture we should use for this sprite
|
/// World position, relative to camera
|
||||||
// (see TextureArray)
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
/// Height of (unrotated) sprite in world units
|
||||||
|
pub height: f32,
|
||||||
|
|
||||||
|
// Sprite aspect ratio (width / height)
|
||||||
|
pub aspect: f32,
|
||||||
|
|
||||||
|
// What texture to use for this sprite
|
||||||
pub texture_index: u32,
|
pub texture_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,29 +106,39 @@ impl BufferObject for SpriteInstance {
|
||||||
// instance when the shader starts processing a new instance
|
// instance when the shader starts processing a new instance
|
||||||
step_mode: wgpu::VertexStepMode::Instance,
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
attributes: &[
|
attributes: &[
|
||||||
|
// 2 arrays = 1 2x2 matrix
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
shader_location: 2,
|
shader_location: 2,
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 3,
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
|
},
|
||||||
|
// Position
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||||
shader_location: 3,
|
shader_location: 4,
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
|
// Height
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 5,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Aspect
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 7]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 6,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Texture
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||||
shader_location: 4,
|
shader_location: 7,
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
|
||||||
},
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 5,
|
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
|
||||||
},
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 6,
|
|
||||||
format: wgpu::VertexFormat::Uint32,
|
format: wgpu::VertexFormat::Uint32,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,13 +1,37 @@
|
||||||
use crate::{physics::Polar, Doodad, Sprite, Spriteable};
|
use crate::{
|
||||||
use cgmath::Deg;
|
physics::{Pfloat, Polar},
|
||||||
|
Doodad, Sprite, Spriteable,
|
||||||
|
};
|
||||||
|
use cgmath::{Deg, Point2};
|
||||||
|
use rand::{self, Rng};
|
||||||
|
|
||||||
|
pub struct StarfieldStar {
|
||||||
|
// Star coordinates, in world space.
|
||||||
|
// These are relative to the center of a starfield tile.
|
||||||
|
pub pos: Point2<Pfloat>,
|
||||||
|
pub parallax: Pfloat,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct System {
|
pub struct System {
|
||||||
bodies: Vec<Doodad>,
|
bodies: Vec<Doodad>,
|
||||||
|
pub starfield: Vec<StarfieldStar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl System {
|
impl System {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut s = System { bodies: Vec::new() };
|
let mut rng = rand::thread_rng();
|
||||||
|
let mut s = System {
|
||||||
|
bodies: Vec::new(),
|
||||||
|
starfield: (0..500)
|
||||||
|
.map(|_| StarfieldStar {
|
||||||
|
pos: Point2 {
|
||||||
|
x: rng.gen_range(-500.0..500.0),
|
||||||
|
y: rng.gen_range(-500.0..500.0),
|
||||||
|
},
|
||||||
|
parallax: rng.gen_range(10.0..11.0),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
s.bodies.push(Doodad {
|
s.bodies.push(Doodad {
|
||||||
pos: (0.0, 0.0).into(),
|
pos: (0.0, 0.0).into(),
|
||||||
|
|
Loading…
Reference in New Issue