Texture Mapping in DirectX Computational Graphics

Texture mapping is a fundamental technique in 3D computer graphics used to add detail, color, and surface properties to geometric models. By applying a 2D image (a texture) to a 3D surface, we can create realistic-looking objects without needing to increase the complexity of the underlying geometry.

The Core Concept

At its heart, texture mapping involves establishing a correspondence between points on a 3D surface and points on a 2D texture image. This correspondence is typically defined using texture coordinates (UV coordinates).

Texture Coordinates (UV)

Each vertex of a 3D model is assigned a pair of texture coordinates, commonly denoted as u and v. These coordinates range from 0.0 to 1.0, mapping to the leftmost/bottommost edge and the rightmost/topmost edge of the texture image, respectively.

When rendering a triangle (or any other primitive), the graphics hardware interpolates these UV coordinates across the surface of the polygon. For each pixel being rendered, a corresponding sample point is determined on the texture using these interpolated coordinates.

3D Vertex
(X, Y, Z)
+
UV Coordinates
(U, V)
Texture Sampler
(Reads pixel color at U, V)
Fragment Color
(Applied to 3D surface)

Implementing Texture Mapping in DirectX

DirectX provides robust support for texture mapping. The process generally involves the following steps:

1. Loading the Texture

Textures are typically loaded from image files (e.g., .dds, .png, .jpg) into DirectX-compatible texture objects. The DirectXTex library is a common and powerful tool for this purpose.

A simplified representation of loading might involve creating a texture resource and initializing it with image data.

// Pseudo-code for texture loading
            ID3D11Device* pDevice = GetDevice();
            ID3D11DeviceContext* pDeviceContext = GetDeviceContext();

            // Load image data from a file (e.g., using DirectXTex)
            std::vector<byte> imageData;
            // ... populate imageData ...

            D3D11_TEXTURE2D_DESC texDesc;
            // ... fill texDesc with image dimensions, format, etc. ...

            D3D11_SUBRESOURCE_DATA initData;
            initData.pSysMem = imageData.data();
            initData.SysMemPitch = imageWidth * bytesPerPixel;
            initData.SysMemSlicePitch = 0; // For 2D textures

            ID3D11Texture2D* pTexture = nullptr;
            HRESULT hr = pDevice->CreateTexture2D(&texDesc, &initData, &pTexture);
            if (FAILED(hr)) { /* Handle error */ }

            // Create shader resource view
            ID3D11ShaderResourceView* pShaderResourceView = nullptr;
            D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
            // ... fill srvDesc ...
            hr = pDevice->CreateShaderResourceView(pTexture, &srvDesc, &pShaderResourceView);
            if (FAILED(hr)) { /* Handle error */ }

            // Clean up intermediate texture resource if needed
            pTexture->Release();
            

2. Creating a Sampler State

A sampler state defines how the texture is sampled. This includes parameters like filtering (e.g., bilinear, anisotropic), addressing modes (e.g., wrap, clamp), and mipmap levels.

// Pseudo-code for sampler state creation
            ID3D11Device* pDevice = GetDevice();

            D3D11_SAMPLER_DESC samplerDesc;
            ZeroMemory(&samplerDesc, sizeof(samplerDesc));

            samplerDesc.Filter = D3D11_FILTER_ANISOTROPIC; // Or D3D11_FILTER_MIN_MAG_MIP_LINEAR
            samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
            samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
            samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
            samplerDesc.MipLODBias = 0.0f;
            samplerDesc.MaxAnisotropy = 16;
            samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
            samplerDesc.BorderColor[0] = 0.0f; // Black border
            samplerDesc.BorderColor[1] = 0.0f;
            samplerDesc.BorderColor[2] = 0.0f;
            samplerDesc.BorderColor[3] = 0.0f;
            samplerDesc.MinLOD = 0.0f;
            samplerDesc.MaxLOD = D3D11_FLOAT_MAX;

            ID3D11SamplerState* pSamplerState = nullptr;
            HRESULT hr = pDevice->CreateSamplerState(&samplerDesc, &pSamplerState);
            if (FAILED(hr)) { /* Handle error */ }
            

3. Shader Implementation

Shaders (vertex and pixel shaders) are crucial for texture mapping. The vertex shader passes the texture coordinates to the pixel shader, where they are used to sample the texture.

Vertex Shader (HLSL Example)

struct VS_INPUT
{
    float4 Pos : POSITION;
    float2 Tex : TEXCOORD0; // Input texture coordinate
};

struct VS_OUTPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD0; // Output texture coordinate to pixel shader
};

cbuffer ConstantBuffer : register(b0)
{
    float4x4 WorldViewProjection;
};

VS_OUTPUT VS(VS_INPUT input)
{
    VS_OUTPUT output = (VS_OUTPUT)0;
    output.Pos = mul(input.Pos, WorldViewProjection);
    output.Tex = input.Tex; // Pass UV coordinates through
    return output;
}
            

Pixel Shader (HLSL Example)

Texture2D g_Texture : register(t0);       // The texture resource
SamplerState g_Sampler : register(s0);       // The sampler state

float4 PS(VS_OUTPUT input) : SV_Target
{
    // Sample the texture using the interpolated UV coordinates and the sampler state
    float4 texColor = g_Texture.Sample(g_Sampler, input.Tex);
    return texColor; // Return the sampled color
}
            

4. Binding Resources and Rendering

Before drawing, the texture, sampler state, and other necessary resources (like constant buffers) must be bound to the appropriate shader stages. The pixel shader's texture register (t0) will be bound to the shader resource view, and the sampler register (s0) will be bound to the sampler state.

// Pseudo-code for binding resources
            ID3D11DeviceContext* pDeviceContext = GetDeviceContext();
            ID3D11ShaderResourceView* pShaderResourceView = GetTextureSRV(); // Your loaded SRV
            ID3D11SamplerState* pSamplerState = GetSamplerState();         // Your created sampler state

            // Bind Shader Resource View to Pixel Shader's Texture Register (t0)
            pDeviceContext->PSSetShaderResources(0, 1, &pShaderResourceView);

            // Bind Sampler State to Pixel Shader's Sampler Register (s0)
            pDeviceContext->PSSetSamplers(0, 1, &pSamplerState);

            // ... Bind Vertex Shader, Pixel Shader, Vertex Buffer, Index Buffer ...

            // Draw call
            pDeviceContext->DrawIndexed(numIndices, 0, 0);
            

Types of Texture Mapping

Considerations

Mastering texture mapping is key to creating visually rich and compelling 3D environments. By understanding these concepts and leveraging DirectX's powerful features, you can bring your virtual worlds to life.

This tutorial provides a foundational understanding of texture mapping. Advanced topics like procedural textures, texture atlases, and shader-based texture effects offer even greater flexibility and visual fidelity.