Refractive Texture Mapping, Part One
Contents |
|
This article is better suited for console development, but for sake of clarity, the sample program was implemented in a Pentium 300 with a 3D graphics card, and using DirectX 7. Even though DirectX 7 has its own implementation of one of the mapping techniques described in this article (sphere mapping), DirectX 7 was only used to transform and render the final polygons in the sample program. All the UV coordinate calculations, perturbations, normal calculations, and so on, were done internally by the program. It's also important to note that the hardware did all the transformations, and all the calculations described here are in world space. However, these calculations can be easily done in object space if necessary as described further.
Motivation
Last year I started to play around with a particular texture mapping technique called sphere mapping. As I went further in my research, I found out that sphere and refractive texture mapping were similar. At the end, I started to be interested in writing a real-time water wave application using refractive texture mapping. Meanwhile, I also found out that the literature about the subject was not too clear. Most of the papers, articles, and books either described the techniques too briefly, missing a number of details, or dove into things such as fluid dynamics.
In this series of articles, I'll present some experiments I did with sphere and refractive texture mapping, as well as few tricks I learned to speed up the code, without compromising the quality of the final rendering too much. The first part will focus on using sphere mapping to simulate curved-surface reflections, and in part two I will describe how to use refractive texture mapping to simulate water refraction, complete with optimizations and a sample program.
Sphere Mapping
Sphere mapping is a technique used to simulate reflections in curved surfaces. There is a really good discussion about sphere mapping in Real-Time Rendering by Möller and Haines, and also in the DirectX 7 SDK documentation. Here I've presented a simplified version of the technique that can be done basically in two steps. First, the reflected rays from the camera's position to the object are computed. Second, the reflected rays' values are interpolated as texture coordinates. Note that a special texture is normally used for the final rendering.
![]() |
By looking at the figure above (Figure 1), the reflected ray is computed by the formula:
In the particular case of a polygonal mesh, the surface normals
(N) are the vertex normals. A simple way of computing the vertex normal is
by averaging the surface normals of all polygons sharing this vertex. The
incident rays (V) are the vectors between the mesh vertices and the camera
position.
Once the reflected rays are computed, their results need
to be transformed in texture coordinates. As in 3D space, the reflected
ray has three values (X, Y, Z) and the texture mapping is a 2D entity, the
values need to be transformed in some way to generate the final UV
coordinates. There are several ways of transforming the reflected ray
values in UV coordinates. An easy an simple way is just to ignore one of
the values (in my particular implementation, the Z value) and interpolate
the remaining two values as UV coordinates. This approach is reasonably
fast and generates good results. After the UV coordinates are stored in
the mesh data structure, the polygons are sent to the hardware to be
transformed and rendered.
Even though the process is simple, there are few details to be considered for the implementation, specially if the target machine is a console. Let's look at pseudocode below to see these details.
//main loop
vertex_current = (VERTEX_TEXTURE_LIGHT
*)water->mesh_info.vertex_list;
for (t0=0;
t0<water->mesh_info.vertex_list_count; t0++,
vertex_current++){
camera_ray.x= camera.camera_pos.x - vertex_current->x;
camera_ray.y= camera.camera_pos.y - vertex_current->y;
camera_ray.z= camera.camera_pos.z - vertex_current->z;//avoid round off errors
math2_normalize_vector (&camera_ray);//let's be more clear (the compiler will optimize this)
vertex_normal.x= vertex_current->nx;
vertex_normal.y= vertex_current->ny;
vertex_normal.z= vertex_current->nz;//reflected ray
dot= math2_dot_product (&camera_ray, &vertex_normal);reflected_ray.x= 2.0f*dot*vertex_normal.x - camera_ray.x;
reflected_ray.y= 2.0f*dot*vertex_normal.y - camera_ray.y;
reflected_ray.z= 2.0f*dot*vertex_normal.z - camera_ray.z;math2_normalize_vector (&reflected_ray);
//interpolate and assign as uv's
vertex_current->u= (reflected_ray.x+1.0f)/2.0f;
vertex_current->v= (reflected_ray.y+1.0f)/2.0f;
}//end main loop
![]() |
First, all the coordinates are assumed to be in world space. This is because the mesh vertices are in world space (in the sample program), and the vectors from camera to the mesh vertices (incident rays) are also in world space. In case you need the object (mesh) to be in object space, you must transform it into world space before applying the calculations. Otherwise you must transform the incident rays back to object space. Whichever is more convenient for you depends on the hardware and the application.
Second, the mesh normals need to be computed and normalized before the loop above starts. The sample code uses the simple average surface normals approach. Third, all the other vectors need to be normalized to avoid roundoff errors, and for the final interpolation. Also, looking at how the incident ray is being computed in the code above, you can see that it's inverted. This is for debugging purposes. The implementation can display the rays (see Figure 2), and if they were in the right direction, they would pierce the object. The inverted incident ray does not cause any problems, it just hits the map inverted. Finally, as the components of each normalized reflected ray vertex are going from -1.0 to 1.0, by simple algebra they can be linearly interpolated as:
If the reflected ray is normalized and the texture coordinates come from this vector, the vector will hit the map in a shape of a sphere. For this reason, a special texture is normally used. The texture needs to represent a full view of the environment distorted as if seen through a fish-eye lens. Figure 3 is an example of this type of texture.
![]() |
In water simulation, sphere mapping can also be used simulate water reflections. Also, in the code above there are couple of vector normalizations, which might not be too efficient on some platforms. However, most console hardware can perform vector normalization reasonably fast. Also, the spherical texture for water simulation, is really not a requirement. If the application does not need to generate a reflection of the environment, graphic artists can experiment with different types of textures until good results are achieved.