스텐실 버퍼는 후면 버퍼, 깊이 버퍼와 해상도가 같다.

스텐실 버퍼는 특정 픽셀 단편들이 후면 버퍼에 기록되지 못하게 하는 역할을 한다.

PSO 에 D3D12_DEPTH_STENCIL_DESC 구조체를 채워 설정할 수 있다.

 

11.1 깊이 스텐실 버퍼의 자료 형식과 버퍼 지우기

사용할 수 있는 자료 형식

1. DXGI_FORMAT_D32_FLOAT_S8X24_UINT : 텍셀 하나에 64비트 사용, 32비트는 부동소수점 깊이값, 8비트는 정수 스텐실 값 나머지는 빈칸

2. DXGI_FORMAT_D24_UNORM_S8_UINT : 텍셀 하나에 32비트 사용, 24비트는 부호 없는 깊이 값, 8비트는 정수 스텐실 값

 

스텐실 값은 [0, 255]로 깊이 값은 [0, 1]로 사상된다는 공통점이 있다.

 

매 프레임마다 draw 하기전에 깊이 스텐실 버퍼를 ClearDepthStencilView 함수를 사용하여 지워줄 수 있다.

 

11.2 스텐실 판정

스텐실 판정은 픽셀이 래스터화되는 과정에서 (출력 병합기 단계)에서 일어난다.

 

11.3 깊이 스텐실 상태의 서술

D3D12_DEPTH_STENCIL_DESC 구조체

ㄴ 깊이 버퍼링 활성화 여부 (비활성화 하면 깊이 판정이 일어나지 않아 깊이에 관계없이 렌더링 순서에 따라 그려짐)

ㄴ 깊이 버퍼 쓰기(기록) 활성화 여부 (비활성화 하면 깊이 판정은 일어나지만 깊이 버퍼에 쓰는 것은 안됨)

ㄴ 깊이 판정에 쓰는 비교 함수

 

ㄴ 스텐실 판정 활성화 여부

ㄴ 스텐실 readmask

ㄴ 스텐실 writemask

ㄴ 후면 삼각형에 대해서 어떻게 처리 할것인지

ㄴ 전면 삼각형에 대해서 어떻게 처리 할것인지

 

11.4 평면 거울 구현 

1. 일반 물체를 그린다.

 

2. 거울을 구성하는 픽셀들의 후면 버퍼에는 아무것도 기록하지 않고 (혼합 상태 설정에서 렌더 대상 쓰기 마스크를 0으로)

스텐실 비교함수를 ALWAYS로 지정 (스텐실 판정 항상 통과)

스텐실 판정과 깊이 판정 모두 통과한 경우 픽셀에 1을 쓰기 위해 스텐실 기준값(StencilRef)를 1로 설정하고, 스텐실 기준값을 버퍼에 덮어쓰는 연산인 REPLACE을 지정해준다.

 

3. 거울에 의해 반사된 물체를 그린다.

스텐실 판정만 켜놓고 일반 물체를 그리는 설정과 동일하게 한다.

스텐실 버퍼에 있는 값이 1인 경우에만 판정을 통과하게 하게 만들기 위해

스텐실 기준값(StencilRef)을 1로 설정하고, 스텐실 비교함수를 EQUAL로 지정해준다.

판정 후 스텐실 버퍼를 변경할 필요가 없으므로 모든 경우(stencil만 통과, depth만 통과, 모두 통과) KEEP 연산을 지정해준다.

 

 

거울 안의 반사된 물체를 그릴 때 사용되는 조명은 따로 상수버퍼를 하나 더 추가해서 저장해야 한다.

래스터라이저 상태 객체에 시계방향으로 감긴 삼각형을 후면 삼각형으로 간주하라고 해야한다. (메시를 반사해도 법선은 뒤집히지 않기 때문에)

 

11.5 평면 그림자의 구현

평행광 그림자

n과 L의 내적이 음수가 되어 (투영된 점의 w 성분이 음수가 되어) 절단되는 상황(시야 공간 바깥에 있는 판정)을 막기 위해서 L은 빛이 나아가는 방향의 반대 방향을 사용한다. 

 

점광 그림자

평행광 그림자 식의 L 자리에 정점으로부터 광원으로 향하는 벡터를 넣으면 된다.

 

범용 그림자 행렬

ㄴ 점광과 평행광 모두에 적용할 수 있는 범용 그림자 행렬

ㄴ L 벡터의 w값을 1로 지정하면 점광을 사용하며  xyz가 점광의 위치를 뜻한다.

ㄴ L 벡터의 w값을 0으로 지정하면 평행광을 사용하며 xyz가 빛이 나아가는 방향의 반대 방향 벡터를 뜻한다.

 

그림자 이중 혼합 문제를 스텐실 버퍼를 이용해 해결하는 방법

1. 0으로 초기화 되어 있는 그림자 메시의 스텐실 버퍼 값이 0인 픽셀만 판정에 성공하도록 해야한다.

2. 스텐실 기준값(StencilRef)을 0으로 설정하고 스텐실 비교 함수를 EQUAL로 한다.

깊이판정과 스텐실판정 모두 성공한 경우 INCR연산을 하게 만들어 해당 픽셀의 스텐실 버퍼 값이 1이 되도록 한다.

 

연습문제

3. 4. 스텐실 판정 안하면 된다.

5.

벽의 깊이 판정이 일어나지 않아 나중에 그려진 반사된 해골픽셀이 벽을 덮어쓴다.
깊이 판정을 정상적으로 한 경우 (반사된 해골의 스텐실 판정은 끈 상태)

7.

bmp 파일을 텍스처로 로드하기 위해서 외부 라이브러리를 사용한다.

https://github.com/Microsoft/DirectXTK12/wiki/WICTextureLoader

 

GitHub - microsoft/DirectXTK12: The DirectX Tool Kit (aka DirectXTK12) is a collection of helper classes for writing DirectX 12

The DirectX Tool Kit (aka DirectXTK12) is a collection of helper classes for writing DirectX 12 code in C++ - GitHub - microsoft/DirectXTK12: The DirectX Tool Kit (aka DirectXTK12) is a collection ...

github.com

원래 Visual studio 2017를 사용하고 있었는데 이 라이브러리를 사용하려면 2019 버전에서 작업해야 한다는 것을 늦게 알아서 몇 시간을 허비했다..

 

Bmp 파일들로 부터 텍스처 자원을 생성하고 srv서술자를 만드는 함수인 LoadBmpTextures() 를 구현했다.

void BlendApp::LoadBmpTextures() {

	HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);

	CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

	hDescriptor.Offset(3, mCbvSrvDescriptorSize);

	for (int i = 1; i <= 60; i++) {
		auto boltAnimTex = std::make_unique<Texture>();

		std::unique_ptr<uint8_t[]> decodedData;
		D3D12_SUBRESOURCE_DATA subresource;

		wchar_t str[30] = L"../../BoltAnim/Bolt";
		wchar_t num[4];
		wchar_t* extention = L".bmp";
		swprintf(num, 4, L"%03d", i);
		wcscat_s(str, num);
		wcscat_s(str, extention);


		std::wstring ws(str);
		std::string name(ws.begin(), ws.end());

		//str texName 
		std::wstring numws(num);
		std::string numstr(numws.begin(), numws.end());

		boltAnimTex->Name = std::string("Bolt" + numstr);

		ThrowIfFailed(LoadWICTextureFromFile(md3dDevice.Get(), str, boltAnimTex->Resource.GetAddressOf(), decodedData, subresource));

		const UINT64 uploadBufferSize = GetRequiredIntermediateSize(boltAnimTex->Resource.Get(), 0, 1);

		CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);

		auto desc = CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);

		md3dDevice->CreateCommittedResource(
			&heapProps,
			D3D12_HEAP_FLAG_NONE,
			&desc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(boltAnimTex->UploadHeap.GetAddressOf()));

		UpdateSubresources(mCommandList.Get(), boltAnimTex->Resource.Get(), boltAnimTex->UploadHeap.Get(),
			0, 0, 1, &subresource);

		auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(boltAnimTex->Resource.Get(),
			D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
		mCommandList->ResourceBarrier(1, &barrier);

		
		D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
		srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
		srvDesc.Format = boltAnimTex->Resource.Get()->GetDesc().Format;
		srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
		srvDesc.Texture2D.MostDetailedMip = 0;
		srvDesc.Texture2D.MipLevels = -1;
		md3dDevice->CreateShaderResourceView(boltAnimTex->Resource.Get(), &srvDesc, hDescriptor);

		hDescriptor.Offset(1, mCbvSrvDescriptorSize);

		mTextures[boltAnimTex->Name] = std::move(boltAnimTex);
	}

}

 

기존에 사용되던 srvheap의 0번 1번 2번 인덱스에는 풀, 물, 철망을 위한 Material 정보가 들어있는데,

60프레임 짜리 애니메이션을 저장해야되므로 srvheap의 크기를 63으로 늘리고 3번 ~ 62번 인덱스에 서술자를 저장한다.

 

가산 혼합을 위한 pso를 따로 만들어주었다.

텍스처를 1 / 60초마다 변경하는 것은 gt.TotalTime()의 소수점 부분을 1 / 60 으로 나눠서 3번 ~ 62번중에 몇번 서술자를 쓸 것인지 지정하도록 만들었다.

결과물

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/11

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

 

'읽은 책 > DirectX 12를 이용한 3D 게임 프로그래밍 입문' 카테고리의 다른 글

13. 계산 셰이더  (0) 2022.06.28
12. 기하 셰이더  (0) 2022.06.24
10. 혼합  (0) 2022.06.22
9. 텍스처 적용  (0) 2022.06.21
8. 조명  (0) 2022.06.20

+ Recent posts