Direct x 11 1주차 복습

나무늘보·2024년 1월 12일

DirectX

목록 보기
4/5

1주차 강의 복습

전체 요약을 하자면

Render Begin / Render End 넣기

Device , DeviceContext , SwapChain 이 삼총사가 매우 핵심적인 기능

// Header 
private:
	void CreateDeviceAndSwapChain();

private:
	ComPtr<ID3D11Device> _device = nullptr;
	ComPtr<ID3D11DeviceContext> _deviceContext = nullptr;
	ComPtr<IDXGISwapChain> _swapChain = nullptr;

DX 12 에서는 Device , DeviceContext 가 합쳐지지만 DX 11 에서는 분리되어있음

Device → 커맨드센터처럼 GPU를 묘사하는 대표 , 거기서 우리가 실질적으로

디바이스를 통해 리소스를 생성할때 사용 , 디바이스를 통해 리소스를 생성하면

GPU쪽에서도 생성이 된다

DeviceContext → 우리가 실제로 렌더링 파이프라인 단계에서 그 리소스를 묶어주는 역할

사실상 얘가 제가 비유하기에는 커맨드센터고 생성된 유닛들에 명령을 내리는

어떻게보면 양대 대장이라고 볼수있다

SwapChain → dxgi 라는 기술 , Direct x 보다 기술 변화 속도가 느린애들

DXgi 라는 graphic infra structure 라는 라이브러리를 만들었고 그 대표적으로

SwapChain 즉 더블 버퍼링을 통해 후면 버퍼에 우리가 그림을 그려주면 그 후면 버퍼에있는 내용을

전면 버퍼로 빠르게 고속복사해서 present 라는 함수를 때리는 순간에 고속복사가 일어나는게 핵심

이 삼총사를 만들때 , Create Device And SwapChain이라는 함수를 이용해서 만들었다

void Game::CreateDeviceAndSwapChain()
{
	//윈도우에서 제공하는함수쓰기 
	// 디바이스 만들고 swap chain 만들면 단계가 늘어나서 설정해야할게많아짐 
	//driver_type_hardware 우리가가지고있는 그래픽카드 사용
	////feature level->dx버전에 해당하는 기능을 다 지원
	//d3d11버전 매크로
	//swap chain description 요구함 , 
	DXGI_SWAP_CHAIN_DESC desc;
	//::memset(&desc, 0, sizeof(desc)); // 여기있는값을 다 0으로초기화
	// memset이랑같은기능 
	ZeroMemory(&desc, sizeof(desc)); 
	{
		// 버퍼 크기 화면크기랑 맞춰줘야함
		desc.BufferDesc.Width = _width;
		desc.BufferDesc.Height = _height;
		desc.BufferDesc.RefreshRate.Numerator = 60;
		desc.BufferDesc.RefreshRate.Denominator = 1;
		desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		desc.SampleDesc.Count = 1; // 멀티샘플링과관련된 문제
		desc.SampleDesc.Quality = 0;
		desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 최종결과물을 그려주는 역할로 정함
		desc.BufferCount = 1;
		desc.OutputWindow = _hwnd;
		desc.Windowed = true;
		desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	}
	
	
	//ID3D11Device*
	//_device.Get(); 

	//ID3D11Device**
	//_device.GetAddressOf();

	HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
		nullptr,                     // pAdapter
		D3D_DRIVER_TYPE_HARDWARE,    // DriverType
		nullptr,                     // Software
		0,                           // Flags
		nullptr,                     // pFeatureLevels
		0,                           // FeatureLevels
		D3D11_SDK_VERSION,           // SDKVersion
		&desc,                       // pSwapChainDesc
		_swapChain.GetAddressOf(),   // ppSwapChain
		_device.GetAddressOf(),      // ppDevice
		nullptr,                     // pFeatureLevel
		_deviceContext.GetAddressOf() // ppImmediateContext
	);

	// 애가 ture면 성공 실패하면 crash 나게 유도 
	CHECK(hr);
}

후면버퍼는 어떻게 될것인지를 장황하게 묘사를 해줬다 , 이렇게 장치 초기화를 해줄수있었는데

매프레임마다 Render가 호출이되는데 Render Begin RenderEnd가 반복적으로 호출이되고

Render Begin 과 Render End 사이에 반복적으로 사물을 그려주면된다 . 이게 하나의 프레임

그럼우리가 여기서 연결해준거는 SetRenderTargetView 라는걸 만들었다

SwapChain을 이용해서 후면버퍼를만들었지만 그걸 이용해서 그려달라는 말을 안했으니

SwapChain 후면에 그림을 그려주세요 라는 명령을 내리기위해서 ( 명령→ deviceContext )

void Game::RenderBegin()
{
	// 준비작업을 하고 
	//렌더링 파이프라인에서 우리가만든 리소스를 묶어줘야한다 
	//_deviceContext를 사용해줘야한다
	// OM이 마지막단계 ,결과물을 이 도화지를 건내줘
	_deviceContext->OMSetRenderTargets(1,_renderTartgetView.GetAddressOf(),nullptr); // 후면버퍼에 요청
	_deviceContext->ClearRenderTargetView(_renderTartgetView.Get(), _clearColor);
	_deviceContext->RSSetViewports(1, &_viewport);
}

이런식으로 OMSetRenderTargets 함수를 이용해서 최종 결과물을 여기에 넣어주세요 라고 입력을 해주었다.

그러기위해 RenderTargetView라는 애를 만들었는데 이미 있는 SwapChain에 BackBufferResource에서 Render Target View 라는 뷰 리소스를 만들었다

void Game::CreateRenderTargetView()
{
	HRESULT hr;
	ComPtr<ID3D11Texture2D> backBuffer = nullptr;
	//swapchain에 후면 버퍼를 가지고와달라는 호출을 해줘야함 
	//swapChain은 이제부터 일반 포인터랑 똑같이 사용가능 , 아무리 스마트포인터라도 다 오버로딩되어있음 
	hr = _swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backBuffer.GetAddressOf());
	//getbuffer해서 backbuffer를 호출하면  id3d11texture2d 얘를 backbuffer에 넣어준다 
	CHECK(hr);
	//swap chain에서 후면 버퍼에 해당하는 리소스를 id3d11 texture2d 라는 이걸로 리턴해주고 
	
	hr = _device->CreateRenderTargetView(backBuffer.Get(), nullptr, _renderTartgetView.GetAddressOf());

	CHECK(hr);
	//우리가 다시 이걸 device의 createRenderTargetView라는 걸 통해 가지고 이 아이를 묘사하는
	//RenderTargetView를 만들어 줬고
	//이제 우리가 gpu랑 통신해서 이 _renderTargetView를 가져다주면 gpu에서 그려준다
	//gpu에 요청할때 사용하는 스페셜한 포인터 느낌 

이걸 이용해서 Render Begin에서 입력을했고 Clear Color는 초창기에 리셋 해주기위한 색상

현재 BackGround 색상이라고 보면된다 초창기에만 해줘도 무방하다 매프레임마다가아니라

이 화면의 크기를 설정해야지만 RS 단계 즉 Rasterizer단계에서 화면에 들어가는지를 결정하니깐

RSSetViewports 도 해준다

Render End에서 Swap은 방법을 가리지않고 우리가 이것저것 다 그렸다고하면 Render End를

호출하게되면 Present를 호출하고 이러면 후면버퍼에서 전면버퍼로 옮겨주고 전면버퍼에그려주는

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

삼각형 하나그리는것도 만만치않았다

Vertex Buffer Index Buffer

Create Geometry를 통해 Vertices 를 아직까지 우리가 가지고있는 CPU 메모리에 있는데 이걸

GPU에 전달해주기위해 Vertext Buffer 라는 아이를 통해 복사를 시켜주고 IMMUTABLE이라는

옵션을 통해 더이상 WRITE을 못하게 오직 GPU에서만 READ가능하게 선언을 해줬고

desc Usage 부분에 D3D11_USAGE_IMMUTABLE

마지막에 IndexBuffer 도 같이 만들어줬고 , 정점이 여러개가 너무 많아지면 중복현상이 일어나니

정점은 한번만 알려주고 정점의 Index를 알려줘서 삼각형을 그려주는 노력

이게 Index Buffer 와 Vertex Buffer → IA 단계에서 삽입되는 애들

Input Layout

우리가 만든 Vertex Buffer 가 어떤식으로 묘사되어있는지를 말그대로 묘사하는 역할

쉐이더랑 매우 밀접하다 , 쉐이더에서 우리가 CPU에서 만들어준 그 데이터를 넘겨줄텐데

struct VS_INPUT
{
					  // input layout 작성할때 이름이랑 맞춰주는 이름
	float4 position : POSITION; 
	//float4 color : COLOR;
	float2 uv : TEXCOORD;
};

struct VS_OUTPUT
{
	float4 position : SV_POSITION; //system value , 얘는 필수적으로 있어야한다 라고명시 (SV)
	//float4 color : COLOR;
	float2 uv : TEXCOORD;
};

// 처음에 VS INPUT으로 들어온다 
// IA - VS - RS - PS - OM 
//VS단계는 이 포멧이 맞게끔 가야 도형이 일단 넘어온다 
// 우리가 game.cpp에서 넘겨준  값들이 여기에 들어온다는 의미 , 다만 얘는 정점단위로 실행 

cbuffer TransformData : register(b0)
{
	float4 offset;
}

이러한 쉐이더의 변수들과 어떤식으로 연결이 되어있는 지를 ex) uv : TEXCOORD같은 거

이러한 정보들을 맞춰주기 위해서 Input element description 말그대로 어떻게 생겨먹었느지를

묘사해서 Input Layout 타입에 하나를 만들어줬고

// 얘를 어떻게 분석해서 전해줘야할지를 정해줘야한다 
void Game::CreateInputLayout()
{
	//배열 형태로 제작 
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{"POSITION" , 0 , DXGI_FORMAT_R32G32B32_FLOAT,0 ,0,D3D11_INPUT_PER_VERTEX_DATA,0},//FLOAT가 각각 32BIT (VEC3)
		{"TEXCOORD" , 0 , DXGI_FORMAT_R32G32_FLOAT,0 ,12,D3D11_INPUT_PER_VERTEX_DATA,0}, //Vec4 COLOR RGBA + 구조묘사
	};

	// 배열의 크기에 배열하나의 아이템크기를 나누면 배열에 해당 아이템 갯수가 나온다 . size로 계산하니깐 
	const int32 count = sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC); // element갯수 구하기 
	
	_device->CreateInputLayout(layout, count, _vsBlob->GetBufferPointer(),_vsBlob->GetBufferSize(), _inputLayout.GetAddressOf());
	//input layout 이기때문에 , VS단계에서 넘겨받은 데이터에 대한 묘사를 하는것이기에 
	//PS의 Blob이랑은 상관없고 VS_Blob의 정보만 주면된다 
	//input layout도 com객체로 만들어지게된다 
	// 우리가 넘겨주는 vertex의 구조열을 묘사하는게 -> inputLayout 
}

이거를 실질적으로 렌더링할때 IA단계에서 얘가 필요하기때문에 왜 ? 정점 정보를 전달해주는

Vertex Buffer 를 묘사하는 거니깐 !

// Render() 
//IA
		//dc에 가서 vertexbuffer를 연결시켜줘야한다 
		_deviceContext->IASetVertexBuffers(0, 1, _vertextBuffer.GetAddressOf(), &stride, &offset);
		//							버퍼슬롯1개 , 버퍼갯수 , 버퍼건내기 , vertex크기 , 
		_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);
		//                               내 인덱스 버퍼 ,     4바이트로했으니깐 32비트짜리다라는뜻 

		//input layout 으로 우리가 건네준거 묘사 
		_deviceContext->IASetInputLayout(_inputLayout.Get()
//정점을 어떻게 이어붙일지 알려줘야함 
		_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 
		/// IA 단계에서는 이렇게 세팅만

복습하다보면 어디단계에서 필요한지 여기 알수있다. GPU 입장에서 내가 입력한 Vertex , Index

버퍼들의 정보를 어떻게 분석해야되는데 → 이걸 InputLayout을 참고를한다 즉 ( 설명서) 같은개념

Topology는 삼각형 목록으로 3 ,3, 3,3 묶어서 → 이건 거의 정석이라 바뀔일이없다

→ IA 단계 끝

우리가 삼각형만 그릴때는 쉐이더를 로드해야하니

//header 
void LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob);
						         // 쉐이더 경로 ,       이 파일의 이름,          쉐이더 버전 , blob 이런 애를 뱉는데 그걸 줘

VS , PS 를 로드하면 Blob 이라는 객체로 만들어지는데 Blob을 통해서 VS , PS 라는 리소스로 변환

void Render()
{
	// iA - 
	// VS
	_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0); // 우리가만든거 써라 
	_deviceContext->VSSetConstantBuffers(0, 1, _constantBuffer.GetAddressOf());
	// RS
	// PS
		_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
		_deviceContext->PSSetShaderResources(0, 1, _shaderResourceView.GetAddressOf());
		_deviceContext->PSSetShaderResources(1, 1, _shaderResourceView2.GetAddressOf());
		_deviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf());
	//OM
}

각각 그걸 사용하기위해 PSsetShader VSSetShader 로 각각 뭘 사용할지를 정해줬다

겸사겸사 쉐이더파일도 만들어주었다


struct VS_INPUT
{

	float4 position : POSITION; 

	float2 uv : TEXCOORD;
};

struct VS_OUTPUT
{
	float4 position : SV_POSITION; //system value , 얘는 필수적으로 있어야한다 라고명시 (SV)

	float2 uv : TEXCOORD;
};

// IA - VS - RS - PS - OM 
cbuffer TransformData : register(b0)
{
	float4 offset;
}
//////////////////////  VS 단계 /////////////////////////////
VS_OUTPUT VS(VS_INPUT input) 
{

	VS_OUTPUT output;

	output.position = input.position + offset;
	output.uv = input.uv;

	return output;
}
//////////////////////        /////////////////////////////

Texture2D texture0 : register(t0); 
							//t->Texture의 약자 
SamplerState sampler0 : register(s0);
//정해진 규약에따른 레지스터 번호 

Texture2D texture1 : register(t1);

//////////////////////  PS 단계       /////////////////////////////
// VS에서 리턴값으로 들어온게 여기서은 input으로 들어온다 
float4 PS(VS_OUTPUT input) : SV_Target // 이 결과물을 SV_Target에 쏴줘야하니 이 키워드를 붙여줘야한다
{

	float4 color = texture1.Sample(sampler0, input.uv);
	//Sample이라는 함수를이용해서 sampler0번이랑 input의uv를 각각건네준다 
	// texture0번의 uv 좌표를 가져와서 거기해당하는 색상을 뺴온다라는 느낌 

	return color;
//////////////////////       /////////////////////////////

}

하나의 함수인데 IA VS RS PS OM 단계로 실행이 되기 때문에 IA 단계에서 넘어오면 VS 단계고

그걸로 넘어가달라고 요구하는게 Shader 설정을해준것 ( Render 함수 부분 )

UV 매핑

텍스쳐라는 걸

Texture2D texture0 : register(t0); 
							//t->Texture의 약자 
SamplerState sampler0 : register(s0);
//정해진 규약에따른 레지스터 번호 

Texture2D texture1 : register(t1);

이런식으로 가져오는데 마치 PS의 인자인것처럼 찔러넣을수있었는데 역시나이것또한

2D resource 를 로드했고

void Game::CreateSRV()
{
	//이미지를 가져오는 함수는 여러개가있다 
	DirectX::TexMetadata md;
	DirectX::ScratchImage img;
	
	HRESULT hr = ::LoadFromWICFile(L"Pig.png", WIC_FLAGS_NONE, &md, img);
	CHECK(hr);
	//여기까지하면 이미지를 로드한것 

	//shader resource view 라는걸 만들어야한다 
	hr = ::CreateShaderResourceView(_device.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf());

	CHECK(hr);

	hr = ::LoadFromWICFile(L"Sample.png", WIC_FLAGS_NONE, &md, img);
	CHECK(hr);
	//여기까지하면 이미지를 로드한것 

	//shader resource view 라는걸 만들어야한다 
	hr = ::CreateShaderResourceView(_device.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView2.GetAddressOf());

	CHECK(hr);
		

}

PNG 파일을 로드해서 Scrach img, metadata 라는 애들이 만들어지는데 그걸이용해서

Create Shader Resource View 라는걸 만들고

// Header 
//SRV 
	ComPtr<ID3D11ShaderResourceView> _shaderResourceView = nullptr;
	ComPtr<ID3D11ShaderResourceView> _shaderResourceView2 = nullptr;

이 SRV 를 만드는것에 끝나지않고 어떤애랑 묶어달라고 쉐이더를 호출할때 파이프라인에 묶어준다

void Render()
{
	//IA - VS - RS 
	//PS
		_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
		_deviceContext->PSSetShaderResources(0, 1, _shaderResourceView.GetAddressOf());
		_deviceContext->PSSetShaderResources(1, 1, _shaderResourceView2.GetAddressOf());
		_deviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf());
	//OM
}

UV 좌표를 이용해서 그려주게된다 , 각각 vertex에 UV좌표를 어떻게 할지 설정해야한다

해당하는 UV 좌표를 샘플링 해서 그 UV 좌표를 가져오고 그거에따른 색상을 가지고 리턴한다

float4 PS(VS_OUTPUT input) : SV_Target // 이 결과물을 SV_Target에 쏴줘야하니 이 키워드를 붙여줘야한다
{
	//float4 color =
	//모든 픽셀대상으로 실행하는 함수 , 색상관련이라고 간단하게 생각해보면된다 

	//우리가 넣어준 색 -> input.color 
	//return input.color;

	float4 color = texture1.Sample(sampler0, input.uv);
	//Sample이라는 함수를이용해서 sampler0번이랑 input의uv를 각각건네준다 
	// texture0번의 uv 좌표를 가져와서 거기해당하는 색상을 뺴온다라는 느낌 

	return color;

}

만약 초과했다면 Sampler 룰에 따라서 결정되게 된다

색상이 구해진다 → 그 색상으로 그려줄수있었다 라는 얘기 보통은 우리가 결정한 UV 좌표에따라결정

복잡한 메시도 원리는 같다 모든 좌표마다 UV 좌표가 있는데 그 UV 좌표에 맞는 텍스쳐가있고

그 UV 좌표에 맞는 텍스쳐에 그부분을 잘라 오려 붙이는 형식으로 3d 도 같다

Constant Buffer 상수버퍼

어떤 Geometry는 사물의 개념인데 처음에 우리가 기하학적인 도형을 표시하는거기떄문에

한번만들어지면 그다음부터는 사실상 절대로 바꿀일이 생기지 않는다 ,

그 상태에서 우리가 이동하고싶다면 vertex buffer를 다시만드는게 아니라 constant buffer라는 상수버퍼를 이용해서 이 객체에 밀어넣고싶은 데이터를 넣어서 , 초반에 이 buffer를 만들떄 조차도

Dynamic Type으로 만든다 , CPU가 Data 를 복사할수있어야하니깐

이런식으로 Usage 부분에 vertex Buffer와는 달리 IMMUTABLE이 아닌 DYNAMIC으로 설정되어있음

이런식으로 Usage 부분에 vertex Buffer와는 달리 IMMUTABLE이 아닌 DYNAMIC으로 설정되어있음

우리가 TransForm이라는 데이터라는것을 갖고있는데 이걸 우리가 게임에 넣어놓고 하나씩 관리하고 있지만 나중에 가면 이중에서 당연히 물체별로 각각 존재할것이다 ,

게임 오브젝트 에서 플레이어라는 객체를 만들면 Transform 데이터는 물체마다 하나씩 들고있고

상수 버퍼도 마찬가지 , 그 다음 이 Geometry는 메쉬의 정보니깐 하나의 리소스이고 이 쉐이더도

하나의 리소스니깐 이런 애들을 조립 조립 조립 해서 하나의 물체가 그려진다 라고 결론을 낼수가있다.

원하는 위치에 원하는 물체를 배치할수있는 능력이 어느정도 생겼다 라는 얘기가 되는것이다

나머지

RSSate , Sampler , BlendState 같은 애들은 우리가 얼마든지 정보를 넣어주고 , 상태에맞는

RS인지 PS 인지에 따라서 우리가 해당 파이프라인에 꽂아줘서 설정해주면 된다

머테리얼 → 쉐이더를 기반으로 쉐이더랑 관련이있는 부분

모델→ 기본적으로 삼각형 모양 , wirefram모양으로 볼수있는것 - fillmode에서 변경

직접 엔진을 만든다면 모든부분을 연결시켜줘서 만들면 된다는뜻

최종적으로 Material이 쉐이더랑 연결이되어있는것 , 각각제질에 옵션을 달아줄수있다

옵션을 설정한다는것 자체가 렌더링 파이프라인에다가 인자를 꽂는다는것이고

Transform Data도 조작하는거에따라서 물체가 이동하는것도 GPU에 상수버퍼를 이용해서

SetConstantBuffer를 해주면 Shader 코드에서 값을 ++ 해주는개념이라고 보면된다

이 기하학적인 모델 → 절대로 변하는게아니다 , 물체를 이동시킨다고해도 , 좌표를 고치는게아니라

이러한 여러 아이를 배치를 한다고해도 , 원래는 Local World에 잘있던 애를 여러마리를 배치하면

중요한건 모든 물체는 Geometry상에서는 동일한 메시 동일한 물체 , 좌표랑 회전을 설정하는건

우리가 만들어준 constant buffer , 물체를 렌더링할때마다 offset 값을 꽂아주는것

우리가 텍스쳐를 볼때 한장에 여러 옷이 이상하게 들어있는 이유 → 우리가 UV mapping통해 해당 물체에 어느부분에 텍스쳐의 어떤 좌표값이 들어가는지를 설정해주면 알맞게 옷이 들어간다

profile
Unreal Programmer , C++

0개의 댓글