Tutorial 5: Texturing

写在前面:本文续写自Rastertek的Direct3D12教程。不知出于何种原因,原作者在完成了前三讲内容之后彻底消失了,时间停留在了4年前便不再更新。从以往的教程来看,本套教程结构清晰,为大家展现了一个游戏引擎的最小单位,是不可多得的入门教程。但由于D3D12相对于之前版本有很大改动,有必要完成整套教程,也正是这个原因,续写工作改动量巨大,又因为原文是用英文写成,续写也只能使用英文进行,本人实在是没有精力去做全文翻译,在此也希望有精力的朋友尝试翻译。同时,由于本人并非计算机专业人员,文中难免有各种错误,也希望大家及时指正,感谢各位!

This tutorial will explain how to use texturing in DirectX 12. Texturing allows us to add photorealism to our scenes by applying photographs and other images onto polygon faces. For example in this tutorial we will take the following image:

http://www.rastertek.com/pic8026.gif

And then apply it to the polygon from the previous tutorial to produce the following:

The format of the textures we will be using is .tga files. This is the common graphics format that supports red, green, blue, and alpha channels. You can create and edit targa files with generally any image editing software. And the file format is mostly straight forward.

And before we get into the code we should discuss how texture mapping works. To map pixels from the .tga image onto the polygon we use what is called the Texel Coordinate System. This system converts the integer value of the pixel into a floating point value between 0.0f and 1.0f. For example if a texture width is 256 pixels wide then the first pixel will map to 0.0f, the 256th pixel will map to 1.0f, and a middle pixel of 128 would map to 0.5f.

In the texel coordinate system the width value is named "U" and the height value is named "V". The width goes from 0.0 on the left to 1.0 on the right. The height goes from 0.0 on the top to 1.0 on the bottom. For example top left would be denoted as U 0.0, V 0.0 and bottom right would be denoted as U 1.0, V 1.0. I have made a diagram below to illustrate this system:

http://www.rastertek.com/pic8028.gif

Now that we have a basic understanding of how to map textures onto polygons we can look at the updated frame work for this tutorial:

The changes to the frame work since the previous tutorial is the new TextureClass which is inside ModelClass and the new TextureShaderClass which replaces the ColorShaderClass. We'll start the code section by looking at the new HLSL texture shader first.


// Filename: texture.hlsl



/
// GLOBALS //
/
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};

 We are no longer using color in our vertex type and have instead moved to using texture coordinates. Since texture coordinates take a U and V float coordinate we use float2 as its type. The semantic for texture coordinates is TEXCOORD0 for vertex shaders and pixel shaders. You can change the zero to any number to indicate which set of coordinates you are working with as multiple texture coordinates are allowed.

//
// TYPEDEFS //
//
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};



// Vertex Shader

PixelInputType TextureVertexShader(VertexInputType input)
{
    PixelInputType output;
    

    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);

The only difference in the texture vertex shader in comparison to the color vertex shader from the previous tutorial is that instead of taking a copy of the color from the input vertex we take a copy of the texture coordinates and pass them to the pixel shader.

// Store the texture coordinates for the pixel shader.
    output.tex = input.tex;

    return output;
}

Texture.ps


// Filename: texture.hlsl

The texture pixel shader has two global variables. The first is Texture2D shaderTexture which is the texture resource. This will be our texture resource that will be used for rendering the texture on the model. The second new variable is the SamplerState SampleType. The sampler state allows us to modify how the pixels are written to the polygon face when shaded. For example if the polygon is really far away and only makes up 8 pixels on the screen then we use the sample state to figure out which pixels or what combination of pixels will actually be drawn from the original texture. The original texture may be 256 pixels by 256 pixels so deciding which pixels get drawn is really important to ensure that the texture still looks decent on the really small polygon face. We will setup the sampler state in the TextureShaderClass also and then attach it to the resource pointer so this pixel shader can use it to determine which sample of pixels to draw.

/
// GLOBALS //
/
Texture2D shaderTexture;
SamplerState SampleType;

The PixelInputType for the texture pixel shader is also modified using texture coordinates instead of the color values.

//
// TYPEDEFS //
//
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};

The pixel shader has been modified so that it now uses the HLSL sample function. The sample function uses the sampler state we defined above and the texture coordinates for this pixel. It uses these two variables to determine and return the pixel value for this UV location on the polygon face.


// Pixel Shader

float4 TexturePixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;


    // Sample the pixel color from the texture using the sampler at this texture coordinate location.
    textureColor = shaderTexture.Sample(SampleType, input.tex);

    return textureColor;
}

Textureshaderclass.h

The TextureShaderClass is just an updated version of the ColorShaderClass from the previous tutorial. This class will be used to draw the 3D models using vertex and pixel shaders.


// Filename: TextureShaderClass.h

#ifndef _TextureShaderClass_H_
#define _TextureShaderClass_H_


//
// INCLUDES //
//
#pragma once
#include <d3d12.h>
#include <d3dx12.h>
#include <directxmath.h>
#include <fstream>
#include <d3dcompiler.h>

#pragma comment(lib, "d3dcompiler.lib")
using namespace std;
using namespace DirectX;



// Class name: TextureShaderClass

class TextureShaderClass
{
private:
	struct MatrixBufferType
	{
		XMMATRIX world;
		XMMATRIX view;
		XMMATRIX projection;
	};

public:
	TextureShaderClass();
	TextureShaderClass(const TextureShaderClass&);
	~TextureShaderClass();

	bool Initialize(ID3D12Device*, HWND, ID3D12Resource*);
	void Shutdown();
	bool Render(ID3D12GraphicsCommandList*, int, XMMATRIX, XMMATRIX, XMMATRIX);
	ID3D12RootSignature* GetSignature();
	ID3D12PipelineState* GetPSO();

private:
	bool InitializeShader(ID3D12Device*, HWND, const WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3DBlob*, HWND, const WCHAR*);

	bool SetShaderParameters(ID3D12GraphicsCommandList*, XMMATRIX, XMMATRIX, XMMATRIX);
	void RenderShader(ID3D12GraphicsCommandList*, int);

private:
	ID3D12Resource* m_matrixBuffer;
	ID3D12RootSignature* m_prtSignature;
	ID3D12PipelineState* m_pipelineState;

The heap pointer is changed to hold 2 descriptors.

        ID3D12DescriptorHeap* m_srvcbvHeap;//2 views on 1 heap

There are new private variables for handling the texture. These will be used to interface with the texture shader.

	ID3D12DescriptorHeap* m_smplHeap;//1 sampler heap
	ID3D12Resource* m_comTexture;

We will keep the buffer pointer to copy data to the constant buffer.

        void* m_pDataPtr;
};

#endif

Textureshaderclass.cpp


// Filename: textureshaderclass.cpp

#include "TextureShaderClass.h"

TextureShaderClass::TextureShaderClass()
{
	m_matrixBuffer = 0;
	m_srvcbvHeap = 0;
	m_prtSignature = 0;
	m_pipelineState = 0;

The new sampler heap variable is set to null in the class constructor.

        m_smplHeap = 0;
	m_comTexture = 0;
}
TextureShaderClass::TextureShaderClass(const TextureShaderClass& other)
{
}

TextureShaderClass::~TextureShaderClass()
{
}

bool TextureShaderClass::Initialize(ID3D12Device* device, HWND hwnd, ID3D12Resource* texture)
{
	bool result;

        //store the texture interface for future use
	m_comTexture = texture;

The new texture.hlsl file is loaded for this shader.

        // Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"texture.hlsl");
	if (!result)
	{
		return false;
	}

	return true;
}

The Shutdown function calls the release of the shader variables.

void TextureShaderClass::Shutdown()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	ShutdownShader();

	return;
}

The Render function keeps the same but it needs the texture resource which has been stored in Initialize function. This is needed to create the shader resource view (SRV) in InitializeShader function so that the texture can be set in the shader and then used for rendering.

bool TextureShaderClass::Render(ID3D12GraphicsCommandList* cmd, int indexCount, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix)
{
	bool result;


	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(cmd, worldMatrix, viewMatrix, projectionMatrix);
	if (!result)
	{
		return false;
	}

	// Now render the prepared buffers with the shader.
	RenderShader(cmd, indexCount);

	return true;
}

InitializeShader sets up the texture shader.

bool TextureShaderClass::InitializeShader(ID3D12Device* device, HWND hwnd, const WCHAR* filename)
{
	HRESULT result;
	ID3DBlob* errorMessage;
	ID3DBlob* vertexShaderBuffer;
	ID3DBlob* pixelShaderBuffer;
	ID3DBlob* pSignatureBlob;
	D3D12_INPUT_ELEMENT_DESC polygonLayout[2];
	unsigned int numElements;
	D3D12_HEAP_PROPERTIES heapp = {};
	D3D12_RESOURCE_DESC bufferDesc;

We have a bunch of new variables to hold the description of the texture resource view and sampler that will be setup in this function. Different from previous versions, in DirectX 12, all the preparation for the texture is done by the user to specify the size and pitch for instance to decide the way of storage in video memory. Thus, we need piles of descriptors for the root signature and the PSO.

	DXGI_SAMPLE_DESC sDesc;
	D3D12_DESCRIPTOR_HEAP_DESC srvcbvHeapDesc;
	D3D12_DESCRIPTOR_HEAP_DESC smplHeapDesc;//New heap desc for the sampler
	D3D12_DESCRIPTOR_RANGE1 cbvRange[3];//New ranges for 3 descs
	D3D12_ROOT_DESCRIPTOR_TABLE1 descTable;
	D3D12_ROOT_PARAMETER1  rootParameters[3];//New parameters for three descs
	D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};//New desc for the texture resource
	D3D12_CONSTANT_BUFFER_VIEW_DESC matrixBufferDesc = {};
	D3D12_SAMPLER_DESC smplDesc = {};

	D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};//decide version
	D3D12_VERSIONED_ROOT_SIGNATURE_DESC vrtSignatureDesc = {};
	D3D12_ROOT_SIGNATURE_DESC1 rtSignatureDesc = {};
	D3D12_GRAPHICS_PIPELINE_STATE_DESC PSODesc = {};

	// Initialize the pointers this function will use to null.
	errorMessage = 0;
	vertexShaderBuffer = 0;
	pixelShaderBuffer = 0;
	pSignatureBlob = 0;

Load in the new texture vertex and pixel shaders.

// Compile the vertex shader code.
	result = D3DCompileFromFile(filename, NULL, NULL, "TextureVertexShader", "vs_5_0", 0, 0, &vertexShaderBuffer, &errorMessage);
	if (FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if (errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, filename);
		}
		// If there was  nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, filename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Compile the pixel shader code.
	result = D3DCompileFromFile(filename, NULL, NULL, "TexturePixelShader", "ps_5_0", 0, 0, &pixelShaderBuffer, &errorMessage);
	if (FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if (errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, filename);
		}
		// If there was nothing in the error message then it simply could not find the file itself.
		else
		{
			MessageBox(hwnd, filename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

The input layout has changed as we now have a texture element instead of color. The first position element stays unchanged but the SemanticName and Format of the second element have been changed to TEXCOORD and DXGI_FORMAT_R32G32_FLOAT. These two changes will now align this layout with our new VertexType in both the ModelClass definition and the typedefs in the shader files.

        // Create the vertex input layout description.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	polygonLayout[0].SemanticName = "POSITION";
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D12_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

Like the previous tutorial, we need to create the resource for the constant buffer and lock it for updating every frame.

        // Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	//Fill up a description for the constant buffer
	heapp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
	heapp.CreationNodeMask = 1;
	heapp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
	heapp.Type = D3D12_HEAP_TYPE_UPLOAD;
	heapp.VisibleNodeMask = 1;

	//Setup the description for the sample desc
	sDesc.Count = 1;
	sDesc.Quality = 0;
	//Setup the description for the vertex buffer
	bufferDesc.Alignment = 0;//for constant buffer we set it to 0
	bufferDesc.DepthOrArraySize = 1;
	bufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
	bufferDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
	bufferDesc.Format = DXGI_FORMAT_UNKNOWN;
	bufferDesc.Height = 1;
	bufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
	bufferDesc.MipLevels = 1;
	bufferDesc.SampleDesc = sDesc;
	bufferDesc.Width = (sizeof(MatrixBufferType) / 256 + 1) * 256;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateCommittedResource(&heapp, D3D12_HEAP_FLAG_NONE,
		&bufferDesc,
		D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_matrixBuffer));
	if (FAILED(result))
	{
		return false;
	}

	// Lock the constant buffer so it can be written to.
	m_matrixBuffer->Map(0, nullptr, &m_pDataPtr);

We will not unlock it now, as it is not populated yet. Then, we are going to handle the texture, i.e. shader resource.

        // Now describe our descriptor heap
	srvcbvHeapDesc.NumDescriptors = 2;//srv+cbv, now we have 2 descriptors for constant and texture
	srvcbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	srvcbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	srvcbvHeapDesc.NodeMask = 0;

	//Create a new descriptor heap for both the shader resource (texture) view SRV and the constant buffer view CBV
	result = device->CreateDescriptorHeap(&srvcbvHeapDesc, IID_PPV_ARGS(&m_srvcbvHeap));
	if (FAILED(result))
	{
		return false;
	}

Note that we have 2 descriptors to be put on 1 heap, so the number should be 2. Next is the turn to create the SRV, which will tell DirectX where our texture resource is.

        //Set up the SRV desc, we only need rgba integer format for our texture
	srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
	srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;//rgba integer
	srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
	srvDesc.Texture2D.MipLevels = 1;

	//Now we can create the view on the desc heap
	device->CreateShaderResourceView(m_comTexture, &srvDesc, m_srvcbvHeap->GetCPUDescriptorHandleForHeapStart());//We don't need an offset for the first view on the heap

Then the constant buffer view (CBV) follows.

        // Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
	matrixBufferDesc.BufferLocation = m_matrixBuffer->GetGPUVirtualAddress();
	matrixBufferDesc.SizeInBytes = (sizeof(MatrixBufferType) / 256 + 1) * 256;//Must be rounded up to 256 or the device will be removed!

Since we have 2 descriptors on the heap, we need an offset value. First we get the heap start, and then we add the increment size to it, which, in most cases, is 32 for an SRV or a CBV.

        //Calculate the offset of the desc on the heap
	UINT offset = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
	D3D12_CPU_DESCRIPTOR_HANDLE handle;
	handle.ptr = m_srvcbvHeap->GetCPUDescriptorHandleForHeapStart().ptr + offset;

	//Create the view on the heap
	device->CreateConstantBufferView(&matrixBufferDesc, handle);

To get a sampler, we need a sample heap first, then we can create a sampler on it.

        //Set up the sampler heap
	smplHeapDesc.NodeMask = 0;//Node mask represents the GPU no.
	smplHeapDesc.NumDescriptors = 1;//We only set one sample for simplicity in this tutorial
	smplHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
	smplHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;

	//Create a desc heap for the sampler
	device->CreateDescriptorHeap(&smplHeapDesc, IID_PPV_ARGS(&m_smplHeap));

The sampler state description is setup here and then can be passed to the pixel shader after. The most important element of the texture sampler description is Filter. Filter will determine how it decides which pixels will be used or combined to create the final look of the texture on the polygon face. In the example here I use D3D12_FILTER_MIN_MAG_MIP_LINEAR which is more expensive in terms of processing but gives the best visual result. It tells the sampler to use linear interpolation for minification, magnification, and mip-level sampling.

AddressU and AddressV are set to Wrap which ensures that the coordinates stay between 0.0f and 1.0f. Anything outside of that wraps around and is placed between 0.0f and 1.0f. All other settings for the sampler state description are defaults.

        //Set up the sampler
	smplDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
	smplDesc.MinLOD = 0;
	smplDesc.MaxLOD = D3D12_FLOAT32_MAX;
	smplDesc.MipLODBias = 0.0f;
	smplDesc.MaxAnisotropy = 1;
	smplDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
	smplDesc.BorderColor[0] = 0.0f;
	smplDesc.BorderColor[1] = 0.0f;
	smplDesc.BorderColor[2] = 0.0f;
	smplDesc.BorderColor[3] = 0.0f;
	smplDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
	smplDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
	smplDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;

	//Create the sampler
	device->CreateSampler(&smplDesc, m_smplHeap->GetCPUDescriptorHandleForHeapStart());//We have only one sampler on the heap so we don't need offset

Since we have 3 descriptors on the heaps (SRV+CBV on heap 1, Sampler on heap 2), we need more work on the ranges. In this case, we need a range array of 3 elements declared at the beginning of this function to separate the 3 descriptors. But most of the fields are the same, so we only need minor modifications.

        //Set up the cbv range
	for (UINT i = 0; i < 3; ++i)
	{
		cbvRange[i].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
		cbvRange[i].NumDescriptors = 1;
		cbvRange[i].BaseShaderRegister = 0;
		cbvRange[i].RegisterSpace = 0;
		cbvRange[i].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
		cbvRange[i].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE;
	}
	cbvRange[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
	cbvRange[2].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
	cbvRange[2].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE;

Now we can set up root parameters, which are also 3 in an array. Note that we only have 1 range for each table, so we set it to 1.

        //Fill srv cbv desc table
	descTable.NumDescriptorRanges = 1;
	
	//Fill in root parameters
	for(UINT i = 0; i<3; ++i)
	{
		descTable.pDescriptorRanges = &cbvRange[i];
		rootParameters[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; // this is a descriptor table type, not a constant
		rootParameters[i].DescriptorTable = descTable; // this is our descriptor table for this root parameter
		rootParameters[i].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; // our VERTEX shader will be the only shader accessing this parameter for now
	}

Time for the root signature, but pay attention to the number of descriptors.

        //Decide the highest version
	featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;
	result = device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData));
	if (FAILED(result))
	{
		featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
	}

	//Fill up root signature desc
	rtSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
	rtSignatureDesc.NumParameters = 3;//Now it is set to 3
	rtSignatureDesc.NumStaticSamplers = 0;
	rtSignatureDesc.pParameters = rootParameters;
	rtSignatureDesc.pStaticSamplers = nullptr;

	vrtSignatureDesc.Desc_1_1 = rtSignatureDesc;
	vrtSignatureDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;

	//Compile root signature
	result = D3DX12SerializeVersionedRootSignature(&vrtSignatureDesc, featureData.HighestVersion, &pSignatureBlob, &errorMessage);
	if (FAILED(result))
	{
		return false;
	}
	result = device->CreateRootSignature(0, pSignatureBlob->GetBufferPointer(), pSignatureBlob->GetBufferSize(), IID_PPV_ARGS(&m_prtSignature));
	if (FAILED(result))
	{
		return false;
	}

Build the pipeline state object (PSO) as the previous tutorial did.

        //Build PSO
	PSODesc.InputLayout = { polygonLayout, numElements };
	PSODesc.pRootSignature = m_prtSignature;
	PSODesc.VS = CD3DX12_SHADER_BYTECODE(vertexShaderBuffer);
	PSODesc.PS = CD3DX12_SHADER_BYTECODE(pixelShaderBuffer);
	PSODesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
	PSODesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
	PSODesc.DepthStencilState.DepthEnable = FALSE;
	PSODesc.DepthStencilState.StencilEnable = FALSE;
	PSODesc.SampleMask = UINT_MAX;
	PSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
	PSODesc.NumRenderTargets = 1;
	PSODesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
	PSODesc.SampleDesc.Count = 1;
	result = device->CreateGraphicsPipelineState(&PSODesc, IID_PPV_ARGS(&m_pipelineState));
	if (FAILED(result))
	{
		return false;
	}

We no longer use the compile buffers, so release them.

        if (errorMessage)
	{
		errorMessage->Release();
		errorMessage = 0;
	}
	if (vertexShaderBuffer)
	{
		vertexShaderBuffer->Release();
		vertexShaderBuffer = 0;
	}
	if (pixelShaderBuffer)
	{
		pixelShaderBuffer->Release();
		pixelShaderBuffer = 0;
	}
	if (pSignatureBlob)
	{
		pSignatureBlob->Release();
		pSignatureBlob = 0;
	}
	return true;
}

The ShutdownShader function releases all the variables used in the TextureShaderClass.

void TextureShaderClass::ShutdownShader()
{
	// Release the matrix constant buffer.
	if (m_matrixBuffer)
	{
		// Unlock the constant buffer.
		m_matrixBuffer->Unmap(0, nullptr);
		m_matrixBuffer->Release();
		m_matrixBuffer = 0;
	}

	// Release the desc heap.
	if (m_srvcbvHeap)
	{
		m_srvcbvHeap->Release();
		m_srvcbvHeap = 0;
	}

	// Release the sampler desc heap
	if (m_smplHeap)
	{
		m_smplHeap->Release();
		m_smplHeap = 0;
	}
	// Release the signature.
	if (m_prtSignature)
	{
		m_prtSignature->Release();
		m_prtSignature = 0;
	}

	// Release the PSO.
	if (m_pipelineState)
	{
		m_pipelineState->Release();
		m_pipelineState = 0;
	}

	return;
}

OutputShaderErrorMessage writes out errors to a text file if the HLSL shader could not be loaded.

void TextureShaderClass::OutputShaderErrorMessage(ID3DBlob* errorMessage, HWND hwnd, const WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for (i = 0; i < bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}

SetShaderParameters function remains the same as the previous tutorial.

bool TextureShaderClass::SetShaderParameters(ID3D12GraphicsCommandList* cmd, XMMATRIX worldMatrix, XMMATRIX viewMatrix,
	XMMATRIX projectionMatrix)
{
	MatrixBufferType cbData;

	// Transpose the matrices to prepare them for the shader.
	cbData.world = XMMatrixTranspose(worldMatrix);
	cbData.view = XMMatrixTranspose(viewMatrix);
	cbData.projection = XMMatrixTranspose(projectionMatrix);

	// Copy the matrices into the constant buffer.
	memcpy(m_pDataPtr, &cbData, sizeof(MatrixBufferType));

	return true;
}

RenderShader calls the shader technique to render the polygons. One more time, we have 3 descs now, then we have to set them to the descriptor table on the GPU side. All we need to care about is the offset. The heap offset needs to be calculated to locate the desc, and the offset in the descriptor table should be confirmed to be 3. You may notice that I use 32 as the increment size without calculating. This is OK in this tutorial since we have known the type of desc but it is not recommended on other occasions.

void TextureShaderClass::RenderShader(ID3D12GraphicsCommandList* cmd, int indexCount)
{
	// Set the texture.
	cmd->SetDescriptorHeaps(1, &m_srvcbvHeap);
	cmd->SetGraphicsRootDescriptorTable(0, m_srvcbvHeap->GetGPUDescriptorHandleForHeapStart());
	
	//Set the constant
	D3D12_GPU_DESCRIPTOR_HANDLE handle;
	handle.ptr = m_srvcbvHeap->GetGPUDescriptorHandleForHeapStart().ptr + 32;
	cmd->SetDescriptorHeaps(1, &m_srvcbvHeap);
	cmd->SetGraphicsRootDescriptorTable(1, handle);//offset 1 for the 2nd element in the table

	//Set sampler
	cmd->SetDescriptorHeaps(1, &m_smplHeap);
	cmd->SetGraphicsRootDescriptorTable(2, m_smplHeap->GetGPUDescriptorHandleForHeapStart());//offset 2 for the 3rd element in the table

	// Render the triangle.
	cmd->DrawIndexedInstanced(indexCount, 1, 0, 0, 0);

	return;
}

Textureclass.h

The TextureClass encapsulates the loading, unloading, and accessing of a single texture resource. For each texture needed an object of this class must be instantiated.


// Filename: textureclass.h

#ifndef _TEXTURECLASS_H_
#define _TEXTURECLASS_H_


//
// INCLUDES //
//
#pragma once
#include <d3d12.h>
#include <stdio.h>



// Class name: TextureClass

class TextureClass
{
private:

We define the targa file header structure here to make reading in the data easier.

	struct TargaHeader
	{
		unsigned char data1[12];
		unsigned short width;
		unsigned short height;
		unsigned char bpp;
		unsigned char data2;
	};

public:
	TextureClass();
	TextureClass(const TextureClass&);
	~TextureClass();

	bool Initialize(ID3D12Device*, ID3D12GraphicsCommandList*, const char*);
	void Shutdown();

	ID3D12Resource* GetTexture();

private:

Here we have our targa reading function. If you wanted to support more formats you would add reading functions here.

	bool LoadTarga(const char*, int&, int&);

private:

This class has five member variables. The first one holds the raw targa data read straight in from the file. The second and the third variables are for the heaps. The fourth is the upload cache. And the last called m_texture will hold the structured texture data that DirectX will use for rendering.

        unsigned char* m_targaData;
	ID3D12Heap* m_heapUpload;
	ID3D12Heap* m_heapTexture;
	ID3D12Resource* m_cacheTexture;
	ID3D12Resource* m_texture;
	
};

#endif

Textureclass.cpp


// Filename: textureclass.cpp

#include "textureclass.h"

Initialize the three pointers to null in the class constructor.

TextureClass::TextureClass()
{
	m_targaData = 0;
	m_heapUpload = 0;
	m_heapTexture = 0;
	m_cacheTexture = 0;
	m_texture = 0;
}


TextureClass::TextureClass(const TextureClass& other)
{
}


TextureClass::~TextureClass()
{
}

The Initialize functions take as input the Direct3D device and the name of the targa image file. It will first load the targa data into an array. Then it will create a texture and load the targa data into it in the correct format (targa images are upside by default and need to be reversed). Then once the texture is loaded it will create a resource view of the texture for the shader to use for drawing.

bool TextureClass::Initialize(ID3D12Device* device, ID3D12GraphicsCommandList* cmd, const char* filename)
{
	bool result;
	int height, width;
	D3D12_HEAP_DESC hpDesc;
	D3D12_RESOURCE_DESC	resourceDesc = {};
	D3D12_PLACED_SUBRESOURCE_FOOTPRINT Srfp = {};
	D3D12_RESOURCE_BARRIER barrier = {};
	HRESULT hResult;
	unsigned int rowPitch;

So first we call the TextureClass::LoadTarga function to load the targa file into the m_targaData array. This function will also pass us back the height and width of the texture.

        // Load the targa image data into memory.
	result = LoadTarga(filename, height, width);
	if (!result)
	{
		return false;
	}

Then we need to make sure some values for the texture. In this tutorial, we need the pitch and the size of the raw data in the texture. In DirectX12, texture is stored by "slices" in the video memory where a slice is smply a row of pixels from a picture. But you must make sure that the slice is aligned to 256 bytes each. The length in bytes is called pitch.

        UINT64 pitch = (width * 4 /256+1)*256;//pitch of a slice should be aligned to 256
	UINT64 textureHeapSize = pitch * (height - 1) + width * 4;//last row is the image pitch

The texture size is used for the heap and the resource for upload.

        //Create an upload heap
	hpDesc.SizeInBytes = textureHeapSize;//For an upload heap, the row pitch must be 256 byte aligned for every row, except for the very last row.
	hpDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
	hpDesc.Properties.Type = D3D12_HEAP_TYPE_CUSTOM;//crash at default
	hpDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE;
	hpDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_L0;
	hpDesc.Properties.CreationNodeMask = 0;
	hpDesc.Properties.VisibleNodeMask = 0;
	hpDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;

Here, we only create a generic heap for generic data buffer since they only work for uploading. 

        // Setup the description of the texture.
	resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
	resourceDesc.Alignment = 0; // may be 0, 4KB, 64KB, or 4MB. 0 will let runtime decide between 64KB and 4MB (4MB for multi-sampled textures)
	resourceDesc.Width = textureHeapSize; // buffer width is only the length
	resourceDesc.Height = 1; // height of buffer is always 1
	resourceDesc.DepthOrArraySize = 1; // if 3d image, depth of 3d image. Otherwise an array of 1D or 2D textures (we only have one image, so we set 1)
	resourceDesc.MipLevels = 1; // Number of mipmaps. We are not generating mipmaps for this texture, so we have only one level
	resourceDesc.Format = DXGI_FORMAT_UNKNOWN; // This is the dxgi format of the image (format of the pixels)
	resourceDesc.SampleDesc.Count = 1; // This is the number of samples per pixel, we just want 1 sample
	resourceDesc.SampleDesc.Quality = 0; // The quality level of the samples. Higher is better quality, but worse performance
	resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; // The arrangement of the pixels. Setting to unknown lets the driver choose the most efficient one
	resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
	
	// Create the upload buffer.
	hResult = device->CreatePlacedResource(m_heapUpload, 0, &resourceDesc
		, D3D12_RESOURCE_STATE_COPY_DEST
		, nullptr
		, IID_PPV_ARGS(&(m_cacheTexture)));
	if (FAILED(hResult))
	{
		return false;
	}

We need to carefully design the default buffer heap for the real texture. The Flags should be set as the texture resource.

        //Modify the heap desc
	hpDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE;//CPU not accessible
	hpDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_L1;//L1 for video memory
	hpDesc.Flags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_BUFFERS;

	//Create the default texture heap, using either D3D12_RESOURCE_DESC or CD3DX12_RESOURCE_DESC::Tex2D
	hResult = device->CreateHeap(&hpDesc, __uuidof(ID3D12Heap), (void**)&m_heapTexture);
	if (FAILED(hResult))
	{
		return false;
	}

Next we need to modify our description of the DirectX texture resource that we will load the targa data into. We use the height and width from the targa image data, and set the format to be a 32 bit RGBA texture. We set the Dimension to a texture 2D dimension. And finally we set the Layout to unknown to be determined by the GPU. If the fields are mismatched there would be an error triggered on the debug layer. Once the description is complete we call CreatePlacedResource to create an empty texture for us. The next step will be to copy the targa data into that empty texture.

        resourceDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	resourceDesc.Alignment = 0; // may be 0, 4KB, 64KB, or 4MB. 0 will let runtime decide between 64KB and 4MB (4MB for multi-sampled textures)
	resourceDesc.Width = width; // width of the texture
	resourceDesc.Height = height; // height of the texture
	resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;

        //Create the empty texture
	hResult = device->CreatePlacedResource(m_heapTexture, 0, &resourceDesc
		, D3D12_RESOURCE_STATE_COPY_DEST
		, nullptr
		, IID_PPV_ARGS(&(m_texture)));
	if (FAILED(hResult))
	{
		return false;
	}

The next we need to do is to copy the upload cache to the empty texture. This is a little complicated in DirectX12. First, the region of the texture should be determined. This region is call “footprint” in DirectX, which contains the format, width, height, pitch, and depth of the image. Therefore, we will retrieve these values next by calling GetCopyableFootprints.

        //Get info about the region we are going to copy
	UINT   nSubRes = 1;
	UINT   ntRow = 0;
	UINT64 ntRowSize = 0;
	UINT64 txulBufSize = 0;
	device->GetCopyableFootprints(&resourceDesc, 0, nSubRes, 0, &Srfp, &ntRow, &ntRowSize, &txulBufSize);

	// Set the row pitch of the targa image data.
	rowPitch = (width * 4) * sizeof(unsigned char);

Then we can copy the raw data to the buffer for upload. Here we have to copy the pixels by rows/slices due to alignment.

        //[begin] copy texture to the upload buffer
	BYTE* pData = nullptr;
	m_cacheTexture->Map(0, NULL, reinterpret_cast<void**>(&pData));//Get buffer pointer
	BYTE* pDestSlice = reinterpret_cast<BYTE*>(pData) + Srfp.Offset;//every row of pixels is taken as a slice
	const BYTE* pSrcSlice = reinterpret_cast<const BYTE*> (m_targaData);
	
	// Copy the targa image data into the texture.
	for (UINT y = 0; y < ntRow; ++y)
	{
		memcpy(pDestSlice + static_cast<SIZE_T>(Srfp.Footprint.RowPitch)* y, pSrcSlice + static_cast<SIZE_T>(width * 4)* y, width* 4);
	}
	//[end] copy texture to the upload buffer

Next, we set up a barrier to alter the attributive of the resource after unlocking it.

        m_cacheTexture->Unmap(0, NULL);

	barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	barrier.Transition.pResource = m_cacheTexture;
	barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_GENERIC_READ;
	barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
	barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;

	// Now we copy the upload buffer contents to the default heap
	cmd->ResourceBarrier(1, &barrier);

Before we uploading the texture, we need to specify the source and dest regions to copy, and also the ways of copying. We can copy the data by either index or footprint, which is based on our cache and texture buffer structures.

        //Specify copy dst by subresource index
	D3D12_TEXTURE_COPY_LOCATION cDst;
	cDst.pResource = m_texture;
	cDst.PlacedFootprint = {};
	cDst.SubresourceIndex = 0;
	cDst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;

	//Specify copy src by footprint
	D3D12_TEXTURE_COPY_LOCATION cSrc;
	cSrc.pResource = m_cacheTexture;
	cSrc.PlacedFootprint = Srfp;
	cSrc.SubresourceIndex = 0;
	cSrc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;

	cmd->CopyTextureRegion(&cDst, 0, 0, 0, &cSrc, nullptr);

Finally, we set the barrier to change our copy dest to texture buffer.

        barrier.Transition.pResource = m_texture;
	barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
	barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
	cmd->ResourceBarrier(1, &barrier);

We are no  longer using the image data, so delete it.

        // Release the targa image data now that the image data has been loaded into the texture.
	delete[] m_targaData;
	m_targaData = 0;

	return true;
}

The Shutdown function releases the texture data and the five pointers are set to null.

void TextureClass::Shutdown()
{
	// Release the texture cache.
	if (m_cacheTexture)
	{
		m_cacheTexture->Release();
		m_cacheTexture = 0;
	}

	// Release the texture.
	if (m_texture)
	{
		m_texture->Release();
		m_texture = 0;
	}

	// Release the upload heap.
	if (m_heapUpload)
	{
		m_heapUpload->Release();
		m_heapUpload = 0;
	}
	//release the texture heap
	if (m_heapTexture)
	{
		m_heapTexture ->Release();
		m_heapTexture = 0;
	}

	// Release the targa data.
	if (m_targaData)
	{
		delete[] m_targaData;
		m_targaData = 0;
	}

	return;
}

GetTexture is a helper function to provide easy access to the texture view for any shaders that require it for rendering.

ID3D12Resource* TextureClass::GetTexture()
{
	return m_texture;
}

This is our targa image loading function. Once again note that targa images are stored upside down and need to be flipped before using. So here we will open the file, read it into an array, and then take that array data and load it into the m_targaData array in the correct order. Note we are purposely only dealing with 32 bit targa files that have alpha channels, this function will reject targa's that are saved as 24 bit.

bool TextureClass::LoadTarga(const char* filename, int& height, int& width)
{
	int error, bpp, imageSize, index, i, j, k;
	FILE* filePtr;
	unsigned int count;
	TargaHeader targaFileHeader;
	unsigned char* targaImage;


	// Open the targa file for reading in binary.
	error = fopen_s(&filePtr, filename, "rb");
	if (error != 0)
	{
		return false;
	}

	// Read in the file header.
	count = (unsigned int)fread(&targaFileHeader, sizeof(TargaHeader), 1, filePtr);
	if (count != 1)
	{
		return false;
	}

	// Get the important information from the header.
	height = (int)targaFileHeader.height;
	width = (int)targaFileHeader.width;
	bpp = (int)targaFileHeader.bpp;

	// Check that it is 32 bit and not 24 bit.
	if (bpp != 32)
	{
		return false;
	}

	// Calculate the size of the 32 bit image data.
	imageSize = width * height * 4;

	// Allocate memory for the targa image data.
	targaImage = new unsigned char[imageSize];
	if (!targaImage)
	{
		return false;
	}

	// Read in the targa image data.
	count = (unsigned int)fread(targaImage, 1, imageSize, filePtr);
	if (count != imageSize)
	{
		return false;
	}

	// Close the file.
	error = fclose(filePtr);
	if (error != 0)
	{
		return false;
	}

	// Allocate memory for the targa destination data.
	m_targaData = new unsigned char[imageSize];
	if (!m_targaData)
	{
		return false;
	}

	// Initialize the index into the targa destination data array.
	index = 0;

	// Initialize the index into the targa image data.
	k = (width * height * 4) - (width * 4);

	// Now copy the targa image data into the targa destination array in the correct order since the targa format is stored upside down.
	for (j = 0; j < height; j++)
	{
		for (i = 0; i < width; i++)
		{
			m_targaData[index + 0] = targaImage[k + 2];  // Red.
			m_targaData[index + 1] = targaImage[k + 1];  // Green.
			m_targaData[index + 2] = targaImage[k + 0];  // Blue
			m_targaData[index + 3] = targaImage[k + 3];  // Alpha

			// Increment the indexes into the targa data.
			k += 4;
			index += 4;
		}

		// Set the targa image data index back to the preceding row at the beginning of the column since its reading it in upside down.
		k -= (width * 8);
	}

	// Release the targa image data now that it was copied into the destination array.
	delete[] targaImage;
	targaImage = 0;

	return true;
}

Modelclass.h

The ModelClass has changed since the previous tutorial so that it can now accommodate texturing.


// Filename: modelclass.h

#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//
// INCLUDES //
//
#pragma once
#include <d3d12.h>
#include <directxmath.h>
using namespace DirectX;

The TextureClass header is now included in the ModelClass header.

///
// MY CLASS INCLUDES //
///
#include "textureclass.h"



// Class name: ModelClass

class ModelClass
{
private:

The VertexType has replaced the color component with a texture coordinate component. The texture coordinates are now replacing the green color that was used in the previous tutorial.

        struct VertexType
	{
		XMFLOAT3 position;
		XMFLOAT2 texture;
	};

public:
	ModelClass();
	ModelClass(const ModelClass&);
	~ModelClass();

	bool Initialize(ID3D12Device*, ID3D12GraphicsCommandList*, const char*);
	void Shutdown();
	void Render(ID3D12GraphicsCommandList*);

	int GetIndexCount();

The ModelClass now has a GetTexture function so it can pass its own texture resource to shaders that will draw this model.

	ID3D12Resource* GetTexture();

private:
	bool InitializeBuffers(ID3D12Device*, ID3D12GraphicsCommandList*);
	void ShutdownBuffers();
	void RenderBuffers(ID3D12GraphicsCommandList* );

ModelClass also now has both a private LoadTexture and ReleaseTexture for loading and releasing the texture that will be used to render this model.

        bool LoadTexture(ID3D12Device*, ID3D12GraphicsCommandList*, const char*);
	void ReleaseTexture();

private:
	ID3D12Heap* m_heapUpload, * m_heapDefault;
	ID3D12Resource *m_vertexBuffer, *m_indexBuffer, *m_vertexUpload, *m_indexUpload;
	int m_vertexCount, m_indexCount;

The m_Texture variable is used for loading, releasing, and accessing the texture resource for this model.

        TextureClass* m_Texture;
};

#endif

Modelclass.cpp


// Filename: modelclass.cpp

#include "modelclass.h"


ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_vertexUpload = 0;
	m_indexUpload = 0;
	m_vertexCount = 0;
	m_indexCount = 0;

The class constructor now initializes the new texture object to null.

        m_Texture = 0;
}


ModelClass::ModelClass(const ModelClass& other)
{
}


ModelClass::~ModelClass()
{
}

Initialize now takes as input the file name of the texture that the model will be using as well as the command list.

bool ModelClass::Initialize(ID3D12Device* device, ID3D12GraphicsCommandList* cmd, const char* textureFilename)
{
	bool result;


	// Initialize the vertex and index buffers.
	result = InitializeBuffers(device, cmd);
	if(!result)
	{
		return false;
	}

The Initialize function calls a new private function that will load the texture.

        // Load the texture for this model.
	result = LoadTexture(device, cmd, textureFilename);
	if (!result)
	{
		return false;
	}

	return true;
}


void ModelClass::Shutdown()
{

The Shutdown function now calls the new private function to release the texture object that was loaded during initialization.

	// Release the model texture.
	ReleaseTexture();

	// Shutdown the vertex and index buffers.
	ShutdownBuffers();

	return;
}


void ModelClass::Render(ID3D12GraphicsCommandList* cmd)
{
	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers(cmd);

	return;
}


int ModelClass::GetIndexCount()
{
	return m_indexCount;
}

GetTexture is a new function that returns the model texture resource. The texture shader will need access to this texture to render the model.

ID3D12Resource* ModelClass::GetTexture()
{
	return m_Texture->GetTexture();
}


bool ModelClass::InitializeBuffers(ID3D12Device* device, ID3D12GraphicsCommandList* cmd)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D12_HEAP_DESC heapDesc;
	D3D12_RESOURCE_DESC bufferDesc;
	DXGI_SAMPLE_DESC sDesc;
	HRESULT result;
	D3D12_RESOURCE_BARRIER barrier = {};
	// Set the number of vertices in the vertex array.
	m_vertexCount = 3;

	// Set the number of indices in the index array.
	m_indexCount = 3;

	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Create the index array.
	indices = new unsigned long[m_indexCount];
	if(!indices)
	{
		return false;
	}

The vertex array now has a texture coordinate component instead of a color component. The texture vector is always U first and V second. For example the first texture coordinate is bottom left of the triangle which corresponds to U 0.0, V 1.0. Use the diagram at the top of this page to figure out what your coordinates need to be. Note that you can change the coordinates to map any part of the texture to any part of the polygon face. In this tutorial I'm just doing a direct mapping for simplicity reasons.

// Load the vertex array with data.
	vertices[0].position = XMFLOAT3(-1.0f, -1.0f, 0.0f);  // Bottom left.
	vertices[0].texture = XMFLOAT2(0.0f, 1.0f);

	vertices[1].position = XMFLOAT3(0.0f, 1.0f, 0.0f);  // Top middle.
	vertices[1].texture = XMFLOAT2(0.5f, 0.0f);

	vertices[2].position = XMFLOAT3(1.0f, -1.0f, 0.0f);  // Bottom right.
	vertices[2].texture = XMFLOAT2(1.0f, 1.0f);

	// Load the index array with data.
	indices[0] = 0;  // Bottom left.
	indices[1] = 1;  // Top middle.
	indices[2] = 2;  // Bottom right.

	// Set up the description of the upload heap.
	heapDesc.SizeInBytes = ((sizeof(VertexType) * m_vertexCount)/65536+1)*65536 + sizeof(unsigned long)* m_indexCount;//we will put both vertex and index buffer together on the upload heap, so we need to align the data to 64k bytes
	heapDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
	heapDesc.Properties.Type = D3D12_HEAP_TYPE_CUSTOM;//We need a customized heap for the resources
	heapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE;
	heapDesc.Properties.CreationNodeMask = 0;
	heapDesc.Properties.VisibleNodeMask = 0;
	heapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_L0;//Use system memory
	heapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;

	//Create the heap for our vertex buffer
	result = device->CreateHeap(&heapDesc, __uuidof(ID3D12Heap), (void**)&m_heapUpload);
	if (FAILED(result))
	{
		return false;
	}
	//Setup the description for the sample desc
	sDesc.Count = 1;
	sDesc.Quality = 0;
	//Setup the description for the vertex buffer
	bufferDesc.Alignment = 65536;//alignment to 64k for more than one resource on the heap
	bufferDesc.DepthOrArraySize = 1;
	bufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
	bufferDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
	bufferDesc.Format = DXGI_FORMAT_UNKNOWN;
	bufferDesc.Height = 1;
	bufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
	bufferDesc.MipLevels = 1;
	bufferDesc.SampleDesc = sDesc;
	bufferDesc.Width = sizeof(VertexType) * m_vertexCount;
	
	//Now create the resources
	result = device->CreatePlacedResource(m_heapUpload, 0, &bufferDesc
		, D3D12_RESOURCE_STATE_GENERIC_READ
		, nullptr
		, IID_PPV_ARGS(&(m_vertexUpload)));
	if (FAILED(result))
	{
		return false;
	}
	bufferDesc.Alignment = 0;
	bufferDesc.Width = sizeof(unsigned long) * m_indexCount;//Set size to index buffer size
	result = device->CreatePlacedResource(m_heapUpload
		, 65536//offset for 64k alignment
		, &bufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr
		, IID_PPV_ARGS(&(m_indexUpload)));
	if (FAILED(result))
	{
		return false;
	}
	//Change the description for a default heap on GPU side
	heapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE;//CPU cannot get access to the VRAM
	heapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_L1;//L1 for VRam

	//Now create the default heap and buffers
	result = device->CreateHeap(&heapDesc, __uuidof(ID3D12Heap), (void**)&m_heapDefault);
	if (FAILED(result))
	{
		return false;
	}
	//Now create the resources
	bufferDesc.Alignment = 0;
	bufferDesc.Width = sizeof(VertexType) * m_vertexCount;//Set size to index buffer size
	result = device->CreatePlacedResource(m_heapDefault, 0, &bufferDesc
		, D3D12_RESOURCE_STATE_COPY_DEST
		, nullptr
		, IID_PPV_ARGS(&(m_vertexBuffer)));
	if (FAILED(result))
	{
		return false;
	}
	bufferDesc.Alignment = 0;//This is the last resource, then set alignment to 0
	bufferDesc.Width = sizeof(unsigned long) * m_indexCount;//Set size to index buffer size
	result = device->CreatePlacedResource(m_heapDefault
		, 65536//offset for 64k alignment
		, &bufferDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr
		, IID_PPV_ARGS(&(m_indexBuffer)));
	if (FAILED(result))
	{
		return false;
	}
	//Copy the vertex and index data to the buffers
	//vertices can only go into upload resource
	//[begin] copy vertices
	void* pvBegin = nullptr;
	m_vertexUpload->Map(0, nullptr, &pvBegin);
	memcpy(pvBegin, vertices, sizeof(VertexType) * m_vertexCount);
	m_vertexUpload->Unmap(0, nullptr);
	//[end] copy vertices

	//[begin] copy indices
	m_indexUpload->Map(0, nullptr, &pvBegin);
	memcpy(pvBegin, indices, sizeof(unsigned long) * m_indexCount);
	m_indexUpload->Unmap(0, nullptr);
	//[end] copy indices
	//Copy to the default heap
	//barrier.Aliasing;
	barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	barrier.Transition.pResource = m_vertexBuffer;
	barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
	barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
	barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	//barrier.UAV;
	cmd->CopyBufferRegion(m_vertexBuffer, 0, m_vertexUpload, 0, sizeof(VertexType) * m_vertexCount);
	cmd->ResourceBarrier(1, &barrier);
	cmd->CopyBufferRegion(m_indexBuffer, 0, m_indexUpload, 0, sizeof(unsigned long) * m_indexCount);
	barrier.Transition.pResource = m_indexBuffer;
	barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_INDEX_BUFFER;
	cmd->ResourceBarrier(1, &barrier);
	//Set up the views
	// Release the arrays now that the vertex and index buffers have been created and loaded.
	delete [] vertices;
	vertices = 0;

	delete [] indices;
	indices = 0;

	return true;
}


void ModelClass::ShutdownBuffers()
{
	// Release the index buffer.
	if(m_indexBuffer)
	{
		m_indexBuffer->Release();
		m_indexBuffer = 0;
	}

	// Release the vertex buffer.
	if(m_vertexBuffer)
	{
		m_vertexBuffer->Release();
		m_vertexBuffer = 0;
	}
	if(m_vertexUpload)
	{
		m_vertexUpload->Release();
		m_vertexUpload = 0;
	}
	if(m_indexUpload)
	{
		m_indexUpload->Release();
		m_indexUpload = 0;
	}
	//Release the heap
	if (m_heapUpload)
	{
		m_heapUpload->Release();
		m_heapUpload = 0;
	}
	//Release the heap
	if (m_heapDefault)
	{
		m_heapDefault->Release();
		m_heapDefault = 0;
	}
	
	return;
}


void ModelClass::RenderBuffers(ID3D12GraphicsCommandList* cmd)
{
	D3D12_VERTEX_BUFFER_VIEW vbView;
	D3D12_INDEX_BUFFER_VIEW idView;
	// Set vertex buffer stride and offset.
	vbView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
	vbView.SizeInBytes = sizeof(VertexType) * m_vertexCount;
	vbView.StrideInBytes = sizeof(VertexType);
	// Set vertex buffer stride and offset.
	idView.BufferLocation = m_indexBuffer->GetGPUVirtualAddress();
	idView.Format = DXGI_FORMAT_R32_UINT;
	idView.SizeInBytes = sizeof(unsigned long) * m_indexCount;
	// Set the vertex buffer to active in the input assembler so it can be rendered.
	cmd->IASetVertexBuffers(0, 1, &vbView);

    // Set the index buffer to active in the input assembler so it can be rendered.
	cmd->IASetIndexBuffer(&idView);

    // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return;
}

LoadTexture is a new private function that will create the texture object and then initialize it with the input file name provided. This function is called during initialization.

bool ModelClass::LoadTexture(ID3D12Device* device, ID3D12GraphicsCommandList* cmd, const char* filename)
{
	bool result;


	// Create the texture object.
	m_Texture = new TextureClass;
	if (!m_Texture)
	{
		return false;
	}

	// Initialize the texture object.
	result = m_Texture->Initialize(device, cmd, filename);
	if (!result)
	{
		return false;
	}

	return true;

}

The ReleaseTexture function will release the texture object that was created and loaded during the LoadTexture function.

void ModelClass::ReleaseTexture()
{
	// Release the texture object.
	if (m_Texture)
	{
		m_Texture->Shutdown();
		delete m_Texture;
		m_Texture = 0;
	}

	return;
}

Graphicsclass.h


// Filename: graphicsclass.h

#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///
// MY CLASS INCLUDES //
///
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"

The GraphicsClass now includes the new TextureShaderClass header and the ColorShaderClass header has been removed.

#include "textureshaderclass.h"


/
// GLOBALS //
/
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;



// Class name: GraphicsClass

class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;

A new TextureShaderClass private object has been added.

        TextureShaderClass* m_TextureShader;
};

#endif

Graphicsclass.cpp


// Filename: graphicsclass.cpp

#include "graphicsclass.h"

The m_TextureShader variable is set to null in the constructor.

GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;
	m_TextureShader = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;


	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_ICONSTOP);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Set the initial position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
	
	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}

The ModelClass::Initialize function now takes in the name of the texture that will be used for rendering the model.

	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), m_D3D->GetCommandList(), "stone01.tga");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_ICONSTOP);
		return false;
	}

	// Create the color shader object.
	m_TextureShader = new TextureShaderClass;
	if(!m_TextureShader)
	{
		return false;
	}

	// Initialize the color shader object.
	result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd, m_Model->GetTexture());//We have to store the texture for future use
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_ICONSTOP);
		return false;
	}

	// Update signature and PSO
	m_D3D->SetRootSignature(m_TextureShader->GetSignature());
	m_D3D->SetRenderState(m_TextureShader->GetPSO());

	//Finish the initialization stage
	result = m_D3D->FinishUp();
	if (!result)
	{
		MessageBox(hwnd, L"Could not finish initialization D3D.", L"Error", MB_ICONSTOP);
		return false;
	}
	return true;
}


void GraphicsClass::Shutdown()
{
	// Release the color shader object.
	if (m_TextureShader)
	{
		m_TextureShader->Shutdown();
		delete m_TextureShader;
		m_TextureShader = 0;
	}

	// Release the model object.
	if(m_Model)
	{
		m_Model->Shutdown();
		delete m_Model;
		m_Model = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the D3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Frame()
{
	bool result;


	// Render the graphics scene.
	result = Render();
	if(!result)
	{
		return false;
	}

	return true;
}


bool GraphicsClass::Render()
{
	XMMATRIX worldMatrix, viewMatrix, projectionMatrix;
	bool result;


	// Clear the buffers to begin the scene.
	result = m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
	if (!result)
	{
		return false;
	}
	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, and projection matrices from the camera and d3d objects.
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetCommandList());

The texture shader is called now instead of the color shader to render the model. Notice it also takes the texture resource pointer from the model so the texture shader has access to the texture from the model object.

        // Render the model using the color shader.
	result = m_TextureShader->Render(m_D3D->GetCommandList(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
	if(!result)
	{
		return false;
	}

	// Present the rendered scene to the screen.
	result = m_D3D->EndScene();
	if (!result)
	{
		return false;
	}
	return true;
}

Summary

You should now understand the basics of loading a texture, mapping it to a polygon face, and then rendering it with a shader.

 

To Do Exercises

1. Re-compile the code and ensure that a texture mapped triangle does appear on your screen. Press escape to quit once done.

2. Create your own tga texture and place it in the same directory with stone01.tga. Inside the GraphicsClass::Initialize function change the model initialization to have your texture name and then re-compile and run the program.

3. Change the code to create two triangles that form a square. Map the entire texture to this square so that the entire texture shows up correctly on the screen.

4. Move the camera to different distances to see the effect of the MIN_MAG_MIP_LINEAR filter.

5. Try some of the other filters and move the camera to different distances to see the different results.

 

Source Code

Source Code and Data Files: dx12src05.zip

Executable: Not provided

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值