Fractals have been something that I've found really intriguing since high school, though, I've never taken too much of a stab at trying to generate them. For a few assignments back in my CS1 class we had to use Python and the Turtle package to draw some. These were done mainly to teach the concepts of recursion, state, and using geometry in computer programs; pretty basic stuff. I imagine that almost everyone has had to use Turtle at some point in their CS coursework, and incidentally, has drawn a fractal.
Last year I was taking a course in Global Illumination. We had to do two projects over the semester. Write a ray tracer (throughout the weeks) and explore a graphics related topic of your choosing. For that second one, some students chose to look into ray marching or real time raytracing. I wanted to look at fractals some more.
After talking with one of my friends, he told me to look up something called "L-Systems." The full name is "Lindenmayer System," and it's the magic behind fractal generation. In a nutshell, the idea behind it is to create a grammar (yeah, one of the CS theory ones) and give each letter (or variable) an action (e.g. "draw line forward 10 units," or "rotate 45 degrees clockwise,"). The simplest example I can think of is the Cantor Set on the L-System wiki page.
Even though I was already making a 3D graphics program for the class, I decided writing a Blender script would be the best. Here are some of my reasons why:
- All the tools, libraries, and rendering infrastructure is already there. I only need to focus on the tree logic
- My Ray Tracer has a .obj file loader, so all I need to do is export it from Blender as a Wavefront object
- On top of it too, I was also able to export it as a .stl, load it into slic3r, and materialise it on my 3D printer. Pretty cool!
- Blender is widely used and some others might find my script useful
So working in Blender there isn't a 3D Turtle object you can rely on to do the drawing. But instead, you have to work with Matrices. The basic idea of what you need to do is this:
- Chain a bunch of matrices (appending and popping to/from the end of a list works well). Most of what you'll need will come from
Matrix.Rotation(). These are equivalent of the Turtle movement commands
- Make sure you chose one axis as your "forward," direction. For my script, I always made translations in the Z+ direction.
- Compute the result of the matrix chain, by taking the leftmost (first) matrix, then multiply it by it's neighbor to the right. Rinse and repeat until you have one matrix left
- Create a new 3D mesh (e.g. via
- Blender will consider the newly created mesh as the "currently active object." So multiply that new mesh's world matrix by the computed matrix chain:
bpy.context.active_object.matrix_world *= computed_matrix_chain
- And voila! It should be in the location and orientation you want it. From here you can go to a deeper level of the fractal or move back up. Don't forgot to pop those matrices off the end of your chain!
If you're still a little bit confused, take a look at the git repo over here. All of the necessary code is in
l-system_tree.py. It's pretty simple and you should be able to follow along. My L-System grammar is quite uncomplicated:
- A → BCDE
- B → A
- C → A
- D → A
- E → A
"A," also means "draw a branch," whereas "B," "C," "D," and "E," mean rotate about an axis (each one is different). Each variable also checks the current depth to make sure that it isn't going on forever, though only "A," really needs to check for that.
There are a few other configuration options that I threw in (e.g.
TWIST) so I could make some different looking trees. Below is an example of one of the trees that I made with
VARIATION_MODE turned on; it gives the tree a much more natural looking feel.