프로그래밍 공부
작성일
2023. 10. 17. 15:29
작성자
WDmil
728x90

이전에 만들었던 Viewport작업을 먼저 전역변수로 빼준다.

 

카메라 위치좌표 작업을 새롭게 잡아준다는 의미이다.


ViewPort

Environment.h를 다음과 같이 선언한다.

 

#pragma once
class Environment : public Singleton<Environment>
{
private:
    friend class Singleton;

    Environment();
    ~Environment();

public:
    void Set();

    void SetViewport(UINT width = WIN_WIDTH, UINT height = WIN_HEIGHT);
    void SetPerspective();

    void CreateProjection();
private:
    MatrixBuffer* viewBuffer;
    MatrixBuffer* projectionBuffer;
};

위 코드에서는. Viewport형태를 Singleton 형태로 받아온다.

 

카메라 뷰포트는 화면상 한개밖에 존재하지 않음으로. 메인 뷰포트는 한개로 진행한다.

만약, 뷰포트를 여러개 집어넣으려면 Singleton형태가 아니라 다른형태로 작업해야할것이다.

 

기본적인 Set()함수와. Viewport 다르게 설정해주는 SetViewport,

Setperspective는 아직 작업하지 않는다.

 

CreateProjection()을 사용하여.  현재 사용되는 뷰포트의 카메라 인풋을 수정한다.

 

#include "Framework.h"

Environment::Environment()
{
	CreateProjection();
	SetViewport();
}

Environment::~Environment()
{
	delete viewBuffer;
	delete projectionBuffer;
}

void Environment::Set()
{
}

void Environment::SetViewport(UINT width, UINT height)
{
	D3D11_VIEWPORT viewPort;
	viewPort.TopLeftX = 0;
	viewPort.TopLeftY = 0;
	viewPort.Width = width;
	viewPort.Height = height;
	viewPort.MinDepth = 0.0f;
	viewPort.MaxDepth = 1.0f;

	DC->RSSetViewports(1, &viewPort);
}

void Environment::SetPerspective()
{
}

void Environment::CreateProjection()
{
	XMVECTOR eye = XMVectorSet(3, 5, -5, 0);//Cam Pos
	XMVECTOR focus = XMVectorSet(0, 0, 0, 0);//Cam Look at Pos
	XMVECTOR up = XMVectorSet(0, 1, 0, 0);//Cam Up Vector

	Matrix view = XMMatrixLookAtLH(eye, focus, up);

	Matrix projection = XMMatrixPerspectiveFovLH(XM_PIDIV4,
		(float)WIN_WIDTH / WIN_HEIGHT, 0.1f, 1000.0f);

	viewBuffer = new MatrixBuffer();
	projectionBuffer = new MatrixBuffer();

	viewBuffer->Set(view);
	viewBuffer->SetVS(1);

	projectionBuffer->Set(projection);
	projectionBuffer->SetVS(2);
}

Cpp는 위와같이 구성한다.

생성자에서 필요한 함수들을 불러와서 진행한다.

 

그외에는 기본적인 SetViewport와 CreateProjection은 전에 작성했던 함수를 그대로 가져와서 붙여넣으면 된다.

 

그 이후에 GameManager에 기입하여 작업을 진행해준다.

싱글톤 베이스의 전역함수들을 전부 선언해주고 종료해주는 작업에 같이 넣어준다.

 


D3D11_INPUT_ELEMENT_DESC 재기입

Viewport를 작업해주었으면. 현재 작업해준 hlsl이 uv와 color값이 따로 들어가야 uv좌표대로 작업하는것과 color대로 작업하는것의 차이를 알 수 있을것이다.

 

그래픽 파이프라인 상. DeviceContext에 데이터를 기입해주기 전에 데이터를 수정하거나 다르게 기입해야한다.

 

전에 작업하였던. VertexShader의 헤더에 데이터를 추가하기위해 Framework에 헤더를 새롭게 추가한다.

32번째 줄을 추가하면 된다.

위 라이브러리는 vertexBlob의 데이터를 DeviceContext에 기입하기 전에 가져와서 재작업하기위한 라이브러리이다.

 

이와같이 기입하였으면. VertexShader.h에 들어가서. 다음과 같은 항목을 추가해준다.

#pragma once

class VertexShader : public Shader
{
private:
	friend class Shader;

	VertexShader(wstring file);
	~VertexShader();

public:
	virtual void Set() override;

private:
	void CreateInputLayout();

private:
	ID3D11VertexShader* vertexShader;
	ID3D11InputLayout* inputLayout;

	ID3D11ShaderReflection* reflection;
	// 버텍스 쉐이더에 들어가서 여러가지 데이터를 빼옴.
};

위에서는 ID3D11ShaderReflection* reflection과. 이 구조체를 사용하기 위한 void CreateInputLayout()이라는 함수를 선언하였다.

 

void VertexShader::CreateInputLayout()
{
    D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(),
        IID_ID3D11ShaderReflection, (void**)&reflection);

    D3D11_SHADER_DESC shaderDesc;
    reflection->GetDesc(&shaderDesc);
    //쉐이더 데이터 뽑아옴

    vector<D3D11_INPUT_ELEMENT_DESC> inputLayouts;
    inputLayouts.reserve(shaderDesc.InputParameters);  // 데이터의 임시데이터 할당량을 정해줌.
    
    for (UINT i = 0; i < shaderDesc.InputParameters; i++)
    {
        D3D11_SIGNATURE_PARAMETER_DESC paramDesc;
        reflection->GetInputParameterDesc(i, &paramDesc);

        D3D11_INPUT_ELEMENT_DESC elementDesc;
        elementDesc.SemanticName = paramDesc.SemanticName;
        elementDesc.SemanticIndex = paramDesc.SemanticIndex;
        elementDesc.InputSlot = 0;
        elementDesc.AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
        elementDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        elementDesc.InstanceDataStepRate = 0;

        if (paramDesc.Mask < 2)
        {
            // MASK가 2보다 작다면, 32비트만 할당
            elementDesc.Format = DXGI_FORMAT_R32_FLOAT;
        }
        else if (paramDesc.Mask < 4)
        {
            elementDesc.Format = DXGI_FORMAT_R32G32_FLOAT;
        }
        else if (paramDesc.Mask < 8)
        {
            // float3 로 넘길떼
            elementDesc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
        }
        else if (paramDesc.Mask < 16)
        {
            // float4 로 넘길떼
            elementDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
        }

        string temp = paramDesc.SemanticName;
        if (temp == "POSITION" && paramDesc.SemanticIndex == 0)
            elementDesc.Format = DXGI_FORMAT_R32G32B32_FLOAT;

        inputLayouts.push_back(elementDesc);
    }

    DEVICE->CreateInputLayout(inputLayouts.data(), inputLayouts.size(),
        blob->GetBufferPointer(), blob->GetBufferSize(), &inputLayout);
}

CreateInputLayout은 위와같이 구성한다.

 

위 함수에서 D3DReflect는. blob에 들어있는 데이터의 포인터와 형태를 가져와서. reflection에 원본데이터를 기입하기 위해 사용되는 함수이다.

 

D3D11_SHADER_DESC는 받아온 blob데이터인 reflection에서 DESC데이터만 뽑아오기 위해 사용되는 구조체이다.

위의 함수부중 reflection->GetDesc(&shaderDesc);함수로. shaderDesc에 reflection의 Desc데이터를 가져온다는 의미이다.

 

그후, D3D11_INPUT_ELEMENT_DESC에 데이터가 얼마나 들어갈지 알 수 없기때문에,( POSITION, UV, COLOR등, 한번에 여러개가 들어갈 수 있기 때문에) 동적으로 데이터를 기입하기위한. vector를 사용한다.

 

이때, 데이터의 사이즈가 임의로 늘어날 수 있기 때문에 reserve나, resize를 사용하여 작업해준다.

 

그 이후, 현재 가져온 shaderDesc의 데이터 갯수에 따라 for문을 돌려서 데이터의 형태를 기입해준다.

 

현재 데이터 유형에 따른 Name값과, index값은 그대로 넣고.

inputSlot은 언제나 버퍼0

Offset은 APPEND_ALIGEND_ELEMENT로.

SlotClass는 현재 InputPerVectrxDATA로 기입한다. ( 나중에 수정할 수도 있다)

InstanceDataStepRate는. 현재 인스턴스 데이터를 처리할 때. 몃번째마다 업데이트할것인지 를 나타낸다.

매번 같은값을 갱신함으로. 새로운 값을 넣어도 전부 같은 값으로 대입한다.

만약, 2가 들어가면 2번째 인스턴스마다 갱신됨으로 약간 줄이 그어지듯 업데이트될것이다.

 

그후, 데이터의 Mask에 따라 Format을 조정해준다.

 

현재 Mask의 데이터숫자가 Float2, 3, 4인지 에 따라 데이터의 사이즈를 정의해주는것 이다.

 

밑의 temp의 경우. 현재 데이터의 POSITION이 Float4로 넘어갈 수도 있기 때문에. POSITION일경우 항상 RGB32로 잡아준다는 뜻이다.

 

그 후 정의된 데이터를 elementDesc에 기입해준다.

 

전부 정의가 끝났으면 DEVICE에 데이터를 INPUTLAYOUT을 정의한다.


Grid

3D맵 에 넓게 정의되는 격자무늬와 화면에 표시되는 0x0y0z에 직선으로 그어지는 좌표계를 그려보자.

Grid 라는 자체의 scene을 정의할 것이다.

 

다음과 같이 헤더를 작성한다.

#pragma once

class GridScene : public Scene
{
private:
	const int MAX_SIZE = 100;

public:
	GridScene();
	~GridScene();

	// Scene을(를) 통해 상속됨
	void Update() override;
	void PreRender() override;
	void Render() override;
	void PostRender() override;
	void GUIRender() override;

private:
	void MakeMesh();
private:
	Material* material;
	Mesh<VertexColor>* mesh;
	MatrixBuffer* worldBuffer;

	UINT width, height;
};

위에서는 현재 그리드 map에 전체 사이즈가 MAX_SIZE를 넘지 않도록 할것이다.

만약 전체적으로 그리드 map이 생성될경우. 매우 큰 용량을 잡아먹게 될것이고. 프로그램이 조만간 폭파될것이다.

 

그걸 막기위해 사이즈를 정의한다.

 

그 외에것은. 일반적인 Mesh를 생성하기위한 Material과 Mesh<VertexColor>, MatrixBuffer. 그리고 전체사이즈 정의를 위한 width, height이다.


#include "Framework.h"
#include "GridScene.h"

GridScene::GridScene()
{
	material = new Material(L"Basic/Grid.hlsl");

	mesh = new Mesh<VertexColor>();
	MakeMesh();
	mesh->CreateMesh();

	worldBuffer = new MatrixBuffer();
}

GridScene::~GridScene()
{
	delete material;
	delete mesh;
	delete worldBuffer;
}

void GridScene::Update()
{
}

void GridScene::PreRender()
{
}

void GridScene::Render()
{
	worldBuffer->SetVS(0);

	material->Set();
	mesh->Draw(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
}

void GridScene::PostRender()
{
}

void GridScene::GUIRender()
{
}

void GridScene::MakeMesh()
{
    vector<VertexColor>& vertices = mesh->GetVertices();

    //Axis X
    vertices.emplace_back(-MAX_SIZE, 0, 0, 1.0f, 0.5f, 0.5f);
    vertices.emplace_back(+MAX_SIZE, 0, 0, 1.0f, 0.5f, 0.5f);

    //Axis Y
    vertices.emplace_back(0, -MAX_SIZE, 0, 0.5f, 1.0f, 0.5f);
    vertices.emplace_back(0, +MAX_SIZE, 0, 0.5f, 1.0f, 0.5f);

    //Axis Z
    vertices.emplace_back(0, 0, -MAX_SIZE, 0.5f, 0.5f, 1.0f);
    vertices.emplace_back(0, 0, +MAX_SIZE, 0.5f, 0.5f, 1.0f);

    int halfSize = MAX_SIZE >> 1;

    for (int x = -halfSize; x <= halfSize; x++)
    {
        if (x == 0) continue;

        vertices.emplace_back(x, 0, -halfSize, 0.5f, 0.5f, 0.5f);
        vertices.emplace_back(x, 0, +halfSize, 0.5f, 0.5f, 0.5f);
    }

    for (int z = -halfSize; z <= halfSize; z++)
    {
        if (z == 0) continue;

        vertices.emplace_back(-halfSize, 0, z, 0.5f, 0.5f, 0.5f);
        vertices.emplace_back(+halfSize, 0, z, 0.5f, 0.5f, 0.5f);
    }
}

Grid의 CPP이다.

항상 정의하듯, material에 hlsl을 정의해주고 새롭게 작성한다.

이때, Basic/Grid.hlsl을 만들어서 그리드 전용 hlsl을 사용하자.

//Grid.hlsl

cbuffer WordBuffer : register(b0)
{
	matrix world;
}

cbuffer ViewBuffer : register(b1)
{
	matrix view;
}

cbuffer ProjectionBuffer : register(b2)
{
	matrix projection;
}
struct VertexInput
{
	float4 pos : POSITION;
	float4 color : COLOR;
};

struct PixelInput
{
	float4 pos : SV_POSITION;
	float4 color : COLOR;
};

PixelInput VS(VertexInput input)
{
	PixelInput output;
	output.pos = mul(input.pos, world);
	output.pos = mul(output.pos, view);
	output.pos = mul(output.pos, projection);
	
	output.color = input.color;
	
	return output;
}

float4 PS(PixelInput input) : SV_TARGET
{
	return input.color;
}

hlsl은 위와같고 경로는 다음과 같다.

Grid에서 정의된 material과, mesh를 통해 데이터를 생성해준다. MaekMesh()함수를 따로 정의해

생성자가 난잡하지 않게 해준다.

 

worldBuffer는, MatrixBuffer()이고, 따로 이동할 일이 없으니 크게 다루지 않는다.

 

Render()에서는

worldBuffer를 통해 Vs값을 넘겨주고 material을 통해 찍힌 vertices를 전부 이어준다.

 

Draw는 삼각형이 아닌 LINE으로 그려주어야 함으로 LINELIST형태로 정의해준다.

 

MakeMesh()에서는

 

각각 MAX_SIZE만큼의 +와 - 최대값에 vertices를 찍어주고.

(x, y, z 에 각각 찍어준다 )

그리고, halfSize 에 >> 연산자를 사용하여. bit단위에서 우측으로 쉬프트하여. 2만큼 나눈 효과를 준다.

(* 0.5 로 해도된다 )

그후, for문을 통해 -부터 +까지. x를 순환시켜서. x최소값부터 x최대값까지 vertices를 찍어놓은다음.

선으로 그어준다.

이것을 z축도 같이 진행한다.


자, GridMap이 정의되었으니, GameManager에서 SceneManager를 사용해보자.

 

현재 GameManager의 생성자에 각 Scene를 생성하고. 대입한 뒤 Render()를 명령해보자.

#include "Framework.h"
#include "GameManager.h"

#include "Scenes/TutorialScene.h"
#include "Scenes/GridScene.h"

GameManager::GameManager()
{
	Create();

	SceneManager::Get()->Create("Grid", new GridScene());
	SceneManager::Get()->Create("Start", new TutorialScene());

	SceneManager::Get()->Add("Grid");
	//SceneManager::Get()->Add("Start");
}

GameManager::~GameManager()
{
	Delete();
}

void GameManager::Update()
{
	KEY->Update();
	Timer::Get()->Update();

	SceneManager::Get()->Update();
}

void GameManager::Render()
{
	SceneManager::Get()->PreRender();

	Device::Get()->Clear();

	SceneManager::Get()->Render();
	SceneManager::Get()->PostRender();
	SceneManager::Get()->GUIRender();

	Device::Get()->Present();
}

void GameManager::Create()
{
	Device::Get();
	Keyboard::Get();
	Timer::Get();
	Environment::Get();
	SceneManager::Get();
}

void GameManager::Delete()
{
	Device::Delete();
	Keyboard::Delete();
	Timer::Delete();
	Shader::Delete();
	Environment::Delete();
	SceneManager::Delete();
}

대강 위와같이 정의하면 될것이다.

 

모든 함수의 순환문은 각 Singleton 의 객체에 정의되어있음으로. GameManager에서는 선언후 실행만 해주면된다.

코드 결과물

 

728x90