Texture Combiners

From OpenGL Wiki
Jump to navigation Jump to search

Texture combiners became core in GL 1.3. You can combine by multiplying, replacing, adding, subtracting, or doing a DOT3 product.
GL 1.4 added to the core a crossbar feature. This means that you sample GL_TEXTUREX from any other texture image unit (TIU).
For example, at TUI 4, you can sample GL_TEXTURE0 or GL_TEXTURE1 or GL_TEXTURE2 or GL_TEXTURE3.
The examples here also show the equivalent shader code in GLSL to encourage people to use shaders instead of the fixed pipeline.

For the fixed pipeline, to know how many texture units are supported, call glGetIntegerv(GL_MAX_TEXTURE_UNITS, &MaxTextureUnits).
For early hw like nVidia TNT and Geforce 256, it would be 2 units.
Even on a modern GPU like a Gf 8, the number is 4, although in reality the GPU can do far more in the programmable pipeline.
Each unit has its own texture bind, its own texture coordinate, its own texture matrix, its own TexGen, its own TexEnv states.
The examples below show us binding a texture and TexEnv.

For the programmable pipeline, the texture image samplers and texcoords are dissociated. You could for example use TexCoord0 to sample Texture Unit 5.
In the programmable pipeline, there are no texture matrices. There are no TexGen states and no TexEnv states.
You upload the matrices yourself with glUniformMatrix and the mathematics of TexGen and TexEnv, you simply code it yourself.
This page explains the math for glTexGen http://www.opengl.org/wiki/Mathematics_of_glTexGen

glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &MaxTextureImageUnits) tells you how many samplers you have. This might be 16 or 32.
glGetIntegerv(GL_MAX_TEXTURE_COORDS, &MaxTextureCoords) tells you how many texcoords you have. This might be 8.
Most people don't need to send much texcoords to their vertex shaders but they need to sample a lot of textures, that is why the numbers are always not equal on all GPUs.
In the programmable pipeline, a sampler means texture image units. The states are the MIN and MAG filter, anisotropy, lower mip level.

Example : multiply tex0 and tex1[edit]

The keyword here is GL_MODULATE, which does the actual multiplication.

Mathematically, it look like this

 result_rgb = texture0_rgb    // Just read the texture
 result_a = texture0_a
 result_rgb = result_rgb * texture1_rgb
 result_a = result_a * texture1_a

The setup code for the Texture Combiners will look like this

  glActiveTexture(GL_TEXTURE0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID0);
  // Simply sample the texture
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  // --------------------
  glActiveTexture(GL_TEXTURE1);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID1);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
  // Sample RGB, multiply by previous texunit result
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);   // Modulate RGB with RGB
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
  // Sample ALPHA, multiply by previous texunit result
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);  // Modulate ALPHA with ALPHA
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);

The equivalent with GL 2.0 shaders

  // Vertex shader
  #version 110
  attribute vec4 InVertex;
  attribute vec2 InTexCoord0;
  attribute vec2 InTexCoord1;
  uniform mat4 ProjectionModelviewMatrix;
  varying vec2 TexCoord0;
  varying vec2 TexCoord1;    // Or just use TexCoord0
  // --------------------
  void main()
  {
    gl_Position = ProjectionModelviewMatrix * InVertex;
    TexCoord0 = InTexCoord0;
    TexCoord1 = InTexCoord1;
  }
  // Fragment shader
  #version 110
  uniform sampler2D Texture0;
  uniform sampler2D Texture1;
  // --------------------
  varying vec2 TexCoord0;
  varying vec2 TexCoord1;    // Or just use TexCoord0
  // --------------------
  void main()
  {
     vec4 texel = texture2D(Texture0, TexCoord0);
     texel *= texture2D(Texture1, TexCoord1);
     gl_FragColor = texel;
  }

Example : Blend tex0 and tex1 based on a blending factor you supply[edit]

The keyword here is GL_INTERPOLATE.

  glActiveTexture(GL_TEXTURE0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID0);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  // --------------------
  glActiveTexture(GL_TEXTURE1);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID1);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);    // Interpolate RGB with RGB
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
  // GL_CONSTANT refers to the call we make with glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mycolor)
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR);
  // --------------------
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_INTERPOLATE);   // Interpolate ALPHA with ALPHA
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE);
  // GL_CONSTANT refers to the call we make with glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mycolor)
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_ALPHA, GL_CONSTANT);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, GL_SRC_ALPHA);
  // --------------------
  float mycolor[4];
  mycolor[0]=mycolor[1]=mycolor[2]=0.0;    // RGB doesn't matter since we are not using it
  mycolor[3]=0.75;                         // Set the blend factor with this
  glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mycolor);

The equivalent with GL 2.0 shaders

  // Vertex shader
  #version 110
  attribute vec4 InVertex;
  attribute vec2 InTexCoord0;
  attribute vec2 InTexCoord1;
  uniform mat4 ProjectionModelviewMatrix;
  varying vec2 TexCoord0;
  varying vec2 TexCoord1;    // Or just use TexCoord0
  // --------------------
  void main()
  {
    gl_Position = ProjectionModelviewMatrix * InVertex;
    TexCoord0 = InTexCoord0;
    TexCoord1 = InTexCoord1;
  }
  // Fragment shader
  #version 110
  uniform sampler2D Texture0;
  uniform sampler2D Texture1;
  uniform float BlendFactor;
  // --------------------
  varying vec2 TexCoord0;
  varying vec2 TexCoord1;    // Or just use TexCoord0
  // --------------------
  void main()
  {
     vec4 texel0 = texture2D(Texture0, TexCoord0);
     vec4 texel1 = texture2D(Texture1, TexCoord1);
     gl_FragColor = mix(texel0, texel1, BlendFactor);
  }

Example : Blend tex0 and tex1 based on alpha of tex0[edit]

The keyword here is GL_INTERPOLATE.

  glActiveTexture(GL_TEXTURE0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID0);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  // --------------------
  glActiveTexture(GL_TEXTURE1);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID1);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);    // Interpolate RGB with RGB
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA);
  // --------------------
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_INTERPOLATE);    // Interpolate ALPHA with ALPHA
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_ALPHA, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, GL_SRC_ALPHA);

The equivalent with GL 2.0 shaders

  // Vertex shader
  #version 110
  attribute vec4 InVertex;
  attribute vec2 InTexCoord0;
  attribute vec2 InTexCoord1;
  uniform mat4 ProjectionModelviewMatrix;
  varying vec2 TexCoord0;
  varying vec2 TexCoord1;    // Or just use TexCoord0
  // --------------------
  void main()
  {
    gl_Position = ProjectionModelviewMatrix * InVertex;
    TexCoord0 = InTexCoord0;
    TexCoord1 = InTexCoord1;
  }
  // Fragment shader
  #version 110
  uniform sampler2D Texture0;
  uniform sampler2D Texture1;
  // --------------------
  varying vec2 TexCoord0;
  varying vec2 TexCoord1;    // Or just use TexCoord0
  // --------------------
  void main()
  {
     vec4 texel0 = texture2D(Texture0, TexCoord0);
     vec4 texel1 = texture2D(Texture1, TexCoord1);
     gl_FragColor = mix(texel0, texel1, texel0.a);
  }

Example : Blend tex1 and tex2 based on alpha of tex0[edit]

The keyword here is GL_INTERPOLATE.

tex0's ALPHA is the mask.

  glActiveTexture(GL_TEXTURE0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID0);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  // --------------------
  glActiveTexture(GL_TEXTURE1);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID1);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);    // Get RGB of this texture (tex1)
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
  // --------------------
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);    // Get ALPHA of previous TUI (tex0)
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
  glActiveTexture(GL_TEXTURE2);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID2);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);    // Interpolate RGB with RGB
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA);
  // --------------------
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_INTERPOLATE);    // Interpolate ALPHA with ALPHA
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_ALPHA, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, GL_SRC_ALPHA);

With shaders, things are much more flexible.

  // Vertex shader
  #version 110
  attribute vec4 InVertex;
  attribute vec2 InTexCoord0;
  uniform mat4 ProjectionModelviewMatrix;
  varying vec2 TexCoord0;
  // --------------------
  void main()
  {
    gl_Position = ProjectionModelviewMatrix * InVertex;
    TexCoord0 = InTexCoord0;
  }
  // Fragment shader
  #version 110
  uniform sampler2D Texture0;  // Mask
  uniform sampler2D Texture1;
  uniform sampler2D Texture2;
  // --------------------
  varying vec2 TexCoord0;
  // --------------------
  void main()
  {
     vec4 texel0 = texture2D(Texture0, TexCoord0);
     vec4 texel1 = texture2D(Texture1, TexCoord0);
     vec4 texel2 = texture2D(Texture2, TexCoord0);
     gl_FragColor = mix(texel1, texel2, texel0.a);
  }

In this one, we place the mask at TIU 2 (You can in fact have your texture with the alpha mask on any TIU you want)

  // Vertex shader
  #version 110
  attribute vec4 InVertex;
  attribute vec2 InTexCoord0;
  uniform mat4 ProjectionModelviewMatrix;
  varying vec2 TexCoord0;
  // --------------------
  void main()
  {
    gl_Position = ProjectionModelviewMatrix * InVertex;
    TexCoord0 = InTexCoord0;
  }
  // Fragment shader
  #version 110
  uniform sampler2D Texture0;
  uniform sampler2D Texture1;
  uniform sampler2D Texture2;  // Mask
  // --------------------
  varying vec2 TexCoord0;
  // --------------------
  void main()
  {
     vec4 texel0 = texture2D(Texture0, TexCoord0);
     vec4 texel1 = texture2D(Texture1, TexCoord0);
     vec4 texel2 = texture2D(Texture2, TexCoord0);
     gl_FragColor = mix(texel0, texel1, texel2.a);
  }

Example : Add tex0 and tex1[edit]

The keyword here is GL_ADD.

  glActiveTexture(GL_TEXTURE0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID0);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  // --------------------
  glActiveTexture(GL_TEXTURE1);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID1);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);    // Add RGB with RGB
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
  // --------------------
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_ADD);    // Add ALPHA with ALPHA
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);

The equivalent with GL 2.0 shaders

  // Vertex shader
  #version 110
  attribute vec4 InVertex;
  attribute vec2 InTexCoord0;
  uniform mat4 ProjectionModelviewMatrix;
  varying vec2 TexCoord0;
  // --------------------
  void main()
  {
    gl_Position = ProjectionModelviewMatrix * InVertex;
    TexCoord0 = InTexCoord0;
  }
  // Fragment shader
  #version 110
  uniform sampler2D Texture0;
  uniform sampler2D Texture1;
  // --------------------
  varying vec2 TexCoord0;
  // --------------------
  void main()
  {
     vec4 texel0 = texture2D(Texture0, TexCoord0);
     vec4 texel1 = texture2D(Texture1, TexCoord0);
     gl_FragColor = clamp(texel0 + texel1, 0.0, 1.0);
  }

Example : Interpolate tex0 and tex1 and multiply the result[edit]

This shows one of the limitations of Texture Combiners and how Shaders really come to the rescue. But at least, this case can be done with Texture Combiners!

From looking at the above examples, you have already noticed how texture image units and texcoord are not decoupled in Texture Combiners. This example also shows this.

Mathematically, we want to do

 result_rgb = (Tex0_rgb * factor) + (Tex1_rgb * (1 - factor))    // Interpolate
 result_rgb = result_rgb * color_rgb    // Multiply by a color factor

Tex0 can be placed on Texture Image Unit (TIU) 0 and Tex1 can be placed on TIU 1. For doing color_rgb, you are going to need another TIU. You have to bind a dummy texture and activate the TIU. You aren't forced to send texcoords for TIU 2.

Here is the Texture Combiner setup code :

  glActiveTexture(GL_TEXTURE0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID0);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  // --------------------
  glActiveTexture(GL_TEXTURE1);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureID1);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);    // Interpolate RGB with RGB
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
  // GL_CONSTANT refers to the call we make with glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mycolor)
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR);
  // --------------------
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE);    // REPLACE just need a SOURCE0 and OPERAND0
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
  // --------------------
  float mycolor[4];
  mycolor[0]=mycolor[1]=mycolor[2]=0.0;    // RGB doesn't matter since we are not using it
  mycolor[3]=0.75;                         // Set the blend factor with this
  glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mycolor);
  glActiveTexture(GL_TEXTURE2);
  glEnable(GL_TEXTURE_2D);    // Activate the unit
  glBindTexture(GL_TEXTURE_2D, dummyTexture);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
  // GL_CONSTANT refers to the call we make with  glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mycolor2)
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
  // --------------------
  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
  glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_CONSTANT);    // REPLACE just need a SOURCE0 and OPERAND0
  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
  // --------------------
  float mycolor2[4];
  mycolor2[0]=0.6;    // Red
  mycolor2[1]=0.8;    // Green
  mycolor2[2]=1.0;    // Blue
  mycolor2[3]=1.0;    // Alpha
  // This glTexEnvfv call is loaded on TIU 2 because of the call to glActiveTexture(GL_TEXTURE2)
  glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mycolor2);

So the above Texture Combiner setup looks really complicated and nasty. The Shader version will look like this.

The equivalent with GL 2.0 shaders

  // Vertex shader
  #version 110
  attribute vec4 InVertex;
  attribute vec2 InTexCoord0;
  attribute vec2 InTexCoord1;
  uniform mat4 ProjectionModelviewMatrix;
  varying vec2 TexCoord0;
  varying vec2 TexCoord1;    // Or just use TexCoord0
  // --------------------
  void main()
  {
    gl_Position = ProjectionModelviewMatrix * InVertex;
    TexCoord0 = InTexCoord0;
    TexCoord1 = InTexCoord1;
  }
  // Fragment shader
  #version 110
  uniform sampler2D Texture0;
  uniform sampler2D Texture1;
  uniform float BlendFactor;
  uniform vec4 ColorFactor;
  // --------------------
  varying vec2 TexCoord0;
  varying vec2 TexCoord1;    // Or just use TexCoord0
  // --------------------
  void main()
  {
     vec4 ColorResult;
     vec4 texel0 = texture2D(Texture0, TexCoord0);
     vec4 texel1 = texture2D(Texture1, TexCoord1);
     ColorResult = mix(texel0,  texel1, BlendFactor);
     gl_FragColor = ColorResult * ColorFactor;
  }

The shader above is using custom attributes InVertex, InTexCoord0 and InTexCoord1. It is using 2 texcoord inputs although perhaps you don't need 2 if both will be the same. That is entirely up to you. There are 2 texture image units we are sampling. You don't need a dummy texture in this world of shaders and total flexibility. The ColorFactor is just another uniform. You just upload it with a glUniform* call. The shader code is short and clearcut compared to the dozens of lines for setting up a Texture Combiners.