Tutorial 2: The Hybrid System ScriptThis tutorial will teach you how to use the hybrid system fragment shader to produce an endless variety of fractals. Using basic transforms like scale, rotate, translate, origami folds like box, abs, reflection and deforming folds including sphere, cylinder and bulb along with basic CSG (constructive solid geometry) shapes you can create many types of fractals.Download Tutorial2 Files |
PrerequisitesYou should have AnimandelPro installed and the tutorial unzipped plus a basic understanding of the user interface.Tutorial 2: The Hybrid System ScriptStep 1: From File->Open choose the file "tutorial2/hybridsystem.frag". It is important to use the file from this tutorial's folder as it may be different from that in the app folder. This opens the fragment shader script for editing. Locate the function called DE. The DE (distance estimate) function is where the fragment/pixel shader "finds" your fractal for each pixel on the screen. It should look like this:float DE(in vec3 z0){ vec4 z = vec4(z0,1.0),p0=z; float r=length(z0.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); for (int n = 0; n < 7 && r < 10.0; n++) { BulbFold(z,r,4.0); z+=p0; r=length(z.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); } if(bColoring){ diffuse=vec3(0.7,0.3,0.1)+z0*0.125; ambient=diffuse*0.25; } return BulbShape(z,r); }Run the script by pressing F5. You should see a typical order 4 mandelbulb. Step 2: Changing colors. This DE function also contains the code for coloring the fractal in the section if(bColoring){. Find the line... diffuse=vec3(0.7,0.3,0.1)+z0*0.125;and change it to... diffuse=vec3(0.1,0.3,0.7)+z0*0.25;and press F5. We now have a blue bulb. Colors are vec3 variables with the format (red,green,blue). diffuse is the color that gets highlighted by light. ambient is a color added even when no light is present. You aren't limited to just changing the overall color. The variable z0 (the initial point being tested) was used to add some variability. Lets add more variability like this: for (int n = 0; n < 7 && r < 10.0; n++) { BulbFold(z,r,4.0); z+=p0; r=length(z.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); if(bColoring && n==1)z0=z.xyz; //THIS IS THE NEW LINE!!! }Press F5 and notice the new cuttlefish look. We captured the x,y,z values after 2 iterations. With each iteration the colors become more intense and are grouped closer together. Try this in the bottom bColoring section: if(z0.x<0.0)diffuse=vec3(1.0,1.0,1.0);else diffuse=vec3(0.0,0.0,0.0);Play around until you get a coloring scheme you like. Step 3: Using return Shapes. We end the DE function by returning BulbShape(z,r); This is the standard function when working with mandelbulbs but we aren't forced to use it. We can use any shape! Try lowering the iterations to 2. for (int n = 0; n < 2 && r < 10.0; n++) {and returning a box with a half width of 1... return BoxShape(z,1.0);We still get the basic mandelbulb shape. What happened to the boxes? The variable z is transformed in just two iterations into the bulb shape. We then take a box shape from this new transformed space as our return value. Lets see the individual boxes by cutting a hole in each one. Use the CSG function Difference to remove a sphere from the middle of each box. return Difference(BoxShape(z,1.0), SphereShape(z,1.25));Run the script. If you have followed along up to now you should see the image at the top of this page. Step 4: Compilation errors. Eventually if you haven't already, you will make a mistake in coding and receive a nice cryptic error message like: 0(145):error C1008: undefined variable "oxShape" AnimandelPro isn't the most user friendly app is it? But there is enough here for you to understand line 145 has a error around the characters "oxShape". You can then scroll with the mouse wheel to line 140 and count down or search for the term "oxShape" to find the error. Step 5: Wait what exactly are hybrids? For the purpose of this tutorial Hybrids are fractals produced from more than one formula. There are obvously many ways to do this,
Alternating Hybrids: When we use the Amazing Box fractal we are actually using an alternating type of fractal. We apply 3 transforms. BoxFold(z); //this is an origami type fold that folds space along straight lines SphereFold(z,4.0); //this is a deforming fold that bends space along curves Scale(z,-2.5); //this expands spaceLets add a Box fold to our bulb and see what we get... for (int n = 0; n < 2 && r < 10.0; n++) { BulbFold(z,r,4.0); BoxFold(z); //this is an origami type fold that folds space along straight lines z+=p0; r=length(z.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); if(bColoring && n==1)z0=z.xyz; }Run it and you see things get complicated very quickly. The bulb is folded over itself numerous times in just 2 iterations. Serial Hydrids: These are my favorites as they let you see how space has been transformed without getting too messy. Change the DE function to this... float DE(in vec3 z0){ vec4 z = vec4(z0,1.0),p0=z; float r=length(z0.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); for (int n = 0; n < 4 && r<10.0; n++) { if(n<2)Octo(z,2.0,vec3(1.0,0.0,0.0)); //THE OCTO IS RUN TWICE else Menger(z,3.0,vec3(1.0)); //THEN THE MENGER r=length(z.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); } if(bColoring){ diffuse=vec3(0.1,0.3,0.7)+z0*0.5; //CHANGED THE COLORING BACK ambient=diffuse*0.25; } return Difference(BoxShape(z,1.25), SphereShape(z,1.5)); //CHANGED THE RADIUS }Run this then reverse the order of the Octo and Menger functions. Order is everything in these first types of hybrids. Conditional Hybrids: We aren't limited to switching formulas between iterations. We can switch at any time, for any reason (WARNING: this can create discontinuities which leave artifacts). Lets try changing the line... if(n<2)Menger(z,3.0,vec3(1.0));to... if(z0.x<0.0)Menger(z,3.0,vec3(1.0));then try... if(z.x<0.0)Menger(z,3.0,vec3(1.0)); //NOTICE WE ARE USING z INSTEAD OF z0!Remember z0 is the initial untransformed space and z is the space we are transforming so it is very difficult to track where z is moving. That's what makes fractals so interesting. Mixed Hybrids: We can also blend the results of functions at any time. We can blend inside the iteration loop... for (int n = 0; n < 2 && r<10.0; n++) { vec4 z2=z; // MADE A COPY OF z BulbFold(z,r,8.0); // AN ORDER 8 BULB BulbFold2(z2,r,4.0); // AND AN ORDER 4 BULB FROM THE COPY OF z z=Blend(z,z2,0.5)+p0; // BLEND THE RESULTS r=length(z.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); } if(bColoring){ diffuse=vec3(0.1,0.3,0.7)+z.xyz*0.25; ambient=diffuse*0.25; } return BulbShape(z,r);or create seperate DE functions and blend the return values... float DE2(in vec3 z0){//this is a sample second estimate for blends etc vec4 z=vec4(z0,1.0),p0=z; float r=length(z0.xyz); for(int n=0;n<4 && r < 10.0;n++){BulbFold2(z,r,4.0);z+=p0;r=length(z.xyz);} return BulbShape(z,r); } float DE(in vec3 z0){ vec4 z = vec4(z0,1.0),p0=z; float r=length(z0.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); for (int n = 0; n < 4 && r<10.0; n++) { BulbFold(z,r,8.0); z+=p0; r=length(z.xyz); minDist = min(minDist,dot(z.xyz,z.xyz)); } if(bColoring){ diffuse=vec3(0.1,0.3,0.7)+z0*0.25; ambient=diffuse*0.25; } return Blend(BulbShape(z,r),DE2(z0),0.5); }Notice the result is quite different. We aren't limited to blending either. Try Union, Intersect, Difference and BlobbyUnion. Also the blend value can be variable... return Blend(BulbShape(z,r),DE2(z0),z0.x); Step 6: Play. Learn what the tranforms/folds/shapes do by playing around with them. Here is a typical use of each. Transforms: Scale(z,2.0); // Resizes (any value) Rotate2D(z.xy,0.785); // Rotates by radians in two dimensions Rotate3D(z,vec3(3.1416,0.0,0.785)); // Rotates in 3d (yaw, pitch, roll) z.xyz+=vec3(1.0,0.0,0.0); // Translates the z position ScaleOffset(z,2.0,vec3(1.0,0.0,0.0)); // Scales and translatesOrigami Folds: BoxFold(z); // Folds space back on itself twice in each dimension MembraneFold(z.xz); // Same as BoxFold only on 2 dimensions instead of three AbsFold(z); // Folds space once in each dimension GenericFold(z,vec3(0.707,0.0,0.707)); // Folds space on a line perpendicular to any normal FunkyReflect(z); // A quick 90 degree rotation and reflection MengerReflect(z); // The menger space folds, MengerFold contains a scale amd translation as well OctoReflect(z); // The octo space folds, OctoFold also contains the scale and translationDeforming Folds: SphereFold(z,4.0); // Inverts space near the origin within the inverse radius >1.0 CylinderFold(z,2.0); // Warps in a cylinder shape CrossFold(z,3.0); // Cylinders in all dimensions (look like crosses) SquareFold(z,8.0); // Warps exponentially SingleFold(z,6.0); // Same but just one dimension KaliFold(z,4.0); // Inverts space with no limits (can reduce the scale which leads to bad DE) BulbFold(z,r); // Standard bulb (same front and back) BulbFold2(z,r); // Original bulb, z=cos() PolyBulbFold(z,r,1.0,8.0,-1.0,4.0); // Polynomial bulb with 2 coefficients and powersShapes: BulbShape(z,r); // For use with BulbFolds, r is length(z.xyz) SphereShape(z,4.0); // To fill the gaps between shapes make sure they are as large as the scale BoxShape(z,1.0); // ...for instance Scale(z,2.0); CylinderShape(z,0.25,1.0); // ...should return Sphere(z,2.0); //use this as a starting point and adjust TorusShape(z,1.0,0.25); // Donut shape. The first radius is the large diameter. PrismShape(z,0.25,1.0); // Extruded triangle, width and length OctoShape(z,1.0); // 8 sided poly PKBasicShape(z,0.25); // Funky horn shape.CSG Functions: Union(d1,d2); // Show both shapes Intersection(d1,d2); // Show the intersection of two shapes (the part they have in common) Difference(d1,d2); // Scoop the second shape out of the first Blend(d1,d2,mx); // Return some inbetween value (linear interpolation) BlobbyUnion(d1,d2,mx); // Show both shapes with a melted look where they touch BlobbyIntersection(d1,d2,mx); // Same but only the common partsDownload Tutorial2 Files |