Menger sponge

Jest przykładem fraktala IFS (Iterated Function System) opracowanego przez austriackiego matematyka Karla Mengera w 1927 roku. Więcej na Wikipedi.

W przypadku opracowania Menger’a przy pomocy shaderów posługujemy się następującym algorytmem:

  1. Generujemy sześcian
  2. Generujemy trójwymiarowy krzyż o o 6 ramionach który w kolejnych iteracjach jest przesuwany i zmieszany
  3. Odejmujemy od utworzonego sześciana
  4. Wykonujemy kolejną iterację

Poniżej kod shadera (sama procedura mapowania):

const float START_SCALE=3.0;
const float START_TIME_ADD=0.0;
const float START_TIME_SPEED=0.0;
 
const float ITER_SCALE=3.0;
const float ITER_TIME_ADD=0.0;
const float ITER_TIME_SPEED=1.0;
 
const float START_POS=3.0;
const float START_POS_TIME_ADD=0.0;
const float START_POS_TIME_SPEED=1.0;
 
const float START_DIV=3.0;
const float START_DIV_TIME_ADD=0.0;
const float START_DIV_TIME_SPEED=1.0;
 
const float START_MOD=2.0;
const float START_MOD_TIME_ADD=0.0;
const float START_MOD_TIME_SPEED=1.0;
 
const int ITERATIONS=1;
 
//-------------------------------------
// Map function
// Return 2 dimensional vector, where:
// x - distance
// y - material ID
//-------------------------------------
vec2 map(vec3 p) {
 
float scale=START_SCALE+(START_TIME_ADD*sin(time*START_TIME_SPEED));
float startPos=START_POS+(START_POS_TIME_ADD*sin(time*START_POS_TIME_SPEED));
float startDiv=START_DIV+(START_DIV_TIME_ADD*sin(time*START_DIV_TIME_SPEED));
float startMod=START_MOD+(START_MOD_TIME_ADD*sin(time*START_MOD_TIME_SPEED));
float startIter=ITER_SCALE + (ITER_TIME_ADD*sin(time*ITER_TIME_SPEED));
 
vec2 hit=box(p, vec3(0.0,0.0,0.0), vec3(1.0,1.0,1.0), 2.0);
	//
	// Fractal Menger sponge
	//
	for (int i=0;i<ITERATIONS;i++){
		vec3 tempPos = startPos - abs(mod(p * scale, startMod) - 1.0)*startDiv;
		scale=scale * startIter;
 
		vec2 cross=cross3D(tempPos, vec3(0.0,0.0,0.0), 10.0, 1.0, 0.0)/scale;
		hit.x=max(hit.x,-cross.x);
	}
 
	hit=join(hit, plane(p, vec3(0.0, -1.0, 0.0), vec3(0.1, 1.0, 0.1), 0.0));
	return hit;
}

do funkcji generujących obiekty trzeba dodać kod funkcji generowania krzyża:

vec2 cross3D(vec3 p, vec3 pos, float size, float thick, float material) {
	p=p - pos;
	vec2 obj1=box(p.xyz, vec3(0.0,0.0,0.0), vec3(size,thick,thick), material);
	vec2 obj2=box(p.yzx, vec3(0.0,0.0,0.0), vec3(thick,size,thick), material);
	vec2 obj3=box(p.zxy, vec3(0.0,0.0,0.0), vec3(thick,thick,size), material);
	return vec2(min(obj1.x,min(obj2.x, obj3.x)), material);
}

oraz efekt pracy:

Ps. Nie jest to do końca Menger sponge bo ma trzy duże dziury zamiast jednej 😛

Stałe poniżej pozwalają na animację generowanego fraktala w czasie:

const float START_SCALE=3.0;
const float START_TIME_ADD=0.0;
const float START_TIME_SPEED=0.0;
 
const float ITER_SCALE=3.0;
const float ITER_TIME_ADD=0.0;
const float ITER_TIME_SPEED=1.0;
 
const float START_POS=3.0;
const float START_POS_TIME_ADD=0.0;
const float START_POS_TIME_SPEED=1.0;
 
const float START_DIV=3.0;
const float START_DIV_TIME_ADD=0.0;
const float START_DIV_TIME_SPEED=1.0;
 
const float START_MOD=2.0;
const float START_MOD_TIME_ADD=0.0;
const float START_MOD_TIME_SPEED=1.0;
 
const int ITERATIONS=1;

Optymalizacje:

  • można inaczej generować krzyż w prostszy sposób
  • generowanie funkcji sin zmieniających parametry startowe przenieś do kodu wyższego rzędu, i podawać je jako zmienne shadera
  • oraz poczytać trochę dobrej lektury podanej na końcu RDF cz. III