OpenGL ES Environment Mapping

Development

OpenGL ES 1.1 doesn’t come equipped with Sphere Mapping. This is in contrast to its desktop non-ES counterpart. Sphere mapping is a fairly important part of any rendering engine, so having support for it is pretty much a requirement. Thankfully it is easy to reproduce in OpenGL ES. In fact, if you can tolerate ‘fake’ Sphere Mapping, where you only need the impression of the effect (and not absolutely accurate reflection) you can push the entire calculation to the GPU. Noel Llopis has written about the latter (‘Normal Environment Mapping’) in a chapter of the recently released iPhone Advanced Projects. However, if you need accurate Sphere Mapping, you can still achieve the effect on the CPU. Here’s how I went about reproducing it in UtopiaGL.

Sphere Mapping

Sphere mapping is one of the easiest ways to implement Environment Mapping. It uses a single, 2D texture, which contains a warped capture of the environment (it looks a little like what you’d expect a chrome sphere would reflect, although that isn’t entirely accurate).

The front part of the sphere is essentially what you’re seeing when you look at a Sphere map, but the back side is also captured, and heavily compressed into the circular boundary of the image. Given a Reflection vector, you can calculate a texture index (s,t) into this Sphere Map to pull out the correct texel to achieve perfect Environment Reflection at a particular vertex.

Reflection

The mathematics of Sphere Mapping are straight forward – it’s a simple reflection with an additional indexing step. You can find a full description of how it works in Desktop OpenGL here. Essentially, the process is this.

  • Bring the World Space Eye into Object Space (for World Space geometry this step isn’t required).

Then for each Vertex:

  • Calculate the Normalized Vector from the Object Space Eye to the Object Space Vertex.
  • Calculate the Reflection Vector given the Object Space Vertex Normal
  • Transform the Reflection Vector to World Space using the Inverse Transpose of the Object to World matrix.
  • Given the World Space Reflection Vector, use it to build (s,t) coordinates to index the Sphere Map.
    utMat4 m = _pRefFrame->GetInvTranspose();

    // pV, pN and pTC point to the XYZ, Normal and Texture Coordinate attributes of a single vertex.

    for( int i=0; i<numVerts; i++, pV+=vstride, pN+=nstride, pTC+=tstride )
    {
        // Calculate the vector from Object Space Eye to the Vertex

        viewVec = _refFrameEye - *(utVec3*)pV;
        viewVec.NormalizeFast();

        // Reflect it
        float d = viewVec.Dot( *(utVec3*)pN );
        reflectedVec = *(utVec3*)pN * (2.f*d) - viewVec;

        // Transform it to World Space
        m.Multiply3( reflectedVec, reflectedVec );

        // Index into Sphere Map.  Optimization: this code takes the Reciprocal Square Root instead of the 1 / sqrt()
        float p = utMath::RSqrt(
            reflectedVec.v[0] * reflectedVec.v[0] +
            reflectedVec.v[1] * reflectedVec.v[1] +
            (reflectedVec.v[2]+1) * (reflectedVec.v[2]+1) ) * .5f;
        ((float*)pTC)[0] = .5f + reflectedVec.v[0] * p;
        ((float*)pTC)[1] = .5f + reflectedVec.v[1] * p;
    }

Transforming Normals

If you are new to OpenGL, something that can trip you up is transformation of normals.  Unlike vertices, normals don’t have a location in space, just an orientation. They are also not subject to scaling, as vertices are.  The MODELVIEW matrix can incorporate scaling, translation, sheering etc. on top of simple rotation, so how can you make use of it to transform normals, in such a way to preserve their direction and length? The answer is to do what OpenGL does under the hood.  It multiplies normals by the Inverse Transpose of the MODELVIEW matrix.  This is actually very easy to understand.  When a matrix is orthogonal (its basis vectors are all at 90 degrees to each other), normalized (no scaling going on), and has no translation (so it just encodes rotation), you can calculate the inverse with a single trivial operation: the Transpose.  What the Inverse Transpose does, is take the inverse of the MODELVIEW (which brings you from World Space to the Identity Reference Frame) and then take the Transpose of that Matrix (to bring us back to World Space, except without any Scale, Sheer, translation etc). This is perfect for transforming Normals.

Optimizations

The above code makes use of the Reciprocal Square Root function, which you can read about here. If you are doing multipass rendering you can of course cache the results of the above and reuse the texture coordinates on subsequent passes. UtopiaGL allows caching of this data in its Shader pipeline.

Uses

The primary reason to use accurate Sphere Mapping is when you need to provide a very clean (and controllable) reflection of the environment. I needed this in a recent contract for a high profile British artist.  The work involved drawing a highly detailed, reflective (and refractive) object where we needed to have good control over Specular and other Diffuse environmental contributions to the lighting of the object.  While it is slower than the alternatives, it is not that slow, particularly if you are clever about how you model the objects in a scene.

Conclusion

OpenGL ES 1.1 may not support Sphere Mapping but nothing prevents you from implementing it yourself.  There are many ways to go about it, each with performance and quality tradeoffs.  The above method perfectly reproduces the type of Sphere mapping Desktop OpenGL uses.

Filed under: , , , .

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>