Texture Filtering
Texture filtering
is algorithms used by graphics hardware that affect how the mapped
image’s contents appear on the surface. They are commonly supported by
graphics hardware. By using the right texture filtering, you can improve
the quality of textured mapped surfaces. Because images are not
displayed to the rendered screen at a 1:1 ratio, artifacts can appear on
textured mapped surfaces as objects move away from the camera, close to
the camera, or are tilted at an angle. This means that every screen
pixel is not shaded with an individual pixel in the image. As the
surface with the texture moves away, a single screen pixel can actually
have multiple image pixels fall within it. This can cause the images to
distort slightly and display artifacts that can damage the rendered
look. Among these are aliasing artifacts, which will be discussed in
more detail later in this book.
The fastest filtering
algorithm is called nearest-neighbor interpolation filtering, also
commonly referred to as point-filtering. Point-filtering essentially
selects the closest pixel to the point being sampled and uses that as
the color. This is the fastest type of filtering because no additional
equations need to be solved to compute the color. It simply uses the
texture coordinates to find the closest pixel.
The second-fastest type
of filtering is called bilinear interpolation, and the third-fastest is
called trilinear interpolation. Bilinear interpolation averages each
pixel with four surrounding pixels and displays the average instead of
the original. This has the effect of slightly softening the images by
using a very simple blur and helps reduce various artifacts such as
aliasing artifacts, as shown in Figure 4.
Trilinear interpolation does the same, but it also includes
interpolation between mip maps (multi-resolution maps). Mip maps will be
discussed in more detail in the upcoming section.
Multi-Resolution Maps
A
mip map is a multi-resolution map of a texture image. Let’s say you
have a 2D texture that is 512 × 512. In computer graphics you can load
the same texture at smaller resolutions and store them all in a single
texture object so that the graphics hardware can choose which resolution
of the image (hence multi-resolution map) to use. The reasoning for
this is fairly straightforward. As objects move away from the camera,
their high-resolution detail is not as visible as if you opened the
image in an editor such as Adobe Photoshop or if you viewed the image up
close. Therefore, if you have an image that is 1024 × 1024, and if that
texture is being displayed on a surface that is so far away that the
texture looks the way it would if it was 128 × 128, then why send the
1024 × 1024 image down the rendering pipeline? The larger the textures,
the larger the bottleneck on the graphics hardware, along with other
factors such as the amount of geometry. Figure 5 shows how detail from a far-away texture cannot be seen as easily.
The purpose of mip maps is
to reduce the amount of texture data that is processed and passed down
the graphics hardware when rendering surfaces at a distance. When mip
maps are enabled, the graphics hardware performs all operations and
chooses the best resolution to display surfaces that are rendered in a
scene. So when using an API such as OpenGL or Direct3D, all you have to
do is supply the API with the mip maps or tell the API to generate them
if you don’t have multiple resolutions. Reducing the amount of data that
is passed down the graphics hardware can lead to better performance,
and that is the purpose of mip maps.
Mip maps can be created
by choosing a texture format that saves mip maps such as the .DDS image
file format. The mip maps can be manually loading from individual images
one at a time into a texture object, or they can be generated by the
graphics hardware, which is an option OpenGL and Direct3D offer. Mip
maps have resolutions that are a factor of 2, and every level of
resolution is half the size as the one before it. So if you had a 1024 ×
1024 texture and four levels of mip maps, the highest level would be
1024 × 1024, the second level would be 512 × 512, the third level would
be 256 × 256, and the fourth level would be 128 × 128. An example of mip
maps as they would look side-by-side is shown in Figure 6.
Loading Textures
There
are three main ways to create a texture in Direct3D 10. Programmers
have the option of loading a texture from a file, loading a texture from
memory, or generating one in a pixel shader. The generation of a
texture in a shader is called procedural texture generation, and these
textures are created using an algorithm.
Throughout the rest of this book we focus on the
function D3DX10CreateShaderResourceViewFromFile().
We will use this function to load 2D textures, but it also works on the
other texture types discussed in the beginning of this article. The
texture functions Direct3D offers include the following.
D3DX10CreateShaderResourceViewFromFile()
D3DX10CreateShaderResourceViewFromMemory()
D3DX10CreateShaderResourceViewFromResource()
D3DX10CreateTextureFromFile()
D3DX10CreateTextureFromMemory()
D3DX10CreateTextureFromResource()
D3DX10LoadTextureFromTexture()
D3DX10GetImageInfoFromFile()
D3DX10GetImageInfoFromMemory()
D3DX10GetImageInfoFromResource()
D3DX10CreateAsyncTextureInfoProcessor()
D3DX10CreateAsyncTextureProcessor()
D3DX10FilterTexture()
D3DX10ComputeNormalMap()
D3DX10SaveTextureToFile()
D3DX10SaveTextureToMemory()
To use textures we need a
texture object and a shader resource view. The texture object is the
texture itself, and the shader resource view is used to allow the shader
to access the resource. The D3DX10CreateShaderResourceViewFromFile(), D3DX10CreateShaderResourceViewFromMemory(), and D3DX10CreateShaderResourceViewFromResource()
functions are used to create a shader resource view along with a
texture object. The result of calling this function is the shader
resource view object that has the type ID3D10ShaderResourceView. If you use one of these three functions, you don’t have to create and load the texture object (of type ID3D10Texture2D
for 2D textures) separately because these functions will do all of the
work of preparing the shader resource view and texture object for you.
Later, when we free the texture, we will see that when using this
function, we must use the shader resource view to obtain a pointer to
the texture so that the texture can be freed from the shader resource
view.
The D3DX10CreateShaderResourceViewFromFile() function takes as parameters the Direct3D device object, the name of the texture file to load, an optional D3DX10_IMAGE_LOAD_INFO
structure that is used to specify the characteristics of the texture, a
thread pump used by multi-threading applications (beyond the scope of
this book), the address to the shader resource view that will be created
as a result of this function, and an optional address that will store
the return value of the function in a multi-threading application if the
thread pump was created. The function’s memory has slightly different
parameters, offering the size and image pixel data instead of the file
name. The resource version has a parameter that represents the
resource’s name rather than a texture file name. The D3DX10CreateShaderResourceViewFromFile() function is the most commonly used function in this book.
The D3DX10CreateTextureFromFile(), D3DX10CreateTextureFromMemory(), and D3DX10CreateTextureFromResource()
functions are used to create a texture object but do not create the
shader resource view object. In contrast, other functions, including the
D3DX10CreateShaderResourceViewFromFile() function, create the shader resource view and internally set the texture object to it. If you call one
of the shader resource view creation functions, you do not need to call
any of these functions that directly create the texture object since it
is done automatically. These functions essentially create the texture
without the shader resource view.
The D3DX10LoadTextureFromTexture() function is used to load a new texture from an existing texture and takes as parameters the source texture, a D3DX10_TEXTURE_LOAD_INFO descriptor, and an address at which to store the new texture.
The D3DX10GetImageInfoFromFile(), D3DX10GetImageInfoFromMemory(), and D3DX10GetImageInfoFromResource() functions are used to retrieve image information from a texture. This information is stored in a D3DX10_IMAGE_INFO
object and includes the image’s width, height, depth, size in bytes,
total mip map levels, file format, color format, dimensions, and
miscellaneous flags. The resource dimensions can be any of the
following:
D3D10_RESOURCE_DIMENSION_UNKNOWN
D3D10_RESOURCE_DIMENSION_BUFFER
D3D10_RESOURCE_DIMENSION_TEXTURE1D
D3D10_RESOURCE_DIMENSION_TEXTURE2D
D3D10_RESOURCE_DIMENSION_TEXTURE3D
The D3DX10CreateAsyncTextureInfoProcessor() and D3DX10CreateAsyncTextureProcessor()
functions are used to create data processors that are used in
multi-threaded applications to load a texture. This is an advanced topic
that requires an understanding of multi-threading, which is beyond the
scope of this book.
The next two texture functions are used to manipulate a texture that is already loaded. The D3DX10FilterTexture()
function takes as parameters the texture object to filter, the mip map
level of the original texture being filtered, and flags for the filter.
The flags can be one of the following values:
D3DX10_DEFAULT
D3DX10_FILTER_NONE
D3DX10_FILTER_POINT (nearest neighbor filtering)
D3DX10_FILTER_LINEAR (bilinear filtering)
D3DX10_FILTER_TRIANGLE
D3DX10_FILTER_BOX
D3DX10_FILTER_MIRROR_U
D3DX10_FILTER_MIRROR_V
D3DX10_FILTER_MIRROR_W
D3DX10_FILTER_MIRROR (same as D3DX10_FILTER_MIRROR_U / D3DX10_FILTER_MIRROR_V / D3DX10_FILTER_MIRROR_W)
D3DX10_FILTER_DITHER
D3DX10_FILTER_DITHER_DIFFUSION
D3DX10_FILTER_SRGB_IN
D3DX10_FILTER_SRGB_OUT
D3DX10_FILTER_SRGB (same as D3DX10_FILTER_SRGB_IN / D3DX10_FILTER_SRGB_OUT)
The second function is the D3DXComputeNormalMap(), which is used to take a texture and to convert it to a normal map image.
The last texture functions are used to save a texture to a file or to memory. The D3DX10SaveTextureToFile() function takes as parameters the texture object that is to be saved, a D3DX10_IMAGE_FILE_FORMAT description object, and the name of the file that will be created. The second texture-saving function is called D3DX10SaveTextureToMemory(), and it takes as parameters the texture object to be saved, the image format description, an out address to a ID3D10BLOG object that will store the texture in memory, and optional flags.
If you want to create the texture and shader resource view separately, you can call D3DX10CreateTextureFromFile() to create the texture, or you can call one of the other texture creation functions—for example, CreateShaderResourceView()—to create only the resource view. The CreateShaderResourceView() takes as parameters the resource (such as the ID3D10Texture2D texture that is created by calling the D3DX10CreateTextureFromFile() function), the D3D10_SHADER_RESOURCE_VIEW_DESC,
which is the descriptor object that specifies the characteristics of
the shader resource view object is being created, and the address at
which to store the shader resource view as an ID3D10ShaderResourceView object.
Applying Textures
Objects and surfaces are rendered in Direct3D 10 using effect shaders .The effect shaders themselves are objects of the ID3D10Effect type. Inside each shader there can be one or more techniques, which are essentially implementations of rendering effects.
To apply a texture to a technique so that a shader bound to that technique can access it, we need to create an ID3D10EffectShaderResourceVariable
object. This variable will bind the application to the shader so that a
value can be stored inside it and be accessed by the shaders. Tthis is done by calling the technique object’s GetVariableByName() function (or an equivalent access function) and calling AsShaderResource() on the returned object to gain access to the ID3D10EffectShaderResourceVariable object that will be used to set the variable or in this case the texture.
Once access to the shader variable is obtained, we set the texture by calling the SetResource() function on the ID3D10EffectShaderResourceVariable object. For example, if the ID3D10EffectShaderResourceVariable object was named g_decalEffectVar, and if we had a texture object named g_decal, we could set it like so:
g_decalEffectVar->SetResource(g_decal);
When initially creating the ID3D10EffectShaderResourceVariable object, we create it after the shader has been initially loaded like so:
HRESULT hr = D3DX10CreateEffectFromFile("TextureMap.fx",
NULL, NULL, "fx_4_0", shaderFlags, 0, g_d3dDevice, NULL, NULL,
&g_shader, NULL, NULL);
if(FAILED(hr))
return false;
g_effect = g_shader->GetTechniqueByName("TextureMapping");
g_decalEffectVar = g_shader->GetVariableByName(
"decal")->AsShaderResource();
When you access the shader variable, the name passed into GetVariableByName() must match the variable name that is defined inside the shader.
Freeing Textures
To free a texture, you can call Release() on the ID3D10Texture2D
object. This will work if you manually created the texture object
directly, but if you loaded the texture from a file using a Direct3D 10
helper function such as D3DX10CreateShaderResourceViewFromFile(), then instead of an ID3D10Texture2D object, you would have an ID3D10ShaderResource object. Even with the shader resource view object, you still have to release the ID3D10Texture2D
object it contains. To do this, if you don’t have a pointer to the
texture but have one to the shader resource view, you call the GetResource() function of the ID3D10ShaderResource object and pass to it an ID3D10Resource pointer that will point to the texture resource object.
Since ID3D10Resource is a base class of ID3D10Texture2D, you can call Release() on this returned base object to release the texture asset. You can then call Release() on the ID3D10ShaderResource
object to release the shader resource view from memory. Keep in mind
that releasing only the shader resource view will not release the
texture object to which the shader resource view is attached, so this
must be done as a separate task. An example of this is shown next, where
g_decal is assumed to be the shader resource view that was created as a result of calling the D3DX10CreateShaderResourceViewFromFile() function.
if(g_decal)
{
ID3D10Resource *pRes;
g_decal->GetResource(&pRes);
pRes->Release();
g_decal->Release();
}
You only need to call
GetResource()
to obtain a pointer to the texture object if you don’t already have it. If you already have theID3D10Texture2D
object, you can call
Release()
on that and
Release()
on any shader resource view that uses it.
|