MultiTexturing

Hey guys,



Sorry to bother you a lot. I am finally getting to grips with the SDK but at the moment I am having troubles multitexturing. I try to load in my second texture using PVRTools from OGLES2Tools but it fires the error and I don’t know what I have done wrong.



At the top I have created an array texture handler that holds 2 values:

// Texture handle

GLuint m_uiTexture[1];




here is how I load in my textures although I don’t think this is where my problem lies(This is done in the initview method):

if(PVRTTextureLoadFromPVR(c_szTextureFile, &m_uiTexture[0]) != PVR_SUCCESS)

{

PVRShellSet(prefExitMessage, “ERROR: Cannot load the texturen”);

return false;

}



glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MIN_FILTER);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);





//This loads in the second texture

if(PVRTTextureLoadFromPVR(c_szTextureFile2, &m_uiTexture[1]) != PVR_SUCCESS)

{

PVRShellSet(prefExitMessage, “ERROR: Cannot load the texture2n”);

return false;

}



glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MIN_FILTER);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);





I then free the textures in release view



bool OGLESIntroducingPVRTools::ReleaseView()

{

// Frees the texture

glDeleteTextures(1,&m_uiTexture[0]);

glDeleteTextures(1,&m_uiTexture[1]);

// Release Vertex buffer object.

glDeleteBuffers(1, &m_ui32Vbo);



// Frees the OpenGL handles for the program and the 2 shaders

glDeleteProgram(m_ShaderProgram.uiId);

glDeleteShader(m_uiVertexShader);

glDeleteShader(m_uiFragShader);



return true;

}




and I bind it in renderscene method:

// Binds the loaded texture

glBindTexture(GL_TEXTURE_2D, m_uiTexture[0]);

glBindTexture(GL_TEXTURE_2D, m_uiTexture[1]);




I wasn’t sure if it was needed but I’ll include the code for my Vertex and Fragment shader as well.



attribute highp vec4 inVertex;

attribute mediump vec2 inTexCoord;



uniform mediump mat4 MVPMatrix;



varying mediump vec2 TexCoord;



void main()

{

gl_Position = MVPMatrix * inVertex;

TexCoord = inTexCoord;

}







uniform sampler2D sTexture;

uniform sampler2D sTexture2;



varying mediump vec2 TexCoord;



void main()

{

mediump vec4 tex1 = texture2D(sTexture, TexCoord);

mediump vec4 tex2 = texture2D(sTexture2, TexCoord);



gl_FragColor = tex2 * tex1;

}




Thank you

Hi John.



There’s a few more steps involved for multitexturing.



First, I assume your first code snippet was a typo, as you’ve only declared an array of size 1, not 2.



Secondly, when you come to bind your textures, you need to use glActiveTexture before binding, i.e:





// Binds the loaded texture

glActiveTexture(GL_TEXTURE0);

glBindTexture(GL_TEXTURE_2D, m_uiTexture[0]);



glActiveTexture(GL_TEXTURE1);

glBindTexture(GL_TEXTURE_2D, m_uiTexture[1]);





Thirdly, you need to notify your fragment shader that sTexture is unit 0, and sTexture2 is unit 1. You’ll need to use glUniform1i for this, i.e:





glUniform1i(glGetUniformLocation(PROGRAM, “sTexture”), 0);

glUniform1i(glGetUniformLocation(PROGRAM, “sTexture2”), 1);





Hope this helps.

Hi Arron,



I have carried out your instructions but, it still fires the error message saying that texture 2 cannot be loaded. I will paste my whole code underneath this:



#include “PVRShell.h”

#include “OGLES2Tools.h”



/******************************************************************************

shader attributes and uniforms

****************************************************************************/

// Vertex attributes

// We define an enum for the attribute position and an array of strings that

// correspond to the attribute names in the shader. These can be used by PVRTools

enum EVertexAttrib {

VERTEX_ARRAY, TEXCOORD_ARRAY, eNumAttribs };

const char
g_aszAttribNames[] = {

“inVertex”, “inTexCoord”, “inNormal” };



// Shader uniforms

enum EUniform {

eMVPMatrix, eNumUniforms };

const char
g_aszUniformNames[] = {

“MVPMatrix”};



/******************************************************************************

Content file names

******************************************************************************/



// Source and binary shaders

const char c_szFragShaderSrcFile[] = “FragShader.fsh”;

const char c_szFragShaderBinFile[] = “FragShader.fsc”;

const char c_szVertShaderSrcFile[] = “VertShader.vsh”;

const char c_szVertShaderBinFile[] = “VertShader.vsc”;



// PVR texture files

const char c_szTextureFile[] = “n_Map1.pvr”;

const char c_szTextureFile2[] = “n_Map2.pvr”;



/******************************************************************************

Camera Constants

*/

const float _FOV = PVRT_PI / 6;

const float _NEAR = 4.0f;



/
!


Class implementing the PVRShell functions.

*/

class OGLESIntroducingPVRTools : public PVRShell

{

// Print3D class used to display text

//CPVRTPrint3D m_Print3D;



// Texture handle

GLuint m_uiTexture[2];



// VBO handle

GLuint m_ui32Vbo;



//

unsigned int m_ui32VertexStride;



float angle;



// The vertex and fragment shader OpenGL handles

GLuint m_uiVertexShader, m_uiFragShader;







// Group shader programs and their uniform locations together

struct

{

GLuint uiId;

GLuint auiLoc[eNumUniforms];

}

m_ShaderProgram;



public:

virtual bool InitApplication();

virtual bool InitView();

virtual bool ReleaseView();

virtual bool QuitApplication();

virtual bool RenderScene();

};





/
!


@Function InitApplication

@Return bool true if no error occured

@Description Code in InitApplication() will be called by PVRShell once per

run, before the rendering context is created.

Used to initialize variables that are not dependant on it

(e.g. external modules, loading meshes, etc.)

If the rendering context is lost, InitApplication() will

not be called again.

/

bool OGLESIntroducingPVRTools::InitApplication()

{

/


CPVRTResourceFile is a resource file helper class. Resource files can

be placed on disk next to the executable or in a platform dependent

read path. We need to tell the class where that read path is.

Additionally, it is possible to wrap files into cpp modules and

link them directly into the executable. In this case no path will be

used. Files on disk will override “memory files”.

/



// Get and set the read path for content files

CPVRTResourceFile::SetReadPath((char
)PVRShellGet(prefReadPath));



// Get and set the load/release functions for loading external files.

// In the majority of cases the PVRShell will return NULL function pointers implying that

// nothing special is required to load external files.

CPVRTResourceFile::SetLoadReleaseFunctions(PVRShellGet(prefLoadFileFunc), PVRShellGet(prefReleaseFileFunc));

return true;

}



/
!


@Function QuitApplication

@Return bool true if no error occured

@Description Code in QuitApplication() will be called by PVRShell once per

run, just before exiting the program.

If the rendering context is lost, QuitApplication() will

not be called.

*/

bool OGLESIntroducingPVRTools::QuitApplication()

{

return true;

}



/
!


@Function InitView

@Return bool true if no error occured

@Description Code in InitView() will be called by PVRShell upon

initialization or after a change in the rendering context.

Used to initialize variables that are dependant on the rendering

context (e.g. textures, vertex buffers, etc.)

/

bool OGLESIntroducingPVRTools::InitView()

{

/


Initialize the textures used by Print3D.

To properly display text, Print3D needs to know the viewport dimensions

and whether the text should be rotated. We get the dimensions using the

shell function PVRShellGet(prefWidth/prefHeight). We can also get the

rotate parameter by checking prefIsRotated and prefFullScreen.

/

bool bRotate = PVRShellGet(prefIsRotated) && PVRShellGet(prefFullScreen);



/if(m_Print3D.SetTextures(0, PVRShellGet(prefWidth), PVRShellGet(prefHeight), bRotate) != PVR_SUCCESS)

{

PVRShellSet(prefExitMessage, “ERROR: Cannot initialise Print3Dn”);

return false;

}
/



// Sets the clear color

glClearColor(0.6f, 0.8f, 1.0f, 1.0f);



/


Loads the texture using the tool function PVRTTextureLoadFromPVR.

The first parameter is the name of the file and the

second parameter returns the resulting texture handle.

The third parameter is a CPVRTString for error message output.

This function can also be used to conveniently set the filter modes. If

those parameters are not given, OpenGL ES defaults are used.

Setting a mipmap filter on a mipmap-less texture will result in an error.

/



//Loads first texture

if(PVRTTextureLoadFromPVR(c_szTextureFile, &m_uiTexture[0]) != PVR_SUCCESS)

{

PVRShellSet(prefExitMessage, “ERROR: Cannot load the texturen”);

return false;

}



glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MIN_FILTER);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);





//This loads in the second texture

if(PVRTTextureLoadFromPVR(c_szTextureFile2, &m_uiTexture[1]) != PVR_SUCCESS)

{

PVRShellSet(prefExitMessage, “ERROR: Cannot load the texture2n”);

return false;

}



glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MIN_FILTER);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);



glUniform1i(glGetUniformLocation(m_ShaderProgram.uiId, “sTexture”), 0);

glUniform1i(glGetUniformLocation(m_ShaderProgram.uiId, “sTexture2”), 1);



/


Compiles the shaders.

First we use CPVRTResourceFile to load a file into memory. After construction with a

file name, we just have to check whether the file is open or an error occured.

We load both source and binary shaders, then try the binary shader first.

The data of a CPVRTResourceFile will always be terminated with a 0 byte so it can

safely be used as a C string.

/

CPVRTResourceFile VertexShaderSrcFile(c_szVertShaderSrcFile);

CPVRTResourceFile VertexShaderBinFile(c_szVertShaderBinFile);



CPVRTString ErrorStr;

/


PVRTShaderLoadBinaryFromMemory takes a pointer to the binary shader and the shader size as

its first arguments. Then follows the shader type and binary format.

On success, the handle to the new shader object is returned in the fifth parameter, while

an error string is returned on failure.

/

if (!VertexShaderBinFile.IsOpen() ||

(PVRTShaderLoadBinaryFromMemory(VertexShaderBinFile.DataPtr(), VertexShaderBinFile.Size(),

GL_VERTEX_SHADER, GL_SGX_BINARY_IMG, &m_uiVertexShader, &ErrorStr) != PVR_SUCCESS))

{

/


Fallback to source shader

PVRTShaderLoadSourceFromMemory() takes the shader source code as its 1st argument.

The shader type as 2nd argument (for now either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.

It returns the shader object in its 3rd argument.

If an error occurs during compilation, the resulting log is returned in the 4th parameter.

We could also use PVRTLoadAndCompileShaderFromFile() to load and

compile a shader from an external text file

/



CPVRTString vertexShaderSrc((const char
) VertexShaderSrcFile.DataPtr(), VertexShaderSrcFile.Size());



if (!VertexShaderSrcFile.IsOpen() ||

(PVRTShaderLoadSourceFromMemory(vertexShaderSrc.c_str(), GL_VERTEX_SHADER, &m_uiVertexShader, &ErrorStr) != PVR_SUCCESS))

{

PVRShellSet(prefExitMessage, ErrorStr.c_str());

return false;

}

}



/


PVRTShaderLoadFromFile can be used to try compiling/loading shaders from files. In this variant,

two files are tried before failing (usually binary and source files). The type of shader is determined

from the file extension (.fsh and .vsh for source, .fsc and .vsc for SGX binary shaders)

/

if (PVRTShaderLoadFromFile(c_szFragShaderBinFile, c_szFragShaderSrcFile, GL_FRAGMENT_SHADER, GL_SGX_BINARY_IMG, &m_uiFragShader, &ErrorStr) != PVR_SUCCESS)

{

PVRShellSet(prefExitMessage, ErrorStr.c_str());

return false;

}



/


PVRTCreateProgram creates a new program object, attaches the shaders, binds attributes (given as an array

of strings and the size thereof), and makes the program current - or it returns an error string on failure.

/

if (PVRTCreateProgram(&m_ShaderProgram.uiId, m_uiVertexShader, m_uiFragShader, g_aszAttribNames, eNumAttribs, &ErrorStr) != PVR_SUCCESS)

{

PVRShellSet(prefExitMessage, ErrorStr.c_str());

return false;

}



// Store the location of uniforms for later use

for (int i = 0; i < eNumUniforms; ++i)

{

m_ShaderProgram.auiLoc = glGetUniformLocation(m_ShaderProgram.uiId, g_aszUniformNames);

}



// Create VBO for the triangle from our data



// Interleaved vertex data

GLfloat sqVert[] = {

0.4f,0.4f,0.0f,

1.0f, 1.0f,//UV

-0.4f,0.4f,0.0f,

0.0f, 1.0f,//UV

0.4f,-0.4f,0.0f, //postions

1.0f, 0.0f, //UV

-0.4f, -0.4f, 0.0f,

0.0f, 0.0f,//UV

-0.4f,0.4f,0.0f,

0.0f, 1.0f,//UV

0.4f,-0.4f,0.0f,

1.0f, 0.0f //UV

};



glGenBuffers(1, &m_ui32Vbo);



m_ui32VertexStride = 5 * sizeof(GLfloat); // 3 floats for the pos, 2 for the UVs



// Bind the VBO

glBindBuffer(GL_ARRAY_BUFFER, m_ui32Vbo);



// Set the buffer’s data

glBufferData(GL_ARRAY_BUFFER, 6 * m_ui32VertexStride, sqVert, GL_STATIC_DRAW);



// Unbind the VBO

glBindBuffer(GL_ARRAY_BUFFER, 0);

return true;

}



/
!


@Function ReleaseView

@Return bool true if no error occured

@Description Code in ReleaseView() will be called by PVRShell when the

application quits or before a change in the rendering context.

/

bool OGLESIntroducingPVRTools::ReleaseView()

{

// Frees the texture

glDeleteTextures(1,&m_uiTexture[0]);

glDeleteTextures(1,&m_uiTexture[1]);

// Release Vertex buffer object.

glDeleteBuffers(1, &m_ui32Vbo);



// Frees the OpenGL handles for the program and the 2 shaders

glDeleteProgram(m_ShaderProgram.uiId);

glDeleteShader(m_uiVertexShader);

glDeleteShader(m_uiFragShader);



return true;

}



/
!


@Function RenderScene

@Return bool true if no error occured

@Description Main rendering loop function of the program. The shell will

call this function every frame.

eglSwapBuffers() will be performed by PVRShell automatically.

PVRShell will also manage important OS events.

Will also manage relevent OS events. The user has access to

these events through an abstraction layer provided by PVRShell.

/

bool OGLESIntroducingPVRTools::RenderScene()

{

// Clears the color and depth buffer

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);



// Binds the loaded texture

glActiveTexture(GL_TEXTURE0);

glBindTexture(GL_TEXTURE_2D, m_uiTexture[0]);

glActiveTexture(GL_TEXTURE1);

glBindTexture(GL_TEXTURE_2D, m_uiTexture[1]);



// Use the loaded shader program

glUseProgram(m_ShaderProgram.uiId);



/


Creates the Model View Projection (MVP) matrix using the PVRTMat4 class from the tools.

The tools contain a complete set of functions to operate on 4x4 matrices.

/

PVRTMat4 mMVP = PVRTMat4::Identity();



if(PVRShellGet(prefIsRotated) && PVRShellGet(prefFullScreen)) // If the screen is rotated

mMVP = PVRTMat4::RotationZ(-1.57f);



/


Pass this matrix to the shader.

The .m field of a PVRTMat4 contains the array of float used to

communicate with OpenGL ES.

/

glUniformMatrix4fv(m_ShaderProgram.auiLoc[eMVPMatrix], 1, GL_FALSE, mMVP.ptr());



/


Draw a triangle.

Please refer to the training course IntroducingPVRShell for a detailed explanation.

/



// Bind the VBO

glBindBuffer(GL_ARRAY_BUFFER, m_ui32Vbo);



// Pass the vertex data

glEnableVertexAttribArray(VERTEX_ARRAY);

glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, m_ui32VertexStride, 0);



// Pass the texture coordinates data

glEnableVertexAttribArray(TEXCOORD_ARRAY);

glVertexAttribPointer(TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, m_ui32VertexStride, (void
) (sizeof(GLfloat) * 3) /
Uvs start after the position /);



// Draws a non-indexed triangle array

glDrawArrays(GL_TRIANGLES, 0, 6);





return true;

}



/
!


@Function NewDemo

@Return PVRShell
The demo supplied by the user

@Description This function must be implemented by the user of the shell.

The user should return its PVRShell object defining the

behaviour of the application.

*****************************************************************************/

PVRShell
NewDemo()

{

return new OGLESIntroducingPVRTools();

}



/******************************************************************************

End of file (OGLESIntroducingPVRTools.cpp)

******************************************************************************/



Hi John,



This is because after you load your first texture, you call glTexParamater with an invalid value. This is causing the texture loading to fail as glGetError is set from the previous issue.



You pass GL_TEXTURE_MIN_FILTER as the last argument to glTexParameter - this is invalid and should be something like GL_LINEAR. See the documentation for glTexParameter for valid values.



Arron.

It now compiles and runs however I just get a black square in the middle. I moved my glUniform1i code to just after when I bind the texture like this:



// Binds the loaded texture

glActiveTexture(GL_TEXTURE0);

glBindTexture(GL_TEXTURE_2D, m_uiTexture[0]);

glUniform1i(glGetUniformLocation(m_ShaderProgram.uiId, “sTexture”), 0);



glActiveTexture(GL_TEXTURE1);

glBindTexture(GL_TEXTURE_2D, m_uiTexture[1]);

glUniform1i(glGetUniformLocation(m_ShaderProgram.uiId, “sTexture2”), 1);

Hi John,



Ideally the glUniform1i code would be around line 254 in your code - after the shaders are loaded. Just after the following bit of code:





// Store the location of uniforms for later use

for (int i = 0; i < eNumUniforms; ++i)

{

m_ShaderProgram.auiLoc = glGetUniformLocation(m_ShaderProgram.uiId, g_aszUniformNames);

}





As to your black texture issue - did you change the texture filtering to GL_LINEAR? It sounds like like either textures are not correctly bound or that mipmapping is enabled and the textures do not have mipmaps.



I’ve tried running your code with the suggested changes and I can confirm that it works.

Hi Arron,



Thanks a lot for your help, much appreciated. I forgot to set them back to Linear as I was messing around with my code.