Local Info

Topics

j3d.org

Cubic Environment Mapping Example

Environment mapping is the technique used to generate reflections in objects. There are a number of different ways to do environment maps - spherical, oval and cubic. The first two can be done by making use of the texture attributes and automatic texture coordinate generation to warp a texture around an object. They do suffer some limitations, and that is why cubic environment map textures were created. Cubic maps require quite a different setup at the low level, and thus reflected by a separate class at the scene graph level.

If you would like to see the complete version of this code, it can be found in the examples/texture directory and is named CubeMapDemo.java.

 

Setting up the application

Cubic environment maps work in the opposite to traditional textures. Although you have an object and a texture, the way it is applied is a little different. In this example we start with the basic texture example of a sphere that is having the texture applied to it. Spheres are good for enivornment mapping demo code as you can see a good 180 degree reflection of the "world" around you.

In addition to the basic geometry, we'll need six images to use as textures. These correspond to the 6 sides of the cube that we'll be mapping. Most demos use a pretty outdoor scene as it looks cool. We'll be a bit different and use textures that just have the side written in an oddball font. It makes it easy to detect which side is missing or if you have something strange going on with the mapping. After you have the basics down, then feel free to go to town with the sexy images.

Creating the texture

The cubic environment map is represented by a special form of texture with it's own subclass of Texture. Where normal textures are applied in one of a number of dimensions to the underlying geometry by taking a slice through the texture with texture coordinates of the appropriate number of dimensions, an environment map is used as 2D objects which are then projected onto the geometry. As such, it is impossible to hand-create the texture coordinates you need, and you'll need to make use of the automatic texture coordinate generation capabilities provided by the TexCoordGeneration class.

As the first step towards creating your environment map, you'll need to load the six images, one for each side. Though it is not required that you provide all six sides, the object would look quite strange if you did not as there will be parts of the reflection "missing". In addition to the normal power of 2 requirements that textures have, cubic environment maps have a further requirement - the image must be exactly square. If your images are not exactly square, then both Aviatrix3D and OpenGL will complain.

To load the images, we'll start by using the same loadImage method from the other texturing examples. This will create for us the basic Aviatrix3D representation - the TextureComponent instance.

TextureComponent2D[] img_sides = new TextureComponent2D[6];

String[] targets =
{
    "textures/left_cube_map.gif",
    "textures/right_cube_map.jpg",
    "textures/top_cube_map.jpg",
    "textures/bottom_cube_map.jpg",
    "textures/back_cube_map.jpg",
    "textures/front_cube_map.jpg"
};

for(int i = 0; i < 6; i++)
    img_sides[i] = loadImage(targets[i]);

The order in this example is somewhat important as each item in the array corresponds to the side of the cube it will be placed on. This is not necessary in real applications, just something that is done for simplicity in this example.

With the images loaded, you need to put them into an instance of the TextureCubicEnvironmentMap class. That's as simple as using the setImages() method that you've seen before (note that if you wanted, you could mipmap environment maps too).

TextureCubicEnvironmentMap texture = new TextureCubicEnvironmentMap();
texture.setImages(Texture.MODE_BASE_LEVEL,
                  Texture.FORMAT_RGB,
                  img_sides,
                  6);

Since we pre-ordered the images as they were being loaded into the environment map, there was no need to do anything particularly funky with the setImages() call. If you wish to make sure you've got the right image in the right spot, the TextureCubicEnvironmentMap class has a collection of predefined, convenience constants for you to use.

Coordinate Generation

Earlier it was stated that hand-creating texture coordinates is going to be useless for this application. The reason being that each time you move, the texture coordinates must change. The coordinates provide a reflection of the world around you based on your current world position ie where the camera is currently located. Sure you could hand calculate these every frame, but why bother when there is hardware to do that for you that can do it much faster?

Creating automatically generated texture coordinates starts with the TexCoordGeneration class. If you're unsure of all the options it provides, then take a look at the Texturing Options page. For cubic environment maps, you have two viable options to use - MAP_REFLECTIONS or MAP_NORMALS. Which you choose depends on the application, but in general the reflection mapping is the better default of the two to use if you have no idea. Either way, it's only a one line change, so play with both and see which generates the option you prefer.

Despite taking only 2D texture components as source data, cubic environment maps work in 3D space. That means you'll need to set up the generation options for all 3 texture dimensions - S, T and R. To make sure your object doesn't look strange, make sure you set the same option for all 3 dimensions. For example:

TexCoordGeneration coord_gen = new TexCoordGeneration();
coord_gen.setParameter(TexCoordGeneration.TEXTURE_S,
                       TexCoordGeneration.MODE_GENERIC,
                       TexCoordGeneration.MAP_REFLECTIONS,
                       null);

coord_gen.setParameter(TexCoordGeneration.TEXTURE_T,
                       TexCoordGeneration.MODE_GENERIC,
                       TexCoordGeneration.MAP_REFLECTIONS,
                       null);

coord_gen.setParameter(TexCoordGeneration.TEXTURE_R,
                       TexCoordGeneration.MODE_GENERIC,
                       TexCoordGeneration.MAP_REFLECTIONS,
                       null);

Final Assembly and display

Finally, combine these all into the usual TextureUnit instance and apply them to your object of choice.

TextureUnit[] tu = new TextureUnit[1];
tu[0] = new TextureUnit();
tu[0].setTexture(texture);
tu[0].setTexCoordGeneration(coord_gen);

Appearance app = new Appearance();
app.setMaterial(material);
app.setTextureUnits(tu, 1);

Shape3D shape = new Shape3D();
shape.setGeometry(geom);
shape.setAppearance(app);

The result of all of this is the sphere shown in the following diagram (or you can see them for yourself by running the CubeMapDemo example application).