From 0945ee4023f2d64d9e646f02d998d67fcb5a0c86 Mon Sep 17 00:00:00 2001 From: Amatsugu Date: Sat, 6 Apr 2024 17:02:11 -0400 Subject: [PATCH] array texture shader --- game/main/assets/shaders/world/chunk.wgsl | 90 ++++++++++++----------- game/main/src/phos.rs | 76 +++++++++++++------ game/main/src/prelude.rs | 12 ++- 3 files changed, 111 insertions(+), 67 deletions(-) diff --git a/game/main/assets/shaders/world/chunk.wgsl b/game/main/assets/shaders/world/chunk.wgsl index e633154..5d4cfe7 100644 --- a/game/main/assets/shaders/world/chunk.wgsl +++ b/game/main/assets/shaders/world/chunk.wgsl @@ -1,56 +1,58 @@ #import bevy_pbr::{ - forward_io::VertexOutput, - mesh_view_bindings::view, - pbr_types::{STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT, PbrInput, pbr_input_new}, - pbr_functions as fns, + pbr_fragment::pbr_input_from_standard_material, + pbr_functions::alpha_discard, } -#import bevy_core_pipeline::tonemapping::tone_mapping -@group(2) @binding(0) var my_array_texture: texture_2d_array; -@group(2) @binding(1) var my_array_texture_sampler: sampler; +#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::{apply_pbr_lighting, main_pass_post_lighting_processing}, +} +#endif + +struct ChunkMaterial { + array_texture: texture_2d_array, + array_texture_sampler: sampler, +} + +@group(2) @binding(100) +var chunk_material: ChunkMaterial; @fragment fn fragment( + in: VertexOutput, @builtin(front_facing) is_front: bool, - mesh: VertexOutput, -) -> @location(0) vec4 { - let layer = i32(mesh.world_position.x) & 0x3; +) -> FragmentOutput { + // generate a PbrInput struct from the StandardMaterial bindings + var pbr_input = pbr_input_from_standard_material(in, is_front); - // Prepare a 'processed' StandardMaterial by sampling all textures to resolve - // the material members - var pbr_input: PbrInput = pbr_input_new(); - pbr_input.material.base_color = textureSample(my_array_texture, my_array_texture_sampler, mesh.uv, layer); -#ifdef VERTEX_COLORS - pbr_input.material.base_color = pbr_input.material.base_color * mesh.color; + // alpha discard + pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color); + +#ifdef PREPASS_PIPELINE + // in deferred mode we can't modify anything after that, as lighting is run in a separate fullscreen shader. + let out = deferred_output(in, pbr_input); +#else + var out: FragmentOutput; + // apply lighting + out.color = apply_pbr_lighting(pbr_input); + + let layer = i32(in.world_position.x) & 0x7; + out.color = textureSample(chunk_material.array_texture, chunk_material.array_texture_sampler, in.uv, layer); + + // 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); + + // we can optionally modify the final result here + out.color = out.color * 2.0; #endif - let double_sided = (pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u; - - pbr_input.frag_coord = mesh.position; - pbr_input.world_position = mesh.world_position; - pbr_input.world_normal = fns::prepare_world_normal( - mesh.world_normal, - double_sided, - is_front, - ); - - pbr_input.is_orthographic = view.projection[3].w == 1.0; - - pbr_input.N = fns::apply_normal_mapping( - pbr_input.material.flags, - mesh.world_normal, - double_sided, - is_front, -#ifdef VERTEX_TANGENTS -#ifdef STANDARD_MATERIAL_NORMAL_MAP - mesh.world_tangent, -#endif -#endif - mesh.uv, - view.mip_bias, - ); - pbr_input.V = fns::calculate_view(mesh.world_position, pbr_input.is_orthographic); - - return tone_mapping(fns::apply_pbr_lighting(pbr_input), view.color_grading); + return out; } \ No newline at end of file diff --git a/game/main/src/phos.rs b/game/main/src/phos.rs index f1422c0..3dab997 100644 --- a/game/main/src/phos.rs +++ b/game/main/src/phos.rs @@ -1,3 +1,4 @@ +use bevy::asset::LoadState; use bevy::pbr::{ExtendedMaterial, MaterialExtension}; use bevy::render::render_resource::{AsBindGroup, ShaderRef}; use bevy::{pbr::CascadeShadowConfig, prelude::*}; @@ -14,9 +15,12 @@ pub struct PhosGamePlugin; impl Plugin for PhosGamePlugin { fn build(&self, app: &mut App) { - app.add_plugins(PhosCameraPlugin); + app.add_plugins(PhosCameraPlugin).add_plugins(MaterialPlugin::< + ExtendedMaterial, + >::default()); app.add_systems(Startup, init_game) .add_systems(Startup, (load_textures, create_map).chain()); + app.add_systems(Update, (check_texture, spawn_map)); app.add_plugins(bevy::diagnostic::FrameTimeDiagnosticsPlugin) .add_plugins(bevy::diagnostic::EntityCountDiagnosticsPlugin) .add_plugins(bevy::diagnostic::SystemInformationDiagnosticsPlugin) @@ -45,24 +49,42 @@ fn init_game(mut commands: Commands) { transform: Transform::from_xyz(500., 260.0, 500.).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); + + commands.insert_resource(PhosMap::default()); } fn load_textures( mut commands: Commands, asset_server: Res, - mut images: ResMut>, ) { let main_tex = asset_server.load("textures/world/stack.png"); commands.insert_resource(ChunkAtlas { handle: main_tex.clone(), + is_loaded: false, }); +} - //todo: wait for texture to load - let image = images.get_mut(&main_tex).unwrap(); +fn check_texture( + asset_server: Res, + mut atlas: ResMut, + mut map: ResMut, + mut images: ResMut>, +) { + if atlas.is_loaded { + return; + } + + if asset_server.load_state(atlas.handle.clone()) != LoadState::Loaded { + return; + } + let image = images.get_mut(&atlas.handle).unwrap(); - // Create a new array texture asset from the loaded texture. let array_layers = 7; image.reinterpret_stacked_2d_as_array(array_layers); + + atlas.is_loaded = true; + map.ready = true; + map.regenerate = true; } fn draw_gizmos(mut gizmos: Gizmos, hm: Res) { @@ -95,13 +117,7 @@ fn draw_gizmos(mut gizmos: Gizmos, hm: Res) { } } -//todo: run after textures are ready -fn create_map( - mut commands: Commands, - mut materials: ResMut>>, - mut meshes: ResMut>, - atlas: Res, -) { +fn create_map(mut commands: Commands) { let heightmap = generate_heightmap( &GenerationConfig { layers: vec![ @@ -162,6 +178,21 @@ fn create_map( 2, ); + commands.insert_resource(heightmap); +} + +fn spawn_map( + heightmap: Res, + mut commands: Commands, + mut materials: ResMut>>, + mut meshes: ResMut>, + atlas: Res, + mut map: ResMut, +) { + if !map.ready || !map.regenerate { + return; + } + map.regenerate = false; let chunk_material = materials.add(ExtendedMaterial { base: StandardMaterial { base_color: Color::WHITE, @@ -175,21 +206,22 @@ fn create_map( for chunk in &heightmap.chunks { let mesh = generate_chunk_mesh(&chunk, &heightmap); let pos = offset_to_world(chunk.chunk_offset * Chunk::SIZE as i32, 0.); - commands.spawn(MaterialMeshBundle { - mesh: meshes.add(mesh), - material: chunk_material.clone(), - transform: Transform::from_translation(pos), - ..default() - }); + commands.spawn(( + MaterialMeshBundle { + mesh: meshes.add(mesh), + material: chunk_material.clone(), + transform: Transform::from_translation(pos), + ..default() + }, + PhosChunk, + )); } - - commands.insert_resource(heightmap); } #[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] struct ChunkMaterial { - #[texture(0, dimension = "2d_array")] - #[sampler(1)] + #[texture(100, dimension = "2d_array")] + #[sampler(101)] array_texture: Handle, } diff --git a/game/main/src/prelude.rs b/game/main/src/prelude.rs index 1ec9019..cfac76c 100644 --- a/game/main/src/prelude.rs +++ b/game/main/src/prelude.rs @@ -1,7 +1,17 @@ use bevy::asset::Handle; -use bevy::prelude::{Image, Resource}; +use bevy::prelude::{Component, Image, Resource}; #[derive(Resource)] pub struct ChunkAtlas { pub handle: Handle, + pub is_loaded: bool, } + +#[derive(Resource, Default)] +pub struct PhosMap { + pub ready: bool, + pub regenerate: bool, +} + +#[derive(Component)] +pub struct PhosChunk;