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 -------------------------------------------------------------------
DemoFrame.java
Description: java/
AlphaDemo.java
Description: java/