How can I pass multiple textures to a single shader?

lofidevops picture lofidevops · Aug 11, 2014 · Viewed 30.1k times · Source

I am using freeglut, GLEW and DevIL to render a textured teapot using a vertex and fragment shader. This is all working fine in OpenGL 2.0 and GLSL 1.2 on Ubuntu 14.04.

Now, I want to apply a bump map to the teapot. My lecturer evidently doesn't brew his own tea, and so doesn't know they're supposed to be smooth. Anyway, I found a nice-looking tutorial on old-school bump mapping that includes a fragment shader that begins:

uniform sampler2D DecalTex; //The texture
uniform sampler2D BumpTex; //The bump-map 

What they don't mention is how to pass two textures to the shader in the first place.

Previously I

//OpenGL cpp file
glBindTexture(GL_TEXTURE_2D, textureHandle);

//Vertex shader
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;

//Fragment shader
gl_FragColor = color * texture2D(DecalTex,gl_TexCoord[0].xy);

so now I

//OpenGL cpp file
glBindTexture(GL_TEXTURE_2D, textureHandle);
glBindTexture(GL_TEXTURE_2D, bumpHandle);

//Vertex shader
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
gl_TexCoord[1] = gl_TextureMatrix[1] * gl_MultiTexCoord1;

//Fragment shader
gl_FragColor = color * texture2D(BumpTex,gl_TexCoord[0].xy);
//no bump logic yet, just testing I can use texture 1 instead of texture 0

but this doesn't work. The texture disappears completely (effectively the teapot is white). I've tried GL_TEXTURE_2D_ARRAY, glActiveTexture and few other likely-seeming but fruitless options.

After sifting through the usual mixed bag of references to OpenGL and GLSL new and old, I've come to the conclusion that I probably need glGetUniformLocation. How exactly do I use this in the OpenGL cpp file to pass the already-populated texture handles to the fragment shader?

(This is homework so please answer with minimal code fragments (if at all). Thanks!)

Failing that, does anyone have a tea cosy mesh?

Answer

glampert picture glampert · Aug 11, 2014

It is very simple, really. All you need is to bind the sampler to some texture unit with glUniform1i. So for your code sample, assuming the two uniform samplers:

uniform sampler2D DecalTex;  // The texture  (we'll bind to texture unit 0)
uniform sampler2D BumpTex;   // The bump-map (we'll bind to texture unit 1)

In your initialization code:

// Get the uniform variables location. You've probably already done that before...
decalTexLocation = glGetUniformLocation(shader_program, "DecalTex");
bumpTexLocation  = glGetUniformLocation(shader_program, "BumpTex");

// Then bind the uniform samplers to texture units:
glUseProgram(shader_program);
glUniform1i(decalTexLocation, 0);
glUniform1i(bumpTexLocation,  1);

OK, shader uniforms set, now we render. To do so, you will need the usual glBindTexture plus glActiveTexture:

glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
glBindTexture(GL_TEXTURE_2D, decalTexHandle);

glActiveTexture(GL_TEXTURE0 + 1); // Texture unit 1
glBindTexture(GL_TEXTURE_2D, bumpHandle);

// Done! Now you render normally.

And in the shader, you will use the textures samplers just like you already do:

vec4 a = texture2D(DecalTex, tc);
vec4 b = texture2D(BumpTex,  tc);

Note: For techniques like bump-mapping, you only need one set of texture coordinates, since the textures are the same, only containing different data. So you should probably pass texture coordinates as a vertex attribute.