Part B - Direct3D

Texturing

Add fine detail to graphics primitives
Describe the filtering methods for texture sampling

Sample | Framework | Design | Coordinator | Object | Graphic | Texture | Exercises

The graphics primitive display paradigm provides a method for simplifying the drawing of objects in a scene.  The rasterizer on the graphics hardware interpolates the colour at any point within a primitive from the colours at the primitive's vertices.  Because this interpolation is linear and vertex based, fine-detail and abrupt variations within the primitive are lost.  Depicting fine detail and abrupt changes using primitives alone would require breaking up the primitive into a large number of sub-primitives and defining colours at each of their vertices. 

Rather than increasing the primitive count for an object, Edwin Catmull proposed a novel approach in his 1974 Ph.D. thesis.  He mapped the colour variations across the object directly onto each of its primitives.  This mapping technique came to be known as texturing.  In texturing, the hardware samples a point on an image called a texture and maps that point's colour to the corresponding point on the primitive.  Since the relation between points on the texture and pixels on the screen is very seldom one to one, some filtering is required is required in this mapping.  For efficient sampling and filtering, we store the texture on video memory and the graphics hardware performs the filtering itself. 

This chapter describes the elements of texturing and incorporates textures and texture filtering into the framework.  The description includes the mapping from a single texture point to many pixels as well as many texture points to a single pixel.  Because textures are stored on video memory, housekkeping around suspension and restoration of focus is required.  This description extends the introductory discussion of texturing in the chapter on Background Images.


Texturing Sample

The Texturing Sample introduces three different textures: a daisy patterned texture, an opaque checkered texture, and a trnaslucent checkered texture.  The sample also introduces an elongated red bar below the world z axis.

texturing sample

The three files are stored in the resources directory ..\..\resources\textures:

  • the daisy patterned texture is stored in file daisy.bmp
  • the opaque checkered texture is stored in file check.bmp
  • the translucent checkered texture is stored in file check.tga

The opaque checkered texture is mapped the the rolling box on the left and the rolling box in the center.  The opaque checkered texture maps to the elongated bar.  The translucent checkered texture maps to the spinning box on the right.  Note that primitives built from lit vertices support texturing as well as primitives built from unlit vertices. 

This sample also demonstrates the effect of using different filtering algorithms.  Pressing the 'Texture Point' key activates the simplest form of filtering; pressing the "Texture Linear' key activiates a more elaborate filtering. 


Framework

Five components involve upgrades to accommodate texturing:

  • Design - the Design object creates the textures and attaches them to the different Objects in the scene.
  • Coordinator - manages all of the textures created by the Design object
  • Object - the Object class holds the filtering data for any attached texture.
  • Graphic - the Vertex class holds the texture mapping coordinates associated with a vertex.
  • Texture - the Texture class attaches a texture to the drawing pipeline and set the filtering states.

Texture Component

Topics

The topics that arise with simple texturing include:

  1. the coordinate system for identifying texture points
  2. mapping the texture to a graphics primitive
  3. filtering to compensate for lack of one to one correspondence between texture points and primitive points
  4. implementing texturing in Direct3D
  5. efficiency considerations

Texture Coordinates

The simplest textures are square and two-dimensional as shown on the left in the figure below.  The hardware stretches them onto their target primitives.  The coordinate system for identifying a point on a texture has its origin in the top-left corner, a horizontal rightward axis (u) and a vertical downward axis (v).  We call each point a texture pixel or simply a texel.  Texel coordinates are typically normalized so that they range from [0.0 0.0] to [1.0 1.0].  The coordinate-value pair [u v] uniquely identifies a texel.  Each texel has its own colour value. 

To map a texture onto a primitive, we associate a specific texel with a vertex of the primitive as shown in the figure on the right.  These vertex-wise associations provide the basis for interpolations across the primitive. 

Sampling

The graphics hardware determines the colour of each pixel within each primitive by sampling the colour of the corresponding texel.  The sampler determines the texel - this is, its coordinates - by simple linear interpolation. 

textures

The clarity of the sampling depends upon the ratio of the number of texels to the number of pixels within the primitive.  Sampling is clearest in the ideal case where each texel maps to a single pixel.  If the mapping is one to one but the texels and pixels do not align perfectly, corrections are necessary.  Typically however, one texel maps to several pixels or several texels map to a single pixel. 

Because the ratio of texels to pixels is very seldom one to one, determining the colour of a pixel is an approximation and can easily produce artifacts.  There are two distinct cases to consider:

  1. magnification - less texels map to more pixels
  2. minification - more texels map to less pixels

Magnification occurs with objects in the foreground.  These objects occupy significant screen space so that one texel stretches over several pixels.

magnification

Minification occurs with objects in the background.  These objects occupy very little screen space so that many texels map to a single pixel. 

minification

Filtering

Filtering controls the sampling techniques during magnification and minification.  These techniques determine the colour of each pixel from a combination of colours of neighbouring texels.  Combining the colours of neighbours improves the result by removing artifacts such as blockiness, aliasing or shimmering. 

The three common filtering techniques are:

  • nearest-neighbour - uses the colour of the texel closest to a pixel - may produce blockiness during magnification and aliasing and shimmering during minification
  • bi-linear - uses the average of the four nearest texels in determining the colour of a pixel - eliminates blockiness during magnification
  • anisotropic - uses the highest quality of filtering available - removes blurriness in receding floors

The figure on the left shows the results of nearest-point filtering for magnification and bilinear filtering for minification.  The texturing in the distance is smoother.  The figure on the right shows the results of nearest-point filtering for minification and bilinear filtering for magnification.  The texturing in the foreground is smoother.

point linear filtering linear point

The figure below shows the results of anisotropic filtering for both magnification and minification. 

anisotropic filtering

Direct3D Implementation

Direct3D implements texturing through the Direct3DTexture COM object.  Because this object stores textures on video memory, interfaces to it need to be released and retrieved across changes in focus. 

Direct3D attaches a texture to a vertex stream by the SetTexture(int, ) method on the display device.  This method receives the sampling stage in its first parameter and a pointer to the texture interface in its second parameter.  Passing nullptr detaches the texture from the vertex stream. 

Direct3D sets the type of texture filtering for magnification and minification separately by calling the SetSamplerState(int, , ) method on the display device.  This method receives in its first parameter the sampling stage, in its second parameter the filter type (D3DSAMP_MINFILTER or D3DSAMP_MAGFILTER), and in its third parameter the filtering technique (D3DTEXF_POINT, D3DTEXF_LINEAR, or D3DTEXF_ANISOTROPIC).  Direct3D supports three types of filtering:

  1. D3DTEXF_POINT - nearest-neighbour
  2. D3DTEXF_LINEAR - bi-linear
  3. D3DTEXF_ANISOTROPIC - anisotropic

The default filtering is nearest-neighbour.  With anisotropic filtering, Direct3D requires the specification of the degree of anisotropy.  The higher the degree, the higher the quality of filtering and the more computationally intensive the calculations.  The number of degrees available varies with the display device and can be retrieved through the MaxAnisotropy member of the capabilities struct. 

Vertex Declarations

Direct3D accepts the texel coordinates for a vertex with the position coordinates.  Direct3D accepts the ordering of vertex data through an interface to the VertexDeclaration COM object.  The CreateVertexDeclaration() method on the display device accepts the vertex format in its first parameter and returns an interface to this COM object in its second parameter.  We specify the vertex format through an instance of the D3DVERTEXELEMENT9 struct. 

Efficiency Considerations

For optimal processing textures should be stored on video memory.  Modeling textures independently from modeling objects lets us associate the same texture with several objects, which avoids duplication.  This one to many relationship minimizes the amount of memory required for all texture data in a scene. 

Several graphics primitives may use the same parts or different parts of the same texture, so that the mapping may be one texture to many primitives. 

Settings

Translation Layer

The action enumeration constants include ones for nearest-neighbour and bi-linear filtering: 

 // Translation.h
 // ...
 typedef enum Action {
     // ...
     TEXTURE_POINT,
     TEXTURE_LINEAR,
 } Action;

 #define ACTION_DESCRIPTIONS {\
     // ...
     L"Nearest Neighbour Filtering", \
     L"Bilinear Filtering", \
 }

 // initial mappings of actions to keys
 //
 #define ACTION_KEY_MAP {\
     // ...
     KEY_J, KEY_K, KEY_L, KEY_G, KEY_H, \
 }

 // ...
 // Texture filtering flags
 //
 #define TEX_MIN_POINT        1
 #define TEX_MIN_LINEAR       2
 #define TEX_MIN_ANISOTROPIC  4
 #define TEX_MAG_POINT        8
 #define TEX_MAG_LINEAR      16
 #define TEX_MAG_ANISOTROPIC 32
 #define TEX_DEFAULT TEX_MIN_LINEAR | TEX_MAG_LINEAR
 

Design

The Design component creates the elongated box and the textures for the sample and attaches them to their respective objects.  At the user's initiative this component switches from nearest-neighbour to bi-linear filtering or vice versa. 

Initialize

The initialize() method creates the elongated box and the textures and attaches them to respective objects: 

 void Design::initialize() {
     // ...
     iTexture* daisy = CreateTexture(L"daisy.bmp");
     parent->attach(daisy);

     iGraphic* box = CreateBox(-10, -10, -10, 10, 10, 10);
     Reflectivity greenish(green);
     rollLeft = CreateObject(box, &greenish);
     rollLeft->attach(daisy);
     rollLeft->translate(-40, -20, 30);
     objectCamera->attachTo(rollLeft);

     Reflectivity whiteish(Colour(1, 1, 1, 0.5f));
     spinRight = CreateObject(box, &whiteish);
     spinRight->translate(30, -15, 30);
     iTexture* checktga = CreateTexture(L"check.tga");
     spinRight->attach(checktga);

     iGraphic* longPlate = CreateBox(-30, -10, 0, 30, 10, 600);
     Reflectivity bluish(blue);
     iObject* floor = CreateObject(longPlate, &whiteish);
     floor->translate(20, -80, 440);
     iTexture* check = CreateTexture(L"check.bmp");
     floor->attach(check);
     // ...
 }

Update

The update() method responds th user requests to change the texture filtering algorithm: 

 void Design::update() {
     // ...
     // texture filtering -------------------------------------------
     if (pressed(TEXTURE_POINT) && rollLeft && spinRight) {
         rollLeft->setTextureFilter(TEX_MIN_POINT | TEX_MAG_POINT);
         spinRight->setTextureFilter(TEX_MIN_POINT | TEX_MAG_POINT);
         release(TEXTURE_POINT);
     }
     if (pressed(TEXTURE_LINEAR) && rollLeft && spinRight) {
         rollLeft->setTextureFilter(TEX_MIN_LINEAR | TEX_MAG_LINEAR);
         spinRight->setTextureFilter(TEX_MIN_LINEAR | TEX_MAG_LINEAR);
         release(TEXTURE_LINEAR);
     }
 }

Coordinator

The Coordinator component class creates the various textures in the sample and attaches them to their respective objects.  of the Model branch creates the Texture objects and associates them with the Box objects in the game.  In this sample, all of the creation calls are in the initialize() method. 

Set Configuration

The setConfiguration() method sets the degree of anisotropy to be used: 

 bool Coordinator::setConfiguration() {
     // ...
     if (userInput->getConfiguration()) {
         // ...
         if (window->setup()) {
             // ...
             if (display->setup()) {
                 // ...
                 Texture::setAnisotropy(10);
                 rc = true;
             } else
                 window->release();
         }
     }
     // ...
 }

Release

The release() method releases an action initiated by the user:

 void Coordinator::release(Action a) { userInput->release(a); }

Note that this method is distinct from the no-argument release() method, which releases all of the design elements from the current configuration.


Object

The Object class incorporates texturing capabilities and provides the option of attaching a Texture object.  If one is attached, then the Object class samples that texture during the drawing process.

The iObject interface exposes a new virtual method that attaches a Texture object to the current object:

 // iObject.h

 class iObject : public Frame, public Base {
   public:
     // ...
     virtual void setTextureFilter(unsigned)          = 0;
     // ...
 };
 // ...

The class definition includes an instance variable that holds the filtering flags for the object:

 class Object : public iObject {
     // ...
     unsigned flags; // texture filtering flags
     // ...
   public:
     // ...
     void setTextureFilter(unsigned f) { flags = f; }
     // ...
 };

Implementation

Construct

The constructors initializes the filtering flag to the default defined in the Translation Layer's settings file:

 Object::Object(Category c, iGraphic* v, unsigned char a) :
  category(c), graphic(v), texture(nullptr), alpha(a ? a : TEX_ALPHA),
  flags(TEX_DEFAULT) {
     coordinator->add(this);
 }

 Object::Object(Category c, iGraphic* v, const Reflectivity* r) :
  category(c), graphic(v), flags(TEX_DEFAULT) {
     coordinator->add(this);
     reflectivity = *r;
     texture = nullptr;
 }

Render

The render() method passes the filtering information to the associated Texture object and in the case of lit vertices attaches the Texture object: 

 void Object::render() {
     if (graphic) {
         if (category == SPRITE) {
             Vector pos = position();
             graphic->beginDraw();
             if (texture) {
                 if (flags) texture->setFilter(flags);
                 texture->attach(graphic->width(), graphic->height());
             }
             graphic->render((int)pos.x, (int)pos.y, alpha);
             if (texture) texture->detach();
             graphic->endDraw();
         }
         else if (category == LIT_OBJECT) {
             graphic->setWorld(&world());
             if (texture) {
                 if (flags) texture->setFilter(flags);
                 texture->attach();
             }
             graphic->render();
             if (texture) texture->detach();
         }
         else {
             graphic->setWorld(&world());
             if (texture) {
                 if (flags) texture->setFilter(flags);
                 texture->attach();
             }
             graphic->set(&reflectivity);
             graphic->render();
             if (texture) texture->detach();
         }
     }
 }
 

Graphic

The Graphic component sets the texture mapping coordinates for the graphics primitives.  Where no coordinates are provided the values stored are those of the origin of texture space. 

Add Functions

The add() functions define the texture coordinates associated with each vertex of a primitive face that is made of a pair of triangles that share their hypotenuese. 

 void add(VertexList<Vertex>* vertexList, const Vector& p1,
  const Vector& p2, const Vector& p3, const Vector& p4,
  const Vector& n) {
     vertexList->add(Vertex(p1, n, 0, 1));
     vertexList->add(Vertex(p2, n, 0, 0));
     vertexList->add(Vertex(p3, n, 1, 0));
     vertexList->add(Vertex(p1, n, 0, 1));
     vertexList->add(Vertex(p3, n, 1, 0));
     vertexList->add(Vertex(p4, n, 1, 1));
 }

 void add(VertexList<LitVertex>* vertexList, const Vector& p1,
  const Vector& p2, const Vector& p3, const Vector& p4,
  const Colour& colour) {
     vertexList->add(LitVertex(p1, colour, 0, 1));
     vertexList->add(LitVertex(p2, colour, 0, 0));
     vertexList->add(LitVertex(p3, colour, 1, 0));
     vertexList->add(LitVertex(p1, colour, 0, 1));
     vertexList->add(LitVertex(p3, colour, 1, 0));
     vertexList->add(LitVertex(p4, colour, 1, 1));
 }

LitVertex Class

The class defines two new instance variables that hold the texture coordinates associated with the vertex:

 // APIVertex.h

 class LitVertex {
     // ...
     float tu; // u coordinate of texture
     float tv; // v coordinate of texture
   public:
     LitVertex();
     LitVertex(const Vector&, const Colour&, float = 0, float = 0);
     // ...
 };

Construct

The constructors initialize the texture coordinates which default to 0: 

 LitVertex::LitVertex() : x(0), y(0), z(0), c(0), tu(0),
  tv(0) {}

 LitVertex::LitVertex(const Vector& p, const Colour& x, float ttu,
  float ttv) : x(p.x), y(p.y), z(p.z), c(x), tu(ttu),
  tv(ttv) {}

Populate

The populate() method includes the texture coordinates in the information passed to the vertex buffer: 

 void  LitVertex::populate(void** pv) const {
     float* p = *(float**)pv;
     *p++ = x;
     *p++ = y;
     *p++ = z;
     *((unsigned*)p++) = COLOUR_TO_ARGB(c);
     *p++ = tu;
     *p++ = tv;
     *pv  = p;
 }

Vertex Class

The class defines two new instance variables that hold the texture coordinates associated with the vertex:

 // APIVertex.h

 class Vertex {
     // ...
     float tu; // u coordinate of texture
     float tv; // v coordinate of texture
   public:
     Vertex();
     Vertex(const Vector&, const Vector&, float = 0, float = 0);
     // ...
 };

Construct

The constructors initialize the texture coordinates which default to 0: 

 Vertex::Vertex() : x(0), y(0), z(0), nx(0), ny(0), nz(0), tu(0),
  tv(0) {}

 LitVertex::LitVertex(const Vector& p, const Vector& n, float ttu,
  float ttv) : x(p.x), y(p.y), z(p.z), nx(n.x), ny(n.y), nz(n.z), tu(ttu),
  tv(ttv) {}

Populate

The populate() method includes the texture coordinates in the information passed to the vertex buffer: 

 void  Vertex::populate(void** pv) const {
     float* p = *(float**)pv;
     *p++ = x;
     *p++ = y;
     *p++ = z;
     *p++ = nx;
     *p++ = ny;
     *p++ = nz;
     *p++ = tu;
     *p++ = tv;
     *pv  = p;
 }

D3DFVF_TEX1 specifies one set of texel coordinates.  D3DFVF_TEXCOORDSIZE2(0) specifies the dimensionality of the set of coordinates as two dimensions for sampling stage 0. 

APIVertexDeclaration Template

The template defines the expanded structure of the Vertex and LitVertex classes:

 // APIVertex.h

 template <>
 D3DVERTEXELEMENT9 APIVertexDeclaration<Vertex>::fmt[MAXD3DDECLLENGTH + 1]
  = {
  { 0,  0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
   D3DDECLUSAGE_POSITION, 0},
  { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
   D3DDECLUSAGE_NORMAL, 0},
  { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
   D3DDECLUSAGE_TEXCOORD, 0},
  D3DDECL_END()};

 template <>
 unsigned APIVertexDeclaration<Vertex>::vertexSize = 32;

 template <>
 D3DVERTEXELEMENT9 APIVertexDeclaration<LitVertex>::fmt[MAXD3DDECLLENGTH + 1]
  = {
  { 0,  0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
   D3DDECLUSAGE_POSITION, 0},
  { 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,
   D3DDECLUSAGE_COLOR, 0},
  { 0, 16, D3DDECLTYPE_FLOAT2,
   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
  D3DDECL_END()};

 template <>
 unsigned APIVertexDeclaration<LitVertex>::vertexSize = 24;

These declarations are used in creating the vertex buffer and interpreting the data in the vertex stream.


Texture

The Texture component class provides the Model branch with the functionality for attaching textures independently to the Graphics API for subsequent sampling.  This functionality is used by the Object class during the drawing process.

The iTexture interface exposes a virtual method for setting the filtering algorithm:

 // iTexture.h

 class iTexture : public Base {
     virtual void setFilter(unsigned) const = 0;
     // ...
 };
 // ...

The Texture class defines the new method along with a class method that sets the degree of anisotropy:

 // Texture.h

 class Texture : public iTexture {
     // ...
   public:
     // ...
     static void setAnisotropy(int);
     void setFilter(unsigned) const;
     // ...
 };

Implementation

Set Anisotropy

The setAnisotropy() class method sets the degree of anisotropy to be used in sampling textures by the display device:

 void Texture::setAnisotropy(int d) {
     APITexture::setAnisotropy(d);
 }

Set Filter

The setFilter() method passes the filtering flags to the APITexture object:

 void Texture::setFilter(unsigned flags) const {
     if (apiTexture)
         apiTexture->setFilter(flags);
 }

API Texture Class

The iAPITexture interface exposes a new virtual method for setting the filtering flags: 

 // iAPITexture.h

 class iAPITexture {
     // ...
     virtual void setFilter(unsigned) = 0;
     // ...
 };

The APITexture class defines an instance variable that holds the filtering flags, a method that sets these flags, a private method that sets the filtering on the display device, and a class method that sets the degree of anisotropy to be used by the display device:

 // APITexture.h

 class APITexture : public iAPITexture, public APIBase {
     // ...
     unsigned filter; // sampling filter flags
     // ...
     void setSamplerState() const;
     // ...
   public:
     // ...
     static void setAnisotropy(int);
     void setFilter(unsigned f) { filter = f; }
     // ...
 };

Implementation

Set Anisotropy

The setAnisotropy() method determines the maximum degree of anisotropic filtering available on the display device and sets the degree of anisotropic filtering to match the index (out of 10) received in its parameter:

 void APITexture::setAnisotropy(int d) {
     if (d3dd) {
         D3DCAPS9 caps;
         // maximum number of lights supported by the APIDisplay device
         d3dd->GetDeviceCaps(&caps);
         // set anisotropic filtering on the display device
         if (FAILED(d3dd->SetSamplerState(0, D3DSAMP_MAXANISOTROPY,
          caps.MaxAnisotropy * d / 10 - 1)))
             error(L"APITexture::17 Failed to set anisotropic filtering");
     }
 }

The SetSamplerState() method on the display device accepts the texturing stage in its first parameter, the state identifier (D3DSAMP_MAXANISOTROPY) in its second parameter and the degree of anisotropy in its third parameter. 

Attach and Detach

The attach() method attaches the texture to sampling stage 0 and sets the filtering state for the texture sampler: 

 void APITexture::attach(int w, int h) { 

     if (!tex) setup(w, h);

     if (tex) {
         texture = tex;
         d3dd->SetTexture(0, tex);
         setSamplerState();
     }
 }

The SetTexture() method on the display device receives the sampling stage index in its first parameter and the address of the interface to the texture COM object in its second parameter. 

The detach() method detaches the texture from sampling stage 0

 void APITexture::detach() { 
     texture = nullptr;
     d3dd->SetTexture(0, nullptr);
 }

Set Sampler State

The setSamplerState() method sets the type of filtering to be implemented by the sampler on the display device: 

 void APITexture::setSamplerState() const {

     if (filter & TEX_MIN_POINT)
         d3dd->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
     else if (filter & TEX_MIN_LINEAR)
         d3dd->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
     else if (filter & TEX_MIN_ANISOTROPIC)
         d3dd->SetSamplerState(0, D3DSAMP_MINFILTER,
          D3DTEXF_ANISOTROPIC); 
     if (filter & TEX_MAG_POINT)
         d3dd->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
     else if (filter & TEX_MAG_LINEAR)
         d3dd->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
     else if (filter & TEX_MAG_ANISOTROPIC)
         d3dd->SetSamplerState(0, D3DSAMP_MAGFILTER,
          D3DTEXF_ANISOTROPIC); 
 } 

The SetSamplerState() method on the display device accepts the sampling stage index in its first parameter, the state identifier (D3DSAMP_MAGFILTER or D3DSAMP_MINFILTER) in its second parameter and the type of filtering (D3DTEXF_POINT, D3DTEXF_LINEAR, or D3DTEXF_ANISOTROPIC) in its third parameter. 


Exercises

  • Read the Wikipedia article on Texture Mapping.
  • Read the Wikipedia article on Texture Filtering.
  • Read the DirectX Documentation on Texture Filtering along with the articles referred therein.



Previous Reading  Previous: Lights Next: Sound   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo