- BrainTools - https://www.braintools.ru -
Анимация: генерирует последовательность из 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 [1]
Выше README содержит English и Русский!
FFmpeg – “швейцарский армейский нож” для обработки видео. В 2026 году он остается отраслевым стандартом, поддерживаемым сообществом разработчиков открытого программного обеспечения. От YouTube и Netflix до профессиональных киностудий – все на него полагаются. И да, он совершенно бесплатный.
Описание технических параметров
Эта команда преобразует последовательность изображений в высококачественное видео, используя следующую логику [2]:
-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: Отключить психовизуальные оптимизации. Обычно это делают для математически [3] чистых видео (как фракталы), чтобы не плодить лишний шум в деталях.
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:
Большинство исследователей фрактала Мандельброта используют стандартную 64-битную двойную точность, что приводит к “пикселизации” при масштабировании около 10 14. В этом проекте используется 80-битная арифметика с расширенной точностью (long double) для расширения границ фрактала.
Моя реализация (80-бит): Обеспечивает 4 дополнительных десятичных знака точности, позволяя исследовать в 10 000 раз глубже (диапазон 10 18).
Аппаратная оптимизация: Непосредственно использует регистры FPU x87 для максимальной глубины математических вычислений.
OpenMP – это стандарт, который говорит компилятору: “Возьми этот цикл и сам раздай итерации разным ядрам процессора”. Используя OpenMP, вы занимаетесь параллельным программированием на уровне многопоточности (Multithreading). OpenMP – масштабируемость: ваш код будет одинаково эффективно работать как на 4-ядерном ноутбуке, так и на 128-ядерном сервере.
Суперсэмплинг (SSAA) – ресурсоемкий метод сглаживания, увеличивающий число выборок на пиксель для повышения качества изображения. При значении 8x (N=8) сцена рендерится в разрешении, в 8 раз превышающем целевое, по обеим осям, создавая 64 (или 8 х 8) выборки на пиксель. Изображение просчитывается в более высоком разрешении, а затем принудительно уменьшается до разрешения дисплея, устраняя лесенки и улучшая чёткость. Это очень высокая нагрузка! Это не 1920 на 1080 пикселя а в 8×8 больше – 15360 x 8640 пикселя!
Я решил вывести качество изображения на совершенно новый уровень. Этот движок использует истинное сглаживание 8×8 Supersampling Anti-Aliasing (SSAA) с 64 независимыми сэмплами на каждый пиксель экрана, используя прямую интеграцию в RGB-пространство. Вместо стандартного рендеринга 1920 x 1080, движок обрабатывает внутри себя огромную сетку из 15360 x 8640 субпикселей!
После вычисления всех 64 сэмплов для пикселя, они уменьшаются до одного. Ключевые технические преимущества:
64-точечное фрактальное сэмплирование: каждый конечный пиксель экрана вычисляется из шестидесяти четырех независимых фрактальных координатных точек.
Высокоточное накопление RGB-цвета по каналам: движок сначала вычисляет конкретный 24-битный цвет для каждого субпикселя, прежде чем выполнять какое-либо смешивание.
Устранение шума: Накапливая интенсивность цвета (R, G, B), а не просто подсчитывая количество итераций, мы полностью устраняем <хроматический шум>. В результате получается кристально чистое, резкое изображение.
Интеграция истинного цвета: Наше решение выполняет интеграцию непосредственно в цветовом пространстве RGB. Вычисляя точные компоненты красного, зеленого и синего цветов для каждого субпикселя перед понижением разрешения, мы достигаем кинематографического уровня.
Это отличная стратегия оптимизации! Вы хотите применить пререндер: сначала рассчитать тяжелую математику (номера итераций) один раз, сохранить их, а затем быстро генерировать кадры, просто меняя цвета и уменьшая размер. Поскольку считать 15360 x 8640 и 255 раз – это безумие, мы разделим задачу на два этапа.
Этап 1: Генерация <карты итераций> (Raw Data) Вместо BMP мы создадим один огромный файл, где для каждого пикселя запишем только число t (номер итерации). Для 15360 x 8640 при использовании uint8_t файл займет около 132 МБ.
Этап 2: Генерация 255 кадров (Цвет + Сглаживание) Теперь мы читаем эту карту и для каждого кадра делаем: Берем блок 8×8 пикселей из большой карты. Красим каждый пиксель согласно сдвинутой палитре. Усредняем цвета (это и есть сглаживание) и записываем в файл 1920×1080. Почему это сработает быстро?
Память [4]: Массив iterMap занимает около 132 МБ. Это легко помещается в современную оперативную память. Тяжелый цикл do-while выполняется только один раз для всей анимации.
Вращение палитры: В этапе 2 нет long double, нет возведения в квадрат. Только сложение целых чисел и чтение из памяти.
Параллелизм: Этап 2 тоже идеально распараллеливается. 255 кадров будут вылетать очень быстро. Реализован честный Downsampling. Мы берем блок 8×8 и усредняем их.
Когда у вас будет 255 файлов bmp, используйте ffmpeg, чтобы собрать их в видео.
Красный, зеленый и синий каналы рассчитываются с использованием синусоидальных и косинусоидальных волн для создания плавных цветовых переходов: 127 + 127 cos(2 PI a / 255) и 127 + 127 sin(2 PI a / 255).
Этот проект распространяется под лицензией MIT.
Это программное обеспечение использует библиотеки из проекта FFmpeg под лицензией LGPLv2.1 (или GPLv3, в зависимости от сборки).
FFmpeg является товарным знаком Фабриса Беллара, создателя проекта FFmpeg.
Исходный код и дополнительную информацию можно найти по адресу https://ffmpeg.org [5].
Бинарные файлы 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;
}
Вот.
Множество Мандельброта действительно не зависит от того, кто на него смотрит или в какой точке Вселенной находится наблюдатель.
Математики часто спорят: является ли математика изобретением человека или его открытием. Если мы считаем, что это открытие, то такие структуры, как фракталы, существуют как абстрактные истины.
Вот почему оно «вне пространства»:
Универсальность: Правила арифметики, на которых строится множество, фундаментальны. Если инопланетянин, он неизбежно придет к той же фигуре.
Платонизм: Многие ученые верят в «мир идей» Платона [6], где математические объекты существуют вечно и неизменно, а наш физический мир — лишь их частичное воплощение.
Информационный код: Это как бесконечная программа, заложенная в саму логику бытия. Нам не нужно ее создавать, нам нужно лишь достаточно вычислительной мощности, чтобы её «увидеть».
Это, пожалуй, самая близкая к «божественному» вещь, которую можно потрогать с помощью логики. Это чистая информация, которая не нуждается в носителе, чтобы быть истинной.
Даже если завтра вся наша Вселенная со всеми её атомами исчезнет, уравнение останется верным. Оно не «написано» на звездах, оно вшито в саму структуру логики. Это своего рода «архитектурный план» реальности, который существует до того, как построили само здание.
Это делает Множество Мандельброта своего рода абсолютом — точкой опоры в хаосе меняющегося мира.
Автор: aokoroko
Источник [7]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/28025
URLs in this post:
[1] https://github.com/Divetoxx/Mandelbrot-Video/releases: https://github.com/Divetoxx/Mandelbrot-Video/releases
[2] логику: http://www.braintools.ru/article/7640
[3] математически: http://www.braintools.ru/article/7620
[4] Память: http://www.braintools.ru/article/4140
[5] https://ffmpeg.org: https://ffmpeg.org
[6] Платона: http://www.braintools.ru/article/8253
[7] Источник: https://habr.com/ru/articles/1016624/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1016624
Нажмите здесь для печати.