Hazel游戏引擎(031)顶点缓冲区布局抽象

文中若有代码、术语等错误,欢迎指正

前言

  • 此节目的

    抽象顶点缓冲布局类:给出对应顶点着色器输入一样的格式,使能够自动计算每个属性的偏移量、分量大小,总大小,而不用手动计算

  • 更详细讨论:为什么要顶点缓冲区布局抽象类

    • directx有缓冲区布局

    • 有了后可以自动计算大小和偏移量,以及能在布局就能看到顶点缓冲区、顶点着色器输入数据的结构,而不用手动计算

      // 手动计算与输入
      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
      
  • 从Api出发设计抽象顶点缓冲布局类

    BufferLayout layout = {
          
          //BufferLayout是顶点缓冲布局类
        {
          
           ShaderDataType::Float3, "a_Position" },// 一个元素是BufferElement类
        {
          
           ShaderDataType::Float4, "a_Color" }
    };
    m_VertexBuffer->SetLayout(layout);//  顶点缓冲布局类与顶点缓冲交互
    // 设置顶点属性指针
    glEnableVertexAttribArray(index);
    glVertexAttribPointer(index,element.GetComponentCount(), ShaderDataTypeToOpenGLBaseType(element.Type),element.Normalized ? GL_TRUE : GL_FALSE,layout.GetStride(),(const void*)element.Offset);// 使用顶点缓冲布局类的函数
    
  • 此节完成后的类图

    请添加图片描述

项目相关

代码

  • 在Buffer.h增加顶点属性布局抽象类

    // Shdader数据类型
    enum class ShaderDataType{
          
          
        None = 0, Float, Float2, Float3, Float4, Mat3, Mat4, Int, Int2, Int3, Int4, Bool
    };
    // 获取Shader数据类型的大小
    static uint32_t ShaderDataTypeSize(ShaderDataType type){
          
          
        switch (type){
          
          
            case ShaderDataType::Float:    return 4;
            case ShaderDataType::Float2:   return 4 * 2;
            case ShaderDataType::Float3:   return 4 * 3;
            case ShaderDataType::Float4:   return 4 * 4;
            case ShaderDataType::Mat3:     return 4 * 3 * 3;
            case ShaderDataType::Mat4:     return 4 * 4 * 4;
            case ShaderDataType::Int:      return 4;
            case ShaderDataType::Int2:     return 4 * 2;
            case ShaderDataType::Int3:     return 4 * 3;
            case ShaderDataType::Int4:     return 4 * 4;
            case ShaderDataType::Bool:     return 1;
        }
        HZ_CORE_ASSERT(false, "Unknown ShaderDataType!");
        return 0;
    }
    // 一个Shader属性类
    struct BufferElement{
          
           	// 对应:{ ShaderDataType::Float3, "a_Position" }
        std::string Name;	// 名称:没太大作用,只是标识而已
        ShaderDataType Type;// Shader数据类型
        uint32_t Size;		// 此属性的大小
        uint32_t Offset;	// 此属性的偏移量
        bool Normalized;	// 是否规范化
        BufferElement() {
          
          }
        BufferElement(ShaderDataType type, const std::string& name, bool normalized = false)
            : Name(name), Type(type), Size(ShaderDataTypeSize(type)), Offset(0), Normalized(normalized)
            {
          
           }
        // 获取此属性有几个分量
        uint32_t GetComponentCount() const{
          
          
            switch (Type) {
          
          
                case ShaderDataType::Float:   return 1;
                case ShaderDataType::Float2:  return 2;
                case ShaderDataType::Float3:  return 3;
                case ShaderDataType::Float4:  return 4;
                case ShaderDataType::Mat3:    return 3 * 3;
                case ShaderDataType::Mat4:    return 4 * 4;
                case ShaderDataType::Int:     return 1;
                case ShaderDataType::Int2:    return 2;
                case ShaderDataType::Int3:    return 3;
                case ShaderDataType::Int4:    return 4;
                case ShaderDataType::Bool:    return 1;
            }
            HZ_CORE_ASSERT(false, "Unknown ShaderDataType!");
            return 0;
        }
    };
    // 顶点缓冲布局抽象类
    class BufferLayout{
          
          
        public:
        BufferLayout() {
          
          }
        // 用初始化列表构造BufferLayout对象
        BufferLayout(const std::initializer_list<BufferElement>& elements)
            : m_Elements(elements)
            {
          
          
                CalculateOffsetsAndStride();
            }
        inline uint32_t GetStride() const {
          
           return m_Stride; }// 获取属性列表总大小
        inline const std::vector<BufferElement>& GetElements() const {
          
           return m_Elements; }// 获取元素列表
        // 写vector的begin与end,以便循环BufferLayout类 for (const auto& element : BufferLayout)
        std::vector<BufferElement>::iterator begin() {
          
           return m_Elements.begin(); }
        std::vector<BufferElement>::iterator end() {
          
           return m_Elements.end(); }
        std::vector<BufferElement>::const_iterator begin() const {
          
           return m_Elements.begin(); }
        std::vector<BufferElement>::const_iterator end() const {
          
           return m_Elements.end(); }
    private:
        //
        // 计算属性列表各个属性的偏移量,以及总大小//
        void CalculateOffsetsAndStride(){
          
          
            uint32_t offset = 0;
            m_Stride = 0;
            for (auto& element : m_Elements){
          
          
                element.Offset = offset;
                offset += element.Size;
                m_Stride += element.Size;
            }
        }
        private:
        std::vector<BufferElement> m_Elements;
        uint32_t m_Stride = 0;
    };
    
  • Application.cpp

    // 将自定义的Shader类型转换为OpenGL定义的类型:临时的,会放在顶点数组抽象类中
    static GLenum ShaderDataTypeToOpenGLBaseType(ShaderDataType type){
          
          
        switch (type){
          
          
            case Hazel::ShaderDataType::Float:    return GL_FLOAT;
            case Hazel::ShaderDataType::Float2:   return GL_FLOAT;
            case Hazel::ShaderDataType::Float3:   return GL_FLOAT;
            case Hazel::ShaderDataType::Float4:   return GL_FLOAT;
            case Hazel::ShaderDataType::Mat3:     return GL_FLOAT;
            case Hazel::ShaderDataType::Mat4:     return GL_FLOAT;
            case Hazel::ShaderDataType::Int:      return GL_INT;
            case Hazel::ShaderDataType::Int2:     return GL_INT;
            case Hazel::ShaderDataType::Int3:     return GL_INT;
            case Hazel::ShaderDataType::Int4:     return GL_INT;
            case Hazel::ShaderDataType::Bool:     return GL_BOOL;
        }
        return 0;
    }
    Application::Application(){
          
          
        // 使用OpenGL函数渲染一个三角形
        // 顶点数据
        float vertices[3 * 7] = {
          
          
            -0.5f, -0.5f, 0.0f, 0.8f, 0.2f, 0.8f, 1.0f,
            0.5f, -0.5f, 0.0f, 0.2f, 0.3f, 0.8f, 1.0f,
            0.0f,  0.5f, 0.0f, 0.8f, 0.8f, 0.2f, 1.0f
        };
        unsigned int indices[3] = {
          
           0, 1, 2 }; // 索引数据
        // 0.生成顶点数组对象VAO、顶点缓冲对象VBO、索引缓冲对象EBO
        glGenVertexArrays(1, &m_VertexArray);
        // 1. 绑定顶点数组对象
        glBindVertexArray(m_VertexArray);
        // 2.1顶点缓冲
        m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));
        
        // 2.2 设定顶点属性指针,来解释顶点缓冲中的顶点属性布局/
        
        {
          
          
            BufferLayout layout = {
          
          
                {
          
           ShaderDataType::Float3, "a_Position" },
                {
          
           ShaderDataType::Float4, "a_Color" }
            };
            m_VertexBuffer->SetLayout(layout);
        }
        // :临时的,会放在顶点数组抽象类中
        uint32_t index = 0;
        const auto& layout = m_VertexBuffer->GetLayout();
        for (const auto& element : layout){
          
          
            glEnableVertexAttribArray(index);
            glVertexAttribPointer(index,
                                  element.GetComponentCount(),
                                  ShaderDataTypeToOpenGLBaseType(element.Type),
                                  element.Normalized ? GL_TRUE : GL_FALSE,
                                  layout.GetStride(),
                                  (const void*)element.Offset);
            index++;
        }
        // 之前手动
        //glEnableVertexAttribArray(0);// 开启glsl的layout = 0输入
        //glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
        // 3.索引缓冲
        m_IndexBuffer.reset(IndexBuffer::Create(indices, sizeof(indices) / sizeof(uint32_t)));
        // 着色器代码
        std::string vertexSrc = R"(
    			#version 330 core
    
    			layout(location = 0) in vec3 a_Position;
    			layout(location = 1) in vec4 a_Color;
    			out vec3 v_Position;
    			out vec4 v_Color;
    			void main()
    			{
    				v_Position = a_Position;
    				v_Color = a_Color;
    				gl_Position = vec4(a_Position, 1.0);	
    			}
    		)";
        std::string fragmentSrc = R"(
    			#version 330 core
    			layout(location = 0) out vec4 color;
    			in vec3 v_Position;
    			in vec4 v_Color;
    			void main()
    			{
    				color = vec4(v_Position * 0.5 + 0.5, 1.0);
    				color = v_Color;
    			}
    		)";
        m_Shader.reset(new Shader(vertexSrc, fragmentSrc));
    

效果

C++知识:初始化列表

  • 引入

    在Buffer.h的BufferLayout的构造函数使用初始化列表(initializer_list)构造,而不是使用vector引用(vector&)来构造,这可以减少BufferElement调用复制构造函数的次数

    • 初始化列表:initializer_list

      BufferLayout(const std::initializer_list<BufferElement>& elements)
          : m_Elements(elements)
          {
              
              
              CalculateOffsetsAndStride();
          }
      BufferLayout layout = {
              
              
          {
              
               ShaderDataType::Float3, "a_Position" },
          {
              
               ShaderDataType::Float4, "a_Color" }
      };
      m_VertexBuffer->SetLayout(layout);
      
    • vector引用:vector&

      BufferLayout(const std::vector<BufferElement>& elements)
          : m_Elements(elements)
          {
              
              
              CalculateOffsetsAndStride();
          }
      // 注意:这里变成vector
      std::vector<BufferElement> vec1 = {
              
              
          {
              
               ShaderDataType::Float3, "a_Position" },
          {
              
               ShaderDataType::Float4, "a_Color" }
      };
      m_VertexBuffer->SetLayout(vec1);
      
  • 说明vector引用会多调用几次复制构造函数

    #include <iostream>
    #include <vector>
    using namespace std;
    
    class TestClass {
          
          
    public:
    	TestClass() :val(0) {
          
           cout << "TestClass()" << endl; }
    	TestClass(int v) :val(v) {
          
           cout << "TestClass(int v)" << endl; }
    	// 复制构造函数
    	TestClass(const TestClass& b) {
          
          
    		cout << "TestClass的复制构造函数" << endl;
    		val = b.val;
    	}
    	int val;
    };
    class TestVectorClass {
          
          
    public:
    	TestVectorClass(){
          
          }
    	TestVectorClass(vector<TestClass>& v):vec(v) {
          
          
    		cout << "--vector参数引用构造TestVectorClass结束--" << endl;
    	}
    	TestVectorClass(const initializer_list<TestClass>& v):vec(v){
          
          
    		cout << "--直接初始化列表构造TestVectorClass结束--" << endl;
    	}
    	vector<TestClass> vec;
    };
    int main() {
          
          
    	cout << "--vector<TestClass>初始化开始--" << endl;
    	// vector<TestClass>初始化,{1, 2, 3}会先调用构造函数临时对象,再调用TestClass复制构造函数给vec的下标元素
    	vector<TestClass> vec = {
          
          1, 2, 3};
    	cout << "--vector<TestClass&>初始化结束--" << endl;
    
    	cout << "--vector参数引用构造TestVectorClass开始--" << endl;
    	TestVectorClass tv1(vec);
    	cout << "----------------------------" << endl;
    
    	cout << "--直接初始化列表构造TestVectorClass开始--" << endl;
    	TestVectorClass tv = {
          
           1, 2, 3 };
    	return 0;
    };
    

    请添加图片描述

顶点缓冲区布局和着色器<联系>在一起的草稿图

请添加图片描述

猜你喜欢

转载自blog.csdn.net/qq_34060370/article/details/131408349
今日推荐