4-使用SSBO存储动画信息

在看完GAMES104动画系统那一部分后,我也想写一个简单的动画系统,又要开一个坑了!本文将简要介绍如何用SSBO存储更多的动画信息。

SSBO

概念

SSBO,即着色器存储缓冲对象,在OpenGL4.3和Vulkan1.0中被支持。一个SSBO可以看成是一个UBO和一个纹理的混合。和UBO相比,SSBO有如下优势:

  • SSBO的存储范围更大,允许存储的最低内存是128MB,而UBO是16KB;
  • SSBO是可写的,而UBO是只读的;
  • SSBO可以存储不定长数组,而UBO则拥有固定大小。

使用

SSBO的初始化和一般的缓冲对象相同,只是类型变成了GL_SHADER_STROAGE_BUFFER。我目前封装的glShaderStorageBuffer类如下:

// .h
class GLShaderStorageBuffer
{
public:
	explicit GLShaderStorageBuffer(size_t bufferSize);
	virtual ~GLShaderStorageBuffer();

	template <typename T>
	void writeSsboData(const std::vector<T>& bufferData, int bindingPoint)
	{
		size_t bufferSize = bufferData.size() * sizeof(T);
		if (bufferSize == 0 || bufferSize > m_InitBufferSize)
		{
			LOG_ERROR(std::format("[{}]: BufferSize invalid (0 or > ssbo's bufferSize) !!", __FUNCTION__));
			return;
		}
		m_CurBufferSize = bufferSize;

		bind();
		GLCall(glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, m_CurBufferSize, bufferData.data()));
		GLCall(glBindBufferRange(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_rendererID, 0, m_CurBufferSize));
		unbind();
	}

    // 这个没测试过, 不知道对不对
	template <typename T>
	void readSsboData(const T* pBufferDataDst, size_t bufferSizeDst)
	{
		bind();

		// 保证数据修改完成后再读取
		GLCall(glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT));
		void* ptr = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);
		memcpy_s(pBufferDataDst, bufferSizeDst, ptr, m_CurBufferSize);
		GLCall(glUnmapBuffer(GL_SHADER_STORAGE_BUFFER));
		unbind();
	}

	void bind() const;
	void unbind() const;

private:
	unsigned int m_rendererID;
	size_t m_InitBufferSize;
	size_t m_CurBufferSize;
};
// .cpp
GLShaderStorageBuffer::GLShaderStorageBuffer(size_t bufferSize)
{
	m_InitBufferSize = bufferSize;

	GLCall(glGenBuffers(1, &m_rendererID));
	bind();
	GLCall(glBufferData(GL_SHADER_STORAGE_BUFFER, m_InitBufferSize, NULL, GL_DYNAMIC_COPY));
	unbind();
}

GLShaderStorageBuffer::~GLShaderStorageBuffer()
{
	GLCall(glDeleteBuffers(1, &m_rendererID));
}

void GLShaderStorageBuffer::bind() const
{
	GLCall(glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_rendererID));
}

void GLShaderStorageBuffer::unbind() const
{
	GLCall(glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0));
}

着色器修改

别忘了修改对应着色器:

layout (std430, binding = 1) readonly buffer BoneMatrices {
	mat4 finalBonesMatrices[];
};

layout (std430, binding = 2) readonly buffer BoneDqs {
	mat2x4 finalBonesDQs[];
};

这里设置为只读,因为我们不需要写它。

有了SSBO,我们的最终蒙皮矩阵/对偶四元数数组就不需要设置定长了,可以根据关节数量灵活变更。

参考资料

  • 《C++ Game Animation Programming》