OpenGL/OpenTK 中不一致的模型纹理
Inconsistent Model Texturing In OpenGL/OpenTK
我正在创建一个模型导入器,它将 .obj 转换为我自己的专有格式,该格式将用于我正在创建的游戏。我的模型解析器一开始似乎工作得很好,从各种游戏(如塞尔达传说)加载小模型,但无论出于何种原因,无法正确纹理化高级模型,如 Borderlands 的 Claptrap。以下是一些屏幕截图,向您展示我的意思:
使用 zelda 模型的模型加载器:
然后 Claptrap 的纹理不正确:
尽管它在 Blender 中的纹理很好:
我不明白这个,也不知道错误来自哪里。这是我的 WavefrontParser 的代码,对于我不知道的较大模型,它可能会产生一些无法预料的错误:
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;
}
}
}
我的 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;
}
}
}
还有我的Materialclass,也可能会报错:
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 );
}
}
}
如果有人能帮我发现这个错误,我会很高兴;我开始扯掉我的头发(我不必开始,哈哈)。
编辑:
我忘了说我是如何渲染的。我使用 OpenTK/OpenGL,并通过即时模式绘制(用于初始应用程序测试)。
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();
}
经过许多痛苦的时间撕扯我的头发,我终于意识到我的问题。我没有想到的是,现在对我来说似乎很愚蠢,是我试图在 OpenGL 环境中为为 DirectX 制作的模型制作纹理。为什么这很重要?那么,在 OpenGL 中,纹理原点是左下角 0,0。在 DirectX 中,它位于左上角。所以通过简单地在 gimp 中垂直翻转纹理我让它工作了。
很高兴知道我的模型加载器没有任何问题,我只是没有考虑我使用的模型的目标 platform/api。
我正在创建一个模型导入器,它将 .obj 转换为我自己的专有格式,该格式将用于我正在创建的游戏。我的模型解析器一开始似乎工作得很好,从各种游戏(如塞尔达传说)加载小模型,但无论出于何种原因,无法正确纹理化高级模型,如 Borderlands 的 Claptrap。以下是一些屏幕截图,向您展示我的意思:
使用 zelda 模型的模型加载器:
然后 Claptrap 的纹理不正确:
尽管它在 Blender 中的纹理很好:
我不明白这个,也不知道错误来自哪里。这是我的 WavefrontParser 的代码,对于我不知道的较大模型,它可能会产生一些无法预料的错误:
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;
}
}
}
我的 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;
}
}
}
还有我的Materialclass,也可能会报错:
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 );
}
}
}
如果有人能帮我发现这个错误,我会很高兴;我开始扯掉我的头发(我不必开始,哈哈)。
编辑:
我忘了说我是如何渲染的。我使用 OpenTK/OpenGL,并通过即时模式绘制(用于初始应用程序测试)。
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();
}
经过许多痛苦的时间撕扯我的头发,我终于意识到我的问题。我没有想到的是,现在对我来说似乎很愚蠢,是我试图在 OpenGL 环境中为为 DirectX 制作的模型制作纹理。为什么这很重要?那么,在 OpenGL 中,纹理原点是左下角 0,0。在 DirectX 中,它位于左上角。所以通过简单地在 gimp 中垂直翻转纹理我让它工作了。
很高兴知道我的模型加载器没有任何问题,我只是没有考虑我使用的模型的目标 platform/api。