Визуализация набора Мандельброта в цвете (и придание ему хорошего вида)

Я пытаюсь отобразить набор Мандельброта в цвете и сделать так, чтобы он хорошо выглядел. Я хочу воспроизвести изображение со страницы Википедии . Изображение из Википедии также включает файл параметров Ultra Fractal 3.

mandelZoom00MandelbrotSet {
fractal:
  title="mandel zoom 00 mandelbrot set" width=2560 height=1920 layers=1
  credits="WolfgangBeyer;8/21/2005"
layer:
  method=multipass caption="Background" opacity=100
mapping:
  center=-0.7/0 magn=1.3
formula:
  maxiter=50000 filename="Standard.ufm" entry="Mandelbrot" p_start=0/0
  p_power=2/0 p_bailout=10000
inside:
  transfer=none
outside:
  density=0.42 transfer=log filename="Standard.ucl" entry="Smooth"
  p_power=2/0 p_bailout=128.0
gradient:
  smooth=yes rotation=29 index=28 color=6555392 index=92 color=13331232
  index=196 color=16777197 index=285 color=43775 index=371 color=3146289
opacity:
  smooth=no index=0 opacity=255
}

Я пытался расшифровать этот файл и написать программу для воспроизведения изображения из Википедии. Это моя лучшая попытка:

множество Мандельброта

Это часть рендерера. Это немного запутанно, потому что я возился и переписывал большие куски, пытаясь понять это.

using real = double;
using integer = long long;

struct complex {
  real r, i;
};

using grey = unsigned char;

struct color {
  grey r, g, b;
};

struct real_color {
  real r, g, b;
};

grey real_to_grey(real r) {
  // converting to srgb didn't help much
  return std::min(std::max(std::round(r), real(0.0)), real(255.0));
}

color real_to_grey(real_color c) {
  return {real_to_grey(c.r), real_to_grey(c.g), real_to_grey(c.b)};
}

real lerp(real a, real b, real t) {
  return std::min(std::max(t * (b - a) + a, real(0.0)), real(255.0));
}

real_color lerp(real_color a, real_color b, real t) {
  return {lerp(a.r, b.r, t), lerp(a.g, b.g, t), lerp(a.b, b.b, t)};
}

complex plus(complex a, complex b) {
  return {a.r + b.r, a.i + b.i};
}

complex square(complex n) {
  return {n.r*n.r - n.i*n.i, real{2.0} * n.r * n.i};
}

complex next(complex z, complex c) {
  return plus(square(z), c);
}

real magnitude2(complex n) {
  return n.r*n.r + n.i*n.i;
}

real magnitude(complex n) {
  return std::sqrt(magnitude2(n));
}

color lerp(real_color a, real_color b, real t) {
  return real_to_grey(lerp(a, b, t));
}

struct result {
  complex zn;
  integer n;
};

result mandelbrot(complex c, integer iterations, real bailout) {
  complex z = {real{0.0}, real{0.0}};
  integer n = 0;
  real bailout2 = bailout * bailout;
  for (; n < iterations && magnitude2(z) <= bailout2; ++n) {
    z = next(z, c);
  }
  return {z, n};
}

struct table_row {
  real index;
  real_color color;
};

real invlerp(real value, real min, real max) {
  return (value - min) / (max - min);
}

color lerp(table_row a, table_row b, real index) {
  return lerp(a.color, b.color, invlerp(index, a.index, b.index));
}

color mandelbrot_color(complex c, integer iterations, real bailout) {
  const result res = mandelbrot(c, iterations, bailout);
  if (res.n == iterations) {
    // in the set
    return {0, 0, 0};
  } else {
    table_row table[] = {
      // colors and indicies from gradient section
      {28.0*0.1, {0x00, 0x07, 0x64}},
      {92.0*0.1, {0x20, 0x6B, 0xCB}},
      {196.0*0.1, {0xED, 0xFF, 0xFF}},
      {285.0*0.1, {0xFF, 0xAA, 0x00}},
      {371.0*0.1, {0x31, 0x02, 0x30}},
      // interpolate towards black as we approach points that are in the set
      {real(iterations), {0, 0, 0}}
    };
    // it should be smooth, but it's not
    const real smooth = res.n + real{1.0} - std::log(std::log2(magnitude(res.zn)));
    // I know what a for-loop is, I promise
    if (smooth < table[1].index) {
      return lerp(table[0], table[1], smooth);
    } else if (table[1].index <= smooth && smooth < table[2].index) {
      return lerp(table[1], table[2], smooth);
    } else if (table[2].index <= smooth && smooth < table[3].index) {
      return lerp(table[2], table[3], smooth);
    } else if (table[3].index <= smooth && smooth < table[4].index) {
      return lerp(table[3], table[4], smooth);
    } else {
      return lerp(table[4], table[5], smooth);
    }
  }
}

Цвета из раздела gradient находятся в таблице в разделе mandelbrot_color. Индексы из раздела gradient тоже есть в таблице, но я их умножил на 0.1. Цвета выглядят совсем не так, если я не умножу на 0.1.

В разделе formula есть maxiter=50000 и p_bailout=10000. Это iterations и bailout в коде. Я не знаю, что означает p_start=0/0 p_power=2/0. Я не знаю, почему в разделе outside упоминается другое спасение, и я не знаю, что означают density=0.42, transfer=none, transfer=log. В разделе gradient также упоминается rotation=29, но я не понимаю, как можно повернуть градиент.

Причина, по которой я задаю этот вопрос, заключается в том, что мне не нравятся белые полосы вокруг моего изображения (я бы предпочел плавное свечение, как на изображении из Википедии). Мне также не нравится темно-фиолетовая кожа, вызванная интерполяцией в сторону черного (последняя строка в таблице в mandelbrot_color). Если мы удалим этот ряд, мы получим темно-синий скин.

Я подозреваю, что есть какое-то отображение индексов в разделе gradient на количество итераций. Возможно, * 0.1 является приближением этого отображения, которое иногда работает. Может иметь какое-то отношение к transfer, density или rotation. Оставьте комментарий, если вы хотите, чтобы я разместил всю программу. Это зависит от stb_image_write (библиотека для записи изображений с одним заголовком).

В качестве примечания: я очистил этот код и закинул его во фрагментный шейдер. Будет ли он работать быстрее (вообще говоря), чем многопоточность на ЦП?


person Indiana Kernick    schedule 30.01.2019    source источник
comment
Похоже, вы просто делаете округление не в том месте. Попробуйте отложить его до момента, когда вы генерируете цвета RGB.   -  person Mark Ransom    schedule 30.01.2019
comment
@MarkRansom Я округляю цвета с плавающей запятой до целых цветов в real_to_grey. Вся математика выполняется с плавающими цветами, а затем я конвертирую в целые числа в конце.   -  person Indiana Kernick    schedule 30.01.2019
comment
Я предполагаю, что real_to_grey слишком рано, основываясь на полосах, которые я вижу на заднем плане.   -  person Mark Ransom    schedule 30.01.2019
comment
@MarkRansom Я думаю, что это вызвано не округлением, а слишком низкими итерациями ...   -  person Spektre    schedule 30.01.2019
comment
@ Kerndog73 взгляните на Набор Мандельброта - Предложения по цветовому спектру?   -  person Spektre    schedule 30.01.2019
comment
@Spektre Интересно. Возможно, изображение из Википедии использует гистограмму. Это может объяснить method=multipass. Может быть, density=0.42 - это плотность гистограммы или что-то в этом роде. Я посмотрю внимательнее на этот пост.   -  person Indiana Kernick    schedule 30.01.2019
comment
Глаз чувствителен к крошечным цветовым различиям, поэтому вам придется фильтровать изображение, чтобы сгладить контуры, как в связанном примере. Одним из способов было бы смешивание соседних цветов для получения плавного градиента, потому что глаз хуже различает отдельные пиксели немного другого цвета.   -  person Weather Vane    schedule 04.02.2019
comment
@Kerndog73 Взгляните на это Не могу найти способ раскрасить набор Мандельброта так, как я стремлюсь Я думаю, что это так ... и это даже быстрее, так как теперь максимальное количество итераций может быть намного меньше с гораздо лучшими результатами.   -  person Spektre    schedule 19.05.2019