OpenGL decomposes any complex object into a series of simple primitives: points, lines, triangles, and so on. The reason for this is that graphics hardware is very good at rendering simple objects. Hardware routines for rendering triangles or lines are thousands of times faster than software implementations. More complex objects are unsupported because of the large variety (and hence algorithmic complexity). Objects can be declared to OpenGL using a variety of rendering methods. Because we are just starting with OpenGL, I will cover only the simplest (and sadly, least efficient) rendering method, leaving more involved constructs for subsequent sections. The method we will be using is called immediate mode rendering, and involves sending the data to the hardware using many calls, one per element. Immediate mode rendering blocks are always started with the glBegin call, which specifies what kind of primitive we want to render, and should finish with a call to glEnd. The calls between Begin and End specify the geometric data. Remember that you can batch as many primitives as you want in a Begin-End construct: the more primitives, the better the performance. As a summary, here is a list of the geometric primitives available in OpenGL, along with how data is interpreted in each one. You can see a graphic representation of each primitive in Figure B.2.
Figure B.2. OpenGL's primitive types.Each vertex in a rendering batch can be assigned a color, texture coordinates, normal, and so on. To begin with, colors are specified with the glColor* family of calls. You must keep in mind that OpenGL colors are active after the respective glColor call, so you need to specify the color prior to sending the vertex to the hardware. Additionally, colors are persistent: All vertices in a batch will use the last declared color until a new one is assigned. For example, the following code paints a triangle: glBegin(GL_TRIANGLES); glColor3f(1,0,0); glVertex3f(-1,0,0); glColor3f(1,1,0); glVertex3f(1,0,0); glVertex3f(0,1,0); glEnd(); The first vertex will be red (1,0,0). The second and third will be yellow. As an efficiency concern when using immediate mode rendering, remember to batch primitives together when you can. The following two code snippets, although similar, yield very different performance: for (i=0;i<1000;i++) { glBegin(GL_QUADS); // paint a quad here glEnd(); } glBegin(GL_QUADS); for (i=0;i<1000;i++) { // paint a quad here } glEnd(); In the first case, we are painting 1,000 primitives and sending 2,000 OpenGL calls (each one with its overhead) to the pipeline, and that's not counting the rendering itself. In the second code snippet, we are sending one large batch of primitives and doing only two OpenGL calls. The second code snippet is significantly faster than the first. As you will soon see, there are very few reasons to break a primitive batch, and those reasons are usually performing a transform or changing the texture map. Other than that, we should create batches as big as possible. |