Видео
Анимация: генерирует последовательность из 255 высокоточных кадров в формате BMP (frame_000.bmp ... frame_254.bmp) и автоматически компилирует их в видеоролик (файл Mandelbrot.mp4) с частотой 30 кадров в секунду, используя встроенный FFmpeg.
Скачать последнюю версию (Windows и Linux)
В windows это Mandelbrot_windows.exe и ffmpeg.exe
https://github.com/Divetoxx/Mandelbrot-Video/releases
Выше README содержит English и Русский!
FFmpeg - "швейцарский армейский нож" для обработки видео. В 2026 году он остается отраслевым стандартом, поддерживаемым сообществом разработчиков открытого программного обеспечения. От YouTube и Netflix до профессиональных киностудий - все на него полагаются. И да, он совершенно бесплатный.
Описание технических параметров
Эта команда преобразует последовательность изображений в высококачественное видео, используя следующую логику:
-stream_loop 3: <Повтори это 4 раза>. Базовый цикл + 3 повтора.-framerate 30: <Считай, что в одной секунде 30 картинок>. Это задает скорость воспроизведения для входящих кадров.-i frame_%03d.bmp: <Ищи файлы с именами от frame_000.bmp до frame_999.bmp>. Маска %03d означает три цифры с ведущими нулями.-color_range full: А это команда энкодеру считать, что на входе у нас Full Range (0-255).-pix_fmt yuv420p: Стандарт <для всех>. Переводит картинку в формат, который гарантированно прочитает любой плеер, браузер или телефон.qp=20: Фиксированное качество. Чем ниже число, тем выше качество (и тяжелее файл). 20 - это <очень хорошо>.no-psy: Отключить психовизуальные оптимизации. Обычно это делают для математически чистых видео (как фракталы), чтобы не плодить лишний шум в деталях.deblock=-6: Сильно ослабить фильтр размытия границ блоков. Сохраняет четкость мелких деталей.
Горячие клавиши
Утилита из командной строке. Либо клавиша 1-8 - это одно из восьми разных мест множество Мандельброта:
case 1: absc = -0.5503432753421602L; ordi = -0.6259312704294012L; size_val = 0.0000000000004L; break; case 2: absc = -0.691488093510181825L; ordi = -0.465680729473216972L; size_val = 0.000000000000003L; break; case 3: absc = -0.550345905862346513L; ordi = 0.625931416301985337L; size_val = 0.000000000000005L; break; case 4: absc = -1.78577278039667471L; ordi = -0.00000075696313293L; size_val = 0.000000000000004L; break; case 5: absc = -1.785772754399825165L; ordi = -0.000000756806080773L; size_val = 0.0000000000000014L; break; case 6: absc = -1.40353608594492038L; ordi = -0.02929181552009826L; size_val = 0.000000000000095L; break; case 7: absc = -1.7485462508265219L; ordi = 0.000002213770706L; size_val = 0.00000000000029L; break; case 8: absc = -1.94053809966024986L; ordi = -0.00000120260253359L; size_val = 0.00000000000003L; break;
Либо читает из файла Mandelbrot.txt три строки - клавиша 9:

Высокоточная отрисовка (80-бит)
Большинство исследователей фрактала Мандельброта используют стандартную 64-битную двойную точность, что приводит к "пикселизации" при масштабировании около 10 14. В этом проекте используется 80-битная арифметика с расширенной точностью (long double) для расширения границ фрактала.
Моя реализация (80-бит): Обеспечивает 4 дополнительных десятичных знака точности, позволяя исследовать в 10 000 раз глубже (диапазон 10 18).
Аппаратная оптимизация: Непосредственно использует регистры FPU x87 для максимальной глубины математических вычислений.
OpenMP
OpenMP - это стандарт, который говорит компилятору: "Возьми этот цикл и сам раздай итерации разным ядрам процессора". Используя OpenMP, вы занимаетесь параллельным программированием на уровне многопоточности (Multithreading). OpenMP - масштабируемость: ваш код будет одинаково эффективно работать как на 4-ядерном ноутбуке, так и на 128-ядерном сервере.
Суперсэмплинг 8x8 (64 прохода на один пиксель)
Суперсэмплинг (SSAA) - ресурсоемкий метод сглаживания, увеличивающий число выборок на пиксель для повышения качества изображения. При значении 8x (N=8) сцена рендерится в разрешении, в 8 раз превышающем целевое, по обеим осям, создавая 64 (или 8 х 8) выборки на пиксель. Изображение просчитывается в более высоком разрешении, а затем принудительно уменьшается до разрешения дисплея, устраняя лесенки и улучшая чёткость. Это очень высокая нагрузка! Это не 1920 на 1080 пикселя а в 8x8 больше - 15360 x 8640 пикселя!
Я решил вывести качество изображения на совершенно новый уровень. Этот движок использует истинное сглаживание 8x8 Supersampling Anti-Aliasing (SSAA) с 64 независимыми сэмплами на каждый пиксель экрана, используя прямую интеграцию в RGB-пространство. Вместо стандартного рендеринга 1920 x 1080, движок обрабатывает внутри себя огромную сетку из 15360 x 8640 субпикселей!
После вычисления всех 64 сэмплов для пикселя, они уменьшаются до одного. Ключевые технические преимущества:
64-точечное фрактальное сэмплирование: каждый конечный пиксель экрана вычисляется из шестидесяти четырех независимых фрактальных координатных точек.
Высокоточное накопление RGB-цвета по каналам: движок сначала вычисляет конкретный 24-битный цвет для каждого субпикселя, прежде чем выполнять какое-либо смешивание.
Устранение шума: Накапливая интенсивность цвета (R, G, B), а не просто подсчитывая количество итераций, мы полностью устраняем <хроматический шум>. В результате получается кристально чистое, резкое изображение.
Интеграция истинного цвета: Наше решение выполняет интеграцию непосредственно в цветовом пространстве RGB. Вычисляя точные компоненты красного, зеленого и синего цветов для каждого субпикселя перед понижением разрешения, мы достигаем кинематографического уровня.
Генерация 255 кадров
Это отличная стратегия оптимизации! Вы хотите применить пререндер: сначала рассчитать тяжелую математику (номера итераций) один раз, сохранить их, а затем быстро генерировать кадры, просто меняя цвета и уменьшая размер. Поскольку считать 15360 x 8640 и 255 раз - это безумие, мы разделим задачу на два этапа.
Этап 1: Генерация <карты итераций> (Raw Data) Вместо BMP мы создадим один огромный файл, где для каждого пикселя запишем только число t (номер итерации). Для 15360 x 8640 при использовании uint8_t файл займет около 132 МБ.
Этап 2: Генерация 255 кадров (Цвет + Сглаживание) Теперь мы читаем эту карту и для каждого кадра делаем: Берем блок 8x8 пикселей из большой карты. Красим каждый пиксель согласно сдвинутой палитре. Усредняем цвета (это и есть сглаживание) и записываем в файл 1920x1080. Почему это сработает быстро?
Память: Массив iterMap занимает около 132 МБ. Это легко помещается в современную оперативную память. Тяжелый цикл do-while выполняется только один раз для всей анимации.
Вращение палитры: В этапе 2 нет long double, нет возведения в квадрат. Только сложение целых чисел и чтение из памяти.
Параллелизм: Этап 2 тоже идеально распараллеливается. 255 кадров будут вылетать очень быстро. Реализован честный Downsampling. Мы берем блок 8x8 и усредняем их.
Когда у вас будет 255 файлов bmp, используйте ffmpeg, чтобы собрать их в видео.
Визуальная эстетика
Красный, зеленый и синий каналы рассчитываются с использованием синусоидальных и косинусоидальных волн для создания плавных цветовых переходов: 127 + 127 cos(2 PI a / 255) и 127 + 127 sin(2 PI a / 255).
Посмотрите на результаты!
Лицензия и стороннее программное обеспечение
Мой код
Этот проект распространяется под лицензией MIT.
FFmpeg
Это программное обеспечение использует библиотеки из проекта FFmpeg под лицензией LGPLv2.1 (или GPLv3, в зависимости от сборки).
FFmpeg является товарным знаком Фабриса Беллара, создателя проекта FFmpeg.
Исходный код и дополнительную информацию можно найти по адресу https://ffmpeg.org.
Бинарные файлы FFmpeg, включенные в релизы, предоставляются как есть, и в исходный код FFmpeg не вносились никакие изменения.
А это код:
#include <iostream> #include <fstream> #include <vector> #include <cmath> #include <cstdint> #include <string> #include <atomic> #include <omp.h> #include <cstdio> using namespace std; const double PI = 3.14159265358979323846; #pragma pack(push, 1) struct BMPHeader { uint16_t type{0x4D42}; uint32_t size{0}; uint16_t reserved1{0}; uint16_t reserved2{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 xpelsPerMeter{2834}; int32_t ypelsPerMeter{2834}; uint32_t clrUsed{0}; uint32_t clrImportant{0}; }; #pragma pack(pop) void save_bmp(const string& filename, const vector<uint8_t>& data, int w, int h) { int rowSize = (w * 3 + 3) & ~3; BMPHeader header; header.width = w; header.height = h; header.sizeImage = rowSize * h; header.size = header.sizeImage + 54; ofstream f(filename, ios::binary); f.write(reinterpret_cast<char*>(&header), 54); f.write(reinterpret_cast<const char*>(data.data()), data.size()); f.close(); } int main() { long double absc, ordi, size_val; int choice; std::cout << "Select point (1-9): "; if (!(std::cin >> choice)) choice = 1; switch (choice) { case 1: absc = -0.5503432753421602L; ordi = -0.6259312704294012L; size_val = 0.0000000000004L; break; case 2: absc = -0.691488093510181825L; ordi = -0.465680729473216972L; size_val = 0.000000000000003L; break; case 3: absc = -0.550345905862346513L; ordi = 0.625931416301985337L; size_val = 0.000000000000005L; break; case 4: absc = -1.78577278039667471L; ordi = -0.00000075696313293L; size_val = 0.000000000000004L; break; case 5: absc = -1.785772754399825165L; ordi = -0.000000756806080773L; size_val = 0.0000000000000014L; break; case 6: absc = -1.40353608594492038L; ordi = -0.02929181552009826L; size_val = 0.000000000000095L; break; case 7: absc = -1.7485462508265219L; ordi = 0.000002213770706L; size_val = 0.00000000000029L; break; case 8: absc = -1.94053809966024986L; ordi = -0.00000120260253359L; size_val = 0.00000000000003L; break; case 9: { ifstream ff("Mandelbrot.txt"); if (!ff.is_open()) { cerr << "Error: Mandelbrot.txt not found!" << endl; return 1; } ff >> absc >> ordi >> size_val; ff.close(); break; } default: std::cout << "Error: No such point!" << std::endl; return 1; } const int targetW = 1920; const int targetH = 1080; const int scale = 8; const int rawW = targetW * scale; const int rawH = targetH * scale; cout << "Step 1: Calculating Raw Map (" << rawW << "x" << rawH << ")..." << endl; vector<uint8_t> iterMap(rawW * rawH); long double step = size_val / rawW; long double startX = absc - (size_val / 2.0); long double startY = ordi - (step * rawH / 2.0); atomic<int> linesDone{0}; #pragma omp parallel for schedule(dynamic) for (int b = 0; b < rawH; ++b) { for (int a = 0; a < rawW; ++a) { long double m = startX + a * step; long double n = startY + b * step; long double c = m, d = n, cc, dd; int t = 50000; do { cc = c * c; dd = d * d; d = (c + c) * d + n; c = cc - dd + m; t--; } while (t > 0 && (cc + dd <= 1000000.0L)); if (t == 0) { iterMap[b * rawW + a] = 255; } else { iterMap[b * rawW + a] = (uint8_t)(t % 255); } } if (++linesDone % 100 == 0) cout << "Progress: " << linesDone << "/" << rawH << "\r" << flush; } uint8_t pal[256][3]; for (int a = 0; a < 255; ++a) { pal[a][0] = (uint8_t)round(127 + 127 * cos(2 * PI * a / 255.0)); pal[a][1] = (uint8_t)round(127 + 127 * sin(2 * PI * a / 255.0)); pal[a][2] = (uint8_t)round(127 + 127 * sin(2 * PI * a / 255.0)); } pal[255][0] = 255; pal[255][1] = 255; pal[255][2] = 255; cout << "\nStep 2: Rendering frames..." << endl; int rowSize = (targetW * 3 + 3) & ~3; for (int frame = 0; frame < 255; ++frame) { vector<uint8_t> frameData(rowSize * targetH); #pragma omp parallel for schedule(static) for (int y = 0; y < targetH; ++y) { for (int x = 0; x < targetW; ++x) { uint32_t rSum = 0, gSum = 0, bSum = 0; for (int j = 0; j < scale; ++j) { for (int i = 0; i < scale; ++i) { uint8_t t = iterMap[(y * scale + j) * rawW + (x * scale + i)]; int colorIdx; if (t == 255) { colorIdx = 255; } else { colorIdx = (t - frame + 255) % 255; } bSum += pal[colorIdx][0]; gSum += pal[colorIdx][1]; rSum += pal[colorIdx][2]; } } int outIdx = y * rowSize + x * 3; frameData[outIdx + 0] = (uint8_t)(bSum >> 6); frameData[outIdx + 1] = (uint8_t)(gSum >> 6); frameData[outIdx + 2] = (uint8_t)(rSum >> 6); } } string filename = "frame_" + to_string(1000 + frame).substr(1) + ".bmp"; save_bmp(filename, frameData, targetW, targetH); cout << "Frame " << frame << "/254 saved. \r" << flush; } cout << "\nStep 3: Compiling video with FFmpeg..." << endl; string videoCmd; #ifdef _WIN32 videoCmd = "ffmpeg.exe -y -stream_loop 3 -framerate 30 -i frame_%03d.bmp -bsf:v h264_metadata=video_full_range_flag=0 -c:v libx264 -x264opts ref=6:me=umh:partitions=all:no-psy:qp=20:subme=9:me_range=24:deblock=-6:bframes=6:ipratio=2:trellis=0:b_adapt=2 -color_range full -pix_fmt yuv420p Mandelbrot.mp4"; #else videoCmd = "./ffmpeg -y -stream_loop 3 -framerate 30 -i frame_%03d.bmp -bsf:v h264_metadata=video_full_range_flag=0 -c:v libx264 -x264opts ref=6:me=umh:partitions=all:no-psy:qp=20:subme=9:me_range=24:deblock=-6:bframes=6:ipratio=2:trellis=0:b_adapt=2 -color_range full -pix_fmt yuv420p Mandelbrot.mp4"; #endif int ret = system(videoCmd.c_str()); if (ret == 0) { cout << "\nVideo compilation successful! Cleaning up frames..." << endl; for (int i = 0; i < 255; ++i) { string filename = "frame_" + to_string(1000 + i).substr(1) + ".bmp"; std::remove(filename.c_str()); } cout << "Done. Result saved as Mandelbrot.mp4" << endl; } else { cerr << "\nError: FFmpeg failed or not found. BMP frames are preserved in the folder." << endl; } return 0; }
Вот.
Множество Мандельброта — это не изобретение человека, а математическое открытие. Оно относится к категории «вечных истин», которые Платон называл миром идей.
Вот почему оно остается неизменным для любого наблюдателя во Вселенной:
Чистая логика: Оно порождается простейшей формулой. Правила арифметики универсальны. Инопланетянин неизбежно придет к тем же фрактальным границам.
Независимость от носителя: Для существования этого множества не нужен компьютер или человеческий мозг. Это абстрактная структура, которая «прошита» в самой логике мироздания.
Фрактальное постоянство: Даже если физические константы в другой галактике были бы иными, математическая топология этого объекта осталась бы незыблемой.
Это действительно один из немногих объектов, который связывает нас с чем-то абсолютно объективным и бесконечным, стоящим выше биологии и истории.
Даже если завтра вся наша Вселенная со всеми её атомами исчезнет, уравнение останется верным. Оно не «написано» на звездах, оно вшито в саму структуру логики. Это делает Множество Мандельброта своего рода абсолютом.
Это классический математический платонизм: идея о том, что математические объекты существуют реально, но в нематериальном мире.
Если исчезнет вся материя, некому будет записать формулу или увидеть её визуализацию, но само соотношение между числами останется истинным. Это как «2 + 2 = 4» — это правило не нуждается в яблоках или камнях, чтобы быть правдой.
Это действительно истина первична по отношению к физическому миру.
Множество Мандельброта абсолютно предопределено. Каждая его точка была <там> еще до Большого взрыва. Но при этом оно абсолютно непредсказуемо - вы не узнаете, что увидите при следующем зуме, пока не сделаете расчет.
Глядя на фрактал, мы видим невероятную сложность, которая кажется хаотичной. Но мы знаем, что в её основе лежит формула из трех символов. Это заставляет задуматься: а не является ли весь хаос нашей Вселенной - турбулентность воды, рост облаков, структура галактик - лишь результатом работы очень простого алгоритма, который мы ещё не вычислили?
