7.1 프레임 자원

매 프레임 CPU가 수정해야 하는 자원들을 순환 배열로 관리하여 CPU와 GPU가 놀지 않도록 하기

 

프레임 자원마다 명령 할당자가 필요하다. (GPU가 명령을 다 처리하지 않았어도 다음 프레임 자원을 수정해야 하기 때문에)

상수 버퍼를 매 프레임 수정한다면 프레임 자원마다 상수 버퍼가 필요하다.

void ShapesApp::Update(const GameTimer& gt)
{
    OnKeyboardInput(gt);
	UpdateCamera(gt);

    // Cycle through the circular frame resource array.
    // Update 함수 호출 될때마다 다음 인덱스의 프레임 자원이 현재 프레임 자원이 됨
    mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources; 
    mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get();

    // Has the GPU finished processing the commands of the current frame resource?
    // If not, wait until the GPU has completed commands up to this fence point.
    // CPU 처리속도가 GPU 처리속도보다 빨라서 한 바퀴를 돌았는데도 아직도 안 끝난 경우에는 기다림
    if(mCurrFrameResource->Fence != 0 && mFence->GetCompletedValue() < mCurrFrameResource->Fence)
    { 
        HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
        ThrowIfFailed(mFence->SetEventOnCompletion(mCurrFrameResource->Fence, eventHandle));
        WaitForSingleObject(eventHandle, INFINITE);
        CloseHandle(eventHandle);
    }

	UpdateObjectCBs(gt);
	UpdateMainPassCB(gt);
}

void ShapesApp::Draw(const GameTimer& gt){
// 현재 프레임의 명령 목록들을 구축하고 제출
	mcurrFrameResource->Fence = ++mCurrentFence; // CPU는 현재 프레임 명령 제출을 끝냈음
    mCommandQueue->Signal(mFence.Get(), mCurrentFence); // GPU기준 마지막으로 끝낸 프레임 작업 번호가 무엇인지 설정

7.2 렌더 항목 (RenderItem 구조체)

하나의 물체를 그리는데 필요한 설정들을  모아놓은 구조체

 

1. 물체의 세계 행렬

2. numframesDirty // 물체의 자료가 변해서(위치가 이동했거나 한 경우) 상수 버퍼를 갱신해야 하는 경우 numframesdirty 값을 프레임 자원의 개수로 설정하면 된다.

update문에서 모든 renderitem들의 numframesdirty 값을 확인하는데 값이 0보다 크다면 해당 renderitem이 사용하는 상수 버퍼의 값을 갱신하고 numframesdirty 값을 -1 시킨다. CPU가 프레임 자원을 한바퀴 돌면 모든 프레임 자원의 상수 버퍼 값은 갱신된다.

3. 기하구조 (Mesh Geometry) (GPU와 CPU 메모리에 기하구조가 사용하는 정점 버퍼와 색인 버퍼 자원을 만들게 되는데

이 자원들을 가리키는 Com객체를 멤버로 가지고 있는 구조체)

4. 기본도형 위상구조

5. GPU상 해당 RenderItem이 사용하는 상수 버퍼의 index

 

7.3 물체별 상수 버퍼와 패스별 상수 버퍼

물체별 상수 버퍼는 물체의 world행렬만 가지고 있으면 된다.

패스별 상수 버퍼는 시점 위치, 시야 행렬, 투영 행렬, 화면 크기, 게임 시간 측정치 등 한 프레임에 한번만 수정되는 상수 자료를 가진다.

 

7.4 도형 기하구조

원기둥 메시

구 메시

측지구 메시

7.5 도형 예제

7.7 지형과 파도 예제

 

 

7.6 루트 서명 추가 설명

이전 장에서 다 설명함

서술자 테이블

루트 상수

루트 서술자 

ㄴ 서술자 테이블에서는 자원을 묶을 때 서술자 힙의 핸들을 묶어 줬지만 루트 서술자는 그냥 자원의 가상 주소를 묶으면 된다. (업로드 힙에 올린 상수 버퍼 자원의 서술자 힙을 만들지 않고도 가능한 방법이다.)

 

7.7.5 동적 정점 버퍼

파도 구현할때, 복잡한 물리계산과 충돌 검출을 수행해서 입자들의 새 위치를 구할때

 

상수 버퍼들의 배열을 업로드 힙에 올리는 방법과 유사하다. 이번에는 정점들의 배열을 업로드 버퍼에 담는다.

 

동적 버퍼를 사용하면 CPU에서 새 자료를 GPU 메모리에 전송하는 데 추가 부담이 발생하므로 정적 버퍼로 충분 할때에는 정적 버퍼를 사용하자

 

1. 간단한 애니메이션은 정점 셰이더에서 수행할 수 있다.

2. 텍스처로의 렌더링 기능이나 계산 셰이더 또는 정점 텍스처 조회 기능을 이용할 수 있다.

3. 기하 셰이더를 이용하면 GPU에서 기본도형의 생성 및 파괴 작업을 GPU에서 할 수 있다.

4. 테셀레이션 단계를 이용하면 GPU에서 세밀한 기하구조를 추가할 수 있다.

 

응용 프로그램에서 update뒤에 draw가 실행되므로

update에서 매 프레임 정점 버퍼를 업로드 힙에 갱신하고 draw하면 된다.

 

 

연습문제

1.

세부수준 0 1
세부수준 2 3

2.

셰이더 파일에서 상수 0번 레지스터가 받는 형식
정점 셰이더 부분
DrawRenderItems함수에서 각각의 renderitem의 world 행렬 갱신하는 부분
루트 서명 작성하는 부분

 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/7/Chapter%207%20Drawing%20in%20Direct3D%20Part%20II/Shapes

 

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 게임 프로그래밍 입문' 카테고리의 다른 글

9. 텍스처 적용  (0) 2022.06.21
8. 조명  (0) 2022.06.20
6. Direct3D의 그리기 연산  (0) 2022.06.16
5. 렌더링 파이프라인  (0) 2022.06.14
4. Direct3D의 초기화(2)  (0) 2022.06.13

 

6.1 정점과 입력 배치

정점 구조체를 정의하고 D3D12_INPUT_DESC 배열또는 벡터를 정의한다.

정점 구조체의 멤버와 입력배치 서술배열의 각 원소의 오프셋이 일치해야 하고

입력 배치 서술 배열의 입력 서명과 정점 셰이더의 입력 서명이 일치해야한다.

 

6.2 정점 버퍼

자원의 종류는 텍스처와 버퍼 두가지로 나누어져 있는데, 버퍼는 밉맵과 달리 다차원이 아니며 밉맵이나 필터, 다중표본화 기능이 없어 단순한 자원이다. 정점 자료 원소의 배열을 CPU에 제공할 때에는 항상 버퍼를 사용한다.

 

범용 GPU 자원으로서의 버퍼에서 너비(width)는 가로 길이가 아니라 버퍼의 바이트 개수를 뜻한다.

 

정적 기하구조(프레임마다 변하지 않는 기하구조)를 그릴 때에는 최적의 성능을 위해 정점 버퍼들을 기본 힙(D3D12_HEAP_TYPE_DEFAULT)에 넣는다. 기본 힙은 GPU만 읽기 때문에 CPU가 기본 힙을 수정하지 못한다.

 

d3dUtil.cpp에 기본 힙의 자료를 초기화 하기 위한 CreateDefaultBuffer함수가 정의되어 있다.

1. 임시 업로드 힙에 버퍼를 만들고

2. D3D12_SUBRESOURCE_DATA 라는 임시 자료를 위한 구조체를 서술

3. 자원 장벽 명령으로 기본힙의 버퍼 상태를 COMMON에서 COPY_DEST로 변경

4. UpdateSubresources 함수는 CPU 메모리를 임시 업로드 힙에 복사하고 임시 업로드 힙의 자료를 

 

기본 힙의 버퍼와 업로드 힙의 버퍼는 이 함수 밖에서도 Comptr로 가리키고 있어야 한다. 실제로 명령 목록이 명령 대기열에 들어가 실행된 상태가 아니기 때문이다. 함수 내부에서 만들어진 기본 힙의 버퍼를 가리키는 Comptr를 이 함수가 반환하기 때문에 소유권을 넘길 수 있다. 아무튼 외부에 존재하는 두 버퍼에 대한 Comptr가 유지되어야 한다.

 

정점 자원을 서술하는 서술자는 서술자 힙이 필요없다.

D3D12_VERTEX_BUFFER_VIEW 구조체를 서술하고(버퍼 자원의 가상주소, 버퍼의 크기(vertex 구조체 크기 * 정점 개수), 정점 원소의 크기(vertex 구조체 크기)) 

 

cmdList->IASetVertexBuffers 함수의 매개변수로 위의 구조체로 만든 배열의 주소를 입력하면 각 정점 버퍼들이 슬롯에 차례대로 묶인다. 

cmdList->IASetPrimitiveTopology 함수로 기본도형 위상구조 상태를 set 시켜놔야 한다.

 

cmdList->DrawInstanced가 호출되어야지 그때 그려진다.

ㄴ 인덱스 없이 정점 버퍼에 있는 정점 순서대로 기본도형을 그린다.

6.3 색인 버퍼

만드는 방식은 정점 버퍼와 같은데 이름만 다르다

D3D12_INDEX_BUFFER_VIEW 구조체를 서술해야하고, 

cmdList->IASetIndexBuffer 함수를 사용한다.

 

색인들을 이용해 기본도형을 그리려면, DrawIndexedInstanced 함수를 사용해야 한다.

인덱스로 접근하기 때문에 다음의 3가지 정보를 사용한다.

1. 첫 정점의 위치

2. 첫 인덱스의 위치

3. 기하구조를 그리는데 사용하는 인덱스의 크기

여러 종류의 기하구조를 하나의 정점 버퍼와 색인 버퍼에 저장하더라도 기하구조를 그리는 첫 번째 정점의 위치와 인덱스 위치를 기록해놓으면 문제가 없다.

 

6.4 정점 셰이더

기본적인 정점 셰이더의 역할은 로컬 position을 동차좌표 position으로 변환하여 픽셀 셰이더에 전달하는 것이다.

셰이더 파일은 포인터나 참조가 없어서 out float oPosH와 같은 출력 매개변수를 사용한다.  

VertexOut VS(VertexIn vin)
{
VertexOut vout;

// Transform to homogeneous clip space.
vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj); 
// float3 형식의 로컬 position의 w값을 1로 하여 float4값을 생성하고 세계시야투영행렬을 곱한다.

// Just pass vertex color into the pixel shader.
    vout.Color = vin.Color;
    
    return vout;
}

 

 

정점 셰이더가 필요로 하는 입력서명을 입력 배치서술이 모두 제공하지 않으면 오류가 발생한다.

동차 절단 공간에서의 정점의 위치를 값는 출력 매개변수는 SV_POSITION 의미소를 부여해야 한다.

6.5 픽셀 셰이더

정점 셰이더에서 출력한 정점 특성들은 래스터화 단계에서 삼각형의 픽셀들을 따라 보간되어 보간된 결과가 픽셀셰이더의 입력으로 들어온다.

float4 PS(VertexOut pin) : SV_Target
{
    return pin.Color;
}

// SV_TARGET : 이 함수의 반환값의 형식이 렌더 대상의 형식과 일치해야 함을 뜻한다.(4차원의 색상 값)

6.6 상수 버퍼

셰이더 프로그램에서 참조하는 자료를 담는 GPU 자원

정점 버퍼와 색인 버퍼와 달리 상수 버퍼는 프레임당 한 번 갱신하는 것이 일반적임(기본 힙이 아니라 업로드 힙에 만들어야 CPU가 버퍼의 내용을 갱신할 수 있음)

하드웨어가 256 * n 바이트 오프셋에서 시작하는 256 * n 바이트 길이의 상수 자료만 볼 수 있기 때문에 상수 버퍼의 원소는 256바이트의 배수여야 한다.

cbuffer cbPerObject : register(b0)
{
	float4x4 gWorldViewProj; // 256바이트 경계에 맞게 바이트들이 암묵적으로 채워진다.
};

6.6.2 상수버퍼의 갱신

자원을 바이트 배열에 Map시킨 뒤

ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));

바이트 배열의 특정 위치(256 * 오프셋) 상수 버퍼 구조체를 memcopy하여 복사하면 된다.

 

6.6.4 상수 버퍼 서술자

상수 버퍼 서술자는 DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV 형식의 서술자 힙에 담긴다.

DESCRIPTOR_HEAP_TYPE_DESC를 서술하여 서술자 힙을 만들 때

Type을 DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV로 해줘야 하고

Flags를 D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE로 해줘야 한다.(셰이더 프로그램에서 상수 버퍼에 접근해야 하기 때문에)

 

사실 서술자를 따로 만들지 않아도 된다. 자원의 GPU 가상주소를 루트 매개변수로 

 

6.6.5 루트 서명과 서술자 테이블

루트 매개변수는 3가지로 정의될 수 있다.

1. 루트 상수

2. 루트 서술자

3. 서술자 테이블

루트 서명은 루트 매개변수의 배열이다.

루트 서명에는 최대 64개의 DWORD를 넣을 수 있는데

서술자 테이블: DWORD 하나

루트 서술자 : DWORD 두 개

루트 상수 : 32비트 상수당 DWORD 하나

CD3DX12_DESCRIPTOR_RANGE texTable;
	texTable.Init(
        D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 
        2,  // number of descriptors
        2); // register t2,t3

    // Root parameter can be a table, root descriptor or root constants.
    CD3DX12_ROOT_PARAMETER slotRootParameter[4];

	// Perfomance TIP: Order from most frequent to least frequent.
    slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
    slotRootParameter[1].InitAsConstantBufferView(0); // register b0
    slotRootParameter[2].InitAsConstantBufferView(1); // register b1
    slotRootParameter[3].InitAsConstantBufferView(2); // register b2

위의 코드는 4개의 루트 파라미터중 1개는 서술자 테이블, 3개는 루트 서술자를 사용한 방법이다.

3가지 종류(srv, uav, cbv)의 서술자를 하나의 테이블에 저장하여 사용하고 싶다면 CD3DX12_DESCRIPTOR_RANGE table[3]의 형태로 선언해야 한다. srv는 t레지스터에 cbv는 b레지스터에 묶인다.

테이블은 서술자는 루트 파라미터 한 개를 사용해서 여러개의 서술자를 여러개의 레지스터와 묶어준다. 

 

루트 서명은 어떤 레지스터에 어떤 루트 매개변수가 연관되는지(루트 서술자인지, 서술자 테이블인지, 루트 상수인지)를 정의 하는 역할이고 실제로 자원을 묶으려면 cmdlist->SetGraphicsRoot****을 호출해서 묶는다.

 

 

6.7 셰이더의 컴파일

셰이더 파일(.hlsl)은 이식성 있는 바이트 코드로 컴파일된 후에 그래픽 드라이버는 그 바이트 코드를 시스템의 GPU에 맞게 최적의 네이티브 명령들로 컴파일한다.

 

오프라인 컴파일 : 셰이더를 실행 시점에서 컴파일하지 않고 오프라인에서 개별적인 단계로 컴파일

ㄴ 복잡한 셰이더의 경우 컴파일에 시간이 오래 걸리는데, 오프라인에서 컴파일하면 게임의 로딩 시간이 빨라진다.

컴파일된 셰이더 파일(.cso)

진입점이 VS인 파일과 PS인 파일로 나누어 컴파일 될 수 있다. (.cso파일이 두 개)

 

오프라인 컴파일 방법이던 아니던 컴파일된 셰이더 목적 바이트코드는 ID3DBlob라는 범용 메모리 버퍼에 저장되고 파이프라인 상태객체를 서술할때 사용된다.

 

6.8 래스터화기 상태

D3D12_RASTERIZER_DESC 구조체를 서술하여 파이프라인 상태 객체 만들때 설정한다.

6.9 파이프라인 상태 객체

루트서명

셰이더 바이트 코드(정점, 영역, 덮개, 기하, 픽셀)

혼합 방식

다중표본화 samplemask (최대 32개의 표본의 비활성화 여부)

래스터화기 상태

깊이 스텐실 판정 방법 지정

입력 배치 서술 구조체

기본도형 위상구조 종류

동시에 사용하는 렌더 대상 개수

렌더 대상 format

깊이 스텐실 버퍼 format

다중표본화 표본개수와 품질 수준

 

연습문제

1.

mInputLayout =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_PER_VERTEX_DATA, 0 },
        { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_PER_VERTEX_DATA, 0 }
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D12_INPUT_PER_VERTEX_DATA, 0 }
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 36, D3D12_INPUT_PER_VERTEX_DATA, 0 }
        { "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 44, D3D12_INPUT_PER_VERTEX_DATA, 0 }
        { "COLOR", 0, DXGI_FORMAT_B8G8R8A8_UNORM, 0, 52, D3D12_INPUT_PER_VERTEX_DATA, 0 } // XMCOLOR는 32비트 자료형
    };

4. 

 

5. 

삼각형의 면 픽셀 색상이 빨간색과 초록색의 그라데이션 효과처럼 보이는 이유는 
래스터화기 상태에서 정점 특성의 보간이 일어나기 때문이다.

 

7.

CBV_SRV_UAV 서술자 힙을 만들때 서술자 크기를 2로 하고
서술자 힙의 핸들을 이동시켜가며 상수 버퍼 서술자를 두 개 채운다.

update()에서 업로드 힙에 있는 상수 버퍼에 변환 행렬 값을 갱신한다.
두개의 버퍼에 각각 다른 변환 행렬값을 갱신한다. (이동변환 적용된 것과 아닌 것)

SetGraphicsRootDescriptorTable(0,  서술자 힙 handle(맨 앞))
그리기 // 첫 번째 서술자 그려짐
handle.Offset(1, mCbvSrvUavDescriptorSize) // 핸들 한칸 움직이고
SetGraphicsRootDescriptorTable(0, 서술자 힙 handle)
그리기 // 두 번째 서술자로 그려짐

11. 

1. 입력 배치 서술들의 순서는 정점 구조체 성분들의 순서와 일치하지 않아도 된다.
alignedByteOffset으로 구조체의 어떤 멤버인지 알 수 있기 때문에

2. 정점 셰이더 구조체의 성분 순서가 C++ 정점 구조체 성분 순서와 일치하지 않아도 된다.
의미소로 이어져 있기 때문에

 

15. 

R픽셀 색상 값이 0.5 이하인 픽셀 단편이 제거된다.

 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/6/Chapter%206%20Drawing%20in%20Direct3D/Box

 

GitHub - lemonyun/Directx12_study: 2022/06/10

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

github.com

 

 

5.3.2 128 비트 색상

R G B A 각각 32bit씩 부동소수점 표현 가능

XMVector 형식으로 색상을 표현할 수 있고 색상 연산을 수행할 때 SIMD의 혜택을 받을 수 있음

 

5.3.3 32 비트 색상

XMCOLOR 구조체는 32bit 색상을 표현한다.

32 bit인 XMCOLOR 는 ARGB 배치를 사용한다.

XMVECTOR를 XMCOLOR로 변환하는 함수 : XMStoreColor

XMCOLOR를 XMVECTOR로 변환하는 함수 : XMLoadColor

 

128비트 색상은 정밀도가 높은 색상 연산이 필요한 곳(픽셀 셰이더)에 쓰인다.

128비트 색상은 유효자릿수가 많아서 산술 정밀도가 높아 산술오차가 과도하게 누적되는 일이 없다.

 

최종적으로 모니터에 출력되는 픽셀 색상은 후면 버퍼에 32비트 색상으로 저장된다.

 

5.4 렌더링 파이프라인

출처 :&nbsp;https://docs.microsoft.com/ko-kr/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline

5.5 입력 조립기 단계 (IA 단계)

정점

정점은 공간적 위치 정보(x, y, z) 외에도 조명을 위한 법선 벡터 정보, 텍스처를 위한 텍스처 좌표를 동시에 포함할 수 있다.

mCommandList->IASetVertexBuffers // 정점 버퍼뷰 set 명령을 명령 목록에 추가

 

기본도형 위상구조

점, 점 목록, 선 목록, 선 띠, 삼각형 목록, 삼각형 띠

인접성 정보를 가진 기본도형 ( 삼각형 목록을 만들 때 옆에 붙어있는 

N(1~ 32)개의 제어진 패치 목록 (정점 자료를 N개의 제어점들로 이루어진 패치 목록으로 해석해야 함)

책의 예제에서는 대부분 삼각형 목록을 사용한다.

mCommandList->IASetPrimitiveTopology // 위상구조 set 명령을 명령 목록에 추가

 

색인

정점의 순서를 인덱스들의 배열로 저장하여 도형을 그리는 방법을 정의

mCommandList->IASetIndexBuffer // 인덱스 버퍼뷰 set 명령을 명령 목록에 추가

 

 

5.6 정점 셰이더 단계

정점 하나를 받아서 정점 하나를 출력하는 함수

world transform, view transform, 투영 변환 진행됨

시야 변환은 카메라 위치를 원점으로 하는 local 좌표계로 변환을 해야 하기 때문에 카메라 local 좌표계를 world 좌표계로 변환하는 행렬의 역행렬을 사용한다.

투영 변환은 카메라(화면)의 종횡비 r, 수직 시야각 α, 가까운 평면 거리(투영되는 창) n, 먼 평면 거리 f를 이용하여

카메라 기준 좌표를 동차 절단 공간(homogeneous space) 으로 변환한다.

동차 나누기 (xyzw를 w 값으로 나눈 이후)에는 NDC(정규화된 장치 좌표)가 된다.

원근 투영 행렬

5.7 테셀레이션 단계

기하구조를 테셀레이션하기 전에 정점 셰이더에서 애니메이션이나 물리 계산을 낮은 주파수에서 수행할 수 있다.

Direct3D 11에서 새로 도입된 단계, 필요하지 않으면 생략할 수 있는 단계

기하구조를 GPU에서 테셀레이션하는 수단을 제공한다.

주어진 메시의 삼각형들을 더 잘게 쪼개서 새로운 삼각형들을 만드는 과정이다.

테셀레이션 단계는 내부적으로 3단계로 구성된다.

 

1. 덮개 셰이더 단계 (hull shader)

ㄴ 상수 덮개 셰이더 : 입력 조립기 단계에서 삼각형들 대신에 제어점 패치를 입력받고, 패치마다 상수 덮개 셰이더가 실행되는데, 테셀레이션 계수들을 출력한다.

ㄴ 제어점 덮개 셰이더 : 제어점마다 호출되며 제어점의 수를 늘릴 수 있다. (곡면을 제어하는 점을 만들 수 있다.)

 

2. 테셀레이터 단계 (tessellator)

ㄴ 하드웨어가 테셀레이션 계수를 덮개셰이더에서 얻은 테셀레이션 계수 기반으로 새로운 정점(매개변수 좌표 (u,v))을 만듬

 

3. 영역 셰이더 단계 (Domain shader)

ㄴ 제어점 덮개 셰이더의 출력 패치 제어점, 테셀레이터가 반환하는 매개변수 좌표, 

ㄴ 테셀레이터가 출력한 정점들에 대해서 정점의 실제위치를 설정하고 동차 절단 공간으로 변환

ㄴ 제어점을 이용해서 베지에 곡면을 구성하는 곡면위의 정점 위치를 계산할 수 있다.

 

5.8 기하 셰이더 단계

생략 가능한 단계, 테셀레이션 단계를 사용하는 경우 생략할 수 없다.

기본도형을 입력받아 GPU에서 새로운 기하구조들로 확장하거나 폐기할 수 있다.

기하 셰이더의 출력을 메모리의 버퍼에 저장해 두고 나중에 활용하는 것이 가능하다.

 

5.9 절단

하드웨어가 시야 절두체 바깥에 있는 기하구조를 Clipping 해주는 작업

 

5.10 래스터화 단계

1. 뷰포트 변환

원근 나누기가 수행되어 정점들이 NDC 공간 좌표계가 되면

정규화된 x, y 값이 후면 버퍼의 크기에 비례하여 변환됨.(픽셀 단위가 된다)

 

2. 후면 선별

삼각형을 구성하는 정점들의 감기는 방향이 시계 반대 방향인 삼각형을 폐기한다.

 

3. 정점 특성의 보간

원근 보정 보간.. ?

 

5.11 픽셀 셰이더 단계

뷰포트 변환에 의해 만들어진 픽셀 단편 (=중간 픽셀: 모든 기하구조의 앞면을 포함) 

픽셀 단편들의 색상을 결정하는 셰이더 단계

각 픽셀에 대해 단순 고정 상수 색깔을 돌려줄 수도 있고, 조명, 반사, 그림자효과들에 의해 계산된 색상을 줄 수도 있다.

 

5.12 출력 병합기 단계

픽셀 단편들 중 z 성분에 의한 깊이 판정이나 스텐실 판정에 의해 기각되는 픽셀 단편을 제외하고 나머지 픽셀 단편들은 후면 버퍼에 기록된다.

혼합도 여기서 일어난다. (새 픽셀이 후면 버퍼의 기존 픽셀을 완전히 덮어쓰는 것이 아니라 두 픽셀을 일정한 공식에 따라 섞은 결과를 기록하는 것, 반투명 같은 특수 효과를 내는 데 쓰인다.)

 

4.3 Direct3D의 초기화

1. 장치 생성

2. 울타리 객체 생성

3. 서술자 크기 얻기

4. md3dDevice->CheckFeatureSupport로 4X MSAA 품질 수준 지원 점검

5. 명령 대기열, 명령 목록, 명령 할당자 생성 (명령 목록을 생성할 때 Draw를 할 것이 아니라면 초기 파이프라인 객체 지정부분을 nullptr로)

6. 교환 사슬 생성

7. 서술자 힙 생성 

8. RTV 서술자 생성 (렌더 버퍼 개수만큼)

9. 깊이 스텐실 버퍼 자원 생성, DSV 서술자 생성

10. 뷰포트 설정과 가위 직사각형 설정(명령 대기열에 명령 추가)

 

4.4 시간 측정과 애니메이션

Win32 함수 QueryPerformanceCounter를 사용해 현재 시간을 틱 단위로 얻고

QueryPerformanceFrequency 함수를 사용해 주파수(초당 틱수)를 얻어 시간을 잴 수 있다.

 

매 프레임마다 Draw함수를 호출하기 전에 Tick 함수를 호출해 응용 프로그램 멤버 변수에 프레임 사이의 시간을 얻을 수 있고 응용프로그램은 이 시간을 이용해서 장면을 갱신 할 수 있다.

 

4.5 예제 응용 프로그램 프레임워크

D3DApp 클래스 : 초기화, main window 생성, 윈도우 메세지 처리, 응용 프로그램의 base 클래스로 쓰임

 

 

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

6. Direct3D의 그리기 연산  (0) 2022.06.16
5. 렌더링 파이프라인  (0) 2022.06.14
4. Direct3D의 초기화(1)  (0) 2022.06.13
3. 변환  (0) 2022.06.12
2. 행렬 대수  (0) 2022.06.11

Direct3D는 응용 프로그램에서 GPU를 제어하고 프로그래밍 하는데 쓰이는 저수준 그래픽 API이다.

 

Direct3D12는 Direct3D11보다 훨씬 낮은 수준의 API가 되었다.

다중 스레드 지원을 개선하기 위해 이전보다 추상화가 줄었고, 개발자가 손수 관리해야할 사항들이 늘어났다.

4.1.2 COM 객체 (Component Object Model)

COM 객체 전용 스마트포인터 ComPtr 클래스를 사용하여 관리

 

COM 인터페이스를 가리키는 포인터는 new 키워드로 직접 생성하지 않고 보통 특별한 함수를 호출해서 얻는다. (Comptr와 desc 구조체를 넘겨 COM 객체를 초기화한다)

 

COM 인터페이스를 다 사용하고 난 후에는 delete로 삭제하는 것이 아니라 그 인터페이스의 Release 메서드를 호출한다.

Release 메서드는 Shared_ptr처럼 COM 객체의 참조 횟수가 0이 될 때 호출되어 메모리에서 해제시킨다.

ComPtr은 범위 밖을 벗어나면 자동으로 COM에 대해 Release 함수를 호출하므로 사용자가 Release를 호출할 필요가 없다.

 

ComPtr<[Com 인터페이스]> [변수이름] 

ComPtr 클래스의 함수

1. Get : ComPtr이 가리키는 Com 인터페이스를 반환

2. GetAddressOf : Comptr이 가리키는 Com 인터페이스의 주소를 반환

3. Reset : ComPtr 인스턴스를 nullptr로 설정하고 Com 인터페이스의 참조 횟수를 1 감소

 

COM 인터페이스들은 대문자 I로 시작한다.

 

4.1.3 텍스처 형식

1. RGBA 색상 원소를 담을 수 있다.

DXGI_FORMAT_R8G8B8A8_UINT

2. 부동 소수점 원소를 담을 수 있다.

DXGI_FORMAT_R32G32B32_FLOAT

3. 무형식 텍스쳐 형식

DXGI_R16G16B16A16_TYPELESS

 

4.1.4 교환 사슬과 페이지 전환

전면 버퍼와 후면 버퍼는 하나의 교환 사슬을 형성한다.

교환 사슬을 대표하는 인터페이스는 IDXGISwapChain이다. 이 인터페이스는 전면 버퍼 텍스처와 후면 버퍼 텍스처를 담는다. 화면에 현재 프레임에 전면 버퍼가 표시될 동안 후면 버퍼에 다음 프레임을 렌더링하고 다음 프레임에 버퍼를 교환하는 방법

이중 버퍼링 : 전면과 후면으로 버퍼를 두개 사용하는 방법

 

4.1.5 깊이 버퍼링

전면 후면 버퍼와 동일한 해상도를 가지는 버퍼

깊이 값은 [0, 1] 사이 값을 가짐

 

깊이 버퍼링을 위한 텍스처 형식

1. DXGI_FORMAT_D32_FLOAT_S8X24_UINT

ㄴ 32비트 부동소수점 깊이, 8비트 스텐실[0, 255], 24 패딩

2. DXGI_FORMAT_D32_FLOAT

ㄴ 32비트 부동소수점 깊이

3. DXGI_FORMAT_D24_UNORM_S8_UINT

ㄴ 24비트 부호없는 깊이[0, 1], 8비트 스텐실[0, 255]

4. DXGI_FORMAT_D16_UNORM

ㄴ 16비트 부호없는 깊이[0, 1]

 

4.1.6 자원과 서술자

자원 : 버퍼, 텍스처

 

렌더링 파이프라인에서 그리기 호출마다 해당 그리기 호출이 참조할 자원을 묶어줘야 한다.

자원을 묶는 대신에 자원을 설명하는 서술자를 파이프라인에 묶는다.

 

같은 자원을 렌더링 파이프라인의 서로 다른 단계에서 사용할 수 있기 때문에 이런 방식을 쓴다.

 

같은 자원이더라도 여러 방법으로 사용될 수 있다.

렌더 대상

깊이 스텐실 버퍼

셰이더 자원

 

무형식으로 생성된 자원은 해당 자원의 서술자 만들때 형식을 명시할 수 있다.

형식이 완전히 지정된 자원은 런타임에서 자원 접근을 최적화할 수 있게 되기 때문에, 필요한 경우에만 무형식으로 자원을 만들어야 한다.

 

서술자는 자원 자료를 지정하는 수단일 뿐만 아니라, 자원을 GPU에 서술하는 수단이기도 하다.

서술자의 종류

1. CRV/SRV/UAV 서술자 : 상수버퍼 / 셰이더 자원 / 순서 없는 접근

2. 표본추출기 서술자 : sampler 자원을 서술

3. RTV 서술자 : 렌더 대상 자원을 서술

4. DSV : 깊이스텐싈 자원을 서술

 

서술자는 프로그램 초기화 시점에서 형식 점검과 유효성 검증이 일어나기 때문에 초기화 시점에 서술자를 생성해야 한다.

 

서술자 힙

ㄴ 서술자들의 배열

서술자의 종류마다 서술자 힙이 필요하다.

4.1.7 다중표본화

초과 표본화 : 화면 해상도보다 4배 큰 후면 버퍼에 렌더링 후 4픽셀의 평균 색상을 최종 색상으로 사용

 

다중 표본화 : 4X 다중표본화(픽셀당 부분픽셀 4개)의 경우 화면 해상도보다 4배 큰 후면버퍼에 렌더링

픽셀당 색상을 한번 계산

그 픽셀이 보이는 부분픽셀에 복제됨(부분픽셀 마다 깊이 스텐실 판정)

4개의 부분픽셀의 색상을 평균낸 것이 결과 픽셀이 됨

4.1.8 Direct3D의 다중표본화

DXGI_SAMPLE_DESC라는 구조체 인스턴스를 채워야 한다.

Count : 픽셀당 추출할 부분픽셀의 개수

Quality : 품질 수준 (하드웨어마다 다름)

 

Direct3D 12 장치는 4X 다중표본화를 지원한다.

 

4.1.9 기능 수준

기능 수준 12를 지원하는 GPU는 반드시 Direct3D 12의 기능 집합 전체를 지원해야 한다.

4.1.10 DXGI

DXGI API가 제공하는 것들

1. 교환사슬

2. 전체화면 모드 전환

3. 디스플레이 어댑터, 모니터, 지원되는 디스플레이 모드 같은 그래픽 시스템 정보의 열거

 

IDXGIFactory 인터페이스

ㄴ IDXGISwapChain 인터페이스 생성

ㄴ 디스플레이 어댑터 열거

어댑터 하나에는 모니터가 여러개 연결될 수 있다. (IDXGIOuput)

모니터 하나에는 여러개의 디스플레이 모드가 있다. (DXGI_MODE_DESC)

ㄴ 해상도 (너비 높이)

ㄴ 디스플레이 형식

4.1.11 기능 지원 점검

CheckFeatureSupport 메서드로 점검 할 수 있는 기능

ㄴ 기능 수준

ㄴ 주어진 텍스처 형식에 대한 기능들 ( 해당 형식을 렌더 대상으로 사용할 수 있는가, 혼합을 적용할 수 있는가)

ㄴ 다중표본화 기능

4.1.12 상주성

자원을 생성하면 자원이 GPU 메모리에 입주하며, 파괴되면 메모리에서 나간다.

함수로 직접 제어할 수도 있다.

ID3D12Pageable 배열을 매개변수로 사용한다.

MakeResident (올리기)

Evict (내리기)

 

4.2 CPU와 GPU의 상호작용

1. mCommandList에 set command, draw command같은 명령들을 추가한다. 

2. mCommandList->close()로 명령들의 기록이 끝났음을 알린다.

3. mCommandQueue->ExecuteCommandLists를 수행하여 GPU 명령대기열에 명령 목록을 제출하면

명령 대기열은 명령 목록에 담긴 명령 메모리 할당자(command allocator)를 참조한다. (명령 목록은 바로 reset 되어도 상관이 없음)

4. 명령 메모리 할당자는 GPU 명령 대기열의 참조가 완전히 끝난 후에 (대기열이 빈 경우) reset 해야 한다.

 

Direct3D 12 API에는 생성하고자하는 COM 인터페이스의 COM ID와 void** 를 받는 함수들이 많기 때문에 매크로를 정의하여 사용한다. 

#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)

4.2.2 CPU 동기화

mCommandQueue->Signal 함수를 이용

명령 대기열에 새 울타리 값을 갱신하는 명령(Signal)을 추가

 

울타리 값은 0부터 시작하는 정수, 새로운 울타리 지점을 만들때마다(명령 대기열 flush) 1씩 증가

cpu 울타리 값은 바로 증가하지만 gpu 울타리 값은 명령 대기열이 비어야 증가하기 때문에 

gpu 울타리 값이 cpu 울타리 값과 같아질 때까지 기다림

 

4.2.3 자원 상태 전이

자원에 상태를 부여하여 자원 위험 상황을 피하기 위해 존재

개발자는 자원이 언제 전이하는지 알고 있기 때문에 프로그램이 자원 전이를 추적하는 부담을 줄일 수 있다.

전이 자원 장벽(transition resource barrier)

mCommandList->ResourceBarrier함수 // 장벽(자원, 이전상태, 이후상태)의 배열을 명령 대기열에 추가

 

4.2.4 명령 목록을 이요한 다중 스레드 활용

장면 전체를 하나의 명령 목록으로 그리려고 하면 명령 목록을 만드는데 CPU 시간이 오래 걸릴 수 있음

4개의 스레드 마다 하나씩 명령 목록을 만들게 하면 CPU 시간이 1/4배가 됨

 

1. 각 스레드는 명령 목록과 명령 메모리 할당자를 독립적으로 가진다.

2. 명령 대기열에는 동시에 접근할 수 있다.

3. 성능상의 이유로 초기화 시점에 동시에 기록할 수 있는 명령 목록들의 최대 개수를 설정해야 한다.

다중 CPU 코어의 장점을 활용할 수 있다.

 

DirectX 기본 프로젝트 빌드 시 VS 2019 버전에서 일어나는 오류

잘 따라했는데 문제가 생긴다
vs2019의 c++ standard conformance check에서 발생한 문제

추가 옵션에 /permissive를 적어주면 해결된다.

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

5. 렌더링 파이프라인  (0) 2022.06.14
4. Direct3D의 초기화(2)  (0) 2022.06.13
3. 변환  (0) 2022.06.12
2. 행렬 대수  (0) 2022.06.11
1. 벡터 대수  (0) 2022.06.11

 

3.1 선형변환

선형 변환은 좌표계를 선형으로 변환하는 변환?

기저 벡터를 바꾸는 변환

비례변환, 회전변환

3.2 아핀변환

아핀변환 : 선형변환에 이동변환을 결합한 변환

동촤 좌표 (Homogeneous coordinate) : 점과 벡터에 대한 변환을 동일한 방식으로 다룰 수 있다.

벡터를 나타내는 동촤좌표는 (x, y, z, 0)

점을 나타내는 동촤좌표는 (x, y, z, 1)

 

3.4 좌표 변경 변환

 

3.6 DirectXMath 라이브러리의 변환 관련 함수들

XMMatrixScaling // float 매개변수 3개로 비례행렬 생성

XMMatrixScalingFromVector // 벡터의 성분으로 비례행렬 생성

 

XMMatrixRotationX // x축에서 라디안 값만큼 회전하는 행렬 생성 ( 축의 방향 바라보고 시계방향)

// Y Z 

XMMatrixRotationAxis(회전축 벡터, 각도) // 임의의 축에 대한 회전행렬

 

XMMatrixTranslation // float 3개로 이동행렬생성

XMMatrixTranslationFromVector // 벡터의 성분들로 이동핼렬 생성

 

XMVector3TransformCoord(벡터, 행렬) // 점 변환을 위해 벡터 마지막 원소 1로 둔다.

XMVector3TransformNormal // 벡터 변환을 위해 벡터 마지막 원소를 0으로 둔다.

 

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

5. 렌더링 파이프라인  (0) 2022.06.14
4. Direct3D의 초기화(2)  (0) 2022.06.13
4. Direct3D의 초기화(1)  (0) 2022.06.13
2. 행렬 대수  (0) 2022.06.11
1. 벡터 대수  (0) 2022.06.11

 

2.5 2.6 2.7

 

 

2.8 DirectXMath의 행렬

XMMATRIX 구조체

XMVECTOR r[4]를 멤버로 가짐 // SIMD 활용을 위해 4개의 XMVECTOR를 멤버로 가짐

 

초기화 방법

1. 행벡터 4개

2. float 성분 16개

3. float 성분 16개 담은 배열

4. XMMatrixSet 함수 성분 flaot 16개

 

XMVector가 클래스 자료멤버에 저장할 때 XMFLOAT4 사용을 권장했었는데

XMMATRIX는 클래스 자료 멤버에 저장할 때 XMFLOAT4X4 형식 사용을 권장함

 

XMMATRIX -> XMFLOAT4x4일때 XMStoreFloat4x4 

XMFLOAT4x4 -> XMMATRIX 일때 XMLoadFloat4x4를 사용

 

2.8.2 행렬 함수

XMMatrixIsIdentity // 단위행렬인지 여부

XMMatrixMultiply // 행렬 곱

XMMatrixTranspose // 전치행렬

XMMatrixDeterminant // (행렬식, 행렬식, 행렬식, 행렬식) 벡터 반환

XMMatrixInverse(행렬식 벡터, M) // 역행렬 반환

 

함수 매개변수로 사용시 

첫 번째 매개변수는 FXMMATRIX

나머지 매개변수는 CXMMATRIX

 

생성자는 무조건 CXMMATRIX

 

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

5. 렌더링 파이프라인  (0) 2022.06.14
4. Direct3D의 초기화(2)  (0) 2022.06.13
4. Direct3D의 초기화(1)  (0) 2022.06.13
3. 변환  (0) 2022.06.12
1. 벡터 대수  (0) 2022.06.11

1.1.2 왼손잡이 좌표계

왼손잡이 좌표계

1.3 내적

내적

세타 값이 90도이면 내적은 0

예각이면 양수

둔각이면 음수

 

1.4 외적

외적 : 두 벡터에 수직인 벡터를 출력

 

1.6 DirectXMath 라이브러리 벡터

SIMD : 128비트 너비의 레지스터

SIMD 명령들을 가지고 있는 라이브러리, 4차원 벡터 연산을 스칼라 연산 4개가 아니라 SIMD 명령 하나로 처리할 수 있다.

x86 플랫폼에서는 sse2(streaming simd extensions 2)옵션을 활성화 해줘야 하지만 x64 플랫폼에서는 따로 해 줄 필요 없다.

 

typedef __m128 XMVECTOR;

// XMVECTOR는 32비트값 4개로 구성된다.

 

지역변수와 전역 변수에는 XMVECTOR를 쓰면 16바이트 경계에 자동으로 정렬이 된다.

클래스 자료 멤버에서는 XMVECTOR 대신 XMFLOAT2, XMFLOAT3, XMFLOAT4를 사용하는 것이 권장된다.

 

SIMD의 장점을 취하기 위해서는 XMFLOAT를 XMVECTOR 형식으로 바꿔서 계산하는 것이 좋으므로 

클래스의 정보를 계산을 위해 XMLoadFloat 함수를 사용하여 XMVECTOR형식으로 변환하고

계산후에 XMStoreFloat함수로 XMFLOAT형식으로 되돌린다.

 

1.6.3 XMVector를 매개변수로 전달 할 때

함수에 XMVector 인수를 전달 할때 

플랫폼과 컴파일러에 따라 SSE 레지스터를 사용할 수 있는 개수가 다르기 때문에 의존성을 없애기 위해

FXMVECTOR, GXMVECTOR, HXMVECTOR, CXMVECTOR 형식을 사용해야 한다.

XM_CALLCONV 호출 규약지시자를 사용해야 한다.

1 ~ 3번째 : FXMVECTOR

4번째 : GXMVECTOR

5 ~ 6번째 : HXMVECTOR

나머지 : CXMVECTOR

 

생성자에 대한 규칙은 다르다

1 ~ 3번째 : FXMVECTOR

나머지 : CXMVECOTR

그리고 XM_CALLCONV 호출 규약 지시자를 사용하지 말아야 한다.

 

1.6.4 상수 벡터

상수 벡터는 XMVECTORF32를 사용해야 한다.

중괄호 초기화 구문을 사용할 때에는 XMVECTORF32를 사용해야 한다.

 

1.6.7 설정함수

XMVECTOR XM_CALLCONV XMVectorZero(); // (0, 0, 0, 0)

XMVECTOR XM_CALLCONV XMVectorSplatOne(); // (1, 1, 1, 1)

XMVECTOR XM_CALLCONV XMVectorSet(float x, float y, float z, float w); // (x, y, z, w)

XMVECTOR XM_CALLCONV XMVectorReplicate(float s) // (s, s, s, s)

XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V) // (v.x, v.x, v.x, v.x)

//Y Z 버전도 존재

 

1.6.8 벡터 함수들

길이 : xmvector3length

내적 : xmvector3dot

외적 : xmvector3cross

정규화 : xmvector3normalize

각도 : xmvector3anglebetweenvectors

proj perp : xmvector3componentsfromnormal ( 출력 매개변수 )

equal : xmvector3equal

 

XMVectorGetX // float 값 반환

// Y Z W도 있음

1.6.9 부동 소수점 오차

부동 소수점 수의 상등을 판정 할 때 

XMVector3NearEqual(벡터1, 벡터2, epsilon벡터) // 두 벡터의 차이가 epsilon벡터보다 작으면 true

 

연습문제 19번 - 다양한 함수 알아보기

 

상수

XM_PIDIV4 // 파이 / 4

XM_PIDIV2 // 파이 / 2

XM_PI // 파이

 

벡터 함수 (원소에 각각 적용)

XMVectorAbs // 절대값

XMVectorCos // cosine (라디안)

XMVectorLog // 밑이 2인 로그

XMVectorExp // 2의 거듭제곱

 

XMVectorPow(u, p) // u의 p승

 

XMVectorSqrt // 제곱근

 

XMVectorSwizzle (u, 2, 2, 1, 3) // 벡터 원소를 인덱스를 이용하여 재배치

 

XMVectorMultiply(u, v) // 각각의 원소를 곱함

XMVectorSaturate(u) // 0과 1사이로 자름

XMVectorMin(u, v) // 두 벡터중 작은 값의 원소를 선택

XMVectorMax(u, v) // 큰 값의 원소를 선택

 

 

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

5. 렌더링 파이프라인  (0) 2022.06.14
4. Direct3D의 초기화(2)  (0) 2022.06.13
4. Direct3D의 초기화(1)  (0) 2022.06.13
3. 변환  (0) 2022.06.12
2. 행렬 대수  (0) 2022.06.11

+ Recent posts