Η συνήθης διαδικασία με την οποία ζητείται η αναπαράσταση γραφικών στοιχείων δια μέσου ενός προγράμματος περιήγησης ιστού (browser), είναι γνωστή και σχετικά απλή. Εάν κάποιος επιθυμεί να απεικονίσει κείμενο, εικόνες, βίντεο, διανυσματικά γραφικά κ.α. δεν έχει παρά να συντάξει με κατάλληλη δομή αρχεία .html και .css και να τα παράσχει στον browser επιλογής ώστε αυτά να εμφανιστούν στην οθόνη.

Τι συμβαίνει ωστόσο όταν οι απαιτήσεις για σύνθετα γραφικά έρθουν στο προσκήνιο; Με ποιόν τρόπο είναι εφικτή η διαχείριση τρισδιάστατων σκηνικών, με πληθώρα αντικειμένων (τάξεως χιλίων εώς και εκατομοιρίων), με πολύπλοκα σκιαστικά προγράμματα για τη δημιουργία διαφόρων εφέ, ενώ ταυτόχρονα η εμπειρία του χρήστη παραμένει αναλλοίωτη σε επίπεδα πραγματικού χρόνου (real time), δηλαδή τουλάχιστον 60 καρέ ανα δευτερόλεπτο; Πως ο ίδιος ο browser επιστρατεύει την επιτάχυνση υλικού που προσφέρει η κάρτα γραφικών και για απλούστερες διαδικασίες φωτοαπόδοσης (rendering)? Η απάντηση στα παραπάνω ερωτήματα βρίσκεται στις δυνατότητες της τεχνολογίας WebGL.

WebGL – Τι είναι, πως λειτουργεί?

WebGL ή (Web Graphics Library), είναι μια διεπαφή χαμηλού επιπέδου υπεύθυνη για την γεφύρωση web εφαρμογών με το υλικό (GPU). Περιέχει σταθερές και συναρτήσεις που επιτρέπουν την τροφοδοσία της κάρτας γραφικών τόσο με δεδομένα οσο και με εντολές απο την ΚΜΕ (CPU). Είναι πλήρως συμβατή με υπόλοιπα πρότυπα που αφορούν εφαρμογές διαδικτύου, πράγμα που αίρει την αναγκαιότητα πρόσθετων (plug-ins), ενώ αξιοποιεί την επιτάχυνση υλικού σε διαδικασίες όπως physics (2D & 3D), επεξεργασία εικόνων και εφέ, δια μέσου DOM στοιχείων όπως canvas. Τα στοιχεία αυτά μπορούν εύκολα να ενσωματωθούν σε άλλα DOM στοιχεία και να αποτελέσουν μέρος του μορφώματος μιας ιστοσελίδας.

Η δομή των WebGL προγραμμάτων αποτελείται απο:

  • Τον κώδικα ελέγχου (control code), σε γλώσσα JavaScript
  • Τον κώδικα σκιαστικού/ών προγράμματος/ων (shader code), σε γλώσσα OpenGL ES Shading Language (σύντομα GLSL ES), μια γλώσσα συντακτικά συγγενής με C ή C++ και εκτελείται απευθείας στην κάρτα γραφικών του συστήματος.

Η WebGL δημιουργήθηκε και συντηρείται απο την Khronos Group και διανέμεται σε δύο πρωτεύουσες εκδόσεις την WebGL1.0 και WebGL2.0, με την τελευταία να είναι η πλέον διαδεδομένη και συνεπώς προτιμητέα.

Graphics Context – Δημιουργία γραφικού περιβάλλοντος

Βασική προϋπόθεση για την αξιοποίηση των δυνατοτήτων της WebGL, είναι η δημιουργία γραφικού περιβάλλοντος (graphics context ή απλά context). Αφού δημιουργηθεί στοιχείο DOM τύπου canvas, μπορεί να κληθεί από περιβάλλον κώδικα JavaScript ώστε να εξουσιοδοτήσει την GPU να αναλάβει πλήρως την απόδοση γραφικών εντός της γεωμετρίας του. Αυτή την εξουσιοδότηση έρχεται να διατυπώσει σαφώς το context που δεν είναι τίποτα περισσότερο από μια σειρά υποδείξεων και χαρακτηριστικών που αφορούν ποιότητα του τελικού αποτελέσματος, δέσμευση πόρων, τύπους και ιεραρχία ενταμιευτών (buffers) κ.α. Πρακτικά αυτό μπορεί να συμβεί κατά αυτόν τον τρόπο:

const canvas  = document.querySelector('canvas');
const context = canvas.getContext('webgl2');

Μερικά από τα χαρακτηριστικά του context μπορεί να είναι:

  • RENDERBUFFER_SAMPLES (επίπεδο πολλαπλών δειγμάτων για ενταμιευτές φωτοαπόδωσης. Γνωστό και ως multisampling)
  • TEXTURE_MIN/MAX_LOD (ελάχιστο/μέγιστο επίπεδο λεπτομέριας υφών. LOD=LevelOfDetail)
  • COLOR (προδιαγεγραμμένα χρώματα)
  • DEPTH (βάθος/λεπτομέρεια χρωμάτων)

Εφόσον το υλικό είναι παρόν στο σύστημα καθώς και η ελάχιστη υποστηριζόμενη έκδοση της WebGL και δεν υπάρχουν άλλα προβλήματα, η διαδικασία δημιουργίας γραφικού περιβάλλοντος θεωρείται επιτυχής και ο προγραμματιστής μπορεί να προβεί στη σύνταξη του κώδικα ελέγχου.

Control Code – Σύνταξη κώδικα ελέγχου

Ο κώδικας ελέγχου οφείλει να περιλαμβάνει το κομμάτι της λογικής της εφαρμογής καθώς φυσικά και όλες τις απαραίτητες διευκρινήσεις σχετικά με τα αντικείμενα προς απόδοση και τον τρόπο που θα αποδοθούν. Για παράδειγμα είναι αναγκαίο να οριστεί ο τύπος πρώιμης γεωμετρίας (primitive geometry type) που θα αντιστοιχεί σε κάθε γραφικό στοιχείο. Τέτοιοι τύποι είναι:

  • Σημεία (points)
  • Γραμμές (lines)
  • Τρίγωνα (Triangles). Η πλέον συνήθης επιλογή για τις περισσότερες καταστάσεις.
  • Ν-γωνα (N-gons). Σπάνια η χρήση τους.

Ως φυσικό επακόλουθο του προσδιορισμού του τύπου γεωμετρίας των στοιχείων είναι και ο προσδιορισμός των ακμών (vertices) που θα την αποδίδουν στον χώρο. Είναι λοιπόν αναγκαίο να οριστούν οι τοποθεσίες τους και να αποθηκευτούν στον αντίστοιχο ενταμιευτή (vertex buffer). Αξίζει να σημειωθεί πως συγκεκριμένα οι ακμές συνήθως φέρουν και άλλα γνωρίσματα (attributes) πέραν της θέσης τους όπως πχ συν/μένες υφών (texture coordinates), κάθετα διανύσματα (normals) κ.α. Έπειτα, για να είναι σαφής η συσχέτιση των κατάλληλων ακμών μεταξύ τους ώστε να συνθέσουν την επόμενη γεωμετρία (πχ τριγώνου), θα πρέπει να οριστεί και ένας επιπλέον ενταμιευτής (index buffer). Άλλοι συχνοί (αλλά όχι απαραίτητοι) προσδιορισμοί είναι:

  • Χρώμα καθαρισμού/ανανέωσης (clear color).
  • Έλεγχος βάθους (depth test).
  • Διαδικασίες ανάμιξης/συνδιασμού (blending operations).
  • Απόκρυψη περιττής γεωμετρίας (backface/frontface culling).

Σε τμήμα του κώδικα ελέγχου λαμβάνει χώρα η μεταγλώττιση του πηγαίου κώδικα σκιαστικών προγραμμάτων, η σύνδεση των διαφόρων τμημάτων (linking) και η επισύναψη ανά μεταξύ τους (attachment) και τέλος η παραγωγή εκτελέσιμου (shader program). Τα προγράμματα που παράγονται κατά αυτόν τον τρόπο χρησιμοποιούνται για να χρωματίσουν τα εικονοστοιχεία (pixels) που θα παράξουν τα primitives, διαδικασία γνωστή και ως ραστεροποίηση (rasterization). Όπως προαναφέρθηκε, τα σκιαστικά προγράμματα προορίζονται να εκτελεστούν στην GPU οπου και αποστέλλονται και συνεπώς παύουν την άμεση επαφή με την CPU. Η μόνη σχετική επαφή που διατηρούν με την εφαρμογή και κατ επέκταση με την CPU, είναι μέσω ειδικών μεταβλητών (uniform variables) και λαμβάνουν τιμές παραμέτρων. Οι μεταβλητές αυτές υπάρχουν αυστηρά ομώνυμες στα σκιαστικά και στον κώδικα ελέγχου.

Shader Code – Σύνταξη κώδικα σκιαστικού προγράμματος

Τα σκιαστικά προγράμματα στην πραγματικότητα δεν είναι ένα ενιαίο κομμάτι κώδικα που εκτελείται σε μία φάση, αλλά αποτελούνται απο πολλά και διάφορα, ανεξάρτητα τμήματα. Τα τμήματα αυτά εκτελούνται σε διάφορες φάσης της αλληλουχίας βημάτων για την απόδοση γραφικών, γνωστή και ως graphics pipeline (Σχήμα 1.). Δύο τμήματα είναι απολύτως βασικά και παρόντα σε κάθε διαδικασία που αφορά την απόδοση γραφικών. Αυτά είναι:

  • Vertex shader (VS)
  • Fragment/Pixel shader (FS)

Άλλα τμήματα που η χρήση τους είναι προεραιτική, καθώς παρέχουν εξειδικευμένες λειτουργίες είναι:

  • Geometry shader (GS)
  • Tessellation Control shader (TCS)
  • Tessellation Evaluation shader (TES)
Σχήμα.1 Απλοποιημένη αναπαράσταση δομής της graphics-pipeline.

Το πρώτο τμήμα που εκτελείται είναι το Vertex shader, που αποδίδει την θέση των ακμών και συνθέτει τα πρώιμα αντικείμενα (primitive assembly). Το τελευταίο κατά σειρά τμήμα είναι πάντα το Fragment shader που χρωματίζει κάθε pixel που δημιουργείται και αποθηκεύει τα αποτελέσματα σε έναν ενταμιευτή “παρασκηνίου” (back buffer). Προφανώς εάν υπάρχει back buffer τότε υφίσταται και front buffer και είναι αυτός που διαβάζει η οθόνη. Στην ουσία κάθε φορά που αποδίδεται ένα στιγμιότυπο (frame) του σκηνικού, οι δύο ενταμιευτές εναλλάσσονται και ο τροποποιημένος back buffer (πλέον front) εμφανίζεται στην οθόνη. Το σύστημα αυτό των δυο ενταμιευτών για αυτόν τον σκοπό ονομάζεται double buffering.

Λόγω της διαφοράς στην φάση εκτέλεσης τους τα προγράμματα δεν μπορούν να έχουν αμφίδρομη επικοινωνία. Παρ ‘όλα αυτά, τμήματα που εκτελούνται πρώτα μπορούν να εξάγουν δεδομένα (outputs) οπου επόμενα τμήματα μπορούν να λάβουν ως είσοδο (inputs) (πχ normals σε Fragment shader για απόδοση φωτισμού).

Ένα βασικό κοινό γνώρισμα των τμημάτων είναι οτι έχουν συγκεκριμένη σύνταξη. Κάθε τμήμα αποτελείται απο:

  • την έκδοση της GLSL. (αυστηρά η πρώτη γραμμή κώδικα)
  • μεταβλητές in/out (εάν υπάρχουν)
  • μεταβλητές uniform (εάν υπάρχουν)
  • συνάρτηση void main()

Επίσης, τα σκιαστηκά προγράμματα στο σύνολο τους δεν αποτελούνται απο σειριακό κώδικα, όπως συμβαίνει στα προγράμματα που εκτελούνται στην CPU. Αυτός είναι άλλωστε και ο λόγος που αξιοποιείται η κράτα γραφικών – η εκμετάλλευση εκτέλεσης παράλληλου κώδικα σε εκατομμύρια πυρήνες (shader cores) – . Αυτό συνεπάγεται την εξαιρετικά μειωμένη χρήση βρόχων (if, for, while) και την αντικατάσταση τους με λογικές και αριθμητικές συναρτήσεις, οπου είναι εφικτό.

Hello Triangle – Η Εφαρμογή

Με βάση τα παραπάνω, είναι κανείς σε θέση να δημιουργήσει το απλούστερο ίσως shader, το Hello Triangle.

Για αρχή, θα δημιουργηθεί ένα νέο στοιχείο τύπου canvas με ορισμένο width και height, σε .html αρχείο. Έπειτα σε αρχείο .js και με αναφορά στο στοιχείο canvas, θα δημιουργηθεί νέο webgl context. Το JavaScript αρχείο θα περιέχει τα δύο βασικά σκιαστικά προγράμματα, πληροφορίες για τις τρείς ακμές (θέσης και χρώματος) και θα οριστεί ως επιλεγμένη πρώιμη γεωμετρία αυτή του τριγώνου. Παρακάτω φαίνεται αναλυτικά η διαδικασία δια μέσου του παρεχόμενου κώδικα:

See the Pen Part1: Hello Triangle! by Xristos Dosis (@GraphXDoses) on CodePen.

Το αποτέλεσμα θα πρέπει να είναι αυτό:

Ομολογουμένως το αποτέλεσμα, αν και το επιθυμητό δεν είναι ιδιαίτερα εντυπωσιακό. Είναι όμως αναμφίβολα ο θεμέλιος λίθος για την κατανόηση και την εξοικείωση με έννοιες και διαδικασίες βασικές για την απόδοση γραφικών (και όχι μόνο στον browser).

Σε προσεχές άρθρο θα αναπτυχθεί περαιτέρω το παρόν πρόγραμμα, με εισαγωγή σε νέες έννοιες και τεχνικές που θα βοηθήσουν στην μετάβαση απο το παραπάνω αποτέλεσμα, σε κάτι σαν και αυτό:

ASCII Vortex

Και αφού ολοκληρώθηκε το βάπτισμα του πυρός, εύχομαι καλή αρχή/συνέχεια στον απύθμενο και πάντοτε συναρπαστικό κόσμο των γραφικών! Μείνετε συντονισμένοι για το Part 2!

Χρήσιμα Links:
Khronos Group
WebGL 2.0 Specification
WebGL: 2D and 3D graphics for the web – Web APIs | MDN