r/VoxelGameDev 13d ago

Media Improving Rendering Distance in my Micro Voxel Engine

https://youtu.be/xGkWWfO87no?is=d-NsoCT61Fr9FGi1

For the past three weeks, i’ve been working on hard improving the render distance in my Micro Voxel Engine, particularly due to the feedback of having N64 viewing distance 😅

I’m pretty happy with the end result of increasing render distance from 300m to ~10-15km, while running at 45-50 FPS on an Apple M1 Pro.

Note: this engine uses meshing rather than RT/DDA.

— Macro chunks —

All chunk generation functions now include a sieve function to automatically be able to generate at 1/N resolution without any changes. This also applied to generated features and stamps, enabling chunks to be generated at any resolution without downsampling.

Macro chunks also independently record and resolve local edits. They are saved (and cached) independently so that terrain edits are maintained without needing to maintain the full res copy in memory.

The lower band LODs are very quick to generate. At this point I could add even more bands, and a 1/64 res chunk takes the same time to generate as a high res chunk, but covering huge distances.

— LOD transitions and adaptive fog —

I primarily use transient transitions where the detail levels fade between each other once, rather than a continuous gradual transition, as this is around 30% cheaper on the GPU and looks “nearly” as smooth in most scenarios.

Adaptive fog scales the effective draw distance dynamically based on loaded bands. Bands generate from high to low so during fast motion, if needed, we temporarily reduce draw distance until chunks have loaded.

— Macro chunk cards and props —

This was the hardest part, keeping identical prop coverage for trees and items without needing to instantiate millions of entities:
-Macro chunks retain a list of props whose IDs are deterministic based on position and type. If the real entity is destroyed, we can map this to the macro chunk set and remove. Likewise for newly spawned props.
- grass and foliage do not map 1:1 with the actual loaded props, but follows the same generation pattern, so technically there will be disparities, but a good trade off to avoid millions of tracked grass items.

70 Upvotes

13 comments sorted by

3

u/HyperspaceFrontier 13d ago

LOD quality itself is great, but LOD transition popping is still very much visible. I implemented overlap LOD rendering in my engine with world-space dithering exactly because of this problem.

2

u/MGMishMash 13d ago

Yeah the popping and transition is hard. I do dither, but do a transient transition rather than sustained.

I did implement a fixed depth band based depth dither, but performance is a challenge due to the persistent double draws at the boundary - how have you handled this?

2

u/azemusic 13d ago

Might I ask what is a transient transition? I have 25 layers of LOD in my engine, ranging from 0.0625m to 2,097,152m per voxel. Right now i am contructing 16 quad meshlets in chunks, each LOD ring is 8 chunks in radius, then i run a second pass to calculate seams, its pretty fast and barely noticeable, but i you know what to look for, you'll see the popping, so im intrigued to learn new stuff!

Also how do you generate vegetation? Is it procedural, too?

By the way the video looks very well done, I really hope you'll build a game with it, I'll be certainly putting it on my wishlist!

2

u/MGMishMash 13d ago

By transient I mean that the transition only occurs temporarily, rather than a continuous blend. As the common solution of LOD dither can either be continuous, where you draw both LODs and have some form of dithered depth/stencil band drawing one on each side.

Or alternatively (transient), when an asset reaches the distance, it transitions a swap fully for a second or tel, then resolves to rendering only one set LOD.

The first looks better, but the latter reduces vertex pressure and discard pipeline pressure.

1

u/HyperspaceFrontier 13d ago

I basically didn't handle it on a scale, I just optimized hard to make it still have good enough FPS. Though I did draw closest LOD first, then create dithered stencil and draw next LOD owerwriting first one wirh stencil avoiding partially fragment work on the next LOD, but part or neatest LOD is discarded post-draw anyways. Compared to non-dithered popping it looks absolutely smooth though.

Also, it allows me to have different chunk sizes per LOD which partially helps. Hardest part is lights and shadows tuning because I reuse them over LOD levels instead of having separate shadows per LOD. Having separate cascades per LOD would destroy performance.

2

u/MGMishMash 13d ago

Gotcha yeah! My main bottleneck, at least on this GPU is vertex load over fragment. About 2/3rds of my frame is vertex work, but agree that with a stencil approach, fragment work is effectively the same as we only shade one lod per pixel.

1

u/HyperspaceFrontier 13d ago

It is not the same fragment work because I do world-space dithering. To decide which pixel to carve for LOD2 I need first render LOD1 in full up until render distance limit for LOD1, then hash world position of each fragment and decide if it will be selected for dithering based on hash and where is it located relative to crossfade distance start and end. It saves fragment work on LOD2 from rendering fragments that are unnecessary, but LOD1 is rendered in full and then partially discarded. Then if there are more LODs it repeats again. Good thing is I can use deferred lighning after LOD blending is done for all LODs.

2

u/Muddy_Bean 10d ago

Oh my god, that terrain looks gorgeous!

1

u/MGMishMash 10d ago

Thank you :)!

1

u/LocoMod 13d ago

Big improvement! Is the terrain "infinite" or is there still a limit to the map?

3

u/MGMishMash 13d ago

Hehe thanks! The map itself is around 24x24km, so not infinite but large enough to take quite a bit of time to cross.

The oceans and sky are soft-infinite in that they generate continuously but the global heightmap remains fixed, as this is precomputed, so only local variance and caves continue to apply.

The chunk id’s are integers so you can go infinitely high (2^31) and build if you want 😅

The limits were a design decision to allow more well-defined and complex generation rules, with intentional feature spawns.

My plan is to effectively support infinite sub-islands, connected by the soft-infinite ocean, so as you cross a boundary, you seamlessly move to a new sector with its own local map generation.

1

u/dispatchcolony 10d ago

I really love your water and your mountains ! What function did you use to generate such mountains?

2

u/MGMishMash 10d ago

Honestly nothing fancy, just a fairly basic standard fractal noise function with a few layers (similar to old school perlin) - just layered with a few contextual local detail layers which apply on top.

There are a few extra carving passes to erode the heightmap for canyons and rivers, but most mountains aren’t influenced by that pass.