(OpenGL) How could I use MultiTexture ?

Nibbie
Posts: 2
Joined: 2009.01
Post: #1
I would like to use MultiTexture through the use of glMultiTexCoord4()
like this.

glBegin( GL_TRIANGLE_LIST )
...
glMultiTexCoord4( TARGET_1 , .... );
glMultiTexCoord4( TARGET_2 , .... );
...
glEnd();

But there's no glBegin() in iPhone-OpenGL.

I'm wondering how could I output MultiTexture ?

Thanks.
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #2
glBegin is convenient, but very inefficient. It is removed from OpenGL ES, and deprecated in OpenGL 3.0. Instead, you must use vertex arrays. There are various tutorials available, but this is an important topic, so I'll show an example of how to convert from glBegin to vertex arrays.

Let's say you want to draw a multitextured quad. You've already set up the state for two texture units, the projection matrix, etc. To draw, you have this code:
Code:
glBegin(GL_TRIANGLE_STRIP);
glMultiTexCoord2f(GL_TEXTURE0, 0, 0); glMultiTexCoord2f(GL_TEXTURE1, 0, 0); glVertex2f(10, 10);
glMultiTexCoord2f(GL_TEXTURE0, 1, 0); glMultiTexCoord2f(GL_TEXTURE1, 4, 0); glVertex2f(50, 10);
glMultiTexCoord2f(GL_TEXTURE0, 0, 1); glMultiTexCoord2f(GL_TEXTURE1, 0, 2); glVertex2f(10, 30);
glMultiTexCoord2f(GL_TEXTURE0, 1, 1); glMultiTexCoord2f(GL_TEXTURE1, 4, 2); glVertex2f(50, 30);
glEnd();

That took 14 calls into OpenGL, and an extra three calls for every vertex you add, which becomes very expensive when you draw more geometry.

With vertex arrays, you place all of the vertex attributes into an array, and give OpenGL pointers into the array. Then, you draw multiple vertices with a single call, which is much more efficient:
Code:
typedef struct {
    float x, y;
    float s0, t0;
    float s1, t1;
} myVertex; // an interleaved vertex, 24 bytes if we use floats

myVertex myQuad[4] = {
    { 10,10, 0,0, 0,0 },
    { 50,10, 1,0, 4,0 },
    { 10,30, 0,1, 0,2 },
    { 50,30, 1,1, 4,2 },
}; // the same data that we previously hardcoded in glBegin

glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(myVertex), &myQuad[0].x);

glClientActiveTexture(GL_TEXTURE1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(myVertex), &myQuad[0].s1);

glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(myVertex), &myQuad[0].s0);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

That took 9 calls into OpenGL, but more importantly it is zero extra calls per vertex. You can draw 4, or 4000 vertices with a single call to glDrawArrays. Please read the documentation about each of these APIs to understand the arguments.

There are a couple of important things to point out here. First, you're passing pointers to GL. This is a potential source of bugs, so please be careful! When you draw, GL will look at all of the state and then copy the enabled vertex attributes from your array to the GPU. If your pointers are wrong, or the address calculation (attribute_base_pointer + index * attribute_stride + attribute_size-1) would access memory past the end of your array, then your app will probably crash. Notice that I did not disable any arrays at the end of the above example. Just like everything else in OpenGL, it is up to you to keep track of the state. If you want to draw another quad with a different set of attributes (for example, using TEXTURE0, but not TEXTURE1) then you must be sure to set up the state properly (for example, disable TEXTURE_COORD_ARRAY on TEXTURE1.)

To further stress that point: every time you draw, GL will always access all of the attributes it needs, based on the current state. So you really need to keep track of the state to understand what's happening. In the above example, let's say that texture units 0 and 1 are enabled for rasterization (with glActiveTexture, glEnable.) That means that texture coords 0 and 1 are both needed. The arrays are both enabled (with glClientActiveTexture, glEnableClientState), so the coordinates will come from the arrays. What about other vertex attributes, like color? If you are using the default TexEnv state (MODULATE) then color is also needed. If the color array isn't enabled, where does it come from? The answer is the "current vertex". Any time you call glColor4f, you're copying your values to a special "current" vertex. That vertex tracks the default values used when an array is not enabled.

Second, I said that GL will copy enabled vertex attributes to the GPU. This is why the APIs have "Client" in the name-- GL will access data owned by your client application. Although client vertex arrays are already a lot better than glBegin, when you have a lot of data, repeatedly copying it is inefficient. The solution to that problem is Vertex Buffer Objects, which also have various tutorials available, so I won't go into detail here. The quick summary is that you can copy vertex data into a buffer object once, which the GL owns (just like glTexImage.) Then you can draw with the same data repeatedly, instead of copying it every time. The API also has a lot of flexibility in it, so that you can treat some attributes (like texture coordinates) as static data, and others (like color, for example) as dynamically changing data.

Also, you'll notice that compared to glBegin, you need to write more code to use vertex arrays. It's true, vertex arrays aren't as convenient, but that is the tradeoff for flexibility and speed.

Finally, in more advanced APIs which use shaders (OpenGL 2.0, OpenGL ES 2), the specific vertex attributes have been abstracted into generic 4-component vectors. So instead of calling glColorPointer etc, you call glVertexAttribPointer, and explicitly access the attribute in your shader. It's still fundamentally the same concept, though. OpenGL 3.0 further enforces the "stop using slow API" policy, by deprecating client vertex arrays-- you have to use Vertex Buffer Objects.
Quote this message in a reply
Nibbie
Posts: 2
Joined: 2009.01
Post: #3
Google coudln't help me but your sample code made it possible
for me to output multi-texture through the use of OpenGL ES@iPhone.

Thanks a lot.LOL

arekkusu Wrote:glBegin is convenient, but very inefficient. It is removed from OpenGL ES, and deprecated in OpenGL 3.0. Instead, you must use vertex arrays. There are various tutorials available, but this is an important topic, so I'll show an example of how to convert from glBegin to vertex arrays.

Let's say you want to draw a multitextured quad. You've already set up the state for two texture units, the projection matrix, etc. To draw, you have this code:
Code:
glBegin(GL_TRIANGLE_STRIP);
glMultiTexCoord2f(GL_TEXTURE0, 0, 0); glMultiTexCoord2f(GL_TEXTURE1, 0, 0); glVertex2f(10, 10);
glMultiTexCoord2f(GL_TEXTURE0, 1, 0); glMultiTexCoord2f(GL_TEXTURE1, 4, 0); glVertex2f(50, 10);
glMultiTexCoord2f(GL_TEXTURE0, 0, 1); glMultiTexCoord2f(GL_TEXTURE1, 0, 2); glVertex2f(10, 30);
glMultiTexCoord2f(GL_TEXTURE0, 1, 1); glMultiTexCoord2f(GL_TEXTURE1, 4, 2); glVertex2f(50, 30);
glEnd();

That took 14 calls into OpenGL, and an extra three calls for every vertex you add, which becomes very expensive when you draw more geometry.

With vertex arrays, you place all of the vertex attributes into an array, and give OpenGL pointers into the array. Then, you draw multiple vertices with a single call, which is much more efficient:
Code:
typedef struct {
    float x, y;
    float s0, t0;
    float s1, t1;
} myVertex; // an interleaved vertex, 24 bytes if we use floats

myVertex myQuad[4] = {
    { 10,10, 0,0, 0,0 },
    { 50,10, 1,0, 4,0 },
    { 10,30, 0,1, 0,2 },
    { 50,30, 1,1, 4,2 },
}; // the same data that we previously hardcoded in glBegin

glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(myVertex), &myQuad[0].x);

glClientActiveTexture(GL_TEXTURE1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(myVertex), &myQuad[0].s1);

glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(myVertex), &myQuad[0].s0);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

That took 9 calls into OpenGL, but more importantly it is zero extra calls per vertex. You can draw 4, or 4000 vertices with a single call to glDrawArrays. Please read the documentation about each of these APIs to understand the arguments.

There are a couple of important things to point out here. First, you're passing pointers to GL. This is a potential source of bugs, so please be careful! When you draw, GL will look at all of the state and then copy the enabled vertex attributes from your array to the GPU. If your pointers are wrong, or the address calculation (attribute_base_pointer + index * attribute_stride + attribute_size-1) would access memory past the end of your array, then your app will probably crash. Notice that I did not disable any arrays at the end of the above example. Just like everything else in OpenGL, it is up to you to keep track of the state. If you want to draw another quad with a different set of attributes (for example, using TEXTURE0, but not TEXTURE1) then you must be sure to set up the state properly (for example, disable TEXTURE_COORD_ARRAY on TEXTURE1.)

To further stress that point: every time you draw, GL will always access all of the attributes it needs, based on the current state. So you really need to keep track of the state to understand what's happening. In the above example, let's say that texture units 0 and 1 are enabled for rasterization (with glActiveTexture, glEnable.) That means that texture coords 0 and 1 are both needed. The arrays are both enabled (with glClientActiveTexture, glEnableClientState), so the coordinates will come from the arrays. What about other vertex attributes, like color? If you are using the default TexEnv state (MODULATE) then color is also needed. If the color array isn't enabled, where does it come from? The answer is the "current vertex". Any time you call glColor4f, you're copying your values to a special "current" vertex. That vertex tracks the default values used when an array is not enabled.

Second, I said that GL will copy enabled vertex attributes to the GPU. This is why the APIs have "Client" in the name-- GL will access data owned by your client application. Although client vertex arrays are already a lot better than glBegin, when you have a lot of data, repeatedly copying it is inefficient. The solution to that problem is Vertex Buffer Objects, which also have various tutorials available, so I won't go into detail here. The quick summary is that you can copy vertex data into a buffer object once, which the GL owns (just like glTexImage.) Then you can draw with the same data repeatedly, instead of copying it every time. The API also has a lot of flexibility in it, so that you can treat some attributes (like texture coordinates) as static data, and others (like color, for example) as dynamically changing data.

Also, you'll notice that compared to glBegin, you need to write more code to use vertex arrays. It's true, vertex arrays aren't as convenient, but that is the tradeoff for flexibility and speed.

Finally, in more advanced APIs which use shaders (OpenGL 2.0, OpenGL ES 2), the specific vertex attributes have been abstracted into generic 4-component vectors. So instead of calling glColorPointer etc, you call glVertexAttribPointer, and explicitly access the attribute in your shader. It's still fundamentally the same concept, though. OpenGL 3.0 further enforces the "stop using slow API" policy, by deprecating client vertex arrays-- you have to use Vertex Buffer Objects.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Multitexture Crashes Jake 6 3,672 Sep 6, 2004 12:46 PM
Last Post: Jake
  Multitexture support on mac NYGhost 9 4,651 Oct 30, 2003 09:25 PM
Last Post: Mars_999
  Probelms with multitexture vertex arrays Bossa Nova 2 2,917 Jun 24, 2003 04:06 PM
Last Post: Bossa Nova
  Multitexture NYGhost 4 3,586 Mar 4, 2003 03:10 PM
Last Post: NYGhost