Well, after much wailing and gnashing of teeth, I *finally* worked out
how to get Alpha textures working under Java3D. It appears there are no
bugs in Java3D's implementation, only some really weird expectations and
dodgy documentation - Kelvin's post of a couple of weeks ago help sort
that out. So here's what I learnt. I'll post this on j3d.org in a day or
so once I've got the site cleaned up from the migration.

What are Alpha Textures?

An alpha texture is a texture that contains only alpha channel (ie
transparency) information. Typically this is used to "stencil" a piece
of geometry to only leave pieces visible without chewing very large
numbers of polygons. Effectively it is a cookie-cutter for geometry.
Where the alpha texture is completely transparent, you see nothing.
Where it is fully opaque, you see whatever underlying geometry is there
- including the material colour. If you go the multi-texture route, it
would also include the other textures. A typical use for alpha textures
is for creating 2D text in a 3D world.

Creating an alpha texture

There are a couple of ways of creating an alpha texture - dynamically
creating one in code and loading one from a file. When you load from a
file, you get whatever the file format has, so we'll ignore that for
now. This will concentrate on application-created textures.

The main feature of an alpha texture is that it only contains one piece
of information - the alpha channel. This could be expressed in two ways
- a full RGBA image with the colour channels ignored, or using a simple
grey-scale image with a single channel, which is interpreted to mean
alpha channel information. In the first case you are using up 4 bytes
per pixel, and effectively throwing three of them away, in the later
case, you use one byte per pixel. If you are doing a lot of alpha
texture work that memory saving can mean an awful lot - especially on
memory constrained video cards. On a typical 256x256 texture that means
260k v 64k.

Getting the RGBA textures to work is a relatively straight forward
affair, but getting alpha-only textures going is not, so we'll just
concentrate on that for now.

Firstly you must start by creating your source image. This is a
BufferedImage, where you set the type to be a greyscale image (1 byte
per channel)

BufferedImage bi =
   new BufferedImage(128, 128, BufferedImage.TYPE_BYTE_GRAY);

You could also used the byte-packed format (TYPE_BYTE_BINARY) if you
wanted to save even more memory, but these are really only useful if all
you want is transparent/non-transparent, rather than shades of
transparency.

At some point you need to draw to the texture. You start with the usual
fetch of the Graphics context to draw with -

Graphics2D g = bi.createGraphics();

Now you need to execute the drawing instructions. The first thing you
need to do is work out how you are going to draw to the image. Are you
going to clear all the alpha bits or make the image all transparent and
just draw the parts you want seen? In either case, you need to decide on
the colours to use, and this is the tricky bit. By just reading the
documentation you would expect that what you would do is create
instances of Color that have the Alpha value set - for example:

Color CLEAR_COLOR = new Color(0, 0, 0, 0);
Color VISIBLE_COLOR = new Color(0, 0, 0, 1f);

This is not correct - remember that you are drawing to an image that
only has one byte of colour representation. If you did the above, all
you would get is a completely clear image. The drawing routines will
just ignore the alpha value completely and only work from the colour
value. Remember that this is a greyscale image so you really need to
think in terms of black and white. Not only that, but you have to
remember what black and white mean in the context of transparency. To
say something is transparent, you have a (normal) alpha value of zero.
If this alpha value is sourced from a black and white image, you need to
have the value that is zero in that image be the transparent bit. In the
colour world, what has a value of zero? Black. And therefore what is the
visible bit? White. Your colour definitions really need to be this:

Color CLEAR_COLOR = Color.black;
Color VISIBLE_COLOR = Color.white;

Now you are ready to draw away. In this example, we clear all the image
and then draw a box with a hole in it:

     g.setColor(CLEAR_COLOR);
     g.fillRect(0, 0, 128, 128);

     g.setColor(VISIBLE_COLOR);
     g.fillRect(32, 32, 64, 64);

     g.setColor(CLEAR_COLOR);
     g.fillRect(48, 48, 32, 32);

     g.dispose();

After finishing the drawing of your image, you will need to apply it to
a Java3D object as a texture. Firstly you need an ImageComponent.
There's nothing really special here, just a way of making sure that you
have the right image format:

   ImageComponent2D img_comp =
     new ImageComponent2D(ImageComponent2D.FORMAT_CHANNEL8,
                          bi,
                          true,
                          false);

Then you need your 2D texture object and apply it to the Appearance
object for your geometry.

   Texture2D texture = new Texture2D(Texture2D.BASE_LEVEL,
                                     Texture.ALPHA,
                                     128,
                                     128);
   texture.setImage(0, img_comp);
   appearance.setTexture(texture);

So far, so good. If you now attempted to render the geometry, all you
would see is .... nothing. The texture completely replaces the geometry
  and it doesn't do a very good job of it. (I think that's a bug in the
j3d code in that I think it should present a grey geometry). The reason
for this is that Java3D has a way of controlling the way textures are
applied to an object through the TextureAttributes class. The way they
are applied is termed the mode - you'll see this in the API calls
setTextureMode(). By default, the mode is REPLACE - replace the object
colour by the texture colour. This is not what we want because it should
really act like a stencil and use the texture to define only where you
want the natural object colour to shine through.

When reading through the Java3D documentation, the way the modes work
together is not really that clear. It makes sense when you have full 3
and 4 component image types, but it is really ambiguous, if not
completely useless for the other image types that use 1 and two
components. In these images, they effect the output completely
differently. An "alpha" texture does not have an alpha channel, so you
need to tell the texturing system that you have to interpret the texture
colour as an alpha channel. The only way to do this is with the COMBINE
mode.

The role of the COMBINE mode is to allow you exact control over which
channels get used and how the maths is used to determine the final
colour of the object. To make the 1 and 2 component images work properly
(you can also use the similar tactic for light map textures) you have to
set up the individual parts.

When reading through the combine mode documentation, you will notice
that there are sets of method calls of the form:

setCombineRgbX()
setCombineAlphaX()

Here's the clue - you have separate control over the way alpha channel
information is sourced compared to the standard colour. This will take a
number of steps, so we start by working out how we want to apply the
colour to the object.

For the combine operations, we have another set of mode operations that
exactly mirror your normal mode operations - COMBINE_REPLACE,
COMBINE_MODULATE etc. Because we want to completely control the object
colour, we want to do our own combination that replaces the colour of
the object with our own custom setup, so we choose the COMBINE_REPLACE
mode for bother Alpha and RGB channels:

TextureAttributes texAttr = new TextureAttributes();
texAttr.setTextureMode(TextureAttributes.COMBINE);
texAttr.setCombineRgbMode(TextureAttributes.COMBINE_REPLACE);
texAttr.setCombineAlphaMode(TextureAttributes.COMBINE_REPLACE);

Next we want to define where we are going to source all our colour
information from. Here you use the setCombineXSource methods to define
exactly what the source colour is. In this case we're going to assume
that you want the source colour to come from the object, and the alpha
comes from the texture.

texAttr.setCombineRgbSource(0, TextureAttributes.COMBINE_OBJECT_COLOR);
texAttr.setCombineAlphaSource(0, TextureAttributes.COMBINE_TEXTURE_COLOR);

In the first line we say to only take the object's colour for the RGB
source values, and for the output alpha value, only use the texture
colour. Since our texture is only a single byte per pixel, that is
perfect for our needs. However, note that in this setup, if your object
already has material colour with some level of transparency already
applied, this will completely ignore the object's alpha value and only
use the texture as the alpha. Using the object's underlying transparency
in addition to the texture is a lot more complex, and I'll write about
that later.

BTW, if you are wondering what the first 0 value is for - that's because
the combine mode may use up to 3 sources of data that go into the final
calculated value. As we only have one source for colour data and one
source for alpha data, we only need to set the first component.

So far, so good. Next we need to tell the texturing system how to
combine the values and generate the output for the final colour. This is
termed the Combine Function. To generate the output colour, we need to
use the source colour for the RGB output and the source Alpha as the
alpha output like so:

texAttr.setCombineRgbFunction(0, TextureAttributes.COMBINE_SRC_COLOR);
texAttr.setCombineAlphaFunction(0, TextureAttributes.COMBINE_SRC_ALPHA);

This seems rather trivial at first glance and you might wonder why it
needs to be done? Well if you consider the implications here, you can do
all sorts of funky stuff, such as using the colour of the image to
change transparency. Think of doing something on the fly like a movie
texture as the control of the transparency rather than as colour. Or,
the other useful use of this is to take the inverse of the source - say
someone used white as the transparent value and black as opaque in the
alpha texture.

Now you are ready to roll with the alpha channel. Just don't forget to
apply the TextureAttributes to the Appearance and you are set to go.
I've attached a hacked up demo program to illustrate the point.

--
Justin Couch                         http://www.vlc.com.au/~justin/
Java Architect & Bit Twiddler              http://www.yumetech.com/
Author, Java 3D FAQ Maintainer                  http://www.j3d.org/
-------------------------------------------------------------------
"Humanism is dead. Animals think, feel; so do machines now.
Neither man nor woman is the measure of all things. Every organism
processes data according to its domain, its environment; you, with
all your brains, would be useless in a mouse's universe..."
                                               - Greg Bear, Slant
-------------------------------------------------------------------

Attachment: DemoFrame.java
Description: java/

Attachment: AlphaDemo.java
Description: java/

Reply via email to