|
p.64 and p.691 of Text
| i = ( 1, 0, 0 ), j = ( 0, 1, 0 ), k = ( 0, 0, 1 ) |
Magnitude of A
e.g.
v = ( 2, 4, -1 ) = 2(1, 0, 0 ) + 4(0, 1, 0) - 1( 0, 0, 1 ) = 2i + 4j - k
|v| = ( 22 + 42 + 12 )½ = ( 21 )½ = 4.5826
B = ( Bx, By, Bz )
Dot Product
Cross Product
| A x B = |
|
A x B is always perpendicular to A and B
a, b -- scalars, P, Q, R -- 3D vectors
A normal vector ( or normal for short ) is a vector that
points in a direction that is perpendicular to a surface.
For a plane ( flat surface ), one perpendical direction is the
same for every point on the surface
Like a vector, a point P can be specified by any three coordinates (i.e. P = ( Px, Py, Pz ). e.g. P = ( 1, 2, 3 )
Any three points P1, P2, P3
determine a unique plane.
To find the normal to the plane, we build two vectors
( Note: Looking at the plane ( i.e. front face ), the points P1 P2, P3 appear counter-clockwise )
Example:
Solution:
Class Exercise:
Why normal vectors are important?
An object's normal vectors define the orientation of its surface in space -- in particular, its orientation relative to light resources. These vectors are used by OpenGL to determine how much light the object receives at its vertices.
|
|
glNormal*() -- set the normal to value the argument passed in
subsequent calls to glVertex*() cause the specified vertices
to be assigned the current normal
Example:
glBegin( GL_POLYGON ); glNormal3fv( n0 ); glVertex3fv( v0 ); glNormal3fv( n1 ); glVertex3fv( v1 ); glNormal3fv( n2 ); glVertex3fv( v2 ); glNormal3f( 1.0, 1.0, 1.0 ); glVertex3f( 2.0, 3.0, 1.0 ); glEnd(); |
A vector that has a length of 1 is said to be normalized
glEnable( GL_NORMALIZE );
![]() |
| void glEnableClientState(GLenum array) |
|---|
|
Specifies the array to enable.
Acceptable Symbolic constants: GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_INDEX_ARRAY, GL_NORMAL_ARRAY, GL_TEXTURE_COORD_ARRAY, and GL_EDGE_FLAG_ARRAY |
Can be turned off by:
| void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); |
|---|
| Specifies where spatial coordinate data can be accessed. pointer is the memory address of the first coordinate of the first vertex in the array. type specifies the data type (GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE) of each coordinate in the array. size is the number of coordinates per vertex, which must be 2, 3, or 4. stride is the byte offset between consecutive vertexes. If stride is 0, the vertices are understood to be tightly packed in the array. |
void glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
void glIndexPointer(GLenum type, GLsizei stride, const GLvoid *pointer);
void glNormalPointer(GLenum type, GLsizei stride, const GLvoid *pointer);
void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
void glEdgeFlagPointer(GLsizei stride, const GLvoid *pointer);
Table Vertex Array Sizes (Values per Vertex) and Data Types
| Command | Sizes | Values for type Argument |
|---|---|---|
| glVertexPointer | 2, 3, 4 | GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE |
| glNormalPointer | 3 | GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE |
| glColorPointer | 3, 4 | GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, GL_DOUBLE |
| glIndexPointer | 1 | GL_UNSIGNED_BYTE, GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE |
| glTexCoordPointer | 1, 2, 3, 4 | GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE |
| glEdgeFlagPointer | 1 | no type argument (type of data must be GLboolean) |
Example:
Enabling and Loading Vertex Arrays:
static GLint vertices[] = {25, 25,
100, 325,
175, 25,
175, 325,
250, 25,
325, 325};
static GLfloat colors[] = {1.0, 0.2, 0.2,
0.2, 0.2, 1.0,
0.8, 1.0, 0.2,
0.75, 0.75, 0.75,
0.35, 0.35, 0.35,
0.5, 0.5, 0.5};
glEnableClientState (GL_COLOR_ARRAY);
glEnableClientState (GL_VERTEX_ARRAY);
glColorPointer (3, GL_FLOAT, 0, colors);
glVertexPointer (2, GL_INT, 0, vertices);
|
Stride:
tells OpenGL how to access the data you provide in your pointer value
its value should be the number of bytes between the starts of two
successive pointer elements, or zero, which is a special case
static GLfloat intertwined[] =
{1.0, 0.2, 1.0, 100.0, 100.0, 0.0,
1.0, 0.2, 0.2, 0.0, 200.0, 0.0,
1.0, 1.0, 0.2, 100.0, 300.0, 0.0,
0.2, 1.0, 0.2, 200.0, 300.0, 0.0,
0.2, 1.0, 1.0, 300.0, 200.0, 0.0,
0.2, 0.2, 1.0, 200.0, 100.0, 0.0};
|
Stride allows a vertex array to access its desired data at regular intervals in the array. For example, to reference only the color values in the intertwined array, the following call starts from the beginning of the array (which could also be passed as &intertwined[0]) and jumps ahead 6 * sizeof (GLfloat) bytes, which is the size of both the color and vertex coordinate values. This jump is enough to get to the beginning of the data for the next vertex.
glColorPointer (3, GL_FLOAT, 6 * sizeof(GLfloat), intertwined); |
For the vertex coordinate pointer, you need to start from further in the array, at the fourth element of intertwined.
glVertexPointer(3, GL_FLOAT,6*sizeof(GLfloat), &intertwined[3]); |
Dereferencing a Single Array Element
| void glArrayElement(GLint ith) |
|---|
| Obtains the data of one (the ith) vertex for all currently enabled arrays. For the vertex coordinate array, the corresponding command would be glVertex[size][type]v(), where size is one of [2,3,4], and type is one of [s,i,f,d] for GLshort, GLint, GLfloat, and GLdouble respectively. Both size and type were defined by glVertexPointer(). For other enabled arrays, glArrayElement() calls glEdgeFlagv(), glTexCoord[size][type]v(), glColor[size][type]v(), glIndex[type]v(), and glNormal[type]v(). If the vertex coordinate array is enabled, the glVertex*v() routine is executed last, after the execution (if enabled) of up to five corresponding array values. |
Example:
glEnableClientState (GL_COLOR_ARRAY); glEnableClientState (GL_VERTEX_ARRAY); glColorPointer (3, GL_FLOAT, 0, colors); glVertexPointer (2, GL_INT, 0, vertices); glBegin(GL_TRIANGLES); glArrayElement (2); glArrayElement (3); glArrayElement (5); glEnd(); |
When executed, the latter five lines of code has the same effect as
glBegin(GL_TRIANGLES); glColor3fv(colors+(2*3*sizeof(GLfloat)); glVertex2iv(vertices+(2*2*sizeof(GLint)); glColor3fv(colors+(3*3*sizeof(GLfloat)); glVertex2iv(vertices+(3*2*sizeof(GLint)); glColor3fv(colors+(5*3*sizeof(GLfloat)); glVertex2iv(vertices+(5*2*sizeof(GLint)); glEnd(); |
Dereferencing a List of Array Elements
| void glDrawElements(GLenum mode, GLsizei count, GLenum type, void *indices ); |
|---|
almost has the same effect as: |
int i; glBegin (mode); for (i = 0; i < count; i++) glArrayElement(indices[i]); glEnd(); |
mode can be GL_POLYGON, GL_POINTS, .. |
![]() |
Example: Two Ways to Use glDrawElements()
static GLubyte frontIndices = {4, 5, 6, 7};
static GLubyte rightIndices = {1, 2, 6, 5};
static GLubyte bottomIndices = {0, 1, 5, 4};
static GLubyte backIndices = {0, 3, 2, 1};
static GLubyte leftIndices = {0, 4, 7, 3};
static GLubyte topIndices = {2, 3, 7, 6};
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, frontIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, rightIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, bottomIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, backIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, leftIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, topIndices);
|
Or better still, crunch all the indices together
static GLubyte allIndices[] = {4, 5, 6, 7, 1, 2, 6, 5,
0, 1, 5, 4, 0, 3, 2, 1,
0, 4, 7, 3, 2, 3, 7, 6};
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, allIndices);
|
Note: It is an error to encapsulate glDrawElements() between a glBegin()/glEnd() pair.
Interleaved Arrays
| void glInterleavedArrays ( GLenum format, GLsizei stride, void *pointer ); |
|---|
e.g. glInterleavedArrays( GL_C3F_V3F, 0, interwined );
Any surface can be approximated by polygons.
Example: Building an Icosahedron
#define X .525731112119133606
#define Z .850650808352039932
static GLfloat vdata[12][3] = {
{-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},
{0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},
{Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0}
};
static GLuint tindices[20][3] = {
{0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},
{8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},
{7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6},
{6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };
int i;
/*
tindices[0][0] = 0, tindices[0][1] = 4, tindices[0][2] = 1
tindices[1][0] = 0, tindices[1][1] = 9, tindices[1][2] = 4
...
each vertex has 3 coordinates ( that's why ..3fv )
vdata[[tindices][0][0]][0] = vdata[0][0] = -X
vdata[[tindices][0][0]][1] = vdata[0][1] = 0.0
vdata[[tindices][0][0]][2] = vdata[0][2] = Z
*/
glBegin(GL_TRIANGLES);
for (i = 0; i < 20; i++) {
/* color information here */
//......
//vertex information below
glVertex3fv(&vdata[tindices[i][0]][0]);
glVertex3fv(&vdata[tindices[i][1]][0]);
glVertex3fv(&vdata[tindices[i][2]][0]);
}
glEnd();
|
Need to calculate normals for surfaces if they are to be lit.
//normalize a vector
void normalize(float v[3]) {
GLfloat d = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
if (d == 0.0) {
printf("\nErrot: zero length vector");
return;
}
v[0] /= d; v[1] /= d; v[2] /= d;
}
//v1[], v2[] are two vectors
//out[] holds the crossproduct v1 x v2
void normcrossprod(float v1[3], float v2[3], float out[3])
{
GLint i, j;
GLfloat length;
out[0] = v1[1]*v2[2] - v1[2]*v2[1];
out[1] = v1[2]*v2[0] - v1[0]*v2[2];
out[2] = v1[0]*v2[1] - v1[1]*v2[0];
normalize(out);
}
|
/*
Note: difference between two points is a vector
i.e. A = P0 - P1
B = P2 - p1
A x B is normal to the plane passing through points P0, P1, P2,
and P0, P1, P2 appear counter-clockwise
( see notes above )
*/
GLfloat d1[3], d2[3], norm[3];
for (j = 0; j < 3; j++) {
d1[j] = vdata[tindices[i][0]][j] - vdata[tindices[i][1]][j];
d2[j] = vdata[tindices[i][2]][j] - vdata[tindices[i][1]][j];
}
normcrossprod(d1, d2, norm);
glNormal3fv(norm);
|
If you're using an icosahedron as an approximation for a shaded sphere, you'll want to use normal vectors that are perpendicular to the true surface of the sphere, rather than being perpendicular to the faces. For a sphere, the normal vectors are simple; each points in the same direction as the vector from the origin to the corresponding vertex. Since the icosahedron vertex data is for an icosahedron of radius 1, the normal and vertex data are identical.
glBegin(GL_TRIANGLES);
for (i = 0; i < 20; i++) {
glNormal3fv(&vdata[tindices[i][0]][0]);
glVertex3fv(&vdata[tindices[i][0]][0]);
glNormal3fv(&vdata[tindices[i][1]][0]);
glVertex3fv(&vdata[tindices[i][1]][0]);
glNormal3fv(&vdata[tindices[i][2]][0]);
glVertex3fv(&vdata[tindices[i][2]][0]);
}
glEnd();
|
Improving the Model
/*
Use subdivision to create 80-sided spherical approximation
*/
void drawtriangle(float *v1, float *v2, float *v3)
{
glBegin(GL_TRIANGLES);
glNormal3fv(v1); glVertex3fv(v1);
glNormal3fv(v2); glVertex3fv(v2);
glNormal3fv(v3); glVertex3fv(v3);
glEnd();
}
void subdivide(float *v1, float *v2, float *v3)
{
GLfloat v12[3], v23[3], v31[3];
GLint i;
for (i = 0; i < 3; i++) {
v12[i] = ( v1[i]+v2[i] ) / 2.0;
v23[i] = ( v2[i]+v3[i] ) / 2.0;
v31[i] = ( v3[i]+v1[i] ) / 2.0;
}
normalize(v12);
normalize(v23);
normalize(v31);
drawtriangle(v1, v12, v31);
drawtriangle(v2, v23, v12);
drawtriangle(v3, v31, v23);
drawtriangle(v12, v23, v31);
}
for (i = 0; i < 20; i++) {
subdivide(&vdata[tindices[i][0]][0],
&vdata[tindices[i][1]][0],
&vdata[tindices[i][2]][0]);
}
|
Recursive Subdivision
void subdivide(float *v1, float *v2, float *v3, long depth)
{
GLfloat v12[3], v23[3], v31[3];
GLint i;
if (depth == 0) {
drawtriangle(v1, v2, v3);
return;
}
for (i = 0; i < 3; i++) {
v12[i] = ( v1[i]+v2[i] ) / 2.0;
v23[i] = ( v2[i]+v3[i] ) / 2.0;
v31[i] = ( v3[i]+v1[i] ) / 2.0;
}
normalize(v12);
normalize(v23);
normalize(v31);
subdivide(v1, v12, v31, depth-1);
subdivide(v2, v23, v12, depth-1);
subdivide(v3, v31, v23, depth-1);
subdivide(v12, v23, v31, depth-1);
}
|
Generalized Subdivision
consider an arbitrary surface parameterized by two variables u[0] and u[1]. Suppose that two routines are provided:
void surf(GLfloat u[2], GLfloat vertex[3], GLfloat normal[3]); float curv(GLfloat u[2]); |
If surf() is passed u[], the corresponding three-dimensional vertex and normal vectors (of length 1) are returned. If u[] is passed to curv(), the curvature of the surface at that point is calculated and returned.
The following hows the recursive routine that subdivides a triangle either until the maximum depth is reached or until the maximum curvature at the three vertices is less than some cutoff.
void subdivide(float u1[2], float u2[2], float u3[2],
float cutoff, long depth)
{
GLfloat v1[3], v2[3], v3[3], n1[3], n2[3], n3[3];
GLfloat u12[2], u23[2], u32[2];
GLint i;
if (depth == maxdepth || (curv(u1) < cutoff &&
curv(u2) < cutoff && curv(u3) < cutoff)) {
surf(u1, v1, n1); surf(u2, v2, n2); surf(u3, v3, n3);
glBegin(GL_POLYGON);
glNormal3fv(n1); glVertex3fv(v1);
glNormal3fv(n2); glVertex3fv(v2);
glNormal3fv(n3); glVertex3fv(v3);
glEnd();
return;
}
for (i = 0; i < 2; i++) {
u12[i] = (u1[i] + u2[i])/2.0;
u23[i] = (u2[i] + u3[i])/2.0;
u31[i] = (u3[i] + u1[i])/2.0;
}
subdivide(u1, u12, u31, cutoff, depth+1);
subdivide(u2, u23, u12, cutoff, depth+1);
subdivide(u3, u31, u23, cutoff, depth+1);
subdivide(u12, u23, u31, cutoff, depth+1);
}
|