Different modes of rendering with OpenGL in Java

This is a brief list of some code snippets showcasing some of the alternative modes of rendering that OpenGL offers. They come in handy when starting new code as a template and are useful when debugging as they serve as a kind of correct and basic implementation to look at. The implementation is in Java using JOGL but they work with very few changes in LWJGL or even in other languages. I took care of building the NIO buffers by hand instead of using the helper functions that both JOGL and LWJGL offer so that the code is more general.

2013-02-06 14_32_11-41 fps

Immediate mode:
This mode is the oldest one and only works with the fixed function pipeline. It’s slow and cumbersome and makes too many OpenGL calls. OpenGL is designed for Client/Server rendering. In many programs the server is the graphics card and the communication is fast but it’s good to always think of the server as some remote machine with a slow communication link. Thinking like that makes easy to understand some of the good and bad practices while using OpenGL. Although deprecated in OpenGL 3.0 and eliminated altogether in 3.1, Immediate mode is still useful to make drafts and fast examples and it’s still present in OpenGL ES and versions of OpenGL up to 3.1.

gl.glEnable(GL2.GL_COLOR_MATERIAL);
gl.glDisable(GL2.GL_LIGHTING);
gl.glColorMaterial(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE);

gl.glBegin(GL2.GL_TRIANGLES);
gl.glColor4f(1.0f, 0.0f, 0.0f, 0.0f);
gl.glVertex3f(0.0f, 0.0f, 0.0f);
gl.glColor4f(0.0f, 1.0f, 0.0f, 0.0f);
gl.glVertex3f(1.0f, 0.0f, 0.0f);
gl.glColor4f(0.0f, 0.5f, 0.5f, 0.0f);
gl.glVertex3f(1.0f, 1.0f, 0.0f);
gl.glColor4f(0.0f, 0.0f, 1.0f, 0.0f);
gl.glVertex3f(1.0f, 1.0f, 0.0f);
gl.glColor4f(0.0f, 0.0f, 1.0f, 0.0f);
gl.glVertex3f(0.0f, 1.0f, 0.0f);
gl.glColor4f(1.0f, 0.0f, 0.0f, 0.0f);
gl.glVertex3f(0.0f, 0.0f, 0.0f);
gl.glEnd();

gl.glEnable(GL2.GL_LIGHTING);
gl.glDisable(GL2.GL_COLOR_MATERIAL);

Indexed Vertex Arrays

Instead of using a separate call for each vertex or vertex attribute, with vertex arrays we can define an array of vertices and indices, fill it with data, and when ready, tell the server to render it with just one call. This mode is much faster since nearly all the data we send is vertex data. With immediate mode we sent additional OpenGL related metadata with every call but now we only have two or three calls. Using indices although more complex and arguably less flexible is more space efficient. With this little example we don’t save anything by using them, but with big models we can save a lot. Good tutorial about vertex arrays.

		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);

		ByteBuffer vertices = ByteBuffer.allocateDirect(4*3*6);
		vertices.order(ByteOrder.nativeOrder());
		FloatBuffer fvertices = vertices.asFloatBuffer();
		float verts[] = {
			//X,Y,Z,
			0.0f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f,
			1.0f, 1.0f, 0.0f,
			0.0f, 1.0f, 0.0f,
		};
		fvertices.put(verts);
		fvertices.flip();

		ByteBuffer indices = ByteBuffer.allocateDirect(4*6);
		indices.order(ByteOrder.nativeOrder());
		IntBuffer iindices = indices.asIntBuffer();
		int indexs[] = { 0, 1, 2, 2, 3, 0 };
		iindices.put(indexs);
		iindices.flip();

		gl.glVertexPointer(3, GL2.GL_FLOAT, 0, vertices);
		gl.glDrawElements(GL2.GL_TRIANGLES, 6, GL2.GL_UNSIGNED_INT, indices);

		gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL2.GL_COLOR_ARRAY);

Indexed interleaved vertex arrays

We can use various different arrays for each vertex attribute: position, color, normal, texture coordinates, etc. Or we can put all the data interleaved in just one array, send it, and let the server process it all at once. This should be always faster than using separate arrays.

		gl.glEnable(GL2.GL_COLOR_MATERIAL);
		gl.glDisable(GL2.GL_LIGHTING);
		gl.glColorMaterial(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE);

		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL2.GL_COLOR_ARRAY);

		ByteBuffer vertices = ByteBuffer.allocateDirect(4*(3+4)*6);
		vertices.order(ByteOrder.nativeOrder());
		FloatBuffer fvertices = vertices.asFloatBuffer();
		float verts[] = {
			//X,Y,Z, R,G,B,A
			0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
			1.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f,
			0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
		};
		fvertices.put(verts);
		fvertices.flip();

		ByteBuffer indices = ByteBuffer.allocateDirect(4*6);
		indices.order(ByteOrder.nativeOrder());
		IntBuffer iindices = indices.asIntBuffer();
		int indexs[] = { 0, 1, 2, 2, 3, 0 };
		iindices.put(indexs);
		iindices.flip();

		ByteBuffer colorOffset = vertices.duplicate();
		colorOffset.position(4*3);

		int stride = (3 + 4) * 4;
		gl.glVertexPointer(3, GL2.GL_FLOAT, stride, vertices);
		gl.glColorPointer(4, GL2.GL_FLOAT, stride, colorOffset);
		gl.glDrawElements(GL2.GL_TRIANGLES, 6, GL2.GL_UNSIGNED_INT, indices);

		gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL2.GL_COLOR_ARRAY);

		gl.glEnable(GL2.GL_LIGHTING);
		gl.glDisable(GL2.GL_COLOR_MATERIAL);

Display lists

Display lists look superficially very similar to immediate mode, the difference is that with this technique we compile a series of commands in one list and then send it to the server for rendering. It’s much faster than the previous modes since there is just one roundtrip to the server. The downside is that it’s limited to static data meaning that we can’t make changes to the data in the fly since it lives in the server. If we want to render a slightly different data we have to recompile the whole list and send it again. Display lists were also deprecated in OpenGL 3.0 and eliminated in 3.1 along with all the fixed function pipeline. Here is a tutorial.

		int numberOfLists = 1;
		int index = gl.glGenLists(numberOfLists);

		// Compile display list
		gl.glNewList(index, GL2.GL_COMPILE);

		gl.glColorMaterial(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE);
		gl.glTranslatef(3.0f, 0.0f, 0.0f);
		gl.glBegin(GL2.GL_TRIANGLES);
		gl.glColor4f(1.0f, 0.0f, 0.0f, 0.0f);
		gl.glVertex3f(0.0f, 0.0f, 0.0f);
		gl.glColor4f(0.0f, 1.0f, 0.0f, 0.0f);
		gl.glVertex3f(1.0f, 0.0f, 0.0f);
		gl.glColor4f(0.0f, 0.5f, 0.5f, 0.0f);
		gl.glVertex3f(1.0f, 1.0f, 0.0f);
		gl.glColor4f(0.0f, 0.0f, 1.0f, 0.0f);
		gl.glVertex3f(1.0f, 1.0f, 0.0f);
		gl.glColor4f(0.0f, 0.0f, 1.0f, 0.0f);
		gl.glVertex3f(0.0f, 1.0f, 0.0f);
		gl.glColor4f(1.0f, 0.0f, 0.0f, 0.0f);
		gl.glVertex3f(0.0f, 0.0f, 0.0f);
		gl.glEnd();

		gl.glEndList();

		// Render display list
		gl.glEnable(GL2.GL_COLOR_MATERIAL);
		gl.glDisable(GL2.GL_LIGHTING);

		gl.glCallList(index);

		gl.glEnable(GL2.GL_LIGHTING);
		gl.glDisable(GL2.GL_COLOR_MATERIAL);

		// cleanup
		gl.glDeleteLists(index, numberOfLists);

Interleaved Vertex Buffer Objects (VBO’s)

Vertex Buffer Objects are like remote Vertex Arrays. In fact to use them we use the same API calls for rendering but now we have to define the remote arrays and upload the data beforehand. The main advantage of VBO’s over normal vertex arrays is that the data is stored in the server, near the graphics hardware. In this sense, VBO’s are very similar to display lists but display lists are simpler and less flexible since they only permit static data. VBO’s on the other hand allow updating vertex data on the fly and have three different modes of operation: static, dynamic and stream. Each one offers different optimizations depending on the concrete task. Here is a more in-depth tutorial about VBO’s.

		//Create NIO buffers
		ByteBuffer vertices = ByteBuffer.allocateDirect(4*(3+4)*6);
		vertices.order(ByteOrder.nativeOrder());
		FloatBuffer fvertices = vertices.asFloatBuffer();
		float verts[] = {
			//X,Y,Z, R,G,B,A
			0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
			1.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f,
			0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
		};
		fvertices.put(verts);
		fvertices.flip();

		ByteBuffer indices = ByteBuffer.allocateDirect(4*6);
		indices.order(ByteOrder.nativeOrder());
		IntBuffer iindices = indices.asIntBuffer();
		int indexs[] = { 0, 1, 2, 2, 3, 0 };
		iindices.put(indexs);
		iindices.flip();

		//Upload NIO buffers
		ByteBuffer bufIdsBytes = ByteBuffer.allocateDirect(4*2);
		IntBuffer bufIds = bufIdsBytes.asIntBuffer();
		gl.glGenBuffers(2, bufIds);

		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, bufIds.get(0));
		gl.glBufferData(GL2.GL_ARRAY_BUFFER, vertices.limit(), vertices, GL2.GL_STATIC_DRAW);

		gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, bufIds.get(1));
		gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, indices.limit(), indices, GL2.GL_STATIC_DRAW);

		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
		gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0);

		//Render VBO's
		gl.glEnable(GL2.GL_COLOR_MATERIAL);
		gl.glDisable(GL2.GL_LIGHTING);
		gl.glColorMaterial(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE);

		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL2.GL_COLOR_ARRAY);

		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, bufIds.get(0));
		gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, bufIds.get(1));

		int stride = (3 + 4) * 4;
		gl.glVertexPointer(3, GL2.GL_FLOAT, stride, 0);
		gl.glColorPointer(4, GL2.GL_FLOAT, stride, 4*3);
		gl.glDrawElements(GL2.GL_TRIANGLES, 6, GL2.GL_UNSIGNED_INT, 0);

		gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
		gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0);

		gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL2.GL_COLOR_ARRAY);

		gl.glEnable(GL2.GL_LIGHTING);
		gl.glDisable(GL2.GL_COLOR_MATERIAL);

		//cleanup
		gl.glDeleteBuffers(2, bufIds);

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

Author: Manuel Martín

Software engineer. 3D and machine learning.

Leave a comment