I can think of these uses of texturing:
1. 3D Models can specify texture coordinate for every triangle coordinate instead of color for every triangle face.
2. use texture in a multi-pass fashion for storing image data like a shadow map or all the G-buffers (depth, color, etc) for deferrred rendering quad draw that basically runs fragment shader for screen space.
Here is exert code for 3D texturing
//C++ side
glGenTextures(1, &TextureObjects[Textures::Texture3D])//create texture resource
texture3DSamplers[i] = glGetUniformLocation(shaderPrograms[i], "TextureMap3D"); //find the uniform location of "TextureMap3D" from GLSL side
glUniform1i(texture3DSamplers[i], 3); //3d texture sampler location found in GL_TEXTURE3
glActiveTexture(GL_TEXTURE3);
//generate tex3d_data of 64 images of 64 width x64 length textures
int tex_width = 64;
int tex_height = 64;
int tex_depth = 64;
int tex_size = tex_width * tex_height * tex_depth*4;
//fill tex_data linearly programmatically in this case. Fill it up with shades of blue. But this 3D texture can also be defined declaratively when defining/importing 3D model.
GLubyte* tex3d_data = new GLubyte[tex_size];
for (int d = 0; d < tex_depth; d++) {
//int d = 0;
for (int h = 0; h < tex_height; h++) {
for (int w=0; w < tex_width; w++){
tex3d_data[d* tex_height* tex_width*4 + h*tex_width*4 +w*4 + 0] = 0; //RED component
tex3d_data[d * tex_height * tex_width*4 + h* tex_width*4 +w*4 + 1] = 0; //Green component
tex3d_data[d * tex_height * tex_width*4 + h* tex_width*4 +w*4 + 2] = 255/tex_depth*d; //Blue component
tex3d_data[d * tex_height * tex_width*4 + h* tex_width*4 + w*4 + 3] = 0; //alpha component
}
}
}
glActiveTexture(GL_TEXTURE3); //binding point to resource type
glBindTexture(GL_TEXTURE_3D, TextureObjects[Textures::Texture3D]); //for the glUseProgram(shaderPrograms[shaderProgramSelected]), bind TextureObjects[Textures::Texture3D] resource to GL_TEXTURE3 binding point. Driver then works in the background to make the resource is available and resident to fs shader using 'uniform sampler3D TextureMap3D' is run
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, tex_width, tex_height, tex_depth, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex3d_data);
glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
//draw for this pass
glActiveTexture(GL_TEXTURE3); //need to set the right texture for TEXTURE3 as different pass may have put a different texture to TEXTURE3
glBindTexture(GL_TEXTURE_3D, TextureObjects[Textures::Texture3D]);
glDrawArraysInstanced(GL_TRIANGLES, 0, 3 * primitive->numTriangles, primitive->instanceCount);
//GLSL side
uniform sampler3D TextureMap3D;
layout (location = 0) out vec4 finalColor; //fragment sent to GL_COLOR_ATTACHMENT0 of the current framebuffer
void main(){
...
finalColor = texture(TextureMap3D, fs_in.TexCoord0.xyz); //TexCoord0 is just passed as defined in 3D model without Model-View-Perspective transform
}
2D texturing would be sampler3D above and on C++ side use glTexImage2D function to upload a single texture.
2D texture atlas would be to from C++ side, provide the correct transform texture coordinates to use the right sub texture in a large single texture, and upload a single texture using glTexImage2D.