Immediate mode
Around the mid-1990s, 3D graphics hardware was appearing in
consumer grade desktop PCs, workstations, and game consoles. OpenGL,
at that time, appeared as a simple "immediate" API. The word "immediate"
refers to the imperative nature of the API where the user programs a
sequence of translation, rotation, and drawing commands. As an example,
the user might call the following function once per frame to draw a
triangle onscreen:
The above code would likely result in an image similar to the
following:
The API modelled the graphics card as a large state machine
with dozens of variables (such as "the current colour", modified
with glColor3d()). It also
required graphics hardware to implement multiple stacks of matrices
to control projection, object orientation, object scaling, amongst
other things.
As hardware evolved, it quickly became clear that the immediate
mode model was hard to optimize for at the driver level. Programmers
could submit vertex data and state changes in any arbitrary order
and the drivers could make very few assumptions about the immutability
of state at any given time. The immediate mode model was also grossly
inefficient in terms of how vertex data was actually specified: real
3D mesh data requires much more than just simple vertex coordinates.
A typical 3D mesh might require a colour value
(glColor()), a normal vector
(glNormal()), multiple
texture coordinate values
(glTexCoord()), fog coordinates
(glFogCoord()), and more. This results
in a huge number of function calls, even for relatively small models.
A complete example is as follows
(
source):
Display Lists
Display lists were a means of compiling a list
of OpenGL instructions and executing them at a later date. They
were arguably not a means of vertex specification, specifically (pun
intended) but were often used to improve the speed of doing so. They
are covered here for the sake of completeness.
A complete example is as follows
(
source):
As can be seen from the output, the triangle drawing commands
are compiled into a display list and then called twice to draw
two triangles. The commands in the display list were typically
stored in the OpenGL implementation's memory in order to reduce
the number of commands sent between the program and the
OpenGL driver. Display lists were obviously not a solution to
the general inefficiencies of the immediate mode.
Vertex Arrays
Vertex arrays first appeared in OpenGL 1.1
as a means to more efficiently specify vertex data. The programmer
allocates an array in ordinary system memory (using standard C
malloc(), for example), fills it
with vertex data and then calls one function to draw vertices using
that array as the data source. As an example
(
source):
The
glEnableClientState(GL_VERTEX_ARRAY)
call enables the use of client-side vertex arrays. The
glVertexPointer(3, GL_FLOAT, 0, vertices)
call tells OpenGL that the array referenced by
vertices contains three-element
vertices of type GL_FLOAT, with
0 bytes between each successive
element. The glDrawArrays(GL_TRIANGLES, 0, 3)
call asks OpenGL to draw three vertices, starting at offset
0.
The API allows the programmer quite a lot of freedom in terms of how
the data is arranged in order to be passed to OpenGL. The following
example shows the storing of vertex coordinates and colour values
in two separate arrays
(
source):
The above example uses separate arrays for each vertex attribute.
For better cache locality, it is preferable to store one array
of a record type and then tell OpenGL how to retrieve the values
it needs from this single array. An example of this is as follows
(
source):
#include <assert.h>
#include <stddef.h>
#include <GL/glut.h>
#include <GL/glext.h>
typedef float vector3[3];
/*
* Interleaved vertex and colour data.
*/
typedef struct
{
vector3 position;
vector3 colour;
} vertex;
static vertex data0[] = {
{ { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 } },
{ { 100.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 } },
{ { 100.0, 100.0, 0.0 }, { 0.0, 0.0, 1.0 } }
};
/*
* Vertex data followed by colour data.
*/
static vector3 data1[] = {
{ 0.0, 0.0, 0.0 },
{ 100.0, 0.0, 0.0 },
{ 100.0, 100.0, 0.0 },
{ 1.0, 0.0, 0.0 },
{ 0.0, 1.0, 0.0 },
{ 0.0, 0.0, 1.0 }
};
static void
reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, -1.0, 100.0);
assert(glGetError() == GL_NO_ERROR);
}
static void
display(void)
{
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPushMatrix();
glTranslated(20.0, 20.0, 0.0);
{
const unsigned char *data0_v = (unsigned char *) &data0;
const unsigned char *data0_c = ((unsigned char *) &data0) + offsetof(vertex, colour);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(vertex), data0_v);
glColorPointer(3, GL_FLOAT, sizeof(vertex), data0_c);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
glTranslated(120.0, 120.0, 0.0);
{
const unsigned char *data1_v = (unsigned char *) &data1;
const unsigned char *data1_c = ((unsigned char *) &data1) + (3 * sizeof(vector3));
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(vector3), data1_v);
glColorPointer(3, GL_FLOAT, sizeof(vector3), data1_c);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);
}
glPopMatrix();
assert(glGetError() == GL_NO_ERROR);
glutSwapBuffers();
}
int
main(int argc, char **argv)
{
glutInit(&argc, argv);
glutCreateWindow("Vertex array triangle");
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutIdleFunc(glutPostRedisplay);
glutMainLoop();
return 0;
}
Each glVertexPointer and
glColorPointer call specifies
a "stride" value and an initial offset into the array. The "stride"
value specifies the number of bytes that need to be skipped in order
to retrieve the next value of a given type from the array.
As the illustration above shows: To read colour values, start
at &data0 + offsetof(vertex, colour),
read 3 values of type
GL_FLOAT and then skip
sizeof(vertex) bytes. Reading
vertex position values is the same, except that the initial
offset is 0 (reading directly
from the start of the array). Obviously, an infinite number
of permutations are possible. It is also possible to interleave
other data into the array using functions such as
glNormalPointer,
glTexCoordPointer, etc.
It is also possible to specify an array of indices to
be used for rendering, as opposed to having
glDrawArrays simply walk through
the current arrays
(
source):
#include <assert.h>
#include <stddef.h>
#include <GL/glut.h>
#include <GL/glext.h>
typedef float vector3[3];
typedef struct
{
vector3 position;
vector3 colour;
} vertex;
static vertex data[] = {
{ { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 } },
{ { 100.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 } },
{ { 100.0, 100.0, 0.0 }, { 0.0, 0.0, 1.0 } },
{ { 0.0, 100.0, 0.0 }, { 1.0, 1.0, 0.0 } },
};
static void
reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, -1.0, 100.0);
assert(glGetError() == GL_NO_ERROR);
}
static void
display(void)
{
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPushMatrix();
glTranslated(20.0, 20.0, 0.0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
{
const unsigned char *data_v = (unsigned char *) &data;
const unsigned char *data_c = ((unsigned char *) &data) + offsetof(vertex, colour);
{
const unsigned char indices[] = { 0, 1, 2 };
glVertexPointer(3, GL_FLOAT, sizeof(vertex), data_v);
glColorPointer(3, GL_FLOAT, sizeof(vertex), data_c);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, indices);
}
glTranslated(120.0, 120.0, 0.0);
{
const unsigned char indices[] = { 1, 2, 3 };
glVertexPointer(3, GL_FLOAT, sizeof(vertex), data_v);
glColorPointer(3, GL_FLOAT, sizeof(vertex), data_c);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, indices);
}
}
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glPopMatrix();
assert(glGetError() == GL_NO_ERROR);
glutSwapBuffers();
}
int
main(int argc, char **argv)
{
glutInit(&argc, argv);
glutCreateWindow("Vertex array triangle");
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutIdleFunc(glutPostRedisplay);
glutMainLoop();
return 0;
}
The only new code is the use of
glDrawElements, which takes
an array of indices. Note that four vertices are declared in the
vertex array and then glDrawElements
is used to draw one triangle from vertices
{0, 1, 2} and another
triangle from vertices {1, 2, 3}.
Vertex Buffer Objects
Whilst vertex arrays removed the large number of function calls
that were required per vertex under immediate mode, the problem
remained that large amounts of vertex data had to be sent from
the program to the OpenGL implementation each frame. If this data
consisted entirely of static models (as is the case for many 3D
applications), removing this repeated transmission of data would
increase efficiency by orders of magnitude. The API already had
functions that facilitated one-time
bulk uploads of data: The glTexImage
family, promoted from the EXT_texture_object
extension in OpenGL 1.1. The programmer performs
the following steps in order to upload a texture to OpenGL:
Later in the program, when actually drawing primitives, the programmer
once again calls glBindTexture() to
select a texture and then supplies texture coordinates to draw textured
polygons. Note that at this point, the actual texture bitmap data could
have been discarded by the program and may only exist in the OpenGL
implementation's memory (most likely hardware texture memory).
In OpenGL 1.5,
Vertex Buffer Objects (VBOs) were
added, allowing vertex data to be uploaded to the OpenGL implementation
and then possibly discarded from the program's memory space.
Somewhat confusingly, the designers reused much of the
Vertex Array
API but added explicitly bound "buffers" in a similar manner to the
texture API. As an example
(
source):
#define GL_GLEXT_PROTOTYPES 1
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <GL/glut.h>
#include <GL/glext.h>
typedef float vector3[3];
typedef struct
{
vector3 position;
vector3 colour;
} vertex;
static GLuint vertices_name;
static GLuint triangles[2];
static void
init(void)
{
vertex *data = malloc(4 * sizeof(vertex));
if (data == NULL) abort();
data[0].position[0] = 0.0;
data[0].position[1] = 0.0;
data[0].position[2] = 0.0;
data[0].colour[0] = 1.0;
data[0].colour[1] = 0.0;
data[0].colour[2] = 0.0;
data[1].position[0] = 100.0;
data[1].position[1] = 0.0;
data[1].position[2] = 0.0;
data[1].colour[0] = 0.0;
data[1].colour[1] = 1.0;
data[1].colour[2] = 0.0;
data[2].position[0] = 100.0;
data[2].position[1] = 100.0;
data[2].position[2] = 0.0;
data[2].colour[0] = 0.0;
data[2].colour[1] = 0.0;
data[2].colour[2] = 1.0;
data[3].position[0] = 0.0;
data[3].position[1] = 100.0;
data[3].position[2] = 0.0;
data[3].colour[0] = 1.0;
data[3].colour[1] = 1.0;
data[3].colour[2] = 0.0;
glGenBuffers(1, &vertices_name);
glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(vertex), data, GL_STATIC_DRAW);
free(data);
glGenBuffers(2, triangles);
{
unsigned char indices[] = { 0, 1, 2 };
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
{
unsigned char indices[] = { 1, 2, 3 };
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
assert(glGetError() == GL_NO_ERROR);
}
static void
reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, -1.0, 100.0);
assert(glGetError() == GL_NO_ERROR);
}
static void
display(void)
{
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPushMatrix();
glTranslated(20.0, 20.0, 0.0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[0]);
glVertexPointer(3, GL_FLOAT, sizeof(vertex), (void *) 0);
glColorPointer(3, GL_FLOAT, sizeof(vertex), (void *) offsetof(vertex, colour));
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *) 0);
glTranslated(120.0, 120.0, 0.0);
glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[1]);
glVertexPointer(3, GL_FLOAT, sizeof(vertex), (void *) 0);
glColorPointer(3, GL_FLOAT, sizeof(vertex), (void *) offsetof(vertex, colour));
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *) 0);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glPopMatrix();
assert(glGetError() == GL_NO_ERROR);
glutSwapBuffers();
}
int
main(int argc, char **argv)
{
glutInit(&argc, argv);
glutCreateWindow("Vertex array triangle");
init();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutIdleFunc(glutPostRedisplay);
glutMainLoop();
return 0;
}
Note that the code is almost identical to that of the
vertex array code, except that vertex and index data is uploaded
once and referenced later via explicitly bound buffers. The data
exists only in the OpenGL implementation's memory. Also, calls to
glVertexPointer(),
glColorPointer(),
and glDrawElements() are now passed
integer byte offsets as opposed to memory addresses. Yes, unfortunately,
this is a type error in languages with stronger type systems!
The vertex buffer API also provides functions for modifying
and/or replacing already buffered data such as
glBufferSubData() (analogous to
glTexSubImage() in the texture
API) and mapping buffers directly into the application's address
space for real-time modification
(glMapBuffer(), which is
somewhat analogous to the POSIX mmap()
function).
Modern Vertex Buffer Objects
At the time of writing, OpenGL 4.2 is the most recent revision
of the specification. Prior to 3.0, the specifications were
backwards compatible with each version having strictly more
features than the last. As stated previously, the programming
models supported by the early revisions of OpenGL do not reflect
how modern graphics hardware works at all. Starting with 3.0,
all methods of vertex specification other than
vertex buffer objects
have been deprecated/removed.
With all modern graphics hardware now supporting a fully programmable
pipeline, the concept of a "vertex buffer" has essentially
been generalized to a "vertex attribute buffer". In other words,
programs no longer use functions such as
glVertexPointer() to mark sections
of an array as containing "vertex position data", or
glColorPointer() to mark sections
of an array as "colour data". Indeed, these functions are also
deprecated and do not exist in versions of OpenGL beyond 3.0. OpenGL
essentially no longer has any concept of "the colour of a vertex"
or "the position of the vertex"!
Now, of course, the previous statement seems nonsensical. How would
it be possible to produce 3D graphics in a system that could not
understand "the position of a vertex"? Essentially, OpenGL now leaves
the interpretation of buffered data up to programs written
by the programmer that execute directly on the GPU. The programs
are written in GLSL ("GL Shading Language") and a full tutorial is
out of the scope of this document. In other words, where "vertex"
and "colour" data (etc.) served as input for a fixed-function rendering
pipeline, data is now treated generically and serves as input to
a programmable rendering pipeline.
With "modern" OpenGL, a simple example is rather difficult to write
without supporting code, as most of the functionality has been
delegated to external libraries. The matrix stack, for example,
no longer exists and must be provided by third party matrix libraries.
In the following example, a few deprecated features are used for the
sake of simplicity (such as the immediate mode
glTranslate() function)
(
source):
#define GL_GLEXT_PROTOTYPES 1
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h>
#include <GL/glext.h>
typedef float vector4[4];
typedef struct
{
vector4 position;
vector4 colour;
} vertex;
static GLuint vertices_name;
static GLuint triangles[2];
static void
init_data()
{
vertex *data = malloc(4 * sizeof(vertex));
if (data == NULL) abort();
data[0].position[0] = 0.0;
data[0].position[1] = 0.0;
data[0].position[2] = 0.0;
data[0].position[3] = 1.0;
data[0].colour[0] = 1.0;
data[0].colour[1] = 0.0;
data[0].colour[2] = 0.0;
data[0].colour[3] = 1.0;
data[1].position[0] = 100.0;
data[1].position[1] = 0.0;
data[1].position[2] = 0.0;
data[1].position[3] = 1.0;
data[1].colour[0] = 0.0;
data[1].colour[1] = 1.0;
data[1].colour[2] = 0.0;
data[1].colour[3] = 1.0;
data[2].position[0] = 100.0;
data[2].position[1] = 100.0;
data[2].position[2] = 0.0;
data[2].position[3] = 1.0;
data[2].colour[0] = 0.0;
data[2].colour[1] = 0.0;
data[2].colour[2] = 1.0;
data[2].colour[3] = 1.0;
data[3].position[0] = 0.0;
data[3].position[1] = 100.0;
data[3].position[2] = 0.0;
data[3].position[3] = 1.0;
data[3].colour[0] = 1.0;
data[3].colour[1] = 1.0;
data[3].colour[2] = 0.0;
data[3].colour[3] = 1.0;
glGenBuffers(1, &vertices_name);
glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(vertex), data, GL_STATIC_DRAW);
free(data);
glGenBuffers(2, triangles);
{
unsigned short indices[] = { 0, 1, 2 };
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
{
unsigned short indices[] = { 1, 2, 3 };
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
assert(glGetError() == GL_NO_ERROR);
}
static GLuint shader_vertex;
static GLuint shader_fragment;
static GLuint shader_program;
static const char *shader_vertex_source[] = {
"#version 110\n",
"\n",
"attribute vec4 position_data;\n",
"attribute vec4 colour_data;\n",
"\n",
"void main()\n",
"{\n",
" gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * position_data;\n",
" gl_FrontColor = colour_data;\n",
"}\n"
};
static const char *shader_fragment_source[] = {
"#version 110\n",
"\n",
"void main()\n",
"{\n",
" gl_FragColor = gl_Color;\n",
"}\n"
};
static void
init_shaders(void)
{
GLint ok;
char shader_log[8192];
/*
* Compile vertex shader.
*/
shader_vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(
shader_vertex,
sizeof(shader_vertex_source) / sizeof(const char *),
shader_vertex_source,
NULL);
glCompileShader(shader_vertex);
glGetShaderiv(shader_vertex, GL_COMPILE_STATUS, &ok);
if (ok == GL_FALSE) {
GLsizei size;
glGetShaderInfoLog(shader_vertex, sizeof(shader_log), &size, shader_log);
fprintf(stderr, "fatal: vertex shader:\n%s\n", shader_log);
exit(1);
}
/*
* Compile fragment shader.
*/
shader_fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(
shader_fragment,
sizeof(shader_fragment_source) / sizeof(const char *),
shader_fragment_source,
NULL);
glCompileShader(shader_fragment);
glGetShaderiv(shader_fragment, GL_COMPILE_STATUS, &ok);
if (ok == GL_FALSE) {
GLsizei size;
glGetShaderInfoLog(shader_fragment, sizeof(shader_log), &size, shader_log);
fprintf(stderr, "fatal: fragment shader:\n%s\n", shader_log);
exit(1);
}
/*
* Link shading program.
*/
shader_program = glCreateProgram();
glAttachShader(shader_program, shader_vertex);
glAttachShader(shader_program, shader_fragment);
glLinkProgram(shader_program);
glGetProgramiv(shader_program, GL_LINK_STATUS, &ok);
if (ok == GL_FALSE) {
GLsizei size;
glGetShaderInfoLog(shader_program, sizeof(shader_log), &size, shader_log);
fprintf(stderr, "fatal: %s\n", shader_log);
exit(1);
}
assert(glGetError() == GL_NO_ERROR);
}
static void
init(void)
{
init_data();
init_shaders();
}
static void
reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, -1.0, 100.0);
assert(glGetError() == GL_NO_ERROR);
}
static void
display(void)
{
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
{
const int position_attrib = glGetAttribLocation(shader_program, "position_data");
const int colour_attrib = glGetAttribLocation(shader_program, "colour_data");
assert (position_attrib != -1);
assert (colour_attrib != -1);
glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[0]);
glEnableVertexAttribArray(position_attrib);
glVertexAttribPointer(position_attrib, 4, GL_FLOAT, GL_FALSE, sizeof(vertex), 0);
glEnableVertexAttribArray(colour_attrib);
glVertexAttribPointer(colour_attrib, 4, GL_FLOAT, GL_FALSE, sizeof(vertex), (void *) offsetof(vertex, colour));
glUseProgram(shader_program);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (void *) 0);
glTranslated(120.0, 120.0, 0.0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[1]);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (void *) 0);
glUseProgram(0);
glDisableVertexAttribArray(position_attrib);
glDisableVertexAttribArray(colour_attrib);
}
assert(glGetError() == GL_NO_ERROR);
glutSwapBuffers();
}
int
main(int argc, char **argv)
{
glutInit(&argc, argv);
glutCreateWindow("Modern GL triangles");
init();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutIdleFunc(glutPostRedisplay);
glutMainLoop();
return 0;
}
The preceding program uses custom vertex and fragment shaders. The
vertex shader defines two attributes, named
position_data and
colour_data, respectively. The
program uses glVertexAttribPointer
to specify that position_data takes
input from the buffered vertex data in the same manner that
glVertexPointer did in the previous
programs. It also uses
glVertexAttribPointer to specify
that colour_data takes input
from the buffered vertex data in the same manner that
glColorPointer did in the previous
programs. As stated before, the interpretation of the data is left up
to the writer of the shading program that runs on the GPU. In the vertex
and fragment shaders above, the position data is multiplied by the
modelview and projection matrices and the colour data is interpolated
and used in the fragment shader. The precise details of these programs
are better described by any of the GLSL tutorials (or the official GLSL
book).