# How to draw depth-based volume in real-time

Normally when we talk about realistic transparent volumes, we care about refraction (and total internal reflection), and we care about scattering. But there's a much simpler case of volume transparency, where the opacity of the object depends on the distance that light travels through it.

I'm writing a real-time visualization with a forward-rendering pipeline (on WebGL) and I'd like to show transparent solids this way, without having to resort to path tracing. My solids are homogeneous and isotropic, and I'm not interested in scattering or refraction. I just want thin parts of the solid to be very transparent and thicker parts of the solid to be more opaque.

It seems like most real-time transparency algorithms (e.g. OIT) are mainly for getting surfaces/sheets right, instead of volumes, and are more concerned about correct stacking, refraction, and scattering.

It seems like making the colour of the object depend on the thickness of material behind each fragment should be easily achievable with something like an extra depth pass where I draw opaque objects that intersect transparent objects, and back faces of transparent objects, possibly with some extra correction where the notional ray for one pixel goes through a torus-like object more than once. Intuition tells me that this ought to be a standard algorithm, but I don't know of one. Does this algorithm already exist, or do I just need to get my pencil and paper out and write one?

The issue is that an extra depth pass won't cut it. You may need an arbitrary number of extra depth passes. Just imagine the volume between two sinusoidal surfaces, you would have infinitely many alternating z-intervals of volume/empty space as long as you're looking from a specific direction.

Edit: Taking into consideration the updated formulation, here's my suggestion for a rather general algorithm with different attenuation coefficients $$\kappa_i$$ for each volume. Note that there are non-trivial parts that are missing, also if you do not require different attenuation coefficients it can be optimized even further. The algorithm relies on you drawing the volumes pixels in reverse z-order (this is the non-trivial missing part). You draw your volumes in a separate pass. You keep an attenuation density accumulation variable $$\kappa$$ for each pixel (initialize to $$0$$), as well as a distance time stamp $$\delta$$ (initialize to the the $$z$$ of the far plane, which I will denote $$\max$$), and a "color" variable $$c$$ (initialize to $$0$$). Whenever you draw a pixel:

• if $$\delta==\max$$ and you drew a front face of volume $$i$$ then: $$d = \delta-pixelDepth$$ $$c = attenuate(c,\kappa_i,d)$$

Where $$\kappa_i$$ is the attenuation of the currently drawn pixel (you can simply draw the volumes in the "color" of their attenuation and use that).

• if $$\delta<\max$$ and you drew a back face of volume $$i$$ then:

$$d = \delta - pixelDepth$$ $$\delta = pixelDepth$$ $$c = attenuate(c,\kappa, d)$$ $$\kappa = \kappa + \kappa_i$$

• if $$\delta<\max$$ and you drew a front face of volume $$i$$ then: $$d = \delta -pixelDepth$$ $$\delta = pixelDepth$$ $$c = attenuate(c,\kappa, d)$$ $$\kappa = \kappa - \kappa_i$$

• after you've drawn all pixels if $$\kappa > 0$$: $$c = attenuate(c,\kappa,\delta)$$

Note that the 4th check handles the case where the camera is inside a volume (and it ends behind the camera), but the frustum is not entirely inside it. Similarly the first check handles the case where a volume is cutoff by the frustum. However, the case where the camera frustum is totally inside a volume is not handled (you can try to check this by verifying that the camera is inside a volume and then simply adding a constant to $$\kappa$$, then you have to modify the end case too, to check $$>const$$, or just not have such large volumes). Note that the $$\delta$$ part can be handled by the depth buffer. Obviously you can use a compute shader, or use additional buffers to perform all of this. The function $$attenuate$$ can be standard Beer-Lambert uniform attenuation or whatever else you want. Combining the volumes with the original scene happens by setting the "far plane" for each pixel to the closest drawn solid geometry depth (that is initialize $$\delta$$ based on what you have in the depth buffer for your solid geometry).

• I'm aware that it doesn't work in the general case without some other adjustments, such as splitting up transparent solids into convex bodies and potentially doing a pass for each. I'm happy to accept that limitation and it seems like even with such a limitation it would be a useful general-purpose algorithm. – Dan Hulme Apr 3 '19 at 14:38
• Sure if you know that you have only a front face and a back face between the camera and the far plane, then you can draw both and then simply compute the distance and use that for attenuation, but that's not a realistic scenario. Your best bet is to look into recent papers on volume rendering, here's something: slideshare.net/DICEStudio/… – lightxbulb Apr 3 '19 at 14:44
• As I said at the outset, most volume rendering is about scattering and participating media. That paper is about volume lighting and shadows, and real-time smoke and flame effects. That's not relevant to me at all. – Dan Hulme Apr 4 '19 at 8:25
• The "smoke" is a (possibly isotropic) heterogeneous density field, so I believe it is quite relevant to what you were looking for (namely: "My solids are heterogeneous and isotropic"). If it is not, please elaborate. – lightxbulb Apr 4 '19 at 13:18
• Oops, my solids are actually homogeneous and isotropic. Silly me. No wonder we were talking past each other a bit. I'll fix my question now. Sorry for the confusion. – Dan Hulme Apr 4 '19 at 13:45