Unreal Engine 5.2
카테고리: DirectX 12
Chapter 1 DirectX12 초기화
01-1 프로젝트 설정
- 렌더링 파이프라인, Diffuse, Ambient, Specular, Double Buffering, Tangent Space, 그림자 원리, 짐벌락 현상, 쿼터니언이 중요하다
- CPU와 GPU의 가장큰 차이는? ALU 개수
// LaunchWindows.cpp
int32 WINAPI WinMain(_In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ char* pCmdLine, _In_ int32 nCmdShow)
{
// 여기가 언리얼이 시작되는 부분이다
int32 Result = LaunchWindowsStartup(hInInstance, hPrevInstance, pCmdLine, nCmdShow, nullptr);
}
// Launch.cpp
int32 GuardedMain( const TCHAR* CmdLine )
{
#if WITH_EDITOR
if (GIsEditor)
{
// 이 안에서 엔진을 셋업한다 ( 솔루션 구성에서 Editor 모드를 선택하면 이곳이 호출된다 )
ErrorLevel = EditorInit(GEngineLoop);
}
#endif
if (!GUELibraryOverrideSettings.bIsEmbedded)
{
// 이 안에서 엔진틱이 돌고있다
while( !IsEngineExitRequested() )
{
EngineTick();
}
}
}
// WindowsPlatformApplicationMisc.cpp
void FWindowsPlatformApplicationMisc::PumpMessages(bool bFromMainLoop)
{
// 이 안에서 PeekMessage 가 처리되고 있다 ( bFromMainLoop == false )
if (!bFromMainLoop)
{
FPlatformMisc::PumpMessagesOutsideMainLoop();
return;
}
// 이 안에서 PeekMessage 가 처리되고 있다 ( bFromMainLoop == true )
GPumpingMessagesOutsideOfMainLoop = false;
WinPumpMessages();
}
- 자주 활용하는 라이브러리를 인클루드 하는게 귀찮기 때문에 사용하는 헤더는? 미리 컴파일된 헤더 ( 07 : 41 )
- 언리얼 엔진의 pch.h 파일을 보는 방법은? 솔루션 탐색기에 pch.h 검색
- d3dx12.h의 특징은? 마이크로소프트 에서 제공해주는 비공식적인 라이브러리 ( 27 : 30 )
- 추가 종속성 대신 lib 파일을 추가할 수 있는 방법은? pragma comment 활용 ( 37 : 41 )
01-2 장치 초기화
// Engine.h
/** Global engine pointer. Can be 0 so don't use without checking. */
extern ENGINE_API class UEngine* GEngine;
// WindowsApplication.cpp
int32 FWindowsApplication::ProcessMessage( HWND hwnd, uint32 msg, WPARAM wParam, LPARAM lParam )
{
// 이 안에서 윈도우 메시지가 처리되고 있다
// FWindowsPlatformApplicationMisc 클래스의 WinPumpMessages() 에서 날아온 메시지 이다 ( 날아오는 원리는 콜백 )
// 아래 메시지 에서 처리되고 있는 자료형 중에 WINDOWINFO 라는게 있는데 그려질 화면 크기와 관련이 있다
switch(msg)
{
// 윈도우 사이즈 조정하는 이벤트
case WM_SIZING:
}
}
// D3D12Commands.cpp
void FD3D12CommandContext::RHISetViewport(float MinX, float MinY, float MinZ, float MaxX, float MaxY, float MaxZ)
{
// 그려질 화면 크기 관련 ( D3D12_VIEWPORT )
D3D12_VIEWPORT Viewport = { MinX, MinY, (MaxX - MinX), (MaxY - MinY), MinZ, MaxZ };
}
void FD3D12CommandContext::RHISetScissorRect(bool bEnable, uint32 MinX, uint32 MinY, uint32 MaxX, uint32 MaxY)
{
if (bEnable)
{
// 그려질 화면 크기 관련 ( CD3DX12_RECT )
const CD3DX12_RECT ScissorRect(MinX, MinY, MaxX, MaxY);
StateCache.SetScissorRect(ScissorRect);
}
}
void FD3D12CommandContext::RHIDrawPrimitive(uint32 BaseVertexIndex, uint32 NumPrimitives, uint32 NumInstances)
{
// 동일한 정점 데이터 집합을 여러 인스턴스로 렌더링하는 데 사용됩니다 ( 인스턴싱 기술 )
// 동일한 객체를 여러 번 그리거나, 동일한 메쉬를 사용하여 다양한 위치나 속성으로 렌더링할 때 유용합니다
// 이 단계를 거치면 정점 정보가 Input Assembler 단계로 넘어간다
// Vertex Buffer만 활용해 그리는 함수이다 ( NumInstances 에서 몇개의 물체를 그릴지 정한다 )
GraphicsCommandList()->DrawInstanced(VertexCount, NumInstances, BaseVertexIndex, 0);
}
void FD3D12CommandContext::RHIDrawIndexedPrimitive(FRHIBuffer* IndexBufferRHI, int32 BaseVertexIndex, uint32 FirstInstance, uint32 NumVertices, uint32 StartIndex, uint32 NumPrimitives, uint32 NumInstances)
{
// Vertex Buffer와 Index Buffer를 함께 활용해 그리는 함수이다
GraphicsCommandList()->DrawIndexedInstanced(IndexCount, NumInstances, StartIndex, BaseVertexIndex, FirstInstance);
}
void FD3D12CommandContext::RHIClearMRTImpl(bool* bClearColorArray, int32 NumClearColors, const FLinearColor* ClearColorArray, bool bClearDepth, float Depth, bool bClearStencil, uint32 Stencil)
{
if (ClearRTV)
{
for (int32 TargetIndex = 0; TargetIndex < BoundRenderTargets.GetNumActiveTargets(); TargetIndex++)
{
FD3D12RenderTargetView* RTView = BoundRenderTargets.GetRenderTargetView(TargetIndex);
if (RTView != nullptr && bClearColorArray[TargetIndex])
{
// Render Target View 초기화 ( ClearColorArray[TargetIndex] 색깔로 배경을 밀어준다 )
GraphicsCommandList()->ClearRenderTargetView(RTView->GetOfflineCpuHandle(), (float*)&ClearColorArray[TargetIndex], ClearRectCount, pClearRects);
UpdateResidency(RTView->GetResource());
}
}
}
if (ClearDSV)
{
// Depth Stencil View 초기화 ( D3D12_CLEAR_FLAGS를 활용해 Depth만 초기화 할것인지, Stencil도 같이 초기화 할것인지 정해줄 수 있다 )
GraphicsCommandList()->ClearDepthStencilView(DepthStencilView->GetOfflineCpuHandle(), (D3D12_CLEAR_FLAGS)ClearFlags, Depth, Stencil, ClearRectCount, pClearRects);
UpdateResidency(DepthStencilView->GetResource());
}
}
// D3D12Adapter.h
// D3D12Adapter.cpp
class FD3D12Adapter : public FNoncopyable
{
// 각종 객체 생성, D3D12CreateDevice() 함수를 활용해 초기화 한다
FORCEINLINE ID3D12Device* GetD3DDevice() const { return RootDevice; }
// 화면 관련 기능들, CreateDXGIFactory() 함수를 활용해 초기화 한다
FORCEINLINE IDXGIFactory2* GetDXGIFactory2() const { return DxgiFactory2; }
}
void FD3D12Adapter::CreateRootDevice(bool bWithDebug)
{
// 디버그 ( IID_PPV_ARGS 매크로는 자주 이용된다 )
TRefCountPtr<ID3D12Debug> DebugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(DebugController.GetInitReference()))))
{
DebugController->EnableDebugLayer();
}
}
void FD3D12Adapter::InitializeDevices()
{
// 01-5 Root Signature에 있는 이미지에서 desc.table을 세팅해주는 코드
D3D12_DESCRIPTOR_HEAP_DESC TempHeapDesc{};
// Constant Buffer View, Shader Resource View, Unordered Access Buffer
TempHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
TempHeapDesc.NumDescriptors = 2 * D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2;
// DX11의 RenderTargetView, DepthStencilView 등등을 이제 통합적으로 관리한다 ( ID3D12DescriptorHeap )
// 렌더 타겟 정보인 ID3D12Resource를 직접 접근 하는 대신 ID3D12DescriptorHeap를 활용해 간접 접근 하자
TRefCountPtr<ID3D12DescriptorHeap> TempHeap;
// 배열 형태로 관리되는 TempHeap.GetInitReference() 가 만들어 진다
HRESULT hr = RootDevice->CreateDescriptorHeap(&TempHeapDesc, IID_PPV_ARGS(TempHeap.GetInitReference()));
}
// D3D12Device.h
// D3D12Device.cpp
class FD3D12Queue final
{
public:
// 일감을 차곡차곡 기록했다가 한 방에 요청하는 곳
// ID3D12CommandAllocator, ID3D12GraphicsCommandList 와 친구 ( ID3D12GraphicsCommandList는 Close 함수를 활용해 무엇을 제출한다 )
TRefCountPtr<ID3D12CommandQueue> D3DCommandQueue;
}
void FD3D12Device::UpdateConstantBufferPageProperties()
{
//In genera, constant buffers should use write-combine memory (i.e. upload heaps) for optimal performance
bool bForceWriteBackConstantBuffers = false;
if (bForceWriteBackConstantBuffers)
{
ConstantBufferPageProperties = GetDevice()->GetCustomHeapProperties(0, D3D12_HEAP_TYPE_UPLOAD);
ConstantBufferPageProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_WRITE_BACK;
}
else
{
//-------------------------------------------------------------------
// D3D12_HEAP_TYPE_UPLOAD 힙 형식은
// CPU-쓰기-한 번, GPU 읽기-한 번 데이터에 가장 적합합니다 ( MSDN )
//-------------------------------------------------------------------
ConstantBufferPageProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
}
}
void FD3D12Device::CreateDefaultViews()
{
{
D3D12_SHADER_RESOURCE_VIEW_DESC SRVDesc{};
// Depth Stencil View 라면 여기서 Depth와 Stencil 사용 여부 세팅이 가능할 것이다
SRVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
// 여기는 2D 형식의 이미지가 세팅되었는데 스카이 박스 이미지 등을 위해
// 큐브 형식의 이미지도 세팅 가능하다
SRVDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
SRVDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
}
{
// Render Target 세팅
D3D12_RENDER_TARGET_VIEW_DESC RTVDesc{};
RTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
RTVDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
RTVDesc.Texture2D.MipSlice = 0;
// Render Target 생성
DefaultViews.NullRTV = new FD3D12ViewDescriptorHandle(this, ERHIDescriptorHeapType::RenderTarget);
DefaultViews.NullRTV->CreateView(RTVDesc, nullptr);
}
}
// D3D12Viewport.h
// D3D12Viewport.cpp
class FD3D12FramePacing : public FRunnable, public FD3D12AdapterChild
{
private:
// CPU와 GPU 동기화를 위한 간단한 도구
TRefCountPtr<ID3D12Fence> Fence;
}
FD3D12Texture* GetSwapChainSurface(FD3D12Device* Parent, EPixelFormat PixelFormat, uint32 SizeX, uint32 SizeY, IDXGISwapChain* SwapChain, uint32 BackBufferIndex, TRefCountPtr<ID3D12Resource> BackBufferResourceOverride)
{
// Grab the back buffer
TRefCountPtr<ID3D12Resource> BackBufferResource;
if (SwapChain)
{
#if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
// 여기서 렌더 타겟(Back Buffer)과 스왑체인이 연동된다
VERIFYD3D12RESULT_EX(SwapChain->GetBuffer(BackBufferIndex, IID_PPV_ARGS(BackBufferResource.GetInitReference())), Parent->GetDevice());
#else // #if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
}
else
{
// D3D12_HEAP_TYPE_DEFAULT 힙 형식은 GPU의 대역폭을 가장 많이 사용하지만
// CPU 액세스를 제공할 수는 없습니다 GPU는 이 풀에서 메모리를 읽고 쓸 수 있습니다 ( MSDN )
const D3D12_HEAP_PROPERTIES HeapProps = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT, (uint32)Parent->GetGPUIndex(), Parent->GetGPUMask().GetNative());
// Create custom back buffer texture as no swap chain is created in pixel streaming windowless mode
// 픽셀 스트리밍 모드에서 윈도우가 없는 환경에서는 스왑 체인(swap chain)이 생성되지 않기 때문에
// 커스텀 백 버퍼 텍스처를 생성해야 한다
D3D12_RESOURCE_DESC TextureDesc;
// GPU RAM에 정점 정보등을 복사해주기 위한 BUFFER를 만든다, BackBufferResource.GetInitReference() 이곳에 만들어 진다
// 해당 공간에 있는 데이터의 주소는 GPU Registers 에게 넘겨질 것이다
// BackBufferResource의 자료형은 ID3D12Resource이다 즉 텍스쳐도 ID3D12Resource에 담긴다는 사실을 알 수 있다
Parent->GetDevice()->CreateCommittedResource(&HeapProps, D3D12_HEAP_FLAG_NONE, &TextureDesc, D3D12_RESOURCE_STATE_PRESENT, nullptr, IID_PPV_ARGS(BackBufferResource.GetInitReference()));
}
}
// D3D12Submission.cpp
FD3D12DynamicRHI::FProcessResult FD3D12DynamicRHI::ProcessInterruptQueue()
{
if (InterruptThread && !CurrentQueue.Fence.bInterruptAwaited)
{
// 여기서 D3DFence와 Event를 활용해 동기화 수행 ( GPU 작업이 완료될 때 까지 기다린다 )
// 이것이 있으면 Unmap을 사용하지 않고 동기화가 가능하다
VERIFYD3D12RESULT(CurrentQueue.Fence.D3DFence->SetEventOnCompletion(Payload->CompletionFenceValue, InterruptThread->Event));
CurrentQueue.Fence.bInterruptAwaited = true;
}
}
FD3D12DynamicRHI::FProcessResult FD3D12DynamicRHI::ProcessSubmissionQueue()
{
for (int32 Index = 0; Index < Payload->CommandListsToExecute.Num(); Index++)
{
// 이 함수 안에서 BarrierCommandList->Close() 를 통해 일감들을 마감 처리한다
if (FD3D12CommandList* BarrierCommandList = GenerateBarrierCommandListAndUpdateState(CurrentCommandList))
{
FD3D12Queue& BarrierQueue = BarrierCommandList->Device->GetQueue(BarrierCommandList->QueueType);
if (&BarrierQueue == &CurrentQueue)
{
// 일감들 실행 요청, 이후에 PayloadToSubmit->CommandListsToExecute.Reset() 함수를 활용해 Reset 한다
BarrierQueue.PayloadToSubmit->CommandListsToExecute.Insert(BarrierCommandList, Index++);
}
}
}
}
// D3D12View.cpp
void FD3D12ViewDescriptorHandle::CreateView(const D3D12_RENDER_TARGET_VIEW_DESC& Desc, ID3D12Resource* Resource)
{
// 렌더 타겟이 Resource 에 담긴다 ( 렌더 타겟은 스왑체인에 활용되는 BackBuffer를 의미하는 듯 )
GetParentDevice()->GetDevice()->CreateRenderTargetView(Resource, &Desc, OfflineCpuHandle);
}
void FD3D12ViewDescriptorHandle::CreateView(const D3D12_DEPTH_STENCIL_VIEW_DESC& Desc, ID3D12Resource* Resource)
{
// DepthStencilView Buffer가 Resource에 담긴다
GetParentDevice()->GetDevice()->CreateDepthStencilView(Resource, &Desc, OfflineCpuHandle);
}
void FD3D12ViewDescriptorHandle::CreateView(const D3D12_CONSTANT_BUFFER_VIEW_DESC& Desc)
{
// Constant Buffer View 생성 ( 디버깅 해보니 이 함수 호출 안되고 있음 )
GetParentDevice()->GetDevice()->CreateConstantBufferView(&Desc, OfflineCpuHandle);
}
// WindowsD3D12Viewport.cpp
void FD3D12Viewport::Init()
{
// 이 안에서 스왑체인 세팅이 이루어진다
bNeedSwapChain = !FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen"));
if (bNeedSwapChain)
{
if (FD3D12DynamicRHI::GetD3DRHI()->IsQuadBufferStereoEnabled())
{
if (Factory2->IsWindowedStereoEnabled())
{
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc1{};
// 버퍼 카운트
SwapChainDesc1.BufferCount = NumBackBuffers;
}
}
}
}
HRESULT FD3D12Viewport::PresentInternal(int32 SyncInterval)
{
if (SwapChain1)
{
// 여기서 SwapChain에 그려줘(Present)가 등록되어야 하는데 디버깅 해보니 호출 안됨
return SwapChain1->Present(SyncInterval, Flags);
}
return S_OK;
}
// 스왑 체인 Present 대한 내용이 여기서 처리되고 있는듯
bool FD3D12Viewport::Present(bool bLockToVsync)
{
if (!IsPresentAllowed())
{
return false;
}
FD3D12Adapter* Adapter = GetParentAdapter();
for (uint32 GPUIndex : FRHIGPUMask::All())
{
FD3D12CommandContext& DefaultContext = Adapter->GetDevice(GPUIndex)->GetDefaultCommandContext();
// Those are not necessarily the swap chain back buffer in case of multi-gpu
FD3D12Texture* DeviceBackBuffer = DefaultContext.RetrieveObject<FD3D12Texture, FRHITexture2D>(GetBackBuffer_RHIThread());
FD3D12Texture* DeviceSDRBackBuffer = DefaultContext.RetrieveObject<FD3D12Texture, FRHITexture2D>(GetSDRBackBuffer_RHIThread());
// 이 함수를 추적해보면 FD3D12ResourceBarrierBatcher::AddTransition 함수와 연결된다
// 그리고 해당 함수 안에 CD3DX12_RESOURCE_BARRIER::Transition 함수 부분이 있다
DefaultContext.TransitionResource(
DeviceBackBuffer->GetShaderResourceView()->GetResource(),
D3D12_RESOURCE_STATE_TBD,
D3D12_RESOURCE_STATE_PRESENT, // 현재 화면 출력
0
);
if (SDRBackBuffer_RHIThread != nullptr)
{
DefaultContext.TransitionResource(
DeviceSDRBackBuffer->GetShaderResourceView()->GetResource(),
D3D12_RESOURCE_STATE_TBD,
D3D12_RESOURCE_STATE_PRESENT, // 현재 화면 출력
0
);
}
// Flushes the batched resource barriers to the current command list
DefaultContext.FlushResourceBarriers();
}
}
// D3D12DescriptorCache.cpp
void FD3D12DescriptorCache::SetRenderTargets(FD3D12RenderTargetView** RenderTargetViewArray, uint32 Count, FD3D12DepthStencilView* DepthStencilTarget)
{
for (uint32 i = 0; i < Count; i++)
{
if (RenderTargetViewArray[i] != NULL)
{
// D3D12_RESOURCE_STATE_RENDER_TARGET 가 의미하는 바는 외주 결과물
Context.TransitionResource(RenderTargetViewArray[i], D3D12_RESOURCE_STATE_RENDER_TARGET);
}
if (DepthStencilTarget != nullptr)
{
// 어떤 버퍼에 결과물을 그릴지 지정 ( HLSL의 SV_Target 부분과 맞물려서 동작한다 )
// RTVDescriptors 여기에 BackBufferView가 들어가고, DSVDescriptor 여기에 DepthStencilView가 들어 가는듯
Context.GraphicsCommandList()->OMSetRenderTargets(Count, RTVDescriptors, 0, &DSVDescriptor);
}
}
}
void FD3D12DescriptorCache::SetVertexBuffers(FD3D12VertexBufferCache& Cache)
{
//-------------------------------------------------------------------------------------------
// CurrentVertexBufferViews의 자료형은 D3D12_VERTEX_BUFFER_VIEW 이다
// D3D12_VERTEX_BUFFER_VIEW 자료형은 D3D12_GPU_VIRTUAL_ADDRESS를 가지고 있는 구조체이다
// IASetVertexBuffers 함수로 렌더링 파이프라인에 GPU 공간을 연동한다
// 즉 CurrentVertexBufferViews를 이용해 GPU에 정점 데이터를 전달한다
// Count가 의미하는 바는 버퍼의 개수이다
//-------------------------------------------------------------------------------------------
Context.GraphicsCommandList()->IASetVertexBuffers(0, Count, Cache.CurrentVertexBufferViews);
}
void FD3D12DescriptorCache::SetRootConstantBuffers(EShaderFrequency ShaderStage, const FD3D12RootSignature* RootSignature, FD3D12ConstantBufferCache& Cache, CBVSlotMask SlotsNeededMask)
{
if (ShaderStage == SF_Compute)
{
// SetComputeRootConstantBufferView
// RootSignature 관련 Registers 에게 현재 GPU RAM BUFFER 데이터 주소(CurrentGPUVirtualAddress)를 넘겨주고 있다
// GPU RAM BUFFER는 여러개 일 수 있다는 것을 명심하자 ( 01-4 Constant Buffer에 있는 이미지 참고 )
Context.GraphicsCommandList()->SetComputeRootConstantBufferView(BaseIndex + SlotIndex, CurrentGPUVirtualAddress);
}
else
{
// SetGraphicsRootConstantBufferView
// RootSignature 관련 Registers 에게 현재 GPU RAM BUFFER 데이터 주소(CurrentGPUVirtualAddress)를 넘겨주고 있다
// GPU RAM BUFFER는 여러개 일 수 있다는 것을 명심하자 ( 01-4 Constant Buffer에 있는 이미지 참고 )
Context.GraphicsCommandList()->SetGraphicsRootConstantBufferView(BaseIndex + SlotIndex, CurrentGPUVirtualAddress);
}
}
void FD3D12DescriptorCache::SetUAVs(EShaderFrequency ShaderStage, const FD3D12RootSignature* RootSignature, FD3D12UnorderedAccessViewCache& Cache, const UAVSlotMask& SlotsNeededMask, uint32 SlotsNeeded, uint32& HeapSlot)
{
// CPU에 있는 Desc.Heap 내용을 GUP에 있는 Desc.Heap으로 옮겨준다
GetParentDevice()->GetDevice()->CopyDescriptors(
1, &DestDescriptor, &SlotsNeeded,
SlotsNeeded, SrcDescriptors, nullptr /* sizes */,
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}
bool FD3D12DescriptorCache::SetDescriptorHeaps(bool bForceHeapChanged)
{
// Set the descriptor heaps.
if (bHeapChanged)
{
// 어떤 Heap을 사용해줄 것인지 지정 ( 무거운 함수여서 프레임 마다 한번씩만 호출해야 한다 )
Context.GraphicsCommandList()->SetDescriptorHeaps(UE_ARRAY_COUNT(ppHeaps), ppHeaps);
}
}
// D3D12Resources.cpp
void FD3D12ResourceBarrierBatcher::FlushIntoCommandList(FD3D12CommandList& CommandList, FD3D12QueryAllocator& TimestampAllocator)
{
// Barriers는 D3D12_RESOURCE_BARRIER 구조로 되어있다
// SwapChain 정보, D3D12_RESOURCE_STATE_PRESENT 정보, D3D12_RESOURCE_STATE_RENDER_TARGET 정보가 있을 수 있다
CommandList.GraphicsCommandList()->ResourceBarrier(BatchEnd - BatchStart, &Barriers[BatchStart]);
}
// D3D12_CLEAR_VALUE는 Depth Buffer를 초기화 해줄때 넣어줄 값이다
// 보통 1.0f가 설정되는데 이는 아무 물체도 그리지 않은 상태이며 0.f가 Min 값이다
HRESULT FD3D12Adapter::CreateCommittedResource(const FD3D12ResourceDesc& InDesc, FRHIGPUMask CreationNode, const D3D12_HEAP_PROPERTIES& HeapProps, D3D12_RESOURCE_STATES InInitialState,
ED3D12ResourceStateMode InResourceStateMode, D3D12_RESOURCE_STATES InDefaultState, const D3D12_CLEAR_VALUE* ClearValue, FD3D12Resource** ppOutResource, const TCHAR* Name, bool bVerifyHResult)
{
// 새로운 GPU 리소스를 생성하는 역할을 하는듯 하다
}
int32 FD3D12ResourceBarrierBatcher::AddTransition(FD3D12Resource* pResource, D3D12_RESOURCE_STATES Before, D3D12_RESOURCE_STATES After, uint32 Subresource)
{
// 렌더 타겟 텍스쳐 등을 가져와 Barrier에 담을 것이다
// 해당 객체는 스왑체인 할때 덜 그려준 버퍼가 렌더링 되는것을 방지하는 용도이다
// 좀 더 자세하게 설명하면 Before 용도를 After 용도로 바꿔줄 것이다
FD3D12ResourceBarrier& Barrier = Barriers.Emplace_GetRef(CD3DX12_RESOURCE_BARRIER::Transition(pResource->GetResource(), Before, After, Subresource));
if (pResource->IsBackBuffer() && EnumHasAnyFlags(After, BackBufferBarrierWriteTransitionTargets))
{
Barrier.Flags |= BarrierFlag_CountAsIdleTime;
}
}
D3D12StateCache.cpp
void FD3D12StateCache::ApplyState(ED3D12PipelineType PipelineType)
{
// 이 구문에 브레이크 포인트 찍고 들어가보면
// CmdContext.GraphicsCommandList()->SetGraphicsRootSignature 이 함수가 사용되지 않고 있다
const bool bRootSignatureChanged = InternalSetRootSignature(PipelineType, PSOCommonData->RootSignature);
if (bNeedSetViewports)
{
// 뷰포트 등록
CmdContext.GraphicsCommandList()->RSSetViewports(PipelineState.Graphics.CurrentNumberOfViewports, PipelineState.Graphics.CurrentViewport);
}
if (bNeedSetScissorRects)
{
// 특정 영역만 렌더링하거나 성능 최적화를 위해 사용되는 중요한 함수
CmdContext.GraphicsCommandList()->RSSetScissorRects(PipelineState.Graphics.CurrentNumberOfScissorRects, PipelineState.Graphics.CurrentScissorRects);
}
if (bNeedSetPrimitiveTopology)
{
// 인풋 어셈블러(Input Assembler, IA) 단계에 사용될 기본 도형 토폴로지를 설정하는 함수
CmdContext.GraphicsCommandList()->IASetPrimitiveTopology(PipelineState.Graphics.CurrentPrimitiveTopology);
}
}
void FD3D12StateCache::InternalSetStreamSource(FD3D12ResourceLocation* VertexBufferLocation, uint32 StreamIndex, uint32 Stride, uint32 Offset)
{
// Initialize the vertex buffer view ( View 데이터는 CPU와 GPU가 공유한다 )
__declspec(align(16)) D3D12_VERTEX_BUFFER_VIEW NewView;
// 뷰의 위치
NewView.BufferLocation = (VertexBufferLocation) ? VertexBufferLocation->GetGPUVirtualAddress() + Offset : 0;
// 정점 1개 크기
NewView.StrideInBytes = Stride;
// 버퍼의 크기
NewView.SizeInBytes = (VertexBufferLocation) ? VertexBufferLocation->GetSize() - Offset : 0; // Make sure we account for how much we offset into the VB
}
void FD3D12StateCache::InternalSetPipelineState(FD3D12PipelineState* InPipelineState)
{
ID3D12PipelineState* const PendingD3DPipelineState = InPipelineState->GetPipelineState();
if (PipelineState.Common.bNeedSetPSO || CurrentD3DPipelineState == nullptr || CurrentD3DPipelineState != PendingD3DPipelineState)
{
// 위의 InternalSetStreamSource() 함수에서 만든 정보를 파이프 라인에 등록한다
// 아마 D3D12_INPUT_ELEMENT_DESC 정보도 들어있을 것이다
CmdContext.GraphicsCommandList()->SetPipelineState(PendingD3DPipelineState);
}
}
void FD3D12StateCache::InternalSetIndexBuffer(FD3D12Resource* Resource)
{
// IndexBufferView를 사용하여 GPU에 인덱스 데이터를 전달한다
CmdContext.GraphicsCommandList()->IASetIndexBuffer(&PipelineState.Graphics.IBCache.CurrentIndexBufferView);
}
void FD3D12StateCache::ApplySamplers(const FD3D12RootSignature* const pRootSignature, uint32 StartStage, uint32 EndStage)
{
FD3D12UniqueSamplerTable* CachedTable = GlobalSamplerSet.Find(Table);
if (CachedTable)
{
if (Stage == SF_Compute)
{
const uint32 RDTIndex = pRootSignature->SamplerRDTBindSlot(EShaderFrequency(Stage));
// GPU 쪽으로 Compute 정보가 넘어간다
CmdContext.GraphicsCommandList()->SetComputeRootDescriptorTable(RDTIndex, CachedTable->GPUHandle);
}
else
{
const uint32 RDTIndex = pRootSignature->SamplerRDTBindSlot(EShaderFrequency(Stage));
// GPU 쪽으로 머티리얼 정보가 넘어간다
CmdContext.GraphicsCommandList()->SetGraphicsRootDescriptorTable(RDTIndex, CachedTable->GPUHandle);
}
}
}
01-3 삼각형 띄우기
// D3D12RootSignature.h
// D3D12RootSignature.cpp
class FD3D12RootSignature : public FD3D12AdapterChild
{
// RootSignature를 불러오는 함수 ( GPU 에게 계약서를 건내주는 일을 담당한다 )
ID3D12RootSignature* GetRootSignature() const { return RootSignature.GetReference(); }
}
//-----------------------------------------------------------------------------------------
// D3D12_VERSIONED_ROOT_SIGNATURE_DESC 안에 플래그 정보를 보면
// D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT 와 같은 정보와
// D3D12_ROOT_PARAMETER 타입 변수에 Registers를 어떻게 활용할 것인지에 대한 정보가 있다
// 강좌에서는 해당 Registers를 Constant Buffer View로 활용했다
// 참고로 INPUT_ASSEMBLER_INPUT_LAYOUT 플래그는 입력 조립기 단계를 뜻한다
//-----------------------------------------------------------------------------------------
void FD3D12RootSignature::Init(const D3D12_VERSIONED_ROOT_SIGNATURE_DESC& InDesc, uint32 BindingSpace)
{
ID3D12Device* Device = GetParentAdapter()->GetD3DDevice();
// Create and analyze the root signature. ( RootSignature 생성 )
VERIFYD3D12RESULT(Device->CreateRootSignature(FRHIGPUMask::All().GetNative(),
RootSignatureBlob->GetBufferPointer(),
RootSignatureBlob->GetBufferSize(),
IID_PPV_ARGS(RootSignature.GetInitReference())));
}
FD3D12RootSignatureDesc::FD3D12RootSignatureDesc(const FD3D12QuantizedBoundShaderState& QBSS, const D3D12_RESOURCE_BINDING_TIER ResourceBindingTier)
{
// Sampler 관련 정보는 여기서 세팅 되는것 같다
const D3D12_DESCRIPTOR_RANGE_FLAGS SamplerDescriptorRangeFlags = (ResourceBindingTier <= D3D12_RESOURCE_BINDING_TIER_1) ?
D3D12_DESCRIPTOR_RANGE_FLAG_NONE :
D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE;
case D3D12_ROOT_PARAMETER_TYPE_CBV:
{
for (uint32 ShaderRegister = 0; (ShaderRegister < Shader.ConstantBufferCount) && (ShaderRegister < MAX_ROOT_CBVS); ShaderRegister++)
{
// RootSignature 관련 MSDN 상에 나와 있는 이미지를 보면 초록색으로
// root CBV 라고 표시된 공간이 있는데 그 공간을 활용 한다는 코드
// 해당 공간은 Light와 같이 전역으로 사용되는 것들이 사용할 것이다
// GetD3D12ShaderVisibility는 Shader 단계들중 어디까지 활용될 것인지를 나타낸다
TableSlots[RootParameterCount].InitAsConstantBufferView(ShaderRegister, BindingSpace, CBVRootDescriptorFlags, GetD3D12ShaderVisibility(Visibility));
}
}
case D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE:
{
if (Shader.ShaderResourceCount > 0)
{
// Shader Resource View로 활용할 것이며, Shader.ShaderResourceCount 만큼 활용할 것이며, 0u 번지부터 활용할 것이다
DescriptorRanges[RootParameterCount].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, Shader.ShaderResourceCount, 0u, BindingSpace, SRVDescriptorRangeFlags);
// desc.table 내용물을 어떻게 구성할 것인지 ( 이 정보는 D3D12_VERSIONED_ROOT_SIGNATURE_DESC에 등록된다 )
TableSlots[RootParameterCount].InitAsDescriptorTable(1, &DescriptorRanges[RootParameterCount], GetD3D12ShaderVisibility(Visibility));
}
}
}
// D3D12StateCachePrivate.h
struct FD3D12VertexBufferCache
{
// ID3D12Resource를 대신하는 객체이다
D3D12_VERTEX_BUFFER_VIEW CurrentVertexBufferViews[MAX_VBS];
}
struct FD3D12IndexBufferCache
{
// ID3D12Resource를 대신하는 객체이다
D3D12_INDEX_BUFFER_VIEW CurrentIndexBufferView;
};
// D3D12RenderTarget.cpp
// Map, Memcpy, Unmap 함수가 핵심
void FD3D12DynamicRHI::ReadSurfaceDataNoMSAARaw(FRHITexture* TextureRHI, FIntRect InRect, TArray<uint8>& OutData, FReadSurfaceDataFlags InFlags)
{
// Lock the staging resource.
void* pData;
D3D12_RANGE ReadRange = { SrcStart, SrcStart + XBytesAligned * (SizeY - 1) + BytesPerLine };
// ID3D12Resource 타입으로 만들어진 GPU RAM BUFFER에 CPU RAM BUFFER인 pData를 연동한다
VERIFYD3D12RESULT(TempTexture2D->GetResource()->Map(0, &ReadRange, &pData));
uint8* DestPtr = OutData.GetData();
uint8* SrcPtr = (uint8*)pData + SrcStart;
for (uint32 Y = 0; Y < SizeY; Y++)
{
// 데이터가 GPU 메모리로 전달된다
// 보통 SrcPtr에 원본 Vertex 정보 등이 담긴다
// 그리고 DestPtr와 pData가 같은 변수명을 사용해야 하는데 왜 다를까
memcpy(DestPtr, SrcPtr, BytesPerLine);
DestPtr += BytesPerLine;
SrcPtr += XBytesAligned;
}
// 데이터 연동 종료
TempTexture2D->GetResource()->Unmap(0, nullptr);
}
SkeletalMeshDuplicatedVerticesBuffer.h
class FDuplicatedVerticesBuffer : public FRenderResource
{
virtual void InitRHI() override
{
{
FResourceArrayInterface* ResourceArray = DupVertData.GetResourceArray();
check(ResourceArray->GetResourceDataSize() > 0);
// 우리가 BP를 월드에 집어 넣을때 여기서 해당 BP의 버텍스 정보가 복사되는듯 하다
FRHIResourceCreateInfo CreateInfo(TEXT("DuplicatedVerticesIndexBuffer"), ResourceArray);
DuplicatedVerticesIndexBuffer.VertexBufferRHI = RHICreateVertexBuffer(ResourceArray->GetResourceDataSize(), BUF_Static | BUF_ShaderResource, CreateInfo);
DuplicatedVerticesIndexBuffer.VertexBufferRHI->SetOwnerName(GetOwnerName());
DuplicatedVerticesIndexBuffer.VertexBufferSRV = RHICreateShaderResourceView(DuplicatedVerticesIndexBuffer.VertexBufferRHI, sizeof(uint32), PF_R32_UINT);
}
{
FResourceArrayInterface* ResourceArray = DupVertIndexData.GetResourceArray();
check(ResourceArray->GetResourceDataSize() > 0);
// 우리가 BP를 월드에 집어 넣을때 여기서 해당 BP의 인덱스 정보가 복사되는듯 하다
FRHIResourceCreateInfo CreateInfo(TEXT("LengthAndIndexDuplicatedVerticesIndexBuffer"), ResourceArray);
LengthAndIndexDuplicatedVerticesIndexBuffer.VertexBufferRHI = RHICreateVertexBuffer(ResourceArray->GetResourceDataSize(), BUF_Static | BUF_ShaderResource, CreateInfo);
LengthAndIndexDuplicatedVerticesIndexBuffer.VertexBufferRHI->SetOwnerName(GetOwnerName());
LengthAndIndexDuplicatedVerticesIndexBuffer.VertexBufferSRV = RHICreateShaderResourceView(LengthAndIndexDuplicatedVerticesIndexBuffer.VertexBufferRHI, sizeof(uint32), PF_R32_UINT);
}
}
}
// ShaderPipelineCache.cpp
bool FShaderPipelineCacheTask::Precompile(FRHICommandListImmediate& RHICmdList, EShaderPlatform Platform, FPipelineCacheFileFormatPSO const& PSO)
{
FVertexShaderRHIRef VertexShader;
if (PSO.GraphicsDesc.VertexShader != FSHAHash())
{
// CreateVertexShader 함수를 호출해 Shader 파일을 읽어 Init을 해줘야 하는데 호출이 안되네
VertexShader = FShaderCodeLibrary::CreateVertexShader(Platform, PSO.GraphicsDesc.VertexShader);
GraphicsInitializer.BoundShaderState.VertexShaderRHI = VertexShader;
}
}
// ShaderResource.cpp
FRHIShader* FShaderMapResource_InlineCode::CreateRHIShaderOrCrash(int32 ShaderIndex)
{
TRefCountPtr<FRHIShader> RHIShader;
switch (Frequency)
{
// 언리얼 에서는 CreateVertexShader 대신 RHICreateVertexShader 라는 함수를 만들었다
case SF_Vertex: RHIShader = RHICreateVertexShader(ShaderCodeView, ShaderHash); break;
case SF_Mesh: RHIShader = RHICreateMeshShader(ShaderCodeView, ShaderHash); break;
case SF_Amplification: RHIShader = RHICreateAmplificationShader(ShaderCodeView, ShaderHash); break;
case SF_Pixel: RHIShader = RHICreatePixelShader(ShaderCodeView, ShaderHash); break;
case SF_Geometry: RHIShader = RHICreateGeometryShader(ShaderCodeView, ShaderHash); break;
case SF_Compute: RHIShader = RHICreateComputeShader(ShaderCodeView, ShaderHash); break;
}
}
- USF Shader 파일이란? USF Shader 파일은 HLSL 언어에 기반한 것으로, 멀티 플랫폼 셰이더 코드가 들어있는 언리얼 엔진 셰이더 파일 포맷입니다 (참고), (참고), (참고), (참고), (참고), (참고)
01-4 Constant Buffer
- RootSignature의 용도는? GPU Registers를 어떻게 활용할 것인지 설정 ( 08 : 38 ) (참고)
- Registers를 활용하기 위한 단계를 설명하면? CPU RAM 에서 GPU RAM으로 데이터를 복사한 뒤 그 데이터의 위치를 Constant Buffer View로 사용할 Registers에 복사해줘야 한다 ( 28 : 48 )
- GPU RAM 공간 생성과 데이터 연동에 대해 API 기반으로 설명하면? CreateCommittedResource -> Map -> Unmap ( 32 : 41 )
- 위의 과정이 끝나면 어떻게 되는가? CPU RAM 데이터가 GPU RAM 으로 즉시 복사된다 ( 33 : 07 )
- GPU RAM에서 GPU Registers로의 복사는 CommandQueue를 활용해 나중에 처리 되기 때문에 발생하는 문제와 해결 방법을 설명하면? 내가 작업을 요청했던 당시와 상황이 달라질 수 있다 때문에 GPU RAM 상에 버퍼를 한개가 아닌 여러개로 만든다 ( 37 : 14 )
- GPU RAM에 생성되는 BUFFER의 타입은 무엇인가? ID3D12Resource ( 40 : 00 )
- 상수 버퍼는 몇 바이트 배수로 만들어야 하는가? 256 ( 42 : 59 )
- 상수 버퍼는 어떻게 활용될 수 있는가? Offset을 GPU Registers로 넘겨줄 때 활용될 수 있다 ( 57 : 20 )
01-5 Root Signature
- root CBV 공간말고 활용할 수 있는 공간은? desc.table ( 01 : 34 )
- desc.table을 동시에 활성화 할 수 있는가? 아니오 ( 01 : 47 )
- desc.table 안에 5개의 공간을 사용하려면 어떻게 해야 하는가? GPU RAM에 Desc.Heap을 만들고 여기에도 5개의 공간을 만들어 줘야 한다 ( 05 : 30 )
- GPU RAM의 Desc.Heap에 있는 5개의 공간은 어떻게 채워야 하는가? 5개의 공간을 가지고 있으며 이 공간이 Constant Buffer 5개를 각각 가리키는 Desc.Heap 으로 부터 즉시 복사해온다 참고로 해당 Desc.Heap은 Constant Buffer View 라고 부른다 ( 06 : 35 )
- GPU RAM의 Desc.Heap에 있는 데이터를 desc.table로 복사하는 것은 즉시복사 인가 나중복사 인가? 나중복사 ( 07 : 49 )
- 위의 복사로 인해 생기는 타이밍 문제를 해결할 방법은? GPU RAM의 Desc.Heap(안에 공간 5개 있음)을 여러개로 만든다 ( 08 : 50 )
- Constant Buffer를 가리키고 있는 Desc.Heap의 자료형은 무엇인가? ID3D12DescriptorHeap ( 19 : 20 )
// D3D12Descriptors.h
struct FD3D12DescriptorHeap : public FD3D12DeviceChild, public FThreadSafeRefCountedObject
{
// Constant Buffer를 가리키고 있는 Desc.Heap의 특정 슬롯 핸들을 가져온다
inline D3D12_CPU_DESCRIPTOR_HANDLE GetCPUSlotHandle(uint32 Slot) const { return CD3DX12_CPU_DESCRIPTOR_HANDLE(CpuBase, Slot, DescriptorSize); }
// GPU RAM의 Desc.Heap에 있는 특정 슬롯 핸들을 가져온다
inline D3D12_GPU_DESCRIPTOR_HANDLE GetGPUSlotHandle(uint32 Slot) const { return CD3DX12_GPU_DESCRIPTOR_HANDLE(GpuBase, Slot, DescriptorSize); }
}
01-6 Index Buffer
- Constant Buffer는 어떻게 활용할 수 있는가? CreateCommittedResource -> Map -> Unmap 활용해 물체 정보를 한번만 생성한 뒤 Constant Buffer를 활용해 여러 물체를 다양한 모양으로 변경해 렌더링 ( 01 : 42 )
- 4번 강좌에 있는 렌더링 파이프 라인을 보고 우리가 렌더링 파이프 라인에 정보를 전달하는 방식 3가지를 설명하면? Root descriptors, Descriptor tables, Root Constant ( 03 : 03 )
- 인덱스 버퍼는 왜 사용해야 하는가? 중복되는 버텍스 정보를 제외하면 삼각형 들이 어떻게 연결되어 있는지 알수없기 때문이다 ( 12 : 18 )
- 인덱스 버퍼를 정할때 순서가 중요한가? 아니오 ( 21 : 09 )
- 4번 강좌에 있는 렌더링 파이프 라인을 보고 Vertex Buffer 정보와 Index Buffer 정보가 어느 단계에서 정해지는지 가리켜 보면? ( 22 : 24 )
01-7 Texture Mapping
- UV 좌표에 대해서 설명하면? ( 03 : 11 )
- 텍스쳐가 맵핑되는 원리를 설명하면? ( 04 : 08 )
- 텍스쳐가 Constant Buffer와 다른점은? Constant Buffer와 달리 Desc.Heap에 View를 딱 하나만 만들어도 된다, Constant Buffer를 만들때는 Desc.Heap에 View를 5개 만들어 해당 View 내용을 Registers로 복사해줌 ( 16 : 16 )
// D3D12Texture.cpp
FTextureRHIRef FD3D12DynamicRHI::RHIAsyncCreateTexture2D(uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags Flags, ERHIAccess InResourceState, void** InitialMipData, uint32 NumInitialMips)
{
// 리소스 로드 ( 여기서 사용하는 CommandList는 리소스 로드 용도로 사용되는 객체 )
UpdateSubresources(
CopyScope.Context.CopyCommandList().Get(),
Resource->GetResource(),
TempResourceLocation.GetResource()->GetResource(),
TempResourceLocation.GetOffsetFromBaseOfResource(),
0, NumMips,
SubResourceData);
}
FD3D12ResourceDesc FD3D12DynamicRHI::GetResourceDesc(const FRHITextureDesc& TextureDesc) const
{
// 2D 텍스쳐를 만들고 있다
ResourceDesc = CD3DX12_RESOURCE_DESC::Tex2D(
PlatformResourceFormat,
TextureDesc.Extent.X,
TextureDesc.Extent.Y,
TextureDesc.ArraySize * (TextureDesc.IsTextureCube() ? 6 : 1), // Array size
TextureDesc.NumMips,
ActualMSAACount,
ActualMSAAQuality,
D3D12_RESOURCE_FLAG_NONE); // Add misc flags later
// 어떤 용도로 사용할 것인지 지정해주고 있다
if (EnumHasAnyFlags(TextureDesc.Flags, TexCreate_Shared))
{
ResourceDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS;
}
// 어떤 용도로 사용할 것인지 지정해주고 있다
if (EnumHasAnyFlags(TextureDesc.Flags, TexCreate_RenderTargetable))
{
ResourceDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
}
else if (EnumHasAnyFlags(TextureDesc.Flags, TexCreate_DepthStencilTargetable))
{
ResourceDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
}
else if (EnumHasAnyFlags(TextureDesc.Flags, TexCreate_ResolveTargetable))
{
if (TextureDesc.Format == PF_DepthStencil || TextureDesc.Format == PF_ShadowDepth || TextureDesc.Format == PF_D24)
{
ResourceDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
}
else
{
ResourceDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
}
}
}
// hlsl
// tex_0는 레지스터 t0 공간을 사용할 것이다
Texture2D tex_0 : register(t0)
// sam_0는 레지스터 s0 공간을 사용할 것이다
SamplerState sam_0 : register(s0)
// VS_IN 예시
struct VS_IN
{
float3 pos : POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD;
}
// D3D12VertexDeclaration.cpp
struct FD3D12VertexDeclarationKey
{
explicit FD3D12VertexDeclarationKey(const FVertexDeclarationElementList& InElements)
{
for (int32 ElementIndex = 0; ElementIndex < InElements.Num(); ElementIndex++)
{
const FVertexElement& Element = InElements[ElementIndex];
// hlsl의 POSITION, COLOR, TEXCOORD 같은 부분과 연동되는 구조체
D3D12_INPUT_ELEMENT_DESC D3DElement = { 0 };
// D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA가 의미하는 바는 인스턴스 마다 교유하게 하나씩 배정되는 데이터
D3DElement.InputSlotClass = Element.bUseInstanceIndex ? D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA : D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
}
}
}
- UV 좌표는 몇 부터 몇 까지인가? ( 41 : 30 )
01-8 Depth Stencil View
- Depth Stencil View를 렌더링 파이프라인 이미지에서 집어보면? ( 00 : 50 )
- 우리가 어떤 물체를 그려야 할지 안그려야 할지 어떻게 판단하는가? ( 09 : 18 )
- 만약 Depth 값이 음수 또는 1을 넘어가면 어떻게 되는가? 레스터 라이저 단계에서 제거된다 ( 13 : 22 )
- Depth Stencil View 단계에서 하고싶은 일을 서술하면? ( 14 : 08 )
- 만약에 우리가 Stencil 값을 사용하고 싶으면 어떤 일이 발생하는가? ( 18 : 45 )
- Stencil 기능에 대해 설명하면? ( 19 : 40 )
- Depth Stencil View Buffer는 몇개를 만들어 줘야 하는가? 1개 ( 25 : 28 )
- Depth와 Stencil값을 모두 사용하려면 어떤 형식으로 버퍼를 만들어 줘야 하는가? ( 26 : 00 )
- 윈도우 화면이 800 X 600 이라면 Depth Stencil Buffer의 크기는 어떻게 지정해야 하는가? ( 27 : 30 )
// D3D12Pipelinestate.cpp
static FD3D12LowLevelGraphicsPipelineStateDesc GetLowLevelGraphicsPipelineStateDesc(const FGraphicsPipelineStateInitializer& Initializer, const FD3D12RootSignature* RootSignature)
{
// DepthStencilState 세팅
FD3D12LowLevelGraphicsPipelineStateDesc Desc{};
Desc.Desc.DepthStencilState = Initializer.DepthStencilState ? CD3DX12_DEPTH_STENCIL_DESC1(FD3D12DynamicRHI::ResourceCast(Initializer.DepthStencilState)->Desc) : CD3DX12_DEPTH_STENCIL_DESC1(D3D12_DEFAULT);
}
// WindowsD3D12PipelineState.cpp
FD3D12_GRAPHICS_PIPELINE_STATE_STREAM FD3D12_GRAPHICS_PIPELINE_STATE_DESC::PipelineStateStream() const
{
// DepthStencilState 세팅
FD3D12_GRAPHICS_PIPELINE_STATE_STREAM Stream = {};
Stream.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC1(this->DepthStencilState);
}
FD3D12_MESH_PIPELINE_STATE_STREAM FD3D12_GRAPHICS_PIPELINE_STATE_DESC::MeshPipelineStateStream() const
{
// DepthStencilState 세팅
FD3D12_MESH_PIPELINE_STATE_STREAM Stream{};
Stream.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC1(this->DepthStencilState);
}
Chapter 2 Component
02-1 Input과 Timer
- 중요한 내용 없음
02-2 Material
- Material 이란 무엇인가? 내가 사용할 Shader와 해당 Shader에서 사용할 인자(Texture2D, SamplerState, POSITION, COLOR, TEXCOORD 등등) 까지 모두 포함해 하나의 클래스로 관리 하는것 ( 07 : 52 )
- Shader에서 사용할 인자는 어디로 전달될까? GPU Registers ( 25 : 40 )
- GPU Registers 에서 텍스쳐를 몇개 활용할 것이라는 내용은 어디에 등록되어 있는가? RootSignature ( 26 : 58 )
- Material 개념의 장점은? 오브젝트 들이 하나의 Material을 공유할 수 있다 ( 40 : 38 )
02-3 Component
- GameObject를 weak_ptr로 갖는 이유는? 컴포넌트와 상호 참조 하는것을 막기위해 ( 22 : 06 )
02-4 Scene
- Scene과 GameObject는 어떤 관계인가? has-a 관계
Chapter 3 Vector and Matrix
03-1 삼각함수
- 함수라는 정의를 충족시키기 위한 arccos, arcsin, arctan의 각도 범위는 어떻게 되는가? arccos만 0 ~ 𝝅 나머지는 -𝝅/2 ~ 𝝅/2 ( 34 : 40 )
- 코사인 덧셈 정리는 언제 활용 되는가? 회전 행렬을 만들때 ( 42 : 48 )
03-2 벡터
- 벡터끼리 곱셈과 나눗셈이 가능한가? 아니오 ( 13 : 47 )
- 벡터와 스칼라 간의 곱셈과 나눗셈이 가능한가? 예 ( 13 : 47 )
- 단위 벡터를 만드는 방법은? 원래 벡터의 각 성분에 크기값을 나눠준다 ( 24 : 30 )
- 외적은 교환 법칙이 성립 하는가? 아니오 ( 46 : 11 )
03-3 행렬
- 행렬을 곱하기 위한 조건은? 선행렬의 열과 후행렬의 행이 같아야 한다 ( 09 : 38 )
- 행렬을 곱한 결과는? 선행렬의 행의 개수와 후행렬의 열의 개수에 해당하는 새로운 행렬을 만든다 ( 10 : 08 )
03-4 Scale, Rotation, Translation 변환 행렬
- 이번 강좌의 주 내용은? Scale, Rotation, Translation 변환 행렬이 어떻게 만들어 지는지 설명 ( 04 : 19 )
- w를 추가해 사용하는 좌표계를 뭐라고 하는가? 동차 좌표계 ( 06 : 03 )
- 캐릭터 발 밑을 원점으로 잡는 이유는? 캐릭터 사이즈가 커져도 땅속에 들어가지 않게 하기위해 ( 18 : 53 )
03-5 좌표계 변환 행렬
- 좌표계 변환 행렬의 역할은? 새로운 좌표계에서 원래 좌표계의 특정 좌표가 어떻게 변할지 알 수 있다 ( 03 : 56 )
- 변화 하려는 대상이 위치 벡터이면 (x,y,z,?) 에서 물음표에 들어갈 값은? 1 ( 21 : 27 )
- 변화 하려는 대상이 방향 벡터이면 (x,y,z,?) 에서 물음표에 들어갈 값은? 0 ( 21 : 27 )
03-6 World, View 변환 행렬
- World 변환 행렬은 어떤 행렬을 응용했는가? 좌표계 변환 행렬 (참고)
- World 변환 행렬에는 어떤 정보가 있는가? 변화할 Position, Rotation, Scale ( 31 : 49 )
- View 변환 행렬이란? 카메라 월드 행렬의 역행렬 ( 48 : 53 )
- View 변환 행렬의 역할은? 카메라를 원점에 배치하고, 그에 따라 다른 사물들을 알맞게 재배치 해준다
03-7 Projection, Screen 변환 행렬
- near와 far의 역할은? 해당 영역 안의 물체만 카메라에 찍히게 해준다 ( 08 : 52 )
- 카메라의 시야를 넓혀주기 위해서는 어떤 것을 조절해야 하는가? FOV ( 10 : 33 )
- x, y 좌표를 z 값에 따라 정규화 시켜주는 방법은? x, y 좌표를 z 값으로 나누어 준다 ( 14 : 18 )
- Projection을 하기위해 z 값을 0~1로 만들어 줘야한다 이때 z 값을 보존하는 방법은? w 좌표에 z 값을 기록해둔다 ( 25 : 37 )
- w 좌표에 기록해둔 값을 활용해 x, y, z 좌표를 나누어 주는 단계는? 레스터 라이저 ( 28 : 29 )
- Normalized Coordination Space의 범위는? x, y 각각 -1 에서 1 ( 35 : 56 )
- Normalized Coordination Space 다음 Space는 무엇인가? ViewPort, 참고로 이 영역의 범위는 (0,0) 에서 (800,600) 이런식임
- ViewPort의 MinDepth와 MaxDepth를 어떻게 응용할 수 있는가? 뷰포트를 여러개 만들었을때 즉 렌더 타겟이 여러개 일때 각각의 MinDepth와 MaxDepth를 설정해 줄 수 있다 ( 45 : 03 )
Chapter 4 Camera and Lighting
04-1 Camera
- 행렬 정보에서 각행이 의미하는 바는? 1행은 Right 2행은 Up 3행은 Look 4행은 Translation ( 02 : 10 )
- 행렬 연산을 최적화 하는데 쓰이는 기법은? SIMD ( 03 : 29 )
04-2 Resources
- 중요한 내용 없음
04-3 Lighting 1
- Ambiet, Diffuse, Specular, Emissive의 각 특징과 연산과정을 설명하면? (참고)
- Specular 반사광 벡터를 구하는 방법을 설명하면? ( 27 : 32 )
04-4 Lighting 2
- Light는 한 프레임당 몇번 세팅 되는가? 한번, Light가 여러개라도 마찬가지 ( 11 : 44 )
- desc.table 방식의 단점은? 요소들을 개별적으로 업데이트 해줄수 없다 ( 12 : 56 )
- 위의 단점 때문에 Light와 같이 전역으로 사용되는 것들은 어떤 방식을 사용하는가? root CBV를 활용한다 ( 13 : 57 )
// MeshPaintVertexShader.usf
// 이 파일이 헤더 역할을 하는것 같다
#include "Common.ush"
// 이 변수는 global variable 이다
// 한 프레임당 한번만 세팅될 것이다
float4x4 c_Transform;
void Main( float4 InPosition : ATTRIBUTE0,
float2 InCloneTextureCoordinates : ATTRIBUTE2,
float3 InWorldSpaceVertexPosition : ATTRIBUTE3,
out float4 OutPosition : SV_POSITION,
out float2 OutCloneTextureCoordinates: TEXCOORD0,
out float3 OutWorldSpaceVertexPosition : TEXCOORD1 )
{
// Position (on texture map, derived from the UVs of the original mesh)
OutPosition = mul( InPosition, c_Transform );
// Pass clone texture coordinates through to the pixel shader
OutCloneTextureCoordinates = InCloneTextureCoordinates;
// Position of this vertex in world space (original mesh)
OutWorldSpaceVertexPosition = InWorldSpaceVertexPosition;
}
- Packing Rules에 대해서 설명하면? 4바이트 단위로 패킹되고, 16바이트 바운더리를 넘어갈 수 없다 하지만 USF 에서는 무언가 처리를 해줘서 이 규칙을 지킬 필요가 없는것 같다 ( 41 : 26 ) (참고)
04-5 Lighting 3
- Light 연산을 할 때 주의해야 할 내용은? 픽셀의 좌표계와 노말 좌표계를 일치 시켜야 한다, 로컬 월드 뷰 중에서 ( 01 : 02 )
- Specular의 특징은? 자신의 색상과 곱해지지 않는다 ( 05 : 58 )
- Specular 연산에 제곱이 들어가는 이유는? Specular가 적용되는 구간을 좁혀주기 위함이다 ( 19 : 26 )
- SpotLight에 대한 설명 ( 26 : 04 )
04-6 Normal Mapping
- 노말맵의 용도는? 각 픽셀에 해당하는 노말 벡터값을 저장한다 ( 10 : 22 )
- 노말맵 전용 좌표계를 뭐라고 부르는가? 탄젠트 스페이스, 탄젠트(X)축 바이노말(Y)축 노말(Z)축 으로 구성되며 픽셀 마다 각기 다른 탄젠트 스페이스를 갖는다 ( 17 : 49 )
- 노말맵 RGB 값은 각각 0~255 이다 이것을 어떤 범위로 정규화 해줘야 하는가? -1.0 ~ 1.0 으로 정규화 해줘야 한다, 방향 벡터이기 때문이다 ( 23 : 03 )
- 픽셀 컬러를 계산할 때 탄젠트 스페이스를 활용할 수 없다 어떻게 하면 좋을까? 뷰스페이스로 변환한다 ( 42 : 31 )
04-7 SKYBOX
- SKYBOX는 카메라 뷰행렬 정보만 있으면 된다 이때 SKYBOX에 적용 해야할 성분과 적용하지 말아야할 성분을 각각 얘기하면? Rotation 성분은 적용하고 Translation 성분은 적용하지 않는다 ( 09 : 17 )
- 컬링 모드는 어느 단계에서 설정해야 하는가? 레스터라이저 단계 ( 17 : 11 )
- 레스터라이저 단계에 대해 간단하게 설명하면? 정점 사이에 속해있는 픽셀 즉 출력해야할 픽셀들을 찾아서 픽셀 셰이더에게 넘겨준다, 부가적으로 색을 보간하는 역할도 담당한다 ( 18 : 14 )
// D3D12State.cpp
FRasterizerStateRHIRef FD3D12DynamicRHI::RHICreateRasterizerState(const FRasterizerStateInitializerRHI& Initializer)
{
FD3D12RasterizerState* RasterizerState = new FD3D12RasterizerState;
D3D12_RASTERIZER_DESC& RasterizerDesc = RasterizerState->Desc;
FMemory::Memzero(&RasterizerDesc, sizeof(D3D12_RASTERIZER_DESC));
// 컬링 모드
RasterizerDesc.CullMode = TranslateCullMode(Initializer.CullMode);
// 필 모드 ( 고체로 그릴것인지, 와이어 프레임으로 그릴것인지 )
RasterizerDesc.FillMode = TranslateFillMode(Initializer.FillMode);
return RasterizerState;
}
FDepthStencilStateRHIRef FD3D12DynamicRHI::RHICreateDepthStencilState(const FDepthStencilStateInitializerRHI& Initializer)
{
FD3D12DepthStencilState* DepthStencilState = new FD3D12DepthStencilState;
D3D12_DEPTH_STENCIL_DESC1 &DepthStencilDesc = DepthStencilState->Desc;
FMemory::Memzero(&DepthStencilDesc, sizeof(D3D12_DEPTH_STENCIL_DESC1));
// DepthEnable 활성화
DepthStencilDesc.DepthEnable = Initializer.DepthTest != CF_Always || Initializer.bEnableDepthWrite;
// 1 보다 작은 애들만 그릴것인지, 동등한 애도 그릴것인지 등등을 설정
DepthStencilDesc.DepthFunc = TranslateCompareFunction(Initializer.DepthTest);
}
FBlendStateRHIRef FD3D12DynamicRHI::RHICreateBlendState(const FBlendStateInitializerRHI& Initializer)
{
for (uint32 RenderTargetIndex = 0; RenderTargetIndex < MaxSimultaneousRenderTargets; ++RenderTargetIndex)
{
const FBlendStateInitializerRHI::FRenderTarget& RenderTargetInitializer = Initializer.RenderTargets[RenderTargetIndex];
// 픽셀 쉐이더 결과물과 렌더 타겟을 어떻게 Blend 할지 결정
D3D12_RENDER_TARGET_BLEND_DESC& RenderTarget = BlendDesc.RenderTarget[RenderTargetIndex];
RenderTarget.BlendEnable =
RenderTargetInitializer.ColorBlendOp != BO_Add || RenderTargetInitializer.ColorDestBlend != BF_Zero || RenderTargetInitializer.ColorSrcBlend != BF_One ||
RenderTargetInitializer.AlphaBlendOp != BO_Add || RenderTargetInitializer.AlphaDestBlend != BF_Zero || RenderTargetInitializer.AlphaSrcBlend != BF_One;
RenderTarget.BlendOp = TranslateBlendOp(RenderTargetInitializer.ColorBlendOp);
RenderTarget.SrcBlend = TranslateBlendFactor(RenderTargetInitializer.ColorSrcBlend);
RenderTarget.DestBlend = TranslateBlendFactor(RenderTargetInitializer.ColorDestBlend);
RenderTarget.BlendOpAlpha = TranslateBlendOp(RenderTargetInitializer.AlphaBlendOp);
RenderTarget.SrcBlendAlpha = TranslateBlendFactor(RenderTargetInitializer.AlphaSrcBlend);
RenderTarget.DestBlendAlpha = TranslateBlendFactor(RenderTargetInitializer.AlphaDestBlend);
RenderTarget.RenderTargetWriteMask =
((RenderTargetInitializer.ColorWriteMask & CW_RED) ? D3D12_COLOR_WRITE_ENABLE_RED : 0)
| ((RenderTargetInitializer.ColorWriteMask & CW_GREEN) ? D3D12_COLOR_WRITE_ENABLE_GREEN : 0)
| ((RenderTargetInitializer.ColorWriteMask & CW_BLUE) ? D3D12_COLOR_WRITE_ENABLE_BLUE : 0)
| ((RenderTargetInitializer.ColorWriteMask & CW_ALPHA) ? D3D12_COLOR_WRITE_ENABLE_ALPHA : 0);
}
return BlendState;
}
- 셰이더 문법에서 clipSpacePos.xyww가 의미하는 바는? 항상 깊이값을 1로 유지하겠다 ( 36 : 42 )
04-8 Frustum Culling
- 평면의 방정식에 대해 설명하면? ( 04 : 13 )
- ax + by + cz + d = 0 에서 a,b,c가 의미하는 바는? 해당 평면의 노말 벡터 (참고)
- 위의 내용을 수학적으로 증명하면? ( 06 : 49 )
- ax + by + cz + d = 0 에서 d가 의미하는 바는? 원점에서 평면까지의 거리 (참고)
- 위의 내용을 수학적으로 증명하면? ( 10 : 40 )
- 절두체 컬링의 원리에 대해 설명하면? ( 16 : 10 )
- 절두체를 구성하는 평면을 어떻게 만들 것인가? ( 20 : 20 )
- XMVector3TransformCoord() 함수의 역할은? ( 27 : 53 )
// ConvexVolume.cpp
void GetViewFrustumBoundsInternal(FConvexVolume& OutResult, const FMatrix& ViewProjectionMatrix, bool bUseNearPlane, bool bUseFarPlane, const FPlane* InFarPlaneOverride)
{
// Frustum Culling 세팅이 여기서 일어나는듯 하다
OutResult.Planes.Empty(6);
FPlane Temp;
}
- BoundScale의 역할은? Frustum Culling에 영향을 받을 부피 ( 38 : 29 )
Chapter 5 Quaternion
05-1 Quaternion 1
- 핸드폰을 Yaw 축으로 45° 돌린뒤 좌표계를 바꾸지 않고 Pitch 축으로 45° 돌린것과, 핸드폰을 Yaw 축으로 45° 돌린뒤 좌표계를 바꾸고 Pitch 축으로 45° 돌린것은 다르다
- 복소수의 기본형 써보면? ( 16 : 00 )
- 켤레 복소수란? ( 21 : 46 )
- 어떤 복소수 에다가 자기자신의 켤려 복소수를 곱하면 어떻게 되는가? ( 24 : 58 )
- 복소수 나눗셈에 대해 설명하면? ( 26 : 20 )
- 극좌표계란 무엇인가? ( 28 : 59 )
- 극좌표 두개를 곱하면 어떤 현상이 일어나는지 설명해보면? 힌트는 삼각함수의 덧셈정리 ( 30 : 41 )
- 복소수를 행렬로 표현해보면? ( 36 : 25 )
- Quaternion 이라는 개념이 발견된 계기를 말해보면? ( 40 : 10 )
05-2 Quaternion 2
- Pure Quaternion에 대해 설명하면? ( 27 : 36 )
- Quaternion의 장점은? 짐벌락 현상을 없앨수 있고, 행렬 연산에 비해 속도가 더 빠르다 ( 43 : 26 )
- q = xi + yj + zk + w 에서 xi + yj + zk가 의미하는 바와 w가 의미하는 바를 설명하면? 회전축(벡터부, 허수부)과 각도(스칼라부, 실수부) (참고)
Chapter 6 Rendering
06-1 Orthographic Projection
- 직교 투영의 특징은? 깊이값의 영향을 받지 않는다 ( 04 : 49 )
- 컬링 마스크 개념에 대해서 설명하면? 렌더할 오브젝트 유형을 설정할 수 있다 ( 22 : 26 )
06-2 Render Target
- SV_Target에 그려진 결과물을 출력하는 함수는? Present 함수 ( 02 : 34 )
- 포워드 렌더링과 디퍼드 렌더링의 가장 큰 차이는? 포워드 렌더링은 SV_Target이 하나이고 디퍼드 렌더링은 SV_Target이 여러개 이다 ( 13 : 04 )
- 렌더 타겟들 중에서 SWAP_CHAIN에 들어있는 정보들과 G_BUFFER에 들어있는 정보들을 각각 말해보면? SWAP_CHAIN 에는 BACK_BUFFER, FRONT_BUFFER가 들어있고 G_BUFFER 에는 POSITION, NORMAL, COLOR가 들어있다 ( 21 : 17 )
- 디퍼드 셰이더 대상에서 제외되는 오브젝트는? UI ( 54 : 10 )
06-3 Deferred Rendering
- 포워드 렌더링의 문제점은? 어떤 빛이 해당 오브젝트에 영향을 줄것인지 미리 판별할 수 없기 때문에 모든 빛과 해당 오브젝트를 비교해야 한다 ( 03 : 30 )
- 디퍼드 렌더링의 픽셀 셰이더 단계에서 일어나는 일은? 광원에 해당하는 영역과 포지션 맵을 비교해 광원 처리를 해줘야 하는 영역을 판별한다 ( 06 : 39 )
- 위의 작업에서 유의할 점은? 광원에 해당하는 영역과 포지션맵이 겹친다 하더라도 해당 광원과 포지션의 거리가 엄청 멀수도 있다 ( 28 : 11 )
- 디퍼드 렌더링 광원의 특징은? 광원이 물체를 찾아 그려준다 ( 56 : 17 )
Chapter 7 Particle
07-1 Compute Shader
- Compute Shader의 특징은? 렌더링 파이프라인에 독립적이다 ( 06 : 11 )
- Compute Shader가 활용되는 예는? 이펙트, 애니메이션 ( 06 : 58 )
- 정보 교환에 있어 CPU와 GPU의 특징은? CPU 또는 GPU가 각자의 RAM과 통신하는 속도는 빠르다 그러나 CPU와 GPU가 서로 통신하는 속도는 느리다 ( 07 : 28 )
- register(u0)의 용도는? UAV 즉 Compute Shader 전용 레지스터 ( 10 : 14 )
- RWTexture2D에 대해 설명하면? 읽기 쓰기 모두 가능 ( 10 : 33 )
- Compute Shader 에게 일을 시키기 위한 함수는? Dispatch ( 11 : 40 )
- CUDA 프로그램 실행과정을 설명하면? 그리드 블록 스레드 순으로 포함 관계를 갖고 일을 처리한다 ( 12 : 02 )
// PostProcessMobile.usf
#if CLEAR_UAV_UINT_COMPUTE_SHADER
uint NumEntries;
uint ClearValue;
RWBuffer<uint> UAV;
//---------------------------------------------------------------------------------
// learn.microsoft.com/ko-kr/windows/win32/direct3dhlsl/sm5-attributes-numthreads
// blog.naver.com/songg90/221145775846
// 쓰레드 그룹당 쓰레드 개수
// max : 1024 ( 현재 64 * 1 * 1 = 64 이니까 괜찮은 상태 )
// 하나의 쓰레드 그룹은 하나의 다중처리기에서 실행
//---------------------------------------------------------------------------------
[numthreads(64, 1, 1)]
void ClearUAVUIntCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
if (DispatchThreadId.x < NumEntries)
{
// 아래 연산은 DispatchThreadId.x 라는 고유 ID를 사용하고 있기 때문에
// 동기화가 필요없다
UAV[DispatchThreadId.x] = ClearValue;
}
}
#endif
// D3D12RHIPrivate.h
inline D3D12_COMMAND_LIST_TYPE GetD3DCommandListType(ED3D12QueueType QueueType)
{
switch (QueueType)
{
// Graphic 용도의 CommandQueue
case ED3D12QueueType::Direct: return D3D12_COMMAND_LIST_TYPE_DIRECT;
case ED3D12QueueType::Copy : return D3D12RHI_PLATFORM_COPY_COMMAND_LIST_TYPE;
// Compute 용도의 CommandQueue
case ED3D12QueueType::Async : return D3D12_COMMAND_LIST_TYPE_COMPUTE;
}
}
- Compute Shader 에서 추가된 View는? UAV ( 38 : 01 )
- UAV 용도의 Texture를 생성할 때 사용하는 enum 값은? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS
// D3D12Buffer.cpp
FD3D12Buffer* FD3D12DynamicRHI::CreateD3D12Buffer(class FRHICommandListBase* RHICmdList, uint32 Size, EBufferUsageFlags Usage, uint32 Stride, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo, ID3D12ResourceAllocator* ResourceAllocator)
{
D3D12_RESOURCE_DESC Desc;
// Desc 내용들( 예를들어 D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS )을 활용해 버퍼를 만드는듯 하다
FD3D12Buffer::GetResourceDescAndAlignment(Size, Stride, Usage, Desc, Alignment);
}
07-2 Particle System
- 파티클 시스템은 어떤 기술을 활용하는가? 인스턴싱 ( 04 : 06 )
- 인스턴싱 기술의 장점은? 1000개의 오브젝트를 그릴때 렌더링 파이프라인이 1000번 동작하는것은 같지만 오브젝트들을 렌더링 파이프 라인으로 넘기는 회수가 1회로 줄어든다 즉 드로우 콜을 줄인다 ( 05 : 19 )
- 인스턴싱을 사용할 때 각각의 물체를 어떻게 구분 하는가? SV_InstanceID를 활용한다 ( 06 : 20 )
- hlsl 에서 cbuffer의 단점은? 크기가 고정되며 GPU가 해당 cbuffer를 Read만 할 수 있다 ( 08 : 00 )
- hlsl 에서 StructuredBuffer, RWStructuredBuffer의 장점은? 크기가 유동적이며 배열이라고 보면 된다 ( 10 : 12 )
- 파티클을 계산하는 영역은 어디인가? GPU 이며 Compute Shader 단계에서 계산이 이루어진다 ( 11 : 31 )
- 특정 파티클이 그려지지 않게 하려면 무엇을 활용해야 하는가? Geometry Shader를 활용한다 ( 16 : 01 )
- Geometry Shader 에서 RestartStrip 함수의 역할은? 도형을 묶는다 ( 17 : 56 )
// hlsl 구조체 예시
// 패딩을 사용해 구조체 크기를 맞춰주는 경우가 있다는 것을 인지하자
// 여기서는 16 바이트 단위로 맞춰주고 있다
struct Particle
{
float3 worldPos;
float curTime;
float3 worldDir;
float lifeTime;
int alive;
float3 padding;
}
- hlsl 에서 GroupMemoryBarrierWithGroupSync 함수의 역할은? 스레드간의 동기화 ( 34 : 29 )
// LumenSurfaceCacheFeedback.usf
bool HashTableAdd(uint Key, inout uint Index)
{
LOOP
for (uint LinearProbingStep = 0; LinearProbingStep < MaxLinearProbingSteps; ++LinearProbingStep)
{
if (StoredKey != Key)
{
uint PrevKey;
// 원자성 보장
// RWHashTableKeys[Index]와 0이 같으면 RWHashTableKeys[Index]에 Key 값을 넣어준다
// 그리고 PrevKey에 RWHashTableKeys[Index]값을 넣어준다
InterlockedCompareExchange(RWHashTableKeys[Index], 0, Key, PrevKey);
}
}
}
- Compute Shader 단계에서 계산이 끝난 파티클은 어디로 보내줘야 하는가? 렌더링 전용 StructuredBuffer 으로 보내줘야 한다 이후 원래 렌더링 파이프 라인을 통해 해당 파티클이 출력될 것이다 ( 48 : 46 )
- Geometry Shader 에서 하는일은? 정점을 추가 해주거나 그리지 않게 해준다 ( 49 : 28 )
- 파티클은 디퍼드 렌더링을 적용해야 할까 포워드 렌더링을 적용해야 할까? 포워드 렌더링 ( 01 : 14 : 50 )
07-2 Instancing
- 인스턴싱을 활용하기 위한 조건은? 버텍스 버퍼, 인덱스 버퍼, 머티리얼, 쉐이더가 같아야 한다 ( 03 : 05 )
Chapter 8 Shadow Mapping
08-1 Shadow Mapping
- Shadow Mapping을 하기위한 첫번째 단계는? 빛의 위치에 가상의 카메라를 배치하고 해당 카메라 기준에서 대상의 깊이값을 텍스쳐로 저장한다 ( 04 : 33 )
- 좌표계 변환 순서를 정확하게 설명하면? WorldMat - ViewMat - ProjMat - ClipPos - W 나누기 - ProjPos ( 07 : 41 )
- 그림자를 그려주는 Shader는 무엇인가? Lighting Shader ( 08 : 47 )
- 그림자를 그려줘야 하는 픽셀인지 어떻게 알 수 있는가? 실제 카메라 이미지의 해당 픽셀 ViewPos에 ViewInverseMatrix를 곱해 WorldPos로 바꿔주고 가상의 카메라 ViewProjMat을 곱해 ClipPos로 바꿔준뒤 W 나누기를 통해 ProjPos를 구한다음 UV 좌표계로 환산한 가상의 카메라 이미지 위치가 그림자가 그려져야 하는 위치와 일치하는지 비교해 그림자를 그린다 ( 08 : 47 )
- 위의 방식에서 유의해야할 점은? 그림자를 그려야 하는지 판별해야 하는 픽셀의 깊이 값과 렌더 타겟에 등록되어 있는 깊이 값을 비교해 렌더 타겟의 깊이값 보다 판별해야 하는 픽셀의 깊이 값이 더 커야만 그림자를 그려줘야 한다 ( 13 : 08 )
- 그림자 계단 현상을 없애려면 어떻게 해야 하는가? 그림자와 관련된 렌더 타겟 텍스쳐의 크기를 키운다 ( 15 : 17 )
- Static 물체는 동적으로 그림자를 그려줄 필요가 있는가? 아니오 ( 30 : 47 )
Chapter 9 Terrain
09-1 Tessellation
- 오늘 배울 내용은 렌더링 파이프 라인 이미지에서 어느 부분에 해당하는가? Hull Shader, Tessellator, Domain Shader ( 00 : 22 )
- Tessellation 단계와 Geometry Shader의 차이는? Tessellation 단계가 더 큰 규모의 정점관리에 사용된다 ( 01 : 11 )
- Tessellation 단계에 활용되는 예는? 동적 LOD ( 01 : 42 )
- 동적 LOD가 사용되는 예는? terrain ( 05 : 06 )
- Hull Shader, Tessellator, Domain Shader 단계중에 우리가 커스터 마이징 할 수 있는 단계는? Hull Shader, Domain Shader ( 05 : 57 )
// D3Dcommon.h
enum D3D_PRIMITIVE_TOPOLOGY
{
// CONTROL_POINT는 제어점을 의미하고 PATCH는 CONTROL_POINT의 그룹 이라고 보면된다
D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST = 35,
} D3D_PRIMITIVE_TOPOLOGY;
- Tessellation을 사용하면 Vertex Shader 단계가 어떻게 달라 지는가? 좌표 변환을 하지 않고 로컬 좌표를 Hull Shader 단계로 넘긴다 ( 09 : 12 )
// NaniteTessellation.ush
// 각 변을 몇등분 할 것인지 리턴할 것이다
float3 GetTessFactors( FNaniteView NaniteView, float3 PointView[3] )
{
// Back face cull
//float3x3 M = { PointView[0], PointView[1], PointView[2] };
//bool bVisible = determinant( M ) > 0;
// Verts are in view space
float3 TessFactors = float3(
length( PointView[0] - PointView[1] ) / ( PointView[0].z + PointView[1].z ),
length( PointView[1] - PointView[2] ) / ( PointView[1].z + PointView[2].z ),
length( PointView[2] - PointView[0] ) / ( PointView[2].z + PointView[0].z ) );
bool bOrtho = NaniteView.ViewToClip[3][3] >= 1;
if( bOrtho )
{
TessFactors = float3(
length( PointView[0] - PointView[1] ),
length( PointView[1] - PointView[2] ),
length( PointView[2] - PointView[0] ) );
TessFactors *= 2;
}
//TessFactors *= 2 * NaniteView.LODScale * View.GeneralPurposeTweak;
TessFactors *= NaniteView.ViewToClip[1][1] * NaniteView.ViewSizeAndInvSize.y / View.GeneralPurposeTweak;
return TessFactors;
}
09-2 Terrain
- Terrain을 구성하는 삼각형의 특징은? 높이가 다르다 ( 03 : 09 )
- Terrain을 구성하는 삼각형의 높이값만 저장해도 되는 이유는? 가로 세로는 모두 일정하다 그래서 Height Map 이라고 부른다 ( 05 : 12 )
// DeferredLightingCommon.ush
float ShadowRayCast(
float3 RayOriginTranslatedWorld, float3 RayDirection, float RayLength,
int NumSteps, float StepOffset, out bool bOutHitCastContactShadow )
{
for( int i = 0; i < NumSteps; i++ )
{
// 픽셀 셰이더 에서만 Sample 함수를 사용하고 그 외에는 SampleLevel 함수를 사용해야 한다
float SampleDepth = SceneTexturesStruct.SceneDepthTexture.SampleLevel( SceneTexturesStruct_SceneDepthTextureSampler, SampleUVz.xy, 0 ).r;
}
}
09-3 Picking
- 보통 ray와 물체의 만남의 장소로 쓰이는 space는? world space ( 32 : 45 )
Chapter 10 Mesh & Animation
10-1 Mesh
- 중요한 내용 없음
10-2 Animation
- 애니메이션은 어떤 Shader 에서 처리되는가? Compute Shader ( 56 : 26 )
댓글남기기