Saturday, July 20, 2013 Eric Richards

Loading a Mesh From a File: Skull Demo

This time around, we are going to be loading a pre-defined mesh from a simple text-file format.  This will be a rather quick post, as the only thing different that we will be doing here is altering our CreateGeometryBuffer function to load the vertex and index data from a file, rather than creating it on-the-fly.  If you are following along with me in Frank Luna’s Introduction to 3D Game Programming with Direct3D 11.0 , this example corresponds to section 6.12, and is a port of the Skull Demo from the example code. skull

Model Data Format

Mr. Luna uses a very simple text format for the model in this example.  It starts with a header that gives the number of vertices and the number of triangles.  Next, it lists the vertex data (in position, normal pairs), and finally lists the triangle indices.  Here is a short excerpt from the skull.txt model file (the full file is over 90,000 lines, I would suggest that you download it from http://www.d3dcoder.net/d3d11.htm or grab it from my GitHub repository at https://github.com/ericrrichards/dx11.git):

VertexCount: 31076
TriangleCount: 60339
VertexList (pos, normal)
{
    0.592978 1.92413 -2.62486 0.572276 0.816877 0.0721907
    0.571224 1.94331 -2.66948 0.572276 0.816877 0.0721907
    0.609047 1.90942 -2.58578 0.572276 0.816877 0.0721907
    1.12127 1.64042 -1.94785 -0.0941668 0.904117 0.416779
    1.04654 1.60198 -1.86107 0.0955379 0.877073 0.47076
    1.06964 1.60894 -1.87873 0.0955378 0.877073 0.47076
    1.151 1.65245 -1.7697 -0.378509 0.925514 -0.01241
    1.11866 1.63277 -1.6602 -0.373131 0.92607 0.0562804
    1.13374 1.64126 -1.6999 -0.373131 0.92607 0.0562804
    ...
}
TriangleList
{
    0 1 2
    3 4 5
    6 7 8
    9 10 11
    12 13 14
    15 16 17
    18 19 20
    21 22 23
    24 25 26
    ...
}

Loading the Model Data

We will be loading the model data in our BuildGeometryBuffers() function.  This is one of the rare times where it is actually more convenient to use C++, rather than C#; the C++ std::ifstream interface is a little bit more simple to use for reading this particular data than the .NET Stream/StreamReader options. 

Note: There may be an easier way to do the file input of which I am not aware.  If that’s the case, please let me know in the comments.

StreamReader doesn’t allow us to read in a token as easily as the C++ stream operators do, so we will need to read the file line-by-line, and parse out the data that we need.  Fortunately, the file format is very simple, so that we need only use string.Split() to extract out the data on each line.

private void BuildGeometryBuffers() {
    try {
        var vertices = new List<Vertex>();
        var indices = new List<int>();
        var vcount = 0;
        var tcount = 0;
        using (var reader = new StreamReader("Models\\skull.txt")) {
            var input = reader.ReadLine();
            if (input != null)
                // VertexCount: X
                vcount = Convert.ToInt32(input.Split(new[] {':'})[1].Trim());

            input = reader.ReadLine();
            if (input != null) 
                //TriangleCount: X
                tcount = Convert.ToInt32(input.Split(new[] { ':' })[1].Trim());

            var c = Color.Black;
            // skip ahead to the vertex data
            do {
                input = reader.ReadLine();
            } while (input != null && !input.StartsWith("{"));
            // Get the vertices  
            for (int i = 0; i < vcount; i++) {
                input = reader.ReadLine();
                if (input != null) {
                    var vals = input.Split(new[] {' '});
                    vertices.Add(new Vertex(
                        new Vector3(
                            Convert.ToSingle(vals[0].Trim()), 
                            Convert.ToSingle(vals[1].Trim()), 
                            Convert.ToSingle(vals[2].Trim())), 
                        c));
                }
            }
            // skip ahead to the index data
            do {
                input = reader.ReadLine();
            } while (input != null && !input.StartsWith("{"));
            // Get the indices
            _skullIndexCount = 3*tcount;
            for (var i = 0; i < tcount; i++) {
                    input = reader.ReadLine();
                if (input == null) {
                    break;
                }
                var m = input.Trim().Split(new[] { ' ' });
                indices.Add(Convert.ToInt32(m[0].Trim()));
                indices.Add(Convert.ToInt32(m[1].Trim()));
                indices.Add(Convert.ToInt32(m[2].Trim()));
            }
        }

        var vbd = new BufferDescription(Vertex.Stride*vcount, ResourceUsage.Immutable, 
            BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
        _vb = new Buffer(Device, new DataStream(vertices.ToArray(), false, false), vbd);

        var ibd = new BufferDescription(sizeof (int)*_skullIndexCount, ResourceUsage.Immutable, 
            BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
        _ib = new Buffer(Device, new DataStream(indices.ToArray(), false, false), ibd);


    } catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}

Anything Else?

No, not really…  You may note that we are drawing in wireframe mode again, as we did with the Shapes Demo; if we rendered the skull solid, without lighting, we would not be able to see much of the detail.

Next Time

We’ll be expanding on our Hills Demo from earlier, adding a dynamic mesh that will be updated each frame to represent the oceans.