With exponential height fog

Without fog

In this blog post I will derive the traditional linear interpolation (lerp) between the surface colour and fog colour using transmittance, and also derive the transmittance for exponential height fog. There's nothing novel here, I just wanted it written down for myself as an easily accessible reference.

Deriving the linear interpolation

Let's start with a rendering equation which includes volumetric scattering. This gives the radiance at some point \(\mathbf{o}\), in direction \(\omega\), at which there is a surface at distance \(l\) with reflected radiance \(L_s\) in the \(-\omega\) direction: $$ L(\mathbf{o}, \omega) = \int_{t=0}^{t=l} T(\mathbf{o}, \mathbf{x}(t)) (\int_{\omega_i \in S} L_i(\mathbf{x}(t), \omega_i) p(\omega_i, \omega) d\omega_i) \sigma_s(\mathbf{x}(t)) dt + T(\mathbf{o}, \mathbf{x}(l)) L_s(x(l), -\omega) $$ Let us suppose that the incoming radiance (for inscattering), \(L_i\) is independent of position and direction, e.g. $$ L_i(\mathbf{x}(t), \omega_i) = L_i $$ Then the inner integral becomes: $$ \int_{\omega_i \in S} L_i(\mathbf{x}(t), \omega_i) p(\omega_i, \omega) d\omega_i = L_i \int_{\omega_i \in S} p(\omega_i, \omega) d\omega_i = L_i $$ As the phase function integrates to 1 over the sphere of directions. So the original integral becomes: $$ L(\mathbf{o}, \omega) = \int_{t=0}^{t=l} T(\mathbf{o}, \mathbf{x}(t)) L_i \sigma_s(\mathbf{x}(t)) dt + T(\mathbf{o}, \mathbf{x}(l)) L_s(x(l), -\omega) \\ = L_i \int_{t=0}^{t=l} T(\mathbf{o}, \mathbf{x}(t)) \sigma_s(\mathbf{x}(t)) dt + T(\mathbf{o}, \mathbf{x}(l)) L_s(x(l), -\omega) $$ Let's rewrite/redefine the transmittance and scattering coefficients as functions of distance along this particular ray: $$ L(\mathbf{o}, \omega) = L_i \int_{t=0}^{t=l} T(t) \sigma_s(t) dt + T(l) L_s $$ Now T itself is defined in terms of an integral: $$ T(t) = e^{-\int_{s=0}^{s=t} \sigma_t(s) ds } $$ Let us consider its derivative: $$ {d \over dt} T(t) = {d \over dt} e^{-\int_{s=0}^{s=t} \sigma_t(s) ds } \\ = {d \over dt} [-\int_{s=0}^{s=t} \sigma_t(s) ds ] e^{-\int_{s=0}^{s=t} \sigma_t(s) ds } \\ = -\sigma_t(t) e^{-\int_{s=0}^{s=t} \sigma_t(s) ds } = -\sigma_t(t) T(t) $$ so $$ -{d \over dt} T(t) = \sigma_t(t) T(t) $$ and suppose that \( {\sigma_s(t) \over \sigma_t(t)} \) is constant for all t. Then $$ {\sigma_s(t) \over \sigma_t(t)} (-{d \over dt} T(t)) = {d \over dt} [- T(t) {\sigma_s(t) \over \sigma_t(t)}] = {\sigma_s(t) \over \sigma_t(t)} \sigma_t(t) T(t) = \sigma_s(t) T(t) $$ In other words, \( -T(t) {\sigma_s(t) \over \sigma_t(t)} \) is an antiderivative of \(\sigma_s(t) T(t) \). So going back to our rendering equation: $$ L(\mathbf{o}, \omega) = L_i \int_{t=0}^{t=l} T(t) \sigma_s(t) dt + T(l)L_s \\ = L_i [-T(t) {\sigma_s(t) \over \sigma_t(t)}]_{t=0}^{t=l} + T(l)L_s \\ = L_i [-T(l) {\sigma_s(l) \over \sigma_t(l)} - (-T(0) {\sigma_s(0) \over \sigma_t(0)})] + T(l)L_s \\ = L_i [-T(l) {\sigma_s(l) \over \sigma_t(l)} + (1) {\sigma_s(0) \over \sigma_t(0)}] + T(l)L_s \\ = L_i {\sigma_s(l) \over \sigma_t(l)} (1 - T(l)) + L_s T(l) \\ $$ So this is a lerp between \(L_i { \sigma_s \over \sigma_t } \): the 'fog colour', and the surface colour, where the blend factor is the transmittance to the surface.

Note that this holds for spatially varying scattering and extinction coefficients (i.e. it holds for heterogeneous participating media), as long as the ratio between the scattering and extinction coefficients is constant.

Exponential height fog

Suppose our extinction coefficient is $$ \sigma_t(\mathbf{x}) = A e^{-B z(\mathbf{x}) } $$ i.e. it is an exponential function of the height (z coord) at position \(\mathbf{x}\), where the exponential is parameterised by two constants \(A\) and \(B\).

The transmittance in terms of length along ray (\(t\) is $$ T(t) = e^ {-\int_{s=0}^{s=t} \sigma_t(\mathbf{x(s)}) ds} $$ or, if we introduce the optical depth $$ \tau = \int_{s=0}^{s=t} \sigma_t(\mathbf{x(s)}) ds $$ then $$ T(t) = e^{-\tau} $$ So, let's work out what \(\tau\) is for our exponential height fog: $$ \tau = \int_{s=0}^{s=t} \sigma_t(\mathbf{x(s)}) ds \\ = \int_{s=0}^{s=t} A e^{-B z(\mathbf{x(s)}) } ds $$ If we use the ray equation for \(\mathbf{x(s)}\): $$ \mathbf{x(s)} = \mathbf{o} + \mathbf{d} s $$ Then $$ z(\mathbf{x(s)}) = z(\mathbf{o} + \mathbf{d} s) = z(\mathbf{o}) + z(\mathbf{d}) s = o_z + d_z s $$ So we have, for the case when \(d_z \neq 0 \): $$ \tau = \int_{s=0}^{s=t} A e^{-B (o_z + d_z s) } ds \\ = [{1 \over {d \over ds} [-B (o_z + d_z s) ]} A e^{-B (o_z + d_z s)} ]_{s=0}^{s=t} \\ = [{1 \over -B d_z} A e^{-B (o_z + d_z s)} ]_{s=0}^{s=t} \\ = [{-A \over B d_z} e^{-B (o_z + d_z s)} ]_{s=0}^{s=t} \\ = [{-A \over B d_z} e^{-B (o_z + d_z t)} - {-A \over B d_z} e^{-B (o_z + d_z (0))}] \\ = [{-A \over B d_z} e^{-B (o_z + d_z t)} - {-A \over B d_z} e^{-B o_z }] \\ = {-A \over B d_z}[ e^{-B (o_z + d_z t)} - e^{-B o_z }] \\ \tau = {A \over B d_z}[ e^{-B o_z } - e^{-B (o_z + d_z t)}] \\ $$ So if we want to substitute the optical depth back in we get: $$ T(t) = e^{-\tau} \\ T(t) = e^{ {-A \over B d_z}[ e^{-B o_z } - e^{-B (o_z + d_z t)}] } $$ For the case where \(d_z = 0 \): $$ \tau = \int_{s=0}^{s=t} A e^{-B (o_z + (0) s) } ds \\ \tau = \int_{s=0}^{s=t} A e^{-B o_z } ds \\ \tau = A e^{-B o_z } \int_{s=0}^{s=t} ds \\ \tau = A e^{-B o_z } t $$ and so $$ \tau = \begin{cases} A e^{-B o_z } t & d_z = 0 \\ {A \over B d_z}[ e^{-B o_z } - e^{-B (o_z + d_z t)}] & d_z \neq 0 \end{cases} $$ So using the linear interpolation result from the first part of this blog post, we have everything we need to compute the fogged colour for exponential height fog.

Further Reading

Scratchapixel has a nice in-depth section on volume rendering.

Fog by Inigo Quilez : Although beware the applyFog function in the 'Non constant density' section has a mistake, it's missing an exponential applied to the optical depth.