一、前言
尝试将在OpenGL下实现的FFT海洋移植到OSG内,用到了计算着色器,发现网上对于OSG使用计算着色器的资料很少,在此做个记录。
参考资料:杨石兴大佬的文章 https://blog.csdn.net/FreeSouthS/article/details/118971243 以及OSG源码中的osgcomputeshaders范例项目
基于OSG版本:3.6.3
二、代码
先直接上代码:
#include <osgViewer/viewer>
#include <osgDB/ReadFile>
#include <osg/Texture2D>
#include <osg/BindImageTexture>
#include <osg/DispatchCompute>
#include <osg/PolygonMode>
osg::Geometry* createFlat();
const int N = 16;
int main()
{
float* Data1 = new float[N * N * 2];
float* Data2 = new float[N * N * 2];
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
Data1[i * 2 * N + j * 2] = 0.5;
Data1[i * 2 * N + j * 2 + 1] = 0.0;
Data2[i * 2 * N + j * 2] = 0.0;
Data2[i * 2 * N + j * 2 + 1] = 1.0;
}
}
osg::ref_ptr<osg::Image> Data1lmg = new osg::Image;
Data1lmg->setImage(N, N, 1, GL_RG32F, GL_RG, GL_FLOAT, (unsigned char*)Data1, osg::Image::USE_NEW_DELETE);
osg::ref_ptr<osg::Texture2D> Data1Texture = new osg::Texture2D(Data1lmg);
Data1Texture->setTextureSize(N, N);
Data1Texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
Data1Texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
Data1Texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
Data1Texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
Data1Texture->setInternalFormat(GL_RG32F);
Data1Texture->setSourceFormat(GL_RG);
Data1Texture->setSourceType(GL_FLOAT);
osg::ref_ptr<osg::Image> Data2lmg = new osg::Image;
Data2lmg->setImage(N, N, 1, GL_RG32F, GL_RG, GL_FLOAT, (unsigned char*)Data2, osg::Image::USE_NEW_DELETE);
osg::ref_ptr<osg::Texture2D> Data2Texture = new osg::Texture2D(Data2lmg);
Data2Texture->setTextureSize(N, N);
Data2Texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
Data2Texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
Data2Texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
Data2Texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
Data2Texture->setInternalFormat(GL_RG32F);
Data2Texture->setSourceFormat(GL_RG);
Data2Texture->setSourceType(GL_FLOAT);
osg::ref_ptr<osg::Texture2D> DataTexture = new osg::Texture2D;
DataTexture->setTextureSize(N, N);
DataTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
DataTexture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
DataTexture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
DataTexture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
DataTexture->setInternalFormat(GL_RG32F);
DataTexture->setSourceFormat(GL_RG);
DataTexture->setSourceType(GL_FLOAT);
osg::ref_ptr<osg::Texture2D> reTexture = new osg::Texture2D;
reTexture->setTextureSize(N, N);
reTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
reTexture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
reTexture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
reTexture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
reTexture->setInternalFormat(0x8814);//GL_RGBA32F
reTexture->setSourceFormat(GL_RGBA);
reTexture->setSourceType(GL_FLOAT);
osg::ref_ptr<osg::BindImageTexture> Data1Binding = new osg::BindImageTexture(0, Data1Texture, osg::BindImageTexture::READ_ONLY, GL_RG32F);
osg::ref_ptr<osg::BindImageTexture> Data2Binding = new osg::BindImageTexture(1, Data2Texture, osg::BindImageTexture::READ_ONLY, GL_RG32F);
osg::ref_ptr<osg::BindImageTexture> DataBinding = new osg::BindImageTexture(2, DataTexture, osg::BindImageTexture::READ_WRITE, GL_RG32F);
osg::ref_ptr<osg::BindImageTexture> reBinding = new osg::BindImageTexture(3, reTexture, osg::BindImageTexture::READ_WRITE, 0x8814);
osg::ref_ptr<osg::Program> DataShader = new osg::Program;
DataShader->addShader(osgDB::readRefShaderFile("./cs1.compute"));
osg::ref_ptr<osg::Node> DataDispatchNode = new osg::DispatchCompute(1, 1, 1);
DataDispatchNode->setDataVariance(osg::Object::DYNAMIC);
DataDispatchNode->getOrCreateStateSet()->setAttributeAndModes(DataShader);
DataDispatchNode->getOrCreateStateSet()->addUniform(new osg::Uniform("red_image", (int)0));
//DataDispatchNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, Data1Texture.get());
DataDispatchNode->getOrCreateStateSet()->setAttributeAndModes(Data1Binding.get());
DataDispatchNode->getOrCreateStateSet()->addUniform(new osg::Uniform("green_image", (int)1));
//DataDispatchNode->getOrCreateStateSet()->setTextureAttributeAndModes(1, Data2Texture.get());
DataDispatchNode->getOrCreateStateSet()->setAttributeAndModes(Data2Binding.get());
DataDispatchNode->getOrCreateStateSet()->addUniform(new osg::Uniform("output_image", (int)2));
//DataDispatchNode->getOrCreateStateSet()->setTextureAttributeAndModes(2, DataTexture.get());
DataDispatchNode->getOrCreateStateSet()->setAttributeAndModes(DataBinding.get());
osg::ref_ptr<osg::Program> reShader = new osg::Program;
reShader->addShader(osgDB::readRefShaderFile("./cs2.compute"));
osg::ref_ptr<osg::Node> reDispatchNode = new osg::DispatchCompute(1, 1, 1);
reDispatchNode->setDataVariance(osg::Object::DYNAMIC);
reDispatchNode->getOrCreateStateSet()->setAttributeAndModes(reShader);
reDispatchNode->getOrCreateStateSet()->addUniform(new osg::Uniform("input_image", (int)2));
//reDispatchNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, DataTexture.get());
reDispatchNode->getOrCreateStateSet()->setAttributeAndModes(DataBinding.get());
reDispatchNode->getOrCreateStateSet()->addUniform(new osg::Uniform("output_image", (int)3));
//reDispatchNode->getOrCreateStateSet()->setTextureAttributeAndModes(1, reTexture.get());
reDispatchNode->getOrCreateStateSet()->setAttributeAndModes(reBinding.get());
auto quad = createFlat();
quad->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
//use Shader
osg::ref_ptr<osg::StateSet> ss = quad->getOrCreateStateSet();
osg::ref_ptr<osg::Program> program = new osg::Program;
osg::ref_ptr<osg::Shader> vertShader = new osg::Shader(osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> fragShader = new osg::Shader(osg::Shader::FRAGMENT);
vertShader->loadShaderSourceFromFile("./Test.vert");
fragShader->loadShaderSourceFromFile("./Test.frag");
program->addShader(vertShader);
program->addShader(fragShader);
ss->setAttributeAndModes(program);
ss->setTextureAttributeAndModes(0, reTexture.get());
ss->setAttributeAndModes(reBinding.get());
osg::ref_ptr<osg::Group> scene = new osg::Group;
scene->addChild(DataDispatchNode);
scene->addChild(reDispatchNode);
scene->addChild(quad);
osgViewer::Viewer viewer;
viewer.setSceneData(scene);
viewer.realize();
//viewer.getCamera()->getGraphicsContext()->getState()->resetVertexAttributeAlias(false);
viewer.getCamera()->getGraphicsContext()->getState()->setUseModelViewAndProjectionUniforms(true);
viewer.getCamera()->getGraphicsContext()->getState()->setUseVertexAttributeAliasing(true);
return viewer.run();
}
osg::Geometry* createFlat()
{
osg::Geometry* geom = new osg::Geometry;
geom->setUseVertexBufferObjects(true);
osg::Vec3Array* vArr = new osg::Vec3Array;
osg::Vec2Array* tArr = new osg::Vec2Array;
vArr->push_back(osg::Vec3(1.0, 1.0, 0.0));
vArr->push_back(osg::Vec3(1.0, -1.0, 0.0));
vArr->push_back(osg::Vec3(-1.0, -1.0, 0.0));
vArr->push_back(osg::Vec3(-1.0, 1.0, 0.0));
tArr->push_back(osg::Vec2(0.0, 0.0));
tArr->push_back(osg::Vec2(1.0, 0.0));
tArr->push_back(osg::Vec2(1.0, 1.0));
tArr->push_back(osg::Vec2(0.0, 1.0));
osg::DrawElementsUInt* el = new osg::DrawElementsUInt(GL_QUADS);
el->push_back(0);
el->push_back(1);
el->push_back(2);
el->push_back(3);
geom->setVertexArray(vArr);
//geom->setTexCoordArray(0, tArr);
geom->setVertexAttribArray(1, tArr, osg::Array::BIND_PER_VERTEX);
geom->addPrimitiveSet(el);
return geom;
}
计算着色器1:
#version 430
layout (local_size_x = 16, local_size_y = 16) in;
layout (binding = 0, rg32f) uniform image2D red_image;
layout (binding = 1, rg32f) uniform image2D green_image;
layout (binding = 2, rg32f) uniform image2D output_image;
void main()
{
vec2 v1 = imageLoad(red_image, ivec2(gl_GlobalInvocationID.xy)).xy;
vec2 v2 = imageLoad(green_image, ivec2(gl_GlobalInvocationID.xy)).xy;
vec2 v3 = v1 + v2;
imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(v3, 0.0, 1.0));
}
计算着色器2:
#version 430
layout (local_size_x = 16, local_size_y = 16) in;
layout (binding = 0, rg32f) uniform image2D input_image;
layout (binding = 1, rgba32f) uniform image2D output_image;
void main()
{
vec2 v1 = imageLoad(input_image, ivec2(gl_GlobalInvocationID.xy)).xy;
imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(v1, 1.0, 1.0));
}
顶点着色器:
#version 430
layout(binding = 0) uniform sampler2D testTexture;
layout(location = 0) in vec3 vPos;
layout(location = 1) in vec2 tPos;
uniform mat4 osg_ModelViewProjectionMatrix;
out vec3 color;
void main()
{
color = texture(testTexture, tPos).rgb;
gl_Position = osg_ModelViewProjectionMatrix*vec4(vPos,1.0);
}
片元着色器:
#version 430
in vec3 color;
out vec4 FragColor;
void main()
{
FragColor = vec4(color, 1.0);
}
结果:
代码功能就是生成一张每个像素0.5红色的材质,生成一张每个像素绿色的材质,在计算着色器1里将每个像素两个颜色相加后输出,然后输入进计算着色器2中将每个像素加上蓝色后输出,然后将材质赋予一个矩形平面。
三、记录
osg::ref_ptr<osg::BindImageTexture> Data1Binding = new osg::BindImageTexture(0, Data1Texture, osg::BindImageTexture::READ_ONLY, GL_RG32F);
//三个函数
DataDispatchNode->getOrCreateStateSet()->addUniform(new osg::Uniform("red_image", (int)0));//1
DataDispatchNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, Data1Texture.get());//2
DataDispatchNode->getOrCreateStateSet()->setAttributeAndModes(Data1Binding.get());//3
0、 最主要的就是上面这个类的对象和这三个函数的调用。
1、 BindImageTexture似乎跟GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2等等相关,相当于将材质绑定到了对应的GL_TEXTURE上。
2、 addUniform(new osg::Uniform(“red_image”, (int)0));对于给Shader中的贴图uniform设置这个,即是将对应序号的GL_TEXTURE绑定到这个uniform上。(这个跟OpenGL中的用法一致)
3、 似乎读数据的话必须要调用setAttributeAndModes(BindImageTexture*)。(因为这个卡了很久,先是参考的osgcomputeshaders范例项目,里面计算着色器并没有读取贴图数据,所以没有调用这个,导致我自己代码的贴图数据读不出来,死活找不到原因,后来还是偶然发现了大佬的文章,里面范例代码的计算着色器虽然也没有读取贴图数据,但是加上了这个调用,就奇怪有什么区别,试了一下发现问题解决了)
4、 在创建BindImageTexture对象后,如果不调用上面三个函数,计算着色器则会默认绑定对应创建时绑定的贴图对象。
5、 顶点着色器中读取sampler2D时必须调用setTextureAttributeAndModes(0, Data1Texture.get());和setAttributeAndModes(Data1Binding.get());这两个函数,跟设置uniform无关。setTextureAttributeAndModes似乎是跟location(binding)设置相关。
6、 对于计算着色器的设置,setTextureAttributeAndModes似乎无效,主要以addUniform设置为准。
小结: 对于计算着色器主要设置addUniform,对于顶点片元着色器主要设置setTextureAttributeAndModes,如果需要读取数据则需要调用setAttributeAndModes(BindImageTexture*),BindImageTexture对象则相当于绑定到对应的GL_TEXTURE位置上。