working voronoi representation using custom flat interpolation in

material shader
This commit is contained in:
Arkitu 2025-06-10 19:16:42 +02:00
parent f46536ecc8
commit 9b4d9d74fc
6 changed files with 227 additions and 39 deletions

136
assets/shaders/vertex.wgsl Normal file
View File

@ -0,0 +1,136 @@
#import bevy_pbr::{
pbr_types,
pbr_functions::alpha_discard,
pbr_fragment::pbr_input_from_standard_material,
decal::clustered::apply_decal_base_color,
mesh_functions,
view_transformations::position_world_to_clip
}
#ifdef PREPASS_PIPELINE
#import bevy_pbr::{
prepass_io::{VertexOutput, FragmentOutput},
pbr_deferred_functions::deferred_output,
}
#else
#import bevy_pbr::{
forward_io::{VertexOutput, FragmentOutput},
pbr_functions,
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT,
}
#endif
#ifdef MESHLET_MESH_MATERIAL_PASS
#import bevy_pbr::meshlet_visibility_buffer_resolve::resolve_vertex_output
#endif
#ifdef OIT_ENABLED
#import bevy_core_pipeline::oit::oit_draw
#endif // OIT_ENABLED
#ifdef FORWARD_DECAL
#import bevy_pbr::decal::forward::get_forward_decal_info
#endif
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
// @location(2) cell_kind: u32
@location(5) color: vec4<f32>,
};
struct FlatVertexOutput {
// This is `clip position` when the struct is used as a vertex stage output
// and `frag coord` when used as a fragment stage input
@builtin(position) position: vec4<f32>,
@location(0) world_position: vec4<f32>,
@location(1) @interpolate(flat) world_normal: vec3<f32>,
// @location(2) @interpolate(flat) cell_kind: u32
@location(5) @interpolate(flat) color: vec4<f32>,
@location(6) @interpolate(flat) instance_index: u32,
}
// struct FragmentOutput {
// @location(0) color: vec4<f32>,
// }
@vertex
fn vertex(vert: Vertex) -> FlatVertexOutput {
var out: FlatVertexOutput;
let mesh_world_from_local = mesh_functions::get_world_from_local(vert.instance_index);
out.world_normal = mesh_functions::mesh_normal_local_to_world(
vert.normal,
vert.instance_index
);
out.world_position = mesh_functions::mesh_position_local_to_world(mesh_world_from_local, vec4<f32>(vert.position, 1.0));
out.position = position_world_to_clip(out.world_position.xyz);
// out.cell_kind = vert.cell_kind;
out.instance_index = vert.instance_index;
out.color = vert.color;
return out;
}
@fragment
fn fragment(
vertex_output: FlatVertexOutput,
@builtin(front_facing) is_front: bool
) -> FragmentOutput {
var in: VertexOutput;
in.instance_index = vertex_output.instance_index;
in.position = vertex_output.position;
in.world_normal = vertex_output.world_normal;
in.color = vertex_output.color;
// generate a PbrInput struct from the StandardMaterial bindings
var pbr_input = pbr_input_from_standard_material(in, is_front);
// alpha discard
pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
// // clustered decals
// pbr_input.material.base_color = apply_decal_base_color(
// in.world_position.xyz,
// in.position.xy,
// pbr_input.material.base_color
// );
#ifdef PREPASS_PIPELINE
// write the gbuffer, lighting pass id, and optionally normal and motion_vector textures
let out = deferred_output(in, pbr_input);
#else
// in forward mode, we calculate the lit color immediately, and then apply some post-lighting effects here.
// in deferred mode the lit color and these effects will be calculated in the deferred lighting shader
var out: FragmentOutput;
if (pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u {
out.color = apply_pbr_lighting(pbr_input);
} else {
out.color = pbr_input.material.base_color;
}
// apply in-shader post processing (fog, alpha-premultiply, and also tonemapping, debanding if the camera is non-hdr)
// note this does not include fullscreen postprocessing effects like bloom.
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
#endif
#ifdef OIT_ENABLED
let alpha_mode = pbr_input.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
if alpha_mode != pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE {
// The fragments will only be drawn during the oit resolve pass.
oit_draw(in.position, out.color);
discard;
}
#endif // OIT_ENABLED
#ifdef FORWARD_DECAL
out.color.a = min(forward_decal_info.alpha, out.color.a);
#endif
return out;
}

View File

@ -24,7 +24,6 @@ fn setup(mut cmds: Commands, window: Query<&Window>) {
DirectionalLight { DirectionalLight {
color: Color::WHITE, color: Color::WHITE,
illuminance: 17000., illuminance: 17000.,
//shadows_enabled: true,
..Default::default() ..Default::default()
}, },
Transform::default().looking_to(Vec3::new(1., -1., -1.), Vec3::ZERO), Transform::default().looking_to(Vec3::new(1., -1., -1.), Vec3::ZERO),

View File

@ -1,7 +1,9 @@
use core::f32;
use std::time::Duration; use std::time::Duration;
use bevy::{ use bevy::{
asset::RenderAssetUsages, asset::RenderAssetUsages,
pbr::MaterialExtension,
picking::PickSet, picking::PickSet,
prelude::*, prelude::*,
render::mesh::{Indices, PrimitiveTopology}, render::mesh::{Indices, PrimitiveTopology},
@ -15,7 +17,10 @@ mod cells;
mod picking; mod picking;
pub use animals::AnimalKind; pub use animals::AnimalKind;
pub use cells::CellKind; pub use cells::CellKind;
use cells::*; use cells::{
material::{CellMaterial, CellMaterialExtension},
*,
};
use picking::*; use picking::*;
pub struct Plugin; pub struct Plugin;
@ -25,7 +30,8 @@ impl bevy::prelude::Plugin for Plugin {
.add_systems(PreUpdate, picking_backend.in_set(PickSet::Backend)) .add_systems(PreUpdate, picking_backend.in_set(PickSet::Backend))
.add_systems(Update, (update_map_mesh, cells_regeneration, expand)) .add_systems(Update, (update_map_mesh, cells_regeneration, expand))
.insert_resource(ClearColor(Color::srgb(0., 0., 1.))) .insert_resource(ClearColor(Color::srgb(0., 0., 1.)))
.insert_resource(Seed(thread_rng().gen())); .insert_resource(Seed(thread_rng().gen()))
.add_plugins(MaterialPlugin::<CellMaterial>::default());
} }
} }
@ -39,16 +45,16 @@ pub const SIZE: usize = 10000;
#[derive(Resource)] #[derive(Resource)]
struct Seed(u32); struct Seed(u32);
#[derive(Component)] #[derive(Resource)]
pub struct Voronoi(voronoice::Voronoi); pub struct Voronoi(voronoice::Voronoi);
#[derive(Component)] #[derive(Component)]
pub struct MapMarker; pub struct MapMarker;
#[derive(Component)] #[derive(Component)]
struct MapColors(Vec<[f32; 4]>); struct MapMeshColors(Vec<[f32; 4]>);
#[derive(Component)] #[derive(Resource)]
pub struct CellsEntities(Vec<Entity>); pub struct CellsEntities(Vec<Entity>);
#[derive(Component)] #[derive(Component)]
@ -57,7 +63,8 @@ pub struct MeshNeedsUpdate(bool);
fn setup( fn setup(
mut cmds: Commands, mut cmds: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, // mut materials: ResMut<Assets<StandardMaterial>>,
mut materials: ResMut<Assets<CellMaterial>>,
seed: Res<Seed>, seed: Res<Seed>,
) { ) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64); let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
@ -81,7 +88,6 @@ fn setup(
let c = voronoi.cell(i); let c = voronoi.cell(i);
let site = c.site_position(); let site = c.site_position();
let z = get_altitude(&z_noise, &[site.x as f32, site.y as f32]); let z = get_altitude(&z_noise, &[site.x as f32, site.y as f32]);
info!(z);
let _m = ( let _m = (
(moisture_noise.get([site.x, site.y]) + 1.) / 2. (moisture_noise.get([site.x, site.y]) + 1.) / 2.
// Noise + [0; 1] // Noise + [0; 1]
@ -105,27 +111,42 @@ fn setup(
let mut poss = Vec::new(); let mut poss = Vec::new();
let mut indices = Vec::new(); let mut indices = Vec::new();
let mut normals = Vec::new(); // let mut normals = Vec::new();
for (c, cd) in voronoi.iter_cells().zip(cells.iter_mut()) { for (i, pos) in voronoi.sites().iter().enumerate() {
let vs = c.iter_vertices().collect::<Vec<_>>(); let z = get_altitude(&z_noise, &[pos.x as f32, pos.y as f32]);
let i = poss.len(); poss.push(Vec3::new(pos.x as f32, pos.y as f32, z));
for v in vs.iter() { cells[i].vertices.push(i);
poss.push(Vec3::new(
v.x as f32,
v.y as f32,
(get_altitude(&z_noise, &[v.x as f32, v.y as f32]) / 1.),
));
normals.push(Vec3::new(0., 0., 1.));
}
for v in 1..(vs.len() - 1) {
indices.extend_from_slice(&[(i + v + 1) as u32, (i + v) as u32, i as u32]);
cd.vertices.extend_from_slice(&[i, i + v, i + v + 1]);
}
} }
for t in voronoi.triangulation().triangles.chunks_exact(3) {
indices.extend_from_slice(&[t[2] as u32, t[1] as u32, t[0] as u32]);
}
// indices.extend(voronoi.triangulation().triangles.iter().map(|t| *t as u32));
// for (c, cd) in voronoi.iter_cells().zip(cells.iter_mut()) {
// let vs = c.iter_vertices().collect::<Vec<_>>();
// let i = poss.len();
// for v in vs.iter() {
// let z = get_altitude(&z_noise, &[v.x as f32, v.y as f32]);
// poss.push(Vec3::new(v.x as f32, v.y as f32, z));
// // const EPSILON: f32 = 0.01;
// // let dzx = get_altitude(&z_noise, &[v.x as f32 + EPSILON, v.y as f32]) - z;
// // let nx = (f32::consts::FRAC_PI_2 - (dzx / EPSILON).atan()).cos();
// // let dzy = get_altitude(&z_noise, &[v.x as f32, v.y as f32 + EPSILON]) - z;
// // let ny = (f32::consts::FRAC_PI_2 - (dzy / EPSILON).atan()).cos();
// // let nz = (1. - (nx.powi(2) + ny.powi(2))).sqrt();
// // normals.push(Vec3::new(nx, ny, nz));
// }
// for v in 1..(vs.len() - 1) {
// indices.extend_from_slice(&[(i + v + 1) as u32, (i + v) as u32, i as u32]);
// cd.vertices.extend_from_slice(&[i, i + v, i + v + 1]);
// }
// }
let colors = vec![[0.; 4]; poss.len()]; let colors = vec![[0.; 4]; poss.len()];
let mesh = Mesh::new( let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList, PrimitiveTopology::TriangleList,
RenderAssetUsages::default(), RenderAssetUsages::default(),
) )
@ -133,17 +154,26 @@ fn setup(
// 3D space), for each of the corners of the parallelogram. // 3D space), for each of the corners of the parallelogram.
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, poss) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, poss)
.with_inserted_attribute(Mesh::ATTRIBUTE_COLOR, colors.clone()) .with_inserted_attribute(Mesh::ATTRIBUTE_COLOR, colors.clone())
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) // .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_indices(Indices::U32(indices)); .with_inserted_indices(Indices::U32(indices));
mesh.compute_smooth_normals();
mesh.generate_tangents();
// mesh.duplicate_vertices();
// mesh.compute_flat_normals();
let mut cells_entities = Vec::with_capacity(cells.len()); let mut cells_entities = Vec::with_capacity(cells.len());
cmds.spawn(( cmds.spawn((
Mesh3d(meshes.add(mesh)), Mesh3d(meshes.add(mesh)),
// StandardMaterial // StandardMaterial
MeshMaterial3d(materials.add(StandardMaterial::default())), // MeshMaterial3d(materials.add(StandardMaterial::default())),
MeshMaterial3d(materials.add(CellMaterial {
base: StandardMaterial::default(),
extension: CellMaterialExtension {},
})),
Transform::default(), Transform::default(),
Voronoi(voronoi), MapMeshColors(colors),
MapColors(colors),
MeshNeedsUpdate(true), MeshNeedsUpdate(true),
MapMarker, MapMarker,
)) ))
@ -166,13 +196,14 @@ fn setup(
cmd.observe(self::cells::on_click); cmd.observe(self::cells::on_click);
cells_entities.push(cmd.id()); cells_entities.push(cmd.id());
} }
}) });
.insert(CellsEntities(cells_entities)); cmds.insert_resource(Voronoi(voronoi));
cmds.insert_resource(CellsEntities(cells_entities));
} }
fn update_map_mesh( fn update_map_mesh(
cells: Query<(&Cell, Option<&Wealth>)>, cells: Query<(&Cell, Option<&Wealth>)>,
mut map: Query<(&Mesh3d, &mut MapColors, &mut MeshNeedsUpdate), With<MapMarker>>, mut map: Query<(&Mesh3d, &mut MapMeshColors, &mut MeshNeedsUpdate), With<MapMarker>>,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
) { ) {
let (mesh, mut cols, mut needs_update) = map.single_mut(); let (mesh, mut cols, mut needs_update) = map.single_mut();

View File

@ -7,6 +7,8 @@ use crate::{time::GameTime, ui::CurrentAction};
use super::{animals::Animal, AnimalKind, CellsEntities, MapMarker, MeshNeedsUpdate, Voronoi}; use super::{animals::Animal, AnimalKind, CellsEntities, MapMarker, MeshNeedsUpdate, Voronoi};
pub mod material;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CellKind { pub enum CellKind {
Sea, Sea,
@ -94,12 +96,12 @@ pub fn cells_regeneration(
pub fn expand( pub fn expand(
mut cells: Query<(&mut Cell, Option<&Wealth>)>, mut cells: Query<(&mut Cell, Option<&Wealth>)>,
map: Query<(&Voronoi, &CellsEntities)>, voronoi: Res<Voronoi>,
cells_entities: Res<CellsEntities>,
mut cmds: Commands, mut cmds: Commands,
t: Res<Time>, t: Res<Time>,
gt: Res<GameTime>, gt: Res<GameTime>,
) { ) {
let (voronoi, cells_entities) = map.single();
let mut random = thread_rng(); let mut random = thread_rng();
let mut changes = Vec::new(); let mut changes = Vec::new();
for (cell, wealth) in cells.iter() { for (cell, wealth) in cells.iter() {
@ -172,7 +174,7 @@ pub fn expand(
pub fn on_click( pub fn on_click(
trigger: Trigger<Pointer<Click>>, trigger: Trigger<Pointer<Click>>,
mut cells: Query<&mut Cell>, mut cells: Query<&mut Cell>,
voronoi: Query<&Voronoi>, voronoi: Res<Voronoi>,
mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>, mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>,
mut cmds: Commands, mut cmds: Commands,
ca: Res<CurrentAction>, ca: Res<CurrentAction>,
@ -216,7 +218,7 @@ pub fn on_click(
}, },
CurrentAction::AddAnimal(ak) => { CurrentAction::AddAnimal(ak) => {
if cell.kind.can_place_animal(ak) { if cell.kind.can_place_animal(ak) {
let v_cell = voronoi.single().0.cell(cell.voronoi_id); let v_cell = voronoi.0.cell(cell.voronoi_id);
let cell_pos = v_cell.site_position(); let cell_pos = v_cell.site_position();
cmds.spawn(( cmds.spawn((
Animal { kind: ak }, Animal { kind: ak },

18
src/map/cells/material.rs Normal file
View File

@ -0,0 +1,18 @@
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
use bevy::prelude::*;
use bevy::render::render_resource::*;
#[derive(Asset, AsBindGroup, Clone, Debug, Reflect)]
pub struct CellMaterialExtension {}
pub type CellMaterial = ExtendedMaterial<StandardMaterial, CellMaterialExtension>;
const SHADER_PATH: &str = "shaders/vertex.wgsl";
impl MaterialExtension for CellMaterialExtension {
fn vertex_shader() -> ShaderRef {
SHADER_PATH.into()
}
fn fragment_shader() -> ShaderRef {
SHADER_PATH.into()
}
}

View File

@ -8,19 +8,21 @@ use bevy::{
}; };
use voronoice::Point; use voronoice::Point;
use crate::camera::CameraMarker;
use super::{CellsEntities, MapMarker, Voronoi}; use super::{CellsEntities, MapMarker, Voronoi};
use crate::camera::CameraMarker;
pub fn picking_backend( pub fn picking_backend(
cam: Query<(&Transform, Entity), With<CameraMarker>>, cam: Query<(&Transform, Entity), With<CameraMarker>>,
window: Query<&Window, With<PrimaryWindow>>, window: Query<&Window, With<PrimaryWindow>>,
pointers: Query<(&PointerId, &PointerLocation)>, pointers: Query<(&PointerId, &PointerLocation)>,
map: Query<(&Voronoi, &CellsEntities, &Transform), With<MapMarker>>, map: Query<&Transform, With<MapMarker>>,
voronoi: Res<Voronoi>,
cells_entities: Res<CellsEntities>,
mut output: EventWriter<PointerHits>, mut output: EventWriter<PointerHits>,
) { ) {
let (cam, cam_id) = cam.single(); let (cam, cam_id) = cam.single();
let window = window.single(); let window = window.single();
let (voronoi, cells_entities, map_pos) = map.single(); let map_pos = map.single();
let mut last_cell = 0; let mut last_cell = 0;
for (id, l) in pointers.iter() { for (id, l) in pointers.iter() {
if let Some(mut pos) = l.location().map(|l| l.position) { if let Some(mut pos) = l.location().map(|l| l.position) {