在OpenGL中使用Compute Shader(一)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在OpenGL中使用Compute Shader(一)相关的知识,希望对你有一定的参考价值。

参考技术A 先了解一些Compute Shader的基本概念
https://www.cg.tuwien.ac.at/courses/Realtime/repetitorium/VU.WS.2014/rtr_rep_2014_ComputeShader.pdf

最小执行单元为thread,也称为invocation,一个thread执行一遍shader程序
网格化的thread构成了work group,可以是1维、2维或者3维的。shader程序中用layout语句指定的local_size_x,local_size_y,local_size_z决定了work group的大小。
网格化的work group构成了dispatch,同样也可以是1维、2维或者3维的。glDispatchCompute中的三个参数指定了x, y, z三维的大小

ps:
https://www.khronos.org/assets/uploads/developers/library/2014-siggraph-bof/KITE-BOF_Aug14.pdf
根据资料,work group的尺寸也可以完全由c程序决定,GLSL中的声明改为
layout( local_size_variable ) in;

对比cuda的概念,compute shader中的work group就是cuda的block,dispatch就是cuda的grid。

OpenGL 4.2中Image介绍
https://blog.csdn.net/u010462297/article/details/50469950

image类型数据与sampler类型数据的区别:sampler取样本的时候,可以是非整数坐标,而且需要对周边像素进行插值才能获取。而image类型只能取整数坐标,直接取出原始数据样本

ps: gl_GlobalInvocationID.x的类型是uint,而imageLoad需要的坐标参数为int。可以用int(表达式)进行强制类型转换

要开始跑计算,首先要解决的是数据的输入和输出问题。ComputeShader中参数的声明非常类似原来的FragmentShader,还是使用uniform作为输入。不过数据类型可以使用image类型了,不再是sampler类型,解决精准取值的问题。另外,作为输入的image,还需要在layout指令中指明格式,例如r32f或者rgba32f(这里只有1、2、4色的格式,没有rgb三色的格式。不过比较奇怪的一点,作为输出的image可以不指明格式,暂时不知道为什么)。而且,image对象需要调用glBindImageTexture跟纹理对象进行绑定。而且还有一点需要非常注意
“A very important restriction for using shader images is that the underlying texture must have been allocated using "immutable" storage, i.e. via glTexStorage*()-like functions, and not glTexImage2D().”

cpu与gpu之间传数据,往gpu传入使用 glTexSubImage,从gpu读出使用glGetTexImage(glReadPixels需要跟FBO一起使用才可以,而glGetTexImage可以直接从texture读取)

至于OpenGL的初始化工作,还是采用freeglut+glew的经典组合就可以完成。

一个最简单的例子,传输一个一维数组,然后给数组里面每个元素加1。

参考
http://wili.cc/blog/opengl-cs.html
https://antongerdelan.net/opengl/compute.html
https://www.cnblogs.com/chen9510/p/12000320.html
https://blog.csdn.net/koibiki/article/details/80590885
https://arm-software.github.io/opengl-es-sdk-for-android/compute_intro.html

OpenGL 之 Compute Shader(通用计算并行加速)

 

 

  平常我们使用的Shader有顶点着色器、几何着色器、片段着色器,这几个都是为光栅化图形渲染服务的,OpenGL 4.3之后新出了一个Compute Shader,用于通用计算并行加速,现在对其进行介绍。

  

  介绍Compute Shader之前需要先介绍一下ImageTexture

    普通的Texture在GLSL中只能进行读取(sampler采样获取数据),写入则必须在Fragment Shader中写入帧缓冲绑定的附件Texture当前像素中,不能随意指定位置写入,并且不能同时读写同一张纹理(我试过不行,有博客同样说不行,应该是不行吧)。

  1、生成Texture

void WKS::ImageTexture::setupTexture() {
    glGenTextures(1, &this->textureID);
    glBindTexture(GL_TEXTURE_2D, this->textureID);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, width, height);
    // turn off filtering and wrap modes
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glBindTexture(GL_TEXTURE_2D, 0);
}

  注意,要是用 glTexStorage2D()生成固定大小纹理,不能使用glTexImage2D()

  2、生成ImageTexture

glBindImageTexture(0, this->inputTexture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);

  inputTexture对应1、中生成的Texture纹理ID。第一个参数是ImageTexture绑定点,与texture纹理绑定点应该不重合。

  3、GLSL中声明

layout (rgba32f, binding = 0) uniform image2D input_image;

  补充:ImageTexture底层是Texture,那么在Host上可以进行访问

    a、初始化,传入数据

void WKS::ImageTexture::Transfer2Texture(float* data) {
    glBindTexture(GL_TEXTURE_2D, this->textureID);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, data);
}

    b、读取数据

float* WKS::Texture::GetTextureData(GLuint width, GLuint height, GLuint channels, GLuint texID) {
    float* data = new float[width * height * channels];
    glBindTexture(GL_TEXTURE_2D, texID);
    if(channels==1)    glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_FLOAT, data);
    if(channels==3) glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_FLOAT, data);
    if (channels == 4) glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, data);
    glBindTexture(GL_TEXTURE_2D, 0);
    return data;
}

 

  现在来介绍Compute Shader

#version 430 core
layout (local_size_x=16, local_size_y=16) in;

uniform float v[4];

layout (rgba32f, binding = 0) uniform image2D input_image;
layout (rgba32f, binding = 1) uniform image2D output_image;

shared vec4 mat_shared[16][16];

void main(void)
{
    ivec2 pos=ivec2(gl_GlobalInvocationID.xy);
    mat_shared[pos.x][pos.y]=imageLoad(input_image,pos);
    barrier();
    vec4 data=mat_shared[pos.x][pos.y];
    data.r=v[0]+data.r;
    data.g=v[1]+data.g;
    data.b=v[2]+data.b;
    data.a=v[3]+data.a;
    imageStore(output_image,pos.xy,data);
}

  

  计算由一个一个计算单元完成,layout (local_size_x=16, local_size_y=16) in; 是表示本地工作组的由16*16的计算单元组成,本地工作组可以共享Shadered变量。
  多个本地工作组构成全局工作组,由:
glDispatchCompute(1, 1, 1);

  启动计算,参数表示全局工作组的维度(以本地工作组为单位),(1,1,1)表示只有一个本地工作组。

  注意:Compute Shader 只有一个阶段(渲染一般是vertex+fragment 2个阶段),编译类型选择GL_COMPUTE_SHADER

Shader(const char* computePath) :programId(0)
    {
        std::vector<ShaderFile> fileVec;
        fileVec.push_back(ShaderFile(GL_COMPUTE_SHADER, computePath));
        loadFromFile(fileVec);
    }

  

  示例:

  对一个4*4的vec4矩阵的所有元素加上vec4(0, 0.1,0.2,0.3)

  初始化:

void SceneRendering::setupAddData() {
    int num = 4 * 4 * 4;
    this->inputData = new float[num];
    for (int i = 0; i < num; i++) inputData[i] = i;
    for (int i = 0; i < 4; i++) v[i] = i*0.1f;
    shader_add = new Shader("./Shader/add.comp");
    WKS::ImageTexture* texturePtr = new WKS::ImageTexture(4, 4);
    this->inputTexture = texturePtr->GetTextureID();
    this->outputTexture = (new WKS::ImageTexture(4, 4))->GetTextureID();
    texturePtr->Transfer2Texture(inputData);
}

  调用Compute Shader:

void SceneRendering::performCompute() {
    this->shader_add->use();
    this->shader_add->setVecN("v", 4, v);
    glBindImageTexture(0, this->inputTexture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
    glBindImageTexture(1, this->outputTexture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
    glDispatchCompute(1, 1, 1);
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
    glFinish();
}

  主函数调用,结果输出:

   glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 我们现在不使用模板缓冲//Compute Shader
    this->performCompute();
    float* data = WKS::Texture::GetTextureData(4, 4, 4, this->outputTexture);
    int index = 0;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            std::cout << "(" <<data[index]<<","<<data[index+1]<<","<<data[index+2]<<","<<data[index+3]<< ")" << " ";
            index += 4;
        }
        std::cout << std::endl;
    }
    std::cout<< std::endl;
    free(data);

  图片:

  技术图片

  

  

以上是关于在OpenGL中使用Compute Shader(一)的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL4.3新特性: 计算着色器 Compute Shader

在DirectX 12中使用compute shader

使用Compute Shader加速Irradiance Environment Map的计算

聊聊如何正确向Compute Shader传递数组

[OpenGL] 使用计算着色器进行预烘焙

OpenGL下多个sampler在shader中的使用