Skip to content

Where the holes in generated meshes come from ​

By Oleg Sidorkin, CTO and Co-Founder of Cinevva

The wing root of a generated bird before and after the fix: a grey flap with bright green slivers on the left, a clean wing-to-body fold on the right
Same image, same generator, same settings. Left is what shipped until this week. Right is what ships now.

I generated a bird with TRELLIS.2, rigged it, and saw bright slivers under both wings when the animation played. The rigger was the first suspect. It had a separate problem, and the holes were already present in the unrigged mesh. We traced them through the generation pipeline and tested nine repairs before finding a local fill that closed the visible gaps without smoothing away the feather detail.

Step one: agree on what a hole is ​

Our mesh checker reported zero boundary edges, and a ray-parity test said the surface was closed. The slivers were still visible in the marked-up image. A mesh can be closed and still have tunnels. We used the Euler characteristic, V minus E plus F, to estimate the number of handles per shell. The bird's initial count was 69. Every wall in the export was doubled, so a slit in the outer wall connecting to the cavity behind it forms a handle. A boundary-edge check misses that kind of defect.

The first image analysis also looked in the wrong place. It searched dark pixels for holes, while the visible defects were bright slivers of the far wall's green texture showing through a grey fold. Checking the image established which pixels the analysis needed to follow.

Step two: find the stage that makes them ​

TRELLIS.2 builds a mesh in three steps. A decoder emits a raw surface at 1024 cubed resolution. A narrow-band dual contouring pass remeshes it. A decimator brings it down to a hundred thousand faces and the texture is baked. We dumped the geometry after each step and shot the same bundle of rays through the wing root each time.

The gaps are already present in the raw surface. It's a soup of open sheets: 266,242 boundary loops, most of them single missing quads, and 65,371 non-manifold edges. At the wing root a third of the test rays went straight through. The hole-fill that runs next adds only 144 faces out of five million. The remesher works on an unsigned distance field. With no inside or outside to work with, it wraps a thin offset shell around every sheet. That produces doubled walls about four voxels apart and turns each porous stripe into a fold with a slit in it. Decimation can make the result worse, with variation between runs: the same raw mesh decimated twice gave 69 and 107 tunnels.

The bird with a heat map of open-edge density: the neck collar, both wing roots, the hips and the tail feather tips glow, the body and head are clean
Density of open edges in the raw extraction. Concentrations appear at the neck collar, wing roots, hips and tail tips. The body and head are clean.

The concentrations around the wing roots and other junctions gave us specific areas to investigate.

The things that didn't work ​

We tried them roughly in order of how obvious they were.

attemptresult
resolution 1536 instead of 1024worse on three of five test models. More resolution resolves more thin contacts, each one a new junction
the built-in cleanup ladderincreased the non-manifold edge count
filling every small hole with MeshLib266k loops filled, wing root unchanged. The skin there was never emitted, so there's no loop to fill
a signed remesh from a winding numbershatters the mesh into thousands of shells. The raw output has no interior to sign
a wider contouring band, 2 and 3 voxelsfewer tunnels overall; the slit widens as small tunnels merge into larger ones
morphological closing of the voxel structuresolidifies the shell, the decoder then emits surface inside every interior voxel, 4x the faces, 969 tunnels
global voxel remesh of the final meshgenus 0 and the slit sealed; feather edges softened too much to use
biasing the decoder's crossing thresholdopen edges fell from 65k to 58k; the visible defect remained
completing missing neighbour voxelsaffected only 560 cases in the whole bird; the visible defect remained
Four columns: the original bird and three voxel-remeshed versions at increasing radius, wing root, ten times zoom, a cross-section slab, the whole bird and a view from below
Global closing. Column three seals the slit and keeps the toes apart. The feather edges and creases are visibly rounded.

Global closing sealed the wing root at a voxel size just above the gap, and it also rounded off the crisp feather edges. We rejected that result because of the lost detail. The wider contouring band had reduced the tunnel count too, while making the visible slit wider. Both attempts showed why we needed to compare the rendered bird alongside the topology measurements.

Reading the code ​

We then read the code that turns the decoder's output into a mesh. TRELLIS.2 uses a representation called O-Voxel. Each active voxel holds one dual vertex and three logits, one per grid edge leaving its corner, each meaning "the surface crosses this edge." A logit above zero emits a quad connecting the four voxels around that edge. If any of the four is missing, the quad is dropped silently. This extraction uses crossing flags without an inside/outside sign field.

We patched the decoder to dump those logits before the threshold, on the same bird, and counted.

There were 560 missing-neighbour cases out of 2.7 million voxels, and 8,600 logits near zero out of 8 million. The repair attempts targeting those cases had left the wing-root defect in place. We next checked the four edges around each grid face. A closed surface crosses a face's boundary an even number of times. On 126,542 faces, 2.3% of them, the crossing flags had odd parity, producing open or non-manifold edges. Some inconsistent flags had large logit margins: the lone flag on a count-one face sat at +10 while its neighbours sat at -4. The median cost to flip one flag and restore parity was 2.3 logits. Correcting these inconsistencies would require changing predictions well away from the threshold.

Three columns on a dark background: the exported bird, the decoder's raw output, and a sign-repaired version. At ten and thirty times zoom the wing junction is a shredded serrated stripe in both raw versions
The decoder's raw output at the wing root. Dark areas are see-through. The junction forms a serrated stripe.

We measured the stripe at the wing-root point used in the earlier comparisons. Within twelve voxels of it there are 2.1 times as many active voxels as a single flat sheet would need. On the chest, the ratio is 1.2. The wing's underside and the body are two skins running inside one voxel layer. The representation has one vertex per voxel, so it cannot hold both. During training, the ground truth for such a voxel is a least-squares average of both skins with "any crossing" flags. That can produce inconsistent targets, which helps explain the serrated output at the junction. The remesher wraps that output into a slab with tunnels.

The width of the stripe follows from the geometry: roughly one voxel divided by the tangent of the contact angle. A steep contact confines the overlap to a narrow crease. At shallow contacts, such as a wing lying against a body, overlapping feathers or toes against the ground, the stripe can extend ten or fifteen voxels. That gives an explanation for the concentrations in the porosity map.

We also tried inferring a per-corner inside/outside sign that best explains the flags, then re-deriving them. That attempt failed. The sign field only existed on the one-voxel shell the decoder emitted, and extending it off that shell introduced spurious geometry. Twelve rounds doubled the voxel count without repairing the junction.

Filling the contact region ​

Joining the wing to the body would replace the narrow gap with a crease. The global closing had already shown that this could seal the slit. To preserve the feather detail, we needed to restrict the fill to the region where the two skins run close together.

The detector counts each voxel's active neighbours within four voxels and uses the median count as a baseline for a single sheet. Where two sheets share the layer, the count is about double. We flag anything above 1.5 times the median, grow the flagged set by one voxel, and place a unit cube at each flagged voxel. The cubes fill the space between the skins so the contouring pass can produce one wall. The fill is confined to the flagged region.

A crop around the wing root contoured three ways: as is, with the two-sheet zone filled at radius three, and at radius four. Red dots mark the filled voxels along the seam. The serrated seam becomes a smooth crease
Proof of concept on a crop, before touching the pipeline. Red marks the filled voxels. The serrated seam becomes a crease and the feathers around it don't move.

We tested it on a crop first, with MeshLib standing in for the GPU contouring. Fifty-three shells and nineteen handles became two shells and five. Then we added it between the decoder and the export and ran the same bird through the production remesher and texture bake.

Four columns, textured, then plain geometry, then a cross-section: original, the same decoder output exported as is, and the two-sheet fill at radius three and four. The grey flap and green slivers vanish in the last two
Results from the production pipeline. Columns one and two are the same decoder output with and without a random decimation. Three and four have the fill.
tunnelsnon-manifold edgeswing-root flaphole on the back
as is79.5318presentpresent
fill, radius 384.597gonegone
fill, radius 47238gonegone

The fill removed the wing-root flap and a second visible hole at the same junction on the bird's back. In this test, the feathers and toes stayed separate, and the texture baked normally because the cubes sat inside the shell where the attribute volume already had data. The fill added six seconds to a four-minute job. It's on by default now, at radius four.

The whole bird from the front, the back, from below with texture, and from below as plain geometry, in four columns: original, as is, fill radius three, fill radius four
The whole bird. The white hole on the back in columns one and two is the same junction from behind. Columns three and four show the filled junction with the surrounding feather and toe detail preserved.

Remaining tunnels ​

The tunnel count barely moved. The remaining tunnels are at feather tips and toe tips, where a thin part ends and the doubled wall pinches shut. They are small and were not visible in these renders. The fill targets contacts between parts, where the visible holes occurred, and leaves the tip defects unresolved.

Addressing the inconsistent crossing flags would require an upstream change. A decoder that predicts per-corner signs would be parity-consistent by construction, as in the previous generation of this model. Changing the decoder's prediction target requires retraining. For the current generator, the contact geometry suggests that poses with limbs standing away from the body should reduce this source of porosity. Mesh checks also need to cover several kinds of defects: boundary edges, tunnels and visible gaps. In this investigation, the boundary-edge check missed the tunnels, and lower tunnel counts sometimes accompanied worse-looking geometry.

The instrumented decoder, stage dumps, parity analysis and local fill are now in our pipeline. We can use them to trace defects in future models and check whether the same repair applies.