# Visualization Library A lightweight C++ OpenGL middleware for 2D/3D graphics | [Home] [Tutorials] [All Classes] [Grouped Classes]

# OpenGL Texture Mapping Tutorial

"Texturing" is the technique used to apply in various ways an image over the surface of an object. Textures can be used to change or modify the color, the transparency and even the lighting properties of an object. In this tutorial we will go through the implementation of the texturing test ("src/examples/Applets/App_Texturing.hpp") part of the Visualization Library regression test suite. This will give us the chance to see how to use several standard and advanced texturing techniques like 2D texturing, multi-texturing, 3D textures, 1D and 2D texture arrays, sphere-mapping and cubemaps environment mapping. We will also see how to use the texture lod bias to simulate opaque reflections.

The most important classes involved in the texturing process are:

- vl::Texture
- vl::TextureUnit
- vl::TexGen
- vl::TexEnv
- vl::TextureMatrix
- vl::Image

Creating the test class

Our test like all the other tests is a subclass of `BaseDemo` so the only thing we do is to derive from it, implement a set of functions testing the different texturing technique and reimplementing the virtual functions `initEvent()` and `run()` to initialize and animate our test.

    class App_Texturing: public BaseDemo
    {
    public:

Multitexturing

This example will show you how to use multiple textures to enhance the detail of your 3D objects. We will create 2 cubes: the one on the right will use a single texture, the one on the left will use multitexturing to add detail to the base texture. The two cubes will be procedurally animated by the run() virtual function (see the sources).

      void multitexturing()
      {

Create a box 5x5x5 and generate texture coordinates (generates only box->texCoordArray(0)).

Compute the normals for the box.

Share the same texture coordinates for texture unit #1 and #0. This is VERY important as for every texture unit we need a corresponding set of texture coordinates. In our case makeBox() automatically generated the texture coordinates for the texture unit #0 and we can simply 'recycle' them also for texture unit #1.

        if (!GLEW_ARB_multitexture)
        {
          vl::Log::error("Multitexturing not supported.\n");
          return;
        }

        vl::ref<vl::Geometry> box = vlut::makeBox( vl::vec3(0,0,0), 5,5,5, true );
        box->computeNormals();
        box->setTexCoordArray(1, box->texCoordArray(0));

Creates a vl::Transform for each cube so we can procedurally animate them and attaches them to the scene's root transform.

        mCubeRightTransform = new vl::Transform;
        mCubeLeftTransform = new vl::Transform;
        vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->transform()->addChild(mCubeRightTransform.get());
        vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->transform()->addChild(mCubeLeftTransform.get());

First of all we need to load the images that we intend to use with our textures

        vl::ref<vl::Image> img_holebox = vl::loadImage("/images/holebox.tif");
        vl::ref<vl::Image> img_detail  = vl::loadImage("/images/detail.tif");

Once we loaded our images we can create our textures from them. Here we use the `GL_RGBA` texture format, enable mipmapping and disable texture border. Note that even if the vl::Texture will be allocated and configured, the actual OpenGL texture object will not be created yet. Instead the OpenGL texture object will be created automatically during the rendering the first time it is used. Alternatively you can explicitly create it by calling

    tex_holebox->createTexture()

Another way to create a 2D texture would be

    vl::ref<vl::Texture> tex_holebox = new vl::Texture;
    tex_holebox->setupTexture2D(img_holebox.get(), vl::TF_RGBA, mMipmappingOn, false);

Note that upon creation of the OpenGL texture object VL will also automatically generate the mipmaps (if requested) using the hardware acceleration where available or using GLU.

        vl::ref<vl::Texture> tex_holebox = new vl::Texture(img_holebox.get(), vl::TF_RGBA, mMipmappingOn, false);
        vl::ref<vl::Texture> tex_detail  = new vl::Texture(img_detail.get(),  vl::TF_RGBA, mMipmappingOn, false);

Since we requested mipmapping we set the MinFilter to `GL_LINEAR_MIPMAP_LINEAR`, i.e. trilinear filtering. Note that while you can set the MinFilter to any of `GL_NEAREST`, `GL_LINEAR`, `GL_NEAREST_MIPMAP_NEAREST`, `GL_LINEAR_MIPMAP_NEAREST`, `GL_NEAREST_MIPMAP_LINEAR`, `GL_LINEAR_MIPMAP_LINEAR`, you can set the MagFilter only to `GL_NEAREST`, `GL_LINEAR`.

        tex_holebox->getTexParameter()->setMagFilter(vl::TPF_LINEAR);
        tex_holebox->getTexParameter()->setMinFilter(vl::TPF_LINEAR_MIPMAP_LINEAR);
        tex_detail->getTexParameter()->setMagFilter(vl::TPF_LINEAR);
        tex_detail->getTexParameter()->setMinFilter(vl::TPF_LINEAR_MIPMAP_LINEAR);

Here we are going to create 2 vl::Effect objects: cube_right_fx and cube_left_fx with the specific settings for the left and right cubes.

The right cube is going to be textured with one simple texture.

        vl::ref<vl::Light> light = new vl::Light(0);

        vl::ref<vl::Effect> cube_right_fx = new vl::Effect;
        // to ensure the cubes are drawn after the textured quads
        cube_right_fx->setRenderRank(1);
        cube_right_fx->shader()->setRenderState( light.get() );
        cube_right_fx->shader()->enable(vl::EN_LIGHTING);
        cube_right_fx->shader()->enable(vl::EN_DEPTH_TEST);
        cube_right_fx->shader()->enable(vl::EN_BLEND);
        cube_right_fx->shader()->enable(vl::EN_ALPHA_TEST);
        cube_right_fx->shader()->gocAlphaFunc()->set(vl::FU_GEQUAL, 1.0f - 0.02f);
        cube_right_fx->shader()->gocLightModel()->setTwoSide(true);
        cube_right_fx->shader()->gocTextureUnit(0)->setTexture( tex_holebox.get() );

The left cube is going to use multitexturing: base texture + detail texture.
The color of the detail texture will be used to modulate the color coming from lighiting and the base texture.

        vl::ref<vl::Effect> cube_left_fx = new vl::Effect;
        // to ensure the cubes are drawn after the textured quads
        cube_left_fx->setRenderRank(1);
        cube_left_fx->shader()->setRenderState( light.get() );
        cube_left_fx->shader()->enable(vl::EN_LIGHTING);
        cube_left_fx->shader()->enable(vl::EN_DEPTH_TEST);
        cube_left_fx->shader()->enable(vl::EN_BLEND);
        cube_left_fx->shader()->enable(vl::EN_ALPHA_TEST);
        cube_left_fx->shader()->gocAlphaFunc()->set(vl::FU_GEQUAL, 1.0f - 0.02f);
        cube_left_fx->shader()->gocLightModel()->setTwoSide(true);
        cube_left_fx->shader()->gocTextureUnit(0)->setTexture( tex_holebox.get() );
        cube_left_fx->shader()->gocTextureUnit(1)->setTexture( tex_detail.get() );
        cube_left_fx->shader()->gocTexEnv(1)->setMode( vl::TEM_MODULATE );

Add the left and right cube to the scene.

        sceneManager()->tree()->addActor( box.get(), cube_right_fx.get(), mCubeRightTransform.get() );
        sceneManager()->tree()->addActor( box.get(), cube_left_fx.get(),  mCubeLeftTransform.get() );
      }

3D textures

In this paragraph we will use 3D textures to implement a sort of animation on a 2d flat plane.

      void texture3D()
      {

As usual we start by loading our image with the difference that this time it is a 3D image and we also convert its format and type to IT_UNSIGNED_BYTE/IF_LUMINANCE

        vl::ref<vl::Image> img_volume = vl::loadImage("/volume/VLTest.dat");

Now we create a white plane 10x10 units wide, oriented towards the viewer (without the rotation it would be on the x/z plane instead of being on the x/y plane).

Note 3 things:

1. Using vl::Geometry::transform() you can apply any kind of matrix transform to any geometry regardless of it's internal format!
2. We don't need to compute the normals as we will not use lighting here.
3. If all the vertices of vl::Geometry have the same color you can set such color directly using vl::Geometry::setColorArray(const fvec4& col).

        vl::ref<vl::Geometry> quad_3d = vlut::makeGrid( vl::vec3(0,0,0), 10, 10, 2, 2 );
        quad_3d->setColorArray(vlut::white);
        quad_3d->transform( vl::mat4::rotation(90, 1,0,0), false );

Since we want to animate the texture coordinates of our plane we manually allocate a vl::ArrayFVec3 to be used as the texture coordinate array for the texture unit #0. We also disable the vertex buffer objects so that any change made locally to our buffer will be immediately visible. If VBO were enabled we would have had to update the VBOs after the animation, this way we make the things a bit less efficient but easyer to follow. The animation of the texture coordinates is done in the run() method (see the sources).

        mTexCoords_3D = new vl::ArrayFVec3;
        quad_3d->setTexCoordArray(0, mTexCoords_3D.get());
        mTexCoords_3D->resize( 2*2 );
        quad_3d->setVBOEnabled(false);

This is how the animation of the 3D texture coordinates looks like:

    // 5 seconds period: t = 0 -> 1
    float t = sin( (float)vl::Time::timerSeconds()*vlfPI*2.0f/5.0f) * 0.5f + 0.5f;

    mTexCoords_3D->at(0) = vl::fvec3(0, 0, t);
    mTexCoords_3D->at(1) = vl::fvec3(0, 1, t);
    mTexCoords_3D->at(2) = vl::fvec3(1, 0, t);
    mTexCoords_3D->at(3) = vl::fvec3(1, 1, t);

Since the 't' coordinate changes from 0 to 1 the 2d plane will show a series of 2d slices of the 3d texture as if it was a movie!

Now we create a vl::Effect which uses our 3D texture.
In order to use any 3D texture though we have to check that our OpenGL implementation actually supports 3D textures. The GLEW library embedded in Visualization Library lets us do this in a very simple and expressive manner as you can see. We also enable mipmapping as we did in the multi-texturing example. The function that actually creates our 3D texture is setupTexture3D().

        vl::ref<vl::Effect> fx_3d = new vl::Effect;

        if(GLEW_VERSION_1_2||GLEW_EXT_texture3D)
        {
          vl::ref<vl::Texture> texture_3d = new vl::Texture;
          texture_3d->setupTexture3D( img_volume.get(), vl::TF_RGBA, mMipmappingOn, false );
          fx_3d->shader()->gocTextureUnit(0)->setTexture( texture_3d.get() );
          texture_3d->getTexParameter()->setMagFilter(vl::TPF_LINEAR);
          texture_3d->getTexParameter()->setMinFilter(vl::TPF_LINEAR_MIPMAP_LINEAR);
        }
        else
          vl::Log::error("Texture 3D not supported.\n");

At this point we have everything that is needed to create and add an Actor to the scene. We will position the plane on the top left corner <-6,+6,0>. Note that in this case we don't bind `act_3d's` Transform to any parent Transform as we will not animate it during the rendering. Instead we simply set the local matrix and compute manually the world matrix (which is the one used during the rendering).

        vl::Actor* act_3d = sceneManager()->tree()->addActor( quad_3d.get(), fx_3d.get(), new vl::Transform );
        act_3d->transform()->setLocalMatrix( vl::mat4::translation(-6,+6,0) );
        act_3d->transform()->computeWorldMatrix();
      }

2D Texture Arrays

      void texture2DArray()
      {

Using 2D texture arrays (`GL_TEXTURE_2D_ARRAY`) is very similar to using normal 3D textures (`GL_TEXTURE_3D`), but with the following differences:

- the "r" coordinate of a 2D texture array is expressed as an integer and not as normalized floats (as it is for normal `GL_TEXTURE_1D/2D/3D` textures), i.e. 1, 2, 3, 4, 5 etc. instead of 0.02, 0.04, 0.06, 0.08 etc. for example.
- The mipmaps are computed in a different way: if you have a 3D texture whose dimensions are 256x256x256 at level #0, it's mipmap level #1 will be 128x128x128. Instead in 2D texture array each "layer" is kept separate somehow from one another. In fact if the mipmap level #0 of a 2D texture array is 256x256x256, its mipmap level #1 will be 128x128x256. That is, the texture size is half but you still have 256 "layers". For this reason 2D texture arrays occupy more memory that 3D textures.
- 3D textures can have a texture border, while 2D texture arrays are not allowed to have a border. For more information about the border of a texture refer to the OpenGL function glTexImage1D/2D/3D() documentation.
- 1D and 2D texture arrays are not available in the fixed function pipeline. In order to take advantage of them you have to use an OpenGL Shading Language program.

For our demo we will use the 2D texture array in the very same way as we did for the 3D textures, but in this case we will put the textured plane on the top right corner. Note the use of the function setupTexture2DArray().

        vl::ref<vl::Image> img_volume = vl::loadImage("/volume/VLTest.dat");
        m2DArraySize = img_volume->depth(); // save this to be used during the animation

        vl::ref<vl::Geometry> quad_2darray = vlut::makeGrid( vl::vec3(0,0,0), 10, 10, 2, 2 );
        quad_2darray->setColorArray(vlut::white);
        quad_2darray->transform( vl::mat4::rotation(90, 1,0,0), false );

        mTexCoords_2DArray = new vl::ArrayFVec3;
        quad_2darray->setTexCoordArray(0, mTexCoords_2DArray.get());
        mTexCoords_2DArray->resize( 2*2 );
        quad_2darray->setVBOEnabled(false);

        vl::ref<vl::Effect> fx_2darray = new vl::Effect;

        if(GLEW_EXT_texture_array||GLEW_VERSION_3_0)
        {
          vl::ref<vl::Texture> texture_2darray = new vl::Texture;
          texture_2darray->setupTexture2DArray( img_volume.get(), vl::TF_RGBA, mMipmappingOn );
          fx_2darray->shader()->gocTextureUnit(0)->setTexture( texture_2darray.get() );
          texture_2darray->getTexParameter()->setMagFilter(vl::TPF_LINEAR);
          texture_2darray->getTexParameter()->setMinFilter(vl::TPF_LINEAR_MIPMAP_LINEAR);

          // we need an OpenGL Shading Language program that uses 'sampler2DArray()' to access the texture!
          vl::GLSLProgram* glsl = fx_2darray->shader()->gocGLSLProgram();
          glsl->attachShader( new vl::GLSLFragmentShader("/glsl/texture_2d_array.fs") );
        }
        else
          vl::Log::error("Texture 2d array not supported.\n");

        vl::Actor* act_2darray = sceneManager()->tree()->addActor( quad_2darray.get(), fx_2darray.get(), new vl::Transform );
        act_2darray->transform()->setLocalMatrix( vl::mat4::translation(+6,+6,0) );
        act_2darray->transform()->computeWorldMatrix();
      }

The fragment shader 'glsl/texture_2d_array.fs' used in the example looks like this:

    #extension GL_EXT_texture_array: enable
    uniform sampler2DArray sampler0;
    void main(void)
    {
        gl_FragColor = texture2DArray(sampler0, gl_TexCoord[0].xyz );
    }

Since the 2D texture arrays take integer coordinates we will animate them using `'t*m2DArraySize'` instead of simply `'t'` like this:

        mTexCoords_2DArray->at(0) = vl::fvec3(0, 0, t*m2DArraySize);
        mTexCoords_2DArray->at(1) = vl::fvec3(0, 1, t*m2DArraySize);
        mTexCoords_2DArray->at(2) = vl::fvec3(1, 0, t*m2DArraySize);
        mTexCoords_2DArray->at(3) = vl::fvec3(1, 1, t*m2DArraySize);

1D Texture Arrays

For 1D texture arrays count the considerations that we did for 2D texture arrays.

In this example we create again a plane oriented towards the views with the difference that this time instead of being a simple plane with 2*2=4 vertices we create `2*img_holebox->height()` vertices, that is, we cut it in `img_holebox->height()` slices. Each slice will be textured using a 1D texture taken from the 1D texture array. The resulting image will look very similar to a 2D textured quad.

      void texture1DArray()
      {
        vl::ref<vl::Image> img_holebox = vl::loadImage("/images/holebox.tif");
        m1DArraySize = img_holebox->height(); // save this to be used during the animation

        // create a grid with img_holebox->height() slices
        vl::ref<vl::Geometry> quad_1darray = vlut::makeGrid( vl::vec3(0,0,0), 10, 10, 2, img_holebox->height() );
        quad_1darray->setColorArray(vlut::white);
        quad_1darray->transform( vl::mat4::rotation(90, 1,0,0), false );

        mTexCoords_1DArray = new vl::ArrayFVec2;
        quad_1darray->setTexCoordArray(0, mTexCoords_1DArray.get());
        mTexCoords_1DArray->resize( 2 * img_holebox->height() );
        quad_1darray->setVBOEnabled(false);

        vl::ref<vl::Effect> fx_1darray = new vl::Effect;

        if(GLEW_EXT_texture_array||GLEW_VERSION_3_0)
        {
          vl::ref<vl::Texture> texture_1darray = new vl::Texture;
          texture_1darray->setupTexture1DArray( img_holebox.get(), vl::TF_RGBA, mMipmappingOn );
          fx_1darray->shader()->gocTextureUnit(0)->setTexture( texture_1darray.get() );
          texture_1darray->getTexParameter()->setMagFilter(vl::TPF_LINEAR);
          texture_1darray->getTexParameter()->setMinFilter(vl::TPF_LINEAR_MIPMAP_LINEAR);

          // we need an OpenGL Shading Language program that uses 'sampler1DArray()' to access the texture!
          vl::GLSLProgram* glsl = fx_1darray->shader()->gocGLSLProgram();
          glsl->attachShader( new vl::GLSLFragmentShader("/glsl/texture_1d_array.fs") );
        }
        else
          vl::Log::error("Texture 1d array not supported.\n");

        vl::Actor* act_1darray = sceneManager()->tree()->addActor( quad_1darray.get(), fx_1darray.get(), new vl::Transform );
        act_1darray->transform()->setLocalMatrix( vl::mat4::translation(+6,-6,0) );
        act_1darray->transform()->computeWorldMatrix();
      }

The fragment shader 'glsl/texture_1d_array.fs' used in the example looks like this:

    #extension GL_EXT_texture_array: enable
    uniform sampler1DArray sampler0;
    void main(void)
    {
        gl_FragColor = texture1DArray(sampler0, gl_TexCoord[0].xyz );
    }

Inside the run() method we also animate the s/t texture coordinates of the plane in a more sofisticated way so that the slices of the plane become more apparent:

    for(int i=0; i<m1DArraySize; ++i)
    {
      mTexCoords_1DArray->at(i*2+0) = vl::fvec2(0+t*0.02f*(i2?+1.0f:-1.0f), (float)i);
      mTexCoords_1DArray->at(i*2+1) = vl::fvec2(1+t*0.02f*(i2?+1.0f:-1.0f), (float)i);
    }

The important part to be noted here is that here `'i'`, an integer coordinate, is used here instead of a normalized floating point number ranging between 0 and 1, as we would have done for 1D, 2D, 3D and cubemap textures.

Texture Rectangle

A texture rectangle (`GL_TEXTURE_RECTANGLE`) is a special kind of 2D textures often used for post processing effects. They differ from normal 2D texture for the following:

- Texture rectangle do not support mipmapping.
- Texture rectangle do not support any texture border.
- Texture rectangle s/t coordinates are expressed as integers.
- Texture rectangle supports only the following clamping modes: `GL_CLAMP`, `GL_CLAMP_TO_EDGE`, `GL_CLAMP_TO_BORDER`.

      void textureRectangle()
      {

First we load our image in the usual way

        vl::ref<vl::Image> img_holebox = vl::loadImage("/images/holebox.tif");

Now we create our plane to be textured as we did in the above examples with one major difference, we ask makeGrid() to generate for us 2d texture coordinates ranging from <0,0> to <`img_holebox->width()`,`img_holebox->height()>`.

        // generate non-normalized uv coordinates, i.e. from <0,0> to <img_holebox->width(),img_holebox->height()>
        vl::ref<vl::Geometry> quad_rect = vlut::makeGrid( vl::vec3(0,0,0), 10.0f, 10.0f, 2, 2, true, vl::fvec2(0,0), vl::fvec2((float)img_holebox->width(),(float)img_holebox->height()) );
        quad_rect->setColorArray(vlut::white);
        quad_rect->transform( vl::mat4::rotation(90, 1,0,0), false );

Now we create our texture rectangle using the function setupTextureRectangle(). Note that we disable mipmapping and set the clamping mode to `vl::TPW_CLAMP` (GL_CLAMP).

        vl::ref<vl::Effect> fx_rect = new vl::Effect;

        if(GLEW_ARB_texture_rectangle||GLEW_EXT_texture_rectangle||GLEW_NV_texture_rectangle/*TODO:||GLEW_VERSION_3_1*/)
        {
          vl::ref<vl::Texture> texture_rect = new vl::Texture;
          texture_rect->setupTextureRectangle( img_holebox.get(), vl::TF_RGBA );
          fx_rect->shader()->gocTextureUnit(0)->setTexture( texture_rect.get() );
          // mipmaps not allowed with texture rectangle!
          texture_rect->getTexParameter()->setMagFilter(vl::TPF_LINEAR);
          texture_rect->getTexParameter()->setMinFilter(vl::TPF_LINEAR);
          // GL_REPEAT (the default) not allowed with texture rectangle!
          texture_rect->getTexParameter()->setWrapS(vl::TPW_CLAMP);
          texture_rect->getTexParameter()->setWrapT(vl::TPW_CLAMP);
          texture_rect->getTexParameter()->setWrapR(vl::TPW_CLAMP);
        }
        else
          vl::Log::error("Texture rectangle not supported.\n");

        vl::Actor* act_rect = sceneManager()->tree()->addActor( quad_rect.get(), fx_rect.get(), new vl::Transform );
        act_rect->transform()->setLocalMatrix( vl::mat4::translation(-6,-6,0) );
        act_rect->transform()->computeWorldMatrix();
      }

Spherical mapping

Spherical mapping is a very simple and cheap way to simulate environmental reflection over an object using simple 2D textures.

Spherical mapping is very simple to use all we need is:

- A spherical map texture
- A 3d object with normals
- Appropriate texture coordinates dynamically generated using glTexGen()/GL_SPHERE_MAP

For more information about spherical mapping see also:

- OpenGL Cube Map Texturing, http://developer.nvidia.com/object/cube_map_ogl_tutorial.html

In our test we will apply spherical mapping to a rotating torus.

      void sphericalMapping()
      {

First of all we load the 2d texture containing our spherical map.

        vl::ref<vl::Image> img_spheric = vl::loadImage("/images/spheremap_klimt.jpg");

Here we create the torus to which we will apply the spherical map. Note that in order to use glTexGen()/GL_SPHERE_MAP our model need to have normals.

        vl::ref<vl::Geometry> torus = vlut::makeTorus(vl::vec3(), 8,3, 40,40);
        // we need normals in order for GL_SPHERE_MAP to work correctly!
        torus->computeNormals();

The Effect applied to the torus will have depht testing, back face culling and lighting. Note that since no transform is specified for the light this means that the light will follow the camera (ie. a headlight).

        mFXSpheric = new vl::Effect;
        mFXSpheric->shader()->enable(vl::EN_DEPTH_TEST);
        mFXSpheric->shader()->enable(vl::EN_CULL_FACE);
        mFXSpheric->shader()->enable(vl::EN_LIGHTING);
        mFXSpheric->shader()->setRenderState( new vl::Light(0) );
        // to ensure the torus is drawn after the textured quads
        mFXSpheric->setRenderRank(1);

Now we apply the texture to the effect used by the torus and enable sphere-map automatic texture coordinate generation.

        vl::ref<vl::Texture> texture_spheric = new vl::Texture;
        texture_spheric->setupTexture2D( img_spheric.get(), vl::TF_RGBA, mMipmappingOn, false );
        mFXSpheric->shader()->gocTextureUnit(0)->setTexture( texture_spheric.get() );
        texture_spheric->getTexParameter()->setAnisotropy(16.0);
        texture_spheric->getTexParameter()->setMagFilter(vl::TPF_LINEAR);
        texture_spheric->getTexParameter()->setMinFilter(vl::TPF_LINEAR_MIPMAP_LINEAR);
        // enable automatic texture generation for s,t
        mFXSpheric->shader()->gocTexGen(0)->setGenModeS(vl::TGM_SPHERE_MAP);
        mFXSpheric->shader()->gocTexGen(0)->setGenModeT(vl::TGM_SPHERE_MAP);

Finally we add the torus to the scene and its transform to the scene's root transform, so that it gets updated every frame.

        mActSpheric = sceneManager()->tree()->addActor( torus.get(), mFXSpheric.get(), new vl::Transform );
        vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->transform()->addChild( mActSpheric->transform() );
      }

Cubemaps

Cubemapping is a very flexible technique used to achieve many different kinds of effects. Here we will use cubmapping to implement the so called "environment mapping" which is a technique that simulates the environmental reflection over an object. While using spherical mapping the reflection always faces the camera (unless you regenerate it on the fly every frame), cubemapping lets you use a single cubemap texture to simulate a much more realistic three-dimensional reflection.

Implementing cubemapping is also very simple with Visualization Library, all you need is:

- A cubemap texture
- A 3d model with normals
- Appropriate texture coordinates dynamically generated using glTexGen()/GL_REFLECTION_MAP

      void cubeMapping()
      {

You can load cubemap images in many different ways. You can assemble them on the fly using the vl::loadAsCubemap() functions or you can load it directly from a DDS file with the vl::loadImage() function.

        // cube mapping, see also http://developer.nvidia.com/object/cube_map_ogl_tutorial.html

        vl::ref<vl::Image> img_cubemap = vl::loadCubemap(
          "/images/cubemap/cubemap00.png", // (x+) right
          "/images/cubemap/cubemap01.png", // (x-) left
          "/images/cubemap/cubemap02.png", // (y+) top
          "/images/cubemap/cubemap03.png", // (y-) bottom
          "/images/cubemap/cubemap04.png", // (z+) back
          "/images/cubemap/cubemap05.png");// (z-) front

Create a torus and compute its normals.

        vl::ref<vl::Geometry> torus = vlut::makeTorus( vl::vec3(), 8,3, 40,40 );
        // we need normals in order for GL_REFLECTION_MAP to work correctly!
        torus->computeNormals();

Setup a simple Effect like we did for the sphere mapping example.

        mFXCubic = new vl::Effect;
        mFXCubic->shader()->enable(vl::EN_DEPTH_TEST);
        mFXCubic->shader()->enable(vl::EN_CULL_FACE);
        mFXCubic->shader()->enable(vl::EN_LIGHTING);
        mFXCubic->shader()->setRenderState( new vl::Light(0) );
        // to ensure the torus is drawn after the textured quads
        mFXCubic->setRenderRank(1);

Here we create the cubemap texture. Note that in order to use cubmaps we need to check that either the GL_ARB_texture_cube_map extension or OpenGL 1.3 is supported. Note that we use the GL_CLAMP_TO_EDGE mode here to minimize the seams of the cubemaps. This does not remove the seams totally. In order to have a cubemap without seams the cubemap must be properly generated and adjusted by the texture artist. Note that we use GL_REFLECTION_MAP texture generation mode for the S, T and R texture coordinates. Remember that this texture generation mode works properly only when the model has the normals, as we already discussed. Note also the line:

    mFXCubic->shader()->gocTextureMatrix(0)->setUseCameraRotationInverse(true);

This tells VL to put in the texture matrix the inverse of the camera rotation. This puts into world-space the cubemap texture coordinates automatically generated by OpenGL, which would otherwise be in eye-space (ie. always facing the camera).

        if (GLEW_VERSION_1_3||GLEW_ARB_texture_cube_map)
        {
          vl::ref<vl::Texture> texture_cubic = new vl::Texture;
          texture_cubic->setupTextureCubemap( img_cubemap.get(), vl::TF_RGBA, mMipmappingOn, false );
          mFXCubic->shader()->gocTextureUnit(0)->setTexture( texture_cubic.get() );
          texture_cubic->getTexParameter()->setAnisotropy(16.0);
          texture_cubic->getTexParameter()->setMagFilter(vl::TPF_LINEAR);
          texture_cubic->getTexParameter()->setMinFilter(vl::TPF_LINEAR_MIPMAP_LINEAR);
          texture_cubic->getTexParameter()->setWrapS(vl::TPW_CLAMP_TO_EDGE);
          texture_cubic->getTexParameter()->setWrapT(vl::TPW_CLAMP_TO_EDGE);
          texture_cubic->getTexParameter()->setWrapR(vl::TPW_CLAMP_TO_EDGE);
          // enable automatic texture generation for s,t,r
          mFXCubic->shader()->gocTexGen(0)->setGenModeS(vl::TGM_REFLECTION_MAP);
          mFXCubic->shader()->gocTexGen(0)->setGenModeT(vl::TGM_REFLECTION_MAP);
          mFXCubic->shader()->gocTexGen(0)->setGenModeR(vl::TGM_REFLECTION_MAP);
          // texture matrix
          mFXCubic->shader()->gocTextureMatrix(0)->setUseCameraRotationInverse(true);
        }
        else
          vl::Log::error("Texture cubemap not supported.\n");

Finally we add the torus to the scene and its transform to the scene's root transform, so that it gets updated every frame.

        mActCubic = sceneManager()->tree()->addActor( torus.get(), mFXCubic.get(), new vl::Transform );
        vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->transform()->addChild( mActCubic->transform() );
      }

Animation

The animation of the texture coordinates and of the transformed objects is implemented in the run() virtual function as shown below:

      void run()
      {
        // rotating cubes

        if (GLEW_ARB_multitexture)
        {
          mCubeRightTransform->setLocalMatrix( vl::mat4::translation(+6,0,0) * vl::mat4::rotation( vl::Time::currentTime()*45, 0, 1, 0) );
          mCubeLeftTransform ->setLocalMatrix( vl::mat4::translation(-6,0,0) * vl::mat4::rotation( vl::Time::currentTime()*45, 0, 1, 0) );
        }

        // 5 seconds period
        float t = sin( (float)vl::Time::currentTime()*vl::fPi*2.0f/5.0f) * 0.5f + 0.5f;
        t = t * (1.0f - 0.02f*2) + 0.02f;

        // 3d texture coordinates animation

        mTexCoords_3D->at(0) = vl::fvec3(0, 0, t);
        mTexCoords_3D->at(1) = vl::fvec3(0, 1, t);
        mTexCoords_3D->at(2) = vl::fvec3(1, 0, t);
        mTexCoords_3D->at(3) = vl::fvec3(1, 1, t);

        // 2d texture array coordinates animation

        mTexCoords_2DArray->at(0) = vl::fvec3(0, 0, t*m2DArraySize);
        mTexCoords_2DArray->at(1) = vl::fvec3(0, 1, t*m2DArraySize);
        mTexCoords_2DArray->at(2) = vl::fvec3(1, 0, t*m2DArraySize);
        mTexCoords_2DArray->at(3) = vl::fvec3(1, 1, t*m2DArraySize);

        // 1d texture array coordinates animation

        for(int i=0; i<m1DArraySize; ++i)
        {
          mTexCoords_1DArray->at(i*2+0) = vl::fvec2(0+t*0.02f*(i%2?+1.0f:-1.0f), (float)i);
          mTexCoords_1DArray->at(i*2+1) = vl::fvec2(1+t*0.02f*(i%2?+1.0f:-1.0f), (float)i);
        }

        // spherical mapped torus rotation

        mActSpheric->transform()->setLocalMatrix( vl::mat4::translation(0,+6,0)*vl::mat4::rotation(45*vl::Time::currentTime(),1,0,0) );
        mActSpheric->transform()->computeWorldMatrix();

        // cube mapped torus rotation

        mActCubic->transform()->setLocalMatrix( vl::mat4::translation(0,-6,0)*vl::mat4::rotation(45*vl::Time::currentTime(),1,0,0) );
        mActCubic->transform()->computeWorldMatrix();
      }

Reflectivity

A classic method to simulate sharp/dull reflectivity is to manually change the lod bias via the glTexEnv() command. The Lod Bias modifies the way OpenGL selects the set of mipmaps to be used during the rendering. A higher lod bias will make OpenGL select mipmaps of a higher level (smaller images) thus the reflected image will look more blurry and less sharp. This will produce an effect similar to a rough and opaque surface. Instead, if the lod bias is set to 0 (default) the reflection will look very sharp and definite as if the surface was a perfectly polished mirror. In our test we can dynamically adjust the lod bias using the mouse wheel:

      virtual void mouseWheelEvent(int w)
      {
        mLodBias += w*0.3f;
        mLodBias = vl::clamp(mLodBias, 0.0f, 4.0f);

        mFXSpheric->shader()->gocTexEnv(0)->setLodBias(mLodBias);
        mFXCubic->shader()->gocTexEnv(0)->setLodBias(mLodBias);
      }

Test initialization

The following function shows the simple steps used to initialize our test and the protected data used by our applet.

      virtual void initEvent()
      {
        BaseDemo::initEvent();

        trackball()->setTransform(vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->transform());

        mMipmappingOn = true;
        mLodBias = 0.0;

        multitexturing();
        textureRectangle();
        texture3D();
        texture2DArray();
        texture1DArray();
        sphericalMapping();
        cubeMapping();
      }

    protected:
      vl::ref<vl::Transform> mCubeRightTransform;
      vl::ref<vl::Transform> mCubeLeftTransform;
      vl::ref<vl::ArrayFVec3> mTexCoords_3D;
      vl::ref<vl::ArrayFVec3> mTexCoords_2DArray;
      vl::ref<vl::ArrayFVec2> mTexCoords_1DArray;
      int m1DArraySize;
      int m2DArraySize;
      vl::Actor* mActSpheric;
      vl::Actor* mActCubic;
      vl::ref<vl::Effect> mFXSpheric;
      vl::ref<vl::Effect> mFXCubic;
      float mLodBias;
      bool mMipmappingOn;
    };

Conclusions

This tutorial gave you the basic knowledge to start using several standard and advanced texturing techniques like 2D texturing, multi-texturing, 3D textures, 1D and 2D texture arrays, sphere-mapping, cubemap environment mapping and lod bias manipulation. For all the details and secrets of OpenGL texturing the definitive guide remains the OpenGL Programmer's Guide.

Visualization Library v2009.08 Reference Documentation
Copyright 2005-2009 Michele Bosi. All rights reserved.
Updated on Sun Nov 29 11:11:42 2009.
Permission is granted to use this page to write and publish articles regarding Visualization Library.