|
Features

Inexpensive
Underwater Caustics Using Cg
Implementation Using Cg
Cg
allows us to move all these computations to the GPU, using a C-like
syntax. In fact, the same wave function previously executed on the
CPU was simply copied into the pixel and vertex shaders using an
include file, with only minor modifications to accommodate the vector-based
structures in Cg.
Calculating
the caustics per-pixel instead of per-vertex improves the overall
visual quality and decouples the effect from geometric complexity.
We considered two approaches when porting the technique to the GPU's
pixel shaders-both use the partial derivatives of the wave function
to generate a normal, unlike the original method which relies on
finite differences.
The
first method takes advantage of the fact that a procedural texture
can be rendered in screen space, thereby saving render time when
only a small portion of the pixels are visible. Unfortunately, this
also means that when a large number of pixels are visible a lot
of work is being done for each one, even though the added detail
may not be appreciable. Sometimes this large amount of work can
slow down the framerate enough to be undesirable, so another method
is needed to overcome this limitation.
The
second method renders in texture space to a fixed resolution render
target. Although rendering in texture space has the advantage of
maintaining a constant workload at every frame, the benefit of only
rendering visible pixels is lost. Additionally, it becomes difficult
to gauge what render target resolution most adequately suits the
current scene, to the point where relying on texture filtering may
introduce the tell-tale bilinear filtering cross pattern if the
texel to pixel ratio drops too low.
As
decribed earlier, the effect calculates the normal of the wave function
to trace the path of the ray to the intercept point on a plane,
or the ocean surface in this example. To generate the normal, the
partial derivatives of the wave function, in x and y, can easily
be found using the chain and product rules, as found in elementary
calculus texts. Let's quickly review these rules.
If
a function f(x) can be written as f(x)=f(g(x)), then
the chain rule states that its derivative with respect to x is given,
in Leibniz notation, as

The
product rule is given as

Let's
write the wave function used in the original approach as

where
i indicates the number of octaves used to generate the wave, c1,
c2 and c3 are constants to give the wave shape
and size, and x and y range from 0 to 1, inclusive, and indicate
a point on a plane at a fixed height.
Therefore,
to calculate the partial derivates in x and y of the wave function,
we let and ,
where 2i is treated as a constant.
Applying
the chain and product rules yield two functions, the partial derivatives,
which are written as
Note
that c1 can be factored outside the summation.
The
partial derivatives are actually components of the gradient vector
at the point where they are evaluated. As the function actually
represents height, or z, the partial derivative with respect to
z is simply 1. The normal is then

The
last equation required to render the caustics is the line-plane
intercept. We can calculate the distance from a point on a line
to the interception point on the plane using the following formula:

where
Dpl is the distance from the plane to the origin, npl
is the normal of the plane, pl is a point on the line,
and vl is the vector describing the direction of the
line, namely the normal we calculated above.
Evidently
the new calculations done per pixel are quite complex, but given
the flexibility of higher level shading languages and the pixel
processing power available in current generation hardware devices,
they are trivial to implement and quick to render. The sample code
below shows the implementation in Cg.
|
|
//
// Caustics
// Copyright (c) NVIDIA Corporation. All rights reserved.
//
// NOTE:
// This shader is based on the original work by Daniel Sanchez-Crespo
// of the Universitat Pompeu Fabra, Barcelona, Spain.
//
#define
VTXSIZE 0.01f // Amplitude
#define WAVESIZE 10.0f // Frequency
#define FACTOR 1.0f
#define SPEED 2.0f
#define OCTAVES 5
// Example of the same wave function used in the vertex engine
float wave(float x,float y,float timer)
{
float z = 0.0f;
float octaves = OCTAVES;
float factor = FACTOR;
float d = sqrt(x*x+y*y);
do {
z -=
factor*cos(timer*SPEED + (1/factor)*x*y*WAVESIZE);
factor = factor/2;
octaves--;
} while (octaves > 0);
return 2*VTXSIZE*d*z;
}
// This is a derivative of the above wave function,
// it returns the d(wave)/dx and d(wave)/dy partial derivatives.
float2 gradwave(float x,float y,float timer)
{
float dZx = 0.0f;
float dZy = 0.0f;
float octaves = OCTAVES;
float factor = FACTOR;
float d = sqrt(x*x+y*y);
do {
dZx += d*sin(timer*SPEED
+ (1/factor)*x*y*WAVESIZE)*y*WAVESIZE;
- factor*cos(timer*SPEED
+ (1/factor)*x*y*WAVESIZE)*x/d;
dZy += d*sin(timer*SPEED
+ (1/factor)*x*y*WAVESIZE)*x*WAVESIZE;
- factor*cos(timer*SPEED
+ (1/factor)*x*y*WAVESIZE)*y/d;
factor = factor/2;
octaves--;
} while (octaves > 0);
return float2(2*VTXSIZE*dZx, 2*VTXSIZE*dZy);
}
float3 line_plane_intercept(float3 lineP, float3 lineN, float3
planeN, float planeD)
{
// Unoptimized
//float distance = (planeD - dot(planeN,
lineP)) / dot(lineN, planeN);
// Optimized (assumes planeN always points
up)
float distance = (planeD - lineP.z) / lineN.z;
return lineP + lineN * distance;
}
|
 |
 |
 |
Listing
1: Cg code sample for the wave function, the gradient of the
wave function, and the line-plane intercept equation.
|
Once
we have calculated the interception point we can use this to fetch
into our caustic light map using a dependent texture read, and then
add the caustic contribution to our ground texture, as shown in
the following code sample.
|
|
float4 main(
VS_OUTPUT vert,
uniform
sampler2D LightMap : register(s0),
uniform
sampler2D GroundMap : register(s1),
uniform
float Timer )
:
COLOR
{
// Generate a normal (line direction) from
the gradient of the wave function
// and intercept with the water plane.
// We use screen-space z to attenuate the
effect to avoid aliasing.
float2 dxdy = gradwave(vert.Position.x,
vert.Position.y, Timer);
float3 intercept = line_plane_intercept(vert.Position.xyz,
float3(dxdy,saturate(vert.Position.w)),
float3(0,0,1), -0.8);
// OUTPUT
float4 color;
color.rgb = (float3)tex2D(LightMap, intercept.xy*0.8);
color.rgb += (float3)tex2D(GroundMap, vert.uv);
color.a = 1;
return color;
}
|
 |
 |
 |
Listing
2: Cg code sample for the final render pass showing the dependent
texture read operations.
|
The
results in Figures 3a and 3b show the quality improvement achieved
by doing the calculations per pixel instead of per vertex. As can
be seen, a sufficiently large resolution texture has as good quality
as screen space rendering, without the performance impact when large
numbers of pixels are displayed. Additionally, when using a render
target texture, we can take advantage of automatic mipmap generation
and anisotropic filtering to reduce aliasing of the caustics, which
cannot be done when rendering to screen space.
Conclusions And Future Work
The
algorithm we have exposed shows a simple, alternative way to tackle
an otherwise complex issue such as caustic simulation. Additionally,
this article has tried to show how a classic algorithm can be upgraded
and enhanced to take advantage of shader-based techniques, which
are becoming more important these days. As future work, we'd like
to extend the technique so it can work on non-horizontal ground
planes, so it can be used to illuminate a submarine below the water,
for example. As shader processing power increases, we hope soon
we will be able to directly implement the full Monte-Carlo approach
on the shader, and thus compute caustics realistically in real-time.
Another
technique we would like to experiment with is incorporating so called
"god rays" (very focused light beams through water) to
the solution, so a complete, aesthetically pleasing model for underwater
rendering using shaders can be described.
I
would like to thank Juan Guardado of Nvidia Corporation for helping
with the Cg implementation and providing feedback on this algorithm.
I would also like to thank everyone at Universitat Pompeu Fabra
for their continued support.
Demos
Caustics
- No Shader
Cg
Caustics
Bibliography
[1]
Foley, van Dam, Feiner and Huges. Computer Graphics. Principles
and Practice. ISBN 0-201-84840-6
[2] "Random Caustics: Natural Textures and Wave Theory Revisited",
Technical Sketch SIGGRAPH'96. In ACM SIGGRAPH Visual Proceedings,
1996, p. 151.
[3] Lasse Staff Jensen, Robert Golias. Deep water animation and
rendering,
http://www.gamasutra.com/gdce/2001/jensen/jensen_01.htm
[4] An introduction to Ray Tracing, Glassner, A. S. (editor),
Academic Press, 1989
______________________________________________________
|