在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的封装在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<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"
其具体实现在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<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();
- }
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的操作封装到类ShaderStorageBuffer中,具体代码就不分析了,与UBO大同小异。