When rendering a voxel grid as batched cubes via add_batched_meshes_simple with opacity < 1.0, the result shows incorrect transparency compositing: backfaces and geometry that is further from the camera are blended over geometry that is closer, and the artifact pattern changes discontinuously as the camera orbits.
I believe the cause is that transparency sorting in three.js happens at the object level, not the instance level. All cubes live in a single InstancedMesh / BatchedMesh, so the renderer performs one depth sort for the whole batch and then draws the instances in buffer order. For a correct back-to-front alpha blend, the instances themselves would need to be sorted per frame against the current camera position.
opacity: 1.0
opacity: 0.5
For Both: flat shading, no cast shadow, no receive shadow, side "front". Note the interior faces of the far side of the beam reading as if they were in front of the near side, and the stair-step regions that invert between the two views.
Reproduction
The effect is subtler here than in my real scene, but visible when orbiting — watch the boundary where the shell's far wall passes behind the near wall, and compare against the opaque control on the right.
import numpy as np
import viser
VOXEL_SIZE = 0.1
# Unit cube, counter-clockwise winding (all face normals point outward).
_h = VOXEL_SIZE / 2.0
CUBE_VERTICES = np.array(
[
[-_h, -_h, -_h],
[+_h, -_h, -_h],
[+_h, +_h, -_h],
[-_h, +_h, -_h],
[-_h, -_h, +_h],
[+_h, -_h, +_h],
[+_h, +_h, +_h],
[-_h, +_h, +_h],
],
dtype=np.float32,
)
CUBE_FACES = np.array(
[
[0, 2, 1],
[0, 3, 2], # -Z
[4, 5, 6],
[4, 6, 7], # +Z
[0, 1, 5],
[0, 5, 4], # -Y
[3, 7, 6],
[3, 6, 2], # +Y
[0, 4, 7],
[0, 7, 3], # -X
[1, 2, 6],
[1, 6, 5], # +X
],
dtype=np.uint32,
)
def hollow_sphere_voxels(res: int = 24, r_inner: float = 6.0, r_outer: float = 11.0):
"""Voxelized spherical shell. The hollow core exposes interior faces."""
idx = np.stack(np.meshgrid(*[np.arange(res)] * 3, indexing="ij"), axis=-1)
center = (res - 1) / 2.0
radius = np.linalg.norm(idx - center, axis=-1)
mask = (radius > r_inner) & (radius < r_outer)
positions = (idx[mask] - center).astype(np.float32) * VOXEL_SIZE
colors = ((idx[mask] / (res - 1)) * 255).astype(np.uint8)
return positions, colors
server = viser.ViserServer()
positions, colors = hollow_sphere_voxels() # 4704 voxels
wxyzs = np.tile(np.array([1.0, 0.0, 0.0, 0.0], np.float32), (len(positions), 1))
shared = dict(
vertices=CUBE_VERTICES,
faces=CUBE_FACES,
batched_wxyzs=wxyzs,
batched_colors=colors,
flat_shading=True,
cast_shadow=False,
receive_shadow=False,
side="front",
)
# Left: transparent. Artifacts appear here.
server.scene.add_batched_meshes_simple(
"/shell_transparent",
batched_positions=positions + np.array([-0.5, 0.0, 0.0], np.float32),
opacity=0.5,
**shared,
)
# Right: identical geometry, fully opaque. Renders correctly -- control case.
server.scene.add_batched_meshes_simple(
"/shell_solid",
batched_positions=positions + np.array([+0.5, 0.0, 0.0], np.float32),
opacity=1.0,
**shared,
)
server.sleep_forever()
Environment
viser version: 1.0.30
Python version: 3.11.15
OS: Ubuntu 24.04
Browser and version: Firefox 152.0.4 (on Windows)
GPU: NVIDIA RTX 4080
When rendering a voxel grid as batched cubes via
add_batched_meshes_simplewithopacity < 1.0, the result shows incorrect transparency compositing: backfaces and geometry that is further from the camera are blended over geometry that is closer, and the artifact pattern changes discontinuously as the camera orbits.I believe the cause is that transparency sorting in three.js happens at the object level, not the instance level. All cubes live in a single InstancedMesh / BatchedMesh, so the renderer performs one depth sort for the whole batch and then draws the instances in buffer order. For a correct back-to-front alpha blend, the instances themselves would need to be sorted per frame against the current camera position.
opacity: 1.0
opacity: 0.5
For Both: flat shading, no cast shadow, no receive shadow, side "front". Note the interior faces of the far side of the beam reading as if they were in front of the near side, and the stair-step regions that invert between the two views.
Reproduction
The effect is subtler here than in my real scene, but visible when orbiting — watch the boundary where the shell's far wall passes behind the near wall, and compare against the opaque control on the right.
Environment
viser version: 1.0.30
Python version: 3.11.15
OS: Ubuntu 24.04
Browser and version: Firefox 152.0.4 (on Windows)
GPU: NVIDIA RTX 4080