For each pixel, we have to evaluate the discrete VRI, \Lout = \displaystyle \sum_{i=0}^n C_i\ \alpha_i\ \left(\ \prod_{j=0}^{i-1}\ (1-\alpha_j)\ \right)
Let \ell(t) be the line from the eye through the pixel and let \ell(t_i) be the 3D position on that ray in the middle of the \ith interval, as shown below:
For i = n we'll let C_i\ \alpha_i = \Lin for the backlighting.
However, for i \lt n, we need to evaluate C_i and \alpha_i.
The step size, \Delta s, is a constant set by the user. A smaller \Delta s will give more accurate renderings, but will take more time.
Since \alpha_i = \tau_i\ \Delta s, we need only to compute \tau_i.
The CT is provided as a volume consisting of CT numbers in Hounsfield units, and is usually converted to a 3D texture of attenuation coefficients, \tau(x,y,z), at each of the discrete (x,y,z) points in the CT volume.
An accurate conversion would require a knowledge of the parameters of the CT (such as voltage), but the usual process is to just linearly transform the CT numbers into the range [0,1].
To determine \tau_i, we need to look up \tau( \ell(t_i) ) in the 3D texture.
Since \ell(t_i) is usually not at the integer coordinates of one of the texels, we ask the GPU to compute a tri-linear blend of the eight nearest texels. That's done in OpenGL with the usual
glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
and the texture unit is set up with
glActiveTexture( GL_TEXTURE0 + VOLUME_TEXTURE_UNIT ); // VOLUME_TEXTURE_UNIT = ID of texture unit glBindTexture( GL_TEXTURE_3D, volumeTextureID ); // volumeTextureID = ID of texture
So computing \alpha_i = \tau_i\ \Delta s is pretty straightforward.
We'll use the Phong illumination model to bounce light off of "surfaces" in the volume:
Then C_i = k_d\ N \cdot L + k_s\ (R \cdot V)^n
The direction, L, to the light and the direction, V, to the viewer are known. And n is the shininess constant.
Ways to determine k_d are discussed in the Transfer Functions lecture.
The surface normal, N, will be the defined using the gradient of the attenuation field, or \nabla \tau(x,y,z) = ( {\partial\: \tau \over \partial\: x}, {\partial\: \tau \over \partial\: y}, {\partial\: \tau \over \partial\: z} )
Since the gradient points in the direction of fastest increase of \tau, it will point in to a surface, since density increases when going from above a surface to below a surface.
Since the surface normal, N, should point out from a surface, we define N = - {\nabla \tau \over |\nabla \tau|}
Computation of the gradient is discussed in the Computing Gradients lecture.
For volume rendering, it's often sufficient to use only the diffuse term of the Phong model.