서의 공간

6. Diffuse Lighting 본문

Graphics API/DirectX 11 - Rastertek

6. Diffuse Lighting

홍서의 2021. 1. 7. 06:58

이번 튜토리얼에서는 디퓨즈 라이팅(분산광 조명, diffuse lighting)과 DirectX 11을 사용해서 3D 객체를 비추는 방법을 다룬다. 이전 튜토리얼의 코드에서 시작하여 수정한다.

 

우리가 구현할 디퓨즈 라이팅 유형을 디렉셔널 라이팅(지향광 조명, directional lighting)이라고 한다. 디렉셔널 라이팅은 태양이 지구를 비추는 방식과 유사하다. 먼 거리에 있는 광원이 빛을 보내는 방향에 따라 모든 물체가 받는 빛의 양을 조절할 수 있으며, 광원의 거리가 아주 멀리 있으므로 모든 입사 광선이 서로 평행하다고 간주한다. 또한 앰비언트 라이팅(주변광 조명, ambient lighting)과는 달리 직접 닿지 않는 표면은 비추지 않는다. 앰비언트 라이팅은 곧 다루게 될 예정이다.

 

디렉셔널 라이팅은 시각적으로 디버그 하기 매우 쉽기 때문에 이 조명을 가장 먼저 학습한다. 또한 디렉셔널 라이팅 공식은 방향만 필요하기 때문에 다른 조명 공식(스포트 라이트 및 포인트 라이트)에 비교해 간단하게 표현할 수 있다.

 

DirectX 11의 디퓨즈 라이팅 구현은 정점 및 픽셀 셰이더를 사용하여 수행한다. 디퓨즈 라이팅은 우리가 비추고자 하는 폴리곤의 방향과 노멀 벡터만 있으면 된다. 방향은 단일 벡터로 정의하고, 노멀은 다각형을 구성하는 세 개의 정점을 사용하여 모두 계산할 수 있다. 이 튜토리얼의 조명 방정식에서는 디퓨즈 라이트의 색상도 같이 구현한다.

 

Framework

씬의 광원을 나타내는 LightClass가 추가되었다. LightClass는 실제로 빛의 방향과 색상 데이터를 저장하는 것 외에는 아무것도 하지 않는다. 또한 TextureShaderClass를 제거하고 모델의 빛 음영을 처리하는 LightShaderClass로 대체한다. 다음 프레임워크의 다이어그램을 참고한다.

HLSL 라이트 셰이더에서 시작해보자.


Light.vs

VertexInputType, PixelInputType 구조체는 이제 float3 타입의 노멀 벡터를 갖는다. 노멀 벡터는 노멀 방향과 빛의 방향 사이의 각도를 사용하여 빛의 양을 계산하기 때문에 필수 속성이다.

LightVertexShader()에서 입력의 노멀을 월드 공간 기준으로 변환하고 정규화하여 픽셀 셰이더에 전달한다.

더보기
// GLOBALS
cbuffer MatrixBuffer
{
	matrix worldMatrix;
	matrix viewMatrix;
	matrix projectionMatrix;
};

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

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

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;

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

상수버퍼 LightBuffer는 디퓨즈 색상과 빛 방향 변수를 가진다. 이 두 변수는 새로운 LightClass 객체에서 설정된다.

LightPixelShader()에서 앞에서 논의한 조명 방정식을 적용한다. 빛의 세기는 람베르트 코사인 법칙에 의해 삼각형의 노멀 벡터와 빛 방향벡터 사이의 내적 값으로 계산된다. 마지막 부분에서는 빛의 디퓨즈 값과 텍스처 픽셀 값이 결합하여 최종 색상 결과를 구한다. [추가예정. 자세한 조명 공식은 다음을 참고한다.]

더보기
// GLOBALS
Texture2D shaderTexture;
SamplerState SampleType;

// 라이트 클래스로부터 넘어오는 상수버퍼
cbuffer LightBuffer
{
	float4 diffuseColor;
	float3 lightDirection;
	float padding;
};

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

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

	// Invert the light direction for calculations.
	lightDir = -lightDirection;
	// Calculate the amount of light on this pixel.
	/* 아래 코드에서 노멀벡터와 빛벡터의 내적을 구하고 [0, 1] 구간으로 clamp하는건데
	만약 노멀벡터와 빛벡터 사이의 각이 90도보다 크다면 내적값은 음수가 되므로
	clamp한 결과는 0이 됨(90도이어도 0). 따라서 lightIntensity = 0이고, 빛을 아예 안받는게 된다.
	각이 90도보다 작다면 clamp한 결과는 0보다 크게 된다. 
	각이 작으면 작을 수록 clamp한 결과는 1에 가까워지고, 빛의 세기는 강해진다. */
	lightIntensity = saturate(dot(input.normal, lightDir));
	// 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

LightShaderClass는 조명을 통합하기 위한 클래스이다. 이전 튜토리얼의 TextureShaderClass과 흡사하여 일부 다시 작성할 뿐이다.

새로운 LightBufferType 구조체는 조명 정보를 저장하는 데 사용한다. 이 타입은 픽셀 셰이더의 새로운 구조체 타입과 동일하다. 구조체의 크기가 16바이트의 배수로 만들기 위해 float 타입의 패딩(padding)을 추가했다. 만약 추가 패딩이 없다면 이 구조체의 크기는 28바이트이므로 sizeof(LightBufferType)을 사용하는 경우 CreateBuffer() 함수의 호출이 실패한다. 함수 호출이 성공하려면 16바이트의 배수이어야만 한다. [추가예정. 이 내용에 대해 자세한 사항은 다음을 참고한다.]

더보기
#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_H_

// INCLUDES
#include <d3d11.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#include <fstream>
using namespace DirectX;
using namespace std;

class LightShaderClass
{
private:
	struct MatrixBufferType
	{
		XMMATRIX world;
		XMMATRIX view;
		XMMATRIX projection;
	};
	struct LightBufferType
	{
		XMFLOAT4 diffuseColor;
		XMFLOAT3 lightDirection;
		float padding;
	};

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

	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, int, XMMATRIX, XMMATRIX, XMMATRIX,
		ID3D11ShaderResourceView*, XMFLOAT3, XMFLOAT4);

private:
	bool InitializeShader(ID3D11Device*, HWND, const WCHAR*, const WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3DBlob*, HWND, const WCHAR*);
	bool SetShaderParameters(ID3D11DeviceContext*, XMMATRIX, XMMATRIX, XMMATRIX,
		ID3D11ShaderResourceView*, XMFLOAT3, XMFLOAT4);
	void RenderShader(ID3D11DeviceContext*, int);

private:
	ID3D11VertexShader* mVertexShader;
	ID3D11PixelShader* mPixelShader;
	ID3D11InputLayout* mLayout;
	ID3D11SamplerState* mSampleState;
	ID3D11Buffer* mMatrixBuffer;
	ID3D11Buffer* mLightBuffer;
};
#endif

 

LightShaderClass.cpp

Render() 함수는 빛의 방향과 빛의 디퓨즈 색상을 입력 파라미터로 받는다. 이 파라미터는 내부의 SetShaderParamters() 함수로 전달된다.

 

InitializeShader() 함수에서 polygonLayout 변수는 이제 3개의 원소를 갖는다. 정점 구조체에 노멀 속성이 추가되었기 때문이다. 또한 조명 상수 버퍼에 대한 description 변수로 새롭게 추가한다. polygonLayout 변수를 설정하는 부분에서 새롭게 추가된 노멀의 시맨틱은 "NORMAL"이고, 포맷은 DXGI_FORMAT_R32G32B32_FLOAT이다. 이 레이아웃은 HLSL 정점 셰이더에서의 입력 구조체와 일치해야 한다.

LightBufferDesc을 작성하는 이유는 디퓨즈 색상과 빛의 방향을 처리하기 위한 버퍼를 만들기 위해서이다. 상수 버퍼의 크기에 주의해야 한다. 16바이트 배수가 아니라면 앞서 말했듯이 CreateBuffer() 함수 호출이 실패한다. 32바이트 크기로 만들기 위해 4바이트 float 타입 패딩 변수가 있는 것이다.

 

SetShaderParameters() 함수에서는 lightDirection 및 diffuseColor 변수를 입력 파라미터로 받는다.

상수 버퍼 LightBuffer는 MatrixBuffer와 동일한 방식으로 설정한다. 먼저 버퍼를 잠그고(lock) 포인터를 얻는다. 그다음 해당 포인터를 사용하여 디퓨즈 색상과 조명 방향을 설정한다. 데이터 값이 설정되면 버퍼를 잠금 해제한 다음 픽셀 셰이더에 바인딩한다. VSSetConstantBuffers() 함수 대신 PSSetConstantBuffers() 함수를 사용한다. 왜냐하면 설정한 빛 관련 상수 버퍼는 픽셀 셰이더 버퍼이기 때문이다.

더보기
#include "LightShaderClass.h"

LightShaderClass::LightShaderClass()
{
	mVertexShader = 0;
	mPixelShader = 0;
	mLayout = 0;
	mSampleState = 0;
	mMatrixBuffer = 0;
}

LightShaderClass::LightShaderClass(const LightShaderClass &)
{
}

LightShaderClass::~LightShaderClass()
{
}

bool LightShaderClass::Initialize(ID3D11Device * device, HWND hwnd)
{
	bool result;

	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"light.vs", L"light.ps");
	if (!result) return false;

	return true;
}

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

	return;
}

bool LightShaderClass::Render(ID3D11DeviceContext * deviceContext, int indexCount, 
	XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix,
	ID3D11ShaderResourceView * texture, XMFLOAT3 lightDirection, XMFLOAT4 diffuseColor)
{
	bool result;

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

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

	return true;
}

bool LightShaderClass::InitializeShader(ID3D11Device * device, HWND hwnd, 
	const WCHAR * vsFileName, const WCHAR * psFileName)
{
	HRESULT result;
	ID3DBlob* errorMessage;
	ID3DBlob* vertexShaderBuffer;
	ID3DBlob* pixelShaderBuffer;

	D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
	unsigned int numElements;
	D3D11_SAMPLER_DESC samplerDesc;
	D3D11_BUFFER_DESC matrixBufferDesc;
	D3D11_BUFFER_DESC lightBufferDesc;

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

	// Compile the vertex shader code.
	result = D3DCompileFromFile(vsFileName, nullptr, nullptr, "LightVertexShader",
		"vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 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, vsFileName);
		}
		// If there was nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, vsFileName, L"Missing Shader FIle", MB_OK);
		}

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

		return false;
	}

	// Create the vertex shader from the buffer.
	result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(),
		vertexShaderBuffer->GetBufferSize(), nullptr, &mVertexShader);
	if (FAILED(result)) return false;

	// Create the pixel shader from the buffer.
	result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(),
		pixelShaderBuffer->GetBufferSize(), nullptr, &mPixelShader);
	if (FAILED(result)) return false;
	
	// Create the vertex input layout description.
	// This setup needs to match the VertexType structure in the ModelClass and in the shader.
	polygonLayout[0] = { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 };
	polygonLayout[1] = { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 
		D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 };
	polygonLayout[2] = { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,
		D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 };

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

	// Create the vertex input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),
		vertexShaderBuffer->GetBufferSize(), &mLayout);
	if (FAILED(result)) return false;

	// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
	vertexShaderBuffer->Release();
	vertexShaderBuffer = 0;
	pixelShaderBuffer->Release();
	pixelShaderBuffer = 0;

	// Create a texture sampler state description.
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

	// Create the texture sampler state.
	result = device->CreateSamplerState(&samplerDesc, &mSampleState);
	if (FAILED(result)) return false;

	// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
	matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
	matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	matrixBufferDesc.MiscFlags = 0;
	matrixBufferDesc.StructureByteStride = 0;
	
	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&matrixBufferDesc, nullptr, &mMatrixBuffer);
	if (FAILED(result)) return false;

	// Setup the description of the light dynamic constant buffer that is in the pixel shader.
	// Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail.
	lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	lightBufferDesc.ByteWidth = sizeof(LightBufferType);
	lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	lightBufferDesc.MiscFlags = 0;
	lightBufferDesc.StructureByteStride = 0;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&lightBufferDesc, nullptr, &mLightBuffer);
	if (FAILED(result)) return false;

	return true;
}

void LightShaderClass::ShutdownShader()
{
	// Release the light constant buffer.
	if (mLightBuffer)
	{
		mLightBuffer->Release();
		mLightBuffer = 0;
	}
	// Release the matrix constant buffer.
	if (mMatrixBuffer)
	{
		mMatrixBuffer->Release();
		mMatrixBuffer = 0;
	}
	// Release the sampler state.
	if (mSampleState)
	{
		mSampleState->Release();
		mSampleState = 0;
	}
	// Release the layout.
	if (mLayout)
	{
		mLayout->Release();
		mLayout = 0;
	}
	// Release the pixel shader.
	if (mPixelShader)
	{
		mPixelShader->Release();
		mPixelShader = 0;
	}
	// Release the vertex shader.
	if (mVertexShader)
	{
		mVertexShader->Release();
		mVertexShader = 0;
	}

	return;
}

void LightShaderClass::OutputShaderErrorMessage(ID3DBlob * errorMessage, HWND hwnd, const WCHAR * shaderFileName)
{
	char* compileErrors;
	unsigned long 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;
}

bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext * deviceContext, XMMATRIX worldMatrix,
	XMMATRIX viewMatrix, XMMATRIX projectionMatrix, ID3D11ShaderResourceView * texture,
	XMFLOAT3 lightDirection, XMFLOAT4 diffuseColor)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	unsigned int bufferNumber;
	MatrixBufferType* dataPtr;
	LightBufferType* dataPtr2;

	// Transpose the matrices to prepare them for the shader.
	worldMatrix = XMMatrixTranspose(worldMatrix);
	viewMatrix = XMMatrixTranspose(viewMatrix);
	projectionMatrix = XMMatrixTranspose(projectionMatrix);
	
	// Lock the constant buffer so it can be written to.
	result = deviceContext->Map(mMatrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if (FAILED(result)) return false;

	// Get a pointer to the data in the contant buffer.
	dataPtr = (MatrixBufferType*)mappedResource.pData;

	// Copy the matrices into the constant buffer.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;

	// Unlock the constant buffer.
	deviceContext->Unmap(mMatrixBuffer, 0);

	// Set the position of the constant buffer in the vertex shader.
	bufferNumber = 0;

	// Now set the constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &mMatrixBuffer);
	// Set shader texture resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 1, &texture);

	// Lock the light constant buffer so it can be written to.
	result = deviceContext->Map(mLightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if (FAILED(result)) return false;

	// Get a pointer to the date in ther constant buffer.
	dataPtr2 = (LightBufferType*)mappedResource.pData;

	// Copy the lighting variables into the constant buffer.
	dataPtr2->diffuseColor = diffuseColor;
	dataPtr2->lightDirection = lightDirection;
	dataPtr2->padding = 0.0f;

	// Unlock the constant buffer.
	deviceContext->Unmap(mLightBuffer, 0);
	// Set the position of the light constant buffer in the pixel shader.
	bufferNumber = 0;

	// Finally set the light constant buffer in the pixel shader with the updated values.
	deviceContext->PSSetConstantBuffers(bufferNumber, 1, &mLightBuffer);

	return true;
}

void LightShaderClass::RenderShader(ID3D11DeviceContext * deviceContext, int indexCount)
{
	// Set the vertex input layout.
	deviceContext->IASetInputLayout(mLayout);

	// Set the vertex and pixel shaders that will be used to render this triangle.
	deviceContext->VSSetShader(mVertexShader, nullptr, 0);
	deviceContext->PSSetShader(mPixelShader, nullptr, 0);

	// Set the sampler state in the pixel shader.
	deviceContext->PSSetSamplers(0, 1, &mSampleState);

	// Render the triangle.
	deviceContext->DrawIndexed(indexCount, 0, 0);
	
	return;
}

ModelClass.cpp

이 클래스의 유일한 변경사항은 InitializeBuffers() 함수 부분이다. 정점의 노멀 속성이 추가되었기 때문에 노멀 값을 설정한다. 3개 정점의 노멀 값은 모두 (0.0f, 0.0f, -1.0f)로 설정한다. 그러면 이 노멀 벡터는 모두 사용자를 향한 방향을 가리키는 벡터이다.

더보기
bool ModelClass::InitializeBuffers(ID3D11Device * device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;

	// Set the number of vertices in the vertex array.
	mVertexCount = 4;
	// Set the number of indices in the index array.
	mIndexCount = 6;
	// Create the vertex array.
	vertices = new VertexType[mVertexCount];
	if (!vertices) return false;
	// Create the index array.
	indices = new unsigned long[mIndexCount];
	if (!indices) return false;

	// Load the vertex array with data.
	vertices[0].position = XMFLOAT3(-1.0f, -1.0f, 0.0f);
	vertices[0].texture = XMFLOAT2(0.0f, 1.0f);
	vertices[0].normal = XMFLOAT3(0.0f, 0.0f, -1.0f);
	vertices[1].position = XMFLOAT3(-1.0f, 1.0f, 0.0f);
	vertices[1].texture = XMFLOAT2(0.0f, 0.0f);
	vertices[1].normal = XMFLOAT3(0.0f, 0.0f, -1.0f);
	vertices[2].position = XMFLOAT3(1.0f, 1.0f, 0.0f);
	vertices[2].texture = XMFLOAT2(1.0f, 0.0f);
	vertices[2].normal = XMFLOAT3(0.0f, 0.0f, -1.0f);
	vertices[3].position = XMFLOAT3(1.0f, -1.0f, 0.0f);
	vertices[3].texture = XMFLOAT2(1.0f, 1.0f);
	vertices[3].normal = XMFLOAT3(0.0f, 0.0f, -1.0f);
	// Load the index array with data.
	indices[0] = 0;
	indices[1] = 1;
	indices[2] = 2;
	indices[3] = 2;
	indices[4] = 3;
	indices[5] = 0;
	// Set up the description of the static vertex buffer.
	vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * mVertexCount;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = 0;
	vertexBufferDesc.MiscFlags = 0;
	vertexBufferDesc.StructureByteStride = 0;
	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;
	// Now create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &mVertexBuffer);
	if (FAILED(result)) return false;

	// Set up the description of the static index buffer.
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * mIndexCount;
	indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;
	indexBufferDesc.StructureByteStride = 0;
	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;
	// Create the index buffer.
	result = device->CreateBuffer(&indexBufferDesc, &indexData, &mIndexBuffer);
	if (FAILED(result)) return false;

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

LightClass.h

이 클래스의 목적은 빛의 방향과 색상을 유지한다.

더보기
#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_

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

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 mDiffuseColor;
	XMFLOAT3 mDirection;
};
#endif

 

LightClass.cpp

더보기
#include "LightClass.h"

LightClass::LightClass()
{
}

LightClass::LightClass(const LightClass &)
{
}

LightClass::~LightClass()
{
}

void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
	mDiffuseColor = XMFLOAT4(red, green, blue, alpha);
	return;
}

void LightClass::SetDirection(float x, float y, float z)
{
	mDirection = XMFLOAT3(x, y, z);
	return;
}

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

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

GraphicsClass.h

더보기
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

// INCLUDES
#include <Windows.h>

// MY CLASS INCLUDES
#include "D3DClass.h"
#include "CameraClass.h"
#include "ModelClass.h"
#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 GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

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

private:
	bool Render(float);

private:
	D3DClass* mDirect3D;
	CameraClass* mCamera;
	ModelClass* mModel;
	LightShaderClass* mLightShader;
	LightClass* mLight;
};
#endif

 

GraphicsClass.cpp

Initialize() 함수에서 새롭게 추가한 조명 객체와 조명 셰이더 객체를 생성하고 초기화한다.

Frame() 함수에서 3D 모델을 회전시키기 위한 코드가 추가되었다.

Render() 함수에서 LightShaderClass 객체의 Render() 함수를 호출한다.

더보기
#include "GraphicsClass.h"

GraphicsClass::GraphicsClass()
{
	mDirect3D = nullptr;
	mCamera = 0;
	mModel = 0;
	mLightShader = 0;
	mLight = 0;
}

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

GraphicsClass::~GraphicsClass()
{
}

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

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

	// Initialize the Direct3D object.
	result = mDirect3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if (!result)
	{
		MessageBox(hwnd, L"Could nor initialize Direct3D", L"Error", MB_OK);
		return false;
	}
	
	// Create the camera object.
	mCamera = new CameraClass;
	if (!mCamera) return false;
	// Set the initial position of the camera.
	mCamera->SetPosition(0.0f, 0.0f, -5.0f);

	// Create the model object.
	mModel = new ModelClass;
	if (!mModel) return false;

	// Initialize the model object.
	result = mModel->Initialize(mDirect3D->GetDevice(),
		const_cast<WCHAR*>(L"Images/dirt01.dds"));
	if (!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}

	// Create the light shader object.
	mLightShader = new LightShaderClass;
	if (!mLightShader) return false;
	// Initialize the texture shader object.
	result = mLightShader->Initialize(mDirect3D->GetDevice(), hwnd);
	if (!result)
	{
		MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
		return false;
	}
	
	// Create the light object.
	mLight = new LightClass;
	if (!mLight) return false;

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


	return true;
}

void GraphicsClass::ShutDown()
{
	// Release the light object.
	if (mLight)
	{
		delete mLight;
		mLight = 0;
	}
	// Release the light shader object.
	if (mLightShader)
	{
		mLightShader->Shutdown();
		delete mLightShader;
		mLightShader = 0;
	}
	// Release the model object.
	if (mModel)
	{
		mModel->Shutdown();
		delete mModel;
		mModel = 0;
	}

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

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

	return;
}

bool GraphicsClass::Frame()
{
	bool result;

	static float rotation = 0.0f;

	// Update the rotation variable each frame.
	rotation += 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.
	mDirect3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
	// Generate the view matrix based on the camera's position.
	mCamera->Render();
	// Get the world, view, and projection matrices from the camera and d3d objects.
	mDirect3D->GetWorldMatrix(worldMatrix);
	mCamera->GetViewMatrix(viewMatrix);
	mDirect3D->GetProjectionMatrix(projectionMatrix);

	// 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.
	mModel->Render(mDirect3D->GetDeviceContext());
	// Render the model using the light shader.
	result = mLightShader->Render(mDirect3D->GetDeviceContext(), mModel->GetIndexCount(),
		worldMatrix, viewMatrix, projectionMatrix, mModel->GetTexture(),
		mLight->GetDirection(), mLight->GetDiffuseColor());
	if (!result) return false;
	// Present the rendered scene to the screen.
	mDirect3D->EndScene();

	return true;
}

실행결과

'Graphics API > DirectX 11 - Rastertek' 카테고리의 다른 글

29. Water  (0) 2021.01.21
7. 3D Model Rendering  (0) 2021.01.07
5. Texturing  (0) 2021.01.07
4. Buffers, Shaders, and HLSL  (0) 2021.01.07
3. Initializing DirectX 11  (0) 2021.01.07
Comments