Τα 10 πιο συνηθισμένα λάθη που κάνουν οι προγραμματιστές Android: Ένας οδηγός προγραμματισμού
Επιστήμη Δεδομένων Και Βάσεις Δεδομένων
OpenGL είναι ένα ισχυρό API πολλαπλών πλατφορμών που επιτρέπει πολύ στενή πρόσβαση στο υλικό του συστήματος σε διάφορα περιβάλλοντα προγραμματισμού.
Λοιπόν, γιατί πρέπει να το χρησιμοποιήσετε;
Παρέχει επεξεργασία πολύ χαμηλού επιπέδου για γραφικά σε 2D και 3D. Σε γενικές γραμμές, αυτό θα αποφύγει τυχόν ακαταστασία που έχουμε λόγω ερμηνευμένων ή υψηλού επιπέδου γλωσσών προγραμματισμού. Το πιο σημαντικό, ωστόσο, παρέχει επίσης πρόσβαση σε επίπεδο υλικού σε μια βασική δυνατότητα: GPU.
Η GPU μπορεί να επιταχύνει σημαντικά πολλές εφαρμογές, αλλά έχει πολύ συγκεκριμένο ρόλο σε έναν υπολογιστή. Οι πυρήνες GPU είναι στην πραγματικότητα πιο αργοί από τους πυρήνες της CPU. Εάν θέλαμε να εκτελέσουμε ένα πρόγραμμα που είναι κυρίως σειριακό χωρίς ταυτόχρονη δραστηριότητα, τότε θα είναι σχεδόν πάντα να είστε πιο αργός σε έναν πυρήνα GPU από έναν πυρήνα CPU. Η κύρια διαφορά είναι ότι η GPU υποστηρίζει μαζική παράλληλη επεξεργασία. Μπορούμε να δημιουργήσουμε μικρά προγράμματα που ονομάζονται shaders και θα λειτουργούν αποτελεσματικά σε εκατοντάδες πυρήνες ταυτόχρονα. Αυτό σημαίνει ότι μπορούμε να αναλάβουμε εργασίες που κατά τα άλλα είναι απίστευτα επαναλαμβανόμενες και να τις εκτελούμε ταυτόχρονα.
Σε αυτό το άρθρο, θα δημιουργήσουμε μια απλή εφαρμογή Android που χρησιμοποιεί το OpenGL για να αποδώσει το περιεχόμενό της στην οθόνη. Πριν ξεκινήσουμε, είναι σημαντικό να είστε ήδη εξοικειωμένοι με το γνώση γραφής εφαρμογών Android και σύνταξη κάποιας γλώσσας προγραμματισμού τύπου C. Ολόκληρος ο πηγαίος κώδικας αυτού του σεμιναρίου είναι διαθέσιμο στο GitHub .
Για να δείξουμε τη δύναμη του OpenGL, θα γράφουμε μια σχετικά βασική εφαρμογή για μια συσκευή Android. Τώρα, το OpenGL σε Android διανέμεται σε ένα υποσύνολο που ονομάζεται OpenGL για ενσωματωμένα συστήματα (OpenGL ES). Μπορούμε ουσιαστικά να το σκεφτούμε ως μια απογυμνωμένη έκδοση του OpenGL, αν και η βασική λειτουργικότητα που απαιτείται θα εξακολουθεί να είναι διαθέσιμη.
Αντί να γράφουμε ένα βασικό 'Γεια σας Κόσμος', θα γράφουμε μια απλώς απατηλή εφαρμογή: μια γεννήτρια σετ Mandelbrot. ο Σετ Mandelbrot βασίζεται στον τομέα του σύνθετοι αριθμοί . Η σύνθετη ανάλυση είναι ένα υπέροχο τεράστιο πεδίο, οπότε θα επικεντρωθούμε στο οπτικό αποτέλεσμα περισσότερο από τα πραγματικά μαθηματικά.
Όταν κάνουμε την εφαρμογή, θέλουμε να βεβαιωθούμε ότι διανέμεται μόνο σε εκείνους με κατάλληλη υποστήριξη OpenGL. Ξεκινήστε δηλώνοντας τη χρήση του OpenGL 2.0 στο αρχείο δήλωσης, μεταξύ δήλωσης δήλωσης και εφαρμογής:
MainActivity
Σε αυτό το σημείο, η υποστήριξη για το OpenGL 2.0 είναι πανταχού παρούσα. Τα OpenGL 3.0 και 3.1 κερδίζουν συμβατότητα, αλλά το γράψιμο και για τα δύο θα παραμείνει εκτός περίπου το 65% των συσκευών , οπότε λάβετε την απόφαση μόνο εάν είστε βέβαιοι ότι θα χρειαστείτε επιπλέον λειτουργικότητα. Μπορούν να εφαρμοστούν ρυθμίζοντας την έκδοση σε «0x000300000» και «0x000300001» αντίστοιχα.
Όταν δημιουργείτε αυτήν την εφαρμογή OpenGL σε Android, θα έχετε γενικά τρεις κύριες τάξεις που χρησιμοποιούνται για να σχεδιάσετε την επιφάνεια: την GLSurfaceView
, μια επέκταση του GLSurfaceView.Renderer
, και μια εφαρμογή ενός MainActivity
. Από εκεί, θα δημιουργήσουμε διάφορα μοντέλα που θα ενσωματώνουν σχέδια.
FractalGenerator
, ονομάζεται GLSurfaceView
σε αυτό το παράδειγμα, ουσιαστικά απλώς θα δημιουργήσει το δικό σας public class FractalGenerator extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Create and set GLSurfaceView mGLView = new FractalSurfaceView(this); setContentView(mGLView); } //[...] @Override protected void onPause() { super.onPause(); mGLView.onPause(); } @Override protected void onResume() { super.onResume(); mGLView.onResume(); } }
και δρομολογήστε τις καθολικές αλλαγές κάτω από τη γραμμή. Ακολουθεί ένα παράδειγμα που ουσιαστικά θα είναι ο κωδικός σας:
GLSurfaceView
Αυτό θα είναι επίσης η τάξη στην οποία θέλετε να βάλετε άλλους τροποποιητές επιπέδου δραστηριότητας (όπως συναρπαστική πλήρης οθόνη ).
Μια κατηγορία πιο βαθιά, έχουμε μια επέκταση setEGLContextClientVersion(int version)
, η οποία θα λειτουργήσει ως η κύρια προβολή μας. Σε αυτήν την τάξη, ορίζουμε την έκδοση, ρυθμίζουμε ένα Renderer και ελέγχουμε τα γεγονότα αφής. Στον κατασκευαστή μας, χρειάζεται μόνο να ρυθμίσουμε την έκδοση OpenGL με public FractalSurfaceView(Context context){ super(context); setEGLContextClientVersion(2); mRenderer = new FractalRenderer(); setRenderer(mRenderer); }
και επίσης δημιουργήστε και ορίστε το πρόγραμμα απόδοσης:
setRenderMode(int renderMode)
Επιπλέον, μπορούμε να ορίσουμε χαρακτηριστικά όπως η λειτουργία απόδοσης με RENDERMODE_WHEN_DIRTY
. Επειδή η δημιουργία ενός σετ Mandelbrot μπορεί να είναι πολύ δαπανηρή, θα χρησιμοποιούμε requestRender()
, η οποία θα καταστήσει τη σκηνή μόνο κατά την προετοιμασία και όταν πραγματοποιούνται ρητές κλήσεις προς GLSurfaceView
. Περισσότερες επιλογές για ρυθμίσεις μπορείτε να βρείτε στο onTouchEvent(MotionEvent event)
ΦΩΤΙΑ .
Αφού έχουμε τον κατασκευαστή, πιθανότατα θα θέλαμε να παρακάμψουμε τουλάχιστον μία άλλη μέθοδο: {a, 0, 0, 0, 0, b, 0, 0, 0, 0, c, 0, v_a, v_b, v_c, 1}
, το οποίο μπορεί να χρησιμοποιηθεί για γενική είσοδο χρήστη με βάση την αφή. Δεν πρόκειται να αναφερθώ σε πολλές λεπτομέρειες εδώ, καθώς αυτό δεν είναι το κύριο επίκεντρο του μαθήματος.
Τέλος, κατεβαίνουμε στο Renderer, το οποίο θα είναι όπου συμβαίνει το μεγαλύτερο μέρος της εργασίας για φωτισμό ή ίσως αλλαγές στη σκηνή. Πρώτον, θα πρέπει να εξετάσουμε λίγο πώς λειτουργούν και λειτουργούν οι πίνακες στον κόσμο των γραφικών.
Το OpenGL βασίζεται σε μεγάλο βαθμό στη χρήση πινάκων. Οι πίνακες είναι ένας θαυμάσιος συμπαγής τρόπος αναπαραγωγής αλληλουχιών γενικευμένων αλλαγές στις συντεταγμένες . Κανονικά, μας επιτρέπουν να κάνουμε αυθαίρετες περιστροφές, διαστολές / συστολές και αντανακλάσεις, αλλά με λίγη φινέτσα μπορούμε επίσης να κάνουμε μεταφράσεις. Ουσιαστικά, αυτό σημαίνει ότι μπορείτε εύκολα να εκτελέσετε οποιοδήποτε λογικός αλλαγή που θέλετε, συμπεριλαμβανομένης της κίνησης μιας κάμερας ή της ανάπτυξης ενός αντικειμένου. Με πολλαπλασιάζοντας τους πίνακες μας με ένα διάνυσμα αντιπροσωπεύοντας τις συντεταγμένες μας, μπορούμε να παράγουμε αποτελεσματικά το νέο σύστημα συντεταγμένων.
ο Μήτρα Η τάξη που παρέχεται από το OpenGL παρέχει έναν αριθμό έτοιμων τρόπων υπολογισμού μητρών που θα χρειαζόμαστε, αλλά η κατανόηση του τρόπου λειτουργίας τους είναι μια έξυπνη ιδέα ακόμη και όταν εργάζεστε με απλούς μετασχηματισμούς.
Πρώτον, μπορούμε να εξετάσουμε γιατί θα χρησιμοποιούμε τεσσάρων διαστάσεων διανύσματα και πίνακες για την αντιμετώπιση συντεταγμένων. Αυτό στην πραγματικότητα πηγαίνει πίσω στην ιδέα της χρήσης των συντεταγμένων για να μπορούμε να κάνουμε μεταφράσεις: ενώ μια μετάφραση σε τρισδιάστατο χώρο είναι αδύνατη χρησιμοποιώντας μόνο τρεις διαστάσεις, η προσθήκη μιας τέταρτης διάστασης επιτρέπει την ικανότητα.
Για να το δείξουμε αυτό, μπορούμε να χρησιμοποιήσουμε μια πολύ βασική γενική κλίμακα / μήτρα μετάφρασης:
Ως σημαντική σημείωση, οι πίνακες OpenGL είναι ανά στήλη, οπότε αυτός ο πίνακας θα γράφεται ως onSurfaceCreated(GL10 gl, EGLConfig config)
, ο οποίος είναι κάθετος στον τρόπο που συνήθως διαβάζεται. Αυτό μπορεί να εξορθολογιστεί διασφαλίζοντας ότι τα διανύσματα, που εμφανίζονται σε πολλαπλασιασμό ως στήλη, έχουν την ίδια μορφή με πίνακες.
Οπλισμένοι με αυτήν τη γνώση των πινάκων, μπορούμε να επιστρέψουμε στο σχεδιασμό του Renderer. Συνήθως, θα δημιουργήσουμε έναν πίνακα σε αυτήν την τάξη που σχηματίζεται από το προϊόν τριών πινάκων: Μοντέλο, Προβολή και προβολή. Αυτό θα ονομάζεται, κατάλληλα, ένα MVPMatrix. Μπορείτε να μάθετε περισσότερα για τις λεπτομέρειες εδώ , καθώς πρόκειται να χρησιμοποιήσουμε ένα πιο βασικό σύνολο μετασχηματισμών - το σετ Mandelbrot είναι ένα δισδιάστατο μοντέλο πλήρους οθόνης και δεν απαιτεί πραγματικά την ιδέα μιας κάμερας.
Οι επιτρεπτικές άδειες ελεύθερου λογισμικού επιλέγουν τρεις
Αρχικά, ας ξεκινήσουμε την τάξη. Θα πρέπει να εφαρμόσουμε το απαιτούμενες μεθόδους για τη διεπαφή Renderer: onSurfaceChanged(GL10 gl, int width, int height)
, onDrawFrame(GL10 gl)
, και public class FractalRenderer implements GLSurfaceView.Renderer { //Provide a tag for logging errors private static final String TAG = 'FractalRenderer'; //Create all models private Fractal mFractal; //Transformation matrices private final float[] mMVPMatrix = new float[16]; //Any other private variables needed for transformations @Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { // Set the background frame color GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //Instantiate all models mFractal = new Fractal(); } @Override public void onDrawFrame(GL10 unused) { //Clear the frame of any color information or depth information GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); //Create a basic scale/translate matrix float[] mMVPMatrix = new float[]{ -1.0f/mZoom, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f/(mZoom*mRatio), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -mX, -mY, 0.0f, 1.0f}; //Pass the draw command down the line to all models, giving access to the transformation matrix mFractal.draw(mMVPMatrix); } @Override public void onSurfaceChanged(GL10 unused, int width, int height) { //Create the viewport as being fullscreen GLES20.glViewport(0, 0, width, height); //Change any projection matrices to reflect changes in screen orientation } //Other public access methods for transformations }
. Η πλήρης τάξη θα καταλήξει να μοιάζει με αυτό:
checkGLError
Υπάρχουν επίσης δύο μέθοδοι χρησιμότητας που χρησιμοποιούνται στον παρεχόμενο κώδικα, loadShaders
και public Fractal() { // initialize vertex byte buffer for shape coordinates ByteBuffer bb = ByteBuffer.allocateDirect( // (# of coordinate values * 4 bytes per float) squareCoords.length * 4); bb.order(ByteOrder.nativeOrder()); vertexBuffer = bb.asFloatBuffer(); vertexBuffer.put(squareCoords); vertexBuffer.position(0); // initialize byte buffer for the draw list ByteBuffer dlb = ByteBuffer.allocateDirect( // (# of coordinate values * 2 bytes per short) drawOrder.length * 2); dlb.order(ByteOrder.nativeOrder()); drawListBuffer = dlb.asShortBuffer(); drawListBuffer.put(drawOrder); drawListBuffer.position(0); // Prepare shaders int vertexShader = FractalRenderer.loadShader( GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = FractalRenderer.loadShader( GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); // create empty OpenGL Program mProgram = GLES20.glCreateProgram(); // add the vertex shader to program GLES20.glAttachShader(mProgram, vertexShader); // add the fragment shader to program GLES20.glAttachShader(mProgram, fragmentShader); // create OpenGL program executables GLES20.glLinkProgram(mProgram); }
για να βοηθήσετε στον εντοπισμό σφαλμάτων και στη χρήση shaders.
Σε όλα αυτά, συνεχίζουμε να περνάμε τη σειρά εντολών προς τα κάτω για να ενσωματώσουμε τα διάφορα μέρη του προγράμματος. Έχουμε επιτέλους φτάσει στο σημείο όπου μπορούμε να γράψουμε τι πραγματικά είναι το πρόγραμμά μας κάνει , αντί για το πώς μπορούμε να κάνουμε θεωρητικές αλλαγές σε αυτό. Όταν το κάνουμε αυτό, πρέπει να δημιουργήσουμε μια κλάση μοντέλου που να περιέχει τις πληροφορίες που πρέπει να εμφανίζονται για οποιοδήποτε δεδομένο αντικείμενο στη σκηνή. Σε σύνθετες τρισδιάστατες σκηνές, αυτό θα μπορούσε να είναι ένα ζώο ή ένα τικ, αλλά θα κάνουμε ένα fractal ως ένα πολύ απλούστερο 2D παράδειγμα.
Στα μαθήματα μοντέλων, γράφουμε ολόκληρη την τάξη - δεν υπάρχουν σούπερ γυαλιά που πρέπει να χρησιμοποιηθούν. Χρειαζόμαστε μόνο να έχουμε έναν κατασκευαστή και κάποια μέθοδο σχεδίασης που να λαμβάνει παραμέτρους.
Τούτου λεχθέντος, εξακολουθούν να υπάρχουν αρκετές μεταβλητές που πρέπει να έχουμε που είναι ουσιαστικά boilerplate. Ας ρίξουμε μια ματιά στον ακριβή κατασκευαστή που χρησιμοποιείται στην κλάση Fractal:
static float squareCoords[] = { -1.0f, 1.0f, 0.0f, // top left -1.0f, -1.0f, 0.0f, // bottom left 1.0f, -1.0f, 0.0f, // bottom right 1.0f, 1.0f, 0.0f }; // top right private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices
Αρκετά μπουκιά, έτσι δεν είναι; Ευτυχώς, αυτό είναι ένα μέρος του προγράμματος που δεν θα χρειαστεί να αλλάξετε καθόλου, εκτός από το όνομα του μοντέλου. Εφόσον αλλάξετε τις μεταβλητές τάξης κατάλληλα, αυτό θα λειτουργήσει καλά για βασικά σχήματα.
Για να συζητήσουμε τμήματα αυτού, ας δούμε μερικές μεταβλητές δηλώσεις:
squareCoords
Στο (-1,-1)
, καθορίζουμε όλες τις συντεταγμένες του τετραγώνου. Σημειώστε ότι όλες οι συντεταγμένες στην οθόνη παρουσιάζονται ως πλέγμα με (1,1)
κάτω αριστερά και drawOrder
πάνω δεξιά.
Στο 0
, καθορίζουμε τη σειρά των συντεταγμένων με βάση τρίγωνα που θα αποτελούσαν το τετράγωνο. Ιδιαίτερα για συνέπεια και ταχύτητα, το OpenGL χρησιμοποιεί τρίγωνα για να αντιπροσωπεύει όλες τις επιφάνειες. Για να φτιάξετε ένα τετράγωνο, απλώς κόψτε μια διαγώνια (σε αυτήν την περίπτωση, 2
έως ByteBuffers
) για να δώσετε δύο τρίγωνα.
Για να προσθέσετε και τα δύο στο πρόγραμμα, πρέπει πρώτα να τα μετατρέψετε σε buffer raw byte για άμεση διασύνδεση των περιεχομένων του πίνακα με τη διεπαφή OpenGL. Η Java αποθηκεύει πίνακες ως αντικείμενα που περιέχουν πρόσθετες πληροφορίες που δεν είναι άμεσα συμβατές με τους πίνακες C με βάση το δείκτη που χρησιμοποιεί η υλοποίηση του OpenGL. Για να το διορθώσετε, loadShader(int type, String shaderCode)
χρησιμοποιούνται, τα οποία αποθηκεύουν την πρόσβαση στην πρώτη μνήμη του πίνακα.
Αφού τοποθετήσουμε τα δεδομένα για τις κορυφές και τη σειρά κλήρωσης, πρέπει να δημιουργήσουμε τα shaders μας.
Κατά τη δημιουργία ενός μοντέλου, πρέπει να δημιουργηθούν δύο shaders: ένα Vertex Shader και ένα Fragment (Pixel) Shader. Όλες οι shaders είναι γραμμένες σε GL Shading Language (GLSL), η οποία είναι μια γλώσσα που βασίζεται σε C με την προσθήκη ενός αριθμού ενσωματωμένες συναρτήσεις , μεταβλητές τροποποιητές , πρωτόγονα , και προεπιλεγμένη είσοδος / έξοδος . Σε Android, αυτές θα μεταβιβαστούν ως τελικές συμβολοσειρές μέσω const
, μιας από τις δύο μεθόδους πόρων στο Renderer. Ας δούμε πρώτα τους διαφορετικούς τύπους προκριματικών:
εκτίμηση κόστους στη διαχείριση έργων λογισμικού
uniform
: Οποιαδήποτε τελική μεταβλητή μπορεί να δηλωθεί ως σταθερή, ώστε η τιμή της να μπορεί να αποθηκευτεί για εύκολη πρόσβαση. Αριθμοί όπως το π μπορούν να δηλωθούν ως σταθερές εάν χρησιμοποιούνται συχνά σε ολόκληρο το shader. Είναι πιθανό ο μεταγλωττιστής να δηλώσει αυτόματα τις μη τροποποιημένες τιμές ως σταθερές, ανάλογα με την εφαρμογή.varying
: Οι ομοιόμορφες μεταβλητές είναι αυτές που δηλώνονται σταθερές για κάθε μεμονωμένη απόδοση. Χρησιμοποιούνται ουσιαστικά ως στατικά επιχειρήματα για τους shaders σας.attribute
: Εάν μια μεταβλητή δηλώνεται ως μεταβλητή και έχει οριστεί σε ένα shader vertex, τότε παρεμβάλλεται γραμμικά στο shader θραύσματος. Αυτό είναι χρήσιμο για τη δημιουργία κάθε είδους κλίσης στο χρώμα και είναι έμμεσο για αλλαγές βάθους.vec2
: Τα χαρακτηριστικά μπορούν να θεωρηθούν ως μη στατικά ορίσματα σε ένα shader. Υποδηλώνουν το σύνολο εισόδων που είναι ειδικές για την κορυφή και θα εμφανίζονται μόνο σε Vertex Shaders.Επιπλέον, πρέπει να συζητήσουμε δύο άλλους τύπους πρωτόγονων που έχουν προστεθεί:
vec3
, vec4
, mat2
: Διανύσματα κυμαινόμενου σημείου δεδομένης διάστασης.mat3
, mat4
, x
: Πίνακες κυμαινόμενου σημείου δεδομένης διάστασης.Μπορείτε να έχετε πρόσβαση στα διανύσματα από τα συστατικά τους y
, z
, w
και r
ή g
, b
, a
, και vec3 a
. Μπορούν επίσης να δημιουργήσουν οποιοδήποτε φορέα μεγέθους με πολλούς δείκτες: για a.xxyz
, vec4
επιστρέφει a a
με τις αντίστοιχες τιμές mat2 matrix
.
Οι πίνακες και τα διανύσματα μπορούν επίσης να ευρετηριαστούν ως πίνακες και οι πίνακες θα επιστρέψουν ένα διάνυσμα με ένα μόνο στοιχείο. Αυτό σημαίνει ότι για matrix[0].a
, matrix[0][0]
είναι έγκυρη και θα επιστρέψει vec2 a = vec2(1.0,1.0); vec2 b = a; b.x=2.0;
. Όταν εργάζεστε με αυτά, θυμηθείτε ότι λειτουργούν ως πρωτόγονα και όχι ως αντικείμενα. Για παράδειγμα, λάβετε υπόψη τον ακόλουθο κώδικα:
a=vec2(1.0,1.0)
Αυτό αφήνει b=vec2(2.0,1.0)
και b
, που δεν είναι αυτό που θα περίμενε κανείς από τη συμπεριφορά του αντικειμένου, όπου η δεύτερη γραμμή θα έδινε a
δείκτης προς private final String vertexShaderCode = 'attribute vec4 vPosition;' + 'void main() {' + ' gl_Position = vPosition;' + '}';
.
Στο Σετ Mandelbrot, η πλειοψηφία του κώδικα θα βρίσκεται στο shader fragment, το οποίο είναι το shader που τρέχει σε κάθε pixel. Ονομαστικά, οι shaders vertex λειτουργούν σε κάθε κορυφή, συμπεριλαμβανομένων των χαρακτηριστικών που θα είναι ανά βάση, όπως αλλαγές στο χρώμα ή το βάθος. Ας ρίξουμε μια ματιά στην εξαιρετικά απλή σκίαση κορυφής για ένα φράκταλ:
gl_Position
Σε αυτό, gl_Position
είναι μια μεταβλητή εξόδου που ορίζεται από το OpenGL για την καταγραφή των συντεταγμένων μιας κορυφής. Σε αυτήν την περίπτωση, περνάμε σε μια θέση για κάθε κορυφή στην οποία ορίζουμε vPosition
. Στις περισσότερες εφαρμογές, πολλαπλασιάζουμε MVPMatrix
από ένα fragmentShaderCode
, μετατρέποντας τις κορυφές μας, αλλά θέλουμε το fractal να είναι πάντα πλήρης οθόνη. Όλοι οι μετασχηματισμοί θα γίνουν με ένα τοπικό σύστημα συντεταγμένων.
Το Fragment Shader πρόκειται να είναι εκεί όπου γίνεται το μεγαλύτερο μέρος της εργασίας για τη δημιουργία του σετ. Θα ορίσουμε precision highp float; uniform mat4 uMVPMatrix; void main() { //Scale point by input transformation matrix vec2 p = (uMVPMatrix * vec4(gl_PointCoord,0,1)).xy; vec2 c = p; //Set default color to HSV value for black vec3 color=vec3(0.0,0.0,0.0); //Max number of iterations will arbitrarily be defined as 100. Finer detail with more computation will be found for larger values. for(int i=0;i4.0){ //The point, c, is not part of the set, so smoothly color it. colorRegulator increases linearly by 1 for every extra step it takes to break free. float colorRegulator = float(i-1)-log(((log(dot(p,p)))/log(2.0)))/log(2.0); //This is a coloring algorithm I found to be appealing. Written in HSV, many functions will work. color = vec3(0.95 + .012*colorRegulator , 1.0, .2+.4*(1.0+sin(.3*colorRegulator))); break; } } //Change color from HSV to RGB. Algorithm from https://gist.github.com/patriciogonzalezvivo/114c1653de9e3da6e1e3 vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 m = abs(fract(color.xxx + K.xyz) * 6.0 - K.www); gl_FragColor.rgb = color.z * mix(K.xxx, clamp(m - K.xxx, 0.0, 1.0), color.y); gl_FragColor.a=1.0; }
στα ακόλουθα:
fract
Μεγάλο μέρος του κώδικα είναι απλώς τα μαθηματικά και ο αλγόριθμος για το πώς λειτουργεί το σετ. Σημειώστε τη χρήση πολλών ενσωματωμένων συναρτήσεων: abs
, mix
, sin
, clamp
, και dot
, τα οποία λειτουργούν όλα σε διανύσματα ή βαθμίδες και διανύσματα επιστροφής ή κλίμακες. Επιπλέον, draw
χρησιμοποιείται το οποίο παίρνει διανυσματικά ορίσματα και επιστρέφει μια βαθμίδα.
Τώρα που έχουμε ρυθμίσει τα shaders μας για χρήση, έχουμε ένα τελευταίο βήμα, το οποίο είναι να εφαρμόσουμε το public void draw(float[] mvpMatrix) { // Add program to OpenGL environment GLES20.glUseProgram(mProgram); // get handle to vertex shader's vPosition member mPositionHandle = GLES20.glGetAttribLocation(mProgram, 'vPosition'); mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, 'uMVPMatrix'); //Pass uniform transformation matrix to shader GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0); //Add attribute array of vertices GLES20.glEnableVertexAttribArray(mPositionHandle); GLES20.glVertexAttribPointer( mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); // Draw the square GLES20.glDrawElements( GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer); // Disable vertex array GLES20.glDisableVertexAttribArray(mPositionHandle); FractalRenderer.checkGlError('Test'); }
λειτουργία στο μοντέλο μας:
uniform
Η συνάρτηση μεταβιβάζει όλα τα ορίσματα στους shaders, συμπεριλαμβανομένου του attribute
μήτρα μετασχηματισμού και το double
θέση.
Μετά τη συναρμολόγηση όλων των τμημάτων του προγράμματος, μπορούμε τελικά να το δοκιμάσουμε. Υπό την προϋπόθεση ότι χειρίζεται την κατάλληλη υποστήριξη αφής, θα ζωγραφίζονται απολύτως μαγευτικές σκηνές:
Εάν μεγεθύνετε λίγο περισσότερο, αρχίζουμε να παρατηρούμε μια ανάλυση στην εικόνα:
Αυτό δεν έχει απολύτως καμία σχέση με τα μαθηματικά του σετ πίσω από αυτό και όλα έχουν να κάνουν με τον τρόπο αποθήκευσης και επεξεργασίας των αριθμών στο OpenGL. Ενώ η πιο πρόσφατη υποστήριξη για float
Έχει γίνει ακρίβεια, το OpenGL 2.0 δεν υποστηρίζει εγγενώς τίποτα περισσότερο από το precision highp float
s. Τα ορίσαμε συγκεκριμένα ως τα πλωτήρες υψηλότερης ακρίβειας που διατίθενται με double
στο shader μας, αλλά ακόμη και αυτό δεν είναι αρκετά καλό.
Για να επιλύσετε αυτό το ζήτημα, ο μόνος τρόπος θα ήταν μιμούμαι float
s χρησιμοποιώντας δύο GLSurfaceView
s. Αυτή η μέθοδος εμπίπτει πραγματικά σε μια τάξη μεγέθους της πραγματικής ακρίβειας ενός εγγενώς εφαρμοζόμενου, αν και υπάρχει ένα αρκετά σοβαρό κόστος για την ταχύτητα. Αυτό θα αφεθεί ως άσκηση στον αναγνώστη, εάν κάποιος επιθυμεί να έχει υψηλότερο επίπεδο ακρίβειας.
Με λίγες τάξεις υποστήριξης, το OpenGL μπορεί να διατηρήσει γρήγορα την απόδοση σύνθετων σκηνών σε πραγματικό χρόνο. Η δημιουργία μιας διάταξης που αποτελείται από ένα Renderer
, τη ρύθμιση της