Ideal Binary Blog 

You will find information on all aspects of our work here on our company blog.

Enter your email to hear about our products.

OpenGL ES Environment Mapping

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.

Then for each Vertex:

    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

~ 3D Bookshelf ~

Robin Hood Edition

Free Download!

3D Bookshelf - Robin Hood Edition uses the world's first fully 3D eBook engine.

Download it for free now!

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.

iPhone Content Creation with Blender – Part 2

This is part 2 in a series of posts describing how to extend Blender to fit with your own content production process, specifically with regard to producing content for iPhone.

The 2.5 release of Blender is just around the corner. From the looks of the feature improvements already described on the Blender site we can expect some substantial changes to almost every aspect of the application. One significant area is the UI, in particular scripting updates to the UI.

What I'll describe here is relevant to scripting a UI for an exporter using the 2.49.2 release of Blender. Below is a snapshot of the UtopiaGL exporter in Blender.

ExporterUI

One of the things that bugged me about setting up a UI for the UtopiaGL exporter was that Blender exposes a pretty low-level API for this purpose. This means that you end up dealing with absolute coordinates when positioning textboxes and buttons and so on. From looking at some of the existing Blender exporter plugins, the following type of UI code is not uncommon.

def draw_gui():
    ... # globals removed to save space!

    # Title
    glClear(GL_COLOR_BUFFER_BIT)
    glRasterPos2d(10, 290)
    Text("UtopiaGL .model Export")

    # VNormals / UVs / VColors / VWeights
    Label( "Properties To Export", 430, 190, 120, 20 )

    g_toggle_outputvnormals = Toggle("Vertex Normals", EVENT_NOEVENT, 430, 160, 100, 20, g_toggle_outputvnormals.val, "Output Vertex Normals" )
    g_toggle_outputuvs = Toggle("Vertex UVs", EVENT_NOEVENT, 540, 160, 100, 20, g_toggle_outputuvs.val, "Output Vertex UV Coordinates" )
    g_toggle_outputvcolors = Toggle("Vertex Colors", EVENT_NOEVENT, 430, 130, 100, 20, g_toggle_outputvcolors.val, "Output Vertex Colors" )
    g_toggle_outputvweights = Toggle("Vertex Weights", EVENT_NOEVENT, 540, 130, 100, 20, g_toggle_outputvweights.val, "Output Vertex Weights" )

    Label( "Faces", 430, 90, 80, 20 )
    g_menu_facewinding = Menu("Face Winding %t|Counter-Clockwise %x1|Clockwise %x2|", EVENT_NOEVENT, 430, 60, 150, 20, g_menu_facewinding.val, "Face winding to use" )

    # Content Root / Model File

    Label( "Content Root Path", 10, 240, 80, 20 )
    g_content_root = String("", EVENT_NOEVENT, 10, 210, 300, 20, g_content_root.val, 255, "Content root path")
    Button( "Browse", EVENT_CHOOSE_CONTENT_ROOT, 310, 210, 80, 20 )

    Label( "Model File", 10, 180, 80, 20 )
    g_filename = String("", EVENT_NOEVENT, 10, 150, 300, 20, g_filename.val, 255, "Model file to save")
    Button( "Browse", EVENT_CHOOSE_FILENAME, 310, 150, 80, 20 )

    g_toggle_outputshaders = Toggle("Output Shaders/Skins", EVENT_NOEVENT, 10, 120, 130, 20, g_toggle_outputshaders.val, "Output Shader and Skin files" )

    # Log / Log Level

    Label( "Logging", 10, 90, 80, 20 )
    g_toggle_outputtolog = Toggle("Output Log", EVENT_NOEVENT, 10, 60, 80, 20, g_toggle_outputtolog.val, "Output export progress to log file" )
    Label( "Log Level", 160, 60, 80, 20 )
    g_integer_loglevel = Menu("Log Level %t|Debug %x1|Info %x2|Warning %x3|Error %x4|Critical %x5", EVENT_NOEVENT, 230, 60, 80, 20, g_integer_loglevel.val, "Logging Level to use" )

    # Export / Exit
                
    Button( "Export", EVENT_SAVE_MODEL, 10, 10, 80, 20 )
    Button( "Exit", EVENT_EXIT ,100, 10, 80, 20 )

The above approach works and if your UI is simple and not subject to change you should be fine to implement a UI like this. If on the other hand, you plan to iteratively extend the exporter as and when new requirements appear, you'll probably want something a little more dynamic. For example, inserting a button in the middle of a UI implemented like this means calculating and changing the coordinates of all other UI elements in the surrounding area.

Silverlight and WPF have various types of Panel controls that makes insertion and removal of controls in a layout very easy. I didn't have time to implement a complete panel control, so instead I implemented a Cursor object that allows me to output UI elements at the current location of the Cursor.

class Cursor:
        """A Cursor object, use to maintain context of where to insert UI elements."""

        def __init__(self):
                self.x = 10
                self.y = 10
                self.width = 80
                self.height = 20
                self.virticalpad = 10
                self.horizontalpad = 10

        def set_x(self, newx):
                self.x = newx
        
        def set_y(self, newy):
                self.y = newy
        
        def set_width(self, newwidth):
                self.width = newwidth
        
        def set_height(self, newheight):
                self.height = newheight

        def set_virtical_pad(self, newvirticalpad):
                self.virticalpad = newvirticalpad
        
        def set_horizontal_pad(self, newhorizontalpad):
                self.horizontalpad = newhorizontalpad

        def move_up(self):
                self.y = self.y + (self.height + self.virticalpad)

        def move_down(self):
                self.y = self.y - (self.height + self.virticalpad)

        def offset_up(self, up):
                self.y = self.y + up

        def offset_down(self, down):
                self.y = self.y - down

        def move_left(self):
                self.x = self.x - (self.width + self.horizontalpad)

        def move_right(self):
                self.x = self.x + (self.width + self.horizontalpad)

        def offset_left(self, left):
                self.x = self.x - left

        def offset_right(self, right):
                self.x = self.x + right

        def Button(self, title, handle):
                Button(title, handle, self.x, self.y, self.width, self.height)

        def Toggle(self, title, handle, value, tip):
                return Toggle(title, handle, self.x, self.y, self.width, self.height, value, tip)
        
        def Label(self, title):
                Label(title, self.x, self.y, self.width, self.height)
        
        def Menu(self, options, handle, value, tip):
                return Menu(options, handle, self.x, self.y, self.width, self.height, value, tip)

        def String(self, text, handle, value, l, tip):
                return String(text, handle, self.x, self.y, self.width, self.height, value, l, tip)

By using the cursor we can convert the hard-coded UI code from above to look more like the following. Note, this code now makes it much easier to add and remove UI elements without the need to update the coordinates of the surrounding elements. It does increase the amount of code a little, but the trade off is probably worth it.

def draw_gui():
        ... # globals removed to save space!

        glClear(GL_COLOR_BUFFER_BIT)

        # Export / Exit

        cursor = Cursor()
        cursor.Button( "Export", EVENT_SAVE_MODEL)
        cursor.move_right()
        cursor.Button( "Exit", EVENT_EXIT)

        # Log / Log Level

        cursor.move_left()
        cursor.move_up()
        cursor.offset_up(20)

        g_toggle_outputtolog = cursor.Toggle("Output Log", EVENT_NOEVENT, g_toggle_outputtolog.val, "Output export progress to log file")
        cursor.offset_right(150)
        cursor.Label( "Log Level" )
        cursor.move_right()
        g_integer_loglevel = cursor.Menu("Log Level %t|Debug %x1|Info %x2|Warning %x3|Error %x4|Critical %x5", EVENT_NOEVENT, g_integer_loglevel.val, "Logging Level to use")
        cursor.set_x(10)
        cursor.move_up()
        cursor.Label( "Logging")

        # Content Root / Model File

        cursor.move_up()
        cursor.set_width(130)
        g_toggle_outputshaders = cursor.Toggle("Output Shaders/Skins", EVENT_NOEVENT, g_toggle_outputshaders.val, "Output Shader and Skin files" )

        cursor.move_up()
        cursor.set_width(300)
        g_filename = cursor.String("", EVENT_NOEVENT, g_filename.val, 255, "Model file to save" )
        cursor.offset_right(300)
        cursor.set_width(80)
        cursor.Button( "Browse", EVENT_CHOOSE_FILENAME )
        cursor.move_up()
        cursor.set_x(10)
        cursor.Label( "Model File")

        cursor.move_up()
        cursor.set_width(300)
        g_content_root = cursor.String("", EVENT_NOEVENT, g_content_root.val, 255, "Content root path" )
        cursor.offset_right(300)
        cursor.set_width(80)
        cursor.Button( "Browse", EVENT_CHOOSE_CONTENT_ROOT)
        cursor.move_up()
        cursor.set_x(10)
        cursor.Label( "Content Root Path")

        # Title

        cursor.move_up()
        cursor.move_up()
        cursor.set_width(300)
        cursor.Label( "UtopiaGL .model Export")

        # VNormals / UVs / VColors / VWeights
        
        cursor.set_x(430)
        cursor.set_y(10)
        cursor.move_up()
        cursor.offset_up(20)
        g_menu_facewinding = cursor.Menu("Face Winding %t|Counter-Clockwise %x1|Clockwise %x2|", EVENT_NOEVENT, g_menu_facewinding.val, "Face winding to use" )
        
        cursor.move_up()
        cursor.Label( "Faces")

        cursor.move_up()
        cursor.offset_up(10)
        cursor.set_width(100)

        g_toggle_outputvcolors = cursor.Toggle("Vertex Colors", EVENT_NOEVENT, g_toggle_outputvcolors.val, "Output Vertex Colors")
        cursor.move_right()
        g_toggle_outputvweights = cursor.Toggle("Vertex Weights", EVENT_NOEVENT, g_toggle_outputvweights.val, "Output Vertex Weights")

        cursor.move_left()
        cursor.move_up()
        g_toggle_outputvnormals = cursor.Toggle("Vertex Normals", EVENT_NOEVENT, g_toggle_outputvnormals.val, "Output Vertex Normals")
        cursor.move_right()
        g_toggle_outputuvs = cursor.Toggle("Vertex UVs", EVENT_NOEVENT, g_toggle_outputuvs.val, "Output Vertex UV Coordinates")

        cursor.move_left()
        cursor.move_up()
        cursor.set_width(120)
        cursor.Label( "Properties To Export")

Here's hoping the improvements made in the 2.5 release make this stuff redundant. But for now, this is a manageable way to implement an exporter UI in Blender to make updates and modifications a little easier.

I didn't get around to writing about the Material to Shader mismatch that I mentioned in the previous post. So, there's definitely going to be a part 3 to this series at a minimum. Hopefully, I'll get to it soon.

National Lottery iPhone App

We recently teamed up with Marino Software to do some work for them on the Irish National Lottery App (published by Agency.com Dublin).  Aidan and I wrote the physics simulator and OpenGL pipeline for the Ball Drum used to generate random numbers by shaking the iPhone / iPod touch.  We are delighted to say the App has gone to Number 1 in the Free App Downloads on the Irish App Store!  Congratulations to everyone at Marino for putting together such a polished App.