My favorites | English | Sign in

Textures

Introduction

This chapter describes how to create a texture sampler and apply it to a shape. In its simplest form, a texture is an image (a 2D array of pixel information) that is applied to the surface of a 3D shape. The image can be in TGA, JPEG, PNG, or DDS format. Using a texture sampler, you set a number of states to describe how to apply the texture to the shape.

The Hello, Cube Textures sample creates a cube with a texture applied to each face. The user can specify the URL of the texture to use. (To view the code for Hello, Cube Textures in a new window, click here).

About Textures

O3D uses the letter u for the horizontal texture coordinate and v for the vertical texture coordinate. Texture coordinates (u, v) range from (0.0, 0.0) in the lower left of the image to (1.0, 1.0) in the upper right, as shown here:

In O3D, a texture can also be a 3D cube. A cube texture stores images for the six faces of a cube and is addressed by three texture coordinates (u, v, and w). The effect of applying a cube texture to a shape is roughly analogous to shrink-wrapping the texture onto a shape that is enclosed by the cube.

Texture Samplers

A texture sampler encapsulates a reference to a texture object and a set of states that define how the texture bitmap is applied to a surface. Sampler states can be set using parameters defined on a Sampler object or specified directly using one of the convenience methods supplied by the Sampler class. The Hello, Cube Textures example sets these values using convenience methods. The Sampler class defines the following attributes (default values are in parentheses):

  • addressModeU(WRAP)
  • addressModeV (WRAP)
  • minFilter (LINEAR)
  • magFilter (LINEAR)
  • mipFilter (POINT)
  • borderColor (Float4(0,0,0,0)
  • maxAnisotropy (1)

Address Mode

The addressModeU and addressModeV attributes specify what happens when the texture coordinates fall outside of the 0.0 to 1.0 range defined for the texture. For example, in Texture Samplers, shown below on this page, the texture coordinates range from 0.0 to 2.0 in both u and v. A different address mode can be specified for the u and v directions. The choices are as follows:

Value Meaning
WRAP Repeat the texture to fill in the area (default)
MIRROR Repeat the texture, inverting it when it crosses a uv boundary (0.0 or 1.0 in either direction)
CLAMP The last row of pixels in the texture is repeated to cover the rest of the face
BORDER A border color is used for the pixels that fall outside the range of the texture coordinates specified

Minification Filter

The minification filter (minFilter) specifies how to apply the texture if the area to be textured has fewer pixels than the texture (that is, the texture needs to be shrunk to fill the area). Possible filtering types for minFilter are as follows:

Value Meaning
POINT Use the closest pixel.
LINEAR Perform a linear interpolation between neighboring pixels and use the result.
ANISOTROPIC Stretch the texture according to its orientation in screen space (may be more in one direction than in the other). See Anisotropy, below.

If the minFilter is set to POINT or LINEAR, a mipmap filter (mipFilter) can be used to control the interpolation of texture values. If the mipFilter is set to NONE, it is ignored. See Mipmap Filter.

Mipmap Filter

Creating minified versions of textures is a more difficult process than creating magnified versions of textures. More care needs to be taken about which pixels are cut from the image to prevent aliasing (jagged diagonal boundaries between color areas). Mipmapping refers to the process of creating a set of reduced textures to use in place of the original full-size texture. The usual technique is to start with the original texture, then create another texture half its size, and continue this process until the reduced texture is only one pixel in size. When a minification filter is required for the texture, the mipmap level is selected according to the value for the mipFilter as follows:

Value of mipFilter Which Mipmap Is Used
NONE Don't use a mipmap at all.
POINT Use the mipmap level that is closest to the size of the triangle on the screen. Within that level, filter the pixels based on the minification filter (minFilter).
LINEAR Start with the two mipmap levels that are closest in size to the triangle on the screen. Filter each level with the minFilter. Then perform a linear interpolation between these two levels to produce the final color values.

Magnification Filter

The magnification filter (magFilter) specifies how to apply the texture if the area to be textured contains more pixels than the texture (that is, the texture needs to be stretched to fill the area). This magnification can result in a blurred image. Possible filtering types for magFilter are as follows:

Value Meaning
POINT Use the closest pixel.
LINEAR Perform a linear interpolation between neighboring pixels and use the result.

Border Color

When the address mode is set to BORDER, the borderColor is used for any pixels that fall outside of the 0.0 to 1.0 range defined for the texture coordinates. The top right texture in the Texture Samplers example, shown below, illustrates using a border color of red.

Anisotropy

When the value of minFilter is ANISOTROPIC, a maxAnisotropy value specifies the degree of anisotropy. Anisotropic filtering takes into account the distance of the texture pixels from the viewer as well as the angle at which the texture is being viewed.

Examples

The following figure shows the output of the Texture Samplers example. The figure is followed by a table that lists the corresponding values set for each nondefault texture sampler variable.

Top Left Top Middle Top Right
  • addressModeU = Sampler.WRAP
  • addressModeV = Sampler.WRAP
  • minFilter = Sampler.LINEAR
  • magFilter = Sampler.LINEAR
  • mipFilter = Sampler.POINT
  • borderColor = Float4(0, 0, 0, 0)
  • maxAnisotropy = 1
  • minFilter = Sampler.ANISOTROPIC
  • maxAnisotopy = 4
  • addressModeU = Sampler.BORDER
  • addressModeV = Sampler.BORDER
  • borderColor = Float4(1, 0, 0, 1)
Bottom Left Bottom Middle Bottom Right
  • minFilter = Sampler.POINT

Uses default (LINEAR) filtering; compare to texture above

  • addressModeU = Sampler.MIRROR
  • addressModeV = Sampler.MIRROR

A Detailed Look at "Hello, Cube Textures"

The Hello, Cube Textures program builds on the Hello, Cube Materials program by specifying a texture sampler for the cube's material. The new tasks related to textures and texture samplers are as follows:

  1. Define the texture coordinates.
  2. Create the texture sampler and set its states.
  3. Obtain the texture from the specified URL.
  4. Create the shaders to access the texture.

The following sections describe these tasks in more detail.

Step 1: Defining the Texture Coordinates

Here are the steps and code for specifying the texture coordinates, adding them to an array, and adding the array to a buffer. The setVertexStream() function specifies how to read the buffer and sets the buffer to a vertex stream.

  • Specify the (u,v) texture coordinates for each vertex and add them to an array (texCoordsArray):
  • var texCoordsArray = [
        0, 0,
        1, 0,
        1, 1,
        0, 1,
        0, 0,
        1, 0,
        1, 1,
        0, 1,
        1, 1,
        0, 1,
        0, 0,
        1, 0,
        0, 0,
        1, 0,
        1, 1,
        0, 1,
        0, 0,
        1, 0,
        1, 1,
        0, 1,
        0, 0,
        1, 0,
        1, 1,
        0, 1
      ];
  • Create a vertex buffer to hold the texture coordinates (texCoordsBuffer). Set the texture coordinates array to this buffer:
  • var texCoordsBuffer = g_pack.createObject('VertexBuffer');
    var texCoordsField = texCoordsBuffer.createField('FloatField', 2);
    texCoordsBuffer.set(texCoordsArray);
  • Call setVertexStream() to associate the texture coordinates buffer with the primitive's stream bank. This function call sets the semantic for the texture coordinates vertex stream (TEXCOORD) as well as the other information about how to read the data in the stream:
  • streamBank.setVertexStream(
       g_o3d.Stream.TEXCOORD,  // semantic
       0,                      // semantic index
       texCoordsField,         // field
       0);                     // start_index

Note that the way this program specifies the vertices for the cube differs from how they are specified in the Hello, Cube Materials program. Two values are specified for each vertex in the triangles that make up the cube: a position and a texture coordinate. The vertices between adjacent cube faces cannot be shared because, although their positions are the same, their texture coordinates are different. This example therefore creates 24 vertices in the positionArray, 4 for each of the cube's 6 faces. Then the coordinates in the texCoordsArray are matched, one for one, to the vertices in the positionArray.

Step 2: Creating the Texture Sampler

Here are the steps and code for creating the texture sampler and setting its state.

  • Call Effect.createUniformParams() to create a material parameter for each of the effect's uniform parameters (that is, parameters that apply to the primitive as a whole). In this example, there is one uniform parameter, the texture sampler:
  • cubeEffect.createUniformParameters(cubeMaterial);
    

    Alternatively, you could create the uniform parameters required by the shader using explicit function calls.

  • Get the material's sampler parameter:
  • var samplerParam = cubeMaterial.getParam('texSampler0');
    
  • Create the texture sampler and set its states. The sampler's minification filter (minFilter) is set to ANISOTROPIC. This value improves the quality of the texture when it is viewed at an angle:
  • g_sampler = g_pack.createObject('Sampler');
    g_sampler.minFilter = g_o3d.Sampler.ANISOTROPIC;
    g_sampler.maxAnisotropy = 4;
    
  • Assign this texture sampler to the material's sampler parameter:
  • samplerParam.value = g_sampler;
    

Step 3: Obtaining the Texture

Here are the steps and code for initializing the value of the texture's URL and changing this value when the user specifies a new texture:

  • Initialize the texture URL (at the beginning of the initStep2() function).
  • var path = window.location.href;
    var index = path.lastIndexOf('/');
    path = path.substring(0, index+1) + 'assets/texture_b3.jpg';
    var url = document.getElementById("url").value = path;
    
  • Whenever the user specifies a new texture, the changeTexture() function fetches a new texture file.
  • function changeTexture() {
      var textureUrl = document.getElementById('url').value;
      try {
        o3djs.io.loadTexture(g_pack, textureUrl, function(texture) {
          // Remove the currently used texture from the pack so that when it's not
          // referenced anymore, it can get destroyed.
          if (g_sampler.texture)
            g_pack.removeObject(g_sampler.texture);
     
          // Set the texture on the sampler object to the newly created texture
          // object returned by the request.
          g_sampler.texture = texture;
    
          // We can now safely add the cube transform to the root of the
          // transform graph since it now has a valid texture.  If the transform
          // is already parented under the root, the call will have no effect.
          g_cubeTransform.parent = g_client.root;
          .
          .
          .//Error handling calls here.
    }
    

    This function uses the utility util.loadTexture() to load the texture from the specified URL. The utility, in turn, calls the createFileRequest() function. By default, createFileRequest() creates a set of mipmaps when it loads a texture. If you know that you will not use mipmapping, you can make explicit calls to load the texture file and specify FALSE for the generateMipmaps attribute of the FileRequest.

  • In the <body> of the HTML page, add the user input box and button that allows the user to specify the URL for a new texture:
  • <body>
    <h1>Hello Cube: Textures</h1>
    This example shows how to texture map a cube using an image fetched from a URL.
    <br/>
    <!-- Start of O3D plugin -->
    <div id="o3d" style="width: 600px; height: 600px;"></div>
    <!-- End of O3D plugin -->
    <br />
    Image URL: <input type="text" id="url" size="100">
    <input type="button" onclick="changeTexture();" value="Update Texture"><BR>
    

Step 4: Creating the Shaders

In this example, the <textarea> element contains the code for the vertex and pixel shaders. The input attributes for the vertex shader are position, which has a semantic of POSITION, and tex, which has a semantic of TEXCOORD0. The vertex shader transforms the POSITION vertices into homogeneous clip space. Here, the positions are contained in the positionsBuffer, and the texture coordinates are contained in the texCoordsBuffer. The vertex shader passes the texture coordinate values (TEXCOORD0) through unchanged (output.tex = input.tex;). The pixel shader then looks up each pixel in the primitive and returns the color of the corresponding bit in the texture map. Here is the code for the vertex and pixel shaders:

<textarea id="effect">
  // World View Projection matrix that will transform the input vertices
  // to screen space.
  float4x4 worldViewProjection : WorldViewProjection;

  // The texture sampler is used to access the texture bitmap in the fragment
  // shader.
  sampler texSampler0;

  // input for our vertex shader
  struct VertexShaderInput {
    float4 position : POSITION;
    float2 tex : TEXCOORD0;  // Texture coordinates
  };

  // input for our pixel shader
  struct PixelShaderInput {
    float4 position : POSITION;
    float2 tex : TEXCOORD0;  // Texture coordinates
  };

  /**
   * The vertex shader simply transforms the input vertices to screen space.
   */
  PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
    PixelShaderInput output;

    // Multiply the vertex positions by the worldViewProjection matrix to
    // transform them to screen space.
    output.position = mul(input.position, worldViewProjection);

    output.tex = input.tex;
    return output;
  }

 /**
  * Given the texture coordinates, our pixel shader grabs the corresponding
  * color from the texture.
  */
  float4 pixelShaderFunction(PixelShaderInput input): COLOR {
    return tex2D(texSampler0, input.tex);
  }

  // Here we tell our effect file *which* functions are
  // our vertex and pixel shaders.

  // #o3d VertexShaderEntryPoint vertexShaderFunction
  // #o3d PixelShaderEntryPoint pixelShaderFunction
  // #o3d MatrixLoadOrder RowMajor
</textarea>
<!-- End of effect -->

Next: Shaders

The O3D Shading Language section describes how to create vertex and pixel shaders.

Back to top