Download src/render/mod.rs from EHS-Studio/hieroxel: direct link, hf CLI and curl.
- Browser
- Download file 7.98 kB
-
https://huggingface.co/EHS-Studio/hieroxel/resolve/main/src/render/mod.rs
- Command line
-
hf download hf://EHS-Studio/hieroxel/src/render/mod.rs
-
curl -L -o mod.rs https://huggingface.co/EHS-Studio/hieroxel/resolve/main/src/render/mod.rs
7.98 kB
| //! Chunk material, block textures, and the day/night sky. | |
| pub mod textures; | |
| use std::f32::consts::TAU; | |
| use bevy::asset::embedded_asset; | |
| use bevy::mesh::MeshVertexBufferLayoutRef; | |
| use bevy::pbr::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin}; | |
| use bevy::prelude::*; | |
| use bevy::render::render_resource::{ | |
| AsBindGroup, RenderPipelineDescriptor, ShaderType, SpecializedMeshPipelineError, | |
| }; | |
| use bevy::shader::ShaderRef; | |
| use crate::world::block::{BLOCKS, BLOCK_COUNT}; | |
| use crate::world::{VoxelWorld, ATTRIBUTE_VOXEL}; | |
| const SHADER: &str = "embedded://hieroxel/render/voxel.wesl"; | |
| pub struct VoxelRenderPlugin; | |
| impl Plugin for VoxelRenderPlugin { | |
| fn build(&self, app: &mut App) { | |
| embedded_asset!(app, "voxel.wesl"); | |
| app.add_plugins(MaterialPlugin::<VoxelMaterial>::default()) | |
| .init_resource::<DayCycle>() | |
| .init_resource::<CameraEnv>() | |
| .add_systems(Startup, (setup_materials, setup_sky_bodies)) | |
| .add_systems(Update, update_sky); | |
| } | |
| } | |
| pub struct VoxelParams { | |
| pub fog_color: Vec4, | |
| /// x: daylight, y: unused, z: fog start, w: fog end | |
| pub sky: Vec4, | |
| /// x: underwater, y: water pass | |
| pub misc: Vec4, | |
| } | |
| pub struct VoxelMaterial { | |
| pub params: VoxelParams, | |
| pub textures: Handle<Image>, | |
| pub water: bool, | |
| } | |
| pub struct VoxelMaterialKey { | |
| water: bool, | |
| } | |
| impl From<&VoxelMaterial> for VoxelMaterialKey { | |
| fn from(m: &VoxelMaterial) -> Self { | |
| Self { water: m.water } | |
| } | |
| } | |
| impl Material for VoxelMaterial { | |
| fn vertex_shader() -> ShaderRef { | |
| SHADER.into() | |
| } | |
| fn fragment_shader() -> ShaderRef { | |
| SHADER.into() | |
| } | |
| fn alpha_mode(&self) -> AlphaMode { | |
| if self.water { AlphaMode::Blend } else { AlphaMode::Opaque } | |
| } | |
| fn enable_prepass() -> bool { | |
| false | |
| } | |
| fn enable_shadows() -> bool { | |
| false | |
| } | |
| fn specialize( | |
| _pipeline: &MaterialPipeline, | |
| descriptor: &mut RenderPipelineDescriptor, | |
| layout: &MeshVertexBufferLayoutRef, | |
| key: MaterialPipelineKey<Self>, | |
| ) -> Result<(), SpecializedMeshPipelineError> { | |
| descriptor.vertex.buffers = vec![layout.0.get_layout(&[ATTRIBUTE_VOXEL.at_shader_location(0)])?]; | |
| if key.bind_group_data.water { | |
| // The surface must be visible from below too. | |
| descriptor.primitive.cull_mode = None; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| pub struct VoxelMaterials { | |
| pub solid: Handle<VoxelMaterial>, | |
| pub water: Handle<VoxelMaterial>, | |
| /// Hotbar icon per block id (side texture). | |
| pub icons: Vec<Handle<Image>>, | |
| } | |
| pub struct DayCycle { | |
| /// 0 = midnight, 0.25 = sunrise, 0.5 = noon, 0.75 = sunset. | |
| pub time: f32, | |
| pub day_length: f32, | |
| pub speed: f32, | |
| } | |
| impl Default for DayCycle { | |
| fn default() -> Self { | |
| Self { | |
| time: 0.3, | |
| day_length: 720.0, | |
| speed: 1.0, | |
| } | |
| } | |
| } | |
| impl DayCycle { | |
| pub fn sun_elevation(&self) -> f32 { | |
| (TAU * (self.time - 0.25)).sin() | |
| } | |
| pub fn clock(&self) -> String { | |
| let minutes = (self.time * 24.0 * 60.0) as u32; | |
| format!("{:02}:{:02}", minutes / 60, minutes % 60) | |
| } | |
| } | |
| /// What the camera is inside; set by the player. | |
| pub struct CameraEnv { | |
| pub underwater: bool, | |
| } | |
| fn setup_materials( | |
| mut commands: Commands, | |
| mut images: ResMut<Assets<Image>>, | |
| mut materials: ResMut<Assets<VoxelMaterial>>, | |
| ) { | |
| let textures = images.add(textures::build_array()); | |
| let solid = materials.add(VoxelMaterial { | |
| params: VoxelParams::default(), | |
| textures: textures.clone(), | |
| water: false, | |
| }); | |
| let water = materials.add(VoxelMaterial { | |
| params: VoxelParams { | |
| misc: Vec4::new(0.0, 1.0, 0.0, 0.0), | |
| ..default() | |
| }, | |
| textures, | |
| water: true, | |
| }); | |
| let icons = (0..BLOCK_COUNT) | |
| .map(|id| images.add(textures::icon(BLOCKS[id].tex[1]))) | |
| .collect(); | |
| commands.insert_resource(VoxelMaterials { solid, water, icons }); | |
| } | |
| struct SkyBody { | |
| /// Offset of the body's angle from the sun's. | |
| phase: f32, | |
| } | |
| fn setup_sky_bodies( | |
| mut commands: Commands, | |
| mut meshes: ResMut<Assets<Mesh>>, | |
| mut materials: ResMut<Assets<StandardMaterial>>, | |
| ) { | |
| let quad = meshes.add(Rectangle::new(60.0, 60.0)); | |
| for (phase, color) in [(0.0, Color::srgb(1.0, 0.93, 0.7)), (0.5, Color::srgb(0.82, 0.86, 0.95))] { | |
| commands.spawn(( | |
| SkyBody { phase }, | |
| Mesh3d(quad.clone()), | |
| MeshMaterial3d(materials.add(StandardMaterial { | |
| base_color: color, | |
| unlit: true, | |
| fog_enabled: false, | |
| cull_mode: None, | |
| ..default() | |
| })), | |
| Transform::default(), | |
| )); | |
| } | |
| } | |
| fn lerp3(a: Vec3, b: Vec3, t: f32) -> Vec3 { | |
| a + (b - a) * t.clamp(0.0, 1.0) | |
| } | |
| fn update_sky( | |
| time: Res<Time>, | |
| keys: Res<ButtonInput<KeyCode>>, | |
| mut cycle: ResMut<DayCycle>, | |
| env: Res<CameraEnv>, | |
| world: Res<VoxelWorld>, | |
| handles: Option<Res<VoxelMaterials>>, | |
| mut materials: ResMut<Assets<VoxelMaterial>>, | |
| mut clear: ResMut<ClearColor>, | |
| camera: Query<&Transform, (With<Camera3d>, Without<SkyBody>)>, | |
| mut bodies: Query<(&SkyBody, &mut Transform)>, | |
| ) { | |
| let fast = if keys.pressed(KeyCode::KeyT) { 60.0 } else { 1.0 }; | |
| cycle.time = (cycle.time + time.delta_secs() * cycle.speed * fast / cycle.day_length).rem_euclid(1.0); | |
| let e = cycle.sun_elevation(); | |
| let daylight = (e * 2.2 + 0.4).clamp(0.12, 1.0); | |
| let night = Vec3::new(0.015, 0.02, 0.055); | |
| let day = Vec3::new(0.52, 0.74, 1.0); | |
| let dusk = Vec3::new(0.98, 0.56, 0.32); | |
| let mut sky = lerp3(night, day, (e + 0.15) / 0.45); | |
| let glow = (1.0 - e.abs() / 0.3).clamp(0.0, 1.0) * 0.55; | |
| sky = lerp3(sky, dusk, glow); | |
| let rd = world.render_distance as f32 * 32.0; | |
| let (fog_color, fog_start, fog_end) = if env.underwater { | |
| (Vec3::new(0.06, 0.2, 0.42) * daylight.max(0.3), 0.0, 28.0) | |
| } else { | |
| (sky, rd * 0.55, rd * 0.95) | |
| }; | |
| let fog_srgb = Color::srgb(fog_color.x, fog_color.y, fog_color.z); | |
| clear.0 = fog_srgb; | |
| let linear = fog_srgb.to_linear(); | |
| // Touching a material re-prepares every chunk that uses it, so only write | |
| // when the sky has visibly changed. | |
| if let Some(handles) = handles { | |
| for (handle, water_pass) in [(&handles.solid, 0.0), (&handles.water, 1.0)] { | |
| let params = VoxelParams { | |
| fog_color: Vec4::new(linear.red, linear.green, linear.blue, 1.0), | |
| sky: Vec4::new(daylight, 0.0, fog_start, fog_end), | |
| misc: Vec4::new(env.underwater as u32 as f32, water_pass, 0.0, 0.0), | |
| }; | |
| let stale = materials.get(handle).is_some_and(|m| { | |
| let old = m.params; | |
| old.misc != params.misc | |
| || (old.sky - params.sky).abs().max_element() > 0.004 | |
| || (old.fog_color - params.fog_color).abs().max_element() > 0.003 | |
| }); | |
| if stale && let Some(mut m) = materials.get_mut(handle) { | |
| m.params = params; | |
| } | |
| } | |
| } | |
| if let Ok(cam) = camera.single() { | |
| for (body, mut t) in &mut bodies { | |
| let angle = TAU * (cycle.time - 0.25 + body.phase); | |
| let dir = Vec3::new(angle.cos(), angle.sin(), 0.25).normalize(); | |
| t.translation = cam.translation + dir * 700.0; | |
| t.look_at(cam.translation, Vec3::Y); | |
| } | |
| } | |
| } | |