Sitzung: Jeden Freitag in der Vorlesungszeit ab 16 Uhr c. t. im MAR 0.005. In der vorlesungsfreien Zeit unregelmäßig (Jemensch da?). Macht mit!

C-Kurs/DreiD: Unterschied zwischen den Versionen

K (verschob „Ckurs/DreiD“ nach „C-Kurs/DreiD“)
 
(16 dazwischenliegende Versionen von 5 Benutzern werden nicht angezeigt)
Zeile 3: Zeile 3:
  
 
== Vorgabe ==
 
== Vorgabe ==
Lade dir zunächst die Vorgaben herunter. Die "screen"-Dateien brauchen nicht verändert werden, sie sorgen für die grafische Ausgabe. Deine Aufgabe ist es, die Rechenoperationen der main.c zu ergänzen.
+
Lade dir zunächst die [http://docs.freitagsrunde.org/Veranstaltungen/ckurs_2010/aufgaben/C-3D_V1.0_Vorlagen.zip Vorgaben] herunter. Die "screen"-Dateien brauchen nicht verändert werden, sie sorgen für die grafische Ausgabe. Deine Aufgabe ist es, die Rechenoperationen der main.c zu ergänzen.
  
 
Kompiliert wird das Programm mit:
 
Kompiliert wird das Programm mit:
 
  gcc main.c screen.c -lX11 -lm -o 3D.out
 
  gcc main.c screen.c -lX11 -lm -o 3D.out
Denke daran die Pfeiltaste Hoch in der Shell zu verwenden, statt es jedesmal neu einzugeben.
+
Denke daran, in der Konsole die Pfeiltaste Hoch zu verwenden, statt es jedesmal neu einzugeben.
  
Werden die Vorgaben direkt kompiliert und ausgeführt, ist ein Quadrad zu sehen. Dies ist ein frontal betrachteter Würfel, welcher eigentlich schon gedreht wird, jedoch fehlen die Transformationsoperationen.
+
Werden die Vorgaben direkt kompiliert und ausgeführt, ist ein Quadrat mit Linien zu sehen. Dies ist ein frontal betrachteter Drahtgitter-Würfel. Für die Rotationen fehlen die zu ergänzenen Transformationsoperationen. Beende das Programm mit Ctrl+C in der Konsole.
  
 
== Rechenoperationen ==
 
== Rechenoperationen ==
Zeile 17: Zeile 17:
 
LinA-Grundkenntnisse sind von Vorteil.
 
LinA-Grundkenntnisse sind von Vorteil.
  
=== Transformation und Projektion ===  
+
=== Transformation ===  
Wir befinden uns in der Methode "transformation" und wollen den Würfel nun erst um die X-Achse und dann um die Y-Achse drehen. Dazu verwenden wir Rotationsmatrizen, welche wir als 3x3 Array umsetzen, wobei wir uns einigen, dass der erste Array-Index die Zeile und der zweite die Spalte der Matrix ist. Die rot_x ist bereits vorgegeben. Initialisiere auf gleiche Weise darunter die rot_y, unter Verwendung des Winkels "alpha".
+
Wir befinden uns in der Funktion "refresh_transformation_matrix" und wollen den Würfel nun erst um die X-Achse und dann um die Y-Achse drehen. Dazu verwenden wir [http://de.wikipedia.org/wiki/Drehmatrix Rotationsmatrizen], welche wir als 3x3-Array umsetzen, wobei wir uns einigen, dass der erste Array-Index die Zeile und der zweite die Spalte der Matrix ist. Die rot_x ist bereits vorgegeben. Initialisiere auf gleiche Weise darunter die rot_y, unter Verwendung des Winkels "alpha".
Schreibe direkt darunter einen Algorithmus, der die Matrizen rot_x und rot_y miteinander multipliziert und das Ergebnis auf der globalen Matrix "transform" speichert. Versuche dabei Schleifen, statt konstanter Indizes zu verwenden. Die resultierende Matrix auf einen Vektor angewandt, rotiert diesen nun entsprechend um die X- und Y-Achse. Dies soll in der Methode "apply_transform" geschehen.
+
Schreibe direkt darunter einen Algorithmus, der die Matrizen rot_x und rot_y miteinander multipliziert und das Ergebnis auf der globalen Matrix "transform" speichert. Versuche dabei Schleifen, statt konstanter Indizes zu verwenden. Beachte: Matrix-Multiplikation ist nicht kommutativ.
apply_transform nimmt einen Punkt der Figur im Raum und soll diesen entsprechend der aktuellen Rotation verändern. Die Parameter sind Call-by-Reference. Ändere mit Hilfe der globalen "transform"-Matrix die Inhalte der Parameter so, dass der x-y-z-Vektor am Ende rotiert ist (Matrix-Vektor-Multiplikation).
+
 
 +
Die resultierende Matrix auf einen Vektor angewandt, rotiert diesen nun entsprechend um die X- und Y-Achse. Dies soll in der Funktion "apply_transform" geschehen.
 +
apply_transform nimmt einen Punkt der Figur im Raum und soll diesen entsprechend der aktuellen Rotation verändern. Die Parameter sind Call-by-Reference. Bisher sind in den Funktionen nur Zuweisungen ohne Effekt. Ändere mit Hilfe der globalen "transform"-Matrix die Inhalte der Parameter so, dass der x-y-z-Vektor am Ende rotiert ist (Matrix-Vektor-Multiplikation).
  
 
Teste deine Implementierung. Zu diesem Zeitpunkt sollte schon ein rotierender Würfel zu sehen sein. Falls der Würfel beim Rotieren verzerrt wird, sind möglicherweise Indizes bei der Matrix Multiplikation verdreht.
 
Teste deine Implementierung. Zu diesem Zeitpunkt sollte schon ein rotierender Würfel zu sehen sein. Falls der Würfel beim Rotieren verzerrt wird, sind möglicherweise Indizes bei der Matrix Multiplikation verdreht.
  
Nebenbei: echte 3D-Anwendungen verwenden 4x4 Matrizen, damit auch Translationen möglich sind. Da wir aber nur rotieren wollen, reichen 3x3 Matrizen.
+
Nebenbei: echte 3D-Anwendungen verwenden 4x4-Matrizen, damit auch Translationen möglich sind. Da wir aber nur rotieren wollen, reichen 3x3-Matrizen.
  
 
=== Culling ===
 
=== Culling ===
Schalte den "WIREFRAMES_MODE" auf 0, wodurch der Würfel ausgefüllte Flächen bekommt und sieh dir zunächst das Ergebnis an. Zu sehen ist das Problem, dass wir jede Fläche einfach zeichnen, egal ob sie vor oder hinter dem bisher gezeichneten Würfel ist. Die allgemeine und standardmäßige Lösung wäre ein Z-Buffer, wodurch Pixel, die verdeckt werden würden, einfach nicht gezeichnet werden. Dies ist für unsere Zwecke zu aufwendig, daher nehmen wir eine speziellere Lösung: Culling.
+
Schalte den "WIREFRAMES_MODE" auf 0, wodurch der Würfel ausgefüllte Flächen bekommt und sieh dir zunächst das Ergebnis an. Zu sehen ist das Problem, dass wir strikt jede Fläche zeichnen, egal ob sie räumlich vor oder hinter den bisher gezeichneten Flächen ist. Die allgemeine und standardmäßige Lösung wäre ein Z-Buffer, wodurch Pixel, die verdeckt werden würden, einfach nicht gezeichnet werden. Dies ist für unsere Zwecke zu aufwändig, daher nehmen wir eine speziellere Lösung: Culling.
  
Die Überlegung ist, dass es bei einem soliden Würfel nicht sein kann, dass wir irgendeine fläche von innen sehen, da diese immer von einer Außenfläche verdeckt wird. Außerdem verdeckt nie eine Außenfläche eine andere (sichtbare) Außenfläche, da der Würfel konvex ist. Wir brauchen also nur die Außenflächen zu zeichnen.  
+
Die Überlegung ist, dass es bei einem soliden Würfel nicht passieren kann, dass wir irgendeine fläche von Innen sehen, da diese immer von einer Außenfläche verdeckt wird. Außerdem verdeckt nie eine Außenfläche eine andere (sichtbare) Außenfläche, da der Würfel konvex ist. Wir brauchen also nur die Außenflächen zu zeichnen.  
  
Wir befinden uns in der Methode "draw_primitive", welche ein einzelnes Dreieck zeichnet. Nachdem die Punkte (x1,y1, ... ,z3) transformiert wurden, muss geprüft werden, ob die drei Punkte im Uhrzeigersinn angeordnet sind. Bilde dazu aus den Koordinaten die beiden Richtungsvektoren des Dreiecks (der Ortsvektor ist egal) und bilde aus ihnen das Kreuzprodukt. Nun kann man sich überlegen, dass das Dreieck genau dann von außen zu sehen ist, wenn die Z-Koordinate des Kreuzprodukts kleiner als 0 ist (somit brauch man streng genommen die X- und Y-Koordinaten des Kreuzprodukts gar nicht). Ist die Z-Koordinate also größer-gleich 0, verlasse die Methode vorzeitig (vor dem Zeichnen des Dreiecks).
+
Wir befinden uns in der Funktion "render_primitive", welche ein einzelnes Dreieck zeichnet. Nachdem die Punkte (x1,y1, ... ,z3) transformiert wurden, muss geprüft werden, ob wir von innen auf die Fläche schauen. Bilde dazu aus den Koordinaten die beiden Richtungsvektoren des Dreiecks (der Ortsvektor ist egal) und bilde aus ihnen das Kreuzprodukt. Nun kann man sich überlegen, dass das Dreieck genau dann von außen zu sehen ist, wenn die Z-Koordinate des Kreuzprodukts kleiner als 0 ist (somit brauch man streng genommen die X- und Y-Koordinaten des Kreuzprodukts gar nicht). Ist die Z-Koordinate also größer-gleich 0, verlasse die Funktion vorzeitig (vor dem Zeichnen des Dreiecks).
  
  
 
Nun sollte ein rotierender, ausgefüllter, fehlerfreier Würfel zu sehen sein.
 
Nun sollte ein rotierender, ausgefüllter, fehlerfreier Würfel zu sehen sein.
 +
 +
=== Licht ===
 +
Wir wollen nun die einfachste Art von Licht implementieren: Direktionales Licht.
 +
Wir haben lediglich einen Licht-Vektor (lgt_x,lgt_y,lgt_z), welcher von der Vorgabe bereits gesetzt wird und normalisiert ist. Zeigt dieser nun in die gleiche Richtung, wie unser Kreuzprodukt von eben, gehen wir davon aus, dass das Dreieck frontal beleuchtet wird. Wie könnte man prüfen, inwiefern die Richtungen der Vektoren übereinstimmen? Als wichtiger Hinweis: Das Kreuzprodukt muss noch normalisiert werden, da die Länge des Vektors das Ergebnis verfälscht.
 +
 +
Das Ergebnis deiner Berechnung soll ein Float zwischen 0 (nicht angeleuchtet) und 1 (voll angeleuchtet) sein. Multipliziere dein Ergebnis jeweils mit den Farb-Parametern im "set_color"-Aufruf.
 +
Wenn alles stimmt, sollte schon ein (wenn auch etwas zu kontrastreiches) Licht zu sehen sein. Experimentiere mit dem Helligkeitswert um die Lichtausbreitung etc. anzupassen. Setze beispielsweise eine Untergrenze größer 0, bilde Wurzeln oder/und gleiche den Wert mit Addition und Multiplikation an.
  
 
== Andere Figuren ==
 
== Andere Figuren ==
Wenn du lust hast, kannst du mit Hilfe von draw_primitive und draw_face eigene 3D-Figuren erstellen. Ersetze dazu den Würfel in der main einfach durch deinen Algorithmus. Denke aber daran, dass sofern Wireframes aus sind, die Figur konvex sein sollte.
+
Wenn du Lust hast, kannst du mit Hilfe von render_primitive und render_face eigene 3D-Figuren erstellen. Ersetze dazu den Würfel in der main.c zwischen begin_scene und end_scene einfach durch deinen Algorithmus. Denke aber daran, dass, sofern Wireframes aus sind, die Figur konvex sein sollte und die Koordinaten der Dreiecke/Vierecke jeweils im Uhrzeigersinn angegeben werden müssen.
 
Eine mögliche Figur ist der Kegel:
 
Eine mögliche Figur ist der Kegel:
 +
 +
    /* Kegel */
 +
    refresh_color(1,1,1);
 +
    int acc = 32; //Genauigkeit
 +
    float rad = 1.2; //Radius
 +
    int i;
 +
    float ang = 0, lst_ang;
 +
    for(i=1; i<acc+1; i++)
 +
    {
 +
        refresh_color(0.8,0.8,1);
 +
        lst_ang = ang;
 +
        ang = (float)i/acc*2*M_PI;
 +
        render_primitive(0,-1.2,0, sin(ang)*rad,1.2,cos(ang)*rad, sin(lst_ang)*rad,1.2,cos(lst_ang)*rad);
 +
        if(i%2 == 0)
 +
            refresh_color(0.1,0.1,0.5);
 +
        else
 +
            refresh_color(0.2,0.2,0.6);
 +
        render_primitive(0,1.2,0, sin(lst_ang)*rad,1.2,cos(lst_ang)*rad, sin(ang)*rad,1.2,cos(ang)*rad);
 +
    }
 +
 +
Theoretisch können auch animierte Figuren gezeichnet werden. Benutze dazu den Zähler "c_time".

Aktuelle Version vom 5. März 2013, 17:37 Uhr

In dieser Aufgabe wollen wir eine stark vereinfachte 3D-Anwendung schreiben, welche einen 3D-Körper rotieren lässt. Du kannst selbst entscheiden, bis zu welchem Zwischenergebnis du die Aufgabe lösen willst.

Vorgabe

Lade dir zunächst die Vorgaben herunter. Die "screen"-Dateien brauchen nicht verändert werden, sie sorgen für die grafische Ausgabe. Deine Aufgabe ist es, die Rechenoperationen der main.c zu ergänzen.

Kompiliert wird das Programm mit:

gcc main.c screen.c -lX11 -lm -o 3D.out

Denke daran, in der Konsole die Pfeiltaste Hoch zu verwenden, statt es jedesmal neu einzugeben.

Werden die Vorgaben direkt kompiliert und ausgeführt, ist ein Quadrat mit Linien zu sehen. Dies ist ein frontal betrachteter Drahtgitter-Würfel. Für die Rotationen fehlen die zu ergänzenen Transformationsoperationen. Beende das Programm mit Ctrl+C in der Konsole.

Rechenoperationen

Für die Aufgabe könnte es hilfreich sein, einen Schmierzettel bereitzulegen.

LinA-Grundkenntnisse sind von Vorteil.

Transformation

Wir befinden uns in der Funktion "refresh_transformation_matrix" und wollen den Würfel nun erst um die X-Achse und dann um die Y-Achse drehen. Dazu verwenden wir Rotationsmatrizen, welche wir als 3x3-Array umsetzen, wobei wir uns einigen, dass der erste Array-Index die Zeile und der zweite die Spalte der Matrix ist. Die rot_x ist bereits vorgegeben. Initialisiere auf gleiche Weise darunter die rot_y, unter Verwendung des Winkels "alpha". Schreibe direkt darunter einen Algorithmus, der die Matrizen rot_x und rot_y miteinander multipliziert und das Ergebnis auf der globalen Matrix "transform" speichert. Versuche dabei Schleifen, statt konstanter Indizes zu verwenden. Beachte: Matrix-Multiplikation ist nicht kommutativ.

Die resultierende Matrix auf einen Vektor angewandt, rotiert diesen nun entsprechend um die X- und Y-Achse. Dies soll in der Funktion "apply_transform" geschehen. apply_transform nimmt einen Punkt der Figur im Raum und soll diesen entsprechend der aktuellen Rotation verändern. Die Parameter sind Call-by-Reference. Bisher sind in den Funktionen nur Zuweisungen ohne Effekt. Ändere mit Hilfe der globalen "transform"-Matrix die Inhalte der Parameter so, dass der x-y-z-Vektor am Ende rotiert ist (Matrix-Vektor-Multiplikation).

Teste deine Implementierung. Zu diesem Zeitpunkt sollte schon ein rotierender Würfel zu sehen sein. Falls der Würfel beim Rotieren verzerrt wird, sind möglicherweise Indizes bei der Matrix Multiplikation verdreht.

Nebenbei: echte 3D-Anwendungen verwenden 4x4-Matrizen, damit auch Translationen möglich sind. Da wir aber nur rotieren wollen, reichen 3x3-Matrizen.

Culling

Schalte den "WIREFRAMES_MODE" auf 0, wodurch der Würfel ausgefüllte Flächen bekommt und sieh dir zunächst das Ergebnis an. Zu sehen ist das Problem, dass wir strikt jede Fläche zeichnen, egal ob sie räumlich vor oder hinter den bisher gezeichneten Flächen ist. Die allgemeine und standardmäßige Lösung wäre ein Z-Buffer, wodurch Pixel, die verdeckt werden würden, einfach nicht gezeichnet werden. Dies ist für unsere Zwecke zu aufwändig, daher nehmen wir eine speziellere Lösung: Culling.

Die Überlegung ist, dass es bei einem soliden Würfel nicht passieren kann, dass wir irgendeine fläche von Innen sehen, da diese immer von einer Außenfläche verdeckt wird. Außerdem verdeckt nie eine Außenfläche eine andere (sichtbare) Außenfläche, da der Würfel konvex ist. Wir brauchen also nur die Außenflächen zu zeichnen.

Wir befinden uns in der Funktion "render_primitive", welche ein einzelnes Dreieck zeichnet. Nachdem die Punkte (x1,y1, ... ,z3) transformiert wurden, muss geprüft werden, ob wir von innen auf die Fläche schauen. Bilde dazu aus den Koordinaten die beiden Richtungsvektoren des Dreiecks (der Ortsvektor ist egal) und bilde aus ihnen das Kreuzprodukt. Nun kann man sich überlegen, dass das Dreieck genau dann von außen zu sehen ist, wenn die Z-Koordinate des Kreuzprodukts kleiner als 0 ist (somit brauch man streng genommen die X- und Y-Koordinaten des Kreuzprodukts gar nicht). Ist die Z-Koordinate also größer-gleich 0, verlasse die Funktion vorzeitig (vor dem Zeichnen des Dreiecks).


Nun sollte ein rotierender, ausgefüllter, fehlerfreier Würfel zu sehen sein.

Licht

Wir wollen nun die einfachste Art von Licht implementieren: Direktionales Licht. Wir haben lediglich einen Licht-Vektor (lgt_x,lgt_y,lgt_z), welcher von der Vorgabe bereits gesetzt wird und normalisiert ist. Zeigt dieser nun in die gleiche Richtung, wie unser Kreuzprodukt von eben, gehen wir davon aus, dass das Dreieck frontal beleuchtet wird. Wie könnte man prüfen, inwiefern die Richtungen der Vektoren übereinstimmen? Als wichtiger Hinweis: Das Kreuzprodukt muss noch normalisiert werden, da die Länge des Vektors das Ergebnis verfälscht.

Das Ergebnis deiner Berechnung soll ein Float zwischen 0 (nicht angeleuchtet) und 1 (voll angeleuchtet) sein. Multipliziere dein Ergebnis jeweils mit den Farb-Parametern im "set_color"-Aufruf. Wenn alles stimmt, sollte schon ein (wenn auch etwas zu kontrastreiches) Licht zu sehen sein. Experimentiere mit dem Helligkeitswert um die Lichtausbreitung etc. anzupassen. Setze beispielsweise eine Untergrenze größer 0, bilde Wurzeln oder/und gleiche den Wert mit Addition und Multiplikation an.

Andere Figuren

Wenn du Lust hast, kannst du mit Hilfe von render_primitive und render_face eigene 3D-Figuren erstellen. Ersetze dazu den Würfel in der main.c zwischen begin_scene und end_scene einfach durch deinen Algorithmus. Denke aber daran, dass, sofern Wireframes aus sind, die Figur konvex sein sollte und die Koordinaten der Dreiecke/Vierecke jeweils im Uhrzeigersinn angegeben werden müssen. Eine mögliche Figur ist der Kegel:

   /* Kegel */
   refresh_color(1,1,1);
   int acc = 32; //Genauigkeit
   float rad = 1.2; //Radius
   int i;
   float ang = 0, lst_ang;
   for(i=1; i<acc+1; i++)
   {
       refresh_color(0.8,0.8,1);
       lst_ang = ang;
       ang = (float)i/acc*2*M_PI;
       render_primitive(0,-1.2,0, sin(ang)*rad,1.2,cos(ang)*rad, sin(lst_ang)*rad,1.2,cos(lst_ang)*rad);
       if(i%2 == 0)
           refresh_color(0.1,0.1,0.5);
       else
           refresh_color(0.2,0.2,0.6);
       render_primitive(0,1.2,0, sin(lst_ang)*rad,1.2,cos(lst_ang)*rad, sin(ang)*rad,1.2,cos(ang)*rad);
   }

Theoretisch können auch animierte Figuren gezeichnet werden. Benutze dazu den Zähler "c_time".