Krythic
Krythic

Reputation: 4364

Strange OpenGL Texture Coordinates When Loading Model

I decided to implement a model loader into my OpenTK C# game; it renders fine except for the fact that the texture coordinates are completely bizarre. The code I am using is a slightly modified version of an OpenTK model loader I found on the internet. I have tried uv-unwrapping my model in blender but it seems as though the results are always just as screwed up.

Here is what my model looks like in Blender:

enter image description here

And here is what it looks like in my game:

enter image description here

Here is my WavefrontModel class:

using System.Collections.Generic;
using OpenTK;

namespace GameProject.Game.Framework.ModelLoader {
    public class WavefrontModel {
        public List<Vector3> Vertices;
        public List<Vector2> TexCoords;
        public List<Vector3> Normals;
        public List<Face> Faces;

        public WavefrontModel( string modelPath ) {
            loadModel( modelPath );
        }

        public WavefrontModel(
            List<Vector3> points , List<Vector2> texCoords , List<Vector3> normals , List<Face> faces ) {
            this.Vertices = points;
            this.TexCoords = texCoords;
            this.Normals = normals;
            this.Faces = faces;
        }

        private void loadModel( string modelPath ) {
            WavefrontModel model = new WavefrontModelLoader().LoadFile( modelPath );
            this.Vertices = model.Vertices;
            this.TexCoords = model.TexCoords;
            this.Normals = model.Normals;
            this.Faces = model.Faces;
        }
    }

    public struct ModelVertex {
        public int Vertex;
        public int Normal;
        public int TexCoord;

        public ModelVertex( int v , int n , int t ) {
            Vertex = v;
            Normal = n;
            TexCoord = t;
        }
    }

    public struct Face {
        public ModelVertex[] Points;

        public Face( int i ) {
            Points = new ModelVertex[ i ];
        }

        public Face( ModelVertex[] i ) {
            Points = i;
        }
    }
}

And Here is my WavefrontModelLoader class:

using System.Collections.Generic;
using System.IO;
using OpenTK;

namespace GameProject.Game.Framework.ModelLoader {
    public class WavefrontModelLoader {

        public WavefrontModel 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<int> indices = new List<int>();
            List<Face> faces = new List<Face>();

            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
                        break;

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

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

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

                    case "f":
                        faces.Add( parseFace( parameters ) );
                        break;
                }
            }
            return new WavefrontModel(points,texCoords,normals,faces);
        }

        public WavefrontModel LoadFile( string file ) {
            using( FileStream s = File.Open( file , FileMode.Open ) ) {
                WavefrontModel mesh = LoadStream( s );
                s.Close();
                return mesh;
            }
        }

        private static Face parseFace( string[] indices ) {
            ModelVertex[] p = new ModelVertex[ indices.Length - 1 ];
            for( int i = 0; i < p.Length; i++ ) {
                p[ i ] = parsePoint( indices[ i + 1 ] );
            }
            return new Face( p );
        }

        private static ModelVertex parsePoint( string s ) {
            char[] splitChars = { '/' };
            string[] parameters = s.Split( splitChars );
            int vert = int.Parse( parameters[ 0 ] ) - 1;
            int tex = int.Parse( parameters[ 1 ] ) - 1;
            int norm = int.Parse( parameters[ 2 ] ) - 1;
            return new ModelVertex( vert , norm , tex );
        }
    }
}

My Model is loaded like this:

rockMesh = new WavefrontModel( "C:/Users/Krynn/Desktop/RockModel/untitled.obj" );

And Rendered like this:

private void DrawMesh(WavefrontModel m) {

            GL.Enable( EnableCap.Texture2D );
            GL.BindTexture( TextureTarget.Texture2D , rockTexture.GetID() );
            GL.Begin( BeginMode.Triangles );
            foreach( Face f in m.Faces ) {

                foreach( ModelVertex p in f.Points ) {

                    Vector3 v = m.Vertices[ p.Vertex ];
                    Vector3 n = m.Normals[ p.Normal ];
                    Vector2 tc = m.TexCoords[ p.TexCoord ];
                    GL.Vertex3( v.X , v.Y , v.Z );
                    GL.Normal3( n.X , n.Y , n.Z );
                    GL.TexCoord2( tc.Y , tc.X );

                }
            }
            GL.End();
        }

And here is the model that Blender generated, I noticed that the "vt" are not 1.0 and 0.0, could this be the issue?

# Blender v2.71 (sub 0) OBJ File: 'untitled.blend'
# www.blender.org
mtllib untitled.mtl
o Cube_Cube.001
v -0.872541 -0.208332 -0.514779
v -0.872541 -0.208332 -2.514779
v 1.127459 -0.208332 -2.514779
v 1.127459 -0.208332 -0.514779
v -0.872541 1.791668 -0.514779
v -0.872541 1.791668 -2.514779
v 1.127459 1.791668 -2.514779
v 1.127459 1.791668 -0.514779
vt 0.999900 0.000100
vt 0.999900 0.999900
vt 0.000100 0.999900
vt 0.000100 0.000100
vn -1.000000 0.000000 0.000000
vn 0.000000 0.000000 -1.000000
vn 1.000000 0.000000 0.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 1.000000 0.000000
usemtl None
s off
f 6/1/1 2/2/1 1/3/1
f 7/1/2 3/2/2 2/3/2
f 8/3/3 4/4/3 3/1/3
f 5/3/4 1/4/4 4/1/4
f 2/4/5 3/1/5 4/2/5
f 7/2/6 6/3/6 5/4/6
f 5/4/1 6/1/1 1/3/1
f 6/4/2 7/1/2 2/3/2
f 7/2/3 8/3/3 3/1/3
f 8/2/4 5/3/4 4/1/4
f 1/3/5 2/4/5 4/2/5
f 8/1/6 7/2/6 5/4/6

Also, here is my Texture class:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK.Graphics.OpenGL;
using System.Drawing.Imaging;

namespace GameProject.Game.Framework {
    public class Texture {

        private int textureID;

        public Texture( string texturePath ) {
            Load( texturePath );
        }

        public Texture( Bitmap texture ) {
            LoadInternal( texture );
        }

        private void LoadInternal( Bitmap bitmap ) {
            GL.Enable( EnableCap.Texture2D );
            GL.Hint( HintTarget.PerspectiveCorrectionHint , HintMode.Nicest );
            GL.GenTextures( 1 , out textureID );
            GL.BindTexture( TextureTarget.Texture2D , textureID );
            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 ,
                OpenTK.Graphics.OpenGL.PixelFormat.Bgra , PixelType.UnsignedByte , data.Scan0 );
            bitmap.UnlockBits( data );
            GL.BindTexture( TextureTarget.Texture2D , 0 );
        }

        public int GetID() {
            return textureID;
        }

        private void Load( string texturePath ) {
            GL.Enable( EnableCap.Texture2D );
            if( System.IO.File.Exists( texturePath ) ) {
                GL.Enable( EnableCap.Texture2D );
                Bitmap bitmap = new Bitmap( texturePath );
                GL.Hint( HintTarget.PerspectiveCorrectionHint , HintMode.Nicest );
                GL.GenTextures( 1 , out textureID );
                GL.BindTexture( TextureTarget.Texture2D , textureID );
                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 ,
                    OpenTK.Graphics.OpenGL.PixelFormat.Bgra , PixelType.UnsignedByte , data.Scan0 );
                bitmap.UnlockBits( data );
            }
        }
    }
}

No other hardcoded geometry in the level has this problem; the geometry itself is also a few thousand voxels. Everything EXCEPT the model has correct texture coordinates.

Upvotes: 1

Views: 1085

Answers (1)

Reto Koradi
Reto Koradi

Reputation: 54592

Using immediate mode in legacy OpenGL, the glVertex*() calls issue a new vertex, using the value of the other vertex attributes (e.g. normal, color) that are set at the time.

In the posted code, the order of the calls is this:

GL.Vertex3( v.X , v.Y , v.Z );
GL.Normal3( n.X , n.Y , n.Z );
GL.TexCoord2( tc.Y , tc.X );

Since the Vertex3 call comes first, it will use the most recently set normal and texture coordinates, which are the values from the previous vertex. The Normal3 and TexCoord2 calls will then change the current value for these attributes, but the new values will only be used on the next Vertex3() call.

To use the specified normal and texture coordinates for this vertex, the Vertex3() call needs to be last:

GL.Normal3( n.X , n.Y , n.Z );
GL.TexCoord2( tc.Y , tc.X );
GL.Vertex3( v.X , v.Y , v.Z );

Upvotes: 4

Related Questions