分形绘图简介(5)分形火焰算法简介(一)

上一篇介绍了怎样用Apophysis从头创做一幅分形图。步骤虽然很简单,但估计通常人(例如我本身)看了都一头雾水,为何这样设几个数字就画出来一幅画呢?这些无厘头的操做和最终结果有什么逻辑关系呢?要搞清楚其中的关联,就不得不去研究一下Apophysis所依赖的算法:The Fractal Flame Algorithm。也不知道中文有没有对应的专有译名,我把它叫做“分形火焰算法”(或者“分形燃烧算法”?) 这个算法不是什么高深的秘密,安装了Apophysis后就能在help里找到pdf文档的连接。也能够直接在[url=http://www.flam3.com/flame_draves.pdf]这里[/url]下载。 不过老实说,我辛辛苦苦看完算法以后,才发现看懂了算法其实对绘画没有半点帮助……所以若是你没兴趣知道这些分形图的原理,应该能够不看算法直接开画,或者只看到三角变换的原理便可。只不过做为一个搞开发的人,不去刨根问底一下实在有点心虚。所以,本文的定位是,简单介绍算法原理,让咱们在做画时对各类的操做的运做原理有个简单的印象。我尽可能不去直译算法文档中的数学语言,用本身的话去描述。其实原文也不算复杂,想了解如算法和实现细节的话彻底能够看原文。 那么下面就开始来看这个算法。 [size=medium][b]IFS系统[/b][/size] 前面已经说过,分形图实际上是把一个平面进行一系列函数映射所获得的结果。那么,这到底是什么样的函数映射呢?这种映射名叫“迭代函数系统”(Iterated Function Systems,简称IFS)。一个在二维平面上的IFS方程是这样的: [img]http://dl.iteye.com/upload/attachment/616829/d22d77b3-da81-3d60-8a33-5f75ddfb7862.jpg[/img] * 这里的Fi是点集到点集的映射。用编程的语言来讲:参数类型和返回类型都是点集。 换言之,有n个函数,咱们但愿在平面上找到一组点集S,使得S在通过这些函数分别变换后再取并集,获得的仍是S自己(数学上这样的解称为函数的不动点)。而咱们所看到的分形图,本质上,就是这个点集S。固然,要令这个S能实际存在,这些函数不能是彻底随意的,它们必须能保证整个系统是收敛的。也就是说,把这个系统函数应用到整个平面,获得的结果再继续应用到这个系统中,若干次后,结果会愈来愈接近实际的不动点集。 [size=medium][b]实际求解算法[/b][/size] 呃,彷佛很抽象,不过没关系,这个定义对咱们做画一点用处也没有。实际上,要求解这个方程是很麻烦的。因此在分形火焰算法中,使用了下面这种近似的求解步骤: [img]http://dl.iteye.com/upload/attachment/616832/605288e5-3c6e-3031-bcf0-8136696370e7.jpg[/img] * 这里的Fi是由点到点的映射。用编程的语言来讲:参数类型和返回类型都是点。 * bi-unit square是指x和y都在[-1,1]之间的那个点集,也就是左上角坐标是(-1,1),右下角坐标是(1,-1)的边长为2的正方形内的全部点。 它的意思是,在bi-unit squre这个正方形点集里随机选取一个点(x,y),而后重复如下步骤: 1. 在n个函数中随机选一个Fi; 2. 用这个函数变换(x,y),获得新的点(x,y); 不停重复以上两步,而且在第20次迭代以后,把后续每次迭代的结果点画出来。 这种方式之因此可行,是由于根据前面的方程,若是(x,y)属于S,那么Fi(x,y)也确定属于S。而既然整个系统是收敛的,以线性函数的平均收敛速度0.5为例,20次迭代以后,求得的点与实际的不动点的偏差大约是10的-6次方,已经大大超出了做画时一个像素的精度。固然,实际使用的未必是线性函数,所以收敛速度也有所不一样。好在咱们如今不是考试,而是做画,具体的迭代的次数能够由用户去选定,若是次数不够,结果不够精确,图像也就不那么好看而已,而不会致使你高考落榜。 [b][size=medium]Variation[/size][/b] 以上是整个系统的运做原理,为了便于在实际操做中描述这些变换函数,咱们引入Variation的概念。在实际操做中,一个变换函数能够被描述为: [img]http://dl.iteye.com/upload/attachment/616855/70940dc7-9d2e-39e7-8918-9add39d20c0b.jpg[/img] 能够看出,这个Fi函数接受了一个点(x,y)做为参数后,先对其作了一个线性变换: x' = ax + by + c y' = dx + ey + f 若是咱们高中解析几何还没全忘掉的话,应该记得对一个形状(点集)进行这种变换包括了缩放,旋转,拉伸和平移等操做。 而后,把这个变换后的点传入一个Vj函数中再做变换。而这个Vj函数,就是咱们以前看到在Apophysis的Variation标签页中列出的那些变换函数(固然,这些函数必须能保证IFS的收敛性)。分形火焰算法中列举了49个基本的Variation函数,在Apophysis中,还能够经过安装plugin的方式增长新的Variation。 这些Variation函数被设计为能对一个形状的点集进行某种有特定含义的变换。例如线性变换(linear),球面变换(spherical),漩涡变换(swirl)等等,而且根据其含义起了名称,方便咱们选用。在算法文档中列出了49个基本Variation的方程,在这里选几个例子看看: [img]http://dl.iteye.com/upload/attachment/616859/4b4253aa-5574-33e7-bfca-6364465d729d.jpg[/img] [img]http://dl.iteye.com/upload/attachment/616861/dc3e8952-f82e-3c39-b98e-11104a699180.jpg[/img] [img]http://dl.iteye.com/upload/attachment/616863/602d7509-6aa9-3917-89de-08708efb2d6f.jpg[/img] 能够看到,linear是最简单的变换(就是变成原来的样子)。不过因为Fi函数在把参数传进Vj以前就做了一次线性变换,因此实际上对于整个Fi来讲,就是普通的线性变换。 另外,咱们在上面的JuliaN变换中看到,这个方程除了使用x,y和一些常量以外,还使用了juliaN.power和juliaN.dist两个额外的参数(除了这两个参数以外,式子中的其余符号都是常量或者随机数),这些额外的参数就称为[b][color=darkblue]Variables(变量)[/color][/b]。若是搞过函数式编程,应该对“自由变量”这种说法不会陌生。这些额外的参数就是自由变量,对于没玩过函数式编程的朋友,只要知道能够在Apophysis的[b]Variable面板[/b]指定这些额外的参数的值就好了。 [size=medium][b]变换三角[/b][/size] 有了这些背景知识,如今咱们能够来看看Apophysis里的变换三角到底是什么一回事。其实很简单,上面说到,每一个Fi都先对传入的点进行了一次线性变换,而这个线性变换涉及到6个参数:a,b,c,d,e,f。所谓变换三角,就是这6个参数的形象描述。 准确来讲: 1. 坐标原点到变换三角的O顶点的矢量表明了c和f。也就是线性变换的平移量。 2. 变换三角的O顶点到X顶点的矢量表明了a与b。 3. 变换三角的O顶点到Y顶点的矢量表明了d与e。 其中a表明了在X方向上的缩放量,e表明了在Y方向上的缩放量。而当b,c不为0时,形状出现拉伸和旋转。 也就是说,Transform面板上的输入数值,分别表明了: [img]http://dl.iteye.com/upload/attachment/616879/04b75850-1cac-335b-a5ee-07bc24b467e2.jpg[/img] 例如上面的原始变换三角,若是Variation取linear(线性变换)时,该变换无位移(O顶点为(0,0)),无缩放(X1即a为1,Y2即e为1),无旋转和拉伸等(X2和Y1即b,c均为0) 因为Variation中的linear的默认值是1,默认使用此变换。此时Apophysis的主窗口预览中显示一个由噪点组成的正方形。这些噪点就是前面的求解算法中所提到的随机选取的点。(对于这个原始变换,这些点自己就是解) 如今咱们把变换三角稍微缩小一点。把Transform参数设为: X:0.999 ,0 Y:0 ,0.999 O:0,0 图像就变成了: [img]http://dl.iteye.com/upload/attachment/616886/be7e33e3-bb3c-31b5-aebe-c3619399738a.jpg[/img] 这是什么回事,为何不是一个小一点的正方形,而是这样的放射线呢。对照前面的算法就明白了,对任意一个随机点,将反复应用这个变换(x和y分别乘以0.999),使得这个点不停向原点靠拢。其实对于这个变换,原点才是真正的不动点。但因为收敛速度太慢,超过了20次迭代后尚未靠近原点,所以每一个点收敛的轨迹被画出来了,变成了放射线。 若是咱们把三角变换缩小得再稍微多一点,例如把0.999设为0.9,就能看到这个图形都不见了,变成了原点上的一个点。 一样,假如咱们在某个方向上缩小。例如设为: X:0.9 ,0 Y:0 ,1 O:0,0 能够看到,图像变成了Y方向上的一条直线。 从这里能够看出一个规律:若是映射以后的图像比原来的图像在某个方向上缩小了(就是在某个方向上不能达到原来图像的边界),那么在这个方向上的图像就会最终缩小成一点。这个规律对理解下面的示例比较重要。 [size=medium][b]做图实例1,塞宾斯基三角[/b][/size] 明白了这些基础知识后,咱们不妨经过一个实例来看看这个迭代系统是怎么起做用的。 先来一个最简单的例子,画一个赛宾斯基三角形。这个三角形的特色是,先做一个大三角形,而后取各边中点连成一个小三角形,挖去这个小三角形。而后在剩下的三个小三角形里重复这个步骤。而后再在剩下的三角形里重复这个步骤……最后就获得了: [img]http://dl.iteye.com/upload/attachment/616890/c9435b28-c7b6-3e60-9f7e-b5cbcbd9d32e.jpg[/img] 那么在Apophysis怎么去画呢?固然,若是咱们是数学家的话,直接写一堆方程解出所需的abcdef就好了。但咱们如今是画家,能不能用点直观的方式去想出来呢? 用惯photoshop的人一般的思路是:Ok,第一步首先要想办法画出一个三角形来。不过,在分形里可不是这样玩的。虽然如今咱们手上有一个正方形(原始变换),但它的形状其实不重要,由于不管它最初的形状是怎样的,迭代变换以后的不动点都是一致的。是咱们选用的迭代函数决定了最终的图像,而跟初始形状没有关系。那么咱们就应该只关注总体与局部的关系。 仔细来看看这个赛宾斯基三角形,能够发现它总体与局部的关系是:总体在高度和宽度都缩小一半以后,复制了三份,两分并排列在下面,一份叠在上面。从前面的收敛规律来看,这样的变换排列方式保证了下方最宽的地方与原图像宽度一致,而高度也与原图像一致。但上方因为只有一份,在横向上宽度比原图像缩小了,最终将缩小为一个点。这不就是个三角形吗?看来有戏,如今就开始在Apophysis里试试看。 首先新建一个变换三角。由于Variation默认就是linear,能够无论。把它缩小一半。 在Transform里设置: X:0.5, 0 Y:0 , 0.5 O:0, 0 能够看到,此时图像变成了一个小点。而后选中这个变换三角,按Insert键复制一个。把Transform设为: X:0.5, 0 Y:0, 0.5 O:0.5, 0 也就是原图像缩小一半后,在下方并排两个副本。不出所料,图像变成了一条直线。(如今新变换在横轴方向大小与原图像一致,但在纵轴方向缩小一半,最终收敛到了横轴上) 再选中任意一个三角,按Insert键复制,把它平移到上端: Transform设为: X:0.5, 0 Y:0, 0.5 O:0.5, 0.5 立杆见影,结果出来了: [img]http://dl.iteye.com/upload/attachment/616926/773acaa3-3d6a-3b74-bad3-23430442ee17.jpg[/img] 若是你尚未想清楚,咱们把这个迭代过程总结一下: * 这里用三个变换,每一个都把原来的图像(不论是啥)缩小一半。 * 经过位移,在原来图像的范围内,下方并排两个,上方叠一个。 * 在这个迭代变换中,每一步原来的图像都先进行这样缩小排列,而后获得新的图像再进行这样的缩小排列。 * 到最后,整个图像收敛成了这个叠出来的三角形的形状,并且其中每一部分也是这样的三角形。 咱们不妨把局部放大一下,能够看到细节里也是这样的三角形: [img]http://dl.iteye.com/upload/attachment/616932/a0947d88-eb7f-3ef7-b187-8e6ca0c16e6a.jpg[/img] ……随便写写就1点多了。看来要分两集了。看了每屏的字,来点干货提提神。昨天试画了一幅,貌似还不错。 [img]http://dl.iteye.com/upload/attachment/616938/52070af3-dfa8-3d2f-926d-5198c6a27be4.png[/img] 代码是: [code="xml"] <flame name="Blank Flame" version="Apophysis 7x Version 15 (High-Memory Version)" size="1500 1000" center="0.0936657854738676 0.0140814830125901" scale="136.670692054355" oversample="1" filter="0.5" quality="50" background="0 0 0" brightness="8.1695652173913" gamma="1.85" vibrancy="1.44" estimator_radius="9" estimator_minimum="0" estimator_curve="0.4" enable_de="0" plugins="" > <xform weight="1" color="0.189" blur="0.4" coefs="1 0 0 1 0 0" opacity="1" /> <xform weight="0.5" color="0.009" linear="1" julian="1.5" coefs="-1 0 0 -1 0 0" julian_power="8" julian_dist="1" opacity="1" /> <xform weight="1.5" color="0" julian="1" coefs="1 0 0 1 -1.5 0" julian_power="12" julian_dist="-1" opacity="1" /> <xform weight="0.75" color="0.038" spherical="1" curl="0.5" coefs="1.25 0 0 1.25 0 0" curl_c1="0" curl_c2="1" opacity="1" /> <xform weight="0.5" color="0" juliascope="0.25" coefs="0 1 -1 0 0.5 0" juliascope_power="12" juliascope_dist="-1" opacity="1" /> <palette count="256" format="RGB"> FEEA31FEEA30FFEA2FF0C124E29919D39524C4922FF9B274 F3C07FEDCE8BEFCF6DF2D150F3E356F4F55CF8F632FAE72E FDD92BE0D241C3CC57B3C660A4C069C47066FC432EA22801 82140D63001955092348132D4D293A533F48B28395B74675 BC0A56DC0641FC032DFB0124FA001BEE0000E0001281010E 50062D1F0C4C0F0929000706001C0300310075A706AAA00D DF9915E68426EE6F38D96E46C46E55A34128A241001A3B28 0D4A15005A0200590201590202311F000D2C000024000021 00001F00011C00021900031C00041F00002201002400092C 00142F001F33002C37003A3B00483D015643023634032C1A 052301021E0A001913002224022A36003638005643116567 56848A9BA4ADC298B3E98CB9C0968AABA38C8AB88A77AA60 649D366CA22B75A7208BB40EA4BE1C94BD0992B5037B8701 79610F773C1E632A1F2D27111B1133010A2B010022000424 0009270018250028241C6410436E03907734DA9460DAC380 D5C181D0C0829CB25F9B976A95A986D47E97FF055CFF057C FF059DED34A3DC63AAEC7792F3A49DF4A59EFD9B82FD0559 FD0559FE0559F30040E80028DF0400C50F02450023311B1A 1E3612025C02007402017604006414016622006545024325 01542101651D2A83034A88032F8200007502005605003F12 002820000A2A000B2B00213129472F79942992B620C5BD1D FEEB2DFDFB37FAF444F4F45EF8FA95BCD39D6DAA9B6AA692 68A289327E720360560255450235382D1B316D0056B80058 D7013DF0003DEE0E19EE0E0CE85004C56F009EAA0CB1B642 C4A433FC7440F06745FA4331FB291AFA3F14FA3F16ED5724 C7861E8B884189905C89674E583136404407414401648200 88A70CA2BF2FD6E621F2F333FAF170F3F89ADBDFBED1DCB4 B0CDAFB7C2BCF2CBE0F5EAE8FAF6D1E5EFE7ECE1E7DBE6D6 BFD3B8A4C495829E886EA3996EA79E6A9790347D6A294546 00453D003839013236011E2E0024320134370F3E2C015410 2B480E4035082B2B0F350D01420123EC0089810E5B1B0045 44004D6D0055992C34C55813D36C10E1800DF3BD29F8D32D </palette> </flame> [code]