←Back

OpenGL Cylinder & Prism

Related Topics: OpenGL Sphere
Download: cylinder.zip, cylinderShader.zip

This page describes how to generate a cylinder geometry using C++ and how to draw it in OpenGL.

Cylinder & Prism

The definition of cylinder is a 3D closed surface that has 2 parallel circular bases at the ends and connected by a curved surface (side). Similarly, a prism is a 3D closed surface that has 2 parallel polygonal bases connected by flat surfaces.

Since we cannot draw a perfect circular base and curved side of the cylinder, we only sample a limited amount of points by dividing the base by sectors (slices). Therefore, it is technically constructing a prism by connecting these sampled points together. As the number of samples increases, the geometry is close to a cylinder.

triangluar prism
Triangular Prism (3)
retangular prism
Rectangluar Prism (4)
octagonal prism
Octagonal Prism (8)
hexadecagonal prism
Hexadecagonal Prism (16)

A point on a cylinder
A vertex coordinate on a cylinder

Suppose a cylinder is centered at the origin and its radius is r and the height is h. An arbitrary point (x, y, z) on the cylinder can be computed from the equation of circle with the corresponding sector angle θ.

formula to find a point on a cylinder

The range of sector angles is from 0 to 360 degrees. The sector angle for each step can be calculated by the following;
step angle per sector

The following C++ code generates all vertices of the cylinder with the given base radius, height and the number of sectors (slices). It also creates other vertex attributes; surface normals and texture coordinates.

In order to optimize the computation of sine and cosine, we can compute only once the vertices of a unit circle on XY plane, then re-use the points multiple times by scaling with the base radius. It is also used for the normal vectors of the side of the cylinder.


// generate a unit circle on XY-plane
std::vector<float> Cylinder::getUnitCircleVertices()
{
    const float PI = 3.1415926f;
    float sectorStep = 2 * PI / sectorCount;
    float sectorAngle;  // radian

    std::vector<float> unitCircleVertices;
    for(int i = 0; i <= sectorCount; ++i)
    {
        sectorAngle = i * sectorStep;
        unitCircleVertices.push_back(cos(sectorAngle)); // x
        unitCircleVertices.push_back(sin(sectorAngle)); // y
        unitCircleVertices.push_back(0);                // z
    }
    return unitCircleVertices;
}
...

// generate vertices for a cylinder
void Cylinder::buildVerticesSmooth()
{
    // clear memory of prev arrays
    std::vector<float>().swap(vertices);
    std::vector<float>().swap(normals);
    std::vector<float>().swap(texCoords);

    // get unit circle vectors on XY-plane
    std::vector<float> unitVertices = getUnitCircleVertices();

    // put side vertices to arrays
    for(int i = 0; i < 2; ++i)
    {
        float h = -height / 2.0f + i * height;           // z value; -h/2 to h/2
        float t = 1.0f - i;                              // vertical tex coord; 1 to 0

        for(int j = 0, k = 0; j <= sectorCount; ++j, k += 3)
        {
            float ux = unitVertices[k];
            float uy = unitVertices[k+1];
            float uz = unitVertices[k+2];
            // position vector
            vertices.push_back(ux * radius);             // vx
            vertices.push_back(uy * radius);             // vy
            vertices.push_back(h);                       // vz
            // normal vector
            normals.push_back(ux);                       // nx
            normals.push_back(uy);                       // ny
            normals.push_back(uz);                       // nz
            // texture coordinate
            texCoords.push_back((float)j / sectorCount); // s
            texCoords.push_back(t);                      // t
        }
    }

    // the starting index for the base/top surface
    //NOTE: it is used for generating indices later
    int baseCenterIndex = (int)vertices.size() / 3;
    int topCenterIndex = baseCenterIndex + sectorCount + 1; // include center vertex

    // put base and top vertices to arrays
    for(int i = 0; i < 2; ++i)
    {
        float h = -height / 2.0f + i * height;           // z value; -h/2 to h/2
        float nz = -1 + i * 2;                           // z value of normal; -1 to 1

        // center point
        vertices.push_back(0);     vertices.push_back(0);     vertices.push_back(h);
        normals.push_back(0);      normals.push_back(0);      normals.push_back(nz);
        texCoords.push_back(0.5f); texCoords.push_back(0.5f);

        for(int j = 0, k = 0; j < sectorCount; ++j, k += 3)
        {
            float ux = unitVertices[k];
            float uy = unitVertices[k+1];
            // position vector
            vertices.push_back(ux * radius);             // vx
            vertices.push_back(uy * radius);             // vy
            vertices.push_back(h);                       // vz
            // normal vector
            normals.push_back(ux);                       // nx
            normals.push_back(uy);                       // ny
            normals.push_back(nz);                       // nz
            // texture coordinate
            texCoords.push_back(-ux * 0.5f + 0.5f);      // s
            texCoords.push_back(-uy * 0.5f + 0.5f);      // t
        }
    }
}

cylinders with different stack counts and base/top radii
Cylinders with different base/top radii and stack count

This C++ class provides buildVerticesSmooth() and buildVerticesFlat() functions depending on surface smoothness. Also, it takes additional parameters to construct various shapes of cylinder, similar to OpenGL gluCylinder() function.

The parameters are base radius, top radius, height, sectors, stacks and smoothness. For more details, please refer to Cylinder.cpp class.

In order to draw the surface of a cylinder in OpenGL, you must triangulate adjacent vertices counterclockwise to form polygons.

Each sector on the side surface requires 2 triangles. The total number of triangles for the side is 2 × sectorCount. And the number of triangles for the base or top surface is the same as the number of sectors. (You may use GL_TRIANGLE_FAN for the base/top instead of GL_TRIANGLES.)

The code snippet to generate all triangles of a cylinder may look like;


// generate CCW index list of cylinder triangles
std::vector<int> indices;
int k1 = 0;                         // 1st vertex index at base
int k2 = sectorCount + 1;           // 1st vertex index at top

// indices for the side surface
for(int i = 0; i < sectorCount; ++i, ++k1, ++k2)
{
    // 2 triangles per sector
    // k1 => k1+1 => k2
    indices.push_back(k1);
    indices.push_back(k1 + 1);
    indices.push_back(k2);

    // k2 => k1+1 => k2+1
    indices.push_back(k2);
    indices.push_back(k1 + 1);
    indices.push_back(k2 + 1);
}

// indices for the base surface
//NOTE: baseCenterIndex and topCenterIndices are pre-computed during vertex generation
//      please see the previous code snippet
for(int i = 0, k = baseCenterIndex + 1; i < sectorCount; ++i, ++k)
{
    if(i < sectorCount - 1)
    {
        indices.push_back(baseCenterIndex);
        indices.push_back(k + 1);
        indices.push_back(k);
    }
    else // last triangle
    {
        indices.push_back(baseCenterIndex);
        indices.push_back(baseCenterIndex + 1);
        indices.push_back(k);
    }
}

// indices for the top surface
for(int i = 0, k = topCenterIndex + 1; i < sectorCount; ++i, ++k)
{
    if(i < sectorCount - 1)
    {
        indices.push_back(topCenterIndex);
        indices.push_back(k);
        indices.push_back(k + 1);
    }
    else // last triangle
    {
        indices.push_back(topCenterIndex);
        indices.push_back(k);
        indices.push_back(topCenterIndex + 1);
    }
}

Example: Cylinder

example of drawing cylinder
Download: cylinder.zip, cylinderShader.zip

This example constructs cylinders with 36 sectors and 8 stacks, but with different shadings; flat, smooth or textured. With the default constructor (without argument), it generates a cylinder with base/top radius = 1, height = 1, sectors = 36 and stacks = 1. Or, you can pass the custom parameters to the constructor, similar to gluCylinder().

Cylinder.cpp class provides pre-defined drawing functions using OpenGL VertexArray; draw(), drawWithLines(), drawLines(), drawSide(), drawBase() and drawTop().


// create a cylinder with base radius=1, top radius=2, height=3,
// height=4, sectors=5, stacks=6, smooth=true
Cylinder cylinder(1, 2, 3, 4, 5, 6, true);

// can change parameters later
cylinder.setBaseRadius(1.5f);
cylinder.setTopRadius(2.5f);
cylinder.setHeight(3.5f);
cylinder.setSectorCount(36);
cylinder.setStackCount(8);
cylinder.setSmooth(false);
...

// draw cylinder using vertexarray
cylinder.draw();

This C++ class also provides getVertices(), getIndices(), getInterleavedVertices(), etc. in order to access the vertex data in GLSL. The following code draws a cylinder with interleaved vertex data using VBO and GLSL. Or, download cylinderShader.zip for more details.


// create a cylinder with default params;
// radii=1, height=1, sectors=36, stacks=1, smooth=true
Cylinder cylinder;

// copy interleaved vertex data (V/N/T) to VBO
GLuint vboId;
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);             // for vertex data
glBufferData(GL_ARRAY_BUFFER,                     // target
             cylinder.getInterleavedVertexSize(), // data size, # of bytes
             cylinder.getInterleavedVertices(),   // ptr to vertex data
             GL_STATIC_DRAW);                     // usage

// copy index data to VBO
GLuint iboId;
glGenBuffers(1, &iboId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboId);     // for index data
glBufferData(GL_ELEMENT_ARRAY_BUFFER,             // target
             cylinder.getIndexSize(),             // data size, # of bytes
             cylinder.getIndices(),               // ptr to index data
             GL_STATIC_DRAW);                     // usage
...


// bind VBOs
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboId);

// activate attrib arrays
glEnableVertexAttribArray(attribVertex);
glEnableVertexAttribArray(attribNormal);
glEnableVertexAttribArray(attribTexCoord);

// set attrib arrays with stride and offset
int stride = cylinder.getInterleavedStride();   // should be 32 bytes
glVertexAttribPointer(attribVertex,   3, GL_FLOAT, false, stride, (void*)0);
glVertexAttribPointer(attribNormal,   3, GL_FLOAT, false, stride, (void*)(sizeof(float)*3));
glVertexAttribPointer(attribTexCoord, 2, GL_FLOAT, false, stride, (void*)(sizeof(float)*6));

// draw a cylinder with VBO
glDrawElements(GL_TRIANGLES,                    // primitive type
               cylinder.getIndexCount(),        // # of indices
               GL_UNSIGNED_INT,                 // data type
               (void*)0);                       // offset to indices

// deactivate attrib arrays
glDisableVertexAttribArray(attribVertex);
glDisableVertexAttribArray(attribNormal);
glDisableVertexAttribArray(attribTexCoord);

// unbind VBOs
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

←Back
Hide Comments
comments powered by Disqus