OpenGL: Some clarification on using multiple textures or texture units
Asked Answered
A

1

6

I need to use two textures in my shader, one in the vertex shader, another in the fragment shader. In both cases they are referenced in the shaders like uniform sampler2D tex1; or uniform sampler2D tex2; However I'm not really sure on how to use the related GL calls properly.

Initialization

First of all, how do I create the two textures? Do I need to use multiple texture units like so

GLuint texIdx[2] = {0, 1};
GLuint texName[2];
GLint  texUniformID[2];

// Initialize first texture
glActiveTexture (GL_TEXTURE0 + texIdx[0]);
glGenTextures   (1, &texName[0]);
glBindTexture   (GL_TEXTURE_2D, texName[0]);
glTexImage2D    (GL_TEXTURE_2D, 0, GL_R32F, xDim0, yDim0, 0, GL_RED, GL_FLOAT, someTextureData);

// Initialize second texture
glActiveTexture (GL_TEXTURE0 + texIdx[1]);
glGenTextures   (1, &texName[1]);
glBindTexture   (GL_TEXTURE_2D, texName[1]);
glTexImage2D    (GL_TEXTURE_2D, 0, GL_RGB, xDim1, yDim1, 0, GL_RGB, GL_FLOAT, someOther1TextureData);

// Set the uniforms to refer to the textures
texUniformID[0] = glGetUniformLocation (myShaderProgram, "tex1");
texUniformID[1] = glGetUniformLocation (myShaderProgram, "tex2");
glUniform1i (texUniformID[0], texIdx[0]);
glUniform1i (texUniformID[1], texIdx[1]);

Or can I use a single texture unit, as glGenTextures allows me to create multiple textures, somewhat like that:

GLuint texName[2];
GLint  texUniformID[2];

// Activate some texture unit
glActiveTexture (GL_TEXTURE0);

// Generate two textures
glGenTextures (2, texName);

// Initialize first texture
glBindTexture   (GL_TEXTURE_2D, texName[0]);
glTexImage2D    (GL_TEXTURE_2D, 0, GL_R32F, xDim0, yDim0, 0, GL_RED, GL_FLOAT, someTextureData);

// Initialize second texture
glBindTexture   (GL_TEXTURE_2D, texName[1]);
glTexImage2D    (GL_TEXTURE_2D, 0, GL_RGB, xDim1, yDim1, 0, GL_RGB, GL_FLOAT, someOther1TextureData);

// Set the uniforms to refer to the textures
texUniformID[0] = glGetUniformLocation (myShaderProgram, "tex1");
texUniformID[1] = glGetUniformLocation (myShaderProgram, "tex2");
glUniform1i (texUniformID[0], /* what parameter here? */);
glUniform1i (texUniformID[1], /* what parameter here? */);

So to summarize, I don't understand what's the point on having multiple texture units on the one hand and the ability to create multiple textures with a call to glGenTextures on the other hand and what is the right way to go if I need multiple textures in a shader program.

Usage during rendering

As a follow up question, If I have initialized my multiple textures with the correct way, what is the right order of calls to activate both textures to become active during a call to glDrawElements and what is the right order of calls to successfully update a texture at runtime glTexSubImage2D?

Follow up question

Now one step further, if I use multiple different shader programs in a render call and all of them use textures, how should that be handled? Should each texture for each shader program use a unique texture unit?

Ariana answered 23/8, 2019 at 7:46 Comment(0)
M
5

If you want to use multiple 2 dimensional textures in one shader program, then you've to bind the different texture objects to different texture units.
It is not necessary that the proper texture unit is selected (glActiveTexture) when the texture name (value) is generated (glGenTextures) or when the texture image is specified (glTexImage2D), but the texture has to be bound to the proper texture unit before the object (mesh) is drawn by the use of the shader program.

The binding between the texture sampler uniform in the shader program and the texture object is achieved, by binding the texture to a texture unit and setting the number of the texture unit to uniform variable.

The value of the uniform can either be set by glUniform1i

texUniformID[0] = glGetUniformLocation(myShaderProgram, "tex1");
texUniformID[1] = glGetUniformLocation(myShaderProgram, "tex2");
glUniform1i(texUniformID[0], 0);
glUniform1i(texUniformID[1], 1);

or in-shader by a Binding point layout qualifier:

layout(binding = 0) uniform sampler2D tex1;
layout(binding = 1) uniform sampler2D tex2;

Since the binding points are 0 and 1, the texture objects have to be bound to the texture units 0 (GL_TEXTURE0) and 1 (GL_TEXTURE1), before drawing the geometry with the shader program:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texName[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texName[1]);

glDrawElements(...);

But it is not necessary to select the texture unit 0 respectively 1, when the texture is "created":

glGenTextures(2, texName);

// Initialize first texture
glBindTexture(GL_TEXTURE_2D, texName[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, xDim0, yDim0, 0, GL_RED, GL_FLOAT, data1);

// Initialize second texture
glBindTexture(GL_TEXTURE_2D, texName[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, xDim1, yDim1, 0, GL_RGB, GL_FLOAT, data2);

Of course you can select a texture unit before creating the textures, then it is superfluous to bind them later. But note, glGenTextures doesn't create a texture object, it just reserves names (values) which can be used for texture objects. The texture is created when the name (value) the first time is bound to a texturing target by glBindTexture. This means glBindTexture creates a texture if it doesn't exist or it use the existing texture. glTexImage2D specifies, creates and initialize the image of the existing texture object which is bound to the specified target of the current texture unit:

glGenTextures(2, texName);

// Initialize first texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texName[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, xDim0, yDim0, 0, GL_RED, GL_FLOAT, data1);

// Initialize second texture
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texName[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, xDim1, yDim1, 0, GL_RGB, GL_FLOAT, data2);

glTexSubImage2D updates the content of a texture image of the texture object which is currently bound to the specified target of the current texture unit. All you've to do is to bind the texture object.

glBindTexture(GL_TEXTURE_2D, texName[0]);
glTexSubImage2D(GL_TEXTURE_2D, ...);

But note that glBindTexture binds the texture to the currently selected texture unit, so it may mess up your combinations of texture objects and texture units, if the "wrong" texture unit is currently selected (The current texture unit is a global state). So it may make sense to select the proper texture unit. If the texture object is still bound to the texture unit, it is unnecessary to bind it agian:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texName[0]); // <-- this is possibly unnecessary
glTexSubImage2D(GL_TEXTURE_2D, ...);

Of course different shader programs can use the same binding points. But note that the number of texture units is limited. If you've a lot of textures it is not possible to bind each texture object to a different texture unit. But, if you've 2 texture objects and different shader programs it is handy to bind the 2 texture objects to different texture inits and to (re)use the same binding points in each shader program.

Mccabe answered 23/8, 2019 at 8:34 Comment(1)
Great explanation, thank you! This clears up ALL my questionsAriana

© 2022 - 2024 — McMap. All rights reserved.