Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I am having an issue where triangles in a model aren't being shown.

Left Wireframe, Right finished

The left is wireframe, the right is the finished image. For some reason a bunch of the triangles aren't being shown.

The project is in C#, using OpenGL (OpenTK). I'll provide more information if neccessary (such as code), but there's a lot of code, so i'd rather check if anyone has a simple solution first... Please comment if you need more info though.

EDIT: Here's the buffer code if it helps...

public void LoadBuffers(MeshData m, out uint indexBuffer, out uint dataBuffer, out int vertOffset, out int normOffset, out int texcoordOffset)
        float[] verts, norms, texcoords;
        uint[] indices;
        m.OpenGLArrays(out verts, out norms, out texcoords, out indices);
        GL.GenBuffers(1, out dataBuffer);
        GL.GenBuffers(1, out indexBuffer);

        int buffersize = (verts.Length + norms.Length + texcoords.Length);
        float[] bufferdata = new float[buffersize];
        vertOffset = 0;
        normOffset = verts.Length;
        texcoordOffset = (verts.Length + norms.Length);

        verts.CopyTo(bufferdata, vertOffset);
        norms.CopyTo(bufferdata, normOffset);
        texcoords.CopyTo(bufferdata, texcoordOffset);

        bool v = false;
        for (int i = texcoordOffset; i < bufferdata.Length; i++)
            if (v)
                bufferdata[i] = 1 - bufferdata[i];
                v = false;
                v = true;

        GL.BindBuffer(BufferTarget.ArrayBuffer, dataBuffer);
        GL.BufferData<float>(BufferTarget.ArrayBuffer, (IntPtr)(buffersize * sizeof(float)), bufferdata,

        GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexBuffer);
                      (IntPtr)(indices.Length * sizeof(uint)), indices, BufferUsageHint.StaticDraw);

    public bool DrawBuffer(MeshData m, uint tex, uint indexBuffer, uint dataBuffer, int vertOffset, int normOffset, int texcoordOffset)
        tex = tempTexture;
        GL.BindTexture(TextureTarget.Texture2D, tex);
        GL.BindBuffer(BufferTarget.ArrayBuffer, dataBuffer);
        GL.NormalPointer(NormalPointerType.Float, 0, (IntPtr)(normOffset * sizeof(float)));
        GL.TexCoordPointer(2, TexCoordPointerType.Float, 0, (IntPtr)(texcoordOffset * sizeof(float)));
        GL.VertexPointer(3, VertexPointerType.Float, 0, (IntPtr)(vertOffset * sizeof(float)));
        GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexBuffer);
        GL.DrawElements(BeginMode.Triangles, m.Tris.Length * 3, DrawElementsType.UnsignedInt, IntPtr.Zero);

        return true;

EDIT 2: And here is my OBJ loader... There's quite a chance this is the problem.

public class ObjLoader
    public MeshData LoadStream(Stream stream)
        StreamReader reader = new StreamReader(stream);
        List<Vector3> points = new List<Vector3>();
        List<Vector3> normals = new List<Vector3>();
        List<Vector2> texCoords = new List<Vector2>();
        List<Tri> tris = new List<Tri>();
        string line;
        char[] splitChars = { ' ' };
        while ((line = reader.ReadLine()) != null)
            line = line.Trim(splitChars);
            line = line.Replace("  ", " ");

            string[] parameters = line.Split(splitChars);

            switch (parameters[0])
                case "p":
                    // Point

                case "v":
                    // Vertex
                    float x = float.Parse(parameters[1], CultureInfo.InvariantCulture.NumberFormat);
                    float y = float.Parse(parameters[2], CultureInfo.InvariantCulture.NumberFormat);
                    float z = float.Parse(parameters[3], CultureInfo.InvariantCulture.NumberFormat);
                    points.Add(new Vector3(x, y, z));

                case "vt":
                    // TexCoord
                    float u = float.Parse(parameters[1], CultureInfo.InvariantCulture.NumberFormat);
                    float v = float.Parse(parameters[2], CultureInfo.InvariantCulture.NumberFormat);
                    texCoords.Add(new Vector2(u, v));

                case "vn":
                    // Normal
                    float nx = float.Parse(parameters[1], CultureInfo.InvariantCulture.NumberFormat);
                    float ny = float.Parse(parameters[2], CultureInfo.InvariantCulture.NumberFormat);
                    float nz = float.Parse(parameters[3], CultureInfo.InvariantCulture.NumberFormat);
                    normals.Add(new Vector3(nx, ny, nz));

                case "f":
                    // Face

        Vector3[] p = points.ToArray();
        Vector2[] tc = texCoords.ToArray();
        Vector3[] n = normals.ToArray();
        Tri[] f = tris.ToArray();

        // If there are no specified texcoords or normals, we add a dummy one.
        // That way the Points will have something to refer to.
        if (tc.Length == 0)
            tc = new Vector2[1];
            tc[0] = new Vector2(0, 0);
        if (n.Length == 0)
            n = new Vector3[1];
            n[0] = new Vector3(1, 0, 0);

        return new MeshData(p, n, tc, f);

    public MeshData LoadFile(string file)
        // Silly me, using() closes the file automatically.
        using (FileStream s = File.Open(file, FileMode.Open))
            return LoadStream(s);

    private static Tri[] parseFace(string[] indices)
        Point[] p = new Point[indices.Length - 1];
        for (int i = 0; i < p.Length; i++)
            p[i] = parsePoint(indices[i + 1]);
        return Triangulate(p);
        //return new Face(p);

    // Takes an array of points and returns an array of triangles.
    // The points form an arbitrary polygon.
    private static Tri[] Triangulate(Point[] ps)
        List<Tri> ts = new List<Tri>();
        if (ps.Length < 3)
            throw new Exception("Invalid shape!  Must have >2 points");

        Point lastButOne = ps[1];
        Point lastButTwo = ps[0];
        for (int i = 2; i < ps.Length; i++)
            Tri t = new Tri(lastButTwo, lastButOne, ps[i]);
            lastButOne = ps[i];
            lastButTwo = ps[i - 1];
        return ts.ToArray();

    private static Point parsePoint(string s)
        s += "//";
        char[] splitChars = { '/' };
        string[] parameters = s.Split(splitChars);
        int vert, tex, norm;
        vert = tex = norm = 0;
        vert = int.Parse(parameters[0]) - 1;
        // Texcoords and normals are optional in .obj files
        if (parameters[1] != "")
            tex = int.Parse(parameters[1]) - 1;
        if (parameters[2] != "")
            norm = int.Parse(parameters[2]) - 1;
        return new Point(vert, norm, tex);
share|improve this question

Looking at your screen it seems that the model consists of quads. Now there are two possible problems you could have:

  1. You render quads as 2 triangles, without properly paying attention to CW/CCW order of vertices. That is one triangle comes facing camera and second facing away.
  2. Or you could be rendering triangles instead of quads. So instead of 4 vertices you render just 3 and 4th gets skipped.
share|improve this answer
I agree with the general diagnosis of “quads being incorrectly converted to triangles”, but the image looks to me like quads being drawn as two triangles (1 2 3) (1 2 4), which makes half-overlapping triangles not covering the fourth part, rather than the proper (1 2 3) (3 2 4) (assuming CCW numbering of the quad's vertices). – Kevin Reid Jul 13 '12 at 15:42
The model is exported from Autodesk Mudbox in a .obj format. So i don't know whether there are particular implications i should know regarding that... But when i tried rendering with quads it went a bit... weird shall we say. It's definitely not meant to be quads. – Randomman159 Jul 13 '12 at 17:20
Perhaps your rendering code is flawed, can you copy here the vertice assignment part? – Krom Stern Jul 13 '12 at 19:14
@KevinReid assuming counterclockwise numbering, your second example would still be overlapping. You'd need (1,2,3) (1,3,4) – Jimmy Jul 13 '12 at 22:18
up vote 1 down vote accepted

Checked the .obj loader, there was a flaw in it so that it didn't correctly load in quads and split them into triangles... It split quad ABCD into ABC and BCA instead of ABC and ACD

Sorry, thanks for all the attempted help though!

next time... I'm writing my own importer :P

share|improve this answer

It's possible that you're not being consistent with whether faces are drawn clockwise or counterclockwise, meaning that some of the faces are "backwards," that is, facing away from the camera. If you have backface culling enabled or any of various lighting setups, those faces will not be rendered or will appear black.

share|improve this answer
Seeing that mesh and polies do not overlap the back side I would assume face culling is working properly. – Krom Stern Jul 13 '12 at 11:03
Yes, only the inside of the model is shown in the screen shots (woops i had it backwards), but the point remains... Culling isn't causing the triangle gaps – Randomman159 Jul 13 '12 at 17:19

The most common problems are (1) that the triangles in question are interpreted as backward facing (2) that the triangles in question are thought to be invisible, but the blocking triangle is transparent, or nearly so. (3) that the corners of the triangle are all invisible, but the "body" of the triangle is not.

In general, there are many ways to lose triangles, especially if floating point numbers are used to represent coordinates.

share|improve this answer
Slightly confused regarding what you mean with points 2 and 3. And no, 1 is not the case. – Randomman159 Jul 13 '12 at 18:20
if you think triangle "a" is completely hidden by triangle "b", you don't need to render "a". Under some assumptions about how a triangle mesh is formed, if the corners of the triangle are invisible then so is the rest of the triangle. – ddyer Jul 13 '12 at 23:24
and how would i go about searching whether this is the issue? – Randomman159 Jul 14 '12 at 16:21

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.