繪製3D模型一般我們都會用到OpenGL裏面的一些api,看看如何使用這些api繪製3的模型。
使用OpenGl繪製3D模型我主要用兩種方式:
1:glDrawArrays(int mode, int first,int count)
參數1:有三種取值
1.GL_TRIANGLES:每三個頂之間繪製三角形,之間不連接
2.GL_TRIANGLE_FAN:以V0V1V2,V0V2V3,V0V3V4,……的形式繪製三角形
3.GL_TRIANGLE_STRIP:順序在每三個頂點之間均繪製三角形。這個方法可以保證從相同的方向上所有三角形均被繪製。以V0V1V2,V1V2V3,V2V3V4……的形式繪製三角形
參數2:從數組緩存中的哪一位開始繪製,一般都定義爲0
參數3:頂點的數量
2:glDrawElements(int mode,
int count,
int type,
java.nio.Buffer indices
)
mode——指明被渲染的是哪種圖元,被允許的符號常量有GL_POINTS,GL_LINE_STRIP,GL_LINE_LOOP,GL_LINES,GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN和GL_TRIANGLES
count——指明被渲染的元素個數。
type——指明索引指的類型,不是GL_UNSIGNED_BYTE就是GL_UNSIGNED_SHORT。
indices——指明存儲索引的位置
第一種 glDrawArrays 繪製3D模型,主要分爲兩大部分。第一步,先將3D模型的3維數組座標寫好。第二步,將GLSurfaceView的渲染器配置好,將座標數組轉爲FloatBuffer,直接開始繪製就OK。使用這種方式繪製3D模型,一定要將做表數組寫好,座標數組的元素順序與後面你使用的繪製類型有關,我建議可以根據obj模型文件來確定數組。我的數組是直接從obj模型文件中提取的。
/** * 解析3D Obj 文件 * 解析obj文件時,數據類型由開頭的首字母決定 * switch(首字母){ * case "v": * 頂點座標數據 * break; * case "vn": * 法向量座標數據 * break; * case "vt": * 紋理座標數據 * break; * case "usemtl": * 材質 * break; * case "f": * 索引 * break; * } */ public class ObjLoaderUtil { // 存放解析出來的頂點的座標 private ArrayList<ObjVertexs> mVertexArrayList; //存放解析出來面的索引 private ArrayList<Integer> mIndexArrayList; //存放解析出來的法線座標 private ArrayList<ObjVertexs> mNormalArrayList; //存放解析出來的法線索引 private ArrayList<Integer> mIndexNormalArrayList; //存放3D模型的頂點座標數據 public float[] mSurfaceFloat; //存放頂點座標的floatbuffer public FloatBuffer mVertexFloatBuffer; //頂點座標索引buffer public FloatBuffer mIndexVertexBuffer; //存放法線座標的floatbuffer public FloatBuffer mNormalFloatBuffer; public ObjLoaderUtil() { mVertexArrayList = new ArrayList(); mIndexArrayList = new ArrayList<>(); mNormalArrayList = new ArrayList<>(); mIndexNormalArrayList = new ArrayList<>(); } /** * 將obj文件中的頂點座標與面索引解析出來,存放集合中 * * @param mContext * @throws IOException */ public void load(Context mContext) throws IOException { //獲取assets文件夾下的obj文件的數據輸入流 InputStream mInputStream = mContext.getClass().getClassLoader().getResourceAsStream("assets/Lion_OBJ.obj"); InputStreamReader mInputStreamReader = new InputStreamReader(mInputStream); BufferedReader mBufferedReader = new BufferedReader(mInputStreamReader); String temps = null; //利用buffer讀取流將obj文件的內容讀取出來存放在temps while ((temps = mBufferedReader.readLine()) != null) { String[] temp = temps.split(" "); // Log.d("tag", "文件信息" + temps); if (temp[0].trim().equals("v")) { mVertexArrayList.add(new ObjVertexs(Float.valueOf(temp[1]), Float.valueOf(temp[2]), Float.valueOf(temp[3]))); } if (temp[0].trim().equals("vn")) { mNormalArrayList.add(new ObjVertexs(Float.valueOf(temp[1]), Float.valueOf(temp[2]), Float.valueOf(temp[3]))); } // 如果讀取到的首元素時"f",則表示讀取到的數據是面索引數據 if (temp[0].trim().equals("f")) { if (temp[1].indexOf("/") == -1) { for (int i = 1; i < temp.length; i++) { mIndexArrayList.add(Integer.valueOf(temp[i])); } } else { for (int i = 1; i < temp.length; i++) { mIndexArrayList.add(Integer.valueOf(temp[i].split("/")[0])); } } } } mSurfaceFloat = getSurfaceFloat(mVertexArrayList, mIndexArrayList); mVertexFloatBuffer = makeFloatBuffer(mSurfaceFloat); mNormalFloatBuffer = makeFloatBuffer(getSurfaceFloat(mNormalArrayList, mIndexNormalArrayList)); } /** * 將解析出來的頂點與索引組合起來,形成一個新的float數組,用於繪製3D模型,並將其返回 * * @param mObjVertexs 存放座標點的集合 * @param mIntegers 存放索引的集合 * @return */ public float[] getSurfaceFloat(ArrayList<ObjVertexs> mObjVertexs, ArrayList<Integer> mIntegers) { float[][] mFloats = new float[mIntegers.size()][3]; float[] surfaceFloat = new float[mIntegers.size() * 3]; for (int i = 0; i < mIntegers.size(); i++) { mFloats[i][0] = mObjVertexs.get(mIntegers.get(i) - 1).x; mFloats[i][1] = mObjVertexs.get(mIntegers.get(i) - 1).y; mFloats[i][2] = mObjVertexs.get(mIntegers.get(i) - 1).z; } int i = 0; for (float[] floats : mFloats) { for (float v : floats) { surfaceFloat[i++] = v; } } return surfaceFloat; } /** * 將存放3D模型的float數組,轉換爲floatbuffer * * @param mFloats * @return */ public FloatBuffer makeFloatBuffer(float[] mFloats) { //爲float數組分配緩存空間,一個float大小爲4個字節 ByteBuffer mByteBuffer = ByteBuffer.allocateDirect(mFloats.length * 4); //規定緩存區的字節順序爲本機字節順序 mByteBuffer.order(ByteOrder.nativeOrder()); //將bytebuffer轉換爲floatbuffer FloatBuffer loatBuffer = mByteBuffer.asFloatBuffer(); //將float數組填充到floatbuffe中,完成轉換 loatBuffer.put(mFloats); //規定緩存區的起始索引 loatBuffer.position(0); return loatBuffer; } }
/** * 繼承GLSurfaceView,實現Renderer接口,開始繪製 */ public class GlModeView extends GLSurfaceView implements GLSurfaceView.Renderer, GestureDetector.OnGestureListener { //obj文件解析類 private ObjLoaderUtil mObjLoaderUtil; //手勢工具類 private GestureDetectorCompat mCompat; //模型沿x,y軸旋轉的角度 private float xrot, yrot; public GlModeView(Context context, AttributeSet attrs) throws IOException { super(context, attrs); setRenderer(this); mObjLoaderUtil = new ObjLoaderUtil(); mObjLoaderUtil.load(context); mCompat = new GestureDetectorCompat(context, this); } @Override public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { //設置清屏色 gl10.glClearColor(0.1f, 1f, 1f, 0); //用當前光線參數計算頂點顏色。否則僅僅簡單將當前顏色與每個頂點關聯。 gl10.glEnable(GL10.GL_LIGHTING); //啓用服務器端GL功能,允許(apply)引入的顏色與顏色緩衝區中的值進行邏輯運算 gl10.glEnable(GL10.GL_BLEND); //這一行啓用smooth shading(陰影平滑)。陰影平滑通過多邊形精細的混合色彩,並對外部光進行平滑。 gl10.glShadeModel(GL10.GL_SMOOTH); //當前活動紋理單元爲二維紋理 gl10.glEnable(GL10.GL_TEXTURE_2D); //做深度比較和更新深度緩存。值得注意的是即使深度緩衝區存在並且深度mask不是0,如果深度測試禁用的話,深度緩衝區也無法更新。 gl10.glEnable(GL10.GL_DEPTH_TEST); // 法向量被計算爲單位向量 gl10.glEnable(GL10.GL_NORMALIZE); //啓用面部剔除功能 gl10.glEnable(GL10.GL_CULL_FACE); //指定哪些面不繪製 gl10.glCullFace(GL10.GL_BACK); //開啓定義多邊形的正面和背面 gl10.glEnable(GL10.GL_CULL_FACE); //改變每個頂點的散射和環境反射材質,可以使用顏色材質。 gl10.glEnable(GL10.GL_COLOR_MATERIAL); // 開啓抗鋸齒 gl10.glEnable(GL10.GL_POINT_SMOOTH); gl10.glHint(GL10.GL_POINT_SMOOTH_HINT, GL10.GL_NICEST); gl10.glEnable(GL10.GL_LINE_SMOOTH); gl10.glHint(GL10.GL_LINE_SMOOTH_HINT, GL10.GL_NICEST); gl10.glEnable(GL10.GL_POLYGON_OFFSET_FILL); gl10.glHint(GL10.GL_POLYGON_SMOOTH_HINT, GL10.GL_NICEST); //定義多邊形的正面和背面,在一個完全由不透明的密閉surface組成的場景中,多邊形的背面永遠不會被看到。剔除這些不能顯示出來的面可以加速渲染器渲染圖像的時間。 gl10.glFrontFace(GL10.GL_CCW); //指明深度緩衝區的清理值 gl10.glClearDepthf(1.0f); gl10.glDepthFunc(GL10.GL_LEQUAL); } @Override public void onSurfaceChanged(GL10 gl10, int i, int i1) { //選擇投影矩陣 gl10.glMatrixMode(GL10.GL_PROJECTION); gl10.glViewport(0, 0, i, i1); gl10.glLoadIdentity(); float x = (float) i / i1; gl10.glFrustumf(-x, x, -1, 1, 5, 200); GLU.gluLookAt(gl10, 0, 0, 50, 0, 0, 0, 0, 1, 0); //選擇模型觀察矩陣 gl10.glMatrixMode(GL10.GL_MODELVIEW); gl10.glLoadIdentity(); } @Override public void onDrawFrame(GL10 gl10) { //清除屏幕和深度緩存。 gl10.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl10.glLoadIdentity(); //設置頂點座標buffer gl10.glVertexPointer(3, GL10.GL_FLOAT, 0, mObjLoaderUtil.mVertexFloatBuffer); //啓用頂點數組 gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY); //沿X軸旋轉 gl10.glRotatef(xrot, 1, 0, 0); //沿Y軸旋轉 gl10.glRotatef(yrot, 0, 1, 0); gl10.glColor4f(1f, 0f, 0f, 0); //模型如果四邊形化,i就是加4,模型如果三角化,i就是加3 for (int i = 0; i <= (mObjLoaderUtil.mSurfaceFloat.length / 3 - 4); i += 4) { gl10.glDrawArrays(GL10.GL_TRIANGLE_FAN, i, 4); } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: getParent().requestDisallowInterceptTouchEvent(false); break; } return mCompat.onTouchEvent(event); } @Override public boolean onDown(MotionEvent motionEvent) { return true; } @Override public void onShowPress(MotionEvent motionEvent) { } @Override public boolean onSingleTapUp(MotionEvent motionEvent) { return false; } @Override public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) { xrot -= v; yrot -= v1; return true; } @Override public void onLongPress(MotionEvent motionEvent) { } @Override public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) { return false; } }
第二種 glDrawElements繪製3D模型,使用這種方式繪製3D模型,需要將頂點座標數據準備好,及頂點索引數組準備好。就ok。
這兩個數組也是直接從obj文件中解析。相對上面的方式而言,就簡單一點。
/** * 解析obj文件 */ public class ObjUtils { //存放頂點座標 private ArrayList<Point> mVertexArrayList; public float[] mVertexFloats; public FloatBuffer mVertexFloatBuffer; //存放頂點索引 private ArrayList<Short> mIndexArrayList; public short[] mIndexShorts; public ShortBuffer mIndexShortBuffer; //存放讀取的obj文件的信息 private String temps; public ObjUtils() { mVertexArrayList = new ArrayList<>(); mIndexArrayList = new ArrayList<>(); } /** * 加載obj文件數據 */ public void loadObj(Context context, String path) throws IOException { //讀取city.obj文件 InputStream inputStream = context.getClass().getClassLoader().getResourceAsStream(path); InputStreamReader mInputStreamReader = new InputStreamReader(inputStream); BufferedReader mReader = new BufferedReader(mInputStreamReader); //如果讀取的文本內容不爲空,則一直讀取,否則停止 while ((temps = mReader.readLine()) != null) { String[] temp = temps.split(" "); //以空格爲分割符,將讀取的一行temps分裂爲數組 //如果讀取到的首元素時"v",則表示讀取到的數據是頂點座標數據 if (temp[0].trim().equals("v")) { mVertexArrayList.add(new Point(Float.valueOf(temp[1]), Float.valueOf(temp[2]), Float.valueOf(temp[3]))); } // 如果讀取到的首元素時"f",則表示讀取到的數據是面索引數據 if (temp[0].trim().equals("f")) { if (temp[1].indexOf("/") == -1) { for (int i = 1; i < temp.length; i++) { int t = Integer.valueOf(temp[i]); mIndexArrayList.add((short) t); } } else { for (int i = 1; i < temp.length; i++) { int t = Integer.valueOf(temp[i].split("/")[0]) - 1; mIndexArrayList.add((short) t); } } } } mReader.close(); mVertexFloats = getVertex(mVertexArrayList); mIndexShorts = getIndex(mIndexArrayList); mVertexFloatBuffer = makeFloatBuffer(mVertexFloats); mIndexShortBuffer = getBufferFromArray(mIndexShorts); } /** * 獲取頂點數組 */ private float[] getVertex(ArrayList<Point> pointArrayList) { float[][] mFloats = new float[pointArrayList.size()][3]; float[] surfaceFloat = new float[pointArrayList.size() * 3]; for (int i = 0; i < pointArrayList.size(); i++) { mFloats[i][0] = pointArrayList.get(i).x; mFloats[i][1] = pointArrayList.get(i).y; mFloats[i][2] = pointArrayList.get(i).z; } int i = 0; for (float[] floats : mFloats) { for (float v : floats) { surfaceFloat[i++] = v; } } return surfaceFloat; } /** * 獲取索引數組 */ private short[] getIndex(ArrayList<Short> integerArrayList) { short[] mFloats = new short[integerArrayList.size()]; for (int i = 0; i < integerArrayList.size(); i++) { mFloats[i] = integerArrayList.get(i); } return mFloats; } /** * 將float數組轉換爲buffer */ private FloatBuffer makeFloatBuffer(float[] floats) { ByteBuffer mByteBuffer = ByteBuffer.allocateDirect(floats.length * 4); mByteBuffer.order(ByteOrder.nativeOrder()); FloatBuffer floatBuffer = mByteBuffer.asFloatBuffer(); floatBuffer.put(floats); floatBuffer.position(0); return floatBuffer; } private static ShortBuffer getBufferFromArray(short[] array) { ByteBuffer buffer = ByteBuffer.allocateDirect(array.length * 2); buffer.order(ByteOrder.nativeOrder()); ShortBuffer shortBuffer = buffer.asShortBuffer(); shortBuffer.put(array); shortBuffer.position(0); return shortBuffer; } }
public class ModeView extends GLSurfaceView implements GLSurfaceView.Renderer, GestureDetector.OnGestureListener { private ObjUtils mObjUtils; private GestureDetectorCompat mCompat; private float xrot, yrot; private FloatBuffer colorBuffer; public ModeView(Context context, AttributeSet attrs) throws IOException { super(context, attrs); mObjUtils = new ObjUtils(); setRenderer(this); mObjUtils.loadObj(context, "assets/mode.obj"); mCompat = new GestureDetectorCompat(context, this); } @Override public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { //設置清屏色 gl10.glClearColor(1f, 1f, 1f, 0); //用當前光線參數計算頂點顏色。否則僅僅簡單將當前顏色與每個頂點關聯。 gl10.glEnable(GL10.GL_LIGHTING); //啓用服務器端GL功能,允許(apply)引入的顏色與顏色緩衝區中的值進行邏輯運算 gl10.glEnable(GL10.GL_BLEND); //這一行啓用smooth shading(陰影平滑)。陰影平滑通過多邊形精細的混合色彩,並對外部光進行平滑。 gl10.glShadeModel(GL10.GL_SMOOTH); //當前活動紋理單元爲二維紋理 gl10.glEnable(GL10.GL_TEXTURE_2D); //做深度比較和更新深度緩存。值得注意的是即使深度緩衝區存在並且深度mask不是0,如果深度測試禁用的話,深度緩衝區也無法更新。 gl10.glEnable(GL10.GL_DEPTH_TEST); // 法向量被計算爲單位向量 gl10.glEnable(GL10.GL_NORMALIZE); //啓用面部剔除功能 gl10.glEnable(GL10.GL_CULL_FACE); //指定哪些面不繪製 gl10.glCullFace(GL10.GL_BACK); //開啓定義多邊形的正面和背面 gl10.glEnable(GL10.GL_CULL_FACE); //改變每個頂點的散射和環境反射材質,可以使用顏色材質。 gl10.glEnable(GL10.GL_COLOR_MATERIAL); // 開啓抗鋸齒 gl10.glEnable(GL10.GL_POINT_SMOOTH); gl10.glHint(GL10.GL_POINT_SMOOTH_HINT, GL10.GL_NICEST); gl10.glEnable(GL10.GL_LINE_SMOOTH); gl10.glHint(GL10.GL_LINE_SMOOTH_HINT, GL10.GL_NICEST); gl10.glEnable(GL10.GL_POLYGON_OFFSET_FILL); gl10.glHint(GL10.GL_POLYGON_SMOOTH_HINT, GL10.GL_NICEST); //定義多邊形的正面和背面,在一個完全由不透明的密閉surface組成的場景中,多邊形的背面永遠不會被看到。剔除這些不能顯示出來的面可以加速渲染器渲染圖像的時間。 gl10.glFrontFace(GL10.GL_CCW); //指明深度緩衝區的清理值 gl10.glClearDepthf(1.0f); gl10.glDepthFunc(GL10.GL_LEQUAL); } @Override public void onSurfaceChanged(GL10 gl10, int i, int i1) { //選擇投影矩陣 gl10.glMatrixMode(GL10.GL_PROJECTION); gl10.glViewport(0, 0, i, i1); gl10.glLoadIdentity(); float x = (float) i / i1; gl10.glFrustumf(-x, x, -1, 1, 4, 200); GLU.gluLookAt(gl10, 0, 0, 60, 0, 0, 0, 0, 1, 0); //選擇模型觀察矩陣 gl10.glMatrixMode(GL10.GL_MODELVIEW); gl10.glLoadIdentity(); } @Override public void onDrawFrame(GL10 gl10) { //啓用頂點數組 gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl10.glEnableClientState(GL10.GL_COLOR_ARRAY); //清除屏幕和深度緩存。 gl10.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl10.glLoadIdentity(); //設置頂點座標buffer gl10.glVertexPointer(3, GL10.GL_FLOAT, 0, mObjUtils.mVertexFloatBuffer); gl10.glTranslatex(0, -20, 0); gl10.glRotatef(xrot, 1, 0, 0); gl10.glRotatef(yrot, 0, 1, 0); gl10.glPushMatrix(); gl10.glDrawElements(GL10.GL_TRIANGLES, mObjUtils.mIndexShorts.length, GL10.GL_UNSIGNED_SHORT, mObjUtils.mIndexShortBuffer); gl10.glDisableClientState(GL10.GL_COLOR_ARRAY); //關閉點座標管道 gl10.glDisableClientState(GL10.GL_VERTEX_ARRAY); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: getParent().requestDisallowInterceptTouchEvent(false); break; } return mCompat.onTouchEvent(event); } @Override public boolean onDown(MotionEvent motionEvent) { return true; } @Override public void onShowPress(MotionEvent motionEvent) { } @Override public boolean onSingleTapUp(MotionEvent motionEvent) { return false; } @Override public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) { xrot -= v; yrot -= v1; return true; } @Override public void onLongPress(MotionEvent motionEvent) { } @Override public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) { return false; } }
這兩種方式繪製3D模型的主要區別就是在 onDrawFrame這個方法中。
一:
for (int i = 0; i <= (mObjLoaderUtil.mSurfaceFloat.length / 3 - 4); i += 4) { gl10.glDrawArrays(GL10.GL_TRIANGLE_FAN, i, 4); }
二:
gl10.glDrawElements(GL10.GL_TRIANGLES, mObjUtils.mIndexShorts.length, GL10.GL_UNSIGNED_SHORT, mObjUtils.mIndexShortBuffer);
在使用OpenGl繪製3D模型前,一定要把這兩種方法研究下。
歡迎各方大佬,指點評論!!!!!!