Adventure of 빠타박스
article thumbnail
728x90
728x90
SMALL

ImGui 활용한 DirectX 11 3D를 이용한 2D를 만드는 내용입니다.
본 내용은 똑같이 사용할 수 없음을 알립니다. (참고및 공부자료로 만들어진 내용임을 밝힙니다.)
전 과정에 대해 이해를 해야 다음 이 부분에 대한 이해를 할 수 있음을 밝힙니다
https://ppatabox.tistory.com/20

목차)

1) Shader 2편 

2) Rectangle/Triangle RecTangle 사각형을 만들어보자 

 

====================================

우리가 실제로 데이터를 넣어줘야 한다 Render에서 

렌더링 부분에서 쉐이더 데이터를 밀어주도록 되어있다. 

VSSetShader. 와 PSSetShader 는 

vertexshader와 pixelshader를 이용해서 렌더링을 하라는 말 

우리는 나중에 쉐이더를 여러개 교체하면서 사용할 것이다. 

DeviceContext->VSSetShader(VertexShader, 0, 0); //vertex
DeviceContext->PSSetShader(PixelShader, 0, 0); //pixel

1) Shader.h 에서 void Render(); 를 만들어주고 정의 하자

 

Shader.h

#pragma once

class Shader
{
public:
	Shader(wstring file, string vs = "VS", string ps = "PS");
	~Shader();

	void CreateInputLayout(D3D11_INPUT_ELEMENT_DESC* desc, UINT count);
	
    void Render(); // Shader.cpp에 정의해주자 
    
private:
	ID3D11InputLayout* InputLayout;

	ID3D11VertexShader* VertexShader;
	ID3D11PixelShader* PixelShader;
	ID3D10Blob* VsBlob;
	ID3D10Blob* PsBlob;
};

 

 

2) shader.cpp에서 Render부분을 작성 해주자 위에서 Device부분을 불러오면 된다. 

Shader.cpp

#include "stdafx.h"
#include "Shader.h"

Shader::Shader(wstring file, string vs, string ps)
	: InputLayout(NULL)
{
	HRESULT hr;
	
	hr = D3DX11CompileFromFile(file.c_str(), NULL, NULL, vs.c_str(), "vs_5_0", 0, 0, NULL, &VsBlob, NULL, NULL);
	assert(SUCCEEDED(hr));

	hr = D3DX11CompileFromFile(file.c_str(), NULL, NULL, ps.c_str(), "ps_5_0", 0, 0, NULL, &PsBlob, NULL, NULL);
	assert(SUCCEEDED(hr));

	
	hr = Device->CreateVertexShader(VsBlob->GetBufferPointer(), VsBlob->GetBufferSize(), NULL, &VertexShader);
	assert(SUCCEEDED(hr));

	hr = Device->CreatePixelShader(PsBlob->GetBufferPointer(), PsBlob->GetBufferSize(), NULL, &PixelShader);
	assert(SUCCEEDED(hr));
}

Shader::~Shader()
{
	SAFE_RELEASE(VsBlob);
	SAFE_RELEASE(PsBlob);

	SAFE_RELEASE(VertexShader);
	SAFE_RELEASE(PixelShader);

	SAFE_RELEASE(InputLayout);
}

void Shader::CreateInputLayout(D3D11_INPUT_ELEMENT_DESC * desc, UINT count)
{
	HRESULT hr = Device->CreateInputLayout(desc, count, VsBlob->GetBufferPointer(), VsBlob->GetBufferSize(), &InputLayout);
	assert(SUCCEEDED(hr));
}

void Shader::Render()
{
	DeviceContext->VSSetShader(VertexShader, NULL, 0); //뒤에는 뭔지 몰라서NULL, 0 
	DeviceContext->PSSetShader(PixelShader, NULL, 0);
    
    DeviceContext->IASetInputLayout(InputLayout);
}

3)  DeviceContext->IASetInputLayout(InputLayout);추가로 넣어주는데

이것은 정점마다 레이아웃이 달라지기 때문 

 

4) Device.cpp로 가자 여기서 Shader 관련된건 다 빼버리자. 

Device.cpp

/ CreateShader 부분 / void destroy() 부분

//Create Shader
	{
		HRESULT hr;
		hr = D3DX11CompileFromFile(L"Effect.hlsl", 0, 0, "VS", "vs_5_0", 0, 0, 0, &VsBlob, 0, 0);
		assert(SUCCEEDED(hr));
		hr = D3DX11CompileFromFile(L"Effect.hlsl", 0, 0, "PS", "ps_5_0", 0, 0, 0, &PsBlob, 0, 0);
		assert(SUCCEEDED(hr));
		hr = Device->CreateVertexShader(VsBlob->GetBufferPointer(), VsBlob->GetBufferSize(), NULL, &VertexShader);
		assert(SUCCEEDED(hr));
		hr = Device->CreatePixelShader(PsBlob->GetBufferPointer(), PsBlob->GetBufferSize(), NULL, &PixelShader);
		assert(SUCCEEDED(hr));

		DeviceContext->VSSetShader(VertexShader, 0, 0);
		DeviceContext->PSSetShader(PixelShader, 0, 0);
} //쉐이더 관련 부분 전부 삭제
void destroy()
{
    VertexShader->Release();
	PixelShader->Release();
	VsBlob->Release();
	PsBlob->Release();
    //쉐이더 관련 부분

 

5) 그다음 Line.h로 가자 이제 보통 렌더링 할 클래스 들이 쉐이더 하나씩 갖고 있게 될 것이다.

안에 보면 class Shader* shader;라고 붙은 부분에 class는 전방 선언이다. 이 것이 만약 쉐이더헤더를 인클루드 할 수 있는데 나중에 자료형이 꼬이는 현상이 생긴다. 앞에 class는 몸체는 없고 일단 이렇게 쓰겠다고 알려만 주는 것 

 

또한 쉐이더 들어갔으니

ID3D11InputLayout* inputLayout; // 지워준다

 

Line.h

#pragma once

#define MAX_LINE_COUNT 1000

class Line
{
public:
	Line();
	~Line();

	void Add(D3DXVECTOR2 start, D3DXVECTOR2 end);
	void Add(D3DXVECTOR2 start, D3DXVECTOR2 end, D3DXCOLOR color);
	void Add(D3DXVECTOR2 start, D3DXVECTOR2 end, D3DXCOLOR startColor, D3DXCOLOR endColor);
	void Render();

private:
	float GetWidth(float position) { return position / (float)Width * 2.0f - 1.0f; }
	float GetHeight(float position) { return -(position / (float)Height * 2.0f - 1.0f); }

private:
	struct Vertex
	{
		D3DXVECTOR3 Location;
		D3DXCOLOR Color;

		Vertex()
		{
			Location = D3DXVECTOR3(0, 0, 0);
			Color = D3DXCOLOR(0, 0, 0, 1);
		}
	};

private:
	UINT drawCount = 0;

	class Shader* shader; // class 는 전방 선언 

	Vertex vertices[MAX_LINE_COUNT];
	ID3D11Buffer* vertexBuffer;
};

 

6) Line.cpp에서 

Device->CreateInputLayout(layoutDesc, elementCount, VsBlob->GetBufferPointer(), VsBlob->GetBufferSize(), &InputLayout);
//이부분을 지워주고 

==> 부분 교체 

/^ 들어가야 할 것*/
shader = new Shader(L"effect.hlsl"); /Wsting 이라서 앞에 L자 붙여줌
shader->CreateInputLayout (layoutDesc, elementCount);

 

7) 그리고 어제 우리는 RELEASE하는 매크로를 만들었다.

그리고 아까 쉐이더를 new로 할당 하였다 

shader = new Shader(L"effect.hlsl");  // 그럼 delete로 지워야 하는데 

if (shader != NULL)

{

    delete shader;

    shader = NULL;

} // 형식으로 delete 해줘야 한다. 그런데 너무 귀찮으니까. 

-> stdafx.h 파일에서 설정해주자 (맨 마지막에 추가해주면 된다) 

 

#define SAFE_DELETE(p) {if(p != NULL){delete p;p = NULL;} }

 

그러고 나서 아래처럼 DELETE형식으로 해주면 된다. 

 

Line::~Line()
{
	SAFE_RELEASE(vertexBuffer);
	SAFE_DELETE(shader);
}

 

 

8) 만들어준 것을 Line에 넣어주었다. 

주의할 점 : 기존 전에 배웠던 파일에서 _> 대문자가 소문자로 바뀌었다는 점 

vertexBuffer / vertices 소문자로 바뀌었음 

 

9) Line.cpp/Render부분 이제 아까 쉐이더로 포함 됐으니 바꿔준다. 

이렇게 하고 실행해보자

 

실행하면 선이 그대로 출력되는 것을 볼 수 있다. 

 

빨간색 

빨-->파

 

 

void Line::Render()
{
	D3D11_MAPPED_SUBRESOURCE subResource;
	DeviceContext->Map(vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	{
		memcpy(subResource.pData, vertices, sizeof(Vertex) * MAX_LINE_COUNT);
	}
	DeviceContext->Unmap(vertexBuffer, 0);


	UINT stride = sizeof(Vertex);
	UINT offset = 0;

	DeviceContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
	//DeviceContext->IASetInputLayout(inputLayout);// 렌더에 포함 되었으니 지워주고 
	DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
	
    shader->Render();
    
	DeviceContext->Draw(drawCount, 0);

	drawCount = 0;
}

Line.cpp

#include "stdafx.h"
#include "System/Device.h"
#include "System/Shader.h"
#include "Line.h"

Line::Line()
{
	D3D11_INPUT_ELEMENT_DESC layoutDesc[] =
	{
		"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0,
		"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0
	};
	UINT elementCount = ARRAYSIZE(layoutDesc);

	shader = new Shader(L"effect.hlsl"); 
	shader->CreateInputLayout(layoutDesc, elementCount);


	D3D11_BUFFER_DESC desc = { 0 };
	desc.Usage = D3D11_USAGE_DYNAMIC;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.ByteWidth = sizeof(Vertex) * MAX_LINE_COUNT;
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

	D3D11_SUBRESOURCE_DATA data = { 0 };
	data.pSysMem = vertices;

	Device->CreateBuffer(&desc, &data, &vertexBuffer);
}

void Line::Add(D3DXVECTOR2 start, D3DXVECTOR2 end)
{
	Add(start, end, D3DXCOLOR(0, 0, 0, 1));
}

void Line::Add(D3DXVECTOR2 start, D3DXVECTOR2 end, D3DXCOLOR color)
{
	Add(start, end, color, color);
}

void Line::Add(D3DXVECTOR2 start, D3DXVECTOR2 end, D3DXCOLOR startColor, D3DXCOLOR endColor)
{
	D3DXVECTOR3 tempStart(0, 0, 0);
	tempStart.x = GetWidth(start.x);
	tempStart.y = GetHeight(start.y);

	D3DXVECTOR3 tempEnd(0, 0, 0);
	tempEnd.x = GetWidth(end.x);
	tempEnd.y = GetHeight(end.y);

	vertices[drawCount].Location = tempStart;
	vertices[drawCount++].Color = startColor;

	vertices[drawCount].Location = tempEnd;
	vertices[drawCount++].Color = endColor;
}

Line::~Line()
{
	SAFE_RELEASE(vertexBuffer);
	SAFE_DELETE(shader);

}

void Line::Render()
{
	D3D11_MAPPED_SUBRESOURCE subResource;
	DeviceContext->Map(vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	{
		memcpy(subResource.pData, vertices, sizeof(Vertex) * MAX_LINE_COUNT);
	}
	DeviceContext->Unmap(vertexBuffer, 0);


	UINT stride = sizeof(Vertex);
	UINT offset = 0;

	DeviceContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
	DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);

	shader->Render();
	DeviceContext->Draw(drawCount, 0);

	drawCount = 0;
}

 

 

우리가 이 버퍼 부분 이너무 길다 (복잡하다) 이곳도 바꾸자 

쉐이더처럼 한쪽으로 모아 두자 쓰기 편한 게 

System-> 추가 새 항목 -> Buffer.h / Buffer.cpp 생성 

 

Line.cpp 버퍼 부분 

Line::Line()
{
	D3D11_INPUT_ELEMENT_DESC layoutDesc[] =
	{
		"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0,
		"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0
	};
	UINT elementCount = ARRAYSIZE(layoutDesc);

	shader = new Shader(L"effect.hlsl"); 
	shader->CreateInputLayout(layoutDesc, elementCount);


	D3D11_BUFFER_DESC desc = { 0 };
	desc.Usage = D3D11_USAGE_DYNAMIC;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.ByteWidth = sizeof(Vertex) * MAX_LINE_COUNT;
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

	D3D11_SUBRESOURCE_DATA data = { 0 };
	data.pSysMem = vertices;

	Device->CreateBuffer(&desc, &data, &vertexBuffer);
}

 

9) 우리가 만든 Line.h 에서 보면 

여기에도 버퍼를 가지고 있는데 그래서 Buffer.h에도 추가해준다. 

 

.
private:
	UINT drawCount = 0;

	class Shader* shader; // class 는 전방 선언 

	Vertex vertices[MAX_LINE_COUNT];

	ID3D11Buffer* vertexBuffer;
	ID3D11InputLayout* inputLayout; //버퍼를 갖고 있음 
};

 

Class명 바꿔줘야함(VertexBuffer임) 버퍼 부분 확인 왼쪽 Buffer.h / 오른쪽 Line.cpp

이렇게 한 다음 생성자()를 만들어주자 

Buffer() //파라미터 들어갈 자리 void 포인터  

~Buffer()

 

우리가 정점 자료형이 버텍스 정의하는 구조체 자료형이 다 달라질 것이다. 

처음에 Line그릴 때는 정점에 Location만 있었는데.

그렇게 했다가 처음 컬러 넣으면서 늘어났는데. 

그러니 어떤 자료형이 들어올지 모른다. 

 

그럴 때 쓰는 것이 void이다. 

어떤 자료형이 들어올지 모를 때 

void* //
int* //이 자료형이 뭐인지 모를때

지금 여기에서 정점을 관리하려는 데이터가 어떤 자료형인지 알 수 없다

받는 입장에서는 그래서 void에 포인터로 받는 것이다.

 

UINT- Unsigned int 형

stride = 정점 하나의 크기 

count 곱하기 stride 하면  정점 전체 바이트 수가 나온다. 

bool bCputWrite = cpu에서 쓸 수 있느냐

GpuWrite = Gpu 쪽에서 처리해서 CPU 쪽으로 보내는 것 

Buffer.h

public:

	Buffer(void* data, UINT count, UINT stride, bool bCpuWirte = false, bool bGpuWrite = false);
	~Buffer();
.

 

10) 멤버를 몇 개 더 만들자 Buffer.h

private:
	ID3D11Buffer* buffer; 

	void* data;
	UINT count;
	UINT stride;
	
	bool bCpuWrite;
	bool bGpuWrite;
};

 

우리가 아래처럼 선언했다고 치면 현재 이름이 같은데 

안에 그냥 value라고 쓰면 (int value) 안에 것을 인식한다. 

예제1) 
class A 
{
	void Add(int value) //이게 우선순위라서 
    {
    	value; //이러면 컴파일러가 void Add(int value) 이 안에 있는 value로 인식한다. 
    }
    
    int value;
}

그래서 우리는 이럴 때 사용할 수 있는 포인터 가

This pointer라고 한다. 

 

this는 자기가 가지고 있는 멤버 

this의 의미가 자기 scene의 객체를 의미한다. 

예를 들어 A* ob = new A();라는 것을 외부에서 선언했다면 

this가 ob가 되는 것이다. ob를 가리키는 포인터 

this => ob 자기 자신 

 

아래처럼 쓰이면 this는 value = value 자기 자신의 value니까 int value; 부분 

왜 이런 식으로 복잡하게 쓰는가? 

객체지향은 유지보수를 쉽게 하려고 하는 것인데 

유지보수가 쉬울려면 이름이 같은 것이 나은데 

그래서 this pointer인데 

만약에 *this; 하면 ob와 같은 것이 된다. 

ob객체 전체를 의미한다. 

this 앞에 포인터를 붙여서 객체 전체를 의미하게 만드는 것을 역참조라고 한다. 

 

역참조)

 

[C언어] 포인터의 역참조(dereference)

저번 강의에서 포인터가 무엇인지에 대해서 배웠습니다. 이번 강의에서는 포인터로 무엇을 할 수 있는지 알...

blog.naver.com

예제1-2)
class A 
{
	void Add(int value) = value
    {
    	this -> value(this의 멤버) = value(이게위에) //자기가 가지고 있는 멤버 int value; 
    }
    
    int value; this의 멤버
} //this의 Scene

 

11) 그렇다면 Buffer.cpp에서 받을 때 this -> data = data

왼쪽 : Buffer.h/ 오른쪽Buffer.cpp 

Buffer.h에 있는 data를 Buffer::Buffer(void * data, 에 있는 데이터에 넣는다고 말하는 것 

그런데 더 편한 방법이 있다. 

 

데이터에 데이터 그러면 색이 같아지는데 이니셜 라이즈 부분에서 쓰면 자기 레퍼?(레퍼런스)? 의미해버린다.

거기에다 이걸 넣는다라는 의미  

VertexBuffer::VertexBuffer(void * data, UINT count, UINT stride, bool bCpuWrite, bool bGpuWrite)
	:data(data), count(count), stride(stried), 
    bCpuWrite(bCputWrite), bGpuWirte(bGpuWrite) //이런식으로 내리거나 관련되 있는 것 끼리 내려도 됨
{
	버퍼 부분 들어갈 자리 (Line.cpp/복사해오기) 
}

클래스명 변경 해야함 (VertexBuffer) 왼쪽 > Buffer.h / 오른쪽 : Buffer.cpp 

서로 같은 색이 보인다. (data)부분은 cpp의 buffer 쪽이랑 같아 진걸 볼 수 있다. 

 

12) 이제 Line.cpp에서 버퍼 부분을 복사해서 가져오자 

Buffer.cpp / 들어갈 버퍼 중에 USAGE를 상황에 따라 바꿀 것이다. 

#include "stdafx.h"
#include "Buffer.h"

VertexBuffer::VertexBuffer(void * data, UINT count, UINT stride, bool bCpuWrite, bool bGpuWrite)
	:data(data), count(count), stride(stride), bCpuWrite(bCpuWrite), bGpuWrite(bGpuWrite)
{
	D3D11_BUFFER_DESC desc = { 0 };

	if (bCpuWrite == false && bGpuWrite == false) //기본 값false로 해놔서 
	{
		desc.Usage = D3D11_USAGE_IMMUTABLE; //기본 IMMUTABLE 이란 말이다. 
	}
	else if (bCpuWrite == true && bGpuWrite == false)//아니라면 CPU에서 쓸수 있다. 그러면 DYNAMIC
	{
		desc.Usage = D3D11_USAGE_DYNAMIC; //DYNAMIC일때 
		desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; //ACCESSCPU부분 들어가야함 
	}
	else if (bCpuWrite == false && bGpuWrite == true)//CPU는 쓰기 불가능 GPU는 쓰기 가능 
	{
		desc.Usage = D3D11_USAGE_DEFAULT;//그러면 기본값 디폴트로 받는다. 
	}
	else  //이도 저도 아니다 else 는 둘다 true
	{
		desc.Usage = D3D11_USAGE_STAGING;// 기본 STAGING
		desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE; //CPU에서 읽기도 쓰기도 가능하다 
	}

	desc.Usage = D3D11_USAGE_DYNAMIC;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.ByteWidth = count * stride; //전체 크기 
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

	D3D11_SUBRESOURCE_DATA subResource = { 0 }; //subResource변경
	subResource.pSysMem = data; //받은 data를 넣어줌
}
  CPU GPU 특성
Default X (RAM->VRAM) 불가 O (VRAM->RAM) 가능 CPU부분(초기화 (CreateBuffer) 할때 넣어주면 그때 한번만 가능
Immutable X (RAM->VRAM) 불가 X (VRAM->RAM) 불가 상수 인데 한번만 밀어넣으면 처음 초기화 시킬때 보내고 더이상 변경이 없을때 거의 이걸 사용 (빠름)
Dynamic O (RAM -> VRAM) 가능 X (VRAM->RAM) 불가 Default와는 속도 거의비슷
/바뀔때 마다 보낼 수 있는 것
Staging(느림) O (RAM -> VRAM) 가능 O (VRAM->RAM) 가능 둘다 가능하나 느림(잘사용안함)


전에 배웠던걸 확인해보면 이때는 이뮤 테이블 이용한다. USAGE = 이뮤테이블 넣어줄 것이다.   

 

우리가 다이나믹 쓸 때는 MAP을 이용해 썼는데 

디폴트도 CPU에서 GPU로 보내는 방법이 있는데 

업데이트 서브 리소스로 가능하다 

Map = 읽기 쓰기 가능 

UpdateSubResource = 쓰기 가능 

desc.Usage=D3D11_USAGE_DYNAMIC; 
//Read,Write둘다 가능  - Map을 이용했는데 

desc.Usage=D3D11_USAGE_DEFAULT;//디폴트에서는 CPU -> GPU로 보내는 방법 Write쓰기만 가능 
// - UpdateSubResource

 

바이트 위드쓰 (ByteWidth)는  어떻게 계산할 수 있는가?

count 개수에다가 stride(자료형 하나의 크기)를 곱해준다

ByteWidth = count * stride (전체 크기를 알 수 있다) 

 

 

Buffer.cpp안에 아래 변경해주고 

.
	desc.Usage = D3D11_USAGE_DYNAMIC;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.ByteWidth = count * stride; //전체 크기 
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

	D3D11_SUBRESOURCE_DATA subResource = { 0 }; //subResource변경
	subResource.pSysMem = data; //받은 data를 넣어줌
    
    HRESULT hr = Device->CreateBuffer(&desc, data != NULL ? &subResource : NULL , &buffer);//HRESULT hr로 리턴 해주자 
	assert(SUCCEEDED(hr));
}

 

 

13) 그다음에 CreateBuffer 해주자 HRESULT로 리턴도 해주자 

그리고 안에 data자체는 포인터 니까 주소이니까 

 

데이터가 NULL이랑 같지 않다면 주소가 들어왔다고 하면 여기다 서브리소스를 넣어주고 

만약에 주소가 들어오지 않는다면 (데이터가 없는 상황이라면) NULL로 표기 

data != NULL ? subResource : NULL

참이면 앞에꺼 거짓이면 뒤에꺼 

 

? ;삼항 연산자

 

C++ 삼항연산자 (조건연산자) 사용 방법 설명 > Dev-Bear

삼항연산자 란? C++에서 삼항연산자(조건연산자) 사용 방법 설명. if ~ else 문을 대신하여 사용할 수 있는 연산자. 조건연산자 또는 삼항연산자라고 불립니다. '조건' ? 'A' : 'B'조건이 참이면 A를 반

dev-bear.com

 

14) Buffer::~Buffer()부분 

VertexBuffer::~VertexBuffer()
{
	SAFE_RELEASE(buffer);
}

 

15)Line.cpp /Render부분 아래를 보면 Render때 

DeviceContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

이게 있는데 이제 buffer.h/cpp에서도 Render함수를 하나 만들어 주자 

 

buffer.cpp부분 

void VertexBuffer::Render()
{
	UINT offset = 0;
	DeviceContext->IASetVertexBuffers(0, 1, &buffer, &stride, &offset);
}

 

우리는 Shader.h/ Buffer.h도 프로그램 전체에서 쓸거기 때문에 stdafx.h에다 넣어주자 

#include "System/Shader.h"
#include "System/Buffer.h" // 경로 지정 Line.h에서 class (전방선언자) 빼도 된다. 

 

class Shader* shader;
=> 변경 
Shader* shader;

 

갑분팁 ) 클래스가 꼭 한 파일 필요는 없다. 한 파일 여러클래스도 가능하다 

 

16)Line.h 돌아가서 

private:
	UINT drawCount = 0;
    Shader* shader;
	
    VertexBuffer* vertexBuffer; // 기존 전방 선언 제거 하고 변경 
	Vertex vertices[MAX_LINE_COUNT];
    //ID3D11Buffer* vertexBuffer;// 통합됬으니까 제거 
};

 

 

17) Line.cpp로 돌아가서 

vertexBuffer = new VertexBuffer(verticese데이터가 들어감, MAX_LINE_COUNT몇개 그릴건지, sizeof(Vertex)데이터 하나의 크기 , true cpu에서 계속Map으로 쓸거기에(true/false));

 

.
    vertexBuffer = new VertexBuffer(vertices, MAX_LINE_COUNT, sizeof(Vertex), true);

	/*D3D11_BUFFER_DESC desc = { 0 };
	desc.Usage = D3D11_USAGE_DYNAMIC;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.ByteWidth = sizeof(Vertex) * MAX_LINE_COUNT;
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

	D3D11_SUBRESOURCE_DATA data = { 0 };
	data.pSysMem = vertices;

	Device->CreateBuffer(&desc, &data, &vertexBuffer); */ //다 필요없어짐 
}

 

18) Line.cpp에서 vertexBuffer가 SAFE_DELETE로 바뀌고 

Line::~Line()
{
	SAFE_DELETE(vertexBuffer);
	SAFE_DELETE(shader);
}

 

19) 이제 Line::Render부분에 함수를 하나 만들 건데. Buffer.h에 들어가서  

class VertexBuffer
{
public:
	ID3D11Buffer* GetBuffer() { return buffer; }//뭐가 리턴되는지 보기 편해서 맨위에 써준다
public:
...

 

20) 그러면 Line::Render()부분 vertexBuffer->GetBuffer (가져옴) 

void Line::Render()
{
	D3D11_MAPPED_SUBRESOURCE subResource;
	DeviceContext->Map(vertexBuffer->GetBuffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	{
		memcpy(subResource.pData, vertices, sizeof(Vertex) * MAX_LINE_COUNT);
	}
	DeviceContext->Unmap(vertexBuffer->GetBuffer(), 0); //변경

 

 

 

 

 

21) 그리고 우리가 Buffer.cpp 에 보면 IASetVertexBuffers를

가져왔었는데 (여기다 넣어뒀으니)

Line.cpp에서 변경 부분 변경후 실행해 보면 전이랑 정상적으로 출력되는걸

볼 수있다. 

void VertexBuffer::Render()
{
	UINT offset = 0;
	DeviceContext->IASetVertexBuffers(0, 1, &buffer, &stride, &offset);
}

 

Line.cpp

void Line::Render()
{
	D3D11_MAPPED_SUBRESOURCE subResource;
	DeviceContext->Map(vertexBuffer->GetBuffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	{
		memcpy(subResource.pData, vertices, sizeof(Vertex) * MAX_LINE_COUNT);
	}
	DeviceContext->Unmap(vertexBuffer->GetBuffer(), 0);


	UINT stride = sizeof(Vertex);
	UINT offset = 0;

	//DeviceContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);필요없음 
	DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);

	shader->Render();
    vertexBuffer->Render();// 이것만 해주면 됨 
	DeviceContext->Draw(drawCount, 0);

	drawCount = 0;
}

 

 

이렇게 했던 이유는 라인말고도 또하나를 그려볼 것이다.사각형을 그려볼 것인데

이제 다음 프로젝트로 가보자 

======================================================================

2) Rectangle/Triangle RecTangle 사각형을 만들어보자 

RecTangle폴더를 만들자 

main.cpp에서 제거

 

라인 같은경우 정점을 계속 추가하는 방식이였는데

사각형은 객체하나가 사각형 하나가 될 것이다 (그런식으로 다루는 편이 더 편하다)

사각형은 정점 몆개가 필요할까? (정점 4개) 

그럼 4개의 위치를 받으면 되겠지? 근데 그렇게 할 필요없다

시작 위치랑 넓이 높이만 있으면 되지 않을까?

 

기준점이 있다하면 가로가 넓이가 될테고

세로가 높이가 될 것이다. 예를 들자면

그래서 나는 위치하고 넓이 높이만 받아 올 것이다.

그거로 부터 정점을 계산 해볼 것이다.

사각형도 정리 해보자 

 

메인 프로젝트 -> 클래스 추가 새항목 -> 폴더 Rectangle 시스템 아님  Rect.h/ Rect.cpp 생성/ 

(수정 : Line.cpp / #include "System/Device.h" 부분 어차피 디바이스 프로그램전체에 쓰이기에  / stdafx.h에 넣자)

 

우리가 일단은 사각형을 그리기 전에 삼각형부터 가야하는데 

 

갑분팁 )

모든 그래픽 프로그램에서는 사각형을 선으로 그을 수 있는데 그러면 색을 못채운다

그런데 색을 채워야 하는데 사각형을 그리기 전에 삼각형을 먼저 그릴 것이다.

왜 삼각형을 먼저 그리냐면 모든 그래픽 프로그램에서는 기본도형이 삼각형 이다. 

 

그래픽 프로그램에서는 사각형이란 도형은 없다. 

삼각형 2개를 붙여서 사각형을 만들 것이다

그래서 중요한 것인 삼각함수 이다. 

 

원래 삼각함수는 기하학에 포함된 학문인데 기하(도형) 

게임프로그래밍에서 삼각형같은 도형을 다루기 때문에

그래서 기하학이 중요하다

 

기하학을 이해못하면 제대로 프로그래밍을 못한다.

도형을 자체를 움직일때 사용하는게 행렬 (대수학) 

대수학을 이해를 못하면 안된다.

 

일반 적인 게임 프로그래밍에서는 대수학을 다루진 않고 선형대수학을 다룬다

그래서 기하학과 선형대수학은 중요하다 

미분/ 적분은 쉐이더를 다룰때 필요하다

 

 

1) Rect.h 수정 

일단은 외부에 위치 안받고 한번 그려보자 

기본생성자 Vertex()부분

= 배열일때는 기본생성자를 다 콜해버린다. 배열일때 기본생성자가 없으면 오류 난다.

#pragma once

class Rect
{
public:
	Rect();
	~Rect();

	void Render();
private:
	struct Vertex
	{
		D3DXVECTOR3 Location;
		D3DXCOLOR Color;
		
		Vertex()
		{
			Location = D3DXVECTOR3();
			Color = D3DXCOLOR();
		} //기본생성자
		Vertex(D3DXVECTOR3 location, D3DXCOLOR color)
		{
			Location = location;
			Color = color;
		}
	};


private:
	Shader* shader;

	Vertex vertices[3];
	VertexBuffer* vertexBuffer;
};

 

2) Line.cpp를 보면서 Rect.cpp 수정 

Line.cpp에서  layoutDesc는 똑같다. Rect::Rect()에붙여넣기 해준다. 

Line.cpp에서 매크로 SAFE_DELETE도 가져오자 

Render부분도 drawCount = 0;빼고 복사해오자 

 

DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);

여기에 LINELIST로 되어있는데 TRIANGLELIST로 변경 해준다. 트라이앵글 리스트 

 

위에서 데이터를 만들어야한다 vertices 

맨위에 vertices[0]

만들기전 Rect.h에다 생성자를 만들자.(구조체는 대문자 쓴다) 

 

Rect.cpp test

#include "stdafx.h"
#include "Rect.h"

Rect::Rect()
{
	vertices[0] = Vertex(D3DXVECTOR3(-1, -1, 0), D3DXCOLOR(0, 1, 0, 1));
	vertices[1] = Vertex(D3DXVECTOR3(-1, +1, 0), D3DXCOLOR(1, 0, 0, 1));
	vertices[2] = Vertex(D3DXVECTOR3(+1, -1, 0), D3DXCOLOR(0, 0, 1, 1));


	D3D11_INPUT_ELEMENT_DESC layoutDesc[] =
	{
		"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0,
		"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0
	};
	UINT elementCount = ARRAYSIZE(layoutDesc);

	shader = new Shader(L"effect.hlsl");
	shader->CreateInputLayout(layoutDesc, elementCount);

	vertexBuffer = new VertexBuffer(vertices, 3, sizeof(Vertex));
}

Rect::~Rect()
{
	SAFE_DELETE(vertexBuffer);
	SAFE_DELETE(shader);
}

void Rect::Render()
{
	DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	shader->Render();
	vertexBuffer->Render();
	DeviceContext->Draw(3, 0);
}

 

그런데 우리가 이걸 그릴때 주의사항이 있다.

예를 들어 . 0 번 1번 2번 정점이 있다 하자

위치가 0 1 2 가 시계방향으로 구성되어야 한다. 

이게 시계 방향이라고 구성이 안되면 

DX에서 그릴애가 아니라고 판단한다

 

0. 

width = 100

height =100

1.

width = 100

height = 0 

2.

width = 100

height = 100 

100 X 100 사이즈 삼각형이 나올것이다

 

(오류 수정사항)

Rect.h 기본생성자(배열) 추가

Vertex()
{
Location = D3DXVECTOR3();
Color = D3DXCOLOR();
}

/

Rect.cpp /Render부분

UINT stride = sizeof(Vertex);
UINT offset = 0;

/

Buffer.cpp 부분 삭제 

desc.Usage = D3D11_USAGE_DYNAMIC;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; 삭제 

 

 

3) main.cpp로 가서 #include "Rect.h" 추가 후 수정 

#include "stdafx.h"
#include "System/Device.h"
#include "Utilities/Math.h"
#include "Text.h"
#include "Rect.h"


Rect* rect = NULL;
void InitScene()
{
	rect = new Rect();
}

void DestroyScene()
{
	SAFE_DELETE(rect);
}


void Update()
{
	
}

void Render()
{
	rect->Render();
}

 

 

 

Rect.cpp에서 이렇게 바꿔주었다 그러면 디버깅하면

Rect::Rect()
{
	vertices[0] = Vertex(D3DXVECTOR3(-1, -1, 0), D3DXCOLOR(0, 1, 0, 1));
	vertices[1] = Vertex(D3DXVECTOR3(-1, +1, 0), D3DXCOLOR(1, 0, 0, 1));
	vertices[2] = Vertex(D3DXVECTOR3(+1, -1, 0), D3DXCOLOR(0, 0, 1, 1));
...

보면 왼쪽이 -1 이고 아래쪽이 녹색 -1 올라올수록 +플러스

 

 

 

이런식으로 나오는걸 알 수 있다.

우리가 지금 그린 모양은 이렇게 나왔다. 

0 -> 1 -> 2 

2번이 내려오면 되겠네 2번 을 중간 -1 로 바꿔주자 

vertices[0] = Vertex(D3DXVECTOR3(-1, -1, 0), D3DXCOLOR(0, 1, 0, 1));
vertices[1] = Vertex(D3DXVECTOR3(-1, +1, 0), D3DXCOLOR(1, 0, 0, 1));
vertices[2] = Vertex(D3DXVECTOR3(+1, -1, 0), D3DXCOLOR(0, 0, 1, 1));

 

그럼 아까 원하던 모양으로 나오는 걸 볼 수 있다. 

 

 

 

 

 

 

 

 

우리가 전에 IA -> VS -> RS -> PS 가 있다고 했다

여기서RS가 하는 역할이 픽셀을 채우는 것도 있지만 공간을 채우면서 보간을 채우면서 보간 인터폴레이션 역할

정점이 3개고 이애들은 연결하기 위해 픽셀들을 쭉 채워간것 

색이 점점 리니어 하게 변해가는걸 볼 수 있다. 

 

RS(Rasterizer Stage): RS거치면서 3D에서 2D로 바뀐다. 

1)중간을 보간해주는 역할 정점 사이를 보간하여 픽셀을 채워줌 

2) 정점이 픽셀로 바뀌고 정점이 연결 

 

 

 

그래픽 파이프라인 (Graphic Pipeline)

그래픽 파이프라인 (Graphic Pipeline) 정점이 들어가 화면에 출력되기 까지의 과정 IA → VS → HS → TS → DS → GS → SO → RS → PS → OM // IA → . . . → SO : 3D 단계 // RS → PS → OM : 2D 단계 //..

hombody.tistory.com

 

 

삼각형이 하나 가지고는 사각형을 못그린다. 그러니까. 삼각형이 2개여야 가능하다. 

지금 저 삼각형을 그대로 뒤집어서 반대로 붙이면 되겠지? 

 

그리고 방금 우리가 0 -> 1 -> 2 뭐 어느 번호에서 스타트 하던지 상관은 없다. 무조건 시계 방향만 만들면 된다. 2번 1번 은 똑같으니까 2번 1번 똑같이 쓰고 마지막 끝점 만 만드는 방식을 쓴다. 즉 2번=3번 이되고 1번은 4번이 되고 끝점이 5번 하나 만들면 되는 것 개발자 마음대로 시작 번호가 다 다르긴 함 그래도 이게 가장 편한다 

 

 

 

4) Rect.h에서 배열 수를 6개로 늘리자 (아래있는 private) 

private:
	Shader* shader;

	Vertex vertices[6];
	VertexBuffer* vertexBuffer;
};

 

 

5) Rect.cpp 사각형을 위해서 2 => 3 1 => 4  5번 +1 +1 오른쪽 위니까 

	vertices[0] = Vertex(D3DXVECTOR3(-1, -1, 0), D3DXCOLOR(0, 1, 0, 1));
	vertices[1] = Vertex(D3DXVECTOR3(-1, +1, 0), D3DXCOLOR(1, 0, 0, 1));
	vertices[2] = Vertex(D3DXVECTOR3(+1, -1, 0), D3DXCOLOR(0, 0, 1, 1));

	vertices[3] = Vertex(D3DXVECTOR3(+1, -1, 0), D3DXCOLOR(0, 0, 1, 1));
	vertices[4] = Vertex(D3DXVECTOR3(-1, +1, 0), D3DXCOLOR(1, 0, 0, 1));
	vertices[5] = Vertex(D3DXVECTOR3(+1, +1, 0), D3DXCOLOR(1, 0, 0, 1));

 

 

6) 아래 갯수도 수정 

vertexBuffer = new VertexBuffer(vertices, 6, sizeof(Vertex));
.
.
.
DeviceContext->Draw(6, 0);

그러면 사각형 완성이 된다. 

그런데 우리 비율로 줄여보자 

 

 

7) Line.h로 가자 비율로 줄이기 위해서

만들어 둔것이 있는데 복사해오자 

Rect.h 에 넣자 private로 

private: 

	float GetWidth(float position) { return position / (float)Width * 2.0f - 1.0f; }
	float GetHeight(float position) { return -(position / (float)Height * 2.0f - 1.0f); }

8) Rect.cpp 로 가서 비율을 맞춰 준다. (현재 뒤집어주자 계산을 다시해야함) 

Rect::Rect()
{
	float minusX = GetWidth(100);
	float plusX = GetWidth(200);

	float minusY = -GetHeight(100); //축 뒤집음
	float plusY = GetHeight(200);

	vertices[0] = Vertex(D3DXVECTOR3(minusX, minusY, 0), D3DXCOLOR(0, 1, 0, 1));
	vertices[1] = Vertex(D3DXVECTOR3(minusX, plusY, 0), D3DXCOLOR(1, 0, 0, 1));
	vertices[2] = Vertex(D3DXVECTOR3(plusX, minusY, 0), D3DXCOLOR(0, 0, 1, 1));

	vertices[3] = Vertex(D3DXVECTOR3(plusX, minusY, 0), D3DXCOLOR(0, 0, 1, 1));
	vertices[4] = Vertex(D3DXVECTOR3(minusX, plusY, 0), D3DXCOLOR(1, 0, 0, 1));
	vertices[5] = Vertex(D3DXVECTOR3(plusX, plusY, 0), D3DXCOLOR(1, 0, 0, 1));

 

우리가 사각형을 움직인다고 치면 정점 6개가 다 움직여야 한다. 

CPU데이터를 바꾸고 GPU로map해서 넣어줘야 한다.

그러면 사각형이 100개면 600개 1000개면 6000개 형식으로 저걸 

다 밀어줄 수가 없다. 

그럼 시스템이 당연히 

 

언리얼 기준

캐릭터 하나에 23.297개 이다 

10개면 대략 20만개 이다 

그래서 게임에서는 NDC공간에 정점을 다 움직이는 방식을 쓰진 않는다.

 

그래서 World라는 개념을 쓴다. 

=================================================================

 

World 

로컬 공간 : World안의 각 정점의 위치

World : 정점이 담겨있는 공간의 크기, 회전, 위치(행렬) 

 

예) 구슬과 종이컵(world)가 있다고 치자

구슬의 좌표를 정점의 좌표를 설정하듯이

Vector3(0.5f, 0.5f, 0.0f) 식으로 설정한 좌표를 로컬 좌표라고 한다. 

이 로컬 좌표를 월드(world)로 변환하는 것은 

Vector3(0.5f, 0.5f, 0.0f)위치에 있는 종이컵(World)에 구슬을 담아서

// 이때 종이컵(world)안에서 Vector3(0.5f, 0.5f, 0.0f)위치에 구슬이 위치하게 된다.

 

Vector3(0.5f, 0.5f, 0.0f)만큼 종이컵(world)을 이동했다면,

구슬 또한 Vector3(0.5f, 0.5f, 0.0f)만큼 이동하여 Vector3(5.5f, 5.5f, 0.0f)위치에 구슬이 위치하게 된다.

// Vector3(0.5f, 0.5f, 0.0f) + Vector3(5.0f, 5.0f, 0.0f)

 

또한 같은 구슬을 다른 종이컵(World)에 각각 담아서 같은 구슬을 2개 출력할 수도 있다.

종이컵(world)를 2배늘려 안에있는 구슬 또한 2배 늘어나고

종이컵(world)를 45도 회전했다면 구슬 또한 45도 회전한다. 

 

View

바라볼 위치, 방향(행렬) 

// 카메라의 위치, 방향 

projection 

보여줄 영역(행렬) 

// 시야각, 카메라가 보여줄 영역 

 

//사각형을 예를 들자면 사각형을 이루는 6개의 정점이 각 정점의 로컬좌표이고

이 로컬 좌표를 하나의 버퍼에 담아 넘기고 World * View * Projection을 곱하여

사각형을 공간좌표로 바꾸게 된다. 

 

World 4X4행렬 

===========================================================

 

엔진은 자기들이 알아서 계산을 하지만

우리는 직접 계산을 해줘야 한다.

 

이처럼 중점이 중앙이면 회전이나 크기변경시에도 일정하지만

영점이 꼭지점 이면 그 중심점으로 회전을 하기 되고 크기를 키워도 한쪽 방향으로만 큰다.

이는 게임에서 가끔 이펙트 같은것을 기준으로부터 회전 시키기 위해서 사용하기도 한다.

 

토탈워 게임같은 모델하나는 있고 월드를 여러개 넣은 형태의 게임또한 

만약 모델 하나하나 다 세워놨다 하면 리소스가 안될 것이다.

그래서 뭐 3D DX에서 배울 수 있을 인스턴싱이라는 기술이 있긴 하지만 이번에 다뤄보진 않을 것이다.

 

 

인스턴싱 (Instancing)

2012/05/30 13:28 http://blog.naver.com/sorkelf/40160183951 인스턴싱 (Instancing)  : 동일한 객체(오브젝트)를 한 화면에 여러개를 렌더링 시킬 경우에 한번의 DP Call로 처리 하는 방식을 말한다.  (나무..

3dmpengines.tistory.com

다음시간에는 Rect는 정점을 가지고 Rect클래스

하나가 객체 하나가 이 정점 6개와 월드 하나를 가질 것이다. 

 

 

 

 

 

 

 

 

 

 

 

 

다음시간) 

 

[C++] DirectX World|View|Projection 활용 구현 랜덤 사각형

ImGui 활용한 DirectX 11 3D를 이용한 2D를 만드는 내용입니다. 본 내용은 똑같이 사용할 수 없음을 알립니다. (참고및 공부자료로 만들어진 내용임을 밝힙니다.) 전 과정에 대해 이해를 해야 다음 이

ppatabox.tistory.com

 

728x90
728x90
LIST
profile

Adventure of 빠타박스

@PPATABOX

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!