tl;dr You can edit shaders in Xcode 6 with live preview. This can be used to render Fractals. It should take you 10-20 minutes to go through all the steps in this tutorial. Full Code
Xcode 6 added a powerfull new editor for SpriteKit scenes that let’s you visually design scenes for your games. You can expect tutorials on programming games with SpriteKit and Swift from us soon.
One really cool hack that you can do with the new editor is making an interactive fractal explorer.
Here are the steps you have to follow:
Create a new project
Open Xcode 6, select the “Other” tab and create a new “Empty” project. We won’t be building or running our project in this tutorial.
Add required files
Click on your project. Select add new file, select “Resource” -> “SpriteKit Scene” and name it “Fractal”.
Add a new file from “Other” -> “Empty” and name it “Fractal.fsh”. This will be the shader that we’ll use to render the Mandelbrot set.
SpriteKit can apply arbitrary shaders to SKSpriteNodes in iOS 8. If you’re not familiar with fragment/pixel shaders then I recommend you check out this great interactive resource to learn the basics.
Basically a fragment shader runs some code for each pixel in the resulting image. Fragment shaders have access to data through uniforms and varyings.
Varyings have different values depending on the pixel being processed, for example: the position of the pixel
. Uniforms have the same value for each pixel, for example: the size of the resulting image
.
You can specify custom shaders in SpriteKit by adding your own uniforms and varyings. Uniforms can even be added from the scene editor.
Here are some of the uniforms and varyings that SpriteKit provides to your shaders by default:
u_sprite_size : vec2
the size of the resulting image in pixelsu_texture : sampler2D
the texture of the sprite before any processingu_time : float
: the amount of time that has elapsed since the shader started runningv_tex_coord : vec2
: the position of the current pixel
Setup files
Open the “Fractal.fsh” file and add the following code:
void main() {
gl_FragColor = vec4(0.0,1.0,0.0,1.0);
}
This just sets every pixel to green.
Open the “Fractal.sks” file and drag a new “Color Sprite” from the components panel on the right.
Select the newly added Sprite and set it’s width to 600 and height to 400. Change the value of the “Custom Shader” textbox to “Fractal.fsh”
Your box should now be green.
Setting up your environment
Click the “assistant editor” button in the top right of your Xcode window. This will display two files side by side. You should have the “Fractal.fsh” file open on the left and the “Fractal.sks” file open on the right. To select the file on the right click on the “Manual” button on the top bar of the right “view”.
Hide your “Project Navigator” and “Inspector” by clicking the corresponding buttons on the top right. Your environment should look like this now:
Whenever you save the shader your scene will be automatically updated. Your workflow is very similar to online tools likeglsl.heroku and Shadertoy with the added bonus of great autocomplete!
Rendering the Mandelbrot set
The Mandelbrot set is defined as the set of complex numbers for which the sequence zn = zn-12 + c remains bounded as n goes to infinity. c = z0
To get a better understanding of the Mandelbrot set you can check out this great resource.
We will treat the coordinates of each pixel as a complex number and color it black if it belongs to the set and red otherwise. A critical observation is that the above sequence diverges if the length of z gets greater than 2. We can approximate the behaviour of the sequence by computing it’s nth term i.e iterating the sequence n times or computing the nth iteration.
Here’s the code that you have to add to “Fractal.fsh” to get a basic Mandelbrot set.
void main() {
#define iterations 256
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 = 1.0;
}
gl_FragColor = vec4(color,1.0);
}
The above code will produce this classic image:
Notice that you can zoom and pan the set in the right window and it will quickly redraw to show you the new view.
Adding color
We can improve the coloring algorithm by considering the escape time of the elements in the sequence. i.e. How many iteration it takes for their length to become greater than 2. Here’s the code with an improved periodic coloring algorithm:
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);
}
You can see the resulting image below and we can of course zoom it in Xcode, just not very far, let’s improve this!
Zooming further into the set
We can use a quick hack to see more of the set. Zoom the view out in the SpriteKit Editor until your sprite is very small. Grab the bottom right corner of the sprite while holding shift
and scale the sprite up. I had to do this step 2 times until I reached the minimum zoom level.
Panning and zooming around gets us to really interesting areas now.
Here’s a quick animation made in Xcode, showing the self-similarity of the set.
Rendering the Julia set
All we need to do to render a Julia set is setting c
to a fixed value for all points instead of setting it to z changing vec2 c = z;
tovec2 c = vec2(-0.7,0.4);
draws the following fractal:
We also implicitly have access to the current time via the uniform u_time
. We can change c to vec2 c = vec2(-0.7 + cos(u_time) / 3.0,0.4 + sin(u_time) / 3.0);
to get the following cool animation:
Hope you enjoyed the tutorial although it’s not strictly Swift related and as always here are some challenges
Challenges:
- experiment with the number of iterations
- experiment with the coloring algorithm. See what cool coloring schemes you can come up with
- experiment with different values of
c
for the Julia set - draw the Mandelbrot set using zn instead of z2. Make an animation that smoothly transitions the used power (Hint: Convert z toPolar Coordinates)
- Try to reproduce the Julia sets seen here. You will need to implement some extra functions for complex numbers to do this. Here’s a formula to get you started.
- implement Orbit Traps for coloring
- draw Newton’s Fractal
- draw a Plasma Effect
- write a simple RayTracer / RayMarcher. Here’s a tutorial to get you started
- Make a Swift iPad app that let’s you zoom/pan the Mandelbrot set (Hints: use SpriteKit, use UIPanGestureRecognizer / UIPinchGestureRecognizer, pass the current zoomlevel / offset as uniforms to the shader)
- Draw the Mandelbulb
You can discuss this on Hacker News https://news.ycombinator.com/item?id=7874739
You’re the man! Was sure that only OpenGL had the ability to use shaders, now I know better. Thanks!
Xcode/SpriteKit does use OpenGL for shaders tough. It just creates a quad for your sprite and runs a shader on it.
Have a look here for more details.
Hey I am so excited I found your blog page, I really
found you by accident, while I was researching on Bing for something else, Regardless I am
here now and would just like to say cheers for a remarkable post and a all round
enjoyable blog (I also love the theme/design), I don’t have time to
browse it all at the minute but I have saved it and also included your RSS feeds,
so when I have time I will be back to read a great deal more,
Please do keep up the great work.
Great tutorial. Just a note:
u_sprite_size is vec2 not a float :]
https://www.shadertoy.com/view/MtfXzN