This commit is contained in:
2026-01-10 17:43:54 +01:00
parent c6513a141d
commit 255c2097d5
15 changed files with 106 additions and 326 deletions

View File

@ -21,5 +21,8 @@ tiff = "0.10.3"
wgpu = {version = "27.0.1", features = ["spirv"]}
winit = "0.30.12"
[build-dependencies]
spirv-builder = {git = "https://github.com/rust-gpu/rust-gpu"}
[profile.release]
opt-level = 3

11
build.rs Normal file
View File

@ -0,0 +1,11 @@
use spirv_builder::Capability;
use spirv_builder::MetadataPrintout;
use spirv_builder::SpirvBuilder;
fn main() -> Result<(), Box<dyn std::error::Error>>
{
SpirvBuilder::new("shaders", "spirv-unknown-spv1.6")
.print_metadata(MetadataPrintout::Full)
.build()?;
Ok(())
}

7
rust-toolchain.toml Normal file
View File

@ -0,0 +1,7 @@
[toolchain]
channel = "nightly-2025-06-30"
components = ["rust-src", "rustc-dev", "llvm-tools"]
# commit_hash = 35f6036521777bdc0dcea1f980be4c192962a168
# Whenever changing the nightly channel, update the commit hash above, and
# change `REQUIRED_RUST_TOOLCHAIN` in `crates/rustc_codegen_spirv/build.rs` too.

2
shaders/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

10
shaders/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[lib]
crate-type = ["dylib"]
[package]
name = "shaders"
version = "0.1.0"
edition = "2024"
[dependencies]
spirv-std = {git = "https://github.com/rust-gpu/rust-gpu"}

23
shaders/src/lib.rs Normal file
View File

@ -0,0 +1,23 @@
#![cfg_attr(target_arch = "spirv", no_std)]
use spirv_std::glam::Vec4;
use spirv_std::glam::vec2;
use spirv_std::glam::vec4;
use spirv_std::spirv;
#[spirv(fragment)]
pub fn main_fs(output: &mut Vec4)
{
*output = vec4(0.2, 1.0, 0.2, 1.0);
}
#[spirv(vertex)]
pub fn main_vs(#[spirv(vertex_id)] in_id: &mut u32, #[spirv(position)] out_pos: &mut Vec4)
{
let positions = [vec2(-1., 3.), vec2(-1., 1.), vec2(3., 1.)];
*out_pos = Vec4::new(
positions[*in_id as usize].x,
positions[*in_id as usize].y,
0.,
1.,
);
}

View File

@ -1,166 +0,0 @@
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use itertools::Itertools;
use tiff::decoder::Decoder;
use wgpu::Device;
use wgpu::RenderPass;
use wgpu::TextureFormat;
use crate::state::Camera;
use crate::voxel_renderer::VoxelRenderer;
pub struct HeightMapRenderer
{
width: u32,
height: u32,
heightmap: Vec<f32>,
height_min: f32,
height_max: f32,
alive_chunks: Vec<cgmath::Vector3<i32>>,
eye_pos: cgmath::Vector3<f32>,
voxel_renderer: VoxelRenderer,
}
impl HeightMapRenderer
{
pub fn from_image(
path: impl AsRef<Path>,
device: &Device,
surface_format: TextureFormat,
) -> Self
{
let file = File::open(path).unwrap();
let mut decoder = Decoder::new(BufReader::new(file)).unwrap();
let (width, height) = decoder.dimensions().unwrap();
let decoded = decoder.read_image().unwrap();
let pixels = match decoded
{
tiff::decoder::DecodingResult::F32(decoded_pixels) => decoded_pixels,
_ =>
{
panic!("Unsuported image format.");
}
};
Self {
width,
height,
height_min: pixels.iter().copied().reduce(f32::min).unwrap_or(0.),
height_max: pixels.iter().copied().reduce(f32::max).unwrap_or(0.),
heightmap: pixels,
alive_chunks: vec![],
eye_pos: cgmath::Vector3::new(0., 0., 0.),
voxel_renderer: VoxelRenderer::new(device, surface_format),
}
}
pub fn set_eye_pos(&mut self, eye_pos: cgmath::Vector3<f32>, device: &Device)
{
self.eye_pos = eye_pos;
// self.voxel_renderer.set_chunks(
// device,
// &[
// cgmath::Vector3::new(-1, 0, -1),
// cgmath::Vector3::new(-1, 0, 1),
// cgmath::Vector3::new(1, 0, -1),
// cgmath::Vector3::new(1, 0, 1),
// ],
// );
// return;
// Compute alive chunks
let eye_chunk = cgmath::Vector3::new(
eye_pos.x.floor() as i32,
eye_pos.y.floor() as i32,
eye_pos.z.floor() as i32,
);
let mut alive_chunks = vec![];
for ((x, y), z) in (-10..=10)
.cartesian_product(-10..=10)
.cartesian_product(-10..=10)
{
let chunk = eye_chunk + cgmath::Vector3::new(x, y, z);
if chunk.x >= 0 && chunk.z >= 0
{
if chunk.y == -1
{
alive_chunks.push(chunk);
}
}
}
self.alive_chunks = alive_chunks;
if !self.alive_chunks.is_empty()
{
self.voxel_renderer.set_chunks(device, &self.alive_chunks);
}
return;
let mut alive_chunks = vec![];
for ((x, y), z) in (-10..=10)
.cartesian_product(-10..=10)
.cartesian_product(-10..=10)
{
let chunk = eye_chunk + cgmath::Vector3::new(x, y, z);
if chunk.x >= 0
&& chunk.x < (self.width / 256) as i32
&& chunk.z >= 0
&& chunk.z < (self.height / 256) as i32
{
if chunk.y == -1
{
alive_chunks.push(chunk);
println!("{}, {}, {}", chunk.x, chunk.y, chunk.z);
}
continue;
let chunk_voxel_height = y * 256;
let submit = [
(chunk.x, chunk.z),
(chunk.x + 1, chunk.z),
(chunk.x, chunk.z + 1),
(chunk.x + 1, chunk.z + 1),
]
.iter()
.map(|(chunk_x, chunk_z)| {
let voxel_x = chunk_x * 256;
let voxel_z = chunk_z * 256;
let altitude =
self.heightmap[voxel_x as usize + voxel_z as usize * self.width as usize];
let voxel_height =
map(altitude, self.height_min, self.height_max, 0., 100. * 256.);
//println!("{}", voxel_height);
(chunk_voxel_height as f32) < voxel_height
})
.reduce(|a, b| a || b)
.unwrap_or(false);
if submit
{
alive_chunks.push(chunk);
}
}
}
}
pub fn render(&mut self, render_pass: &mut RenderPass, camera: &Camera)
{
if !self.alive_chunks.is_empty()
{}
self.voxel_renderer.render(render_pass, camera);
}
}
fn map(x: f32, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> f32
{
((x - x_min) / (x_max - x_min)) * (y_max - y_min) + y_min
}

View File

@ -2,11 +2,8 @@
#![feature(generic_const_exprs)]
pub mod egui_renderer;
pub mod hm_renderer;
pub mod state;
pub mod voxel;
pub mod voxel_renderer;
use std::sync::Arc;
use winit::application::ApplicationHandler;

View File

@ -15,6 +15,7 @@ use wgpu::Device;
use wgpu::Extent3d;
use wgpu::FeaturesWGPU;
use wgpu::FeaturesWebGPU;
use wgpu::RenderPipeline;
use wgpu::TextureDescriptor;
use wgpu::TextureFormat;
use wgpu::TextureUsages;
@ -26,7 +27,6 @@ use winit::keyboard::KeyCode;
use winit::window::Window;
use crate::egui_renderer::EguiState;
use crate::voxel_renderer::VoxelRenderer;
pub struct State
{
@ -46,8 +46,7 @@ pub struct State
last_frame: Instant,
depth_buffer: TextureView,
voxel_renderer: VoxelRenderer,
test_pipeline: RenderPipeline,
}
#[derive(Clone, Copy)]
@ -72,6 +71,8 @@ impl State
{
pub async fn new(window: Arc<Window>) -> State
{
let shaders = wgpu::include_spirv!(env!("shaders.spv"));
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: Backends::VULKAN,
..wgpu::InstanceDescriptor::default()
@ -107,9 +108,52 @@ impl State
let cap = surface.get_capabilities(&adapter);
let surface_format = cap.formats[0];
let shader_module = device.create_shader_module(shaders);
let test_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("chunk_pipeline"),
layout: None,
vertex: wgpu::VertexState {
module: &shader_module,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader_module,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
let state = State {
egui_state: EguiState::new(&device, surface_format, &window),
test_pipeline,
window,
queue,
size,
@ -139,7 +183,6 @@ impl State
TextureFormat::Depth24PlusStencil8,
TextureUsages::RENDER_ATTACHMENT,
),
voxel_renderer: VoxelRenderer::new(&device, surface_format),
device,
};
@ -249,6 +292,9 @@ impl State
timestamp_writes: None,
occlusion_query_set: None,
});
hm_pass.set_pipeline(&self.test_pipeline);
hm_pass.draw(0..3, 0..1);
}
// Egui Pass

View File

@ -1,153 +0,0 @@
use cgmath::EuclideanSpace;
use cgmath::SquareMatrix;
use crevice::std430::AsStd430;
use wgpu::Buffer;
use wgpu::BufferDescriptor;
use wgpu::BufferUsages;
use wgpu::Device;
use wgpu::PushConstantRange;
use wgpu::RenderPass;
use wgpu::RenderPipeline;
use wgpu::ShaderStages;
use wgpu::TextureFormat;
use wgpu::VertexAttribute;
use wgpu::VertexBufferLayout;
use wgpu::VertexFormat;
use wgpu::include_wgsl;
use wgpu::util::BufferInitDescriptor;
use wgpu::util::DeviceExt;
use crate::state::Camera;
pub struct VoxelRenderer
{
chunk_instances_capacity: u32,
chunk_instances: Buffer,
chunk_pipeline: RenderPipeline,
}
impl VoxelRenderer
{
pub fn new(device: &Device, surface_format: TextureFormat) -> Self
{
let chunk_shader_module =
device.create_shader_module(include_wgsl!("../shaders/chunk.wgsl"));
let chunk_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render3D Mesh Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[PushConstantRange {
stages: ShaderStages::VERTEX,
range: 0..ChunkPushConstants::std430_size_static() as u32,
}],
});
let chunk_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("chunk_pipeline"),
layout: Some(&chunk_pipeline_layout),
vertex: wgpu::VertexState {
module: &chunk_shader_module,
entry_point: Some("vertex_main"),
buffers: &[VertexBufferLayout {
array_stride: size_of::<i32>() as u64 * 3,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[VertexAttribute {
format: VertexFormat::Sint32x3,
offset: 0,
shader_location: 0,
}],
}],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &chunk_shader_module,
entry_point: Some("fragment_main"),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
Self {
chunk_instances_capacity: 1,
chunk_instances: device.create_buffer(&BufferDescriptor {
label: Some("chunk_instances_buffer"),
size: size_of::<ChunkInstance>() as u64,
usage: BufferUsages::VERTEX,
mapped_at_creation: false,
}),
chunk_pipeline,
}
}
pub fn set_chunks(&mut self, device: &Device, chunks: &[cgmath::Vector3<i32>])
{
self.chunk_instances = device.create_buffer_init(&BufferInitDescriptor {
label: Some("chunk_instances_buffer"),
contents: unsafe {
std::slice::from_raw_parts(
chunks.as_ptr() as *const u8,
std::mem::size_of_val(chunks),
)
},
usage: BufferUsages::VERTEX,
});
self.chunk_instances_capacity = chunks.len() as u32;
}
pub fn render(&mut self, render_pass: &mut RenderPass, camera: &Camera)
{
render_pass.set_pipeline(&self.chunk_pipeline);
//renderpass.set_vertex_buffer(0, self.positions_buffer.slice(..));
render_pass.set_push_constants(
ShaderStages::VERTEX,
0,
ChunkPushConstants {
view_projection: camera.view_proj(),
transform: cgmath::Matrix4::identity(),
eye_position: camera.eye.to_vec(),
}
.as_std430()
.as_bytes(),
);
render_pass.set_vertex_buffer(0, self.chunk_instances.slice(..));
render_pass.draw(0..(6 * 2 * 3), 0..(self.chunk_instances_capacity));
}
}
struct ChunkInstance
{
location: cgmath::Vector3<i32>,
}
#[derive(crevice::std430::AsStd430)]
pub struct ChunkPushConstants
{
view_projection: cgmath::Matrix4<f32>,
transform: cgmath::Matrix4<f32>,
eye_position: cgmath::Vector3<f32>,
}