OpenGL ES SDK for Android ARM Developer Center
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Instanced Tessellation

The application displays a rotating solid torus with a low-polygon wireframed mesh surrounding it. The torus is drawn by means of instanced tessellation technique using OpenGL ES 3.0.

Introduction

It's assumed that you have read and understood all of the mechanisms described in Asset Loading,Simple Triangle and Texture Cube.

Overview

InstancedTessellation_android.png
The application displays a rotating solid torus with a low-polygon wireframed mesh surrounding it.

To perform instanced tessellation, we need to divide our model into several patches. Each patch is densely packed with triangles and improves the effect of round surfaces. In the first stage of tessellation, patches consist of vertices placed in a form of a square. Once passed to the shader, they are transformed into Bezier surfaces on the basis of control points stored in uniform blocks. Each instance of a draw call renders next part of the torus.

The following application instantiates two classes, these manage both the solid torus model and the wireframe that surrounds it. The first class is responsible for configuration of a program with shaders capable of instanced drawing, initialization of data buffers and handling instanced draw calls. To simplify the mathematics and satisfy conditions for C1 continuity between patches, we assume that torus is constructed by 12 circles, each also defined by 12 points. In that manner, we are able to divide "big" and "small" circle of torus into four quadrants and build Bezier surfaces that approximate perfectly round shapes. For that purpose, the control points cannot lay on the surface of the torus, but have to be distorted as appropriate.

The second class manages components corresponding to the wireframe. It uses vertices placed on the surface of torus and uses a simple draw call with GL_LINES mode. The size of its "small circle" is slightly bigger than the corresponding dimension of the solid torus, so there is a space between both the models.

Common elements for both classes are placed in an abstract Torus class.

Setup Graphics

First of all, we need to generate coordinates of the model that we will render. This is achieved in constructors of WireframeTorus and InstancedSolidTorus classes.

wireframeTorus = new WireframeTorus(torusRadius, circleRadius + distance);
solidTorus = new InstancedSolidTorus(torusRadius, circleRadius);

Please note that we wanted the wireframe object to be a little bit larger than the solid one, this is why the circleRadius is increased by distance. Thanks to that we will see a solid object which is surrounded by the wireframe object.

The coordinates are generated in the same way for both models as shown below

void TorusModel::generateVertices(float torusRadius, float circleRadius, unsigned int circlesCount, unsigned int pointsPerCircleCount, float* vertices)
{
if (vertices == NULL)
{
LOGE("Cannot use null pointer while calculating torus vertices.");
return;
}
/* Index variable. */
unsigned int componentIndex = 0;
for (unsigned int horizontalIndex = 0; horizontalIndex < circlesCount; ++horizontalIndex)
{
/* Angle in radians on XZ plane. */
float xyAngle = (float) horizontalIndex * 2.0f * M_PI / circlesCount;
for (unsigned int verticalIndex = 0; verticalIndex < pointsPerCircleCount; ++verticalIndex)
{
/* Angle in radians on XY plane. */
float theta = (float) verticalIndex * 2.0f * M_PI / pointsPerCircleCount;
/* X coordinate. */
vertices[componentIndex++] = (torusRadius + circleRadius * cosf(theta)) * cosf(xyAngle);
/* Y coordinate. */
vertices[componentIndex++] = circleRadius * sinf(theta);
/* Z coordinate. */
vertices[componentIndex++] = (torusRadius + circleRadius * cosf(theta)) * sinf(xyAngle);
/* W coordinate. */
vertices[componentIndex++] = 1.0f;
}
}
}

There are separate program objects that are used while rendering a requested model. There is no lighting applied on the wireframe model nor complicated vertex translations which makes things much easier. Please look at the shaders that are used while rendering the wireframe model.

Vertex shader source for the wireframe torus

/* Input vertex coordinates. */
in vec4 position;
/* Constant transformation matrices. */
uniform mat4 cameraMatrix;
uniform mat4 scaleMatrix;
/* Coefficients of rotation needed for configuration of rotation matrix. */
uniform vec3 rotationVector;
void main()
{
/* Matrix rotating Model-View matrix around X axis. */
mat4 xRotationMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, cos(radians(rotationVector.x)), sin(radians(rotationVector.x)), 0.0,
0.0, -sin(radians(rotationVector.x)), cos(radians(rotationVector.x)), 0.0,
0.0, 0.0, 0.0, 1.0);
/* Matrix rotating Model-View matrix around Y axis. */
mat4 yRotationMatrix = mat4( cos(radians(rotationVector.y)), 0.0, -sin(radians(rotationVector.y)), 0.0,
0.0, 1.0, 0.0, 0.0,
sin(radians(rotationVector.y)), 0.0, cos(radians(rotationVector.y)), 0.0,
0.0, 0.0, 0.0, 1.0);
/* Matrix rotating Model-View matrix around Z axis. */
mat4 zRotationMatrix = mat4( cos(radians(rotationVector.z)), sin(radians(rotationVector.z)), 0.0, 0.0,
-sin(radians(rotationVector.z)), cos(radians(rotationVector.z)), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
/* Model-View matrix transformations. */
modelViewMatrix = scaleMatrix;
modelViewMatrix = xRotationMatrix * modelViewMatrix;
modelViewMatrix = yRotationMatrix * modelViewMatrix;
modelViewMatrix = zRotationMatrix * modelViewMatrix;
modelViewMatrix = cameraMatrix * modelViewMatrix;
/* Configure Model-View-ProjectionMatrix. */
modelViewProjectionMatrix = projectionMatrix * modelViewMatrix;
/* Set vertex position in Model-View-Projection space. */
gl_Position = modelViewProjectionMatrix * position;
}

Fragment shader source for the wireframe torus

precision mediump float;
uniform vec4 color;
/* Output variable. */
out vec4 fragColor;
void main()
{
fragColor = color;
}

If we now would like to render the wireframe torus on the screen, it's enough to issue the glDrawElements() call using GL_LINES mode. Of course there should be a proper program object and vertex array object used.

void WireframeTorus::draw(float* rotationVector)
{
GLint rotationVectorLocation = GL_CHECK(glGetUniformLocation(programID, "rotationVector"));
/* Set required elements to draw mesh torus. */
GL_CHECK(glUseProgram(programID));
GL_CHECK(glBindVertexArray(vaoID));
GL_CHECK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBufferID));
/* Pass Model-View matrix elements to the shader. */
GL_CHECK(glUniform3fv(rotationVectorLocation, 1, rotationVector));
/* Draw lines described by previously determined indices. */
GL_CHECK(glDrawElements(GL_LINES, indicesCount, GL_UNSIGNED_INT, 0));
}

Please look into the shader objects that we are using to render the solid torus. The situation here is more complicated as there is some lighting applied and instanced drawing technique with vertices transformed into Bezier surface is issued.

Vertex shader source for the solid torus

/* Number of control points in one dimension for a patch.. */
const uint patchDimension = 4u;
/* Total number of control points in a patch. */
const uint controlPointsPerPatchCount = patchDimension * patchDimension;
/* Number of quads in a patch. */
const uint quadsInPatchCount = (patchDimension - 1u) * (patchDimension - 1u);
/* Total number of vertices in a patch. */
const uint verticesCount = 144u;
/* Input patch vertex coordinates. */
in vec2 patchUVPosition;
/* Constant transofrmation matrices. */
uniform mat4 cameraMatrix;
uniform mat4 scaleMatrix;
/* Coefficients of rotation needed for configuration of rotation matrix. */
uniform vec3 rotationVector;
/* Uniform block that stores control mesh indices. */
uniform ControlPointsIndices
{
uint indices[controlPointsPerPatchCount * verticesCount / quadsInPatchCount];
};
/* Uniform block that stores control mesh vertices. */
uniform ControlPointsVertices
{
vec4 vertices[verticesCount];
};
/* Normal vector set in Model-View-Projection space. */
out vec3 modelViewProjectionNormalVector;
void main()
{
const float pi = 3.14159265358979323846;
/* Array storing control vertices of current patch. */
vec4 controlVertices[controlPointsPerPatchCount];
/* Initialize array of current control vertices. */
for (uint i = 0u; i < controlPointsPerPatchCount; ++i)
{
controlVertices[i] = vertices[indices[uint(gl_InstanceID) * controlPointsPerPatchCount + i]];
}
/* Coefficients of Bernstein polynomials. */
vec2 bernsteinUV0 = (1.0 - patchUVPosition) * (1.0 - patchUVPosition) * (1.0 - patchUVPosition);
vec2 bernsteinUV1 = 3.0 * patchUVPosition * (1.0 - patchUVPosition) * (1.0 - patchUVPosition);
vec2 bernsteinUV2 = 3.0 * patchUVPosition * patchUVPosition * (1.0 - patchUVPosition);
vec2 bernsteinUV3 = patchUVPosition * patchUVPosition * patchUVPosition ;
/* Position of a patch vertex on Bezier surface. */
vec3 position = bernsteinUV0.x * (bernsteinUV0.y * controlVertices[ 0].xyz + bernsteinUV1.y * controlVertices[ 1].xyz + bernsteinUV2.y * controlVertices[ 2].xyz + bernsteinUV3.y * controlVertices[ 3].xyz) +
bernsteinUV1.x * (bernsteinUV0.y * controlVertices[ 4].xyz + bernsteinUV1.y * controlVertices[ 5].xyz + bernsteinUV2.y * controlVertices[ 6].xyz + bernsteinUV3.y * controlVertices[ 7].xyz) +
bernsteinUV2.x * (bernsteinUV0.y * controlVertices[ 8].xyz + bernsteinUV1.y * controlVertices[ 9].xyz + bernsteinUV2.y * controlVertices[10].xyz + bernsteinUV3.y * controlVertices[11].xyz) +
bernsteinUV3.x * (bernsteinUV0.y * controlVertices[12].xyz + bernsteinUV1.y * controlVertices[13].xyz + bernsteinUV2.y * controlVertices[14].xyz + bernsteinUV3.y * controlVertices[15].xyz);
/* Matrix rotating Model-View matrix around X axis. */
mat4 xRotationMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, cos(radians(rotationVector.x)), sin(radians(rotationVector.x)), 0.0,
0.0, -sin(radians(rotationVector.x)), cos(radians(rotationVector.x)), 0.0,
0.0, 0.0, 0.0, 1.0);
/* Matrix rotating Model-View matrix around Y axis. */
mat4 yRotationMatrix = mat4( cos(radians(rotationVector.y)), 0.0, -sin(radians(rotationVector.y)), 0.0,
0.0, 1.0, 0.0, 0.0,
sin(radians(rotationVector.y)), 0.0, cos(radians(rotationVector.y)), 0.0,
0.0, 0.0, 0.0, 1.0);
/* Matrix rotating Model-View matrix around Z axis. */
mat4 zRotationMatrix = mat4( cos(radians(rotationVector.z)), sin(radians(rotationVector.z)), 0.0, 0.0,
-sin(radians(rotationVector.z)), cos(radians(rotationVector.z)), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
/* Model-View matrix transformations. */
modelViewMatrix = scaleMatrix;
modelViewMatrix = xRotationMatrix * modelViewMatrix;
modelViewMatrix = yRotationMatrix * modelViewMatrix;
modelViewMatrix = zRotationMatrix * modelViewMatrix;
modelViewMatrix = cameraMatrix * modelViewMatrix;
/* Configure Model-View-ProjectionMatrix. */
modelViewProjectionMatrix = projectionMatrix * modelViewMatrix;
/* Set vertex position in Model-View-Projection space. */
gl_Position = modelViewProjectionMatrix * vec4(position, 1.0);
/* Angle on the "big circle" of torus. */
float phi = (patchUVPosition.x + mod(float(gl_InstanceID), 4.0)) * pi / 2.0;
/* Angle on the "small circle" of torus. */
float theta = (patchUVPosition.y + mod(float(gl_InstanceID / 4), 4.0)) * pi / 2.0;
/* Horizontal tangent to torus. */
vec3 dBdu = vec3(-sin(phi), 0.0, cos(phi));
/* Vertical tangent to torus. */
vec3 dBdv = vec3(cos(phi) * (-sin(theta)), cos(theta), sin(phi) * (-sin(theta)));
/* Calculate normal vector. */
vec3 normalVector = normalize(cross(dBdu, dBdv));
/* Calculate normal matrix. */
mat3 normalMatrix = transpose(inverse(mat3x3(modelViewMatrix)));
/* Transform normal vector to Model-View-Projection space. */
modelViewProjectionNormalVector = normalize(normalMatrix * normalVector);
}

Fragment shader source for the solid torus

precision mediump float;
/* Input normal vector. */
in vec3 modelViewProjectionNormalVector;
/* Structure storing directional light parameters. */
struct Light
{
vec3 lightColor;
vec3 lightDirection;
float ambientIntensity;
};
/* Color of the drawn torus. */
uniform vec4 color;
/* Uniform representing light parameters. */
uniform Light light;
/* Output variable. */
out vec4 fragColor;
void main()
{
/* Calculate the value of diffuse intensity. */
float diffuseIntensity = max(0.0, -dot(modelViewProjectionNormalVector, normalize(light.lightDirection)));
/* Calculate the output color value considering the light. */
fragColor = color * vec4(light.lightColor * (light.ambientIntensity + diffuseIntensity), 1.0);
}

If we now would like to render the solid torus on the screen, it's enough to issue the glDrawElementsInstanced() call using GL_TRIANGLES mode. Of course there should be proper program object and vertex array object used.

void InstancedSolidTorus::draw(float* rotationVector)
{
/* Location of rotation vector. */
GLint rotationVectorLocation = GL_CHECK(glGetUniformLocation(programID, "rotationVector"));
/* Set required OpenGL ES state. */
GL_CHECK(glUseProgram (programID ));
GL_CHECK(glBindVertexArray(vaoID ));
GL_CHECK(glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, patchIndicesBufferID));
if (rotationVectorLocation != -1)
{
/* Pass rotation parameters to the shader. */
GL_CHECK(glUniform3fv(rotationVectorLocation, 1, rotationVector));
}
else
{
LOGE("Could not locate \"rotationVector\" uniform in program [%d]", programID);
}
/* Draw patchInstancesCount instances of patchTriangleIndicesCount triangles. */
GL_CHECK(glDrawElementsInstanced(GL_TRIANGLES, patchTriangleIndicesCount, GL_UNSIGNED_INT, 0, patchInstancesCount));
}

Result

We wanted the model to rotate, which is why we need to calculate a new rotation angle each frame. Once we have a rotation vector generated, it's used to update the vertices position for both: wireframe and solid torus.

/* Increment rotation angles. */
angleX += 0.5;
angleY += 0.5;
angleZ += 0.5;
if(angleX >= 360.0f) angleX = 0.0f;
if(angleY >= 360.0f) angleY = 0.0f;
if(angleZ >= 360.0f) angleZ = 0.0f;
float rotationVector[] = {angleX, angleY, angleZ};

The calculated values are then used while drawing the requested models.

wireframeTorus->draw(rotationVector);
solidTorus->draw(rotationVector);
InstancedTessellation_result.png
The result model consists of both: wireframe and solid torus.