KoreanFoodie's Study
4-3. Direct3D 초기화 (ID3D12Device, Fence, 4X MSAA 점검 등) 본문
4-3. Direct3D 초기화 (ID3D12Device, Fence, 4X MSAA 점검 등)
GoldGiver 2021. 11. 17. 14:52
'DirectX 12를 이용한 3D 게임 프로그래밍 입문'을 읽으며 내용을 정리하고 중요한 부분을 기록하는 글입니다.
4-3. Direct3D 초기화 (ID3D12Device, Fence, 4X MSAA 점검 등)
Direct3D 초기화 과정은 다음과 같은 단계들로 구성된다.
1. D3D12CreateDevice 함수를 이용해서 ID3D12Device를 생성한다.
2. ID3D12Fence 객체를 생성하고 서술자들의 크기를 얻는다
3. 4X MSAA(Multisample anti-aliasing) 품질 수준 지원 여부를 점검한다.
4. 명령 대기열과 명령 목록 할당자, 그리고 주 명령 목록을 생상한다.
5. 교환 사슬을 서술하고 생성한다.
6. 응용 프로그램에 필요한 서술자 힙들을 생성한다.
7. 후면 버퍼의 크기를 설정하고, 후면 버퍼에 대한 렌더 대상 뷰를 생성한다.
8. 깊이/스텐실 버퍼를 생성하고, 그와 연관된 깊이/스텐실 뷰를 생성한다.
9. 뷰포트와 가위 판정용 사각형들을 설정한다.
알아 두어야 할 개념들 :
1. 장치 생성
장치(Device)는 디스플레이 어댑터를 나타내는 객체이다. 일반적으로 그래픽 카드를 나타내지만, WARP 같은 소프트웨어디스플레이 어댑터를 가리키기도 한다.
장치 생성에는 다음과 같은 함수를 사용한다.
HRESULT WINAPI D3D12CreativeDevice(
IUnKnown* pAdapter; // 장치가 나타내는 디스플레이 어댑터 지정. nullptr시 시스템의 기본(primary) 디스플레이 어댑터가 쓰임
D3D_FEATURE_LEVEL MinimumFeaturelevel; // 응용 프로그램이 요구하는 최소 기능 수준. e.g. D3D_FEATURE_LEVEL_11_0
REFIID riid; // 생성하고자 하는 ID3D12Device 인터페이스의 COM ID
void** ppDevice; // 출력 매개변수
);
다음은 이 함수의 호출 예이다.
#if defined(DEBUG) || defined(_DEBUG)
// Enable the D3D12 debug layer.
{
ComPtr<ID3D12Debug> debugController;
ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_
ARGS(&debugController)));
debugController->EnableDebugLayer();
}
#endif
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));
// Try to create hardware device.
HRESULT hardwareResult = D3D12CreateDevice(
nullptr, // default adapter
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice));
// Fallback to WARP device.
if(FAILED(hardwareResult))
{
ComPtr<IDXGIAdapter> pWarpAdapter;
ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_
ARGS(&pWarpAdapter)));
ThrowIfFailed(D3D12CreateDevice(
pWarpAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice)));
}
// Fallback to WARP device 를 보면, 첫 D3D12CreateDevice 호출이 실패 시 소프트웨어 어댑터인 WARP를 생성한다는 것을 알 수 있다. WARP는 Windows Advanced Rasterization Platform의 약자이다. mdxgiFactory 객체는 교환 사슬을 생성하는 데에도 쓰인다.
2. 울타리 생성과 서술자 크기 얻기
CPU/GPU의 동기화를 위해 울타리 객체를 생성해 보자.
ThrowIfFailed(md3dDevice->CreateFence(
0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
3. 4X MSAA 품질 수준 지원 점검
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&msQualityLevels,
sizeof(msQualityLevels)));
m4xMsaaQuality = msQualityLevels.NumQualityLevels;
assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");
4X MSAA가 지원되면 반환된 품질 수준(m4xMsaaQuality)가 0보다 커야 한다.
4. 명령 대기열과 명령 목록 생성
명령 대기열은 ID3D12CommandQueue, 명령 할당자는 ID3D12CommandAllocator, 명령 목록은 ID3D12GraphicsCommandList 인터페이스를 이용한다. 생성 코드를 보자.
ComPtr<ID3D12CommandQueue> mCommandQueue;
ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
ComPtr<ID3D12GraphicsCommandList> mCommandList;
void D3DApp::CreateCommandObjects()
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(
&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(), // Associated command allocator
nullptr, // Initial PipelineStateObject
IID_PPV_ARGS(mCommandList.GetAddressOf())));
// Start off in a closed state. This is because the first time we
// refer to the command list we will Reset it, and it needs to be
// closed before calling Reset.
mCommandList->Close();
}
5. 교환 사슬의 서술과 생성
교환 사슬(swap chain)을 생성하기 위해서는 DXGI_SWAP_CHAIN_DESC 구조체 인스턴스의 멤버들을 설정해야 한다. 정의는 다음과 같다.
typedef struct DXGI_SWAP_CHAIN_DESC
{
DXGI_MODE_DESC BufferDesc; // 생성하고자 하는 후면 버퍼의 속성을 서술(너비, 높이, 픽셀 형식 등)
DXGI_SAMPLE_DESC SampleDesc; // 다중표본화 표본 개수와 품질 수준 서술
DXGI_USAGE BufferUsage; // 후면 버퍼에 렌더링 시 DXGI_USAGE_RENDER_TARGET_OUTPUT 지정
UINT BufferCount; // 교환 사슬이 사용할 버퍼 갯수. 이중 버퍼링에서는 2를 지정
HWND OutputWindow; // 렌더링 결과가 표시될 창의 핸들
BOOL Windowed; // 창 모드이면 TRUE, 전체 화면 모드이면 FALSE
DXGI_SWAP_EFFECT SwapEffect; // DXGI_SWAP_EFFECT_FLIP_DISCARD를 지정
UINT Flags; // 추가적인 플래그들. DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH이면
// 응용 프로그램이 전체화면이 될 때,
// Direct3D는 현재 크기에 가장 잘 맞는 디스플레이 모드를 선택
} DXGI_SWAP_CHAIN_DESC;
DXGI_MODE_DESC 형식은 구조체로, 다음과 같이 정의된다.
typedef struct DXGI_MODE_DESC
{
UINT Width; // Buffer resolution width
UINT Height; // Buffer resolution height
DXGI_RATIONAL RefreshRate;
DXGI_FORMAT Format; // Buffer display format
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; //Progressive vs. interlaced
DXGI_MODE_SCALING Scaling; // How the image is stretched
// over the monitor.
} DXGI_MODE_DESC;
서술자를 다 채웠으면, IDXGIFactory::CreateSwapChain 메서드를 호출해 교환 사슬을 생성한다.
HRESULT IDXGIFactory::CreateSwapChain(
IUnknown *pDevice, // Pointer to ID3D12CommandQueue.
DXGI_SWAP_CHAIN_DESC *pDesc, // Pointer to swap chain description.
IDXGISwapChain **ppSwapChain);// Returns created swap chain interface.
교환 사슬을 생성하는 예제 코드를 보자. 이 함수는 기존 교환 사슬을 해제(Reset)후 새 교환 사슬을 생성한다.
DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
void D3DApp::CreateSwapChain()
{
// Release the previous swapchain we will be recreating.
mSwapChain.Reset();
DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth;
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_
UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = SwapChainBufferCount;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
// Note: Swap chain uses queue to perform flush.
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()));
}
6. 서술자 힙 생성
서술자 힙(descriptor heap)은 ID3D12DescriptorHeap 인터페이스로 대표된다. 힙을 생성하는 메서드는 ID3D12Device::CreateDescriptorHeap이다. 다음 예제 프로그램에는 SwapChainBufferCount에 설정된 개수만큼의 렌더 대상 뷰(render target view, RTV)들과 하나의 깊이/스텐실 뷰(depth/stencil view, DSV)가 필요하다.
서술ㅎ자 힘은 서술자 종류마다 따로 만들어야 한다. 따라서, 예제에서는 SwapChainBufferCount개의 RTV들을 담을 힙 하나와 하나의 DSV를 담을 힙이 필요하다. 다음 코드를 보자.
ComPtr<ID3D12DescriptorHeap> mRtvHeap;
ComPtr<ID3D12DescriptorHeap> mDsvHeap;
void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}
힙을 생성한 후 저장된 서술자들에 접근할 수 있는데, 예제에서는 핸들을 통해 서술자들을 참조한다. 힙의 첫 서술자에 대한 핸들은 ID3D12DescriptorHeap::GetCPUDescriptorHandleForHeapStart 메서드로 얻는다. 다음은 각각 현재의 후면 버퍼 RTV와 DSV에 대한 핸들을 얻는 함수들이다.
D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView()const
{
// CD3DX12 constructor to offset to the RTV of the current back buffer.
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),// handle start
mCurrBackBuffer, // index to offset
mRtvDescriptorSize); // byte size of descriptor
}
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView()const
{
return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}
7. 렌더 대상 뷰(RTV) 생성
우리는 자원 자체를 직접 파이프라인 단계에 묶는 것이 아닌 뷰(서술자, descriptor)를 생성해 그 뷰를 파이프라인에 묶는다. 특히 후면 버퍼를 출력 병합기(output merger) 단계에 묶으려면 후면 버퍼에 대한 RTV를 생성해야 한다. 먼저 교환 사슬에 저장되어 있는 버퍼 자원을 얻는다.
HRESULT IDXGISwapChain::GetBuffer(
UINT Buffer,
REFIID riid,
void **ppSurface
);
IDXGISwapChain::GetBuffer을 호출하면 해당 후면 버퍼의 COM 참조 횟수가 증가한다. 따라서 버퍼를 다 사용한 후에는 반드시 해제해야 한다. ComPtr을 사용하면 해제가 자동으로 처리된다.
RTV를 생성 시에는 ID3D12Device::CreateRenderTargetView 를 사용한다.
void ID3D12Device::CreateRenderTargetView(
ID3D12Resource *pResource, // 렌더 대상으로 사용할 자원을 가리키는 포인터.
// 여기서 자원은 후면 버퍼이다
const D3D12_RENDER_TARGET_VIEW_DESC *pDesc, // 이 구조체는 RTV를 서술.
// 자원에 담긴 원소들의 자료 형식의 멤버를 갖고 있음
D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor); // RTV가 저장될 서술자의 핸들
이 메서드를 이용해서 swap chain의 두 버퍼에 대해 각각 RTV를 생성해 보자.
ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(
mRtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
// Get the ith buffer in the swap chain.
ThrowIfFailed(mSwapChain->GetBuffer(
i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
// Create an RTV to it.
md3dDevice->CreateRenderTargetView(
mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);
// Next entry in heap.
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
8. 깊이/스텐실 버퍼와 뷰 생성
깊이 버퍼는 그냥 가장 가까운 가시 물체들의 깊이 정보를 저장하는 2차원 텍스처이다. 텍스처는 GPU 자원의 하나이므로, 텍스처 자원을 서술하는 D3D12_RESOURCE_DESC 구조체를 채운 후 ID3D12Device::CreateCommittedResource를 호출하면 깊이/스텐실 버퍼를 생성할 수 있다. D3D12_RESOURCE_DESC 구조체의 정의는 다음과 같다.
typedef struct D3D12_RESOURCE_DESC
{
D3D12_RESOURCE_DIMENSION Dimension; // 자원의 차원. 열거형 값들 중 하나 지정
UINT64 Alignment;
UINT64 Width; // 텍스처의 너비. 버퍼 자원의 경우 버퍼의 바이트 개수를 지정
UINT Height; // 텍스처의 높이
UINT16 DepthOrArraySize; // 3차원 텍스처의 깊이 또는 1/2차원 텍스처 배열 크기.
UINT16 MipLevels; // 밉맵 수준 개수
DXGI_FORMAT Format; // 텍셀의 자료 형식. DXGI_FORMAT 열거형의 한 멤버를 지정
DXGI_SAMPLE_DESC SampleDesc; // 다중표본화의 표본 개수와 품질 수준. 4X MSAA에서는 4배이어야
D3D12_TEXTURE_LAYOUT Layout; // 텍스처의 배치. D3D12_TEXTURE_LAYOUT 열거형 중 하나.
D3D12_RESOURCE_MISC_FLAG MiscFlags; // 기타 자원 플래그들. D3D12_RESOURCE_MISC_DEPTH_STENCIL
} D3D12_RESOURCE_DESC;
D3D12_RESOURCE_DIMENSION의 경우, D3D12_RESOURCE_DIMENSION_UNKNOWN/BUFFER/TEXTURE1D/ ... 등의 열거형 값들 중 하나를 지정한다.
GPU 자원들은 GPU의 힙에 존재한다. 본질적으로 GPU 힙은 GPU 메모리의 블록인데, ID3D12Device::CreateCommiteedResource 메서드는 자원을 생성하고, 지정된 속성들에 부합하는 힙에 그 자원을 맡긴다(commit).
HRESULT ID3D12Device::CreateCommittedResource(
const D3D12_HEAP_PROPERTIES *pHeapProperties, // 힙의 속성들을 담은 구조체를 가리키는 포인터.
// D3D12_HEAP_TYPE_DEFAULT/UPLOAD/READBACK/CUSTOM의 열거형이 담긴다.
// 최적의 성능을 위해서는 기본 힙에 넣기 (DEFAULT)
D3D12_HEAP_MISC_FLAG HeapMiscFlags, // 힙의 속성 플래그. 흔히 D3D12_HEAP_MISC_NONE 지정
const D3D12_RESOURCE_DESC *pResourceDesc, // D3D12_RESOURCE_DESC 인스턴스의 포인터
D3D12_RESOURCE_USAGE InitialResourceState, // 자원의 초기 상태 지정
// 깊이/스텐실 버퍼로 사용할 자원은 D3D12_RESOURCE_USAGE_INITIAL이 초기 상태
// 생성 후 D3D12_RESOURCE_USAGE_DEPTH로 전이 후 파이프라인에 묶기
const D3D12_CLEAR_VALUE *pOptimizedClearValue, // 자원 지우기에 최적화된 값을 나타내는
// D3D12_CLEAR_VALUE 구조체를 가리키는 포인터.
// 설정하지 않으려면 NULL을 지정해도 된다.
REFIID riidResource, // ID3D12Resource 인터페이스의 COM ID
void **ppvResource // 새로 생성된 ID3D12Resource 포인터
);
typedef struct D3D12_HEAP_PROPERTIES {
D3D12_HEAP_TYPE Type;
D3D12_CPU_PAGE_PROPERTIES CPUPageProperties;
D3D12_MEMORY_POOL MemoryPoolPreference;
UINT CreationNodeMask;
UINT VisibleNodeMask;
} D3D12_HEAP_PROPERTIES;
깊이 스텐실 텍스처와 뷰를 생성하는 예제 코드를 보자.
/ Create the depth/stencil buffer and view.
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.Format = mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));
// Create descriptor to mip level 0 of entire resource using the
// format of the resource.
md3dDevice->CreateDepthStencilView(
mDepthStencilBuffer.Get(),
nullptr,
DepthStencilView());
// Transition the resource from its initial state to be used as a depth buffer.
mCommandList->ResourceBarrier(
1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_DEPTH_WRITE));
9. 뷰포트 설정
뷰포트를 통해 3차원 장면 중 실제로 그릴 영역을 설정할 수 있다. 이러한 후면 버퍼의 부분직사각형(subrectangle) 영역을 뷰포트(viewport)라고 부른다.
typedef struct D3D12_VIEWPORT {
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D12_VIEWPORT;
보통은 MinDepth를 0으로, MaxDepth를 1로 설정해 깊이 값들이 바뀌지 않게 한다. D3D12_VIEWPORT 구조체를 채운 후에는 ID3D12CommandList::RSSetViewports메서드를 이용해 Direct3D에 설정한다.
// 첫 매개변수는 뷰포트들의 개수이다.
mCommandList->RSSetViewports(1, &vp);
10. 가위 직사각형 설정
가위 직사각형(scissor rectangle)은 특정 픽셀들을 선별(culling) 하는 용도로 쓰인다. 후면 버퍼를 기준으로 scissor rectangle을 정의/설정하면 렌터링 시 가위 직사각형의 바깥에 있는 픽셀들은 후면 버퍼에 래스터화되지 않는다.
가위 직사각형은 D3D12_RECT라는 구조체로 서술한다(사실은 RECT의 typedef임). RECT 구조체는 다음과 같다.
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
'Game Dev > DirectX' 카테고리의 다른 글
4-4. 시간 측정과 애니메이션 (타이머, GameTimer 클래스) (0) | 2021.11.18 |
---|---|
4-2. Direct3D 기초 : CPU와 GPU의 상호작용 , 명령 대기열, CPU/GPU 동기화 (0) | 2021.11.17 |
4-1. Direct3D 기초 : COM, 텍스쳐 형식, 교환사슬과 페이지 전환, 깊이 버퍼링, 다중 표본화, DXGI, 상주성 (0) | 2021.11.16 |
3. 선형변환, 아핀변환, 좌표 변환, DirectXMath 변환 함수 (0) | 2021.11.15 |
2. DirectXMath 라이브러리의 행렬 다루기 (0) | 2021.11.15 |