• TechConficio

OpenGL ES multi-texture example

This is the first in what I hope to be a couple of examples on using OpenGL on the iPhone. As we all know, there are examples around, but not that many. Jeff LaMarche is porting some of the NeHe tutorial examples, which is great.

I know that OpenGL ES 1.1 is what’s in use on the iPhone, but have been working my way through the OpenGL ES 2.0 Programming Guide anyway, trying to make things work.

To make a long story short, I started by wanting to do some water effects alá Koi Pond and ended up looking at multi-texturing. After some thrashing around, the solution is really straightforward. The main “mental” hurdle (in my case) was to remember that OpenGL implementations are state machines. You set the state and then execute the rendering.

What we’re going for in this example is what is shown on page 223 of the OpenGL ES 2.0 Programming Guide, namely one texture (brick wall) modulated by another (a light map). The effect is that of a light shining on a wall.


To make this simple, I’ve based this example on the PVRTextureLoader example from Apple, which you need to get and, if you are going to run the example on a device, configure for your registered device(s). We’ll stick with the brick texture that comes with it, but will need a light map for the modulation. For that you can download the OpenGL ES 2.0 Programming Guide sources and grab the image, or just use the image below.


The inspiration for the multi-texturing was taken from the Oolong engine Per-PixelLighting example (Oolong Engine2/Examples/Renderer/). All the code changes required are made in the EAGLView.h and EAGLView.m files.

The light map is a second texture, so we need to define an index for it to be used in the GLuint _textures[kNumTextures] array. Near the top of EAGLView.h, add the enum indicated below.

enum
{
	kTexturePvrtcMipmap4,
	kTexturePvrtcMipmap2,
	kTexturePvrtc4,
	kTexturePvrtc2,
	kTextureMipmap,
	kTexture,
	kTextureLightMap,
	kNumTextures
};

Add the lightmap.png file to the Resources group in the project.

Now in EAGLView.m, add a loadImageFile statement to the loadTextures method after the two existing statments;

	[self loadImageFile:@"Brick" ofType:@"png" mipmap:TRUE texture:kTextureMipmap];
	[self loadImageFile:@"Brick" ofType:@"png" mipmap:FALSE texture:kTexture];
	[self loadImageFile:@"lightmap" ofType:@"png" mipmap:FALSE texture:kTextureLightMap];

The last change is to the drawView method. Instead of showing diffs, here’s the whole method;

- (void)drawView
{
	const GLfloat squareVertices[] = {
		-1.0f, -1.0f,
		1.0f, -1.0f,
		-1.0f,  1.0f,
		1.0f,  1.0f
	};
	const GLfloat squareTexCoords[] = {
		0.0, 1.0,
		1.0, 1.0,
		0.0, 0.0,
		1.0, 0.0
	};
	GLenum err;
	
	[EAGLContext setCurrentContext:_context];
	
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, _viewFramebuffer);
	glViewport(0, 0, _backingWidth, _backingHeight);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustumf(-1.0f, 1.0f, -1.5, 1.5, 1.0f, 10.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glScalef(_scale, _scale, 1.0f);
	glTranslatef(0.0f, 0.0f, -2.0f);
	glRotatef(_rotate, 1.0f, 0.0f, 0.0f);
	
	glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	
	glVertexPointer(2, GL_FLOAT, 0, squareVertices);
	glEnableClientState(GL_VERTEX_ARRAY);
	
	// we'll be doing multitexture operations, so we need to specify texture 
	// coords for all (2 in this example) textures
	glClientActiveTexture(GL_TEXTURE0); // first texture
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, squareTexCoords);
	glClientActiveTexture(GL_TEXTURE1); // second texture
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, squareTexCoords);
	
	// Enable 2D texturing for the first texture.
	glActiveTexture(GL_TEXTURE0);
	glEnable(GL_TEXTURE_2D);
	
	// Enable 2D texturing for the second texture.
	glActiveTexture(GL_TEXTURE1);
	glEnable(GL_TEXTURE_2D);
	
	// We are trying for the multi-textured lighting effect from the OpenGL
	// ES 2.0 book, page 223, Figure 10-3. The relevant shader equation is
	// basecolor * (lightcolor + 0.25), so we set the constant as a base colour
	glColor4f(0.25f, 0.25f, 0.25f, 0.25f);
	
	// Set the light map has the current texture
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D,  _textures[kTextureLightMap]);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _minTexParam);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _magTexParam);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, _anisotropyTexParam);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	
  // add the light map to the constant 0.25
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);
	glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS);
	
	// Now set the background map (bricks) and modulate (multiply)
	glActiveTexture(GL_TEXTURE1);
	// we'll keep this code here as _texSelection will never be our lightmap
	glBindTexture(GL_TEXTURE_2D, _textures[_texSelection]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _minTexParam);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _magTexParam);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, _anisotropyTexParam);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	
	/* Set the texture environment mode for this texture to combine */
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
	
	/* Set the method we're going to combine the two textures by. */
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
	
	/* Use the previous combine texture as source 0*/
	glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
	
	/* Use the current texture as source 1 */
	glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
	
	/*
	 Set what we will operate on, in this case we are going to use
	 just the texture colours.
	 */
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	
	glDisable(GL_TEXTURE_2D);
	
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, _viewRenderbuffer);
	[_context presentRenderbuffer:GL_RENDERBUFFER_OES];
	
	err = glGetError();
	if (err != GL_NO_ERROR)
		NSLog(@"Error in frame. glError: 0x%04X", err);
}

I’ve added extra comments to help understand the changes. The only other thing worth noting is the glTexParameteri statements applied to the light map texture. Since the glBindTexture call for the light map is fixed, we could have moved these outside the drawView method and saved some cycles. However, I put them in as the germ of a “reader exercise” to add mipmap support for the light map. When you try the demo controls, you’ll see what I mean ;-)

The final result should look like the screenshot below.

After this, I’ll be playing with normals. I’ve managed to create a faux distortion effect by shifting texture vertices, so the next step is to look at lighting and surface effects...


Based in Awesome Alberta, Canada

  • GitHub-Mark-Light-200px-plus
  • White Pinterest Icon
  • Twitter Clean

© 2019 by TechConficio Inc.. Proudly created with Wix.com