6.1 정점과 입력 배치

  • 원하는 자료(특성)를 가진 커스텀 정점 형식을 만들려면 그러한 자료를 담을 구조체 정의
    • 사용자 정의 정점 예시
        struct Vertex1
        {
            XMFLOAT3 Pos;
            XMFLOAT3 Color;
        };
        struct Vertex2
        {
        XMFLOAT3 Pos;
        XMFLOAT3 Normal;
        XMFLOAT2 Tex0;
        }
  • 입력 배치 서술(input layout descrpitino) :
    • 정점의 각 성분으로 무엇을 해야 하는지를 D3D에 알려주어야 함
    • D3D12_INPUT_LAYOUT_DESC 구조체로 대표
        typedef struct D3D12_INPUT_LAYOUT_DESC
        {
            const D3D12_INPUT_ELEMENT_DESC *pInputElementDesccs;
            UINT NumElements;
        } D3D12_INPUT_LAYOUT_DESC;
      • D3D12_INPUT_ELEMENT_DESC 형식 원소들을 담은 배열과 개수를 나타내는 구조체
      • 각 원소는 정점 구조체의 각 성분을 서술
      • 배열의 원소들과 정점 구조체의 성분들 은 일대일로 대응
    • D3D12_INPUT_ELEMENT_DESC 정의
        typedef struct D3D12_INPUT_ELEMENT_DESC
        {
            LPCSTR SemanticName;
            UINT SematicIndex;
            DXGI_FORMAT Format;
            UINT InputSlot;
            UINT AlignedByteOffset;
            D3D12_INPUT_CLASSIFICATION InputSlotClass;
            UINT InstanceDataStepRate;    
        } D3D12_INPUT_ELEMENT_DESC;
      • SemanticName
        • 성분에 부여된 문자열 이름
        • 정점 셰이더에서 의미소(semantic) 이름으로 쓰이므로, 반드시 유효한 변수이름이어야 함
        • 정점 구조체의 성분을 정점 셰이더 입력 서명과 대응시키는 역할
      • SematicIndex
        • 의미소 색인
        • 하나의 정점 구조체에 텍스처 좌표가 여러 개 있을 수 있는데, 각 텍스처 좌표에 개별적인 의미소 이름을 부여하는 대신 색인을 통해 구별
        • 셰이더 코드에서 색인이 지정되지 않은 의미소는 0인 의미소로 간주
      • Format
        • DXGI_FORMAT 열거형의 한 멤버
        • 4.1.3 텍스쳐 형식 참고
      • InputSlot
        • 성분의 자료를 가져올 정점 버퍼 슬롯 색인
        • 총 16개의 정점 버퍼 슬롯(0~15)를 통해서 정점 자료 공급 가능
      • AlignedByteOffset
        • 지정 입력 슬롯에서 정점 구조체의 시작 위치와 정점 성분의 시작 위치 사이의 거리를 나타내는 오프셋(바이트 단위)
        • Pos 성분 바이트 시작 ~ 12byte 이므로, 다음 성분인 Normal은 12byte가 시작위치이므로 offset 값 설정
      • InputSlotClass
        • 입력 슬롯의 데이터 클래스를 정의
        • 셰이더가 처리하는 데이터가 정점 데이터인지 아니면 인스턴스 데이터인지 구분하는 데 사용
          • D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA :
            • 슬롯이 정점 데이터를 포함
            • 각 정점에 대해 데이터가 셰이더로 전달
            • Instance 수 = 0
          • D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA :
            • 슬롯이 인스턴스 데이터를 포함
            • 각 인스턴스에 대해 데이터를 전달할 때 사용
            • 여러 오브젝트를 인스턴스 렌더링할 때, 각각의 인스턴스에 고유한 데이터가 필요
      • InstanceDataStepRate
        • 인스턴스 데이터의 전달 주기를 제어
        • 셰이더가 데이터를 몇 개의 인스턴스마다 업데이트해야 하는지를 결정
          • 1 : 모든 인스턴스에 대해 새로운 데이터를 제공
          • N : 값이 2라면 두 개의 인스턴스가 동일한 데이터를 공유
        • 인스턴스 데이터 사용 시만 적용(PER_INSTANCE_DATA)

6.2 정점 버퍼

  • 정점버퍼 : 정점들을 저장하는 버퍼
    • GPU가 정점 배열에 접근하려면, 정점들을 버퍼라고 부르는 GPU자원 (ID3D12Resource)에 넣어 두어야 함
  • 버퍼는 텍스처보다 단순 자원
    • 다차원 X
    • 밉맵, 다중표본화 기능 X
  • 정점 같은 자료 원소들의 배열을 GPU에 제공할 떄, 항상 버퍼 사용
  • 버퍼 자원을 채우고, ID3D12Device::CreateCommittedResource 메서드를 호출해서
    ID3D12Resource 객체 생성
  • 버퍼 자원 서술 : D3D12_RESOURCE_DESC
    • D12에서 제공하는 해당 구조체를 상속해서 쓰는 편의용 생성자들과 메서드들을 추가한 C++ 래퍼 클래스 CD3DX12_RESOURCE_DESC 제공
        static inline CD3DX12_RESOURCE_DESC Buffer(
            UINT64 width,
            D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FALG_NONE,
            UINT64 alignment = 0 )
        {
            return CD3DX12_RESOURCE_DESC(
                D3D12_RESOURCE_DIMENSION_BUFFER,
                aligment, width, 1, 1, 1,
                DXGI_FORMAT_UNKNOWN, 1, 0,
                D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags );
        }
      • width :
        • 버퍼의 바이트 개수를 나타냄
        • flaot 64개 담는 버퍼 => 64*sizeof(flaot)
      • D3D12_RESOURCE_DESC::D3D12_RESOURCE_DIMENSION 필드에 의해서 자원의 구체적인 종류 지정
      • CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(1024); // 1024바이트 버퍼 생성
  • 정적 기하구조(프레임마다 변하지는 않는 기하구조)를 그릴 떄, 최적 성능을 위해 정점 버퍼들을 기본 힙(D3D12_HEAP_TYPE_DEFAULT)에 넣음
    • 정점 버퍼를 초기화한 후에는 GPU만 버퍼의 정점들을 읽으므로(기하구조 그리기위해), 기본 힙에 넣는 것이 합당
    • CPU는 기본 힙에 있는 정점 버퍼 수정 불가

6.2.2 기본 버퍼 생성 편의용 함수 작성

  • 기본 버퍼 생성성

      Microsoft::WRL::ComPtr<ID3D12Resource> d3dUtil::CreateDefaultBuffer(
          ID3D12Device* device,
          ID3D12GraphicsCommandList* cmdList,
          const void* initData,
          UINT64 byteSize,
          Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
      {
          ComPtr<ID3D12Resource> defaultBuffer;
    
          // 실제 기본 버퍼 자원 생성
          ThrowIfFailed(device->CreateCommittedResource(
              &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
              D3D12_HEAP_FLAG_NONE,
              &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
              D3D12_RESOURCE_STATE_COMMON,
              nullptr,
              IID_PPV_ARGS(defaultBuffer.GetAddressOf())));
    
          // CPU 메모리를 기본 버퍼에 복사하기 위해, 임시 업로드 힙 생성
          ThrowIfFailed(device->CreateCommittedResource(
              &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
              D3D12_HEAP_FLAG_NONE,
              &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
              D3D12_RESOURCE_STATE_GENERIC_READ,
              nullptr,
              IID_PPV_ARGS(uploadBuffer.GetAddressOf())));
    
    
    // 기본 버퍼에 복사할 자료 서술
    D3D12_SUBRESOURCE_DATA subResourceData = {};
    subResourceData.pData = initData;
    subResourceData.RowPitch = byteSize;
    subResourceData.SlicePitch = subResourceData.RowPitch;

    // 기본 버퍼 자원으로의 자료 복사 요청
    // 보조 함수 UpdateSubresources는 CPU 메모리를 임시 업로드 힙에 복사,
    // ID3D12CommandList::CopySubresourceRegion을 이용해서 임시 업로드 힘의 자료를
    // mBuffer에 복사
    cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(), 
        D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST));
    UpdateSubresources<1>(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);
    cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
        D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ));

    // 주의 : 위의 함수 호출 이후에도 uploadBuffer를 계속 유지
    // 실제로 복사를 수행하는 명령 목록이 아직 실행 X
    // 복사 완료되었음이 확실해진 후에 호출자가 UploadBuffer 해제

    return defaultBuffer;
}
```
  • D3D12_SUBRESOURCE_DATA 구조체

      typedef struct D3D12_SUBRESOURCE_DATA
          {
          const void *pData;
          LONG_PTR RowPitch;
          LONG_PTR SlicePitch;
          }     D3D12_SUBRESOURCE_DATA;
    • pData :
      • 버퍼 초기화용 자료를 담은 시스템 메모리 배열 가리키는 포인터
      • 버퍼에 n 개의 정점을 담을 수 있을 떄, 전체를 초기화 하려면 시스템 메모리 배열에 최소 n개의 정점 필요
    • RowPitch :
      • 버퍼의 경우, 복사할 자료이 크기(바이트 개수)
    • SlicePitch :
      • 버퍼의 경우, 복사할 자료이 크기(바이트 개수)
  • 정점 버퍼 기본 버퍼 예시

      Vertex vertices[] = {
          { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(COLORS::White)},
          ...    
      };
    
      const UINT vbByteSize = 8 * sizeof(Vertex); // 8개 정점
    
      ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
      ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
    VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
          mCommandList.Get(), vertices, vbByteSize, VertexBufferUploader);
          // 사용자 정의 정점 버퍼 생성 메서드
      D3D12_VERTEX_BUFFER_VIEW vbv;
      vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
      vbv.StrideInBytes = sizeof(Vertex);
      vbv.SizeInByte = vbByteSize;
    
      D3D12_VERTEX_BUFFER_VIEW vertexBuffers[1] = { vbv };
      mCommandList->IASetVertexBuffers(0, 1, vertexBuffers);
    • RTV와 달리 정점 버퍼 뷰에는 서술자 힙이 필요x
  • 정점 버퍼 뷰 대표 형식 D3D12_VERTEX_BUFFER_VIEW_DESC 구조체

      typedef struct D3D12_VERTEX_BUFFER_VIEW
      {
          D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
          UINT SizeInBytes;
          UINT StrideInBytes;
      }     D3D12_VERTEX_BUFFER_VIEW;
    • BufferLocation :
      • 생성할 뷰의 대상이 되는 정점 버퍼 자원의 가상 주소
      • ID3D12Resource::GetGPUVirtualAddress 메서드로 얻을 수 있음
    • SizeInBytes:
      • BufferLocation에서 시작하는 정점 버퍼의 크기(바이트 개수)
    • StrideInBytes :
      • 버퍼에 담긴 한 정점의 원소의 크기 (바이트 개수)
  • 정점 버퍼를 IA 파이프라인에 묶는 메서드

      void ID3D12GraphicsCommandList::IASetVertexBuffers(
          UINT StartSlot,
          UINT NumBuffers,
          const D3D12_VERTEX_BUFFER_VIEW *pViews );
    • StartSlot :
      • 시작슬롯, 첫쨰 정점 버퍼를 묶을 입력 슬롯의 색인
      • 총 16개 (0~15)
    • NumBuffers :
      • 입력 슬롯들에 묶을 정점 버퍼 개수
    • pViews :
      • 정점 버퍼 뷰 배열의 첫 원소를 가리키는 포인터
  • 정점들을 이용해 기본도형을 그리려면 ID3D12GraphicsCommandList::DrawInstanced 메서드 사용

      void ID3D12GraphicsCommandList::DrawInstanced(
          UINT VertexCountPerInstance,
          UINT InstanceCount,
          UINT StartVertexLocation,
          UINT StartInstanceLocation);
    • VertexCountPerInstance : 그리기에 사용할 정점들의 개수 (인스턴스 당)
    • InstanceCount : 그릴 인스턴스 개수
    • StartVertexLocation :
      • 정점 버퍼의 시작 위치를 지정;
      • 정점 버퍼의 색인들 중 이 그리기 호출에서 사용할 첫 색인( 0 기반)
    • StartInstanceLocation :
      • 첫 번째 인스턴스의 시작 위치 지정
      • 특정 인스턴스부터 렌더링을 시작 가능
  • 위상구조 상태 결정은 5.5.2 참고

    • cmdList->IASetPrivitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST)

6.3 색인과 색인 버퍼

  • 색인 버퍼

    • GPU가 색인들의 배열에 접근 할 수 있을려면, 색인들을 GPU 자원에 넣어 두어야 함
  • 색인 버퍼 뷰 대표 형식

    • D3D12_INDEX_BUFFER_VIEW 구조체
        typedef struct D3D12_INDEX_BUFFER_VIEW
        {
            D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
            UINT SizeInBytes;
            DXGI_FORMAT Format;
        } D3D12_INDEX_BUFFER_VIEW;
      • BufferLoation
        • 뷰의 대상이 되는 정점 버퍼 자원의 가상 주소
        • ID3D12Resource::GetGPUVirtualAddress 메서드로 얻을 수 있음
      • SizeInBytes
        • BufferLocation에서 시작하는 색인 버퍼 크기 (바이트 개수)
      • Format
        • 색인 형식 (R16_INT, R32_UINT) 사용
        • 메모리와 대역폭을 절약할려면 16비트 사용
  • ID3D12CommandList::IASetIndexBuffer 메서드를 통해서 입력 조립기 단계에 묶음

  • 사각형 색인 버퍼 IA바인드 예시

      std::uint16_t indices[] = {
          // 앞면
          0, 1, 2,
          0, 2, 3,
          // 뒷면
          ...
      };
    
      const UINT ibByteSize = 36 * sizeof(std::uint_16_t); // 36개 정점
    
      ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
      ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
      IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
          mCommandList.Get(), indices, ibByteSize, IndexBufferUploader);
          // 사용자 정의 정점 버퍼 생성 메서드
      D3D12_INDEX_BUFFER_VIEW ibv;
      ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
      ibv.Foramt = DXGI_FORMAT_R16_UINT;
      ibv.SizeInBytes = ibByteSize;
    
      mCommandList->IASetIndexBuffer(&ibv);
  • 색인들을 이용해 기본도형을 그리려면 ID3D12GraphicsCommandList::DrawIndexedInstanced 메서드 사용

      void ID3D12GraphicsCommandList::DrawIndexedInstanced(
          UINT IndexCountPerInstance,
          UINT InstanceCount,
          UINT StartIndexLocation,
          INT BaseVertexLocation,
          UINT StartInstanceLocation);
    • IndexCountPerInstance : 그리기에 사용할 색인들의 개수 (인스턴스 당)
    • InstanceCount : 그릴 인스턴스 개수
    • StartIndexLocation :
      • 인덱스 버퍼의 시작 위치를 지정
      • 색인 버퍼의 색인들 중 이 그리기 호출에서 사용할 첫 색인( 0 기반)
    • BaseVertexLocation :
      • 정점 버퍼에서의 기준 오프셋 지정
      • 호출에 쓰이는 색인들에 더할 정수 값, 더한 결과를 최종 색인으로사용해서 정점 버퍼에서 정점 가져옴, 음수 값 허용
    • StartInstanceLocation :
      • 첫 번째 인스턴스의 시작 위치 지정
      • 특정 인스턴스부터 렌더링을 시작 가능

6.4 예제 정점 셰이더

  • 셰이더는 HLSL(High Level Shading Language)로 작성
  • 정점 셰이더는 하나의 함수
  • 매개 변수 4 개 중 앞의 2개는 입력 매개변수, 뒤의 2개는 출력 매개변수
    • 입력 매개변수는 입력 서명(input signature)을 형성
      • 현재의 그리기 작업에 쓰이는 커스텀 정점 구조체의 멤버들에 대응
    • 출력 매개변수
      • 정점 셰이더의 출력을 파이프라인 다음 단계(기하셰이더, 픽셀셰이더)의 해당 입력에 대응 시키는 역할
      • SV_POSITION
        • SV는 SystemValue 라는 의미를 담고 있음
        • GPU는 SV_POSITION으로 명시된 데이터를 하드웨어 수준에서 특별히 처리
        • 렌더링 엔진과 DirectX가 정점의 위치 데이터를 명확히 처리할 수 있도록 보장
      • COLOR
        • D3D12_INPUT_ELEMENT_DESC 배열을 통해 지정한 이름
        • HLSL의 유효 식별자이기만 하면, 아무 이름이나 상관 x
  • HLSL에는 참조나 포인터가 없어, 함수가 여러개의 값을 돌려주려면 구조체 사용 or out이 지정된 출력 매개변수 사용
  • HLSL에서 함수는 항상 인라인화
      void VS(
          float iPosL : POSITION,
          float iColor : COLOR,
          out float4 oPOSH : SV_POSTIION,   // SV는 SystemValue 라는 의미를 담고 있음
          out float4 oColor : COLOR)
      {
          // 동차 절단 공간 변환
          oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
          // 정점 색상을 그대로 픽셀 셰이더에 전달
          oColor = iColor;
      }
    • mul 함수 :
      • HLSL 내장 함수
      • 벡터 대 행렬 곱셈 수행
    • gWorldViewProj :
      • 상수 버퍼라고 부르는 버퍼에 들어 있는 것
  • 기하 셰이더 사용 시, 동차 절단 공간 위치의 출력을 기하 셰이더에 미룰 수 있음
  • 원근 나누기는 나중에 하드웨어가 수행하기에, 투영 핼렬 곱하는 부분만 책임

6.4.1 입력 배치 서술과 입력 서명 연결

  • 입력 배치 서술
    • 파이프라인에 공급되는 정점들의 특성들과 정점 셰이더의 매개변수들 사이의 연관 관계 정의
    • 정점 셰이더가 기대하는 모든 입력을 제공하지못하면 오류 발생
      • 추가 공급은 되지만, 적은 건 용납 x
      • D3D에서 입력 레지스터 비트들의 재해석을 허용하기에 float -> int 의 공급을 허용하지만 VC++의 디버그 출력 창은 경고 메시지
        //--------------
        // C++ app code
        //--------------
        struct Vertex
        {
        XMFLOAT3 Pos;
        XMFLOAT4 Color;
        XMFLOAT3 Normal;
        };
        D3D12_INPUT_ELEMENT_DESC desc[] =
        {
        {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0};
           D3D12_INPUT_PER_VERTEX_DATA, 0 },
        { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
          D3D12_INPUT_PER_VERTEX_DATA, 0 },
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 28,
          D3D12_INPUT_PER_VERTEX_DATA, 0 }
        //--------------
        // Vertex shader
        //--------------
        struct VertexIn
        {
        float3 PosL : POSITION;
        float4 Color : COLOR;
        };
        struct VertexOut
        {
        float4 PosH : SV_POSITION;
        float4 Color : COLOR;
        };

'프레임워크 > DirectX' 카테고리의 다른 글

06.그리기_연산01_2  (7) 2025.04.08
06.그리기_연산01_1  (0) 2025.04.05
05. 렌더링파이프 개념문제 개인제작  (1) 2025.03.15
05. 렌더링 파이프라인  (7) 2025.03.11
04.Direct3D의_초기화03_요약그림  (0) 2025.03.07

+ Recent posts

let textNodes = document.querySelectorAll("div.tt_article_useless_p_margin.contents_style > *:not(figure):not(pre)"); textNodes.forEach(function(a) { a.innerHTML = a.innerHTML.replace(/`(.*?)`/g, '$1'); });