Kambi VRML game engine
← Users Developers →
 
Intro and News
 
view3dscene
 
The Castle
 
All Programs
 
Forum
 
Engine
 
VRML/X3D
 
Other
 

Texturing component

This component provides extensive textures support. 2D textures may be loaded from image files (ImageTexture), movie files (MovieTexture) or encoded directly in VRML/X3D files (PixelTexture, also ImageTexture with data urls). Multiple textures may be overlayed on a single polygon in a variety of ways. Texture coordinates may be explicitly provided or automatically calculated, and may be transformed.

See also X3D specification of Texturing component.

Contents:

1. Support

Supported nodes:

  • ImageTexture, TextureTransform, TextureCoordinate, PixelTexture

    Note: ImageTexture allows various texture formats, including JPEG, PNG, BMP, PPM, RGBE. GIF format is supported by running convert program from ImageMagick package "under the hood". See glViewImage (link to vrmlengine.sf.net) documentation for more detailed list.

    Note about alpha channel: alpha channel of the textures is fully supported, both a simple yes-no transparency (done by alpha_test in OpenGL) and full range transparency (done by blending in OpenGL, just like partially transparent materials). See "override alpha channel detection" extension description for details. The bottom line is: everything will magically work fast and look perfect.

    Note about REPLACE vs MODULATE modes: VRML 2 / X3D specifications say that RGB textures should REPLACE the color (as opposed to MODULATE the color from lighting calculations, material etc.). The problem with that is that this makes RGB textures nearly useless in typical 3D world (when you usually expect textured surfaces to be properly lit, regardless of RGB or grayscale format). That's why the default engine behavior contradicts the specification: it's MODULATE, making an RGB texture modulated by lighting just like a grayscale texture.

    I didn't decide it lightly (noone likes to deliberately contradict the specification...), but I think this case is justified --- MODULATE behavior is much more useful and usually desired, IMO. Feel welcome to send me emails and argument against this. After all, I'm trying to fit the needs of most people with default behavior. If many people think that specification is right and I'm dumb, and the default behavior should follow the spec and be REPLACE, I'll obey :)

    You have menu item in view3dscene RGB Textures Color Mode -> GL_REPLACE to change this (from code, use Scene.Attributes.TextureModeRGB := GL_REPLACE;).

  • MovieTexture

    TODO: for now, the sound of the movie is not played.

    Notes:

    • Current implementation keeps the whole encoded video in memory (images may be discarded after loading (by TVRMLScene.FreeResources feature), but still the textures for all frames are kept in memory). The disadvantage is that this makes it impractical to load "real" movies, normal 2-hour movie will most usually eat all of your memory. The advantage is that once the movie is loaded, the playback is super-fast, just like you would display normal nodes with static textures. Since there's no streaming, decoding etc. in the background while you browse your models.

      In other words, this is quite perfect for movie textures with game effects, like smoke or flame. But it's not a substitute for your "real" multimedia movie player.

    • ffmpeg must be installed and available on $PATH to actually open any movie format. See instructions for installing ffmpeg in view3dscene docs. Thanks to ffmpeg, we can handle probably any movie format you will ever need to open.

    • We can also open movies from images sequence. This doesn't require ffmpeg, and allows for some tricks (movie texture with alpha channel). See "Movies from images sequence" extension description.

  • MultiTexture, MultiTextureCoordinate, MultiTextureTransform

    TODO: modes MODULATEALPHA_ADDCOLOR, MODULATEINVALPHA_ADDCOLOR, MODULATEINVCOLOR_ADDALPHA are temporarily not supported.

    TODO: source values "DIFFUSE" and "SPECULAR" are treated the same, as PRIMARY_COLOR (in the sense of OpenGL ARB_texture_env_combine extension). Primary color contains material ambient, diffuse and specular factors, multiplied by lighting properties, summed over all lights. I don't know of any way to efficiently implement separate diffuse / specular sources — please report if you do, otherwise there's no way this can be fixed (note that engine's multi-texturing must work without shaders too).

    TODO: function field is not supported for now. It's somewhat uncomfortable, corresponding OpenGL settings (GL_OPERANDx) operate before normal texture unit calculations are done, while X3D spec requires function to act afterwards. To implement it generally, I'd have to use 1 more texture unit than requested (if the last texture unit will use any non-default function).

    See clarifications to X3D multi-texturing specification for more details about multi-texture handling.

  • TextureCoordinateGenerator

    Supported modes are now "SPHERE", "COORD", "COORD-EYE", "CAMERASPACEPOSITION", "CAMERASPACENORMAL", "CAMERASPACEREFLECTIONVECTOR".

    Note that "CAMERASPACEPOSITION" and "COORD-EYE" are exactly the same thing. Google confirms it (e.g. this source code also treats them as equal and in this old bitmanagement spec they mention they are equal).

    As an extension, we also allow "WORLDSPACEREFLECTIONVECTOR" and "WORLDSPACENORMAL" texture generation modes.

    TODO: not implemented modes: "SPHERE-LOCAL", "NOISE", "NOISE-EYE", "SPHERE-REFLECT", "SPHERE-REFLECT-LOCAL".

  • TextureProperties

    minificationFilter, magnificationFilter, anisotropicDegree are supported. TODO: rest is not.

2. Clarifications to X3D multi-texturing specification

2.1. Precise and corrected MultiTexture.mode specification (aka "how do we handle it")

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.
BLENDDIFFUSEALPHA Output.RGBA :=
  Arg1 * PRIMARY_COLOR.Alpha +
  Arg2 * (1 - PRIMARY_COLOR.Alpha)
Not allowed.
BLENDTEXTUREALPHA Output.RGBA :=
  Arg1 * Arg1.A +
  Arg2 * (1 - Arg1.A)
Not allowed.
BLENDFACTORALPHA Output.RGBA :=
  Arg1 * MULTI_TEXTURE_CONSTANT.Alpha +
  Arg2 * (1 - MULTI_TEXTURE_CONSTANT.Alpha)
Not allowed.
BLENDCURRENTALPHA Output.RGBA :=
  Arg1 * PREVIOUS_STAGE.Alpha +
  Arg2 * (1 - PREVIOUS_STAGE.Alpha)
Not allowed.

2.2. MultiTexture.source extensions

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" }
  ]
  mode [ "REPLACE" "SUBTRACT / SELECTARG2" ]
  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;

2.3. Problems with existing X3D MultiTexture.mode specification

Unfortunately, X3D specification is awfully ambiguous when talking about multi-texturing modes. Below is my list of spotted problems, and an explanation how we handle it in our engine (for a short summary, modes table in section above should also be informative). I tried to make my implementation following common-sense, hopefully it will be somewhat compatible to other implementations and at the same time comfortable to users.

Please report if any other VRML/X3D browser treats it differently, although it doesn't necessarily mean that I will fix to be compatible (I know that Octaga seems to revert the order of textures, for starters, which seems to contradict the spec... No wonder people get this messed up, since specification is so poor at this point.)

(And if you have any power over the spec, please fix issues mentioned below in the next version. It seems I'm not the only one confused by specs. I posted on forum asking for input about this, without any answer so far.)

  1. The mode field may contain an additional blending mode for the alpha channel. — this is the most troublesome part of the specification. It contradicts most of the remaining specification for MultiTexture node — other 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 only 1 mode means that no mode specific for alpha channel is available. Doing some smart detection when to expect the next mode to be for alpha channel seems very risky — since the specification says absolutely nothing about it. Should I expect separate mode for alpha channel only when the texture in current unit has some alpha channel? This isn't as sensible on the 2nd look, since operating on alpha channel in multi-texturing makes sense even if current texture unit doesn't provide any (after all, alpha may come from previous unit, or constant).

    Not to mention that some modes are clearly not possible for alpha channel.

    Our interpretation: One string inside the mode field always describes behavior for both RGB and alpha channel. So one string inside mode field always corresponds to one texture unit, Ok?.

    To allow different modes for RGB and alpha channel, you should just write two mode names inside one string, and separate them by a comma or slash. Like "MODULATE / REPLACE". See the modes table in previous section for a list of all possible values.

  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.

    Our interpretation: "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 tex unit (or mat color for 1st unit))".

  3. The meaning of "ADDSIGNED" and "ADDSIGNED2X" is unsure. 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, or each component.

    Note that no interpretation results in the output range of values in -0.5 ... 0.5, so it's not clear what is the "effective" range they talk about in "ADDSIGNED" spec.

    Our interpretation: 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. Although some modes say explicitly what happens with alpha channel, some do not. This is especially visible with "subtract" mode, that will subtract alphas making resulting alpha = 0 for the most common situation when both textures have alpha = 1.

    Our interpretation: I interpret this all as operating on all RGBA channels the same way. Comparing with Octaga, results for "subtract" seem equal this way: with default alphas = 1, result gets alpha = 0.

    If you don't like this behavior, you can specify separate mode names for RGB and alpha channel, separating them by a slash. For example, "SUBTRACT / MODULATE" will subtract RGB but modulate alpha.

  5. It's not specified what channels are inverted by function="COMPLEMENT" value. Only RGB seems most sensible (that's what would seem usually useful), but it's not written explicitly.

    Our interpretation: I plan to treat it as "only RGB", that is not invert alpha channel. Although for now "function" field is not handled.

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

  7. Another note: MODULATEINVCOLOR_ADDALPHA mentions that another mode, somehow forgotten, should exist: MODULATECOLOR_ADDALPHA (that doesn't invert the color).

3. DDS (DirectDraw Surface) support details

DirectDraw Surface (DDS) image format is supported. A number of technical details about DDS implementation are below, but in short: we try to support all formats and all options of DDS in a standard way.

Implementation history:

  • DDS support is natively built into the engine. Since I knew that I want to use many of DDS features, like cube maps, 3D textures, mipmaps, compression, I decided the best way to go will be to create my own reader, instead of relying on external tools.
  • Other notable open-source implementations of DDS are by GIMP-DDS plugin and new ImageMagick (since 6.3.9).
  • While implementing, I was looking at GIMP DDS source code (it's on GNU GPL >= 2 :) ) and MS documentation for DDS.

Cube maps in DDS are supposed to be oriented as usual for DDS:

  1. Which means that they match Direct X "positive/negative x/y/z". For OpenGL rendering we swap positive/negative Y faces (because Direct X has left-handed coordinate system, see here for drawing of DirectX cube map images orientation and compare with OpenGL cube map orientation).

  2. It's also a different orientation then the one of X3D ComposedCubeMap specification (left/right, bottom/top, front/back, with bottom/top on Y axis; X3D orientation needs rotating left,right,front,back images by 180 degrees for OpenGL orientation).

Images in DDS are supposed to be written from top to bottom row, as is the standard in DDS. (One particular tool, AMD CubeMapGen, allows to invert rows of the DDS images to match OpenGL bottom-to-top ordering; don't use this — we expect rows ordered as is standard in DDS, top-to-bottom.) Internally, our engine just inverts the rows for OpenGL (yes, this is doable also for S3TC compressed images.)

Pixel formats supported:

  1. Absolutely all uncompressed non-float pixel formats are supported.

    Details:

    The formats that are currently loaded optimally are ABGR8, BGR8, AL8, L8. They translate to RGBA8, RGB8 etc. OpenGL formats (reversed order, as DDS color masks are little-endian). Popular ARGB8 and RGB8 are also loaded very fast.

    Grayscale (luminance) images are allowed. AL8 and L8 are optimized. Note that grayscale images aren't officially allowed by DDS docs, but at least GIMP-DDS plugin can write it (just sets all R, G and B masks equal, and doesn't set any of DDPF_RGB, DDPF_FOURCC, DDPF_PALETTEINDEXED8).

    Also only-alpha images are allowed (another undocumented DDS feature, GIMP-DDS can write it, for now they will result in grayscale(white) with alpha image).

  2. Compressed texture formats handled: DXT1, DXT3, DXT5 are supported. Texture with DXT1 is always treated like a texture with simple (yes/no) alpha channel (so it will be rendered with alpha testing) and DXT3 / DXT5 are always treated like a texture with full range alpha channel (so they will be rendered with blending).

    Both normal (2D) textures and cube maps may be compressed. (There is no compression possible for 3D textures — neighter DDS format allows it, nor do common graphic cards.)

  3. Float textures are for now not supported, so our DDS reader also doesn't support them.

If DDS file includes mipmaps, and mipmaps are required for texture minification filter, we will use DDS mipmaps (instead of generating mipmaps automatically). Works for all 2D, 3D, cubemap DDS files.