You can (somewhat) do it thanks to the stencill buffer.
To create a hole in a box:
- Render a cylinder where you want the hole to be with
colorWrite = false so that it only updates the zbuffer
- Draw the box. There will be a hole in the plane where the cylinder has been drawn:
Note: to avoid z-fighting between the face of the box and the top of the cylinder, the cylinder should be a little longer so that it protrudes a bit at the top and bottom
- Draw the inside of the (uncapped) cylinder (cull front faces instead of back faces) with a material that disables z-testing (else the cylinder won’t show up because of step 1):
You can see it’s not ok, there’s an artifact because the cylinder is not clipped by the plane (because of z-test = false)
- To overcome the problem, after step 2 draw again the cylinder from step 1 but with
colorWrite = false and
depthWrite = false but with
z-testing = true. In effect, the hole in itself will be redrawn, which is pointless from a visual standpoint but is important for the stencil buffer: the stencil is enabled and a value (1) is written where the hole appears.
- Now we can do step 3 but this time by enabling the stencil buffer so that we only draw where the stencil = 1:
We can see the cylinder corresponding to the leftmost hole is visible through the second hole because this cylinder is drawn where stencil=1, and stencil=1 for both holes. To fix the problem, you can use a different stencil value for each hole (1 and 2 for eg) that way they won’t collide. In all generality, each hole should have its own stencil value, but we are limited to 255 different values… In the PG below I have simply used 2 values and I have alternated between 1 and 2 between each hole. It does fix some artifacts but not everything, notably if you look at the plane at grazing angles…
Last thing, to make it fast, use thin instances for the holes!
Here’s a PG with the 4500+ holes, without the fix for the artifact: https://playground.babylonjs.com/#V518NB#11
With the fix:
At grazing angles you can see artifacts because some cylinders leak into other cylinder holes:
We would need a 32 bit stencil in WebGL to have a unique value for each hole, but I don’t think it’s possible…