LearnGL - 16 - UBO(Uniform Block Object) - 实现 Shader Program Object 之间的数据共享


LearnGL - 学习笔记目录

前些篇:

做了一些简单的 类环境映射的方法,还有其他的一些优化

这一篇:将使用 UBO 来实现 Shader Program Object 之间的数据共享

注意是 Shader Program Object - Shader 程序对象,而不是 Shader 着色器对象,因为 着色器之间的共享数据在原来使用的 uniform 变量本身就是共享的,但是要在 Shader 着色器程序对象之间共享,就是能 UBO 或是下面简单介绍到的 SSBO)

建议先阅读 OpenGL - UBO 前置内容

本人才疏学浅,如有什么错误,望不吝指出。


数据共享有 UBO(Uniform Block Object - Uniform 块对象) 和 SSBO(Shader Storage Buffer Object - 存储缓存对象,或是叫 SBO 也行)

  • UBO 一般用于绘制用,这里使用的是 UBO 的方式,它是 只读
  • SSBO 以后再使用,一般用通用计算程序使用(类似Compute Shader),它与 UBO 最大区别于,它是 可读写

使用流程

  • 根据 ubo 名称,获取所有使用了这个 ubo 块的在该 shader 中 ubo 块的索引
  • 根据该 ubo 块中的每个属性名字,获取该 ubo 块的每个属性的索引值
  • 根据该 ubo 块中每个属性索引值,获取 offset(偏移)、size(大小,这里的大小理解为:元素数量)、type(数据类型)
  • 给这个 ubo 块 分配的 binding point index(这个下面会特别说明)
  • 创建一个 GL_UNIFORM_BUFFER 的缓存对象 ubo
  • 并将 ubo 绑定到 上面分配的 binding point index 中
  • 获取该 ubo 块在 GLSL 编译器分配的字节大小
  • 使用该字节大小,创建对应应用层的缓存数据 buffer
  • 在需要的时候对该应用层的 buffer 缓存数据进行更新

实践


根据 ubo 名称,获取所有使用了这个 ubo 块的在该 shader 中 ubo 块的索引

首先我们得在 GLSL shader 中声明好 ubo 的内容

// camera
// layout (std140) 
uniform Camera_UBO {
	// world pos
	vec3 _CamWorldPos;		// 镜头世界坐标
	// clear flag
	int ClearType;			// 渲染相机的 clear type
	vec3 ClearColor;		// 清理的颜色
	// camera transform
	mat4 vMat; 				// v 矩阵
	mat4 pMat; 				// p 矩阵
};

然后在应用层获取这个 “Camera_UBO” 名字的 ubo 块在该 shader 中的索引值

使用到 API glGetUniformBlockIndex

		// 获取指定 name 的 shader 中的 ubo 索引
		GLuint ubo_idx = glGetUniformBlockIndex(shader, name);
		if (ubo_idx == -1) {
			// noops
			std::cout << "Shader(" << shader << ") have no ubo(" << name << "\n";
			return;
		}

这里简单介绍一下:

GLuint glGetUniformBlockIndex(	GLuint program,
 	const GLchar *uniformBlockName);

  • program 是指定的 shader 程序对象
  • uniformBlockName 是要获取 ubo 的名字,对应我们上面 shader 代码中的 "Camera_UBO"

如果该 shader 程序对象没有使用 uniformBlockName 指定的 ubo,那么返回 -1


根据该 ubo 块中的每个属性名字,获取该 ubo 块的每个属性的索引值

需要使用到 API glGetUniformIndices

			const size_t count = name_map_vec.size();

			const char** names = (const char**)malloc(sizeof(size_t) * count);
			
			for (size_t i = 0; i < count; i++) {
				*(names + i) = name_map_vec[i].name.c_str();
			}
			
			// 获取每个 uniform 块中对应 names 属性的索引,并保存到 indices
			glGetUniformIndices(shader, count, names, indices);

glGetUniformIndices 的原型

void glGetUniformIndices(	GLuint program,
 	GLsizei uniformCount,
 	const GLchar **uniformNames,
 	GLuint *uniformIndices);

  • program 就 shader 程序对象
  • uniformCount 就是指定 uniformNames 和 uniformIndices 的数量,也就是该 ubo 块中需要查询的属性名字的数量,一般我们会将所有的都列进去
  • uniformNames 就是 ubo 块中每个属性的名字的字符串数组
  • uniformIndices 就是获取到后的结果 都保存在 uniformIndices 中,它保存的就是每个 ubo 块中对应名字的属性在该 ubo 块中的索引值

根据该 ubo 块中每个属性索引值,获取 offset(偏移)、size(大小,这里的大小理解为:元素数量)、type(数据类型)

			const size_t count = name_map_vec.size();

			const char** names = (const char**)malloc(sizeof(size_t) * count);
			
			for (size_t i = 0; i < count; i++) {
				*(names + i) = name_map_vec[i].name.c_str();
			}

			GLuint* indices			= (GLuint*)malloc(sizeof(GLuint) * count);	// 每个 uniform 在 ubo 中的索引值
			GLint* size				= (GLint*)malloc(sizeof(GLint) * count);	// 每个 uniform 的数组元素数量,不是数组则为1
			GLint* offset			= (GLint*)malloc(sizeof(GLint) * count);	// 每个 uniform 在 ubo 中的偏移值
			GLint* type				= (GLint*)malloc(sizeof(GLint) * count);	// 每个 uniform 的类型
			
			// 获取每个 uniform 块中对应 names 属性的索引,并保存到 indices
			glGetUniformIndices(shader, count, names, indices);
			// 获取 indices 中每个属性索引 对应的地址字节偏移
			glGetActiveUniformsiv(shader, count, indices, GL_UNIFORM_OFFSET, offset);
			// 获取 indices 中每个属性索引 对应的类型数组长度,如果不是数组,则为 1
			glGetActiveUniformsiv(shader, count, indices, GL_UNIFORM_SIZE, size);
			// 获取 indices 中每个属性索引 对应的数据类型
			glGetActiveUniformsiv(shader, count, indices, GL_UNIFORM_TYPE, type);

			GLint* real_size		= (GLint*)malloc(sizeof(GLint) * count);	// uniform 中每个属性的真实字节大小
			for (size_t i = 0; i < count; i++) {
				// 根据数组元素的数据类型 与 数组元素个数 相乘,求得真实大小
				real_size[i] = size[i] * TypeSize(type[i]);
			}

给这个 ubo 块 分配的 binding point index


		GLuint _binding_point_idx = -1;						// 分配到的 UBO 缓存列表中的 binding point 的索引

		...
			// 如果该 ubo 未分配过 binding point 索引值,则分配一个 ubo binding 索引
			if (_binding_point_idx == -1) {
				_binding_point_idx = binding_idx_counter++;
			}
			else {
				assert(false);
			}
			...
		// 将 shader 的第 ubo_idx 个 ubo 绑定到全局 ubo index 索引
		glUniformBlockBinding(shader, ubo_idx, _binding_point_idx);

GLuint _binding_point_idx = -1; 就是我们 ubo 管理类中的一个成员,-1 说明它还未分配过,一般是由于没有 shader 找到过有这个 ubo 的使用

这个 _binding_point_idx 有必要特别说明一下,它到底是个啥:

引用 高级GLSL 的一张图:(这张图说明足够清晰,我就不再自己画多一张了)
在这里插入图片描述
从图中可以看到 Binding points 只是一些索引值,你也可以理解为 Binding points 是 每个 OpenGL Context 上下文对象中都分配好的一些 buffer 插槽,需要绑定使用到全局使用的都可以先将这些 buffer 对象绑定到这些插槽中。

如果我们将所有用到这些 ubo 都绑定到对应的全局插槽中。

这些每个 shader 程序对象中使用到的 ubo 都是同一个缓存。

这么制作的好处:

  • 提升性能 - 减少内存使用,相比之前每个 shader 中相同的一些冗余数据,它减少了GPU内存
  • 提升性能 - 应用层只需要在渲染前设置好该 ubo 的缓存数据并上传到 GPU 即可(只需要设置一次,后续所有用到该 ubo 的 shader 都有这些数据)

创建一个 GL_UNIFORM_BUFFER 的缓存对象 ubo

这个没啥好说,与之前的一些文章中说明的 VBO,EBO 的缓存对象的创建都一样,使用到 API glGenBuffers

			// 创建 ubo
			ubo = new GLuint();
			glGenBuffers(1, ubo);

VBO,EBO相关之前的文章,有兴趣可以参考:


并将 ubo 绑定到 上面分配的 binding point index 中

上面创建好缓存对象后,接着就将它绑定到 当前的 GL_UNIFORM_BUFFER 目标中,还是上面所说的文章中有介绍的 API glBindBufferglBindBufferBase,其中 glBindBufferBase 在前面说的 前置篇也有详细说明:OpenGL - UBO 前置内容

			// 当前要设置的 ubo 信息
			glBindBuffer(GL_UNIFORM_BUFFER, *ubo);

			// 将 binding point 的索引值 与 ubo 数据缓存对象
			glBindBufferBase(GL_UNIFORM_BUFFER, _binding_point_idx, *ubo);

先通过 glBindBuffer 将创建的 ubo 设置为当前要操作的 GL_UNIFORM_BUFFER 目标

再通过 glBindBufferBase 来将 ubo 对象绑定到前面介绍的全局的 buffer 插槽,插槽位置为:前面分配好的 _binding_point_idx


获取该 ubo 块在 GLSL 编译器分配的字节大小

使用到 API glGetActiveUniformBlockiv

			// 获取 shader 中 ubo 的大小
			GLint glsl_uboSize = 0;
			glGetActiveUniformBlockiv(shader, ubo_idx, GL_UNIFORM_BLOCK_DATA_SIZE, &glsl_uboSize);

这样我们就可以查询 GLSL 中编译器分配给该 ubo 块的内存大小了,并存储与 glsl_uboSize 指针中

该函数原型声明:

void glGetActiveUniformBlockiv(	GLuint program,
 	GLuint uniformBlockIndex,
 	GLenum pname,
 	GLint *params);
  • program 就是 shader 程序对象
  • uniformBlockIndex 该 ubo 块在该 shader 程序中的索引值
  • pname 是要获取这个块的属性标记,这里我们要获取 ubo 块的大小,填入:GL_UNIFORM_BLOCK_DATA_SIZE 即可
  • params 填入用于存储查询结果的 指针

使用该字节大小,创建对应应用层的缓存数据 buffer

还是与之前文章中使用的 API glBufferData

			// 分配 glsl 中当前要配置的 ubo 缓存大小
			glBufferData(GL_UNIFORM_BUFFER, glsl_uboSize, NULL, GL_STATIC_DRAW);

			// TODO : buffer 可以优化成外部通用的一个缓存
			// 分配前应用层的 ubo 缓存大小
			buffer = calloc(glsl_uboSize, 1);
			buffer_size = glsl_uboSize;
			if (buffer == NULL) {
				std::cerr << "out of memory!" << __FILE__ << ", " << __LINE__ << "\n";
				exit(-1);
			}

这样就给当前要操作的 ubo 缓存对象分配了 glsl_uboSize 返回的大小


在需要的时候对该应用层的 buffer 缓存数据进行更新

最后是给该 ubo 块数据进行更新

主要使用到 API glBufferSubData

	void UBO_DATA::upload() {
		if (_binding_point_idx == -1 || buffer_size == 0 || buffer == NULL) {
			// 对应的 shader 没有这个 ubo
			return;
		}

		// 更新缓存
		for (size_t i = 0; i < name_map_vec.size(); i++) {
			UBO_Name_Ptr_Map names = name_map_vec[i];
			memcpy((void*)((size_t)buffer + offset[i]), (void*)names.ptr, real_size[i]);
		}

		// 当前要设置的 ubo 信息
		glBindBuffer(GL_UNIFORM_BUFFER, *ubo);
		// 设置 应用层 buffer 缓存指针的 buffer_size 的数据到 当前要设置的 ubo 缓存对象中
		glBufferSubData(GL_UNIFORM_BUFFER, 0, buffer_size, buffer);
	}

函数声明原型:

void glBufferSubData(	GLenum target,
 	GLintptr offset,
 	GLsizeiptr size,
 	const GLvoid * data);
 
void glNamedBufferSubData(	GLuint buffer,
 	GLintptr offset,
 	GLsizei size,
 	const void *data);

  • target 传入 GL_UNIFORM_BUFFER 即可
  • offset 要设置的缓存的内存地址偏移
  • size 要给 offset 位置设置后续的 size 个内存字节
  • data 要设置到当前 GL_UNIFORM_BUFFER 目标中的数据指针(将 data 的数据复制过去)

封装 UBO_DATA

// my_ubo_data.h
/* author : jave.lin
uniform block object 数据管理
*/
#ifndef _MY_UBO_DATA__H_
#define _MY_UBO_DATA__H_

#include<algorithm>
#include<unordered_set>
#include"glad/glad.h"
#include"my_gameobject_etc.h"

namespace my_util {
	//
	// 声明
	//

	// uniform 块中对应 属性的名字与应用层的数据指针映射
	struct UBO_Name_Ptr_Map {
	public:
		std::string name;
		size_t ptr;
	};

	class UBO_DATA : public Object {
	private:
		static size_t TypeSize(GLenum type);
		static GLuint binding_idx_counter;
	public:
		UBO_DATA(const char* name);

		inline void addNameMap(UBO_Name_Ptr_Map name_map);

		inline const GLuint ubo_id() const;
		void binding(GLuint shader, size_t object_id);
		void upload();
	private:
		~UBO_DATA();										// 应用 析构,外部统一 DESTROY(Object*)

		GLuint _binding_point_idx = -1;						// 分配到的 UBO 缓存列表中的 binding point 的索引
		GLuint* ubo = NULL;									// 该 ubo (uniform block object) 的ID
		void* buffer = NULL;								// 对应应用层的缓存指针
		size_t buffer_size = 0;								// 对应应用层的缓存指针数据的大小
		GLint* offset = NULL;								// 每个 uniform 块中对应成员的内存偏移(字节)
		GLint* real_size = NULL;							// 每个 uniform 块中对应成员的内存大小(字节)
		std::vector<UBO_Name_Ptr_Map> name_map_vec;			// 每个 uniform 块名字对应的应用层的数据指针
		std::unordered_set<GLuint> binded_shader_obj_id_set;	// 该 ubo 已绑定过的 shader id
	};

	//
	// 实现
	//
	GLuint UBO_DATA::binding_idx_counter = 0;

	size_t UBO_DATA::TypeSize(GLenum type) {
		size_t size;

#define CASE(Enum, Count, Type) \
case Enum: size = Count * sizeof(Type); break;

		switch (type) {
			CASE(GL_FLOAT,				1, GLfloat);
			CASE(GL_FLOAT_VEC2,			2, GLfloat);
			CASE(GL_FLOAT_VEC3,			3, GLfloat);
			CASE(GL_FLOAT_VEC4,			4, GLfloat);
			CASE(GL_INT,				1, GLint);
			CASE(GL_INT_VEC2,			2, GLint);
			CASE(GL_INT_VEC3,			3, GLint);
			CASE(GL_INT_VEC4,			4, GLint);
			CASE(GL_UNSIGNED_INT,		1, GLuint);
			CASE(GL_UNSIGNED_INT_VEC2,	2, GLuint);
			CASE(GL_UNSIGNED_INT_VEC3,	3, GLuint);
			CASE(GL_UNSIGNED_INT_VEC4,	4, GLuint);
			CASE(GL_BOOL,				1, GLboolean);
			CASE(GL_BOOL_VEC2,			2, GLboolean);
			CASE(GL_BOOL_VEC3,			3, GLboolean);
			CASE(GL_BOOL_VEC4,			4, GLboolean);
			CASE(GL_FLOAT_MAT2,			4, GLfloat);
			CASE(GL_FLOAT_MAT2x3,		6, GLfloat);
			CASE(GL_FLOAT_MAT2x4,		8, GLfloat);
			CASE(GL_FLOAT_MAT3,			9, GLfloat);
			CASE(GL_FLOAT_MAT3x2,		6, GLfloat);
			CASE(GL_FLOAT_MAT3x4,		12, GLfloat);
			CASE(GL_FLOAT_MAT4,			16, GLfloat);
			CASE(GL_FLOAT_MAT4x2,		8, GLfloat);
			CASE(GL_FLOAT_MAT4x3,		12, GLfloat);
#undef CASE
		default:
			fprintf(stderr, "%s, %d, Unknown type : 0x%x\n", __FILE__, __LINE__, type);
			exit(-1);
			break;
		}
		return size;
	}

	UBO_DATA::UBO_DATA(const char* name) : Object(name) {

	}
	UBO_DATA::~UBO_DATA() {
		if (ubo != NULL) {
			glDeleteBuffers(1, ubo);
			delete ubo;
			ubo = NULL;
		}
		if (buffer != NULL) {
			free(buffer);
			buffer = NULL;
		}
		if (offset != NULL) {
			free(offset);
			offset = NULL;
		}
		if (real_size != NULL) {
			free(real_size);
			real_size = NULL;
		}
		name_map_vec.clear();
		binded_shader_obj_id_set.clear();
	}
	inline void UBO_DATA::addNameMap(UBO_Name_Ptr_Map name_map) {
		name_map_vec.push_back(name_map);
	}
	inline const GLuint UBO_DATA::ubo_id() const {
		return *ubo;
	}
	void UBO_DATA::binding(GLuint shader, size_t object_id) {

		if (binded_shader_obj_id_set.find(object_id) != binded_shader_obj_id_set.end()) {
			// 如果绑定过,则跳过
			return;
		}

		// 记录该 shader 绑定过
		binded_shader_obj_id_set.insert(object_id);

		// 获取指定 name 的 shader 中的 ubo 索引
		GLuint ubo_idx = glGetUniformBlockIndex(shader, name);
		if (ubo_idx == -1) {
			// noops
			std::cout << "Shader(" << shader << ") have no ubo(" << name << "\n";
			return;
		}

		// 判断是否初始化过
		if (ubo == NULL) {
			
			const size_t count = name_map_vec.size();

			const char** names = (const char**)malloc(sizeof(size_t) * count);
			
			for (size_t i = 0; i < count; i++) {
				*(names + i) = name_map_vec[i].name.c_str();
			}

			GLuint* indices			= (GLuint*)malloc(sizeof(GLuint) * count);	// 每个 uniform 在 ubo 中的索引值
			GLint* size				= (GLint*)malloc(sizeof(GLint) * count);	// 每个 uniform 的数组元素数量,不是数组则为1
			GLint* offset			= (GLint*)malloc(sizeof(GLint) * count);	// 每个 uniform 在 ubo 中的偏移值
			GLint* type				= (GLint*)malloc(sizeof(GLint) * count);	// 每个 uniform 的类型
			
			// 获取每个 uniform 块中对应 names 属性的索引,并保存到 indices
			glGetUniformIndices(shader, count, names, indices);
			// 获取 indices 中每个属性索引 对应的地址字节偏移
			glGetActiveUniformsiv(shader, count, indices, GL_UNIFORM_OFFSET, offset);
			// 获取 indices 中每个属性索引 对应的类型数组长度,如果不是数组,则为 1
			glGetActiveUniformsiv(shader, count, indices, GL_UNIFORM_SIZE, size);
			// 获取 indices 中每个属性索引 对应的数据类型
			glGetActiveUniformsiv(shader, count, indices, GL_UNIFORM_TYPE, type);

			GLint* real_size		= (GLint*)malloc(sizeof(GLint) * count);	// uniform 中每个属性的真实字节大小
			for (size_t i = 0; i < count; i++) {
				// 根据数组元素的数据类型 与 数组元素个数 相乘,求得真实大小
				real_size[i] = size[i] * TypeSize(type[i]);
			}

			// 如果该 ubo 未分配过 binding point 索引值,则分配一个 ubo binding 索引
			if (_binding_point_idx == -1) {
				_binding_point_idx = binding_idx_counter++;
			}
			else {
				assert(false);
			}

			// 创建 ubo
			ubo = new GLuint();
			glGenBuffers(1, ubo);

			// 当前要设置的 ubo 信息
			glBindBuffer(GL_UNIFORM_BUFFER, *ubo);

			// 将 binding point 的索引值 与 ubo 数据缓存对象
			glBindBufferBase(GL_UNIFORM_BUFFER, _binding_point_idx, *ubo);

			// 获取 shader 中 ubo 的大小
			GLint glsl_uboSize = 0;
			glGetActiveUniformBlockiv(shader, ubo_idx, GL_UNIFORM_BLOCK_DATA_SIZE, &glsl_uboSize);

			// 分配 glsl 中当前要配置的 ubo 缓存大小
			glBufferData(GL_UNIFORM_BUFFER, glsl_uboSize, NULL, GL_STATIC_DRAW);

			// TODO : buffer 可以优化成外部通用的一个缓存
			// 分配前应用层的 ubo 缓存大小
			buffer = calloc(glsl_uboSize, 1);
			buffer_size = glsl_uboSize;
			if (buffer == NULL) {
				std::cerr << "out of memory!" << __FILE__ << ", " << __LINE__ << "\n";
				exit(-1);
			}

			free(type);
			free(size);
			free(indices);
			free(names);

			// 设置、记录上每个 uniform 块中每个属性的内存偏移与大小
			this->offset = offset;
			this->real_size = real_size;
		} // end if (ubo == NULL)

		// 将 shader 的第 ubo_idx 个 ubo 绑定到全局 ubo index 索引
		glUniformBlockBinding(shader, ubo_idx, _binding_point_idx);
	}
	void UBO_DATA::upload() {
		if (_binding_point_idx == -1 || buffer_size == 0 || buffer == NULL) {
			// 对应的 shader 没有这个 ubo
			return;
		}

		// 更新缓存
		for (size_t i = 0; i < name_map_vec.size(); i++) {
			UBO_Name_Ptr_Map names = name_map_vec[i];
			memcpy((void*)((size_t)buffer + offset[i]), (void*)names.ptr, real_size[i]);
		}

		// 当前要设置的 ubo 信息
		glBindBuffer(GL_UNIFORM_BUFFER, *ubo);
		// 设置 应用层 buffer 缓存指针的 buffer_size 的数据到 当前要设置的 ubo 缓存对象中
		glBufferSubData(GL_UNIFORM_BUFFER, 0, buffer_size, buffer);
	}
}

#endif

驱动 UBO 更新

驱动 UBO 的更新思路比较简单:

  • 尝试对每个 shader 绑定 ubo(如果有使用该 ubo 的话)
  • 然后对每个 ubo 的上述更新后更上传到 GPU
	void ShaderProgram::uploadUBO() {
		// 驱动 UBO 的更新思路比较简单:
		// - 尝试对每个 shader 绑定 ubo(如果有使用该 ubo 的话)
		// - 然后对每个 ubo 的上述更新后更上传到 GPU
		std::vector<ShaderProgram*>::iterator shader_start_it;
		std::vector<ShaderProgram*>::iterator shader_end_it;

		std::vector<UBO_DATA*>::iterator ubo_start_it = ubo_vec.begin();
		std::vector<UBO_DATA*>::iterator ubo_end_it = ubo_vec.end();

		ShaderProgram* shader = NULL;
		UBO_DATA* ubo_data = NULL;

		for (; ubo_start_it != ubo_end_it; ubo_start_it++) {
			ubo_data = *ubo_start_it;
			shader_start_it = vec.begin();
			shader_end_it = vec.end();
			for (; shader_start_it != shader_end_it; shader_start_it++) {
				shader = *(shader_start_it);
				// - 尝试对每个 shader 绑定 ubo(如果有使用该 ubo 的话)
				ubo_data->binding(shader->ShaderProgram::id(), shader->Object::id());
			}
			// 然后对每个 ubo 的上述更新后更上传到 GPU
			ubo_data->upload();
		}
	}

外部使用接口

经过我们封装后,外部使用起来就变得相当简单了:

Shader

// camera
// layout (std140) 
uniform Camera_UBO {
	// world pos
	vec3 _CamWorldPos;		// 镜头世界坐标
	// clear flag
	int ClearType;			// 渲染相机的 clear type
	vec3 ClearColor;		// 清理的颜色
	// camera transform
	mat4 vMat; 				// v 矩阵
	mat4 pMat; 				// p 矩阵
};

// scene
// layout (std140)
uniform Scene_UBO {
	vec4 _Ambient;		// .xyz 环境光颜色, .w 环境光系数
	int AmbientType;	// 环境光类别,[测试用]
};

// times
// layout (std140) 
uniform Time_UBO {
	// .x 程序到现在的时间(无缩放时间影响)
	// .y 程序到现在的时间(有缩放时间影响)
	// .z 帧间隔时间(无缩放时间影响)
	// .w 帧间隔时间(有缩放时间影响)
	vec4 _Time;
};

CPP 应用层

		{
			// camera ubo info
			UBO_DATA* ubo_data = new UBO_DATA("Camera_UBO");
			ubo_data->addNameMap({ "_CamWorldPos",	(size_t)&Camera_UBO_Info::_CamWorldPos });
			ubo_data->addNameMap({ "ClearType",		(size_t)&Camera_UBO_Info::ClearType });
			ubo_data->addNameMap({ "ClearColor",	(size_t)&Camera_UBO_Info::ClearColor });
			ubo_data->addNameMap({ "vMat",			(size_t)&Camera_UBO_Info::vMat });
			ubo_data->addNameMap({ "pMat",			(size_t)&Camera_UBO_Info::pMat });

			ShaderProgram::registerUBO(ubo_data);
		}

		{
			// scene ubo info
			UBO_DATA* ubo_data = new UBO_DATA("Scene_UBO");
			ubo_data->addNameMap({ "_Ambient",		(size_t)&Scene_UBO_Info::_Ambient });
			ubo_data->addNameMap({ "AmbientType",	(size_t)&Scene_UBO_Info::AmbientType });

			ShaderProgram::registerUBO(ubo_data);
		}

		{
			// time ubo info
			UBO_DATA* ubo_data = new UBO_DATA("Time_UBO");
			ubo_data->addNameMap({ "_Time",			(size_t)&Time_UBO_Info::_Time });

			ShaderProgram::registerUBO(ubo_data);
		}

与 ShaderProgram.setGlobalXXX 的区别

在这里插入图片描述

如上,我在 ShaderProgram 类中使用宏定义批量的封装过一些 SetGlobalXXX 的接口

但这与我们上面的 UBO 使用是不同的

因为每一个 ShaderProgram 的创建都会自动存放在 std::vector<ShaderProgram*> ShaderProgram::vec

ShaderProgram 的销毁也会自动从 ShaderProgram::vec 移除

我们在调用 ShaderProgram::setGlobalXXX 就会对 ShaderProgram::vec 遍历 设置 setXXX 的接口

咋一看,与我们的 ubo 功能有点混淆吗?

其实不一样,setGlobalXXX 的设置目的是为了方便对一些不便于使用 ubo 不支持的数据类型而添加的,这与 Unity 中的 Material.SetGlobalXXX 或是 Shader.SetGlobalXXX 的功能非常类似。

ubo 块是不支持:不透明 的数据类型的,在 OpenGL - UBO 前置内容 有讲到


References

OpenGL UBOUniform Buffer Object)是一种高效的缓存机制,用于存储渲染管线中的uniform变量。这些变量可以是顶点着色器、片元着色器或计算着色器中的uniform变量。 以下是OpenGL UBO的使用方法: 1. 创建UBO对象 使用以下代码创建UBO对象: GLuint ubo; glGenBuffers(1, &ubo); 2. 绑定UBO对象 使用以下代码将UBO对象绑定到OpenGL上下文中: glBindBuffer(GL_UNIFORM_BUFFER, ubo); 3. 分配UBO对象内存 使用以下代码分配UBO对象所需的内存: glBufferData(GL_UNIFORM_BUFFER, size, data, GL_STATIC_DRAW); 其中,size是UBO对象所需的内存大小,data是指向要存储在UBO对象中的数据的指针。 4. 绑定UBO对象到uniform变量 使用以下代码将UBO对象绑定到uniform变量: GLuint blockIndex = glGetUniformBlockIndex(program, "uniformBlockName"); glUniformBlockBinding(program, blockIndex, bindingPoint); glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, ubo); 其中,program是要绑定的着色器程序对象,"uniformBlockName"是要绑定的uniform的名称,bindingPoint是UBO对象的绑定点。 5. 更新UBO对象中的数据 使用以下代码更新UBO对象中的数据glBindBuffer(GL_UNIFORM_BUFFER, ubo); glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data); 其中,offset是要更新的数据UBO对象中的偏移量,size是要更新的数据大小,data是指向要更新的数据的指针。 6. 渲染 在渲染对象之前,使用以下代码将UBO对象绑定到OpenGL上下文中: glBindBuffer(GL_UNIFORM_BUFFER, ubo); 然后,就可以在着色器程序中使用uniform变量了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值