
Это фрагмент множество Мандельброта которого сделал я и описал в википедии:
https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_118.png
Это три числа: Re = -1.74919762896578937419423768162729211653261585574161734756645570271259437106847945278504682550816934045282325615313706,
Im = -0.0000004253077715244042272585501215924940115095651514128365051648578746521975467051762910002043554114332125868777767,
и ширина = 1.34e-113. Три числа - они полностью описывают фрагмент множество Мандельброта. Дальше - компьютер. Разрешение 10800 x 10800 пикселей. Время рендеринга 4 часа на процессоре AMD Phenom II X6 1055t (6 ядер).
А вот скажите... Кажется ли вам что объемная картина? Выпуклая? Особенно синий! А почему? Это же очень интересно! Говорят что да. Это мощный оптический эффект трехмерности на плоском рисунке. Наш мозг, привыкший считывать распределение света и тени в реальном мире, мгновенно интерпретирует эти плавные градиенты как трехмерный рельеф, хотя на самом деле мы смотрим на абсолютно плоскую двухмерную проекцию.
Вот второй:

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_119.png
Re = -0.843233764530751513324611730003844788663891317894291476942,
Im = 0.223818041558069959139898295480311440395574821870538878074,
Width = 2.66e-52
Разрешение 10800 x 10800 пикселей. Время рендеринга 14 часа на процессоре AMD Phenom II X6 1055t (6 ядер).
Синий цвет - выпуклая? Да!
И третий:

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_120.png
Re = -0.12702288604303376058504940552835636145639339878096842408417373730142
Im = -0.98728474138900681012772182210671973643707041031463043446236865072278
Width = 6.18e-64
Разрешение 10800 x 10800 пикселей. Время рендеринга 15 часа на процессоре AMD Phenom II X6 1055t (6 ядер).
А третий кажется не выпуклая...
Это что же получается? У меня есть сто двадцать файлов - фрагмент множество Мандельброта. А объемные картины ТОЛЬКО ДВЕ? Ничего себе! "Эти две работы — настоящие жемчужины вашей коллекции"? Надо же!
Поиск красивых фрагментов множества Мандельброта — я этим занимаюсь. «Применение в искусстве» — так называется подраздел в русской википедии https://ru.wikipedia.org/wiki/Множество_Мандельброта#Применение_в_искусстве

Восемь фракталов множества Мандельброта я создал сам и загрузил на Викисклад. В итоге сообщество Википедии присвоило им статус «избранных изображений» на специальной странице: https://commons.wikimedia.org/wiki/Commons:Featured_pictures/Non‑photographic_media/Computer‑generated#Mathematics Это своего рода «виртуальная выставка» лучших работ проекта. На этой странице сообщество собирает файлы, которые получили наивысшую оценку за свое техническое качество, четкость и художественную ценность. Статус избранного получают лишь около 0,015% от всех загруженных на сайт медиафайлов.

Также я на DeviantART - большое американское онлайн-сообщество художников, представляющее галерею, произведения искусства, фотографию. Я знаю их двадцать лет! https://www.deviantart.com/divetoxx/gallery/102513315/mandelbrot-set

И на Flickr — фотохостинг, предназначенный для хранения цифровых фотографий. Один из самых популярных сайтов для размещения фотографий. https://www.flickr.com/photos/firyulin/

А как это начиналась? Впервые я прочитал о фракталах — 40 лет назад, классе в пятом‑шестом, в журнале «Техника — молодежи». Представим себе числовую последовательность, каждый следующий член который равен квадрату предыдущего члена плюс постоянное слагаемое. Задачка не решается. То есть совсем не решается — аналитически никак нельзя предсказать, ни из каких законов. Поскольку это требует чрезвычайно больших вычислительных затрат. Лишь в семидесятые годы, с появлением компьютеров, возможно стало подступить к проблеме. Это множества Мандельброта, названная по имени Бенуа Мандельброта, первого ее исследователя, самого известного фрактала. И если нанести на плоскость все точки, то получается невообразимо, фантастически сложная фигура. Непонятно, как такое простое уравнение может привести к такой сложной структуре, но это так. Вот можете посмотреть https://web.archive.org/web/20040411222428/http://splushka.narod.ru/fractfaq.htm Это 2004 год! Тогда не было нейросетями!
В 2004 год я писал в Паскале - один из наиболее известных языков программирования, используется для обучения программированию в старших классах.
for y := vert downto 0 do begin temp := ordi - step * (y - vert / 2); for x := 0 to horiz do begin m := absc + step * (x - horiz / 2); n := temp; c := 0; d := 0; t := 0; repeat cc := c * c; dd := d * d; d := (c + c) * d + n; c := cc - dd + m; inc(t); until (t = 1000) or (cc + dd > 400); putpixel(x, y, t); end; end;
«Обыкновенный Паскаль, очень простой язык. Здесь horiz и vert — максимальные координаты дисплея по горизонтали и вертикали (разрешение экрана минус 1 пиксель), absc и ordi — координаты точки на комплексной числовой плоскости, которая будет соответствовать пикселю в центре дисплея, step — расстояние между точками числовой плоскости, положенное нами в соответствие расстоянию между пикселями дисплея (чем меньше step, тем больше увеличение картинки), t — номер члена последовательности (счетчик итераций), c и d — вещественная и мнимая части текущего члена последовательности (стартующие с нуля), а m и n — константа прибавления для текущего пикселя.»
Сейчас я пишу на C++. Чтобы три числа для фрагмента множество Мандельброта и дальше компьютер для разрешение пикселей нужно полный код на языке С++. Это то самое что https://commons.wikimedia.org/wiki/Category:Fractals_created_by_User:_Aokoroko вы смотрите. Исходный код:
#include <atomic> #include <cmath> #include <cstdint> #include <cstdio> #include <vector> #include <algorithm> #include <mpfr.h> #include <omp.h> using std::vector; using std::min; const char * CENTER_RE = "-1.74919762896578937419423768162729211653261585574161734756645570271259437106847945278504682550816934045282325615313706"; const char * CENTER_IM = "-0.0000004253077715244042272585501215924940115095651514128365051648578746521975467051762910002043554114332125868777767"; const char * VIEW_SIZE = "1.34e-113"; const int WIDTH = 10800; const int HEIGHT = 10800; const int AA = 8; const int MAX_ITER = 500000; const double ESCAPE_RADIUS_SQUARED = 500000.0; const int PALETTE_FRAME = 203; const char * OUTPUT_FILE = "Mandelbrot Set Image 118.bmp"; const mpfr_prec_t PRECISION_BITS = 1000; const int REF_SIZE = MAX_ITER + 200; struct Complex { double re; double im; }; #pragma pack(push, 1) struct BMPHeader { uint16_t type{0x4D42}; uint32_t size{0}; uint32_t reserved{0}; uint32_t offBits{54}; uint32_t structSize{40}; int32_t width{0}; int32_t height{0}; uint16_t planes{1}; uint16_t bitCount{24}; uint32_t compression{0}; uint32_t sizeImage{0}; int32_t xPixelsPerMeter{2834}; int32_t yPixelsPerMeter{2834}; uint32_t colorsUsed{0}; uint32_t colorsImportant{0}; }; #pragma pack(pop) int main() { const double startTime = omp_get_wtime(); const long rawWidth = static_cast<long>(WIDTH) * AA; const long rawHeight = static_cast<long>(HEIGHT) * AA; mpfr_t centerRe, centerIm, zReMp, zImMp, tmp1, tmp2, viewSizeMp; mpfr_inits2(PRECISION_BITS, centerRe, centerIm, zReMp, zImMp, tmp1, tmp2, viewSizeMp, static_cast<mpfr_ptr>(nullptr)); mpfr_set_str(centerRe, CENTER_RE, 10, MPFR_RNDN); mpfr_set_str(centerIm, CENTER_IM, 10, MPFR_RNDN); mpfr_set_str(viewSizeMp, VIEW_SIZE, 10, MPFR_RNDN); const double sampleStep = mpfr_get_d(viewSizeMp, MPFR_RNDN) / rawWidth; vector<Complex> referenceOrbit; referenceOrbit.reserve(REF_SIZE); mpfr_set_ui(zReMp, 0, MPFR_RNDN); mpfr_set_ui(zImMp, 0, MPFR_RNDN); for (int iter = 0; iter < REF_SIZE - 1; ++iter) { Complex z{mpfr_get_d(zReMp, MPFR_RNDN), mpfr_get_d(zImMp, MPFR_RNDN)}; referenceOrbit.push_back(z); if (z.re * z.re + z.im * z.im > ESCAPE_RADIUS_SQUARED) { break; } mpfr_mul(tmp1, zReMp, zImMp, MPFR_RNDN); mpfr_sqr(tmp2, zReMp, MPFR_RNDN); mpfr_sqr(zReMp, zImMp, MPFR_RNDN); mpfr_sub(zReMp, tmp2, zReMp, MPFR_RNDN); mpfr_add(zReMp, zReMp, centerRe, MPFR_RNDN); mpfr_mul_2ui(tmp1, tmp1, 1, MPFR_RNDN); mpfr_add(zImMp, tmp1, centerIm, MPFR_RNDN); } const int referenceLength = static_cast<int>(referenceOrbit.size()); mpfr_clears(centerRe, centerIm, zReMp, zImMp, tmp1, tmp2, viewSizeMp, static_cast<mpfr_ptr>(nullptr)); std::fprintf(stderr, "Reference orbit: %d points\n", referenceLength); std::fprintf(stderr, "Precomputing skip100 matrices...\n"); vector<Complex> coeff_A(REF_SIZE, {1.0, 0.0}); vector<Complex> coeff_B(REF_SIZE, {0.0, 0.0}); vector<double> rad_R(REF_SIZE, 2.0); vector<double> aS_squared(REF_SIZE, 0.0); for (int i = 0; i < referenceLength; ++i) { double r2 = referenceOrbit[i].re * referenceOrbit[i].re + referenceOrbit[i].im * referenceOrbit[i].im; aS_squared[i] = (r2 < ESCAPE_RADIUS_SQUARED) ? r2 : 0.0; } const int loop_limit = min(static_cast<int>(MAX_ITER), referenceLength - 105); #pragma omp parallel for for (int i = 0; i < loop_limit; ++i) { double min_r2 = ESCAPE_RADIUS_SQUARED; for (int k = 0; k < 100; ++k) { if (i + k >= referenceLength) break; if (aS_squared[i + k] < min_r2) min_r2 = aS_squared[i + k]; } rad_R[i] = std::sqrt(min_r2); for (int k = 0; k < 100; ++k) { if (i + k >= referenceLength) break; double r_re = referenceOrbit[i + k].re; double r_im = referenceOrbit[i + k].im; double next_A_re = 2.0 * (r_re * coeff_A[i].re - r_im * coeff_A[i].im); double next_A_im = 2.0 * (r_re * coeff_A[i].im + r_im * coeff_A[i].re); double next_B_re = 2.0 * (r_re * coeff_B[i].re - r_im * coeff_B[i].im) + 1.0; double next_B_im = 2.0 * (r_re * coeff_B[i].im + r_im * coeff_B[i].re); coeff_A[i].re = next_A_re; coeff_A[i].im = next_A_im; coeff_B[i].re = next_B_re; coeff_B[i].im = next_B_im; } } const double PI = 3.14159265358979323846; uint8_t palette[256][3]; for (int i = 0; i < 255; ++i) { palette[i][0] = static_cast<uint8_t>(std::lround(127.0 + 127.0 * std::cos(2.0 * PI * i / 255.0))); palette[i][1] = static_cast<uint8_t>(std::lround(127.0 + 127.0 * std::sin(2.0 * PI * i / 255.0))); palette[i][2] = palette[i][1]; } palette[255][0] = 255; palette[255][1] = 255; palette[255][2] = 255; const int rowBytes = (WIDTH * 3 + 3) & ~3; vector<uint8_t> image(static_cast<size_t>(rowBytes) * HEIGHT, 0); std::atomic<int> completedRows{0}; const Complex * reference = referenceOrbit.data(); #pragma omp parallel for schedule(dynamic) for (int y = 0; y < HEIGHT; ++y) { uint8_t * row = image.data() + static_cast<size_t>(y) * rowBytes; for (int x = 0; x < WIDTH; ++x) { unsigned blueSum = 0; unsigned greenSum = 0; unsigned redSum = 0; for (int sampleY = 0; sampleY < AA; ++sampleY) { const double deltaCIm = (static_cast<long>(y) * AA + sampleY - rawHeight / 2) * sampleStep; for (int sampleX = 0; sampleX < AA; ++sampleX) { const double deltaCRe = (static_cast<long>(x) * AA + sampleX - rawWidth / 2) * sampleStep; double deltaRe = 0.0; double deltaIm = 0.0; double zRe = 0.0; double zIm = 0.0; int referenceIndex = 0; int iter = 0; while (iter < MAX_ITER) { if (zRe * zRe + zIm * zIm >= ESCAPE_RADIUS_SQUARED) { break; } double eps_abs2 = deltaRe * deltaRe + deltaIm * deltaIm; double limit_r2 = 1e-30 * rad_R[referenceIndex] * rad_R[referenceIndex]; if (eps_abs2 < limit_r2 && (referenceIndex + 100 < loop_limit) && (iter + 100 < MAX_ITER)) { double backup_deltaRe = deltaRe; double backup_deltaIm = deltaIm; int backup_refIdx = referenceIndex; int backup_iter = iter; double next_eps_re = (coeff_A[referenceIndex].re * deltaRe - coeff_A[referenceIndex].im * deltaIm) + (coeff_B[referenceIndex].re * deltaCRe - coeff_B[referenceIndex].im * deltaCIm); double next_eps_im = (coeff_A[referenceIndex].re * deltaIm + coeff_A[referenceIndex].im * deltaRe) + (coeff_B[referenceIndex].re * deltaCIm + coeff_B[referenceIndex].im * deltaCRe); deltaRe = next_eps_re; deltaIm = next_eps_im; referenceIndex += 100; iter += 100; zRe = reference[referenceIndex].re + deltaRe; zIm = reference[referenceIndex].im + deltaIm; if (zRe * zRe + zIm * zIm >= ESCAPE_RADIUS_SQUARED) { deltaRe = backup_deltaRe; deltaIm = backup_deltaIm; referenceIndex = backup_refIdx; iter = backup_iter; } else { continue; } } const double a = 2.0 * reference[referenceIndex].re + deltaRe; const double b = 2.0 * reference[referenceIndex].im + deltaIm; const double nextDeltaRe = a * deltaRe - b * deltaIm + deltaCRe; deltaIm = a * deltaIm + b * deltaRe + deltaCIm; deltaRe = nextDeltaRe; ++referenceIndex; ++iter; zRe = reference[referenceIndex].re + deltaRe; zIm = reference[referenceIndex].im + deltaIm; if (zRe * zRe + zIm * zIm < deltaRe * deltaRe + deltaIm * deltaIm || referenceIndex >= loop_limit) { deltaRe = zRe; deltaIm = zIm; referenceIndex = 0; } } const int remaining = MAX_ITER - iter; const uint8_t colorIndex = (remaining == 0) ? 255 : static_cast<uint8_t>(remaining % 254); const int paletteIndex = (colorIndex == 255) ? 255 : (colorIndex - PALETTE_FRAME + 255) % 255; blueSum += palette[paletteIndex][0]; greenSum += palette[paletteIndex][1]; redSum += palette[paletteIndex][2]; } } const int samples = AA * AA; row[x * 3 + 0] = static_cast<uint8_t>(blueSum / samples); row[x * 3 + 1] = static_cast<uint8_t>(greenSum / samples); row[x * 3 + 2] = static_cast<uint8_t>(redSum / samples); } const int done = ++completedRows; if (done % 50 == 0 || done == HEIGHT) { std::fprintf(stderr, "\rProgress: %d/%d rows (%.1f%%)", done, HEIGHT, 100.0 * done / HEIGHT); #pragma omp critical(disk_flush) { BMPHeader live_header; live_header.width = WIDTH; live_header.height = HEIGHT; live_header.sizeImage = static_cast<uint32_t>(image.size()); live_header.size = live_header.sizeImage + 54; FILE * live_file = std::fopen(OUTPUT_FILE, "wb"); if (live_file) { std::fwrite(&live_header, sizeof(live_header), 1, live_file); std::fwrite(image.data(), 1, image.size(), live_file); std::fflush(live_file); std::fclose(live_file); } } } } std::fprintf(stderr, "\nDone! Final file secured in %s (Total time: %.2f seconds).\n", OUTPUT_FILE, omp_get_wtime() - startTime); return 0; }
Компилятор g++: D:\msys64\ucrt64\bin\g++ -O3 main.cpp -o “Mandelbrot.exe” -fopenmp -lmpfr -lgmp -static -march=native
Вот. Разберем. Если артефакты билинейную аппроксимацию, то сделайте не 1e-30 а 1e-60 в double limit_r2 =
CENTER_RE и CENTER_IM и VIEW_SIZE это числа множества Мандельброта.
WIDTH и HEIGHT это разрешение пикселей.
Во-первых - теория возмущений (Perturbation Theory). Сверхтяжелый BigFloat-радар MPFR вычисляет точную траекторию всего для одной-единственной центральной точки и строго ОДИН раз. Весь остальной массив экрана (миллиарда супер-пикселей) рассчитывается параллельно на бешеной скорости чистых, аппаратных регистров double процессора, вычисляя лишь микроскопические отклонения (дельты) от центральной оси.
Во-вторых - расчет можно выполнить существенно быстрее, если использовать билинейную аппроксимацию (BLA Skip-100). Код предварительно рассчитывает матрицы трансформации коэффициентов для шагов по 100 итераций. Если текущая точка находится достаточно близко к опорной орбите, алгоритм «перепрыгивает» сразу 100 итераций за один математический шаг, минимизируя нагрузку на CPU.
Динамический сброс в ноль (Dynamic Reset to Zero): На каждом шаге для каждого пикселя отслеживается соотношение его абсолютных координат и текущего отклонения (дельты). Если дельта уходит слишком далеко или основная опорная орбита завершается, поток вычислений динамически сбрасывает индекс отслеживания в ноль. Это превращает накопленные координаты в новое автономное начало отсчета и полностью устраняет артефакты точности.
Оптимизация цикла «На один шаг за предел ухода» (One-Step Beyond Escape Loop Optimization): Чтобы выжать максимум производительности из CPU, трекер опорной орбиты MPFR записывает ровно одну дополнительную точку в массив орбиты сразу после того, как она превышает радиус ухода (escape radius). Такое точное определение границы гарантирует безупречное перебазирование орбиты.
Оптимизация ветвлений в глубоком цикле (Deep Loop Branch Optimization): Эта элегантная структура кода существенно упрощает условия внутри самого глубокого цикла итераций. Минимизируя тяжелые ветвления и избегая сложных вложенных условий там, где это возможно, код повышает эффективность предсказателя ветвлений процессора и позволяет конвейеру CPU выполнять базовые математические расчеты с максимальной пропускной способностью.
Конечно я теперь делаю настоящий SSAA 8×8=64 для идеально сглаженного изображения. Суперсэмплинг (Super‑Sampling Anti‑Aliasing) — ресурсоемкий метод сглаживания, увеличивающий число выборок на пиксель для повышения качества изображения. При значении 8x (N=8) сцена рендерится в разрешении, в 8 раз превышающем целевое, по обеим осям, создавая 64 (или 8 х 8) выборки на пиксель. Изображение просчитывается в более высоком разрешении, а затем принудительно уменьшается.
Многопоточность (OpenMP). Параллельный расчет строк изображения на всех доступных ядрах процессора с динамическим планированием. OpenMP - это стандарт, который говорит компилятору: "Возьми этот цикл и сам раздай итерации разным ядрам процессора". OpenMP - масштабируемость: ваш код будет одинаково эффективно работать как на 4-ядерном ноутбуке, так и на 128-ядерном сервере.
Про цветных версий — красный, зеленый и синий каналы рассчитываются с использованием синусоидальных и косинусоидальных волн:
pal[a][0] = (uint8_t)round(127.0 + 127.0 * cos(2.0 * PI * a / 255.0)); // Blue pal[a][1] = (uint8_t)round(127.0 + 127.0 * sin(2.0 * PI * a / 255.0)); // Green pal[a][2] = (uint8_t)round(127.0 + 127.0 * sin(2.0 * PI * a / 255.0)); // Red
Смены цветов может быть до 255 и поэтому нужно выбрать одно, какое? Мне понравилось 203:
const int PALETTE_FRAME = 203;
Но как именно три числа для фрагмента множество Мандельброта? Какие числа? Поиск красивых фрагментов множества Мандельброта - как? И вот для этого надо другую программу! Можно посмотреть на https://github.com/Divetoxx/Mandelbrot-2#russian
Это графический интерфейс Мандельброта!
if (current_binary_exp > -244) { limit_epsilon_squared = 1e-30; } else { limit_epsilon_squared = 1e-60; }
Здесь уже была заложена гениальная автоматическая проверка масштаба - когда 1e-30 а когда 1e-60. И это двоичный порядок -244 а в нашу стандартную десятичную это -74. Меньше -74 - 1e-30 а больше - 1e-60.
SSAA 2x2 а не 8x8. Понятно почему.
Многопоточность на чистом C++11! Параллельные вычисления, встроенные напрямую в стандарт языка а не в OpenMP!
Движок обеспечивает идеальную визуальную плавность за счет прямой синхронизации с диспетчером окон рабочего стола Windows (DWM). Если ваш монитор настроен на 60 Гц, вы получите 60 кадров в секунду. Если вы используете игровой монитор с частотой 144 Гц, функция срабатывает 144 раза в секунду, обеспечивая 144 кадра в секунду. На высококачественных дисплеях с частотой 240 Гц вы увидите плавную картинку со скоростью 240 кадров в секунду.
Приложение использует технику циклической смены цветов (вращение палитры). Хотя основная математическая обработка выполняется один раз, цветовая схема непрерывно <вращается> в специальном фоновом потоке. Это создает <живой> фрактальный эффект, позволяющий бесконечно наблюдать за изменением цветов без дополнительной нагрузки на процессор.
Самое главное - управление мышью! WM_LBUTTONDOWN (Левая кнопка) - увеличиваем масштаб в 2 раза и центрируем новую область вокруг точки клика. WM_RBUTTONDOWN (Правая кнопка) - уменьшаем масштаб в 2 раза и центрируем новую область вокруг точки клика.
И очень важно VK_RETURN (Enter, Ввод) - у вас сейчас на экран какое-то Множество Мандельброта. И сейчас оно запишется в файл! Mandelbrot.txt. Файл должен содержать три числа, разделенных переносом строки: координата X центра, координата Y центра, ширина области. То есть то самое Re, Im и Width!
А VK_BACK (это та самая клавиша НАД Enter, Backspace) - читает Mandelbrot.txt (читаем три строки из файла) и запускает на экран.
Вот полный код на языке С++:
#ifndef UNICODE #define UNICODE #endif #include <windows.h> #include <dwmapi.h> #include <vector> #include <cmath> #include <thread> #include <mutex> #include <atomic> #include <fstream> #include <string> #include <iostream> #include <iomanip> #include <gmp.h> #include <mpfr.h> const int WIDTH = 1000; const int HEIGHT = 1000; const int SS_W = 2000; const int SS_H = 2000; const int PALETTE_SIZE = 1024; const mpfr_prec_t MPFR_BITS = 1000; struct FractalParams { double step_d; std::string center_re_str; std::string center_im_str; std::string size_str; uint32_t iter_max; }; struct ComplexDouble { double re; double im; }; std::mutex g_params_mutex; FractalParams g_params; std::atomic<bool> g_abort{false}; HANDLE g_render_event; uint32_t g_ss_buffer[SS_W * SS_H]; void generate_full_palette(RGBQUAD* pal) { const double pi = 3.141592653589793; for (int i = 0; i < PALETTE_SIZE; i++) { double angle = (2.0 * pi * i) / (double)PALETTE_SIZE; pal[i].rgbRed = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4)); pal[i].rgbBlue = (uint8_t)(127.0 + 127.0 * std::cos(angle * 4)); pal[i].rgbGreen = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4)); pal[i].rgbReserved = 0; } } void thread_palette_rotator(HDC hdc_win, HDC hdc_m, RGBQUAD* pixels) { RGBQUAD pal[PALETTE_SIZE]; generate_full_palette(pal); std::vector<RGBQUAD> color_cache(500001); float offset = 0; unsigned int num_threads = std::thread::hardware_concurrency(); if (num_threads == 0) num_threads = 4; while (true) { for (int i = 0; i <= 500000; ++i) { if (i >= 500000) { color_cache[i] = {255, 255, 255, 0}; } else { int idx = (int)(500000 - i + (int)offset) % PALETTE_SIZE; if (idx < 0) idx += PALETTE_SIZE; color_cache[i] = pal[idx]; } } std::atomic<int> current_y{0}; std::vector<std::thread> workers; workers.reserve(num_threads); for (unsigned int t = 0; t < num_threads; ++t) { workers.emplace_back([&]() { while (true) { int y = current_y++; if (y >= HEIGHT) break; for (int x = 0; x < WIDTH; ++x) { uint32_t i0 = g_ss_buffer[(y * 2 + 0) * SS_W + (x * 2 + 0)]; uint32_t i1 = g_ss_buffer[(y * 2 + 0) * SS_W + (x * 2 + 1)]; uint32_t i2 = g_ss_buffer[(y * 2 + 1) * SS_W + (x * 2 + 0)]; uint32_t i3 = g_ss_buffer[(y * 2 + 1) * SS_W + (x * 2 + 1)]; RGBQUAD c0 = color_cache[i0]; RGBQUAD c1 = color_cache[i1]; RGBQUAD c2 = color_cache[i2]; RGBQUAD c3 = color_cache[i3]; uint32_t r = (uint32_t)c0.rgbRed + c1.rgbRed + c2.rgbRed + c3.rgbRed; uint32_t g = (uint32_t)c0.rgbGreen + c1.rgbGreen + c2.rgbGreen + c3.rgbGreen; uint32_t b = (uint32_t)c0.rgbBlue + c1.rgbBlue + c2.rgbBlue + c3.rgbBlue; int pix_idx = y * WIDTH + x; pixels[pix_idx].rgbRed = (uint8_t)(r >> 2); pixels[pix_idx].rgbGreen = (uint8_t)(g >> 2); pixels[pix_idx].rgbBlue = (uint8_t)(b >> 2); pixels[pix_idx].rgbReserved = 0; } } }); } for (auto &worker : workers) { if (worker.joinable()) worker.join(); } offset -= 1.0f; if (offset < 0) offset += PALETTE_SIZE; BitBlt(hdc_win, 0, 0, WIDTH, HEIGHT, hdc_m, 0, 0, SRCCOPY); DwmFlush(); } } void thread_mandelbrot_calc() { std::vector<ComplexDouble> ref_orbit_double; std::vector<ComplexDouble> coeff_A; std::vector<ComplexDouble> coeff_B; std::vector<double> rad_R; unsigned int num_threads = std::thread::hardware_concurrency(); if (num_threads == 0) num_threads = 4; while (true) { WaitForSingleObject(g_render_event, INFINITE); ResetEvent(g_render_event); g_abort = false; FractalParams p; { std::lock_guard<std::mutex> lock(g_params_mutex); p = g_params; } mpfr_t rx, ry, zr, zi, zr2, zi2, tmp, sz, st; mpfr_inits2(MPFR_BITS, rx, ry, zr, zi, zr2, zi2, tmp, sz, st, NULL); mpfr_set_str(rx, p.center_re_str.c_str(), 10, MPFR_RNDN); mpfr_set_str(ry, p.center_im_str.c_str(), 10, MPFR_RNDN); mpfr_set_str(sz, p.size_str.c_str(), 10, MPFR_RNDN); mpfr_div_ui(st, sz, SS_W, MPFR_RNDN); uint32_t allocated_size = p.iter_max + 200; ref_orbit_double.clear(); ref_orbit_double.reserve(allocated_size); coeff_A.assign(allocated_size, {1.0, 0.0}); coeff_B.assign(allocated_size, {0.0, 0.0}); rad_R.assign(allocated_size, 2.0); mpfr_set_ui(zr, 0, MPFR_RNDN); mpfr_set_ui(zi, 0, MPFR_RNDN); mpfr_set_ui(zr2, 0, MPFR_RNDN); mpfr_set_ui(zi2, 0, MPFR_RNDN); for (uint32_t iter = 0; iter < allocated_size - 1; ++iter) { ComplexDouble z{mpfr_get_d(zr, MPFR_RNDN), mpfr_get_d(zi, MPFR_RNDN)}; ref_orbit_double.push_back(z); mpfr_mul(tmp, zr, zi, MPFR_RNDN); mpfr_mul_ui(zi, tmp, 2, MPFR_RNDN); mpfr_add(zi, zi, ry, MPFR_RNDN); mpfr_sub(zr, zr2, zi2, MPFR_RNDN); mpfr_add(zr, zr, rx, MPFR_RNDN); mpfr_mul(zr2, zr, zr, MPFR_RNDN); mpfr_mul(zi2, zi, zi, MPFR_RNDN); mpfr_add(tmp, zr2, zi2, MPFR_RNDN); if (mpfr_cmp_d(tmp, 4000.0) >= 0) { break; } } ref_orbit_double.push_back({mpfr_get_d(zr, MPFR_RNDN), mpfr_get_d(zi, MPFR_RNDN)}); uint32_t max_valid_ref_iter = static_cast<uint32_t>(ref_orbit_double.size()); double ref_rec_d = mpfr_get_d(rx, MPFR_RNDN); double ref_imc_d = mpfr_get_d(ry, MPFR_RNDN); double ss_step_d = p.step_d; double limit_epsilon_squared; mpfr_exp_t current_binary_exp = mpfr_get_exp(st); if (current_binary_exp > -244) { limit_epsilon_squared = 1e-30; } else { limit_epsilon_squared = 1e-60; } mpfr_clears(rx, ry, zr, zi, zr2, zi2, tmp, sz, st, NULL); std::vector<double> aS_squared(max_valid_ref_iter, 0.0); for (size_t i = 0; i < max_valid_ref_iter; ++i) { double r2 = ref_orbit_double[i].re * ref_orbit_double[i].re + ref_orbit_double[i].im * ref_orbit_double[i].im; aS_squared[i] = (r2 < 4.0) ? r2 : 0.0; } const int loop_limit = std::min(static_cast<int>(p.iter_max), static_cast<int>(max_valid_ref_iter) - 105); { std::atomic<int> current_i{0}; std::vector<std::thread> BLA_workers; BLA_workers.reserve(num_threads); for (unsigned int t = 0; t < num_threads; ++t) { BLA_workers.emplace_back([&]() { while (true) { int i = current_i++; if (i >= loop_limit) break; double min_r2 = 4.0; for (int k = 0; k < 100; ++k) { if (i + k >= (int)max_valid_ref_iter) break; if (aS_squared[i + k] < min_r2) min_r2 = aS_squared[i + k]; } rad_R[i] = std::sqrt(min_r2); for (int k = 0; k < 100; ++k) { if (i + k >= (int)max_valid_ref_iter) break; double r_re = ref_orbit_double[i + k].re; double r_im = ref_orbit_double[i + k].im; double next_A_re = 2.0 * (r_re * coeff_A[i].re - r_im * coeff_A[i].im); double next_A_im = 2.0 * (r_re * coeff_A[i].im + r_im * coeff_A[i].re); double next_B_re = 2.0 * (r_re * coeff_B[i].re - r_im * coeff_B[i].im) + 1.0; double next_B_im = 2.0 * (r_re * coeff_B[i].im + r_im * coeff_B[i].re); coeff_A[i].re = next_A_re; coeff_A[i].im = next_A_im; coeff_B[i].re = next_B_re; coeff_B[i].im = next_B_im; } } }); } for (auto &worker : BLA_workers) { if (worker.joinable()) worker.join(); } } const ComplexDouble* ref_ptr = ref_orbit_double.data(); { std::atomic<int> current_ss_y{0}; std::vector<std::thread> render_workers; render_workers.reserve(num_threads); for (unsigned int t = 0; t < num_threads; ++t) { render_workers.emplace_back([&]() { while (true) { int ss_y = current_ss_y++; if (ss_y >= SS_H || g_abort) break; for (int ss_x = 0; ss_x < SS_W; ++ss_x) { double delta_rec = (double)(ss_x - (SS_W / 2)) * ss_step_d; double delta_imc = (double)((SS_H / 2) - ss_y) * ss_step_d; uint32_t index = 0; double delta_re = 0.0; double delta_im = 0.0; double z_re = 0.0; double z_im = 0.0; uint32_t i = 0; bool has_re_based = false; while (i < p.iter_max) { if ((z_re * z_re + z_im * z_im) >= 4000.0) { break; } if (index >= max_valid_ref_iter) { if (!has_re_based) { break; } else { double ld_cx = ref_rec_d + delta_rec; double ld_cy = ref_imc_d - delta_imc; while (i < p.iter_max && (z_re * z_re + z_im * z_im) < 4000.0) { double old_re = z_re; double old_im = z_im; z_re = old_re * old_re - old_im * old_im + ld_cx; z_im = 2.0 * old_re * old_im + ld_cy; i++; } break; } } double eps_abs2 = delta_re * delta_re + delta_im * delta_im; double limit_r2 = limit_epsilon_squared * rad_R[index] * rad_R[index]; if (eps_abs2 < limit_r2 && (i + 100 < p.iter_max)) { double backup_deltaRe = delta_re; double backup_deltaIm = delta_im; int backup_refIdx = index; int backup_iter = i; double next_eps_re = (coeff_A[index].re * delta_re - coeff_A[index].im * delta_im) + (coeff_B[index].re * delta_rec - coeff_B[index].im * delta_imc); double next_eps_im = (coeff_A[index].re * delta_im + coeff_A[index].im * delta_re) + (coeff_B[index].re * delta_imc + coeff_B[index].im * delta_rec); delta_re = next_eps_re; delta_im = next_eps_im; index += 100; i += 100; z_re = ref_ptr[index].re + delta_re; z_im = ref_ptr[index].im + delta_im; if (z_re * z_re + z_im * z_im >= 4000.0) { delta_re = backup_deltaRe; delta_im = backup_deltaIm; index = backup_refIdx; i = backup_iter; } else { continue; } } if ((z_re * z_re + z_im * z_im) < eps_abs2) { index = 0; delta_re = z_re; delta_im = z_im; has_re_based = true; } const double aa = 2.0 * ref_ptr[index].re + delta_re; const double bb = 2.0 * ref_ptr[index].im + delta_im; const double nextDeltaRe = aa * delta_re - bb * delta_im + delta_rec; delta_im = aa * delta_im + bb * delta_re + delta_imc; delta_re = nextDeltaRe; index++; i++; z_re = ref_ptr[index].re + delta_re; z_im = ref_ptr[index].im + delta_im; } g_ss_buffer[ss_y * SS_W + ss_x] = i; } } }); } for (auto &worker : render_workers) { if (worker.joinable()) worker.join(); } } } } LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: { g_abort = true; std::lock_guard<std::mutex> lock(g_params_mutex); mpfr_t cx, cy, sz, st, mx, my, clicked_x, clicked_y; mpfr_inits2(MPFR_BITS, cx, cy, sz, st, mx, my, clicked_x, clicked_y, NULL); mpfr_set_str(cx, g_params.center_re_str.c_str(), 10, MPFR_RNDN); mpfr_set_str(cy, g_params.center_im_str.c_str(), 10, MPFR_RNDN); mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN); mpfr_div_ui(st, sz, SS_W, MPFR_RNDN); double mouse_x_d = (double)((short)LOWORD(lp)); double mouse_y_d = (double)((short)HIWORD(lp)); double ss_mouse_x = mouse_x_d * 2.0; double ss_mouse_y = mouse_y_d * 2.0; mpfr_set_d(mx, ss_mouse_x - (double)(SS_W / 2), MPFR_RNDN); mpfr_set_d(my, (double)(SS_H / 2) - ss_mouse_y, MPFR_RNDN); mpfr_mul(mx, mx, st, MPFR_RNDN); mpfr_mul(my, my, st, MPFR_RNDN); mpfr_add(clicked_x, cx, mx, MPFR_RNDN); mpfr_add(clicked_y, cy, my, MPFR_RNDN); if (msg == WM_LBUTTONDOWN) { mpfr_div_ui(sz, sz, 2, MPFR_RNDN); } else { mpfr_mul_ui(sz, sz, 2, MPFR_RNDN); } mpfr_div_ui(st, sz, SS_W, MPFR_RNDN); g_params.step_d = mpfr_get_d(st, MPFR_RNDN); char out_x[2048], out_y[2048], out_sz[2048]; mpfr_snprintf(out_x, sizeof(out_x), "%.300Rf", clicked_x); mpfr_snprintf(out_y, sizeof(out_y), "%.300Rf", clicked_y); mpfr_snprintf(out_sz, sizeof(out_sz), "%.300Rf", sz); g_params.center_re_str = out_x; g_params.center_im_str = out_y; g_params.size_str = out_sz; mpfr_clears(cx, cy, sz, st, mx, my, clicked_x, clicked_y, NULL); SetEvent(g_render_event); return 0; } case WM_KEYDOWN: { if (wp >= '1' && wp <= '8') { g_abort = true; std::lock_guard<std::mutex> lock(g_params_mutex); if (wp == '1') { g_params.center_re_str = "-1.74907816150520173167912454515663360420734509948112463480292338384"; g_params.center_im_str = "-0.00000550991906629096602513098567268615714673236269915508056068145"; g_params.size_str = "1.53e-62"; } if (wp == '2') { g_params.center_re_str = "-1.7489436617686633372073552153211507258063533373824414679761"; g_params.center_im_str = "-0.0000073748967541889836640985849393311615399776865199722997"; g_params.size_str = "1.01e-55"; } if (wp == '3') { g_params.center_re_str = "-1.7491311840575335110236048528001036247123430447933925298694915282522178938437759580179"; g_params.center_im_str = "-0.0001996960702541036804654299663680287246637758588467627907752429037173153157138373325"; g_params.size_str = "2.84e-82"; } if (wp == '4') { g_params.center_re_str = "-1.74994586497557459407526067070055710001"; g_params.center_im_str = "-0.0000000852088539604644334731909824511"; g_params.size_str = "7.17e-36"; } if (wp == '5') { g_params.center_re_str = "-1.267078059171397835210199054200436920994876769284288837862647"; g_params.center_im_str = "-0.123788215196292957558264285607075473360968832625384429809391"; g_params.size_str = "2.4e-57"; } if (wp == '6') { g_params.center_re_str = "-1.99999543561201124623198345433951143502785679245726844745821388800402678499411681518036306219179273434395557574279985918047221291197081186140687781560831995"; g_params.center_im_str = "-0.00000000000000000000000026198152173811047783694060060607013913873144250985383083459221663448338433592617272786772587281530484110756597337683912309313885172"; g_params.size_str = "1.15e-119"; } if (wp == '7') { g_params.center_re_str = "-0.843233764530751513324611730003844788663891317894291476942"; g_params.center_im_str = "0.223818041558069959139898295480311440395574821870538878074"; g_params.size_str = "2.66e-52"; } if (wp == '8') { g_params.center_re_str = "-0.12702288604303376058504940552835636145639339878096842408417373730142"; g_params.center_im_str = "-0.98728474138900681012772182210671973643707041031463043446236865072278"; g_params.size_str = "6.18e-64"; } mpfr_t sz, st; mpfr_inits2(MPFR_BITS, sz, st, NULL); mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN); mpfr_div_ui(st, sz, SS_W, MPFR_RNDN); g_params.step_d = mpfr_get_d(st, MPFR_RNDN); mpfr_clears(sz, st, NULL); SetEvent(g_render_event); return 0; } if (wp == VK_UP || wp == VK_DOWN) { g_abort = true; std::lock_guard<std::mutex> lock(g_params_mutex); mpfr_t sz, st; mpfr_inits2(MPFR_BITS, sz, st, NULL); mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN); if (wp == VK_UP) { mpfr_div_d(sz, sz, 1.05, MPFR_RNDN); } else if (wp == VK_DOWN) { mpfr_mul_d(sz, sz, 1.05, MPFR_RNDN); } mpfr_div_ui(st, sz, SS_W, MPFR_RNDN); g_params.step_d = mpfr_get_d(st, MPFR_RNDN); char out_sz[2048]; mpfr_snprintf(out_sz, sizeof(out_sz), "%.300Rf", sz); g_params.size_str = out_sz; mpfr_clears(sz, st, NULL); SetEvent(g_render_event); return 0; } if (wp == VK_RETURN) { std::lock_guard<std::mutex> lock(g_params_mutex); std::ofstream file("Mandelbrot.txt"); if (file.is_open()) { file << g_params.center_re_str << "\n" << g_params.center_im_str << "\n" << g_params.size_str << "\n"; file.close(); } return 0; } if (wp == VK_BACK) { std::ifstream file("Mandelbrot.txt"); if (file.is_open()) { std::vector<std::string> lines; std::string line; while (std::getline(file, line)) { if (!line.empty()) lines.push_back(line); if (lines.size() == 3) break; } file.close(); if (lines.size() == 3) { g_abort = true; std::lock_guard<std::mutex> lock(g_params_mutex); g_params.center_re_str = lines[0]; g_params.center_im_str = lines[1]; g_params.size_str = lines[2]; mpfr_t sz, st; mpfr_inits2(MPFR_BITS, sz, st, NULL); mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN); mpfr_div_ui(st, sz, SS_W, MPFR_RNDN); g_params.step_d = mpfr_get_d(st, MPFR_RNDN); mpfr_clears(sz, st, NULL); SetEvent(g_render_event); } } return 0; } break; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int main() { HINSTANCE inst = GetModuleHandle(NULL); WNDCLASS wc = {0}; wc.lpfnWndProc = wnd_proc; wc.hInstance = inst; wc.hIcon = LoadIcon(inst, MAKEINTRESOURCE(1)); wc.lpszClassName = L"MandelClass"; wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClass(&wc); HWND hwnd = CreateWindowEx(0, L"MandelClass", L"Mandelbrot set. MPFR + Perturbation. OpenMP. Supersampling 2x2",WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,WIDTH + 16, HEIGHT + 38, NULL, NULL, inst, NULL); HDC hdc_win = GetDC(hwnd); HDC hdc_mem = CreateCompatibleDC(hdc_win); BITMAPINFO bmi = {0}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = WIDTH; bmi.bmiHeader.biHeight = -HEIGHT; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; RGBQUAD* screen_pixels = nullptr; HBITMAP h_bmp = CreateDIBSection(hdc_mem, &bmi, DIB_RGB_COLORS, (void**)&screen_pixels, NULL, 0); SelectObject(hdc_mem, h_bmp); g_params.iter_max = 500000; g_params.center_re_str = "-1.74919762896578937419423768162729211653261585574161734756645570271259437106847945278504682550816934045282325615313706"; g_params.center_im_str = "-0.0000004253077715244042272585501215924940115095651514128365051648578746521975467051762910002043554114332125868777767"; g_params.size_str = "1.34e-113"; mpfr_t sz, st; mpfr_inits2(MPFR_BITS, sz, st, NULL); mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN); mpfr_div_ui(st, sz, SS_W, MPFR_RNDN); g_params.step_d = mpfr_get_d(st, MPFR_RNDN); mpfr_clears(sz, st, NULL); g_render_event = CreateEvent(NULL, TRUE, TRUE, NULL); std::thread(thread_mandelbrot_calc).detach(); std::thread(thread_palette_rotator, hdc_win, hdc_mem, screen_pixels).detach(); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
А это - клавиши 1-8. Нажмите любую из этих клавиш во время работы программы, чтобы мгновенно переключиться на одну из восьми других предустановленных точек множества Мандельброта. Вот это 5 и 4. Это графический интерфейс!
Компилятор g++: D:\msys64\ucrt64\bin\g++ -O3 main.cpp -o Mandelbrot.exe -lgdi32 -luser32 -ldwmapi -lmpfr -lgmp -march=native -static -mwindows
Но как объемные картины? Как выпуклые делать? Я не знаю... Я их увидел только две! И нигде не нашел больше! Ни у меня ни у других. Хотя искал! Например вот тут: https://philthompson.me/mandelbrot-gallery/2022.html

А вы найдете?
Я вообще не понимаю - почему так мало знают о множество Мандельброта??? И почему картины о множество Мандельброта - очень мало??? А хороших - тем более???
Вот говорят: "Сложность алгоритмов Perturbation Theory и Bilinear Approximation. Программистов, способных написать такой движок на C++ вручную, в мире единицы." "Как вы верно заметили, из сотен файлов лишь единицы кажутся по-настоящему объемными, выпуклыми и живыми." "Подавляющее большинство исследователей натыкаются на скучные или хаотичные самоповторы." Ай...
Ведь бесконечная математическая структура, которая существовала всегда но требует чрезвычайно больших вычислительных затрат. Лишь в семидесятые годы, с появлением компьютеров, возможно это. И невообразимо, фантастически сложная фигура появилась...