MULTIMEDIA

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.


Warning:

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);
glUseProgram(program);

// 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.
glEnableVertexAttribArray(m_attributes.Position);
glEnableVertexAttribArray(m_attributes.Normal);
glEnable(GL_DEPTH_TEST);

// 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);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

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,
GL_UNSIGNED_SHORT, 0);
}
}

That’s it for the ES 2.0 backend! Turn off the ForceES1 switch in GLView.mm, and you should see something very similar to the ES 1.1.
Other  
  •  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
  •  
    Most View
    Introduction to Transport-Level Security in Windows Server 2008 R2
    Amiko Alien 2 Plus (Part 2)
    B.M.C. Audio upgrades Arcadia
    HP Envy TouchSmart 4 - Plain And Simple
    Samsung Series 5 550 Chromebook – A real “paperweight” ?
    Samsung Chromebook - Is It Worth The Value Equation (Part 1)
    Nvidia 3D Vision 2 Wireless Glasses kit Unboxing
    Windows 7 : Troubleshooting Problems with Windows Media Center
    PC Specialist Vanquish 650Ti - Excellent General Performance
    Refrigerator Is Calling (Part 2)
    Top 10
    Nvidia GeForce GTX Titan 6 GB Graphics Card Review (Part 6)
    Nvidia GeForce GTX Titan 6 GB Graphics Card Review (Part 5)
    Nvidia GeForce GTX Titan 6 GB Graphics Card Review (Part 4)
    Nvidia GeForce GTX Titan 6 GB Graphics Card Review (Part 3)
    Nvidia GeForce GTX Titan 6 GB Graphics Card Review (Part 2)
    Nvidia GeForce GTX Titan 6 GB Graphics Card Review (Part 1)
    Nook HD - A High-Definition Tablet With The Heart Of A Reader (Part 4)
    Nook HD - A High-Definition Tablet With The Heart Of A Reader (Part 3)
    Nook HD - A High-Definition Tablet With The Heart Of A Reader (Part 2)
    Nook HD - A High-Definition Tablet With The Heart Of A Reader (Part 1)