Resources

Use the Dev Guide to learn about specific features and how best to integrate these into your app.

Replacing the Teapot

Some pointers on replacing the teapot model with custom models using OpenGL.

Vuforia uses OpenGL ES for rendering 3D content.  The samples show how to render simple static (non-animated) models using both OpenGL ES 1.1 and 2.0.  The biggest difference between OpenGL ES 1.1 and 2.0 is the use of shaders in 2.0 instead of the fixed function pipeline in 1.1.  GLES 2.0 is recommended for performance reasons, and also because some samples (e.g. BackgroundTextureAccess and VideoPlayback) only support GLES 2.0.  The ImageTargets and Dominoes samples are the only ones that show a 1.1 rendering pathway.  To enable GLES 1.1 rendering in these samples, open the jni/Android.mk file and set the USE_OPENGL_ES_1_1 flag to true.

OpenGL calls in the renderFrame method

We can break down the renderFrame method in ImageTargets.cpp to examine the various OpenGL calls.  We'll focus on the GLES 2.0 pathway here. 

First, for each active (visible) trackable we create a modelview matrix from its pose.  Then we apply transforms to this matrix in order to scale and position our model.  Finally we multiply it by the projection matrix to create the MVP (model view projection) matrix that brings the 3D content to the screen.  Later in the code, we bind this MVP matrix to the uniform variable in our shader.  Each vertex of our 3D model will be multiplied by this matrix, effectively bringing that vertex from world space to screen space (the transforms are actually object > world > eye > window).

QCAR::Matrix44F modelViewMatrix =
    QCAR::Tool::convertPose2GLMatrix(trackable->getPose());
...
QCAR::Matrix44F modelViewProjection;
SampleUtils::translatePoseMatrix(0.0f, 0.0f, kObjectScale,
                                 &modelViewMatrix.data[0]);
SampleUtils::scalePoseMatrix(kObjectScale, kObjectScale, kObjectScale,
                             &modelViewMatrix.data[0]);
SampleUtils::multiplyMatrix(&projectionMatrix.data[0],
                            &modelViewMatrix.data[0] ,
                            &modelViewProjection.data[0]);
...
glUniformMatrix4fv(mvpMatrixHandle, 1, GL_FALSE,
                   (GLfloat*)&modelViewProjection.data[0] );

For more on positioning 3D content on the target see this article: Positioning 3D Content

For more on OpenGL transforms see this page: http://www.songho.ca/opengl/gl_transform.html

Next, we need to feed the model arrays (vertices, normals, and texture coordinates) to our shader.  We start by binding our shader, then assigning our model arrays to the attribute fields in our shader (see the vertex shader in CubeShaders.h).  Then we enable these attribute arrays:

glUseProgram(shaderProgramID);
glVertexAttribPointer(vertexHandle, 3, GL_FLOAT, GL_FALSE, 0,
                      (const GLvoid*) &teapotVertices[0]);
glVertexAttribPointer(normalHandle, 3, GL_FLOAT, GL_FALSE, 0,
                      (const GLvoid*) &teapotNormals[0]);
glVertexAttribPointer(textureCoordHandle, 2, GL_FLOAT, GL_FALSE, 0,
                      (const GLvoid*) &teapotTexCoords[0]);
glEnableVertexAttribArray(vertexHandle);
glEnableVertexAttribArray(normalHandle);
glEnableVertexAttribArray(textureCoordHandle);

Note that our samples don't actually make use of the normal array, but you could optionally do lighting calculations in the shader using the normals.  If you want to use a model without normals simply comment out those lines. 

Finally, we activate the correct texture unit (typically 0 unless you have multiple textures) and bind our texture.  Then we render the model using the glDrawElements call.

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, thisTexture->mTextureID);
...
glDrawElements(GL_TRIANGLES, NUM_TEAPOT_OBJECT_INDEX, GL_UNSIGNED_SHORT,
               (const GLvoid*) &teapotIndices[0]);

Note that glDrawElements takes an array of indices.  This allows us to randomly index into the other model arrays when building the triangles that make up our model.  Sometimes you have a model without indices, however.  This means that the model arrays are meant to be read linearly, that is, the triangles are listed in consecutive order.  In that case, you would use the glDrawArrays method instead:

glDrawArrays(GL_TRIANGLES, 0, numVertices);

Swapping out the model

To change out the teapot model with your own model, you need to start by obtaining the vertex array for your model (and optionally texture coordinates and normals).  Our samples use a simple header format to store the model data as arrays, see Teapot.h for example.

Once you have the arrays, you simply need to switch out the teapotVertices, teapotNormals, teapotTexCoords, and teapotIndices variables in the code above.  Also change the NUM_TEAPOT_OBJECT_INDEX, and be sure to change the GL_UNSIGNED_SHORT type in the glDrawElements call if your model uses and index array with a different type.  If your model doesn't have indices use glDrawArrays instead, as described above. 

Finally, you'll probably want to change out the texture.  See this article on advice on working with textures: Adding Textures

Problem:

  • My rendered model has holes in it.

Solution:

OpenGL ES only supports triangles (not quads).  Make sure you have triangulated the model, any 3D modeling software should be able to do this.  It is also possible the model has triangles winding in different directions (some clockwise, some counter-clockwise).  This can happen when the normals are incorrect in the modeling software.  In this case, try turning off culling: glDisable(GL_CULL_FACE);

Problem:

  • My application crashes as soon as it renders something.

Solution:

Check to make sure that your call to glDrawElements or glDrawArrays is using the correct number of elements.  Also, for glDrawElements make sure you are using the correct index type (e.g. GL_UNSIGNED_SHORT vs. GL_UNSIGNED_BYTE).

Keywords: Model, Teapot, OpenGL, Transforms