It's free to join Gamasutra!|Have a question? Want to know who runs this site? Here you go.|Targeting the game development market with your product or service? Get info on advertising here.||For altering your contact information or changing email subscription preferences.
Registered members can log in here.Back to the home page.

Search articles, jobs, buyers guide, and more.

By Daniel Sanchez-Crespo
[Author's Bio]

Gamasutra
September 3, 2003

Introduction

Implementation Using Cg

Printer Friendly Version
   

 

Change Login/Pwd
Post A Job
Post A Project
Post Resume
Post An Event
Post A Contractor
Post A Product
Write An Article
Get In Art Gallery
Submit News

 


 


[Submit Letter]

[View All...]
  



Upcoming Events:
Workshop on Network and Systems Support for Games (NetGames 2009)
Paris, France
11.23.09

EVA 09 - Exposicion de Videojuegos Argentina
Buenos Aires, Argentina
12.04.09

Flash GAMM Kyiv 2009
Kyiv, Ukraine
12.05.09

Game Connect: Asia Pacific (GCAP)
Melbourne, Australia
12.06.09

ICIDS 2009 – Interactive Storytelling
Guimaraes, Portugal
12.09.09

[Submit Event]
[View All...]

 


[Enter Forums...]

Note: Discussion forums for Gamasutra are hosted by the IGDA, which is free to join.
 

 

 


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.


Figure 3a: Cg version, using screen space rendering.



Figure 3b: Cg version, using render to texture.

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

 

______________________________________________________

[back to] Introduction


join | contact us | advertise | write | my profile
news | features | companies | jobs | resumes | education | product guide | projects | store



Copyright © 2003 CMP Media LLC

privacy policy
| terms of service