Linux Inside
91
αγνοούμε αυτή τη φωτεινή πηγή, μιας και δεν μπορεί να συν-
δράμει στο φωτισμό.
Χωρίζουμε την αλληλεπίδραση του φωτός με την επιφάνεια
των αντικειμένων σε δύο κατηγορίες, με βάση το υλικό της
επιφάνειας που προσπαθούμε να προσεγγίσουμε:
•
Οι τραχιές επιφάνειες διαχέουν το φως που λαμβάνουν
ισόποσα προς όλες τις κατευθύνσεις του ημισφαιρίου που τις
περιβάλλει. Αυτή η αλληλεπίδραση λέγεται diffuse και συμπε-
ριφέρεται σύμφωνα με το νόμο του Lambert, που λέει ότι το
ποσό της ακτινοβολίας που διαχέεται προς οποιαδήποτε κα-
τεύθυνση είναι ίσο με το συνημίτονο της προσπίπτουσας γω-
νίας από το normal.
•
Οι λείες επιφάνειες ανακλούν το φως που λαμβάνουν
προς μία μικρή δέσμη κατευθύνσεων γύρω από την κατεύ-
θυνση ανάκλασης της προσπίπτουσας. Αυτή η αλληλεπίδρα-
ση λέγεται specular.
Πολλές επιφάνειες, στην πραγματικότητα, παρουσιάζουν
συνδυασμό των παραπάνω αλληλεπιδράσεων, είτε γιατί βρί-
σκονται κάπου ενδιάμεσα από τις δύο ακραίες περιπτώσεις
που περιγράψαμε, είτε γιατί έχουν τραχιά επιφάνεια καλυμ-
μένη από κάποια γυαλιστερή επίστρωση. Για να προσεγγί-
σουμε αυτές τις επιφάνειες, υπολογίζουμε και τις δύο πιθα-
νές αλληλεπιδράσεις (diffuse και specular) και χρησιμοποιού-
με ένα σταθμικό άθροισμά τους ως το τελικό χρώμα που θα
επιστρέψει ο υπολογισμός του φωτισμού. Τα βάρη που θα
χρησιμοποιήσουμε τα παίρνουμε από το material του αντικει-
μένου που περιέχει όλες τις παραμέτρους που χρειαζόμαστε
για τους υπολογισμούς φωτισμού για κάθε επιφάνεια
(
material.h).
Την diffuse αλληλεπίδραση την υπολογίζουμε σύμφωνα με
το νόμο του Lambert που προαναφέραμε. Για να υπολογίσου-
με το συνημίτονο της γωνίας ανάμεσα στο διάνυσμα κατεύ-
θυνσης του φωτός και το normal, αρκεί να υπολογίσουμε το
εσωτερικό γινόμενο των δύο διανυσμάτων, αφού φροντίσου-
με να είναι μοναδιαία.
Για τη specular αλληλεπίδραση θα χρησιμοποιήσουμε το
εμπειρικό μοντέλο του phong, που, αν και δεν έχει κάποια
εδραίωση στη φυσική, είναι απλό και βγάζει πιστευτά specular
highlights. Υπολογίζουμε, κατ’ αρχάς, τη διεύθυνση ανάκλα-
σης του φωτός και κατόπιν σηκώνουμε σε κάποια δύναμη το
εσωτερικό γινόμενο μεταξύ του διανύσματος αυτού και του
διανύσματος κατεύθυνσης του παρατηρητή (δηλαδή την αντί-
θετη κατεύθυνση από αυτή της ακτίνας). Όσο μεγαλύτερη αυ-
τή η δύναμη, τόσο πιο συγκεντρωμένο το highlight και άρα
πιο λεία δείχνει η επιφάνεια.
Αφού επαναλάβουμε αυτή τη διαδικασία για κάθε φως και
προσθέσουμε τα αποτελέσματα, μένει να δούμε αν το αντικεί-
μενο είναι ανακλαστικό (παράμετρος reflectivity στο material)
και, αν ναι, να υπολογίσουμε την κατεύθυνση ανάκλασης της
ακτίνας και να καλέσουμε την trace_ray recursively. Το χρώμα
που θα πάρουμε από την ανακλώμενη ακτίνα το προσθέτου-
με στο τελικό χρώμα που θα επιστρέψουμε, αφού το πολλα-
πλασιάσουμε πρώτα με το reflectivity factor. Φυσικά, πρέπει
να φροντίσουμε να μην πέσουμε σε ατέρμονο recursion εάν η
ακτίνα πιαστεί ανάμεσα σε δύο ανακλαστικά αντικείμενα, οπό-
τε φροντίζουμε να τερματίσουμε το recursion μετά από ένα
όριο. Για την πλήρη υλοποίηση της shade δείτε το αρχείο
scene.cc.
Συμπληρώνοντας το παζλ
Αφού υλοποιήσουμε όλα τα παραπάνω, ας δούμε και πώς
τα συνδυάζουμε για να κάνουμε render 3D σκηνές.
Ξεκινώντας ο απλοϊκός ray-tracer μας, δημιουργεί τον
framebuffer και καλεί μία συνάρτηση create_test_scene, που
φτιάχνει όλα τα αντικείμενα που θα περιέχει η σκηνή. Κατό-
πιν, για κάθε pixel ζητά από την κάμερα το primary ray που
του αντιστοιχεί, το οποίο και δίνει στην trace_ray, για να υπο-
λογιστεί το χρώμα αυτού του pixel.
Image frame(width, height);
Scene *scn = create_test_scene();
Camera *cam = scn->get_camera();
//
for every pixel ...
Color *pixel = frame.pixels;
for(int i=0; i<frame.ysz; i++) {
for(int j=0; j<frame.xsz; j++) {
//
construct a ray passing through the pixel
Ray ray = cam->get_primary_ray(j, i, frame.xsz,
frame.ysz);
//
trace the ray and write the pixel
*
pixel++ = scn->trace_ray(ray, 0);
}
}
frame.save("output.ppm");
Το μόνο κομμάτι που δεν καλύψαμε είναι το πώς υπολογί-
ζεται το primary ray που αντιστοιχεί σε κάθε pixel. Δυστυχώς,
λόγω περιορισμένου χώρου, δεν μπορούμε να το αναλύσου-
με εκτενώς.
Συνοπτικά, υπολογίζουμε τη θέση του pixel σε ένα επίπεδο
μπροστά από την αρχή του συστήματος συντεταγμένων τρι-
γωνομετρικά και από αυτό υπολογίζουμε την κατεύθυνση της
ακτίνας. Τέλος, μετασχηματίζουμε την ακτίνα με τον πίνακα
μετασχηματισμού της κάμερας, που έχουμε υπολογίσει με
βάση την επιθυμητή θέση και κατεύθυνσή της (βλ. camera.cc
για λεπτομέρειες).
Το αποτέλεσμα του προγράμματός μας δεν είναι καθόλου
άσχημο για την απλότητα του αλγορίθμου που υλοποιήσαμε,
όπως φαίνεται στις επόμενες εικόνες.
Για οποιεσδήποτε απορίες ή ερωτήσεις σχετικά με το ray
tracing και τον προγραμματισμό γραφικών γενικότερα, μη δι-
στάσετε να επικοινωνήσετε στο nuclear@member.fsf.org.
Happy hacking!
•
Linux Labs – Ray Τracing
Σχήμα 3: Μοντέλο φωτισμού.
3