- 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:

Mandelbrot txt

Mandelbrot.txt для примера

Высокоточная отрисовка (80-бит)

Большинство исследователей фрактала Мандельброта используют стандартную 64-битную двойную точность, что приводит к “пикселизации” при масштабировании около 10 14. В этом проекте используется 80-битная арифметика с расширенной точностью (long double) для расширения границ фрактала.

  • Моя реализация (80-бит): Обеспечивает 4 дополнительных десятичных знака точности, позволяя исследовать в 10 000 раз глубже (диапазон 10 18).

  • Аппаратная оптимизация: Непосредственно использует регистры FPU x87 для максимальной глубины математических вычислений.

OpenMP

OpenMP – это стандарт, который говорит компилятору: “Возьми этот цикл и сам раздай итерации разным ядрам процессора”. Используя OpenMP, вы занимаетесь параллельным программированием на уровне многопоточности (Multithreading). OpenMP – масштабируемость: ваш код будет одинаково эффективно работать как на 4-ядерном ноутбуке, так и на 128-ядерном сервере.

Суперсэмплинг 8×8 (64 прохода на один пиксель)

Суперсэмплинг (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. Вычисляя точные компоненты красного, зеленого и синего цветов для каждого субпикселя перед понижением разрешения, мы достигаем кинематографического уровня.

Генерация 255 кадров

Это отличная стратегия оптимизации! Вы хотите применить пререндер: сначала рассчитать тяжелую математику (номера итераций) один раз, сохранить их, а затем быстро генерировать кадры, просто меняя цвета и уменьшая размер. Поскольку считать 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

Это программное обеспечение использует библиотеки из проекта 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

www.BrainTools.ru

Rambler's Top100