←Back

OpenGL Torus

Related Topics: OpenGL Sphere, OpenGL Cylinder, Prism & Pipe
Download: torus.zip, torusShader.zip

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

Torus

A torus is a ring-like 3D geometry by revolving a circle around a circular path. The radius of the circle is called the minor radius, r. And, the radius of the circular path is called the major radius, R which is the distance from the origin to the center of the circle.

torus
Torus: Circle revolving along a circular path
torus
Major/Minor Raddi of Torus

An arbitrary point (x, y, z) on a torus can be computed by the parametric equations with the major radius R, minor radius r, and the corresponding sector and side angles θ and ϕ.

parametric equation of torus

Due to limited resources, we only use a limited number of sectors for the circular path and the sides for the circle itself to approximate the 3D geometry. The range of sector angles is from 0 to 360 degrees, and the side angle of the circle is from 180 to -180 degrees. The sector and side angle for each step can be calculated by the following;

The following C++ code generates all vertices of the torus for the given radii, sectors and sides. It also creates other vertex attributes; surface normals, texture coordinates and indices. For more details, please refer to buildVerticesSmooth() or buildVerticesFlat() functions in Torus.cpp class.


// x = (R + r * cos(u)) * cos(v) = R * cos(v) + r * cos(u) * cos(v)
// y = (R + r * cos(u)) * sin(v) = R * sin(v) + r * cos(u) * sin(v)
// z = r * sin(u)
// where u: side angle (-180 ~ 180)
//       v: sector angle (0 ~ 360)

// clear memory of prev arrays
std::vector<float>().swap(vertices);
std::vector<float>().swap(normals);
std::vector<float>().swap(texCoords);
std::vector<unsigned int>().swap(indices);

float x, y, z, xy;                              // vertex position
float nx, ny, nz;                               // normal
float lengthInv = 1.0f / minorRadius;           // to normalize normals
float s, t;                                     // texCoord

float sectorStep = 2 * PI / sectorCount;
float sideStep = 2 * PI / sideCount;
float sectorAngle, sideAngle;

for(int i = 0; i <= sideCount; ++i)
{
    // start the tube side from the inside where sideAngle = pi
    sideAngle = PI - i * sideStep;              // starting from pi to -pi
    xy = minorRadius * cosf(sideAngle);         // r * cos(u)
    z = minorRadius * sinf(sideAngle);          // r * sin(u)

    // add (sectorCount+1) vertices per side
    // the first and last vertices have same position and normal,
    // but different tex coords
    for(int j = 0; j <= sectorCount; ++j)
    {
        sectorAngle = j * sectorStep;           // starting from 0 to 2pi

        // tmp x and y to compute normal vector
        x = xy * cosf(sectorAngle);
        y = xy * sinf(sectorAngle);

        // add normalized vertex normal first
        nx = x * lengthInv;
        ny = y * lengthInv;
        nz = z * lengthInv;
        addNormal(nx, ny, nz);

        // shift x & y, and vertex position
        x += majorRadius * cosf(sectorAngle);   // (R + r * cos(u)) * cos(v)
        y += majorRadius * sinf(sectorAngle);   // (R + r * cos(u)) * sin(v)
        addVertex(x, y, z);

        // vertex tex coord between [0, 1]
        s = (float)j / sectorCount;
        t = (float)i / sideCount;
        addTexCoord(s, t);
    }
}

// indices
//  k1--k1+1
//  |  / |
//  | /  |
//  k2--k2+1
unsigned int k1, k2;
for(int i = 0; i < sideCount; ++i)
{
    k1 = i * (sectorCount + 1);     // beginning of current side
    k2 = k1 + sectorCount + 1;      // beginning of next side

    for(int j = 0; j < sectorCount; ++j, ++k1, ++k2)
    {
        // 2 triangles per sector
        addIndices(k1, k2, k1+1);   // k1---k2---k1+1
        addIndices(k1+1, k2, k2+1); // k1+1---k2---k2+1
    }
}


// add vertex attributes to arrays
void Torus::addVertex(float x, float y, float z)
{
    vertices.push_back(x);
    vertices.push_back(y);
    vertices.push_back(z);
}
void Torus::addNormal(float nx, float ny, float nz)
{
    normals.push_back(nx);
    normals.push_back(ny);
    normals.push_back(nz);
}
void Torus::addTexCoord(float s, float t)
{
    texCoords.push_back(s);
    texCoords.push_back(t);
}
void Torus::addIndices(unsigned int i1, unsigned int i2, unsigned int i3)
{
    indices.push_back(i1);
    indices.push_back(i2);
    indices.push_back(i3);
}

Example: Torus

example of drawing torus
Download: torus.zip, torusShader.zip (with GLFW) (Updated: 2024-07-25)

This example constructs torues with major radius 1, minor radius 0.5, 36 sectors and 18 sides, but with different shadings; flat, smooth or textured. Torus.cpp class provides pre-defined functions; draw(), drawWithLines() and drawLines(), to draw a sphere using OpenGL VertexArray.

By default, the revolving axis of the torus is +Z axis. But, it can be changed by the last parameter of Torus class contructor (X=1, Y=2, or Z=3), or by calling setUpAxis() function after it is constructed.


// create a torus with R=1, r=0.5, sectors=36, stacks=18, smooth=true, up=Z
Torus torus(1.0f, 0.5f, 36, 18);
Torus torus(1.0f, 0.5f, 36, 18, true, 3);  // same as above

// can change parameters later
torus.setMajorRadius(2.0f);
torus.setMinorRadius(1.0f);
torus.setSectorCount(72);
torus.setSideCount(24);
torus.setSmooth(false);
torus.setUpAxis(2);                   // X=1, Y=2, Z=3
...

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

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


// create a torus with default params; R=1, r=0.5, sectors=36, sides=18, smooth=true
Torus torus;

// creat VAO to store VBO states
GLuint vaoId;
glGenVertexArrays(1, &vaoId1);
glBindVertexArray(vaoId1);

// 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
             torus.getInterleavedVertexSize(),    // data size, # of bytes
             torus.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
             torus.getIndexSize(),                // data size, # of bytes
             torus.getIndices(),                  // ptr to index data
             GL_STATIC_DRAW);                     // usage

// enable vertex array attributes for bound VAO
glEnableVertexAttribArray(attribPos);
glEnableVertexAttribArray(attribNorm);
glEnableVertexAttribArray(attribTex);

// store vertex array pointers to bound VAO
int stride = torus.getInterleavedStride();
glVertexAttribPointer(attribPos,  3, GL_FLOAT, false, stride, 0);
glVertexAttribPointer(attribNorm, 3, GL_FLOAT, false, stride, (void*)(3*sizeof(float)));
glVertexAttribPointer(attribTex,  2, GL_FLOAT, false, stride, (void*)(6*sizeof(float)));
...


// bind VAO before drawing
glBindVertexArray(vaoId);

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

// unbind VAO
glBindVertexArray(0);

Example: WebGL Torus (Interactive Demo)

Major Radius
Minor Radius
Sector Count
Side Count




It is a JavaScript version of Torus class, Torus.js, and rendering it with WebGL. Drag the sliders to change the parameters of the totus. The fullscreen version is available at WebGL Torus.

The following JavaScript code is to create and to render a torus object.


// create a torus with 5 params: R, r, sectors, sides, smooth
let torus = new Torus(gl, 1, 0.5, 36, 18, false);
...

// change params of torus later
torus.setMajorRadius(2);
torus.setMinorRadius(3);
torus.setSectorCount(8);
torus.setSideCount(4);
torus.setSmooth(true);
torus.setUpAxis(2);
...

// draw a torus with interleaved mode
gl.bindBuffer(gl.ARRAY_BUFFER, torus.vboVertex);
gl.vertexAttribPointer(gl.program.attribPosition,  3, gl.FLOAT, false, torus.stride, 0);
gl.vertexAttribPointer(gl.program.attribNormal,    3, gl.FLOAT, false, torus.stride, 12);
gl.vertexAttribPointer(gl.program.attribTexCoord0, 2, gl.FLOAT, false, torus.stride, 24);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, torus.vboIndex);
gl.drawElements(gl.TRIANGLES, torus.getIndexCount(), gl.UNSIGNED_SHORT, 0);
...

←Back
 
Hide Comments
comments powered by Disqus