0

I am writing a program, which draws the Mandelbrot set. For every pixel, I run a function and it returns an activation number between 0 and 1. Currently, this is done in a fragment shader and activation is my color.

But Imagine you zoom in on the fractal, suddenly all the activations you can see on the screen are between .87 and .95. You can't see the difference very well.

I am looking for a way to first calculate all the activations and store them in an array then based on that array choose the colors. Both of those need to run on the GPU for performance reasons.

  • see [Can't find a way to color the Mandelbrot-set the way i'm aiming for](https://stackoverflow.com/a/56197067/2521214) it uses fractional escape to enhance the result many times without any additional iterations and also it uses techniques from this [Mandelbrot Set - Color Spectrum Suggestions?](https://stackoverflow.com/a/53666890/2521214) which is exactly answering what you ask here. The 2 pass histogram based method is the way – Spektre Mar 02 '21 at 08:07
  • Does this answer your question? [Can't find a way to color the Mandelbrot-set the way i'm aiming for](https://stackoverflow.com/questions/56195735/cant-find-a-way-to-color-the-mandelbrot-set-the-way-im-aiming-for) – Spektre Mar 02 '21 at 08:18
  • I am so sorry I took so long to respond. I tried my best at implementing the code from the question you posted. I failed miserably. I can't even run the code provided. But when I download the app it works and does look right. – Miki Vanoušek Mar 04 '21 at 16:01
  • Honestly, I do not understand why it does not work for me, but I gave up. I will just have to figure out how to do it on my own... Thank you so much for your help, though. – Miki Vanoušek Mar 04 '21 at 16:06
  • I found this wiki article super useful. https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set#Escape_time_algorithm – Miki Vanoušek Mar 04 '21 at 17:19

2 Answers2

0

So you need to find minimum and maximum intensity of a picture you've rendered. This cannot be done in a single draw, since these values are nonlocal. A possible way to do this is to recursively apply a pipeline that downscales an image in half, computing the minimum and maximum values of 2x2 squares and storing them e.g. in an RG texture (kind of mipmap generation, with min/max instead of averaging colours). In the end you have a 1x1 texture which contains the minimal and maximal values of your image in its only pixel. You can sample this texture in the final render that maps activation values to colours.

lisyarus
  • 15,025
  • 3
  • 43
  • 68
  • 1. I do not see how mipmaping would help here as with zoom the number of max iterations usually increase making it not the same as unzoomed result of the same area (unless the max iterations are constant). 2. yes two pass is the way however just finding min and max is not enough as the range contains a majority of gaps between used intensties ... I developed this [Mandelbrot Set - Color Spectrum Suggestions?](https://stackoverflow.com/a/53666890/2521214) histogram based approach that deals with it much more better – Spektre Mar 02 '21 at 08:11
0

I solved my issue by creating a new gll program and attaching a compute shader to it.

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);

Then, in the Render loop I switch programs and run the compute shader.

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);

I pass the data from compute shader to fragment shader via shader storage buffer.

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);
}

In the compute shader, I can access the buffer like this:

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;
};

And finally, in the fragment shader, I can read the buffer like this:

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.