diff --git a/assets/shaders/black_hole.wgsl b/assets/shaders/black_hole.wgsl index b2897ea..d5b1084 100644 --- a/assets/shaders/black_hole.wgsl +++ b/assets/shaders/black_hole.wgsl @@ -1,11 +1,36 @@ #import bevy_sprite::mesh2d_vertex_output::VertexOutput -@group(#{MATERIAL_BIND_GROUP}) @binding(0) var time: f32; +struct BlackHoleUniforms { + eye: vec4, + forward: vec4, + right: vec4, + up: vec4, + resolution: vec2, + time: f32, + _pad3: f32, + rs: f32, + disk_inner: f32, + disk_outer: f32, + disk_tilt: f32, + disk_brightness: f32, + disk_rotation_speed: f32, + doppler_strength: f32, + star_intensity: f32, + skybox_intensity: f32, + grid_density: f32, + doppler_enabled: u32, + grid_enabled: u32, + planet_count: u32, + steps: u32, + _pad4: f32, + _pad5: f32, +}; + +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var uniforms: BlackHoleUniforms; @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { - // Gradient from time, just to prove the uniform flows. - let t = (sin(time) + 1.0) * 0.5; - let col = mix(vec3(0.02, 0.02, 0.05), vec3(0.08, 0.04, 0.12), t); - return vec4(col, 1.0); + // Prove the camera basis + uniforms flow: tint by eye direction. + let n = normalize(uniforms.eye.xyz); + return vec4(abs(n) * 0.5 + 0.3, 1.0); } diff --git a/src/render/material.rs b/src/render/material.rs index 40b5d88..391e846 100644 --- a/src/render/material.rs +++ b/src/render/material.rs @@ -1,13 +1,105 @@ use bevy::prelude::*; use bevy::reflect::TypePath; -use bevy::render::render_resource::AsBindGroup; +use bevy::render::render_resource::{AsBindGroup, ShaderType}; +use bevy::render::storage::ShaderBuffer; use bevy::shader::ShaderRef; use bevy::sprite_render::Material2d; -#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] +/// GPU uniform: params + camera packed into one struct bound at binding 0. +#[derive(Clone, ShaderType)] +pub struct BlackHoleUniforms { + // Camera basis + eye (4 vec3s = must align; pad each to vec4 via the shader struct) + pub eye: Vec3, + pub _pad0: f32, + pub forward: Vec3, + pub _pad1: f32, + pub right: Vec3, + pub _pad2: f32, + pub up: Vec3, + pub fov: f32, + // Resolution + pub resolution: Vec2, + pub time: f32, + pub _pad3: f32, + // Physics + disk + pub rs: f32, + pub disk_inner: f32, + pub disk_outer: f32, + pub disk_tilt: f32, + pub disk_brightness: f32, + pub disk_rotation_speed: f32, + pub doppler_strength: f32, + pub star_intensity: f32, + pub skybox_intensity: f32, + pub grid_density: f32, + // Flags packed as u32 (bools aren't valid uniform scalar types in WGSL) + pub doppler_enabled: u32, + pub grid_enabled: u32, + pub planet_count: u32, + pub steps: u32, + pub _pad4: f32, + pub _pad5: f32, +} + +impl Default for BlackHoleUniforms { + fn default() -> Self { + Self { + eye: Vec3::new(0.0, 0.0, 30.0), + _pad0: 0.0, + forward: Vec3::new(0.0, 0.0, -1.0), + _pad1: 0.0, + right: Vec3::new(1.0, 0.0, 0.0), + _pad2: 0.0, + up: Vec3::new(0.0, 1.0, 0.0), + fov: 1.0, + resolution: Vec2::new(1280.0, 720.0), + time: 0.0, + _pad3: 0.0, + rs: 1.0, + disk_inner: 3.0, + disk_outer: 15.0, + disk_tilt: 1.318, + disk_brightness: 1.0, + disk_rotation_speed: 0.5, + doppler_strength: 1.0, + star_intensity: 1.0, + skybox_intensity: 0.0, + grid_density: 1.0, + doppler_enabled: 1, + grid_enabled: 0, + planet_count: 0, + steps: 300, + _pad4: 0.0, + _pad5: 0.0, + } + } +} + +/// One planet's data, uploaded in a storage buffer (binding 3). +#[derive(Clone, Copy, ShaderType, Default)] +pub struct SphereData { + pub center: Vec3, + pub radius: f32, + pub color: Vec3, + pub emissive: u32, + pub _pad0: f32, + pub _pad1: f32, + pub _pad2: f32, +} + +pub const MAX_PLANETS: usize = 32; + +#[derive(Asset, TypePath, AsBindGroup, Clone)] pub struct BlackHoleMaterial { #[uniform(0)] - pub time: f32, + pub uniforms: BlackHoleUniforms, + // Texture at binding 1 + its matching sampler at binding 2. The derive + // requires the texture and sampler attributes to live on the same field. + #[texture(1)] + #[sampler(2)] + pub skybox: Option>, + #[storage(3, read_only)] + pub planets: Handle, } impl Material2d for BlackHoleMaterial { @@ -15,3 +107,13 @@ impl Material2d for BlackHoleMaterial { "shaders/black_hole.wgsl".into() } } + +impl Default for BlackHoleMaterial { + fn default() -> Self { + Self { + uniforms: BlackHoleUniforms::default(), + skybox: None, + planets: Handle::default(), + } + } +} diff --git a/src/render/plugin.rs b/src/render/plugin.rs index 590dfee..637eae6 100644 --- a/src/render/plugin.rs +++ b/src/render/plugin.rs @@ -7,12 +7,11 @@ pub struct BlackHolePlugin; impl Plugin for BlackHolePlugin { fn build(&self, app: &mut App) { - app.add_plugins(Material2dPlugin::::default()) - .add_systems(Startup, spawn_fullscreen_quad) - .add_systems(Update, update_time) - .init_resource::() + app.init_resource::() .init_resource::() - .add_systems(Update, crate::camera::orbit_controller); + .add_plugins(Material2dPlugin::::default()) + .add_systems(Startup, spawn_fullscreen_quad) + .add_systems(Update, (crate::camera::orbit_controller, mirror_params)); } } @@ -20,17 +19,56 @@ fn spawn_fullscreen_quad( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, + window: Query<&Window>, ) { - commands.spawn(Camera2d); + let win = match window.single() { + Ok(w) => w, + Err(_) => return, + }; + let aspect = win.width() / win.height(); commands.spawn(( - Mesh2d(meshes.add(Rectangle::new(2.0, 2.0))), - MeshMaterial2d(materials.add(BlackHoleMaterial { time: 0.0 })), + Mesh2d(meshes.add(Rectangle::new(2.0 * aspect, 2.0))), + MeshMaterial2d(materials.add(BlackHoleMaterial::default())), Transform::default(), )); + commands.spawn(Camera2d); } -fn update_time(time: Res