Skip to content

Commit

Permalink
stitch terrain to avoid t junctions + no vertex buffer + bit more set…
Browse files Browse the repository at this point in the history
…tings
  • Loading branch information
Uriopass committed Dec 11, 2023
1 parent daf0eb3 commit 04e86bb
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 81 deletions.
2 changes: 1 addition & 1 deletion assets/shaders/background.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn frag(@location(0) in_pos: vec3<f32>, @builtin(position) position: vec4<f32>)
3.40282347E+38,
);

//color = textureSampleLevel(t_environment, s_environment, pos, 0.0).rgb;
//var color: vec3<f32> = textureSampleLevel(t_environment, s_environment, pos, 0.0).rgb;

color = color + max(pos.z + 0.1, 0.0) * 5.0 * textureSample(t_starfield, s_starfield, vec2(longitude, pos.z)).rgb; // starfield
color = color + max(pos.z, 0.0) * 10000.0 * smoothstep(0.99993, 1.0, dot(fsun, pos)); // sun
Expand Down
44 changes: 36 additions & 8 deletions assets/shaders/terrain.vert.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ struct VertexOutput {
}

struct ChunkData {
lod: u32,
resolution: u32,
lod: u32, // 0 = most details, 1 = half details, 2 = quarter details, etc.
lod_pow2: f32, // 2^lod
resolution: u32, // number of vertices per side
distance_lod_cutoff: f32, // max distance at which to switch to the next lod to have smooth transitions
cell_size: f32,
inv_cell_size: f32,
cell_size: f32, // size of a cell in world space at lod0
inv_cell_size: f32, // 1 / cell_size
}

@group(0) @binding(0) var<uniform> global: Uniforms;
Expand Down Expand Up @@ -50,7 +51,33 @@ fn unpack_diffs(v: u32) -> vec2<f32> {
}

@vertex
fn vert(@builtin(vertex_index) vid: u32, @location(0) in_position: vec2<f32>, @location(1) in_off: vec2<f32>) -> VertexOutput {
fn vert(@builtin(vertex_index) vid: u32,
@location(0) in_off: vec2<f32>,
@location(1) stitch_dir_flags: u32, // 4 lowest bits are 1 if we need to stitch in that direction. 0 = x+, 1 = y+, 2 = x-, 3 = y-
) -> VertexOutput {
let idx_x: u32 = vid % cdata.resolution;
let idx_y: u32 = vid / cdata.resolution;

var in_position: vec2<f32> = vec2(f32(idx_x), f32(idx_y)) * cdata.cell_size * cdata.lod_pow2;

let stitch_x_pos: bool = (stitch_dir_flags & 1u) != 0u;
let stitch_y_pos: bool = (stitch_dir_flags & 2u) != 0u;
let stitch_x_neg: bool = (stitch_dir_flags & 4u) != 0u;
let stitch_y_neg: bool = (stitch_dir_flags & 8u) != 0u;

if (stitch_x_pos && idx_x == cdata.resolution - 1u && (idx_y % 2u) == 1u) {
in_position = in_position - vec2<f32>(0.0, cdata.cell_size * cdata.lod_pow2);
}
if (stitch_x_neg && idx_x == 0u && (idx_y % 2u) == 1u) {
in_position = in_position - vec2<f32>(0.0, cdata.cell_size * cdata.lod_pow2);
}
if (stitch_y_pos && (vid >= (cdata.resolution * (cdata.resolution - 1u))) && ((idx_x) % 2u) == 1u) {
in_position = in_position - vec2<f32>(cdata.cell_size * cdata.lod_pow2, 0.0);
}
if (stitch_y_neg && (vid < cdata.resolution) && ((idx_x) % 2u) == 1u) {
in_position = in_position - vec2<f32>(cdata.cell_size * cdata.lod_pow2, 0.0);
}

let tpos: vec2<i32> = vec2<i32>((in_position + in_off) * cdata.inv_cell_size);

let texLoad: vec2<u32> = textureLoad(t_terraindata, tpos, 0).rg;
Expand All @@ -60,8 +87,8 @@ fn vert(@builtin(vertex_index) vid: u32, @location(0) in_position: vec2<f32>, @l

let step: i32 = i32(pow(2.0, f32(cdata.lod)));

let zf_off: vec2<i32> = vec2( step * (i32(vid % cdata.resolution) % 2),
step * (i32(vid / cdata.resolution) % 2));
let zf_off: vec2<i32> = vec2( step * (i32(idx_x) % 2),
step * (i32(idx_y) % 2));

let world_pos: vec3<f32> = vec3(in_position + in_off, height);

Expand All @@ -73,7 +100,8 @@ fn vert(@builtin(vertex_index) vid: u32, @location(0) in_position: vec2<f32>, @l
let position: vec4<f32> = global.u_view_proj * vec4(world_pos, 1.0);

#ifdef DEBUG
var debug = f32(cdata.lod);
var debug = 0.0;
debug = f32(cdata.lod);

if(height >= MAX_HEIGHT) {
debug = diffs.x;
Expand Down
114 changes: 66 additions & 48 deletions engine/src/drawables/terrain.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
bg_layout_litmesh, pbuffer::PBuffer, CompiledModule, Drawable, FrameContext, GfxContext,
IndexType, PipelineBuilder, RenderParams, TerrainVertex, Texture, Uniform, TL,
IndexType, PipelineBuilder, RenderParams, Texture, Uniform, TL,
};
use geom::{vec2, vec3, Camera, InfiniteFrustrum, Intersect3, Matrix4, Vec2, AABB3};
use std::sync::Arc;
Expand All @@ -23,24 +23,22 @@ pub struct TerrainRender<const CSIZE: usize, const CRESOLUTION: usize> {
terrain_tex: Arc<Texture>,
#[allow(unused)]
grass_tex: Arc<Texture>, // kept alive
vertices: [PBuffer; LOD],
indices: [(PBuffer, u32); LOD],
instances: [(PBuffer, u32); LOD],
bgs: [Arc<wgpu::BindGroup>; LOD],
bgs: Arc<[wgpu::BindGroup; LOD]>,
w: u32,
h: u32,
}

pub struct TerrainPrepared {
terrainbgs: [Arc<wgpu::BindGroup>; LOD],
vertices: [PBuffer; LOD],
terrainbgs: Arc<[wgpu::BindGroup; LOD]>,
indices: [(PBuffer, u32); LOD],
instances: [(PBuffer, u32); LOD],
}

impl<const CSIZE: usize, const CRESOLUTION: usize> TerrainRender<CSIZE, CRESOLUTION> {
pub fn new(gfx: &mut GfxContext, w: u32, h: u32, grass: Arc<Texture>) -> Self {
let (indices, vertices) = Self::generate_indices_mesh(gfx);
let indices = Self::generate_indices_mesh(gfx);
let mut tex = Texture::create_fbo(
&gfx.device,
(w * CRESOLUTION as u32 + 1, h * CRESOLUTION as u32 + 1),
Expand All @@ -60,10 +58,12 @@ impl<const CSIZE: usize, const CRESOLUTION: usize> TerrainRender<CSIZE, CRESOLUT

let mut bgs = vec![];
for lod in 0..LOD {
let scale = 1 << lod as u32;
let uni = Uniform::new(
TerrainChunkData {
lod: lod as u32,
resolution: 1 + CRESOLUTION as u32 / (1 << lod as u32),
lod_pow2: scale as f32,
resolution: 1 + CRESOLUTION as u32 / scale,
distance_lod_cutoff: 2.0f32.powf(1.0 + LOD_MIN_DIST_LOG2 + lod as f32)
- std::f32::consts::SQRT_2 * CSIZE as f32,
cell_size: CSIZE as f32 / CRESOLUTION as f32,
Expand All @@ -76,7 +76,7 @@ impl<const CSIZE: usize, const CRESOLUTION: usize> TerrainRender<CSIZE, CRESOLUT
let mut bg_entries = Vec::with_capacity(3);
bg_entries.extend(Texture::multi_bindgroup_entries(0, texs));
bg_entries.push(uni.bindgroup_entry(4));
bgs.push(Arc::new(
bgs.push(
gfx.device.create_bind_group(&BindGroupDescriptor {
layout: &gfx
.get_pipeline(TerrainPipeline {
Expand All @@ -87,16 +87,15 @@ impl<const CSIZE: usize, const CRESOLUTION: usize> TerrainRender<CSIZE, CRESOLUT
entries: &bg_entries,
label: Some("terrain bindgroup"),
}),
));
);
}

defer!(log::info!("finished init of terrain render"));
Self {
bgs: collect_arrlod(bgs),
bgs: Arc::new(collect_arrlod(bgs)),
terrain_tex: Arc::new(tex),
grass_tex: grass,
indices,
vertices,
w,
h,
instances: collect_arrlod((0..LOD).map(|_| (PBuffer::new(BufferUsages::VERTEX), 0))),
Expand Down Expand Up @@ -217,6 +216,11 @@ impl<const CSIZE: usize, const CRESOLUTION: usize> TerrainRender<CSIZE, CRESOLUT
let eye = cam.eye();

let mut instances = vec![Vec::<TerrainInstance>::new(); LOD];

// We calculate lod in 2 passes to be able to generate the stitches
// special: lod 0 = dont render, stored as 1 + lod
let mut assigned_lod: Vec<u8> = vec![0; (self.h * self.w) as usize];

for y in 0..self.h {
for x in 0..self.w {
let chunk_corner = vec2(x as f32, y as f32) * CSIZE as f32;
Expand All @@ -233,8 +237,34 @@ impl<const CSIZE: usize, const CRESOLUTION: usize> TerrainRender<CSIZE, CRESOLUT
(eye.distance(chunk_center.z0()).log2() - LOD_MIN_DIST_LOG2).max(0.0) as usize;
let lod = lod.min(LOD - 1);

instances[lod].push(TerrainInstance {
offset: chunk_corner,
assigned_lod[(y * self.w + x) as usize] = 1 + lod as u8;
}
}

// generate the instances and the stitches thanks to the LOD data
// if neighbor lod > our lod, we need to stitch
// lod 0 means dont render
let h = self.h as usize;
let w = self.w as usize;
for y in 0..h {
for x in 0..w {
let idx = y * w + x;
let lod = assigned_lod[idx];
if lod == 0 {
continue;
}

let stitch_right = (x + 1 != w) && (assigned_lod[idx + 1] > lod);
let stitch_left = (x != 0) && (assigned_lod[idx - 1] > lod);
let stitch_up = (y + 1 != h) && (assigned_lod[idx + w] > lod);
let stitch_down = (y != 0) && (assigned_lod[idx - w] > lod);

instances[lod as usize - 1].push(TerrainInstance {
offset: vec2(x as f32, y as f32) * CSIZE as f32,
stitch_dir_flags: (stitch_right as u32)
| (stitch_up as u32) << 1
| (stitch_left as u32) << 2
| (stitch_down as u32) << 3,
})
}
}
Expand All @@ -248,43 +278,34 @@ impl<const CSIZE: usize, const CRESOLUTION: usize> TerrainRender<CSIZE, CRESOLUT

fctx.objs.push(Box::new(TerrainPrepared {
terrainbgs: self.bgs.clone(),
vertices: self.vertices.clone(),
indices: self.indices.clone(),
instances: self.instances.clone(),
}));
}

fn generate_indices_mesh(gfx: &GfxContext) -> ([(PBuffer, u32); LOD], [PBuffer; LOD]) {
fn generate_indices_mesh(gfx: &GfxContext) -> [(PBuffer, u32); LOD] {
let mut indlod = vec![];
let mut vertlod = vec![];
let cell_size = (CSIZE / CRESOLUTION) as f32;

for lod in 0..LOD {
let scale = 1 << lod;
let resolution = CRESOLUTION / scale;

let mut indices: Vec<IndexType> = Vec::with_capacity(6 * resolution * resolution);
let mut vertices: Vec<TerrainVertex> =
Vec::with_capacity((resolution + 1) * (resolution + 1));

let resolution = resolution as IndexType;
let w = resolution + 1;
for y in 0..=resolution {
for x in 0..=resolution {
let pos = vec2(x as f32, y as f32) * cell_size * scale as f32;
vertices.push(TerrainVertex {
position: [pos.x, pos.y],
});

if x < resolution && y < resolution {
let idx = y * w + x;
indices.push(idx);
indices.push(idx + 1);
indices.push(idx + w + 1);

indices.push(idx);
indices.push(idx + w + 1);
indices.push(idx + w);
}

// iterate over the grid, adding two triangles for each cell
for y in 0..resolution {
for x in 0..resolution {
let idx = y * w + x;
indices.push(idx);
indices.push(idx + 1);
indices.push(idx + w + 1);

indices.push(idx);
indices.push(idx + w + 1);
indices.push(idx + w);
}
}

Expand All @@ -293,12 +314,9 @@ impl<const CSIZE: usize, const CRESOLUTION: usize> TerrainRender<CSIZE, CRESOLUT
let mut buf = PBuffer::new(BufferUsages::INDEX);
buf.write(gfx, bytemuck::cast_slice(&indices));
indlod.push((buf, l as u32));

let mut buf = PBuffer::new(BufferUsages::VERTEX);
buf.write(gfx, bytemuck::cast_slice(&vertices));
vertlod.push(buf);
}
(collect_arrlod(indlod), collect_arrlod(vertlod))

collect_arrlod(indlod)
}
}

Expand All @@ -312,21 +330,23 @@ struct TerrainPipeline {
#[repr(C)]
pub(crate) struct TerrainInstance {
pub offset: Vec2,
pub stitch_dir_flags: u32, // 4 lowest bits are 1 if we need to stitch in that direction. 0 = x+, 1 = y+, 2 = x-, 3 = y-
}
u8slice_impl!(TerrainInstance);

#[derive(Copy, Clone)]
#[repr(C)]
pub struct TerrainChunkData {
lod: u32, // 0 = highest resolution, 1 = half resolution, etc.
lod: u32, // 0 = highest resolution, 1 = half resolution, etc.*
lod_pow2: f32, // 2^lod
resolution: u32, // width of the vertex grid
distance_lod_cutoff: f32, // max distance at which to switch to the next lod to have smooth transitions
cell_size: f32,
inv_cell_size: f32,
}
u8slice_impl!(TerrainChunkData);

const ATTRS: &[VertexAttribute] = &wgpu::vertex_attr_array![1 => Float32x2];
const ATTRS: &[VertexAttribute] = &wgpu::vertex_attr_array![0 => Float32x2, 1 => Uint32];

impl TerrainInstance {
fn desc() -> VertexBufferLayout<'static> {
Expand All @@ -347,11 +367,9 @@ impl TerrainPrepared {
}

let (ind, n_indices) = &self.indices[lod];
let vertices = &self.vertices[lod];

rp.set_bind_group(2, &self.terrainbgs[lod], &[]);
rp.set_vertex_buffer(0, vertices.slice().unwrap());
rp.set_vertex_buffer(1, instances.slice().unwrap());
rp.set_vertex_buffer(0, instances.slice().unwrap());
rp.set_index_buffer(ind.slice().unwrap(), IndexFormat::Uint32);
rp.draw_indexed(0..*n_indices, 0, 0..*n_instances);
}
Expand Down Expand Up @@ -387,14 +405,14 @@ impl PipelineBuilder for TerrainPipeline {
&terrainlayout,
&bg_layout_litmesh(&gfx.device),
],
&[TerrainVertex::desc(), TerrainInstance::desc()],
&[TerrainInstance::desc()],
vert,
frag,
);
}

gfx.depth_pipeline_bglayout(
&[TerrainVertex::desc(), TerrainInstance::desc()],
&[TerrainInstance::desc()],
vert,
None,
self.smap,
Expand Down
8 changes: 7 additions & 1 deletion engine/src/gfx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ pub struct GfxSettings {
pub fog: bool,
pub ssao: bool,
pub terrain_grid: bool,
pub shader_debug: bool,
pub pbr_enabled: bool,
}

impl Default for GfxSettings {
Expand All @@ -135,6 +137,8 @@ impl Default for GfxSettings {
fog: true,
ssao: true,
terrain_grid: true,
shader_debug: false,
pbr_enabled: true,
}
}
}
Expand Down Expand Up @@ -514,6 +518,8 @@ impl GfxContext {
self.set_define_flag("FOG", settings.fog);
self.set_define_flag("SSAO", settings.ssao);
self.set_define_flag("TERRAIN_GRID", settings.terrain_grid);
self.set_define_flag("DEBUG", settings.shader_debug);
self.set_define_flag("PBR_ENABLED", settings.pbr_enabled);

self.settings = Some(settings);
}
Expand Down Expand Up @@ -592,7 +598,7 @@ impl GfxContext {
let enc_dep_ext = &mut encs.depth_prepass;
let enc_smap_ext = &mut encs.smap;

{
if self.defines.contains_key("PBR_ENABLED") {
profiling::scope!("pbr prepass");
let mut pbr_enc = self
.device
Expand Down
Loading

0 comments on commit 04e86bb

Please sign in to comment.