Hlavní navigace

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

8. 3. 2006
Doba čtení: 12 minut

Sdílet

V jubilejní dvacáté části seriálu věnovaného fraktálům používaným (nejenom) v počítačové grafice si popíšeme kubické Juliovy množiny a také Mandelbrotovy i Juliovy množiny, u kterých je použit iterační vztah se čtvrtou mocninou komplexní hodnoty Z.

1. Kubické Juliovy množiny s iteračním vztahem Zn+1=Zn3+C

V předchozím pokračování tohoto seriálu jsme si ukázali, jakým způsobem se počítá a následně také vykresluje rastrový obraz takzvané „kubické“ Mandelbrotovy množiny, v jejímž iteračním vztahu Zn+1=Znr+C je původní člen Zn2 nahrazen novým členem Zn3. Reálnou a imaginární složku komplexní hodnoty Z3 jsme odvodili následujícím způsobem:

Z3=(zre+izim)3=
=(zre+izim)2(zre+i­zim)=
=(zre2-zim2+2izrezim)(zre+i­zim)=
=zre3-izim3+3izre2zim-3zrezim2

Re(Z3): zre3-3zrezim2
Im(Z3): -zim3+3zre2zim 

Kde i značí imaginární jednotku, pro kterou platí i2=-1. Pravidla pro výpočet reálné (Re(Z3)) a imaginární (Im(Z3)) složky byla použita ve funkci recalcCubicMan­delbrot(), v níž se prováděl přepočet a následné překreslení kubické Mandelbrotovy množiny s případnou modifikací barvy pixelů, jež leží vně množiny – viz demonstrační příklady použité v předchozí části seriálu. Zcela stejná pravidla pro výpočet reálné a imaginární složky Z3 je možné samozřejmě využít i při výpočtu a zobrazení „kubické“ Juliovy množiny, jak je ostatně patrné na kódu funkce recalcCubicJu­lia() napsané v programovacím jazyku C. Kubická Juliova množina má stejný vztah ke kubické Mandelbrotově množině, jako má „klasická“ kvadratická Juliova množina ke kvadratické množině Mandelbrotově – Mandelbrotova množina v obou případech představuje mapu všech tvarů Juliových množin, která pro každou hodnotu komplexní konstanty C určuje, zda ke konstantě příslušející Juliova množina bude spojitá či nikoli. Před uvedením zdrojového tvaru funkce recalcCubicJu­lia() opět připomínám, že v programovém kódu nepoužívám rozšíření platná v normě C99 pro komplexní čísla, i když by to programový kód zjednodušilo:

//-----------------------------------------------------------------------------
// Výpočet a překreslení kubické Juliovy množiny
//-----------------------------------------------------------------------------
void recalcCubicJulia( 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 cx0,
                  double cy0)                   // aktuálně nastavená hodnota C
{
    double zx, zy, zx2, zy2, zx3, zy3;          // složky komplexní proměnné Z, Z^2 a Z^3
    double zxn, zyn;
    double cx, cy;                              // složky komplexní konstanty C
    double zx0, zy0;
    double xmin, ymin, xmax, ymax;              // rohy vykreslovaného obrazce
                                                // v komplexní rovině
    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);
    zy0=ymin;
    for (y=0; y<pix->height; y++) {             // pro všechny řádky v pixmapě
        zx0=xmin;
        for (x=0; x<pix->width; x++) {          // pro všechny pixely na řádku
            cx=cx0;                             // nastavit počáteční hodnotu Z(0)
            cy=cy0;
            zx=zx0;
            zy=zy0;
            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;
                zx3=zx2*zx;                     // zkrácený výpočet třetí mocniny složek Z
                zy3=zy2*zy;
                zxn= zx3-3.0*zx*zy2+cx;         // Z^3+C
                zyn=-zy3+3.0*zx2*zy+cy;
                if (zx2+zy2>4.0) break;         // kontrola překročení meze divergence
                zx=zxn;
                zy=zyn;
            }
            if (iter==maxiter)                  // pixely uvnitř Juliovy
                r=g=b=0;                        // množiny jsou černé
            else                                // výpočet barev podle počtu iterací
                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);
            zx0+=(xmax-xmin)/pix->width;        // posun na další bod na řádku
        }
        zy0+=(ymax-ymin)/pix->height;           // posun na další řádek
    }
} 

Funkce recalcCubicJu­lia() je použita v dnešním prvním demonstračním příkladu, screenshoty získané z tohoto demonstračního příkladu jsou zobrazeny na prvním a druhém obrázku.

fractals20_1
Obrázek 1: Pohled na kubickou Mandelbrotovu (levá část obrázku) a Juliovu množinu (pravá část obrázku)

fractals20_2
Obrázek 2: Kubická Juliova množina (pravá část obrázku) s odlišnou hodnotou komplexní konstanty C

2. Rozepsání komplexní hodnoty Z4 na reálnou a imaginární část

Uvedením demonstračního příkladu vykreslujícího kubickou Juliovu množinu opouštíme část věnovanou fraktálům vytvářeným za pomoci iteračního vztahu Zn+1=Zn3+C a začneme se věnovat Mandelbrotovým a samozřejmě také Juliovým množinám, které vzniknou při použití vyšší mocniny hodnoty Z, konkrétně Zn+1=Zn4+C. Podobně jako u kubických Mandelbrotových a Juliových množin, i v tomto případě je nutné provést rozpis komplexní hodnoty Z4 na její reálnou a imaginární složku. To se provede následujícím způsobem:

Z4=(zre+izim)4=
=(zre+izim)2(zre+i­zim)2=
=(zre2-zim2+2izrezim)(zre2-zim2+2izrezim)=
=zre4-zre2zim2+2izre3zim-zre2zim2+zim4-2izrezim3+2iz­re3zim-2izrezim3-4zre2zim2=
=zre4+zim4-6zre2zim2+ 4izre3zim-4izrezim3

Re(Z4): zre4+zim4-6zre2zim2
Im(Z4): 4zre3zim-4zrezim3

 

3. Mandelbrotova množina s iteračním vztahem Zn+1=Zn4+C

Výše uvedené vztahy pro reálnou a imaginární složku komplexního výrazu Z4 jsou použity ve funkci recalcMandelbrot4A(), jejíž zdrojový kód vypadá následovně (všimněte si způsobu použití pomocných proměnných zx2, zy2, zxn a zyn uvnitř iterační smyčky):

//-----------------------------------------------------------------------------
// Překreslení Mandelbrotovy množiny s iteračním vztahem Z=Z^4+C přímým
// dosazením do vztahu pro reálnou a imaginární složku Z^4.
//-----------------------------------------------------------------------------
void recalcMandelbrot4A( 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, zx4, zy4;          // složky komplexní proměnné Z, Z^2 a Z^4
    double zxn, zyn;
    double cx, cy;                              // složky komplexní konstanty C
    double cx0, cy0;
    double xmin, ymin, xmax, ymax;              // rohy vykreslovaného obrazce v komplexní
                                                // rovině
    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
            cx=cy0;                             // nastavit počáteční hodnotu Z(0)
            cy=cx0;
            zx=zy=0.0;                          // nastavení nulového orbitu
            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;
                if (zx2+zy2>4.0) break;         // kontrola překročení meze divergence
                zxn=zx2*zx2+zy2*zy2-6.0*zx2*zy2;// přímý výpočet Z^4
                zyn=4.0*zx2*zx*zy-4.0*zx*zy2*zy;
                zx=zxn+cx;
                zy=zyn+cy;
            }
            if (iter==maxiter)                  // pixely uvnitř Mandelbrotovy
                r=g=b=0;                        // množiny jsou černé
            else                                // výpočet barev podle počtu iterací
                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
    }
} 

Pokud si však uvědomíme, že i v oboru komplexních čísel platí vztah Z4=Z2×Z2, je možné funkci pro výpočet Mandelbrotovy množiny upravit (zjednodušit) takovým způsobem, který je patrný z výpisu funkce recalcMandelbrot4B(). Tato funkce je použita v dnešním druhém demonstračním příkladu, po jehož spuštění se vykreslí Mandelbrotova množina, se kterou je možné manipulovat stejným způsobem, jako tomu bylo u většiny předchozích demonstračních příkladů. Screenshoty z tohoto demonstračního příkladu jsou zobrazeny na třetím, čtvrtém a pátém obrázku.

//-----------------------------------------------------------------------------
// Překreslení Mandelbrotovy množiny s iteračním vztahem Z=Z^4+C s upraveným
// vztahem Z^4=Z^2*Z^2
//-----------------------------------------------------------------------------
void recalcMandelbrot4B( 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, zx4, zy4;          // složky komplexní proměnné Z, Z^2, Z^3 a Z^4
    double zxn, zyn;
    double cx, cy;                              // složky komplexní konstanty C
    double cx0, cy0;
    double xmin, ymin, xmax, ymax;              // rohy vykreslovaného obrazce v komplexní
                                                // rovině
    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
            cx=cy0;                             // nastavit počáteční hodnotu Z(0)
            cy=cx0;
            zx=zy=0.0;                          // nastavení nulového orbitu
            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;
                if (zx2+zy2>4.0) break;         // kontrola překročení meze divergence
                zxn=zx2-zy2;
                zyn=2.0*zx*zy;                  // nyní máme vypočteno Z^2
                zx4=zxn*zxn;
                zy4=zyn*zyn;
                zy=2.0*zxn*zyn+cy;              // zde je už vypočteno Z^4
                zx=zx4-zy4+cx;
            }
            if (iter==maxiter)                  // pixely uvnitř Mandelbrotovy
                r=g=b=0;                        // množiny jsou černé
            else                                // výpočet barev podle počtu iterací
                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
    }
} 

fractals20_3
Obrázek 3: Pohled na Mandelbrotovu množinu s iteračním vztahem Zn+1=Zn4+C

fractals20_4
Obrázek 4: Detail Mandelbrotovy množiny s iteračním vztahem Zn+1=Zn4+C

fractals20_5
Obrázek 5: Další detail Mandelbrotovy množiny s iteračním vztahem Zn+1=Zn4+C

4. Juliovy množiny s iteračním vztahem Zn+1=Zn4+C

V předchozí kapitole jsme si ukázali způsob výpočtu a následného vykreslení Mandelbrotovy množiny s využitím iteračního vztahu Zn+1=Zn4+C. Tentýž iterační vztah je samozřejmě možné použít i pro vytvoření odpovídající Juliovy množiny, což je ukázáno na níže uvedené funkci recalcJulia4(). Tato funkce slouží k vykreslení Juliových množin, jejichž tvar je odvislý od hodnoty parametrů cx0 a cy0 podobně, jako tomu bylo u kvadratické Juliovy množiny – pokud komplexní konstanta C=cx0+icy0 leží uvnitř Mandelbrotovy množiny, je příslušná Juliova množina spojitá a naopak. O kousek dále vypsaná funkce recalcJulia4() je použita v dnešním třetím demonstračním příkladu, po jehož překladu a následném spuštění se v levé části okna vykreslí Mandelbrotova množina a v pravé části množina Juliova. Pohybem kurzoru myši (se stlačeným levým tlačítkem) po obrazu Mandelbrotovy množiny se mění hodnota komplexní konstanty C, která je následně použita pro výpočet a překreslení Juliovy množiny. Screenshoty ze třetího demonstračního příkladu jsou zobrazeny na šestém a sedmém obrázku. Na těchto obrázcích stojí za povšimnutí, že symetrie Mandelbrotovy množiny je o jedničku menší než symetrie odpovídající Juliovy množiny – tak tomu ostatně bylo i u iteračních vztahů Zn+1=Zn2+C a Zn+1=Zn3+C.

//-----------------------------------------------------------------------------
// Překreslení Juliovy množiny s iteračním vztahem Z=Z^4+C
//-----------------------------------------------------------------------------
void recalcJulia4(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 cx0,
                  double cy0)                   // aktuálně nastavená hodnota C
{
    double zx, zy, zx2, zy2, zx4, zy4;          // složky komplexní proměnné Z, Z^2, Z^3 a Z^4
    double zxn, zyn;
    double cx, cy;                              // složky komplexní konstanty C
    double zx0, zy0;
    double xmin, ymin, xmax, ymax;              // rohy vykreslovaného obrazce v komplexní
                                                // rovině
    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);
    zy0=ymin;
    for (y=0; y<pix->height; y++) {             // pro všechny řádky v pixmapě
        zx0=xmin;
        for (x=0; x<pix->width; x++) {          // pro všechny pixely na řádku
            cx=cx0;                             // nastavit počáteční hodnotu Z(0)
            cy=cy0;
            zx=zx0;                             // nastavení nulového orbitu
            zy=zy0;
            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;
                if (zx2+zy2>4.0) break;         // kontrola překročení meze divergence
                zxn=zx2-zy2;
                zyn=2.0*zx*zy;                  // nyní máme vypočteno Z^2
                zx4=zxn*zxn;
                zy4=zyn*zyn;
                zy=2.0*zxn*zyn+cy;              // zde je už vypočteno Z^4
                zx=zx4-zy4+cx;
            }
            if (iter==maxiter)                  // pixely uvnitř Mandelbrotovy
                r=g=b=0;                        // množiny jsou černé
            else                                // výpočet barev podle počtu iterací
                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);
            zx0+=(xmax-xmin)/pix->width;        // posun na další bod na řádku
        }
        zy0+=(ymax-ymin)/pix->height;           // posun na další řádek
    }
} 

fractals20_6
Obrázek 6: Pohled na Juliovu množinu s iteračním vztahem Zn+1=Zn4+C

fractals20_7
Obrázek 7: Další Juliova množina s iteračním vztahem Zn+1=Zn4+C

5. Hodnota perturbation a její vztah k Mandelbrotově množině s iteračním vztahem Zn+1=Zn4+C

V minulém pokračování tohoto seriálu jsme si řekli, že tvar Mandelbrotovy množiny dané vztahem Zn+1=Zn3+C je možné změnit pomocí takzvané hodnoty „perturbation“ prakticky stejným způsobem jako u „klasické“ kvadratické Mandelbrotovy množiny. Princip vysvětlený minule samozřejmě můžeme aplikovat i na Mandelbrotovu množinu s iteračním vztahem Zn+1=Zn4+C. Praktická aplikace tohoto principu je ukázána na funkci recalcMandelbrot­Pert(). Tato funkce je použita v dnešním čtvrtém (a současně posledním) demonstračním příkladu. Po překladu a následném spuštění tohoto příkladu se vypočte dvojice Mandelbrotových množin. Na levou Mandelbrotovu množinu není hodnota perturbation aplikována (je nulová), kdežto obraz pravé Mandelbrotovy množiny je ovlivněn (nenulovou) hodnotou perturbation. Tuto hodnotu je možné měnit pohybem myši současně se stlačeným levým tlačítkem. Screenshot ze čtvrtého demonstračního příkladu je zobrazen na osmém obrázku.

//-----------------------------------------------------------------------------
// Překreslení Mandelbrotovy množiny s iteračním vztahem Z=Z^4+C. Při výpočtu
// pixelů uvnitř Mandelbrotovy množiny je možné změnit hodnotu perturbation.
//-----------------------------------------------------------------------------
void recalcMandelbrotPert( 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 pcx, double pcy)       // hodnota perturbace
{
    double zx, zy, zx2, zy2, zx4, zy4;          // složky komplexní proměnné Z, Z^2, Z^3 a Z^4
    double zxn, zyn;
    double cx, cy;                              // složky komplexní konstanty C
    double cx0, cy0;
    double xmin, ymin, xmax, ymax;              // rohy vykreslovaného obrazce v komplexní
                                                // rovině
    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
            cx=cx0;                             // nastavit počáteční hodnotu Z(0)
            cy=cy0;
            zx=pcx;                             // nastavit hodnotu perturbace
            zy=pcy;
            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;
                if (zx2+zy2>4.0) break;         // kontrola překročení meze divergence
                zxn=zx2-zy2;
                zyn=2.0*zx*zy;                  // nyní máme vypočteno Z^2
                zx4=zxn*zxn;
                zy4=zyn*zyn;
                zy=2.0*zxn*zyn+cy;              // zde je už vypočteno Z^4
                zx=zx4-zy4+cx;
            }
            if (iter==maxiter)                  // pixely uvnitř Mandelbrotovy
                r=g=b=0;                        // množiny jsou černé
            else                                // výpočet barev podle počtu iterací
                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
    }
} 

fractals20_8
Obrázek 8: Pohled na nezkreslenou Mandelbrotovu množinu (levá část obrázku) a na množinu zkreslenou aplikací nenulové hodnoty perturbation (pravá část obrázku)

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

Ústředním tématem příštího pokračování tohoto seriálu bude odvození vztahu, který je možné použít pro vytváření Mandelbrotových a Juliových množin s neceločíselnou mocninou u iteračního vztahu Zn+1=Znr+C. Také si ukážeme zajímavé animace Mandelbrotovy a Juliovy množiny při průběžné změně mocniny Znr.

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.