221125 강의
일주일간 했던 파일입니다.
오늘은 코드에 대한 설명까지 다 해주셔서 내용이 많이 길어질 것 같습니다.
불필요한 부분도 없애고 이전에 썼던 파일에서도 변경된 내용이 있습니다.
먼저 어제 다 쓰다가 말았던 cpp 파일부터 보겠습니다.
//Graphics.cpp
#include "Framework.h"
#include "Graphics.h"
void Graphics::Resize(const UINT& width, const UINT& height)
{
DeleteSurface();
{
HRESULT hr = swapChain->ResizeBuffers
(
0,
width,
height,
DXGI_FORMAT_UNKNOWN,
0
);
assert(SUCCEEDED(hr));
}
CreateRenderTargetView();
SetViewport(width, height);
}
void Graphics::SetViewport(const UINT& width, const UINT& height)
{
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;
viewport.Width = (float)width;
viewport.Height = (float)height;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
}
void Graphics::Begin()
{
deviceContext->OMSetRenderTargets(1, &rtv, nullptr);
deviceContext->RSSetViewports(1, &viewport);
deviceContext->ClearRenderTargetView(rtv, clearColor);
}
void Graphics::End()
{
HRESULT hr = swapChain->Present(bVsync == true ? 1 : 0, 0);
assert(SUCCEEDED(hr));
}
void Graphics::GUI()
{
static bool bOpen = true;
ImGui::SetNextWindowPos({ 0, 15 });
ImGui::SetNextWindowSize(ImVec2(200, 30));
ImGui::Begin
(
"Vsync",
&bOpen,
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar
);
ImGui::Checkbox("##Vsync", &bVsync);
ImGui::End();
}
void Graphics::CreateSwapchain()
{
SAFE_RELEASE(device);
SAFE_RELEASE(deviceContext);
SAFE_RELEASE(swapChain);
DXGI_SWAP_CHAIN_DESC desc;
ZeroMemory(&desc, sizeof(DXGI_ADAPTER_DESC));
desc.BufferDesc.Width = 0;
desc.BufferDesc.Height = 0;
if (bVsync)
{
desc.BufferDesc.RefreshRate.Numerator = adapterInfos[0]->outputInfo->numerator;
desc.BufferDesc.RefreshRate.Denominator = adapterInfos[0]->outputInfo->denominator;
}
else
{
desc.BufferDesc.RefreshRate.Numerator = 0;
desc.BufferDesc.RefreshRate.Denominator = 1;
}
desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
desc.BufferDesc.ScanlineOrdering =
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
desc.BufferCount = 1;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.OutputWindow = handle;
desc.Windowed = true;
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
vector<D3D_FEATURE_LEVEL> featureLevel
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};
UINT maxVRam = 0
for (UINT i = 0; i < adapterInfos.size(); i++)
{
if (adapterInfos[i]->adapterDesc.DedicatedVideoMemory > maxVRam)
{
selectedAdapterIndex = i;
maxVRam = adapterInfos[i]->adapterDesc.DedicatedVideoMemory;
}
}
for (int i = 0; i < adapterInfos.size(); i++)
{
gpuName = adapterInfos[i]->adapterDesc.Description;
wcout << "GPU Name : " << adapterInfos[i]->adapterDesc.Description << endl;
cout << "VRam : " << adapterInfos[i]->adapterDesc.DedicatedVideoMemory << endl;
cout << endl;
}
wcout << "Selected GPU Name : "
<< adapterInfos[selectedAdapterIndex]->adapterDesc.Description << endl;
HRESULT hr = D3D11CreateDeviceAndSwapChain
(
adapterInfos[selectedAdapterIndex]->adapter,
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
0,
featureLevel.data(),
featureLevel.size(),
D3D11_SDK_VERSION,
&desc,
&swapChain,
&device,
nullptr,
&deviceContext
);
assert(SUCCEEDED(hr));
Resize(WinMaxWidth, WinMaxHeight);
}
void Graphics::CreateRenderTargetView()
{
ID3D11Texture2D* backbuffer = nullptr;
HRESULT hr = swapChain->GetBuffer
(
0,
__uuidof(ID3D11Texture2D),
(void**)&backbuffer);
assert(SUCCEEDED(hr));
hr = device->CreateRenderTargetView
(backbuffer,
nullptr,
&rtv);
assert(SUCCEEDED(hr));
SAFE_RELEASE(backbuffer);
}
void Graphics::DeleteSurface()
{
SAFE_RELEASE(rtv);
}
Graphics::Graphics()
{
EnumrateAdapters();
CreateSwapchain();
}
Graphics::~Graphics()
{
SAFE_RELEASE(rtv);
SAFE_RELEASE(swapChain);
SAFE_RELEASE(deviceContext);
SAFE_RELEASE(device);
}
void Graphics::EnumrateAdapters()
{
IDXGIFactory1* factory;
if (FAILED(CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory)))
return;
UINT index = 0;
while (true)
{
IDXGIAdapter1* adapter = nullptr;
HRESULT hr = factory->EnumAdapters1(index, &adapter);
if (hr == DXGI_ERROR_NOT_FOUND)
break;
assert(SUCCEEDED(hr));
D3DEnumAdapterInfo* adapterInfo = new D3DEnumAdapterInfo();
ZeroMemory(adapterInfo, sizeof(D3DEnumAdapterInfo));
adapterInfo->adapter = adapter;
adapter->GetDesc1(&adapterInfo->adapterDesc);
adapterInfo->adapter = adapter;
EnumerateAdapterOutput(adapterInfo);
adapterInfos.push_back(adapterInfo);
index++;
}
SAFE_RELEASE(factory);
}
bool Graphics::EnumerateAdapterOutput(D3DEnumAdapterInfo* adapterInfo)
{
IDXGIOutput* output = nullptr;
HRESULT hr = adapterInfo->adapter->EnumOutputs(0, &output);
if (DXGI_ERROR_NOT_FOUND == hr)
return false;
assert(SUCCEEDED(hr));
D3DEnumOutputInfo* outputInfo = new D3DEnumOutputInfo();
ZeroMemory(outputInfo, sizeof(D3DEnumOutputInfo));
output->GetDesc(&outputInfo->outputDesc);
outputInfo->output = output;
UINT numModes = 0;
DXGI_MODE_DESC* displayModes = nullptr;
DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM;
hr = output->GetDisplayModeList(format, DXGI_ENUM_MODES_INTERLACED, &numModes, nullptr);
assert(SUCCEEDED(hr));
displayModes = new DXGI_MODE_DESC[numModes];
hr = output->GetDisplayModeList(format, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModes);
assert(SUCCEEDED(hr));
for (UINT i = 0; i < numModes; i++)
{
bool bCheck = true;
bCheck &= displayModes[i].Width == WinMaxWidth;
bCheck &= displayModes[i].Height == WinMaxHeight;
if (bCheck == true)
{
outputInfo->numerator = displayModes[i].RefreshRate.Numerator;
outputInfo->denominator = displayModes[i].RefreshRate.Denominator;
}
}
adapterInfo->outputInfo = outputInfo;
SAFE_DELETE_ARRAY(displayModes);
return true;
}
D3DEnumAdapterInfo::~D3DEnumAdapterInfo()
{
SAFE_RELEASE(adapter);
SAFE_DELETE(outputInfo);
}
D3DEnumOutputInfo::~D3DEnumOutputInfo()
{
SAFE_RELEASE(output);
}
어제에 이어서 오늘 쓴 Graphic.cpp입니다.
bool Graphics::EnumerateAdapterOutput(D3DEnumAdapterInfo* adapterInfo)
D3DEnumAdapterInfo::~D3DEnumAdapterInfo()
D3DEnumOutputInfo::~D3DEnumOutputInfo()
를 전부 작성 하였습니다.
새롭게 추가된 내용은 설명하고 어제 코드 설명했던 부분은 빼겠습니다.
먼저 RTV에 대해서 설명하겠습니다.
RTV(RenderingTargetView)
화면에 렌더링 될 백 버퍼가 아닌 임시 중간 버퍼로 장면이 렌더링 되게 할 수 있습니다.
이걸로 반사 텍스쳐 또는 다른 목적으로 렌더링 될 수 있는 복잡한 장면을 사용하거나 렌더링 하기 전에 화면에 추가 픽셀 셰이더 효과를 추가할 수 있습니다.
(프론트 버퍼와 백 버퍼는 교차하는 부분이고 RTV는 또 다른 버퍼처럼 쓰는데 RTV에서 백 버퍼로 넘기고 그 백 버퍼가 프론트 버퍼로 넘깁니다.)
뷰 포트(Viewport)
장면을 그려놓고자 하는 후면 버퍼의 직사각형 영역입니다.
aliasing은 계단 현상입니다. 그 계단 현상을 줄이기 위해 하는 것이 antialiasing입니다.
antialiasing에는 두 가지 방법이 있습니다.
1.초과표본화(supersampling), 하향표준화(downsampling) - 일단 두 단어의 의미는 같습니다.
순서는 다음과 같습니다.
Ⅰ. 후면 버퍼와 깊이 버퍼를 화면 해상도보다 4배 크게 잡습니다.
Ⅱ. 3차원 장면을 4배 크기의 해상도에서 후면 버퍼에 렌더링 합니다.
Ⅲ. 그러면 이미지를 화면에 제시할 때 후면 버퍼를 원래 크기의 버퍼로 환원하게 됩니다.
(4픽셀 블록의 네 색상의 평균을 최종 색상으로 사용합니다.)
결국 화면 해상도를 소프트웨어상에서 강제로 증가시킵니다.
2. 다중표본화(multisampling)
일부 계산 결과를 부분픽셀드 사이에서 공유합니다.
초과표본화 하는 비용이 적어집니다.
4배 다중표본화의 경우 초과표본화처럼 크기가 해상도의 4배인 후면 버퍼와 깊이 버퍼를 사용합니다.
하지만, 이미지 색상을 각 부분 픽셀마다 계산하는 것이 아닌 픽셀당 한 번씩 계산합니다.
그 색상과 부분 픽셀들의 가시성과 포괄도를 이용해서 최종적으로 색상을 결정합니다.
(포괄도 : 부분 픽셀을 다각형이 어느 정도 덮고 있는지 뜻하는 값입니다.)
void Graphics::Resize(const UINT& width, const UINT& height)은 리사이즈 호출을 의미합니다.
Resize(WinMaxWidth, WinMaxHeight)의 버퍼 교체하는 이 코드를 호출합니다.
DeleteSurface()는 백 버퍼를 그리는 코드입니다.
HRESULT hr = swapChain->ResizeBuffers는 후면 버퍼 크기를 변경합니다.
0은 몇 번째 버퍼인지를 나타냅니다.
width는 너비를 나타냅니다.
height는 높이를 나타냅니다.
DXGI_FORMAT_UNKNOWN은 포맷 방식을 의미합니다.
0은 SwapChain Flag입니다.
스왑 체인 플래그에 관한 내용은
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=rrhoi&logNo=80012171974
스왑체인 플래그 ( CreateDevice() )
스왑이팩스 swap chain D3DSWAPEFFECT_FLIP The swap chain might include ...
blog.naver.com
여기에 나와 있습니다.
viewport.MinDepth = 0.0f
viewport.MaxDepth = 1.0f
이 부분은 깊이를 의미하는데 지금은 2D를 하기 때문에 크게 의미는 없습니다.
void Graphics::GUI()는 외부의 ImGui 하는 부분을 나타냅니다.
SAFE_DELETE와 SAFE_RELEASE의 차이점은
SAFE_DELETE는 상관없지만 인터페이스를 해제할 때는 SAFE_RELEASE를 사용해야 합니다.
RefreshRate는 갱신 주기를 말합니다.
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD는 백 버퍼와 교환 시 이펙트를 나타내지만 아무런 효과가 없습니다.
어제 vector<D3D_FEATURE_LEVEL> featureLevel은 기능 수준 검사라고 말했지만 다이렉트 버전을 순서대로 검사합니다.
또한 어제 HRESULT hr = D3D11CreateDeviceAndSwapChain이 부분을 추가하자면 스왑 체인 생성을 한다고 볼 수 있습니다.
adapterInfos[selectedAdapterIndex]->adapter는 그래픽 카드입니다.
D3D_DRIVER_TYPE_UNKNOWN은 드라이버 타입입니다.
nullptr는 소프트웨어지만 하드웨어 사용 시 null이라고 표기합니다.
0은 디바이스 플래그입니다.
featureLevel.data()는 featureLevel 배열입니다. 시작점이 vector입니다.
featureLevel.size()은 featureLevel 배열입니다.
D3D11_SDK_VERSION은 SDK의 버전입니다.
&desc은 swapchain 생성 구조체입니다.
&swapChain은 swapchain 객체입니다.
&device는 device 객체입니다.
nullptr는 피처 레벨 정보입니다.
&deviceContext는 devicecontext 객체입니다.
HRESULT hr = swapChain->GetBuffer는 SwapChain을 가리키는 포인터를 얻습니다.
0은 얻고자 하는 후면 버퍼의 인덱스입니다. (여러 개 있는 경우 중요합니다.)
__uuidof(ID3D11Texture2D)는 인터페이스 형식입니다.
(void**)&backbuffer)는 후면 버퍼를 가리키는 포인터입니다.
hr = device->CreateRenderTargetView는 RTV를 생성합니다.
backbuffer는 RTV Desc 구조체를 가리키는 포인터입니다.
nullptr는 형식을 완전히 지정해서 자원을 생성했으면 null을 줘도 무관합니다.
SAFE_RELEASE(backbuffer)는 다 쓰고 필요 없는 포인터를 삭제합니다.
void Graphics::EnumrateAdapters()는 어댑터라고 불리는 것들을 나열합니다.
191~214줄은 그래픽 카드의 정보를 가져옵니다.
2212~239줄은 모니터의 정보를 가져옵니다.
outputInfo->numerator = displayModes[i].RefreshRate.Numerator;
outputInfo->denominator = displayModes[i].RefreshRate.Denominator;
이 코드는 주사율을 설정합니다.
어제 Numerator가 분자이고 Denominator가 분모라고 했는데 그 반대입니다.
Numerator가 분모이고 Denominator가 분자입니다.
(어제 강의 내용에서 해당 내용은 수정했습니다.)
stdafx.h가 Framework.h로 바뀜에 따라 윈도우, 타이머, 마우스, 키보드의 헤더 파일이 바뀌었습니다.
PosRect와 PosStr이 삭제되었습니다.
어제 IObject.h가 생성됨에 따라 Framework.h에 #include "Interface/IObject.h"가 추가되었습니다.
Framework 프로젝트 내에서 Render 폴더가 생성되고 하위 폴더로 IA, Resources, Shaders 폴더가 추가되었습니다.
//VertexBuffer.h
#pragma once
class VertexBuffer
{
public:
~VertexBuffer();
template<typename T>
void Create(const vector<T>& vertices, const D3D11_USAGE& usage = D3D11_USAGE_DEFAULT);
ID3D11Buffer* GetResource() { return buffer; }
uint GetStride() { return stride; }
uint GetOffset() { return offset; }
uint GetCount() { return count; }
void SetIA();
private:
ID3D11Buffer* buffer = nullptr;
uint stride = 0;
uint offset = 0;
uint count = 0;
};
template<typename T>
inline void VertexBuffer::Create(const vector<T>& vertices, const D3D11_USAGE& usage)
{
stride = sizeof(T);
count = vertices.size();
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
desc.Usage = usage;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = stride * count;
switch (usage)
{
case D3D11_USAGE_DEFAULT:
case D3D11_USAGE_IMMUTABLE:
break;
case D3D11_USAGE_DYNAMIC:
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
break;
case D3D11_USAGE_STAGING:
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
break;
}
D3D11_SUBRESOURCE_DATA subData;
ZeroMemory(&subData, sizeof(D3D11_SUBRESOURCE_DATA));
subData.pSysMem = vertices.data();
HRESULT hr = DEVICE->CreateBuffer(&desc, &subData, &buffer);
CHECK(hr);
}
IA 폴더 안에 새로 생성한 VertexBuffer.h입니다.
템플릿으로 지정한 이유는 이후 코드를 작성하면서 어떤 형식으로 들어올지 알 수 없기 때문입니다.
VertexBuffer.cpp는 아직 작성하지 않았습니다. 다음 주 월요일에 작성될 예정입니다.
다음은 D3D11_USAGE의 CPU와 GPU의 읽기 및 쓰기의 가능 여부를 나타낸 표입니다.
D3D11_USAGE | GPU 읽기 | GPU 쓰기 | CPU 읽기 | CPU 쓰기 |
D3D11_USAGE_DEFAULT | O | O | X | X |
D3D11_USAGE_IMMUTABLE | O | X | X | X |
D3D11_USAGE_DYNAMIC | O | X | X | O |
D3D11_USAGE_STAGING | GPU에서 CPU 복사 허용 (단, 매우 느림) |
여담으로 오늘 "더 샌드박스 메타버스에서의 NFT크리에이터 게임 생태계" 강의를 듣고 왔는데 메타버스에 대해 조금이나마 자세히 알게 되었고 앞으로도 성장 가능성과 사람하고 더 밀접해지게 되고 2023년에 터닝 포인트가 될 것 같다는 전망이라는 내용이 나왔습니다. 짧게나마 도움이 되었으면 좋겠습니다.
읽어주셔서 감사합니다.