Linux Inside
89
σουμε σε ένα κοινό σύστημα συντεταγμένων. Παρ’ όλα αυτά,
θα χρειαστούμε πίνακες για το μετασχηματισμό της «κάμε-
ρας» που γεννά primary rays, όπως θα δούμε παρακάτω.
Για να περιγράψουμε γραμμικούς μετασχηματισμούς σε
τρεις διαστάσεις, αρκεί ένας πίνακας 3x3.
Θα μας βόλευε, όμως, να μπορούμε να περιγράψουμε
affine μετασχηματισμούς, δηλαδή γραμμικούς συν παράλλη-
λη μεταφορά.
Αυτό μπορούμε να το πετύχουμε χρησιμοποιώντας μία επι-
πλέον διάσταση, θεωρώντας, δηλαδή, ότι τα διανύσματά μας
είναι τετρα-διάστατα στο υπερεπίπεδο w = 1 και χρησιμοποι-
ώντας πίνακες 4x4 – για περισσότερα πάνω σε αυτό το μαθη-
ματικό τέχνασμα, βλ. «ομογενείς συντεταγμένες» (homoge-
neous coordinates).
Τέλος, χρώματα θα αναπαραστήσουμε χρησιμοποιώντας
ένα τρισδιάστατο διάνυσμα, θεωρώντας ότι οι συντεταγμένες
xyz αντιστοιχούν στα στοιχεία rgb (red green blue) του χρώ-
ματος. Έτσι, μία εικόνα όπως ο framebuffer, μπορεί να είναι
απλά ένα array από τέτοια διανύσματα:
typedef Vector3 Color;
class Image {
public:
Color *pixels;
int xsz, ysz;
Image();
Image(int xsz, int ysz);
~Image();
bool save(const char *fname) const;
};
Σκηνή και αντικείμενα
Ο απλοϊκός ray tracer που θα φτιάξουμε, θα χρησιμοποιεί
σκηνές φτιαγμένες από σφαίρες και επίπεδα. Αργότερα μπο-
ρούμε εύκολα να τον επεκτείνουμε να χειρίζεται και αντικείμε-
να φτιαγμένα από πολύγωνα για πλήρη ευελιξία, όμως αυτό
παρουσιάζει διάφορα ενδιαφέροντα προβλήματα, που χρειά-
ζονται ένα ολόκληρο άρθρο από μόνα τους.
Θα φτιάξουμε, λοιπόν, μία ιεραρχία κλάσεων με βάση το
class Object και δύο υποκλάσεις: Sphere και Plane. Την κλά-
ση Object θα την κάνουμε abstract base class, με pure virtual
συνάρτηση intersect, την οποία θα υλοποιήσουν οι υποκλά-
σεις που αναφέραμε για να βρίσκουν εάν και πού τέμνει μία
ακτίνα το αντικείμενο.
class Object {
public:
Material material;
virtual ~Object();
virtual bool intersect(const Ray &ray, HitPoint *pt)
const = 0;
};
Η συνάρτηση intersect επιστρέφει true/false ανάλογα με το
αν η ακτίνα τέμνει το αντικείμενο ή όχι, και στην πρώτη περί-
πτωση γεμίζει και ένα HitPoint structure με επιπλέον πληρο-
φορίες για το σημείο τομής μέσω του pointer που περιμένει
ως δεύτερη παράμετρο.
Το struct HitPoint περιέχει, κατ’ αρχάς, την παραμετρική
απόσταση της τομής πάνω στην ακτίνα, δηλαδή έναν αριθμό
(
t) που, αν τον αντικαταστήσουμε στην παραμετρική εξίσωση
της ευθείας origin + direction * t (όπου origin και direction τα
δύο διανύσματα που ορίζουν την ακτίνα μας, όπως είπαμε
παραπάνω), παίρνουμε το ακριβές σημείο της τομής στο σύ-
στημα συντεταγμένων του 3D κόσμου μας. Αυτό τον υπολογι-
σμό τον κάνει η intersect και αποθηκεύει στο δεύτερο πεδίο
το διάνυσμα αυτού του σημείου. Το τρίτο πεδίο είναι το
normal στο σημείο τομής, δηλαδή ένα μοναδιαίο διάνυσμα,
κάθετο στην επιφάνεια του αντικειμένου σε αυτό το σημείο,
που θα μας χρειαστεί για να υπολογίσουμε το φωτισμό και τη
γωνία ανάκλασης αργότερα.
struct HitPoint {
double dist;
Vector3 pos;
Vector3 normal;
const Object *obj;
};
Ένα επίπεδο ορίζεται από την εξίσωση
Αx + Βy + Γz + Δ = 0, όπου (Α Β Γ) είναι
απλά το normal (κάθετο διάνυσμα) του επιπέδου και Δ η
απόστασή του από την αρχή του συστήματος συντεταγμένων.
Οπότε στην κλάση Plane, απλώς έχουμε ένα Vector3 normal
και ένα double dist.
Ο υπολογισμός της τομής μεταξύ ακτίνας και επιπέδου εί-
ναι πολύ απλός, χρησιμοποιώντας τη γεωμετρική κατασκευή
του
σχήματος 1.
Αυτό που χρειαζόμαστε είναι να υπολογίσουμε το t = a/b.
Από το σχήμα είναι προφανές ότι a/b = a'/b', όπου a' είναι η
απόσταση της αρχής της ακτίνας από το επίπεδο και b' η
προβολή της κατεύθυνσης D στην ευθεία που ορίζεται από το
normal του επιπέδου (N). Ξεκινάμε βρίσκοντας ένα οποιοδή-
ποτε σημείο στο επίπεδο: P = NΔ. Κατόπιν, υπολογίζουμε το
a' ως το εσωτερικό γινόμενο του N με το διάνυσμα (P - O),
μιας και το εσωτερικό γινόμενο μεταξύ δύο διανυσμάτων
πρακτικά μας δίνει την προβολή του ενός στο άλλο επί το μή-
κος τους, και το μήκος του N είναι 1. Με παρόμοιο τρόπο
υπολογίζουμε και το b' με το εσωτερικό γινόμενο του N και
του D.
bool Plane::intersect(const Ray &ray, HitPoint *pt) const
{
double ndotdir = dot(normal, ray.dir);
if(fabs(ndotdir) < EPSILON) {
return false; // ray is parallel
}
Vector3 planept = normal * dist;
Vector3 pptdir = planept - ray.origin;
double t = dot(normal, pptdir) / ndotdir;
Linux Labs – Ray Τracing
Σχήμα 1: Τομή ακτίνας με επίπεδο.
1