【Overload游戏引擎分析】UBO与SSBO的封装

一、OpenGL的UBO

  在OpenGL Shader中,如果逻辑比较复杂,使用的uniform变量较多。通常多个着色器使用同一个uniform变量。由于uniform变量的位置是着色器链接时候产生的,因此它在应用程序中获得的索引会有变化。Uniform Buffer Object(UBO)是一种优化uniform变量访问,不同着色器直接共享unfiorm数据的方法。  

在Overload引擎中,很多Shader包含如下片段,这里就是定义了一个UBO变量。 它将MVP矩阵一起放入到UBO变量中。

layout (std140) uniform EngineUBO
{
    mat4    ubo_Model;
    mat4    ubo_View;
    mat4    ubo_Projection;
    vec3    ubo_ViewPos;
    float   ubo_Time;
};

std140是内存布局限定符,除此之外还有std430、binding、packed等限定符。

二·、Overload对UBO的封装

   Overload引擎中对UBO的封装在UniformBuffer.h、UniformBuffer.inl、UniformBuffer.cpp文件中,将其操作包装成了一个类UniformBuffer。使用的时候先调用Bind,结束后UnBind,设置值使用SetSubData。

namespace OvRendering::Buffers
{
	/**
	* OpenGL UBO的封装
	*/
	class UniformBuffer
	{
	public:
		/**
		* Create a UniformBuffer
		* @param p_size (Specify the size in bytes of the UBO data)
		* @param p_bindingPoint (Specify the binding point on which the uniform buffer should be binded)
		* @parma p_offset (The offset of the UBO, sizeof previouses UBO if the binding point is != 0)
		* @param p_accessSpecifier
		*/
		UniformBuffer(size_t p_size, uint32_t p_bindingPoint = 0, uint32_t p_offset = 0, EAccessSpecifier p_accessSpecifier = EAccessSpecifier::DYNAMIC_DRAW);

		/**
		* Destructor of the UniformBuffer
		*/
		~UniformBuffer();

		/**
		* Bind the UBO
		*/
		void Bind();

		/**
		* Unbind the UBO
		*/
		void Unbind();

		/**
		* Set the data in the UBO located at p_offset to p_data
		* @param p_data
		* @param p_offset
		*/
		template
		void SetSubData(const T& p_data, size_t p_offset);

		/**
		* Set the data in the UBO located at p_offset to p_data
		* @param p_data
		* @param p_offsetInOut (Will keep track of the current stride of the data layout)
		*/
		template
		void SetSubData(const T& p_data, std::reference_wrapper p_offsetInOut);

		/**
		* Return the ID of the UBO
		*/
		uint32_t GetID() const;

		/**
		* Bind a block identified by the given ID to given shader
		* @param p_shader
		* @param p_uniformBlockLocation
		* @param p_bindingPoint
		*/
		static void BindBlockToShader(OvRendering::Resources::Shader& p_shader, uint32_t p_uniformBlockLocation, uint32_t p_bindingPoint = 0);

		/**
		* Bind a block identified by the given name to the given shader
		* @param p_shader
		* @param p_name
		* @param p_bindingPoint
		*/
		static void BindBlockToShader(OvRendering::Resources::Shader& p_shader, const std::string& p_name, uint32_t p_bindingPoint = 0);

		/**
		* Return the location of the block (ID)
		* @param p_shader
		* @param p_name
		*/
		static uint32_t GetBlockLocation(OvRendering::Resources::Shader& p_shader, const std::string& p_name);

	private:
		uint32_t m_bufferID;
	};
}

#include "OvRendering/Buffers/UniformBuffer.inl"

其具体实现在UniformBuffer.cpp中。我们先看看构造函数代码:

OvRendering::Buffers::UniformBuffer::UniformBuffer(size_t p_size, uint32_t p_bindingPoint, uint32_t p_offset, EAccessSpecifier p_accessSpecifier)
{
	// 生成buffer
	glGenBuffers(1, &m_bufferID);
	// 绑定UBO
	glBindBuffer(GL_UNIFORM_BUFFER, m_bufferID);
	// 分配内存
	glBufferData(GL_UNIFORM_BUFFER, p_size, NULL, static_cast(p_accessSpecifier));
	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	// 将缓存对象m_bufferID绑定到索引为p_bindingPoint的UBO上
	glBindBufferRange(GL_UNIFORM_BUFFER, p_bindingPoint, m_bufferID, p_offset, p_size);
}

在构造函数中直接创建了UBO的buffer,并绑定到索引是p_bindingPoint的UBO上。这里用到了OpenGL函数glBindBufferRange,如无需指定偏移量与size值可使用glBindBufferBase函数。

UniformBuffer.cpp中Bind()、UnBind()过于简单不再分析。往下接着看有个static函数BindBlockToShader。这个函数主要是显式绑定一个uniform块到p_bindingPoint索引,这样可以绑定同一个缓存。这里使用到了glUniformBlockBinding函数,这个函数主要是显示指定BUO的索引,可以保证多个不同的Shader程序之间UBO的索引是一样的,但需要在调用glLinkProgram之前调用。

void OvRendering::Buffers::UniformBuffer::BindBlockToShader(OvRendering::Resources::Shader& p_shader, uint32_t p_uniformBlockLocation, uint32_t p_bindingPoint)
{
	glUniformBlockBinding(p_shader.id, p_uniformBlockLocation, p_bindingPoint);
}

void OvRendering::Buffers::UniformBuffer::BindBlockToShader(OvRendering::Resources::Shader& p_shader, const std::string& p_name, uint32_t p_bindingPoint)
{
	glUniformBlockBinding(p_shader.id, GetBlockLocation(p_shader, p_name), p_bindingPoint);
}

// 获取UBO的索引位置
uint32_t OvRendering::Buffers::UniformBuffer::GetBlockLocation(OvRendering::Resources::Shader& p_shader, const std::string& p_name)
{
	return glGetUniformBlockIndex(p_shader.id, p_name.c_str());
}

但在Overload引擎中,调用这个方法是在调用glProgram之后调用的,而且索引值使用的是GetBlockLocation获取的,这也是UBO在Shader的默认索引值,所以这个方法应该是可以删除的。我注释这个方法使用上没有发现什么问题。

最后看一下如何给UBO设置值,其实现是在UniformBuffer.inl文件中,主要使用glBufferSubData函数,指定其偏移值与数据大小即可。

	template
	inline void UniformBuffer::SetSubData(const T& p_data, size_t p_offsetInOut)
	{
		Bind();
		glBufferSubData(GL_UNIFORM_BUFFER, p_offsetInOut, sizeof(T), std::addressof(p_data));
		Unbind();
	}

	template
	inline void UniformBuffer::SetSubData(const T& p_data, std::reference_wrapper p_offsetInOut)
	{
		Bind();
		size_t dataSize = sizeof(T);
		glBufferSubData(GL_UNIFORM_BUFFER, p_offsetInOut.get(), dataSize, std::addressof(p_data));
		p_offsetInOut.get() += dataSize;
		Unbind();
	}

三、OpenGL的SSBO

Shader Storage Buffer Object(SSBO),着色器存储缓存对象,其行为类似于UBO,但其功能上更为强大。首先,着色器可以写入buffer块,修改其内容并呈现给其他Shader或应用程序本身。其次,可以在渲染之前再觉得其大小,而不是编译与链接时。在Overload中,灯光信息是用SSBO存储的,看以下Shader片段:

layout(std430, binding = 0) buffer LightSSBO
{
    mat4 ssbo_Lights[];
};

在着色器中可以使用length()获取ssbo_Lights的长度。

设置SSBO的方式与设置UBO类似,不过glBindBuffer()、glBindBufferRange()、glBindBufferBase()需要使用GL_SHADER_STORAGE_BUFFER作为目标参数。

四、Overload对SSBO的封装

Overload是将SSBO的操作封装到类ShaderStorageBuffer中,具体代码就不分析了,与UBO大同小异。

你可能感兴趣的:(游戏引擎)