I have written my own Wavefront Model parser, which I have been using for quite some time to render meshes in immediate mode. However, I now wish to upgrade my system to Vertex Buffer Objects
. I was doing a lot of reading online about how to create Vertex Buffer Objects
, but most(really all) of the links I found online did not seem to exactly answer the question of how to upload the indices(or in my case, Faces); it's entirely plausible that my Google Kung-Fu has just been lacking though.
I'm interested in taking my current Wavefront Model implementation, tweaking it if necessary, and creating a VBO out of the data that it contains, and then rendering it. I'm certain that the only real issue is how I store the Indice data(I do so in Faces), which I think needs to be implemented as a single array. Again, I'm not very knowledgable in modern OpenGL, so I don't know how to proceed or what modifications are required going forward.
Here is my implementation:
WavefrontModel.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using GrimoireEngine.Framework.Maths;
using GrimoireEngine.Framework.Rendering.OpenGL.Texturing;
using GrimoireEngine.Framework.Rendering.OpenGL.Utilities;
using OpenTK;
using OpenTK.Graphics.OpenGL;
namespace GrimoireEngine.Framework.Rendering.OpenGL.Modeling
{
public class WavefrontModel
{
/// <summary>
/// The String Identifier of this Model
/// </summary>
public string Identifier { get; set; }
/// <summary>
/// The Vertices of this Model
/// </summary>
public Vector3[] Vertices;
/// <summary>
/// The TexCoords of this Model
/// </summary>
public Vector2[] TexCoords;
/// <summary>
/// The Normals of this Model
/// </summary>
public Vector3[] Normals;
/// <summary>
/// The Faces of this Model
/// </summary>
public Face[] Faces; // May be entirely deprecated.
/// <summary>
/// Testing some shit.
/// </summary>
public List<int> Indices;
/// <summary>
/// The Original File Source
/// </summary>
public string ModelSource { get; set; }
/// <summary>
/// The Material of this Model
/// </summary>
public Material Material { get; set; }
/// <summary>
/// The Triangle Count of this model.
/// </summary>
public int TriCount
{
get
{
return Faces.Length;
}
}
public WavefrontModel()
{
}
public WavefrontModel(string modelPath, Material material, bool load = true)
{
this.ModelSource = modelPath;
this.Material = material;
if (load)
{
Load(modelPath);
}
}
public WavefrontModel(string modelPath, bool load = true)
{
this.ModelSource = modelPath;
this.Material = null;
if (load)
{
Load(modelPath);
}
}
public WavefrontModel(string[] data)
{
this.Material = null;
Load(data);
}
public WavefrontModel(string[] data, Material material)
{
this.Material = material;
Load(data);
}
/// <summary>
/// Loads a model from the desired Wavefront.obj source given
/// at constructor initialization.
/// </summary>
public void Load()
{
Load(this.ModelSource);
}
/// <summary>
/// Loads a model from a Wavefront.obj located on disk.
/// </summary>
/// <param name="file"></param>
public void Load(string file)
{
Parse(File.ReadAllLines(file));
}
/// <summary>
/// Loads a model from a Wavefront.obj located on disk.
/// </summary>
/// <param name="data"></param>
public void Load(string[] data)
{
Parse(data);
}
/// <summary>
/// Sets the internal Model Arrays to null.
/// </summary>
public void Clear()
{
Vertices = null;
TexCoords = null;
Normals = null;
Faces = null;
}
/// <summary>
/// Parses Wavefront data from desired array.
/// </summary>
/// <param name="data"></param>
public void Parse(string[] data)
{
// Create File Manifest
int totalVertices = 0;
int totalNormals = 0;
int totalTextureCoordinates = 0;
int totalFaces = 0;
for (int i = 0; i < data.Length; i++)
{
char firstChar = data[i][0];
switch (firstChar)
{
case 'v':
char secondChar = data[i][1];
switch (secondChar)
{
case ' ':
totalVertices++;
break;
case 't':
totalTextureCoordinates++;
break;
case 'n':
totalNormals++;
break;
}
break;
case 'f':
totalFaces++;
break;
}
}
// Create the Buffers
Vertices = new Vector3[totalVertices];
Normals = new Vector3[totalNormals];
TexCoords = new Vector2[totalTextureCoordinates];
Faces = new Face[totalFaces];
// Create File Reading Buffers
string[] fileBuffer = new string[20];
string[] indiceBuffer = new string[3];
Vertex[] vertexBuffer = new Vertex[3];
// Iterators
int verticesIterator = 0;
int normalsIterator = 0;
int textureCoordinatesIterator = 0;
int facesIterator = 0;
for (int line = 0; line < data.Length; line++)
{
SplitStringFast(data[line], ' ', fileBuffer);
char firstChar = data[line][0];
switch (firstChar)
{
case 'v':
char secondChar = data[line][1];
switch (secondChar)
{
case ' ':
Vector3 vertex = new Vector3
{
X = Single.Parse(fileBuffer[1]),
Y = Single.Parse(fileBuffer[2]),
Z = Single.Parse(fileBuffer[3])
};
Vertices[verticesIterator] = vertex;
verticesIterator++;
break;
case 't':
Vector2 textureCoordinate = new Vector2
{
X = Single.Parse(fileBuffer[1]), // U
Y = -Single.Parse(fileBuffer[2]) // V (Inverted)
};
TexCoords[textureCoordinatesIterator] = textureCoordinate;
textureCoordinatesIterator++;
break;
case 'n':
Vector3 normal = new Vector3
{
X = Single.Parse(fileBuffer[1]),
Y = Single.Parse(fileBuffer[2]),
Z = Single.Parse(fileBuffer[3])
};
Normals[normalsIterator] = normal;
normalsIterator++;
break;
}
break;
case 'f':
for (int i = 0; i < 3; i++)
{
SplitStringFast(fileBuffer[i + 1], '/', indiceBuffer);
Vertex indices = new Vertex
{
VertexIndice = GrimoireMathHelper.ParsePositiveInt32Fast(indiceBuffer[0]) - 1,
TextureIndice = GrimoireMathHelper.ParsePositiveInt32Fast(indiceBuffer[1]) - 1,
NormalIndice = GrimoireMathHelper.ParsePositiveInt32Fast(indiceBuffer[2]) - 1
};
vertexBuffer[i] = indices;
}
Faces[facesIterator] = new Face(vertexBuffer);
facesIterator++;
break;
}
}
}
/// <summary>
/// A custom implementation of String.Split(). Realistically, this
/// function is not much faster than what .NET offers; it gains speed
/// more from a preset buffer mechanism. There is no bounds checking done
/// for the buffer, which is done by design to assure optimal performance.
/// </summary>
/// <param name="value"></param>
/// <param name="delimiter"></param>
/// <param name="buffer"></param>
/// <returns></returns>
private static void SplitStringFast(string value, char delimiter, string[] buffer)
{
int resultIndex = 0;
int startIndex = 0;
for (int i = 0; i < value.Length; i++)
{
if (value[i] == delimiter)
{
buffer[resultIndex] = value.Substring(startIndex, i - startIndex);
resultIndex++;
startIndex = i + 1;
}
}
buffer[resultIndex] = value.Substring(startIndex, value.Length - startIndex);
}
/// <summary>
/// Renders the Model using deprecated immediate mode. This
/// function exists only for testing purposes.
/// </summary>
public void DrawImmediate()
{
//GL.Enable(EnableCap.Texture2D);
GL.Color3(Material.AmbientColor);
Material.Bind();
GL.Begin(PrimitiveType.Triangles);
for (int i = 0; i < Faces.Length; i++)
{
/**
* One
*/
GL.Normal3(Normals[Faces[i].One.NormalIndice]);
//GL.VertexAttrib2(1, TexCoords[Faces[i].One.TextureIndice]); // For use with shaders.
GL.TexCoord2(TexCoords[Faces[i].One.TextureIndice]);
GL.Vertex3(Vertices[Faces[i].One.VertexIndice]);
/**
* Two
*/
GL.Normal3(Normals[Faces[i].Two.NormalIndice]);
GL.TexCoord2(TexCoords[Faces[i].Two.TextureIndice]);
//GL.VertexAttrib2(1, TexCoords[Faces[i].Two.TextureIndice]); // For use with shaders
GL.Vertex3(Vertices[Faces[i].Two.VertexIndice]);
/**
* Three
*/
GL.Normal3(Normals[Faces[i].Three.NormalIndice]);
GL.TexCoord2(TexCoords[Faces[i].Three.TextureIndice]);
//GL.VertexAttrib2(1, TexCoords[Faces[i].Three.TextureIndice]); // For use with Shaders
GL.Vertex3(Vertices[Faces[i].Three.VertexIndice]);
}
GL.End();
//GL.BindTexture(TextureTarget.Texture2D,0);
//GL.Disable(EnableCap.Texture2D);
}
/// <summary>
/// Compiles this WavefrontModel into a VertexBufferObject
/// </summary>
public void Compile(BufferUsageHint bufferUsageHint)
{
/**
* This is where I'm starting to create my VBO's...I just don't really
* know how to proceed.
*/
int vboHandle;
int size;
// Vertices
GL.GenBuffers(1, out vboHandle);
GL.BindBuffer(BufferTarget.ArrayBuffer, vboHandle);
GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(Vertices.Length * BlittableValueType.StrideOf(Vertices)), Vertices, bufferUsageHint);
GL.GetBufferParameter(BufferTarget.ArrayBuffer, BufferParameterName.BufferSize, out size);
if (Vertices.Length * BlittableValueType.StrideOf(Vertices) != size)
{
throw new ApplicationException("Vertex data not uploaded correctly");
}
//
// How do I handle indices?
}
/// <summary>
/// Draw the VBO
/// </summary>
public void Render()
{
throw new NotImplementedException("Rendering VBO's not yet implemented!");
}
}
}
Face.cs
using System;
namespace GrimoireEngine.Framework.Rendering.OpenGL.Modeling
{
public struct Face : IEquatable<Face>
{
public Vertex One;
public Vertex Two;
public Vertex Three;
public static Face Empty
{
get
{
return new Face();
}
}
public Face(Vertex[] vertexIndices)
{
this.One = vertexIndices[0];
this.Two = vertexIndices[1];
this.Three = vertexIndices[2];
}
public Face(Vertex one, Vertex two, Vertex three)
{
this.One = one;
this.Two = two;
this.Three = three;
}
public bool Equals(Face other)
{
return One == other.One && Two == other.Two && Three == other.Three;
}
public override bool Equals(object obj)
{
return obj is Face && Equals((Face)obj);
}
public static bool operator ==(Face f1, Face f2)
{
return f1.Equals(f2);
}
public static bool operator !=(Face f1, Face f2)
{
return !(f1.Equals(f2));
}
public Vertex[] ToArray()
{
return new[] { One, Two, Three };
}
public override int GetHashCode()
{
unchecked
{
int hashCode = One.GetHashCode();
hashCode = (hashCode * 31) ^ Two.GetHashCode();
hashCode = (hashCode * 31) ^ Three.GetHashCode();
return hashCode;
}
}
}
}
Vertex.cs
using System;
namespace GrimoireEngine.Framework.Rendering.OpenGL.Modeling
{
public struct Vertex : IEquatable<Vertex>
{
public int VertexIndice;
public int TextureIndice;
public int NormalIndice;
public static Vertex Empty
{
get
{
return new Vertex();
}
}
public Vertex(int[] indices)
{
this.VertexIndice = indices[0];
this.NormalIndice = indices[1];
this.TextureIndice = indices[2];
}
public Vertex(int vertexIndice, int normalIndice, int textureIndice)
{
this.VertexIndice = vertexIndice;
this.TextureIndice = textureIndice;
this.NormalIndice = normalIndice;
}
public bool Equals(Vertex other)
{
return VertexIndice == other.VertexIndice &&
TextureIndice == other.TextureIndice &&
NormalIndice == other.NormalIndice;
}
public override bool Equals(object obj)
{
return obj is Vertex && Equals((Vertex)obj);
}
public static bool operator ==(Vertex v1, Vertex v2)
{
return v1.Equals(v2);
}
public static bool operator !=(Vertex v1, Vertex v2)
{
return !(v1 == v2);
}
/// <summary>
/// Order = VertexIndice, TextureIndice, NormalIndice
/// </summary>
/// <returns></returns>
public int[] ToArray()
{
return new[] { VertexIndice, TextureIndice, NormalIndice };
}
public override int GetHashCode()
{
unchecked
{
int hashCode = VertexIndice;
hashCode = (hashCode * 31) ^ TextureIndice;
hashCode = (hashCode * 31) ^ NormalIndice;
return hashCode;
}
}
}
}
My Question:
How can I modify this implementation to create Vertex Buffer Objects
? Or, if applicable, how can I use this implementation to create and render a Vertex Buffer Object
? Thank you for your time and assistance!
Questions like this that I feel don't answer my question: