I keep trying to make fractals in 3D, using different methods, but each method I had come up with had been dissatisfying, slow, unrefined, or difficult to work with… at least until now. Introducing, Metal IFS.
It’s not technically “fractal”, but it is iterated functional series. However, authentic-looking IFS is hard to do without using textures, which, by the way, I plan on incorporating into this.
Here’s an outline of the project:
- Mesh-Creation Process
- IFS Textures
Before you begin further reading, I’d advise you to become aware of how fractal flames are usually created, using the flam3 algorithm by Scott Draves.
Constructing a fractal using meshes means there will be some limitations on the final outcome. First, I can’t create an infinite number of meshes, as real fractals would require. Second, the details for meshes can only be so refined. Mesh drawing is limited to floating point variable accuracy (called “float”) since it is fast when drawing meshes and information can be lost without the rendering engine worrying over it. That’s good for video games, but it’s bad to rendering detailed fractals.
In a nutshell:
- The transforms/variations are held in a table.
- The order that those variations in the table are processed is held in a node tree.
The variations are held, with some other data, in an array that acts as a table to be accessed by a special class called the “mesh unit”. The mesh unit does all of the dirty work – it handles the processing of the node tree, the construction of meshes and mesh buffers, and the application of materials, and contains pointers to classes for construction basic geometric shapes or manipulating the mesh vertices. The 3D engine I’m using already has built-in classes for meshes, mesh handling, and matrices, and the mesh unit helps ease the use of those classes.
The node tree contains two things: an integer and a mesh. The integer is an index to a transform/variation in the table. (Edit, Jan 15, 2015: I no longer use a table/list of transforms. Instead, the transforms are in the tree.) Once applied, the output mesh is stored in the tree, and a node is created if the transform/variation is set to visible. Each child of a node processes the output mesh of the current transform. That way, they build on each other.
You might note that there are some similarities between this method and the flam3 algorithm, particularly that it plays a game of “who comes next in line”. There are two key differences. One is that, rather than limit the iterations through the table, I limit the number of iterations by simply using a tree. It isn’t as easy for an uneducated user to crash the program by simply setting a number to a large value. The second key difference is that each node in the tree can have several of its own unique attributes, such as a special material, that do not need to be applied every time a specific transform/variation is used. I have considered also allowing for these nodes to set parameters of the transforms, which would provide even more control, although it would add more work for someone who doesn’t know what they are doing.
On that topic…
Despite being a programmer, I can become rather weary when working with complex code. By “complex”, I mean I have to use multiple functions to do one simple repetitive thing. But when you’re prototyping, you don’t always think of ways to make those methods more convenient to do. This time, I have. I’ve designed everything to make it easy to add and modify variations, whether I’m creating them or just adding them to the tree. It shouldn’t take much effort to modify existing transform/variation code from flam3-algorithm-based programs to add to mine. Plus, I have total control, simple syntax, and other people can contribute while knowing a bare minimum about the actual 3D engine being used.
Speaking of the 3D engine I’m using…
How are textures going to make things more fractalesque? Irrlicht (the 3D engine I’m using) is quite powerful and easy to modify. (Notably, if I decide to avoid learning OpenGL, I can still modify the engine to do some fancy work with drawing fractals in the meshes as transforms.) Even without modifying the engine, I can draw everything on-screen to a back-buffer that can then be used as a texture, allowing me to nest the entire mesh in itself as a material. This allows for the iterative appearance of the mesh without adding more vertices.
I’m much happier with this approach than any of my other approaches. Like other people considered, I’ve tried Blender, and while that came in as second-best for having the most potential (because of what it could do), it is a memory hog, it ran really slow when I created a large number of objects, and I would have to learn the tiny ins and outs of Blender in order to do the cool things I wanted to do, much less try to apply a simple material to a mesh.
My program is written in C++, as usual, but it is cross-platform code, and I plan on porting it to Linux if I’m still working on it in the future. If you are interested in contributing to it, you are more than welcome to contact me – it’s as easy as replying to this post.
The main part of the program is done. Now I need more variations and a GUI interface, both of which should be easy.
The ironic thing is that doing things this way has caused me to start to rethink how I construct fractal flames. When constructing fractal flames, many times it is a question of how many ways you can invert space and nest it in itself, which often means building out to build inward. But with what I’m doing, much of it is a building outward, especially since I would like to have details all the way around my IFS creations, not merely from one angle. I’ll try to keep my blog updated with more details and screenshots as I continue development. Until then, thanks for reading!
Update for September
Late posting this update, but here’s what I had for September as of the 9th:
~ Update Jan 12, 2015 ~
Most of the GUI is done (with the exception of a save dialog). File loading and saving needs to be tested, and then I’ll be free to add some useful transforms. Here’s a recent test-render:
~ Update Jan 15, 2015 ~
The program is officially “completed” as far as its core is concerned. There are, interesting enough, a few differences from the original concept I had in mind, but it is mostly the same. At this point, I now have a long list of features I want to implement before I do any serious work with it. One such feature I would like is material inheritance, allowing a transform to inherit its parent’s material (if there is a parent), which would save me a great deal of time in copying material properties (and the only downside being that I couldn’t edit the individual material attributes unless the copy was made instantly). Other features include more transforms and a space-point selector, which would allow me to pick a point in 3D space very easily. Such a selector would be useful for certain transforms, such as point-attractors (points in space that draw nearby vertices closer to the points themselves).
Features I dream of including are a Catmull-Clark mesh smoothing procedure and a mesh ray tracer. Fortunately, other programs can do that job, and I have a mesh exporter and converters that will allow me to load my output meshes in programs such as Blender and Povray.
Below is a screenshot of the first official render of the completed program. It comes from my saved project “testproject22.txt” after I finally fixed the file loading and saving features. Which reminds me – I haven’t given my project files their own official extension. Not that they need one – the project file is nothing more than a JSON file with a particular set of nodes. The project file contents are actually easy to modify (nothing cryptic in the JSON), and with a little know-how, you can modify the project files by hand.
Large list of current features:
- Simple menu bar with large buttons.
- Project open and saving options are available.
- Build project menu option creates a mesh viewable in a (almost) full-screen, easily accessible part of the program.
- Export menu option exports the project as mesh in an engine-supported format that the user specifies, such as Wavefront OBJ (.obj), Collada (.dae), and STL (.stl).
- File-name edit box in open/save/export dialog doubles as a search bar, giving filtered results.
- Large workspace for editing the transform tree.
- Transforms are represented by tree nodes in the workspace and are connected as the workspace tree is connected.
- Ability to create nodes for the tree without needing to use them.
- Ability to set the root tree node to any existing node.
- Keyboard shortcuts for moving, adding, and removing nodes.
- Convenient menu for editing the tree.
- Non-forced production method (no snapping tree nodes).
- Transform controls in a tabbing system for intuitive editing.
- Transforms possess pre-relative, post-relative, pre-absolute, and post-absolute spacial matrix transformations that are applied to the mesh vertices.
- Transform spacial matrices are made easy to edit via editing their translation, rotation, and scaling properties directly (rather than forcing the user to calculate them).
- Transform controls contains a flexible parameters tab that can incorporate a variety of parameter type editing GUI elements (as opposed to forcing the user to use floating point number edit boxes for all parameter values).
- Transform controls contain a tab for the mesh material after it is modified by the transform.
- Mesh materials that can be edited are: ambient, diffuse, emissive, and specular color; setting the material to respond to lighting; using Gouraud shading; be shown as a wireframe; force the engine to use mipmaps for textures (feature not added yet).
- Mesh material colors can be edited with either edit-boxes restricted to HTML color format or via color-selection windows.
- And finally and very importantly: None of the values that are input anywhere require you to hit “Enter” or click a confirm button to save the value, which would otherwise be a serious inhibitor on efficient productivity.
- Basic shape creation transform, which allows the user to select from cube, sphere, cone, cylinder, sphere, plane, or even load a mesh. The transform can also duplicate that mesh in either the shape of a cross or a square.
- Rod mesh creation transform, which creates a set of points without creating a mesh itself. Transforms can then modify these points before a final mesh is created. All transforms have the option of converting these points to a final mesh before sending them to the transforms in their child nodes. The rod mesh transform can position these points to start from the origin and be stacked up or to have them be balanced around the center (such that, if converted to a final mesh) a rod is created whose center is at the origin).
- Circlify transform, which repeats the mesh in a circle around the existing mesh.
- Allowing the user to set textures, including for bump-maps (if and when that is implemented).
- Material inheritance.
- Showing a 3D sphere in the material panel that shows what the material looks like.
- Reset-camera key that sets the final mesh scene camera back to its original starting point.
- Space point positioning dialog, allowing the user to easily pick a point in space that can be used for point attractors and other transform parameters.
- Mini color-samples next to the color edit-boxes on the material panel to show the current color value (rather than forcing the user to open the color-select dialog to see what it is).
Transforms to be implemented:
- Multiple iterations transform, to allow creating multiple mesh buffers side-by-side at once in one mesh, rather than forcing the user to make a large transform tree and scooting mesh copies around with the post-absolute space matrix translation values. It would also be nice to have matrix modifying per iteration / mesh copy, such as scaling down each copy of the mesh.
- Point attractor transforms, allowing the user to set points (or perhaps areas or circles, etcetera) in space that attract vertices.
I will probably think of more ideas as I go along, but what I already have on my to-do list should keep me very busy for quite some time.
~ Update May 18, 2015 ~
I have never worked so long on a single project. That said, I have made a significant amount of progress on it. But that’s hard to show when most of the work is still “under the hood” so to speak.
Some of the new features (since last time):
- Conveniently located workspace buttons for speedy editing
- Multiple new transforms for creating recognizable fractal shapes, such as Menger Cube and Julia
- 3D shape in the material panel for displaying the applied material
- Material inheritance
- Material alpha fix (may be replaced – see below)
- Torus mesh transform (similar to rod mesh) for perfect torus creation
- A ton of bug fixes
- amongst lots of other things…
I’m getting to the point where the rendering is slowing down… alot. Think 7fps. Yes, I’m still way ahead of everyone making fractals, but I have less quality and no anti-aliasing. Oh, and I’d love to do raytracing, which, as I’m reading, is apparently is not going to be as difficult as I thought. The engine I’m using is equipped with the tools for it, so that’s nice.