Nanite voxelization holes in the mesh

Hello,

In vanilla Unreal Engine 5.7.4.

When using “Voxelize” as “Shape Preservation” option for a Static Nanite mesh, holes show up when the mesh becomes small in screen (as soon as it turns into voxels).

This is specially problematic with Nanite Foliage, which require voxelization for leaves, so the trunk is also voxelized, and it is creating these holes in the tree trunk.

How to avoid these holes? (specially for Nanite Foliage tree trunks)

Thanks in advance! :smiley:

[Attachment Removed]

Steps to Reproduce

  • Open any level in UE 5.7.4.
  • Import a mesh similar to the one in the attached picture. In my case it is a cylinder with some displaced vertices. The displacement is important: a smooth cylinder will not trigger the issue.
  • In the mesh settings, turn on “Enable Nanite Support”, and also set “Shape Preservation” to “Voxelize”. Then click “Apply Changes”.
  • Drag the mesh into the viewport.
  • Now, start moving the camera away from the mesh. Notice how, at some point, when it is small enough in screen, it will switch to voxels, and holes will show up.
    [Attachment Removed]

Hello,

Holes in voxelized meshes are a known issue in certain cases like these and the current approach to address it is to use the “Separable” flag in Nanite settings. We find this to provide better representation of sparser meshes with larger solid sections like big trunks or large branches that can get holes in the distance.

Separable was an experiment to try and solve the holes that get generated in what otherwise should be solid surfaces. We don’t consider it the final solution and hope to have develop a better one in the future. Separable refers to a separable voxelization, i.e. one that maintains the property of a surface that separates space. There are different versions of it: 6-separable is thin and means that occupied voxels block paths following a chess rook, i.e. paths that are axis aligned. 26-separable is thicker and means occupied voxels block paths following a chess queen so diagonals as well. 26 because there are 26 neighbors if you include diagonals.

This separable flag is doing 6-separable voxelization. So if the original source would separate space as in block all rays through its local region then the voxels will too. It only really properly does that for ray count=1.

The problem is if the intention was to voxelize surfaces then it would be very easy. Voxelizing 6-sep is straightforward and there would be no holes. The problem is we don’t really want to voxelize surfaces. The whole point is to voxelize stuff that isn’t surface-like. Surfaces work well as meshes so best to keep them that way. It’s the aggregate bits that we want to turn into voxels because mesh doesn’t capture them well anymore. The problem is the decision of switching to voxels is done at the cluster granularity. Other surface stuff might be mixed in.

Voxelizing aggregates with traditional 6-sep voxelization makes it look denser than it should. We’ve seen much better looking results that preserve the look by randomly shooting rays through the volume of the voxel and recording the % that hit. That becomes the voxels opacity. That opacity gets thresholded to statically pick whether the binary opacity is 0 or 1. This process of opacity to binary is something that is very imperfect and a key area to improve, but what is there now is the best we’ve developed so far.

It doesn’t necessarily give best results for solid surfaces though. Only a small bit of a continuous surface could pass through a voxel and the % ray hits could be really low. Or more bizarrely, a surface that crosses right through the center and covers the entire cross section will only get 50% ray hits since many will travel along the plane instead of into it.

That’s why there can be holes in what should be solid surfaces and the reason why setting that flag helps to the point where it basically solves those cases. That was the intention of the flag. The problem is setting it makes the thing that voxels is intending to solve, preserving the appearance of the actual foliage, worse because they may look overly dense.

Using separable simply changes the logic in the builder for which voxels get filled in. It’s possible that more filled voxels becomes more disk data which you can see yourself in the static mesh editor, but shouldn’t have a large impact on performance.

[Attachment Removed]

There is already the concept of MATUSAGE_Voxels, so potentially a way already exists to designate some parts of the mesh to not be voxelized by using a material without the MATUSAGE_Voxels flag, however this may not be best practice - I’ll discuss with my colleagues who are more familiar with valid approaches here.

[Attachment Removed]

OK, I’ve confirmed we can only separate voxel/non-voxel parts by putting them in separate meshes currently. Currently, with voxelization enabled, the root cluster must be a single voxel cluster. To pick and choose per material or mesh section would require us to support both triangle and voxel simplification paths separately all the way down to the root, including supporting both voxel and triangle roots, which would likely add some complexity to the hierarchy and/or streaming, and other code that assumes a single root cluster and a root cluster group (mip level) with a single cluster.

This is all theoretically possible but not currently planned. We might want to still do things to prevent gaps in voxels before going with a solution like this. We’d likely have to add per-section voxelization flags, separate the triangles during simplification, and prevent mixing.

If you end up pursuing this and have success, please let us know.

[Attachment Removed]

Thank you very much for your answer!

I really appreciate all the details and context in case we might need to look into modifying the engine code or parameterize it further. Also, thanks for pointing out that this is a temporary flag but it is expected to find a better solution, and also for specifying the downsides of using Separable.

Specifically to my testing project. I have tested, and Separable does help a lot with the issue, nice!

I am experimenting a bit with Nanite Foliage. I am marking the tree asset as Voxelize, because I need that for trees leaves, but then the tree trunk is also forced to be voxelized. I wonder if I would be able to do an engine modification to be able to mark the tree trunk triangles somehow to forbid voxelization on them (in any way e.g. arbitrary vertex color channel). Then, in the voxelization code, when tracing rays, if a high % of hit triangles are marked as voxelization-forbidden, then you choose to use triangles. I don’t know if something similar to this would be possible, as I know specifically for my project assets where I want voxels and where I don’t, so I might be able to take advantage of this specificity with some custom engine modification.

Thanks in advance! :smiley:

[Attachment Removed]

Oah, this extra information is really useful. I’ll let you know if I ever pursue this engine modification.

Thank you very much.

[Attachment Removed]