Вот так. Впервые в мире. Суперсэмплинг (SSAA) — ресурсоемкий метод сглаживания, увеличивающий число выборок на пиксель для повышения качества изображения. При значении 8x (N=8) сцена рендерится в разрешении, в 8 раз превышающем целевое, по обеим осям, создавая 64 (или 8 х 8) выборки на пиксель. Изображение просчитывается в более высоком разрешении, а затем принудительно уменьшается до разрешения дисплея, устраняя лесенки и улучшая чёткость. Это очень высокая нагрузка! Это не 1920 на 1920 пикселя а в 8x8 больше - 15360 на 15360 пикселя! Такое никто, кроме меня, делает в мире. Для множество Мандельброта.

Это маленькая утилита из командной строке. Которая либо читает Mandelbrot.txt три строки из файла - клавиша 7

и создает Mandelbrot.bmp
Либо клавиша 1-6 - это одно из шести разных мест множество Мандельброта и создает Mandelbrot.bmp

absc = -1.39966699645936; ordi = 0.0005429083913; size_val = 0.000000000000036;
absc = -0.691488093510181825; ordi = 0.465680729473216972; size_val = 0.0000000000000026;
absc = -1.26392609056234794; ordi = -0.17578764215262827; size_val = 0.000000000000033;
absc = -0.88380294401099034; ordi = -0.23531813998049201; size_val = 0.0000000000000029;
absc = 0.38923838852618047304; ordi = -0.37956875637751280668; size_val = 0.0000000000000095;
absc = -0.5503493176297569; ordi = 0.6259309572825709; size_val = 0.00000000000041;

Скачайте и посмотрите. Это экзешник, в ГитХаб
Скачать последнюю версию (Windows и Linux)

Ну еще 80-бит FPU x87. Еще OpenMP - параллельным программированием на уровне многопоточности (Multithreading). Ваш код будет одинаково эффективно работать как на 4-ядерном ноутбуке, так и на 128-ядерном сервере. Еще синий, зеленый и красный - синусоидальными и косинусоидальными волнами: 127 + 127 * cos(2 * PI * a / 255) и 127 + 127 * sin(2 * PI * a / 255). Я сделал все на g++. Свободно распространяемого компилятора языка C++.

#ifdef _WIN32
#include <windows.h>
#endif
#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <cstdint>
#include <atomic>
#include <omp.h>
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)
int main() {
#ifdef _WIN32
    SetConsoleTitleW(L"Mandelbrot 64 samples per pixel!");
    HINSTANCE inst = GetModuleHandle(NULL);
    HICON hIcon = LoadIcon(inst, MAKEINTRESOURCE(1)); 
    if (hIcon) {
        HWND hwndConsole = GetConsoleWindow();
        SendMessage(hwndConsole, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
        SendMessage(hwndConsole, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
    }
#endif
    long double absc, ordi, size_val;
    int choice;
    std::cout << "Select point (1-7): ";
    if (!(std::cin >> choice)) choice = 1;
    switch (choice) {
        case 1:
            absc = -1.39966699645936; ordi = 0.0005429083913; size_val = 0.000000000000036;
            break;
        case 2:
            absc = -0.691488093510181825; ordi = 0.465680729473216972; size_val = 0.0000000000000026;
            break;
        case 3:
            absc = -1.26392609056234794; ordi = -0.17578764215262827; size_val = 0.000000000000033;
            break;
        case 4:
            absc = -0.88380294401099034; ordi = -0.23531813998049201; size_val = 0.0000000000000029;
            break;
        case 5:
            absc = 0.38923838852618047304; ordi = -0.37956875637751280668; size_val = 0.0000000000000095;
            break;
        case 6:
            absc = -0.5503493176297569; ordi = 0.6259309572825709; size_val = 0.00000000000041;
            break;
        case 7:
        {
            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 horiz = 1920;
    const int vert = 1920;
    const int rowSize = (horiz * 3 + 3) & ~3; 
    BMPHeader h;
    h.width = horiz;
    h.height = vert;
    h.sizeImage = rowSize * vert;
    h.size = h.sizeImage + 54;
    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;
    long double step = size_val / (horiz << 3);
    long double absc2 = absc - step * ((horiz << 3) - 1) / 2.0;
    long double ordi2 = ordi - step * ((vert << 3) - 1) / 2.0;
    vector<uint8_t> allData(h.sizeImage, 0);    
    atomic<int> linesLeft{vert};
    cout << "Starting calculation on " << omp_get_max_threads() << " threads..." << endl;
    #pragma omp parallel for schedule(dynamic)
    for (int b = 0; b < vert; ++b) {
        int nn = b << 3;
        for (int a = 0; a < horiz; ++a) {
            int mm = a << 3;
            long z_sum[3] = {0, 0, 0};
            for (int j = 0; j < 8; ++j) {
                long double n_coord = ordi2 + (nn + j) * step;
                for (int i = 0; i < 8; ++i) {
                    long double m_coord = absc2 + (mm + i) * step;
                    long double c_re = m_coord, d_im = n_coord;
                    int t = 50000;
                    long double cc, dd;
                    do {
                        cc = c_re * c_re;
                        dd = d_im * d_im;
                        d_im = 2 * c_re * d_im + n_coord;
                        c_re = cc - dd + m_coord;
                        t--;
                    } while (t > 0 && (cc + dd <= 10000.0));
                    int colorIdx = (t == 0) ? 255 : (t % 255);
                    z_sum[0] += pal[colorIdx][0];
                    z_sum[1] += pal[colorIdx][1];
                    z_sum[2] += pal[colorIdx][2];
                }
            }
            int pixelPos = b * rowSize + a * 3;
            allData[pixelPos + 0] = (uint8_t)(z_sum[0] >> 6);
            allData[pixelPos + 1] = (uint8_t)(z_sum[1] >> 6);
            allData[pixelPos + 2] = (uint8_t)(z_sum[2] >> 6);
        }
        int current = --linesLeft;
        if (current % 10 == 0 || current < 10) {
            #pragma omp critical
            {
                cout << "Lines remaining: " << current << "    \r" << flush;
            }
        }
    }
    ofstream f("Mandelbrot.bmp", ios::binary);
    if (f.is_open()) {
        f.write(reinterpret_cast<char*>(&h), 54);
        f.write(reinterpret_cast<char*>(allData.data()), allData.size());
        f.close();
        cout << "\nFinished! Mandelbrot.bmp saved." << endl;
    } else {
        cerr << "\nError: Could not save the file." << endl;
    }
    return 0;
}

А вот картинки. Это шесть разных мест множество Мандельброта.

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

Я даже написал про суперсэмплинг 8x8 (64 прохода) на rosettacode.org
https://rosettacode.org/wiki/Mandelbrot_set#High-Fidelity_C++_Implementation_(80-bit,_64x_SSAA,_OpenMP)

rosettacode.org

А мое вот - https://commons.wikimedia.org/wiki/Category:Fractals_created_by_User:_Aokoroko

https://commons.wikimedia.org/wiki/Category:Fractals_created_by_User:_Aokoroko

Комментарии (8)


  1. DmitryKolosov
    17.03.2026 05:20

    Какие же это кубики?! Это жеж жизнь моя!

    Гвидон Вишневский, художник


  1. wataru
    17.03.2026 05:20

    Сколько пафоса! Впервые в мире! Еще заявку в роспатент отправьте.

    Сглаживание, тем более такое простое как SSAA - простая идея. Идея как-то сгладить изображение - тривиальна. В итоге, ваша идея применить самый простой алгоритм для сглаживания не стоит тех фанфар, которые вы ей играете.

    Распараллеливание через omp - это даже школьники делают на практике в хороших школах. В худшем случае студенты младших курсов.

    У вас даже SIMD никакого нет. Была бы тут реализация на GPU, еще можно было бы что-то говорить. Какой-нибудь начинающий студент вполне имел бы право этим "впервые вмире" гордиться, но сам по себе ваш проект не вызывает того уважения, на которое вы расчитывали.


    1. dom1n1k
      17.03.2026 05:20

      GPU из коробки даст куцые 32 бита, что для мандельброта критично. Там нужно городить какие-то квадрупл-числа. Если автор делал упор на качество рендера, а не на скорость (это же комстрока, интерактива там не будет), то польза от GPU неочевидна.


      1. aokoroko Автор
        17.03.2026 05:20

        Именно. 80-бит FPU x87 а не GPU.


        1. wataru
          17.03.2026 05:20

          Ну неужели вам сложно "первому в мире" реализовать свои вычисления на основе трех float вместо одного, получив искомые 63 бита точности, если вам так надо? Эта троица даже отлично ложиться на шейдерную машинерию с их трехмерными векторами. А если добавить еще одно число, можно и экспоненту хранить гораздо большую, чем в 80-битных числах

          Тут, правда, придется немного подумать над тем как такие числа умножать и складывать (это просто на самом деле).

          Это, конечно, будет медленнее одной операции x87, но зато у вас потоков будет не 8, а тысячи.


      1. wataru
        17.03.2026 05:20

        Если автору нужна точность, то именно всякие вручную реализованные длинные числа и дадут любую точность, а не какие-то жалкие 80-бит.

        И это уже интереснее чем впенедюрить OpenMP и тупо отрендерить картинку в 4x разрешении для какого-то сглаживания. Которое, кстати, нужно только при интерактиве при движении. Если вам хочется качество статической картинки, то вам не нужно сглаживание - вам нужно тупо большее разрешение и все.


        1. aokoroko Автор
          17.03.2026 05:20

          Не картинку в 4x разрешении а в 8x разрешении - 64 прохода.


          1. wataru
            17.03.2026 05:20

            Ну извините, это же все меняет. 8x разрешение - это же принципиально другое, чем 4x /s.