<p>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.</p>

<p>Edit:
Taking into consideration the updated formulation, here's my suggestion for a rather general algorithm with different attenuation coefficients <span class="math-container">$\kappa_i$</span> 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 <span class="math-container">$\kappa$</span> for each pixel (initialize to <span class="math-container">$0$</span>), as well as a distance time stamp <span class="math-container">$\delta$</span> (initialize to the the <span class="math-container">$z$</span> of the far plane, which I will denote <span class="math-container">$\max$</span>), and a "color" variable <span class="math-container">$c$</span> (initialize to <span class="math-container">$0$</span>). Whenever you draw a pixel:</p>

<ul>
<li>if <span class="math-container">$\delta==\max$</span> and you drew a front face of volume <span class="math-container">$i$</span> then:
<span class="math-container">$$d = \delta-pixelDepth$$</span>
<span class="math-container">$$c = attenuate(c,\kappa_i,d)$$</span></li>
</ul>

<p>Where <span class="math-container">$\kappa_i$</span> is the attenuation of the currently drawn pixel (you can simply draw the volumes in the "color" of their attenuation and use that).</p>

<ul>
<li>if <span class="math-container">$\delta&lt;\max$</span> and you drew a back face of volume <span class="math-container">$i$</span> then:</li>
</ul>

<p><span class="math-container">$$d = \delta - pixelDepth$$</span>
<span class="math-container">$$\delta = pixelDepth$$</span>
<span class="math-container">$$c = attenuate(c,\kappa, d)$$</span>
<span class="math-container">$$\kappa = \kappa + \kappa_i$$</span></p>

<ul>
<li><p>if <span class="math-container">$\delta&lt;\max$</span> and you drew a front face of volume <span class="math-container">$i$</span> then:
<span class="math-container">$$d = \delta -pixelDepth$$</span>
<span class="math-container">$$\delta = pixelDepth$$</span>
<span class="math-container">$$c = attenuate(c,\kappa, d)$$</span>
<span class="math-container">$$\kappa = \kappa - \kappa_i$$</span></p></li>
<li><p>after you've drawn all pixels if <span class="math-container">$\kappa &gt; 0$</span>:
<span class="math-container">$$c = attenuate(c,\kappa,\delta)$$</span></p></li>
</ul>

<p>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 <span class="math-container">$\kappa$</span>, then you have to modify the end case too, to check <span class="math-container">$&gt;const$</span>, or just not have such large volumes).
Note that the <span class="math-container">$\delta$</span> 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 <span class="math-container">$attenuate$</span> 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 <span class="math-container">$\delta$</span> based on what you have in the depth buffer for your solid geometry).</p>

CC BY-SA 4.0
2019-04-03T14:38:44.953Z
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
CC BY-SA 4.0
2019-04-03T14:44:26.900Z
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: https://www.slideshare.net/DICEStudio/physically-based-and-unified-volumetric-rendering-in-frostbite
lightxbulb
CC BY-SA 4.0
2019-04-04T08:25:53.150Z
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
CC BY-SA 4.0
2019-04-04T13:18:42.530Z
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
CC BY-SA 4.0
2019-04-04T13:45:10.540Z
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
CC BY-SA 4.0
2019-04-04T16:18:09.760Z
@DanHulme See the update. I have no idea how to deal with the ordering except for painters' algorithm, but that doesn't always work. You can of course encode distances for the different volumes in different bits, then you don't necessarily need an ordering, but that seems a bit more complex.
lightxbulb
CC BY-SA 4.0
2019-04-03T12:30:42.633Z
2019-04-04T16:20:06.560Z
2019-04-04T16:20:06.560Z
lightxbulb
<p>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.</p>

<p>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.</p>

<p>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.</p>

<p>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?</p>

<real-time><transparency>
How to draw depth-based volume in real-time
Dan Hulme
# How to draw depth-based volume in real-time

(Asked by: username_0 on 2019-04-03)

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?

**Tags:** real-time, transparency


---

## 1 answers

### Answer by username_1 on 2019-04-03. Score: 1

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).

> **Comments:**
> **username_0**: 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.  
> **username_1**: 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: https://www.slideshare.net/DICEStudio/physically-based-and-unified-volumetric-rendering-in-frostbite  
> **username_0**: 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.  
> **username_1**: 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.  
> **username_0**: 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.  
> **username_1**: @username_0 See the update. I have no idea how to deal with the ordering except for painters' algorithm, but that doesn't always work. You can of course encode distances for the different volumes in different bits, then you don't necessarily need an ordering, but that seems a bit more complex.  

---

