Unreal Engine 5.2

Date:     Updated:

카테고리:

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 )

dir

  • 추가 종속성 대신 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

pipeline

cpugpu

  • 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

desc

  • 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 )


맨 위로 이동하기

댓글남기기