Tutorial 4: Buffers, Shaders, and HLSL

Tutorial 4: Buffers, Shaders, and HLSL This tutorial will be the introduction to writing vertex and pixel shaders in DirectX 11. It will also be the introduction to using vertex and index buffers in DirectX 11. These are the most fundamental concepts that

www.rastertek.com

DirectX11의 공부 목적으로 위 링크의 튜토리얼을 보고 제 생각을 포함하여 정리하였습니다.


들어가며

 

이 튜토리얼에서는 DirectX 11에서 정점 및 픽셀 셰이더를 작성하는 방법을 소개합니다. 또한 DirectX 11에서 정점 및 인덱스 버퍼를 사용하는 방법을 소개합니다.

이는 3D 그래픽을 렌더링하기 위해 이해하고 활용해야 하는 가장 기본적인 개념입니다.

 

1. 정점 버퍼(Vertex buffer)

구의 3D모델을 예로 들어보겠습니다.

이러한 거의 모든 모델들은 실제로 수백 개의 삼각형으로 구성됩니다.

삼각형(폴리곤)에는 세 개의 점이 있으며 각 점을 정점이라고 부릅니다. 따라서 위 모델을 렌더링하려면 구를 형성하는 모든 정점을 정점 버퍼라고 하는 배열에 넣어야 합니다. 정점 데이터를 가진 이 정점 버퍼를 GPU로 전송하면 GPU에서 모델을 렌더링할 수 있습니다.

 

2. 인덱스 버퍼 (Index buffer)

인덱스 버퍼는 정점 버퍼와 관련이 있습니다. 그 목적은 정점 버퍼에 있는 각 정점이 그려질 순서를 기록하는 것입니다. 그러면 GPU는 인덱스 버퍼를 사용하여 정점 버퍼에서 특정 정점을 빠르게 찾아 그려냅니다.

인덱스 버퍼의 개념은 책에서 인덱스를 사용하는 개념과 유사하며, 원하는 정점을 훨씬 빠른 속도로 찾는데 도움이 됩니다.

DirectX 문서에 따르면 인덱스 버퍼를 사용하면 VRAM의 더 빠른 위치에 정점 데이터를 캐싱할 가능성이 높아질 수 있습니다. 따라서 성능상의 이유로 이러한 기능을 사용해야합니다.

 

3. 셰이더 (Shaders)

셰이더는 GPU에 존재하는, 화면에 출력할 픽셀의 위치와 색상을 계산하는 함수입니다.

 

4. 정점 셰이더 (Vertex shaders)

정점 셰이더란 정점 버퍼에 들어있는 정점을 3D공간으로 변환하기 위해 작성된 작은 함수입니다. 각 정점에 대한 법선 계산과 같이 수행할 수 있는 다른 계산도 있습니다. 정점 셰이더 프로그램은 처리해야 하는 각 정점에 대해 GPU가 호출합니다. 그러므로 정점 셰이더의 호출횟수는 처리해야하는 정점의 개수와 같다. 예시로 정점이 5000개인 모델에 정점 셰이더는 5000번 실행됩니다.

 

5. 레스터라이저 (Rasterizer)

정점 셰이더로 정점을 3D공간으로 불러 3개씩 모으면 삼각형이 만들어집니다. 그럼 이 삼각형을 픽셀화 해주는게 래스터라이저가 하는 일입니다.(삼각형에 픽셀이 몇개 들어가고 어디에 위치하는지 계산합니다.)

 

6. 픽셀 셰이더 (Pixel shaders)

픽셀 셰이더란 래스터라이저로 구해진 픽셀이 화면에 출력할 최종색상을 계산하는 작은 함수입니다. 이 또한 정점 셰이더와 같이 GPU가 호출합니다. 모델에 사진을 입힌다던지, 조명 및 기타 대부분의 효과는 픽셀 셰이더에서 처리됩니다.

 

7. HLSL (High level shader language)

HLSL은 DirectX11에서 정점, 픽셀 셰이더를 코딩하는데 사용하는 언어입니다. 

 

이번 시간에 업데이트 할 프레임워크 구성

ApplicationClass 하위에 Modle, Camera, ColorShader 클래스가 추가했습니다.

Camera클래스는 이전에 이야기한 뷰 행렬을 처리합니다. 이는 게임월드에서의 카메라의 위치를 제어하고 셰이더들이 장면을 그릴 때 어디를 보고 있는지를 계산해야 할 때 카메라의 위치를 셰이더들에게 전달합니다.

Model클래스는 3D모델의 형태와 구조를 제어합니다. 이번 시간에서는 단순성을 위해 3D모델을 삼각형 평면으로 만듭니다.

ColorShader클래스는 HLSL 셰이더를 호출하여 모델을 화면에 렌더링하는 역할을 담당합니다.

 

먼저 셰이더 함수를 살펴보는 것으로 코드를 시작하겠습니다.


Color.vs

 

정점 셰이더이며 아직은 단순한 텍스트 파일입니다. 그렇기에 빌드에 참여하지 않습니다. 이번시간에 만드는 셰이더의 목적은 가능한 한 단순하게 유지하면서 색상이 있는 삼각형을 그리는 것입니다.

 

정점 셰이더는 전역 변수의 정의로 시작합니다. 이 전역 변수는  int, float과 같은 다양한 타입을 사용할 수 있고, 셰이더가 그 전역변수를 이용하여 작업을 하며 이 값들은 C++ 코드에서 접근해 수정할 수 있습니다. 

 

일반적으로 전역 변수가 한 개, 그 이상이더라도 모든 전역 변수는 "cbuffer"라는 버퍼 개체 유형에 넣습니다. 이러한 버퍼를 논리적으로 구성하는 것은 셰이더의 효율적인 실행뿐만 아니라 그래픽 카드가 버퍼를 저장하는 방식에도 영향을 미치기에  중요합니다. 이 예제에서는 3개의 행렬을 각 프레임을 동시에 업데이트할 것이므로 동일한 버퍼에  넣었습니다.

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

이제 이 정점 셰이더 함수의 Input, Output 데이터의 구조를 정의합니다.

셰이더도 C++언어와 마찬가지로 구조체 같은 사용자 정의 타입을 만들 수 있습니다. 밑의 코드에서는 두 개의 float4를 사용하여 위치(x, y, z, w)와, 색상(r, g, b, a)으로 정점 데이터 구조를 정의합니다.

 

POSITION, COLOR, SV_POSITION 같은코드는 시멘틱(Semantics)이라 불립니다.

시멘틱은 셰이더 함수에 들어오는 데이터가 어떤 데이터와 매핑해야 하는지에 대한 힌트를 주는 동시에 용도를 직관적으로 알수 있게 해줍니다.

 

정점 셰이더와 픽셀 셰이더의 의미가 다르기 때문에 POSITION은 버텍스 셰이더에 작동하고, SV_POSITION은 픽셀 셰이더에 작동하기 때문에 VertexInputType과 PixelInputType의 데이터 구조가 동일하여도 따로 정의하였습니다.

COLOR는 두 가지 모두에 작동합니다. 동일한 유형을 두 개 이상 원하는 경우 끝에 COLOR0, COLOR1 등과 같은 숫자를 추가해야 합니다.

struct VertexInputType
{
    float4 position : POSITION;
    float4 color : COLOR;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};

정점 셰이더는 정점 버퍼가 GPU로 전송되어 GPU가 정점 데이터를 처리할 때 GPU에서 호출합니다. 즉, 정점 버퍼에 들어 있는 정점만큼 호출한다는 뜻입니다.

 

정점 셰이더의 수행 로직은 ColorVertexShaader입니다. 정점 셰이더에 들어오는 input 데이터는(매개변수)는 당연히 VertexInputType의 데이터 구조와 같아야 합니다.

들어온 데이터(VertexInputType input)에서 world, view, projection행렬을 곱하여 공간 변환을 실행합니다. 그럼 3D게임 월드에서 우리의 관점(카메라)으로, 우리의 관점이 2D 화면에 정점이 배치됩니다. 들어온 데이터의 w 값을 1.0f로 변경한 이유는 1.0f로 변경하지 않으면 위치에 대한 XYZ 벡터만 읽기 때문입니다.(일반적으로 위치는 3차원좌표지만 공간 변환시에는 4차원 벡터를 사용하기 때문에 1로 설정하는 것, 나중에 더 자세히 기술)

 

그리고 들어온 데이터의 색상 정보를 그대로 아까 계산한 position과 함께 픽셀 셰이더로 결과를 전송합니다.

PixelInputType ColorVertexShader(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 input color for the pixel shader to use.
    output.color = input.color;
    
    return output;
}

 

Color.ps

 

픽셀 셰이더는 화면에 렌더링될 폴리곤의 각 픽셀의 최종 색상을 계산합니다. 픽셀 셰이더는 정점 셰이더의 출력에서 데이터가 들어옵니다.

이번 시간의 픽셀 셰이더는 들어온 데이터의 색을 바로 반환할것이기 때문에 매우 간단합니다.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};


float4 ColorPixelShader(PixelInputType input) : SV_TARGET
{
    return input.color;
}

 

ColorShaderClass.h

 

이 클래스는 GPU에 있는 3D모델(정점)을 그리기 위해 HLSL 셰이더를 호출하는 데 사용됩니다.

class ColorShaderClass
{
private:

	// 이 구조체가 바로 정점 셰이더의 cbuffer 값을 수정할 데이터 유형
	// 당연히 cbuffer 형식과 정확히 동일해야함
	struct MatrixBufferType
	{
		XMMATRIX world;
		XMMATRIX view;
		XMMATRIX projection;
	};

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

	// 셰이더의 초기화 및 종료를 처리합니다.
	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	// 셰이더가 사용하는 매개변수를 설정하고 셰이더가 사용할 준비된 모델의 정점을 그립니다.
	bool Render(ID3D11DeviceContext*, int, XMMATRIX, XMMATRIX, XMMATRIX);

private:
	bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

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

private:
	ID3D11VertexShader* m_vertexShader;
	ID3D11PixelShader* m_pixelShader;
	ID3D11InputLayout* m_layout;
	ID3D11Buffer* m_matrixBuffer;
};

 

ColorShaderClass.cpp

 

1. 생성자

ColorShaderClass::ColorShaderClass()
{
	m_vertexShader = nullptr;
	m_pixelShader = nullptr;
	m_layout = nullptr;
	m_matrixBuffer = nullptr;
}

 

2. Initialize

정점, 픽셀 셰이더의 파일 이름을 가져와 InitializeShader에 전달하면서 호출한다.

bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;
	wchar_t vsFilename[128];
	wchar_t psFilename[128];
	int error;

	// Set the filename of the vertex shader.
	error = wcscpy_s(vsFilename, 128, L"../Engine/color.vs");

	// Set the filename of the pixel shader.
	error = wcscpy_s(psFilename, 128, L"../Engine/color.ps");

	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, vsFilename, psFilename);

	return true;
}

3. Shoutdown

ShoutdownShader함수를 호출합니다.

void ColorShaderClass::Shutdown()
{
	// vertex, pixel shaders와 관련된 개체들을 종료합니다.
	ShutdownShader();

	return;
}

 

4. Render

SetShaderParameters로 셰이더의 cbuffer 값을 수정하고, RenderShader를 통해 셰이더를 통해 준비된 버퍼를 렌더링 합니다.

bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, XMMATRIX worldMatrix, XMMATRIX viewMatrix,
	XMMATRIX projectionMatrix)
{
	bool result;

	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix);

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

	return true;
}

 

5. InitializeShader

이번 튜토리얼에서 가장 중요한 기능 중 하나인 함수입니다. 

이 함수는 셰이더 파일을 불러오고 DirectX와 GPU에서 사용할 수 있도록 만드는 기능입니다. 또한 레이아웃 설정과 정점 버퍼 데이터가 GPU의 그래픽 파이프라인에서 어떻게 보이는지 확인할 수 있습니다.

bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
	HRESULT result;
	ID3D10Blob* errorMessage = nullptr;
	ID3D10Blob* vertexShaderBuffer = nullptr;
	ID3D10Blob* pixelShaderBuffer = nullptr;
	D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
	unsigned int numElements;
	D3D11_BUFFER_DESC matrixBufferDesc;

셰이더 파일을 컴파일한 코드를 버퍼(ID3D10Blob)에 넣어줍니다. 컴파일이 실패하면 셰이더 파일을 찾을 수 없다는 의미로 팝업창으로 표시 됩니다.

	// 정점 셰이더 코드를 컴파일 합니다.
	result = D3DCompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0",D3D10_SHADER_ENABLE_STRICTNESS, 0,
		&vertexShaderBuffer, &errorMessage);
	
	/* -- 오류 메세지 코드 생략 -- */
	
	// 픽셀 셰이더 코드를 컴파일 합니다.
	result = D3DCompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0,
		&pixelShaderBuffer, &errorMessage);
	
	/* -- 오류 메세지 코드 생략 -- */

성공적으로 코드가 버퍼에 들어가면 각 버퍼를 이용해 각 셰이더의 개체를 생성하여 포인터로 저장합니다.

이 포인터를 정점, 픽셀 셰이더의 인터페이스로 사용합니다.

	// 버퍼를 이용해 정점 버퍼 개체를 생성합니다.
	result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);

	// 버퍼를 이용해 픽셀 버퍼 개체를 생성합니다.
	result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);

 

다음 단계는 Input layout을 생성하는데 목적이 있습니다.

Input layout은 우리가 정점 데이터를 만들어서 GPU(셰이더의 Input data)에 전달하면, 전달 된 정점 데이터의 형식과 구성을 알려주는 구조체 입니다.

즉, 정점 데이터가 어떻게 구성되어 어떻게 사용할건지 설명하는 역할입니다.

 

▼의문점. 어차피 정점 데이터가 정점 셰이더에서 이용이 되는데 왜 GPU에 전달하는지?

더보기

아직 구조를 잘 몰라서 생겼던 의문, 셰이더 함수며 셰이더의 데이터며 모두 GPU 메모리에 위치해 있다.

그래서 GPU에게 전달하는게 당연한 것!

 

다시 글로 돌아가서 먼저 셰이더에서 처리할 정점 데이터의 레이아웃(형식과 구성) 설정을 다룹니다.

우리가 만든 셰이더에서 사용하는 정점 데이터는 위치 벡터(float4)와 색상 벡터(float4) 두 가지를 사용하므로 두 가지의 정점 데이터 레이아웃 설정 생성해야 합니다.

 

가장 먼저 작성해야하는 중요한 SemanticName은 정점 셰이더에 들어갈 정점 데이터가 정점 셰이더의 입력 구조체의 멤버에 매핑 될 수 있게 하는 힌트가 됩니다.

 

아까 말했듯이 우리는 두 개의 서로 다른 정점 데이터를 사용하므로 첫 번째 레이아웃에는 POSITION을 사용하고 두 번째 레이아웃에는 COLOR를 사용합니다.

 

다음 중요한 부분은 Format(형식)입니다. 위치 벡터에는 DXGI_FORMAT_R32G32B32_FLOAT를 사용하고 색상에는 DXGI_FORMAT_R32G32B32A32_FLOAT를 사용합니다.

마지막으로 주의해야 할 사항은 버퍼에서 데이터의 간격을 나타내는 AlignedByteOffset입니다.

이 레이아웃의 경우 처음 12바이트는 위치이고 다음 16바이트는 색상이며 AlignedByteOffset은 각 요소가 시작되는 위치를 보여줍니다.AlignedByteOffset에 직접 값을 넣는 대신 D3D11_APPEND_ALIGNED_ELEMENT를 사용하면 간격이 자동으로 파악됩니다.다른 설정은 이 튜토리얼에서는 필요하지 않으므로 지금은 기본값으로 설정했습니다.

	// 셰이더에서 사용하는 데이터의 레이아웃 설명 생성
	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 = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "COLOR";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

그 후 레이아웃이 몇 개인지 구하여 정점 셰이더의 Input layout을 생성합니다. 또한 Input Layout이 생성되면 더 이상 필요없는 Vertex,Pixel shader buffer를 해제 합니다.

	// 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(), &m_layout);
        
	// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
	vertexShaderBuffer->Release();
	vertexShaderBuffer = 0;

	pixelShaderBuffer->Release();
	pixelShaderBuffer = 0;

 

셰이더를 활용하기 위해 설정해야 하는 마지막은 상수 버퍼(Constant Buffer)를 생성하는 일입니다.

정점 셰이더에서 우리가 전역변수라며 cbuffer의 구조를 정의만 했는데 바로 이 cbuffer가 상수 버퍼의 데이터 유형입니다.

다음 코드에서 CreateBuffer을 이용해 실제로 GPU 메모리에 상수 버퍼를 생성하여 포인터로 가지고 있을 것입니다.

 

상수 버퍼는 셰이더에서 여러 모델이 공통적으로 사용되는 변수를 런타임에서 동적으로 업데이트하기 위해 사용되며, 상수버퍼의 포인터와 SetShaderParmeters를 사용하여 셰이더의 상수 버퍼 값에 접근할 수 있습니다.

	// 정점 셰이더에 있는 상수 버퍼(cbuffer)의 설명을 설정합니다.
	// 상수 버퍼의 사용 방식을 지정합니다. 여기서는 매 프레임마다 업데이트되므로 D3D11_USAGE_DYNAMIC을 사용합니다.
	matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; 
	// ByteWidth는 상수 버퍼의 크기를 바이트 단위로 지정합니다. 이 경우에는 MatrixBufferType 구조체의 크기를 사용합니다.
	matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 
	// BindFlags는 버퍼가 어떤 유형의 버퍼가 될지 지정합니다. 여기서는 상수 버퍼로 사용하므로 D3D11_BIND_CONSTANT_BUFFER를 사용합니다.
	matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	// CPUAccessFlags는 Usage와 맟춰 사용해야하기에 여기서 D3D11_CPU_ACCESS_WRITE를 사용합니다.
	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, NULL, &m_matrixBuffer);
    
	return true;
}

 

6. ShutdownShader

InitializeShader함수에서 설정된 4개의 인터페이스를 해제 합니다.

void ColorShaderClass::ShutdownShader()
{
	// Release the matrix constant buffer.
	if (m_matrixBuffer)
	{
		m_matrixBuffer->Release();
		m_matrixBuffer = nullptr;
	}

	// Release the layout.
	if (m_layout)
	{
		m_layout->Release();
		m_layout = nullptr;
	}

	// Release the pixel shader.
	if (m_pixelShader)
	{
		m_pixelShader->Release();
		m_pixelShader = nullptr;
	}

	// Release the vertex shader.
	if (m_vertexShader)
	{
		m_vertexShader->Release();
		m_vertexShader = nullptr;
	}

	return;
}

 

7. OuputShaderErrorMessage

셰이더관련 에러인지 알려주는 창을 띄워줍니다.

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


	// error message text buffer의 포인터를 가져옵니다.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// 메세지의 길이 구하기
	bufferSize = errorMessage->GetBufferSize();

	// 에러 메세지를 저장할 텍스트 파일 열기 (없으면 생성)
	fout.open("shader-error.txt");

	// 파일에 에러 메세지 작성
	for (i = 0; i < bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// 텍스트 파일 닫기
	fout.close();

	// 에러메세지 메모리 해제
	errorMessage->Release();
	errorMessage = nullptr;

	// compile errors를 확인하라는 메세지를 팝업창으로 띄웁니다.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}

 

8. SetShaderParameters

이 함수는 셰이더의 상수 버퍼를 쉽게 설정할 수 있도록 하기 위해 존재합니다.

이 함수에 사용된 행렬은 ApplicationClass 내부에서 생성된 후 Render함수 호출 중에 정점 셰이더로 보내기 위해 이 함수가 호출 됩니다.

bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix,
	XMMATRIX projectionMatrix)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	MatrixBufferType* dataPtr;
	unsigned int bufferNumber;

행렬을 셰이더로 보내기 전에 DirectX11의 요구사항으로 행렬을 전치 해야합니다.

	// 셰이더에 사용하기 위해 행렬을 전치합니다. (대각선 \ 을 기준으로 뒤집음)
	worldMatrix = XMMatrixTranspose(worldMatrix);
	viewMatrix = XMMatrixTranspose(viewMatrix);
	projectionMatrix = XMMatrixTranspose(projectionMatrix);

그리고 상수 버퍼의 값을 수정해야 하는데 잠금(Map), 잠금 해제(UnMap)라는 개념이 나옵니다. 지금 우리가 수정하려는 값을 가지고 있는건 CPU쪽(+ RAM)이고, 상수 버퍼는 GPU(VRAM)에 존재합니다. 그래서 값을 수정하면서 데이터의 동기화, 일관성을 가지게 해야하기에 나온 개념이 잠금, 잠금 해제 입니다.

 

잠금은 CPU가 상수버퍼의 메모리 영역에 접근할 수 있게 하며 GPU가 상수 버퍼 메모리 영역에 접근하지 못하게 하며,

잠금해제는 그 반대가 됩니다.

bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix,
	XMMATRIX projectionMatrix)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	MatrixBufferType* dataPtr;
	unsigned int bufferNumber;

	// 셰이더에 사용하기 위해 행렬을 전치합니다. (대각선 \ 을 기준으로 뒤집음)
	worldMatrix = XMMatrixTranspose(worldMatrix);
	viewMatrix = XMMatrixTranspose(viewMatrix);
	projectionMatrix = XMMatrixTranspose(projectionMatrix);

	// 상수 버퍼 포인터를 이용해 상수버퍼를 잠급니다.
	result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if (FAILED(result))
	{
		return false;
	}

	// 상수 버퍼의 데이터에 대한 포인터를 가져옵니다.
	dataPtr = (MatrixBufferType*)mappedResource.pData;

▼의문점. 마지막에 상수 버퍼의 데이터에 대한 포인터를 가져오는데 이미 상수 버퍼 포인터가 있지만 굳이 또 가져오는 이유가 뭘까?

더보기

이 답이 확실할지는 모르겠지만 내가 찾아 결론 내린건,

 

일반적으로 개체 포인터만 있어도 개체의 멤버에 접근할 수 있지만 cpu와 gpu의 메모리 관리 방식이 다르기에 gpu에 있는 개체 포인터로는 그 데이터에 직접 접근할 수 있는 방법을 제공하지 않기 때문이다.

 

그래서 Map을 이용하여 상수 버퍼를 잠구고 데이터를 변환할거니까 그 데이터를 수정할 수 있는 포인터를 넘겨주는 것이라 생각한다.

Map으로 잠그고 난 후 상수 버퍼의 데이터 값을 수정하고 잠금 해제 후 VS단계에 등록합니다.

	// constant buffer의 데이터에 값을 수정한다.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;

	// constant buffer의 잠금 해제
	deviceContext->Unmap(m_matrixBuffer, 0);

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

	// 셰이더의 업데이트된 상수버퍼를 0번 슬롯에 1개 VS단계에 등록한다.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

	return true;
}

 

9. RenderShader

이 함수는 Render 함수에서 호출되는 두 번째 함수 입니다. 세 가지 기능을 합니다.

 

첫 번째 기능은 IA단계에서 Input layout을 등록하는 것 입니다. 이를 통해 GPU는 정점 버퍼의 데이터 형식을 알 수 있습니다.

두 번째 기능은 정점 버퍼를 렌더링하는 데 사용할 정점 셰이더와 픽셀 셰이더를 렌더링 파이프 라인에 등록하는 것입니다.

세 번째 기능은 D3D device context를 이용하여 DX11함수인 DrawIndexed를 호출해 폴리곤을 렌더링 합니다.

 

우리의 목표는 녹색 삼각형을 렌더링하는 것입니다.

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

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

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

	return;
}

 

ModelClass.h

 

이 클래스는 3D모델의 기하 구조를 캡슐화하는 역할을 합니다. 이 튜토리얼에서는 단순한 녹색 삼각형에 대한 데이터를 수동으로 설정합니다. 또한 삼각형이 렌더링 될 수 있도록 정점,인덱스 버퍼를 생성합니다.

 

구조체 VertexType은 정점 데이터의 유형입니다. 이 형식의 정의는 아까 생성했던 Input layout과 같아야 합니다.

#pragma once
#include <d3d11.h>
#include <directxmath.h>
using namespace DirectX;

class ModelClass
{
private:
	struct VertexType
	{
		XMFLOAT3 position;
		XMFLOAT4 color;
	};

Render 함수는 모델의 기하 구조를 그래픽 카드에 배치하여 color shader로 그릴 수 있도록 준비합니다.

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

	bool Initialize(ID3D11Device*);
	void Shutdown();
	void Render(ID3D11DeviceContext*);

	int GetIndexCount();

private:
	bool InitializeBuffers(ID3D11Device*);
	void ShutdownBuffers();
	void RenderBuffers(ID3D11DeviceContext*);

ModelClass의 private 변수는 정점 버퍼와 인덱스 버퍼, 그리고 각 버퍼의 크기를 저장하는 두 개의 정수 입니다.

Dx11의 모든 변수는 일반적으로 ID3D11Buffer유형을 사용합니다.

private:
	ID3D11Buffer* m_vertexBuffer;
	ID3D11Buffer * m_indexBuffer;
	int m_vertexCount;
	int m_indexCount;
};

 

ModelClass.cpp

 

1. 생성자

생성자는 버퍼를 null로 초기화 합니다.

ModelClass::ModelClass()
{
	m_vertexBuffer = nullptr;
	m_indexBuffer = nullptr;
}

 

2. Initialize

초기화 함수는 정점, 인덱스 버퍼에 대한 초기화 함수를 호출합니다.

bool ModelClass::Initialize(ID3D11Device* device)
{
	bool result;

	// Initialize the vertex and index buffers.
	result = InitializeBuffers(device);

	return true;
}

 

3. Shoutdown

Shutdown 함수는 정점 및 인덱스 버퍼에 대한 종료 함수를 호출합니다.

void ModelClass::Shutdown()
{
	// Shutdown the vertex and index buffers.
	ShutdownBuffers();

	return;
}

 

4. Render

이 함수는 ApplicationClass::Render 함수에서 호출됩니다. 이 함수는 RenderBuffers를 호출하여 정점, 인덱스 버퍼를 렌더링 파이프라인에 배치하므로 color shader가 이를 렌더링할 수 있습니다.

void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
	// vertex and index buffers를 렌더링 파이프 라인에 배치합니다.
	RenderBuffers(deviceContext);

	return;
}

 

5. GetIndexCount

GetIndexCount는 모델의 인덱스 수를 반환합니다. color shader는 이 모델을 그리려면 이 정보가 필요합니다.

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

 

6. InitialzieBuffers

이 함수는 정점,인덱스 버퍼 생성을 처리하는 곳입니다. 일반적으로 모델을 읽고 해당 데이터 파일에서 버퍼를 생성합니다.

이 튜토리얼에서는 모델이 아닌 단순 삼각형이기에 수동으로 정점을 설정합니다.

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.
	m_vertexCount = 3;

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

	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];

	// Create the index array.
	indices = new unsigned long[m_indexCount];

이제 정점과 인덱스 배열을 세 점과 각 점의 인덱스로 채웁니다. 시계방향으로 점을 그린다는 점에 유의하시길 바랍니다.

이 시계방향으로 그려진 면은 앞면이고 반대방향으로 그려지면 뒷면으로 인식됩니다. 뒷면일 경우 back face culling으로 인해 그려지지 않습니다. 색상은 녹색으로 설정합니다.

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

	vertices[1].position = XMFLOAT3(0.0f, 1.0f, 0.0f);  // Top middle.
	vertices[1].color = XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f);

	vertices[2].position = XMFLOAT3(1.0f, -1.0f, 0.0f);  // Bottom right.
	vertices[2].color = XMFLOAT4(0.0f, 1.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.

정점, 인덱스 배열이 채워지면 이제 이를 사용하여 정점 버퍼와 인덱스 버퍼를 만들 수 있습니다. 두 버퍼의 생성은 동익한 방식으로 수행됩니다.

먼저 버퍼에 대한 설명과 SubResource 포인터를 가지고 CreateBuffer을 호출하여 버퍼를 생성할 수 있습니다 

	// Set up the description of the static vertex buffer.
	vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
	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, &m_vertexBuffer);

	// Set up the description of the static index buffer.
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
	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, &m_indexBuffer);

정점, 인덱스 버퍼가 생성된 후에는 데이터가 버퍼에 복사되었기 때문에 필요하지 않은 정점, 인덱스 배열을 삭제할 수 있습니다.

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

 

7. ShoutdownBuffers

InitializeBuffers함수에서 생성된 정점, 인덱스 버퍼를 메모리 해제 합니다.

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

	// Release the vertex buffer.
	if (m_vertexBuffer)
	{
		m_vertexBuffer->Release();
		m_vertexBuffer = nullptr;
	}

	return;
}

 

8. RenderBuffers

이 함수는 Render함수에서 호출됩니다. 이 함수의 목적은 정점, 인덱스 버퍼를 렌더링 파이프 라인의 IA단계에 배치하는 것입니다. GPU에 배치된 정점 버퍼가 있으면 셰이더를 사용하여 해당 버퍼로 정점을 렌더링할 수 있습니다.

또한 이 함수는 삼각형뿐만 아니라 점, 선, 등 같이 그리는 방법을 정의 합니다. 이 튜토리얼에서는 정점 버퍼와 인덱스 버퍼를 IA단계에 배피하고 IASetPrimitiveTopology함수를 이용하여 버퍼를 삼각형으로 그려야 함을 GPU에 알립니다. 

void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
	unsigned int stride;
	unsigned int offset;


	// Set vertex buffer stride and offset.
	stride = sizeof(VertexType);
	offset = 0;

	// Set the vertex buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

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

	return;
}

 

CameraClass.h

 

우리는 HLSL셰이더를 코딩하는 법, ColorShaderClass에서 셰이더를 호출하여 각 버퍼를 렌더링하는 법을 살펴보았습니다. 그러나 우리가 놓치고 있는 한 가지는 그것들을 그리는 관점입니다. 이를 위해 Dx11이 장면을 어디서 어떻게 보고 있는지 알려주는 카메라 클래스가 필요합니다. 카메라 클래스는 카메라의 위치와 현재 회전을 추적합니다. 위치, 회전 정보를 사용하여 렌더링을 위해 셰이더에 전달될 뷰 행렬을 생성합니다.

 

CameraClass의 헤더는 4개의 함수만 사용하여 매우 간단합니다.

SetPosition, SetRotation 함수는 카메라 개체의 위치와 회전을 설정하는 데 사용됩니다.

Render는 카메라의 위치와 회전을 기반으로 뷰 행렬을 생성하는데 사용됩니다.

GetViewMatrix는 셰이더가 렌더링에 사용할 수 있도록 카메라 개체에서 뷰 행렬을 검색하는데 사용됩니다.

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

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

	void SetPosition(float, float, float);
	void SetRotation(float, float, float);

	XMFLOAT3 GetPosition();
	XMFLOAT3 GetRotation();

	void Render();
	void GetViewMatrix(XMMATRIX&);

private:
	float m_positionX, m_positionY, m_positionZ;
	float m_rotationX, m_rotationY, m_rotationZ;
	XMMATRIX m_viewMatrix;
};

 

CameraClass.cpp

 

1. 생성자

카메라의 위치와 회전을 원점으로 초기화 합니다.

CameraClass::CameraClass()
{
	m_positionX = 0.0f;
	m_positionY = 0.0f;
	m_positionZ = 0.0f;

	m_rotationX = 0.0f;
	m_rotationY = 0.0f;
	m_rotationZ = 0.0f;
}

 

2. SetPosition & SetRotation

카메라의 위치, 회전을 설정합니다.

void CameraClass::SetPosition(float x, float y, float z)
{
	m_positionX = x;
	m_positionY = y;
	m_positionZ = z;
	return;
}


void CameraClass::SetRotation(float x, float y, float z)
{
	m_rotationX = x;
	m_rotationY = y;
	m_rotationZ = z;
	return;
}

 

3. GetPosition & GetRotation

카메라의 위치, 회전을 반환합니다.

XMFLOAT3 CameraClass::GetPosition()
{
	return XMFLOAT3(m_positionX, m_positionY, m_positionZ);
}


XMFLOAT3 CameraClass::GetRotation()
{
	return XMFLOAT3(m_rotationX, m_rotationY, m_rotationZ);
}

 

4. Render

이 함수는 카메라의 위치, 회전을 토대로 뷰 행렬을 생성하고 업데이트 합니다.

먼저 카메라의 윗방향을 의미하는 벡터, 위치, 회전에 대한 변수를 설정합니다.

그 후 원점에서 먼저 카메라의 x, y, z 회전을 기준으로 카메라를 회전합니다.

적절히 회전되면 카메라를 3D공간의 위치로 변환합니다.

올바른 윗방향, 위치, 회전을 이용하여 XMMatrixLookAtLH 함수로 현재 카메라 회전 및 이동을 나타내는 뷰 행렬을 생성할 수 있습니다.

void CameraClass::Render()
{
	XMFLOAT3 up, position, lookAt;
	XMVECTOR upVector, positionVector, lookAtVector;
	float yaw, pitch, roll;
	XMMATRIX rotationMatrix;


	// Setup the vector that points upwards.
	up.x = 0.0f;
	up.y = 1.0f;
	up.z = 0.0f;

	// Load it into a XMVECTOR structure.
	upVector = XMLoadFloat3(&up);

	// Setup the position of the camera in the world.
	position.x = m_positionX;
	position.y = m_positionY;
	position.z = m_positionZ;

	// Load it into a XMVECTOR structure.
	positionVector = XMLoadFloat3(&position);

	// Setup where the camera is looking by default.
	lookAt.x = 0.0f;
	lookAt.y = 0.0f;
	lookAt.z = 1.0f;

	// Load it into a XMVECTOR structure.
	lookAtVector = XMLoadFloat3(&lookAt);

	// Set the yaw (Y axis), pitch (X axis), and roll (Z axis) rotations in radians.
	pitch = m_rotationX * 0.0174532925f;
	yaw = m_rotationY * 0.0174532925f;
	roll = m_rotationZ * 0.0174532925f;

	// Create the rotation matrix from the yaw, pitch, and roll values.
	rotationMatrix = XMMatrixRotationRollPitchYaw(pitch, yaw, roll);

	// Transform the lookAt and up vector by the rotation matrix so the view is correctly rotated at the origin.
	lookAtVector = XMVector3TransformCoord(lookAtVector, rotationMatrix);
	upVector = XMVector3TransformCoord(upVector, rotationMatrix);

	// Translate the rotated camera position to the location of the viewer.
	lookAtVector = XMVectorAdd(positionVector, lookAtVector);

	// Finally create the view matrix from the three updated vectors.
	m_viewMatrix = XMMatrixLookAtLH(positionVector, lookAtVector, upVector);

	return;
}

 

5. GetViewMatrix

Render함수로 뷰 행렬을 생성,업데이트 한 후, 이 GetViewMatrix를 사용하여 업데이트 된 뷰 행렬을 제공할 수 있습니다.

뷰 행렬은 셰이더에 사용되는 주 행렬중 하나입니다.

void CameraClass::GetViewMatrix(XMMATRIX& viewMatrix)
{
	viewMatrix = m_viewMatrix;
	return;
}

 

ApplicationClass.h

 

ApplicaitionClass에 이번 시간에 만든 세 개의 새로운 클래스를 include하고 멤버 포인터를 추가합니다.

ApplicaitionClass는 프로젝트에 필요한 모든 클래스 개체를 호출하여 장면을 렌더링하는 데 사용되는 기본 클래스라는 점을 기억하세요.

#include "CameraClass.h"
#include "ModelClass.h"
#include "ColorShaderClass.h"
private:

    CameraClass* m_Camera;
	ModelClass* m_Model;
	ColorShaderClass* m_ColorShader;

 

ApplicationClass.cpp

 

1. 생성자

첫 번째 변경 사항은 클래스 생성자에서 추가한 포인터를 null로 초기화 하는 것입니다.

ApplicationClass::ApplicationClass()
{
	m_Direct3D = nullptr;
	//////////////////////////////////////////////////////////////////////
	// 추가 부분
	m_Camera = nullptr;
	m_Model = nullptr;
	m_ColorShader = nullptr;
	//////////////////////////////////////////////////////////////////////
}

 

2. Initialize

세 개의 새로운 개체를 생성하고 초기화하는 코드를 추가합니다.

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

	m_Direct3D = new D3DClass;

	result = m_Direct3D->Initialize( screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	
	//////////////////////////////////////////////////////////////////////
	// 추가 부분
	// Create the camera object.
	m_Camera = new CameraClass;

	// Set the initial position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -5.0f);

	// Create and initialize the model object.
	m_Model = new ModelClass;

	result = m_Model->Initialize(m_Direct3D->GetDevice());

	// Create and initialize the color shader object.
	m_ColorShader = new ColorShaderClass;

	result = m_ColorShader->Initialize(m_Direct3D->GetDevice(), hwnd);
	//////////////////////////////////////////////////////////////////////

	return true;
}

 

3. Shutdown

세 개의 개체를 추가로 메모리 해제 합니다.

void ApplicationClass::Shutdown()
{
	//////////////////////////////////////////////////////////////////////
	// 추가 부분
	// Release the color shader object.
	if (m_ColorShader)
	{
		m_ColorShader->Shutdown();
		delete m_ColorShader;
		m_ColorShader = nullptr;
	}

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

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


	// Direct3D 개체를 해제합니다. 
	if (m_Direct3D)
	{
		m_Direct3D->Shutdown();
		delete m_Direct3D;
		m_Direct3D = nullptr;
	}

	return;
}

 

4. Render

여전히 장면을 초기화 하는 것으로 시작 됩니다. 그런 다음 CameraClass 개체를 이용해 뷰 행렬을 생성하여 얻어내고

D3DClass개체에서 월드, 투영 행렬을 얻습니다.

그런 다음 ModelClass::Render함수를 이용해 녹색 삼각형 모델 형상을 렌더링 파이프 라인에 배치합니다.

이제 정점이 준비되었으므로 ColorShader를 이용해 정점을 그립니다. 그럼 녹색 삼각형이 백 버퍼에 그려집니다.

이것으로 한 장면이 완성되고 EndScene을 호출하여 백 버퍼를 화면에 렌더링합니다.

bool ApplicationClass::Render()
{
	//////////////////////////////////////////////////////////////////////
	//추가 부분
	XMMATRIX worldMatrix, viewMatrix, projectionMatrix;
	bool result;

	// 화면을 검은색으로 초기화
	m_Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// 카메라 위치를 기반으로 뷰 행렬 생성
	m_Camera->Render();

	// camera 와 d3d objects에서 world, view, projection 행렬을 가져옵니다.
	m_Direct3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_Direct3D->GetProjectionMatrix(projectionMatrix);

	// 렌더링 파이프라인에 모델의 정점, 인덱스 버퍼를 배치하여 그릴 준비를 합니다.
	m_Model->Render(m_Direct3D->GetDeviceContext());

	// ColorShaderClass를 사용하여 모델을 렌더링 합니다.
	result = m_ColorShader->Render(m_Direct3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
	if (!result)
	{
		return false;
	}
	//////////////////////////////////////////////////////////////////////

	// 렌더링된 장면을 화면에 표시한다.
	m_Direct3D->EndScene();

	return true;
}

 


행렬에 대한 첨언

 

행렬이 이 튜토리얼에서 어떻게 작동하는 설명합니다.

정점 셰이더를 살펴보면 해당 셰이더에 들어오는 정점을 월드, 뷰, 투영 행렬로 곱하는 것을 볼 수 있습니다.

정점에 월드 행렬을 곱하면 해당 정점을 3D 세계에 배치하게 됩니다. 월드 행렬에는 이동, 회전, 크기 등과 같은 항목이 적용될 수 있습니다. 따라서 이를 통해 3D공간에서 해당 정점을 올바르게 배치할 수 있습니다.

다음으로 3D세계에 배치된 정점에 뷰 행렬을 곱하면 정점의 위치를 카메라가 보고 있는 위치를 기준으로 다시 업데이트 합니다.

마지막으로 카메라 위치를 기준으로 업데이트된 정점을 투영행렬에 곱하면 2D화면을 기준으로 위치가 업데이트 됩니다.

 

요약

 

이제 정점 버퍼와 인덱스 버퍼가 어떻게 작동하는지 기본적인 내용을 배웠을 것입니다. 또한 HLSL을 사용하여 정점 셰이더와 픽셀 셰이더를 작성하는 기본적인 방법을 배웠을 것입니다. 마지막으로, 이러한 새로운 개념을 우리의 프레임워크에 통합하여 화면에 초록색 삼각형을 렌더링하는 방법을 이해했을 것입니다.

그리고 저는 단일 삼각형을 그리기 위해 코드가 꽤 길다는 것을 인지하고 있습니다. 이 모든 것을 하나의 main() 함수 안에 넣을 수도 있었을 것입니다. 그러나 앞으로의 튜토리얼에서는 코드의 변경을 최소화하면서 더 복잡한 그래픽을 처리할 수 있도록 하기 위해 이렇게 올바른 프레임워크를 사용하여 작성했습니다.


초기화

  • Camera : 카메라 초기 위치 설정
  • Model : 정점, 인덱스 버퍼 생성 후, 각 버퍼에 데이터 하드코딩
  • ColorShader : GPU에 셰이더 생성,  셰이더가 사용할 (shader) Input Layout 생성, 상수 버퍼 생성

업데이트(Render)

  • Camera : 뷰 행렬 생성
  • Model : 정점, 인덱스 버퍼를 IA단계에 배치
  • ColorShader : 상수 버퍼 값(3개의 행렬) 업데이트 후 VS단계에 배치, 셰이더 각 단계에 배치, DrawIndexed 호출(그리기 명령)

 

연습

 

1. 삼각형의 색상을 빨간색으로 변경합니다.

정점 데이터의 색상코드를 변경

 

2. 삼각형을 정사각형으로 바꿔보세요.

정점, 인덱스 버퍼에 들어가는 데이터 수정

 

3. 카메라를 10만큼 뒤로 이동해보세요.

카메라 초기 위치설정의 깊이(z) 값 조정

 

4. 색상의 밝기를 절반으로 줄이도록 픽셀 셰이더를 변경해보세요.

픽셀셰이더의 최종 값에 0.5를 곱한다.(나는 더 명확한 차이를 위해 0.2를 곱함)

 


용어 정리

 

시멘틱 : 셰이더 함수에 들어온 Input data 구조의 멤버 데이터와 매핑해주는 일종의 태그


셰이더 : 화면에 출력할 픽셀의 위치와 색상을 계산하는 함수

Input layout : 셰이더 함수의 Input data(매개변수)에 들어가는 구조이다.
내 생각으로는 (Shader) Input layout이라고도 생각한다.

상수 버퍼 : 셰이더함수에서 여러 모델이 공통적으로 사용하는 데이터


+ CreateBuffer로 버퍼를 생성하면 GPU에 생성되고 그 인터페이스를 CPU쪽에서 포인터로 관리한다고 하는데 나중에 확인 해 볼 것.

+ 그래서 IASet(Vertex,Index)Buffer는 GPU에 생성된 버퍼를 렌더링 파이프에 배치하는것이라고 한다는데 확인 해 볼 것.

'DirectX 11 > DX11 Tutorial' 카테고리의 다른 글

[06] 난반사 조명(Diffuse light)  (0) 2024.05.24
[05] 텍스쳐링  (0) 2024.05.23
[03] DirectX 11 초기화 (3)  (0) 2024.05.20
[02] DirectX 11 초기화 (2)  (0) 2024.05.20
[01] DirectX 11 초기화 (1)  (0) 2024.05.19

+ Recent posts