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
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.
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;
}
unsigned int componentIndex = 0;
for (unsigned int horizontalIndex = 0; horizontalIndex < circlesCount; ++horizontalIndex)
{
float xyAngle = (
float) horizontalIndex * 2.0
f *
M_PI / circlesCount;
for (unsigned int verticalIndex = 0; verticalIndex < pointsPerCircleCount; ++verticalIndex)
{
float theta = (
float) verticalIndex * 2.0
f *
M_PI / pointsPerCircleCount;
vertices[componentIndex++] = (torusRadius + circleRadius * cosf(theta)) * cosf(xyAngle);
vertices[componentIndex++] = circleRadius * sinf(theta);
vertices[componentIndex++] = (torusRadius + circleRadius * cosf(theta)) * sinf(xyAngle);
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
{
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);
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);
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);
modelViewMatrix = scaleMatrix;
gl_Position = modelViewProjectionMatrix * position;
}
Fragment shader source for the wireframe torus
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.
{
GL_CHECK(glUniform3fv(rotationVectorLocation, 1, rotationVector));
}
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
const uint patchDimension = 4u;
const uint controlPointsPerPatchCount = patchDimension * patchDimension;
const uint quadsInPatchCount = (patchDimension - 1u) * (patchDimension - 1u);
const uint verticesCount = 144u;
{
uint
indices[controlPointsPerPatchCount * verticesCount / quadsInPatchCount];
};
{
vec4 vertices[verticesCount];
};
out
vec3 modelViewProjectionNormalVector;
{
const float pi = 3.14159265358979323846;
vec4 controlVertices[controlPointsPerPatchCount];
for (uint i = 0u; i < controlPointsPerPatchCount; ++i)
{
controlVertices[i] = vertices[
indices[uint(gl_InstanceID) * controlPointsPerPatchCount + i]];
}
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 ;
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);
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);
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);
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);
modelViewMatrix = scaleMatrix;
gl_Position = modelViewProjectionMatrix *
vec4(position, 1.0);
float phi = (patchUVPosition.x + mod(
float(gl_InstanceID), 4.0)) * pi / 2.0;
float theta = (patchUVPosition.y + mod(float(gl_InstanceID / 4), 4.0)) * pi / 2.0;
vec3 dBdu =
vec3(-sin(phi), 0.0, cos(phi));
vec3 dBdv =
vec3(cos(phi) * (-sin(theta)), cos(theta), sin(phi) * (-sin(theta)));
modelViewProjectionNormalVector =
normalize(normalMatrix * normalVector);
}
Fragment shader source for the solid torus
in
vec3 modelViewProjectionNormalVector;
struct Light
{
float ambientIntensity;
};
{
float diffuseIntensity =
max(0.0, -dot(modelViewProjectionNormalVector,
normalize(light.lightDirection)));
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.
{
GLint rotationVectorLocation =
GL_CHECK(glGetUniformLocation(
programID,
"rotationVector"));
if (rotationVectorLocation != -1)
{
GL_CHECK(glUniform3fv(rotationVectorLocation, 1, rotationVector));
}
else
{
LOGE(
"Could not locate \"rotationVector\" uniform in program [%d]",
programID);
}
}
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.
The calculated values are then used while drawing the requested models.
The result model consists of both: wireframe and solid torus.