[Análisis detallado del motor de juego de sobrecarga] Encapsulación de UBO y SSBO

1. UBO de OpenGL

  En OpenGL Shader, si la lógica es más compleja, se utilizan variables más uniformes. Por lo general, varios sombreadores utilizan la misma variable uniforme. Dado que la ubicación de la variable uniforme se genera cuando se vincula el sombreador, el índice que obtiene en la aplicación cambiará. Uniform Buffer Object (UBO) es un método que optimiza el acceso uniforme a variables y permite que diferentes sombreadores compartan directamente datos uniformes.  

En el motor Overload, muchos Shaders contienen los siguientes fragmentos, donde se define una variable UBO. Reúne la matriz MVP en la variable UBO.

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

std140 es el calificador de diseño de memoria, además de std430, vinculante, empaquetado y otros calificadores.

2. Encapsulación de UBO por sobrecarga

   El UBO en el motor Overload está encapsulado en los archivos UniformBuffer.h, UniformBuffer.inl y UniformBuffer.cpp, y sus operaciones están empaquetadas en una clase UniformBuffer. Cuando lo use, llame primero a Bind, luego a UnBind y establezca el valor usando 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<typename T>
		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<typename T>
		void SetSubData(const T& p_data, std::reference_wrapper<size_t> 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"

Su implementación específica está en UniformBuffer.cpp. Veamos primero el código del constructor:

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<GLint>(p_accessSpecifier));
	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	// 将缓存对象m_bufferID绑定到索引为p_bindingPoint的UBO上
	glBindBufferRange(GL_UNIFORM_BUFFER, p_bindingPoint, m_bufferID, p_offset, p_size);
}

El búfer UBO se crea directamente en el constructor y se vincula al UBO cuyo índice es p_bindingPoint. Aquí se utiliza la función OpenGL glBindBufferRange. Si no necesita especificar el valor de desplazamiento y tamaño, puede utilizar la función glBindBufferBase.

Bind() y UnBind() en UniformBuffer.cpp son demasiado simples y no se analizarán nuevamente. Mirando hacia abajo, hay una función estática BindBlockToShader. Esta función vincula principalmente explícitamente un bloque uniforme al índice p_bindingPoint para que se pueda vincular el mismo caché. Aquí se utiliza la función glUniformBlockBinding. Esta función muestra principalmente el índice del BUO especificado. Puede garantizar que el índice de UBO sea el mismo entre varios programas Shader diferentes, pero debe llamarse antes de llamar a 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());
}

Pero en el motor de sobrecarga, este método se llama después de llamar a glProgram y el valor de índice se obtiene usando GetBlockLocation, que también es el valor de índice predeterminado de UBO en Shader, por lo que este método debe eliminarse. Comenté que no encontré problemas al utilizar este método.

Finalmente, echemos un vistazo a cómo establecer el valor de UBO. La implementación está en el archivo UniformBuffer.inl, principalmente usando la función glBufferSubData y especificando su valor de compensación y tamaño de datos.

	template<typename T>
	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<typename T>
	inline void UniformBuffer::SetSubData(const T& p_data, std::reference_wrapper<size_t> 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();
	}

3. SSBO de OpenGL

Shader Storage Buffer Object (SSBO), un objeto de caché de almacenamiento de sombreador, se comporta como UBO, pero es más potente. Primero, un sombreador puede escribir en un bloque de búfer, modificar su contenido y renderizarlo a otros sombreadores o a la propia aplicación. En segundo lugar, el tamaño se puede medir antes de renderizar, en lugar de durante la compilación y la vinculación. En Overload, la información de la luz se almacena mediante SSBO; consulte el siguiente clip de Shader:

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

Puedes usar length() en el sombreador para obtener la longitud de ssbo_Lights.

El método para configurar SSBO es similar a configurar UBO, pero glBindBuffer(), glBindBufferRange() y glBindBufferBase() necesitan usar GL_SHADER_STORAGE_BUFFER como parámetro de destino.

4. Encapsulación de SSBO por sobrecarga

Overload encapsula el funcionamiento de SSBO en la clase ShaderStorageBuffer, el código específico no será analizado, es similar a UBO.

Supongo que te gusta

Origin blog.csdn.net/loveoobaby/article/details/133564169
Recomendado
Clasificación