יום רביעי, דצמבר 29

גרפיקה בתלת מימד\openGL: חלק 1.


OpenGL הוא אחד הכלים שבהחלט נותנים את ההרגשה של "וואואו". בעזרת ה- OpenGL API אפשר לצייר גרפיקה תלת מימדית. אנדרואיד תומך  בגרסה המצומצמת של OpenGL - ה-OpenGL ES המיועדת ל-Embedded Systems. למעשה הוא לא תומך בכל הפונקציונליות של ES.
עקרונות העבודה עם OpenGL:
OpenGL ES יודעת לצייר צורות בסיסיות:  קווים ומשלולשים.  ה-OpenGL המלא כולל גם מלבנים כחלק מהצורות הבסיסיות, אך אנו נאלץ לצייר את כל הצורות, ובכלל זה מלבנים, בעזרת קומבינציה של משולשים.
הציור מתבצע ע"י קביעת קודקודים (Vertices), במרחב התלת מימדי וחיבורן עם קוים ומשולשים.

 בהמשך מובא הסבר רקע על ה-openGL. הסבר מקיף ניתן למצוא בספר האדום - המדריך הרישמי ללימוד openGL.
המדל המרחבי של OpenGL
המודל לפיו מצייר ה-OpenGL את האובייקט הגרפי, דומה לזה של מצלמה. הראיה המרחבית של OpenGL מאופינת ע"י שלוש מתודות:
  1. gluLookAt 
  2.  glFrustum
  3. glViewport


 gluLookAt - מגדירה את צורת ההסתכלות על האובייקט, וכוללת שלושה פרמטרים:
  1. מיקום המצלמה
  2. מיקום האובייקט
  3. הפוזיציה של המצלמה, למשל: מאונכת (על ציר x), מאוזנת, וכו.

הנה תמונה להמחשת הרעיון של lookAt. את התמונות העתקתי מהספר האדום - ,The Official Guide to Learning OpenGL, Version 1.1





בתמונה הנ"ל, ערכי  3 הפרמטרים של LookAt הם:
  1. מיקום המצלמה: x,y,z = 4,2,1
  2. מיקום האובייקט: x,y,x = 2,4,-3
  3. הפוזיציה של המצלמה: מסובבת ב-45 מעלות. הוקטור x,y,z=2,2,-1 למשל, יכול לבטא זווית סיבוב זו. 
ה-API יראה כך:

(gluLookAt(gl,4,2,1,2,4,-3,2,2,-1

הערכים הדיפולטיבים של הצופה, האובייקט והפוזיציה של המצלמה  מוצגים בשרטוט הבא:



המצלמה נמצאת בנקודה 0,0,0
האובייקט נמצא ב- 100-,0,0
המצלמה מיושרת לאורך ציר y.
זה אקוויולנטי ל:

(gluLookAt(0,0,0,0,0,-100,0,1,0

glFrustum

מתאר את התא במרחב בו נמצא העצם.
נשתמש להמחשה בשרטוט הבא:

בתמונה הנ"ל מתואר תא הנפח בתוכו נמצא האובייקט: מדובר בפרמידה הקטומה שבציור. הוא מוגדר ע"י הפרמטרים הבאים:
  1. מיקום הצד הימני של תא הנפח. תא הנפח לא מלבני, והמימדים הנ"ל נמדדים על המשטח העליון של הפירמידה הקטומה.
  2. מיום הצד השמאלי של תא הנפח.
  3. מיקום החלק העליון של הבסיס הקטן.
  4. מיקום החלק התחתון.של הבסיס הקטן.
  5. המרחק של הבסיס הקטן למצלמה.
  6. המרחק של הבסיס הגדול (הרחוק) למצלמה.

לדוגמא:
ratio = width/height
(gl.glFrustumF(-ratio,ratio,1,-1,5,10

glViewport


 מתודה זו מקבלת כפרמטרים את גודל המלבן שעל המסך  עליו תוטל התמונה.
 הפרמטרים של המתודה:
קואורדינטת x של הנקודה השמאלית התחתונה של המסך.
קואורדינטת y של של הנקודה השמאלית התחתונה.
רוחב המלבן
אורך המלבן.



שמות ה-API של openGL
השמות מתחילים ב-gl ולאחריו שם המתודה.
השם יכול לסתיים במספר שמסמן את מס הארגומנטים או מס המימדים שהמתודה מטפלת בהם.
לאחר מכן מתווספת האות שמתארת את סוג בפרמטרים למשל f עבור float, או i עבור integer.


נסקור מספר מתודות חשובות נוספות:

  •  glVertexPointer
  •  glDrawElements

glVertexPointer
זו המתודה שמתארת את הקואורדינטות שירכיבו את הציור, והיא מופעלת לפני המתודה שמציירת.
היא מקבלת את 4 הפרמטרים הבאים:
  1. מספר המימדים המרכיבים כל קואורדינטות. 2- עבור קואורדינטות דו מימדיות, 3 עבור תלת, כלומר x,y,z.
  2. סוג הפרמטרים של הקואורדינטות, למשל Float, Short וכו,
  3. גודל הצעד - מרווח הזיכרון הפנוי בין הנקודות. ניתן להוסיף נתונים אחרי כל נקודה, למשל צבע. הפרמטר הנ"ל מאפשר לקפוץ בין הנקודות. אם הנקודות מסודרות אחת אחרי השניה ללא מרווח, גודל הצעד הוא 0.
  4. מצביע לתחילת ה-buffer בו נזמרות הנקודות.
דוגמא:

(glVertexPointer(3,GL10.GL_FLOAT,0,mVertexBuffer
עוד נדון על ה-buffer בתוך הדוגמא.
glDrawElements 
זו המתודה שמפעילה ציור של אלמנטים.

המתודה מקבלת ארבעה פרמטרים:
  1. סוג הצורה: נקודה, קו או משולש.
  2.  מספר האינדקסים = מספר הנקודות שיצוירו בפועל.
  3. סוג משתנה ה-index: למשל, Short.
  4.  ה-buffer שמכיל את האינדקסים, שמצביעים על הנקודות אותן נצייר.
נסביר את נושא האינדקסים בתוך הדוגמא.


נעבור לדוגמא, שמתארת ציור של ריבוע דו מימדי  (z=0).

הנה תמונת המסך שמתקבל עם הרצת התוכנית:


בתמונה מופיע הריבוע הדו מימדי, ועליו טקסטורה (Texture) שמוכבת מתמונת icon של אנדרואיד. הריבוע מסתובב באופן קבוע במרחב התלת מימדי- סביב הצירים x,y,z, עם זמן מחזור של 10 שניות.
ה-class שמחבר את ה-openGL ל-view של אנדרואיד הוא ה-GLSurfaceView. הוא מנהל Thread מיוחד האחראי לפעולת הציור. זהו ה-renderer.

הנה הפעולות שצריך לבצע לבניית אפליקצית גרפיקה של openGL:

  • צור view עם GLSurfaceView.
  • ממש את ה-renderer Interface.  ה-GLSurfaceView.Renderer הוא Interface הכולל 3 מתודות אותן צריך לממש:
  1. onSurfaceCreated
  2. onSurfaceChanged
  3. onDrawFrame
שמן של 3 מתודות ה-callback הנ"ל מרמז על תהליך הפעלתן.

  • צור class עזר עבור ה-renderer שיתאר את האובייקט אותו נצייר.
 
ניגש למלאכת הקידוד
הקוד שלנו מכיל 3 קבצים:
  1. OpenGlPlayActivity.java : מכיל את הActivity ומהווה מעטפת  שיוצרת את ה-view.
  2. SquareRenderer.java - מימוש שלושת ה-callbacks של GLSurfaceView.Renderer.
  3. Square.java - מגדיר את הקואורדינטות של הריבוע, ומספק מתודה לציור הריבוע.
נתחיל בהתחלה, ונשתדל להמשיך לפי הסדר.
OpenGlPlayActivity.java 



package com.ahs.androheb.openglplay;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
  1. public class OpenGlPlayActivity extends Activity {
  2.     private GLSurfaceView mGLView;
  3.     @Override
  4.     protected void onCreate(Bundle savedInstanceState) {
  5.         super.onCreate(savedInstanceState);
  6.         mGLView = new GLSurfaceView(this);
  7.         mGLView.setEGLConfigChooser(false);
  8.         mGLView.setRenderer(new SquareRenderer(this));
  9.         setContentView(mGLView);
  10.        /* integrate GLSurfaceView with some other views, e.g. TextView */
            TextView textView = new TextView(this);
            textView.setText("An Introduction to openGL");
            LinearLayout layout = new LinearLayout(this);
            layout.addView(textView);
            layout.setGravity(Gravity.CENTER_HORIZONTAL);
            textView.setTextSize(26f);
            textView.setTextColor(0xff000000);
            this.addContentView(layout, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
     
  11.     }
  12.     @Override
  13.     protected void onPause() {
  14.         super.onPause();
  15.         mGLView.onPause();
  16.     }
  17.     @Override
  18.     protected void onResume() {
  19.         super.onResume();
  20.         mGLView.onResume();
  21.     }
  22.  
  23. }

ה-Activity class מכיל 3 מתודות:
OnCreate - בונה את ה-view ושולח אותו לתצוגה.
onPause ו-onResume, שהן callbacks של ה-life cycle של Activity, אליהן צריך לחבר את מתודות ה-life cycle של ה-GLview, כך שה-GLview יתחיל ויפסיק לפעול ב-pause ו-resume בהתאמה.

נתבונן ב-onCreate:
שלב 1: יצירת view מסוג GLSurfaceView. הוא יחבר את ה-openGL למערכת.



     mGLView = new GLSurfaceView(this);

שלב 2: האם להשתמש בקונפיגורציה אחרת מאשר הדיפולטיבית? התשובה היא לא. לא נרחיב כאן לגבי מכלולל הפרמטרים הדיפולטיבים.

        mGLView.setEGLConfigChooser(false);

שלב 3: אתחל את ה-renderer.  זהו ה-interface שמכיל את שלושת ה-callbacks שבינתיים הפכו מפורסמים...מיד נעבור אליו.


        mGLView.setRenderer(new SquareRenderer(this));
שלב 4 ואחרון: חבר את הview לתצוגה.
        setContentView(mGLView);


בשורות הבאות משולב view נוסף עם ה-GLSurfaceView. בדקתי מספר דרכים לביצוע השילוב, אך בכל דרך בה הוספתי   את ה-GLSurfaceView לתוך layout או view אחר, הוא תמיד השתלט על כל התצוגה.
הצלחתי להציג view נוספים רק מעל ה-GLSurfaceView . הבעיה: לא ניתן לשלוט על ה-layout של ה-GLSurfaceView עצמו. אשמח לתגובות בעניין!.


SquareRenderer.java

הנהו:

import java.io.IOException;
import java.io.InputStream;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.os.SystemClock;
import android.util.Log;

  1. public class SquareRenderer implements GLSurfaceView.Renderer{
  2.        private Context mContext;
  3.         private Square mSquare;
  4.         private int mTextureID;
  5.     public SquareRenderer(Context context) {
  6.         mContext = context;
  7.         mSquare = new Square();
  8.     }
  9.     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  10.          gl.glDisable(GL10.GL_DITHER);
  11.         gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
  12.                 GL10.GL_FASTEST);
  13.         gl.glClearColor(1f, 1f, 1f, 1);
  14.         gl.glShadeModel(GL10.GL_SMOOTH);
  15.         gl.glEnable(GL10.GL_DEPTH_TEST);
  16.         gl.glEnable(GL10.GL_TEXTURE_2D);
  17.         textureSetup( gl,  mContext, R.drawable.icon);
  18.      }
  19.     public void onDrawFrame(GL10 gl) {
  20.         gl.glDisable(GL10.GL_DITHER);
  21.         gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
  22.                 GL10.GL_MODULATE);
  23.         gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
  24.         gl.glMatrixMode(GL10.GL_MODELVIEW);
  25.         gl.glLoadIdentity();
  26.         GLU.gluLookAt(gl, 0, 0, -7, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
  27.         gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
  28.         gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
  29.         gl.glActiveTexture(GL10.GL_TEXTURE0);
  30.         gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
  31.         gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
  32.                 GL10.GL_REPEAT);
  33.         gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
  34.                 GL10.GL_REPEAT);
  35.        long time = SystemClock.uptimeMillis() % 10000L;
  36.         float angle = 0.036f * ((int) time);
  37.          gl.glRotatef(angle, 1, 1, 1);
  38.         mSquare.draw(gl);
  39.     }
  40.     public void onSurfaceChanged(GL10 gl, int w, int h) {
  41.         gl.glViewport(0, 0, w, h);
  42.         float ratio = (float) w / h;
  43.         gl.glMatrixMode(GL10.GL_PROJECTION);
  44.         gl.glLoadIdentity();
  45.         gl.glFrustumf(-ratio, ratio, -1, 1, 3, 10);
  46.     }
  47.     private void textureSetup(GL10 gl, Context context, int resource){
  48.         int[] textures = new int[1];
  49.         gl.glGenTextures(1, textures, 0);
  50.         mTextureID = textures[0];
  51.         gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
  52.        gl.glTexParameterf(GL10.GL_TEXTURE_2D, //GL10.GL_TEXTURE_MIN_FILTER,
  53.   //              GL10.GL_NEAREST);
  54.         gl.glTexParameterf(GL10.GL_TEXTURE_2D,
  55.                 GL10.GL_TEXTURE_MAG_FILTER,
  56.                 GL10.GL_LINEAR);
  57.         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
  58.                 GL10.GL_CLAMP_TO_EDGE);
  59.         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
  60.                 GL10.GL_CLAMP_TO_EDGE);
  61.         gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
  62.                 GL10.GL_REPLACE);
  63.         InputStream is = mContext.getResources()
  64.          .openRawResource(R.drawable.icon);
  65.         Bitmap bitmap;
  66.         try {
  67.             bitmap = BitmapFactory.decodeStream(is);
  68.         } finally {
  69.             try {
  70.                 is.close();
  71.             } catch(IOException e) {
  72.                 Log.e(getClass().getSimpleName(), "Bitmap factory decode prob");            
  73.                 }
  74.         }
  75.         GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
  76.         bitmap.recycle();
  77.    }

}

ה-class מממש את GLSurfaceView.Renderer Interface. מלבד ה-constructor הוא ממש שלושה callbacks של ה-Interface הנ"ל. וגם מתודת עזר textureSetup.

ה-constructor
  1.   public SquareRenderer(Context context) {
  2.         mContext = context;
  3.         mSquare = new Square();
  4.     }
שורה 3: איתחול האוביקט מסוג Square. נגיע ל-class הזה בהמשך, רק נציין שבתהליך האיתחול, ה-buffers מכילים את הקואורדינטות הדרושות לציור הריבוע, ומכונות לצייר כשתינתן הפקודה.


onSurfaceCreated

ה-callback מופעל עם יצירת משטח הציור.
כאן מופעלות מספר מתודות קונפיגורציה.


  1. public void onSurfaceCreated(GL10 gl, EGLConfig config) {
בטל את הכנסת Dither לצבעים. זו היכולת היחידה שברירת המחדל שלה היא enabled.


  1.         gl.glDisable(GL10.GL_DITHER);
הפרמטר GL_PERSPECTIVE_CORRECTION_HINT מתייחס לאיכות הצבע, קואורדינטות הטקסטורה וכדומה. הפרמטר השני - FASTEST קובע שהטיפול יעשה בצורה היעילה ביותר. אפשרויות אחרות לבחירה:
  • NICEST
  • DONT_CARE

  1.         gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
  2.                 GL10.GL_FASTEST);
 מחיקת המשטח\צביעתו מחדש. הפרמטרים הם RGBA כשכל שדה מקבל ערכים בתחום 0-1, כך שכאן הרקע נצבע בלבן אטום.
  1.         gl.glClearColor(1f, 1f, 1f, 1);
 קביעת טכניקת ההצללה. אפשרויות הבחירה:
GL_FLAT
GL_SMOOTH

 
  1.         gl.glShadeModel(GL10.GL_SMOOTH);
ה-Depth Buffer מבצע מעקב אחר המרחק בין כל פיקסל שעל המסך לבין ה-view point. עבור כל פיקסל שעל המסך ולפי המיקום מעדכן את ערך ה-Depth של הפיקסל. כדי שזה יתבצע יש לעשות שתי פעולות:
1. לאפשר את הבדיקה עם glEnable.
2. לעשות reset ל-DEPTH BUFFER לפני כל ציור מסך - ראה glClear במתודה onDrawBuffer.
  1.         gl.glEnable(GL10.GL_DEPTH_TEST);


עוד פקודה הכרחית, אם עובדים עם texture: אפשור העבודה.
  1.         gl.glEnable(GL10.GL_TEXTURE_2D);
שתי הפקודות האחרונות קשורות ל-texture. שאר הפקודות הקשורות ל-texture נמצאות במתודת עזר זו. נסקור אותה מיד.

  1.         textureSetup(gl, mContext, R.drawable.icon);
  2.       }



texture setup

זוהי מתודת העזר שנקראת ע"י onSurfaceCreate ומכילה איתחולים של הקשורים ל-texture.

  1. private void textureSetup(GL10 gl, Context context, int resource){
  2.         int[] textures = new int[1];
יצירת שם של texture ושמירתו ב-textures לצורך הצמדתו כמזהה ל-texture שניצור. הפרמטר הראשון (1) - מספר השמות אותם מבקשים לקבלץ

  1.         gl.glGenTextures(1, textures, 0);
שתי השורות הבאות: חיבור השם שקיבלנו ל-texture.
        mTextureID = textures[0];
        gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);

השורות הבאות: קינפוגים של פרמטרים:


GL_TEXTURE_MAG_FILTER: הפיקסל עליו מתבצעת הטקסטורה מתמפה לאיזור קטן או שווה לאלמנט טקסטורה. 
שתי האפשרויות מקונפגות כאן (הראשונה מסומנת כהערה) :
  1. NEAREST
  2. LINEAR
משמעות NEAREST: הבא את ערך הטקסטורה של הפיקסל הכי קרוב.
 LINEAR: מבצע מיצוע של הערכים וכך יוצר החלקה. נותן תוצאה טובה יותר, אך כבד יותר לביצוע.
 
    //    gl.glTexParameterf(GL10.GL_TEXTURE_2D, //GL10.GL_TEXTURE_MIN_FILTER,
            //    GL10.GL_NEAREST);



        gl.glTexParameterf(GL10.GL_TEXTURE_2D,
                GL10.GL_TEXTURE_MAG_FILTER,
                GL10.GL_LINEAR);
קביעת התנהגות הטקסטורה:
CLAMP - קטימה.
WRAP - ליפוף.
פקודה אחת מתייחסת לקואורדינטות S, והשניה ל-T. כאשר s,t הן הקואורדינטות של הטקסטורה המקבילות ל-x,y,z של הקודקודים.

        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
                GL10.GL_CLAMP_TO_EDGE);

        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
                GL10.GL_CLAMP_TO_EDGE);
GL_TEXTURE_ENV_MODE יכול לקבל את הערכים:
GL_BLEND
GL_MODULATE
GL_REPLACE
GL_COMBINE

המשמעות של REPLACE: ה-renderer תעלם  מהצבע הקיים בצבע של הטקסטורה.
דוגמא:

(glColor3f(1,0,0
אם הפקודה REPLACEקיימת, אז אלמנט הטקסטורה לא יצבע אדום.

        gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
                GL10.GL_REPLACE);
 השורות הבאות שולפות את הציור מה-R class, מיצרות ממנו bitmap

        InputStream is = mContext.getResources()
         .openRawResource(R.drawable.icon);
        Bitmap bitmap;
        try {
            bitmap = BitmapFactory.decodeStream(is);
        } finally {
            try {
                is.close();
            } catch(IOException e) {
                Log.e(getClass().getSimpleName(), "Bitmap factory decode prob");            
                }
        }
GLUtils הוא class של אנדרואיד לממשק עבור openGL.
textImage2D מיצר texture מה-bitmap.

        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
שיחרור הזיכרון שה-bitmap תפס:
        bitmap.recycle();
   }
 

onSurfaceChanged

   public void onSurfaceChanged(GL10 gl, int w, int h) {

עידכון גודל המסך לפי פרמטרים החדשים:
        gl.glViewport(0, 0, w, h);
        float ratio = (float) w / h;

שלושה ערכים אפשריים ל-MatrixMode:
ModelView
Projection
Texture   
Projection
לא ניכנס לנושא מטריצות טרנסםורמציה בשלב זה.
  gl.glMatrixMode(GL10.GL_PROJECTION);
 החלפת המטריצה הנוכחית במטריצת היחידה:

        gl.glLoadIdentity();
קביעה מחדש של ה-frustum - היות שהמימדים השתנו.
        gl.glFrustumf(-ratio, ratio, -1, 1, 3, 10);
    }
 onDrawFrame
אתייחס למספר רק לחלק מהמתודות:
public void onDrawFrame(GL10 gl) {
        gl.glDisable(GL10.GL_DITHER);
        gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
                GL10.GL_MODULATE);
ריסט ל-buffers. הם יחושבו מחדש.
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
 ראה הסבר על LookOut בתחילת הפוסט.
        GLU.gluLookAt(gl, 0, 0, -7, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        gl.glActiveTexture(GL10.GL_TEXTURE0);
הצמדת ה-buffer של ה-Texture:
        gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
        gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
                GL10.GL_REPEAT);
        gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
                GL10.GL_REPEAT);
סיבוב הציור. כל 10שניות יושלם סיבוב של 3600 מעלות, על שלושת הצירים, וזוית הרוטציה תתאפס.
       long time = SystemClock.uptimeMillis() % 10000L;
        float angle = 0.036f * ((int) time);
         gl.glRotatef(angle, 1, 1, 1);
קריאה למתודת הציור בתוך ה-Square. מיד נגיע לזה.
        mSquare.draw(gl);
    }
נעבור ל-class האחרון:
Square


import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

  1. public class Square {
  2.     private final static int VERTS = 4;
  3.     private FloatBuffer mFVertexBuffer;
  4.     private FloatBuffer mTexBuffer;
  5.     private ShortBuffer mIndexBuffer;
  6.     public Square() {
  7.         ByteBuffer vbb = ByteBuffer.allocateDirect((VERTS) * 3 * 4);
  8.         vbb.order(ByteOrder.nativeOrder());
  9.         mFVertexBuffer = vbb.asFloatBuffer();
  10.         ByteBuffer tbb = ByteBuffer.allocateDirect((VERTS) * 2 * 4);
  11.         tbb.order(ByteOrder.nativeOrder());
  12.         mTexBuffer = tbb.asFloatBuffer();
  13.         ByteBuffer ibb = ByteBuffer.allocateDirect((6* 2);
  14.         ibb.order(ByteOrder.nativeOrder());
  15.         mIndexBuffer = ibb.asShortBuffer();
  16.         float vetexEdge = 1f;
  17.         float coords[] = {  -vetexEdge, -vetexEdge, 0,  -vetexEdge, vetexEdge, 0,
  18.                           vetexEdge, vetexEdge, 0,vetexEdge, -vetexEdge, 0 };
  19.         for (int i = 0; i <VERTS; i++) {
  20.             mFVertexBuffer.put(coords[i*3+0]);
  21.             mFVertexBuffer.put(coords[i*3+1]);
  22.             mFVertexBuffer.put(coords[i*3+2]);
  23.         }
  24.         for (int i = 0; i < VERTS; i++) {
  25.            mTexBuffer.put(coords[3*i+0]*2);
  26.            mTexBuffer.put(coords[3*i+1]*2);
  27.         }
  28.          mIndexBuffer.put((short)(0));
            mIndexBuffer.put((short)(1));
              mIndexBuffer.put((short)(2));
            mIndexBuffer.put((short)(2));
              mIndexBuffer.put((short)(3));
            mIndexBuffer.put((short)(0));
  29.             mFVertexBuffer.position(0);
  30.             mTexBuffer.position(0);
  31.             mIndexBuffer.position(0);
  32.         }
  33.         public void draw(GL10 gl) {
  34.             gl.glFrontFace(GL10.GL_CCW);
  35.             gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
  36.             gl.glEnable(GL10.GL_TEXTURE_2D);
  37.             gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexBuffer);
  38.             gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 6,
  39.                     GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
  40.         }
  41.     }
ה-class כולל שתי מתודות:
  1. Constructor
  2. draw
 Class Constructor

כאן מתבצעת הכנתם של שלושה buffers:
  1. ה-buffer עם הקודקודים שמתארים את הריבוע.
  2. ה-buffer עם האינדקסים המתארים את סדר ציור המשולשים.
  3. ה-buffer המכיל את נקודות ה-texture.

3 ה-buffers מסודרים בפורמט של bytes.
השימוש הוא ב-buffers מתוך חבילת nio

נתחיל עם בניית ה-buffer הראשון:
  1.   ByteBuffer vbb = ByteBuffer.allocateDirect((VERTS) * 3 * 4);
  2.         vbb.order(ByteOrder.nativeOrder());
  3.         mFVertexBuffer = vbb.asFloatBuffer();
 שורה 1: מקצה את גודל ה-buffer ב-bytes. הגודל של ה-buffer הוא VERTS*3*4. הסבר:
 VERTS - מספר הקודקודים.  עבור הריבוע VERTS=4
כל קודקוד מאופיין ע"י קואורדינטות x,y,z
כל קואורדינטה היא מסוג float ותופסת 4 bytes.
סהכ 48 bytes.

שורה 2: סידור ה-bytes - לnativeOrder מביא את הסדר עפי הפלטפורמה.



יצירת ה-buffer עבור הטקסטורה נעשית באופן דומה, רק כאן מדובר במשטח דו מימדי:
  1.   ByteBuffer tbb = ByteBuffer.allocateDirect((VERTS) * 2 * 4);
  2.         tbb.order(ByteOrder.nativeOrder());
  3.         mTexBuffer = tbb.asFloatBuffer();

כעת נעבור לאינדקסים.
  1. ByteBuffer ibb = ByteBuffer.allocateDirect((6* 2);
  2.         ibb.order(ByteOrder.nativeOrder());
  3.         mIndexBuffer = ibb.asShortBuffer();
אני דוחה את ההסבר על האינדקסים עוד במעט...
הכנסת קודקודי הריבוע ל-buffer:

  for (int i = 0; i <VERTS; i++) {
            mFVertexBuffer.put(coords[i*3+0]);
            mFVertexBuffer.put(coords[i*3+1]);
            mFVertexBuffer.put(coords[i*3+2]);
        }
coords הוא מערך המכיל 4 נקודות עם הקואורדינטות הבאות:
  1. -1,-1,0
  2. -1,1,0
  3. 1,1,0
  4. 1,-1,0


הלופ הנ"ל מכניס את כל הקואורדינטות ל-buffer.
התהליך דומה עם ה-buffer של ה-texture.
נעבור לאינדקסים.

ברצוננו לצייר ריבוע. המערכת מסוגלת לצייר רק נקודות קוים או משולשים. נצייר את הריבוע ע"י חיבור של 2 משולשים.
בהתייחס לארבע הנקודות הנ"ל מצייר את המשולשים הבאים:
משולש ראשון: נקודות 1,2,3
משולש שני: נקודות 3,4,1
היות שהאינדקסים מתחילים ב-0, נגדיר את שני המשולשים כך:
משולש ראשון: נקודות 0,1,2.
משולש שני: נקודות 2,3,0
האינדקסים מציינים את סדר ציור המשולשים. על סמך ההסבר הנ"ל, הכנסת האינדקסים נראית כ:
  mIndexBuffer.put((short)(0));
        mIndexBuffer.put((short)(1));
          mIndexBuffer.put((short)(2));
        mIndexBuffer.put((short)(2));
          mIndexBuffer.put((short)(3));
        mIndexBuffer.put((short)(0));



המתודה draw

       public void draw(GL10 gl) {
קביעת האוריינטציה של הפוליגון.
אפשרויות:
CCW - נגד כיוון השעון.
CW- עם כיוון השעון.

            gl.glFrontFace(GL10.GL_CCW);
את ה-api הזה כבר סקרנו בתחילת הפוסט. חיבור הbuffer של הקודקודים למערכת. 
הפרמטר הראשון: 3 קואורדינטות לכל נקודה.
פרמטר שני: סוג ה-data.
פרמטר שלישי: הצעד (stride) בין הנתונים.
פרמטר שלישי: ה-buffer.
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);

            gl.glEnable(GL10.GL_TEXTURE_2D);
חיבור ה-texture buffer. הסבר הפרמטרים - כמו ב-buffer של הקודקודים.

            gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexBuffer);
ו...סופסופ...פקודת הציור:
TRIANGLE_STRIP - ציור משולשים.
אפשרויות נוספות:
GL_TRIANGLE_FAN
GL_TRIANGLES
GL_LINES
-שימוש במשולשים
-6 קודקודים
-סוג ה-data של האינדקסים.
-ה-index buffer.
            gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 6,
                    GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
        }

אין תגובות:

הוסף רשומת תגובה