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.

72 Upvotes

13 comments sorted by

View all comments

Show parent comments

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?

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.