Upcoming Events
Southwest Gaming Expo
11/20 - 11/22 @ Dallas, TX

Workshop on Network and Systems Support for Games (NetGames 2009)
11/23 - 11/25 @ Paris, France

ICIDS 2009 Interactive Storytelling
12/9 - 12/11 @ Guimarăes, Portugal

Global Game Jam
1/29 - 1/31  

More events...


Quick Stats
6410 people currently visiting GDNet.
2341 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!



Link to us

Link to us

  Intel sponsors gamedev.net search:   

Real-Time Realistic Cloud Rendering and Lighting


4. The phase function – Scattering in the eye direction

To simulate multiple scattering in the eye direction a phase function is used. The phase function allows the calculation of the distribution of light scattering for a given direction of incident light.

The phase function takes as parameters two directions, in our case the light direction and the direction of light arriving at the observer. Thus, when doing lighting, the direction of incident light is the negative of the direction of the light. When rendering normally the direction the light arrives at the observer is the vector between the particle position and the camera eye point.

The function Harris uses is a simple Rayleigh scattering function:

Where θ is the angle between ω and ω' and thus equal to their dot product if they are normalized. When ω is in the direction of ω' the function is equal to 1.5 giving the value used in lighting.

5. Creating Cloud Impostors

To speed up rendering, the 3D clouds, composed of particles, can be rendered to a 2D surface which is then mapped onto a billboard. This saves fillrate as the impostor is only updated when the change in angle between the camera and the cloud center exceeds a certain threshold.

The hardest part in rendering the impostor is setting up the camera. In this case the camera will lie at the eye position and will point at the cloud center. The particles are sorted back to front to eliminate transparent blending problems.

Setting up the camera is easily done with OpenGL's functions and again we will use an orthographic projection.

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(-Cloud->Radius-pr, Cloud->Radius+pr,
        -Cloud->Radius-pr, Cloud->Radius+pr, d - r, d + r);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
gluLookAt(Camera.x, Camera.y, Camera.z,
          Cloud->Center.x, Cloud->Center.y, Cloud->Center.z,
		  0, 1, 0);

glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0, 0, ViewportSize, ViewportSize);

The viewport size can be set to trade between speed and quality. A more advanced implementation will set the viewport size depending on the distance of the cloud center to the camera.

After the viewport is set up we can simply render the particles with their respective colors as billboards. Again we will enable blending and set the blend function to GL_ONE, GL_ONE_MINUS_SRC_ALPHA. The up and right vectors for the billboards can be obtained straight from the modelview matrix as such:

float mat[16];
glGetFloatv(GL_MODELVIEW_MATRIX, mat);

Vector3 vx(mat[0], mat[4], mat[8] );
Vector3 vy(mat[1], mat[5], mat[9] );

When rendering each cloud particle we have to calculate the phase function and modulate the particle color with it:

costheta = Dot(Omega, Light);
phase = 0.75f * (1.0f + costheta * costheta);

Now we can upload the frame buffer into a texture which will be used for creating the cloud billboard:

glBindTexture(GL_TEXTURE_2D, Cloud->ImpostorTex);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, ViewportSize, ViewportSize, 0);

Now the cloud can be simply rendered as a billboard with the texture we uploaded. To detect when an impostor update is needed we store the vector from the cloud to the camera of the last update and compute the angle between it and the current vector.

float dot = Dot(ToCam, Clouds[i].LastCamera);
bool in_frustum = Frustum.SphereInFrustum(Clouds[i].Center, Clouds[i].Radius);
int mip_size = GetImpostorSize(SqDist(Camera, Clouds[i].Center));

if ((dot < 0.99f || Clouds[i].ImpostorSize < mip_size)  && in_frustum)
{
  RenderCloudImpostor(&Clouds[i], Sun, Camera);
  Clouds[i].LastCamera = ToCam;
}

6. Creating the Splat Texture

The splat texture that we use encodes the intensity of light lost as the light passes through the cloud particle's volume. Thus, as less light passes through the center than through the edges, the texture needs to exhibit a falloff from the center to the edges. To our aid comes a nice interpolating polynomial which, using an interpolant between 0 and 1, varies from 1 to 0 in a smooth way. Since we are interpolating between a value in the center of the texture and 0 on the outside, the polynomial can be further simplified to give a function of the center value and the interpolant.

v1 can be chosen arbitrarily to give a good result and f is the distance between the pixel we are coloring and the center divided by the radius of the texture. Instead of computing the distance in pixels we can have variables which vary from -1 to 1 from left to right, top to bottom on the texture and thus also eliminate the need for division by the texture radius.

Y = -1.0f;
for (int y = 0; y < N; y++)
{
  X = -1.0f;
  for (int x=0; x<N; x++, i++, j+=4)
  {
    Dist = (float)sqrt(X*X+Y*Y);
    if (Dist > 1) Dist=1;
    value = 2*Dist*Dist*Dist - 3*Dist*Dist + 1;
    value *= 0.4f;

    B[j+3] = B[j+2] = B[j+1] = B[j] = (unsigned char)(value * 255);

    X+=Incr;
  }
  Y+=Incr;
}




From Impostors to Full 3D Models


Contents
  Introduction
  The phase function
  From Impostors to Full 3D Models

  Source code
  Printable version
  Discuss this article