Hello, next I will introduce OpenGL ES 3. OBJ file rendering.
The previous section introduces the OBJ file and its text structure. Next, it introduces how to load the OBJ file into memory and render it through OpenGL.
1. OBJ file parsing class
Because OBJ text files are stored according to certain rules (see the previous section for details), we first introduce the loading and parsing of OBJ files, and the LoadedObjectVertexNormalTexture class used to render objects after loading. This paper introduces loading vertex coordinates, triangle faces, texture coordinates and other information.
import android.opengl.GLES30; //Loaded object - only carrying vertex information, random color public class LoadedObjectVertexNormalTexture { int mProgram;//Custom render pipeline shader program id int muMVPMatrixHandle;//Total transformation matrix reference int muMMatrixHandle;//Transformation matrix of position and rotation int maPositionHandle; //Vertex position attribute reference int maNormalHandle; //Vertex normal vector attribute reference int maLightLocationHandle;//Light location attribute reference int maCameraHandle; //Camera position property reference int maTexCoorHandle; //Vertex texture coordinate attribute reference String mVertexShader;//Vertex shader code script String mFragmentShader;//Chip shader code script int vCount=0; FloatBuffer mVertexBuffer;//Vertex coordinate data buffer FloatBuffer mNormalBuffer;//Vertex normal vector data buffer FloatBuffer mTexCoorBuffer;//Vertex texture coordinate data buffer public LoadedObjectVertexNormalTexture(MySurfaceView mv,float[] vertices,float[] normals,float texCoors[]) {//constructor //Initialize vertex coordinate, normal vector, texture coordinate data initVertexData(vertices,normals,texCoors); //Initialize shader initShader(mv); } //Method of initializing vertex coordinate, normal vector and texture coordinate data public void initVertexData(float[] vertices,float[] normals,float texCoors[]) { //Initialization of vertex coordinate data============================ vCount=vertices.length/3; //Create vertex coordinate data buffer //vertices.length*4 is because an integer has four bytes ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4); vbb.order(ByteOrder.nativeOrder());//Set byte order mVertexBuffer = vbb.asFloatBuffer();//Convert to Float buffer mVertexBuffer.put(vertices);//Put vertex coordinate data into buffer mVertexBuffer.position(0);//Set buffer start position //Special note: because the byte order of different platforms is different, the data unit is not byte and must pass ByteBuffer //The key to the transformation is to set the nativeOrder() through ByteOrder, otherwise there may be problems //Initialization of vertex coordinate data============================ //Initialization of vertex normal vector data============================ ByteBuffer cbb = ByteBuffer.allocateDirect(normals.length*4); cbb.order(ByteOrder.nativeOrder());//Set byte order mNormalBuffer = cbb.asFloatBuffer();//Convert to Float buffer mNormalBuffer.put(normals);//Put vertex normal vector data into buffer mNormalBuffer.position(0);//Set buffer start position //Special note: because the byte order of different platforms is different, the data unit is not byte and must pass ByteBuffer //The key to the transformation is to set the nativeOrder() through ByteOrder, otherwise there may be problems //Initialization of vertex shading data============================ //Initialization of vertex texture coordinate data============================ ByteBuffer tbb = ByteBuffer.allocateDirect(texCoors.length*4); tbb.order(ByteOrder.nativeOrder());//Set byte order mTexCoorBuffer = tbb.asFloatBuffer();//Convert to Float buffer mTexCoorBuffer.put(texCoors);//Put vertex texture coordinate data into buffer mTexCoorBuffer.position(0);//Set buffer start position //Special note: because the byte order of different platforms is different, the data unit is not byte and must pass ByteBuffer //The key to the transformation is to set the nativeOrder() through ByteOrder, otherwise there may be problems //Initialization of vertex texture coordinate data============================ } //Method of drawing loading object public void drawSelf(int texId) { //Make use of a set of shader programs GLES30.glUseProgram(mProgram); //Pass the final transform matrix into the shader program GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0); //Pass position, rotation transform matrix into shader program GLES30.glUniformMatrix4fv(muMMatrixHandle, 1, false, MatrixState.getMMatrix(), 0); //Pass the light position into the shader program GLES30.glUniform3fv(maLightLocationHandle, 1, MatrixState.lightPositionFB); //Pass camera position into shader program GLES30.glUniform3fv(maCameraHandle, 1, MatrixState.cameraFB); // Passing vertex position data into the rendering pipeline GLES30.glVertexAttribPointer (maPositionHandle, 3, GLES30.GL_FLOAT, false, 3*4, mVertexBuffer); //Passing vertex normal vector data into the rendering pipeline GLES30.glVertexAttribPointer (maNormalHandle, 3, GLES30.GL_FLOAT, false, 3*4, mNormalBuffer); //Passing vertex texture coordinate data into the rendering pipeline GLES30.glVertexAttribPointer (maTexCoorHandle, 2, GLES30.GL_FLOAT, false, 2*4, mTexCoorBuffer); //Enable vertex position, normal vector, texture coordinate data array GLES30.glEnableVertexAttribArray(maPositionHandle); GLES30.glEnableVertexAttribArray(maNormalHandle); GLES30.glEnableVertexAttribArray(maTexCoorHandle); //Enable texture coordinate data array //Binding texture GLES30.glActiveTexture(GLES30.GL_TEXTURE0);//Enable texture 0 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texId);//Binding texture //Drawing loaded objects GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vCount); } }
Specific loading tool class LoadUtil: alv is the list of original vertex coordinates -- load directly from obj file, alvResult is the list of result vertex coordinates -- organize by face, alt is the list of original texture coordinates, altResult is the list of texture coordinate results, aln is the list of original normal vectors, and alnResult is the list of normal vector results.
public class LoadUtil { //Loading objects with vertex information from obj file public static LoadedObjectVertexNormalTexture loadFromFile (String fname, Resources r,MySurfaceView mv) { //References to objects after loading LoadedObjectVertexNormalTexture lo=null; //Original vertex coordinate list -- load directly from obj file ArrayList<Float> alv=new ArrayList<Float>(); //Result vertex coordinate list -- organized by face ArrayList<Float> alvResult=new ArrayList<Float>(); //Original texture coordinates list ArrayList<Float> alt=new ArrayList<Float>(); //Texture coordinate result list ArrayList<Float> altResult=new ArrayList<Float>(); //Original normal vector list ArrayList<Float> aln=new ArrayList<Float>(); //List of normal vector results ArrayList<Float> alnResult=new ArrayList<Float>(); try { InputStream in=r.getAssets().open(fname); InputStreamReader isr=new InputStreamReader(in); BufferedReader br=new BufferedReader(isr); String temps=null; //Scan the file, and execute different processing logic according to different line types while((temps=br.readLine())!=null) {//Read a line of text String[] tempsa=temps.split("[ ]+");//Split text lines with spaces if(tempsa[0].trim().equals("v")) {//This line is vertex coordinate line //If it is a vertex coordinate row, the XYZ coordinates of this vertex are extracted and added to the original vertex coordinate list alv.add(Float.parseFloat(tempsa[1])); alv.add(Float.parseFloat(tempsa[2])); alv.add(Float.parseFloat(tempsa[3])); } else if(tempsa[0].trim().equals("vt")) {//This is the texture coordinate line //If it is a texture coordinate line, extract the ST coordinate and add it to the original texture coordinate list alt.add(Float.parseFloat(tempsa[1])); alt.add(1-Float.parseFloat(tempsa[2])); } else if(tempsa[0].trim().equals("vn")) {//This line is a normal vector line //If it is a texture coordinate line, extract the ST coordinate and add it to the original texture coordinate list aln.add(Float.parseFloat(tempsa[1]));//Put in the aln list aln.add(Float.parseFloat(tempsa[2])); //Put in the aln list aln.add(Float.parseFloat(tempsa[3])); //Put in the aln list } else if(tempsa[0].trim().equals("f")) {//This behavior is triangular face //Calculate the index of the 0th vertex and get the XYZ coordinates of the vertex int index=Integer.parseInt(tempsa[1].split("/")[0])-1; float x0=alv.get(3*index); float y0=alv.get(3*index+1); float z0=alv.get(3*index+2); alvResult.add(x0); alvResult.add(y0); alvResult.add(z0); //Calculate the index of the first vertex and get the XYZ three coordinates of this vertex index=Integer.parseInt(tempsa[2].split("/")[0])-1; float x1=alv.get(3*index); float y1=alv.get(3*index+1); float z1=alv.get(3*index+2); alvResult.add(x1); alvResult.add(y1); alvResult.add(z1); //Calculate the index of the second vertex and get the XYZ coordinates of the vertex index=Integer.parseInt(tempsa[3].split("/")[0])-1; float x2=alv.get(3*index); float y2=alv.get(3*index+1); float z2=alv.get(3*index+2); alvResult.add(x2); alvResult.add(y2); alvResult.add(z2); //Organize texture coordinates into the resulting texture coordinates list //Texture coordinates of vertex 0 int indexTex=Integer.parseInt(tempsa[1].split("/")[1])-1; altResult.add(alt.get(indexTex*2)); altResult.add(alt.get(indexTex*2+1)); //Texture coordinates of the first vertex indexTex=Integer.parseInt(tempsa[2].split("/")[1])-1; altResult.add(alt.get(indexTex*2)); altResult.add(alt.get(indexTex*2+1)); //Texture coordinates of the second vertex indexTex=Integer.parseInt(tempsa[3].split("/")[1])-1; altResult.add(alt.get(indexTex*2)); altResult.add(alt.get(indexTex*2+1)); //================================================= //Calculate the normal vector index of the 0th vertex int indexN=Integer.parseInt(tempsa[1].split("/")[2])-1;//Get normal vector number float nx0=aln.get(3*indexN);//Get x value of normal vector float ny0=aln.get(3*indexN+1);//Get y value of normal vector float nz0=aln.get(3*indexN+2);//Get z value of normal vector alnResult.add(nx0);//Put in the alnResult list alnResult.add(ny0);//Put in the alnResult list alnResult.add(nz0); //Put in the alnResult list //Calculate the index of the first vertex and get the XYZ three coordinates of this vertex indexN=Integer.parseInt(tempsa[2].split("/")[2])-1; float nx1=aln.get(3*indexN); float ny1=aln.get(3*indexN+1); float nz1=aln.get(3*indexN+2); alnResult.add(nx1); alnResult.add(ny1); alnResult.add(nz1); //Calculate the index of the second vertex and get the XYZ coordinates of the vertex indexN=Integer.parseInt(tempsa[3].split("/")[2])-1; float nx2=aln.get(3*indexN); float ny2=aln.get(3*indexN+1); float nz2=aln.get(3*indexN+2); alnResult.add(nx2); alnResult.add(ny2); alnResult.add(nz2); } } //Generate vertex array int size=alvResult.size(); float[] vXYZ=new float[size]; for(int i=0;i<size;i++) { vXYZ[i]=alvResult.get(i); } //Generate texture array size=altResult.size(); float[] tST=new float[size]; for(int i=0;i<size;i++) { tST[i]=altResult.get(i); } //Generate normal vector array size=alnResult.size();//Get the size of the normal vector list float[] nXYZ=new float[size];//Create an array of normal vectors for(int i=0;i<size;i++) { nXYZ[i]=alnResult.get(i);//Store the normal vector value in the array } //Create a load object object lo=new LoadedObjectVertexNormalTexture(mv,vXYZ,nXYZ,tST); } catch(Exception e) { Log.d("load error", "load error"); e.printStackTrace(); } return lo;//Returns a reference to the created object object } }
2. Describes the MySurfaceView class used to render the entire 3D scene: customize the rendererscenerer, and set the rendering mode to render rendermode ﹐ continuously.
class MySurfaceView extends GLSurfaceView { private final float TOUCH_SCALE_FACTOR = 180.0f/320;//Angular scaling private SceneRenderer mRenderer;//Scene renderer private float mPreviousY;//Last touch Y coordinate private float mPreviousX;//Last touch position X coordinate int textureId;//System assigned texture id public MySurfaceView(Context context) { super(context); this.setEGLContextClientVersion(3); //Set to use OPENGL ES3.0 mRenderer = new SceneRenderer(); //Create scene renderer setRenderer(mRenderer); //Set up the renderer setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//Set render mode to active } //Touch event callback method @Override public boolean onTouchEvent(MotionEvent e) { float y = e.getY(); float x = e.getX(); switch (e.getAction()) { case MotionEvent.ACTION_MOVE: float dy = y - mPreviousY;//Calculate stylus Y displacement float dx = x - mPreviousX;//Calculate stylus X displacement mRenderer.yAngle += dx * TOUCH_SCALE_FACTOR;//Set rotation angle along y axis mRenderer.xAngle+= dy * TOUCH_SCALE_FACTOR;//Set rotation angle along x axis requestRender();//Redraw } mPreviousY = y;//Record the position of the stylus mPreviousX = x;//Record the position of the stylus return true; } private class SceneRenderer implements GLSurfaceView.Renderer { float yAngle;//Angle of rotation about Y axis float xAngle; //Angle of rotation about X axis //Objects loaded from the specified obj file LoadedObjectVertexNormalTexture lovo; public void onDrawFrame(GL10 gl) { //Clear depth buffer and color buffer GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT); //Coordinate system extrapolation MatrixState.pushMatrix(); MatrixState.translate(0, -16f, -60f); //Rotate around Y-axis and X-axis MatrixState.rotate(yAngle, 0, 1, 0); MatrixState.rotate(xAngle, 1, 0, 0); //If the loaded object is not empty, draw the object if(lovo!=null) { lovo.drawSelf(textureId); } MatrixState.popMatrix(); } public void onSurfaceChanged(GL10 gl, int width, int height) { //Set window size and location GLES30.glViewport(0, 0, width, height); //Calculate the aspect ratio of GLSurfaceView float ratio = (float) width / height; //Call this method to calculate and generate perspective projection matrix MatrixState.setProjectFrustum(-ratio, ratio, -1, 1, 2, 100); //Call this method to generate camera 9 parameter position matrix MatrixState.setCamera(0,0,0,0f,0f,-1f,0f,1.0f,0.0f); } public void onSurfaceCreated(GL10 gl, EGLConfig config) { //Set screen background color RGBA GLES30.glClearColor(0.0f,0.0f,0.0f,1.0f); //Open depth detection GLES30.glEnable(GLES30.GL_DEPTH_TEST); //Turn on back clipping GLES30.glEnable(GLES30.GL_CULL_FACE); //Initialize transformation matrix MatrixState.setInitStack(); //Initialize light location MatrixState.setLightLocation(40, 10, 20); //Load objects to draw lovo=LoadUtil.loadFromFile("ch_t.obj", MySurfaceView.this.getResources(),MySurfaceView.this); //Load texture map textureId=initTexture(R.drawable.ghxp); } } public int initTexture(int drawableId)//textureId { //Generate texture ID int[] textures = new int[1]; GLES30.glGenTextures ( 1, //Number of texture IDs generated textures, //Array of texture IDS 0 //Offset ); int textureId=textures[0]; GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId); GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER,GLES30.GL_NEAREST); GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,GLES30.GL_TEXTURE_MAG_FILTER,GLES30.GL_LINEAR); GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S,GLES30.GL_REPEAT); GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T,GLES30.GL_REPEAT); //Load picture through input stream ==================== begin=================== InputStream is = this.getResources().openRawResource(drawableId); Bitmap bitmapTmp; try { bitmapTmp = BitmapFactory.decodeStream(is); } finally { try { is.close(); } catch(IOException e) { e.printStackTrace(); } } //Load picture through input stream ==================== end===================== GLUtils.texImage2D ( GLES30.GL_TEXTURE_2D, //Texture type 0, GLUtils.getInternalFormat(bitmapTmp), bitmapTmp, //texture image GLUtils.getType(bitmapTmp), 0 //Texture border size ); bitmapTmp.recycle(); //Release image after texture loading return textureId; } }
Implementation of the onSurfaceCreated method, in which an object of the LoadedObjectVertexNormalTexture class is created after the transformation matrix is initialized.
3. Example rendering:
Finally, welcome to exchange and study together: wechat: liaosy666; QQ:2209115372 .