Can't Find a Way to Color the Mandelbrot-Set the Way I'm Aiming For

Can't find a way to color the Mandelbrot-set the way i'm aiming for

First take a look at this related QA:

  • Mandelbrot Set - Color Spectrum Suggestions?

The main idea is to use histogram to distribute the color gradients more effectively to used indexes instead of uniformly wasting many colors on unused indexes. Also it uses a specific visually pleasing gradient function:

  • RGB values of visible spectrum

Dynamic max iterations count suggested by others will only affect overall performance and details in zooms. However if you want nice colors without zoom then you need to compute floating point iterations count which is also called Mandelbrot Escape. There is a math way that can compute the iterations count fractional part from the last sub-results of the equation. For more info see:

  • Renormalizing the Mandelbrot Escape

However I never tried it so read this with prejudice: If I read it right what you want is to compute this equation:

mu = m + frac = n + 1 - log (log  |Z(n)|) / log 2

Where n is your iteration count, Z(n) is the complex domain sub-result of the equation you are iterating on. So now compute color from mu which is floating point now instead of from n...

[Edit2] GLSL mandelbrot with fractional escape based on links above

I added the fractional escape and modified the histogram multi pass recoloring to match new output...

Vertex:

// Vertex
#version 420 core
layout(location=0) in vec2 pos; // glVertex2f <-1,+1>
out smooth vec2 p; // texture end point <0,1>
void main()
{
p=pos;
gl_Position=vec4(pos,0.0,1.0);
}

Fragment:

// Fragment
#version 420 core
uniform vec2 p0=vec2(0.0,0.0); // mouse position <-1,+1>
uniform float zoom=1.000; // zoom [-]
uniform int n=100; // iterations [-]
uniform int sh=7; // fixed point accuracy [bits]
uniform int multipass=0; // multi pass?
in smooth vec2 p;
out vec4 col;

const int n0=1; // forced iterations after escape to improve precision

vec3 spectral_color(float l) // RGB <0,1> <- lambda l <400,700> [nm]
{
float t; vec3 c=vec3(0.0,0.0,0.0);
if ((l>=400.0)&&(l<410.0)) { t=(l-400.0)/(410.0-400.0); c.r= +(0.33*t)-(0.20*t*t); }
else if ((l>=410.0)&&(l<475.0)) { t=(l-410.0)/(475.0-410.0); c.r=0.14 -(0.13*t*t); }
else if ((l>=545.0)&&(l<595.0)) { t=(l-545.0)/(595.0-545.0); c.r= +(1.98*t)-( t*t); }
else if ((l>=595.0)&&(l<650.0)) { t=(l-595.0)/(650.0-595.0); c.r=0.98+(0.06*t)-(0.40*t*t); }
else if ((l>=650.0)&&(l<700.0)) { t=(l-650.0)/(700.0-650.0); c.r=0.65-(0.84*t)+(0.20*t*t); }
if ((l>=415.0)&&(l<475.0)) { t=(l-415.0)/(475.0-415.0); c.g= +(0.80*t*t); }
else if ((l>=475.0)&&(l<590.0)) { t=(l-475.0)/(590.0-475.0); c.g=0.8 +(0.76*t)-(0.80*t*t); }
else if ((l>=585.0)&&(l<639.0)) { t=(l-585.0)/(639.0-585.0); c.g=0.84-(0.84*t) ; }
if ((l>=400.0)&&(l<475.0)) { t=(l-400.0)/(475.0-400.0); c.b= +(2.20*t)-(1.50*t*t); }
else if ((l>=475.0)&&(l<560.0)) { t=(l-475.0)/(560.0-475.0); c.b=0.7 -( t)+(0.30*t*t); }
return c;
}

void main()
{
int i,j,N;
vec2 pp;
float x,y,q,xx,yy,mu;
pp=(p/zoom)-p0; // y (-1.0, 1.0)
pp.x-=0.5; // x (-1.5, 0.5)
for (x=0.0,y=0.0,xx=0.0,yy=0.0,i=0;(i<n-n0)&&(xx+yy<4.0);i++)
{
q=xx-yy+pp.x;
y=(2.0*x*y)+pp.y;
x=q;
xx=x*x;
yy=y*y;
}
for (j=0;j<n0;j++,i++) // 2 more iterations to diminish fraction escape error
{
q=xx-yy+pp.x;
y=(2.0*x*y)+pp.y;
x=q;
xx=x*x;
yy=y*y;
}
mu=float(i)-log(log(sqrt(xx+yy))/log(2.0));
mu*=float(1<<sh); i=int(mu);
N=n<<sh;
if (i>N) i=N;
if (i<0) i=0;

if (multipass!=0)
{
// i
float r,g,b;
r= i &255; r/=255.0;
g=(i>> 8)&255; g/=255.0;
b=(i>>16)&255; b/=255.0;
col=vec4(r,g,b,255);
}
else{
// RGB
q=float(i)/float(N);
q=pow(q,0.2);
col=vec4(spectral_color(400.0+(300.0*q)),1.0);
}
}

CPU side C++/VCL code:

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl\\OpenGL3D_double.cpp"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
OpenGLscreen scr;
GLSLprogram shd;
float mx=0.0,my=0.0,mx0=0.0,my0=0.0,mx1=0.0,my1=0.0;
TShiftState sh0,sh1;
int xs=1,ys=1;
float zoom=1.000;
int sh=7;
int N=256;
int _multi=0;
unsigned int queryID[2];
#define multi_pass
OpenGLtexture txr;
//---------------------------------------------------------------------------
DWORD spectral_color(float l) // RGB <0,1> <- lambda l <400,700> [nm]
{
float t; float r,g,b; DWORD c,x; r=0.0; g=0.0; b=0.0;
if ((l>=400.0)&&(l<410.0)) { t=(l-400.0)/(410.0-400.0); r= +(0.33*t)-(0.20*t*t); }
else if ((l>=410.0)&&(l<475.0)) { t=(l-410.0)/(475.0-410.0); r=0.14 -(0.13*t*t); }
else if ((l>=545.0)&&(l<595.0)) { t=(l-545.0)/(595.0-545.0); r= +(1.98*t)-( t*t); }
else if ((l>=595.0)&&(l<650.0)) { t=(l-595.0)/(650.0-595.0); r=0.98+(0.06*t)-(0.40*t*t); }
else if ((l>=650.0)&&(l<700.0)) { t=(l-650.0)/(700.0-650.0); r=0.65-(0.84*t)+(0.20*t*t); }
if ((l>=415.0)&&(l<475.0)) { t=(l-415.0)/(475.0-415.0); g= +(0.80*t*t); }
else if ((l>=475.0)&&(l<590.0)) { t=(l-475.0)/(590.0-475.0); g=0.8 +(0.76*t)-(0.80*t*t); }
else if ((l>=585.0)&&(l<639.0)) { t=(l-585.0)/(639.0-585.0); g=0.84-(0.84*t) ; }
if ((l>=400.0)&&(l<475.0)) { t=(l-400.0)/(475.0-400.0); b= +(2.20*t)-(1.50*t*t); }
else if ((l>=475.0)&&(l<560.0)) { t=(l-475.0)/(560.0-475.0); b=0.7 -( t)+(0.30*t*t); }
r*=255.0; g*=255.0; b*=255.0;
x=r; c =x;
x=g; c|=x<<8;
x=b; c|=x<<16;
return c;
}
//---------------------------------------------------------------------------
void gl_draw()
{
scr.cls();

// matrix for old GL rendering
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_TEXTURE);
glLoadIdentity();

// GLSL uniforms
shd.bind();
shd.set2f("p0",mx,my); // pan position
shd.set1f("zoom",zoom); // zoom
shd.set1i("n",N); // iterations
shd.set1i("sh",sh); // fixed point accuracy (shift)
shd.set1i("multipass",_multi); // single/multi pass

// issue the first query
// Records the time only after all previous
// commands have been completed
glQueryCounter(queryID[0], GL_TIMESTAMP);

// QUAD covering screen
glColor3f(1.0,1.0,1.0);
glBegin(GL_QUADS);
glVertex2f(-1.0,+1.0);
glVertex2f(-1.0,-1.0);
glVertex2f(+1.0,-1.0);
glVertex2f(+1.0,+1.0);
glEnd();
shd.unbind();

// [multipas]
if (_multi)
{
float t,m,n=N<<sh;
DWORD *hist=new DWORD[n+1];
int sz=txr.xs*txr.ys,i,j;
// get rendered image
glReadPixels(0,0,txr.xs,txr.ys,GL_RGBA,GL_UNSIGNED_BYTE,txr.txr);
// compute histogram
for (i=0;i<=n;i++) hist[i]=0;
for (i=0;i<sz;i++) hist[txr.txr[i]&0x00FFFFFF]++;
// histogram -> used color index (skip holes)
for (i=1,j=1;i<=n;i++)
if (hist[i]){ hist[i]=j; j++; }
// used color index -> color
m=1.0/float(j); hist[0]=0x00000000;
for (i=1;i<=n;i++)
if (hist[i]){ t=hist[i]; t*=m; hist[i]=spectral_color(400.0+(300.0*t)); }
else hist[i]=0x00000000;
// recolor image
for (i=0;i<sz;i++) txr.txr[i]=hist[txr.txr[i]&0x00FFFFFF];
// render it back
scr.cls();
txr.bind();
glColor3f(1.0,1.0,1.0);
glBegin(GL_QUADS);
glTexCoord2f(0.0,1.0); glVertex2f(-1.0,+1.0);
glTexCoord2f(0.0,0.0); glVertex2f(-1.0,-1.0);
glTexCoord2f(1.0,0.0); glVertex2f(+1.0,-1.0);
glTexCoord2f(1.0,1.0); glVertex2f(+1.0,+1.0);
glEnd();
txr.unbind();
glDisable(GL_TEXTURE_2D);
delete[] hist;
}

// issue the second query
// records the time when the sequence of OpenGL
// commands has been fully executed
glQueryCounter(queryID[1], GL_TIMESTAMP);

// GL driver info and GLSL log
scr.text_init_pix(0.75);
glColor4f(1.0,1.0,1.0,0.9);
scr.text(glGetAnsiString(GL_VENDOR));
scr.text(glGetAnsiString(GL_RENDERER));
scr.text("OpenGL ver: "+glGetAnsiString(GL_VERSION));
if (_multi) scr.text("Multi pass");
else scr.text("Single pass");
if (shd.log.Length()!=41)
for (int i=1;i<=shd.log.Length();) scr.text(str_load_lin(shd.log,i,true));
scr.text_exit();

scr.exe();
scr.rfs();

// wait until the results are available
int e;
unsigned __int64 t0,t1;
for (e=0;!e;) glGetQueryObjectiv(queryID[0],GL_QUERY_RESULT_AVAILABLE,&e);
for (e=0;!e;) glGetQueryObjectiv(queryID[1],GL_QUERY_RESULT_AVAILABLE,&e);
glGetQueryObjectui64v(queryID[0], GL_QUERY_RESULT, &t0);
glGetQueryObjectui64v(queryID[1], GL_QUERY_RESULT, &t1);
Form1->Caption=AnsiString().sprintf("dt: %f ms p0:%.3fx%.3f zoom: %.1lf N:%i<<%i\n",(t1-t0)/1000000.0,mx,my,zoom,N,sh);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
scr.init(this);
shd.set_source_file("","","","Mandelbrot_set.glsl_vert","Mandelbrot_set.glsl_frag");
glGenQueries(2, queryID);
// nice spirals
_multi=1;
zoom=300.0;
mx = 0.268;
my =-0.102;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
scr.exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
scr.resize();
xs=ClientWidth;
ys=ClientHeight;
txr.resize(xs,ys);
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X,int Y)
{
bool q0,q1;
mx1=1.0-divide(X+X,xs-1);
my1=divide(Y+Y,ys-1)-1.0;
sh1=Shift;
q0=sh0.Contains(ssLeft);
q1=sh1.Contains(ssLeft);
if (q1)
{
mx-=(mx1-mx0)/zoom;
my-=(my1-my0)/zoom;
}
mx0=mx1; my0=my1; sh0=sh1;
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y)
{
FormMouseMove(Sender,Shift,X,Y);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y)
{
FormMouseMove(Sender,Shift,X,Y);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
{
if (WheelDelta>0) zoom*=1.2;
if (WheelDelta<0) zoom/=1.2;
Handled=true;
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
Caption=Key;
if (Key==32){ _multi=!_multi; gl_draw(); } // [Space]
if (Key==33){ if (N<8192) N<<=1; gl_draw(); } // [PgUp]
if (Key==34){ if (N> 128) N>>=1; gl_draw(); } // [PgDown]
}
//---------------------------------------------------------------------------

This is single pass fractional escape n=100*32:

fractional escape

This is single pass integer escape n=100:

integer escape

As you can see the fractional escape is much better for the same number of iterations (100).

And finally nice multi pass (as a showoff) only 256 iterations and ~300x zoom:

multi pass

versus single pass:

single pass

Some explanations on the modification:

I added sh fractional bits part to the counter (fixed point). So the max count is now n<<sh instead of just n. I also added n0 constant that lowers the error of fractional part of escape. The link suggest to use 2 iterations but 1 looks better I think (It also removes the i+1 increment from the logarithmic equation). The iteration loop is unchanged I just add the same n0 iterations after it and then compute the fractional escape mu and convert it to fixed point (as my shader outputs integer).

Multi pass is changed only on the CPU side code. It simply re-index the used indexes so there are no holes in them and recolor using visible spectra colors.

Here Demo:

  • Win32 C++/GL/GLSL Mandelbrot set demo 32 bit float (old)
  • Win32 C++/GL/GLSL Mandelbrot set demo 64 bit float (new)

Mutli Threaded Mandelbrot Set viewer with Java Swing

  1. You made a mistake by using static fields in ComputeThread. That way threads use the same arrays and they overwrite each other. So you get 1/4 of your image.

  2. Repaint runs concurrently with threads so if it finishes earlier, not the whole image is drawn. You have to inform JFrame about finished calculations so it can repaint when all is ready.

Below the code that works better:

Mandelbrot.java

package mb;

import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.Graphics;
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.EventListener;
import java.util.Stack;

public class Mandelbrot extends JPanel implements CalcListener {
private static double size = 4.0; // complex plane is size units in width and length
private static int n = 1000; // window is n x n in pixels
private static int max = 255; // iterate through hex color values
private static double x0 = -size/2; // set window origin
private static double y0 = size/2; // set window origin
private static double xC = 0; // set complex plane origin
private static double yC = 0; // set complex plane origin
private static double scaleFactor = size/n; // translate complex plane to pixels
private static Stack<double[]> origins = new Stack<double[]>(); // track origins for zooming out
private static double zoom = 2.0; // modify to alter brightness scheme on zoom
private static Color[][] colors = new Color[n][n];
static Mandelbrot me;

public Mandelbrot() {
this.me=this;
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
double[] center = new double[]{xC, yC}; // rescale
origins.push(center);
zoom = (zoom * 2) - (0.5 * zoom);
xC = x0 + (scaleFactor * e.getX());
yC = y0 - (scaleFactor * e.getY());
size = size/2.0;
scaleFactor = size/n;
x0 = xC - (size/2);
y0 = yC + (size/2);
ComputeThread quadrant1 = new ComputeThread(n, zoom, x0, y0, 0, n/2, 0, n/2, max, scaleFactor, colors, me);
ComputeThread quadrant2 = new ComputeThread(n, zoom, x0, y0, n/2, n, 0, n/2, max, scaleFactor, colors, me);
ComputeThread quadrant3 = new ComputeThread(n, zoom, x0, y0, 0, n/2, n/2, n, max, scaleFactor, colors, me);
ComputeThread quadrant4 = new ComputeThread(n, zoom, x0, y0, n/2, n, n/2, n, max, scaleFactor, colors, me);
Thread thread1 = new Thread(quadrant1);
Thread thread2 = new Thread(quadrant2);
Thread thread3 = new Thread(quadrant3);
Thread thread4 = new Thread(quadrant4);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
repaint(0, 0, getWidth(), getHeight());
}
});
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
double[] center = origins.pop(); // rescale
zoom = (zoom + (zoom / 3)) / 2;
xC = center[0];
yC = center[1];
size = size*2.0;
scaleFactor = size/n;
x0 = xC - (size/2);
y0 = yC + (size/2);
ComputeThread quadrant1 = new ComputeThread(n, zoom, x0, y0, 0, n/2, 0, n/2, max, scaleFactor, colors,me);
ComputeThread quadrant2 = new ComputeThread(n, zoom, x0, y0, n/2, n, 0, n/2, max, scaleFactor, colors,me);
ComputeThread quadrant3 = new ComputeThread(n, zoom, x0, y0, 0, n/2, n/2, n, max, scaleFactor, colors,me);
ComputeThread quadrant4 = new ComputeThread(n, zoom, x0, y0, n/2, n, n/2, n, max, scaleFactor, colors,me);
Thread thread1 = new Thread(quadrant1);
Thread thread2 = new Thread(quadrant2);
Thread thread3 = new Thread(quadrant3);
Thread thread4 = new Thread(quadrant4);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
repaint(0, 0, getWidth(), getHeight());
}
}
});
}

public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
g.setColor(colors[j][i]);
g.drawLine(i, j, i, j);
}
}
}

public static int mand(Complex z0, int max) {
Complex z = z0;
for (int t = 0; t < max; t++) {
if (z.abs() > zoom) {
return t;
}
z = Complex.ad(Complex.mult(z, z), z0);
}
return max;
}


public static void main(String[] args) {


for (int i = 0; i < n; i++) { // initialize array for initial drawing
for (int j = 0; j < n; j++) {
double a = x0 + (i * scaleFactor);
double b = y0 - (j * scaleFactor);
Complex z0 = new Complex(a, b);
int gray = max - mand(z0, max);
Color color = new Color(gray, gray, gray);
colors[j][i] = color;
}
}


SwingUtilities.invokeLater(() -> {
var panel = new Mandelbrot();
panel.setBackground(Color.WHITE);
panel.setFocusable(true);
var frame = new JFrame("Mandelbrot");
frame.setSize(n, n);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel, BorderLayout.CENTER);
frame.setVisible(true);
});

}

public void calcDoneEvent()
{
repaint();
}
}

ComputeThread.java

package mb;

import java.awt.Color;

public class ComputeThread implements Runnable {

private int n;
private double x0;
private double y0;
private int xLow;
private int xHigh;
private int yLow;
private int yHigh;
private int max;
private double scaleFactor;
private double zoom;
private Color[][] colors = new Color[1000][1000];
private CalcListener listener;

public ComputeThread(int n, double zoom, double x0, double y0, int xLow, int xHigh, int yLow, int yHigh, int max, double scaleFactor, Color[][] colors,CalcListener l) {
this.n = n;
this.zoom = zoom;
this.x0 = x0;
this.y0 = y0;
this.xLow = xLow;
this.xHigh = xHigh;
this.yLow = yLow;
this.yHigh = yHigh;
this.max = max;
this.scaleFactor = scaleFactor;
this.colors = colors;
this.listener = l;
}

@Override
public void run() {
for (int i = xLow; i < xHigh; i++) {
for (int j = yLow; j < yHigh; j++) {
double a = x0 + (i * scaleFactor);
double b = y0 - (j * scaleFactor);
Complex z0 = new Complex(a, b);
int gray = max - mand(z0, max);
Color color = new Color(gray, gray, gray);
colors[j][i] = color;
}
}
listener.calcDoneEvent();
}

public int mand(Complex z0, int max) {
Complex z = z0;
for (int t = 0; t < max; t++) {
if (z.abs() > zoom) {
return t;
}
z = Complex.ad(Complex.mult(z, z), z0);
}
return max;
}

}

CalcListener.java

package mb;

public interface CalcListener
{
public void calcDoneEvent();
}

And Complex.java is without modifications.

Another issue is coding style. I made a shortcut by putting static "me" variable in Mandelbrot which is bad practice. Correctly, you should rather inherit Mouse and Key Adapters to own classes that would be capable of passing listener to threads.

Drawing a Mandelbrot Set

i manged to get my code working after some time and i got some answars to share if anyone has my problen:

well i only wanted to make the function of the zn + 1 = zn * zn + c
i dident made the full set only this function, heres my code:

    #region Actions
private void OnDestroy()
{
MoveBlack.HasMoved -= HasMoved;
MoveBlack.HasStoped -= HasStoped;

MoveRed.HasMoved -= HasMoved;
MoveRed.HasStoped -= HasStoped;
}
private void LateUpdate()
{
if (moved) { updateCircles(); }
if (hasparty)
{
foreach(GameObject game in CleanSqud)
{
game.GetComponent<Image>().color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f));
}
}
}
private void HasMoved()
{
moved = true;
}

private void HasStoped()
{
moved = false;
}

#endregion

#region Updateing
private void updateCircles()
{
foreach (GameObject Circle in CleanSqud) { if (Circle.gameObject.name != "BlackCirlce") { Destroy(Circle); } }
StartCircles();
}

private void StartCircles()
{

float x = BlackCircle.anchoredPosition.x;
float y = BlackCircle.anchoredPosition.y;
GameObject[] AllCircles = new GameObject[itarations];
AllCircles[0] = BlackCircle.gameObject;
for (int i = 1; i < itarations; i++)
{

GameObject Circle = Instantiate(BlackCircle.gameObject, Vector3.zero, Quaternion.identity);
Circle.transform.SetParent(CanvasPerent);
AllCircles[i] = Circle;

x = Mathf.Pow(x, 2);
x -= Mathf.Pow(AllCircles[i - 1].GetComponent<RectTransform>().anchoredPosition.y, 2);
x += RedCircle.anchoredPosition.x;

y = (2 * AllCircles[i - 1].GetComponent<RectTransform>().anchoredPosition.x
* AllCircles[i - 1].GetComponent<RectTransform>().anchoredPosition.y) + RedCircle.anchoredPosition.y;

Circle.GetComponent<RectTransform>().anchoredPosition = new Vector2(x, y);

}
CleanSqud = new GameObject[itarations];
CleanSqud = AllCircles;
}
#endregion

so what you should do is instad of showing the y as a imaginary and the x as real show it using the equastion:
this x = power of the old x - power of the old y + c.x
this y = 2 * the old x * the old y + c.y

this should work!
thanks.

Working

Mandelbrot set smooth colouring function

It looks like you're selecting the pixel-color based on a discrete value (n, the escape-time).

I would suggest:

1) Plot your color-vs.-n on a 1D line. Does it appear continuous? If not, choose a color-map that appears continuous w.r.t. increasing n.

2) Find a way to make n continuous, not discrete. One way to do this is by calculating your max-escape-time for the viewing window, and then dividing all other escape-times by this value, to yield a more continuous field. See this link on escape-time normalization:

https://linas.org/art-gallery/escape/escape.html

I believe if you meet both of the above criteria, the field surrounding the main image will appear continuous instead of having the 'iso-line' appearance.

My Mandelbrot does not look like it should. Does anyone know why?

I zoomed it out a bit, and the long tails keep extending to infinity. Since all |C| > 2 should diverge, this makes it easy to find a specific case that fails, such as cReal = 2; cImg = -1.5;

Your code says it converges, but doing it by hand shows it diverges very quickly:

Z0 = 0 + 0i
Z1 = (0 + 0i)^2 + 2 - 1.5i = 2 - 1.5i
Z2 = 2*2 - 2*2*1.5i - 1.5^2 = 1.75 - 6i

Stepping through your code gives zReal, zImg

-1.5, -0.25
-0.75, -0.1875
-1.21875, -0.21484375
-0.976318359375, -0.2038421630859375
-1.1019703075289726, -0.20844837254844606
[...]

In other words, your loop is wrong. The immediately suspect line of code is this:

double temp = zReal * zReal - cIm * cIm + cReal;

It's doing cIm*cIm, but there's not supposed to be any multiplication of any components of C: it's simply added at the end.

So what's happened is that you accidentally switched zIm for cIm.

Switch them back and you should get a better result:

double temp = zReal * zReal - zIm * zIm + cReal;

How to write an OpenGL fragment shader in two stages?

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


Related Topics



Leave a reply



Submit