C++ OpenGL计算着色器法线贴图生成性能差

C++ OpenGL计算着色器法线贴图生成性能差,c++,opengl,graphics,glsl,compute-shader,C++,Opengl,Graphics,Glsl,Compute Shader,我有一个高度立方体贴图,我想从中生成一个法线立方体贴图纹理。我的高度立方体贴图只是一个2048x2048图像,我在应用程序开始时为立方体的每个面加载该图像,我可以实时修改“最大高度”值,该值在检索高度贴图中的像素时用作乘法器 最初我在顶点着色器中计算法线,但它给了我糟糕的照明结果,所以我决定在片段着色器中移动计算 由于高度贴图不会更改每一帧(仅当我修改“最大高度”值时),我希望使用计算着色器从它生成法线贴图纹理,因为我不需要任何光栅化,但它的性能非常差 使用片段着色器时,我以200FPS的速度运

我有一个高度立方体贴图,我想从中生成一个法线立方体贴图纹理。我的高度立方体贴图只是一个2048x2048图像,我在应用程序开始时为立方体的每个面加载该图像,我可以实时修改“最大高度”值,该值在检索高度贴图中的像素时用作乘法器

最初我在顶点着色器中计算法线,但它给了我糟糕的照明结果,所以我决定在片段着色器中移动计算

由于高度贴图不会更改每一帧(仅当我修改“最大高度”值时),我希望使用计算着色器从它生成法线贴图纹理,因为我不需要任何光栅化,但它的性能非常差

使用片段着色器时,我以200FPS的速度运行,但使用计算着色器时,我以40fps的速度运行

以下是我如何绑定图像并开始计算工作:

_computeShaderProgram.use();

glUniform1f(_computeShaderProgram.getUniformLocation("maxHeight"), maxHeight);

glBindImageTexture(
    0,
    static_cast<GLuint>(heightMap),
    0,
    GL_TRUE,
    0,
    GL_READ_ONLY,
    GL_RGBA32F
);

glBindImageTexture(
    1,
    static_cast<GLuint>(normalMap),
    0,
    GL_TRUE,
    0,
    GL_WRITE_ONLY,
    GL_RGBA32F
);

// Start compute work
// I only compute for one face of the cube map
glDispatchCompute(normalMap.getWidth() / 16, normalMap.getWidth() / 16, 1);

glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
我尝试了不同的工作组大小(宽度、宽度/8、宽度/16、宽度/32)和本地大小(1、8、16、32),但性能总是很差,对于全宽的工作组,大约为40 FPS或20 FPS

我知道我可以对同一工作组中的线程使用共享内存,以防止获取相同的纹理坐标4次,但稍后我将按程序生成高度贴图,并且我认为会大于2048x2048

片段着色器和计算着色器之间的区别是什么使其如此缓慢?我做错什么了吗

是否有其他解决方案生成此法线贴图

编辑:

我上面给出的fps是不正确的,因为我生成了法线贴图的1/16(当我有40FPS时),并且我还使用了中心差分技术来计算法线,这很便宜,但没有提供良好的照明效果,所以我切换到Sobel技术,这稍微贵一点

我做了一些测试,以了解哪种技术可以提供最好的性能。 每一帧我都会生成法线贴图(以后不会出现这种情况,但只是为了测试性能)。以下是我的测试:

  • CPU端单线程:1.5FPS
  • 计算局部大小为1且每个图像像素有一个工作组的着色器:4FPS
  • 使用16的局部大小和每个16x16图像像素块的一个工作组计算着色器:11FPS
  • 使用帧缓冲区和MRT的片段着色器,带有6个颜色附件(法线贴图的每个面一个):12.5FPS

当我修改最大高度(再次生成法线贴图)时,这有点滞后,但我认为这没关系,因为我不会对其进行太多修改。

你说“不需要光栅化”,但你刚刚在计算着色器中重新实现了光栅化。对不起,我应该更精确,我指的是顶点着色器和片段着色器之间的值插值。我可以使用顶点着色器+片段着色器,但我真的不需要图形管道中发生的所有阶段,为此我还需要在VBO中存储一个平面。没关系,你不必使用顶点插值。您也不需要在VBO中存储平面,只需从顶点ID在顶点着色器中创建顶点数据:您说“不需要图形管道中发生的所有阶段”,但这些阶段通过硬件实现进行了高度优化,并且您实际上不需要为使用它们支付额外费用。片段着色器也将在组中运行,以优化内存位置,以便在常见情况下进行纹理访问,这是您的计算着色器所不具备的。从根本上说,普通图形管道经过了大量优化,可以完全执行您想要执行的操作,使用计算着色器会丢弃所有优化,这样您也可以去掉其他部分。我想到了“把孩子连同洗澡水一起扔出去”这句话。很抱歉反应太晚。感谢@DietrichEpp的帮助。您是对的,对于我的情况,片段着色器比计算着色器快(但接近计算着色器性能,请参见我的编辑)。
#version 430 core
#extension GL_ARB_compute_shader : enable

layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;

layout(rgba32f, binding = 0) readonly uniform imageCube heightMap;
layout(rgba32f, binding = 1) writeonly uniform imageCube normalMap;

uniform float maxHeight;

float getHeight(ivec3 heightMapCoord) {
    vec4 heightMapValue = imageLoad(heightMap, heightMapCoord);

    return heightMapValue.r * maxHeight;
}

void main() {
    ivec3 textCoord = ivec3(gl_GlobalInvocationID);

    // Calculate height of neighbors
    float leftCubePosHeight = getHeight(textCoord + ivec3(-1, 0, 0));
    float rightCubePosHeight = getHeight(textCoord + ivec3(1, 0, 0));
    float topCubePosHeight = getHeight(textCoord + ivec3(0, -1, 0));
    float bottomCubePosHeight = getHeight(textCoord + ivec3(0, 1, 0));

    // Calculate normal using central differences method
    vec3 horizontal = vec3(2.0, rightCubePosHeight - leftCubePosHeight, 0.0);
    vec3 vertical = vec3(0.0, bottomCubePosHeight - topCubePosHeight, 2.0);
    vec3 normal = normalize(cross(vertical, horizontal));

    imageStore(normalMap, textCoord, vec4(normal, 1.0));
}