Make RGB and grayscale textures treatment consistent - michaliskambi/x3d-tests GitHub Wiki

Table of Contents:

THIS IS DONE

The issue described on this page is DONE. I have made the necessary prose and equations fixes in X3D 4.0 (2nd working draft). It is summarized in X3D version 4: New features of materials, lights and textures.

When sampling any texture, the grayscale texture is exactly equivalent to using an RGB texture with all 3 components (red, green, blue) equal.

When sampling any texture, the texture without an alpha channel is exactly equivalent to using a texture with an alpha channel filled with 1 (indicating opaque).

These rules make treatment of the textures simple, and consistent with other 3D authoring software. They are also consistent with how the graphic APIs and GPU shaders query the textures.

(The browsers are encouraged to optimize loading of the textures, to not load all textures as 4 channels (RGBA) to the GPU. Intensity texture can be loaded as just 1 channel, intensity + alpha is only 2 channels, RGB texture without alpha is 3 channels. Optimizing this loading is useful to keep GPU memory usage low, and to keep texture loading time smaller. However, this is just an optimization. Performing it is optional, and the rendering result should be the same as if all textures were loaded as full RGBA textures.

Backward compatibility note: In X3D version 3, the treatment of grayscale and RGB textures was not consistent. In some cases (using ImageTexture node, but not inside MultiTexture) grayscale texture resulted in a different rendering result than the equivalent RGB texture (with all red, green, blue components equal). As this was inconsistent (within X3D, and with other software and model formats), uneasy to implement (browsers needed to investigate the image header), needlessly limiting to authors, and the implementation was inconsistent across the existing browsers — in X3D 4.0 it was streamlined.

Summary

The goal is to change the specification such that "grayscale texture" is always treated the same as the "RGB texture with the red, green and blue components equal, on all pixels".

Current X3D specification rule is more complicated, saying to use "modulate mode" (multiply with material) for grayscale textures, but "replace mode" (override material) for RGB textures. See https://castle-engine.io/x3d_multi_texturing.php#section_default_texture_mode . This rule is:

  • Surprising for X3D authors. No other graphical system (e.g. default Unity3d shaders or Blender materials) makes such distinction between grayscale and RGB textures. Everyone else (by default) just always modulates, i.e. texture color is multiplied by material color, and it does not matter if the texture image is RGB or grayscale.

  • Limiting for X3D authors. Approach to "modulate always" is more flexible, and does not cost anything on existing GPUs.

  • It is uneasy to implement. Some image loaders do not expose the necessary information about the image header, to easily judge is the image grayscale or RGB.

    Some browsers (at least view3dscene/Castle Game Engine and FreeWRL) detect grayscale/RGB based on the image contents. But it seems dangerous that the existence of such detection changes the behavior of the calculation, and the texture author has no way to "force the texture to be treated as RGB" while keeping all pixels with equal red = green = blue components. After my proposed change, the existence of such detection would be merely an optimization -- something that X3D browsers can certainly do, to optimize GPU memory usage, but it does not change the correctness of the output.

  • This rule is already inconsistently applied in current X3D implementations. See my tests on https://castle-engine.io/x3d_multi_texturing.php -- the relevant test is 9. material_color_mixed_with_texture_color on that page. Only 1 X3D browser out of 7 implements the current specification 100% correctly.

    Also, Blender -> X3D exporter assumes that the texture (RGB or grayscale) always modulates. Since Blender materials modulate, regardless of RGB or grayscale. So it also ignores this part of X3D 3 spec. (It's unsure how Blender could do otherwise. Blender would need to implement special material setting to show X3D-spec-3-complaint behavior. And this X3D-spec-3-complaint is unwanted by everyone (no benefits, and additional limitation).)

Our proposal is to simply always multiply (component-wise) texture.rgba * material.rgba, regardless if the texture is RGB or grayscale. My implementation (view3dscene and Castle Game Engine) behaves like this already. This is the one place where I knowingly, deliberately implemented something differently than what X3D specification says --- because the specification behavior is really uncomfortable in my eyes, for X3D authors too.

It seems that a simple change to the specification (treat grayscale textures same as RGB) would make things easier both for developers and for X3D authors.

The proposed course of action is to make this change dependent on declared X3D version, i.e. each browser can implement this mechanism:

if X3D.major_version >= 4 then
  result.rgba := material.rgba * texture.rgba
else
  result.rgba := <do whatever you were doing previously, to not surprise your own users>;

This can be worded in X3D spec by adding a section like Compatibility with previous X3D versions:

Previous X3D specifications requested a different, incompatible behavior when you use a single texture (not MultiTexture) that is RGB. The X3D 3 specifications requested that texture RGB color should then replace the material color diffuse color. Unfortunately, this rule was never implemented consistently, and various X3D browsers deviated from this rule in different (incompatible) ways.

That is why we advise the existing browsers to keep their current behavior (whatever it is) for X3D 3, and only perform the new (simple, consistent) calculation for X3D versions 4 and onward. If you implement a new X3D browser right now, you can practically choose any behavior for X3D 3 (since it is not realistically possible to be consistent with existing browsers anyway), in this case it's simplest to implement the new rule (always multiply) for all X3D versions.

(Yes, it means that X3D 4 specification advises to ignore one paragraph of X3D 3 specification. I do not think it is practical to advise anyone to follow X3D 3 specification behavior of RGB/grayscale, since existing browsers do not follow it, so you do not gain any interoperability.)

Note about above from talk with Don and Richard: This is not necessary. X3D 4 spec talks about how to interpret X3D 4 files, that's it. Advises how to implement it, live above, should be outside.

The exact new equations, that constitute the proposed specification change, are shown at the end of this document. They account for the Color node, and specify what to do with alpha channel too (see the discussion lower on this page about alpha).

Testcase

  1. The trivial testcase is linked on https://castle-engine.io/x3d_multi_texturing.php , with test results on various X3D browsers. It's named "9. material_color_mixed_with_texture_color:". This tests how does the RGB/grayscale texture mix with color in Material.diffuseColor and Color node.

    The X3D in classic encoding is here:

    https://raw.githubusercontent.com/castle-engine/demo-models/master/multi_texturing/material_color_mixed_with_texture_color.x3dv

    The X3D in XML encoding is here:

    https://raw.githubusercontent.com/castle-engine/demo-models/master/multi_texturing/material_color_mixed_with_texture_color.x3d

  2. The testcase to see how does the alpha in the texture mix with alpha derived from Material.transparency and ColorRGBA node is here:

    X3D in classic encoding:

    https://raw.githubusercontent.com/castle-engine/demo-models/master/multi_texturing/material_alpha_mixed_with_texture_alpha.x3dv

    X3D in XML encoding:

    https://raw.githubusercontent.com/castle-engine/demo-models/master/multi_texturing/material_alpha_mixed_with_texture_alpha.x3d

What about alpha channel?

It should modulate by default too. That is, multiply alpha channel of the texture with Material.alpha (which is of course "1-Material.transparency"). This follows the idea that "a texture without alpha channel should behave the same as a texture with alpha channel existing, but always equal 1.0". In total, it makes a simple, consistent set of rules:

  • The complete texture color (red, green, blue, alpha) is always multiplied by the material color (Material.diffuseColor, or per-vertex color from Color node).
  • If a texture is grayscale, then it always behaves like red = green = blue on all pixels.
  • If a texture does not have alpha channel, then it always behaves like alpha = 1 on all pixels.

The point is to make a behavior that is unsurprising and obvious to X3D authors, so it should just be the default.

What if we want to explicitly request a "REPLACE" mode? Or "REPLACE" alpha channel, but "MODULATE" RGB channels?

If you want to change the behavior of channels (e.g. to "replace" instead of "modulate" on all RGBA channels, or to "replace" only on the alpha channel but modulate on RGB) then use the multi-texturing nodes with a single texture. MultiTexture.mode field was invented exactly to specify such thing, and it should work equally well when you just place a single texture inside MultiTexture. Unfortunately, the MultiTexture specification is ambiguous... but I propose how to fix it, and also extend, on https://castle-engine.io/x3d_multi_texturing.php . (My view3dcene and Castle Game Engine already implement it, if you want to check.)

If we would apply X3D specification changes that I propose on https://github.com/michaliskambi/x3d-tests/wiki/Make-RGB-and-grayscale-textures-treatment-consistent and on https://castle-engine.io/x3d_multi_texturing.php then both of these behave the same:

  1. ImageTexture { url "my_texture.png" }

  2. MultiTexture { texture ImageTexture { url "my_texture.png" } }

Both of the above modulate, by default, on all (RGBA) channels. They are exactly equal for all texture types (RGB or grayscale, with or without alpha channel). And a texture without alpha is like texture with alpha = 1, and a grayscale texture is like texture with red=green=blue.

You can actually test it with view3dscene and Castle Game Engine already.

(If one would instead follow the current X3D specification literally, then

  1. would sometimes replace, sometimes modulate, depending on RGB/grayscale format of the texture.
  2. would always modulate (regardless if texture is grayscale or RGB), at least on RGB channels. The spec is ambiguous what to do with alpha in this case. See the first point of my page https://castle-engine.io/x3d_multi_texturing.php#section_problems_solutions for explanations.)

And if you want to explicitly request some mode, then (after multi-texturing spec is fixed+extended as I propose on https://castle-engine.io/x3d_multi_texturing.php ) you can write this:

# replace RGBA
MultiTexture { texture ImageTexture { url "my_texture.png" } mode "REPLACE" }

# modulate RGBA (this is the default anyway, but you can specify it explicitly)
MultiTexture { texture ImageTexture { url "my_texture.png" } mode "MODULATE" }

# modulate RGB, replace alpha
MultiTexture { texture ImageTexture { url "my_texture.png" } mode "MODULATE / REPLACE" }

# replace RGB, modulate alpha
MultiTexture { texture ImageTexture { url "my_texture.png" } mode "REPLACE / MODULATE" }

Backward compatibility

  1. I initially made my proposal a few years ago, partially because the existing browsers were already very incompatible in this case. ("Whether to replace, or multiply, texture color.") And today, 2017, on existing latest X3D browsers, the results are different, better.. and still incompatible. Only 1 browser out of 6 manages to be 100% conforming to the current specification.

    (And I'm not counting here my own view3dscene, that does something different than the current specification deliberately.)

    See the details on https://castle-engine.io/x3d_multi_texturing.php , the table cell titled "9. material_color_mixed_with_texture_color".

    So the 1st goal of my proposition was to make this behavior more consistent in future X3D version, across all browsers.

    Bottom line: The compatibility is already poor, when the existing implementations (mis)interpret the current specification in various (different) ways.

  2. Note that view3dscene and Castle Game Engine work like this (that is: always multiply, even for RGB textures, contradicting the X3D specification) from day one. And there was zero bugreports about this. Probably because some X3D browsers already work like this, or because all other graphic software (outside of X3D) already works like this (Unity3d materials, Blender materials... they all multiply, always).

  3. The 2nd goal of my proposal is to increase interoperability with other software (outside X3D). Everyone else is just multiplying colors, not differentiating between RGB and grayscale textures. I mentioned already default Unity3d materials and Blender materials as example. I worked with various 3D authoring tools, and as far as I know --- no software, and no other 3D format, works like X3D specification dictates (replace RGB textures, multiply grayscale textures), at least by default. My proposal would make default X3D material behavior more consistent with others.

    E.g. right now Blender exporter is not strictly correct, if we consider that Blender materials always multiply texture*color, while the exported X3D scene will not always multiply (if current X3D specification is implemented literally).

X3D version

I propose to change it with a major version, that is X3D 4.0.

Exact equations change

To be clear, I propose to make 2 changes to the tables "Table 17.2 — Unlit colour and alpha mapping" and "Table 17.3 — Lit colour and alpha mapping" in http://www.web3d.org/documents/specifications/19775-1/V3.2/Part01/components/lighting.html#Lightingmodel .

  1. First, in both of these tables, rows RGB texture and RGBA texture should have an additional term to multiply color (from Color, ColorRGBA nodes or Material.diffuseColor) by texture. In the current specification, this term is only at grayscale textures.

  2. Second, in all cases, alpha (from Material.transparency / Color / ColorRGBA node) should be multiplied by the texture alpha.

New version is below. The changes are marked in bold. Multiplications of 3D vectors below are component-wise.

Table 17.2 — Unlit colour and alpha mapping

Texture type Colour per-vertex or per-face Colour NULL
No texture Irgb= ICrgb
A = 1
Irgb= (1, 1, 1)
A = 1
Intensity
(one-component)
Irgb= IT × ICrgb
A = 1
Irgb = (IT,IT,IT)
A = 1
Intensity+Alpha
(two-component)
Irgb= IT × ICrgb
A = AT
Irgb= (IT,IT,IT)
A = AT
RGB
(three-component)
Irgb= ITrgb × ICrgb
A = 1
Irgb= ITrgb
A = 1
RGBA
(four-component)
Irgb= ITrgb × ICrgb
A = AT
Irgb= ITrgb
A = AT

Table 17.3 — Lit colour and alpha mapping

Texture type Colour per-vertex or per-face Color node NULL
No texture ODrgb = ICrgb
A = 1-TM
ODrgb = IDrgb
A = 1-TM
Intensity
(one-component)
ODrgb = IT × ICrgb
A = 1-TM
ODrgb = IT × IDrgb
A = 1-TM
Intensity+Alpha
(two-component)
ODrgb = IT × ICrgb
A = AT × (1-TM)
ODrgb = IT × IDrgb
A = AT × (1-TM)
RGB
(three-component)
ODrgb = ITrgb × ICrgb
A = 1-TM
ODrgb = ITrgb × IDrgb
A = 1-TM
RGBA
(four-component)
ODrgb = ITrgb × ICrgb
A = AT × (1-TM)
ODrgb = ITrgb × IDrgb
A = AT × (1-TM)

ColorRGBA node

Note that the tables above do not take into account the possibility of ColorRGBA node. It seems that this part of X3D specification was just not updated correctly when ColorRGBA node was introduced. I did not try to update them to account for ColorRGBA node now, as this would cause more changes, clouding the changes I want to propose...

In case ColorRGBA node is present:

  • In case of Lit colour table: The term 1-TM is just replaced everywhere by alpha from ColorRGBA node.
  • In case of Unlit colour table: The resulting A is always (in all table cells) just multiplied by alpha from ColorRGBA node.

This all follows the X3D specification of ColorRGBA node, as I understand it. I don't propose any changes around ColorRGBA node. The rule is this: If ColorRGBA node is present, then:

  1. ColorRGBA provides an alpha value that is always multiplied with the texture alpha value.
  2. And Material.transparency is ignored.

Implementors seem to agree

⚠️ **GitHub.com Fallback** ⚠️