如何分两个阶段编写 OpenGL 片段着色器?

问题描述

我正在编写一个程序,用于绘制 Mandelbrot 集。对于每个像素,我运行一个函数,它返回一个介于 0 和 1 之间的激活数。目前,这是在片段着色器中完成的,激活是我的颜色。

但是想象一下你放大分形,突然间你能在屏幕上看到的所有激活值都在 0.87 到 0.95 之间。你看不出区别。

我正在寻找一种方法来首先计算所有激活并将它们存储在一个数组中,然后根据该数组选择颜色。出于性能原因,这两者都需要在 GPU 上运行。

解决方法

所以你需要找到你渲染的图片的最小和最大强度。这不能在一次绘制中完成,因为这些值是非本地的。一种可能的方法是递归地应用将图像缩小一半的管道,计算 2x2 正方形的最小值和最大值并存储它们,例如在 RG 纹理中(一种 mipmap 生成,使用 min/max 而不是平均颜色)。最后,您有一个 1x1 纹理,其中包含图像的唯一像素中的最小值和最大值。您可以在将激活值映射到颜色的最终渲染中对该纹理进行采样。

,

我通过创建一个新的 gll 程序并将计算着色器附加到它来解决了我的问题。

unsigned int vs = CompileShader(vertShaderStr,GL_VERTEX_SHADER);
unsigned int fs = CompileShader(fragShaderStr,GL_FRAGMENT_SHADER);
unsigned int cs = CompileShader(compShaderStr,GL_COMPUTE_SHADER);

glAttachShader(mainProgram,vs);
glAttachShader(mainProgram,fs);
glAttachShader(computeProgram,cs);

glLinkProgram(computeProgram);
glValidateProgram(computeProgram);

glLinkProgram(mainProgram);
glValidateProgram(mainProgram);

glUseProgram(computeProgram);

然后,在渲染循环中,我切换程序并运行计算着色器。

glUseProgram(computeProgram);
    glDispatchCompute(resolutionX,resolutionY,1);
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(mainProgram);

    /* Drawing the whole screen using the shader */
    glDrawArrays(GL_TRIANGLE_STRIP,4);

    /* Poll for and process events */
    glfwPollEvents();
    
    updateBuffer();
    Update();

    /* Swap front and back buffers */
    glfwSwapBuffers(window);

我通过着色器存储缓冲区将数据从计算着色器传递到片段着色器。

void setupBuffer() {
    glGenBuffers(1,&ssbo);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER,ssbo);
    glNamedBufferStorage(ssbo,sizeof(float) * (resolutionX * resolutionY + 

    SH_EXTRA_FLOATS),&data,GL_MAP_WRITE_BIT | GL_MAP_READ_BIT | GL_DYNAMIC_STORAGE_BIT); //sizeof(data) only works for statically sized C/C++ arrays.
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER,1,ssbo);
}

void updateBuffer() {
    float d[] = { data.min,data.max };
    glNamedBufferSubData(ssbo,2 * sizeof(float),&d);
}

在计算着色器中,我可以像这样访问缓冲区:

layout(std430,binding = 1) buffer bufferIn
    {
        float min;
        float max;
        float data[];
    };

layout(std430,binding = 1) buffer destBuffer
{
    float min;
    float max;
    float data[];
} outBuffer;

void main() {
    screenResolution;
    int index = int(gl_WorkGroupID.x + screenResolution.x * gl_WorkGroupID.y);
    dvec2 coords = adjustCoords();

    dvec4 position = rotatedPosition(coords);

    for (i = 0; i < maxIter; i++) {
        position = pow2(position);
        double length = lengthSQ(position);
        if (length > treashold) {
            float log_zn = log(float(length)) / 2.0;
            float nu = log(log_zn / log(2.0)) / log2;
            float iterAdj = 1.0 - nu + float(i);
            float scale = iterAdj / float(maxIter);
            if (scale < 0)
                data[index] = -2;
            data[index] = scale;
            if (scale > max) max = scale;
            if (scale < min && scale > 0) min = scale;
            return;
        }
    }
    data[index] = -1;
};

最后,在片段着色器中,我可以像这样读取缓冲区:

layout(std430,binding = 1) buffer bufferIn
{
    float min;
    float max;
    float data[];
};

if (data[index] == -1) {
    color = notEscapedColor;
    return;
}
float value = (data[index] - min) / (max - min);
if (value < 0) value = 0;
if (value > 1) value = 1;

Here is the code in its entirety.