Reputation: 4364
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:
And here is what it looks like in my game:
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
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