使用OpenGL進行Mandelbrot集的可視化

Mandelbrot集是哪一集??

Mandelbrot集不是哪一集!! 啊不對……
Mandelbrot集是哪一集!! 好像也不對……
Mandelbrot集是數集!! 因此……他不是一集而是數集??……算法

因此這個M...dem...集究竟是什麼啊??函數

Mandelbrot集是一個數集

Mandelbrot集\(\mathbb{M}\)(簡稱曼集)是一個由二元複數構成的集合,也就是一個複數集:
\[ \mathbb{M}\subset\mathbb{C} \]spa

也就是說,曼集的元素都是複數,也就是以下的形式:
\[ \forall m\in \mathbb{M},m=a+b\mathrm{i},\left(a,b\in \mathbb{R}\right) \]3d

爲何叫這麼複雜的名字??

本華·曼德勃羅特(Benoit B.Mandelbrot)是分形學(Fractals) 的鼻祖,是的,Fractals這個詞就是他提出的。
不過,這個集合自己並非他本人提出的,而是一個叫作阿德里安·杜阿迪的法國數學家提出的,至於爲何以曼德勃羅特命名,根據wiki的說法是提出者爲了向曼德勃羅特致敬而放棄用本身的名字命名。code

這是個什麼樣子的集合??

一個複數\(z\)在或不在曼集中,取決於以下數列是否收斂
\[ \zeta:z,f(z;c),f(f(z;c);c),\cdots,f\circ^n(z;c) \tag{1} \]
\[ z,c\in\mathbb{C} \]
其中,\(f(z;c)=z^2+c\)\(c\)是一個復參數。
若是把\(f\circ^n(z;c)\)展開寫的話就是:
\[ \begin{split} \zeta_n&=f\circ^n(z;c)=z_n^2+nc \\ z_n&=z_{n-1}^2+c \end{split} \tag{2} \]
因爲在其中迭代的前一個\(z\)都是以平方的形式出現,這也就意味着
\[ \left|\Re\left(z_n\right)\right|\ge\left|\Re\left(z_{n-1}\right)\right|\ge 0 \\ \left|\Im\left(z_n\right)\right|\ge\left|\Im\left(z_{n-1}\right)\right|\ge 0 \]
這樣一來,若\(z_n\)收斂,則\(z_m(m<n)\)必然收斂。blog

綜上描述,有下結論:
\[ z\in\mathbb{M} \Leftrightarrow \lim_{n\rightarrow\infty}\zeta_n\in\mathbb{C} \tag{3} \]遞歸

式3即曼集斷定的充要條件或定義。圖片

爲了讓問題變得簡單,這裏能夠令初始值\(z=0\),這樣式2變成了僅與參數\(c\)相關的形式:
\[ \begin{split} z_0 &=0 \\ z_1 &=c \\ z_2 &= z_1^2+c = c^2+c \\ z_3 &= z_2^2+c = c^4+2c^3+c^2+c \\ &\cdots \\ \end{split} \]ci

複數??迭代??還收斂??聽起來好像很複雜……

這個……是的,這些東西聽起來很不直觀,以致於我也花了很久看了大量資料才搞清楚是什麼意思。數學

可是,這件事自己的中心思想並不複雜,其無非作了這樣的一件事情:

  1. 給定一個複數\(c\)
  2. 計算\(z_+=z^2+c\)\(z_+\)會成爲下一次的\(z\)
  3. 就這樣,反覆執行2,直到山窮水盡之時
    1. \(z_+\)仍然沒有飛昇爲\(\infty\),那\(c\in\mathbb{M}\)
    2. 不然存在某一次\(z_+=\infty\)\(c\notin\mathbb{M}\)

山窮水盡之時??那我恐怕等不到那一天了……

沒必要沮喪,咱們甚至沒打算去推導出\(z_n\)的完整展開形式,並且,計算機也沒法處理須要運行無數次才能解決的問題(停機問題)。

可是,在有限的,可預見的次數(迭代數)內,進行這樣的斷定仍是可行的而且能得出結果的。

可是這樣的話確定會有紕漏吧

說的對,由於只要迭代數\(n\)有限,\(z_n\)是總能求出肯定值。但正由於如此,迭代數的增加纔會產生實際的意義。這就好像,沒有人能求出\(\pi\)的全部小數位數,可是每當有人多求出正確的一位的時候,其結果與實際值就總會更加接近。也就是說,只要範圍有限,咱們求得的曼集就總比實際的曼集大,但隨着次數的增多,結果集合會向曼集逐漸地靠近。

所幸,在有限的次數內,咱們儘管不能當即斷言哪些數必定屬於曼集,但咱們能斷言哪些數必定不屬於曼集,那正是:

還沒上高速公路就要起飛的,那必然是要玩完的

在有限次計算就體現出明顯的發散趨勢的時候,那基本也就搶救無效了

好吧好吧,那有哪些值是能夠放棄治療的?

根據迭代函數的定義,\(f(z;c)=z^2+c\),咱們對函數的範數(也就是,或者稱爲複數的絕對值)進行考察,至少若是\(f\)收斂,其模與帶入z的模至少是相等的(固然,原則上幅角也應當相等),:
\[ |f(z;c)|=\left|z^2+c\right|=|z| \\ |z|\ge0 \]

根據絕對值三角不等式(這個對複數域一樣成立,實際上就是向量絕對值三角不等式的移植),若\(|z|=0\)\(|c|\)必然爲\(0\),不然
\[ |z|^2-|c|=|z^2|-|c|\le|f(z;c)|=|z|\le|z^2|+|c|=|z|^2+|c| \\ 1-|c|\le|z|\le1+|c| \]
因爲迭代初始條件\(z_0=0\),所以通過n次迭代後的\(z\)必定是
\[ z_n=\sum_{m=0}^{(n-2)^2}{k_mc^{m}} \\ c\le|z_n|\le\sum_{m=0}^{(n-2)^2}{|k_m||c^{m}|} \]
上面的不等數右側產生了一個很熟悉的形式——冪級數,若是要冪級數\(\sum_{m=0}^{(n-2)^2}{|k_m||c^{m}|}\)收斂,則\(|c|<1\)必要條件(因爲\(k_m\)的大小這裏沒有多加考慮,即便\(|c|<1\)知足,發散的可能一樣存在,這個在後續的迭代過程當中會進一步的被髮掘出來),不然根據冪級數的性質級數必然發散,這樣獲得了一個必要閾值\(|c_\Theta|=1\),將這個閾值帶入,咱們就能獲得\(|z|\)的基礎發散斷定值:
\[ |z|\ge2\Rightarrow \lim_{n\rightarrow\infty}{\zeta_n}=\infty \]

在後面的程序中,咱們將使用這個值做爲發散斷定,意思就是說,若是算出\(|z|\ge 2\)了,那就不用考慮了,你不可能收斂的,至於剩下的值,我只假設但不斷言它必定在集合內,所以我暫且將它認爲是集合內的,若是通過更多的計算髮現也達到這個紅線條件了,那麼一樣不論這個值掙扎了多久,OUT!

可是,複數的收斂並不限於模收斂,當複數被整理爲輻角形式\(r\angle\theta\)時,若\(\theta\)沒有穩定值即使\(r\)已經收斂仍然不能斷言複數收斂(畢竟啊,那個複平面上的向量若是一直在原地打轉,份量就總在以一個有界值變化,宛如論證的喪鐘在轉動),可是對於這個函數而言根本不會不存在這種情形

哇,斷言的這麼輕鬆??何出此言??

其實很容易,找到這樣的向量,利用迭代函數平方相加後保持模不變便可,所幸的是,這樣的值很是有限,由於首先一個條件就將範圍急劇縮減:
\[ |c|=1 \]

是的,只有\(|c|=1\)的時候,平方計算後纔可能保證模不變(依據輻角計算法若\(z=r_z\angle\theta\),則\(z^2=r_z^2\angle{2\theta}\)),固然我是指僅一次,由於還另外須要一件事保證\(z^2+c=c^2+c\)後的模長也是\(|c|\)或者說是\(1\)
\[ \cos{<c^2,c>}=-\frac{1}{2} \]
也就是說\(\arg{z^2}-\arg{c}=\frac{2k\pi}{3}\)才能夠(這個式子寫的不是很嚴謹,但你當成是兩個夾角爲\(120^\circ\)就好),固然你會反駁:

\(c\)是隨意取得,也就是說幅角就能夠是任意值,固然,\(z^2\)的取值決定於\(c\),也就是\(z^2\)能夠和\(c\)構造連續的函數映射,那麼你根本保證不了\(z^2\)必定可以避開這些值啊你個代數白癡!!

啊是的,可是你很快就會意識到一個事實:

這個喪鐘,轉不久的!!

什麼??

確實,當知足上述兩個條件以後,獲得的\(z_+\)的結果模仍然是\(1\),只是轉了個角度,可是架不住這個函數是迭代的啊,此次獲得的\(z_+\)很明顯位於這\(z^2\)\(c\)的角平分線處……

而後,夾角條件被打破了!!

就像雪崩同樣,這個微妙的平衡很快就被打破了,而且立刻開始急劇的發散(或收斂)。

如丸走阪
- Ramp Rollerball

這樣一來,經過屢次的迭代運算,不合格的點一點一點被篩出,若是次數足夠多,這個集合最後造成的圖形被稱爲Mandelbrot分形
大概長出來是這樣的:

這個M..d什麼b什麼的分形有什麼特色麼??

做爲一個分形,它最大的特色之一就是:

德羅斯特效應(自類似性,Droste Effect)

若是對這個圖形局部放大,你會看到這個與最開始看到一致的子圖性,並且:

放大一次有,一直放大一直有!放大放大再放大!每個子部都看的清清楚楚!

德羅斯特效應本質是遞歸式的圖形體現,由於迭代函數經過\(z_+=f(z;c)\)進行了遞推,這樣的話,也就意味着,分形具備無限細微的結構,並且跟總體是類似的。

而具備無限精細的結構也就意味着:它具備有限的面積,而周長卻極可能無窮大

固然因爲曼集的邊緣性質很是複雜,這裏就再也不詳細討論他的周長是否收斂了(面積收斂是一看就能看出來的),畢竟這篇文章是爲了說明如何經過OpenGL實現這個集合的可視化,上面其實已經花了大量的篇幅去推導一個閾值了。

Mandelbrot集的兄弟——Julia集簡介

以前是以\(c\)做爲參數進行迭代,獲得了曼集,那麼,若是把\(c\)\(z\)交換一下,讓\(z\)作參數,就獲得了Mandelbrot集的兄弟——Julia集(如下簡稱朱集),以法國數學家加斯頓·朱利亞(Gaston Julia)命名。

— Oh,Julia~
— Oh,Mandelbrot~
— W...What??

一樣,由於遞歸仍然存在,Julia集獲得的也會是Julia分形

迭代函數沒變,所以它的發散斷定也是和曼集是一致的,這裏僅僅介紹一下這個親戚,並不打算實現這個Julia集的可視化。

OpenGL可視化實現

其實上面的函數都已經推導完畢的狀況下,而且也知道集合是如何生成的以後,代碼實現就容易多了,繪製曼集的代碼以下(只考慮整數點):

基本繪製(二值化)

void DrawMandelbrot(int maxIter)
{
    complex<double> z(0,0); // 位於標準庫<complex>頭文件中
    complex<double> c;
    complex<double> f(0,0);
    bool diverge = false;
    glBegin(GL_POINTS);
    for (GLint x = -400; x < 400; x++)
    {
        diverge = false;
        for (GLint y = -300; y < 300; y++)
        {
            c = { static_cast<double>(x)/150,static_cast<double>(y)/150 };
            int iter;
            for (iter = 0; iter < maxIter; iter++)
            {
                z = z*z + c;
                if ( abs(z) >= 2 ) //發散斷定
                {
                    diverge = true;
                    break;
                }
            }
            if (!diverge)
            {
                glColor3f(0.0f, 0.0f, 0.0f); //集合內黑色
            }
            else
            {
                glColor3f(1.0f, 1.0f, 1.0f); //集合外白色
                diverge = false;
            }
            z = { 0,0 };
            glVertex2i(x, y);
        }
    }
    glEnd();
}

效果圖:

有問題,若是發散斷定值不取2會有什麼影響??

問得好,實際上我正想說,其實斷定值最好取2,若是實在沒法取得的話,也至少請保證它大於2
若是小於2,則有些原本應該在曼集內的點被剔除,這樣造成的曼集圖形是不完整的。

而若是大於2,當迭代次數很是大的時候,實際上獲得的結果與2的時候相差不大(細微的內部可能會有所差異),由於次數很是大的時候,該發散的值通常早都散得沒影了,用再大的有限值根本攔不住。可是若是迭代次數很是小的時候,獲得的曼集會包含大量冗餘的點,由於閾值沒能稱職地起到約束做用。

迭代着色

固然,不管如何,咱們可讓上面的圖更加漂亮一些,好比咱們以迭代多少次就發散了做爲着色的依據,這裏對上面的圖形進行着色處理。
實際上只須要把集合外的着色代碼裏面的顏色份量改爲與迭代數iter相關的形式:

//...
else
{
    glColor3f(static_cast<float>(iter) / maxIter, (0.5f*iter) / maxIter, 0.15f*iter / maxIter);
    diverge = false;
}

繪製結果:

值得注意的是,因爲迭代次數是一個離散的值,所以整個圖片的着色顯得並非那般的絲滑和連續,在其餘文章中還有提到過對數化將使着色更加柔和漂亮的方法,這裏再也不闡述。

當指定參數maxIter的次數不一樣,造成的圖形也不一樣,而且,隨着迭代次數的增大, 形狀逐漸趨於穩定,更接近於標準的曼集圖樣:

相關文章
相關標籤/搜索