Objects in OpenGL, and Life Without Orion's Graphics Library
CS 381 Lecture, Dr. Lawlor
You can download all these tiny examples (Zip, Tar-gzip).
There are lots of interesting programming idioms in OpenGL.
Flags
Flags are the simplest idiom in OpenGL. For example, GL_BLEND represents alpha blending. "glEnable(GL_BLEND);"
turns on alpha blending. "glDisable(GL_BLEND);" turns off alpha
blending. glEnable and glDisable take any of a giant list
of flags, and can be used to turn on and off all sorts of stuff.
Here are the flags I use most often:
- glEnable(GL_BLEND); turns on alpha blending. I often
turn off alpha blending when writing a pixel shader, which means I
don't have to worry about generating a correct alpha value.
Rendering opaque geometry is sometimes a few percent faster with alpha
blending disabled. Also see glColorMask (to turn off color writes) and glBlendFunc (to control the alpha blending equation).
- glEnable(GL_DEPTH_TEST); turns on depth buffer reads and
writes. I usually turn off depth buffer stuff when doing
full-screen operations (e.g., darken the whole screen). Also see glDepthMask (to turn off depth writes) and glDepthFunc (to control the depth test).
- glEnable(GL_STENCIL_TEST); turns on stencil buffer reads and
writes. You usually don't mess with the stencil buffer, and turn
it on only when you need it. Also see glStencilMask (enable stencil writes), glStencilFunc (control stencil test), and glStencilOp (control stencil writes).
Most of the other flags control fixed-function hardware, and aren't useful if you use GLSL.
Objects
There's a standard way that OpenGL deals with stuff that takes some
time to create--textures, GLSL programs, framebuffers, arrays of
vertices, and so on. This is the OpenGL "object model".
To create a new OpenGL object of type T, you call a routine named
something like glGenT. This returns a GLuint, which is never
zero. Usually the first time you call the routine, it returns 1,
then 2, etc. OpenGL calls these GLuints "names", but they're just
handles, or indices into the hardware's list of resources.
To set OpenGL's current T object, you call a routine named something
like glBindT. Any subsequent operations then refer to this
object. Calling glBindT with an object of 0 goes back to the
default object.
Texture Objects
Here's how you set up a texture: call glGenTextures, bind the texture, upload pixels, and set sampling mode.
GLuint texNum=0;
glGenTextures(1,&texNum); /* texNum is now a unique "texture name"--probably the value 1! */
glBindTexture(GL_TEXTURE_2D,texNum); /* texNum is now the current texture--all texture ops now refer to texNum */
/* Fill the currently bound texture (texNum) with pixel data--red, green, blue, white. */
unsigned char texData[]={0xff,0x00,0x00, 0x00,0xff,0x00, 0x00,0x00, 0xff, 0xff,0xff,0xff};
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB8,4,1,0,GL_RGB,GL_UNSIGNED_BYTE,texData);
/* Set the magnification, minification, and wrap modes on the current texture (texNum) */
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
glBindTexture(GL_TEXTURE_2D,0); /* back to the default texture name--zero */
Note that the last glBindTexture to reset back to the zero texture isn't actually needed, but it's a good habit.
Here's how you set up to draw using a texture: just activate a "texture
unit" to process the texture, and the bind the texture name.
glActiveTextureARB(GL_TEXTURE0_ARB); /* we're using texture unit zero (this is the default, actually) */
glBindTexture(GL_TEXTURE_2D,texNum);
Now you'd copy the texture unit (0) into a GLSL "uniform sampler2D", and call texture2D in GLSL to look up texture values.
Here's how to throw away all the texture data bound to texNum.
Note you can have many textures at once, so only throw out stuff you're
really done using!
The sequence to keep in mind is:
Creation: gen, bind, and then set up
Draw: bind
Delete: delete
My "oglTexture" class (in ogl/util.h) wraps these calls.
One slightly odd thing about textures is that you actually *can* have
several different textures active at once, but they have to be in
different texture "units". You set the current texture unit to i
with "glActiveTextureARB(i+GL_TEXTURE0_ARB);". A glBindTexture
call changes the current texture of the current unit. So to set
texture unit 0 to foo and texture unit 1 to bar, you'd call:
glActiveTextureARB(0+GL_TEXTURE0_ARB);
glBindTexture(GL_TEXTURE_2D,foo);
glActiveTextureARB(1+GL_TEXTURE0_ARB);
glBindTexture(GL_TEXTURE_2D,bar);
glActiveTextureARB(0+GL_TEXTURE0_ARB);
In GLSL, you actually put the texture *unit* number into a "uniform sampler2D" variable as an int.
It's good practice to leave texture unit 0 active--that's the default texture unit.
You can have at least tens of thousands of texture names in your
program, and switch between them pretty quickly. However, there are
only 32 texture units, so you can have at most 32 textures bound and
active in a single fragment program. This usually isn't a
very serious limitation!
Framebuffer Objects
Here's how to create and use a framebuffer object (FBO), for rendering to a texture. See the documentation in EXT_framebuffer_object
GLuint fb;///< Framebuffer object
glGenFramebuffersEXT(1, &fb); /* Make a new framebuffer object */
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb); /* set FB as the current framebuffer object */
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, /* bind texture name "tex" as the color buffer */
GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D, tex, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); /* back to default framebuffer (the screen) */
// Here's how to draw using the framebuffer (this is fairly expensive: 35us)
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status!=GL_FRAMEBUFFER_COMPLETE_EXT) Quit("Bad framebuffer setup!");
glViewport(0,0,tex_w,tex_h); /* set viewport to size of texture */
// .. drawing calls now go to the our framebuffer...
// Switch back to drawing to screen:
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glViewport(0,0,win_w,win_h);
// Throw away our framebuffer (fairly expensive!)
glDeleteFramebuffersEXT(1,&fb);
You can use any texture as the color buffer for a framebuffer.
Rendered stuff directly affects the currently bound texture.
You can actually bind *multiple* color buffers into a single
framebuffer (GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT,
GL_COLOR_ATTACHMENT2_EXT...). You can write to these with
"gl_FragColor[i]=..." in GLSL. This is called "Multiple Render
Targets", or MRT. For some reason, you're allowed to rearrange
the mapping from gl_FragColor to COLOR_ATTACHMENT, although I always
use the identity mapping:
int n_out=2;
int out_names[2];
out_names[0]=GL_COLOR_ATTACHMENT0_EXT; /* destination for gl_FragColor[0] */
out_names[1]=GL_COLOR_ATTACHMENT1_EXT; /* destination for gl_FragColor[1] */
glDrawBuffersATI(n_out,out_names);
Modern hardware only supports up to 4 render targets, and using more
than one target does have an appreciable slowdown. About the only
time you really need MRT is when doing weird computations that need
many output values (e.g., spectral rendering, or raytracing, or some
complicated per-pixel simulation), not when doing normal color
rendering.
GLSL Compiled Shader Objects
Here's how to set up a GLSL "shader object" (these routines are documented in the ARB_shader_objects extension)
/* Make a program object */
GLhandleARB prog=glCreateProgramObjectARB();
/* Make vertex shader object */
GLhandleARB vert=glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
const char *glsl_code="void main(void) {gl_Position=vec4(vec3(0.8),1.0)*gl_Vertex;}";
glShaderSourceARB(vert,1,&glsl_code,NULL);
glCompileShaderARB(vert);
glAttachObjectARB(prog,vert);
/* Make fragment shader object */
GLhandleARB frag=glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
glsl_code="void main(void) {gl_FragColor=vec4(1.0,0.0,0.0,1.0);}";
glShaderSourceARB(frag,1,&glsl_code ,NULL);
glCompileShaderARB(frag);
glAttachObjectARB(prog,frag);
/* Link the program. FIXME: check for link
errors, with glGetObjectParameterivARB and glGetInfoLogARB */
glLinkProgramARB(prog);
Here's how to set a uniform variable, assuming you've got a "uniform
float fooness;" somewhere in your vertex or fragment shader:
glUseProgramObjectARB(prog);
int loc=glGetUniformLocationARB(prog,"fooness");
glUniform1fARB(loc,0.76);
You can save the integer "loc", if you're updating uniform variables all the time.
Here's how to use the program object for rendering:
glUseProgramObjectARB(prog);
// Everything we draw (glBegin/glEnd) here will use our programmable shader...
glUseProgramObjectARB(0); /* reset back to default fixed-function program */
Here's how to delete a program object:
glDeleteObjectARB(vert);
glDeleteObjectARB(frag);
glDeleteObjectARB(prog);