Real-time graphics hardware is rapidly becoming programmable, and has
recently incorporated the features needed for direct volume rendering.
Unfortunately ...
Real-Time Programmable Volume Rendering Ren Ng Bill Mark ∗ NVIDIA Corporation Stanford University David Ebert Purdue University
Abstract Real-time graphics hardware is rapidly becoming programmable, and has recently incorporated the features needed for direct volume rendering. Unfortunately, the standard interfaces to this hardware are at the assembly language level. When programming at this level, it is difficult re-use and modify existing implementations of volume rendering algorithms, and it is extremely tedious to experiment with the many possible tradeoffs between performance, visualization technique, and image quality. We address these problems with a programmable volume rendering pipeline that is implemented in a high-level shading language, and executes efficiently on commodity graphics hardware. We show how to write re-usable code modules for a variety of volume resampling, classification and shading algorithms, and demonstrate how these modules can be combined in mix-and-match fashion to build custom pipelines. We present results on current hardware.
1
Introduction
Commodity graphics hardware is evolving from a fixed function pipeline into a highly programmable processor, providing the flexibility to experiment with a wide variety of real-time algorithms. This flexibility is especially valuable for data-visualization applications such as volume rendering. For volume-rendering applications, experience has shown that the choice of an effective rendering algorithm and data exploration technique ∗ The
author was at Stanford University while this research was done.
is highly dependent on the particular application and even on the specific data set [14, 2]. Today there are numerous volume rendering techniques that use graphics hardware acceleration. These techniques usually depend on 3D texture mapping to resample the volumetric data into screen space [4], and per-pixel operations to compute a final color and opacity for each sample [17, 18, 11]. The most sophisticated algorithms are currently implemented on commodity graphics hardware, using dependent texturing and bumpmapping hardware for interactive classification and shading [8, 6, 1]. A very different approach uses hardware designed specially for volume rendering, such as the VolumePro architecture [13]. These systems can provide high performance for specific applications, but they provide only fixed-function rendering pipelines. They do not support the programmable operations needed for the techniques in this paper, and for this reason we focus on the commodity hardware approach. One of the major difficulties with the programmable approach is that the common interfaces to the hardware are still very low-level. Published implementations of volume rendering algorithms are at the level of assembly language [8, 6], which creates a serious barrier to modifying and re-using them. Such low-level programming also impedes experimentation into algorithmic trade-offs, which are essential for both optimization and scientific exploration. Higher level interfaces to the hardware are emerging, however. New compiler technology has evolved to enable the expression of real-time lighting calculations in high-level languages [15, 12]. Code written in these languages can be compiled to run efficiently on a range of graphics hardware. In this paper we use the Stanford real-time shading language to address implementation difficulties in real-time volume rendering. Our contribution is a coherent and general technique for implementing and re-using custom volume visualization algorithms. This technique uses a programmable volume rendering pipeline abstraction, where the core stages of resampling, classification and shading are expressed in highlevel code. We show how to implement specific algorithms as re-usable code modules, and how to combine these in mix-and-match fashion to create pipelines appropriate for specific applications. Section 2 details our programmable pipeline abstraction and explains where it fits into a working system. Section 3 presents specific examples of re-usable modules for each stage of the pipeline and complete pipelines composed of these modules. While the examples are primarily intended
2
Eye Space Position Resampling Parameters
RESAMPLE
(e.g. 3D textures)
Volume Data (e.g. density)
Classification Parameters
CLASSIFY
(e.g. transfer function tables)
Volume Data (e.g. gradient)
Material Properties (e.g. base color)
Shading Parameters
SHADE
(e.g. light and view vectors)
Opacity
Shaded Color Final RGBA Color
Figure 1: The three programmable stages of our pipeline. This pipeline conceptually operates on volume samples, which we generate as pixels in decreasing depth order. The pipe computes final colors and opacities, which are composited into the framebuffer to generate an image. to illustrate the features of the technique, they are practical algorithms that are immediately applicable to volume rendering. Section 4 reports resource utilization, performance and sample images on current hardware, for the pipelines built in Section 3. Finally, in Section 5 we discuss our experiences in using this technique, and how it applies to future hardware.
2
Programmable Pipeline Abstraction
Figure 1 illustrates the components of our pipeline. Before we discuss the pipeline itself, let us briefly place it in the context of a working system. We generate the volume samples that feed into the pipeline in the usual fashion, by drawing view-perpendicular polygons that slice through the volume from back to front [4]. For each sample, its eye-space position (with depth) enters the pipeline, and its final color and opacity exit. This RGBA value is composited into the framebuffer with the over operator to generate an image. We draw polygons and control compositing through OpenGL. Calls to the shading system API [15] are used to set auxiliary stage parameters (parameters entering from the left in Figure 1). The pipeline itself comprises three stages: resampling, classification
3
and shading. We chose to modularize the pipeline around these stages because they represent functional components that are conceptually independent. The resampling stage is simply a source of data; the data may be visualized in different ways. The classification stage defines which portions of the volume we want to see. Finally, shading adds shape and depth cues to help understand the geometric structure of the visual volume. This pipeline abstraction focuses attention on the critical inner loop of the rendering process. It is important to note that it is the programmability of the pipeline that is new, not its basic structure. The three stages depicted in Figure 1 were building block operations in the original volume rendering papers [5, 9], and they are present in both the VolumePro architecture and the hand-coded pipelines that have been demonstrated on commodity graphics hardware [6, 8]. In our pipeline, the resampling stage assembles the volumetric data for the current sample. It accepts as input the eye space position of the sample, as well as references to 3D texture objects that store the volume data. A typical resampling strategy is to transform the sample’s eye space position to texture space and perform a lookup in the 3D textures to interpolate the volumetric data. These data feed into the second stage, which classifies the current sample as a particular material type. A common algorithm for this stage is to apply a transfer function that maps the density of the sample to a base color and opacity. Such an algorithm might take a transfer function texture as a stage parameter. The material properties computed by classification feed into the shading stage, which computes the final color for the sample. These parameters typically include lighting and viewing directions, for example. A typical shading strategy for scalar fields is to use the gradient as a normal vector in computing a surface lighting model.
3
Implementing Custom Pipelines
Pipelines are implemented in shading language as C-like functions called shaders [15] that operates on each volume sample. Thus, each shader is a custom implementation of the pipeline illustrated in Figure 1. In Sections 3.1–3.3 we present code modules for two algorithmic alternatives at each pipeline stage (a total of 6 modules). These modules
4
were chosen to illustrate specific implementation trade-offs that are easily made in the language, such as the trade-off between computing values in the hardware and looking them up in textures. The code in Sections 3.1–3.3 implicitly assumes idealized hardware, with floating point precision and operators, and no hardware limits on the number of instructions or number of texture lookups. In spite of these assumptions, current hardware constraints can be accommodated with relatively minor code modifications, which we detail in Section 3.4. Finally, in Section 3.5 we combine the optimized modules in a shader for a complete pipeline.
3.1
Two Resampling Modules
These modules present a trade-off between pre-computation of volume data, which consumes more texture memory, and computing the data dynamically in the hardware, which consumes more instructions. We assume that we are visualizing a scalar density volume, and that the classification and shading stages take the density value, gradient vector and gradient magnitude as inputs. The function of the resampling modules is to generate these three pieces of data. 3.1.1
Pre-Computed Gradient Data
This module assumes that the normalized gradient vector and gradient magnitude are pre-computed before rendering begins. The gradient magnitude is stored with the density value in a single, 2-component texture, and the gradient vector is stored in a second, 3-component texture. These textures are referenced, respectively, by the dens gradmag tex and gradient tex variables: float4 uvw float4 dens_gradmag float density float gradient_mag float3 gradient
= = = = =
eyeToTex * P; texture3d(dens_gradmag_tex, uvw); dens_gradmag[0]; dens_gradmag[3]; rgb(texture3d(gradient_tex, uvw));
The transformation from eye to object space is given in the eyeToTex variable, which is a 4 × 4 matrix specified as a stage parameter. P is a built-in shading language variable for the eye space position of the current sample. The texture3d operator performs a 3D texture lookup and the rgb operator extracts the first three components of a 4-component vector.
5
3.1.2
Dynamic Gradient Estimation
This module computes the gradient dynamically by directly estimating the first derivative with a differential calculation: float3 ex = {1,0,0}; float3 ey = {0,1,0}; float3 ez = float4 uvwz = eyeToTex * P; float3 uvw = rgb(uvwz) / uvwz[3]; float dens = texture3d(density_tex, { uvw, 1 })[2]; float Nx = texture3d(density_tex, { uvw + epsilon * ex, float Ny = texture3d(density_tex, { uvw + epsilon * ey, float Nz = texture3d(density_tex, { uvw + epsilon * ez, float3 N = (1.0 / episilon) * (Nx * ex + Ny * ey + Nz * float gradmag = sqrt(dot(N,N)); float3 grad = N / gradmag;
{0,0,1};
1})[2] - dens; 1})[2] - dens; 1})[2] - dens; ez);
The epsilon variable contains a number small compared to the distance between voxels in texture space. As mentioned above, the two modules present a trade-off between graphics memory and pipeline computation. The second module uses twice as many texture lookups and more than a dozen additional shading operations compared to the first algorithm, but it eliminates the need to store large 3D textures with the gradient data.
3.2
Two Classification Modules
These modules present a trade-off between compute and memory bandwidth resources, as well as simplicity versus generality of control over the classification. We elaborate on these trade-offs below. For these examples we assume that the material classification is simply an RGBA value representing a base color and an opacity. 3.2.1
Isosurface Transfer Function
This module implements one of the original classification functions: Levoy’s isovalue contour surface transfer function [9]: float triangle(float x, float center, float width, float height) { float hdivw = height / width; float dx = dens - center; float y1 = (width - dx) * hdivw; float y2 = (dx + width) * hdivw; return select(dx >= 0, y1, y2); } float4 isosurface_classify(float density, float grad_mag, float isovalue, float falloff, float scale, float3 color) {
6
float opacity = triangle(density, isovalue, falloff / grad_mag, scale); return { color, opacity }; }
The select operator returns its second argument if the first argument evaluates to true, and its third argument otherwise. This classification visualizes the isosurface at the given isovalue, with the given falloff in opacity as density values vary from the isosurface. 3.2.2
2D Transfer Function
This classification generalizes the first to an arbitrary two dimensional transfer function, mapping from density and gradient magnitude to base color and opacity. Multi-dimensional transfer functions of this kind (and of higher dimension) are beginning to receive more attention in the literature [8]. float4 transfer_2d_classify(float4 dens_gradmag, texref transfer_map2d) { float4 uv = { dens_gradmag[3], dens_gradmag[0], 0, 1 }; return texture(transfer_map2d, uv); }
The transfer function is supplied in the transfer map2d variable, which references a 2D texture. The texture is indexed by gradient magnitude and density. The texture operation in this code is a dependent lookup, since the coordinates depend on values from texture lookups performed in the resampling step. This classification module is more general than the first, since isosurface visualization can be implemented with an appropriate transfer map2d texture. However, the first is much simpler to control, with just three float parameters rather than a 2D texture map. The two modules also present alternatives in hardware resource usage, trading off computation and on-chip memory bandwidth. The first algorithm uses more instructions, but the second requires an additional texture lookup. As we shall see in Section 4, the first algorithm executes more quickly on 2001 hardware.
3.3
Two Shading Modules
These modules present alternatives in the type of visualization: specular lighting, or an artistic shading model that emphasizes silhouettes. In practice the gradient would be passed in for the normal vector in the shading functions below.
7
3.3.1
Specular Lighting
This function is an adaptation of the standard bump-mapping algorithm for programmable hardware [7, 15]. float3 specular_shading(float3 L, float3 E, float3 N, float4 light_color, float3 a, float3 d, float3 s, float power) { float NdotE = dot(N,E); float normalFlip = select(NdotE >= 0, 1, -1); float NdotL = dot(N,L) * normalFlip; float3 diff = d * clamp01(NdotL); float3 H = normalize(E + L); float NdotH = dot(N, H) * normalFlip; float3 spec = s * pow(clamp01(NdotH), power); return (a + rgb(light_color) * (diff + spec)); }
Notice that the lighting is two-sided: the normal vector is flipped if necessary so that it faces the eye. 3.3.2
Silhouette Enhancement
The silhouette shading model measures the proximity of the volume sample to the silhouette of its containing isosurface by taking the dot product of the eye and normal vectors [16]. It interpolates between the background and the base classification color based on this proximity. float3 silhouette_shading(float3 E, float3 N, float power, float3 classification_color, float3 background) { float NdotE = dot(N, E); float normalFlip = select(NdotE >= 0, 1, -1); float alpha = pow(1 - (NdotE * normalFlip), power); return (alpha * classifiation_color) + ((1-alpha) * background); }
The right-most image of Figure 2 is rendered with this algorithm; in comparison, the left-most image is rendered with the specular model.
3.4
Simplifying and Optimizing Modules
The code modules in Sections 3.1–3.3 will not run as written on our target platform (NVIDIA GeForce 3/4 Ti). In general, 2001 hardware has the following serious limitations at the pixel level: fixed-point precision; lack of support for division and higher order functions (such as exponentiation); and relatively low numbers of instructions and texture lookups. However, as we show below, we can modify most of our code to accomodate these limitations. Only one module (3.1.2) cannot be made to work
8
in a reasonable way, and we discuss below the hardware extensions that it requires. We present the modifications in order of increasing complexity. Two modules require no changes at all: the pre-computed gradient resampling (3.1.1) and the 2D transfer function (3.2.2). The specular shading (3.3.1) and silhouette shading (3.3.2) functions are modified to eliminate the unsupported pow function. We use one of the following two approximations in practice, depending on how many instructions are available with a particular classification algorithm: float pow8(float x) { float x2 = x * x; float x4 = x2 * x2; return x4 * x4; }
float pow8approx(float x) { return clamp01(4.0 * (x - 0.75)); }
The approximation on the left computes the eighth power (fixed exponent) with repeated multiplication. The approximation on the right uses fewer instructions for a coarser approximation that gives a similar visual effect [7]. Approximations of this kind are common in real-time shading applications. The sihouette shading uses one additional simplification: in the last line, the linear interpolation between background and classification color is simplified by assuming that the background is black. The last line therefore simplifies to return alpha * classifiation color;. No simple approximation to the isosurface classify (3.2.1) function is possible, because it uses a division by grad mag in its first line and division is not supported by the hardware. We simplify the function by removing this division. The resulting classification differs from the original in that the rate of falloff in opacity away from the isovalue does not depend on the gradient magnitude. However, the visual results are similar to the original algorithm, since both render the same isosurface. The other change to (3.2.1) is a modification to the triangle helper function to accomodate the fixed point precision of the hardware: float triangle(float dens, float center, float width, float height) { float hdiv16w = (1.0/16.0) * height / width; float dx = dens - center; float y1 = (4.0 * (width - dx) * hdiv16w); float y2 = (4.0 * (dx + width) * hdiv16w); return 4.0 * select(dx >= 0, y1, y2); }
In the original code, fraction (height / width) would clamp to 1 because that is the maximum value supported internally by the hardware. In the
9
modified code, the fraction is artificially clamped only when it exceeds 16. In practice, this modification greatly increases the usable range of height and width parameters. The remaining module is the dynamic gradient estimation resampling of Section 3.1.2. This module cannot be adequately approximated on the current hardware because it uses division and the square root operator. These are required to compute the gradient magnitude and normalize the gradient vector. At best on 2001 hardware we can compute an unnormalized vector that is parallel to the gradient direction. To compute gradients accurately in the hardware, we require floating point precision, and support for division and the sqrt operation.
3.5
Complete Pipelines
All complete pipelines that we implemented use the pre-computed gradient resampling module. However, using the approximations presented in Section 3.4 we have implemented all four pipelines that can be formed by picking different classification and shading combinations. To avoid redundancy, we present code for just one complete pipeline. Results using the other pipelines are presented in Section 4. The pipeline shown here uses 2D transfer function classification and specular shading: surface shader float4 pipeline(primitive group texref dens_gradmag_tex, primitive group texref grad_tex, primitive group texref transfer_2d_tex, primitive group float vox_per_slice, primitive group float4 light_clr, primitive group float4 light_dir, primitive group float4 a, primitive group float4 s, primitive group matrix4 modelview, primitive group matrix4 eyeToTex) { // Resampling float4 uvw = eyeToTex * P; float4 dens_gradmag = texture3d(dens_gradmag_tex, uvw); float3 gradient = rgb(texture3d(grad_tex, uvw)); // Classification float4 basecolor_alpha = transfer_2d_classify(transfer_2d_tex, dens_gradmag); float alpha = basecolor_alpha[3] * vox_per_slice; // Shading matrix3 eyeToObj float3 Lobj float3 Eobj float3 color
= = = =
invert(affine(modelview)); normalize(eyeToObj * rgb(light_dir)); normalize(eyeToObj * -rgb(P)); specular_shading(Lobj, Eobj, gradient, light_clr, rgb(a), rgb(basecolor_alpha), rgb(s)); return { alpha * color, alpha }; }
The primitive group modifier before each shader parameter indicates to
10
Classify 2D Map 2D Map 2D Map Isosurf. Isosurf. Isosurf.
Shade Spec. Silh. None Spec. Silh. None
Combiner Instr 8 8 3 8 8 6
3D Tex 2 2 1 2 2 1
Dep Tex 1 1 1 0 0 0
Fill Rate (Msamples/s) 70 78 145 130 141 311
Frame Rate (frames/s) 4.3 4.8 9.0 8.0 8.8 19
Table 1: Resource usage and performance of various pipelines on a GeForce 4 Ti. Frame rates are for a 2563 volume.
Figure 2: Volume renderings with three different pipelines. The first uses 2D classification and specular shading. The second substitutes the isosurface classification, and the third substitutes silhouette shading. Mouse lung dataset courtesy of the Duke Center for In Vivo Microscopy.
the shading language compiler that these values change only once per volume. The volume is rendered as a “primitive group” composed of all the polygon slices. The shading portion of the code transforms the light and eye vectors into object space — the space of the pre-computed gradient vector. We assume a distance light and distant viewer, a common simplification in real-time graphics, allowing us to transform the light and eye vectors into object space once per volume: the Lobj and Eobj vectors are calculated once per volume because they depend only on primitive group variables.
11
4
Results
Table 1 presents the resource consumption and rendering performance on a GeForce 4 Ti for all classification and shading combinations. Figure 2 illustrates images generated by three of the pipelines listed in Table 1. Our GeForce 4 Ti has 128 MB of graphics memory, and we have measured its peak fill rate at ∼ 1200 Mpix/sec. The first two pipelines completely exhaust the hardware resources. These render at less than one sixteenth of the peack fill rate rate, and the simplest pipeline at one fourth. Opacity projection (no classification and no shading) renders no faster than the simplest shader in Table 1, so a single 3D texture lookup immediately decreases performance to one fourth the peak rate. The largest contiguous volume that can be stored in graphics memory is 2563 voxels (assuming 5 bytes per voxel for density, gradient vector and gradient magnitude). A volume of this size renders at 4 – 19 fps, depending on the complexity of the pipeline. The rendering rates of these pipelines reveals a fortunate characteristic of current commodity graphics hardware: they contain several times more texture memory than needed to saturate their computational resources during real-time volume rendering.
5
Discussion
Shading language greatly simplifies the implementation and re-use of volume rendering components, but the language has limitations. The most significant of these is lack of support for looping or branching, features which are not supported by current hardware [15]. As a result, the volume rendering pipelines that can be written in the language perform exactly the same computation at each volume sample. A more minor language limitation is that procedures have only a single return value. This prevents the resampling modules in Section 3.2 from being implemented as procedures, which is a barrier to creating libraries of re-usable resampling functions. Syntax enhancements, such as support for passing function parameters by reference, would solve this problem. In spite of these limitations, our experience is that the shading language is a very effective platform for implementing real volume rendering algorithms. As a specific example, we found it very straightforward to implement the pre-integrated classification algorithm that was recently
12
proposed by Engel and colleagues [6]. Our implementation uses a new resampling module to look up the density at both the current sample and the next sample in depth. A new classification module uses these values as 2D lookup coordinates in a texture of pre-integrated transfer function values. We have composed these modules with specular shading to recreate the pipeline in the original paper, and composed them with our silhouette shading module to create a novel pipeline. Both pipelines have shader code about the same length as the one in Section 3.5, and render at about the same rate on a GeForce 3/4 Ti. In addition to supporting implementation re-use, our shading-languag approach provides support for high-level optimization that is surprisingly useful in practice. In our experience, experimentation with the kinds of simplifications and optimizations described in Section 3.4 is critical — and very common — when designing pipelines that are close to resource limits. Such experimentation would be extremely tedious in hand-coded implementations. As a concrete example of the effectiveness of such optimization, we note that our specular shading model is the first one implemented on GeForce 3/4 Ti that computes correct two-sided lighting. In contrast, other hand-coded implementations of volume shading on this hardware have ignored the direction of the viewer [6, 8]. As a result, parts of the volume where the gradient vector points away from the viewer are lit unnaturally. We were able to include the two-sided lighting term only after experimenting with the approximations to the pow function presented in Section 3.4; the most straightforward implementation of two-sided lighting exceeds the instruction count allowed by the hardware. While the need for such heavy optimization illustrates the benefits of a high-level approach, it indicates that 2001 graphics hardware is only barely adequate for the techniques presented in this paper. The severe restrictions on available instructions alone prevents implementation of modules significantly more sophisticated than the ones we have shown. Despite these limitations, the current hardware demonstrates that our basic technique is sound and will only become more useful as the hardware matures. The critical missing features that we have identified as necessary to fully reap the benefts of our approach — floating point support, and a large increase in the number of instructions and texture lookups — have already been proposed and are likely to appear in the next one or two years [10, 3].
13
6
Conclusion
We have demonstrated a technique for implementing custom volume visualizations through the use of a real-time shading language and a programmable volume rendering pipeline abstraction. We have shown that resampling, classification and shading algorithms can be implemented as re-usable code modules, and that libraries of such modules can be flexibly combined to form new pipelines. Furthermore, we have shown that our technique supports highly effective optimization of pipeline code to meet hardware constraints and boost performance. Commodity graphics hardware is becoming a very general-purpose parallel computing machine, and we are positioned at the exciting point of its exponential growth curve where real-time programmable volume rendering is just becoming feasible. As the performance of graphics hardware continues to double and its capabilities become more general, we can expect a dramatic increase in the sophistication and complexity of real-time volume rendering techniques, and a renaissance in experimentation with interactive volume visualization.
Acknowledgments Kekoa Proudfoot helped with using and debugging the shading language system. We would like to thank Pat Hanrahan and Marc Levoy for discussions about this paper, and Eric Chan for reading the final draft. The mouse dataset is courtesty of G. A. Johnson, G. P. Cofer, S. L. Gewalt, and L. W. Hedlund from the Duke Center for In Vivo Microscopy (an NIH/NCRR National Resource). This work was sponsored by the DOE Multi-Graphics program (contract B504665-2).
References [1] High-quality volume graphics on consumer PC hardware. Course organized by Joe Kniss. To appear in SIGGRAPH 2002 Course Notes. [2] Image processing for volume graphics. Course organized by Terry S. Yoo and Raghu Machiraju. In SIGGRAPH 2001 Course Notes. [3] 3DLabs. 3DLabs OpenGL 2.0 Specifications web site, 2001. http://www.3dlabs.com/support/developer/ogl2. [4] Brian Cabral, Nancy Cam, and Jim Foran. Accelerated volume rendering and tomographic reconstruction using texture mapping hardware. In Symposium on Volume Visualization, 1994.
14
[5] Robert A. Drebin, Loren Carpenter, and Pat Hanrahan. Volume rendering. Proceedings of SIGGRAPH, 1988. [6] Klaus Engel, Martin Kraus, and Thomas Ertl. High-quality pre-integrated volume rendering using hardware-accelerated pixel shading. In Proceedings of SIGGRAPH/EUROGRAPHICS Workshop on Graphics Hardware, 2001. [7] Mark J. Kilgard. A practical and robust bump-mapping technique for today’s GPU’s. February 2000. Technical report, NVIDIA Corporation. Available at http://www.nvidia.com/. [8] Joe Kniss, Gordon Kindlmann, and Charles Hansen. Interactive volume rendering using multi-dimensional transfer functions and direct manipulation widgets. In IEEE Visualization, October 2001. [9] Marc Levoy. Display of surfaces from volume data. IEEE Computer Graphics and Applications, 8(3):29–37, May 1988. [10] Brian Marshall. DirectX graphics future. Meltdown Conference, 2001. http://www.microsoft.com/mscorp/corpevents/meltdown2001/ppt/DXG9.ppt.
[11] Michael Meissner, Ulrich Hoffmann, and Wolfgang Straßer. Enabling classification and shading for 3D texture mapping based volume rendering. In Proceedings of the IEEE Conference on Visualization, pages 207–214, 1999. [12] Mark S. Peercy, Marc Olano, John Airey, and P. Jeffrey Ungar. Interactive Multi-Pass programmable shading. In Proceedings of SIGGRAPH, 2000. [13] Hanspeter Pfister, Jan Hardenbergh, Jim Knittel, Hugh Lauer, and Larry Seiler. The VolumePro real-time ray-casting system. In Proceedings of SIGGRAPH, 1999. [14] Hanspeter Pfister, Bill Lorensen, Chandrajit Baja, Gordon Kinklmann, Will Schroeder, Lisa Sobierajski Avila, Ken Martin, Raghu Machiraju, and Jinho Lee. Visualization viewpoints: The transfer function bake-off. IEEE Computer Graphics and Applications, 21(3):16–23, May / Jun 2001. [15] Kekoa Proudfoot, William R. Mark, Svetoslav Tzvetkov, and Pat Hanrahan. A real-time procedural shading system for programmable graphics. In Proceedings of SIGGRAPH, 2001. [16] Penny Rheingans and David Ebert. Volume illustration: Nonphotorealistic rendering of volume models. In IEEE Transactions on Visualization and Computer Graphics, volume 7(3), pages 253–264. IEEE Computer Society, 2001. [17] Allen Van Gelder and Kwansik Kim. Direct volume rendering with shading via three-dimensional textures. In 1996 Volume Visualization Symposium, pages 23–30. IEEE, 1996. [18] R¨ udiger Westermann and Thomas Ertl. Efficiently using graphics hardware in volume rendering applications. In Proceedings of SIGGRAPH, 1998.
15