multiple textures on a single VBO?

Right now I have a UV mapped cube using a VBO. The textures are applied from a single TGA file. See picture:


Instead of reading from a single TGA file I would like to be able to read the texture for each side of the cube from individual files. I’ve looked at using a texture array or just cramming all of the individual files into one texture at run-time (what some people are calling a “texture-atlas”?). I’m not sure what would be best solution.

The end goal is to be able to apply an arbitrary number of textures from individual files to a VBO (which won’t necessarily be a cube, I just picked a cube for simplicity’s sake). So cube maps are out for that reason.

Here are the relevant sections of the code. Any help on how to accomplish this would be appreciated.

struct vertexType
{
	GLfloat xPos,yPos,zPos;
	GLfloat xNormal,yNormal,zNormal;
	GLfloat uTCoord,vTCoord;
};

GLShaderManager	shaderManager;
std::vector<vertexType> vertexArray;
std::vector<GLushort> vertexIndex;
std::vector<GLfloat> fNormals;

GLuint vdataBufferID; //vertex data
GLuint viBufferID; //vertex index buffer
GLFrame cameraFrame;
GLFrame modelFrame;
bool rightMouseDown;
int oldMouseX,oldMouseY;
int deltaMouseX,deltaMouseY;
GLfloat lightPos[]={0.0f,1.0f,0.0f};
GLfloat lightColor[]={1.0f,1.0f,1.0f,1.0f};
GLuint textureID;

GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLFrustum frustum;
GLGeometryTransform transformPipeline;

//texture and draw
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode);
void loadVertexArray(GLfloat x,GLfloat y,GLfloat z, GLfloat u, GLfloat v);
void drawTriCube();
//initialization
void initTriCube(float xPos,float yPos,float zPos);
void SetupRC();
//calculate normals
void calcFaceNormal(GLfloat x0,GLfloat y0,GLfloat z0,
					GLfloat x1,GLfloat y1,GLfloat z1,
					GLfloat x2,GLfloat y2,GLfloat z2,
					GLfloat & fNormalX, GLfloat & fNormalY, GLfloat & fNormalZ);
void getFaceNormal(unsigned int triNum, GLfloat & fnx,GLfloat & fny,GLfloat & fnz);
void populateFaceNormals();
void calculateNormals();
//GLUT Callbacks
void MouseMoved(int x,int y);
void MouseClicked(int button, int state, int x, int y);
void SpecialKeys(int key, int x, int y);
void ChangeSize(int w, int h);
void RenderScene(void);

bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
	GLbyte *pBits;
	int nWidth, nHeight, nComponents;
	GLenum eFormat;
	
	// Read the texture bits
	pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
	if(pBits == NULL) 
		return false;
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
				 eFormat, GL_UNSIGNED_BYTE, pBits);
	
    free(pBits);
    
    if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 
       minFilter == GL_LINEAR_MIPMAP_NEAREST ||
       minFilter == GL_NEAREST_MIPMAP_LINEAR ||
       minFilter == GL_NEAREST_MIPMAP_NEAREST)
        glGenerateMipmap(GL_TEXTURE_2D);
    
	return true;
}
void loadVertexArray(GLfloat x,GLfloat y,GLfloat z, GLfloat u, GLfloat v)
{
	vertexType tempVertex;
	tempVertex.xPos=x;
	tempVertex.yPos=y;
	tempVertex.zPos=z;
	tempVertex.xNormal=0.0f; //calculate later
	tempVertex.yNormal=0.0f; //calculate later
	tempVertex.zNormal=0.0f; //calculate later
	tempVertex.uTCoord=u;
	tempVertex.vTCoord=v;
	vertexArray.push_back(tempVertex);
}

void initTriCube(float xPos,float yPos,float zPos)
{
	//vertex positions and colors
	loadVertexArray(-0.5f,0.5f,0.0f,0.5f,0.25f); //0 (0.0) BACK x
	loadVertexArray(-0.5f,0.5f,0.0f,0.5f,0.25f); //1 (0.1) TOP x
	loadVertexArray(-0.5f,0.5f,0.0f,0.25f,0.5f); //2 (0.2) LEFT x
	loadVertexArray(-0.5f,-0.5f,0.0f,0.5f,0.0f); //3 (1.0) BACK x
	loadVertexArray(-0.5f,-0.5f,0.0f,0.0f,0.25f); //4 (1.1) BOTTOM x
	loadVertexArray(-0.5f,-0.5f,0.0f,0.25f,0.25f); //5 (1.2) LEFT x
	loadVertexArray(0.5f,-0.5f,0.0f,0.25f,0.0f); //6 (2.0) BACK x
	loadVertexArray(0.5f,-0.5f,0.0f,0.25f,0.25f); //7 (2.1) BOTTOM x
	loadVertexArray(0.5f,-0.5f,0.0f,0.75f,0.25f); //8 (2.2) RIGHT x
	loadVertexArray(0.5f,0.5f,0.0f,0.25f,0.25f); //9 (3.0) BACK x
	loadVertexArray(0.5f,0.5f,0.0f,0.75f,0.25f); //10 (3.1) TOP x
	loadVertexArray(0.5f,0.5f,0.0f,0.75f,0.5f); //11 (3.2) RIGHT x
	loadVertexArray(-0.5f,0.5f,1.0f,0.0f,0.25f); //12 (4.0) FRONT x
	loadVertexArray(-0.5f,0.5f,1.0f,0.5f,0.0f); //13 (4.1) TOP x
	loadVertexArray(-0.5f,0.5f,1.0f,0.5f,0.5f); //14 (4.2) LEFT x
	loadVertexArray(-0.5f,-0.5f,1.0f,0.0f,0.0f); //15 (5.0) FRONT x
	loadVertexArray(-0.5f,-0.5f,1.0f,0.0f,0.5f); //16 (5.1) BOTTOM x
	loadVertexArray(-0.5f,-0.5f,1.0f,0.5f,0.25f); //17 (5.2) LEFT x
	loadVertexArray(0.5f,-0.5f,1.0f,0.25f,0.0f); //18 (6.0) FRONT x
	loadVertexArray(0.5f,-0.5f,1.0f,0.25f,0.5f); //19 (6.1) BOTTOM x
	loadVertexArray(0.5f,-0.5f,1.0f,0.5f,0.25f); //20 (6.2) RIGHT x
	loadVertexArray(0.5f,0.5f,1.0f,0.25f,0.25f); //21 (7.0) FRONT x
	loadVertexArray(0.5f,0.5f,1.0f,0.75f,0.0f); //22 (7.1) TOP x
	loadVertexArray(0.5f,0.5f,1.0f,0.5f,0.5f); //23 (7.2) RIGHT x

	//tri indixies
	//front 0
	vertexIndex.push_back(12);	vertexIndex.push_back(15);	vertexIndex.push_back(18);
	vertexIndex.push_back(18);	vertexIndex.push_back(21);	vertexIndex.push_back(12);
	//back 1
	vertexIndex.push_back(6);	vertexIndex.push_back(3);	vertexIndex.push_back(0);
	vertexIndex.push_back(0);	vertexIndex.push_back(9);	vertexIndex.push_back(6);
	//top 2
	vertexIndex.push_back(22);	vertexIndex.push_back(10);	vertexIndex.push_back(1);
	vertexIndex.push_back(1);	vertexIndex.push_back(13);	vertexIndex.push_back(22);
	//bottom 3
	vertexIndex.push_back(7);	vertexIndex.push_back(19);	vertexIndex.push_back(16);
	vertexIndex.push_back(16);	vertexIndex.push_back(4);	vertexIndex.push_back(7);
	//left side 4
	vertexIndex.push_back(17);	vertexIndex.push_back(14);	vertexIndex.push_back(2);
	vertexIndex.push_back(2);	vertexIndex.push_back(5);	vertexIndex.push_back(17);
	//right side 5
	vertexIndex.push_back(8);	vertexIndex.push_back(11);	vertexIndex.push_back(23);
	vertexIndex.push_back(23);	vertexIndex.push_back(20);	vertexIndex.push_back(8);

	//calcualte vertex normals
	calculateNormals();

	//build VBO (verticies)
	glGenBuffers(1,&vdataBufferID);
	glBindBuffer(GL_ARRAY_BUFFER,vdataBufferID);
	glBufferData(GL_ARRAY_BUFFER,sizeof(vertexType)*vertexArray.size(),&(vertexArray[0]),GL_STATIC_DRAW);

	//bind texture
	glGenTextures(1,&textureID);
	glBindTexture(GL_TEXTURE_2D,textureID);
	LoadTGATexture("cubetest_flat.tga",GL_LINEAR,GL_LINEAR,GL_CLAMP_TO_EDGE);

	//build VBO (indixies)
	glGenBuffers(1,&viBufferID);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,viBufferID);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(GLushort)*vertexIndex.size(),&(vertexIndex[0]),GL_STATIC_DRAW);

	//set frame
	modelFrame.SetOrigin(xPos,yPos,zPos);
	modelFrame.SetUpVector(0.0f,1.0f,0.0f);
	modelFrame.SetForwardVector(0.0f,0.0f,1.0f);
}
void drawTriCube()
{
	glBindBuffer(GL_ARRAY_BUFFER,vdataBufferID);
	glEnableVertexAttribArray(GLT_ATTRIBUTE_VERTEX);
	glVertexAttribPointer(GLT_ATTRIBUTE_VERTEX,3,GL_FLOAT,GL_FALSE,sizeof(vertexType),BUFFER_OFFSET(0));
	glEnableVertexAttribArray(GLT_ATTRIBUTE_NORMAL);
	glVertexAttribPointer(GLT_ATTRIBUTE_NORMAL,3,GL_FLOAT,GL_FALSE,sizeof(vertexType),BUFFER_OFFSET(12));
	glEnableVertexAttribArray(GLT_ATTRIBUTE_TEXTURE0);
	glVertexAttribPointer(GLT_ATTRIBUTE_TEXTURE0,2,GL_FLOAT,GL_FALSE,sizeof(vertexType),BUFFER_OFFSET(24));

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,viBufferID);
	glDrawElements(GL_TRIANGLES,vertexIndex.size(),GL_UNSIGNED_SHORT,BUFFER_OFFSET(0));

	glDisableVertexAttribArray(GLT_ATTRIBUTE_TEXTURE0);
	glDisableVertexAttribArray(GLT_ATTRIBUTE_NORMAL);
	glDisableVertexAttribArray(GLT_ATTRIBUTE_VERTEX);
}
void SetupRC()
{
	glClearColor(255.0f, 255.0f, 255.0f, 1.0f );
	glCullFace(GL_BACK);
	glFrontFace(GL_CCW);
	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_MULTISAMPLE);

	shaderManager.InitializeStockShaders();
	initTriCube(0.0f,0.0f,-8.0f);
}
void RenderScene(void)
{
	// Clear the window with current clearing color
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	modelViewMatrix.PushMatrix(); //save identity matrix
	M3DMatrix44f cameraMatrix;
	M3DVector4f	vLightTransformed;
	cameraFrame.GetCameraMatrix(cameraMatrix);
	m3dTransformVector4(vLightTransformed, lightPos, cameraMatrix);
	modelViewMatrix.PushMatrix(cameraMatrix);

	modelViewMatrix.PushMatrix();
	modelViewMatrix.MultMatrix(modelFrame);
	glBindTexture(GL_TEXTURE_2D,textureID);
	shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,modelViewMatrix.GetMatrix(),transformPipeline.GetProjectionMatrix(),vLightTransformed,lightColor,0);
	drawTriCube();
	modelViewMatrix.PopMatrix();

	modelViewMatrix.PopMatrix();
	modelViewMatrix.PopMatrix(); //restore identity matrix

	glutSwapBuffers();
	glutPostRedisplay();
}
int main(int argc, char* argv[])
{
	gltSetWorkingDirectory(argv[0]);
	rightMouseDown=false;
	oldMouseX=0;
	oldMouseY=0;
	deltaMouseX=0;
	deltaMouseY=0;

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_MULTISAMPLE);
	glutInitWindowSize(800, 600);
	glutCreateWindow("Triangle");
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
	glutSpecialFunc(SpecialKeys);
	glutMotionFunc(MouseMoved);
	glutMouseFunc(MouseClicked);

	GLenum err = glewInit();
	if (GLEW_OK != err) 
	{
		fprintf(stderr, "GLEW Error: %s
", glewGetErrorString(err));
		return 1;
	}
	SetupRC();

	glutMainLoop();
	//delete texture
	glDeleteTextures(1,&textureID);
	//delete VBO
	glDeleteBuffers(1,&vdataBufferID);
	glDeleteBuffers(1,&viBufferID);
	return 0;
}

You could add a border to each texture and make it black {0, 0, 0, 0}. Use GL_CLAMP_TO_EDGE if you add it within the texture.
If you use the border feature of GL, use GL_CLAMP_TO_BORDER.

Then you just sample the texture and add them together.