最近正好在学习这些,所以索性一起整理下,先说说两者的使用场景和区别吧。
UBO:和普通的uniform变量相比,就像是一个结构体,可以容纳更多变量,存储在显存的常量区,速度较快,编译时大小是确定的,而且大小是有限制的,在着色器里可读但不可写,修饰符uniform,一般用于少量的变量设置,在所有着色器都常用到。
SSBO:和UBO相比,SSBO功能更强大,在着色器里是可读可写的,修饰符buffer,存储在全局变量区,速度比UBO慢些,但是其大小可以在编译时不确定,而且大小基本没有限制,一般用于两个着色器之间处理后数据的传递,多见于计算着色器中。
一、UBO与SSBO的创建及着色器的绑定
先给着色器里的两个Uniform块的定义:
layout (std140,binding = 0) uniform UBO_data
{
float randNum;
float vtime;
};
layout (std430,binding = 1) buffer SSBO_data
{
vec3 SSBO_color;
};
再是代码里与SSBO块对应声明的结构体:
struct SSBO_Data
{
float color[3];
} ssbo_data;
什么都没有代码来得直接:
GLuint UBO,SSBO;//声明两个Buffer对象
GLint bindingIndex = 0; //设置Uniform块的binding值
GLuint ubIndex = glGetUniformBlockIndex(programObject[0], "UBO_data"); //获取Uniform块在着色器对应的索引号,不是binding值
glUniformBlockBinding(programObject[0], ubIndex, bindingIndex); //着色器中Uniform块的索引值与binding值的绑定,也可以在着色器中对Uniform块代码里设置binding值绑定
GLint bufferSize = 0;
glGetActiveUniformBlockiv(programObject[0], ubIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &bufferSize);//获取Uniform块的大小
glGenBuffers(1, &UBO);//创建UBO对象
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
glBufferData(GL_UNIFORM_BUFFER, bufferSize, NULL, GL_DYNAMIC_READ);//分配只读的内存
glBindBufferBase(GL_UNIFORM_BUFFER, 0, UBO);//将缓冲区对象绑定到索引缓冲区目标,由于对象只有一个,所以索引号为0,如果是数组,需要按索引号设置
ssbo_data.color[0] = 1.0;
ssbo_data.color[1] = 0.0;
ssbo_data.color[2] = 0.0;
bindingIndex = 1;
//着色器未设置binding时的绑定方式,已过时,不建议使用,容易出问题 - start
//GLuint block_index = glGetProgramResourceIndex(programObject[0],GL_SHADER_STORAGE_BLOCK, "SSBO_data");//获取着色器中索引号
//glShaderStorageBlockBinding(programObject[0], block_index, bindingIndex);//着色器中Uniform块的索引值与binding值的绑定,也可以在着色器中对Uniform块代码里设置binding值绑定
//注释 - end
glGenBuffers(1, &SSBO);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, SSBO);
//glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(vec3), NULL, GL_DYNAMIC_DRAW);//分配内存的话这种方式也是可以的,注意数据类型就好,计算好内存大小
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(ssbo_data), &ssbo_data, GL_DYNAMIC_COPY);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, SSBO); //与着色器中binding值绑定,这里设置是1
上面注释也挺详细的了,耐心点看吧,glBuffData分配内存,只要内存大小设置好就没问题。
二、UBO与SSBO设置参数和使用
没有什么特殊的仪式,在渲染时,需要用到着色器时,就需要先把参数设置好。
UBO参数的设置:
glBindBuffer(GL_UNIFORM_BUFFER, UBO);//先绑定到UBO对象
float a = 0.0;
float nowtime = glfwGetTime();
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(float), &a);//直接传入参数,简单直接,0代表的是偏移值,单位为字节,第一个参数偏移为0
glBufferSubData(GL_UNIFORM_BUFFER, 4, sizeof(float), &nowtime);
SSBO参数的设置:
glBindBuffer(GL_SHADER_STORAGE_BUFFER, SSBO);//先绑定到SSBO对象
vec3 color = vec3(1.0,1.0,0.0);
//glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(vec3), &color);//因为是传入值,所以这种方式也可以,注意设置好偏移和数据内存大小就行
GLvoid* p = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_WRITE_ONLY);//获取着色器中buffer块的内存地址
memcpy(p, &ssbo_data, sizeof(ssbo_data));//将结构体数据拷贝到着色器buffer块地址上
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);//释放内存地址
三、SSBO参数数据的获取
上面说过,SSBO是支持可读可写的,如果在着色器里改变了buffer块的数据,怎么获取呢?
起初我也百度了很久,好像没人说这一块,后面想想,可能是因为太简单了,所以没人提吧,但是对于新手来说,属实要郁闷一会儿,还好没有放弃。第二段里说了SSBO参数的传入,是通过将数据拷贝到着色器buffer块的内存地址,那么获取数据是否可以从着色器buffer块地址拷贝到结构体呢?试了下,确实是这样,代码如下:
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);//隔断作用,为了让数据修改完成
p = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY); //获取着色器buffer块内存地址
memcpy(&ssbo_data, p, sizeof(ssbo_data));//拷贝buffer块数据到结构体
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
到这里,SSBO就算完结了。
结尾:写这篇博客主要是为了检验自己学习后的理解程度,也希望能给有缘人带来帮助,有问题欢迎交流,我们一起进步!