Loading 3D Models into Direct3DAs we’ve seen so far, you have to manually (or algorithmically) create all the geometry you use. This is absolutely fine for things like cubes, spheres, triangles etc… but how about trying to create a model of a person - in particular an animated character? I know you people aren’t stupid - and you wouldn’t try to manually type in all the vertices… J The other important factor is the data-driven architecture - A very powerful and popular game programming system. If all your geometry is stored in files, then you need only alter those files for it to be globally changed across the whole game - rather than sifting through all your code, making the changes then recompiling… So what exactly makes up a model? In general it’s just a collection of vertices, indices, textures and materials that when rendered appear as a complete model. They are often referred to as objects or meshes. In general, it’s a self contained instance that will, to a certain degree, manage itself. This is where 3D modelling packages such as maya, milkshape 3d, 3DS Max, Truespace, lightwave etc… come into play. One of more of these programs will become your best friend when it comes to creating 3D geometry, purely because they’re designed to do it, and are exceptionally good at doing it. Learning a 3D modelling package properly can be as hard, or harder than the actual programming - I strongly recommend getting and reading a good book on your 3D modeller (unless you’re already competent of course). Our task for this section is to take a model created in one of these programs and load it into our Direct3D program for real-time viewing. This is actually a surprisingly easy task. I happen to use 3D Studio Max to do my modelling, and it’s native format is the .3DS format - which is convenient as Microsoft built in a converter for the .3DS format to .X format. The .X format is Microsoft’s native DirectX file format - and is therefore built into the D3DX API quite nicely. This is all great IF you’re using the correct tools, if you aren’t then you’ll need to write your own data parsing function to convert the data in the file into D3D acceptable triangle/vertex data. To demonstrate using models in direct3d I made a more complicated version of the original cube model. Using 3D Studio Max this was a fairly trivial process of extruding backwards certain parts of each face - such that I was left with a raised border around each face. I then used Max’s powerful texturing tools to make the raised borders use the grey part of the texture, and the inner panel to use the purple/blue part of the texture. The final results looked like this: The 3D effect of the borders isn’t too apparent from this static shot; but when you see it rotating in real-time you can notice them quite easily. This next shot shows the geometry in wireframe mode: This final shot looks quite complicated, because you can see all 6 faces of the cube at once - and as each one has quite a bit of geometry it does look like a mess of lines! However, you can still clearly see that this version is considerably more complicated than the original hard-coded cube model. Now, onto the code. Luckily for you, the code is really quite simple for both loading, and rendering of models. If you have to write your own loading function then it could get a little more complicated - depending upon how you code it. We need 4 global declarations before we can get started loading models: Dim nMaterials As Long Dim MeshMaterials() As D3DMATERIAL8 Dim MeshTextures() As Direct3DTexture8 Dim CubeMesh As D3DXMesh All models are sub-divided into sections - take a car model for example, you may have a section for 4 wheels, another for the windows, and another for the main body. These sections are usually separated by different materials or textures. This explains the first declaration, nMaterials, which keeps track of how many sections we have, and will also be used later to redefine the next two arrays of materials and textures. The final declaration, CubeMesh, is a class type built into the D3DX runtime library; it will handle the storage/rendering of the model geometry; in essence it just manages a couple of vertex buffers and index buffers. This next little bit of code will load a model from the hard drive / CD-ROM… Set CubeMesh = D3DX.LoadMeshFromX(App.Path & "\cube_3d.x", D3DXMESH_MANAGED, _ D3DDevice, Nothing, mtrlBuffer, nMaterials) If CubeMesh Is Nothing Then GoTo BailOut: '//Dont continue if the above call did not work ReDim MeshMaterials(nMaterials) As D3DMATERIAL8 ReDim MeshTextures(nMaterials) As Direct3DTexture8 For i = 0 To nMaterials - 1 '//Get D3DX to copy the data that we loaded from the file into our structure D3DX.BufferGetMaterial mtrlBuffer, i, MeshMaterials(i) '//Fill in the missing gaps - the Ambient properties MeshMaterials(i).Ambient = MeshMaterials(i).diffuse '//get the name of the texture used for this part of the mesh TextureFile = D3DX.BufferGetTextureName(mtrlBuffer, i) '//Now create the texture If TextureFile <> "" Then 'Dont try to create a texture from an empty string Set MeshTextures(i) = D3DX.CreateTextureFromFileEx(D3DDevice, App.Path & "\" & TextureFile, _ 256, 256, D3DX_DEFAULT, 0, _ D3DFMT_UNKNOWN, D3DPOOL_MANAGED, _ D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, _ 0, ByVal 0, ByVal 0) End I Next I Debug.Print "Number of Faces in mesh: " & CubeMesh.GetNumFaces Debug.Print "Number of Vertices in mesh: " & CubeMesh.GetNumVertices Debug.Print "Number of segments in mesh: " & nMaterials Not hugely complicated really. The last bit isn’t really necessary - it just provides some interesting statistics for you. As far as vertex and face count goes, it isn’t wise to trust your 3D-renderer when it comes to vertex/face counts, whilst those programs are 100% correct for the geometry in their program, the various converters, and this loading function sometimes messes things up and adds more vertices. Also note, that the LoadMeshFromX( ) function gets extremely slow when dealing with medium-large geometry files. Because all the processing is done away from your application you cant easily output a status bar showing the progress of loading it. This is one reason why people often write their own object formats - Ones I have written in the past have loaded a 2000 vertex model in <250ms, whereas with D3DX it’s taken 3 seconds or more… This is also partly due to the .X file format specification including lots and lots of other rubbish that you may not actually be interested in - frame hierarchies, animation information; in general it’s a very flexible format, but you can get a considerably smaller file, and considerably faster loading times should you design a custom format that is specific to exactly what you want. The final part for dealing with models is to render them. Luckily for us, this is also very, very simple. These following lines should be placed within a BeginScene()…EndScene() block: For i = 0 To nMaterials - 1 D3DDevice.SetTexture 0, MeshTextures(i) D3DDevice.SetMaterial MeshMaterials(i) CubeMesh.DrawSubset i Next I Basically, all we’re doing is looping through all the sections with different materials, committing those materials and textures to the device, then rendering the relevant geometry. Several of the more popular file formats - mdl, md2, md3, 3ds etc… are covered on www.wotsit.org - a great site for all file specifications! You will also find several articles on this site about 3ds file formats. |