iPhone 3D Programming : Adding Shaders to ModelViewer (part 1) - New Rendering Engine

1/14/2011 2:38:05 PM
OpenGL ES 2.0 does not automatically perform lighting math behind the scenes; instead, it relies on developers to provide it with shaders that perform whatever type of lighting they desire. Let’s come up with a vertex shader that mimics the math done by ES 1.1 when lighting is enabled.

To keep things simple, we’ll use the infinite light source model for diffuse combined with the infinite viewer model for specular . We’ll also assume that the light is white. Example 1 shows the pseudocode.

Example 1. Basic lighting pseudocode
vec3 ComputeLighting(vec3 normal)
N = NormalMatrix * normal
L = Normalize(LightPosition)
E = (0, 0, 1)
H = Normalize(L + E)
df = max(0, NL)
sf = max(0, NH)
sf = sf ^ Shininess
return AmbientMaterial + DiffuseMaterial * df + SpecularMaterial * sf

Note the NormalMatrix variable in the pseudocode; it would be silly to recompute the inverse-transpose of the model-view at every vertex, so we’ll compute it up front in the application code then pass it in as the NormalMatrix uniform. In many cases, it happens to be equivalent to the model-view, but we’ll leave it to the application to decide how to compute it.

Let’s add a new file to the ModelViewer project called SimpleLighting.vert for the lighting algorithm. In Xcode, right-click the Shaders folder, and choose AddNew file. Select the Empty File template in the Other category. Name it SimpleLighting.vert, and add /Shaders after the project folder name in the location field. Deselect the checkbox in the Targets list, and click Finish.

Example 2 translates the pseudocode into GLSL. To make the shader usable in a variety of situations, we use uniforms to store light position, specular, and ambient properties. A vertex attribute is used to store the diffuse color; for many models, the diffuse color may vary on a per-vertex basis (although in our case it does not). This would allow us to use a single draw call to draw a multicolored model.

Example 2. SimpleLighting.vert

attribute vec4 Position;
attribute vec3 Normal;
attribute vec3 DiffuseMaterial;

uniform mat4 Projection;
uniform mat4 Modelview;
uniform mat3 NormalMatrix;
uniform vec3 LightPosition;
uniform vec3 AmbientMaterial;
uniform vec3 SpecularMaterial;
uniform float Shininess;

varying vec4 DestinationColor;

void main(void)
vec3 N = NormalMatrix * Normal;
vec3 L = normalize(LightPosition);
vec3 E = vec3(0, 0, 1);
vec3 H = normalize(L + E);

float df = max(0.0, dot(N, L));
float sf = max(0.0, dot(N, H));
sf = pow(sf, Shininess);

vec3 color = AmbientMaterial + df * DiffuseMaterial + sf * SpecularMaterial;

DestinationColor = vec4(color, 1);
gl_Position = Projection * Modelview * Position;

Take a look at the pseudocode in Example 1; the vertex shader is an implementation of that. The main difference is that GLSL requires you to qualify many of the variables as being attributes, uniforms, or varyings. Also note that in its final code line, Example 4-15 performs the standard transformation of the vertex position, just as it did for the nonlit case.


GLSL is a bit different from many other languages in that it does not autopromote literals from integers to floats. For example, max(0, myFloat) generates a compile error, but max(0.0, myFloat) does not. On the other hand, constructors for vector-based types do perform conversion implicitly; it’s perfectly legal to write either vec2(0, 0) or vec3(0.0, 0.0).

1. New Rendering Engine

To create the ES 2.0 backend to ModelViewer, let’s start with the ES 1.1 variant and make the following changes, some of which should be familiar by now:

  1. Copy the contents of RenderingEngine.ES1.cpp into RenderingEngine.ES2.cpp.

  2. Remove the _OES and OES suffixes from the FBO code.

  3. Change the namespace from ES1 to ES2.

  4. Change the two #includes to point to the ES2 folder rather than the ES1 folder.

  5. Add the BuildShader and BuildProgram methods . You must change all instances of RenderingEngine2 to RenderingEngine because we are using namespaces to distinguish between the 1.1 and 2.0 renderers.

  6. Add declarations for BuildShader and BuildProgram to the class declaration.

  7. Add the #include for iostream.

Now that the busywork is out of the way, let’s add declarations for the uniform handles and attribute handles that are used to communicate with the vertex shader. Since the vertex shader is now much more complex than the simple pass-through program we’ve been using, let’s group the handles into simple substructures, as shown in Example 3. Add this code to RenderingEngine.ES2.cpp, within the namespace declaration, not above it. (The bold part of the listing shows the two lines you must add to the class declaration’s private: section.)

Example 3. ES2::RenderingEngine structures
#define STRINGIFY(A)  #A
#include "../Shaders/SimpleLighting.vert"
#include "../Shaders/Simple.frag"

struct UniformHandles {
GLuint Modelview;
GLuint Projection;
GLuint NormalMatrix;
GLuint LightPosition;

struct AttributeHandles {
GLint Position;
GLint Normal;
GLint Ambient;
GLint Diffuse;
GLint Specular;
GLint Shininess;

class RenderingEngine : public IRenderingEngine {
// ...
UniformHandles m_uniforms;
AttributeHandles m_attributes;

Next we need to change the Initialize method so that it compiles the shaders, extracts the handles to all the uniforms and attributes, and sets up some default material colors. Replace everything from the comment // Set up various GL state to the end of the method with the contents of Example 4.

Example 4. ES2::RenderingEngine::Initialize()

// Create the GLSL program.
GLuint program = BuildProgram(SimpleVertexShader, SimpleFragmentShader);

// Extract the handles to attributes and uniforms.
m_attributes.Position = glGetAttribLocation(program, "Position");
m_attributes.Normal = glGetAttribLocation(program, "Normal");
m_attributes.Ambient = glGetAttribLocation(program, "AmbientMaterial");
m_attributes.Diffuse = glGetAttribLocation(program, "DiffuseMaterial");
m_attributes.Specular = glGetAttribLocation(program, "SpecularMaterial");
m_attributes.Shininess = glGetAttribLocation(program, "Shininess");
m_uniforms.Projection = glGetUniformLocation(program, "Projection");
m_uniforms.Modelview = glGetUniformLocation(program, "Modelview");
m_uniforms.NormalMatrix = glGetUniformLocation(program, "NormalMatrix");
m_uniforms.LightPosition = glGetUniformLocation(program, "LightPosition");

// Set up some default material parameters.
glVertexAttrib3f(m_attributes.Ambient, 0.04f, 0.04f, 0.04f);
glVertexAttrib3f(m_attributes.Specular, 0.5, 0.5, 0.5);
glVertexAttrib1f(m_attributes.Shininess, 50);

// Initialize various state.

// Set up transforms.
m_translation = mat4::Translate(0, 0, -7);

Next let’s replace the Render() method, shown in Example 5.

Example 5. ES2::RenderingEngine::Render()
void RenderingEngine::Render(const vector<Visual>& visuals) const
glClearColor(0, 0.125f, 0.25f, 1);

vector<Visual>::const_iterator visual = visuals.begin();
for (int visualIndex = 0;
visual != visuals.end();
++visual, ++visualIndex) {

// Set the viewport transform.
ivec2 size = visual->ViewportSize;
ivec2 lowerLeft = visual->LowerLeft;
glViewport(lowerLeft.x, lowerLeft.y, size.x, size.y);

// Set the light position.
vec4 lightPosition(0.25, 0.25, 1, 0);
glUniform3fv(m_uniforms.LightPosition, 1, lightPosition.Pointer());

// Set the model-view transform.
mat4 rotation = visual->Orientation.ToMatrix();
mat4 modelview = rotation * m_translation;
glUniformMatrix4fv(m_uniforms.Modelview, 1, 0, modelview.Pointer());

// Set the normal matrix.
// It's orthogonal, so its Inverse-Transpose is itself!
mat3 normalMatrix = modelview.ToMat3();
glUniformMatrix3fv(m_uniforms.NormalMatrix, 1,
0, normalMatrix.Pointer());

// Set the projection transform.
float h = 4.0f * size.y / size.x;
mat4 projectionMatrix = mat4::Frustum(-2, 2, -h / 2, h / 2, 5, 10);
glUniformMatrix4fv(m_uniforms.Projection, 1,
0, projectionMatrix.Pointer());

// Set the diffuse color.
vec3 color = visual->Color * 0.75f;
glVertexAttrib4f(m_attributes.Diffuse, color.x,
color.y, color.z, 1);

// Draw the surface.
int stride = 2 * sizeof(vec3);
const GLvoid* offset = (const GLvoid*) sizeof(vec3);
GLint position = m_attributes.Position;
GLint normal = m_attributes.Normal;
const Drawable& drawable = m_drawables[visualIndex];
glBindBuffer(GL_ARRAY_BUFFER, drawable.VertexBuffer);
glVertexAttribPointer(position, 3, GL_FLOAT,
GL_FALSE, stride, 0);
glVertexAttribPointer(normal, 3, GL_FLOAT, GL_FALSE,
stride, offset);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, drawable.IndexBuffer);
glDrawElements(GL_TRIANGLES, drawable.IndexCount,

That’s it for the ES 2.0 backend! Turn off the ForceES1 switch in, and you should see something very similar to the ES 1.1.
  •  iPhone 3D Programming : Adding Depth and Realism - Shaders Demystified
  •  Programming with DirectX : Transformation Demo
  •  Programming with DirectX : View Transformations
  •  Programming with DirectX : World Transformations
  •  Programming with DirectX : Projection Transformations
  •  iPhone 3D Programming : Adding Depth and Realism - Lighting Up (part 2)
  •  iPhone 3D Programming : Adding Depth and Realism - Lighting Up (part 1)
  •  iPhone 3D Programming : Adding Depth and Realism - Surface Normals (part 2)
  •  iPhone 3D Programming : Adding Depth and Realism - Surface Normals (part 1)
  •  iPhone 3D Programming : Adding Depth and Realism - Filling the Wireframe with Triangles
  •  iPhone 3D Programming : Adding Depth and Realism - Creating and Using the Depth Buffer
  •  iPhone 3D Programming : Adding Depth and Realism - Examining the Depth Buffer
  •  iPhone 3D Programming : HelloCone with Fixed Function
  •  iPhone 3D Programming : Vector Beautification with C++
  •  jQuery 1.3 : An image carousel
  •  jQuery 1.3 : Headline rotator
  •  Silverlight : Print a Document
  •  Silverlight : Capture a Webcam
  •  Silverlight : Make Your Application Run out of the Browser
  •  Silverlight : Put Content into a 3D Perspective
    Top 10
    Windows Vista : Installing and Running Applications - Launching Applications
    Windows Vista : Installing and Running Applications - Applications and the Registry, Understanding Application Compatibility
    Windows Vista : Installing and Running Applications - Practicing Safe Setups
    Windows Server 2003 : Domain Name System - Command-Line Utilities
    Microsoft .NET : Design Principles and Patterns - From Principles to Patterns (part 2)
    Microsoft .NET : Design Principles and Patterns - From Principles to Patterns (part 1)
    Brother MFC-J4510DW - An Innovative All-In-One A3 Printer
    Computer Planet I7 Extreme Gaming PC
    All We Need To Know About Green Computing (Part 4)
    All We Need To Know About Green Computing (Part 3)
    Most View
    Managing Xen : Xen Domain Configuration Files
    Programming WCF Services : Queued Services - Transactions
    Alienware M17X - Best In Class Gaming Performance
    We Help You Find Your Ideal Smartphone (Part 4)
    Choosing The Right Parts For Your Build (Part 3) - Picking the right video card
    Homeplug Problems In A Factory Setting (Part 2)
    Guide To Upgrades With The Greatest Effects (Part 2)
    Microsoft ASP.NET 4 : Profiles - Understanding Profiles
    VMware Fusion 5 - Your Mac is Virtually a PC
    Running Windows 8 (part 2) - Power Plans, Sleep Modes, and Shutdown
    The Ultimate PC Security Toolbox (Part 1)
    The big test … Inter Core Power (Part 1) - Acer Aspire Ethos 8951G
    Windows Vista : Build Your Network (part 4) - Troubleshoot Wireless Networks
    Mac - That Syncing Feeling
    Macbook Air vs. Ultrabook Platform (Part 2)
    Find Yourself With Geolocation Technology (Part 3)
    The Complete Guide To Garageband (Part 1)
    Generation I (For Insecure)?
    Kingston SSDNow mS100 64GB - One Of The Cheapest SSDs Around
    You Can Master RAW (Part 4)