计算着色器

计算着色器是在GPU上运行的,是在普通渲染管线之外的着色器程序。计算着色器是完全用于计算任意信息的着色器阶段。虽然它可以进行渲染,但它通常用于与绘制三角形和像素没有直接关系的任务。利用GUP的快速计算和并行性,可以用于处理大量的计算。

opengl4.3才开始支持计算着色器,opengl es是3.1才开始支持计算着色器。

1
2
3
4
5
6
7
8
9
10
11
12
13
#version 320 es
layout (local_size_x = 1) in;
layout (rgba32f, binding = 0) uniform readonly mediump imageBuffer po_buffer;
layout (rgba32f, binding = 1) uniform writeonly mediump imageBuffer po_buffer1;
void main()
{
//此处全局工作组和本地工作组yz均为1,因此可以通过x确定位置
int pos = int(gl_GlobalInvocationID.x);
vec4 value = imageLoad(po_buffer, pos);
value.x = value.x+0.4;
value.y = value.y+0.4;
imageStore(po_buffer1, pos, value);
};

binding与之后glBindImageTexture的第一个参数相同,通过ID绑定对应的tbo,而tbo绑定对应的buffer。相当于通过绑定知道从哪个buffer读取数据,从哪个buffer写入数据。因为此处是读取buffer所以使用imageBuffer,如果是数组的话可能使用image2D。es需要指定精度,所以添加mediump。同时因为需要读取的是rgba而不是r(如果只设置r,value就无法读取y值),所以需要指定readonly和writeonly。但是在opengl4.3中不需要指定精度,并且读写可以在一个buffer中进行。

imageLoad从buffer的指定位置读取数据,imageStore将值写入buffer的指定位置。

计算着色器任务以组为单位进行执行,称为工作组。拥有邻居的工作组被称为本地工作组(局部), 这些组可以组成更大的组,称为全局工作组,而全局工作组通常作为执行命令的一个单位。计算着色器会被全局工作组中每一个本地工作组中的每一个单元调用一次,工作组的每一个单元称为工作项,每一次调用称为一次执行。执行的单元之间可以u通过变量和显存进行通信(不同工作组不能通信),且可执行同步操作保持一致性(单个工作组内可以并行)。

如下例子假设全局工作组yz轴都为1 ,局部工作组只有xy:

gl_LocalInvocationID.x × local_size_y+gl_LocalInvocationID.y+local_size_x×local_size_y×gl_WorkGroupID.x=gl_GlobalInvocationID.x×local_size_y×y轴的工作组个数+gl_GlobalInvocationID.y

local_size是本地工作组的大小,有xyz三个维度,不设置默认为1。gl_WorkGroupID本地工作组在全局工作组的索引(从0开始),local_size_x×local_size_y本地工作组的大小(该大小尽量与硬件匹配),gl_LocalInvocationID工作项在本地工作组的坐标(从0开始)。gl_GlobalInvocationID:在全局工作组中,当前工作项所在位置。(在es中imageLoad后的pos只能使用int,所以可以设置本地工作组和全局工作组yz均为1,这样可以只使用gl_GlobalInvocationID.x就可以遍历到所有数据,否则可以通过上述公式计算。)本地工作组的大小×全局工作组的大小->所有需要遍历的数据

与普通着色器一样正常链接

1
2
3
4
5
6
7
8
computeShader = LoadShader(GL_COMPUTE_SHADER, cShaderStr);
compute_prog = glCreateProgram();
if (compute_prog == 0)
{
return 0;
}
glAttachShader(compute_prog, computeShader);
glLinkProgram(compute_prog);

将顶点数据arraybuffer与texbuffer绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
glGenBuffers(1, &po_buffer);
glBindBuffer(GL_ARRAY_BUFFER, po_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices), vVertices, GL_DYNAMIC_COPY);

glGenBuffers(1, &po_buffer1);
glBindBuffer(GL_ARRAY_BUFFER, po_buffer1);
glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices),vVertices, GL_DYNAMIC_COPY);

glGenTextures(1, &po_tbo);
glBindTexture(GL_TEXTURE_BUFFER, po_tbo);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, po_buffer);

glGenTextures(1, &po_tbo1);
glBindTexture(GL_TEXTURE_BUFFER, po_tbo1);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, po_buffer1);

先使用计算着色器,绑定tbo和id,GL_READ_WRITE设置读写权限(如果计算着色器规定了这里可以就设置读写,opengl因为读写可以在一个buffer进行因此一定要设置可读可写)。glDispatchCompute的三个参数设置了全局工作组的大小,三个参数可以理解为xyz方向各有几个本地工作组。glMemoryBarrier是隔断作用,为了保证计算着色器中纹理像素全部写入完成才进行下一步。最后使用渲染着色器进行绘制。如果至使用一次计算着色器的话,可以不用vao,渲染着色器默认使用writeonly的buffer。

1
2
3
4
5
6
glUseProgram(userData->compute_prog);
glBindImageTexture(0, po_tbo, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
glBindImageTexture(1, po_tbo1, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
glDispatchCompute(3, 1, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
vao(po_tbo1配置数据与tbo1绑定)

如果要进行类似transformfeedback的操作,进行累加变换,需要交换读写tbo。

1
2
3
4
5
6
7
8
9
if(flag){
glBindImageTexture(0, po_tbo, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
glBindImageTexture(1, po_tbo1, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
vao(po_tbo配置数据与tbo绑定)
}else{
glBindImageTexture(1, po_tbo, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
glBindImageTexture(0, po_tbo1, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
vao1(po_tbo1配置数据与tbo1绑定)
}
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码

请我喝杯咖啡吧~

支付宝
微信