Site hosted by Angelfire.com: Build your free website today!

Tutorial 2: The Hybrid System Script

This 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

Prerequisites

You should have AnimandelPro installed and the tutorial unzipped plus a basic understanding of the user interface.

Tutorial 2: The Hybrid System Script

Step 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 (box,bulb,box,bulb)
  • in serial (some iterations of box then some of bulb)
  • conditionally (if x>0 box else bulb)
  • mixing the results (blend the return values of box and bulb in some way).
We will cover hybrids produced by all these methods.
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 space
Lets 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 translates
Origami 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 translation
Deforming 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 powers
Shapes:
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 parts
Download Tutorial2 Files