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:

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

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: code format="bb4w" 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% code

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:

code format="bb4w" DIM SimpleVertex{Pos{}=XMFLOAT3{},Tex{}=XMFLOAT2{}} code

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

code format="bb4w" 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 code

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

code format="c" struct VS_INPUT {   float4 Pos : POSITION; float2 Tex : TEXCOORD; }; code

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:

code format="bb4w" 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 code 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:

code format="bb4w" SYS ID3D11DeviceContext.PSSetShaderResources%, pImmediateContext%, 0, 1, \ \                                             ^pTextureRV% SYS ID3D11DeviceContext.PSSetSamplers%, pImmediateContext%, 0, 1, \ \                                      ^pSamplerLinear% code

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.

code format="c" // Pixel Shader float4 PS( PS_INPUT input) : SV_Target {   return txDiffuse.Sample( samLinear, input.Tex ) * vMeshColor; } code
 * 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:

code format="c" // 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; } code

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:

code format="c" cbuffer cbNeverChanges {       matrix View; };   cbuffer cbChangeOnResize {       matrix Projection; };   cbuffer cbChangesEveryFrame {       matrix World; float4 vMeshColor; }; code

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:

code format="bb4w" 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

code