How To Export Blender Models to OpenGL ES: Part 3/3

How To Export Blender Models to OpenGL ES: Part 3/3
Export Blender Models to OpenGL ES

Custom shaders FTW!

Welcome back to the three-part tutorial series that teaches you how to make an awesome 3D model viewer for iOS by exporting your Blender models to OpenGL ES.

Here’s an overview of the series:

  • Part 1: In the first part, you learned all about the OBJ geometry definition and file format, and used this new knowledge to create a command line tool to parse a simple Blender cube into suitable arrays for OpenGL ES. You also created a simple iOS OpenGL ES app that displayed your model.
  • Part 2: In the second part, you learned all about the MTL material definition and file format, which allowed you to add Blender materials to your model.
  • Part 3: You are here! Get ready to implement a simple lighting model for your 3D scene by writing your own OpenGL ES shaders! A shader is a dedicated program that instructs a graphics processor how to render a scene.

The Return Of The King, The Dark Knight Rises, The Last Crusade… all great trilogy conclusions, but none like this one: GLBlender3!

Getting Started

First, download the starter pack for this tutorial. Here’s a quick overview of each component—you can find more information in their dedicated tutorial sections later on. The contents are a bit different from those in Parts 1 and 2:

  • /Blender/: This folder contains the Blender scene for your new model (starship.blend). You won’t have to open it in this tutorial, but it’s there if you want to explore or modify it.
  • /Code/: Here you’ll find the Xcode project for your iOS app (GLBlender3). The iOS app is the same as GLBlender2 from Part 2, but with a different model (and additional resources). The project also has an extra class called Shader that you’ll use later on. Your command line tool is gone since you completed it in Part 2 and won’t be needing it here.
  • /Maya/: This folder contains the Maya scene for your new model (starship.mb). The model was built in Maya and then exported to Blender, so this is the original copy. Again, you won’t have to open it in this tutorial, but it’s there if you want to explore or modify it—if you have Autodesk Maya, of course.
  • /Resources/: This folder contains two subfolders: /cube/ and /starship/. They both contain all of your models’ files required by OpenGL ES and a few extras. Inside /starship/ you’ll find starship.obj and starship.mtl, which were exported from Blender and then processed by blender2opengles (from Part 2) to create starship.h and starship.c. starship_decal.png is a texture for your model that you’ll use later on. You can find similar files for your cube model from Part 2 in /cube/.

This time you won’t be accessing or modifying your directory as much, but it doesn’t hurt to be neat and tidy.

Your New Model: The Star (Fox) of the Show

Meet your new model! Based on the Arwing from Star Fox for SNES, I’m pleased to introduce starship. Here’s what the model looks like in Blender and Maya:



I chose this model for its popularity, simplicity and historical significance in 3D computer graphics. If you open starship.obj and starship.mtl with a text editor, you should be able to analyze the file quickly to understand the model a bit better. You could do the same with starship.h and starship.c, but these might be a little more daunting.

I’ve done all of the file exporting and processing for you already, so let’s jump straight into the app!

The Starship Model Viewer

Launch Xcode and open your GLBlender3 project.

Build and run! You’ll see your new model in all its polygonal glory.


Let the animation run for a little while and you’ll witness some odd behavior, most noticeably when the fins (blue) overlap with the wings (gray). GL_CULL_FACE works well with your previous cube model because there is no instance where two front-facing triangles overlap each other. With this new starship model, though, this happens many times. Culling is still very useful, but now you must also introduce a depth buffer.

Rendering With Depth

You’re going to use your storyboard to modify your rendering, so open MainStoryboard.storyboard and click on GLKit View. In the Utilities sidebar, click on the Attributes inspector tab and select 16 from the Depth Format drop-down menu. This creates a depth buffer for your OpenGL ES scene with a 16-bit entry for each pixel.

While you’re here, it’s not a bad idea to improve the quality of your rendering. In the same sidebar, select 4X from the Multisample drop-down menu. You should be careful with multisampling because it incurs a huge increase in memory/processing requirements, though with the simple starship model, that’s not an issue. With 4X enabled, you are essentially rendering to a frame buffer object (FBO) four times the size of your screen (2x width, 2x height) and then scaling the resulting image down to the proper screen size.

Take a look at the setup for both properties below:


Your view is set, but you also need to tell OpenGL ES that your scene has depth. Open MainViewController.m and add the following line to viewDidLoad, at the end of your OpenGL ES settings:


Next, scroll down to glkView:drawInRect: and add the depth buffer to your glClear() parameters, like so:


Build and run! Your model now renders properly and looks much better, too.


Implementing Shaders

You’re about to take a deep dive into 3D graphics programming by writing your own shaders! You learned a little bit about shaders in Part 2 and now you’ll get to see them in action. The gist is that they are programs that instruct the GPU how to draw your scene.

Shaders work in tandem, with the two components being the vertex shader and the fragment shader. You may have encountered them in one, two or three other tutorials before, but in case you missed them, here’s a quick overview:

  • A vertex shader is called once per vertex in your scene. In this tutorial, the vertex shader’s main job is to compute a value for gl_Position, which contains the position of the current vertex.
  • A fragment shader is called once per fragment in your scene. Fragments contain the raw data necessary to generate a pixel, including color, depth and transparency, whereas pixels are the actual points on your screen/image. The terms are used interchangeably, but it’s definitely worth knowing the difference. In this tutorial, the fragment shader’s main job is to compute a value for gl_FragColor, which contains the color of the current fragment.

Note: Both shaders get called on every frame, but one more than the other.

Your starship has 66 vertices, equaling the number of calls to your vertex shader.

For an iPhone 5, your scene contains 640×1136 pixels multi-sampled at 4X, meaning your fragment shader could get called up to 2,908,160 times—holy macaroni!

That’s a lot more than 66, although in this tutorial your fragment shader only gets called for every fragment on screen occupied by your model. Remember when I said multisampling is expensive? Well, there you go…

OK, it’s time for you to add some shaders to your project!

In Xcode, go to File\New\File…, choose the iOS\Other\Empty template and click Next. Name your new file Phong.vsh, uncheck the box next to your GLBlender3 target, placing the new file in your Shaders group, and click Create, as shown in the screenshot below:


Repeat this process once more, but name this second file Phong.fsh.

Next, open Phong.vsh and add the following code inside:

// Vertex Shader
static const char* PhongVSH = STRINGIFY
void main(void)
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);

Similarly, open up Phong.fsh and add the following:

// Fragment Shader
static const char* PhongFSH = STRINGIFY
void main(void)
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);

To get that nice syntax coloring, go to Editor\Syntax Coloring\GLSL. Do this now for both files. As you can see, shaders are short programs written in a C-like language called GLSL (OpenGL Shading Language). They are processed as strings, which is why they are wrapped inside STRINGIFY, a macro you’ll call later that returns a pointer to a string.

As mentioned before, the vertex shader writes out data to gl_Position, which expects an XYZW coordinate. In this tutorial, you only need to worry about the XYZ part, since W will always be 1.0 in representation of points, not vectors.

The fragment shader writes out data to gl_FragColor, which expects an RGBA color. For the most part, you’re only concerned with the RGB channels, but A will be useful later on when you implement transparency.

Implementing the Vertex Shader

OK, it’s time to beef up your vertex shader! Add the following lines to Phong.vsh, just above main():

// Attributes
attribute vec3 aPosition;
attribute vec3 aNormal;
// Uniforms
uniform mat4 uProjectionMatrix;
uniform mat4 uModelViewMatrix;
uniform mat3 uNormalMatrix;
// Varying
varying vec3 vNormal;

These are your shader variables and they’re separated into three main categories that determine the type and source of data they will receive from the main program:

  • Attributes typically change per vertex and are thus exclusive to the vertex shader. Your model’s positions and normals change per vertex, so you declare them as attributes.
  • Uniforms typically change per frame and can also be implemented by the fragment shader. Your scene’s projection and model-view matrices affect all vertices uniformly, so you declare them as uniforms.
  • Varying variables act as outputs from the vertex shader to the fragment shader—geometry first, coloring second.

Furthermore, vec variables refer to vectors with two, three or four components and mat variables refer to matrices with 2×2, 3×3 or 4×4 dimensions.

This tutorial will cover uNormalMatrix and vNormal later, but for now know that they’re both essential to your model’s normals and how they affect the lighting of your starship.

Finally, replace the contents of main() with the following:

vNormal = uNormalMatrix * aNormal;
gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);

You learned all about MVP matrices in Part 1 and this is how GLKBaseEffect implements them under the hood, along with GLKVertexAttribPosition (replaced by aPosition). See? Looking under the hood isn’t so scary. :]

Implementing the Fragment Shader

Let’s move on to your fragment shader. Add the following lines to Phong.fsh, just above main():

// Varying
varying highp vec3 vNormal;
// Uniforms
uniform highp vec3 uDiffuse;
uniform highp vec3 uSpecular;

Remember the scary calculations that showed how many more fragments than vertices there could be?


Every little bit of optimization helps avoid a nightmare filled with millions of fragments. This is why your fragment shader variables require a precision qualifier such as lowp, mediump or highp to determine the range and precision of your variable. You can read more about them in the docs, but your scene will be so simple that you’ll be totally fine just sticking to highp.

GLKEffectPropertyMaterial also sends its diffuse and specular color data to a shader, but you don’t get to see that. Here, you’re doing exactly the same and you get to define your own shading model! Let’s start with a simple one.

Replace the contents of main() with the following:

highp vec3 material = (0.5*uDiffuse) + (0.5*uSpecular);
gl_FragColor = vec4(material, 1.0);

With these two lines, you color your fragments 50% diffuse and 50% specular. :]

The CPU-GPU Bridge

Your shaders are all set, but they happen to live in the GPU, far away from your CPU-based app. In order to get all your model’s data from your app to your shaders, you need to build some sort of “bridge” to connect the two. It’s time to ditch GLSL and switch back to Objective-C!

Click File\New\File… and choose the iOS\Cocoa Touch\Objective-C class subclass template. Enter PhongShader for the class and Shader for the subclass. Make sure both checkboxes are unchecked, click Next and then click Create.

Open PhongShader.h and replace the existing file contents with the following:

#import "Shader.h"
@interface PhongShader : Shader
// Program Handle
@property (readwrite) GLint program;
// Attribute Handles
@property (readwrite) GLint aPosition;
@property (readwrite) GLint aNormal;
// Uniform Handles
@property (readwrite) GLint uProjectionMatrix;
@property (readwrite) GLint uModelViewMatrix;
@property (readwrite) GLint uNormalMatrix;
@property (readwrite) GLint uDiffuse;
@property (readwrite) GLint uSpecular;

With this list of properties, you create a set of handles for your GLSL variables—notice the identical naming. The only new variable is program, which I’ll explain in a moment. All properties are type GLint because they are only indices to your shader variables—they’re not the actual data.

Next, open PhongShader.m and replace the existing file contents with the following:

#import "PhongShader.h"
// 1
// Shaders
#define STRINGIFY(A) #A
#include "Phong.vsh"
#include "Phong.fsh"
@implementation PhongShader
- (id)init
    if(self = [super init])
        // 2
        // Program
        self.program = [self BuildProgram:PhongVSH with:PhongFSH];
        // 3
        // Attributes
        self.aPosition = glGetAttribLocation(self.program, "aPosition");
        self.aNormal = glGetAttribLocation(self.program, "aNormal");
        // 4
        // Uniforms
        self.uProjectionMatrix = glGetUniformLocation(self.program, "uProjectionMatrix");
        self.uModelViewMatrix = glGetUniformLocation(self.program, "uModelViewMatrix");
        self.uNormalMatrix = glGetUniformLocation(self.program, "uNormalMatrix");
        self.uDiffuse = glGetUniformLocation(self.program, "uDiffuse");
        self.uSpecular = glGetUniformLocation(self.program, "uSpecular");
    return self;

There’s a lot more going on here, so let’s walk through it step-by-step:

  1. There’s your STRINGIFY macro! This converts your shaders into strings so they can be processed by the GPU. You can learn more about stringification here.
  2. Since your shaders run on the GPU, they’re only readable at runtime with OpenGL ES. That means that the CPU needs to give the GPU special instructions to compile and link your shaders into a single program. The OpenGL ES 2.0 for iPhone Tutorial covers this process in detail, so give it a read for more information. The program creation functions from said tutorial have been encapsulated in your Shader class for an easy implementation here.
  3. You create your CPU-GPU handles by referencing your shader variable’s name and program. For calls to glGetAttribLocation(), the first parameter specifies the OpenGL ES program to be queried and the second parameter points to the name of the attribute within the program.
  4. Similar to the above, for calls to glGetUniformLocation(), the first parameter specifies the program to be queried and the second parameter points to the name of the uniform.

Good job—your bridge is looking pretty sturdy!

Shader Data

Time to send your shaders some meaningful data from your app.

Open MainViewController.m and import your new shader by adding the following line at the top of your file:

#import "PhongShader.h"

Just below, replace the line:

@property (strong, nonatomic) GLKBaseEffect* effect;


@property (strong, nonatomic) PhongShader* phongShader;

Xcode won’t like that and will flag a ton of errors for you, but you’re here to say goodbye to GLKBaseEffect and say hello to PhongShader. Xcode just needs to be more patient.

Next, remove the function createEffect and add the following right below viewDidLoad:

- (void)loadShader
    self.phongShader = [[PhongShader alloc] init];

Consequently, remove this line from viewDidLoad:

[self createEffect];

And add this one instead:

[self loadShader];

There are just a couple more to go, but you can already see that you are replacing GLKBaseEffect line-by-line. This way, I hope you can appreciate its underlying shader-based functionality.

Locate the function setMatrices and replace the (non-consecutive) lines:

self.effect.transform.projectionMatrix = projectionMatrix;
self.effect.transform.modelviewMatrix = modelViewMatrix;

With this:

glUniformMatrix4fv(self.phongShader.uProjectionMatrix, 1, 0, projectionMatrix.m);
glUniformMatrix4fv(self.phongShader.uModelViewMatrix, 1, 0, modelViewMatrix.m);

These calls are slightly more complex, but really you’re just telling OpenGL ES the location of your shader’s uniform variable and the data for this variable.

Finally, turn your attention to glkView:drawInRect: and replace the following lines:

// Positions
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, starshipPositions);
// Normals
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 0, starshipNormals);

With these:

// Positions
glVertexAttribPointer(self.phongShader.aPosition, 3, GL_FLOAT, GL_FALSE, 0, starshipPositions);
// Normals
glVertexAttribPointer(self.phongShader.aNormal, 3, GL_FLOAT, GL_FALSE, 0, starshipNormals);

Everything remains the same except the attribute index.

One more to go! Within the for loop, replace the following lines:

self.effect.material.diffuseColor = GLKVector4Make(starshipDiffuses[i][0], starshipDiffuses[i][1], starshipDiffuses[i][2], 1.0f);
self.effect.material.specularColor = GLKVector4Make(starshipSpeculars[i][0], starshipSpeculars[i][1], starshipSpeculars[i][2], 1.0f);

With these:

glUniform3f(self.phongShader.uDiffuse, starshipDiffuses[i][0], starshipDiffuses[i][1], starshipDiffuses[i][2]);
glUniform3f(self.phongShader.uSpecular, starshipSpeculars[i][0], starshipSpeculars[i][1], starshipSpeculars[i][2]);

And remove this:

// Prepare effect
[self.effect prepareToDraw];

Drum roll, please… Build and run!


Your starship appears with its geometry and materials intact. You’ve pretty much replaced GLKBaseEffect with your own shader, note for note, and got a pretty good result by simply mixing your diffuse and specular colors.

The reason your starship looks flat is because you haven’t implemented any lighting… yet. So give yourself a quick Hi-5 and let’s keep on truckin’. 😀

The Phong Reflection Model

Also called Phong illumination, this is the mathematical model that will light your scene. But first, let’s discuss the normal matrix.

Most lighting calculations are done in eye space to account for viewer-perceived effects such as specular highlights. The model-view matrix exists in object space, so you need a way to translate normals into eye space.

A normal is a vector exclusively representing an orientation, while the model-view matrix contains translation and scale data on top of that. The 3×3 upper-left matrix of the model-view matrix actually contains orientation data exclusively, but it can be distorted by a non-uniform scale operation, as shown below:


N is a normal on the triangle before any modifications. After stretching the triangle along the y-axis, N’ would be the resulting “normal” if the model-view matrix were applied to the normal N. Clearly, this result is wrong because it is no longer perpendicular to the triangle.

Normals are essential to Phong illumination, so you need a way to transform them into eye space while conserving their orientation—enter the normal matrix. A full mathematical explanation is available from Lighthouse3D, but all you need to know is that the normal matrix is the 3×3 inverse-transpose of the model-view matrix.

Once you’ve cured your math headache, let’s implement that definition! Open up MainViewController.m and add the following lines inside setMatrices, at the very bottom:

// Normal Matrix
// Transform normals from object-space to eye-space
bool invertible;
GLKMatrix3 normalMatrix = GLKMatrix4GetMatrix3(GLKMatrix4InvertAndTranspose(modelViewMatrix, &invertible));
    NSLog(@"MV matrix is not invertible");
glUniformMatrix3fv(self.phongShader.uNormalMatrix, 1, 0, normalMatrix.m);

That looks quite nice. GLKit is phenomenal in providing a top-notch 3D math library, which makes the normal matrix implementation a breeze. Don’t worry about invertible and its warning—you won’t have any problems with it in this tutorial, but GLKit requires it.

Moving on, who’s ready for more math? You are!

Phong components, from Wikipedia

Phong components, from Wikipedia

The image above describes the three main components of a surface in the Phong reflection model. Let’s introduce a new term, ambient, which accounts for the uniform lighting that affects an entire scene equally, caused by the scattering of light. Think of it as the base intensity and color of your scene; in the real world, rarely do you find yourself in “pitch black” conditions. In the above graphic, the object is rendered under a pure white light, which your implementation will also assume.

There’s an additional term, shininess, not pictured above, which simply defines the size of your material’s specular highlights. There is an inverse relationship between shininess and highlights: a shiny mirror has a clear reflection with small highlights, whereas glossy paper has a blurry reflection with large highlights.

Phong vectors, modified from Wikipedia

Phong vectors, modified from Wikipedia

The Phong reflection model requires four vectors to calculate the perceived color and intensity of light on a surface point:

  • L, which is the directional vector from the surface point to the light source.
  • N, which is the normal of the surface point.
  • R, which is the reflection of the ray of light L against the normal N.
  • V, which is the direction from the surface point towards the viewer.

All vectors are normalized, since this model only uses the direction of these vectors to perform the scene’s illumination. More sophisticated lighting models, like area or spotlights, factor in the distance of these objects.

Perfect! Given the physical complexities of real light, the Phong reflection model does a great job of simplifying things into four material properties and four surface vectors. The full equation combining all components with a single light source, for a surface point Ip, is:


It may seem long, but it’s very easy to analyze by parts:

  • Material: Ambient (ka); Diffuse (kd); Specular (ks); Shininess (α).
  • Light: Ambient (ia); Diffuse (id); Specular (is).
  • Vectors: Light (L); Normal (N); Reflection (R); Viewer (V).

Furthermore, the equation is well separated into ambient, diffuse and specular terms, where L.N and (R.V)^α may even be referred to as the diffuse and specular factors, respectively.

OK, its time to implement the Phong reflection model! You’ll be writing your lighting algorithm in the fragment shader, a practice also referred to as per-pixel lighting, as opposed to per-vertex lighting in the vertex shader. In Part 1, you calculated your scene’s lighting by using GLKLightingTypePerPixel, which is exactly what you’re replicating here. Straight from the docs, here are the definitions for both lighting types:

  • Per-vertex lighting indicates that the lighting calculations are performed at each vertex in a triangle and then interpolated across the triangle.
  • Per-pixel lighting indicates that the inputs to the lighting calculation are interpolated across a triangle and the lighting calculations are performed at each fragment.

So basically, use per-vertex lighting to boost your performance and use per-pixel lighting to improve your image quality. In this case, your scene is so small that you can use per-pixel lighting for a better-looking model without incurring a noticeable performance hit.

Open up Phong.fsh and replace the contents of main() with the following:

// 1
// Material
highp vec3 ka = vec3(0.05);
highp vec3 kd = uDiffuse;
highp vec3 ks = uSpecular;
highp float alpha = 1.0;
// 2
// Light
highp vec3 ia = vec3(1.0);
highp vec3 id = vec3(1.0);
highp vec3 is = vec3(1.0);
// 3
// Vectors
highp vec3 L = normalize(vec3(1.0, 1.0, 1.0));
highp vec3 N = normalize(vNormal);
highp vec3 V = normalize(vec3(0.0, 0.0, 1.0));
highp vec3 R = reflect(L, N);
// 4
// Illumination factors
highp float df = max(0.0, dot(L, N));
highp float sf = pow(max(0.0, dot(R, V)), alpha);
// 5
// Phong reflection equation
highp vec3 Ip = ka*ia + kd*id*df + ks*is*sf;
gl_FragColor = vec4(Ip, 1.0);

All in all, your shader is still quite a short program performing a specific function. Let’s see what’s going on:

  1. You carry on the diffuse (kd) and specular (ks) components of your material from your starship model. Ambient shading (ka) is 5% white and shininess (alpha) is 1 (no effect on the highlights). Since your starship model is made up of hard and flat triangular faces, it doesn’t have the curved shape necessary to really showcase specular highlights.
  2. All light components are pure white, meaning that your model’s materials are in charge.
  3. As mentioned before, all vectors in the Phong reflection model are normalized. Just like your GLKBaseEffect implementation, your light source is in the top-right corner of the scene, behind the viewer. Similarly, the viewer is looking at the model straight-on, with the line of sight into the screen.
  4. There is no such thing as negative lighting (true black is 0.0), so the max() function ensures your diffuse and specular factors are always in range. If you’re wondering, a negative dot product between two vectors would occur if they are facing in opposite directions.
  5. Finally, plug all of your components into the equation.

Build and run! Your starship looks fantastic and you can now see all the different shading intensities and even the cyan color that was hard to appreciate with GLKBaseEffect.


That was a very hard section, but I hope the rendering was worth it! You’re quickly turning into a fearless graphics programmer. :O

Adding a Decal Texture

Textures and materials aren’t mutually exclusive, so let’s bring textures back into your app (but don’t call it a comeback). You’ll be implementing a decal texture, which is a sticker-like effect transferred onto a surface that retains its own characteristics, like vinyl car graphics. Take a look at starship_decal.png in your /Resources/starship/ folder to give yourself an idea of what you’ll be adding to your model. You’ll start the implementation in your shaders.

Open Phong.vsh and add the following variables to your shader:

attribute vec2 aTexel;
varying vec2 vTexel;

Then, add the following line to main():

vTexel = aTexel;

With these three lines, you’re simply passing your aTexel attribute to your fragment shader via vTexel. Remember that fragment shaders can’t access attributes, so the varying type solves your problem here.

Next, open Phong.fsh and add vTexel to your list of variables:

varying highp vec2 vTexel;

Also add the following to your list of uniforms:

uniform sampler2D uTexture;

sampler2D is a special GLSL variable exclusively used for 2D texture access. It’s attached to a predetermined texture—more on this later.

Next, delete the statement assigning a value to gl_FragColor, your current output, and instead add the following lines at the bottom of main():

// Decal
highp vec4 decal = texture2D(uTexture, vTexel);
// Surface
highp vec3 surface;
if(decal.a > 0.0)
    surface = decal.rgb;
    surface = Ip;
gl_FragColor = vec4(surface, 1.0);

texture2d() extracts the RGBA color value of a texture (uTexture) at a certain texel point (vTexel). Since a decal works like a sticker, your shader will make a decision based on the alpha value of decal: if there is a graphic, stick it onto your model’s surface; if there isn’t a graphic, let the calculated Phong illumination define your model’s surface. Notice that your scene affects neither your decal’s color nor its intensity.

With your shaders set, let’s move onto their bridge. Open PhongShader.h and add the following line to your list of attribute handles:

@property (readwrite) GLint aTexel;

Similarly, add the following to your list of uniform handles:

@property (readwrite) GLint uTexture;

Next, open PhongShader.m and add the following lines to init in their respective attribute and uniform sections.

self.aTexel = glGetAttribLocation(self.program, "aTexel");
self.uTexture = glGetUniformLocation(self.program, "uTexture");

There, your updated bridge is all set! Let’s move onto the actual texture now.

Open MainViewController.m and add the following function, just below viewDidLoad:

- (void)loadTexture
    NSDictionary* options = @{GLKTextureLoaderOriginBottomLeft: @YES};
    NSError* error;
    NSString* path = [[NSBundle mainBundle] pathForResource:@"starship_decal.png" ofType:nil];
    GLKTextureInfo* texture = [GLKTextureLoader textureWithContentsOfFile:path options:options error:&error];
    if(texture == nil)
        NSLog(@"Error loading file: %@", [error localizedDescription]);
    glUniform1i(self.phongShader.uTexture, 0);

You’ve already seen the majority of this texture-loading process in Part 1, so I’ll only discuss the new gl-prefixed functions:

  • glBindTexture() specifies the target to which the texture is bound. In this case, your decal is a single 2D texture.
  • glUniform1i() sends the texture to your shader. You send a 0 because you only have one active texture in your program, at the first position of 0.

Call your new function by adding the following to viewDidLoad, at the very end:

// Load texture
[self loadTexture];

Finally, send your starship’s texel data to your shader by adding the following lines to glkView:drawInRect:, just after the setup for your other attributes (positions and normals) and before the loop that renders the materials:

// Texels
glVertexAttribPointer(self.phongShader.aTexel, 2, GL_FLOAT, GL_FALSE, 0, starshipTexels);

Build and run! Your starship sports a cool decal finish—a proud testament of your achievements. :]


Adding a Second Model

While your starship looks amazing, it would be a shame not to see your cube from Part 1 and Part 2 rendered with your new shaders. It would be too easy to just replace every instance of starship with cube in your source code, so instead you’ll implement a new structure extendable for multiple models.

Open MainViewController.m and add the following line to the top of your file:

#import "cube.h"

Then, add the following lines just below:

typedef enum Models

This is an enumerator to easily reference your models by name. Create a variable for said enumerator by adding the following line to your @interface variables:

Models  _model;

Then initialize it to your cube model by adding the following to viewDidLoad:

_model = M_CUBE;

The next steps in this section are essentially a light refactor of your code to create a variable-dependent rendering scenario based on the value of _model. First up is your texture loader.

In your function loadTexture, replace the line:

NSString* path = [[NSBundle mainBundle] pathForResource:@"starship_decal.png" ofType:nil];

With the following:

NSString* path;
    case M_CUBE:
        path = [[NSBundle mainBundle] pathForResource:@"cube_decal.png" ofType:nil];
    case M_STARSHIP:
        path = [[NSBundle mainBundle] pathForResource:@"starship_decal.png" ofType:nil];

This simple switch statement makes sure your program loads the correct texture for each model.

Now you’ll refactor your app’s rendering loop. Add the following functions at the end of MainViewController.m, before the @end statement:

- (void)renderCube
    glVertexAttribPointer(self.phongShader.aPosition, 3, GL_FLOAT, GL_FALSE, 0, cubePositions);
    glVertexAttribPointer(self.phongShader.aTexel, 2, GL_FLOAT, GL_FALSE, 0, cubeTexels);
    glVertexAttribPointer(self.phongShader.aNormal, 3, GL_FLOAT, GL_FALSE, 0, cubeNormals);
    for(int i=0; i<cubeMaterials; i++)
        glUniform3f(self.phongShader.uDiffuse, cubeDiffuses[i][0], cubeDiffuses[i][1], cubeDiffuses[i][2]);
        glUniform3f(self.phongShader.uSpecular, cubeSpeculars[i][0], cubeSpeculars[i][1], cubeSpeculars[i][2]);
        glDrawArrays(GL_TRIANGLES, cubeFirsts[i], cubeCounts[i]);
- (void)renderStarship
    glVertexAttribPointer(self.phongShader.aPosition, 3, GL_FLOAT, GL_FALSE, 0, starshipPositions);
    glVertexAttribPointer(self.phongShader.aTexel, 2, GL_FLOAT, GL_FALSE, 0, starshipTexels);
    glVertexAttribPointer(self.phongShader.aNormal, 3, GL_FLOAT, GL_FALSE, 0, starshipNormals);
    for(int i=0; i<starshipMaterials; i++)
        glUniform3f(self.phongShader.uDiffuse, starshipDiffuses[i][0], starshipDiffuses[i][1], starshipDiffuses[i][2]);
        glUniform3f(self.phongShader.uSpecular, starshipSpeculars[i][0], starshipSpeculars[i][1], starshipSpeculars[i][2]);
        glDrawArrays(GL_TRIANGLES, starshipFirsts[i], starshipCounts[i]);

These functions are virtually identical to one another, each responsible for rendering a model. You’re lifting the rendering process directly from glkView:drawInRect, which is about to be a lot lighter. Replace said function with the new approach below:

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
    // Set matrices
    [self setMatrices];
    // Render model
        case M_CUBE:
            [self renderCube];
        case M_STARSHIP:
            [self renderStarship];

That looks a lot nicer, doesn’t it? The rendering loop can get quite complicated in a large graphics scene, so allowing simple calls to functions like renderCube or renderStarship makes life much easier.

Build and run… Whoops, your cube is way too close! It’s actually twice the size of your starship model, so you’ll have to scale it down.

Locate the function setMatrices and look for the line:

glUniformMatrix4fv(self.phongShader.uModelViewMatrix, 1, 0, modelViewMatrix.m);

Immediately above it, after you translate and rotate your model-view matrix, add the following lines:

    case M_CUBE:
        modelViewMatrix = GLKMatrix4Scale(modelViewMatrix, 0.5f, 0.5f, 0.5f);
    case M_STARSHIP:
        modelViewMatrix = GLKMatrix4Scale(modelViewMatrix, 1.0f, 1.0f, 1.0f);

Build and run! Your cube is now 50% of its former size and it looks great. :]


Switching Between Models

Since you now have two models to show off, as the final step in this tutorial you’ll be implementing a simple UI element to switch between the two while your app is running.

Open MainStoryboard.storyboard and add a Segmented Control to your GLKit View. With your new segmented control selected, look to the sidebar on your right and click on the Attributes inspector tab. Choose 2 for the number of Segments and name them Cube and Starship (in the Title field).

The image below illustrates this whole process:


Next, show the Assistant editor in Xcode with MainStoryboard.storyboard in the left window and MainViewController.m in the right window. Click on your Segmented Control, then control+drag a connection from it to MainViewController.m. This creates an Action in the file.

Name the new action selectModel with Type UISegmentedControl. Make sure the Event for this action is Value Changed and the Argument is Sender. Use the image below to help yourself out:


Go back to the Standard editor and open MainViewController.m. Add the following lines inside your new selectModel: function:

self.paused = YES;
int m = (int)sender.selectedSegmentIndex;
    case 0:
        _model = M_CUBE;
    case 1:
        _model = M_STARSHIP;
[self loadTexture];
self.paused = NO;

With this, you have a control that determines which model to render. When a user selects a new model, the app loads its texture with a simple call to loadTexture. You want to treat OpenGL ES nicely, so performing the model switch between two calls to self.paused ensures you aren’t switching data in the middle of a render.

Build and run! Use your new UI to switch between your models with ease. The transition and experience should all be very smooth—good job!


Congratulations, you’ve completed the entire Blender to OpenGL ES tutorial series!

Where to Go From Here?

Here is the completed project with all of the code and resources from this part of the Blender to OpenGL ES tutorial. You can also find its repository on GitHub.

This was a challenging series, with Part 3 being particularly rewarding because it took you deep into OpenGL ES shaders and lighting/shading theory. More impressively, you pretty much replicated a big portion of Apple’s very own GLKit framework.

Now that you’ve completed this three-part series, you should have a very good understanding of back-end computer graphics, command line tools, geometry and materials, mathematical illumination models, shaders, CPU-GPU communication, rendering and a whole heap of low-level graphics! These are all advanced programming topics not for the faint of heart, and you should feel proud of yourself for getting through them.

There is so much that you can do now, but here are a few suggestions:

If you have any questions, comments or suggestions, feel free to join the discussion below!

How To Export Blender Models to OpenGL ES: Part 3/3 is a post from: Ray Wenderlich

The post How To Export Blender Models to OpenGL ES: Part 3/3 appeared first on Ray Wenderlich.



Write a comment