bonsairobo / building-blocks

A voxel library for real-time applications.
MIT License
368 stars 30 forks source link

Multiresolution surface nets #26

Open Dimev opened 3 years ago

Dimev commented 3 years ago

I know this is planned, but I have an idea on how to do this that I like to share, although I haven't managed to implement it myself.

A cheap trick I found to work with marching cubes is skirts. It works by simply running marching squares on the faces of the chunk, and then making an extra quad if the cell the face belongs to is active, resulting in something that looks like this: image

It's not perfect, but it hides the edges quite well for what it is, especially if the skirts have the same normal as the face they are attached to.

My idea was to adapt this to surface nets as well I haven't managed to get this working however. Running 2d surface nets on the chunk faces doesn't work 100%, because it misses the chunk corners, and I haven't managed to get the regular 3d surface nets connected to the face either.

bonsairobo commented 3 years ago

I think having a way to generate skirts would be a good addition, even if we eventually have something that's more "correct."

I only worry that skirts would cause overlapping meshes and Z fighting. Apparently this can be remedied with shaders, but it's something to remember.

Dimev commented 3 years ago

I haven't had issues with overlapping/z-fighting with it, as it's only against the edge of the chunk face, and generates "downwards", so it doesn't generate a skirt into a space where the other mesh would be, but instead generates it downwards.

I have a demo program under this video if you want to look at it in detail: https://www.youtube.com/watch?v=Wz-tX8SKgYo

bonsairobo commented 3 years ago

generates "downwards", so it doesn't generate a skirt into a space where the other mesh would be

I'm not sure what you mean by this.

Dimev commented 3 years ago

Sorry if I suck at explaining it. I think the demo can show it best if you go below the terrain

For marching cubes I generate the extra skirt faces on the chunk faces as well, that way they don't overlap and cause Z-fighting. Viewed from below: image

My idea with surface nets was to do roughly the same: generate an extra vertice on the face of the chunk, and connect the regular vertices to that.

This would make the mesh generated perfectly fit within the chunk, and make it touch the chunk faces, like so (although with proper triangle orientation and normals): image

Then from there it's possible to hide the last bit of lod cracks by generating an extra bit of mesh that extends "downwards" (Towards where the cell is full)

Dimev commented 3 years ago

I also found this, which describes a way to do it in a bit more detail: https://www.reddit.com/r/VoxelGameDev/comments/5uzics/how_to_generate_continuous_levels_of_detail_for_a/ddz4t9o/?context=3

bonsairobo commented 3 years ago

The reddit link doesn't actually mention skirts at all. They're talking about continuous level of detail using mesh decimation.

If I understand you correctly, the way you intend to avoid z fighting is to make sure that skirts are always placed on the interior of the isosurface. If you can actually do that, I think it would look OK is most cases. But it seems difficult to ensure that property, and I bet it would also have edge cases where you can still see through the cracks.

Dimev commented 3 years ago

Yeah, here they don't mention skirts, but they do have this picture image

That's basically how I would ensure the vertices would be on the face of the chunk, then from there you can generate the skirts to sit on the face of the chunk and inside the isosurface

I'm sure there would be some edge cases where you can see the cracks, but for the marching cubes variant of skirts I haven't found them/seen them yet

bonsairobo commented 3 years ago

I want to a quick brain dump on the "multiresolution surface nets" issue in general...

Surface Nets Background

Surface Nets is essentially a form of dual contouring without hermite data. The canonical dual contouring algorithm for an octree will create a quad for each "minimal" edge being intersected by the isosurface. By minimal, I mean the edge can't be subdivided further in the octree. I've observed that doing this traversal on a hashed octree can be difficult to optimize (see the adf-experiment branch of this repo). This is partly because of the way a node must store redundant samples that all adjacent (parent, sibling, child) nodes also store. Maybe there is a better way to do this, but I haven't seen it.

In contrast, running surface nets on a regular grid is pretty fast. I've even seen multiple examples of "fast dual contouring" on the internet which tend to use a uniform grid. And uniform grids also lend themselves to being meshed on the GPU. So the octree ADF scheme seems like a tough sell for me.

LOD Stitching for Surface Nets

Here's my current idea for using chunks of at different resolutions.

surface_nets_lod_transition

In this picture we've got a chunk of LOD N and some adjacent chunks of LOD N+1. Note that every chunk actually has the same number of samples, but a chunk of LOD N+1 covers a larger region than a chunk of LOD N. (The entirety of LOD N+1 chunks is not shown). This is totally compatible with the existing ChunkMap structure.

The picture is trying to illustrate how we could generate a boundary mesh between chunks of different levels of detail. The core idea is to use stitching quad elements that look like this:

v-------v
| \   / |
|  \ /  |
v---v---v

with LOD N+1 vertices at the top and LOD N vertices at the bottom.

Here's a plan for how we could implement this:

Right now, we always copy voxels from a chunk and its neighbors while generating a chunk's mesh. We could change this so that we have two kinds of meshes: a "core" chunk mesh and a "boundary" mesh. While generating a core mesh would only require voxels from a single chunk, generating a boundary mesh would require voxels from multiple chunks (I think at most 9 for a single chunk face). Then we can have two kinds of boundary mesh:

The picture above highlights the LOD N to LOD N + 1 boundary mesh; it's the blue vertices and any vertices adjacent to a blue vertex.

I am a little less clear on how we would actually generate this mesh. I think it would look nearly identical to surface nets, except when you create a quad, you'd have to know if you need a regular quad or a transition quad.

Transvoxel

Another option that is probably easier is to just implement marching cubes and transvoxel. This is a proven algorithm for LOD. There a some downsides though:

Dimev commented 3 years ago

I also found this, which generates a lower detail version along with the high detail one, and then just blends between the meshes: https://github.com/PorkStudios/FarPlaneTwo/issues/49

I haven't actually done anything with getting data from other chunks, how hard is it to do that (assuming the octree does not have any chunk getting implemented yet)?

bonsairobo commented 3 years ago

I haven't actually done anything with getting data from other chunks, how hard is it to do that (assuming the octree does not have any chunk getting implemented yet)?

If all chunks are at the same LOD, then you can use copy_extent on the ChunkMap and it will figure out which chunks need to be accessed for you.

If you want to access chunks at different LODs, there is currently no "high level" operation for this.

bonsairobo commented 3 years ago

Another option that sounds a bit simpler than stitching...

If we had a vertex hierarchy, similar to an octree, where each vertex had up to 8 child vertices, then we could blend the vertices based on distance from the camera.

This does rely on specific shading techniques to be efficient though. It would also require more GPU memory on LOD boundaries, I think twice as much since each vertex also needs a parent to blend with.

But as a bonus, it removes the popping issue and gives smooth LOD transitions.

This is roughly what's done in Galaxia: https://dexyfex.com/2016/07/14/voxels-and-seamless-lod-transitions/

jedjoud10 commented 1 year ago

I actually have managed to implement multi-resolution surface nets in my custom engine, however the "skirts" methods does produce some very odd results (due to the clamping of the vertices to the edges of the volumes). However, when using derived normals from the density field, these artefacts disappear completely.

This is when using derived normals inside the fragment shader (which clearly allows you to see the "edge" between the chunks) image

And this is when you use derived normals (though with the use of the "flat" shader to make it lowpoly) image

Another potential problem that might arise with skirts is self shadowing issues in case it's 3D image though that might simply be due to my bad octree heuristic.