HazelEngine 学习记录 - Vertex Buffer Layouts

Vertex Buffer Layouts

本节主要是完成 VertexBuffer Layouts 的抽象,

关于 Buffer Layout ,实际上就是指明了顶点缓冲区中数据的布局方式,例如我在一个顶点缓冲区中存放的数据是这样的:

对于每个顶点,前 3 个浮点数存放的是顶点的 Position,之后 4 个浮点数是该顶点的颜色,在之后 3 个浮点数是 Normal,之后两个浮点数是纹理坐标,也就是说,每个顶点我们的数据是 12 个浮点数,那么要如何让 GPU 知道我们的数据是怎么排布的呢?这就要用到 glEnableVertexAttribArrayglVertexAttribPointer 来对我们的数据布局进行描述

先观察这两个函数:

		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (const void*)0);
  • 第一个参数指定我们要配置的顶点属性在布局中处于什么位置,例如第一个部分存储的是顶点位置,那么0对应的就是顶点位置的布局。在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置
  • 第二个参数指定顶点某个属性的大小。如果该属性由三个浮点数组成,那么就是3。
  • 第三个参数指定数据的类型,这里是GL_FLOAT。
  • 下个参数定义我们是否希望数据被归一化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
  • 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。可以理解为每一个顶点的所有属性所占的长度。
  • 最后一个参数的类型是void*,数据指针,代表的是这个顶点属性的偏移量,第一个定点属性的偏移量是 0 ,之后每一个属性依次向后偏移。

用一个更具体的例子来解释这两个函数:

	Application::Application()
	{
		HZ_CORE_ASSERT(!s_Instance, "Application already exists!");
		s_Instance = this;
 
		m_Window = std::unique_ptr<Window>(Window::Create());
		m_Window->SetEventCallback(BIND_EVENT_FN(OnEvent));
 
		m_ImGuiLayer = new ImGuiLayer();
		PushOverLay(m_ImGuiLayer);
 
		float vertices[3 * 7] = {									//一共有 3 个顶点,每个顶点两个属性,七个浮点值组成
			-0.5f,-0.5f,0.0f,	1.0f,0.0f,0.0f,1.0f,	//前三个 float 代表顶点位置,之后四个代表顶点对应颜色
			 0.5f,-0.5f,0.0f,	0.0f,1.0f,0.0f,1.0f,
			 0.0f, 0.5f,0.0f,	0.0f,0.0f,1.0f,1.0f,
 
		};
		unsigned int indices[3] = { 0,1,2 };
 
		glGenVertexArrays(1, &m_VertexArray);
		glBindVertexArray(m_VertexArray);
		m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));
		m_IndexBuffer.reset(IndexBuffer::Create(indices, 3));
 
 
		glEnableVertexAttribArray(0);			// 0 代表的是顶点位置
		glEnableVertexAttribArray(1);			// 1 代表的是顶点颜色
 
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), nullptr);
		glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)12);
 
 
		const 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);
				}
				)";
		const std::string fragmentSrc = R"(
				#version 330 core
				
				layout(location = 0) out vec4 color;
				layout(location = 1) out vec4 color1;
				in vec3 v_Position;
				in vec4 v_Color;
				
				void main()
				{
					color = v_Color;//vec4(v_Position*0.5+0.5, 1.0);
				}
				)";
		
		m_Shader.reset(new Shader(vertexSrc, fragmentSrc));
	}

之后就可以进行其他部分了:

首先定义 BufferElement 也就是顶点属性:

struct BufferElement
	{
		std::string Name;				
		ShaderDataType Type;
		uint32_t Size;
		uint32_t Offset;
		bool Normalized;

		BufferElement() = default;
		BufferElement(ShaderDataType type, const std::string& name, bool normalized = false)
			: Type(type), Name(name), 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;
		}
	};

之后定义属性的数据类型以及大小:

enum class ShaderDataType
	{
		None = 0, Float, Float2, Float3, Float4, Mat3, Mat4, Int, Int2, Int3, Int4, Bool
	};
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;
	}

然后就可以定义 VertexBuffer Layout 了

	class BufferLayout
	{
	public:

		BufferLayout() {}
		BufferLayout(const std::initializer_list<BufferElement>& elements)
		: m_Elements(elements)
		{
			CalculateOffsetAndStride();
		}

		inline uint32_t GetStride() const { return m_Stride; }
		inline const std::vector<BufferElement>& GetElements() const { return m_Elements; }

		std::vector<BufferElement>::const_iterator begin() const { return m_Elements.begin(); }
		std::vector<BufferElement>::const_iterator end() const { return m_Elements.end(); }
	private:
		void CalculateOffsetAndStride()
		{
			uint32_t offset = 0;
			m_Stride = 0;
			for (auto& element : m_Elements)
			{
				element.Offset = offset;		//初始化每个element的 offset 并计算总步长
				offset += element.Size;
				m_Stride += element.Size;
			}
		}
	private:
		std::vector<BufferElement> m_Elements;  //	存放每一个顶点属性对应一个 Element
		uint32_t m_Stride = 0;									//步长,代表一个顶点的所有数据的长度
	};

在 Layout 中使用的是 const_iterator,是因为 GetLayout 的返回值是 const Vector 因此迭代器也要用 const 类型。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mdJwaJVy-1665542651148)(/Users/liangjie/Library/Application Support/typora-user-images/image-20221011160531916.png)]

Application.cpp 中,添加 layout 并使用

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;
		}

		HZ_CORE_ASSERT(false, "Unknown ShaderDataType!");
		return 0;
	}



Application(){
  ...
    glGenVertexArrays(1, &m_VertexArray);
		glBindVertexArray(m_VertexArray);

		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.3f, 0.8f, 0.0f, 1.0f
		};
		
		m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));

		{
			BufferLayout layout = {
				{ ShaderDataType::Float3, "a_Position", false},
				{ ShaderDataType::Float4, "a_Color", false}
			};

			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++;
		}
}

运行结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值