[vk] Vertex buffers - Vertex input description

2021. 8. 14. 03:17그래픽스/vk

Vertex input description

1. Introduction

- 셰이더에 하드코딩된 vertex data를 vertex buffer로 수정할것이다

- 가장 쉬운 접근법인 CPU visible buffer 를생성하고 memcpy 로 데이터를 직접 복사하는것부터 시작할것이다.

- 그리고 이후에 고성능 메모리에 vertex data를 복사하기 위해

- 어떻게 staging buffer 를 사용할것인지를 살펴볼것이다.

 

2. Vertex shader

- 일단 vertex shader에서 하드코딩된 부분을 지우자

- vertex shader는 in 키워드를 사용하여  vertex buffer로부터 입력을 받는다.

#version 450

layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;

layout(location = 0) out vec3 fragColor;

void main() {
    gl_Position = vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

- inPositioninColor 변수는 vertex attributes이다.

- 그들은 vertex buffer에서 지정한 vertex마다 지정된 속성이다.

  (우리가 직접 두개의 배열로 vertex마다 color와 position을 하드코딩한것처럼)

- 이제 vertex shader를 다시 컴파일해야한다.

 

- fragColor 처럼 layout(location = x) annotations 은 나중에 입력을 목적으로 참조할수있도록하는 인덱스에 해당함
- dvec3 같은 타입은 64 비트 벡터와 같은 일부 타입들은 다중 슬롯을 사용한다는것을 알아야한다.
- 이는 location 인덱스를 2만큼 차지한다는것을 의미하며, 다음 인덱스가 최소한 2 이상 더 높아야한다 
    layout(location = 0) in dvec3 inPosition;                                                     
    layout(location = 2) in vec3 inColor;                                                          
- OpenGL wiki. 에서 layout 한정자에 대해 알아볼 수 있다.

 

 

 

 

3. Vertex data

- vertex data를 shader code에서 우리의 프로그램 코드의 배열로 옮겨야한다.

- 벡터 및 행렬과 같은 선형 대수 관련 유형을 제공하는 GLM 라이브러리를 포함하여 시작해야한다. 

#include <glm/glm.hpp>

- 새로운 구조체인 Vertex를 만들어 vertex shader내부에서 사용할 두 속성 을 멤버로 구성하자

struct Vertex {
    glm::vec2 pos;
    glm::vec3 color;
};

- GLM은 shader 언어에서 사용되는 벡터 타입과 정확히 일치하는 c++ 타입을 제공한다.

const std::vector<Vertex> vertices = {
    {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}},
    {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
    {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
};

- 이제 Vertex 구조체를 사용하여 vertex data의 배열을 명시하자.

- 우리는 이제 이전과 정확히 같은 color 와 position을 가지게 되었지만

- 지금은 이제 하나의 배열로 결합되었다.

- 이런 방식을 "interleaving vertex attributes" 라고 부른다.

 

4. Binding descriptions

- 이런 데이터 포맷을 일단 GPU메모리로 업로드 하고 vertex shader에 전달하는 방법을 Vulkan에게 알려야한다.

- 이런 정보를 전달하는데에는 두가지 구조체가 필요하다.

 

- 첫번째 구조체는 VkVertexInputBindingDescription 이다.

- Vertex 구조체에 올바른 데이터로 Description구조체를 채우기 위한 멤버함수를 추가해야한다.

struct Vertex {
    glm::vec2 pos;
    glm::vec3 color;

    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription bindingDescription{};

        return bindingDescription;
    }
};

- vertex binding 은 vertices의 전체 메모리로부터 데이터를 로드하는 속도(rate)를 기술한다.

 (A vertex binding describes at which rate to load data from memory throughout the vertices)

 

- 이것은 데이터 항목 사이에 몇개의 바이트가 있는지를 명시하고

  (It specifies the number of bytes between data entries and)

 

- 이것은 데이터가 정점단위인지, 인스턴스단위인지 명시하여 데이터를 이동시키는 속도?를 기술한다.

  (whether to move to the next data entry after each vertex or after each instance)

VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

- 모든 vertex마다의 데이터는 하나의 배열에 함께 저장되어있음.

- 그래서 하나의 바인딩만 생성하면된다. (binding 의 인덱스 0)

- binding: 이 구조체가 기술할 바인딩 번호 (binding의 배열에서 바인딩 인덱스를 지정함)

- stride :  entry에서 다음 entry 사이에 몇개의 bytes이 있는지 지정 

- inputRate : 아래와 같은 변수를 파라미터로 가질 수 있음

  • VK_VERTEX_INPUT_RATE_VERTEX: Move to the next data entry after each vertex
  • VK_VERTEX_INPUT_RATE_INSTANCE: Move to the next data entry after each instance

- instanced rendering 을 할경우 후자를 사용한다.

- 우리는 지금 instanced renndering을 사용하지 않기 때문에

- per-vertex 데이터를 고수할 것임.

 

 

 

 

 

5. Attribute descriptions

- 두번째 구조체 VkVertexInputAttributeDescription는 어떻게 vertex 입력을 처리할지를 기술한다.

- Vertex  구조체에 도우미 함수를 하나 더 추가하자.

#include <array>

...

static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
    std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};

    return attributeDescriptions;
}

- 프로토타입 함수에서 볼 수 있듯이, 두개의 구조체를 생성할것임.

- 이 구조체는  binding description에서 생성된 vertex data의 큰 덩어리로부터 어떻게 vertex attribute를 추출할것인지를 기술한다.

- 여기서는 두가지 attributes, position 과 color를 추출해야하므로 두개의 구조체가 필요하다.

 

attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);

- binding : Vulkan 에게 정점별 바인딩이 어디에서 오는지 알려줌

    (이 속성이 데이터를 가져오는 바인딩 번호)

- location : vertex shader안 input 에서의  location 지시자 번호 

    (shader 안에서 location 0 은 position이고, 두개의 32-bit float를 가짐.)

- format  : attribute의 데이터 타입을 설명함. 일반적으로 다음과 같은 셰이더 타입이 함께 사용됨.

    (color formats의 열거형을 사용)

  • float: VK_FORMAT_R32_SFLOAT
  • vec2: VK_FORMAT_R32G32_SFLOAT
  • vec3: VK_FORMAT_R32G32B32_SFLOAT
  • vec4: VK_FORMAT_R32G32B32A32_SFLOAT

- - - - format을 사용할때, color의 채널의 개수와, shader data 타입의 컴포넌트의 개수가 같아야한다.

- - - - 더 많은 채널을 사용하는것은 허용되지만, 암시적으로 제거됨.

- - - - 만일 채널의 수가 더 적으면, BGA 컴포넌트는 기본값으로 (0, 0, 1) 를 사용함.

- - - - 이 칼라 타입(SFLOAT, UINT, SINT) 과 bit width 또한 shader input 타입과 일치해야한다.   

  • ivec2: VK_FORMAT_R32G32_SINT, a 2-component vector of 32-bit signed integers
  • uvec4: VK_FORMAT_R32G32B32A32_UINT, a 4-component vector of 32-bit unsigned integers
  • double: VK_FORMAT_R64_SFLOAT, a double-precision (64-bit) float

- - - - format 파라미터는 암시적으로 attribute data의 바이트 사이즈를 정의한다.

offset : 읽을 정점별 데이터에서 의 시작점

 ( the offset parameter specifies the number of bytes since the start of the per-vertex data to read from)

attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);

 

- 색상 속성 또한 같은 방식,

offsetof(struct, member) : stddef.h의 매크로 함수. 
- member 변수가 구조체에서 메모리상 얼마나 떨어져있는지 계산해줌

6. Pipeline vertex input

- 이제 이 format에서 정점데이터를 얻어오기 위해서

- createGraphicsPipeline 함수 안의 vertexInputInfo 구조체를 참조하여 graphics pipeline를 손봐야함

- vertexInputInfo  를 아래와 같이 수정하자.

auto bindingDescription = Vertex::getBindingDescription();
auto attributeDescriptions = Vertex::getAttributeDescriptions();

vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

- 이 파이프라인은 이제 vertices의 포맷의  vertex data를 받아들일 준비가 되어있고,

- vertex shader로 전달할 준비가 되어있다.

- 만일 지금 유효성 검사를 활성화하고, 프로그램을 실행하면

- 바인딩된 정점버퍼이 없다는 오류를 볼 수있을 것이다.

- 다음 스텝에선 vertex buffer를 만들고, vertex data를 거기로 옮겨

- GPU가 접근가능하게 할것이다.

 

C++ code / Vertex shader / Fragment shader