X3D MultiTexturing problems and proposed solutions

The X3D specification has, unfortunately, numerous problems related to it's multi-texturing nodes (MultiTexture, MultiTextureCoordinate, MultiTextureTransform). We have documented these problems below, along with the tests on various X3D browsers, and with the proposed solutions, to encourage X3D authors to fix the specification.

This page also documents a problem related to single-texturing behavior, that is connected with some multi-texturing troubles: "What happens when you have a color texture, and a color material?" This is the one and only place where our engine deliberately does something different from X3D specification, because we feel that the X3D specification behavior is really not useful. In our engine, the texture color is by default multiplied by the material color.


1. Tests

X3D multi-texturing tests are available in SVN repository under https://svn.code.sf.net/p/castle-engine/code/trunk/demo_models/multi_texturing/ . You can browse this by WWW browser or do svn checkout on this URL. You can also download zip with all multi-texturing test files. For ease of browsing / downloading, everything there is self-contained, i.e. these test files do not refer to any files outside.

All tests are also listed below, roughly in the order basic -> advanced. Click on the image to view reference rendering (matches view3dscene from snapshots result, as our view3dscene implements all proposed specification fixes). Download X3D in classic or XML version and open with X3D browser of your choice. Files in X3D classic (VRML) encoding contain many comments, read them to know what the test is about!

modes_and_sources 1. modes_and_sources:

Test various values for MultiTexture.mode and MultiTexture.source.
Test results:
  • FreeWRL: Incorrect. MultiTexture.source seems ignored by FreeWRL. MultiTexture.mode = SUBTRACT is weird (not sure what is does). MultiTexture.mode = SELECTARG2 seems equal to MultiTexture.mode = SELECTARG1. Other MultiTexture.mode values seem Ok.
  • BS Contact (Screen) Incorrect MultiTexture.mode = SUBTRACT column (all the other columns are correct, so BS Contact is closest to reference/view3dscene). Looks like BS Contact subtracts only RGB channel, does not touch alpha. This contradicts our proposed clarifications. The X3D spec is ambigous about this, see problem 4.
  • Instant Player (Screen) Incorrect, many problems. MultiTexture.source does something weird (not sure why it changes the result like that). Various MultiTexture.mode values incorrectly handled. Note MultiTexture.mode = SUBTRACT is correct (subtract alpha 1-1 makes invisible surface).
  • Octaga Player: (Screen) Incorrect, many problems.
modes_blend 2. modes_blend:

Test of MultiTexture special modes "BLENDxxx".
Test results:
  • FreeWRL: All incorrect. Not sure how FreeWRL interprets MultiTexture.mode = BLENDxxx, it doesn't seem to follow spec or match reference images.
  • BS Contact (Screen) Ok.
  • Instant Player (Screen) All incorrect.
  • Octaga Player: (Screen) Incorrect 2nd, 3rd and 4th quads. So only BLENDDIFFUSEALPHA is correct.
modes_modulate_add_order 3. modes_modulate_add_order:

Test of multitexturing MODULATE and ADD modes used together. Shows that A * B + C <> A * C + B (compare 3rd and 4th box).

Note that whether the 1st and 2nd cube should be yellowish or not is a separate question, related to the default mode when using single-texturing (see below on this page, and test 9). For this test, we accepted the test as "Ok" regardless if the 1st and 2nd cubes are yellowish or not. Only 3rd and 4th cubes of this test were taken into account when judging if test passed/failed.

Test results:
  • FreeWRL: Ok.
  • BS Contact (Screen) Possibly incorrect? 3rd cube should look a little different than 4th, but it seems exactly the same (unless it is just a lighting playing tricks).
  • Instant Player (Screen) Incorrect (3rd and 4th cube). Not really sure what is happening there, why the effet is like it is.
  • Octaga Player: (Screen) Ok.
primitives 4. primitives:

Test MultiTexture on primitives (Box, Sphere, Cone, Cylinder).
Test results:
  • FreeWRL: Ok.
  • BS Contact (Screen) Incorrect. Texture transformation is not applied, but at least two textures are mixed Ok.
  • Instant Player (Screen) Incorrect. Multi-texturing is not used (squirrel textue is not mixed with brick texture). But texture transformation for squirrel is applied.
  • Octaga Player: (Screen) Ok.
functions 5. functions:

Test MultiTexture.function.
Test results:
  • FreeWRL: All incorrect. It seems FreeWRL doesn't handle MultiTexture.function.
  • BS Contact (Screen) Incorrect: 2nd and 3rd cube invalid. It looks like BS Contact supports MultiTexture.function (COMPLEMENT and ALPHAREPLICATE), but the COMPLEMENT support is buggy.
  • Instant Player (Screen) Incorrect 2nd and 3rd, it doesn't seem to apply MultiTexture.function at the right place. This is actually similar to BS Contact, but definitely contradicts the spec.
  • Octaga Player: (Screen) Incorrect. Looks like COMPLEMENT negates the alpha as well, which may be caused by specification problem 5.
transform_and_coordinates_faces 6. transform_and_coordinates_faces:

Test various MultiTextureTransform and MultiTextureCoordinate values.
Test results:
  • FreeWRL: Incorrect, various problems. It seems FreeWRL doesn't honor the multi-texture transformation properly, it also makes warnings "not enough textures in MultiTextureTransform...." instead of following the spec that says when identity matrices are assumed for transformation. Possibly caused by spec problem 6. below.
  • BS Contact (Screen) Incorrect, various problems (but *different* than e.g. FreeWRL problems).
  • Instant Player (Screen) Incorrect, various problems, but *different* than BS Contact and FreeWRL.
  • Octaga Player: (Screen) Incorrect, various problems.
transform_and_coordinates_quads 7. transform_and_coordinates_quads:

Test MultiTexture together with IndexedQuadSet from CAD component. Very similar to transform_and_coordinates_faces.x3dv (in fact the result should look exactly the same) but now uses IndexedQuadSet instead of IndexedFaceSet.
Test results:
  • FreeWRL: FreeWRL doesn't support CADGeometry component. Results are incorrect (you see nothing), but that's somewhat acceptable since the console warns that CADGeometry level support is 0 (none) in FreeWRL.
  • BS Contact (Screen) BS Contact doesn't seem to support CADGeometry component.
  • Instant Player (Screen) Incorrect. Result equal to 6. That's good, this means that CADGeometry quads correctly work with multi-texturing. But, since results of test 6. were not correct, results for test 7. show exactly the same problems.
  • Octaga Player: (Screen) Incorrect. Equal to 6. Which is good, it means CADGeometry quads work with multi-texturing. But, since result 6. was incorrect, this is incorrect too.
image_with_movie_multi_texture 8. image_with_movie_multi_texture:

Test ImageTexture and MovieTexture mixing using MultiTexture.
Test results:
  • FreeWRL: Incorrect. FreeWRL doesn't seem to support MovieTexture (although it doesn't complain when we request Texturing component at level 3, so it should support MovieTexture). Also makes warnings "not enough textures in MultiTextureTransform....", so probably would also exhibit problems from test 6.
  • BS Contact (Screen) Incorrect. MovieTexture support is weird (movie seems played in a separate window instead of as a texture). Also, transformation of squirrel texture is wrong.
  • Instant Player (Screen) Incorrect. MovieTexture does not seem supported, at least for multi-texturing.
  • Octaga Player: (Screen) Incorrect. MovieTexture not supported? At least for multi-texturing.
material_color_mixed_with_texture_color 9. material_color_mixed_with_texture_color:

This is not a MultiTexture test, but it tests a feature related to some multi-texturing problems: how various X3D browsers mix (single) texture with Material.diffuseColor and Color node. See lower on this page for details why this is tested.

The reference of this test (and view3dscene result) follows our proposition to always modulate by default. This contradicts the specification, although we argue (see link above) that in this case the specification 1. proposes a behavior that is not very useful and 2. is already implemented inconsistently.

Test results:
  • FreeWRL:

    Version: 1.22.13: Seems to never mix texture color with Material.diffuseColor (for both RGB (correct) and grayscale (incorrect) textures), and always mixes texture color with Color node (for both RGB (incorrect) and grayscale (correct) textures).

    Version: 4.0.0: RGB texture is multiplied with Material.diffuseColor (incorrect, although this is how we propose to change X3D spec), grayscale texture is multiplied with Material.diffuseColor (correct), RGB texture overrides Color node (correct) and grayscale texture multiplies Color node (correct).

  • BS Contact (Screen)

    Version: 8.101: RGB texture replaces Material.diffiseColor (correct). Grayscale texture is replaced by Material.diffuseColor (incorrect and weird). RGB texture modulates with Color node (incorrect according to spec). Grayscale texture modulates Color node (correct).

    Version: 8.300: RGB texture overrides Material.diffuseColor (correct), grayscale texture is multiplied with Material.diffuseColor (correct), RGB texture multiplies Color node (incorrect) and grayscale texture multiplies Color node (correct).

  • Instant Player (Screen)

    Version: 2.1.0: Equal to BS Contact 8.101 result for this test, which means incorrect (but at least, this time, consistent with BS Contact). 2.2.0: it seems it changed to be better (but still not exactly spec-complaing): InstantPlayer 2.2.0 doesn't mix texture color with Material.diffuseColor for RGB textures (correct) and does mix with grayscale textures (correct). However, it always mixes texture color with Color (for both RGB (incorrect) and grayscale (correct) textures).

    Version: 2.8.0: Equal to BS Contact 8.300 result for this test (so almost correct, only RGB texture incorrectly multiplies Color node).

  • Octaga Player: (Screen)

    Version: 4.0.3: RGB texture overrides Material.diffuseColor (correct). Grayscale texture is overridden by Material.diffuseColor (incorrect and weird, seems to match BS Contact 8.101). RGB texture overrides Color node (correct). Grayscale texture modules with Color node (correct).

    Version: 5.0.0: All correct! Congrats, this is the only browser that handles all 4 cases following the specification: RGB texture overrides Material.diffuseColor (correct), grayscale texture is multiplied with Material.diffuseColor (correct), RGB texture overrides Color node (correct) and grayscale texture multiplies Color node (correct).

  • X_ITE:

    Version 4.1.4-200: In all cases, texture color is multiplied by Material.diffuseColor or Color. This contradicts the X3D specification, but matches view3dscene, and is exactly as we propose to require in future X3D versions.

  • X3DOM:

    Version 1.7.2: In all cases, texture color overrides Material.diffuseColor or Color, even when the texture is grayscale. This is incorrect.

subtract_and_force_alpha 10. subtract_and_force_alpha:

Test MultiTexture with separate modes and sources for RGB/alpha, see below for our proposal to allow separate RGB/alpha specification for modes and sources (problem 1.), and proposed extended MultiTexture.mode table.
Test results:

Testing this is not fair. All VRML/X3D browsers except view3dscene fail on this, because this tests a proposed (not yet part of X3D spec) extension to specify separate modes and sources for RGB/Alpha (and clear some of the confusion around modes spec along the way). See lower on this page about proposed separate MultiTexture.mode and lower on this page about proposed separate MultiTexture.source

subtract_rgb_various_sources 11. subtract_rgb_various_sources:

One more test of MultiTexture with separate modes and sources for RGB/alpha. Similar to "subtract" column of modes_and_sources, but showing what happens when we subtract only RGB.
Test results:

Testing this is not fair. See previous test for more comments.

License: For the widest possible use, consider these files public domain, you're welcome to copy them to other examples repositories etc. Yes, the sample textures/movies inside are in public domain too (see data/AUTHORS.txt inside for details).

How these files were created: All the X3D test files were written manually in X3D classic encoding. XML encoding versions were automatically generated from classic encoding by tovrmlx3d (a tool distributed with view3dscene). The reference images were also generated by view3dscene (using --screenshot option to make screenshots in batch mode).

1.1. Details about browsers tested

Unless otherwise specified above, we tested on:

  • FreeWRL 1.22.13 (on Debian testing 32-bit, NVidia GeForce GPU)
  • BS Contact 8.101
  • Instant Player 2.1.0
  • Octaga Player 4.0.3
  • view3dscene (version right before 3.13.0) (on Debian testing 32-bit, NVidia GeForce GPU). view3dscene is Michalis' own browser, so the implementation 100% matches the reference images and all proposed clarifications/solutions mentioned on this page.

Screenshots obtained from BS Contact, Instant Player, Octaga Player are available as a zip file or or just browse/clone the GIT repository. Many, many thanks to Cecile Muller for testing!

2. Problems and proposed solutions

X3D specification about multi-texturing has a couple of problems. Below is a list of spotted problems, and an explanation how we handle it in our engine (Castle Game Engine and view3dscene) and how we propose to fix X3D specification. You probably want to read this along with MultiTexture specification in X3D 3.2 (or MultiTexture specification in X3D 3.3, there weren't any important changes since X3D 3.2).

Please report any comments, preferably to x3d-public mailing list.

Specification problems and our solutions:

  1. The mode field may contain an additional blending mode for the alpha channel. This is the most troublesome sentence of the MultiTexture specification. It contradicts most of the remaining specification for MultiTexture node. Other spec parts clearly suggest that exactly one mode string corresponds to one texture unit, for example 1. it's mentioned explicitly that if the mode.length is less than texture.length, remaining modes should be assumed as "modulate" 2. many modes are clearly used over both RGB and Alpha channels, and they specify results for both RGB and Alpha channels.

    This means that the meaning of mode=["MODULATE","REPLACE"] is not clear.

    What did the authors meant by the word may in the sentence "may contain an additional blending mode"?

    • Expecting two mode strings for one texture unit clearly contradicts the spec.
    • Expecting a single mode string for one texture unit means that no mode specific for alpha channel is available.
    • Smart detection when to expect the next mode to be for alpha channel (for example expect the additional mode for alpha channel only when texture image has alpha channel) is also a bad idea. First, because the specification says absolutely nothing about it. Second, because operating on alpha channel makes sense even if the image in the current texture unit doesn't have alpha channel (because alpha may come from previous texture unit, or from a constant).

    Also, some modes are clearly not possible (or sensible) for the alpha channel alone. For example, it doesn't make much sense to apply modes like DOTPRODUCT3 or BLEND* only to the alpha channel.

    Proposed clarification: a single string inside mode field always corresponds to exactly one texture unit. This string may be a simple name of the mode (like "MODULATE"), in which case it describes behavior for both RGB and alpha channel. This string may also contain two mode names, separated by a comma or slash (like "MODULATE / REPLACE"), in which case a separate behavior is specified for RGB channels and for alpha channel.

    The table in section Proposed improved MultiTexture.mode specification contains the exact equations for all the modes, when used for both RGB and alpha or when used for only RGB or only alpha.

  2. In Table 18.3 - Multitexture modes, "REPLACE" mode is specified as "Arg2", which makes no sense. Arg2 comes by default from previous unit (or material color), this is implicated by the sentence "The source field determines the colour source for the second argument". So mode "REPLACE" interpreted as "Arg2" would then 1. completely ignore current texture unit 2. contradict the normal meaning of "REPLACE", which is explicitly mentioned in specification at paragraph before this table ("REPLACE for unlit appearance"). An example with alpha (although ambiguous on it's own, more about this in previous point) clearly shows that "REPLACE" takes from 1st argument.

    Proposed clarification: "REPLACE" copies the "Arg1" (that is, current texture unit values). IOW, it's equivalent to "SELECTARG1".

    To make it absolutely clear, it would also help if the spec would clearly say something along the lines "Arg1 is the current texture unit, Arg2 is what is determined by the source field (by default, it's previous texture unit (or material color for 1st texture unit))". This would also make it clear what is the order of calculation for texture units (and would clarify that Octaga "reversed order" is incorrect — everyone else does it correctly).

  3. The meaning of ADDSIGNED and ADDSIGNED2X modes is not clear. Spec doesn't give the exact equation, and from the wording description it's not clear whether the -0.5 bias is applied to the sum (Arg1 + Arg2 - 0.5), or each component (Arg1 - 0.5 + Arg2 - 0.5 = Arg1 + Arg2 - 1.0). The first interpretation seems more reasonable, and it follows OpenGL GL_ADD_SIGNED behavior.

    Neither interpretation results in the output range of values in -0.5 ... 0.5. The claim making the effective range of values from −0.5 through 0.5 (at the ADDSIGNED value in table 18.3) doesn't seem to make any sense, regardless how you try to interpret it.

    Proposed clarification: I interpret it as "-0.5 bias is added to the sum", this follows OpenGL GL_ADD_SIGNED constant, so I guess this was the intention of the spec.

  4. Some modes say explicitly what happens with alpha channel, but some do not. This is especially troublesome in case of the "subtract" mode, that will subtract alphas making resulting alpha = 0 (invisible) for the most common situation when both textures have alpha = 1 (opaque).

    Proposed clarification: See point 1. If you specify a simple mode name, then it applies to both RGB and alpha channels. Comparing with Octaga, our results for "subtract" are equal this way: with default alphas = 1, result gets alpha = 0.

    This interpretation is consistent. In most cases, it also matches "what the author expects". The one exception is the "subtract" operation, when you usually do not want to subtract alphas — authors should just remember that usually they want subtract only RGB, using mode like "SUBTRACT / MODULATE".

    The table in section above (Precise and corrected MultiTexture.mode specification) makes it clear how to use each mode for only RGB, or only alpha, or both.

  5. It's not specified what channels are inverted by the function="COMPLEMENT" value. Well, obviously RGB are inverted, but is alpha channel inverted too?

    Tests show that view3dscene, Instant Player, BS Contact do it on RGB (not alpha). Octaga does it on RGBA (it negates alpha channel too). Other tested browsers do not support this operation.

    Proposed clarification: function="COMPLEMENT" works only on RGB, does not touch alpha channel. This seems more suitable for usual cases, and follows the majority of implementations.

  6. The paragraphs for MultiTextureTransform (texture coordinates for channel 0 are replicated...) and MultiTextureCoordinate (identity matrices are assumed...) should be swapped in the spec.

  7. MODULATEINVCOLOR_ADDALPHA refers to non-existing mode MODULATECOLOR_ADDALPHA (that doesn't invert the color).

  8. The definition of source="DIFFUSE" and source="SPECULAR" doesn't play nicely with lighting.

    Reading the definitions of source="DIFFUSE" and source="SPECULAR" it would seem that X3D specification forces the Gouraud shading (calculate lighting at each vertex, not pixel). Which is unacceptable, and I'm absolutely sure that all X3D browsers ignore it. Most browsers, including ours, allow to choose Gouraud shading or Phong shading. The default shading depends on hardware capabilities, user settings, and maybe other X3D features (like our extensions to force Phong shading or use bump mapping). In any case, the shading definitely should not depend on whether we use multi-texturing or not.

    Also, reading their descriptions it would seem that texture is applied after performing lighting calculation. Which contradicts the lighting equations in X3D spec " Lighting equations", that clearly say that textures affect the diffuse color which is then used for lighting.

    Proposed solution:

    1. At the very least, just change description of these source values to not talk about Gouraud shading. Just say for source="DIFFUSE", "The texture argument is the interpolated material diffuse color.". And analogously for specular. Do not talk about Gouraud shading here, because 1. you do not want to force Gouraud shading and 2. according to X3D lighting spec, the texture color calculation should happen before the shading.

    2. Moreover, speaking about diffuse or specular colors at this point doesn't really make much sense. According to lighting equations from X3D spec " Lighting equations", the texture color only changes (replaces or modulates) the material diffuse color, which is then used inside lighting equations.

      This means that source="SPECULAR" doesn't make much practical sense. Why use the specular color inside diffuse factor calculation?

      It would be best to remove source="DIFFUSE" and source="SPECULAR" and add source="MATERIAL", with the meaning This is the Material diffuse color (eventually replaced with the Color or ColorRGBA node). The result of multi-texturing is the calculated diffuse color used as Irgb and A parameters inside lighting equations. This color is then used for subsequent lighting calculations (is multiplied by diffuse factor, summed with specular, multiplied by light color and summed for all lights, and so on)."

    3. In all of this, there is also a recurring problem of X3D lighting specification.

      The implementations that use Gouraud shading (for example, OpenGL fixed-function implementations) do not really implement the X3D lighting equations. It's not possible, really. Textures have to be mixed after lighting calculation in case of Gouraud shading. Which means that we have to calculate non-textured source color, with lighting (with all diffuse and specular and all lights already summed), and only then it can be used for textures.

      This is actually a problem of X3D lighting specification, for which we have no simple solution (it would require changing the lighting equations). Anyway, it makes the source="MATERIAL" sound more sensible than source="DIFFUSE" and source="SPECULAR": in cases of these implementations, the source must already include both diffuse and specular.

    4. Our current implementation: Currently our implementation always mixes the textures after lighting calculation. Just like described above for Gouraud shading. We do it also in case of Phong shading for now (for consistency), although the Phong shading may be fixed one day.

      We treat both source="DIFFUSE" and source="SPECULAR" as equal, and actually they just mean "the result of lighting equations (for non-textured appearance)".

  9. The default mode is always modulate, for both RGB and grayscale textures. This is inconsistent with single-texturing (using normal ImageTexture instead of MultiImageTexture), when the default mode is to modulate for grayscale textures, but replace for RGB textures. This means that you cannot blindly change ImageTexture node into a MultiImageTexture node (with a single ImageTexture inside): because the default mode (possibly) changed.

    Proposed solution: In this case, I propose to change the specification parts related to single-texturing (ImageTexture), and leave existing multi-texturing spec unchanged. That is, always modulate by default (regardless if texture is RGB or grayscale).

    See RGB texture color by default modulates material color for a more detailed description of this problem. Existing browsers already disagree on this. Changing the spec to say "we always modulate by default" would greatly simplify the situation.

  10. It would be useful to clarify what happens with grayscale texture images and images without alpha channel. Following the GPU behaviors (and common sense), we propose to add such statement to X3D specification:

    For the purpose of multitexturing calculations,

    1. Grayscale texture is equivalent to an RGB texture with all color components (red, green, blue) equal.
    2. Texture without an alpha channel is equivalent to a texture with alpha channel filled with value 1.0 (completely opaque).

3. Proposed improved MultiTexture.mode specification

To allow different texture modes for RGB and for alpha channel, you should just write two mode names inside one string, and separate them by a comma or slash (additional whitespace around is allowed). For example, mode [ "MODULATE / REPLACE" ] means that on the 1st texture unit, RGB is modulated and alpha is replaced. Contrast this with mode [ "MODULATE" "REPLACE" ], that means to modulate (both RGB and alpha) on the 1st texture unit, and then to replace (both RGB and alpha) on the 2nd texture unit.

This way we keep the interpretation that "one string on the mode field always describes full behavior of exactly one texture unit". Of course, some modes are not available for alpha channel (these are the OpenGL constraints).

Table below describes precise behavior and disallowed situations for all mode names. Treat this as a corrected and precise version of the similar table in X3D spec of MultiTexture (see text down for details where and why it's corrected, short version: specification is simply poor and inconsistent). In table below,

  1. Arg1 is the current texture unit,
  2. Arg2 is determined by the source field. By default, it's the result of previous texture stage, or (for the 1st stage) it's interpolated material*lighting.
Mode name Behavior when used alone
(like "REPLACE")
Behavior when used for only RGB channel
(like "REPLACE / ...")
Behavior when used for only alpha channel
(like "... / REPLACE")
MODULATE Output.RGBA := Arg1.RGBA * Arg2.RGBA Output.RGB := Arg1.RGB * Arg2.RGB Output.A := Arg1.A * Arg2.A
MODULATE2X Output.RGBA := Arg1.RGBA * Arg2.RGBA * 2 Output.RGB := Arg1.RGB * Arg2.RGB * 2 Output.A := Arg1.A * Arg2.A * 2
MODULATE4X Output.RGBA := Arg1.RGBA * Arg2.RGBA * 4 Output.RGB := Arg1.RGB * Arg2.RGB * 4 Output.A := Arg1.A * Arg2.A * 4
REPLACE or SELECTARG1 Output.RGBA := Arg1.RGBA Output.RGB := Arg1.RGB Output.A := Arg1.A
SELECTARG2 Output.RGBA := Arg2.RGBA Output.RGB := Arg2.RGB Output.A := Arg2.A
ADD Output.RGBA := Arg1.RGBA + Arg2.RGBA Output.RGB := Arg1.RGB + Arg2.RGB Output.A := Arg1.A + Arg2.A
ADDSIGNED Output.RGBA := Arg1.RGBA + Arg2.RGBA - 0.5 Output.RGB := Arg1.RGB + Arg2.RGB - 0.5 Output.A := Arg1.A + Arg2.A - 0.5
ADDSIGNED2X Output.RGBA := (Arg1.RGBA + Arg2.RGBA - 0.5) * 2 Output.RGB := (Arg1.RGB + Arg2.RGB - 0.5) * 2 Output.A := (Arg1.A + Arg2.A - 0.5) * 2
SUBTRACT Output.RGBA := Arg1.RGBA - Arg2.RGBA Output.RGB := Arg1.RGB - Arg2.RGB Output.A := Arg1.A - Arg2.A
OFF Texture stage is simply turned off. Not allowed.
DOTPRODUCT3 NewArg1.RGB := (Arg1.RGB - 0.5) * 2;
NewArg2.RGB := (Arg2.RGB - 0.5) * 2;
Output.RGBA := dot(NewArg1.RGB, NewArg2.RGB)
... (calculate NewArg* same as on the left)...
Output.RGB := dot(NewArg1.RGB, NewArg2.RGB)
Not allowed.
  Arg1 * PRIMARY_COLOR.Alpha +
  Arg2 * (1 - PRIMARY_COLOR.Alpha)
Not allowed.
  Arg1 * Arg1.A +
  Arg2 * (1 - Arg1.A)
Not allowed.
Not allowed.
  Arg1 * PREVIOUS_STAGE.Alpha +
  Arg2 * (1 - PREVIOUS_STAGE.Alpha)
Not allowed.

4. Proposed MultiTexture.source extension

In the same spirit, you can specify separate sources for RGB and alpha channels, just separate them by comma or slash within a single string. For example, source string "DIFFUSE / FACTOR" says to take diffuse color as a source for Arg2.RGB and constant factor (MultiTexture.alpha field) for Arg2.Alpha.

Note that the empty string is also a source name (it means to take color from previous texture stage). So source string like "/ FACTOR" is also Ok (takes RGB from previous stage, and alpha from constant factor), and "FACTOR /" is Ok (takes RGB from constant factor MultiTexture.color, and alpha from previous stage).

An example: suppose you have two textures that you want to subtract on RGB (tex2 - tex1) channel, and you want to set resulting alpha channel to 1.0 (regardless of any texture value). This will work:

MultiTexture {
  texture [
    ImageTexture { url "tex1.png" }
    ImageTexture { url "tex2.png" }
  source [ "" " / FACTOR" ]
  alpha 1.0

# Calculations on texture unit 1:
#   Stage1Output.RGBA := Tex1.RGBA;
# Calculations on texture unit 2:
#   Output.RGB := Tex2.RGB - Stage1Output.RGB;
#   Output.A := Arg2.A := 1.0;

5. Related single-texturing problem: RGB texture color by default modulates material color

VRML 2 / X3D specifications say that RGB textures should by default REPLACE the material color (as opposed to MODULATE). This is when no multi-texturing is used. This is said by the specification at tables "Table 17.2 — Unlit colour and alpha mapping" and "Table 17.3 — Lit colour and alpha mapping": note that RGB and RGBA texture colors are not multiplied by color from Material.diffuseColor or Color nodes. Also spec about Color nodes (11.4.2 Color, 11.4.3 ColorRGBA) says explicitly that "RGB or RGBA textures take precedence over colours; specifying both an RGB or RGBA texture and a Color* node for geometric shape will result in the Color* node being ignored.".

Problems with the specification text:

  1. It makes Material.diffuseColor and Color useless with RGB textures, which is a shame. GPUs do not have such limitations.
  2. It is inconsistent with MultiTexture behavior, when the modulate mode is the default — regardless if we have RGB or grayscale texture.
  3. In case of our current implementation, the texture color is mixed with the whole resulting lighting calculation. Using the "replace" mode by default would mean that shapes are unlit when you use RGB textures.

A separate problem is that browsers are already inconsistent in the implementation of this rule, see test results. That's understandable, because the spec behavior is a little useless.

That's why we propose to change the specification: simply always MODULATE (component-wise multiply on RGBA channels). In other words, treat a grayscale texture exactly like an RGB texture with all color components (red, green, blue) equal. Our engine and view3dscene already implement this behavior.