Search code examples
c#openglmodelingopentkuv-mapping

Inconsistent Model Texturing In OpenGL/OpenTK


I am creating a Model Importer which converts .obj's to my own proprietary format, that will be used for the game I am creating. My model parser appeared to work just fine at first, loading small models from various games like Legend of Zelda, but for whatever reason, fails at correctly texturing advanced models, like Claptrap from Borderlands. Here are some screen grabs to show you what I mean:

The model loader working with a zelda model: enter image description here

And then there is Claptrap NOT being textured correctly: enter image description here

Despite the fact that it is textured just fine in Blender: enter image description here

I don't understand this, nor do I know where the error is coming from. Here is the code for my WavefrontParser, which may yield some unforeseen bug for larger models that I am not aware of:

using System;
using System.Collections.Generic;
using System.IO;
using OpenTK;
using StardustModeling.Modeling.DataTypes;

namespace StardustModeling.Modeling.Parsers
{
    /// <summary>
    /// 
    /// Stardust Engine
    /// 
    /// A simple, lightweight WavefrontModel Parser. This
    /// class serves as the basis for the Stardust Model
    /// conversion wizard. All Stardust Models(.sdm) start
    /// out as .obj files; this class is how we load them
    /// before the conversion to .sdm begins.
    /// 
    /// Original Author: Gordon Kyle Wallace, "Krythic"
    /// 
    /// </summary>
    public static class WavefrontModelParser
    {
        /// <summary>
        /// Parses a Wavefront .obj file. The given
        /// file must be triangulated and have normals
        /// included during exportation from Blender.
        /// </summary>
        /// <param name="path">The path of the .obj on disk</param>
        /// <returns>A WavefrontModel Instance</returns>
        public static WavefrontModel Parse( string path )
        {
            WavefrontModel model = new WavefrontModel();
            VertexIndex[] verticesIndex;
            string[] wavefrontFileData = File.ReadAllLines( path );
            int loopLength = wavefrontFileData.Length; // Squeeze out every last drop!
            for( int lines = 0; lines < loopLength; lines++ )
            {
                string[] lineTokens = wavefrontFileData[ lines ].Split( ' ' );
                switch( lineTokens[ 0 ] )
                {
                    case "v": // Vector
                        float x = Single.Parse( lineTokens[ 1 ] );
                        float y = Single.Parse( lineTokens[ 2 ] );
                        float z = Single.Parse( lineTokens[ 3 ] );
                        model.Vertices.Add( new Vector3( x , y , z ) );
                        break;
                    case "vt": // Texture Coordinate
                        float u = Single.Parse( lineTokens[ 1 ] );
                        float v = Single.Parse( lineTokens[ 2 ] );
                        model.TexCoords.Add( new Vector2( u , v ) );
                        break;
                    case "vn": // Normal
                        float normalX = Single.Parse( lineTokens[ 1 ] );
                        float normalY = Single.Parse( lineTokens[ 2 ] );
                        float normalZ = Single.Parse( lineTokens[ 3 ] );
                        model.Normals.Add( new Vector3( normalX , normalY , normalZ ) );
                        break;
                    case "f":
                        verticesIndex = new VertexIndex[ 3 ];
                        for( int i = 0; i < 3; i++ )
                        {
                            string[] parameters = lineTokens[ i + 1 ].Split( '/' );
                            int vertice = Int32.Parse( parameters[ 0 ] ) - 1;
                            int texture = Int32.Parse( parameters[ 1 ] ) - 1;
                            int normal = Int32.Parse( parameters[ 2 ] ) - 1;
                            verticesIndex[ i ] = new VertexIndex( vertice , normal , texture );
                        }
                        model.Faces.Add( new Face( verticesIndex ) );
                        break;
                }
            }
            return model;
        }
    }
}

My WavefrontModel class:

using System.Collections.Generic;
using OpenTK;
using StardustModeling.Modeling.Parsers;

namespace StardustModeling.Modeling.DataTypes
{
    public class WavefrontModel
    {
        public List<Vector3> Vertices;
        public List<Vector2> TexCoords;
        public List<Vector3> Normals;
        public List<Face> Faces;
        public string ModelSource;

        public int TotalTriangles
        {
            get
            {
                return this.Vertices.Count/3;
            }
        }

        public WavefrontModel()
        {
            this.Vertices = new List<Vector3>();
            this.TexCoords = new List<Vector2>();
            this.Normals = new List<Vector3>();
            this.Faces = new List<Face>();
        }

        public WavefrontModel(int buffer)
        {
            this.Vertices = new List<Vector3>(buffer);
            this.TexCoords = new List<Vector2>(buffer);
            this.Normals = new List<Vector3>(buffer);
            this.Faces = new List<Face>(buffer);
        }

        public WavefrontModel(string modelPath, bool loadImmediately)
        {
            this.ModelSource = modelPath;
            if (loadImmediately)
            {
                Load();
            }
        }

        private void Load()
        {
            WavefrontModel model = WavefrontModelParser.Parse(ModelSource);
            this.Vertices = model.Vertices;
            this.TexCoords = model.TexCoords;
            this.Normals = model.Normals;
            this.Faces = model.Faces;
        }
    }

}

And my Material class, which may also yield a mistake:

using System.Drawing;
using System.Drawing.Imaging;
using OpenTK.Graphics.OpenGL;
using PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat;

namespace StardustFramework.Framework.OpenGL.Texturing
{
    public enum MaterialType
    {
        /// <summary>
        /// Represents a Diffuse Texture
        /// </summary>
        Diffuse,
        /// <summary>
        /// Represents a Normal Texture
        /// </summary>
        Normal
    }

    public class Material
    {
        /// <summary>
        /// The name of the Material
        /// </summary>
        public string Name;
        /// <summary>
        /// The Diffuse Texture
        /// </summary>
        public int Diffuse;
        /// <summary>
        /// The Normal Texture
        /// </summary>
        public int NormalMap;
        /// <summary>
        /// The Ambient Color for the Material
        /// </summary>
        public Color AmbientColor;

        public Material( string materialName )
        {
            this.Name = materialName;
            this.AmbientColor = Color.White;
            this.Diffuse = 0;
            this.NormalMap = 0;
        }

        /// <summary>
        /// Loads a Bitmap as a Diffuse texture.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        public void LoadDiffuse( Bitmap bitmap)
        {
            GL.Enable( EnableCap.Texture2D );
            //GL.Hint( HintTarget.PerspectiveCorrectionHint , HintMode.Nicest );
            GL.GenTextures( 1 , out Diffuse );
            GL.BindTexture( TextureTarget.Texture2D , Diffuse );
            GL.TexParameter( TextureTarget.Texture2D , TextureParameterName.TextureMinFilter , ( int )TextureMinFilter.Nearest );
            GL.TexParameter( TextureTarget.Texture2D , TextureParameterName.TextureMagFilter , ( int )TextureMagFilter.Nearest );
            BitmapData data = bitmap.LockBits( new Rectangle( 0 , 0 , bitmap.Width , bitmap.Height ) ,
                ImageLockMode.ReadOnly , System.Drawing.Imaging.PixelFormat.Format32bppArgb );
            GL.TexImage2D( TextureTarget.Texture2D , 0 , PixelInternalFormat.Rgba , data.Width , data.Height , 0 ,
                PixelFormat.Bgra , PixelType.UnsignedByte , data.Scan0 );
            bitmap.UnlockBits( data );
            GL.BindTexture( TextureTarget.Texture2D , 0 );
        }
    }
}

I would really like it if someone could help me spot this bug; I am starting to tear my hair out(which I don't have to begin with, lol).

EDIT:

I forgot to mention how I am rendering this. I use OpenTK/OpenGL, and draw via immediate mode(for initial application testing).

private void DrawMesh( WavefrontModel m )
        {

            GL.Enable( EnableCap.Texture2D );
            GL.Color3( _modelMaterial.AmbientColor );
            if( _modelMaterial.Diffuse > 0 )
            {
                GL.BindTexture( TextureTarget.Texture2D , _modelMaterial.Diffuse );
            }
            GL.Begin( PrimitiveType.Triangles );
            for( int i = 0; i < m.Faces.Count; i++ )
            {
                for( int index = 0; index < m.Faces[ i ].Indices.Length; index++ )
                {
                    Vector3 v = m.Vertices[ m.Faces[ i ].Indices[ index ].Vector ];
                    Vector3 n = m.Normals[ m.Faces[ i ].Indices[ index ].Normal ];
                    Vector2 tc = m.TexCoords[ m.Faces[ i ].Indices[ index ].TextureCoordinateIndex ];
                    GL.Normal3( n.X , n.Y , n.Z );
                    GL.TexCoord2( tc.X , tc.Y );
                    GL.Vertex3( v.X , v.Y , v.Z );
                }
            }
            GL.End();
        }

Solution

  • After many excruciating hours of tearing my hair out, I finally realized my problem. What I did not think about, and it seems dumb to me now, was that I was trying to texture a model crafted for DirectX within an OpenGL environment. Why does this matter? Well, in OpenGL the texture origin is bottom-left hand corner 0,0. In DirectX it's top-left hand corner. So by simply flipping the texture vertically in gimp I got it to work.

    enter image description here

    It's nice to know that there was nothing wrong with my Model Loader, I just wasn't thinking about the targeted platform/api of the models I was using.