User Tools

Site Tools


tutorial_207_20-_20texture_20mapping_20and_20constant_20buffers

Tutorial 7 - Texture Mapping and Constant Buffers

by Richard Russell, August 2015

Note that the code in this tutorial requires Windows 8.1 or Windows 10

This tutorial is closely based on Microsoft's Direct3D 11 Tutorial 7 but with the code translated from C++ to BBC BASIC for Windows. You should refer to the original for a detailed explanation of the code.



Summary


In the previous tutorial, we introduced lighting to our project. Now we will build on that by adding textures to our cube. Also, we will introduce the concept of constant buffers, and explain how you can use buffers to speed up processing by minimizing bandwidth usage.

The purpose of this tutorial is to modify the centre cube to have a texture mapped onto it.

Source


The source files, libraries etc. may be downloaded from here.

Texture Mapping


Texture mapping refers to the projection of a 2D image onto 3D geometry. We can think of it as wrapping a present, by placing decorative paper over an otherwise bland box. To do this, we have to specify how the points on the surface of the geometry correspond with the 2D image.

The trick is to properly align the coordinates of the model with the texture. For complex models, it is difficult to determine the coordinates for the textures by hand. Thus, 3D modeling packages generally will export models with corresponding texture coordinates. Since our example is a cube, it is easy to determine the coordinates needed to match the texture. Texture coordinates are defined at the vertices, and are then interpolated for individual pixels on a surface.

Creating a Shader Resource from the Texture and Sampler State


The texture is a 2D image that is retrieved from file and used to create a shader-resource view, so that it can be read from a shader:

        REM Load the Texture:
        hr% = FN_CreateShaderResourceViewFromFile(pd3dDevice%, @dir$+"seafloor.jpg", ^pTextureRV%)
        IF hr% <> 0 OR pTextureRV% = 0 ERROR 100, "CreateShaderResourceView failed: "+STR$~hr%

We also need to create a sampler state that controls how the shader handles filtering, MIPs, and addressing. For this tutorial we will enable simple sampler state that enables linear filtering and wrap addressing. To create the sampler state, we will use ID3D11Device::CreateSamplerState():

        REM Create the sample state:
        DIM sampDesc{} = D3D11_SAMPLER_DESC{}
        sampDesc.Filter% = D3D11_FILTER_MIN_MAG_MIP_LINEAR
        sampDesc.AddressU% = D3D11_TEXTURE_ADDRESS_WRAP
        sampDesc.AddressV% = D3D11_TEXTURE_ADDRESS_WRAP
        sampDesc.AddressW% = D3D11_TEXTURE_ADDRESS_WRAP
        sampDesc.ComparisonFunc% = D3D11_COMPARISON_NEVER
        sampDesc.MinLOD% = 0
        sampDesc.MaxLOD% = FN_f4(D3D11_FLOAT32_MAX)
        SYS ID3D11Device.CreateSamplerState%, pd3dDevice%, sampDesc{}, ^pSamplerLinear% TO hr%
        IF hr% <> 0 OR pSamplerLinear% = 0 ERROR 100, "ID3D11Device::CreateSamplerState failed: "+STR$~hr%

Defining the Coordinates


Before we can map the image onto our cube, we must first define the texture coordinates on each of the vertices of the cube. Since images can be of any size, the coordinate system used has been normalized to [0, 1]. The top left corner of the texture corresponds to (0,0) and the bottom right corner maps to (1,1).

In this example, we're having the whole texture spread across each side of the cube. This simplifies the definition of the coordinates, without confusion. However, it is entirely possible to specify the texture to stretch across all six faces, although it's more difficult to define the points, and it will appear stretched and distorted.

First, we update the structure used to define our vertices to include the texture coordinates:

        DIM SimpleVertex{Pos{}=XMFLOAT3{},Tex{}=XMFLOAT2{}}

Next, we update the input layout to the shaders to also include these coordinates.

        DIM layout{(1)} = D3D11_INPUT_ELEMENT_DESC{}
        sn0$ = "POSITION" + CHR$(0)
        layout{(0)}.SemanticName% = !^sn0$
        layout{(0)}.Format% = DXGI_FORMAT_R32G32B32_FLOAT
        layout{(0)}.AlignedByteOffset% = 0
        layout{(0)}.InputSlotClass% = D3D11_INPUT_PER_VERTEX_DATA
        sn1$ = "TEXCOORD" + CHR$(0)
        layout{(1)}.SemanticName% = !^sn1$
        layout{(1)}.Format% = DXGI_FORMAT_R32G32_FLOAT
        layout{(1)}.AlignedByteOffset% = 12
        layout{(1)}.InputSlotClass% = D3D11_INPUT_PER_VERTEX_DATA
        numElements% = DIM(layout{()},1) + 1

Since the input layout changed, the corresponding vertex shader input must also be modified to match the addition:

  struct VS_INPUT
  {
      float4 Pos : POSITION;
      float2 Tex : TEXCOORD;
  };


Finally, we are ready to include texture coordinates in our vertices we defined back in Tutorial 4. Note the second parameter input is a D3DXVECTOR2 containing the texture coordinates. Each vertex on the cube will correspond to a corner of the texture. This creates a simple mapping where each vertex gets (0,0) (0,1) (1,0) or (1,1) as the coordinate:

        REM Create vertex buffer:
        DIM vertices{(23)} = SimpleVertex{}
        FOR v% = 0 TO DIM(vertices{()},1)
          READ x, y, z, p, q
          vertices{(v%)}.Pos.x% = FN_f4(x)
          vertices{(v%)}.Pos.y% = FN_f4(y)
          vertices{(v%)}.Pos.z% = FN_f4(z)
          vertices{(v%)}.Tex.x% = FN_f4(p)
          vertices{(v%)}.Tex.y% = FN_f4(q)
        NEXT v%
        DATA -1.0,  1.0, -1.0,  0.0,  0.0
        DATA  1.0,  1.0, -1.0,  1.0,  0.0
        DATA  1.0,  1.0,  1.0,  1.0,  1.0
        DATA -1.0,  1.0,  1.0,  0.0,  1.0
 
        DATA -1.0, -1.0, -1.0,  0.0,  0.0
        DATA  1.0, -1.0, -1.0,  1.0,  0.0
        DATA  1.0, -1.0,  1.0,  1.0,  1.0
        DATA -1.0, -1.0,  1.0,  0.0,  1.0
 
        DATA -1.0, -1.0,  1.0,  0.0,  0.0
        DATA -1.0, -1.0, -1.0,  1.0,  0.0
        DATA -1.0,  1.0, -1.0,  1.0,  1.0
        DATA -1.0,  1.0,  1.0,  0.0,  1.0
 
        DATA  1.0, -1.0,  1.0,  0.0,  0.0
        DATA  1.0, -1.0, -1.0,  1.0,  0.0
        DATA  1.0,  1.0, -1.0,  1.0,  1.0
        DATA  1.0,  1.0,  1.0,  0.0,  1.0
 
        DATA -1.0, -1.0, -1.0,  0.0,  0.0
        DATA  1.0, -1.0, -1.0,  1.0,  0.0
        DATA  1.0,  1.0, -1.0,  1.0,  1.0
        DATA -1.0,  1.0, -1.0,  0.0,  1.0
 
        DATA -1.0, -1.0,  1.0,  0.0,  0.0
        DATA  1.0, -1.0,  1.0,  1.0,  0.0
        DATA  1.0,  1.0,  1.0,  1.0,  1.0
        DATA -1.0,  1.0,  1.0,  0.0,  1.0

When we sample the texture, we will need to modulate it with a material colour for the geometry underneath.

Bind Texture as Shader Resource


A texture and sampler state are objects like the constant buffers that we have seen in previous tutorials. Before they can be used by the shader, they need to be set with the ID3D11DeviceContext::PSSetSamplers() and ID3D11DeviceContext::PSSetShaderResources() APIs:

          SYS ID3D11DeviceContext.PSSetShaderResources%, pImmediateContext%, 0, 1, \
          \                                              ^pTextureRV%
          SYS ID3D11DeviceContext.PSSetSamplers%, pImmediateContext%, 0, 1, \
          \                                       ^pSamplerLinear%

There we go, now we're ready to use the texture within the shader.

Applying the Texture


To map the texture on top of the geometry, we will be calling a texture lookup function within the pixel shader. The function Sample will perform a texture lookup of a 2D texture, and then return the sampled colour. The pixel shader shown below calls this function and multiplies it by the underlying mesh colour (or material colour), and then outputs the final colour.

  // Pixel Shader
  float4 PS( PS_INPUT input) : SV_Target
  {
      return txDiffuse.Sample( samLinear, input.Tex ) * vMeshColor;
  }
  • txDiffuse is the object storing our texture that we passed in from the code above, when we bound the resource view g_pTextureRV to it.
  • samLinear is the sampler specifications for the texture lookup.
  • input.Tex is the coordinates of the texture that we have specified in the source.


Another thing we must remember to do is to pass the texture coordinates through the vertex shader. If we don't, the data is lost when it gets to the pixel shader. Here, we just copy the input's coordinates to the output, and let the hardware handle the rest:

  // Vertex Shader
  PS_INPUT VS( VS_INPUT input )
  {
      PS_INPUT output = (PS_INPUT)0;
      output.Pos = mul( input.Pos, World );
      output.Pos = mul( output.Pos, View );
      output.Pos = mul( output.Pos, Projection );
      output.Tex = input.Tex;
 
      return output;
  }


Constant Buffers


In the previous tutorials, we used a single constant buffer to hold all of the shader constants we need. But the best way to efficiently use constant buffers is to organize shader variables into constant buffers based on their frequency of update. This allows an application to minimize the bandwidth required for updating shader constants. As an example, this tutorial groups constants into three structures: one for variables that change every frame, one for variables that change only when a window size is changed, and one for variables that are set once and then do not change.

The following constant buffers are defined in this tutorial's .fx file:

      cbuffer cbNeverChanges
      {
          matrix View;
      };
 
      cbuffer cbChangeOnResize
      {
          matrix Projection;
      };
 
      cbuffer cbChangesEveryFrame
      {
          matrix World;
          float4 vMeshColor;
      };


To work with these constant buffers, you need to create a ID3D11Buffer object for each one. Then you can call ID3D11DeviceContext::UpdateSubresource() to update each constant buffer when needed without affecting the other constant buffers:

          REM Update variables that change every frame:
          PROC_MatrixTranspose(CBChangesEveryFrame{}, CBChangesEveryFrame.mWorld{}, mWorld())
          PROC_StoreFloat4(CBChangesEveryFrame{}, CBChangesEveryFrame.vMeshColor{}, vMeshColor())
          SYS ID3D11DeviceContext.UpdateSubresource%, pImmediateContext%, pCBChangesEveryFrame%, \
          \                                           0, NULL, CBChangesEveryFrame{}, 0, 0
This website uses cookies for visitor traffic analysis. By using the website, you agree with storing the cookies on your computer.More information
tutorial_207_20-_20texture_20mapping_20and_20constant_20buffers.txt · Last modified: 2018/04/17 19:16 by tbest3112