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


    一、OpenGL的UBO

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

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

    1. layout (std140) uniform EngineUBO
    2. {
    3. mat4 ubo_Model;
    4. mat4 ubo_View;
    5. mat4 ubo_Projection;
    6. vec3 ubo_ViewPos;
    7. float ubo_Time;
    8. };

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

    二·、Overload对UBO的封装

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

    1. namespace OvRendering::Buffers
    2. {
    3. /**
    4. * OpenGL UBO的封装
    5. */
    6. class UniformBuffer
    7. {
    8. public:
    9. /**
    10. * Create a UniformBuffer
    11. * @param p_size (Specify the size in bytes of the UBO data)
    12. * @param p_bindingPoint (Specify the binding point on which the uniform buffer should be binded)
    13. * @parma p_offset (The offset of the UBO, sizeof previouses UBO if the binding point is != 0)
    14. * @param p_accessSpecifier
    15. */
    16. UniformBuffer(size_t p_size, uint32_t p_bindingPoint = 0, uint32_t p_offset = 0, EAccessSpecifier p_accessSpecifier = EAccessSpecifier::DYNAMIC_DRAW);
    17. /**
    18. * Destructor of the UniformBuffer
    19. */
    20. ~UniformBuffer();
    21. /**
    22. * Bind the UBO
    23. */
    24. void Bind();
    25. /**
    26. * Unbind the UBO
    27. */
    28. void Unbind();
    29. /**
    30. * Set the data in the UBO located at p_offset to p_data
    31. * @param p_data
    32. * @param p_offset
    33. */
    34. template<typename T>
    35. void SetSubData(const T& p_data, size_t p_offset);
    36. /**
    37. * Set the data in the UBO located at p_offset to p_data
    38. * @param p_data
    39. * @param p_offsetInOut (Will keep track of the current stride of the data layout)
    40. */
    41. template<typename T>
    42. void SetSubData(const T& p_data, std::reference_wrapper<size_t> p_offsetInOut);
    43. /**
    44. * Return the ID of the UBO
    45. */
    46. uint32_t GetID() const;
    47. /**
    48. * Bind a block identified by the given ID to given shader
    49. * @param p_shader
    50. * @param p_uniformBlockLocation
    51. * @param p_bindingPoint
    52. */
    53. static void BindBlockToShader(OvRendering::Resources::Shader& p_shader, uint32_t p_uniformBlockLocation, uint32_t p_bindingPoint = 0);
    54. /**
    55. * Bind a block identified by the given name to the given shader
    56. * @param p_shader
    57. * @param p_name
    58. * @param p_bindingPoint
    59. */
    60. static void BindBlockToShader(OvRendering::Resources::Shader& p_shader, const std::string& p_name, uint32_t p_bindingPoint = 0);
    61. /**
    62. * Return the location of the block (ID)
    63. * @param p_shader
    64. * @param p_name
    65. */
    66. static uint32_t GetBlockLocation(OvRendering::Resources::Shader& p_shader, const std::string& p_name);
    67. private:
    68. uint32_t m_bufferID;
    69. };
    70. }
    71. #include "OvRendering/Buffers/UniformBuffer.inl"

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

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

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

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

    1. void OvRendering::Buffers::UniformBuffer::BindBlockToShader(OvRendering::Resources::Shader& p_shader, uint32_t p_uniformBlockLocation, uint32_t p_bindingPoint)
    2. {
    3. glUniformBlockBinding(p_shader.id, p_uniformBlockLocation, p_bindingPoint);
    4. }
    5. void OvRendering::Buffers::UniformBuffer::BindBlockToShader(OvRendering::Resources::Shader& p_shader, const std::string& p_name, uint32_t p_bindingPoint)
    6. {
    7. glUniformBlockBinding(p_shader.id, GetBlockLocation(p_shader, p_name), p_bindingPoint);
    8. }
    9. // 获取UBO的索引位置
    10. uint32_t OvRendering::Buffers::UniformBuffer::GetBlockLocation(OvRendering::Resources::Shader& p_shader, const std::string& p_name)
    11. {
    12. return glGetUniformBlockIndex(p_shader.id, p_name.c_str());
    13. }

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

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

    1. template<typename T>
    2. inline void UniformBuffer::SetSubData(const T& p_data, size_t p_offsetInOut)
    3. {
    4. Bind();
    5. glBufferSubData(GL_UNIFORM_BUFFER, p_offsetInOut, sizeof(T), std::addressof(p_data));
    6. Unbind();
    7. }
    8. template<typename T>
    9. inline void UniformBuffer::SetSubData(const T& p_data, std::reference_wrapper<size_t> p_offsetInOut)
    10. {
    11. Bind();
    12. size_t dataSize = sizeof(T);
    13. glBufferSubData(GL_UNIFORM_BUFFER, p_offsetInOut.get(), dataSize, std::addressof(p_data));
    14. p_offsetInOut.get() += dataSize;
    15. Unbind();
    16. }

    三、OpenGL的SSBO

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

    1. layout(std430, binding = 0) buffer LightSSBO
    2. {
    3. mat4 ssbo_Lights[];
    4. };

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

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

    四、Overload对SSBO的封装

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

  • 相关阅读:
    数据结构-----排序的概念、常见排序的实现以及排序算法的特点、非比较排序、排序相关例题
    记一次web登录通杀渗透测试
    c++1237. 找出给定方程的正整数解,四种解法(二分+有限状态机)
    XUbuntu22.04之关闭todesk开机自启动(二百二十一)
    2022年最新java之异常
    Halcon 第一章『Halcon语言』◆第5节:读取图像
    【C++】匿名对象 ① ( 匿名对象引入 | 匿名对象简介 | 匿名对象概念 | 匿名对象作用域 - 对象创建与销毁 )
    MXNet对NIN网络中的网络的实现
    信息学奥赛一本通:2037:【例5.4】约瑟夫问题
    双十一电商流量暴增的背后,用户体验如何保障?
  • 原文地址:https://blog.csdn.net/loveoobaby/article/details/133564169