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

Android OpenGL Complex Models




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


הכנה מקדימה

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


גלגל שיניים

 *.OBJ

פורמט ותיק מתחום התלת מימד שנקרא Wavefront OBJ, ניתן ליצא אותו בכל תוכנת תלת מימד שבשוק, זה קובץ טקסטואלי שמכיל בתוכו את נתוני ה Vertex ו Textures, מבנה הקובץ פשוט, הוא מורכב משורות שמכילות את המידע על האובייקט לדוגמה רצף של Vertices מהגלגל שיניים.

//V for Vertices 
v -0.0179763 -0.000869437 -1.04022
v -0.0910632 0.00717378 -1.04074
v -0.161893 0.00603062 -1.02813
v -0.232611 0.00575502 -1.01982
v -0.301366 0.00604349 -1.00169
v -0.371174 0.00721471 -0.987629
//....


הפורמט מכיל מידע על ה  Normal Vertices שהם דומים ל Vertices רק שבעזרתם ניתן להפוך את האובייקט ליותר חלק (Smooth), נקודת Vertex אחת יכולה להיות מחוברת ליותר ממשטח אחד, Normal Vertices היא נקודה ממוצעת של ה Vertices שמתחברים בין המשטחים.

//VN for Vertices Normal
vn -0.175242 0 -0.984525
vn -0.146028 0 -0.98928
vn -0.186303 0 -0.982492
vn -0.226327 0 -0.974051
vn -0.197461 0 -0.980311
vn -0.629326 0 -0.777142
//....


המשטחים של ה Vertices על פיו נקבע סדר הצביעה במסך.

//F for Faces
f 98//1 2//1 3//2 99//2
f 99//2 3//2 4//3 100//3
f 100//3 4//3 5//4 101//4
f 101//4 5//4 6//5 102//5
f 106//6 10//6 11//7 107//7
//....

מידע שקשור ל Texture של האובייקט.

//VT for Vertices Texture 
vt 0.466116 0.136309
vt 0.497293 0.162254
vt 0.477739 0.17729
vt 0.458501 0.145891
vt 0.450565 0.182356
vt 0.443936 0.147743
//....

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

אחרי שהכרנו את המבנה הבסיסי של קובץ OBJ השלב הבא הוא לכתוב Parser שאוסף את המידע מהקובץ ומעביר אותו למנגנונים של ה OpenGL בדומה למאמר הקודם, אבל לפני שנתחיל חשוב לייצר בפרויקט תיקיית Assets שלתוכה נעתיק את הקובץ.


ObjectParser

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.Vector;
import javax.microedition.khronos.opengles.GL10;
import android.util.Log;

public class ObjectParser {

private Vector<Short> faces=new Vector<Short>();
private Vector<Float> v=new Vector<Float>();
private FloatBuffer vertexBuffer;
private ShortBuffer faceBuffer;
private FloatBuffer mColorBuffer;
private BufferedReader reader;
private GL10 gl;
private float mCubeRotation;
float[] colors;

public ObjectParser(InputStreamReader stream, GL10 _gl)
{
gl = _gl;
reader = new BufferedReader(stream);
loadFile();
loadBuffer();
}
public void Draw()
{
gl.glLoadIdentity();
gl.glTranslatef(0.1f, 0.0f, -5.0f);
// rotate the cube
gl.glRotatef(mCubeRotation, 1.0f, 1.0f, 1.0f);

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, faceBuffer.capacity(),
GL10.GL_UNSIGNED_SHORT,faceBuffer);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
mCubeRotation -= 0.25f;
}
//create color for each vertex
private void createColorBuffer()
{
colors = new float[v.size() * 4];
float f = 1.0f;
for (int i = 0; i < v.size() * 4;i++)
{
//0f, 1f, 0f, 1f
colors[i] = 0f;
i++;
colors[i] = f;
i++;
colors[i] = 0f;
i++;
colors[i] = 1f;
f= f - 0.0004f;
}
}
private void loadBuffer()
{
createColorBuffer();
ByteBuffer byteBuf = ByteBuffer.allocateDirect(v.size() * 4);
byteBuf.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuf.asFloatBuffer();
vertexBuffer.put(toPrimitiveArrayF(v));
vertexBuffer.position(0);
byteBuf = ByteBuffer.allocateDirect(colors.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
mColorBuffer = byteBuf.asFloatBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);
ByteBuffer fBuf = ByteBuffer.allocateDirect(faces.size() * 2);
fBuf.order(ByteOrder.nativeOrder());
faceBuffer = fBuf.asShortBuffer();
faceBuffer.put(toPrimitiveArrayS(faces));
faceBuffer.position(0);
mCubeRotation = -0.25f;
}
    //read object file and process lines
    private  void loadFile()
{
String line = null;
try {
while((line = reader.readLine()) != null)
{
//polygon faces
if(line.startsWith("f")){
processFace(line);
Log.d("Face Loaded", line);
}
//vertices
if(line.startsWith("v")){
processV(line);
Log.d("Vetex Loaded", line);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
    
    //export vertices from line 
    private void processV(String line){
   
           //split spaces
String [] tokens=line.split("[ ]+"); 
int c=tokens.length; 
for(int i=1; i<c; i++){ 
//add vertex to array
v.add(Float.valueOf(tokens[i]));
}
}
    
    //export polygon face from line and convert to triangles
private void processFace(String line){
String [] tokens=line.split("[ ]+");
int c=tokens.length;
Vector<Short> tmpFaces=new Vector<Short>();
Vector<Short> tmpVn=new Vector<Short>();
for(int i=1; i<tokens.length; i++){
Short s=Short.valueOf(tokens[i].split("//")[0]);
s--;
tmpFaces.add(s);
s=Short.valueOf(tokens[i].split("//")[1]);
s--;
tmpVn.add(s);
}
faces.addAll(triangulate(tmpFaces));
}

//convert polygon to triangles.
public  Vector<Short> triangulate(Vector<Short> polygon){
Vector<Short> triangles=new Vector<Short>();
for(int i=1; i<polygon.size()-1; i++){
triangles.add(polygon.get(0));
triangles.add(polygon.get(i));
triangles.add(polygon.get(i+1));
}
return triangles;
}
//convert Vector to Float Array
private float[] toPrimitiveArrayF(Vector<Float> vector) {
float[] f;
f = new float[vector.size()];
for (int i = 0; i < vector.size(); i++) {
f[i] = vector.get(i);
}
return f;
}
//convert Short Vector to Short Array
private static short[] toPrimitiveArrayS(Vector<Short> vector){
short[] s;
s=new short[vector.size()];
for (int i=0; i<vector.size(); i++){
s[i]=vector.get(i);
}
return s;
}
}


המחלקה מקבלת את המידע כ Stream ומתחילה לקרוא שורה, שורה בעזרת ה Reader , אם השורה מתחילה עם האות V אנחנו מעבדים את השורה כ Vertex ואם השורה מתחילה עם האות F מעבדים את השורה כ Face, ממלאים 2 מערכים ואח"כ מייצרים מהם Buffers שטוענים ל OpenGL.

MyRender

import java.io.IOException;
import java.io.InputStreamReader;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLSurfaceView.Renderer;

public class MyRender extends GLSurfaceView implements Renderer {

private Context ctx;
private ObjectParser _model;
public MyRender(Context context) {
super(context);
ctx = context;
}

@Override
public void onDrawFrame(GL10 arg0) {
_model.Draw();
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
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) {
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);
try {
//loading object from assets folder
_model = new ObjectParser(new InputStreamReader(ctx.getAssets().open("gear1.obj")),gl);
} catch (IOException e) {
e.printStackTrace();
}
}
}

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


תמונה מהמכשיר:



הורדת הפרויקט:


סיכום:

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

בהצלחה...

אין תגובות:

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