Memory Leak's picture

Loading .obj models

I have attempted to write a program which parses a .obj file and 'should' display it. However once the program is executed the product is just a blank display screen. I only started with openGL today so there is probably something obvious I'm looking over :p

Form1:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void glControl1_Load(object sender, EventArgs e)
        {
            GL.ClearColor(Color.Black);
 
            glControl1.Paint += glControl1_Paint;
        }
 
        private static void setUpCamera()
        {
 
 
        }
 
        private void glControl1_Paint(object sender, PaintEventArgs e)
        {
            double aspect = glControl1.Width / (double)glControl1.Height;
            GL.Clear(ClearBufferMask.ColorBufferBit);
            GL.Clear(ClearBufferMask.DepthBufferBit);
 
            //'Basic Setup for viewing
            Matrix4 perspective = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)aspect, 0.0001f, 5000.0f); //'Setup Perspective
            Matrix4 lookAt = Matrix4.LookAt(100, 20, 0, 0, 0, 0, 0, 1, 0);//'Setup camera
            GL.MatrixMode(MatrixMode.Projection); //'Load Perspective
            GL.LoadIdentity();
            GL.LoadMatrix(ref perspective);
            GL.MatrixMode(MatrixMode.Modelview);// 'Load Camera
            GL.LoadIdentity();
            GL.LoadMatrix(ref lookAt);
            GL.Viewport(0, 0, glControl1.Width, glControl1.Height); //'Size of window
            GL.Enable(EnableCap.DepthTest); //'Enable correct Z Drawings
            GL.DepthFunc(DepthFunction.Less); //'Enable correct Z Drawings
            //'Rotating
           // GL.Rotate(float.Parse(numericUpDown1.Value.ToString()), 0, 0, 1.0f);
            //GL.Rotate(float.Parse(numericUpDown2.Value.ToString()), 0, 1.0f, 0);
 
            GL.PolygonMode(MaterialFace.FrontAndBack,PolygonMode.Fill);
 
            int objectDisplayList = GL.GenLists(1);
            GL.NewList(objectDisplayList, ListMode.Compile);
            {
                Model m;
                m = OBJLoader.loadModel(@"\bunny.obj");
 
                GL.Begin(BeginMode.Triangles);
 
                foreach (Face face in m.faces){
 
                    //MessageBox.Show((m.normals[5]).ToString());
                    Vector3 n1 = m.normals[(int)face.normal.X - 1];
                    GL.Normal3(n1.X, n1.Y, n1.Z);
                    Vector3 v1 = m.vertices[(int)face.vertex.X - 1];
                    GL.Vertex3(v1.X, v1.Y, v1.Z);
 
                    Vector3 n2 = m.normals[(int)face.normal.Y - 1];
                    GL.Normal3(n1.X, n1.Y, n1.Z);
                    Vector3 v2 = m.vertices[(int)face.vertex.Y - 1];
                    GL.Vertex3(v2.X, v2.Y, v2.Z);
 
                    Vector3 n3 = m.normals[(int)face.normal.Z - 1];
                    GL.Normal3(n1.X, n1.Y, n1.Z);
                    Vector3 v3 = m.vertices[(int)face.vertex.Z - 1];
                    GL.Vertex3(v3.X, v3.Y, v3.Z);
                }
 
                GL.End();
 
            }
 
            //Draw pyramid, Y is up, Z is twards you, X is left and right
            //'Vertex goes (X,Y,Z)
          /*  GL.Begin(BeginMode.Triangles);
            // 'Face 1
            GL.Color3(Color.Red);
            GL.Vertex3(50, 0, 0);
            GL.Color3(Color.White);
            GL.Vertex3(0, 25, 0);
            GL.Color3(Color.Blue);
            GL.Vertex3(0, 0, 50);
            //  'Face 2
            GL.Color3(Color.Green);
            GL.Vertex3(-50, 0, 0);
            GL.Color3(Color.White);
            GL.Vertex3(0, 25, 0);
            GL.Color3(Color.Blue);
            GL.Vertex3(0, 0, 50);
            //  'Face 3
            GL.Color3(Color.Red);
            GL.Vertex3(50, 0, 0);
            GL.Color3(Color.White);
            GL.Vertex3(0, 25, 0);
            GL.Color3(Color.Blue);
            GL.Vertex3(0, 0, -50);
            //   'Face 4
            GL.Color3(Color.Green);
            GL.Vertex3(-50, 0, 0);
            GL.Color3(Color.White);
            GL.Vertex3(0, 25, 0);
            GL.Color3(Color.Blue);
            GL.Vertex3(0, 0, -50);
 
            //   'Finish the begin mode with "end"
            GL.End();*/
 
            // 'Finally...
            GraphicsContext.CurrentContext.VSync = true; // 'Caps frame rate as to not over run GPU
            glControl1.SwapBuffers(); //'Takes from the 'GL' and puts into control
        }
    }

OBJLoader:

class OBJLoader
    {
        public static Model loadModel(String f) {
 
            StreamReader reader = new StreamReader(f); 
 
		Model m = new Model();
		String line;
		while ((line = reader.ReadLine()) != null){
 
			if(line.StartsWith("v ")){
				float x = float.Parse(line.Split(' ')[1]);
				float y = float.Parse(line.Split(' ')[2]);
				float z = float.Parse(line.Split(' ')[3]);
                //MessageBox.Show("" + x + " " + y + " " + z);
				m.vertices.Add(new Vector3(x,y,z));
			}
			else if(line.StartsWith("vn ")){
				float x = float.Parse(line.Split(' ')[1]);
				float y = float.Parse(line.Split(' ')[2]);
				float z = float.Parse(line.Split(' ')[3]);
				m.normals.Add(new Vector3(x,y,z));
			}
			else if(line.StartsWith("f ")){
				Vector3 vertexIndices = new Vector3(float.Parse(line.Split(' ')[1].Split('/')[0]),
													  float.Parse(line.Split(' ')[2].Split('/')[0]),
													  float.Parse(line.Split(' ')[3].Split('/')[0]));
 
                Vector3 normalIndices = new Vector3(float.Parse(line.Split(' ')[1].Split('/')[2]),
                                                      float.Parse(line.Split(' ')[2].Split('/')[2]),
                                                      float.Parse(line.Split(' ')[3].Split('/')[2]));
				m.faces.Add(new Face(vertexIndices, normalIndices));
			}
 
		}
        reader.Close();
		return m;
	}
    }

Model:

class Model
    {
        public IList<Vector3> vertices = new List<Vector3>();
        public IList<Vector3> normals = new List<Vector3>();
        public IList<Face> faces = new List<Face>();
 
    }

Face:

public Vector3 vertex = new Vector3(); //Three indices, not vertices or normals!
        public Vector3 normal = new Vector3();
        public Face(Vector3 vertex, Vector3 normal)
        {
            this.vertex = vertex;
            this.normal = normal;
        }

This is my result after executing:

Any information to help me overcome this issue would be greatly appreciated :)


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
migueltk's picture

Need to call "GL.CallList ()" before calling "SwapBuffers ()".
You should create the list outside the method "Paint ()" for reasons of optimization.

Greetings ...

Memory Leak's picture

Thanks for the reply, what would I use as the parameter for GL.CallList(); in this case? Sorry I am really in-competent.

Memory Leak's picture

Bump

migueltk's picture

Well, I've never used CallLists () in my programs, I have always used VBO (Vertex Buffer Object http://www.songho.ca/opengl/gl_vbo.html), but a simple Google search can show you how it works this. I recommend http://www.songho.ca/opengl/gl_displaylist.html.

In your case and as indicated in Songo must put GL.CallList (objectDisplayList).

Greetings ...

daniklad's picture

First of all, a few tips:

Don't use line.Split() inside another function call when you are reusing the results. Also consider using float.TryParse() instead:

string[] parts = line.Split(' '); // and then use this for each float.Parse()
CultureInfo culture = new CultureInfo("en-US");
float.TryParse(parts[1], NumberStyles.Float, culture, out x);
float.TryParse(parts[2], NumberStyles.Float, culture, out y);
float.TryParse(parts[3], NumberStyles.Float, culture, out z);

This will load your model a lot faster if its big!
Next, try clearing the background with a different color than black. Make sure you have Backface Culling working the right way, lighting enabled, texturing, viewport, perspective projection enabled etc. It's very common to have a black model on a black background when you start programming OpenGL and think nothing is working.

The next thing to think about is that instead of reusing complete vertexes (texcoord + normal + position + color) OBJ reuses each component separately which requires separate index buffers for each component but when you render you probably want each vertex to be complete because it's easier to handle if you only have one vertex buffer and one index buffer for a model.
What you want to do is this. First store the positions (v), normals (vn), texcoords (vt) etc. in separate arrays:

List<Vector3> vertices = new List<Vector3>(512);
List<Vector3> normals = new List<Vector3>(512);
List<Vector2> texcoords = new List<Vector2>(512);

Then when you parse the faces you keep a Hashtable of every vertex combination you have used (24/6/73 for example) which stores the index as it's value.
My example will use a vertex struct like this:

public struct MyVertex{
	float tu,tv, nx,ny,nz, x,y,z;
}
Hashtable vertexLookup = new Hashtable(); // this keeps track of doubles
List<MyVertex> tempvertexlist = new List<MyVertex>(512); // this stores your temporary vertices in the order you encounter them in the face listing
List<float> vertexlist = new List<float>(8192); // this is where you need to put your vertices to load into VBO
List<ushort> indexlist = new List<ushort>(512); // this stores your indices, you can use int but I didn't

Now as you go through the faces, if you encounter a vertex that you have already stored in your final list, reuse it by getting the index from the Hashtable. If the vertex is new, use vertexlist.Length-1 as index and also store it into the Hashtable. Some pseudocode follows:

for each face
string[] parts = line.Split(' ');

for each vertex definition
if vertexLookup.ContainsKey(parts[i])
indexlist.Add( vertexLookup[ parts[i] ] );
else
parse vertex parts by Split('/')

store vertex into tempvertexlist

store index (tempvertexlist.Count-1) into index list

add vertex definition to vertexLookup
endif
endfor
endfor

Remember that C#-arrays are zero-based (starts with 0) but OBJ-indexes are one-based (starts with 1).
Now make sure your tempvertexlist is converted to a float-array (I am not sure this is needed but I did this and it worked) and loaded into a VBO:

// prepare VBO and IBO
int vbo;
int ibo;
 
GL.GenBuffers(1, out vbo);
GL.GenBuffers(1, out ibo);
 
GL.EnableClientState(ArrayCap.TextureCoordArray);
GL.EnableClientState(ArrayCap.NormalArray);
GL.EnableClientState(ArrayCap.VertexArray);
GL.EnableClientState(ArrayCap.IndexArray);
 
int vboSize = vertexarray.Length * sizeof(float);
int iboSize = indexarray.Length * sizeof(ushort);
 
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)vboSize, vertexarray, BufferUsageHint.StaticDraw);
 
GL.BindBuffer(BufferTarget.ElementArrayBuffer, ibo); // notice ElementArrayBuffer here instead of just ArrayBuffer
GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)iboSize, indexarray, BufferUsageHint.StaticDraw);
 
GL.DisableClientState(ArrayCap.TextureCoordArray);
GL.DisableClientState(ArrayCap.NormalArray);
GL.DisableClientState(ArrayCap.VertexArray);
GL.DisableClientState(ArrayCap.IndexArray);

Now you need to draw the result:

GL.EnableClientState(ArrayCap.TextureCoordArray);
GL.EnableClientState(ArrayCap.NormalArray);
GL.EnableClientState(ArrayCap.VertexArray);
GL.EnableClientState(ArrayCap.IndexArray);
 
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, ibo);
 
// setup pointers into VBO
int stride = 8 * sizeof( float ); // adjust this if your Vertex-struct size differs from mine
GL.TexCoordPointer( 2, TexCoordPointerType.Float, stride, (IntPtr) 0  );
GL.NormalPointer( NormalPointerType.Float, stride, (IntPtr) ( 2 * sizeof( float ) ) );
GL.VertexPointer( 3, VertexPointerType.Float, stride, (IntPtr) ( 5 * sizeof( float ) ) );
 
// draw model
GL.DrawElements(BeginMode.Triangles, indexlist.Length, DrawElementsType.UnsignedShort, (IntPtr) 0);
 
GL.DisableClientState(ArrayCap.TextureCoordArray);
GL.DisableClientState(ArrayCap.NormalArray);
GL.DisableClientState(ArrayCap.VertexArray);
GL.DisableClientState(ArrayCap.IndexArray);
// drawing finished

A final note. If you are loading an OBJ-model exported from Blender it seems you have to reverse the second texture coordinate tv (tv = -tv) to get the correct result. Or maybe I did something else wrong. Good luck!

------------------------------------------------------------
The best method for accelerating a computer is the one that boosts it by 9.8 m/s^2.