Tutorial 6: Diffuse Lighting

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

In this tutorial I will cover how to light 3D objects using diffuse lighting and DirectX 12. We will start with the code from the previous tutorial and modify it. 


The type of diffuse lighting we will be implementing is called directional lighting. Directional lighting is similar to how the Sun illuminates the Earth. It is a light source that is a great distance away and based on the direction it is sending light you can determine the amount of light on any object. However unlike ambient lighting (another lighting model we will cover soon) it will not light up surfaces it does not directly touch. 


I picked directional lighting to start with because it is very easy to debug visually. Also since it only requires a direction the formula is simpler than the other types of diffuse lighting such as spot lights and point lights.
The implementation of diffuse lighting in DirectX 12 is done with both vertex and pixel shaders. Diffuse lighting requires just the direction and a normal vector for any polygons that we want to light. The direction is a single vector that you define, and you can calculate the normal for any polygon by using the three vertices that compose the polygon. In this tutorial we will also implement the color of the diffuse light in the lighting equation.

 

Framework

For this tutorial we will create a new class called LightClass which will represent the light sources in the scenes. LightClass won't actually do anything other than hold the direction and color of the light. We will also remove the TextureShaderClass and replace it with LightShaderClass which handles the light shading on the model. With the addition of the new classes the frame work now looks like the following:

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

We will start the code section by looking at the HLSL light shader. You will notice that the light shader is just an updated version of the texture shader from the previous tutorial.

 

Light.vs


// Filename: light.hlsl



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

Both structures now have a 3 float normal vector. The normal vector is used for calculating the amount of light by using the angle between the direction of the normal and the direction of the light.

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

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



// Vertex Shader

PixelInputType LightVertexShader(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);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;

The normal vector for this vertex is calculated in world space and then normalized before being sent as input into the pixel shader.

    // Calculate the normal vector against the world matrix only.
    output.normal = mul(input.normal, (float3x3)worldMatrix);
	
    // Normalize the normal vector.
    output.normal = normalize(output.normal);

    return output;
}

Light.ps


// Filename: light.hlsl



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

We have two new global variables inside the LightBuffer that hold the diffuse color and the direction of the light. These two variables will be set from values in the new LightClass object.

cbuffer LightBuffer
{
    float4 diffuseColor;
    float3 lightDirection;
};


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



// Pixel Shader

float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;
    float3 lightDir;
    float lightIntensity;
    float4 color;


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

This is where the lighting equation that was discussed earlier is now implemented. The light intensity value is calculated as the dot product between the normal vector of triangle and the light direction vector.

    // Invert the light direction for calculations.
    lightDir = -lightDirection;

    // Calculate the amount of light on this pixel.
    lightIntensity = saturate(dot(input.normal, lightDir));

And finally the diffuse value of the light is combined with the texture pixel value to produce the color result.

    // Determine the final amount of diffuse color based on the diffuse color combined with the light intensity.
    color = saturate(diffuseColor * lightIntensity);
    // Multiply the texture pixel and the final diffuse color to get the final pixel color result.
    color = color * textureColor;

    return color;
}

Lightshaderclass.h

The new LightShaderClass is just the TextureShaderClass from the previous tutorials re-written slightly to incorporate lighting.


// Filename: lightshaderclass.h

#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_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: LightShaderClass

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

The new LightBufferType structure will be used for holding lighting information. This typedef is the same as the new typedef in the pixel shader as a constant buffer. The device will be removed if we used a sizeof(LightBufferType) because it requires sizes for a constant buffer that are a multiple of 256 to succeed. When we are going to specify the size, we will calculate the size so that it aligns to 256 bytes.

	struct LightBufferType
	{
		XMFLOAT4 diffuseColor;
		XMFLOAT3 lightDirection;
	};

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

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

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

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

private:
	ID3D12DescriptorHeap* m_scvHeap;//3 descs on 1 heap
	ID3D12DescriptorHeap* m_smplHeap;//1 sampler heap
	ID3D12Resource* m_matrixBuffer;

There is a new private constant buffer for the light information (color and direction). The light buffer will be used by this class to set the global light variables inside the HLSL pixel shader.

	ID3D12Resource* m_lightBuffer;
	ID3D12RootSignature* m_prtSignature;
	ID3D12PipelineState* m_pipelineState;
	ID3D12Resource* m_comTexture;
	void* m_pDataPtr;

And we also have a pointer to the locked memory for copying data every frame.

	void* m_pLightPtr;
};
#endif

Lightshaderclass.cpp

///
// Filename: lightshaderclass.cpp

#include "LightShaderClass.h"

LightShaderClass::LightShaderClass()
{
	m_matrixBuffer = 0;
	m_lightBuffer = 0;
	m_scvHeap = 0;
	m_prtSignature = 0;
	m_pipelineState = 0;
	m_smplHeap = 0;
	m_comTexture = 0;
}

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

LightShaderClass::~LightShaderClass()
{
}

bool LightShaderClass::Initialize(ID3D12Device* device, HWND hwnd, ID3D12Resource* texture, const WCHAR* filename)
{
	bool result;

The new light.hlsl shader file is used as input to initialize the light shader.

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

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

	return true;
}

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

	return;
}

The Render function now takes in the light direction and light diffuse color as inputs. These variables are then sent into the SetShaderParameters function and finally set inside the shader itself.

bool LightShaderClass::Render(ID3D12GraphicsCommandList* cmd, int indexCount, XMMATRIX worldMatrix, XMMATRIX viewMatrix,
		XMMATRIX projectionMatrix, XMFLOAT3 lightDirection, XMFLOAT4 diffuseColor)
{
	bool result;


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

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

	return true;
}

bool LightShaderClass::InitializeShader(ID3D12Device* device, HWND hwnd, const WCHAR* filename)
{
	HRESULT result;
	ID3DBlob* errorMessage;
	ID3DBlob* vertexShaderBuffer;
	ID3DBlob* pixelShaderBuffer;
	ID3DBlob* pSignatureBlob;

The polygonLayout variable has been changed to have three elements instead of two. This is so that it can accommodate a normal vector in the layout.

        D3D12_INPUT_ELEMENT_DESC polygonLayout[3];
	unsigned int numElements;
	D3D12_HEAP_PROPERTIES heapp = {};
	D3D12_RESOURCE_DESC bufferDesc;
	D3D12_DESCRIPTOR_HEAP_DESC scvHeapDesc;

We also add a bunch of new description and parameter variables for the light constant buffer and the buffer views.

	D3D12_DESCRIPTOR_HEAP_DESC smplHeapDesc;//New heap desc for the sampler
	D3D12_DESCRIPTOR_RANGE1 cbvRange[4];//New ranges for 3 descs
	D3D12_ROOT_DESCRIPTOR_TABLE1 descTable;
	D3D12_ROOT_PARAMETER1  rootParameters[4];//New parameters for three descs
	D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};//New desc for the texture resource
	D3D12_CONSTANT_BUFFER_VIEW_DESC constantBufferDesc = {};
	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 light vertex shader.

        // Compile the vertex shader code.
	result = D3DCompileFromFile(filename, NULL, NULL, "LightVertexShader", "vs_5_0", 0, 0, &vertexShaderBuffer, &errorMessage);
	if (FAILED(result))
	{
		// If the shader failed to compile it should have written 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;
	}

Load in the new light pixel shader.

        // Compile the pixel shader code.
	result = D3DCompileFromFile(filename, NULL, NULL, "LightPixelShader", "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;
	}


	// 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;

One of the major changes to the shader initialization is here in the polygonLayout. We add a third element for the normal vector that will be used for lighting. The semantic name is NORMAL and the format is the regular DXGI_FORMAT_R32G32B32_FLOAT which handles 3 floats for the x, y, and z of the normal vector. The layout will now match the expected input to the HLSL vertex shader.

        polygonLayout[2].SemanticName = "NORMAL";
	polygonLayout[2].SemanticIndex = 0;
	polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[2].InputSlot = 0;
	polygonLayout[2].AlignedByteOffset = D3D12_APPEND_ALIGNED_ELEMENT;
	polygonLayout[2].InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
	polygonLayout[2].InstanceDataStepRate = 0;

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

	//Fill up a description for the constant buffer heap
	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 constant 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.Count = 1;
	bufferDesc.SampleDesc.Quality = 0;
	bufferDesc.Width = (sizeof(MatrixBufferType) / 256 + 1) * 256; //We calculate the padded value for 256-byte alignment here.

	// Create the matrix 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);

	// Change the size. In this case the same as above. 
	bufferDesc.Width = (sizeof(LightBufferType) / 256 + 1) * 256;

Here we setup the light constant buffer description which will handle the diffuse light color and light direction. Pay attention to the size of the constant buffers, if they are not multiples of 256 you need to pad extra space on by rounding up the value or the D3D12 runtime will fail when executing the command list. In this case the constant buffer is 28 bytes which are far from exceeding 256 bytes.

	// Create the light 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_lightBuffer));
	if (FAILED(result))
	{
		return false;
	}

	// Lock the constant buffer so it can be written to.
	m_lightBuffer->Map(0, nullptr, &m_pLightPtr);
I have changed the NumDescriptors in the heap to 3
// Now describe our descriptor heap
	scvHeapDesc.NumDescriptors = 3;//srv+ 2cbvs, now we have 3 descriptors for constant and texture
	scvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	scvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	scvHeapDesc.NodeMask = 0;

 

When we have 3 descriptors we need to care about the offset of each. We can run CreateConstantBufferView twice to create as many descriptors as we want but just keep this in mind that every offset is 32:

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

	// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
	constantBufferDesc.BufferLocation = m_lightBuffer->GetGPUVirtualAddress();
	constantBufferDesc.SizeInBytes = (sizeof(LightBufferType) / 256 + 1) * 256;

	//Calculate the offset of the desc on the heap
	offset = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
	
	handle.ptr = m_scvHeap->GetCPUDescriptorHandleForHeapStart().ptr + offset * 2;// Now we have 2 descs

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

Now we have 4 parameters, we need to put them in the root signature.

        //Set up the srv cbv range
	for (UINT i = 0; i < 4; ++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].BaseShaderRegister = 1;//We will use register b1 for the light buffer
	cbvRange[3].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
	cbvRange[3].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE;

	//Fill srv cbv desc table
	descTable.NumDescriptorRanges = 1;

	//Fill in root parameters
	for (UINT i = 0; i < 4; ++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
	}

Finally, we can build root signature and the PSO to be used in D3DClass as stated in the previous tutorial.

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

The new light constant buffer is released in the ShutdownShader function.

        if (m_lightBuffer)
	{
		// Unlock the constant buffer.
		m_lightBuffer->Unmap(0, nullptr);
		m_lightBuffer->Release();
		m_lightBuffer = 0;
	}
	// Release the desc heap.
	if (m_scvHeap)
	{
		m_scvHeap->Release();
		m_scvHeap = 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;
}

The SetShaderParameters function now takes in lightDirection and diffuseColor as inputs.

bool LightShaderClass::SetShaderParameters(XMMATRIX worldMatrix, XMMATRIX viewMatrix,
	XMMATRIX projectionMatrix, XMFLOAT3 lightDirection, XMFLOAT4 diffuseColor)
{
	MatrixBufferType cbData;
	LightBufferType ltData;
	// 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));

The light constant buffer is setup the same way as the matrix constant buffer. We have locked the buffer and got a pointer to it. After that we set the diffuse color and light direction using that pointer. Once the data is set we unlock the buffer and then set it in the pixel shader. Note that we set the root parameter to at least D3D12_SHADER_VISIBILITY_PIXEL instead of D3D12_SHADER_VISIBILITY_VERTEX since this is a pixel shader buffer we are setting.

	ltData.diffuseColor = diffuseColor;
	ltData.lightDirection = lightDirection;
	// Copy the light color into the constant buffer.
	memcpy(m_pLightPtr, &ltData, sizeof(LightBufferType));
	return true;
}

void LightShaderClass::RenderShader(ID3D12GraphicsCommandList* cmd, int indexCount)
{
	// Set the texture.
	cmd->SetDescriptorHeaps(1, &m_scvHeap);
	cmd->SetGraphicsRootDescriptorTable(0, m_scvHeap->GetGPUDescriptorHandleForHeapStart());

	//Set the constant
	D3D12_GPU_DESCRIPTOR_HANDLE handle;
	handle.ptr = m_scvHeap->GetGPUDescriptorHandleForHeapStart().ptr + 32;
	cmd->SetDescriptorHeaps(1, &m_scvHeap);
	cmd->SetGraphicsRootDescriptorTable(1, handle);//offset 1 for the 2nd element in the table

	//Set the constant
	handle.ptr = m_scvHeap->GetGPUDescriptorHandleForHeapStart().ptr + 32*2;//the 2nd desc
	cmd->SetDescriptorHeaps(1, &m_scvHeap);
	cmd->SetGraphicsRootDescriptorTable(2, handle);//offset 1 for the 2nd element in the table

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

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

	return;
}

Modelclass.h

The ModelClass has been slightly modified to handle lighting components.


// Filename: modelclass.h

#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


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

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



// Class name: ModelClass

class ModelClass
{
private:
	struct VertexType
	{
		XMFLOAT3 position;
		XMFLOAT2 texture;
		XMFLOAT3 normal;//We added a normal member
	};

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

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

	int GetIndexCount();
	ID3D12Resource* GetTexture();

private:
	bool InitializeBuffers(ID3D12Device*, ID3D12GraphicsCommandList*);
	void ShutdownBuffers();
	void RenderBuffers(ID3D12GraphicsCommandList* );
	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;
	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;
	m_Texture = 0;
}


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


ModelClass::~ModelClass()
{
}


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;
	}

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

	return true;
}


void ModelClass::Shutdown()
{
	// 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;
}

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 only change to the InitializeBuffers function is here in the vertex setup. Each vertex now has normals associated with it for lighting calculations. The normal is a line that is perpendicular to the face of the polygon so that the exact direction the face is pointing can be calculated. For simplicity purposes I set the normal for each vertex along the Z axis by setting each Z component to -1.0f which makes the normal point towards the viewer.

	// 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[0].normal = XMFLOAT3(0.0f, 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[1].normal = XMFLOAT3(0.0f, 0.0f, -1.0f);

	vertices[2].position = XMFLOAT3(1.0f, -1.0f, 0.0f);  // Bottom right.
	vertices[2].texture = XMFLOAT2(1.0f, 1.0f);
	vertices[2].normal = XMFLOAT3(0.0f, 0.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;
}

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;

}

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

	return;
}

Lightclass.h

Now we will look at the new light class which is very simple. Its purpose is only to maintain the direction and color of lights.


// Filename: lightclass.h

#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_


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



// Class name: LightClass

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

	void SetDiffuseColor(float, float, float, float);
	void SetDirection(float, float, float);

	XMFLOAT4 GetDiffuseColor();
	XMFLOAT3 GetDirection();

private:
	XMFLOAT4 m_diffuseColor;
	XMFLOAT3 m_direction;
};

#endif

Lightclass.cpp


// Filename: lightclass.cpp

#include "LightClass.h"

LightClass::LightClass()
{
}

LightClass::LightClass(const LightClass&)
{
}

LightClass::~LightClass()
{
}

void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
	m_diffuseColor.x = red;
	m_diffuseColor.y = green;
	m_diffuseColor.z = blue;
	m_diffuseColor.w = alpha;
}

void LightClass::SetDirection(float x, float y, float z)
{
	m_direction.x = x;
	m_direction.y = y;
	m_direction.z = z;
}

XMFLOAT4 LightClass::GetDiffuseColor()
{
	return m_diffuseColor;
}

XMFLOAT3 LightClass::GetDirection()
{
	return m_direction;
}

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 has two new includes for the LightShaderClass and the LightClass.

#include "lightshaderclass.h"
#include "lightclass.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(float);

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

There are two new private variables for the light shader and the light object.

	LightShaderClass* m_LightShader;//new members
	LightClass* m_Light;
};

#endif

Graphicsclass.cpp


// Filename: graphicsclass.cpp

#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;
	m_LightShader = 0;
	m_Light = 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;
	}

	// 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;
	}

The new light shader object is created and initialized here.

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

	// Initialize the color shader object.
	result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd, m_Model->GetTexture(), L"light.hlsl");//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;
	}

The new light object is created here.


	// Create the light object.
	m_Light = new LightClass;
	if (!m_Light)
	{
		return false;
	}

The color of the light is set to purple and the light direction is set to point down the positive Z axis.

        // Initialize the light object.
	m_Light->SetDiffuseColor(1.0f, 0.0f, 1.0f, 1.0f);
	m_Light->SetDirection(0.0f, 0.0f, 1.0f);

	// Update signature and PSO
	m_D3D->SetRootSignature(m_LightShader->GetSignature());
	m_D3D->SetRenderState(m_LightShader->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 light object.
	if (m_Light)
	{
		delete m_Light;
		m_Light = 0;
	}

	// Release the light shader object.
	if (m_LightShader)
	{
		m_LightShader->Shutdown();
		delete m_LightShader;
		m_LightShader = 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;

We add a new static variable to hold an updated rotation value each frame that will be passed into the Render function.

	static float rotation = 0.0f;


	// Update the rotation variable each frame.
	rotation += (float)XM_PI * 0.01f;
	if (rotation > 360.0f)
	{
		rotation -= 360.0f;
	}
	// Render the graphics scene.
	result = Render(rotation);
	if(!result)
	{
		return false;
	}

	return true;
}


bool GraphicsClass::Render(float rotation)
{
	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);

Here we rotate the world matrix by the rotation value so that when we render the triangle using this updated world matrix it will spin the triangle by the rotation amount.

	// Rotate the world matrix by the rotation value so that the triangle will spin.
	worldMatrix = XMMatrixRotationY(rotation);

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

The light shader is called here to render the triangle. The new light object is used to send the diffuse light color and light direction into the Render function so that the shader has access to those values.

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

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

Summary

With a few changes to the code we were able to implement some basic directional lighting. Make sure you understand how normal vectors work and why they are important to calculating lighting on polygon faces. Note that the back of the spinning triangle will not light up since we have back face culling enabled in our D3DClass.

To Do Exercises

1. Recompile the project and ensure you get a spinning textured triangle that is being illuminated by a purple light. Press escape to quit.

2. Comment out "color = color * textureColor;" in the pixel shader so that the shaderTexture is no longer used and you should see the lighting effect without the texture.

3. Change the color of the light to green at the m_Light->SetDiffuseColor line of code in the GraphicsClass.

4. Change the direction of the light to go down the positive and the negative X axis. You might want to change the speed of the rotation also. 

Source Code

Source Only: dx12src06@github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值