Hlavní navigace

Fraktály v počítačové grafice XXVII

25. 4. 2006
Doba čtení: 13 minut

Sdílet

Sedmadvacátá část seriálu věnovaného fraktálům používaným (nejenom) v počítačové grafice je zaměřena na popis druhé (a poslední) varianty fraktálních množin typu Magnet. Popíšeme si jak Mandelbrotovu, tak i Juliovu verzi těchto fraktálních množin a jejich praktický výpočet a následné vykreslení si ukážeme na třech demonstračních příkladech.

Obsah

1. Iterační vztah platný pro fraktální množinu Magnet 2
2. Mandelbrotova verze fraktální množiny Magnet 2
3. První demonstrační příklad
4. Hodnota perturbace a Mandelbrotova verze fraktální množiny Magnet 2
5. Druhý demonstrační příklad
6. Juliova verze fraktální množiny Magnet 2
7. Třetí demonstrační příklad
8. Obsah dalšího pokračování tohoto seriálu

1. Iterační vztah platný pro fraktální množinu Magnet 2

Fraktální množina Magnet 2 je v komplexní rovině (resp. v projekci komplexní roviny na obrazovku počítače) počítána způsobem, který je takřka shodný se způsobem vykreslení minule popsané fraktální množiny Magnet 1. Jediný, zato však velmi podstatný rozdíl spočívá v použití odlišného iteračního vztahu, který je aplikován při výpočtu bodů, jež konvergují k fixním bodům nebo naopak divergují. Vztah použitý pro fraktální množinu Magnet 2 nabývá následující podoby:

fractals27_e.png

ve které jsou pomocí symbolů zn a zn+1 označeny prvky posloupnosti řady zi a symbol c představuje komplexní konstantu nastavenou pro právě probíhající iterační smyčku. Vidíme, že tento iterační vztah je již velmi složitý, zejména v porovnání se vztahem platným pro „klasickou“ Mandelbrotovu množinu a k ní příslušejících Juliových množin:

zn+1=zn2+c

To mimo jiné znamená, že výpočet obrázku množiny Magnet 2 je až několikrát pomalejší než výpočet obrázku množiny Mandelbrotovy, a to i při použití stejného rozlišení a maximálního počtu iterací. V dalších kapitolách si ukážeme způsoby výpočtu a vykreslení jak Mandelbrotovy, tak i Juliovy varianty fraktální množiny Magnet 2 (jak již víme z předchozích pokračování tohoto seriálu, Mandelbrotovy a Juliovy množiny se liší „pouze“ způsobem nastavení počátečních podmínek pro iterační smyčku).

2. Mandelbrotova verze fraktální množiny Magnet 2

V předchozí kapitole jsme si uvedli iterační vztah, který je možné použít pro výpočet bodů ležících ve fraktální množině typu Magnet 2. Tento složitý iterační vztah je možné rozepsat na rovnice platné pro reálnou i imaginární složku hodnoty zn+1 a dále se tyto rovnice mohou prakticky implementovat v programovacím jazyce C pro účely zařazení do zobrazovacích funkcí. Fragment programu provádějícího iterační výpočet vypadá při zápisu v programovacím jazyce C následovně:

    double zx, zy, zx2, zy2, zxn, zyn;  // složky komplexní proměnné Z a Z^2
    double cx0, cy0;                    // vstupní parametr iterační smyčky
    double tzx, tzy, bzx, bzy;          // čitatel a jmenovatel zlomku
    double div;                         // hodnota, kterou se bude výsledek dělit
                                        // před umocněním
    zx2=zx*zx;                          // zkrácený výpočet druhé mocniny složek Z
    zy2=zy*zy;
    tzx=zx*(zx2-3.0*zy2+cm1x)-zy*cm1y+cm1cm2x; // čitatel zlomku
    tzy=zy*(3.0*zx2-zy2+cm1x)+zx*cm1y+cm1cm2y;
    bzx=zx2-zy2;                        // jmenovatel zlomku
    bzx=3.0*bzx+zx*cm2x-zy*cm2y+cm1cm2x+1.0;
    bzy=2.0*zx*zy;
    bzy=3.0*bzy+zx*cm2y+zy*cm2x+cm1cm2y;
    div=bzx*bzx+bzy*bzy;                // zn=top/bot
    if (div<MIN_VALUE) break;           // zajistit, aby nedošlo k dělení nulou
    zxn=(tzx*bzx+tzy*bzy)/div;
    zyn=(tzy*bzx-tzx*bzy)/div; 

Test zajišťující, aby při výpočtu nedošlo k dělení nulou, je implementován stejným způsobem, jako u minule popsané fraktální množiny Magnet 1. Všimněte si, že ve výše uvedeném fragmentu kódu jsou použity „záhadné“ proměnné, například cm1cm2× apod. Hodnoty těchto proměnných se uvnitř iterační smyčky ani pro jednotlivé pixely (body v komplexní rovině) nemění, proto je možné jejich předpočítání, aby se běh iterační smyčky co nejvíce urychlil. Následuje výpis části kódu, který zajišťuje naplnění všech předpočítaných proměnných jejich hodnotami ještě před provedením iterační smyčky:

    cm1x=cx0-1.0; cm1y=cy0;             // předpočítané parametry
    cm2x=cx0-2.0; cm2y=cy0;             // závisející na hodnotě
    cm1cm2x=cm1x*cm2x-cm1y*cm2y;        // C=cx+i*cy
    cm1cm2y=cm1x*cm2y+cm1y*cm2x;
    cm1x+=cm1x+cm1x; cm1y+=cm1y+cm1y;
    cm2x+=cm2x+cm2x; cm2y+=cm2y+cm2y; 

Po doplnění nezbytných podmínek pro ukončení iterační smyčky je již možné oba dva fragmenty kódu začlenit do céčkovské funkce, která provádí výpočet a následné vykreslení obrázku Mandelbrotovy verze fraktální množin Magnet 2. Funkce má tento tvar:

//-----------------------------------------------------------------------------
// Výpočet a překreslení Mandelbrotovy verze fraktální množiny Magnet 2
//-----------------------------------------------------------------------------
void recalcMagnetM2(pixmap *pix,                // pixmapa pro vykreslování
                  int    maxiter,               // maximální počet iterací
                  double scale,                 // měřítko obrazce
                  double xpos,                  // posun obrazce
                  double ypos,
                  int    palette,               // barvová paleta
                  int    rf, int gf, int bf)    // příznaky barvových složek
{
    double zx, zy, zx2, zy2, zxn, zyn;          // složky komplexní proměnné Z, Z^2 a Z^3
    double cx0, cy0;
    double xmin, ymin, xmax, ymax;              // rohy vykreslovaného obrazce v komplexní
                                                // rovině
    double div;                                 // hodnota, kterou se bude výsledek dělit
                                                // před umocněním
    double tzx, tzy, bzx, bzy;                  // čitatel a jmenovatel zlomku
    double cm1x, cm1y, cm2x, cm2y, cm1cm2x, cm1cm2y;
    int    x, y;                                // počitadla sloupců a řádků v pixmapě
    int    iter;                                // počitadlo iterací
    unsigned char r, g, b;

    calcCorner(xpos, ypos, scale, &xmin, &ymin, &xmax, &ymax);
    cy0=ymin;
    for (y=0; y<pix->height; y++) {             // pro všechny řádky v pixmapě
        cx0=xmin;
        for (x=0; x<pix->width; x++) {          // pro všechny pixely na řádku
            zx=0.0;                             // nastavit počáteční hodnotu Z(0)
            zy=0.0;
            cm1x=cx0-1.0; cm1y=cy0;             // předpočítané parametry
            cm2x=cx0-2.0; cm2y=cy0;
            cm1cm2x=cm1x*cm2x-cm1y*cm2y;
            cm1cm2y=cm1x*cm2y+cm1y*cm2x;
            cm1x+=cm1x+cm1x; cm1y+=cm1y+cm1y;
            cm2x+=cm2x+cm2x; cm2y+=cm2y+cm2y;

            for (iter=0; iter<maxiter; iter++) {// iterační smyčka
                zx2=zx*zx;                      // zkrácený výpočet druhé mocniny složek Z
                zy2=zy*zy;
                tzx=zx*(zx2-3.0*zy2+cm1x)-zy*cm1y+cm1cm2x; // čitatel zlomku
                tzy=zy*(3.0*zx2-zy2+cm1x)+zx*cm1y+cm1cm2y;
                bzx=zx2-zy2;                    // jmenovatel zlomku
                bzx=3.0*bzx+zx*cm2x-zy*cm2y+cm1cm2x+1.0;
                bzy=2.0*zx*zy;
                bzy=3.0*bzy+zx*cm2y+zy*cm2x+cm1cm2y;
                div=bzx*bzx+bzy*bzy;            // zn=top/bot
#define MIN_VALUE 1.0-100
                if (div<MIN_VALUE) break;       // zajistit, aby nedošlo k dělení nulou
                zxn=(tzx*bzx+tzy*bzy)/div;
                zyn=(tzy*bzx-tzx*bzy)/div;
                zx=(zxn+zyn)*(zxn-zyn);         // z=zn^2
                zy=2.0*zxn*zyn;
                if (zx2+zy2>100*100) break;
                if (((zx-1.0)*(zx-1.0)+zy*zy)<0.001) break; // test na fixní bod
            }
            mapPalette(palette, iter, &r, &g, &b);
            r=r*rf;                             // uživatelem řízené vynulování
            g=g*gf;                             // vybraných barvových složek
            b=b*bf;
            putpixel(pix, x, y, r, g, b);
            cx0+=(xmax-xmin)/pix->width;        // posun na další bod na řádku
        }
        cy0+=(ymax-ymin)/pix->height;           // posun na další řádek
    }
} 

Příklady Mandelbrotových verzí fraktální množiny Magnet 2 jsou uvedeny na prvním a druhém obrázku.

fractals27_1.png

Obrázek 1: Mandelbrotova verze fraktální množiny Magnet 2

fractals27_2.png

Obrázek 2: Další pohled na Mandelbrotovu verzi fraktální množiny Magnet 2

3. První demonstrační příklad

Výše uvedená céčkovská funkce recalcMagnetM2() je použita v dnešním prvním demonstračním příkladu. Po úspěšném překladu a následném spuštění této aplikace se zobrazí základní pohled na Mandelbrotovu verzi fraktální množiny Magnet 2. Pomocí klávesnice je možné měnit maximální počet iterací, volit barvovou paletu, uložit obrázek fraktální množiny do externího souboru apod. Ovládání je přehledně popsáno v následující tabulce, screenshot prvního demonstračního příkladu je zobrazen na třetím obrázku.

Ovládání
Klávesa Význam
, snížení počtu iterací o jednotku
. zvýšení počtu iterací o jednotku
< snížení počtu iterací o deset jednotek
> zvýšení počtu iterací o deset jednotek
S uložení rastrového obrázku s fraktálem do externího souboru typu TGA
Q ukončení tohoto demonstračního příkladu
Esc má stejný význam jako klávesa Q – ukončení aplikace
kurzorové šipky posun obrazce ve směru šipek (o cca pět procent velikosti obrázku)
Page Up změna měřítka (zvětšení výřezu o deset procent)
Page Down změna měřítka (zmenšení výřezu o deset procent)
0 nastavení barvové palety „Blues“
1 nastavení barvové palety „Gold“
2 nastavení barvové palety „Greens“
3 nastavení barvové palety „Ice“
4 nastavení barvové palety „Juteblue“
5 nastavení barvové palety „Jutemap“
6 nastavení barvové palety „Jutes“
7 nastavení barvové palety „Mandmap“
8 nastavení barvové palety „Phong“
9 nastavení barvové palety „Rose“

fractals27_3.png

Obrázek 3: Screenshot prvního demonstračního příkladu s Mandelbrotovou verzí fraktální množiny Magnet 2

4. Hodnota perturbace a Mandelbrotova verze fraktální množiny Magnet 2

I tvar Mandelbrotovy verze fraktální množiny Magnet 2 je možné dosti podstatným způsobem změnit nastavením nenulové hodnoty perturbace (perturbation). Praktická implementace změny perturbation je ukázána na céčkovské funkci recalcMagnetM2P(), která jako své vstupní parametry akceptuje i hodnoty pcx a pcy. Tyto hodnoty ovlivňují vstupní podmínky iterační smyčky a tím pádem mají velký dopad na výsledný tvar fraktální množiny, což ostatně dokazují ilustrační obrázky 4 a 5.

//-----------------------------------------------------------------------------
// Výpočet a překreslení fraktální množiny Magnet 2 s možností nastavení
// hodnoty perturbace
//-----------------------------------------------------------------------------
void recalcMagnetM2P(pixmap *pix,               // pixmapa pro vykreslování
                  int    maxiter,               // maximální počet iterací
                  double scale,                 // měřítko obrazce
                  double xpos,                  // posun obrazce
                  double ypos,
                  double pcx,                   // nastavená hodnota perturbace
                  double pcy,
                  int    palette,               // barvová paleta
                  int    rf, int gf, int bf)    // příznaky barvových složek
{
    double zx, zy, zx2, zy2, zxn, zyn;          // složky komplexní proměnné Z, Z^2 a Z^3
    double cx0, cy0;
    double xmin, ymin, xmax, ymax;              // rohy vykreslovaného obrazce v komplexní
                                                // rovině
    double div;                                 // hodnota, kterou se bude výsledek dělit
                                                // před umocněním
    double tzx, tzy, bzx, bzy;                  // čitatel a jmenovatel zlomku
    double cm1x, cm1y, cm2x, cm2y, cm1cm2x, cm1cm2y;
    int    x, y;                                // počitadla sloupců a řádků v pixmapě
    int    iter;                                // počitadlo iterací
    unsigned char r, g, b;

    calcCorner(xpos, ypos, scale, &xmin, &ymin, &xmax, &ymax);
    cy0=ymin;
    for (y=0; y<pix->height; y++) {             // pro všechny řádky v pixmapě
        cx0=xmin;
        for (x=0; x<pix->width; x++) {          // pro všechny pixely na řádku
            zx=pcx;                             // nastavit počáteční hodnotu Z(0)
            zy=pcy;                             // => perturbace
            cm1x=cx0-1.0; cm1y=cy0;             // předpočítané parametry
            cm2x=cx0-2.0; cm2y=cy0;
            cm1cm2x=cm1x*cm2x-cm1y*cm2y;
            cm1cm2y=cm1x*cm2y+cm1y*cm2x;
            cm1x+=cm1x+cm1x; cm1y+=cm1y+cm1y;
            cm2x+=cm2x+cm2x; cm2y+=cm2y+cm2y;

            for (iter=0; iter<maxiter; iter++) {// iterační smyčka
                zx2=zx*zx;                      // zkrácený výpočet druhé mocniny složek Z
                zy2=zy*zy;
                tzx=zx*(zx2-3.0*zy2+cm1x)-zy*cm1y+cm1cm2x; // čitatel zlomku
                tzy=zy*(3.0*zx2-zy2+cm1x)+zx*cm1y+cm1cm2y;
                bzx=zx2-zy2;                    // jmenovatel zlomku
                bzx=3.0*bzx+zx*cm2x-zy*cm2y+cm1cm2x+1.0;
                bzy=2.0*zx*zy;
                bzy=3.0*bzy+zx*cm2y+zy*cm2x+cm1cm2y;
                div=bzx*bzx+bzy*bzy;            // zn=top/bot
#define MIN_VALUE 1.0-100
                if (div<MIN_VALUE) break;       // zajistit, aby nedošlo k dělení nulou
                zxn=(tzx*bzx+tzy*bzy)/div;
                zyn=(tzy*bzx-tzx*bzy)/div;
                zx=(zxn+zyn)*(zxn-zyn);         // z=zn^2
                zy=2.0*zxn*zyn;
                if (zx2+zy2>100*100) break;
                if (((zx-1.0)*(zx-1.0)+zy*zy)<0.001) break; // test na fixní bod
            }
            mapPalette(palette, iter, &r, &g, &b);
            r=r*rf;                             // uživatelem řízené vynulování
            g=g*gf;                             // vybraných barvových složek
            b=b*bf;
            putpixel(pix, x, y, r, g, b);
            cx0+=(xmax-xmin)/pix->width;        // posun na další bod na řádku
        }
        cy0+=(ymax-ymin)/pix->height;           // posun na další řádek
    }
} 

fractals27_4.png

Obrázek 4: Mandelbrotova verze fraktální množiny Magnet 2 s nenulovou hodnotou perturbace

fractals27_5.png

Obrázek 5: Odlišný pohled na Mandelbrotovu verzi fraktální množiny Magnet 2 s nenulovou hodnotou perturbace

5. Druhý demonstrační příklad

V dnešním druhém demonstračním příkladu je implementována výše uvedená funkce recalcMagnetM2P(). Po spuštění tohoto demonstračního příkladu se zobrazí dva obrázky fraktálních množin typu Magnet 2. Na množinu zobrazenou v levé části okna není hodnota perturbace aplikována (resp. je nastavena na nulovou hodnotu), zatímco na pravou fraktální množinu se (obecně nenulová) hodnota perturbace aplikuje. Aktuální hodnotu perturbace je možné měnit pohybem myši současně se stlačeným levým tlačítkem. Ukázka obrazovky druhého demonstračního příkladu je uvedena na šestém obrázku.

fractals27_6.png

Obrázek 6: Screenshot druhého demonstračního příkladu s Mandelbrotovou verzí fraktální množiny Magnet 2

6. Juliova verze fraktální množiny Magnet 2

Ve výše uvedených funkcích recalcMagnetM2() a recalcMagnetM2P (použitých v demonstračních příkladech 1 a 2) je při vstupu do iterační smyčky komplexní proměnná z nastavena na konstantní hodnotu z0, která je pro celý fraktál (tj. pro všechny pixely) stejná. Můžeme však vytvořit i Juliovu verzi fraktální množiny Magnet 2, při které zůstává hodnota komplexní konstanty c pro celý počítaný obrázek zachována a naopak se mění počáteční hodnota z0. Juliovy varianty této fraktální množiny jsou ukázány na sedmém a osmém obrázku, funkce, která tyto obrázky vytváří, se jmenuje recalcMagnetJ2():

//-----------------------------------------------------------------------------
// Výpočet a překreslení fraktální množiny Magnet 2 v Juliově variantě
//-----------------------------------------------------------------------------
void recalcMagnetJ2(pixmap *pix,                // pixmapa pro vykreslování
                  int    maxiter,               // maximální počet iterací
                  double scale,                 // měřítko obrazce
                  double xpos,                  // posun obrazce
                  double ypos,
                  double cx,
                  double cy,                    // poloha bodu v komplexní rovině
                  int    palette,               // barvová paleta
                  int    rf, int gf, int bf)    // příznaky barvových složek
{
    double zx, zy, zx2, zy2, zxn, zyn;          // složky komplexní proměnné Z, Z^2 a Z^3
    double cx0, cy0;
    double xmin, ymin, xmax, ymax;              // rohy vykreslovaného obrazce v komplexní
                                                // rovině
    double div;                                 // hodnota, kterou se bude výsledek dělit
                                                // před umocněním
    double tzx, tzy, bzx, bzy;                  // čitatel a jmenovatel zlomku
    double cm1x, cm1y, cm2x, cm2y, cm1cm2x, cm1cm2y;
    int    x, y;                                // počitadla sloupců a řádků v pixmapě
    int    iter;                                // počitadlo iterací
    unsigned char r, g, b;

    calcCorner(xpos, ypos, scale, &xmin, &ymin, &xmax, &ymax);
    cy0=ymin;
    for (y=0; y<pix->height; y++) {             // pro všechny řádky v pixmapě
        cx0=xmin;
        for (x=0; x<pix->width; x++) {          // pro všechny pixely na řádku
            zx=cx0;                             // nastavit počáteční hodnotu Z(0)
            zy=cy0;
            cm1x=cx-1.0; cm1y=cy;               // předpočítané parametry
            cm2x=cx-2.0; cm2y=cy;
            cm1cm2x=cm1x*cm2x-cm1y*cm2y;
            cm1cm2y=cm1x*cm2y+cm1y*cm2x;
            cm1x+=cm1x+cm1x; cm1y+=cm1y+cm1y;
            cm2x+=cm2x+cm2x; cm2y+=cm2y+cm2y;

            for (iter=0; iter<maxiter; iter++) {// iterační smyčka
                zx2=zx*zx;                      // zkrácený výpočet druhé mocniny složek Z
                zy2=zy*zy;
                tzx=zx*(zx2-3.0*zy2+cm1x)-zy*cm1y+cm1cm2x; // čitatel zlomku
                tzy=zy*(3.0*zx2-zy2+cm1x)+zx*cm1y+cm1cm2y;
                bzx=zx2-zy2;                    // jmenovatel zlomku
                bzx=3.0*bzx+zx*cm2x-zy*cm2y+cm1cm2x+1.0;
                bzy=2.0*zx*zy;
                bzy=3.0*bzy+zx*cm2y+zy*cm2x+cm1cm2y;
                div=bzx*bzx+bzy*bzy;            // zn=top/bot
#define MIN_VALUE 1.0-100
                if (div<MIN_VALUE) break;       // zajistit, aby nedošlo k dělení nulou
                zxn=(tzx*bzx+tzy*bzy)/div;
                zyn=(tzy*bzx-tzx*bzy)/div;
                zx=(zxn+zyn)*(zxn-zyn);         // z=zn^2
                zy=2.0*zxn*zyn;
                if (zx2+zy2>100*100) break;
                if (((zx-1.0)*(zx-1.0)+zy*zy)<0.001) break; // test na fixní bod
            }
            mapPalette(palette, iter, &r, &g, &b);
            r=r*rf;                             // uživatelem řízené vynulování
            g=g*gf;                             // vybraných barvových složek
            b=b*bf;
            putpixel(pix, x, y, r, g, b);
            cx0+=(xmax-xmin)/pix->width;        // posun na další bod na řádku
        }
        cy0+=(ymax-ymin)/pix->height;           // posun na další řádek
    }
} 

fractals27_7.png

Obrázek 7: Juliova verze fraktální množiny Magnet 2

fractals27_8.png

Obrázek 8: Odlišný pohled na Juliovu verzi fraktální množiny Magnet 2 s odlišným parametrem C

7. Třetí demonstrační příklad

Výše uvedená funkce recalcMagnetJ2() je použita v dnešním třetím a současně i posledním demonstračním příkladu. Po překladu a spuštění tohoto příkladu se zobrazí okno aplikace o rozměrech 512×384 pixelů, v jehož levé horní části se nalézá pohled na fraktální množinu Magnet M2 a v pravé horní části pohled na fraktální množinu Magnet J2. Ve spodní části okna se zobrazují základní informace o nastavených parametrech použitých pro generování a zobrazení obou fraktálních množin. Tyto množiny, z nichž každá má při zobrazení rozlišení 256×256 pixelů, jsou, podobně jako původní Mandelbrotova množina a množiny Juliovy, mezi sebou svázány stejným iteračním vztahem a hodnotou komplexní konstanty c. Pohybem kurzoru myši po fraktální množině Magnet M2, tj. po levé polovině okna, se mění komplexní konstanta c, která je ihned použita pro dynamický přepočet fraktální množiny Magnet J2. Screenshot ze třetího demonstračního příkladu je zobrazen na devátém obrázku.

root_podpora

fractals27_9.png

Obrázek 9: Screenshot třetího demonstračního příkladu s Juliovou verzí fraktální množiny Magnet 2

8. Obsah dalšího pokračování tohoto seriálu

V následujícím pokračování tohoto seriálu si ukážeme velmi zajímavé fraktální množiny vytvořené v komplexní rovině. Bude se jednat o fraktály pojmenované Phoenix, které existují jak v Mandelbrotově, tak i Juliově variantě.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.