如何做一个iOS分形App
- 原文链接 : 如何做一个iOS分形App
- 原文作者 : Silviu Pop
- 译文出自 : 开发技术前线 www.devtf.cn
- 译者 : alier1226
- 校对者: MrLoong
- 状态 : 完成
介绍
在这个教程中,我们会做一个可以渲染Mandelbrot Set的应用程序,我们可以缩放和平铺它来看分形那令人惊叹的复杂之美。最终的结果如下:
着色程序的代码
void main() {
#define iterations 128
vec2 position = v_tex_coord; // gets the location of the current pixel in the intervals [0..1] [0..1]
vec3 color = vec3(0.0,0.0,0.0); // initialize color to black
vec2 z = position; // z.x is the real component z.y is the imaginary component
// Rescale the position to the intervals [-2,1] [-1,1]
z *= vec2(3.0,2.0);
z -= vec2(2.0,1.0);
vec2 c = z;
float it = 0.0; // Keep track of what iteration we reached
for (int i = 0;i < iterations; ++i) {
// zn = zn-1 ^ 2 + c
// (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
z += c;
if (dot(z,z) > 4.0) { // dot(z,z) == length(z) ^ 2 only faster to compute
break;
}
it += 1.0;
}
if (it < float(iterations)) {
color.x = sin(it / 3.0);
color.y = cos(it / 6.0);
color.z = cos(it / 12.0 + 3.14 / 4.0);
}
gl_FragColor = vec4(color,1.0);
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
void #define iterations 128 vec2 vec3 vec2 // Rescale the position to the intervals [-2,1] [-1,1] z *= z vec2 float for // zn = zn-1 ^ 2 + c // (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi z z if break; } it } if color.x color.y color.z } gl_FragColor } |
你可以下载起始版本跟着教程一起做,也可以在本文结尾找到最终版本的代码。
项目设置
Gamescene.sks
文件里包含一个名为 fractal
的子画面,它填充了整个界面并且着色程序程序
Fractal.fsh
也附在它上。
Fractal.fsh
包含了上面着色程序的代码
GameViewController.swift
包含了设置游戏场景的代码
GameScene.swift
为空
如果你现在运行代码,你将会得到如下的结果:
请注意纵横比固定为3/2,我们需要先根据屏幕大小调节它。
并且由于画面是静态的,所以你不可能与它有任何方式的交互。
设置界面
我们将用一个透明的scrollview来处理平铺缩放。scrollview将自动跟踪我们的位置以及我们在分形中的缩放程度。
打开`Main.storyboard`文件,拖进去一个scrollview。将scrollview设置成fill the view,并对它的宽度,到顶部距离,到底部距离设置限制。
将scrollview的最大缩放程度设置为100000,意味着我们将可以把分享放大到十万倍!我们不能再放大更多了因为已经接近了`float`类型的准确极限。
拖一个view(画面)到scrollview里,它将用作处理缩放。这个view本身不会展示任何东西,我们将用到它的contentOffset
和scrollView的zoom
属性来更新我们的着色程序。要确保这个画面可以填满scrollView,并且设定好宽度,到顶部底部左右距离的限制。将画面的背景色设置为 Clear Color (透明色)。
接下来我们将连接我们所需要的outlet和scrollView的代理。
给scrollView和scrollView的contentView拖进outlet。
class GameViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var contentView: UIView!
@IBOutlet weak var scrollView: UIScrollView!
...
}
1 2 3 4 5 6 7 |
class @IBOutlet @IBOutlet ... } |
接下来我们去掉代理方法,并且实现 viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
这个方法
class GameViewController: UIViewController, UIScrollViewDelegate {
...
func scrollViewDidScroll(scrollView: UIScrollView) {
}
func scrollViewDidZoom(scrollView: UIScrollView) {
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return contentView
}
...
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class ... func } func } func return } ... } |
向着色程序发送数据
着色程序可以从你的swift代码里的uniform变量里获得数据。uniform变量可以在SpriteKit编辑器里声明。那现在我们来声明一下uniform变量。
打开 GameScene.sks
文件,选择 mandelbrote sprite。将insepctor拖到底部,在“Custom shader Uniforms”里添加两项:float
类型的
zoom
,值为1
, 以及 vec2
类型的offset
。我们将用这两项uniform变量储存scrollView的
contentOffset
以及 zoom
属性。
警告:Xcode 6.3的uniform变量有bug。它不能直接在编辑器里赋值初始化,你必须在代码里初始化它们。
我们可以通过shader属性来获取节点上(node)着色程序,用 theuniformedName()
方法来从着色程序得到uniform变量。以下是我们获取zoom uniform变量的例子:
let zoomUniform = node.shader!.uniformNamed("zoom")!
1 2 |
let |
Once we have a uniform we can change its value via one of of the properties
当我们有了uniform变量后,我们可以通过它的属性来改变它的值。
var textureValue: SKTexture!
var floatValue: Float
var floatVector2Value: GLKVector2
var floatVector3Value: GLKVector3
var floatVector4Value: GLKVector4
var floatMatrix2Value: GLKMatrix2
var floatMatrix3Value: GLKMatrix3
var floatMatrix4Value: GLKMatrix4
1 2 3 4 5 6 7 8 9 |
var var var var var var var var |
We’re only interested in using floatValue
and floatVector2Value
for this tutorial.
在本教程里,我们只对 floatValue
和floatVector2Value
感兴趣。
Ex: to set the zoom to 2 we use
例子:将zoom的值设置成2
zoomUniform.floatValue = 2
1 2 |
zoomUniform.floatValue |
Coordinate systems and mapping intervals
坐标系以及映射出区间
我们将在保持比例的基础上映射不同的坐标系。我们将用这个来转化scrollview的坐标到复平面。
让我们先看一下一维的情况:
将x从区间[0,a]映射到区间[0,1],我们只需要除以区间长度 x‘ = x / a
。
将x从区间[0,1]映射到区间[a,b],我们可以乘上区间长度,然后再加上区间起始值,x‘ = x * (b - a) + a
。
举个例子,比如iPhone4的x坐标,x坐标为0到480之间。映射 x
到[0,1]
, 我们用
。映射
x‘ = x / 480x‘
从 [0,1]
到[-2,2]
,我们用
x‘‘ = x‘ * 4 - 2
如果我们屏幕上有一点x,坐标值为120,那么对应到区间[0,1]
将成为 120 / 480 = 0.25
,以及在区间
[-2,2]
,如下所见它将成为 0.25 * 4 - 2 = -1
。
Mapping between the scrollview and the complex plane
scrollView及复平面互相映射
我们需要讲scrollView上的点转换到复平面。第一步,先将scrollView上的点转换到区间[0,1]
。通过将contentOffset
除以contentSize
可以将
contentOffset
转换到区间[0,1]
。
var offset = scrollView.contentOffset
offset.x /= scrollView.contentSize.width
offset.y /= scrollView.contentSize.height
1 2 3 4 5 |
var offset.x offset.y |
我们着色程序x,y坐标都有点在区间[0,1]
,所以我们要在scrollView的contentView里映射出这些店。
标准化过的contentView为 1.0 / zoom
,所以contentView里标准化过的点坐标讲在区间[contentOffset / contentSize,contentOffset / contentSize + 1.0 / zoom]
。
还有我们必须牢记的是,y轴的点在GLSL上,而点(0,0)在左下角,所以我们必须翻转y轴来对应我们的scrollView。
下面的GLSL代码转换scrollView的contentView里点的位置。
// Fractal.fsh
void main {
vec2 position = v_tex_coord;
position.y = 1.0 - position.y; // flip y coordinate
vec2 z = offset + position / zoom;
...
}
1 2 3 4 5 6 7 8 9 10 11 |
// Fractal.fsh void vec2 position.y vec2 ... } |
如下你可以看见蓝色的scrollView的contentView在标准化与未标准化过的边框。contentSize = (960,640)
,contentOffset = (240,160)
,zoom = 2.0
ScrollView
标准化过的ScrollView
最后我们将点映射到复平面。为了在mandelbrot里得到好看的效果,我们将希望映射区域[-1.5,0.5] x [-1,1]
复平面。
我们还想使纵横比正确。现在我们的x、y轴的比例一样,我们要乘以x和纵横比使得图片不会变形。
纵横比是什么
纵横比是屏幕宽度和高度的比例。
// Fractal.fsh
void main {
...
z *= 2.0;
z -= vec2(1.5,1.0);
float aspectRatio = u_sprite_size.x / u_sprite_size.y;
z.x *= aspectRatio;
...
}
1 2 3 4 5 6 7 8 9 10 11 12 |
// Fractal.fsh void ... z *= z float z.x *= ... } |
下面你可以看到我们scrollView的contentView映射到的平复面以及纠正过纵横比的结果。
为了整合上面所有代码,我们建了一个新的方法叫updateShader,它可以传一个contentView坐标到着色程序。我们所需要做的就是在scrollView的代理方法里调用updateShader方法。
class GameViewController: UIViewController, UIScrollViewDelegate {
...
func updateShader(scrollView: UIScrollView) {
let zoomUniform = node.shader!.uniformNamed("zoom")!
let offsetUniform = node.shader!.uniformNamed("offset")!
var offset = scrollView.contentOffset
offset.x /= scrollView.contentSize.width
offset.y /= scrollView.contentSize.height
zoomUniform.floatValue = Float(scrollView.zoomScale)
offsetUniform.floatVector2Value = GLKVector2Make(Float(offset.x), Float(offset.y))
}
func scrollViewDidScroll(scrollView: UIScrollView) {
updateShader(scrollView)
}
func scrollViewDidZoom(scrollView: UIScrollView) {
updateShader(scrollView)
}
...
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class ... func let let var offset.x offset.y zoomUniform.floatValue offsetUniform.floatVector2Value } func updateShader(scrollView) } func updateShader(scrollView) } ... } |
同时也别忘了当view出现时调用updateShader方法,这样你才可以初始化uniform变量。
class ViewController {
...
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
updateShader(scrollView)
}
...
}
1 2 3 4 5 6 7 8 9 10 |
class ... override super.viewDidAppear(animated) updateShader(scrollView) } ... } |
最终着色程序的如下所示:
void main() {
#define iterations 128
vec2 position = v_tex_coord; // gets the location of the current pixel in the intervals [0..1] [0..1]
position.y = 1.0 - position.y;
vec2 z = offset + position / zoom;
z *= 2.0;
z -= vec2(1.5,1.0);
float aspectRatio = u_sprite_size.x / u_sprite_size.y;
z.x *= aspectRatio;
vec2 c = z;
float it = 0.0; // Keep track of what iteration we reached
for (int i = 0;i < iterations; ++i) {
// zn = zn-1 ^ 2 + c
// (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
z += c;
if (dot(z,z) > 4.0) { // dot(z,z) == length(z) ^ 2 only faster to compute
break;
}
it += 1.0;
}
vec3 color = vec3(0.0,0.0,0.0); // initialize color to black
if (it < float(iterations)) {
color.x = sin(it / 3.0);
color.y = cos(it / 6.0);
color.z = cos(it / 12.0 + 3.14 / 4.0);
}
gl_FragColor = vec4(color,1.0);
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
void #define iterations 128 vec2 position.y vec2 z *= z float z.x *= vec2 float for // zn = zn-1 ^ 2 + c // (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi z z if break; } it } vec3 if color.x color.y color.z } gl_FragColor } |
挑战
1 . 优化
黑色部分渲染的最慢。幸好根据下图,我们可以很快知道一个点是否在两块黑色部分之一里 (心形部分或者区域2)。这里你可以找到如何判断点是否在两块黑色区域之一里的方法。加上这些代码来改进着色程序,它们只会在点不在这两个区域里执行mandelbrot循环。这将大幅度提高app在这些区域可见时的表现。
见下图,主要的心形为红色,区域2为绿色。
Hint
提示
只当点在这些区域中的一个以外的时候执行mandelbrot循环。
Solution
答案
void main() {
#define iterations 128
vec2 position = v_tex_coord; // gets the location of the current pixel in the intervals [0..1] [0..1]
position.y = 1.0 - position.y;
vec2 z = offset + position / zoom;
z *= 2.0;
z -= vec2(1.5,1.0);
float aspectRatio = u_sprite_size.x / u_sprite_size.y;
z.x *= aspectRatio;
vec2 c = z;
bool skipPoint = false;
// cardioid checking
if ((z.x + 1.0) * (z.x + 1.0) + z.y * z.y < 0.0625) {
skipPoint = true;
}
// period 2 checking
float q = (z.x - 0.25) * (z.x - 0.25) + z.y * z.y;
if (q * (q + (z.x - 0.25)) < 0.25 * z.y * z.y) {
skipPoint = true;
}
float it = 0.0; // Keep track of what iteration we reached
if (!skipPoint) {
for (int i = 0;i < iterations; ++i) {
// zn = zn-1 ^ 2 + c
// (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
z += c;
if (dot(z,z) > 4.0) { // dot(z,z) == length(z) ^ 2 only faster to compute
break;
}
it += 1.0;
}
}
vec3 color = vec3(0.0,0.0,0.0); // initialize color to black
if (it < float(iterations) && !skipPoint) {
color.x = sin(it / 3.0);
color.y = cos(it / 6.0);
color.z = cos(it / 12.0 + 3.14 / 4.0);
}
gl_FragColor = vec4(color,1.0);
}
```
[完整代码](https://www.weheartswift.com/wp-content/uploads/2015/06/MandelbrotTutorial-Challenge-1.zip)
2 . 做一个类似的app,可以让你探索Julia set 的某点c。
例子: vec2 c = vec2(-0.76, 0.15);
![Julia](https://camo.githubusercontent.com/29cf99df9aeb340c50ba8c1d7687489bb56ecefe/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30362f6a756c69612e706e67)
**Solution**
**答案**
void main() {
#define iterations 128
vec2 position = v_tex_coord; // gets the location of the current pixel in the intervals [0..1] [0..1]
position.y = 1.0 - position.y;
vec2 z = offset + position / zoom;
z *= 2.0;
z -= vec2(1.0,1.0);
float aspectRatio = u_sprite_size.x / u_sprite_size.y;
z.x *= aspectRatio;
vec2 c = vec2(-0.76, 0.15);
float it = 0.0; // Keep track of what iteration we reached
for (int i = 0;i < iterations; ++i) {
// zn = zn-1 ^ 2 + c
// (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
z += c;
if (dot(z,z) > 4.0) { // dot(z,z) == length(z) ^ 2 only faster to compute
break;
}
it += 1.0;
}
vec3 color = vec3(0.0,0.0,0.0); // initialize color to black
if (it < float(iterations)) {
color.x = sin(it / 3.0);
color.y = cos(it / 6.0);
color.z = cos(it / 12.0 + 3.14 / 4.0);
}
gl_FragColor = vec4(color,1.0);
}
[完整代码](https://www.weheartswift.com/wp-content/uploads/2015/06/MandelbrotTutorial-Challenge-2.zip)
3 . 添加一个点c的uniform变量,使用户可以用两个手指改变其值。
**提示**
用UIPanGestureRecognizer来检测两个手指的范围。你需要标准化手势识别器传来的结果。
**答案**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
void #define iterations 128 vec2 position.y vec2 z *= z float z.x *= vec2 bool // cardioid checking if skipPoint } // period 2 checking float if skipPoint } float if for // zn = zn-1 ^ 2 + c // (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi z z if break; } it } } vec3 if color.x color.y color.z } gl_FragColor } ``` [完整代码](https://www.weheartswift.com/wp-content/uploads/2015/06/MandelbrotTutorial-Challenge-1.zip) 2 例子: ![Julia](https://camo.githubusercontent.com/29cf99df9aeb340c50ba8c1d7687489bb56ecefe/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30362f6a756c69612e706e67) **Solution** **答案** void #define iterations 128 vec2 position.y vec2 z *= z float z.x *= vec2 float for // zn = zn-1 ^ 2 + c // (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi z z if break; } it } vec3 if color.x color.y color.z } gl_FragColor } [完整代码](https://www.weheartswift.com/wp-content/uploads/2015/06/MandelbrotTutorial-Challenge-2.zip) 3 **提示** 用UIPanGestureRecognizer来检测两个手指的范围。你需要标准化手势识别器传来的结果。 **答案** |
class GameViewController: UIViewController, UIScrollViewDelegate {
…
var c: GLKVector2 = GLKVector2Make(0, 0)
override func viewDidLoad() {
...
let panGr = UIPanGestureRecognizer(target: self, action: "didPan:")
panGr.minimumNumberOfTouches = 2
view.addGestureRecognizer(panGr)
}
func didPan(panGR: UIPanGestureRecognizer) {
var translation = panGR.translationInView(view)
translation.x /= view.frame.size.width
translation.y /= view.frame.size.height
c = GLKVector2Make(Float(translation.x) + c.x, Float(translation.y) + c.y)
let cUniform = node.shader!.uniformNamed("c")!
cUniform.floatVector2Value = c
panGR.setTranslation(CGPointZero, inView: view)
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
override ... let panGr.minimumNumberOfTouches view.addGestureRecognizer(panGr) } func var translation.x translation.y c let cUniform.floatVector2Value panGR.setTranslation(CGPointZero, } } |
<br />[完整代码](https://www.weheartswift.com/wp-content/uploads/2015/06/MandelbrotTutorial-Challenge-3.zip)
4 . 用一个图片来给julia分形涂色。有很多方法都可以实现,其中有一个很有意思的方法如下:
1. 每一次循环都从图片里得到对应z的颜色。如果颜色不是透明的就跳出循环。
2. 如果跑完所有循环,得到的颜色依旧不是透明的,那么就用它来填色对应的像素。
3. 如果是透明的,那么就用另外一个公式来填点的颜色。比如标准化过的循环次数。
下面是一个用兔子照片来填色的julia分形。
![Bunny](https://camo.githubusercontent.com/0be3f31e3f64d34053ad0627224aebcb37f53e7b/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30352f62756e6e792e706e67)
![兔子](https://camo.githubusercontent.com/0be3f31e3f64d34053ad0627224aebcb37f53e7b/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30352f62756e6e792e706e67)
![Fractal Bunny](https://camo.githubusercontent.com/cee740f50001ef98b64243995fb580caf2746bb0/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30352f62756e6e794672616374616c2e706e67)
![分形兔子](https://camo.githubusercontent.com/cee740f50001ef98b64243995fb580caf2746bb0/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30352f62756e6e794672616374616c2e706e67)
**Hint**
**提示**
你需要再添加一个Texture类型的uniform变量,命名为image。你可以用`vec4 color = texture2D(image,p)` 来得到texture在p位置的颜色。
**答案**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<br 4 1. 2. 3. 下面是一个用兔子照片来填色的julia分形。 ![Bunny](https://camo.githubusercontent.com/0be3f31e3f64d34053ad0627224aebcb37f53e7b/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30352f62756e6e792e706e67) ![兔子](https://camo.githubusercontent.com/0be3f31e3f64d34053ad0627224aebcb37f53e7b/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30352f62756e6e792e706e67) ![Fractal ![分形兔子](https://camo.githubusercontent.com/cee740f50001ef98b64243995fb580caf2746bb0/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30352f62756e6e794672616374616c2e706e67) **Hint** **提示** 你需要再添加一个Texture类型的uniform变量,命名为image。你可以用`vec4 **答案** |
class GameViewController: UIViewController, UIScrollViewDelegate {
...
override func viewDidLoad() {
...
let imageUniform = node.shader!.uniformNamed("image")!
imageUniform.textureValue = SKTexture(imageNamed: "bunny")
}
...
}
1 2 3 4 5 6 7 8 9 10 11 12 |
class ... override ... let imageUniform.textureValue } ... } |
<br />
1 |
<br |
vec4 getColor(vec2 p) {
if (p.x > 0.99 || p.y > 0.99 || p.x < 0.01 || p.y < 0.01) {
return vec4(0.0);
}
return texture2D(image,p);
}
void main() {
#define iterations 128
vec2 position = v_tex_coord; // gets the location of the current pixel in the intervals [0..1] [0..1]
position.y = 1.0 - position.y;
vec2 z = offset + position / zoom;
z *= 2.0;
z -= vec2(1.0,1.0);
float aspectRatio = u_sprite_size.x / u_sprite_size.y;
z.x *= aspectRatio;
vec2 c = vec2(-0.76, 0.15);
vec4 color = vec4(0.0); // initialize color to black
float it = 0.0; // Keep track of what iteration we reached
for (int i = 0;i < iterations; ++i) {
// zn = zn-1 ^ 2 + c
// (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
z += c;
color = getColor(z);
if (dot(z,z) > 4.0 || color.w > 0.1) { // dot(z,z) == length(z) ^ 2 only faster to compute
break;
}
it += 1.0;
}
if (color.w < 0.1) {
float s = it / 80.0;
color = vec4(s,s,s,1.0);
}
gl_FragColor = color;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
vec4 if return } return } void #define iterations 128 vec2 position.y vec2 z *= z float z.x *= vec2 vec4 float for // zn = zn-1 ^ 2 + c // (x + yi) ^ 2 = x ^ 2 - y ^ 2 + 2xyi z z color if break; } it } if float color } gl_FragColor } |
<br />[完整代码](https://www.weheartswift.com/wp-content/uploads/2015/06/MandelbrotTutorial-Challenge-4.zip)
5 .类比分形,实验一下Mandelbrot的公式。**这个是开放性的挑战。**以下提供了两个例子
**燃烧之船的分形**
![Burning Ship](https://camo.githubusercontent.com/f91ac892791b93dfc03df9b4237fe3ed56d280f5/68747470733a2f2f7777772e7765686561727473776966742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30352f6275726e696e67536869702e706e67)
**Formula** `zn = abs(zn-12 + c)`
**公式** `zn = abs(zn-12 + c)`
**GLSL**
**GLSL**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<br 5 **燃烧之船的分形** ![Burning **Formula** **公式** **GLSL** **GLSL** |
z = vec2(z.x * z.x – z.y * z.y, 2.0 * z.x * z.y);
z += c;
z = abs(z);
`
Sierpinski Julia
公式 zn = zn-12 + 0.5 * c / (zn-12)
GLSLS
vec2 powc(vec2 z,float p) {
vec2 polar = vec2(length(z),atan(z.y,z.x));
polar.x = pow(polar.x,p);
polar.y *= p;
return vec2(polar.x * cos(polar.y),polar.x * sin(polar.y));
}
void main() {
...
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
z += 0.5 * c * powc(z,-2.0);
...
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
vec2 vec2 polar.x polar.y *= return } void ... z z ... } |