上一篇介绍了怎样用Apophysis从头创作一幅分形图。步骤虽然很简单,但估计一般人(例如我自己)看了都一头雾水,为什么这样设几个数字就画出来一幅画呢?这些无厘头的操作和最终结果有什么逻辑关系呢?要搞清楚其中的关联,就不得不去研究一下Apophysis所依赖的算法:The Fractal Flame Algorithm。也不知道中文有没有对应的专有译名,我把它叫作“分形火焰算法”(或者“分形燃烧算法”?)
这个算法不是什么高深的秘密,安装了Apophysis后就能在help里找到pdf文档的链接。也可以直接在
这里下载。
不过老实说,我辛辛苦苦看完算法之后,才发现看懂了算法其实对绘画没有半点帮助……因此如果你没兴趣知道这些分形图的原理,应该可以不看算法直接开画,或者只看到三角变换的原理即可。只不过作为一个搞开发的人,不去刨根问底一下实在有点心虚。因此,本文的定位是,简单介绍算法原理,让我们在作画时对各种的操作的运作原理有个简单的印象。我尽量不去直译算法文档中的数学语言,用自己的话去描述。其实原文也不算复杂,想了解如算法和实现细节的话完全可以看原文。
那么下面就开始来看这个算法。
IFS系统
前面已经说过,分形图其实是把一个平面进行一系列函数映射所得到的结果。那么,这究竟是什么样的函数映射呢?这种映射名叫“迭代函数系统”(Iterated Function Systems,简称IFS)。一个在二维平面上的IFS方程是这样的:
* 这里的Fi是点集到点集的映射。用编程的语言来说:参数类型和返回类型都是点集。
换言之,有n个函数,我们希望在平面上找到一组点集S,使得S在经过这些函数分别变换后再取并集,得到的还是S本身(数学上这样的解称为函数的不动点)。而我们所看到的分形图,本质上,就是这个点集S。当然,要令这个S能实际存在,这些函数不能是完全随意的,它们必须能保证整个系统是收敛的。也就是说,把这个系统函数应用到整个平面,得到的结果再继续应用到这个系统中,若干次后,结果会越来越接近实际的不动点集。
实际求解算法
呃,似乎很抽象,不过不要紧,这个定义对我们作画一点用处也没有。实际上,要求解这个方程是很麻烦的。所以在分形火焰算法中,使用了下面这种近似的求解步骤:
* 这里的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次方,已经大大超出了作画时一个像素的精度。当然,实际使用的未必是线性函数,因此收敛速度也有所不同。好在我们现在不是考试,而是作画,具体的迭代的次数可以由用户去选定,如果次数不够,结果不够精确,图像也就不那么好看而已,而不会导致你高考落榜。
Variation
以上是整个系统的运作原理,为了便于在实际操作中描述这些变换函数,我们引入Variation的概念。在实际操作中,一个变换函数可以被描述为:
可以看出,这个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的方程,在这里选几个例子看看:
可以看到,linear是最简单的变换(就是变成原来的样子)。不过由于Fi函数在把参数传进Vj之前就作了一次线性变换,所以实际上对于整个Fi来说,就是普通的线性变换。
另外,我们在上面的JuliaN变换中看到,这个方程除了使用x,y和一些常量之外,还使用了juliaN.power和juliaN.dist两个额外的参数(除了这两个参数之外,式子中的其他符号都是常量或者随机数),这些额外的参数就称为
Variables(变量)。如果搞过函数式编程,应该对“自由变量”这种说法不会陌生。这些额外的参数就是自由变量,对于没玩过函数式编程的朋友,只要知道可以在Apophysis的
Variable面板指定这些额外的参数的值就行了。
变换三角
有了这些背景知识,现在我们可以来看看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面板上的输入数值,分别代表了:
例如上面的原始变换三角,如果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
图像就变成了:
这是什么回事,为什么不是一个小一点的正方形,而是这样的放射线呢。对照前面的算法就明白了,对任意一个随机点,将反复应用这个变换(x和y分别乘以0.999),使得这个点不停向原点靠拢。其实对于这个变换,原点才是真正的不动点。但由于收敛速度太慢,超过了20次迭代后还没有靠近原点,因此每个点收敛的轨迹被画出来了,变成了放射线。
如果我们把三角变换缩小得再稍微多一点,例如把0.999设为0.9,就能看到这个图形都不见了,变成了原点上的一个点。
同样,假如我们在某个方向上缩小。例如设为:
X:0.9 ,0
Y:0 ,1
O:0,0
可以看到,图像变成了Y方向上的一条直线。
从这里可以看出一个规律:如果映射之后的图像比原来的图像在某个方向上缩小了(就是在某个方向上不能达到原来图像的边界),那么在这个方向上的图像就会最终缩小成一点。这个规律对理解下面的示例比较重要。
作图实例1,塞宾斯基三角
明白了这些基础知识后,我们不妨通过一个实例来看看这个迭代系统是怎么起作用的。
先来一个最简单的例子,画一个赛宾斯基三角形。这个三角形的特点是,先作一个大三角形,然后取各边中点连成一个小三角形,挖去这个小三角形。然后在剩下的三个小三角形里重复这个步骤。然后再在剩下的三角形里重复这个步骤……最后就得到了:
那么在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
立杆见影,结果出来了:
如果你还没有想清楚,我们把这个迭代过程总结一下:
* 这里用三个变换,每个都把原来的图像(不管是啥)缩小一半。
* 通过位移,在原来图像的范围内,下方并排两个,上方叠一个。
* 在这个迭代变换中,每一步原来的图像都先进行这样缩小排列,然后得到新的图像再进行这样的缩小排列。
* 到最后,整个图像收敛成了这个叠出来的三角形的形状,而且其中每一部分也是这样的三角形。
我们不妨把局部放大一下,可以看到细节里也是这样的三角形:
……随便写写就1点多了。看来要分两集了。看了每屏的字,来点干货提提神。昨天试画了一幅,貌似还不错。
代码是:
<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>
- 大小: 11.9 KB
- 大小: 32 KB
- 大小: 16.4 KB
- 大小: 50.7 KB
- 大小: 54.1 KB
- 大小: 46.2 KB
- 大小: 20.6 KB
- 大小: 12.6 KB
- 大小: 10.1 KB
- 大小: 98.7 KB
- 大小: 51.9 KB
- 大小: 2.5 MB
分享到:
相关推荐
分形算法是计算机科学中的一个重要概念,它用于生成这些复杂且精细的结构。在本案例中,我们将深入探讨如何使用VC(Visual C++)来实现一个分形树的生成算法。 分形树的生成通常基于迭代过程,每一步迭代都会增加树...
分形与混沌是数学、物理学和计算机科学领域中极具...总的来说,分形与混沌的研究为我们提供了一种新的视角来看待世界,而分形制图软件和程序则为我们打开了通向这一神秘领域的窗口,让我们得以窥见自然界的深邃之美。
通过Java实现分形算法不仅可以帮助我们更好地理解和探索分形的奇妙世界,还能为学习图形编程提供一个有趣且实用的切入点。随着技术的发展,未来还会有更多创新的方法和工具出现,使得分形技术在更多领域得到应用。
5. **源码组织**:项目中的源码可能包含了主函数、分形生成函数、绘图函数等多个部分。理解源码需要阅读代码结构,识别各个函数的作用,了解数据结构和算法的实现细节。 6. **迭代计算**:分形图案通常通过迭代算法...
5. **分形算法优化**: - **缓存技术**:已计算过的点可以存储在数组中,避免重复计算。 - **双缓冲**:防止屏幕闪烁,提高用户体验。 - **多线程**:对于大规模计算,可以利用多核CPU并行计算。 通过这个"分形...
在MATLAB环境下实现Koch分形算法,可以使用MATLAB的绘图函数如plot等,根据递归规则逐步绘制出图形。MATLAB的脚本语言支持循环和条件语句,非常适合实现递归算法,此外,MATLAB还提供了丰富的图形处理功能,使得生成...
总的来说,"分形算法与程序设计—VB实现(光盘源码).rar"这个资源为学习者提供了一个实践分形算法的VB编程平台,结合源码和相关文档,可以帮助深入理解分形理论,提高编程技能,并激发对数学和计算机科学的兴趣。
同时,分形理论也为现代计算机图形学提供了灵感,例如在图像处理、纹理生成等领域,分形算法被广泛应用以创建逼真的自然景观。 总的来说,唐代铜镜纹饰的分形制图思想是中国传统艺术与现代科学的完美结合,它体现了...
分形算法是一种在数学、计算机科学以及艺术领域广泛应用的技术,它通过迭代过程生成复杂而自相似的几何结构。在这个特定的程序中,我们利用分形算法来生成雪花图案,同时实现了用户交互功能,允许用户拾取和拖动生成...
接着,"Rand" 函数是随机数生成器,对于分形图的生成至关重要,因为许多分形算法需要在每次迭代中使用随机数来确定下一步的动作。例如,在 Mandelbrot 集中,可能会用随机数来决定点是否离开特定区域;在 Julia 集中...
从给定的信息来看,本文将围绕“易语言源码中的树枝分形算法”这一主题进行深入探讨。首先,我们需要理解几个关键概念:易语言、源码、树枝分形算法。 ### 易语言简介 易语言是一种面向中国用户的编程语言,其最大...
然后,我们可以使用 Matlab 的绘图函数来将这些分支连接起来,从而形成一个完整的分形树。 Matlab 代码分析 下面是 Matlab 实现分形树的源码: ```matlab clear; clf; for kmax = 1:4 subplot(2,2,kmax); theta ...
在计算机图形学领域,Sierpinski分形是一种经典的几何构造,它展示了分形理论的美妙之处。在VC++环境中,结合MFC(Microsoft Foundation Classes)框架和OpenGL库,我们可以创建一个程序来可视化Sierpinski分形图像...
Fraclab是一个专为MATLAB设计的多重分形分析工具箱,它提供了丰富的函数和算法,使得用户能够方便地在MATLAB环境中进行多重分形的研究和应用。多重分形是一种扩展了传统分形理论的概念,它能更好地描述具有不同尺度...
分形树是一种在计算机图形学中常见的几何形态,它展示了自然界中许多复杂结构的自相似性。在本项目中,我们使用Visual C++(VC++)编程环境,结合Microsoft Foundation Classes(MFC)库,来实现一个简单的分形树...
总之,这个 Delphi 实例展示了递归分形算法在图形处理中的应用,为学习 Delphi 编程、图形编程以及递归算法的开发者提供了宝贵的实践素材。通过深入研究和理解这个项目,你不仅可以提升 Delphi 技能,还能对分形艺术...
5. **可视化**:最后,使用图形库或编程语言的绘图功能将构建好的分形树可视化展示出来。 源代码可能会使用诸如Python、C++或Java等编程语言,利用递归或迭代的方式来实现这个过程。递归方法通常更加直观,因为它...
单规则L系统的分形草是指使用一组简单的生成规则来模拟草的生长。例如,可以定义如下规则: - 公理:`F` - 规则:`F -> F+F-F-F+F` 这里,`F` 表示向前移动一步并绘制一条线段,`+` 和 `-` 分别表示向左和向右旋转...
在本资源中,“广义分形Matlab源程序—Word版”提供了用Matlab编程语言实现广义分形算法的详细示例。Matlab作为一种强大的数值计算和数据可视化工具,非常适合进行复杂的数学运算,包括处理分形几何。 首先,我们来...