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

How to write an OpenGL fragment shader in two stages?

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

但假设您放大分形,突然间您在屏幕上看到的所有激活都在 .87 和 .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, 0, 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, 0, 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.