본문 바로가기

DirectX/DirectX11 기초 개념

Direct3D 리소스

DirectX 11로 시작하는 3D 게임 프로그래밍 // 이용희 지음 // 프레릭

 

 

 

3D 화면을 구성하고 그리려면 여러 가지 형태의 데이터가 필요하다.

 

이러한 데이터는 기하학적 데이터(정점 데이터), 텍스처, 셰이더 데이터 등을 포함하며 Direct3D 렌더링 파이프라인에서 참조할 수 있는 메모리 영역에 저장되어야 한다.

 

이러한 리소스 데이터는 미리 작성된 것을 파일로부터 읽어들이거나 프로그램 실행 중에 동적으로 생성할 수 있다.

 

리소스 데이터응용 프로그램(CPU)에서 생성되고 GPU에서 렌더링을 위해 사용하는 것이 일반적이다.



Direct3D에서는 이러한 메모리를 효율적으로 참고하고자 리소스(Resource)라는 개념과 이와 관련된 인터페이스를 제공한다.

 

Direct3D에서 리소스Direct3D 렌더링 파이프라인에서 사용할 수 있는 메모리 영역이다.

 

그래픽 리소스는 최적의 성능을 위해서 가능하면 비디오 메모리에 있어야 하고 GPU가 효율적으로 읽고 쓸 수 있어야 한다.

 

Direct3D 렌더링 파이프라인에서 기하학적 데이터, 텍스처 셰이더 데이터 등은 리소스로 저장되어야 한다.



모든 Direct3D 리소스는 버퍼 또는 텍스처 중 하나의 형식을 가진다.

 

각각의 파이프라인 단계에서 128개의 리소스까지 사용할 수 있다.

 

리소스의 예는 정점 버퍼, 인덱스 버퍼, 상수 버퍼, 텍스처 등이 있다.



3D 화면을 표현하기 위해서는 보통 많은 텍스처를 사용하기 때문에 Direct3D는 텍스처를 간단하게 관리하고자 텍스처 배열을 제공한다.

 

텍스처 배열하나의 인터페이스를 사용하여 여러 가지 텍스처를 사용할 수 있도록 해준다.



 

하나 이상의 단계에서 같은 리소스에 접근할 필요가 있기 때문에 Direct3D는 리소스 뷰(View) 개념을 도입하였다.

 

하나의 접근할 수 있는 리소스의 부분을 나타낸다.




리소스 뷰리소스(텍스처, 버퍼 등)에 대한 접근을 위한 일반적인 모델을 제공한다.

 

리소스 뷰는 어떤 데이터를 사용하고 어떻게 사용하는 가를 표현하기 때문에 리소스 뷰를 사용하면 형식이 없는 리소스를 생성할 수 있다.




1. 리소스 형식

 

Direct3D 렌더링 파이프라인에서 사용되는 모든 리소스는 두 가지의 기본 리소스 형식, 버퍼텍스처 중 하나에서 파생된다.

 

버퍼 : 원시 데이터의 집합

텍스처 : 텍셀(Texel, Texture Element)의 집합



리소스의 메모리 배열을 나타내는 방식

 

유형(Typed) 방식 : 

리소스가 생성될 때 리소스의 데이터 형식 지정

실행 중에 리소스에 대한 접근을 최적화할 수 있게 된다.

 

무형(Typeless) 방식 : 

리소스가 파이프라인에 연결될 때 데이터 형식 지정

이러한 리소스를 생성할 때도 리소스가 사용하는 메모리의 크기는 지정해야 한다.

텍스처는 파이프라인에 연결될 때까지 텍스처 형식은 유동적이므로 텍스처 리소스는 무형 리소스로 취급한다.



하나의 리소스는 각 파이프라인이 각각 다른 뷰를 가지는 경우 여러 개의 파이프라인 단계에 연결될 수 있다. 동시에 다른 파이프라인 단계에서 사용될 수 있다.

 

리소스는 여러 개의 파이프라인 단계에서 공유될 수 있도록 저장될 수 있다.

하나의 파이프라인은 하나의 뷰를 사용하여 리소스 데이터를 해석한다.

리소스 뷰개념적으로 리소스 데이터를 형 변환하는 것과 유사하다.





2. 버퍼 리소스

 

버퍼 리소스는 자료형을 갖는 데이터의 집합이다.

버퍼는 Element를 포함, 각 Element는 1 ~ 4개까지의 Component로 구성

원소 데이터 형식의 예는 압축된 데이터, 8비트 정수, 4개의 32비트 실수 등이다.

 

Direct3D가 제공하는 버퍼 리소스의 유형은 다음과 같이 세 가지로 구분할 수 있으며 ID3D11Buffer 인터페이스로 사용할 수 있다.

  • 정점 버퍼
  • 인덱스 버퍼
  • 상수 버퍼

 

D3D11_USAGE 열거형 상수

렌더링하는 동안 CPU 또는 GPU가 리소스에 접근할 수 있는가의 여부를 나타낸다.

 

D3D11_BIND_FLAG 열거형 상수

리소스(버퍼 또는 텍스처)가 파이프라인에 어떻게 연결되는가를 나타낸다.

즉, 리소스가 파이프라인에서 어떤 용도로 사용되는가를 나타낸다.

 

ID3D11Buffer 인터페이스

구조화되지 않은 버퍼 리소스를 참조하기 위한 인터페이스

버퍼는 읽기를 위한 여러 개의 파이프라인 단계에 연결될 수 있다.

버퍼는 쓰기를 위한 하나의 파이프라인 단계에 연결될 수 있다.

그러나 하나의 버퍼가 읽기와 쓰기를 위해서 동시에 파이프라인 단계에 연결될 수 없다.





3. 정점 버퍼

 

정점 버퍼는 메시를 정의하기 위한 기하학적 정보를 나타낸다.

즉, 메시의 각 정점에 대한 데이터의 집합을 나타낸다.

일반적으로 

정점 위치 좌표는 3개의 32비트 실수(DXGI_FORMAT_R32G32B32_FLOAT)로 표현되고

텍스처 좌표는 2개의 32비트 실수(DXGI_FORMAT_R32G32_FLOAT)로 표현된다.

 

정점 버퍼를 사용하는 과정

1. 정점 버퍼를 생성한다. ID3D11Device::CreateBuffer()

2. 정점 버퍼를 입력 조립 단계에 연결한다. ID3D11Device::IASetVertexBuffers()

3. 입력 레이아웃 객체를 생성한다. ID3D11Device::CreateInputLayout()

4. 입력 레이아웃 객체입력 조립 단계연결한다. ID3D11Device::IASetInputLayout()

 

정점 버퍼의 데이터에 접근하려면 다음의 정보가 필요하다.

{

오프셋(Offset) : 정점 버퍼의 시작에서 첫 번째 정점을 위한 데이터까지의 바이트 수

 

기본 정점 위치(Base Vertex Location) : 오프셋에서 부터 Draw()함수 호출에서 사용되는 첫 번째 정점까지의 바이트 수

}



4. 시맨틱

 

시맨틱(Semantics)은 셰이더 입력 또는 출력에 부착되는 문자열이며 해당 매개 변수의 사용 의도를 의미하는 정보를 전달하기 위하여 사용된다.

 

또한, 셰이더 컴파일러는 이 문자열을 셰이더 입력과 출력에 연결하기 위해 사용한다.

셰이더 단계들 사이전달되는 모든 변수에 시맨틱을 사용해야 한다.

 

셰이더 변수에 시맨틱을 지정하는 방법은 변수 이름 다음에 쌍점(:)과 시맨틱을 추가하는 것이다.

셰이더 프로그램의 전역 변수와 셰이더 함수의 매개 변수에만 시맨틱을 사용해야 한다.



시스템 값 시맨틱

시스템 값 시맨틱은 SV_Position과 같이 SV_로 시작한다.

시스템 값은 파이프라인의 다른 부분에서 유효하다.

예를 들어, SV_Position 시맨틱은 정점 셰이더출력뿐 아니라 입력으로 사용할 수 있다.

픽셀 셰이더SV_DepthSV_Target 시스템 값 시맨틱을 가진 매개 변수에만 쓸 수 있다.



SV_VertexID, SV_PrimitiveID, SV_InstanceID, SV_IsFrontFace는 특정한 값을 해석할 수 있는 파이프라인의 첫 번째 셰이더로만 입력될 수 있다.

이후에 이 값을 다음 셰이더 단계로 전달하는 것은 첫 번째 셰이더의 책임이다.

예를 들어, SV_PrimitiveID정점 셰이더에서 해석할 수 없다. 결과적으로 SV_PrimitiveID는 기하 셰이더 단계 또는 픽셀 셰이더 단계에서 사용할 수 있다.




5. 시스템 생성 값의 사용

 

시스템이 생성한 값은 사용자가 제공한 입력 시맨틱에 기반을 두어 입력 조립 단계에서 생성된다.

인스턴스 ID, 정점 ID, 프리미티브 ID와 같은 데이터를 덧붙임으로써 다음 셰이더 단계가 셰이더 프로그램 처리를 최적화하기 위해 이 값을 참조할 수 있게 된다.

 

예를 들어, 정점 셰이더 단계는 추가적인 정점마다 데이터를 사용하기 위해 또는 다른 연산을 수행하기 위해 인스턴스 ID를 참조할 수 있다. 기하 셰이더와 픽셀 셰이더는 프리미티브 ID를 사용할 수 있다.



정점 ID

 

정점 ID는 각 정점을 구별하기 위해 각 셰이더 단계에서 사용할 수 있는 인덱스이다.

이 정수 값은 입력 조립 단계에서 프리미티브가 처리될 때 각 정점에 지정된다.

입력 조립 단계에서 정점마다 정점 ID를 생성한 것을 알리고자 셰이더 입력 선언에 SV_VertexID 시맨틱을 사용한다.

입력 조립 단계는 각 정점에 정점 ID를 추가한다.

각 Draw()함수 호출에 대하여 정점 ID는 1만큼씩 증가한다. (232를 넘으면 다시 0이 된다)

모든 프리미티브 유형에 대하여 정점은 프리미티브와 연관된 정점 ID를 가진다.



프리미티브 ID

 

프리미티브 ID는 각 프리미티브를 구별하기 위하여 각 셰이더 단계에서 사용된다.

이 정수 값은 입력 조립 단계에서 프리미티브가 처리될 때 각 프리미티브에 지정된다.

입력 조립 단계에서 프리미티브 ID를 생성한 것을 알리고자 셰이더 입력 선언에 SV_PrimitiveID 시맨틱을 사용한다.

입력 조립 단계는 기하 셰이더 또는 픽셀 셰이더 단계가 사용할 각 프리미티브에 프리미티브 ID를 더할 것이다.



인스턴스 ID

 

인스턴스 ID는 현재 처리되고 있는 인스턴스를 구분하는 각 셰이더 단계에서 사용된다.

입력 조립 단계는 셰이더 입력 선언이 SV_InstanceID 시맨틱을 포함하면 각 정점에 인스턴스 ID를 추가한다.




6. 시그니처

 

셰이더 시그니처셰이더 함수의 입력 또는 출력이 되는 매개 변수의 리스트이다.

 

인접한 셰이더 단계들은 레지스터 배열을 효과적으로 공유한다.

하나의 셰이더는 레지스터 배열의 특정한 위치에 데이터를 쓰고 다른 셰이더는 같은 위치에서 읽어야 한다.

 

Direct3D API는 시맨틱 해결의 부담 없이 셰이더 출력과 입력을 연결하기 위해 셰이더 시그니처를 사용한다.

 

시그니처가 호환되도록 하는 가장 쉬운 방법은 셰이더 입력과 셰이더 출력을 같은 구조체 형으로 연결하는 것이다.




7. 프리미티브 유형

 

Direct3D 11은 몇 가지 프리미티브 유형(Primitive Topology)을 제공한다.

프리미티브 유형은 렌더링 파이프라인이 정점 버퍼의 데이터를 어떻게 해석하여 렌더링할 것인가를 나타낸다.

같은 정점 데이터라도 프리미티브 유형이 달라지면 다른 프리미티브로 조립된다.

 

  • 점 리스트 : 떨어져 있는 정점들의 집합이다.
  • 선 리스트 : 선분들의 집합
  • 선 스트립 : 연결된 선분들의 집합
  • 삼각형 리스트 : 삼각형들의 집합
  • 삼각형 스트립 : 연속적으로 연결된 삼각형들의 집합
    ex) 연결된 4개의 삼각형을 그리고자 할 때
    --> 삼각형 스트립 - 6개 정점 // 삼각형 리스트-12개 정점
  • 선 리스트 인접성 : 각 선분에 인접한 정점들의 리스트가 추가된 선 리스트
  • 선 스트립 인접성 : 연결된 선분의 양끝에 인접한 정점들의 리스트가 추가된 선 스트립
  • 삼각형 리스트 인접성 : 삼각형 리스트 인접성은 삼각형의 각 선분에 인접한 정점들의 리스트가 추가된 삼각형 리스트
  • 삼각형 스트립 인접성 : 삼각형의 각 선분에 인접한 정점들의 리스트가 추가된 삼각형 스트립




8. 인덱스 버퍼

 

인덱스 버퍼는 정점 버퍼의 정점 위치를 나타내는 16비트 또는 32비트의 정수 오프셋(인덱스)의 순차적 집합을 나타낸다.

 

인덱스는 정점 버퍼에서 한 정점의 위치를 나타낸다.

 

입력 조립 단계에 데이터를 제공하기 위해 하나 이상의 정점 버퍼와 함께 인덱스 버퍼를 사용하는 것을 인덱싱(Indexing)이라고 한다.

 

인덱스 버퍼를 사용하면 정점 데이터의 중복을 피할 수 있으며, 메시를 구성하는 각 정점은 정점 버퍼에 한 번만 저장된다.

그리하여 정점 버퍼에는 메시를 구성하는 정점 개수만큼의 정점만이 존재하므로 정점의 변환은 각 정점에 대하여 한 번만 수행된다.

 

인덱스 버퍼를 사용하면 기하를 구성하기 위해 변환된 정점을 재사용하면 되므로 변환에 필요한 실수 연산이 중복되지 않게 된다.

 

인덱스 버퍼는 ID3D11Device::CreateBuffer() 함수로 생성된다.



 

인스턴싱(Instancing)은 같은 객체를 다른 위치에 여러 번 그리는 것을 의미한다.

이때 메시 데이터를 재사용하여 그린다.



9. 상수 버퍼

 

상수 버퍼(Constant Buffer)는 파이프라인에서 셰이더 상수를 효율적으로 제공하기 위해 사용

상수 : 셰이더에서 변하지 않는 값

 

셰이더 함수의 매개 변수로 전달되는 데이터는 셰이더 함수가 호출될 때마다 변하는 데이터가 된다.

 

하지만 정점의 위치 벡터를 변환하기 위한 월드 변환 행렬이나 카메라 변환 행렬을 등은 하나의 정점 버퍼에 있는 정점마다 변하지 않는다.

 

기본적으로 셰이더 프로그램에서 전역 변수는 상수이다.




10. 텍스처 리소스

 

텍스처 리소스텍셀(Texel)을 저장하는 데 사용되는 구조화된 데이터의 집합이다.

 

텍셀은 파이프라인이 읽거나 쓸 수 있는 텍스처의 가장 작은 단위

 

버퍼와 다르게 텍스처는 셰이더가 읽을 때 텍스처 샘플러로 필터링될 수 있다.



리소스는 데이터의 형식이 지정되지 않아도 뷰는 데이터 형식이 반드시 지정되어야 한다.

뷰를 통해 데이터를 읽을 때 리소스 데이터의 형식이 뷰의 형식으로 자동으로 변환된다.

리소스 뷰의 데이터 형식 리소스의 데이터 형식 호환 가능해야 한다.




11. 서브 리소스

 

Direct3D는 리소스 전체 또는 리소스의 일부를 참조할 수 있다.

서브 리소스는 리소스의 일부분을 나타내기 위한 리소스의 부분집합을 의미한다.

하나의 버퍼는 기본적으로 하나의 서브 리소스로 정의된다.

즉, 버퍼 자체가 서브 리소스이다.

 

텍스처에 대한 서브 리소스는 텍스처가 가질 수 있는 밉맵 레벨과 텍스처 배열 때문에 조금 복잡하다.



텍스처는 Direct3D 렌더링 파이프라인에 직접 연결될 수 없고 텍스처 리소스에 대한 뷰를 생성하여 파이프라인에 연결해야 한다.

텍스처에 대한 뷰를 사용함으로써 텍스처는 실행중에 여러가지 목적으로 다르게 해석될 수 있다.

{

2차원 텍스처를 렌더 타깃으로 사용하기 위하여 ID3D11Device::CreateRenderTargetView() 함수를 사용하여 렌더 타깃 뷰를 생성할 수 있다.

2차원 텍스처를 깊이/스텐실 버퍼로 사용하기 위하여 ID3D11Device::CreateDepthStencilView() 함수를 사용하여 깊이/스텐실 뷰를 생성할 수 있다.

텍스처를 셰이더의 입력으로 사용하기 위하여 ID3D11Device::CreateShaderResourceView()함수를 사용하여 셰이더 리소스 뷰를 생성할 수 있다.

}

 

'DirectX > DirectX11 기초 개념' 카테고리의 다른 글

Direct3D 11 렌더링 파이프라인  (0) 2021.04.30
Direct3D 11 디바이스  (0) 2021.04.22
3D 그래픽과 Direct3D  (0) 2021.04.22
3D 그래픽의 이해  (0) 2021.04.22