multitexturing, combiners, and lighting...

my goal is to render a shaded (using GL-rendered vertex lighting) multi-textured polygon. each texture covers a portion of the polygon; when combined, the textures cover the polygon entirely.

without lighting, i used GL_DECAL to get the desired effect (where the current texture is transparent, the source color shows through). naturally with lighting this causes the lighting to disappear. i need a more complex operation.

so… texture combiners then. what i’d like to achieve is a blending mode in which the texture fragments are multiplied against their alpha, and the sum of the results are multiplied against the fragment’s primary color.

in the style of tables 9-4 and 9-5 in the red book (3rd ed; tables 9-2 and 9-3 in 2nd ed.), the mode i’m looking for is something like:

<pre>
C = Cf * (Ct1 * At1 + Ct2 * At2 + … + Ctn * Atn)
A = Af
</pre>

(note that the distributitive property can be used with C, so that the contribution of each texture to the final sum can be calculated independantly)

i’ve established that the following code produces what i want for a single texture:

<pre>
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
</pre>

i need to somehow add the results of PREVIOUS_EXT for all but the first texture, and i’m not exactly sure how to go about this.

i’d be grateful for a clue.

Hint: In your example, the texture alpha isn’t contributing anything.

You’re asking a bit too much here. While there are extensions to the texture environment (search the registry for NV_texture_env_combine4 or NV_register_combiners or even ATIX_texture_env_combine3) that allow more flexible combiner operations, you still need to burn an extra texture environment for the final mult with the primary color.

Basically you want this:
ENV0 : out=atct
ENV1 : out=at
ct + env0.out
ENV2 : out=atct + env1.out
ENV3 : out=cprimary
env2.out

This can be done easily with both the NVIDIA (all caps!) and ATI extensions, but of course it takes lots of texture environments, one more than the number of textures you want to apply.

You can also break it up into multipass if you use blending.

HTH, ask back if it’s not sufficient.

Originally posted by zeckensack:

<snip>

Basically you want this:
ENV0 : out=atct
ENV1 : out=at
ct + env0.out
ENV2 : out=atct + env1.out
ENV3 : out=cprimary
env2.out

This can be done easily with both the NVIDIA (all caps!) and ATI extensions, but of course it takes lots of texture environments, one more than the number of textures you want to apply.

You can also break it up into multipass if you use blending.

HTH, ask back if it’s not sufficient.[/b]

thanks! i believe i understand. i thought i was expecting a bit much of the texture combiners. in my situation, it’s possible that i’ll need to blend 2-6 textures with the surface lighting… i’d be exceeeding most card’s texture units occasionally anyhow. the second pass i think is simple; the final alpha consists of the sum of all texture alphas thus far, otherwise the pass is the same as the first (and blending has to be enabled).

the texture combiners are now EXT_texture_env_combine, correct? with both NVIDIA (heh) and ATI implementing this extension? does anyone else implement it? (i guess matrox is gonna have to. the kyro?)

i’ve determined that what i really need is actually a bit simpler than what i described above. i can decal each of the textures, and then post-multiply. (combiners are only needed for the last step.) however, i’m having a small problem… the post-multiply only seems to work if one and only one (other) texture unit is active. i’m missing something… i’m not sure whether it’s obvious or subtle.

if it’s not much trouble… why doesn’t this seem to work for multiple units?

int curUnit = 0; // the offset of the current texture unit

// loop, applying active textures (at most 3 – for now,
// this code has no multipass and i’m requiring at least 4
// texture units. this can change, of course, once i make it work
for (int i = 0; i < 3; i++) {
glActiveTextureARB(GL_TEXTURE0_ARB + curUnit);

// if a certain texture needs to be applied
if (types[i] != NULL) {
    curUnit++;
    glBindTexture(GL_TEXTURE_2D, types[i]->getTexId());
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glEnable(GL_TEXTURE_2D);
    
    // generates texture coordinates (it works; not a concern).
    types[i]->getTexCoords(tile[i], s1[i], t1[i], s2[i], t2[i]);
}

}

// use the next successive texture unit to post-multiply
// with the lighting term
glActiveTextureARB(GL_TEXTURE0_ARB + curUnit);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
glEnable(GL_TEXTURE_2D);
curUnit++;

// turn off any unused texture units
for (i = curUnit; i < 4; i++) {
glActiveTextureARB(GL_TEXTURE0_ARB + i);
glDisable(GL_TEXTURE_2D);
}

aha! to answer my own question: a texture must be bound to any texture unit, even if the texture is not used.

in my example above, i frequently have no texture bound to the modulation unit. even though i enable GL_TEXTURE_2D, i don’t explicitly bind a texture. (my thinking, of course, ran along the lines of: “don’t use the texture, so why bind one?”)

anyway, problem solved.

Heh, funny thing

I’ve never used the vanilla texture functions (well, the NeHe tuts I tried actually did …) but isn’t GL_DECAL supposed to completely ignore the previous texture unit’s results?

In table 3.22 of the version 1.3 spec document, the GL_DECAL function is described as
Cv=Cf*(1-As)+CsAs

where Cv is the result, Cf is the primary fragment color (not the result of the previous unit), As is texture alpha and Cs is texture RGB.

What’s going on there? Are you sure that all three textures contribute to your final result?

I first thought it might be a typo in the spec … but generelly an implementation should obey the spec regardless

Or am I just being stupid?

Originally posted by zeckensack:
[b]Heh, funny thing

I’ve never used the vanilla texture functions (well, the NeHe tuts I tried actually did …) but isn’t GL_DECAL supposed to completely ignore the previous texture unit’s results?

In table 3.22 of the version 1.3 spec document, the GL_DECAL function is described as
Cv=Cf*(1-As)+CsAs

where Cv is the result, Cf is the primary fragment color (not the result of the previous unit), As is texture alpha and Cs is texture RGB.

What’s going on there? Are you sure that all three textures contribute to your final result?

I first thought it might be a typo in the spec … but generelly an implementation should obey the spec regardless

Or am I just being stupid?[/b]

it’s clear that all three texture units contribute to the result. recall that the spec was written prior to multitexturing capabilities. i just assumed (and correctly, this time) the Cf in these equations actually equates to GL_PREVIOUS_ARB, rather than GL_PRIMARY_COLOR_ARB. this does appear to be the case.

Eeeeks.
See this (gl 1.3 spec already includes multitexturing!):

Cf and Af are the primary color components of the incoming fragment; Cs
and As are the components of the texture source color, derived from the filtered
texture values Rt , Gt , Bt , At , Lt , and It as shown in table 3.21; Cc and Ac are
the components of the texture environment color; Cp and Ap are the components
resulting from the previous texture environment (for texture environment 0, Cp and Ap are identical to Cf and Af , respectively); and Cv and Av are the primary color components computed by the texture function.

Still they go on and use Cf and Af instead of Cp and Ap in the formulas.

That’s definitely a typo in the spec. It applies to the MODULATE function as well …