יום שבת, 3 באוגוסט 2013

Android OpenGL Guide





בסדרת המאמרים הקרובה על Android נתמקד בספרייה של OpenGL ES שמאפשרת לנצל את משאבי ה GPU לצורך משחקים וגרפיקה מבלי שנפגע בביצועים של המעבד הרגיל במכשיר, הספרייה מבוססת על תכנות מקבילי ובעצם מבצעת מספר פעולות בו זמנית לעומת המעבד הרגיל שמבצע פקודה בודדת בכל רגע נתון.

OpenGL פותחה ע"י חברת  Silicon Graphics בשנת 1991 מה שגרם למהפכה לא קטנה בעולם המחשבים, פתאום החלו לצוץ מאיצים גרפיים שונים שהפכו את המחשב למכונה ויזואלית עשירה שדחקו את ה Console שאליו היו רגילים, תוכנות תלת מימד נכנסו לשוק ועולם המשחקים המציא את עצמו מחדש.

אפשר לכתוב המון על OpenGL ועל השינויים שעבר במשך השנים אבל נחזור ל Android שתומך ב (Open Graphics Library Embedded System)  כבר מתחילת דרכה של המערכת ומוגדרת כ Native Library כפי שכתבתי במאמר אחר בנושא, שפת המקור של OpenGL היא C עם טיפוסים מיוחדים כמו Vertex, Polygon, Texture וכדומה, עבור הסביבה הרגילה של Android יצרו לנו API יחסית נוח ב Java שמאפשר לנו להתחבר ל Driver של ה OpenGL ולשלוח לו פקודות.

מושגים בסיסיים לפני שמתחילים:

Coordinates

לכל רכיב תלת מימדי יש קואורדינטות ביחס לעולם או ביחס לאובייקט, לדוגמה טקסטורה מתלבשת על הקואורדינטות של המשטח ואובייקטים נמצאים ע"פ קואורדינטות של העולם עצמו.



Vertex

נקודה בודדת בעולם תלת מימדי, מספר נקודות יוצרות לנו משטח, לצורך הדוגמה משולש מורכב מ 3 נקודות, לכל נקודה יש קואורדינטות ביחס לעולם.


Triangle / Polygon

חלק מהמעבדים הגרפיים יודעים לעבוד עם משטחים שמורכבים מ 4 נקודות (Polygon) אך עדיין רוב המעבדים עובדים עם Triangle שמורכב מ 3 נקודות שזו הכמות המינימלית ליצירת משטח, אחת הדרכים לדעת עד כמה המאיץ הגרפי שלנו טוב הוא לבדוק כמה מרובעים \ משולשים הוא יודע לצייר ברגע נתון.

Objects / Models

חיבור של מספר משטחים יוצר לנו אובייקט לדוגמה 2 משולשים יוצרים משטח מרובע ו  6 מרובעים יוצרים לנו קובייה תלת מימדית.





Texture

תמונה (Bitmap) שמתלבשת ע"ג המשטח וצובעת אותו,ניתן לשנות את איכות ה Texture ככל שמתרחקים מהמשטח ובכך לחסוך זיכרון, קיימים סוגים שונים של Textures במטרה להעשיר את חומריות המשטח לדוגמה Color Texture עבור הצבעים ו Bump Texture עובר מרקם המשטח, שילוב של מספר טקסטורות יוצר לנו Material (חומר).


Perspective

נקודת המבט של המצלמה ביחס לסביבה, לדוגמה במשחקי  First Person Shooter המצלמה זזה איתנו בכל המשחק וכל תנועה משנה את המצלמה לעומת משחקי Real Time Strategy שהמצלמה בזווית קבועה מלמעלה ושדה הראיה גדול יותר.




Lights
תאורת הסביבה והאובייקטים בעולם התלת מימדי, זו תורה שלמה שנגע בה בהמשך.

Render


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


3D Triangle

האובייקט הראשון לצורך הדוגמה הוא משולש תלת מימדי שמורכב מ 4 משולשים בדפנות ועוד 4 משולשים שמרכיבים את הבסיס וכך נוצר אובייקט אטום, כלומר יש סה"כ 24 נקודות שהן 8 משולשים.




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

import javax.microedition.khronos.opengles.GL10;

public class TriangleNaked {

private float vertices[] = {
//x    y     z  
0.0f, 1.0f, 0.0f,  
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
0.0f, 1.0f, 0.0f,
1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
0.0f, 1.0f, 0.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
0.0f, 1.0f, 0.0f,
-1.0f,-1.0f,-1.0f,
-1.0f,-1.0f, 1.0f,

0.0f, -1.0f, 0.0f, 
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
0.0f, -1.0f, 0.0f,
1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
0.0f, -1.0f, 0.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
0.0f, -1.0f, 0.0f,
-1.0f,-1.0f,-1.0f,
-1.0f,-1.0f, 1.0f,  
};

//order of the surface according the vertices positions
byte indices[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
//order of the color according the vertices positions
private float colors[] = {
//R    G    B   A
1.0f,0.0f,1.0f,0.0f,
1.0f,1.0f,0.0f,1.0f,
1.0f,1.0f,1.0f,0.0f,
1.0f,0.0f,0.0f,0.0f,
1.0f,0.0f,1.0f,0.0f,
1.0f,1.0f,1.0f,0.0f,
1.0f,1.0f,0.0f,1.0f,
1.0f,1.0f,1.0f,0.0f,
1.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,0.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,0.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,1.0f,1.0f,
};

//convert the Vertex, Color, and indices to buffers 
private FloatBuffer mVertexBuffer;
private ByteBuffer mIndexBuffer;
private FloatBuffer mColorBuffer;
private float mCubeRotation;
private GL10 gl;

public TriangleNaked(GL10 _gl) {
gl = _gl;
init();
}

private void init() {
// buffer for the vertex coordinates
ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
mVertexBuffer = byteBuf.asFloatBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
// buffer for the color coordinates
byteBuf = ByteBuffer.allocateDirect(colors.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
mColorBuffer = byteBuf.asFloatBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);

// buffer for the indices
mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
mIndexBuffer.put(indices);
mIndexBuffer.position(0);
mCubeRotation = -0.25f;
}

public void draw() {
//set gl focus to triangle
gl.glLoadIdentity();

gl.glTranslatef(0.1f, 0.0f, -10.0f);
//rotate the triangle
gl.glRotatef(mCubeRotation, 1.0f, 1.0f, 0.0f);

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);

//render the 3d triangle
gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE,
mIndexBuffer);

gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
mCubeRotation -= 0.25f;
}

}

3d Cube

האובייקט הבא הוא קובייה תלת מימדית, יש 6 דפנות שמורכבות מ 2 משולשים כל אחת, אם נעשה חישוב מהיר (6 * 2 *3 ) מדובר על 36 נקודות.



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

import javax.microedition.khronos.opengles.GL10;

public class CubeNaked {

private float vertices[] = {
//x    y     z  
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,

1.0f,1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,

1.0f, 1.0f,1.0f,
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,

-1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,

-1.0f, -1.0f, -1.0f,
-1.0f,-1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,

-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f,-1.0f,
1.0f, -1.0f, -1.0f,

-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,

1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
1.0f,1.0f, -1.0f,
    };

private byte indices[] = {
//order of the surface according the vertices positions, reuse vertices
0, 1, 3, 0, 3, 2, 4, 5, 7, 4, 7, 6, 8, 9, 11, 8, 11, 10, 12, 13,
15, 12, 15, 14, 16, 17, 19, 16, 19, 18, 20, 21, 23, 20, 23, 22, };

//order of the color according the vertices positions
private float colors[] = {
//R    G    B   A
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 0.5f, 0.0f, 1.0f,
1.0f, 0.5f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f,1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 0.0f,1.0f, 1.0f
};

//convert the Vertex, Color, and indices to buffers 
private FloatBuffer mVertexBuffer;
private ByteBuffer mIndexBuffer;
private FloatBuffer mColorBuffer;

private float mCubeRotation;
private GL10 gl;

public CubeNaked(GL10 _gl) {
gl = _gl;
init();
}

private void init() {
// buffer for the vertex coordinates
ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
mVertexBuffer = byteBuf.asFloatBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);

// buffer for the color coordinates
byteBuf = ByteBuffer.allocateDirect(colors.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
mColorBuffer = byteBuf.asFloatBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);

// buffer for the indices
mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
mIndexBuffer.put(indices);
mIndexBuffer.position(0);
mCubeRotation = -0.25f;
}

public void draw()
{
//set gl focus to cube
gl.glLoadIdentity();

gl.glTranslatef(0.1f, 0.0f, -10.0f);
//rotate the cube
gl.glRotatef(mCubeRotation, 1.0f, 1.0f, 1.0f);

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);

//render the cube
gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE,
mIndexBuffer);

gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
mCubeRotation -= 0.25f;


}
}

בניגוד למשולש התלת מימדי כאן ניתן למחזר את הנקודות  ולחסוך הגדרות כפולות, אם נעבור על מערך indices שקובע את סדר הנקודות שהולכות לעבור Render  נראה שיש נקודות שחוזרים עליהן יותר מפעם אחת.



GLSurfaceView.Renderer

אובייקט שהופך את המידע הגיאומטרי ל Frame, הוא מכיל מספר פונקציות חשובות שבעזרתם ניתן להכין את האובייקטים ובכלל את הסביבה שיוצגו ע"ג ה SurfaceView.


import android.opengl.GLSurfaceView;
import android.opengl.GLU;

public class OpenGLRenderBasic implements GLSurfaceView.Renderer  {

private CubeNaked mCube;
@Override
public void onDrawFrame(GL10 gl) {
//clear GL buffer
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

mCube.draw();
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

//enter to Perspective Projection Mode Settings
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f,
100.0f);
gl.glViewport(0, 0, width, height);
//enter to ModelView Projection Mode Settings
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig arg1) {

//clear the suraface color to black
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
mCube = new CubeNaked(gl);
}

}



GLSurfaceView
אובייקט שבעזרתו ניתן לנהל את ה OpenGL מ Android.

import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      
        //create GLSurfaceView Instance and connecting the renderer
        GLSurfaceView view = new GLSurfaceView(this);
        view.setRenderer(new OpenGLRenderBasic());
        setContentView(view);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    
}

סיכום

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

בהצלחה...


אין תגובות:

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