Κυριακή 23 Οκτωβρίου 2016

ΕΙΣΑΓΩΓΙΚΟ ΜΑΘΗΜΑ


Πρώτο Εργαστήριο Γραφικών - OpenGL

Έκδοση 2009-2010
  • Γενική Περιγραφή της OpenGL και του GLUT
  • Αρχικοποίηση της OpenGL
  • Διαχείριση συμβάντων (event management)
  • Παράσταση στην οθόνη (Rendering)
  • Παράθυρα (Windows) και Πεδία Παράστασης (Viewports)
  • Ορθογραφική Προβολή (Orthographic Projection)
  • Χρωματισμός και Xρώμα RGB
  • Βασικά Στερεά


1. Λίγα λόγια για την OpenGL

Η OpenGL είναι μία διεπαφή προγραμματισμού εφαρμογών (ΑΡΙ) για την επικοινωνία με την κάρτα γραφικών. Αποτελείται από περίπου 150 διαφορετικές εντολές. Εμείς στο εργαστήριο θα ασχοληθούμε με ένα βασικό υποσύνολο των εντολών αυτών. Μπορείτε να βρείτε περισσότερες πληροφορίες για την OpenGL στο OpenGL Redbook που βρίσκεται στο:
Η OpenGL είναι σχεδιασμένη ώστε να είναι ανεξάρτητη από το υλικό (hardware) και από το λειτουργικό σύστημα - παραθυρικό περιβάλλον. Μπορεί να χρησιμοποιηθεί σχεδόν σε όλες τις «γνωστές» γλώσσες προγραμματισμού (C, C++, JAVA, Visual Basic, Delphi).
Παρέχει ένα σύνολο εντολών για την επικοινωνία με το υλικό και υποστηρίζεται από όλες τις εταιρείες κατασκευής καρτών γραφικών. Είναι το τρέχον standard παρά τις προσπάθειες της Microsoft με το DirectX. Για αυτό το λόγο χρησιμοποιείται ευρέως σε πολλές εφαρμογές και παιχνίδια.
Η OpenGL είναι μια μηχανή καταστάσεων (state machine) με την έννοια ότι είμαστε διαρκώς σε μια κατάσταση μέχρι με μια εντολή να μεταβούμε σε κάποια άλλη. Για παράδειγμα, όταν χρωματίζουμε αντικείμενα, μπορούμε να θέσουμε το χρώμα σε κόκκινο, κίτρινο, … και θα χρησιμοποιούμε το ίδιο χρώμα συνέχεια, μέχρι να ορίσουμε ένα καινούριο χρώμα...
2. Άνοιγμα του πρώτου εργαστηρίου
Κατεβάστε τον κώδικα του εργαστηρίου. Είναι συμπιεσμένος σε ένα .zip αρχείο. Ανοίξτε το και κάντε extract όλα τα αρχεία σε ένα κατάλογο.
Επίσης θα χρειαστείτε τη βοηθητική βιβλιοθήκη glut από εδώ:
http://www.xmission.com/~nate/glut.html
και φυσικά την OpenGL από εδώ (στην περίπτωση που δεν είναι ήδη στο σύστημά σας):
http://opengl.org/resources/faq/getting_started.html
Ύστερα πηγαίνετε στον κατάλογο που κάνατε extract το εργαστήριο εκείνο και:

2.1 Με Visual Studio

Στο Visual Studio 6 ανοίξτε το αρχείο με την κατάληξη .dsw.
Στο Visual Studio.net ανοίξτε το αρχείο με την κατάληξη .sln.
Για να κάνετε compile και να τρέξετε το πρόγραμμα, πατήστε CTRL-F5

Απαραίτητα αρχεία για να τρέξει σε Visual Studio

Στον κατάλογο System

  • glu32.dll
  • opengl32.dll
  • glut32.dll

Στον κατάλογο include του Visual Studio

  • gl\gl.h
  • gl\glu.h
  • gl\glut.h (includes both gl.h and glu.h)

Στον κατάλογο lib του Visual Studio

  • gl\glu32.lib
  • gl\opengl32.lib
  • gl\glut32.lib
Στο .net, το GLUT μπορεί να βγάλει error αν χρησιμοποιηθεί μαζί με το stdlib.h
Στην περίπτωση αυτή κάντε την ακόλουθη αλλαγή στο glut.h:
Από
extern _CRTIMP void __cdecl exit(int);
σε
extern _CRTIMP __declspec(noreturn) void __cdecl exit(int);



2.2 Με Unix compilers

Τρέξτε το make για να κάνετε compile τα προγράμματα.
Για την προετοιμασία του συστήματος σε Linux κοιτάξτε στον παρακάτω σύνδεσμο:
http://www.opengl.org/resources/faq/technical/gettingstarted.htm#gett0090

3. Το πρώτο εργαστήριο


3.1 main.cpp

Στο αρχείο main.cpp η main ξεκινά με τη συνάρτηση glutInit(). Η glutInit() αρχικοποιεί τη βιβλιοθήκη glut η οποία κάνει το προγραμματισμό για τη δημιουργία του παραθύρου πιο εύκολο και μεταφέρσιμο. Στη συνέχεια η glutInitDisplayMode() με την οποία επιλέγουμε αν θα έχουμε χρώμα κατά το μοντέλο RGB (κόκκινο-πράσινο-μπλε) ή με παλέτα, αν θα έχουμε μονό ή διπλό buffer, και αν θα έχουμε διάφορους άλλους buffer (βάθους, κτλ). Στο παράδειγμά μας με τις παραμέτρους GLUT_RGBA|GLUT_DOUBLE επιλέγουμε να έχουμε χρώμα κατά το μοντέλο κόκκινο-πράσινο-μπλε και διπλό buffer.

Εδώ πρέπει να αναφέρουμε πώς αναπαριστάται το χρώμα στο μοντέλο κόκκινο- πράσινο-μπλε (RGB). Στο μοντέλο αυτό κάθε χρώμα είναι ένας γραμμικός συνδυασμός των τριών βασικών χρωμάτων: κόκκινο, πράσινο, μπλε. Το κάθε χρώμα παίρνει τιμές από 0 έως 255, δηλαδή 256 τιμές, άρα για να το αποθηκεύσουμε χρειαζόμαστε 28 τιμές = 8 bits = 1 byte. Οπότε και για τα τρία χρώματα χρειαζόμαστε 3 bytes. Αν επιπλέον αποθηκεύουμε και διαφάνεια (παράγοντας άλφα (Α) ) θα χρειαστούμε ένα byte επιπλέον. Τότε λέμε ότι έχουμε RGBA μοντέλο.

Όσον αφορά τη χρήση διπλού buffer (double buffering), αυτό χρησιμοποιείται για να αποφύγουμε το «τρεμόπαιγμα» (flickering) της σκηνής όταν έχουμε κίνηση. Αυτό επιτυγχάνεται ως εξής:
Έχουμε δύο buffers στους οποίους «ζωγραφίζουμε» τη σκηνή μας. Ο ένας δείχνεται στο χρήστη όσο εμείς ζωγραφίζουμε στον άλλο. Ύστερα με μια εντολή οι δύο buffer αλλάζουν ρόλους και εμείς ζωγραφίζουμε πλέον στον buffer που πριν από λίγο έβλεπε ο χρήστης. Με τη χρήση της τεχνικής αυτής το κάθε καρέ δείχνεται μόνο όταν είναι πλήρως ζωγραφισμένο. Ο χρήστης του προγράμματός μας δε βλέπει ποτέ ένα ημιτελές καρέ.

Παρακάτω συναντάμε τις εντολές:
glutInitWindowSize(480,480);
glutInitWindowPosition(50,50);
Η glutInitWindowSize μας λέει πόσα pixel θα έχει πλάτος και ύψος το παράθυρο μας. Ύστερα με την glutInitWindowPosition το τοποθετούμε στην οθόνη. Εδώ πρέπει να αναφέρουμε ότι οι συντεταγμένες οθόνης ξεκινάνε από την πάνω αριστερά γωνία (0,0) και αυξάνονται προς τα κάτω και δεξιά. Οπότε το (50,50) στο παράδειγμα θα τοποθετήσει το παράθυρο κοντά στην πάνω αριστερά γωνία της οθόνης.
Αντίθετα στην OpenGL οι συντεταγμένες ξεκινάνε (0,0) στην κάτω αριστερή γωνία και αυξάνονται προς τα πάνω και δεξιά, όπως φαίνεται στο ακόλουθο σχήμα.

Εικόνα 1: Συντεταγμένες κατά OpenGL

Τέλος, αφού έχουμε επιλέξει το μέγεθος και τη θέση του παραθύρου το δημιουργούμε με την glutCreateWindow("Course1"). Το αλφαριθμητικό που παίρνει σαν παράμετρο είναι ο τίτλος που θα έχει το παράθυρο.

Στη συνέχεια ακολουθεί μια κλήση στη Setup(), την οποία όμως δεν πρόκειται να αναλύσουμε στο εργαστήριο αυτό. Προς το παρόν αρκεί να ξέρετε ότι εκεί αρχικοποιούνται οι μεταβλητές και οι καταστάσεις της OpenGL πριν ξεκινήσει το πρόγραμμα.

Με τις εντολές
glutDisplayFunc(Render);
glutReshapeFunc(Resize);

καθορίζουμε τη συνάρτηση που θα καλείται όταν πρέπει να ξανασχεδιασθεί το περιεχόμενο του παραθύρου (π.χ. λόγω του ότι αυτό κρύφτηκε από κάποιο άλλο παράθυρο και μετά ξαναήρθε στο προσκήνιο) και τη συνάρτηση που θα καλείται όταν αλλάζουμε το μέγεθος του παραθύρου, αντίστοιχα. Το όρισμα της glutDisplayFunc πρέπει να είναι μια συνάρτηση τύπου void Render() ενώ το όρισμα της glutReshapeFunc πρέπει να είναι μια συνάρτηση τύπου
void Resize(int w, int h).

Τελικά η συνάρτηση glutMainLoop() εκτελεί συνεχώς ένα βρόχο που τερματίζει όταν κλείσουμε το παράθυρο και στον οποίο καλούνται οι συναρτήσεις που καθορίσαμε με τις glutDisplayFunc και glutReshapeFunc.

3.2 visuals.cpp


Θα εξετάσουμε τις εξής δύο συναρτήσεις:

void Render()
void Resize(int w, int h)

3.2.1 Resize

Η Resize μέσω των ορισμάτων της int w και int h επιστρέφει πάντα το τρέχον πλάτος και ύψος σε pixels του παραθύρου στο οποίο γίνεται η απεικόνηση της σκηνής μας, για να χρησιμοποιηθεί κατάλληλα. Κάθε φορά που γίνεται resize το παράθυρο απεικόνισης, καλείται η Resize(int w, int h), η οποία έχει καθοριστεί μέσω της glutReshapeFunc(Resize), και η οποία επιστρέφει τις νέες διαστάσεις του παραθύρου w και h.
Η γραμμή
if (h==0) h=1;
μας εγγυάται ότι δε θα έχουμε ύψος ίσο με το μηδέν.
Με την εντολή
glViewport(0,0,w,h);
καθορίζουμε ένα ορθογώνιο μέσα στο παράθυρό μας (το πεδίο παράστασης- Viewport), στο οποίο θα απεικονιστεί τελικά η εικόνα μας, και το οποίο μπορεί να έχει πλάτος και ύψος το πλάτος και το ύψος όλου του παραθύρου απεικόνισης. Αφού γίνουν οι διάφοροι μετασχηματισμοί και προβολές το τελικό αποτέλεσμα «συστέλλεται» ή «διαστέλλεται» ώστε να χωρέσει στο ορθογώνιο αυτό, και συγχρόνως γίνεται ο μετασχηματισμός των σημείων των αντικειμένων, που εκφράζονται σε αυθαίρετες μονάδες (πραγματικοί αριθμοί), σε pixels. Αυτό φαίνεται και στην παρακάτω εικόνα, όπου η γραφική παράσταση «συστέλλεται» ώστε να χωρέσει σε ένα ορθογώνιο μικρότερο από το παράθυρο μας.

Εικόνα 2: Ορισμός viewport μικρότερου από το παράθυρο

Οι παράμετροι της εντολής είναι οι εξής:

  • Η πρώτη τιμή είναι η τετμημένη (Χ-άξονας) της κάτω αριστερής γωνίας του ορθογωνίου μέσα στο παράθυρό μας.
  • Η δεύτερη τιμή είναι η τεταγμένη (Υ-άξονας).
  • Η τρίτη είναι το πλάτος του ορθογωνίου, και
  • Η τέταρτη είναι το ύψος του ορθογωνίου
όλες οι τιμές σε pixels.

Ας αλλάξουμε τώρα τις τιμές αυτές για να το καταλάβουμε καλύτερα:
    1. Αντικαταστήστε την με την εντολή glViewport(0,0,w/2,h); Καθορίζουμε ένα ορθογώνιο που θα εκτείνεται μέχρι τα μισά του παραθύρου.
    2. Αντικαταστήστε την με την εντολή glViewport(0,0,w/2,h/2); Καθορίζουμε ένα ορθογώνιο που καταλαμβάνει την κάτω αριστερά γωνία του παραθύρου.
    3. Αντικαταστήστε την με την εντολή glViewport(w/3,h/3,w,h); Καθορίζουμε ένα ορθογώνιο που ξεκινάει από τη θέση (w/3, h/3) του παραθύρου και εκτείνεται σε μέγεθος ίσο με το παράθυρο.

Παρατηρούμε ότι η τσαγιέρα «συστέλλεται» και «επεκτείνεται» ανάλογα με το μέγεθος του ορθογωνίου πεδίου παράστασης ώστε να το καταλαμβάνει ολόκληρο. Επίσης παρατηρήστε οτι κάνοντας resize το παράθυρο απεικόνισης, το ορθογώνιο του viewport αλλάζει διαστάσεις σύμφωνα με τις διαστάσεις του παραθύρου.
    1. Αντικαταστήστε την με την εντολή glViewport(100,100,400,400); Καθορίζουμε ένα ορθογώνιο με αρχή το σημείο (100,100) και διαστάσεις στατικές 400x400.

Παρατηρείτε ότι η τσαγιέρα απεικονίζεται με αρχή το (100,100) και παραμένει «αμετάβλητη» ασχέτως αν κάνετε resize το παράθυρο απεικόνισης.

Έπονται οι εντολές
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
Για να κατανοήσουμε τι κάνουν οι εντολές αυτές πρέπει πρώτα να εμβαθύνουμε λίγο στη λειτουργία της OpenGL. Η OpenGL αποθηκεύει πληροφορίες για τους μετασχηματισμούς:
α) της προβολής (GL_PROJECTION),
β) της θέασης των αντικειμένων (GL_MODELVIEW), και
γ) της υφής των αντικειμένων (GL_TEXTURE)
σε τρεις στοίβες πινάκων (matrix stacks).
Με την εντολή glMatrixMode καθορίζουμε σε ποιόν από τους πίνακες αυτούς θα αναφέρονται οι μετασχηματισμοί που θα ακολουθήσουν. Με το GL_PROJECTION αναφερόμαστε στον πίνακα για τους μετασχηματισμούς προβολών.

Η εντολή glLoadIdentity φορτώνει το μοναδιαίο πίνακα στον πίνακα που αναφερόμαστε. Εδώ στην ουσία αρχικοποιεί τον πίνακα προβολών.

Τέλος, η εντολή
glOrtho (-50.0f, 50.0f, -50.0f, 50.0f, 100.0f, -100.0f);
καθορίζει ότι ο μετασχηματισμός προβολής που θα χρησιμοποιήσουμε θα είναι η παράλληλη (ορθογραφική) προβολή.
Οι παράμετροι που παίρνει καθορίζουν τα όρια αποκοπής και είναι οι εξής:
glOrtho (Left, Right, Bottom, Top, Near, Far);
και η χρήση τους φαίνεται στην παρακάτω Εικόνα 3.

Εικόνα 3: Η σημασία των παραμέτρων της glOrtho (Left, Right, Bottom, Top, Near, Far)

Ουσιαστικά η glOrtho κάνει μια απεικόνιση από τον χώρο R3 στον οποίο βρίσκονται τα αντικείμενα στον R2, δηλ. στο επίπεδο x-y. Σημειώστε πως στην ορθογραφική προβολή ο παρατηρητής βρίσκεται στο +άπειρο του άξονα Z, οπότε οι ακτίνες του φωτός που φτάνουν στον παρατηρητή είναι παράλληλες και τέμνουν κάθετα το επίπεδο x-y (front clipping plane).

3.2.2 Render

Στη συνάρτηση αυτή γίνεται ο σχεδιασμός των αντικειμένων. Κάθε φορά που χρειάζεται να γίνει redisplay το παράθυρο (την πρώτη φορά, ή επειδή αποκαλύφθηκε ένα μέρος του που ήταν καλυμένο από άλλο παράθυρο, ή επειδή έγινε resize) καλείται η Render(), η οποία έχει καθοριστεί στην glutDisplayFunc(Render).

Ξεκινάμε με την εντολή
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
η οποία αρχικοποιεί τους buffers που χρησιμοποιεί η OpenGL με το πέρασμα σε αυτή προκαθορισμένων παραμέτρων, που χωρίζονται με το σύμβολο του bitwise OR. Στο παράδειγμά μας καθαρίζεται ο buffer του χρώματος και του βάθους (z-buffer).

Ύστερα επιλέγουμε τον πίνακα μετασχηματισμού των αντικειμένων και του φορτώνουμε τον μοναδιαίο πίνακα, για να τον αρχικοποιήσουμε:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

Με την εντολή
glColor3f(1.0, 0.5, 0.2);
θέτουμε το χρώμα που θέλουμε να χρησιμοποιήσουμε. Οι παράμετροι της glColor3f είναι πραγματικοί αριθμοί από 0.0 εώς 1.0 που καθορίζουν την ένταση των χρωμάτων κόκκινο, πράσινο, μπλε (RGB) με τη σειρά που εμφανίζονται. Μπορείτε να δώσετε διάφορες τιμές στις παραμέτρους και να δείτε τις αλλαγές στο χρώμα της τσαγιέρας.

Ακολουθεί η εντολή που ζωγραφίζει την τσαγιέρα.
glutSolidTeapot( 20.0 );
Η εντολή αυτή είναι μέρος της βοηθητικής βιβλιοθήκης glut, για το σχεδιασμό βασικών σχημάτων. Η παράμετρος είναι το μέγεθος της τσαγιέρας. Όσο μεγαλύτερη η τιμή της παραμέτρου, τόσο μεγαλύτερη η τσαγιέρα. Άλλα σχήματα που μπορείτε να δοκιμάσετε είναι:
glutWireTeapot( 20.0 );
glutSolidSphere( 20.0, 30, 24);

Τέλος υπάρχει η εντολή που αλλάζει τους buffers (για το double-buffering) .
glutSwapBuffers();



Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου