3D mit Processing


Implizite Flächen

1.Idee

Lesen Sie zunächst auf der Übersichtsseite Analytische Flächen die Definition einer Implizite Flächen durch!
Die gesuchte Fläche ist die Menge der Nullstellen der Funktion f :

Hier die Nullstellen zu finden, dürfte im Normalfall ein wesentlich größeres Problem darstellen, als bei expliziten und parametrischen Flächen die Punkte auf eben diesen Flächen zu bestimmen.
Das Bild unten zeigt beispielsweise einen Ausschnitt der implizitenFläche bei folgender Nullstellenmenge:


Wie Sie erkennen können, besteht die Fläche aus lauter sehr kleinen Sphären. Das Bild entstand nach einem sehr einfachen, aber äußerst zeitaufwendigen Algorithmus: Man legt in Gedanken ein Gitter über das zu untersuchende Volumen. So entstehen bei gleichem Maßstab lauter Würfel einer bestimmten Kantenlänge. Die Länge der Kanten und die Größe des untersuchten Raumes bestimmen die Rechenzeit. Nun setzt man das Zentrum der Würfel in die Gleichung ein. Dass sich dann exakt Null ergibt, dürfte äußerst selten vorkommen. Daher definiert man einen Grenzwert g. Ergib die Einsetzung des Gitterpunktes einen Fehler betragsmäßig kleiner als g dann wird der Würfel als "Treffer" markiert. Im Bild oben wurden anstatt Würfel kleine Kugeln eingesetzt. Sieht gut aus, ist aber unnötig rechenaufwendig, weil der 3D-Renderer für jede Kugel Licht und Schatten berechnen muss. Gleichgültig ob Würfel oder Kugel, diese kleinen räumlichen Einheiten werden Voxel genannt - zusammengesetzt aus Volumen und Pixel . Eine sehr grobe Voxelpräsentation der obigen Fläche sieht dann zum Beispiel so aus:


Die Schrittweite im Gitter beträgt hier 100 Pixel. Von daher kann man nur eine äußerst grobe Vorstellung der Fläche bekommen. Maximale Auflösung bekommt man mit der Schrittweite ein Pixel. Das Voxel wird dann nur noch durch dieses eine Pixel repräsentiert. Die Farbe wird hier durch den Abstand vom Ursprung bestimmt. Die Voxelstruktur ist nun natürlich nicht mehr sichtbar:



Um die Flächen von allen Seiten anschauen zu können, bietet sich die Methode camera an. Wir richten sie auf den Ursprung aus und bewegen sie auf einer Sphäre mit Radius r. Man benötigt für Positionierung zusätzlich zwei Winkel. Sie kennen das System vermutlich unter dem Namen Polarkoordinaten. So kann die passende Methode aussehen:


In draw rufen wir camera auf und sorgen gleichzeitig für eventuelle direkte Beleuchtung von allen Seiten.


Bleibt der Aufruf unter keyPressed() :


Beachten Sie dabei, dass der Winkel für UP und DOWN nur zwischen -PI/2 und PI/2 liegen darf!
Im folgenden Bild sind vier weitere implizite Flächen abgebildet:



Die Fläche unten links wird Römerfläche oder Steinerfläche genannt. Ihre Gleichung ist:

x2y2+x2z2+z2y2+xyz = 0
Oben rechts ist die Boysche Fläche zu sehen. Ihre Gleichung ist ungleich komplizierter:
64(1-z)3-48(1-z)2z2(3x2+3y2+2z2) +12(1-z)z(27(x2+y2)2-24z2(x2+y2)+36√ 2 yz(y2-3x2)+4z4 )= 0
Unten können Sie die fünf hier behandelten Sketche runterladen.

2.Idee

Wenn man wie oben camera und cube benutzt, werden hinter den Kulissen Drehungen durchgeführt und fertige Shapes verwendet. Im folgenden Sketch machen wir alles selbst, ohne rotateX oder ähnliches zu verwenden. Drehungen mit Hilfe von Matrizen und Voxel mit selbst erzeugten Shapes. Dadurch wird das Programm deutlich langsamer, ein Zeichen dafür, wie elegant die fertigen Routinen programmiert sind. Der Vorteil aber, ist, dass man auf diese Weise sozusagen in den "Motorraum" schauen kann. So zum Beispiel kann die Drehung um die x-Achse aussehen:



Die benötigte Matrix realisiert man auf folgende Weise:



Im folgenden Bild sieht man das Voxelbild, wenn man den dargestellten Term Null setzt. Wenn Sie mit dem unten herunterzuladenden Sketch selbst experimentieren wollen, ist allerdings Geduld gefragt ;-))



3.Idee

Sowohl im Buch als auch hier haben wir das Thema Threads behandelt. Anlass war, dass die heutigen Prozessoren mehrere unabhängig voneinander arbeitende Cores besitzten, so dass man bei geeigneten Sketchen, bestimmte Programmteile auf die verschiedenen Prozessorkerne verteilen kann ("beiläufige Programmierung"). Dieses Kapitel sollten sie zuvor durchlesen.



Dem obigen Bild (Tangle Cube) liegt die folgende Gleichung zugrunde:
sq(x)*sq(x)+sq(y)*sq(y)+sq(z)*sq(z) - 5*(sq(x)+sq(y)+sq(z)) +11.8 = 0
Trotz eines sehr engen Prüfgitters (d = 0.01) und einer dadurch einige Millionen starken Treffermenge, benötigt ein PC mit beispielsweise 12 unabhängigen Cores nur wenige Sekunden, bis das Bild entstanden ist. Das liegt hier natürlich auch daran, dass man, wegen der Symetrie der Gleichung, nur ein Achtel des Gitters überprüfen muss.
Und dieses Achtel teilt man in so viele Bereiche ein, wie Cores zur Verfügung stehen. So sieht dann der Programmteil, eine dreifache Schleife, für einen Core aus, sofern man M davon beteiligen will:

x[n] = xMin+n*(xMax-xMin)/M; y[n] = yMin;z[n] = zMin;
while(x[n]<=xMin+(n+1)*(xMax-xMin)/M) {
while (y[n]<=yMax) {
while(z[n]<=zMax){
D[n]= implFunktion(x[n],y[n],z[n]);
R[n]= abstandVomUrsprung(x[n],y[n],z[n]);
if(R[n] <=1) abstandsFaktor =1;
else abstandsFaktor = sq(R[n])*sq(R[n]);
if(D[n] < abstandsFaktor*P){
punktHinzu(n,new Punkt(x[n],y[n],z[n],D[n]));
}
z[n]=z[n]+delta;
}

z[n]=zMin;
y[n]=y[n]+delta;
}
y[n]=yMin;
x[n]=x[n]+delta;
} break;

Die Methode punktHinzu() befüllt die M ListArrays nach ihrer Nummer n.

Ein weiteres Beispiel ist die Nordstrand's-Weird Fläche. Man muss im Vergleich zu oben lediglich die Gleichung und die Grenzen austauschen:
25*(x*sq(x)*(y+z)+y*sq(y)*(x+z)+z*sq(z)*(x+y))+50*(sq(x)*sq(y)+sq(x)*sq(z)+sq(y)*sq(z)).... -125*(sq(x)*y*z+sq(y)*x*z+sq(z)*x*y)+60*x*y*z-4*(x*y+x*z+y*z) = 0




Interessant ist auch die Banchoff's Chmutov Fläche:



Die zugehörige Gleichung:
sq((2.92*(x-1)*sq(x)*(x+1)+1.7*sq(y)))*sq((sq(y)-0.88)) + sq((2.92*(y-1)*sq(y)*(y+1)+1.7*sq(z)))*sq((sq(z)-0.88) + sq((2.92*(z-1)*sq(z)*(z+1)+1.7*sq(x)))*sq((sq(x)-0.88))-0.02 = 0

Bei impliziten Flächen kann man mehrere kombinieren, indem man die Gleichungen mit einander multipliziert. Das Produkt ist Null, wenn einer der Faktoren Null ist. Zwei Beispiele:


Drei Tori, die auf einander senkrecht stehen



Die zugehörige Gleichung mit den Parametern R und a:
(d1-e1)*(d1-e2)*(d1-e3) = 0 wobei
d1= sq(sq(x)+sq(y)+sq(z)+sq(tR)-sq(a));
e1=4*sq(tR)*(sq(x)+sq(y));
e2=4*sq(tR)*(sq(x)+sq(z));
e3=4*sq(tR)*(sq(y)+sq(z));



Tangle Cube und Nordstrands Weird



Die zugehörige Gleichung:

(sq(x)*sq(x)+sq(y)*sq(y)+sq(z)*sq(z) - 5*(sq(x)+sq(y)+sq(z)) +11.8) *
(25*(x*sq(x)*(y+z)+y*sq(y)*(x+z)+z*sq(z)*(x+y))+50*(sq(x)*sq(y)+sq(x)*sq(z)+sq(y)*sq(z))- 125*(sq(x)*y*z+sq(y)*x*z+sq(z)*x*y)+60*x*y*z-4*(x*y+x*z+y*z))= 0

Alle fünf Multithreading-Sketche können Sie am Seitenende runterladen.
4.Idee

Im letzten Kapitel des Lehrbuches werden Bibliotheken behandelt. Für unsere Zwecke wären die toxiclibs von Karsten Schmidt hilfreich. Mit ihnen ist es möglich, die Flächen mit der Maus zu drehen und so problemlos von allen Seiten zu betrachten. Die dort verwendbaren Methoden sind hoch effektiv und benötigen daher sogar noch weniger Zeit, als die oben gezeigten Sketche mit Multithreading. Die Flächen werden mit einstellbarer Genauigkeit "triangulisiert". Der Nachteil soll nicht verschwiegen werden: Wer sich nicht die Mühe macht, die Klassen und ihre Methoden in der Dokumentation nachzuvollziehen, versteht oftmals nicht, was genau gerechnet wird. Da dies bereits die vierte Methode ist, derartige Probleme zu behandeln, ist dieser Mangel akzeptabel, zumal man ja die Möglichkeit hat, genauere Informationen in der Dokumentation zu bekommen.
Ein Mittelweg wäre, sich den Marching Cubes Algorithmus , der hier verwendet wird, anzuschauen.



Im Bild oben sehen Sie den schon bekannten Tangle Cube. Links in geringer Auflösung (16 Bit) das aus lauter Dreiecken bestehende Netz. Rechts daneben mit hoher Auflösung (256 Bit) das wesentlich feinere Netz, bei dem die Dreiecke gefärbt worden sind. Mit diesem Sketch ist es möglich, die Flächen durch die bewegte Maus zu drehen. Die Beleuchtung ist so eingestellt, dass die räumliche Struktur besser zu erkennen ist. Die folgenden Bibliotheken benötigt man:

import toxi.geom.*;
import toxi.geom.mesh.*;
import toxi.math.*;
import toxi.volume.*;
import toxi.processing.*;

Initialisierungen im setup():

gfx = new ToxiclibsSupport(this);
VolumetricSpace vol = new EvaluatingVolume(new Vec3D(800,800,800), RES, MAX_ISO);
IsoSurface surface = new HashIsoSurface(vol);
mesh = new WETriangleMesh();
surface.computeSurfaceMesh(mesh, ISO);

Gezeichnet wird in draw():

if(!inFarbe) gfx.mesh(mesh, true);//einfarbig
else gfx.meshNormalMapped(mesh, !isWireframe);//bunt

Zunächst werden den durchnummerierten Voxeln die Koordinaten x,y und z zugeordnet:

public final float getVoxelAt(int i) {
X= i/(RES*RES);//print(X+" ");
Y = (i-X*RES*RES)/RES; //print(Y+" ");
Z = (i-X*RES*RES)- Y*RES;
return getVoxelAt(Z, Y, X);
}

Schließlich muss noch kontrolliert werden, welcher Wert sich ergibt, wenn man x,y und z in die implizite Funktion einsetzt. Das Ergebnis wird in valTest gespeichert. Sollte der Wert kleiner als die vorgegebene Schranke MAX_ISO sein, wird dieser Wert ausgegeben, ansonsten Null.

public final float getVoxelAt(int x, int y, int z) {
float val;
float valTest;
float Radius;
//Je kleiner der Faktor (hier 8) desto größer u,v und w !!
float u =8*((float) x / (float) (RES-1) - 0.5); //RES = 256,128,64,32,16
float v =8*((float) y / (float) (RES-1) - 0.5);
float w =8*((float) z / (float) (RES-1) - 0.5);
valTest = sq(u)*sq(u)-5*sq(u)+sq(v)*sq(v)-5*sq(v)+sq(w)*sq(w)-5*sq(w)+11.8 ;
val = 0; //Initialisierung
if(abs(valTest)< MAX_ISO){val = valTest;}
return val;
}

Hier eine Auswahl erstellter impliziter Flächen:
OL: Banchoff Surface, OR: Barth Sextic, UL: Blob Surface, UR: Boy Surface



OL: Butterfly Surface, OR: Chair, UL: Neovius Surface, UR: No name Surface



Es gibt zwei Methoden, die man in diesem Sketch anwenden kann: Glättung der Fläche durch Lapalace und "voxelize Mesh", was nichts anderes bedeutet, ls dass man das Netz gleichsam mit Legosteinen erzeugt. Bei beiden Methoden geht Information verloren. Außerdem entstehen Lücken, wo sie eigentlich nicht hingehören. Optisch interessant sind solche Gebilde in jedem Fall:


OL: No name Surface, OR: Nordstrand-Weird Surface, UL: Helocoid, UR: Trefoil Surface

Alle zweiundzwanzig zugehörigen Sketche könne Sie unten runterladen.
5.Idee

Vier Methoden zur Bestimmung impliziter Flächen wurden aufgeführt. Weshalb eine 5. Methode?
In einem der folgenden Kapitel soll der "Mandelbulb" behandelt werden. Dort werden wir eine Technik namens "Ray-Tracing" kennenlernen. Um diese Idee vorzubereiten, besprechen wir etwas ganz ähnliches zur Untersuchung bestimmter impliziter Flächen, das hier "Mesh-Tracing" genannt werden soll. Dabei benötigen wir P3D überhaupt nicht mehr. Bedenken Sie dabei: Auch in den vier vorhergehenden Methoden ist das Ergebnis jedes Mal ein zweidimensionales Bild. Bleiben wir doch einfach in zwei Dimensionen! Der Sketch unten gibt eine erste Idee: Die zu untersuchende analytische Fläche ist hier eine Kugeloberfläche. Eine Fläche, die in einem bestimmten Abstand zur Kugel aufgestellt ist, denken wir uns ein enges Netz (mesh!) von Punkten. Jeder Punkt auf der Fläche entspricht einem Pixel. Die Fläche ist in alle Richtungen drehbar, was hier als Rotation um die y-Achse dargestellt wird. Somit kann man die Kugel von allen Seiten betrachten. Von jedem Pixel geht nun ein Strahl (ray) senkrecht von der Fläche aus Richtung Gegenstand (Kugel). Auf diesen Strahlen läuft man in kleinen Schritten vorwärts. Die Anzahl der Schritte bis zum Gegenstand wird als Farbe des Pixels codiert. Eine zweite Möglichkeit wäre, die Farbe nach dem Abstand zum Ursprung zu wählen.
Da der Rechenaufwand bei kleiner Schrittweite auf den Strahlen sehr hoch ist, übergeben wir Teilflächen an die einzelnen Core. Die Drehung der Projektionsfläche wird durch Matrizen erzeugt. Wichtig dabei ist, dass man dann den neuen Einheitsnormalen-Vektor (EVN) bestimmt, in dessen Richtung die Punkte auf dem Strahl laufen werden.
Man lässt nun eine Doppelschleife über die Pixel der Projektionsfläche laufen und schreitet auf jedem Strahl so lange vorwärts, bis der Punkt, eingesetzt in die implizite Gleichung einen vorher festgelegten kleinen Wert ergibt. Die Schritte werden gezählt und deren Anzahl bestimmt einen Farb- bzw Grauwert.
So könnten die Schleifen implementiert werden:

for(int j = 0; j< aufloesung*aufloesung/aT;j++){
 ; ; raumBildPunkt[n] = raumBildPunkte[n].get(j);
 ; ;for(int k = startWert; k>-1 ; k--){
 ; ;  ; ;di[n] = distance(n,raumBildPunkt[n][0],raumBildPunkt[n][1],raumBildPunkt[n][2]);
 ; ;  ; ;if(di[n] < 0.00001){
 ; ;  ; ;  ; ;float K = map(k,max,min,255,0); //Spreizung - max und min muss durch Test bestimmt werden
 ; ;  ; ;  ; ;pixValuesGray[n][j] = K; //wird benötigt zur Helligkeitsanpassung
 ; ;  ; ;  ; ;if(farbigZeichnen){
 ; ;  ; ;  ; ;  ; ; abstand[n] = sqrt(sq(raumBildPunkt[n][0])+sq(raumBildPunkt[n][1])+sq(raumBildPunkt[n][1]));
 ; ;  ; ;  ; ;  ; ; pixValuesColor[n][j] = k%255;
 ; ;  ; ;  ; ; }
 ; ;  ; ; break;
 ; ;}else{
 ; ;  ; ;raumBildPunkt[n][0] = raumBildPunkt[n][0] -0.005*raumENV[n][0];
 ; ;  ; ;raumBildPunkt[n][1] = raumBildPunkt[n][1] -0.005*raumENV[n][1];
 ; ;  ; ;raumBildPunkt[n][2] = raumBildPunkt[n][2] -0.005*raumENV[n][2];
 ; ;  ; ;} //end of for else k
 ; ; }//end of for k
}//end of for j

Wenden wir unsere Erkenntnisse auf eine hier noch nicht behandelte implizite Fläche an:

(x2 + y2)2 - x2 + y2 + z2 - 0.005 = 0

Es handelt sich hierbei um einen "Doppeltorus". Das folgende Bild zeigt beide Ergebnisse.




Sketche implizite Flächen (Voxel)

Sketche implizite Flächen (Matrixdrehungen)

Sketche implizite Flächen Multithreading

Sketche implizite Flächen mit Toxiclibs

Sketche implizite Flächen mit Mesh-Tracing

Menu