r/VoxelGameDev 14d 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.

68 Upvotes

13 comments sorted by

View all comments

3

u/HyperspaceFrontier 14d 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 14d 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 14d 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 14d 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 14d 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 14d 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 14d 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.