Please send suggestions or corrections for this page to the author at sbuss@ucsd.edu.
This page documents how to use the GlGeomShapes functions. These are a collection of classes that render geometric shapes, including spheres, cylinders, cones, tori, and Bezier patches, within the framework of Modern OpenGL. They were developed to support programming examples for Math 155AB at UCSD, and more generally to use with software for a possible second edition of the book 3D Computer Graphics: A mathematical approach with OpenGL, Cambridge University Press, 2003.
Currently supported geometric shapes, and the required source files, are:
The shapes all share a similar interface. The routines take care of:
All of these things are taken care of by the GlGeom routions. Your calling code is still responsible for setting a modelview matrix so that the object is rendered at the desired position, size and orientation (instead of being rendered in a standard position at the origin).
The basic usage for all the geometric shapes is as follows:
InitializeAttribLocations
to specify the
locations of the vertex position, normal and texture coordinates
as needed for the shader program.Render()
to render the object. (This
in turn invokes the OpenGL glDrawElements() function (or another
glDraw function) to render the object as a mesh of triangles.)
For examples, see SolarModern for how to render and animate spheres, or see GlGeomShapesTester for examples of how to use all the supported shapes.
All GlGeom shapes have a member function
InitializeAttribLocations
which will create
the needed VAO, VBO and EBO needed for OpenGL to render the shape.
To build the VBO, the program needs to know how to layout the
vertex data in an array; the parameters to InitializeAttribLocations
are:
InitializeAttribLocations(unsigned int pos_loc, unsigned int normal_loc, unsigned int texcoords_loc);
The three arguments specify the layout for the (a) the vertex position,
(b) the vertex normal, and (c) the vertex texture coordinates.
These layout values can be omitted, or set to UINT_MAX
,
to indicate that the shader program does not need that value.
In this case, the values are not stored in the VBO.
For example, the GLSL shader code may contain lines such as the following:
layout (location = 0) in vec3 aPos; // Position in attribute location 0
layout (location = 2) in vec3 aNormal; // Normal Vector in attribute location 2
layout (location = 3) in vec2 vTexCoords; // Color in attribute location 3
The corresponding needed call to InitializeAttribLocations is:
InitializeAttribLocations(0,2,3);
If only some of these vertex attributes are needed, you can use calls such as
InitializeAttribLocations(0); // Only position is generated.
InitializeAttribLocations(0,2); // Position and normal values are generated.
InitializeAttribLocations(0,UINT_MAX,3); // Only position and texture coordinates are generated.
InitializeAttribLocations regenerates all the VAO, VBO and EBO data each time it is invoked. Therefore, it is recommended that it be called only once during initialization. If you have multiple shaders that use different layout values, then you should create different GlGeom objects for each shader.
A sphere is rendered as a mesh of triangles; the mesh resolution is controlled by setting the numbers of "slices" and "stacks". The slices and stacks split the sphere similarly to lines of latitude and longitude on a globe. The "slices" are vertical cuts (similar to the slices of an orange). The "stacks" are horizontal cuts. In practice, we usually use equal numbers of slices and stacks.
To create a sphere, your program must use #include "GlGeomSphere.h", and then use a constructor, such as:
GlGeomSphere mySphere(8,8); // Create a sphere with 8 slices and 8 stacks.
An equivalent way to do the same thing is:
GlGeomSphere mySphere; // Create a sphere with default number of slices and stacks.
mySphere.Remesh(8,8); // Remesh to have eight slices and eight stacks.
The first parameter is the number of slices; the second is
the number of stacks. Remesh()
can called repeatedly, and
can be called either before or after calling
InitializeAttribLocations()
.
After creating and meshing the sphere, and calling
InitializeAttribLocations()
, your program calls
Render()
as desired to render the
object.
mySphere.Render();
The Render
function issues a glDrawElements()
command to render the sphere as triangles. The sphere is rendered
as a unit sphere, centered at the origin, with one pole at
<0,1,0> and the other at <0,-1,0>. It can be repositioned,
resized, etc. by the shader program; usually this is done by
using a modelview matrix.
Texture coordinates for a sphere use a spherical projection. Texture coordinates all lie in [0,1]×[0,1]. The front, center point has s, t texture coordinates equal to (0.5,0.5). Points near the north pole have t texture coordinate close to 1; points near the sorth pole have t texture coordinate close to 0;
There are a couple specialized methods for rendering
a single slice or a single stack.
mySphere.RenderSlice(i)
renders the i-th slice, and
RenderStack(j)
renders the j-th stack. In 155A, students
have used RenderSlice
to render petals of
a flower.
A cylinder is rendered as a mesh of triangles; the mesh resolution is controlled by setting the numbers of "slices" and "stacks" and "rings". The "slices" are vertical cuts (similar to slices of cake). The "stacks" are horizontal cuts (similar to cake layers). The "rings" are the number of concentric bands on the two end faces of the cylinder (this has no analogy with normal cake cutting!).
To create a cylinder, your program must use #include "GlGeomCylinder.h", and then use a constructor, such as:
GlGeomCylinder myCylinder(8,2,3); // Create a cylinder with 8 slices, 2 stacks and 3 rings.
An equivalent way to do the same thing is:
GlGeomCylinder myCylinder; // Create a cylinder with default number of slices, stacks, and rings.
myCylinder.Remesh(8,2,3); // Remesh to have eight slices, two stacks and three rings.
Remesh()
can called repeatedly, and
can be called either before or after calling
InitializeAttribLocations()
.
A low resolution GlGeomCylinder can be used to create a rectangular prism (i.e., a box) by using four (4) slices, one (1) stack and one (1) ring.
The entire cylinder (top, bottom and side) can
be rendered with the Render
member
function:
myCylinder.Render(); // Render: renders entire cylinder
This draws the cylinder using the OpenGL glDrawElements() command, rendering the cylinder as triangles. The cylinder is rendered as having height 2 and radius 1, positioned with its center at the origin and with y-axis as its central axis. This can be changed by using a modelview matrix (for instance) in the shader program.
There are also special render functions for rendering the top face, the bottom face, and the side of the cylinder.
myCylinder.void RenderTop();
myCylinder.void RenderBase();
myCylinder.void RenderSide();
It is convenient to use these last three methods, instead of
Render()
, when putting
a texture on the cylinder. This is because rendering different texture maps
on different faces requires rendering the faces separately.
Each of the three faces uses
texture coordinates in the domain [0,1]×[0,1]: in other words,
they share texture coordinates. The top and bottom faces
rectangularly map the texture coordinates onto the circular
region inscribed in [0,1]×[0,1].
The side uses texture coordinates that range over [0,1]×[0,1]. The side uses s texture coordinates are equal to 0.5 for the center front of the cylinder; and equal to 0 and 1 on the back of the cylinder (where the s coordinate wraps). The side uses texture coordinate t equal to 1 where it touches the top face, and equal to 0 where it touches the bottom face.
A cone is rendered as a mesh of triangles; the mesh resolution is controlled by setting the numbers of "slices" and "stacks" and "rings". The "slices" are vertical cuts (similar to slices of cake). The "stacks" are horizontal cuts (similar to cake layers). The "rings" are the number of concentric bands on the circular base.
To create a cone, your program must use #include "GlGeomCone.h", and then use a constructor, such as:
GlGeomCone myCone(8,2,3); // Create a cone with 8 slices, 2 stacks and 3 rings.
An equivalent way to do the same thing is:
GlGeomCone myCone; // Create a cone with default number of slices, stacks, and rings.
myCone.Remesh(8,2,3); // Remesh to have eight slices, two stacks and three rings.
Remesh()
can called repeatedly, and
can be called either before or after calling
InitializeAttribLocations()
.
The entire cone (side and base) can
be rendered with the Render
member
function:
myCone.Render(); // Render: renders entire cone
This draws the cone using the OpenGL glDrawElements() command, rendering the cone as triangles. The cone is rendered as having height 1 and base radius 1. The central axis of the cone is on the y-axis. The apex of cone is at the point (0,1,0); the base is a unit circle lying in the x-z plane centered at the origin (0,0,0). As usual, the position and scaling of the cone can be changed by using a modelview matrix (for instance) in the shader program.
There are also special render functions for rendering the bottom face and the side of the cone.
myCone.void RenderBase();
myCone.void RenderSide();
It is convenient to use these last two methods, instead of
Render()
, when putting
a texture on the cone. This is because rendering different texture maps
on different faces requires rendering the faces separately.
The side and the base both use
texture coordinates in the domain [0,1]×[0,1]: in other words,
they share texture coordinates. The base face
rectangularly maps the texture coordinates onto the circular
region inscribed in [0,1]×[0,1].
The side uses texture coordinates that range over [0,1]×[0,1]. The side uses s texture coordinates are equal to 0.5 for the center front of the cylinder; and equal to 0 and 1 on the back of the cylinder (where the s coordinate wraps). The side uses texture coordinate t equal to 0 where it touches the base, and equal to 1 at the apex. The apex vertex has texture coordinates s=0.5 and t=1.
A torus is rendered as a mesh of triangles; the mesh resolution is controlled by setting the numbers of "rings" and "sides". The torus is always created as having major radius equal to 1, but the minor radius can be set by your program. The torus is oriented horizonally: the center of the torus is at the origin, it goes around the y-axis, so the inner circular path of the torus lies in the x-z plane. (As usual, the default position and size, etc., can be changed, say by using a modelview matrix in the shader program.)
The "rings" are vertical cuts (similar to way a doughnut might be sliced into small bites). The "sides" are wedges running around the torus at right angles to the rings (similar to the way a single ring might be cut into yet smaller pieces).
To create a torus, your program must use #include "GlGeomTorus.h", and then use a constructor, such as:
GlGeomTorus myTorus(10,6,0.4); // Create a torus with 10 rings, 6 sides and minor radius 0.4
The minor radius defaults to 0.5 if no value is given. An equivalent way to do the same thing is:
GlGeomTorus myTorus; // Create a torus with default values.
myTorus.Remesh(10,6,0.4); // Remesh to have 10 rings, 6 sides and minor radius 0.4
If the minor radius is not specified, Remesh()
will leave it unchanged.
Remesh()
can called repeatedly, and
can be called either before or after calling
InitializeAttribLocations()
.
The member function Render()
renders the
entire torus. Texture coordinates are set so that t coordinates
wrap from 1 to 0 at the inner circular seam of the torus. The s
coordinates are equal to 0.5 at the front of the torus (where x
equals 0 and z is positive), and they wrap from 1 to 0 at the
back of the torus (where x equals 0 and z is negative).
myTorus.Render();
There are a couple specialized methods for rendering
a single slice or a single stack; these are
myTorus.RenderRing(i)
to
render the i-th ring, and
myTorus.RenderSideStrip(j)
to
render the j-th strip.
A cube is rendered as a mesh of triangles. By default each face of the cube is a pair of triangles (so the mesh resolution is equal 1), but this can be changed to havea higher mesh resolution. The cube is axis-aligned, centered at the origin, and has side length equal to one. Thus, the six corner vertices of the cube are at (± 1/2,±1/2, ±1/2) (As usual, the default position and size, etc., can be changed, say by using a modelview matrix in the shader program.)
The mesh resolution is equal to the number of segments each edge of the cube is split into.
To create a cube, your program must use #include "GlGeomCube.h", and then use a constructor, such as:
GlGeomCube myCube(); // Create a cube with mesh resolution equal to 1.
The mesh resolution can be set with the constructor by calling
GlGeomCube myCube(meshRes);
or it can be
changed by calling
myCube.Remesh(8); // Set each face to be a 8x8 mesh of (triangulate) squares.
The member function Render()
renders the
entire cube. Texture coordinates are set separately for
each face. When viewing any side face the texture coordinates
range from 0 to 1 from the bottom left viewed corner to the
upper right viewed corner. The texture coordinates for the top face
and bottom face are set to the the second (t value) increases
in the positive z-axis direction.
myCube.Render();
There a methods for rendering
a single face of the cube. Call
myCube.RenderFace(i)
to
render just the i-th face. The face numbers range
from 0 to 5, and specify (respectively) the front face, the right
face, the back face, the left face, the top face and the bottom face.
(Thanks are due to Derrick Yao for creating GlGeomCube.)
The GlGeomBezier class supports rendering a set of Bezier patches. The patches may be rational Bezier patches. The patches can be of any order (any degree), and meshed differently in the u and v directions.
To create a collection of Bezier patches in a GlGeomBezier object, your program must use #include "GlGeomBezier.h", and then use a constructor, such as:
GlGeomBezier myPatches(); // Create a object that holds Bezier patches
It is also possible to use GlGeomBezier myPatches(uRes,vRes);
to
create a collection of Bezier patches that will be rendered with mesh resolution
uRes
×vRes
. The default mesh resolution is
8× 8. Alternately, a call to myPatches.Remesh(uRes,vRes)
can be
used to change the mesh resolution, and this can be done either before or
after the call to InitializeAttribLocations()
.
The constructor does not actually create any patches. To finish forming the patches, you load the control points for a complete set of Bezier patchs into the GlGeomBezier object by calling
myBezier.LoadControlPts(uOrder, vOrder, numCoordinates, numPatches, controlPointsArray);
The first two parameters uOrder
and vOrder
give the order
of the control patches; recall that the order of a Bezier curve or patch is
equal to the degree of the curve or patch plus one. For instance, patches of
order 4×4, and thus of degree 3 ×3 are a popular choice.
The value numCoordinates
must equal either 3 or 4,
and this is the number of coordinates
in each control point. If numCoordinates
is equal to 3, then each control
point consists of x, y, z values specifying a point in 3-space.
If numCoordinates
is equal to 4, then each control
point consists of x, y, z, w values that are the homogeneous coordinates
for a point in 3-space. In the latter case, the Bezier patches are rational Bezier patches.
The parameter numPatches
is the total number of Bezier patches
to be uploaded to the GlGeomBezier object. Each patch has
uOrder*vOrder
many control points; each control point
consists of numCoordinates
floating point numbers
(of type double
). herefore, the total of double
's in the array of
control points is equal the product
uOrder*vOrder*numCoordinates*numPatches
.
The parameter
controlPointsArray
is of type double*
it is a pointer
to the array of double precision floating point numbers defining all the constrol
points of the Bezier patches. The GlGoemBezier object creates a copy of the
array of control points, thus it is not necessary for the C++ code to maintain
the array of control points after calling LoadControlPts
.
The member function Render()
renders the
entire set of Bezier patches:
myBezier.Render();
Texture coordinates are set so that each patch has texture coordinates ranging over [0,1]×[0,1]. (There is currently no provision for the shader program to know which patch is being rendered.)
There are a couple specialized methods for rendering
individual patches from the GlGeomBezier oject. These are
myBezier.RenderPatch(i)
to
render the i-th ring where 0 ≤ i
< numPatches
,
and myBezier.RenderPatch(i)
myBezier.RenderPatches(i,n)
to render n
consecutive
patches beginning with patch i
.
For examples on using GlGeomBezier objects, see the source code in GlGeomShapesTester and in GlGeomTeapot.[c,h] for the Utah teapot, which is discussed next.
The Utah teapot is a commonly used geometric shape in computer graphics, constructed from Bezier patches. It consists of a body, a top (or lid), a handle and a spout. The teapot is rendered upright with the body of the teapot centered on the y-axis; the center of the base is placed at the origin. The spout is facing in the direction of the positive z-axis. The Bezier patches have degree 3×3; that is, they have order 4×4.
To create a teapot, your program uses #include "GlGeomTeapot.h", and then use a constructor, such as:
GlGeomTeapot myTeapot(); // Create teapot
By default the Bezier patches making up the teapot have mesh resolution of 8 by 8, so each patch is rendered into 8 by 8 subpatches. The default can be changed with the construction, for instance,
GlGeomTeapot myTeapot(10,10); // Create teapot with patches of mesh resolution 10 by 10.
The Remesh()
method can also be used to change the mesh resolution of the
patches, namely, calling myTeapot.Remesh(10,10)
. The Remesh()
method
can be called either before or after calling InitializeAttribLocations
.
After InitializeAttribLocations
has been called, the teapot can be rendered
by calling Render()
:
myTeapot.Render(); // Render the teapot
There are also methods for rendering and of the four parts of the teapot, these
are RenderBody()
, RenderLid()
, RenderHandle()
,
and RenderSpout()
.