如何做一个iOS分形App

如何做一个iOS分形App

介绍

在这个教程中,我们会做一个可以渲染Mandelbrot Set的应用程序,我们可以缩放和平铺它来看分形那令人惊叹的复杂之美。最终的结果如下:

着色程序的代码

你可以下载起始版本跟着教程一起做,也可以在本文结尾找到最终版本的代码。

项目设置

Gamescene.sks文件里包含一个名为 fractal 的子画面,它填充了整个界面并且着色程序程序 Fractal.fsh也附在它上。

Fractal.fsh包含了上面着色程序的代码

GameViewController.swift包含了设置游戏场景的代码

GameScene.swift 为空

如果你现在运行代码,你将会得到如下的结果:

Starter
起始

请注意纵横比固定为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。

接下来我们去掉代理方法,并且实现 viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? 这个方法

向着色程序发送数据

着色程序可以从你的swift代码里的uniform变量里获得数据。uniform变量可以在SpriteKit编辑器里声明。那现在我们来声明一下uniform变量。

打开 GameScene.sks 文件,选择 mandelbrote sprite。将insepctor拖到底部,在“Custom shader Uniforms”里添加两项:float 类型的 zoom ,值为1, 以及 vec2类型的offset。我们将用这两项uniform变量储存scrollView的 contentOffset 以及 zoom 属性。

Uniforms

警告:Xcode 6.3的uniform变量有bug。它不能直接在编辑器里赋值初始化,你必须在代码里初始化它们。

我们可以通过shader属性来获取节点上(node)着色程序,用 theuniformedName()方法来从着色程序得到uniform变量。以下是我们获取zoom uniform变量的例子:

Once we have a uniform we can change its value via one of of the properties

当我们有了uniform变量后,我们可以通过它的属性来改变它的值。

We’re only interested in using floatValue and floatVector2Value for this tutorial.

在本教程里,我们只对 floatValuefloatVector2Value感兴趣。

Ex: to set the zoom to 2 we use

例子:将zoom的值设置成2

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 / 480。映射x'[0,1][-2,2],我们用 x'' = x' * 4 - 2

如果我们屏幕上有一点x,坐标值为120,那么对应到区间[0,1] 将成为 120 / 480 = 0.25,以及在区间 [-2,2],如下所见它将成为 0.25 * 4 - 2 = -1

Intervals

Mapping between the scrollview and the complex plane

scrollView及复平面互相映射

我们需要讲scrollView上的点转换到复平面。第一步,先将scrollView上的点转换到区间[0,1]。通过将contentOffset 除以contentSize可以将 contentOffset转换到区间[0,1]

我们着色程序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里点的位置。

如下你可以看见蓝色的scrollView的contentView在标准化与未标准化过的边框。contentSize = (960,640)contentOffset = (240,160)zoom = 2.0

ScrollView
ScrollView

标准化过的ScrollView

Normalized ScrollView

最后我们将点映射到复平面。为了在mandelbrot里得到好看的效果,我们将希望映射区域[-1.5,0.5] x [-1,1]复平面。

我们还想使纵横比正确。现在我们的x、y轴的比例一样,我们要乘以x和纵横比使得图片不会变形。

纵横比是什么

纵横比是屏幕宽度和高度的比例。

下面你可以看到我们scrollView的contentView映射到的平复面以及纠正过纵横比的结果。

Complex Plane

为了整合上面所有代码,我们建了一个新的方法叫updateShader,它可以传一个contentView坐标到着色程序。我们所需要做的就是在scrollView的代理方法里调用updateShader方法。

同时也别忘了当view出现时调用updateShader方法,这样你才可以初始化uniform变量。

最终着色程序的如下所示:

Complete Source Code

完整代码

挑战

1 . 优化

黑色部分渲染的最慢。幸好根据下图,我们可以很快知道一个点是否在两块黑色部分之一里 (心形部分或者区域2)。这里你可以找到如何判断点是否在两块黑色区域之一里的方法。加上这些代码来改进着色程序,它们只会在点不在这两个区域里执行mandelbrot循环。这将大幅度提高app在这些区域可见时的表现。

见下图,主要的心形为红色,区域2为绿色。

Bulb region 2

Hint

提示

只当点在这些区域中的一个以外的时候执行mandelbrot循环。

Solution

答案

class GameViewController: UIViewController, UIScrollViewDelegate {

var c: GLKVector2 = GLKVector2Make(0, 0)

z = vec2(z.x * z.x – z.y * z.y, 2.0 * z.x * z.y);
z += c;

z = abs(z);
`

源代码

Sierpinski Julia

Sierpinski Julia

公式 zn = zn-12 + 0.5 * c / (zn-12)

GLSLS

源代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值