This tutorial is part of a Collection: 03. DirectX 11 - Braynzar Soft Tutorials
rate up
2
rate down
11843
views
bookmark
22. Loading Static 3D Models (.obj Format)

In this lesson we will learn how to load a static 3d model from an .obj file. .obj files are not usually what you will want to use in a game, as they don't contain animation, and they are in ascii format, so a bit larger than other formats such as .3ds, but they are a good starting point for learning how to load models. This lesson will build directly off the last lesson, spotlights. The upcoming lessons (Specular lighting, and Normal mapping. Probably others too) will use the code from this lesson as the starting point. BTW, please go easy on me when making fun of my poor modeling skills...

2027 downloads
metalpanel.jpg 52.23 kb
1123 downloads
##Introduction## In this lesson, we will learn how to load a 3d model from an obj file. .obj files are ascii based files, so they are easy to read. Along with .obj files is the .mtl file, which is the material library. We will also learn to load the materials for the model using the .mtl file. .obj files are large and contain no animation, so you will usually want to use a different model format for video games. However, the .obj format is a great file to start with when learning how to load models! This is a longer lesson, so i will go through in sections, instead of line by line like i usually like to do. Let's get to it! ##.obj File Format## .obj files have been around for a long time. They are one of the oldest 3d model formats out there, but still widely used. .obj files do not contain header information like other file formats. Headers are the beginning of a file which will give you information about it. For example a header in a 3d model file might tell you how many vertices, texture coordinates, normals, materials, subsets, and faces there are in the file. Since .obj files do not contain this information, we can do one of two things. The first thing we could do is read the file twice. The first read would gather information like how many vertices, faces, normals, etc. there are, so we can initialize arrays for each. The second read would get the actual model information, like vertex positions, and store them in the initialized arrays. This is the inneficient way of how it used to be done before we had something called "vectors". The second way we can load an obj file, and the way we will do it, is use vectors. A vector is a dynamic array (which you should already know), where instead of initializing it with a set size, we can "push_back" elements to increase the size as we need. Another cool thing about vectors is they clean themselves, so we do not need to "delete" them like we do standard arrays. Using this method, we only need to read the file once, and store the models information in the vectors as we go. Each piece of information in an obj file is (supposed to be...) put onto it's own line, where a string of one or more characters (eg. "v") explains what the rest of the information on that line is used for, and "\n" is the terminating character of each line. Here is an example of a sample .obj file taken from the one this lesson uses: # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware # File Created: 26.07.2011 13:47:43 mtllib spaceCompound.mtl # # object Window # v -8.6007 1.3993 10.0000 v -8.6007 8.6007 10.0000 ... # 4 vertices vn 0.0000 0.0000 -1.0000 # 1 vertex normals vt 0.0000 0.0000 0.0000 vt 0.0000 1.0000 0.0000 ... # 4 texture coords g Window usemtl Window f 1/1/1 2/2/1 3/3/1 f 3/3/1 4/4/1 1/1/1 # 2 faces **"#" - Comments** # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware Comments start the line with "#". Whenever you read a "#", you can skip that line. **"mtllib" - Material Library File** mtllib spaceCompound.mtl mtllib is the start of a line which contains a material library filename. A material library is a separate file that goes with .obj files, which define the materials for each group. .obj files don't always contain a library file, but they usually do. Not only that but once in a while they may contain multiple library files. It shouldn't be too hard to modify the code to take this into consideration when reading an obj file. **"v" - Vertex Positions** v -8.6007 1.3993 10.0000 Lines containing a vertex position are started with "v". Vertex positions follow the (x, y, z) format, where each number is separated by a single space. **"vn" - Normals** vn 0.0000 0.0000 -1.0000 Lines containing a vertex normal are started by "vn". Vertex normals follow the (x, y, z) format, where each number is also separated by a single space. They are used to specify which direction a triangle is facing, which can be used to impliment lighting. To get smooth lighting across a surface you will usually want to do something called "normal averaging", which is finding the normal for each face sharing a single vertex, then average them together to get that vertices normal. In this lesson we will not need to load vertex normals from the file, as we will learn how to create our own vertex normals. However, for completion, we do load the normals in from the file, but do not use them. **"vt" - Texture Coordinates** vt 0.0000 1.0000 0.0000 Lines containing texture coordinate data are started with "vt". Texture coordinates are used to correctly map an image to a triangle or set of triangles. They follow the (u, v, w) format, where each number is separated by a single space. Usually you will only need a "u" and "v" coordinate for mapping 2d images, but sometimes you might use a cubic texture (3d texture) to map round objects such as spheres, where you will need the "w" coordinate. **"o" - Objects** o spaceCompound The obj file for this lesson does not specify "o". Objects are a group of groups. An example is a town, where you might have a couple houses, which would each be considered objects, and each house contains groups, like windows, doors, walls, roof, etc. **"g" - Groups (Subsets)** g Window Groups are a set of faces which all use the same attributes, such as the same material. In this example, we have a group for the window, each of the rooms, and each of the doorways between rooms. We will call groups "subsets" in this lesson. **"usemtl" - Group Materials** usemtl Window Like what was said a moment ago, each group contains a Material, and lines specifying which material to use are started with "usemtl". The materials are found in the material library (.mtl) file usually accompanying an obj file. In this lesson, we have two materials, a transparent material for the window, and a textured material for the walls. **"s" - Smoothing Groups** s 1 ... s 2 ... Smooth shading can also be turned off s off Smooth shading can be used to for smoothing across groups. This can create a nice round looking surface. We will not get into this in this lesson, so we will just skip lines with "s". **"f" - Faces** A face containing a position, texture coordinate and normal f 1/1/1 2/2/1 3/3/1 A face containing a position, and a normal f 1//1 2//1 3//1 A face containing a position, and a texture coordinate f 1/1 2/1 3/1 A face containing only a position f 1 2 3 Lines defining a face are started with "f". Faces are a set of three or more vertices, where each vertex follows the format "Position/Texture Coordinate/Normal". Each vertex is separated by a space. Each vertex defined in a face can contain a position and/or a texture coordinate, and/or a normal, where each number is separated by a "/". If a vertex contains only a position, there is no "/", if it contains a position and a texture coordinate, there is only one "/", which is between the two number. If the vertex contains a position, texture coordinate and a normal, there is a "/" between all three numbers. And last, if it contains only a position and a normal, there is a "//", or TWO "/" between the numbers. The numbers represent an index value STARTING WITH "1" from the beginning of the file. I stress the "starting with 1" because C++ arrays start with "0", so you MUST subtract one from these numbers before storing them. I'm going to try to make this a little more clear. Consider the line "f 1 2 3". This face contains only a position for each vertex. So at the beginning of the file, the first vertex position was read, "v -8.6007 1.3993 10.0000". The "1" in the face "1 2 3" represents this vertex position. However, in C++ (in our code) when we stored this vertex position, we stored it in the [0] element of the vector. So when we store this face, we need to actually store it like this "0 1 2" instead of "1 2 3". And when there is multiple groups and objects, the numbers are still based on the collective index value for each defenition starting from the beginning of the file. One more thing about faces, is they are not always stored as triangles in an obj file. Sometimes they are stored as squares or other shapes containing more edges. Here is an example f 1 2 3 4 5 DirectX can only work with triangles. So we must take this into consideration when getting these faces. To fix this problem, we will turn each polygon with more vertices than three into triangles. We will do this by taking the first three vertices in a face and making a triangle of it. Each vertex defined after is a new triangle. We can make a new triangle by taking the very first vertex defined in the face, and use it as the first vertex for every other triangle in the current face. Then take the last vertex of the triangle before the current one, and use that as the second vertex in the current triangle. For example, the polygon above is defined by "1 2 3 4 5". We will need to turn this into three triangles, because the first three make up the first triangle, and the two after the first three are two new triangles. So the three triangles will look like this: tri1 = "1 2 3" tri2 = "1 3 4" tri3 = "1 4 5" I want to mention one more thing about faces in obj models. Some programs such as lightwave are able to make shapes that are concave... An example of a concave shape is pacman, or a star. The problem with this is that when we create triangles from the vertices of this shape, some of the triangles will "overlap" other triangles. Have a look below. +[http://www.braynzarsoft.net/image/100051][Convcave vs. Convex] You can fix this problem by calculating the angle between edges to find where it is concave, and retriangulate the face depending on the concavities, but we will not do that in this lesson. ##.mtl File Format## The mtl file is the material library for the object file. This file will contain the surface properties of each group. Not all obj models include a material library file, but we will assume our's do. As was mentioned above, this lesson's model uses two materials, one for the window, and the other for the walls. Loading the model is not the ONLY new thing in this lesson. We have now created a "SurfaceMaterial" structure, which will hold the various surface properties of each subset, such as diffuse color, transparency, and textures. We will cover this structure soon enough, but for now lets look at the mtl file's contents. This is the mtl file that we will be using for this lesson. # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware # File Created: 26.07.2011 13:47:43 newmtl Window Ns 32.0000 Ni 1.5000 d 0.8000 Tr 0.2000 Tf 1.0000 1.0000 1.0000 illum 2 Ka 0.0000 0.0000 0.0100 Kd 0.0000 0.0000 0.0100 Ks 0.3500 0.3500 0.3500 Ke 0.0000 0.0000 0.0000 newmtl MetalPanels Ns 10.0000 d 1.0000 Tr 0.0000 Tf 1.0000 1.0000 1.0000 illum 2 Ka 0.5882 0.5882 0.5882 Kd 0.5882 0.5882 0.5882 Ks 0.0000 0.0000 0.0000 Ke 0.0000 0.0000 0.0000 map_Ka metalpanel.jpg map_Kd metalpanel.jpg map_bump metalpanelnormals.jpg **"#" - Comments** # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware mtl files also contain comments, and again they start the line with "#". We can skip lines starting with "#". **"newmtl" - New Material** **newmtl Window** A new material is started with "newmtl". The word after "newmtl" is the name of the material. Everything after "newmtl" is that materials defenition, until another "newmtl" is reached, or the end of file (eof) is reached. **"Ns" - Specular Power** Ns 10.0000 A line starting with "Ns" is the specular power of the surface. The value can range from "0" to "1000", where 1000 is the highest power of specular, or most reflective of light. **"Ni" - Optical Density** Ni 1.5000 A line starting with "Ni" is the optical density of the surface. The value can range from "0" to "10", where 10 is the most dense. Optical density is used to "bend light" for transparent surfaces. **"d" - Transparency** d 0.8000 A line starting with "d" is the transparency of the surface. The value can range from "0" to "1", where 0 is completely transparent, or invisible. **"Tr" - Transparency** Tr 0.2000 A line starting with "Tr" is also the transparency of the surface. Some obj models use either "d", "Tr", or both to define the transparency of a material. The value can range from "0" to "1", but this time 1 is completely transparent or invisible. **"Tf" - Transmission Filter** Tf 1.0000 1.0000 1.0000 This one has to do with light passing through a transparent surface. The light passing through a transparent surface is filtered by this attribute. **"illum" - Illumination Model** illum 2 The illumination model is a value between 0 and 10. Each number is a different equation which impliments lighting and shading for a surface. We won't worry about these here. **"Ka" - Ambient Color** Ka 0.0000 0.0000 0.0100 This is the color of the surface when no light is hitting it. Usually this is the same as the diffuse color of the surface, so in this lesson, we will load this value into the diffuse color of our material if there is no diffuse color defined. **"Kd" - Diffuse Color** Kd 0.0000 0.0000 0.0100 This is the actual color of our material. If there is no diffuse texture defined for the material, we will use this color to color the surface using this material. **"Ks" - Specular Color** Ks 0.3500 0.3500 0.3500 We will not use specular color in this lesson, but we will in an upcoming lesson. This is the color of the specular light which is reflected off of shiny surfaces. **"Ke" - Emissive Color** Ke 0.0000 0.0000 0.0000 Emissive color is the color of a surface when it "lights up". For example a lightbulb. When a lightbulb is off, it might look dark grey, but when it is turned on, it turnes bright white. Emissive lighting is used to "light up" the surface which is sending light out. In video games, a lightbulb may not actually light itself up when sending out light, so we need to give it emissive light. This is not used in this lesson, but would be easy to impliment if needed. **"map_Ka" - Ambient Color Map** map_Ka metalpanel.jpg This is the texture used for the ambient color of a surface. This is almost always the same as the diffuse texture **"map_Kd" - Diffuse Color Map** map_Kd metalpanel.jpg This is the texture or image used to do the actual coloring of the surface. **"map_Ks" - Specular Color Map** map_Ks metalpanel.jpg This is the texture or image used to say how "shiny" the different parts of a surface are. **"map_bump" or "bump" - Bump Map** map_bump metalpanel.jpg bump metalpanel.jpg Bump maps are used to give the surface actual texture, where some parts of a surface may appear to be a little deeper, while others appear to be sticking out. We will be using normal maps in place of bump maps in a later lesson. ##Right Hand Vs. Left Hand Coordinate Systems## Directx uses a "Left Hand Coordinate System". What this means is that the positive direction of the z-axis is facing away from the camera, while the right hand coordinate system's positive z-axis is facing towards the camera. positive y is facing up, positive x is facing right. A lot of modeling programs such as maya and 3ds max use a right handed coordinate system. Since directx uses a left had coordinate system, we will need to convert these models into a left hand coordinate system. To convert from right hand to left hand, we must do a couple things. First is invert the z-axis of the vertice's positions by multiplying it with -1.0f. We will also need to invert the v-axis of the texture coordinats by subtracting it from 1.0f. Finally we will need to convert the z-axis of the vertex normals by multiplying it by -1.0f. I have put a boolean variable in the parameters of the load model function, where if it is set to true, we will do the conversion, and if it is set to false we will not do the conversion. ##Transparent Subsets## We have taken transparency into consideration when loading models in this lesson. Remember from the blending lesson, that the blending technique works by "blending" whats already on the render target with the transparent object. Because of this, we need to make sure that the transparent subsets are the last thing drawn. We have also talked about multiple transparent objects in the blending lesson, but I will quickly go over the solution here. We will not impliment this technique, but a more advanced technique of the drawing order of transparent objects would be to find their distance from the camera, and draw them from furthest to nearest. You may run into this problem which is why I wanted to mention it. ##The Removal of the Mesh Interface (eg. ID3DX10Mesh)## Direct3D 11 has removed the mesh interface. Because of this, we now need to handle all the 3d model stuff by hand, such as the vertex and index buffers, subset counts, mesh intersection (which was damn usefull for picking a 3d object with the mouse). Anyway, its not a huge problem, but instead of loading the 3d model from the file directly to a mesh object, we will now need to store the vertices and index list into buffers, and keep track of subsets and materials. I wanted to mention this to you before we begin! If you have read other lessons on this site, you have no doubt noticed they don't use classes. This is because I try to make the lessons's code easy to read straight through (classes can make reading code more time consuming because you have to jump around the code to see whats happening). Classes make code MUCH more efficient, and you will most definitely want to use classes when making a game. So in the bottom of this lesson I have included an exercise to make a mesh class including methods to load the model from a file and to draw the models subsets, and members to store the models information to replace the removed mesh interface. ##New Includes## Here are a couple new includes we will need to do this lesson. The first enables us to use vectors, which are dynamic arrays. The other two are used to read the file and use stringstreams, which we will talk about in a bit. #include <vector> #include <fstream> #include <istream> ##Globals## I kept the globals together here, the globals used for a specific model, and globals used for the textures of all objects. We create a new transparency state, so we can blend our transparent objects with their alpha values. Then we have a vertex and index buffer. Every model you load will need their own. Next we have the models world space matrix. after that we have a variable to keep track of the number of subsets this model has. We do this so we can apply each subsets own texture to it. Next you can see we make use of vectors. The first vector is used so we know where in the index buffer the start that subset is. We will draw that subset starting with the index value of its meshSubsetIndexStart value, and stop at the next subsets meshSubsetIndexStart value. After that we have a vector which contains the index value of the subsets texture. That subsets texture index will be used to look up its material from the next global, meshSRV, which stands for mesh shader resource view. The last globabl we have is used to make sure we don't load the same material twice. ID3D11BlendState* Transparency; ID3D11Buffer* meshVertBuff; ID3D11Buffer* meshIndexBuff; XMMATRIX meshWorld; int meshSubsets = 0; std::vector<int> meshSubsetIndexStart; std::vector<int> meshSubsetTexture; std::vector<ID3D11ShaderResourceView*> meshSRV; std::vector<std::wstring> textureNameArray; ##Updated Constant Buffer## We have updated our constant buffers structure. The two members we created will be used for the Pixel Shader, while the other two matrices are used for the vertex shader. You will probably want to create a separate constant buffer for each shader, but we put them together here. struct cbPerObject { XMMATRIX WVP; XMMATRIX World; ///////////////**************new**************//////////////////// //These will be used for the pixel shader XMFLOAT4 difColor; bool hasTexture; ///////////////**************new**************//////////////////// }; ##The Material Structure## ( SurfaceMaterial() ) We have created a material structure called SurfaceMaterial. This structure will hold the properties of a subset's surface. We will get our surface materials from the .mtl file accompanying the .obj file. The first member we have is a wide string called matName, which contains the name of the material. The name comes from the line starting with "newmtl" in the .mtl file. Each time a "newmtl" is reached in the .mtl file, we will create a new material, and push it back into our surface material vector called simply material (I simply call it "material" because every material in the game could be stored in this). The next line is an XMFLOAT4 variable called difColor, and contains the RGBA (red, green, blue, and alpha) data for the surface. The RGB data will come from the line starting with "Kd" or "Ka", which will be the solid color of the surface. The alpha value will come from the line starting with "Tr" or "d", which will say how transparent or opaque the surface is. After that we have an integer, texArrayIndex, containing the index value of the texture stored in our meshSRV vector that this material's surface will use. After that is hasTexture, a boolean variable which will say if the material uses a texture. We will set this to true if the material uses a texture, and false if the material does not. If the material uses a texture, the color and alpha values of the texture will "over-write" the solid diffuse color and alpha value contained in the difColor variable. We will see this over-writting done in the pixel shader. We will know if the material uses a texture if the material in the .mtl file contains a line with "map_Kd" or "map_Ka". The last member we have is called transparent. This is another boolean variable, and this will say if the texture is transparent or not. We will use this variable to determine the drawing order of our transparent and non-transparent subsets. If a subset's material has a false value for transparent, then the subset will get drawn in the first group of subsets drawn for this model (each model is split into two different groups, transparent and non-transparent, where the transparent subsets are drawn AFTER the non-transparent). If the subset's material is true for transparent, the subset will get drawn in the last group of subsets drawn for this model. This will make sure that the opaque objects are drawn to the render target first, so that the transparent objects can "blend" with the opaque objects. Of course this structure could be much more complex, but it will do for now. We will add members later when we learn about normal mapping and stuff like that. After we create the material structure, we declare a vector of SurfaceMaterials, called material. struct SurfaceMaterial { std::wstring matName; XMFLOAT4 difColor; int texArrayIndex; bool hasTexture; bool transparent; }; std::vector<SurfaceMaterial> material; ##The Model Loading Function Prototype## ( LoadObjModel() ) This is the prototype of the LoadObjModel function, which is the heart of this lesson. This function will read in a 3D model from an obj file. Let's just go through the parameters of this function (This function is quite a bit larger than it really needs to be, but I tried to make it as functional and flexible as possible). **filename -** *A wide string containing the filename (with or without the filepath) of the obj model to be loaded.* **vertBuff -** *This is a pointer to a ID3D11Buffer object which will store the vertices for the model. Every model you load will need their own vertBuff object.* **indexBuff -** *A pointer to another ID3D11Buffer object which will store the index list for the model. Every model you load will need their own indexBuff object.* **subsetIndexStart -** *This is an integer vector which will store the offset, or starting index value of the index list for each subset. For example, the first subset will start at index value "0". When we want to draw the first subset, we will start drawing from the 0 index value in the index buffer, until we reach the starting index value of the next subset. After all the subsets are loaded from the file, we will push back one last subsetIndexStart, which will be the total number of indices in the index buffer. Every model you load will need their own subsetIndexStart Vector.* **subsetMaterialArray -** *This is another integer vector which will store the index value of the each subset will use. This index value will be used to get the material from the material vector in the next parameter, material. Every model you load will need their own subsetMaterialArray Vector.* **material -** *This is a vector of SurfaceMaterial structures. This vector can be used for all loaded models in your scene, as the function will check to make sure the material does not already exist before pushing back a new material. We will use the vector above to find which material each subset uses.* **subsetCount -** *Number of subsets in this model. We will use this to loop through each of the subsets, and stop looping when we reach subsetCount. Every model you load will need their own subsetCount variable.* **isRHCoordSys -** *This is a true or false value. If the model was created in a Right Hand Coordinate System based modeler such as 3ds max and maya, we will set this to true, so that the function will do the appropriate conversions since directx uses a Left Hand Coordinate System. Specify false if the model was created in a left hand coordinate system. If you don't know, try both to see which one looks more accurate.* **computeNormals -** *This is a true or false value. Specify true if the function should create the vertex normals, and false if the function should use the models own vertex normals. Not all models define their normals, so in the function by default (to prevent errors) we will give each vertex the normal (0,0,0), which is totally useless, and why true should be specified for models that don't define their own normals.* //Define LoadObjModel function after we create surfaceMaterial structure bool LoadObjModel(std::wstring filename, //.obj filename ID3D11Buffer** vertBuff, //mesh vertex buffer ID3D11Buffer** indexBuff, //mesh index buffer std::vector<int>& subsetIndexStart, //start index of each subset std::vector<int>& subsetMaterialArray, //index value of material for each subset std::vector<SurfaceMaterial>& material, //vector of material structures int& subsetCount, //Number of subsets in mesh bool isRHCoordSys, //true if model was created in right hand coord system bool computeNormals); //true to compute the normals, false to use the files normals ##Clean Up## Don't forget to do this! void CleanUp() { ... ///////////////**************new**************//////////////////// meshVertBuff->Release(); meshIndexBuff->Release(); ///////////////**************new**************//////////////////// } ##The LoadObjModel() Function## Here it is! The heart of this lesson! I really am sorry for such a lot of code, such a large function, but if you don't know how to load an obj model, this will do it for you! This function does not support smooth surface shading, so there is a LOT of room for improvement. Please let me know if you see something that could be optimized, improved, added in, or taken out! Since there is a lot of code here, I'm going to go through it in sections instead of line by line. Let's start disecting! bool LoadObjModel(std::wstring filename, ID3D11Buffer** vertBuff, ID3D11Buffer** indexBuff, std::vector<int>& subsetIndexStart, std::vector<int>& subsetMaterialArray, std::vector<SurfaceMaterial>& material, int& subsetCount, bool isRHCoordSys, bool computeNormals) { HRESULT hr = 0; std::wifstream fileIn (filename.c_str()); //Open file std::wstring meshMatLib; //String to hold our obj material library filename //Arrays to store our model's information std::vector<DWORD> indices; std::vector<XMFLOAT3> vertPos; std::vector<XMFLOAT3> vertNorm; std::vector<XMFLOAT2> vertTexCoord; std::vector<std::wstring> meshMaterials; //Vertex definition indices std::vector<int> vertPosIndex; std::vector<int> vertNormIndex; std::vector<int> vertTCIndex; //Make sure we have a default if no tex coords or normals are defined bool hasTexCoord = false; bool hasNorm = false; //Temp variables to store into vectors std::wstring meshMaterialsTemp; int vertPosIndexTemp; int vertNormIndexTemp; int vertTCIndexTemp; wchar_t checkChar; //The variable we will use to store one char from file at a time std::wstring face; //Holds the string containing our face vertices int vIndex = 0; //Keep track of our vertex index count int triangleCount = 0; //Total Triangles int totalVerts = 0; int meshTriangles = 0; //Check to see if the file was opened if (fileIn) { while(fileIn) { checkChar = fileIn.get(); //Get next char switch (checkChar) { case '#': checkChar = fileIn.get(); while(checkChar != '\n') checkChar = fileIn.get(); break; case 'v': //Get Vertex Descriptions checkChar = fileIn.get(); if(checkChar == ' ') //v - vert position { float vz, vy, vx; fileIn >> vx >> vy >> vz; //Store the next three types if(isRHCoordSys) //If model is from an RH Coord System vertPos.push_back(XMFLOAT3( vx, vy, vz * -1.0f)); //Invert the Z axis else vertPos.push_back(XMFLOAT3( vx, vy, vz)); } if(checkChar == 't') //vt - vert tex coords { float vtcu, vtcv; fileIn >> vtcu >> vtcv; //Store next two types if(isRHCoordSys) //If model is from an RH Coord System vertTexCoord.push_back(XMFLOAT2(vtcu, 1.0f-vtcv)); //Reverse the "v" axis else vertTexCoord.push_back(XMFLOAT2(vtcu, vtcv)); hasTexCoord = true; //We know the model uses texture coords } //Since we compute the normals later, we don't need to check for normals //In the file, but i'll do it here anyway if(checkChar == 'n') //vn - vert normal { float vnx, vny, vnz; fileIn >> vnx >> vny >> vnz; //Store next three types if(isRHCoordSys) //If model is from an RH Coord System vertNorm.push_back(XMFLOAT3( vnx, vny, vnz * -1.0f )); //Invert the Z axis else vertNorm.push_back(XMFLOAT3( vnx, vny, vnz )); hasNorm = true; //We know the model defines normals } break; //New group (Subset) case 'g': //g - defines a group checkChar = fileIn.get(); if(checkChar == ' ') { subsetIndexStart.push_back(vIndex); //Start index for this subset subsetCount++; } break; //Get Face Index case 'f': //f - defines the faces checkChar = fileIn.get(); if(checkChar == ' ') { face = L""; std::wstring VertDef; //Holds one vertex definition at a time triangleCount = 0; checkChar = fileIn.get(); while(checkChar != '\n') { face += checkChar; //Add the char to our face string checkChar = fileIn.get(); //Get the next Character if(checkChar == ' ') //If its a space... triangleCount++; //Increase our triangle count } //Check for space at the end of our face string if(face[face.length()-1] == ' ') triangleCount--; //Each space adds to our triangle count triangleCount -= 1; //Ever vertex in the face AFTER the first two are new faces std::wstringstream ss(face); if(face.length() > 0) { int firstVIndex, lastVIndex; //Holds the first and last vertice's index for(int i = 0; i < 3; ++i) //First three vertices (first triangle) { ss >> VertDef; //Get vertex definition (vPos/vTexCoord/vNorm) std::wstring vertPart; int whichPart = 0; //(vPos, vTexCoord, or vNorm) //Parse this string for(int j = 0; j < VertDef.length(); ++j) { if(VertDef[j] != '/') //If there is no divider "/", add a char to our vertPart vertPart += VertDef[j]; //If the current char is a divider "/", or its the last character in the string if(VertDef[j] == '/' || j == VertDef.length()-1) { std::wistringstream wstringToInt(vertPart); //Used to convert wstring to int if(whichPart == 0) //If vPos { wstringToInt >> vertPosIndexTemp; vertPosIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 //Check to see if the vert pos was the only thing specified if(j == VertDef.length()-1) { vertNormIndexTemp = 0; vertTCIndexTemp = 0; } } else if(whichPart == 1) //If vTexCoord { if(vertPart != L"") //Check to see if there even is a tex coord { wstringToInt >> vertTCIndexTemp; vertTCIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 } else //If there is no tex coord, make a default vertTCIndexTemp = 0; //If the cur. char is the second to last in the string, then //there must be no normal, so set a default normal if(j == VertDef.length()-1) vertNormIndexTemp = 0; } else if(whichPart == 2) //If vNorm { std::wistringstream wstringToInt(vertPart); wstringToInt >> vertNormIndexTemp; vertNormIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 } vertPart = L""; //Get ready for next vertex part whichPart++; //Move on to next vertex part } } //Check to make sure there is at least one subset if(subsetCount == 0) { subsetIndexStart.push_back(vIndex); //Start index for this subset subsetCount++; } //Avoid duplicate vertices bool vertAlreadyExists = false; if(totalVerts >= 3) //Make sure we at least have one triangle to check { //Loop through all the vertices for(int iCheck = 0; iCheck < totalVerts; ++iCheck) { //If the vertex position and texture coordinate in memory are the same //As the vertex position and texture coordinate we just now got out //of the obj file, we will set this faces vertex index to the vertex's //index value in memory. This makes sure we don't create duplicate vertices if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists) { if(vertTCIndexTemp == vertTCIndex[iCheck]) { indices.push_back(iCheck); //Set index for this vertex vertAlreadyExists = true; //If we've made it here, the vertex already exists } } } } //If this vertex is not already in our vertex arrays, put it there if(!vertAlreadyExists) { vertPosIndex.push_back(vertPosIndexTemp); vertTCIndex.push_back(vertTCIndexTemp); vertNormIndex.push_back(vertNormIndexTemp); totalVerts++; //We created a new vertex indices.push_back(totalVerts-1); //Set index for this vertex } //If this is the very first vertex in the face, we need to //make sure the rest of the triangles use this vertex if(i == 0) { firstVIndex = indices[vIndex]; //The first vertex index of this FACE } //If this was the last vertex in the first triangle, we will make sure //the next triangle uses this one (eg. tri1(1,2,3) tri2(1,3,4) tri3(1,4,5)) if(i == 2) { lastVIndex = indices[vIndex]; //The last vertex index of this TRIANGLE } vIndex++; //Increment index count } meshTriangles++; //One triangle down //If there are more than three vertices in the face definition, we need to make sure //we convert the face to triangles. We created our first triangle above, now we will //create a new triangle for every new vertex in the face, using the very first vertex //of the face, and the last vertex from the triangle before the current triangle for(int l = 0; l < triangleCount-1; ++l) //Loop through the next vertices to create new triangles { //First vertex of this triangle (the very first vertex of the face too) indices.push_back(firstVIndex); //Set index for this vertex vIndex++; //Second Vertex of this triangle (the last vertex used in the tri before this one) indices.push_back(lastVIndex); //Set index for this vertex vIndex++; //Get the third vertex for this triangle ss >> VertDef; std::wstring vertPart; int whichPart = 0; //Parse this string (same as above) for(int j = 0; j < VertDef.length(); ++j) { if(VertDef[j] != '/') vertPart += VertDef[j]; if(VertDef[j] == '/' || j == VertDef.length()-1) { std::wistringstream wstringToInt(vertPart); if(whichPart == 0) { wstringToInt >> vertPosIndexTemp; vertPosIndexTemp -= 1; //Check to see if the vert pos was the only thing specified if(j == VertDef.length()-1) { vertTCIndexTemp = 0; vertNormIndexTemp = 0; } } else if(whichPart == 1) { if(vertPart != L"") { wstringToInt >> vertTCIndexTemp; vertTCIndexTemp -= 1; } else vertTCIndexTemp = 0; if(j == VertDef.length()-1) vertNormIndexTemp = 0; } else if(whichPart == 2) { std::wistringstream wstringToInt(vertPart); wstringToInt >> vertNormIndexTemp; vertNormIndexTemp -= 1; } vertPart = L""; whichPart++; } } //Check for duplicate vertices bool vertAlreadyExists = false; if(totalVerts >= 3) //Make sure we at least have one triangle to check { for(int iCheck = 0; iCheck < totalVerts; ++iCheck) { if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists) { if(vertTCIndexTemp == vertTCIndex[iCheck]) { indices.push_back(iCheck); //Set index for this vertex vertAlreadyExists = true; //If we've made it here, the vertex already exists } } } } if(!vertAlreadyExists) { vertPosIndex.push_back(vertPosIndexTemp); vertTCIndex.push_back(vertTCIndexTemp); vertNormIndex.push_back(vertNormIndexTemp); totalVerts++; //New vertex created, add to total verts indices.push_back(totalVerts-1); //Set index for this vertex } //Set the second vertex for the next triangle to the last vertex we got lastVIndex = indices[vIndex]; //The last vertex index of this TRIANGLE meshTriangles++; //New triangle defined vIndex++; } } } break; case 'm': //mtllib - material library filename checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == 'i') { checkChar = fileIn.get(); if(checkChar == 'b') { checkChar = fileIn.get(); if(checkChar == ' ') { //Store the material libraries file name fileIn >> meshMatLib; } } } } } } break; case 'u': //usemtl - which material to use checkChar = fileIn.get(); if(checkChar == 's') { checkChar = fileIn.get(); if(checkChar == 'e') { checkChar = fileIn.get(); if(checkChar == 'm') { checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == ' ') { meshMaterialsTemp = L""; //Make sure this is cleared fileIn >> meshMaterialsTemp; //Get next type (string) meshMaterials.push_back(meshMaterialsTemp); } } } } } } break; default: break; } } } else //If we could not open the file { SwapChain->SetFullscreenState(false, NULL); //Make sure we are out of fullscreen //create message std::wstring message = L"Could not open: "; message += filename; MessageBox(0, message.c_str(), //display message L"Error", MB_OK); return false; } subsetIndexStart.push_back(vIndex); //There won't be another index start after our last subset, so set it here //sometimes "g" is defined at the very top of the file, then again before the first group of faces. //This makes sure the first subset does not conatain "0" indices. if(subsetIndexStart[1] == 0) { subsetIndexStart.erase(subsetIndexStart.begin()+1); meshSubsets--; } //Make sure we have a default for the tex coord and normal //if one or both are not specified if(!hasNorm) vertNorm.push_back(XMFLOAT3(0.0f, 0.0f, 0.0f)); if(!hasTexCoord) vertTexCoord.push_back(XMFLOAT2(0.0f, 0.0f)); //Close the obj file, and open the mtl file fileIn.close(); fileIn.open(meshMatLib.c_str()); std::wstring lastStringRead; int matCount = material.size(); //total materials //kdset - If our diffuse color was not set, we can use the ambient color (which is usually the same) //If the diffuse color WAS set, then we don't need to set our diffuse color to ambient bool kdset = false; if (fileIn) { while(fileIn) { checkChar = fileIn.get(); //Get next char switch (checkChar) { //Check for comment case '#': checkChar = fileIn.get(); while(checkChar != '\n') checkChar = fileIn.get(); break; //Set diffuse color case 'K': checkChar = fileIn.get(); if(checkChar == 'd') //Diffuse Color { checkChar = fileIn.get(); //remove space fileIn >> material[matCount-1].difColor.x; fileIn >> material[matCount-1].difColor.y; fileIn >> material[matCount-1].difColor.z; kdset = true; } //Ambient Color (We'll store it in diffuse if there isn't a diffuse already) if(checkChar == 'a') { checkChar = fileIn.get(); //remove space if(!kdset) { fileIn >> material[matCount-1].difColor.x; fileIn >> material[matCount-1].difColor.y; fileIn >> material[matCount-1].difColor.z; } } break; //Check for transparency case 'T': checkChar = fileIn.get(); if(checkChar == 'r') { checkChar = fileIn.get(); //remove space float Transparency; fileIn >> Transparency; material[matCount-1].difColor.w = Transparency; if(Transparency > 0.0f) material[matCount-1].transparent = true; } break; //Some obj files specify d for transparency case 'd': checkChar = fileIn.get(); if(checkChar == ' ') { float Transparency; fileIn >> Transparency; //'d' - 0 being most transparent, and 1 being opaque, opposite of Tr Transparency = 1.0f - Transparency; material[matCount-1].difColor.w = Transparency; if(Transparency > 0.0f) material[matCount-1].transparent = true; } break; //Get the diffuse map (texture) case 'm': checkChar = fileIn.get(); if(checkChar == 'a') { checkChar = fileIn.get(); if(checkChar == 'p') { checkChar = fileIn.get(); if(checkChar == '_') { //map_Kd - Diffuse map checkChar = fileIn.get(); if(checkChar == 'K') { checkChar = fileIn.get(); if(checkChar == 'd') { std::wstring fileNamePath; fileIn.get(); //Remove whitespace between map_Kd and file //Get the file path - We read the pathname char by char since //pathnames can sometimes contain spaces, so we will read until //we find the file extension bool texFilePathEnd = false; while(!texFilePathEnd) { checkChar = fileIn.get(); fileNamePath += checkChar; if(checkChar == '.') { for(int i = 0; i < 3; ++i) fileNamePath += fileIn.get(); texFilePathEnd = true; } } //check if this texture has already been loaded bool alreadyLoaded = false; for(int i = 0; i < textureNameArray.size(); ++i) { if(fileNamePath == textureNameArray[i]) { alreadyLoaded = true; material[matCount-1].texArrayIndex = i; material[matCount-1].hasTexture = true; } } //if the texture is not already loaded, load it now if(!alreadyLoaded) { ID3D11ShaderResourceView* tempMeshSRV; hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(), NULL, NULL, &tempMeshSRV, NULL ); if(SUCCEEDED(hr)) { textureNameArray.push_back(fileNamePath.c_str()); material[matCount-1].texArrayIndex = meshSRV.size(); meshSRV.push_back(tempMeshSRV); material[matCount-1].hasTexture = true; } } } } //map_d - alpha map else if(checkChar == 'd') { //Alpha maps are usually the same as the diffuse map //So we will assume that for now by only enabling //transparency for this material, as we will already //be using the alpha channel in the diffuse map material[matCount-1].transparent = true; } } } } break; case 'n': //newmtl - Declare new material checkChar = fileIn.get(); if(checkChar == 'e') { checkChar = fileIn.get(); if(checkChar == 'w') { checkChar = fileIn.get(); if(checkChar == 'm') { checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == ' ') { //New material, set its defaults SurfaceMaterial tempMat; material.push_back(tempMat); fileIn >> material[matCount].matName; material[matCount].transparent = false; material[matCount].hasTexture = false; material[matCount].texArrayIndex = 0; matCount++; kdset = false; } } } } } } break; default: break; } } } else { SwapChain->SetFullscreenState(false, NULL); //Make sure we are out of fullscreen std::wstring message = L"Could not open: "; message += meshMatLib; MessageBox(0, message.c_str(), L"Error", MB_OK); return false; } //Set the subsets material to the index value //of the its material in our material array for(int i = 0; i < meshSubsets; ++i) { bool hasMat = false; for(int j = 0; j < material.size(); ++j) { if(meshMaterials[i] == material[j].matName) { subsetMaterialArray.push_back(j); hasMat = true; } } if(!hasMat) subsetMaterialArray.push_back(0); //Use first material in array } std::vector<Vertex> vertices; Vertex tempVert; //Create our vertices using the information we got //from the file and store them in a vector for(int j = 0 ; j < totalVerts; ++j) { tempVert.pos = vertPos[vertPosIndex[j]]; tempVert.normal = vertNorm[vertNormIndex[j]]; tempVert.texCoord = vertTexCoord[vertTCIndex[j]]; vertices.push_back(tempVert); } //////////////////////Compute Normals/////////////////////////// //If computeNormals was set to true then we will create our own //normals, if it was set to false we will use the obj files normals if(computeNormals) { std::vector<XMFLOAT3> tempNormal; //normalized and unnormalized normals XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f); //Used to get vectors (sides) from the position of the verts float vecX, vecY, vecZ; //Two edges of our triangle XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //Compute face normals for(int i = 0; i < meshTriangles; ++i) { //Get the vector describing one edge of our triangle (edge 0,2) vecX = vertices[indices[(i*3)]].pos.x - vertices[indices[(i*3)+2]].pos.x; vecY = vertices[indices[(i*3)]].pos.y - vertices[indices[(i*3)+2]].pos.y; vecZ = vertices[indices[(i*3)]].pos.z - vertices[indices[(i*3)+2]].pos.z; edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our first edge //Get the vector describing another edge of our triangle (edge 2,1) vecX = vertices[indices[(i*3)+2]].pos.x - vertices[indices[(i*3)+1]].pos.x; vecY = vertices[indices[(i*3)+2]].pos.y - vertices[indices[(i*3)+1]].pos.y; vecZ = vertices[indices[(i*3)+2]].pos.z - vertices[indices[(i*3)+1]].pos.z; edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our second edge //Cross multiply the two edge vectors to get the un-normalized face normal XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2)); tempNormal.push_back(unnormalized); //Save unormalized normal (for normal averaging) } //Compute vertex normals (normal Averaging) XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); int facesUsing = 0; float tX; float tY; float tZ; //Go through each vertex for(int i = 0; i < totalVerts; ++i) { //Check which triangles use this vertex for(int j = 0; j < meshTriangles; ++j) { if(indices[j*3] == i || indices[(j*3)+1] == i || indices[(j*3)+2] == i) { tX = XMVectorGetX(normalSum) + tempNormal[j].x; tY = XMVectorGetY(normalSum) + tempNormal[j].y; tZ = XMVectorGetZ(normalSum) + tempNormal[j].z; normalSum = XMVectorSet(tX, tY, tZ, 0.0f); //If a face is using the vertex, add the unormalized face normal to the normalSum facesUsing++; } } //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex normalSum = normalSum / facesUsing; //Normalize the normalSum vector normalSum = XMVector3Normalize(normalSum); //Store the normal in our current vertex vertices[i].normal.x = XMVectorGetX(normalSum); vertices[i].normal.y = XMVectorGetY(normalSum); vertices[i].normal.z = XMVectorGetZ(normalSum); //Clear normalSum and facesUsing for next vertex normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); facesUsing = 0; } } //Create index buffer D3D11_BUFFER_DESC indexBufferDesc; ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) ); indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(DWORD) * meshTriangles*3; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA iinitData; iinitData.pSysMem = &indices[0]; d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, indexBuff); //Create Vertex Buffer D3D11_BUFFER_DESC vertexBufferDesc; ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) ); vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof( Vertex ) * totalVerts; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA vertexBufferData; ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) ); vertexBufferData.pSysMem = &vertices[0]; hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, vertBuff); return true; } ##Function variables## These are the variables used by the function. I'll let you read the comments instead of explaining in detail. I will mention quickly we are loading the file in using a stringstream. A stringstream will let us manipulate and take data from a string as if it were an input/output stream. As you will see soon, we can use ">>" to obtain the next type from the stream. HRESULT hr = 0; std::wifstream fileIn (filename.c_str()); //Open file std::wstring meshMatLib; //String to hold our obj material library filename //Arrays to store our model's information std::vector<DWORD> indices; std::vector<XMFLOAT3> vertPos; std::vector<XMFLOAT3> vertNorm; std::vector<XMFLOAT2> vertTexCoord; std::vector<std::wstring> meshMaterials; //Vertex definition indices std::vector<int> vertPosIndex; std::vector<int> vertNormIndex; std::vector<int> vertTCIndex; //Make sure we have a default if no tex coords or normals are defined bool hasTexCoord = false; bool hasNorm = false; //Temp variables to store into vectors std::wstring meshMaterialsTemp; int vertPosIndexTemp; int vertNormIndexTemp; int vertTCIndexTemp; wchar_t checkChar; //The variable we will use to store one char from file at a time std::wstring face; //Holds the string containing our face vertices int vIndex = 0; //Keep track of our vertex index count int triangleCount = 0; //Total Triangles int totalVerts = 0; int meshTriangles = 0; ##Opening the Obj File## First we want to check if the file can even be opened. If it cannot, we make sure we are out of fullscreen, then present a message saying we could not open the specified file. //Check to see if the file was opened if (fileIn) { ... } else //If we could not open the file { SwapChain->SetFullscreenState(false, NULL); //Make sure we are out of fullscreen //create message std::wstring message = L"Could not open: "; message += filename; MessageBox(0, message.c_str(), //display message L"Error", MB_OK); return false; } ##Reading the Obj File## Now that our file is open, we can start reading it. We first jump into a while loop, which will keep looping until we have reached the end of the file (eof). We will read the file character by character. We could actually read it string by string using ">>" instead of the get() method, but for now we will get characters (this could be a little exercise). We are able to get the next single character from the stringstream by using the method get(), the next type by using ">>", or a line by using getline(). After we read a character, we jump into a switch condition. In this condition, we will first check to see if the line is a comment (remember obj files are sectioned by lines). If the line is a comment, we will skip that line by continuing to get() characters until we reach a new line character "\n". Next we check to see if the line is a vertex position by checking to see if the character was a "v". If it was we get the next character and (if it was a space " ") get the vertex position. Vertex positions are float value separated by " ", so we are able to use the ">>" to get the values from the string stream, and store them into the three float variables, vx, vy, and vz. After we got the values, we check to see if this model was specified as a RH coord system. If it was, we inverse the z value of the position by multiplying it with -1.0f and push back the values into the vertPos vector. If it wasn't, then we just push back the values without modification. Then we do the same thing for texture coordinates. Obj models sometimes specify a third value representing the w in the (u, v, w) texture coordinate system. Like we said above, the w is used for cubic textures, and we will not take cubic textures into consideration in this lesson. Of course, this would be very easy to change if you happen to need this w value. We also need to check to see if the model is a RH coord system or LH coord system. If we need to convert it from RH, we simply invert the v-axis by subtracting v from 1.0f. After we store the texture coord, we set hasTexCoord to true. We do this so we know we don't have to make a default texture coord later, because if there are no texture coordinates, and we try to set the 0th texture coordinate (which will be the default texture coord if none are specified in the faces), we will get an error. Next we check for normals. Same thing as checking for vertex positions, except after we push back the normal into the vertNorm vector, we need to set hasNorm to true, which is used the same way as hasTexCoord. We don't take "objects" ("o") into consideration, but we do take groups into consideration. Groups are another name for "subsets". Groups, or subsets, are a set of triangles which share the same attributes, so whenever we reach a line starting with "g", we will increase the subset count, and push back a new subsetIndexStart. Next we will get the faces information, but we will talk about that in a moment. We check for the material library "mtllib". Not all obj files contain a material library, and some contain multiple libraries, however, we will suppose they always contain only one. Easy to modify though. We will store the material libraries filename in the wide string meshMatLib. Lastly, we get the groups material "usemtl" and push it back into the meshMaterials vector, where later we will use this to find the index value of the material in the material vector. while(fileIn) { checkChar = fileIn.get(); //Get next char switch (checkChar) { case '#': checkChar = fileIn.get(); while(checkChar != '\n') checkChar = fileIn.get(); break; case 'v': //Get Vertex Descriptions checkChar = fileIn.get(); if(checkChar == ' ') //v - vert position { float vz, vy, vx; fileIn >> vx >> vy >> vz; //Store the next three types if(isRHCoordSys) //If model is from an RH Coord System vertPos.push_back(XMFLOAT3( vx, vy, vz * -1.0f)); //Invert the Z axis else vertPos.push_back(XMFLOAT3( vx, vy, vz)); } if(checkChar == 't') //vt - vert tex coords { float vtcu, vtcv; fileIn >> vtcu >> vtcv; //Store next two types if(isRHCoordSys) //If model is from an RH Coord System vertTexCoord.push_back(XMFLOAT2(vtcu, 1.0f-vtcv)); //Reverse the "v" axis else vertTexCoord.push_back(XMFLOAT2(vtcu, vtcv)); hasTexCoord = true; //We know the model uses texture coords } //Since we compute the normals later, we don't need to check for normals //In the file, but i'll do it here anyway if(checkChar == 'n') //vn - vert normal { float vnx, vny, vnz; fileIn >> vnx >> vny >> vnz; //Store next three types if(isRHCoordSys) //If model is from an RH Coord System vertNorm.push_back(XMFLOAT3( vnx, vny, vnz * -1.0f )); //Invert the Z axis else vertNorm.push_back(XMFLOAT3( vnx, vny, vnz )); hasNorm = true; //We know the model defines normals } break; //New group (Subset) case 'g': //g - defines a group checkChar = fileIn.get(); if(checkChar == ' ') { subsetIndexStart.push_back(vIndex); //Start index for this subset subsetCount++; } break; //Get Face Index ... case 'm': //mtllib - material library filename checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == 'i') { checkChar = fileIn.get(); if(checkChar == 'b') { checkChar = fileIn.get(); if(checkChar == ' ') { //Store the material libraries file name fileIn >> meshMatLib; } } } } } } break; case 'u': //usemtl - which material to use checkChar = fileIn.get(); if(checkChar == 's') { checkChar = fileIn.get(); if(checkChar == 'e') { checkChar = fileIn.get(); if(checkChar == 'm') { checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == ' ') { meshMaterialsTemp = L""; //Make sure this is cleared fileIn >> meshMaterialsTemp; //Get next type (string) meshMaterials.push_back(meshMaterialsTemp); } } } } } } break; default: break; } } ##Loading Faces "f"## Since loading the faces is a bit more complex, and the section above was already getting a little large, I am describing the faces section here instead of above. So, remember how we talked about faces in obj models? sometime modeling programs are able to create faces with more than 3 sides, and not only that, sometimes the faces are concave (look above to see what concave is). Also remember direct3d uses triangles. While we will fix the first problem here, by converting faces with more than 3 vertices to triangles, we will not fix the second problem here, which is concave faces. What will happen when you try loading a concave face using the function as it is now, is there might be triangles overlapping each other,causing flickering and an inacurate representation of the model. To fix this problem isn't too difficult, all you would need to do is calculate the angle of the two edges for each vertex, and retriangulate the face depending on angles that are larger than 180 degrees, or 3.14 radians. The way we retriangulate the faces in the function right now is we use a single vertex for all the triangles to start with, and the last vertex of the previous triangle as the second vertex in the current triangle. I will try to explain that better in a moment. The first thing we do when checking for faces, is get the entire line. While we are getting the entire line, we check for spaces " ". The reason we check for spaces is because each vertex is separated by a space, and we need to make sure that the face is retriangulated if there are more than 3 vertices in the face. After we get the line, we check to make sure the last character in the line was not a space, since each space would add an additional triangle. Then we subtract 1 from the triangle count, since the first three vertices in a face are separated by two spaces, which will give our triangleCount a total of two triangles instead of one. We turn the string containing the face line into a stringstream called ss. We will be able to use the stringstream to take out one vertex definition at a time, and parse through it to find the index values of the vertex position, texture coordinate, and normal. Then we check to make sure that the line is greater than 0. We create two integers, one for the index value of the first vertex in the face, and one for the index value of the last vertex in the last created triangle. These are used to retriangulate the face, which we will see soon. Now we enter a loop, which will create the indices for the first triangle in the face. This loop will exit when the first three vertices in the face have been turned into a triangle and stored in the index list. We get one vertex definition at a time by using the ">>" for the stringstream ss and storing it into VertDef. We create a string which will keep track of the index value, and another integer value which keeps track of which part of the vertex defenition we are on (position/texcoord/normal). If this value is "0", we are on the vertex position, if its "1" we are on the texture coordinate, and if its "2" we are on the normal. Next we need to parse the string VertDef which contains the definition of the current vertex. The definition is index values which will be used to find the position, texture coordinate, and normal from the loaded positions, texture coordinates, and normals which were put into vectors. We loop through the VertDef string. First we check to make sure the current character was not a "/". If it was not we move on to the next character. When we come across a "/" OR if the current character is the last character in the string, we can set the value we just got from the string to the appropriate temp variable. We know which variable it was because of the whichPart variable. If whichPart was "0", we know we are on the position. First we need to create another stringstream from the vertPart string. We are able to extract types from stringstreams, so we can extract the number from this and store it in an int variable. This integer is the index value used to look up which element in the position, texcoord, or normal vectors this vertex uses. If whichPart was "0", we will subtract 1 from it since obj files start their index count with 1, while c++ starts arrays with 0. We talked about this above. Next we check to see if we are on the last character in the vertex string (eg. "f 1 2 3"). If we are, we know that only the position was specified, and set a default index value of "0" to vertNormIndexTemp and vertTCIndexTemp. After that we reset vertPart and increment whichPart. If whichPart is "1" we know we are where the texture coordinate should be. If a vertPart was not empty "" (eg. "f 1/1/1 2/2/2 3/3/3"), we will set the temp index value for the texture coord and subtract 1 from it. If vertPart was empty "" (eg. "f 1//1 2//2 3//3"), we set a default index value of "0" to the temp texture coordinate index. After that we check to see if the current character was the last in the vertPart string (eg. "f 1/1 2/2 3/3"). If it was, we know there is no normal and set vertNormIndexTemp to "0". Finally, if we have not reached the end of the string already, and whichPart is "2", we are looking at the vertex normal. If we have made it this far, all we have to do is just subtract one from the normal value, and store it in vertNormIndexTemp. case 'f': //f - defines the faces checkChar = fileIn.get(); if(checkChar == ' ') { face = L""; std::wstring VertDef; //Holds one vertex definition at a time triangleCount = 0; checkChar = fileIn.get(); while(checkChar != '\n') { face += checkChar; //Add the char to our face string checkChar = fileIn.get(); //Get the next Character if(checkChar == ' ') //If its a space... triangleCount++; //Increase our triangle count } //Check for space at the end of our face string if(face[face.length()-1] == ' ') triangleCount--; //Each space adds to our triangle count triangleCount -= 1; //Ever vertex in the face AFTER the first two are new faces std::wstringstream ss(face); if(face.length() > 0) { int firstVIndex, lastVIndex; //Holds the first and last vertice's index for(int i = 0; i < 3; ++i) //First three vertices (first triangle) { ss >> VertDef; //Get vertex definition (vPos/vTexCoord/vNorm) std::wstring vertPart; int whichPart = 0; //(vPos, vTexCoord, or vNorm) //Parse this string for(int j = 0; j < VertDef.length(); ++j) { if(VertDef[j] != '/') //If there is no divider "/", add a char to our vertPart vertPart += VertDef[j]; //If the current char is a divider "/", or its the last character in the string if(VertDef[j] == '/' || j == VertDef.length()-1) { std::wistringstream wstringToInt(vertPart); //Used to convert wstring to int if(whichPart == 0) //If vPos { wstringToInt >> vertPosIndexTemp; vertPosIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 //Check to see if the vert pos was the only thing specified if(j == VertDef.length()-1) { vertNormIndexTemp = 0; vertTCIndexTemp = 0; } } else if(whichPart == 1) //If vTexCoord { if(vertPart != L"") //Check to see if there even is a tex coord { wstringToInt >> vertTCIndexTemp; vertTCIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 } else //If there is no tex coord, make a default vertTCIndexTemp = 0; //If the cur. char is the second to last in the string, then //there must be no normal, so set a default normal if(j == VertDef.length()-1) vertNormIndexTemp = 0; } else if(whichPart == 2) //If vNorm { std::wistringstream wstringToInt(vertPart); wstringToInt >> vertNormIndexTemp; vertNormIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 } vertPart = L""; //Get ready for next vertex part whichPart++; //Move on to next vertex part } } ##Storing the face## After we have loaded the first triangles vertices index information, we need to store this information, but only after we check for duplicate vertices. First we make sure there is at least one subset. We do this because sometimes obj models will not specify "g" at all, and we rely on "g" to tell us how many subsets there are, and how to separate the faces into subsets. Next we check for duplicate vertices. We do this by going through each of the vectors containing the vertex positions and vertex texture coordinates, and checking to see if the current vertex definition is the same as a vertex definition already stored. If the same vertex is already stored, we push back the index value of that already stored vertex into the indices list. If the vertex definition was not already stored, we store the vertex definition into the three vectors, vertPosIndex, vertTCIndex, and vertNormIndex. We will use the index values from these three vectors to reference the actual positions, texture coordinates, and normals stored in vertPos, vertTexCoord, and vertNorm to create the vertices which will be stored in the vertex buffer of this model. Next we store the first and last vertex index from this triangle in the firstVIndex and lastVIndex, which will be used for retriangulating the face if there are more than three vertices in this face. After we have loaded and stored the first triangle in this face, we increment the total triangles count. //Check to make sure there is at least one subset if(subsetCount == 0) { subsetIndexStart.push_back(vIndex); //Start index for this subset subsetCount++; } //Avoid duplicate vertices bool vertAlreadyExists = false; if(totalVerts >= 3) //Make sure we at least have one triangle to check { //Loop through all the vertices for(int iCheck = 0; iCheck < totalVerts; ++iCheck) { //If the vertex position and texture coordinate in memory are the same //As the vertex position and texture coordinate we just now got out //of the obj file, we will set this faces vertex index to the vertex's //index value in memory. This makes sure we don't create duplicate vertices if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists) { if(vertTCIndexTemp == vertTCIndex[iCheck]) { indices.push_back(iCheck); //Set index for this vertex vertAlreadyExists = true; //If we've made it here, the vertex already exists } } } } //If this vertex is not already in our vertex arrays, put it there if(!vertAlreadyExists) { vertPosIndex.push_back(vertPosIndexTemp); vertTCIndex.push_back(vertTCIndexTemp); vertNormIndex.push_back(vertNormIndexTemp); totalVerts++; //We created a new vertex indices.push_back(totalVerts-1); //Set index for this vertex } //If this is the very first vertex in the face, we need to //make sure the rest of the triangles use this vertex if(i == 0) { firstVIndex = indices[vIndex]; //The first vertex index of this FACE } //If this was the last vertex in the first triangle, we will make sure //the next triangle uses this one (eg. tri1(1,2,3) tri2(1,3,4) tri3(1,4,5)) if(i == 2) { lastVIndex = indices[vIndex]; //The last vertex index of this TRIANGLE } vIndex++; //Increment index count } meshTriangles++; //One triangle down ##Retriangulating the Face## Now, like we said before, sometimes obj models contain faces with more than three vertices. If this happens, we need to "retriangulate" the face, and turn the one face into multiple triangles. First thing we do is check to see if there is more than one triangle in this face (this check is basically combined with the loop if you look "triangleCount-1"). If there is, then we move on to retriangulation. We jump into a loop which will go through each vertex after the three vertices in the face, and create an additional triangle. We push back the firstVIndex value into the indices list, and add to vIndex. Remember firstVIndex is the index value of the very first vertex of this face. After that we push back lastVindex into the indices list. lastVIndex is the last vertex of the last stored triangle. Now we have the first two vertices for this triangle, The third one will come from the file. We get this vertex the same way we did above, so it look above for a description. After we got the next vertex, completing this triangle, we want to store it, but before we store it, we want to again check to make sure this vertex definition does not already exist (also explained above). Finally we set lastVIndex to the index value of this last vertex we just got, so we can use this as the second vertex in the next triangle, if there is a next triangle. Then we increment meshTriangles and vIndex, completing this face. (All that for one face!) //If there are more than three vertices in the face definition, we need to make sure //we convert the face to triangles. We created our first triangle above, now we will //create a new triangle for every new vertex in the face, using the very first vertex //of the face, and the last vertex from the triangle before the current triangle for(int l = 0; l < triangleCount-1; ++l) //Loop through the next vertices to create new triangles { //First vertex of this triangle (the very first vertex of the face too) indices.push_back(firstVIndex); //Set index for this vertex vIndex++; //Second Vertex of this triangle (the last vertex used in the tri before this one) indices.push_back(lastVIndex); //Set index for this vertex vIndex++; //Get the third vertex for this triangle ss >> VertDef; std::wstring vertPart; int whichPart = 0; //Parse this string (same as above) for(int j = 0; j < VertDef.length(); ++j) { if(VertDef[j] != '/') vertPart += VertDef[j]; if(VertDef[j] == '/' || j == VertDef.length()-1) { std::wistringstream wstringToInt(vertPart); if(whichPart == 0) { wstringToInt >> vertPosIndexTemp; vertPosIndexTemp -= 1; //Check to see if the vert pos was the only thing specified if(j == VertDef.length()-1) { vertTCIndexTemp = 0; vertNormIndexTemp = 0; } } else if(whichPart == 1) { if(vertPart != L"") { wstringToInt >> vertTCIndexTemp; vertTCIndexTemp -= 1; } else vertTCIndexTemp = 0; if(j == VertDef.length()-1) vertNormIndexTemp = 0; } else if(whichPart == 2) { std::wistringstream wstringToInt(vertPart); wstringToInt >> vertNormIndexTemp; vertNormIndexTemp -= 1; } vertPart = L""; whichPart++; } } //Check for duplicate vertices bool vertAlreadyExists = false; if(totalVerts >= 3) //Make sure we at least have one triangle to check { for(int iCheck = 0; iCheck < totalVerts; ++iCheck) { if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists) { if(vertTCIndexTemp == vertTCIndex[iCheck]) { indices.push_back(iCheck); //Set index for this vertex vertAlreadyExists = true; //If we've made it here, the vertex already exists } } } } if(!vertAlreadyExists) { vertPosIndex.push_back(vertPosIndexTemp); vertTCIndex.push_back(vertTCIndexTemp); vertNormIndex.push_back(vertNormIndexTemp); totalVerts++; //New vertex created, add to total verts indices.push_back(totalVerts-1); //Set index for this vertex } //Set the second vertex for the next triangle to the last vertex we got lastVIndex = indices[vIndex]; //The last vertex index of this TRIANGLE meshTriangles++; //New triangle defined vIndex++; } } } break; ##After-Loading Preparation## Now we have to do a couple things after loading our models geometry. First, we need to push back one final subsetIndexStart, since each model will begin drawing at the index their corresponding subsetIndexStart value, and stop drawing at the next subsets corresponding subsetIndexStart value. When we get to this point, each subset will have their own, but the last subset will not have one to "stop" drawing at, so we will set that here. We push back vIndex which is the total number of indices we have stored for the model. Next, we check to make sure the first subset does not contain a total of 0 indices to be drawn. This can happen because once in a while a group "g" is defined at the top of the file, and once again before the actual faces are defined. This will create a subset with "0" indices to be drawn. If this happens, we will subtract one subset from the total count, and erase the second element in the subsetIndexStart vector, which contains the faulty "0" value. Next we make sure we have a default texture coordinate and normal for our vertices, as sometimes the obj file does not contain any, and our program's vertex structure expects SOMETHING to be passed in. Next we close the obj file, and open the mtl file. Now we have to start getting ready to load and store our materials. First we create a new wide string called lastStringRead, and a new integer called matCount to keep track of the total number of materials loaded. Then we create a boolean, called kdset. What this boolean is used for is to know if a diffuse color has been loaded or not, since we will assume ambient color is the same as the diffuse color. If we load a diffuse color, we will set this boolean to true, so when we come to load ambient color, we will not store it since we already have a diffuse color. But if we come across ambient first, or diffuse was not specified at all, then kdset will be false, and ambient will be the materials diffuse color. subsetIndexStart.push_back(vIndex); //There won't be another index start after our last subset, so set it here //sometimes "g" is defined at the very top of the file, then again before the first group of faces. //This makes sure the first subset does not conatain "0" indices. if(subsetIndexStart[1] == 0) { subsetIndexStart.erase(subsetIndexStart.begin()+1); meshSubsets--; } //Make sure we have a default for the tex coord and normal //if one or both are not specified if(!hasNorm) vertNorm.push_back(XMFLOAT3(0.0f, 0.0f, 0.0f)); if(!hasTexCoord) vertTexCoord.push_back(XMFLOAT2(0.0f, 0.0f)); //Close the obj file, and open the mtl file fileIn.close(); fileIn.open(meshMatLib.c_str()); std::wstring lastStringRead; int matCount = material.size(); //total materials //kdset - If our diffuse color was not set, we can use the ambient color (which is usually the same) //If the diffuse color WAS set, then we don't need to set our diffuse color to ambient bool kdset = false; ##Opening the Material Library (mtl file)## Like when we opened the obj file, we first check to make sure it could be opened, and if it couldn't, we exit out of fullscreen, and display a message, then return false, ultimately ending the program. if (fileIn) { ... } else { SwapChain->SetFullscreenState(false, NULL); //Make sure we are out of fullscreen std::wstring message = L"Could not open: "; message += meshMatLib; MessageBox(0, message.c_str(), L"Error", MB_OK); return false; } ##Loading the Materials## Now we can load the materials. This is very similar to what we did above when loading the models geometry. First we jump into a loop that will exit when we have reached the end of the file (eof). The we start by getting a character using the get() method. We then enter a switch condition, and check what the character was. First we check for a comment. Then we check for a "K". The only "K"'s we check for are the diffuse color and the ambient color. However, you could also check for specular and emissive, "Ks" and "Ke". Looking at the code you can probably see whats happening without too much explanation. Next we check for transparency, "Tr" or "d". "Tr" is short for Transparency, and "d" is short for diffuse. Remember "Tr" is a value 0 to 1, where 1 is completely transparent or invisible and 0 is opaque. "d" is the opposite. Its a value between "0" and "1", but this time where "0" is most transparent or invisible, and "1" is opaque. Next we check for texture maps, "map_". The first one we check for is the diffuse map "map_Kd". We load the filename or filepath by going through each character in the line until we reach a period ".". After we reach a period, we assume the extension is three letters long, and take the next three characters. Next we check to make sure this texture has not already been loaded by comparing it with each of the strings stored in the textureNameArray vector. If it was already loaded, we store its index value in the current materials texArrayIndex member, which is used to find the actual texture in the meshSRV vector. If the texture has not already been loaded, we load it next. If it was successfully loaded, we push back its filename in textureNameArray, set the current materials texArrayIndex member to the size of meshSRV (which is the index value of the last texture stored in meshSRV), we push back the temp loaded shader resource view to meshSRV, and finally set the materials hasTexture member to true, so that we know to use a texture to color the subset and not the solod diffuse color loaded from the file. We also check for an alpha map "map_d". Almost always this is the same texture as the diffuse map, so we will assume that here, and just set the transparent member of the material to true, so that we know to use blending on the subsets that use this material, and draw them last. Remember how we mentioned materials were separated by a line starting with "newmtl". Well, we also need to check for this line, so we know to create a new material. When we come across this line, we push back a new material and set its defaults. You can look at the code to see exactly whats going on. while(fileIn) { checkChar = fileIn.get(); //Get next char switch (checkChar) { //Check for comment case '#': checkChar = fileIn.get(); while(checkChar != '\n') checkChar = fileIn.get(); break; //Set diffuse color case 'K': checkChar = fileIn.get(); if(checkChar == 'd') //Diffuse Color { checkChar = fileIn.get(); //remove space fileIn >> material[matCount-1].difColor.x; fileIn >> material[matCount-1].difColor.y; fileIn >> material[matCount-1].difColor.z; kdset = true; } //Ambient Color (We'll store it in diffuse if there isn't a diffuse already) if(checkChar == 'a') { checkChar = fileIn.get(); //remove space if(!kdset) { fileIn >> material[matCount-1].difColor.x; fileIn >> material[matCount-1].difColor.y; fileIn >> material[matCount-1].difColor.z; } } break; //Check for transparency case 'T': checkChar = fileIn.get(); if(checkChar == 'r') { checkChar = fileIn.get(); //remove space float Transparency; fileIn >> Transparency; material[matCount-1].difColor.w = Transparency; if(Transparency > 0.0f) material[matCount-1].transparent = true; } break; //Some obj files specify d for transparency case 'd': checkChar = fileIn.get(); if(checkChar == ' ') { float Transparency; fileIn >> Transparency; //'d' - 0 being most transparent, and 1 being opaque, opposite of Tr Transparency = 1.0f - Transparency; material[matCount-1].difColor.w = Transparency; if(Transparency > 0.0f) material[matCount-1].transparent = true; } break; //Get the diffuse map (texture) case 'm': checkChar = fileIn.get(); if(checkChar == 'a') { checkChar = fileIn.get(); if(checkChar == 'p') { checkChar = fileIn.get(); if(checkChar == '_') { //map_Kd - Diffuse map checkChar = fileIn.get(); if(checkChar == 'K') { checkChar = fileIn.get(); if(checkChar == 'd') { std::wstring fileNamePath; fileIn.get(); //Remove whitespace between map_Kd and file //Get the file path - We read the pathname char by char since //pathnames can sometimes contain spaces, so we will read until //we find the file extension bool texFilePathEnd = false; while(!texFilePathEnd) { checkChar = fileIn.get(); fileNamePath += checkChar; if(checkChar == '.') { for(int i = 0; i < 3; ++i) fileNamePath += fileIn.get(); texFilePathEnd = true; } } //check if this texture has already been loaded bool alreadyLoaded = false; for(int i = 0; i < textureNameArray.size(); ++i) { if(fileNamePath == textureNameArray[i]) { alreadyLoaded = true; material[matCount-1].texArrayIndex = i; material[matCount-1].hasTexture = true; } } //if the texture is not already loaded, load it now if(!alreadyLoaded) { ID3D11ShaderResourceView* tempMeshSRV; hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(), NULL, NULL, &tempMeshSRV, NULL ); if(SUCCEEDED(hr)) { textureNameArray.push_back(fileNamePath.c_str()); material[matCount-1].texArrayIndex = meshSRV.size(); meshSRV.push_back(tempMeshSRV); material[matCount-1].hasTexture = true; } } } } //map_d - alpha map else if(checkChar == 'd') { //Alpha maps are usually the same as the diffuse map //So we will assume that for now by only enabling //transparency for this material, as we will already //be using the alpha channel in the diffuse map material[matCount-1].transparent = true; } } } } break; case 'n': //newmtl - Declare new material checkChar = fileIn.get(); if(checkChar == 'e') { checkChar = fileIn.get(); if(checkChar == 'w') { checkChar = fileIn.get(); if(checkChar == 'm') { checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == ' ') { //New material, set its defaults SurfaceMaterial tempMat; material.push_back(tempMat); fileIn >> material[matCount].matName; material[matCount].transparent = false; material[matCount].hasTexture = false; material[matCount].texArrayIndex = 0; matCount++; kdset = false; } } } } } } break; default: break; } } ##Setting the Subsets' Material Index Value## Now that we have loaded all our materials, we need to basically bind each subset to it's material. We do this by looping through each subset, and comparing its material name stored in meshMaterials with each of the material names stored in matName of the material vector. When we come across the right material in the material vector, we push back subsetMaterialArray to the index element of the material vector. Then we set hasMat to true to exit the loop. If all the materials in the material vector have been checked, and the subsets material was not there, we will give it a default material, which will be the first material in the material vector. //Set the subsets material to the index value //of the its material in our material array for(int i = 0; i < meshSubsets; ++i) { bool hasMat = false; for(int j = 0; j < material.size(); ++j) { if(meshMaterials[i] == material[j].matName) { subsetMaterialArray.push_back(j); hasMat = true; } } if(!hasMat) subsetMaterialArray.push_back(0); //Use first material in array } ## Create the Vertices## Now we will create the vertices which will be stored in the models vertex buffer. First we initialize a new Vertex vector to store the created vertices. Then a temporary Vertex to store the current vertex. We enter a loop which will end after we have created the number of vertices represented by totalVerts. Each cycle in the loop will create a single vertex and push it back into the vertices vector. First we find the current vertices position. We do this by finding the j'th element in the vertPosIndex vector, which is the index value of the position stored in vertPos. Then we do the same for the normals and texture coordinates. Finally we push back the temporary vertex, tempVert, into the vertices vector. The normals will be over-written after we have created all the vertices if we have set "true" for this functions computeNormals parameter. std::vector<Vertex> vertices; Vertex tempVert; //Create our vertices using the information we got //from the file and store them in a vector for(int j = 0 ; j < totalVerts; ++j) { tempVert.pos = vertPos[vertPosIndex[j]]; tempVert.normal = vertNorm[vertNormIndex[j]]; tempVert.texCoord = vertTexCoord[vertTCIndex[j]]; vertices.push_back(tempVert); } ##Computing Normals (Face Normals)## Now we will comput the vertex normals using normal averaging. First we will go through every triangle in the model and find it's "unormalized" normal. We will then go through every vertex and find all the faces that use that specific vertex. We will average the "unormalized" normals of these faces sharing this vertex, normalize that, then store it in the normal member of the current vertex. The first thing we do is initialize a couple things. The tempNormal vector will store the unormalized normal for each of the triangles in our mesh. We do not normalize this so we can get the correct normal average of faces sharing a single vertex. We have an XMFLOAT3 variable called unnormalized which will hold the x, y and z values of the current faces unormaized normal, so it can be pushed back to the tempNormal vector. Then three more floats, one for each of the x, y, and z values we will use to find the two of the edges of a triangle. Then we have two XMVECTORs to store the edges in. This would have been much more simple if we were able to store XMVECTOR's in a standard vector array. However, after searching, I have found the best solution is to create an XMFLOAT3 vector instead of an XMVECTOR vector. If you try to create an XMVector vector, you will get an access violation. It has to do with how XMVECTOR is stored in memory, and is not able to be stored in a dynamically allocated class or structure like a vector. If you are interested in a little bit more information, visit asawicki, where I found out why I was having this problem. Now we comput the face normals. We enter a loop which will exit once we have looped through every triangle in the model. To get a face normal, we need to first get two edges from the face (edge1X = v1X - v3X, edge2X = v3X - v2X, use this equation for each of the x, y and z axis). To get the face normal from these two edges, we cross multiply them. We are able to store an XMVECTOR into an XMFLOAT3 variable by using the xna math function XMStoreFloat3(), where the first parameter is a pointer to the XMFLOAT3 variable you wish to recieve the x, y, and z values of the XMVECTOR in the second parameter. After we find the "unormalized" normal, we push it back into the tempNormal vector. //////////////////////Compute Normals/////////////////////////// //If computeNormals was set to true then we will create our own //normals, if it was set to false we will use the obj files normals if(computeNormals) { std::vector<XMFLOAT3> tempNormal; //normalized and unnormalized normals XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f); //Used to get vectors (sides) from the position of the verts float vecX, vecY, vecZ; //Two edges of our triangle XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //Compute face normals for(int i = 0; i < meshTriangles; ++i) { //Get the vector describing one edge of our triangle (edge 0,2) vecX = vertices[indices[(i*3)]].pos.x - vertices[indices[(i*3)+2]].pos.x; vecY = vertices[indices[(i*3)]].pos.y - vertices[indices[(i*3)+2]].pos.y; vecZ = vertices[indices[(i*3)]].pos.z - vertices[indices[(i*3)+2]].pos.z; edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our first edge //Get the vector describing another edge of our triangle (edge 2,1) vecX = vertices[indices[(i*3)+2]].pos.x - vertices[indices[(i*3)+1]].pos.x; vecY = vertices[indices[(i*3)+2]].pos.y - vertices[indices[(i*3)+1]].pos.y; vecZ = vertices[indices[(i*3)+2]].pos.z - vertices[indices[(i*3)+1]].pos.z; edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our second edge //Cross multiply the two edge vectors to get the un-normalized face normal XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2)); tempNormal.push_back(unnormalized); //Save unormalized normal (for normal averaging) } ##Computing Normals (Vertex Normals - Normal Averaging)## Now that we have the face normals for each of the triangles in the model, we need to find the vertex normals, since these are what is sent to the shaders. First we initialize a couple more variables, including an XMVECTOR called normalSum, an integer called facesUsing, and three floats called tX, tY, tZ. Then we enter the loop which exits after we have created every vertices normal. The first thing we do in this loop is find every triangle that uses the current vertex. We do this by using the vertex's index represented by "i", and looping through every triangle in the mesh to see if any of its vertex indices equals "i". If one of them do, we know it is using the current vertex. If it's using the current vertex, we add its "unormalized" normal to the normalSum XMVECTOR, and increase facesUsing. After we have found all faces using this vertex, we get the real or actual normal from the sum of all face's normals sharing this vertex by dividing normalSum by facesUsing, and storing it back into normalSum. After that we "normalize" the "unormalized" normal stored in normalSum. We set this "normalized" normal as the current vertex's normal, clear normalSum and facesUsing and move on to the next vertex. //Compute vertex normals (normal Averaging) XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); int facesUsing = 0; float tX; float tY; float tZ; //Go through each vertex for(int i = 0; i < totalVerts; ++i) { //Check which triangles use this vertex for(int j = 0; j < meshTriangles; ++j) { if(indices[j*3] == i || indices[(j*3)+1] == i || indices[(j*3)+2] == i) { tX = XMVectorGetX(normalSum) + tempNormal[j].x; tY = XMVectorGetY(normalSum) + tempNormal[j].y; tZ = XMVectorGetZ(normalSum) + tempNormal[j].z; normalSum = XMVectorSet(tX, tY, tZ, 0.0f); //If a face is using the vertex, add the unormalized face normal to the normalSum facesUsing++; } } //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex normalSum = normalSum / facesUsing; //Normalize the normalSum vector normalSum = XMVector3Normalize(normalSum); //Store the normal in our current vertex vertices[i].normal.x = XMVectorGetX(normalSum); vertices[i].normal.y = XMVectorGetY(normalSum); vertices[i].normal.z = XMVectorGetZ(normalSum); //Clear normalSum and facesUsing for next vertex normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); facesUsing = 0; } } ##Creating the Vertex and Index Buffers## Now we have successfully loaded the model and its materials, created all the vertices and the index list, and (optionally) computed the vertex normals. All thats left to do with this function is create the index and vertex buffers. We should already know how to do this by now, but I will mention something about using vectors for the vertex and index list. I had a problem with this before, but when setting the vertex or index vectors as the pSysMem or data for the buffers, you need to use a pointer to them, and make sure to be explicit about where to start in the vector, eg. [0] for the first element in the vector. Have a look below to see what I mean. //Create index buffer D3D11_BUFFER_DESC indexBufferDesc; ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) ); indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(DWORD) * meshTriangles*3; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA iinitData; iinitData.pSysMem = &indices[0]; d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, indexBuff); //Create Vertex Buffer D3D11_BUFFER_DESC vertexBufferDesc; ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) ); vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof( Vertex ) * totalVerts; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA vertexBufferData; ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) ); vertexBufferData.pSysMem = &vertices[0]; hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, vertBuff); return true; } ##Calling the LoadObjModel() Function## Now go down to the InitScene() function, where we will now call our model loading function. We can call it with the if statement, so that if there was a problem loading the model, we can return false out of the InitScene() function, ending the program. if(!LoadObjModel(L"spaceCompound.obj", &meshVertBuff, &meshIndexBuff, meshSubsetIndexStart, meshSubsetTexture, material, meshSubsets, true, false)) return false; ##New Blending State## We have to create a blending state for the transparent subsets. This blending state will blend the objects depending on the alpha value of the diffuse color, either the solid alpha value loaded from the file ("Tr" or "d"), or from the diffuse map loaded from the file ("map_Kd"). We will save this blending state in Transparency. ZeroMemory( &rtbd, sizeof(rtbd) ); rtbd.BlendEnable = true; rtbd.SrcBlend = D3D11_BLEND_INV_SRC_ALPHA; rtbd.DestBlend = D3D11_BLEND_SRC_ALPHA; rtbd.BlendOp = D3D11_BLEND_OP_ADD; rtbd.SrcBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; rtbd.DestBlendAlpha = D3D11_BLEND_SRC_ALPHA; rtbd.BlendOpAlpha = D3D11_BLEND_OP_ADD; rtbd.RenderTargetWriteMask = D3D10_COLOR_WRITE_ENABLE_ALL; blendDesc.AlphaToCoverageEnable = false; blendDesc.RenderTarget[0] = rtbd; d3d11Device->CreateBlendState(&blendDesc, &Transparency); ##UpdateScene() Function## In our update scene function, we need to make sure we define the world space for our model. meshWorld = XMMatrixIdentity(); //Define cube1's world space matrix Rotation = XMMatrixRotationY(3.14f); Scale = XMMatrixScaling( 1.0f, 1.0f, 1.0f ); Translation = XMMatrixTranslation( 0.0f, 0.0f, 0.0f ); meshWorld = Rotation * Scale * Translation; ##Drawing the NON-Transparent Subsets## Here we will draw the non-transparent subsets of our model. We enter a loop which goes through every subset in the mesh. We set the correct vertex and index buffer, and the world space matrix, and update our constant buffer per object. Notice how we have two new members we are updating in our constant buffer, one is the diffuse color of the material, the other is a boolean value which says whether the material uses a texture or not. Then we set the constant buffer to the vertex shader AND the pixel shader (whereas before we set it only to the vertex shader). Next we check to make sure the material uses a texture. If it does, we bind the correct texture to the pixel shader. Then we set the sampler state. After that we set the render state (we are not using backface culling here, but we actually should be (more efficient)). Now we create two integers, one for the start index of the current subset, and the other for the amound of indices to draw. Finally we check to see if the subset is transparent or not. If it isn't, we draw the subset using the last two variable we set. //Draw our model's NON-transparent subsets for(int i = 0; i < meshSubsets; ++i) { //Set the grounds index buffer d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0); //Set the grounds vertex buffer d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset ); //Set the WVP matrix and send it to the constant buffer in effect file WVP = meshWorld * camView * camProjection; cbPerObj.WVP = XMMatrixTranspose(WVP); cbPerObj.World = XMMatrixTranspose(meshWorld); cbPerObj.difColor = material[meshSubsetTexture[i]].difColor; cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture; d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 ); d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer ); d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer ); if(material[meshSubsetTexture[i]].hasTexture) d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] ); d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState ); d3d11DevCon->RSSetState(RSCullNone); int indexStart = meshSubsetIndexStart[i]; int indexDrawAmount = meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i]; if(!material[meshSubsetTexture[i]].transparent) d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 ); } ##Drawing the Transparent Subsets## Make sure you draw these AFTER the skybox, so that they are able to blend with the skybox. Most everything is the same here as drawing the subsets from above, with only a couple changes. First, we need to set the blend state to Transparency, so that our subsets will use blending since these ones are transparent. Then looking through this, everything else is the same except for when we are about to draw the subset. We check to make sure the subsets ARE transparent this time, instead of non-transparent like above. That's everything for the main code, now we need to move on to the couple changes in our effect file. //Draw our model's TRANSPARENT subsets now //Set our blend state d3d11DevCon->OMSetBlendState(Transparency, NULL, 0xffffffff); for(int i = 0; i < meshSubsets; ++i) { //Set the grounds index buffer d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0); //Set the grounds vertex buffer d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset ); //Set the WVP matrix and send it to the constant buffer in effect file WVP = meshWorld * camView * camProjection; cbPerObj.WVP = XMMatrixTranspose(WVP); cbPerObj.World = XMMatrixTranspose(meshWorld); cbPerObj.difColor = material[meshSubsetTexture[i]].difColor; cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture; d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 ); d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer ); d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer ); if(material[meshSubsetTexture[i]].hasTexture) d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] ); d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState ); d3d11DevCon->RSSetState(RSCullNone); int indexStart = meshSubsetIndexStart[i]; int indexDrawAmount = meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i]; if(material[meshSubsetTexture[i]].transparent) d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 ); } ##Effect File (cbPerObject)## We need to modify our cbPerObject constant buffer to include the diffuse color of our material, and the boolean value of whether or not our material uses a texture. cbuffer cbPerObject { float4x4 WVP; float4x4 World; float4 difColor; bool hasTexture; }; ##Effect File (Pixel Shader)## The very last thing that needs to be done is modify the PS. First you can see we set our diffuse color to the solid diffuse color in the cbPerObject buffer After that, we check to see if the material uses a texture or not. If it uses a texture, we overwrite the diffuse color with the color from the texture. And thats it!!!!! float4 PS(VS_OUTPUT input) : SV_TARGET { input.normal = normalize(input.normal); //Set diffuse color of material float4 diffuse = difColor; //If material has a diffuse texture map, set it now if(hasTexture == true) diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord ); float3 finalColor = float3(0.0f, 0.0f, 0.0f); ... } Whew! I feel like i'm out of breath... Well anyway, now you know how to load an obj model, and you can easily modify this to load other ascii formats as well. ##Exercise:## 1. Optimize the LoadObjModel() function, correct errors, and get back to me via the comment function below. 3 browny points awarded to whoever completes this exercise! 2. Create a mesh class which contains a method to load the model from a file, a method to draw the model, and all the appropriate members. 3. Modify the function to retriangulate "concave" faces correctly. 4. Send me a comment! Some Feedback! I always appreciate comments and will get back to you if you provide an email (of course email is optional, I don't want people thinking I'll use their email for a mailing list). Here's the final code: metalpanel.jpg: +[http://www.braynzarsoft.net/image/100055][Metal Panel Texture] spaceCompound.ob: # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware # File Created: 26.07.2011 13:47:43 mtllib spaceCompound.mtl # # object Window # v -8.6007 1.3993 10.0000 v -8.6007 8.6007 10.0000 v 8.6007 8.6007 10.0000 v 8.6007 1.3993 10.0000 # 4 vertices vn 0.0000 0.0000 -1.0000 # 1 vertex normals vt 0.0000 0.0000 0.0000 vt 0.0000 1.0000 0.0000 vt 1.0000 1.0000 0.0000 vt 1.0000 0.0000 0.0000 # 4 texture coords g Window usemtl Window f 1/1/1 2/2/1 3/3/1 f 3/3/1 4/4/1 1/1/1 # 2 faces # # object DoorWay1 # v 15.0000 7.6721 -3.5444 v 15.0000 7.6721 3.6507 v 10.0000 7.6721 3.5444 v 10.0000 7.6721 -3.6507 v 15.0000 0.0000 3.6507 v 10.0000 0.0000 3.5444 v 15.0000 0.0000 -3.5444 v 10.0000 0.0000 -3.6507 # 8 vertices vn 0.0000 -1.0000 -0.0000 vn 0.0213 0.0000 -0.9998 vn 0.0213 -0.0000 -0.9998 vn 0.0000 1.0000 -0.0000 vn -0.0213 -0.0000 0.9998 vn -0.0213 0.0000 0.9998 # 6 vertex normals vt 0.5000 0.2911 0.8836 vt 0.5000 1.7301 0.8836 vt 1.5000 1.7089 0.8836 vt 1.5000 0.2699 0.8836 vt 1.5000 1.7672 0.8651 vt 1.5000 0.2328 0.8651 vt 0.5000 0.2328 0.8544 vt 0.5000 1.7672 0.8544 vt 0.5000 1.7089 0.1164 vt 1.5000 1.7301 0.1164 vt 1.5000 0.2911 0.1164 vt 0.5000 0.2699 0.1164 vt 0.5000 0.2328 0.1456 vt 0.5000 1.7672 0.1456 vt 1.5000 1.7672 0.1349 vt 1.5000 0.2328 0.1349 # 16 texture coords g DoorWay1 usemtl MetalPanels f 5/5/2 6/6/2 7/7/2 f 7/7/2 8/8/2 5/5/2 f 6/9/3 9/10/3 10/11/3 f 10/11/4 7/12/4 6/9/4 f 10/13/5 9/14/5 11/15/5 f 11/15/5 12/16/5 10/13/5 f 11/17/6 5/18/6 8/19/6 f 8/19/7 12/20/7 11/17/7 # 8 faces # # object DoorWay3 # v -15.0000 7.6721 3.5444 v -15.0000 7.6721 -3.6507 v -10.0000 7.7028 -3.6317 v -10.0000 7.7028 3.5386 v -10.0000 0.0000 3.5386 v -15.0000 0.0000 3.5444 v -15.0000 0.0000 -3.6507 v -10.0000 0.0000 -3.6317 # 8 vertices vn 0.0061 -1.0000 -0.0000 vn -0.0012 0.0000 -1.0000 vn -0.0012 -0.0000 -1.0000 vn 0.0000 1.0000 -0.0000 vn -0.0038 -0.0000 1.0000 vn -0.0038 0.0000 1.0000 # 6 vertex normals vt 1.5000 1.7195 0.8821 vt 1.5000 0.2805 0.8821 vt 0.5000 0.2843 0.8851 vt 0.5000 1.7184 0.8851 vt 1.5000 1.7703 0.8592 vt 1.5000 0.2297 0.8592 vt 0.5000 0.2297 0.8598 vt 0.5000 1.7641 0.8598 vt 0.5000 0.2805 0.1149 vt 0.5000 1.7195 0.1149 vt 1.5000 1.7184 0.1149 vt 1.5000 0.2843 0.1149 vt 0.5000 0.2297 0.1421 vt 0.5000 1.7703 0.1421 vt 1.5000 1.7641 0.1402 vt 1.5000 0.2297 0.1402 # 16 texture coords g DoorWay3 usemtl MetalPanels f 13/21/8 14/22/8 15/23/8 f 15/23/8 16/24/8 13/21/8 f 16/25/9 17/26/9 18/27/9 f 18/27/10 13/28/10 16/25/10 f 19/29/11 18/30/11 17/31/11 f 17/31/11 20/32/11 19/29/11 f 20/33/12 15/34/12 14/35/12 f 14/35/13 19/36/13 20/33/13 # 8 faces # # object MainRoom # v 10.0000 10.0000 -10.0000 v 10.0000 10.0000 10.0000 v -10.0000 10.0000 10.0000 v -10.0000 10.0000 -10.0000 v 10.0000 0.0000 3.5444 v 10.0000 0.0000 10.0000 v 10.0000 7.6721 3.5444 v 3.6270 0.0000 -10.0000 v 10.0000 0.0000 -10.0000 v 3.6270 7.6842 -10.0000 v -10.0000 0.0000 -3.6317 v -10.0000 0.0000 -10.0000 v -10.0000 7.7028 -3.6317 v 10.0000 0.0000 -3.6507 v 10.0000 7.6721 -3.6507 v -3.5401 0.0000 -10.0000 v -3.5401 7.6842 -10.0000 v -10.0000 0.0000 10.0000 v -10.0000 0.0000 3.5386 v -10.0000 7.7028 3.5386 v -8.6007 1.3993 10.0000 v 8.6007 1.3993 10.0000 v 8.6007 8.6007 10.0000 v -8.6007 8.6007 10.0000 # 24 vertices vn 0.0000 -1.0000 -0.0000 vn -1.0000 -0.0000 0.0000 vn -1.0000 0.0000 0.0000 vn -1.0000 0.0000 -0.0000 vn 0.0000 0.0000 1.0000 vn -0.0000 0.0000 1.0000 vn 1.0000 0.0000 -0.0000 vn -1.0000 -0.0000 -0.0000 vn -0.0000 -0.0000 1.0000 vn 0.0000 -0.0000 -1.0000 vn 0.0000 0.0000 -1.0000 vn -0.0000 0.0000 -1.0000 vn 0.0000 1.0000 -0.0000 # 13 vertex normals vt -1.0000 -1.0000 1.0000 vt -1.0000 3.0000 1.0000 vt 3.0000 3.0000 1.0000 vt 3.0000 -1.0000 1.0000 vt 0.2911 0.0000 -0.5000 vt -1.0000 0.0000 -0.5000 vt -1.0000 2.0000 -0.5000 vt 0.2911 1.5344 -0.5000 vt 0.2746 0.0000 -0.5000 vt 0.2746 1.5368 -0.5000 vt 0.2737 0.0000 1.5000 vt -1.0000 0.0000 1.5000 vt -1.0000 2.0000 1.5000 vt 0.2737 1.5406 1.5000 vt 3.0000 0.0000 -0.5000 vt 1.7301 0.0000 -0.5000 vt 1.7301 1.5344 -0.5000 vt 3.0000 2.0000 -0.5000 vt 1.7080 0.0000 -0.5000 vt 1.7080 1.5368 -0.5000 vt 3.0000 0.0000 1.5000 vt 1.7077 0.0000 1.5000 vt 1.7077 1.5406 1.5000 vt 3.0000 2.0000 1.5000 vt -0.7201 0.2799 1.5000 vt 2.7201 0.2799 1.5000 vt 2.7201 1.7201 1.5000 vt -0.7201 1.7201 1.5000 vt -1.0000 3.0000 0.0000 vt 3.0000 3.0000 0.0000 vt 3.0000 1.7089 0.0000 vt 3.0000 0.2699 0.0000 vt 3.0000 -1.0000 0.0000 vt 1.7254 -1.0000 0.0000 vt 0.2920 -1.0000 0.0000 vt -1.0000 -1.0000 0.0000 vt -1.0000 0.2737 0.0000 vt -1.0000 1.7077 0.0000 # 38 texture coords g MainRoom usemtl MetalPanels f 21/37/14 22/38/14 23/39/14 f 23/39/14 24/40/14 21/37/14 f 25/41/15 26/42/16 22/43/17 f 22/43/17 27/44/17 25/41/15 f 28/45/18 29/42/18 21/43/19 f 21/43/19 30/46/19 28/45/18 f 31/47/20 32/48/20 24/49/20 f 24/49/20 33/50/20 31/47/20 f 29/51/15 34/52/15 35/53/21 f 35/53/21 21/54/21 29/51/15 f 21/54/21 35/53/21 27/44/17 f 27/44/17 22/43/17 21/54/21 f 32/51/18 36/55/18 37/56/22 f 37/56/22 24/54/22 32/51/18 f 24/54/22 37/56/22 30/46/19 f 30/46/19 21/43/19 24/54/22 f 38/57/20 39/58/20 40/59/20 f 40/59/20 23/60/20 38/57/20 f 23/60/20 40/59/20 33/50/20 f 33/50/20 24/49/20 23/60/20 f 38/48/23 41/61/23 42/62/23 f 42/62/24 26/57/24 38/48/24 f 26/57/25 42/62/25 43/63/25 f 43/63/25 22/60/25 26/57/25 f 22/60/23 43/63/23 44/64/23 f 44/64/24 23/49/24 22/60/24 f 23/49/25 44/64/25 41/61/25 f 41/61/25 38/48/25 23/49/25 f 38/65/26 26/66/26 25/67/26 f 38/65/26 25/67/26 34/68/26 f 38/65/26 34/68/26 29/69/26 f 38/65/26 29/69/26 28/70/26 f 38/65/26 28/70/26 36/71/26 f 36/71/26 32/72/26 31/73/26 f 36/71/26 31/73/26 39/74/26 f 38/65/26 36/71/26 39/74/26 # 36 faces # # object Room1 # v -10.0000 0.0000 -35.0000 v -10.0000 0.0000 -15.0000 v -3.5444 0.0000 -15.0000 v 3.6507 0.0000 -15.0000 v 10.0000 0.0000 -15.0000 v 10.0000 0.0000 -35.0000 v 10.0000 10.0000 -15.0000 v -10.0000 10.0000 -15.0000 v -10.0000 10.0000 -35.0000 v 10.0000 10.0000 -35.0000 v -3.5444 7.7103 -15.0080 v 3.6507 7.7103 -15.0080 # 12 vertices vn 0.0000 1.0000 -0.0000 vn 0.0000 -1.0000 -0.0000 vn 1.0000 0.0000 0.0000 vn -1.0000 0.0000 -0.0000 vn -0.0000 0.0000 1.0000 vn -0.0007 0.0003 -1.0000 vn -0.0007 0.0017 -1.0000 vn -0.0006 -0.0004 -1.0000 vn -0.0000 0.0000 -1.0000 vn 0.0003 0.0019 -1.0000 vn 0.0010 0.0008 -1.0000 vn 0.0006 -0.0006 -1.0000 vn -0.0000 -0.0010 -1.0000 # 13 vertex normals vt -6.0000 -6.0000 0.5000 vt -6.0000 -2.0000 0.5000 vt -4.7089 -2.0000 0.5000 vt -3.2699 -2.0000 0.5000 vt -2.0000 -2.0000 0.5000 vt -2.0000 -6.0000 0.5000 vt 4.0000 -2.0000 1.5000 vt 8.0000 -2.0000 1.5000 vt 8.0000 -6.0000 1.5000 vt 4.0000 -6.0000 1.5000 vt -2.0000 3.0000 4.0000 vt -2.0000 1.0000 4.0000 vt -6.0000 1.0000 4.0000 vt -6.0000 3.0000 4.0000 vt 8.0000 3.0000 2.0000 vt 8.0000 1.0000 2.0000 vt 4.0000 1.0000 2.0000 vt 4.0000 3.0000 2.0000 vt 8.0000 3.0000 -3.0000 vt 8.0000 1.0000 -3.0000 vt 4.0000 1.0000 -3.0000 vt 4.0000 3.0000 -3.0000 vt -6.0000 3.0000 -1.0000 vt -4.7089 2.5421 -1.0008 vt -4.7089 1.0000 -1.0000 vt -6.0000 1.0000 -1.0000 vt -3.2699 2.5421 -1.0008 vt -2.0000 3.0000 -1.0000 vt -2.0000 1.0000 -1.0000 vt -3.2699 1.0000 -1.0000 # 30 texture coords g Room1 usemtl MetalPanels f 45/75/27 46/76/27 47/77/27 f 45/75/27 47/77/27 48/78/27 f 45/75/27 48/78/27 49/79/27 f 45/75/27 49/79/27 50/80/27 f 51/81/28 52/82/28 53/83/28 f 53/83/28 54/84/28 51/81/28 f 52/85/29 46/86/29 45/87/29 f 45/87/29 53/88/29 52/85/29 f 54/89/30 50/90/30 49/91/30 f 49/91/30 51/92/30 54/89/30 f 53/93/31 45/94/31 50/95/31 f 50/95/31 54/96/31 53/93/31 f 52/97/32 55/98/33 47/99/34 f 47/99/34 46/100/35 52/97/32 f 56/101/36 51/102/37 49/103/38 f 49/103/38 48/104/39 56/101/36 f 55/98/33 52/97/32 51/102/37 f 51/102/37 56/101/36 55/98/33 # 18 faces # # object Room2 # v -35.0000 0.0000 10.0000 v -15.0000 0.0000 10.0000 v -15.0000 0.0000 3.5444 v -15.0000 0.0000 -3.6507 v -15.0000 0.0000 -10.0000 v -35.0000 0.0000 -10.0000 v -35.0000 10.0000 10.0000 v -35.0000 10.0000 -10.0000 v -15.0000 10.0000 -10.0000 v -15.0000 10.0000 10.0000 v -15.0000 7.6721 3.5444 v -15.0000 7.6721 -3.6507 # 12 vertices vn 0.0000 1.0000 -0.0000 vn 0.0000 -1.0000 -0.0000 vn 0.0000 0.0000 -1.0000 vn -1.0000 0.0000 0.0000 vn -0.0000 0.0000 1.0000 vn 1.0000 0.0000 -0.0000 # 6 vertex normals vt -1.0000 3.0000 0.0000 vt 3.0000 3.0000 0.0000 vt 3.0000 1.7089 0.0000 vt 3.0000 0.2699 0.0000 vt 3.0000 -1.0000 0.0000 vt -1.0000 -1.0000 0.0000 vt 3.0000 3.0000 1.0000 vt 3.0000 -1.0000 1.0000 vt -1.0000 -1.0000 1.0000 vt -1.0000 3.0000 1.0000 vt -1.0000 0.0000 1.5000 vt -1.0000 2.0000 1.5000 vt 3.0000 2.0000 1.5000 vt 3.0000 0.0000 1.5000 vt 0.2911 0.0000 -0.5000 vt -1.0000 0.0000 -0.5000 vt -1.0000 2.0000 -0.5000 vt 0.2911 1.5344 -0.5000 vt 3.0000 2.0000 -0.5000 vt 3.0000 0.0000 -0.5000 vt 1.7301 0.0000 -0.5000 vt 1.7301 1.5344 -0.5000 # 22 texture coords g Room2 usemtl MetalPanels f 57/105/40 58/106/40 59/107/40 f 57/105/40 59/107/40 60/108/40 f 57/105/40 60/108/40 61/109/40 f 57/105/40 61/109/40 62/110/40 f 63/111/41 64/112/41 65/113/41 f 65/113/41 66/114/41 63/111/41 f 57/115/42 63/116/42 66/117/42 f 66/117/42 58/118/42 57/115/42 f 59/119/43 58/120/43 66/121/43 f 66/121/43 67/122/43 59/119/43 f 61/120/44 65/121/44 64/123/44 f 64/123/44 62/124/44 61/120/44 f 62/115/45 64/116/45 63/117/45 f 63/117/45 57/118/45 62/115/45 f 61/124/43 60/125/43 68/126/43 f 68/126/43 65/123/43 61/124/43 f 65/123/43 68/126/43 67/122/43 f 67/122/43 66/121/43 65/123/43 # 18 faces # # object Room3 # v 35.0000 0.0000 -10.0000 v 15.0000 0.0000 -10.0000 v 15.0000 0.0000 -3.5444 v 15.0000 0.0000 3.6507 v 15.0000 0.0000 10.0000 v 35.0000 0.0000 10.0000 v 35.0000 10.0000 -10.0000 v 35.0000 10.0000 10.0000 v 15.0000 10.0000 10.0000 v 15.0000 10.0000 -10.0000 v 15.0000 7.6721 -3.5444 v 15.0000 7.6721 3.6507 # 12 vertices vn 0.0000 1.0000 -0.0000 vn 0.0000 -1.0000 -0.0000 vn 0.0000 0.0000 1.0000 vn 1.0000 0.0000 -0.0000 vn -0.0000 0.0000 -1.0000 vn -1.0000 0.0000 0.0000 # 6 vertex normals vt 3.0000 -1.0000 0.0000 vt -1.0000 -1.0000 0.0000 vt -1.0000 0.2911 0.0000 vt -1.0000 1.7301 0.0000 vt -1.0000 3.0000 0.0000 vt 3.0000 3.0000 0.0000 vt -1.0000 -1.0000 1.0000 vt -1.0000 3.0000 1.0000 vt 3.0000 3.0000 1.0000 vt 3.0000 -1.0000 1.0000 vt -1.0000 0.0000 -0.5000 vt -1.0000 2.0000 -0.5000 vt 3.0000 2.0000 -0.5000 vt 3.0000 0.0000 -0.5000 vt 0.2911 0.0000 1.5000 vt -1.0000 0.0000 1.5000 vt -1.0000 2.0000 1.5000 vt 0.2911 1.5344 1.5000 vt 3.0000 2.0000 1.5000 vt 3.0000 0.0000 1.5000 vt 1.7301 0.0000 1.5000 vt 1.7301 1.5344 1.5000 # 22 texture coords g Room3 usemtl MetalPanels f 69/127/46 70/128/46 71/129/46 f 69/127/46 71/129/46 72/130/46 f 69/127/46 72/130/46 73/131/46 f 69/127/46 73/131/46 74/132/46 f 75/133/47 76/134/47 77/135/47 f 77/135/47 78/136/47 75/133/47 f 69/137/48 75/138/48 78/139/48 f 78/139/48 70/140/48 69/137/48 f 71/141/49 70/142/49 78/143/49 f 78/143/49 79/144/49 71/141/49 f 73/142/50 77/143/50 76/145/50 f 76/145/50 74/146/50 73/142/50 f 74/137/51 76/138/51 75/139/51 f 75/139/51 69/140/51 74/137/51 f 73/146/49 72/147/49 80/148/49 f 80/148/49 77/145/49 73/146/49 f 77/145/49 80/148/49 79/144/49 f 79/144/49 78/143/49 77/145/49 # 18 faces # # object DoorWay2 # v 3.6270 7.6842 -10.0000 v -3.5401 7.6842 -10.0000 v -3.5444 7.7103 -15.0080 v 3.6507 7.7103 -15.0080 v -3.5401 0.0000 -10.0000 v -3.5444 0.0000 -15.0000 v 3.6507 0.0000 -15.0000 v 3.6270 0.0000 -10.0000 # 8 vertices vn 0.0000 -1.0000 -0.0052 vn 1.0000 0.0000 -0.0009 vn 1.0000 -0.0000 -0.0009 vn 0.0000 1.0000 -0.0000 vn -1.0000 0.0000 -0.0047 vn -1.0000 -0.0000 -0.0047 # 6 vertex normals vt 0.2852 1.5008 0.8829 vt 1.7187 1.5008 0.8829 vt 1.7195 0.4992 0.8855 vt 0.2805 0.4992 0.8855 vt 1.5008 1.7658 0.8593 vt 1.5008 0.2290 0.8593 vt 0.5008 0.2290 0.8598 vt 0.4992 1.7710 0.8598 vt 1.7195 0.5008 0.1145 vt 0.2805 0.5008 0.1145 vt 0.2813 1.5008 0.1145 vt 1.7148 1.5008 0.1145 vt 0.4992 0.2290 0.1426 vt 0.4992 1.7658 0.1426 vt 1.5008 1.7710 0.1402 vt 1.4992 0.2290 0.1402 # 16 texture coords g DoorWay2 usemtl MetalPanels f 81/149/52 82/150/52 83/151/52 f 83/151/52 84/152/52 81/149/52 f 82/153/53 85/154/53 86/155/53 f 86/155/54 83/156/54 82/153/54 f 87/157/55 86/158/55 85/159/55 f 85/159/55 88/160/55 87/157/55 f 88/161/56 81/162/56 84/163/56 f 84/163/57 87/164/57 88/161/57 # 8 faces spaceCompound.mtl: # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware # File Created: 26.07.2011 13:47:43 newmtl Window Ns 32 Ni 1.5000 d 0.8000 Tr 0.2000 Tf 1.0000 1.0000 1.0000 illum 2 Ka 0.0000 0.0000 0.0100 Kd 0.0000 0.0000 0.0100 Ks 0.3500 0.3500 0.3500 Ke 0.0000 0.0000 0.0000 newmtl MetalPanels Ns 10.0000 d 1.0000 Tr 0.0000 Tf 1.0000 1.0000 1.0000 illum 2 Ka 0.5882 0.5882 0.5882 Kd 0.5882 0.5882 0.5882 Ks 0.0000 0.0000 0.0000 Ke 0.0000 0.0000 0.0000 map_Ka metalpanel.jpg map_Kd metalpanel.jpg map_bump metalpanelnormals.jpg main.cpp: //Include and link appropriate libraries and headers// #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d3dx11.lib") #pragma comment(lib, "d3dx10.lib") #pragma comment (lib, "D3D10_1.lib") #pragma comment (lib, "DXGI.lib") #pragma comment (lib, "D2D1.lib") #pragma comment (lib, "dwrite.lib") #pragma comment (lib, "dinput8.lib") #pragma comment (lib, "dxguid.lib") #include <windows.h> #include <d3d11.h> #include <d3dx11.h> #include <d3dx10.h> #include <xnamath.h> #include <D3D10_1.h> #include <DXGI.h> #include <D2D1.h> #include <sstream> #include <dwrite.h> #include <dinput.h> ///////////////**************new**************//////////////////// #include <vector> #include <fstream> #include <istream> ///////////////**************new**************//////////////////// //Global Declarations - Interfaces// IDXGISwapChain* SwapChain; ID3D11Device* d3d11Device; ID3D11DeviceContext* d3d11DevCon; ID3D11RenderTargetView* renderTargetView; ID3D11Buffer* squareIndexBuffer; ID3D11DepthStencilView* depthStencilView; ID3D11Texture2D* depthStencilBuffer; ID3D11Buffer* squareVertBuffer; ID3D11VertexShader* VS; ID3D11PixelShader* PS; ID3D11PixelShader* D2D_PS; ID3D10Blob* D2D_PS_Buffer; ID3D10Blob* VS_Buffer; ID3D10Blob* PS_Buffer; ID3D11InputLayout* vertLayout; ID3D11Buffer* cbPerObjectBuffer; ID3D11BlendState* d2dTransparency; ID3D11RasterizerState* CCWcullMode; ID3D11RasterizerState* CWcullMode; ID3D11ShaderResourceView* CubesTexture; ID3D11SamplerState* CubesTexSamplerState; ID3D11Buffer* cbPerFrameBuffer; ID3D10Device1 *d3d101Device; IDXGIKeyedMutex *keyedMutex11; IDXGIKeyedMutex *keyedMutex10; ID2D1RenderTarget *D2DRenderTarget; ID2D1SolidColorBrush *Brush; ID3D11Texture2D *BackBuffer11; ID3D11Texture2D *sharedTex11; ID3D11Buffer *d2dVertBuffer; ID3D11Buffer *d2dIndexBuffer; ID3D11ShaderResourceView *d2dTexture; IDWriteFactory *DWriteFactory; IDWriteTextFormat *TextFormat; IDirectInputDevice8* DIKeyboard; IDirectInputDevice8* DIMouse; ID3D11Buffer* sphereIndexBuffer; ID3D11Buffer* sphereVertBuffer; ID3D11VertexShader* SKYMAP_VS; ID3D11PixelShader* SKYMAP_PS; ID3D10Blob* SKYMAP_VS_Buffer; ID3D10Blob* SKYMAP_PS_Buffer; ID3D11ShaderResourceView* smrv; ID3D11DepthStencilState* DSLessEqual; ID3D11RasterizerState* RSCullNone; ///////////////**************new**************//////////////////// ID3D11BlendState* Transparency; //Mesh variables. Each loaded mesh will need its own set of these ID3D11Buffer* meshVertBuff; ID3D11Buffer* meshIndexBuff; XMMATRIX meshWorld; int meshSubsets = 0; std::vector<int> meshSubsetIndexStart; std::vector<int> meshSubsetTexture; //Textures and material variables, used for all mesh's loaded std::vector<ID3D11ShaderResourceView*> meshSRV; std::vector<std::wstring> textureNameArray; ///////////////**************new**************//////////////////// std::wstring printText; //Global Declarations - Others// LPCTSTR WndClassName = L"firstwindow"; HWND hwnd = NULL; HRESULT hr; int Width = 1920; int Height = 1200; DIMOUSESTATE mouseLastState; LPDIRECTINPUT8 DirectInput; float rotx = 0; float rotz = 0; float scaleX = 1.0f; float scaleY = 1.0f; XMMATRIX Rotationx; XMMATRIX Rotationz; XMMATRIX Rotationy; XMMATRIX WVP; XMMATRIX cube1World; XMMATRIX cube2World; XMMATRIX camView; XMMATRIX camProjection; XMMATRIX d2dWorld; XMVECTOR camPosition; XMVECTOR camTarget; XMVECTOR camUp; XMVECTOR DefaultForward = XMVectorSet(0.0f,0.0f,1.0f, 0.0f); XMVECTOR DefaultRight = XMVectorSet(1.0f,0.0f,0.0f, 0.0f); XMVECTOR camForward = XMVectorSet(0.0f,0.0f,1.0f, 0.0f); XMVECTOR camRight = XMVectorSet(1.0f,0.0f,0.0f, 0.0f); XMMATRIX camRotationMatrix; XMMATRIX groundWorld; float moveLeftRight = 0.0f; float moveBackForward = 0.0f; float camYaw = 0.0f; float camPitch = 0.0f; int NumSphereVertices; int NumSphereFaces; XMMATRIX sphereWorld; XMMATRIX Rotation; XMMATRIX Scale; XMMATRIX Translation; float rot = 0.01f; double countsPerSecond = 0.0; __int64 CounterStart = 0; int frameCount = 0; int fps = 0; __int64 frameTimeOld = 0; double frameTime; //Function Prototypes// bool InitializeDirect3d11App(HINSTANCE hInstance); void CleanUp(); bool InitScene(); void DrawScene(); bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter); void InitD2DScreenTexture(); void UpdateScene(double time); void UpdateCamera(); void RenderText(std::wstring text, int inInt); void StartTimer(); double GetTime(); double GetFrameTime(); bool InitializeWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed); int messageloop(); bool InitDirectInput(HINSTANCE hInstance); void DetectInput(double time); void CreateSphere(int LatLines, int LongLines); LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); //Create effects constant buffer's structure// struct cbPerObject { XMMATRIX WVP; XMMATRIX World; ///////////////**************new**************//////////////////// //These will be used for the pixel shader XMFLOAT4 difColor; bool hasTexture; ///////////////**************new**************//////////////////// }; cbPerObject cbPerObj; ///////////////**************new**************//////////////////// //Create material structure struct SurfaceMaterial { std::wstring matName; XMFLOAT4 difColor; int texArrayIndex; bool hasTexture; bool transparent; }; std::vector<SurfaceMaterial> material; //Define LoadObjModel function after we create surfaceMaterial structure bool LoadObjModel(std::wstring filename, //.obj filename ID3D11Buffer** vertBuff, //mesh vertex buffer ID3D11Buffer** indexBuff, //mesh index buffer std::vector<int>& subsetIndexStart, //start index of each subset std::vector<int>& subsetMaterialArray, //index value of material for each subset std::vector<SurfaceMaterial>& material, //vector of material structures int& subsetCount, //Number of subsets in mesh bool isRHCoordSys, //true if model was created in right hand coord system bool computeNormals); //true to compute the normals, false to use the files normals ///////////////**************new**************//////////////////// struct Light { Light() { ZeroMemory(this, sizeof(Light)); } XMFLOAT3 pos; float range; XMFLOAT3 dir; float cone; XMFLOAT3 att; float pad2; XMFLOAT4 ambient; XMFLOAT4 diffuse; }; Light light; struct cbPerFrame { Light light; }; cbPerFrame constbuffPerFrame; struct Vertex //Overloaded Vertex Structure { Vertex(){} Vertex(float x, float y, float z, float u, float v, float nx, float ny, float nz) : pos(x,y,z), texCoord(u, v), normal(nx, ny, nz){} XMFLOAT3 pos; XMFLOAT2 texCoord; XMFLOAT3 normal; }; D3D11_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0} }; UINT numElements = ARRAYSIZE(layout); int WINAPI WinMain(HINSTANCE hInstance, //Main windows function HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { if(!InitializeWindow(hInstance, nShowCmd, Width, Height, true)) { MessageBox(0, L"Window Initialization - Failed", L"Error", MB_OK); return 0; } if(!InitializeDirect3d11App(hInstance)) //Initialize Direct3D { MessageBox(0, L"Direct3D Initialization - Failed", L"Error", MB_OK); return 0; } if(!InitScene()) //Initialize our scene { MessageBox(0, L"Scene Initialization - Failed", L"Error", MB_OK); return 0; } if(!InitDirectInput(hInstance)) { MessageBox(0, L"Direct Input Initialization - Failed", L"Error", MB_OK); return 0; } messageloop(); CleanUp(); return 0; } bool InitializeWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed) { typedef struct _WNDCLASS { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS; WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = NULL; wc.cbWndExtra = NULL; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = NULL; wc.lpszClassName = WndClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if (!RegisterClassEx(&wc)) { MessageBox(NULL, L"Error registering class", L"Error", MB_OK | MB_ICONERROR); return 1; } hwnd = CreateWindowEx( NULL, WndClassName, L"Lesson 4 - Begin Drawing", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, NULL ); if (!hwnd) { MessageBox(NULL, L"Error creating window", L"Error", MB_OK | MB_ICONERROR); return 1; } ShowWindow(hwnd, ShowWnd); UpdateWindow(hwnd); return true; } bool InitializeDirect3d11App(HINSTANCE hInstance) { //Describe our SwapChain Buffer DXGI_MODE_DESC bufferDesc; ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC)); bufferDesc.Width = Width; bufferDesc.Height = Height; bufferDesc.RefreshRate.Numerator = 60; bufferDesc.RefreshRate.Denominator = 1; bufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; //Describe our SwapChain DXGI_SWAP_CHAIN_DESC swapChainDesc; ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC)); swapChainDesc.BufferDesc = bufferDesc; swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.BufferCount = 1; swapChainDesc.OutputWindow = hwnd; ///////////////**************new**************//////////////////// swapChainDesc.Windowed = true; ///////////////**************new**************//////////////////// swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // Create DXGI factory to enumerate adapters/////////////////////////////////////////////////////////////////////////// IDXGIFactory1 *DXGIFactory; HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&DXGIFactory); // Use the first adapter IDXGIAdapter1 *Adapter; hr = DXGIFactory->EnumAdapters1(0, &Adapter); DXGIFactory->Release(); //Create our Direct3D 11 Device and SwapChain////////////////////////////////////////////////////////////////////////// hr = D3D11CreateDeviceAndSwapChain(Adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, NULL, NULL, D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &d3d11Device, NULL, &d3d11DevCon); //Initialize Direct2D, Direct3D 10.1, DirectWrite InitD2D_D3D101_DWrite(Adapter); //Release the Adapter interface Adapter->Release(); //Create our BackBuffer and Render Target hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (void**)&BackBuffer11 ); hr = d3d11Device->CreateRenderTargetView( BackBuffer11, NULL, &renderTargetView ); //Describe our Depth/Stencil Buffer D3D11_TEXTURE2D_DESC depthStencilDesc; depthStencilDesc.Width = Width; depthStencilDesc.Height = Height; depthStencilDesc.MipLevels = 1; depthStencilDesc.ArraySize = 1; depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthStencilDesc.SampleDesc.Count = 1; depthStencilDesc.SampleDesc.Quality = 0; depthStencilDesc.Usage = D3D11_USAGE_DEFAULT; depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; depthStencilDesc.CPUAccessFlags = 0; depthStencilDesc.MiscFlags = 0; //Create the Depth/Stencil View d3d11Device->CreateTexture2D(&depthStencilDesc, NULL, &depthStencilBuffer); d3d11Device->CreateDepthStencilView(depthStencilBuffer, NULL, &depthStencilView); return true; } bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter) { //Create our Direc3D 10.1 Device/////////////////////////////////////////////////////////////////////////////////////// hr = D3D10CreateDevice1(Adapter, D3D10_DRIVER_TYPE_HARDWARE, NULL,D3D10_CREATE_DEVICE_BGRA_SUPPORT, D3D10_FEATURE_LEVEL_9_3, D3D10_1_SDK_VERSION, &d3d101Device ); //Create Shared Texture that Direct3D 10.1 will render on////////////////////////////////////////////////////////////// D3D11_TEXTURE2D_DESC sharedTexDesc; ZeroMemory(&sharedTexDesc, sizeof(sharedTexDesc)); sharedTexDesc.Width = Width; sharedTexDesc.Height = Height; sharedTexDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; sharedTexDesc.MipLevels = 1; sharedTexDesc.ArraySize = 1; sharedTexDesc.SampleDesc.Count = 1; sharedTexDesc.Usage = D3D11_USAGE_DEFAULT; sharedTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; sharedTexDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; hr = d3d11Device->CreateTexture2D(&sharedTexDesc, NULL, &sharedTex11); // Get the keyed mutex for the shared texture (for D3D11)/////////////////////////////////////////////////////////////// hr = sharedTex11->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex11); // Get the shared handle needed to open the shared texture in D3D10.1/////////////////////////////////////////////////// IDXGIResource *sharedResource10; HANDLE sharedHandle10; hr = sharedTex11->QueryInterface(__uuidof(IDXGIResource), (void**)&sharedResource10); hr = sharedResource10->GetSharedHandle(&sharedHandle10); sharedResource10->Release(); // Open the surface for the shared texture in D3D10.1/////////////////////////////////////////////////////////////////// IDXGISurface1 *sharedSurface10; hr = d3d101Device->OpenSharedResource(sharedHandle10, __uuidof(IDXGISurface1), (void**)(&sharedSurface10)); hr = sharedSurface10->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex10); // Create D2D factory/////////////////////////////////////////////////////////////////////////////////////////////////// ID2D1Factory *D2DFactory; hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), (void**)&D2DFactory); D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties; ZeroMemory(&renderTargetProperties, sizeof(renderTargetProperties)); renderTargetProperties.type = D2D1_RENDER_TARGET_TYPE_HARDWARE; renderTargetProperties.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED); hr = D2DFactory->CreateDxgiSurfaceRenderTarget(sharedSurface10, &renderTargetProperties, &D2DRenderTarget); sharedSurface10->Release(); D2DFactory->Release(); // Create a solid color brush to draw something with hr = D2DRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), &Brush); //DirectWrite/////////////////////////////////////////////////////////////////////////////////////////////////////////// hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&DWriteFactory)); hr = DWriteFactory->CreateTextFormat( L"Script", NULL, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 24.0f, L"en-us", &TextFormat ); hr = TextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); hr = TextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR); d3d101Device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST); return true; } bool InitDirectInput(HINSTANCE hInstance) { hr = DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&DirectInput, NULL); hr = DirectInput->CreateDevice(GUID_SysKeyboard, &DIKeyboard, NULL); hr = DirectInput->CreateDevice(GUID_SysMouse, &DIMouse, NULL); hr = DIKeyboard->SetDataFormat(&c_dfDIKeyboard); hr = DIKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); hr = DIMouse->SetDataFormat(&c_dfDIMouse); hr = DIMouse->SetCooperativeLevel(hwnd, DISCL_EXCLUSIVE | DISCL_NOWINKEY | DISCL_FOREGROUND); return true; } void UpdateCamera() { camRotationMatrix = XMMatrixRotationRollPitchYaw(camPitch, camYaw, 0); camTarget = XMVector3TransformCoord(DefaultForward, camRotationMatrix ); camTarget = XMVector3Normalize(camTarget); XMMATRIX RotateYTempMatrix; RotateYTempMatrix = XMMatrixRotationY(camYaw); //walk camRight = XMVector3TransformCoord(DefaultRight, RotateYTempMatrix); camUp = XMVector3TransformCoord(camUp, RotateYTempMatrix); camForward = XMVector3TransformCoord(DefaultForward, RotateYTempMatrix); camPosition += moveLeftRight*camRight; camPosition += moveBackForward*camForward; moveLeftRight = 0.0f; moveBackForward = 0.0f; camTarget = camPosition + camTarget; camView = XMMatrixLookAtLH( camPosition, camTarget, camUp ); } void DetectInput(double time) { DIMOUSESTATE mouseCurrState; BYTE keyboardState[256]; DIKeyboard->Acquire(); DIMouse->Acquire(); DIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &mouseCurrState); DIKeyboard->GetDeviceState(sizeof(keyboardState),(LPVOID)&keyboardState); if(keyboardState[DIK_ESCAPE] & 0x80) PostMessage(hwnd, WM_DESTROY, 0, 0); float speed = 15.0f * time; if(keyboardState[DIK_A] & 0x80) { moveLeftRight -= speed; } if(keyboardState[DIK_D] & 0x80) { moveLeftRight += speed; } if(keyboardState[DIK_W] & 0x80) { moveBackForward += speed; } if(keyboardState[DIK_S] & 0x80) { moveBackForward -= speed; } if((mouseCurrState.lX != mouseLastState.lX) || (mouseCurrState.lY != mouseLastState.lY)) { camYaw += mouseLastState.lX * 0.001f; camPitch += mouseCurrState.lY * 0.001f; mouseLastState = mouseCurrState; } UpdateCamera(); return; } void CleanUp() { SwapChain->SetFullscreenState(false, NULL); PostMessage(hwnd, WM_DESTROY, 0, 0); //Release the COM Objects we created SwapChain->Release(); d3d11Device->Release(); d3d11DevCon->Release(); renderTargetView->Release(); squareVertBuffer->Release(); squareIndexBuffer->Release(); VS->Release(); PS->Release(); VS_Buffer->Release(); PS_Buffer->Release(); vertLayout->Release(); depthStencilView->Release(); depthStencilBuffer->Release(); cbPerObjectBuffer->Release(); Transparency->Release(); CCWcullMode->Release(); CWcullMode->Release(); d3d101Device->Release(); keyedMutex11->Release(); keyedMutex10->Release(); D2DRenderTarget->Release(); Brush->Release(); BackBuffer11->Release(); sharedTex11->Release(); DWriteFactory->Release(); TextFormat->Release(); d2dTexture->Release(); cbPerFrameBuffer->Release(); DIKeyboard->Unacquire(); DIMouse->Unacquire(); DirectInput->Release(); sphereIndexBuffer->Release(); sphereVertBuffer->Release(); SKYMAP_VS->Release(); SKYMAP_PS->Release(); SKYMAP_VS_Buffer->Release(); SKYMAP_PS_Buffer->Release(); smrv->Release(); DSLessEqual->Release(); RSCullNone->Release(); ///////////////**************new**************//////////////////// meshVertBuff->Release(); meshIndexBuff->Release(); ///////////////**************new**************//////////////////// } ///////////////**************new**************//////////////////// bool LoadObjModel(std::wstring filename, ID3D11Buffer** vertBuff, ID3D11Buffer** indexBuff, std::vector<int>& subsetIndexStart, std::vector<int>& subsetMaterialArray, std::vector<SurfaceMaterial>& material, int& subsetCount, bool isRHCoordSys, bool computeNormals) { HRESULT hr = 0; std::wifstream fileIn (filename.c_str()); //Open file std::wstring meshMatLib; //String to hold our obj material library filename //Arrays to store our model's information std::vector<DWORD> indices; std::vector<XMFLOAT3> vertPos; std::vector<XMFLOAT3> vertNorm; std::vector<XMFLOAT2> vertTexCoord; std::vector<std::wstring> meshMaterials; //Vertex definition indices std::vector<int> vertPosIndex; std::vector<int> vertNormIndex; std::vector<int> vertTCIndex; //Make sure we have a default if no tex coords or normals are defined bool hasTexCoord = false; bool hasNorm = false; //Temp variables to store into vectors std::wstring meshMaterialsTemp; int vertPosIndexTemp; int vertNormIndexTemp; int vertTCIndexTemp; wchar_t checkChar; //The variable we will use to store one char from file at a time std::wstring face; //Holds the string containing our face vertices int vIndex = 0; //Keep track of our vertex index count int triangleCount = 0; //Total Triangles int totalVerts = 0; int meshTriangles = 0; //Check to see if the file was opened if (fileIn) { while(fileIn) { checkChar = fileIn.get(); //Get next char switch (checkChar) { case '#': checkChar = fileIn.get(); while(checkChar != '\n') checkChar = fileIn.get(); break; case 'v': //Get Vertex Descriptions checkChar = fileIn.get(); if(checkChar == ' ') //v - vert position { float vz, vy, vx; fileIn >> vx >> vy >> vz; //Store the next three types if(isRHCoordSys) //If model is from an RH Coord System vertPos.push_back(XMFLOAT3( vx, vy, vz * -1.0f)); //Invert the Z axis else vertPos.push_back(XMFLOAT3( vx, vy, vz)); } if(checkChar == 't') //vt - vert tex coords { float vtcu, vtcv; fileIn >> vtcu >> vtcv; //Store next two types if(isRHCoordSys) //If model is from an RH Coord System vertTexCoord.push_back(XMFLOAT2(vtcu, 1.0f-vtcv)); //Reverse the "v" axis else vertTexCoord.push_back(XMFLOAT2(vtcu, vtcv)); hasTexCoord = true; //We know the model uses texture coords } //Since we compute the normals later, we don't need to check for normals //In the file, but i'll do it here anyway if(checkChar == 'n') //vn - vert normal { float vnx, vny, vnz; fileIn >> vnx >> vny >> vnz; //Store next three types if(isRHCoordSys) //If model is from an RH Coord System vertNorm.push_back(XMFLOAT3( vnx, vny, vnz * -1.0f )); //Invert the Z axis else vertNorm.push_back(XMFLOAT3( vnx, vny, vnz )); hasNorm = true; //We know the model defines normals } break; //New group (Subset) case 'g': //g - defines a group checkChar = fileIn.get(); if(checkChar == ' ') { subsetIndexStart.push_back(vIndex); //Start index for this subset subsetCount++; } break; //Get Face Index case 'f': //f - defines the faces checkChar = fileIn.get(); if(checkChar == ' ') { face = L""; std::wstring VertDef; //Holds one vertex definition at a time triangleCount = 0; checkChar = fileIn.get(); while(checkChar != '\n') { face += checkChar; //Add the char to our face string checkChar = fileIn.get(); //Get the next Character if(checkChar == ' ') //If its a space... triangleCount++; //Increase our triangle count } //Check for space at the end of our face string if(face[face.length()-1] == ' ') triangleCount--; //Each space adds to our triangle count triangleCount -= 1; //Ever vertex in the face AFTER the first two are new faces std::wstringstream ss(face); if(face.length() > 0) { int firstVIndex, lastVIndex; //Holds the first and last vertice's index for(int i = 0; i < 3; ++i) //First three vertices (first triangle) { ss >> VertDef; //Get vertex definition (vPos/vTexCoord/vNorm) std::wstring vertPart; int whichPart = 0; //(vPos, vTexCoord, or vNorm) //Parse this string for(int j = 0; j < VertDef.length(); ++j) { if(VertDef[j] != '/') //If there is no divider "/", add a char to our vertPart vertPart += VertDef[j]; //If the current char is a divider "/", or its the last character in the string if(VertDef[j] == '/' || j == VertDef.length()-1) { std::wistringstream wstringToInt(vertPart); //Used to convert wstring to int if(whichPart == 0) //If vPos { wstringToInt >> vertPosIndexTemp; vertPosIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 //Check to see if the vert pos was the only thing specified if(j == VertDef.length()-1) { vertNormIndexTemp = 0; vertTCIndexTemp = 0; } } else if(whichPart == 1) //If vTexCoord { if(vertPart != L"") //Check to see if there even is a tex coord { wstringToInt >> vertTCIndexTemp; vertTCIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 } else //If there is no tex coord, make a default vertTCIndexTemp = 0; //If the cur. char is the second to last in the string, then //there must be no normal, so set a default normal if(j == VertDef.length()-1) vertNormIndexTemp = 0; } else if(whichPart == 2) //If vNorm { std::wistringstream wstringToInt(vertPart); wstringToInt >> vertNormIndexTemp; vertNormIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1 } vertPart = L""; //Get ready for next vertex part whichPart++; //Move on to next vertex part } } //Check to make sure there is at least one subset if(subsetCount == 0) { subsetIndexStart.push_back(vIndex); //Start index for this subset subsetCount++; } //Avoid duplicate vertices bool vertAlreadyExists = false; if(totalVerts >= 3) //Make sure we at least have one triangle to check { //Loop through all the vertices for(int iCheck = 0; iCheck < totalVerts; ++iCheck) { //If the vertex position and texture coordinate in memory are the same //As the vertex position and texture coordinate we just now got out //of the obj file, we will set this faces vertex index to the vertex's //index value in memory. This makes sure we don't create duplicate vertices if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists) { if(vertTCIndexTemp == vertTCIndex[iCheck]) { indices.push_back(iCheck); //Set index for this vertex vertAlreadyExists = true; //If we've made it here, the vertex already exists } } } } //If this vertex is not already in our vertex arrays, put it there if(!vertAlreadyExists) { vertPosIndex.push_back(vertPosIndexTemp); vertTCIndex.push_back(vertTCIndexTemp); vertNormIndex.push_back(vertNormIndexTemp); totalVerts++; //We created a new vertex indices.push_back(totalVerts-1); //Set index for this vertex } //If this is the very first vertex in the face, we need to //make sure the rest of the triangles use this vertex if(i == 0) { firstVIndex = indices[vIndex]; //The first vertex index of this FACE } //If this was the last vertex in the first triangle, we will make sure //the next triangle uses this one (eg. tri1(1,2,3) tri2(1,3,4) tri3(1,4,5)) if(i == 2) { lastVIndex = indices[vIndex]; //The last vertex index of this TRIANGLE } vIndex++; //Increment index count } meshTriangles++; //One triangle down //If there are more than three vertices in the face definition, we need to make sure //we convert the face to triangles. We created our first triangle above, now we will //create a new triangle for every new vertex in the face, using the very first vertex //of the face, and the last vertex from the triangle before the current triangle for(int l = 0; l < triangleCount-1; ++l) //Loop through the next vertices to create new triangles { //First vertex of this triangle (the very first vertex of the face too) indices.push_back(firstVIndex); //Set index for this vertex vIndex++; //Second Vertex of this triangle (the last vertex used in the tri before this one) indices.push_back(lastVIndex); //Set index for this vertex vIndex++; //Get the third vertex for this triangle ss >> VertDef; std::wstring vertPart; int whichPart = 0; //Parse this string (same as above) for(int j = 0; j < VertDef.length(); ++j) { if(VertDef[j] != '/') vertPart += VertDef[j]; if(VertDef[j] == '/' || j == VertDef.length()-1) { std::wistringstream wstringToInt(vertPart); if(whichPart == 0) { wstringToInt >> vertPosIndexTemp; vertPosIndexTemp -= 1; //Check to see if the vert pos was the only thing specified if(j == VertDef.length()-1) { vertTCIndexTemp = 0; vertNormIndexTemp = 0; } } else if(whichPart == 1) { if(vertPart != L"") { wstringToInt >> vertTCIndexTemp; vertTCIndexTemp -= 1; } else vertTCIndexTemp = 0; if(j == VertDef.length()-1) vertNormIndexTemp = 0; } else if(whichPart == 2) { std::wistringstream wstringToInt(vertPart); wstringToInt >> vertNormIndexTemp; vertNormIndexTemp -= 1; } vertPart = L""; whichPart++; } } //Check for duplicate vertices bool vertAlreadyExists = false; if(totalVerts >= 3) //Make sure we at least have one triangle to check { for(int iCheck = 0; iCheck < totalVerts; ++iCheck) { if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists) { if(vertTCIndexTemp == vertTCIndex[iCheck]) { indices.push_back(iCheck); //Set index for this vertex vertAlreadyExists = true; //If we've made it here, the vertex already exists } } } } if(!vertAlreadyExists) { vertPosIndex.push_back(vertPosIndexTemp); vertTCIndex.push_back(vertTCIndexTemp); vertNormIndex.push_back(vertNormIndexTemp); totalVerts++; //New vertex created, add to total verts indices.push_back(totalVerts-1); //Set index for this vertex } //Set the second vertex for the next triangle to the last vertex we got lastVIndex = indices[vIndex]; //The last vertex index of this TRIANGLE meshTriangles++; //New triangle defined vIndex++; } } } break; case 'm': //mtllib - material library filename checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == 'i') { checkChar = fileIn.get(); if(checkChar == 'b') { checkChar = fileIn.get(); if(checkChar == ' ') { //Store the material libraries file name fileIn >> meshMatLib; } } } } } } break; case 'u': //usemtl - which material to use checkChar = fileIn.get(); if(checkChar == 's') { checkChar = fileIn.get(); if(checkChar == 'e') { checkChar = fileIn.get(); if(checkChar == 'm') { checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == ' ') { meshMaterialsTemp = L""; //Make sure this is cleared fileIn >> meshMaterialsTemp; //Get next type (string) meshMaterials.push_back(meshMaterialsTemp); } } } } } } break; default: break; } } } else //If we could not open the file { SwapChain->SetFullscreenState(false, NULL); //Make sure we are out of fullscreen //create message std::wstring message = L"Could not open: "; message += filename; MessageBox(0, message.c_str(), //display message L"Error", MB_OK); return false; } subsetIndexStart.push_back(vIndex); //There won't be another index start after our last subset, so set it here //sometimes "g" is defined at the very top of the file, then again before the first group of faces. //This makes sure the first subset does not conatain "0" indices. if(subsetIndexStart[1] == 0) { subsetIndexStart.erase(subsetIndexStart.begin()+1); meshSubsets--; } //Make sure we have a default for the tex coord and normal //if one or both are not specified if(!hasNorm) vertNorm.push_back(XMFLOAT3(0.0f, 0.0f, 0.0f)); if(!hasTexCoord) vertTexCoord.push_back(XMFLOAT2(0.0f, 0.0f)); //Close the obj file, and open the mtl file fileIn.close(); fileIn.open(meshMatLib.c_str()); std::wstring lastStringRead; int matCount = material.size(); //total materials //kdset - If our diffuse color was not set, we can use the ambient color (which is usually the same) //If the diffuse color WAS set, then we don't need to set our diffuse color to ambient bool kdset = false; if (fileIn) { while(fileIn) { checkChar = fileIn.get(); //Get next char switch (checkChar) { //Check for comment case '#': checkChar = fileIn.get(); while(checkChar != '\n') checkChar = fileIn.get(); break; //Set diffuse color case 'K': checkChar = fileIn.get(); if(checkChar == 'd') //Diffuse Color { checkChar = fileIn.get(); //remove space fileIn >> material[matCount-1].difColor.x; fileIn >> material[matCount-1].difColor.y; fileIn >> material[matCount-1].difColor.z; kdset = true; } //Ambient Color (We'll store it in diffuse if there isn't a diffuse already) if(checkChar == 'a') { checkChar = fileIn.get(); //remove space if(!kdset) { fileIn >> material[matCount-1].difColor.x; fileIn >> material[matCount-1].difColor.y; fileIn >> material[matCount-1].difColor.z; } } break; //Check for transparency case 'T': checkChar = fileIn.get(); if(checkChar == 'r') { checkChar = fileIn.get(); //remove space float Transparency; fileIn >> Transparency; material[matCount-1].difColor.w = Transparency; if(Transparency > 0.0f) material[matCount-1].transparent = true; } break; //Some obj files specify d for transparency case 'd': checkChar = fileIn.get(); if(checkChar == ' ') { float Transparency; fileIn >> Transparency; //'d' - 0 being most transparent, and 1 being opaque, opposite of Tr Transparency = 1.0f - Transparency; material[matCount-1].difColor.w = Transparency; if(Transparency > 0.0f) material[matCount-1].transparent = true; } break; //Get the diffuse map (texture) case 'm': checkChar = fileIn.get(); if(checkChar == 'a') { checkChar = fileIn.get(); if(checkChar == 'p') { checkChar = fileIn.get(); if(checkChar == '_') { //map_Kd - Diffuse map checkChar = fileIn.get(); if(checkChar == 'K') { checkChar = fileIn.get(); if(checkChar == 'd') { std::wstring fileNamePath; fileIn.get(); //Remove whitespace between map_Kd and file //Get the file path - We read the pathname char by char since //pathnames can sometimes contain spaces, so we will read until //we find the file extension bool texFilePathEnd = false; while(!texFilePathEnd) { checkChar = fileIn.get(); fileNamePath += checkChar; if(checkChar == '.') { for(int i = 0; i < 3; ++i) fileNamePath += fileIn.get(); texFilePathEnd = true; } } //check if this texture has already been loaded bool alreadyLoaded = false; for(int i = 0; i < textureNameArray.size(); ++i) { if(fileNamePath == textureNameArray[i]) { alreadyLoaded = true; material[matCount-1].texArrayIndex = i; material[matCount-1].hasTexture = true; } } //if the texture is not already loaded, load it now if(!alreadyLoaded) { ID3D11ShaderResourceView* tempMeshSRV; hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(), NULL, NULL, &tempMeshSRV, NULL ); if(SUCCEEDED(hr)) { textureNameArray.push_back(fileNamePath.c_str()); material[matCount-1].texArrayIndex = meshSRV.size(); meshSRV.push_back(tempMeshSRV); material[matCount-1].hasTexture = true; } } } } //map_d - alpha map else if(checkChar == 'd') { //Alpha maps are usually the same as the diffuse map //So we will assume that for now by only enabling //transparency for this material, as we will already //be using the alpha channel in the diffuse map material[matCount-1].transparent = true; } } } } break; case 'n': //newmtl - Declare new material checkChar = fileIn.get(); if(checkChar == 'e') { checkChar = fileIn.get(); if(checkChar == 'w') { checkChar = fileIn.get(); if(checkChar == 'm') { checkChar = fileIn.get(); if(checkChar == 't') { checkChar = fileIn.get(); if(checkChar == 'l') { checkChar = fileIn.get(); if(checkChar == ' ') { //New material, set its defaults SurfaceMaterial tempMat; material.push_back(tempMat); fileIn >> material[matCount].matName; material[matCount].transparent = false; material[matCount].hasTexture = false; material[matCount].texArrayIndex = 0; matCount++; kdset = false; } } } } } } break; default: break; } } } else { SwapChain->SetFullscreenState(false, NULL); //Make sure we are out of fullscreen std::wstring message = L"Could not open: "; message += meshMatLib; MessageBox(0, message.c_str(), L"Error", MB_OK); return false; } //Set the subsets material to the index value //of the its material in our material array for(int i = 0; i < meshSubsets; ++i) { bool hasMat = false; for(int j = 0; j < material.size(); ++j) { if(meshMaterials[i] == material[j].matName) { subsetMaterialArray.push_back(j); hasMat = true; } } if(!hasMat) subsetMaterialArray.push_back(0); //Use first material in array } std::vector<Vertex> vertices; Vertex tempVert; //Create our vertices using the information we got //from the file and store them in a vector for(int j = 0 ; j < totalVerts; ++j) { tempVert.pos = vertPos[vertPosIndex[j]]; tempVert.normal = vertNorm[vertNormIndex[j]]; tempVert.texCoord = vertTexCoord[vertTCIndex[j]]; vertices.push_back(tempVert); } //////////////////////Compute Normals/////////////////////////// //If computeNormals was set to true then we will create our own //normals, if it was set to false we will use the obj files normals if(computeNormals) { std::vector<XMFLOAT3> tempNormal; //normalized and unnormalized normals XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f); //Used to get vectors (sides) from the position of the verts float vecX, vecY, vecZ; //Two edges of our triangle XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //Compute face normals for(int i = 0; i < meshTriangles; ++i) { //Get the vector describing one edge of our triangle (edge 0,2) vecX = vertices[indices[(i*3)]].pos.x - vertices[indices[(i*3)+2]].pos.x; vecY = vertices[indices[(i*3)]].pos.y - vertices[indices[(i*3)+2]].pos.y; vecZ = vertices[indices[(i*3)]].pos.z - vertices[indices[(i*3)+2]].pos.z; edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our first edge //Get the vector describing another edge of our triangle (edge 2,1) vecX = vertices[indices[(i*3)+2]].pos.x - vertices[indices[(i*3)+1]].pos.x; vecY = vertices[indices[(i*3)+2]].pos.y - vertices[indices[(i*3)+1]].pos.y; vecZ = vertices[indices[(i*3)+2]].pos.z - vertices[indices[(i*3)+1]].pos.z; edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our second edge //Cross multiply the two edge vectors to get the un-normalized face normal XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2)); tempNormal.push_back(unnormalized); //Save unormalized normal (for normal averaging) } //Compute vertex normals (normal Averaging) XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); int facesUsing = 0; float tX; float tY; float tZ; //Go through each vertex for(int i = 0; i < totalVerts; ++i) { //Check which triangles use this vertex for(int j = 0; j < meshTriangles; ++j) { if(indices[j*3] == i || indices[(j*3)+1] == i || indices[(j*3)+2] == i) { tX = XMVectorGetX(normalSum) + tempNormal[j].x; tY = XMVectorGetY(normalSum) + tempNormal[j].y; tZ = XMVectorGetZ(normalSum) + tempNormal[j].z; normalSum = XMVectorSet(tX, tY, tZ, 0.0f); //If a face is using the vertex, add the unormalized face normal to the normalSum facesUsing++; } } //Get the actual normal by dividing the normalSum by the number of faces sharing the vertex normalSum = normalSum / facesUsing; //Normalize the normalSum vector normalSum = XMVector3Normalize(normalSum); //Store the normal in our current vertex vertices[i].normal.x = XMVectorGetX(normalSum); vertices[i].normal.y = XMVectorGetY(normalSum); vertices[i].normal.z = XMVectorGetZ(normalSum); //Clear normalSum and facesUsing for next vertex normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); facesUsing = 0; } } //Create index buffer D3D11_BUFFER_DESC indexBufferDesc; ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) ); indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(DWORD) * meshTriangles*3; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA iinitData; iinitData.pSysMem = &indices[0]; d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, indexBuff); //Create Vertex Buffer D3D11_BUFFER_DESC vertexBufferDesc; ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) ); vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof( Vertex ) * totalVerts; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA vertexBufferData; ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) ); vertexBufferData.pSysMem = &vertices[0]; hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, vertBuff); return true; } ///////////////**************new**************//////////////////// void CreateSphere(int LatLines, int LongLines) { NumSphereVertices = ((LatLines-2) * LongLines) + 2; NumSphereFaces = ((LatLines-3)*(LongLines)*2) + (LongLines*2); float sphereYaw = 0.0f; float spherePitch = 0.0f; std::vector<Vertex> vertices(NumSphereVertices); XMVECTOR currVertPos = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); vertices[0].pos.x = 0.0f; vertices[0].pos.y = 0.0f; vertices[0].pos.z = 1.0f; for(DWORD i = 0; i < LatLines-2; ++i) { spherePitch = (i+1) * (3.14f/(LatLines-1)); Rotationx = XMMatrixRotationX(spherePitch); for(DWORD j = 0; j < LongLines; ++j) { sphereYaw = j * (6.28f/(LongLines)); Rotationy = XMMatrixRotationZ(sphereYaw); currVertPos = XMVector3TransformNormal( XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), (Rotationx * Rotationy) ); currVertPos = XMVector3Normalize( currVertPos ); vertices[i*LongLines+j+1].pos.x = XMVectorGetX(currVertPos); vertices[i*LongLines+j+1].pos.y = XMVectorGetY(currVertPos); vertices[i*LongLines+j+1].pos.z = XMVectorGetZ(currVertPos); } } vertices[NumSphereVertices-1].pos.x = 0.0f; vertices[NumSphereVertices-1].pos.y = 0.0f; vertices[NumSphereVertices-1].pos.z = -1.0f; D3D11_BUFFER_DESC vertexBufferDesc; ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) ); vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof( Vertex ) * NumSphereVertices; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA vertexBufferData; ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) ); vertexBufferData.pSysMem = &vertices[0]; hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &sphereVertBuffer); std::vector<DWORD> indices(NumSphereFaces * 3); int k = 0; for(DWORD l = 0; l < LongLines-1; ++l) { indices[k] = 0; indices[k+1] = l+1; indices[k+2] = l+2; k += 3; } indices[k] = 0; indices[k+1] = LongLines; indices[k+2] = 1; k += 3; for(DWORD i = 0; i < LatLines-3; ++i) { for(DWORD j = 0; j < LongLines-1; ++j) { indices[k] = i*LongLines+j+1; indices[k+1] = i*LongLines+j+2; indices[k+2] = (i+1)*LongLines+j+1; indices[k+3] = (i+1)*LongLines+j+1; indices[k+4] = i*LongLines+j+2; indices[k+5] = (i+1)*LongLines+j+2; k += 6; // next quad } indices[k] = (i*LongLines)+LongLines; indices[k+1] = (i*LongLines)+1; indices[k+2] = ((i+1)*LongLines)+LongLines; indices[k+3] = ((i+1)*LongLines)+LongLines; indices[k+4] = (i*LongLines)+1; indices[k+5] = ((i+1)*LongLines)+1; k += 6; } for(DWORD l = 0; l < LongLines-1; ++l) { indices[k] = NumSphereVertices-1; indices[k+1] = (NumSphereVertices-1)-(l+1); indices[k+2] = (NumSphereVertices-1)-(l+2); k += 3; } indices[k] = NumSphereVertices-1; indices[k+1] = (NumSphereVertices-1)-LongLines; indices[k+2] = NumSphereVertices-2; D3D11_BUFFER_DESC indexBufferDesc; ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) ); indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(DWORD) * NumSphereFaces * 3; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA iinitData; iinitData.pSysMem = &indices[0]; d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &sphereIndexBuffer); } void InitD2DScreenTexture() { //Create the vertex buffer Vertex v[] = { // Front Face Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f,-1.0f, -1.0f, -1.0f), Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f,-1.0f, 1.0f, -1.0f), Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f), Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f), }; DWORD indices[] = { // Front Face 0, 1, 2, 0, 2, 3, }; D3D11_BUFFER_DESC indexBufferDesc; ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) ); indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(DWORD) * 2 * 3; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA iinitData; iinitData.pSysMem = indices; d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &d2dIndexBuffer); D3D11_BUFFER_DESC vertexBufferDesc; ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) ); vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 4; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA vertexBufferData; ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) ); vertexBufferData.pSysMem = v; hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &d2dVertBuffer); //Create A shader resource view from the texture D2D will render to, //So we can use it to texture a square which overlays our scene d3d11Device->CreateShaderResourceView(sharedTex11, NULL, &d2dTexture); } bool InitScene() { InitD2DScreenTexture(); CreateSphere(10, 10); ///////////////**************new**************//////////////////// if(!LoadObjModel(L"spaceCompound.obj", &meshVertBuff, &meshIndexBuff, meshSubsetIndexStart, meshSubsetTexture, material, meshSubsets, true, false)) return false; ///////////////**************new**************//////////////////// //Compile Shaders from shader file hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_4_0", 0, 0, 0, &VS_Buffer, 0, 0); hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_4_0", 0, 0, 0, &PS_Buffer, 0, 0); hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "D2D_PS", "ps_4_0", 0, 0, 0, &D2D_PS_Buffer, 0, 0); hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "SKYMAP_VS", "vs_4_0", 0, 0, 0, &SKYMAP_VS_Buffer, 0, 0); hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "SKYMAP_PS", "ps_4_0", 0, 0, 0, &SKYMAP_PS_Buffer, 0, 0); //Create the Shader Objects hr = d3d11Device->CreateVertexShader(VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), NULL, &VS); hr = d3d11Device->CreatePixelShader(PS_Buffer->GetBufferPointer(), PS_Buffer->GetBufferSize(), NULL, &PS); hr = d3d11Device->CreatePixelShader(D2D_PS_Buffer->GetBufferPointer(), D2D_PS_Buffer->GetBufferSize(), NULL, &D2D_PS); hr = d3d11Device->CreateVertexShader(SKYMAP_VS_Buffer->GetBufferPointer(), SKYMAP_VS_Buffer->GetBufferSize(), NULL, &SKYMAP_VS); hr = d3d11Device->CreatePixelShader(SKYMAP_PS_Buffer->GetBufferPointer(), SKYMAP_PS_Buffer->GetBufferSize(), NULL, &SKYMAP_PS); //Set Vertex and Pixel Shaders d3d11DevCon->VSSetShader(VS, 0, 0); d3d11DevCon->PSSetShader(PS, 0, 0); light.pos = XMFLOAT3(0.0f, 1.0f, 0.0f); light.dir = XMFLOAT3(0.0f, 0.0f, 1.0f); light.range = 1000.0f; light.cone = 20.0f; light.att = XMFLOAT3(0.4f, 0.02f, 0.000f); light.ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f); light.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f); //Create the vertex buffer Vertex v[] = { // Bottom Face Vertex(-1.0f, -1.0f, -1.0f, 100.0f, 100.0f, 0.0f, 1.0f, 0.0f), Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 100.0f, 0.0f, 1.0f, 0.0f), Vertex( 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f), Vertex(-1.0f, -1.0f, 1.0f, 100.0f, 0.0f, 0.0f, 1.0f, 0.0f), }; DWORD indices[] = { 0, 1, 2, 0, 2, 3, }; D3D11_BUFFER_DESC indexBufferDesc; ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) ); indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(DWORD) * 2 * 3; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA iinitData; iinitData.pSysMem = indices; d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &squareIndexBuffer); D3D11_BUFFER_DESC vertexBufferDesc; ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) ); vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 4; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; D3D11_SUBRESOURCE_DATA vertexBufferData; ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) ); vertexBufferData.pSysMem = v; hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &squareVertBuffer); //Create the Input Layout hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), &vertLayout ); //Set the Input Layout d3d11DevCon->IASetInputLayout( vertLayout ); //Set Primitive Topology d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); //Create the Viewport D3D11_VIEWPORT viewport; ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT)); viewport.TopLeftX = 0; viewport.TopLeftY = 0; viewport.Width = Width; viewport.Height = Height; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; //Set the Viewport d3d11DevCon->RSSetViewports(1, &viewport); //Create the buffer to send to the cbuffer in effect file D3D11_BUFFER_DESC cbbd; ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC)); cbbd.Usage = D3D11_USAGE_DEFAULT; cbbd.ByteWidth = sizeof(cbPerObject); cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; cbbd.CPUAccessFlags = 0; cbbd.MiscFlags = 0; hr = d3d11Device->CreateBuffer(&cbbd, NULL, &cbPerObjectBuffer); //Create the buffer to send to the cbuffer per frame in effect file ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC)); cbbd.Usage = D3D11_USAGE_DEFAULT; cbbd.ByteWidth = sizeof(cbPerFrame); cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; cbbd.CPUAccessFlags = 0; cbbd.MiscFlags = 0; hr = d3d11Device->CreateBuffer(&cbbd, NULL, &cbPerFrameBuffer); //Camera information camPosition = XMVectorSet( 0.0f, 5.0f, -8.0f, 0.0f ); camTarget = XMVectorSet( 0.0f, 0.0f, 0.0f, 0.0f ); camUp = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f ); //Set the View matrix camView = XMMatrixLookAtLH( camPosition, camTarget, camUp ); //Set the Projection matrix camProjection = XMMatrixPerspectiveFovLH( 0.4f*3.14f, (float)Width/Height, 1.0f, 1000.0f); D3D11_BLEND_DESC blendDesc; ZeroMemory( &blendDesc, sizeof(blendDesc) ); D3D11_RENDER_TARGET_BLEND_DESC rtbd; ZeroMemory( &rtbd, sizeof(rtbd) ); rtbd.BlendEnable = true; rtbd.SrcBlend = D3D11_BLEND_SRC_COLOR; rtbd.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; rtbd.BlendOp = D3D11_BLEND_OP_ADD; rtbd.SrcBlendAlpha = D3D11_BLEND_ONE; rtbd.DestBlendAlpha = D3D11_BLEND_ZERO; rtbd.BlendOpAlpha = D3D11_BLEND_OP_ADD; rtbd.RenderTargetWriteMask = D3D10_COLOR_WRITE_ENABLE_ALL; blendDesc.AlphaToCoverageEnable = false; blendDesc.RenderTarget[0] = rtbd; d3d11Device->CreateBlendState(&blendDesc, &d2dTransparency); ///////////////**************new**************//////////////////// ZeroMemory( &rtbd, sizeof(rtbd) ); rtbd.BlendEnable = true; rtbd.SrcBlend = D3D11_BLEND_INV_SRC_ALPHA; rtbd.DestBlend = D3D11_BLEND_SRC_ALPHA; rtbd.BlendOp = D3D11_BLEND_OP_ADD; rtbd.SrcBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; rtbd.DestBlendAlpha = D3D11_BLEND_SRC_ALPHA; rtbd.BlendOpAlpha = D3D11_BLEND_OP_ADD; rtbd.RenderTargetWriteMask = D3D10_COLOR_WRITE_ENABLE_ALL; blendDesc.AlphaToCoverageEnable = false; blendDesc.RenderTarget[0] = rtbd; d3d11Device->CreateBlendState(&blendDesc, &Transparency); ///////////////**************new**************//////////////////// hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, L"grass.jpg", NULL, NULL, &CubesTexture, NULL ); ///Load Skymap's cube texture/// D3DX11_IMAGE_LOAD_INFO loadSMInfo; loadSMInfo.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE; ID3D11Texture2D* SMTexture = 0; hr = D3DX11CreateTextureFromFile(d3d11Device, L"skymap.dds", &loadSMInfo, 0, (ID3D11Resource**)&SMTexture, 0); D3D11_TEXTURE2D_DESC SMTextureDesc; SMTexture->GetDesc(&SMTextureDesc); D3D11_SHADER_RESOURCE_VIEW_DESC SMViewDesc; SMViewDesc.Format = SMTextureDesc.Format; SMViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; SMViewDesc.TextureCube.MipLevels = SMTextureDesc.MipLevels; SMViewDesc.TextureCube.MostDetailedMip = 0; hr = d3d11Device->CreateShaderResourceView(SMTexture, &SMViewDesc, &smrv); // Describe the Sample State D3D11_SAMPLER_DESC sampDesc; ZeroMemory( &sampDesc, sizeof(sampDesc) ); sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER; sampDesc.MinLOD = 0; sampDesc.MaxLOD = D3D11_FLOAT32_MAX; //Create the Sample State hr = d3d11Device->CreateSamplerState( &sampDesc, &CubesTexSamplerState ); D3D11_RASTERIZER_DESC cmdesc; ZeroMemory(&cmdesc, sizeof(D3D11_RASTERIZER_DESC)); cmdesc.FillMode = D3D11_FILL_SOLID; cmdesc.CullMode = D3D11_CULL_BACK; cmdesc.FrontCounterClockwise = true; hr = d3d11Device->CreateRasterizerState(&cmdesc, &CCWcullMode); cmdesc.FrontCounterClockwise = false; hr = d3d11Device->CreateRasterizerState(&cmdesc, &CWcullMode); cmdesc.CullMode = D3D11_CULL_NONE; //cmdesc.FillMode = D3D11_FILL_WIREFRAME; hr = d3d11Device->CreateRasterizerState(&cmdesc, &RSCullNone); D3D11_DEPTH_STENCIL_DESC dssDesc; ZeroMemory(&dssDesc, sizeof(D3D11_DEPTH_STENCIL_DESC)); dssDesc.DepthEnable = true; dssDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; dssDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; d3d11Device->CreateDepthStencilState(&dssDesc, &DSLessEqual); return true; } void StartTimer() { LARGE_INTEGER frequencyCount; QueryPerformanceFrequency(&frequencyCount); countsPerSecond = double(frequencyCount.QuadPart); QueryPerformanceCounter(&frequencyCount); CounterStart = frequencyCount.QuadPart; } double GetTime() { LARGE_INTEGER currentTime; QueryPerformanceCounter(&currentTime); return double(currentTime.QuadPart-CounterStart)/countsPerSecond; } double GetFrameTime() { LARGE_INTEGER currentTime; __int64 tickCount; QueryPerformanceCounter(&currentTime); tickCount = currentTime.QuadPart-frameTimeOld; frameTimeOld = currentTime.QuadPart; if(tickCount < 0.0f) tickCount = 0.0f; return float(tickCount)/countsPerSecond; } void UpdateScene(double time) { //Reset cube1World groundWorld = XMMatrixIdentity(); //Define cube1's world space matrix Scale = XMMatrixScaling( 500.0f, 10.0f, 500.0f ); Translation = XMMatrixTranslation( 0.0f, 10.0f, 0.0f ); //Set cube1's world space using the transformations groundWorld = Scale * Translation; //Reset sphereWorld sphereWorld = XMMatrixIdentity(); //Define sphereWorld's world space matrix Scale = XMMatrixScaling( 5.0f, 5.0f, 5.0f ); //Make sure the sphere is always centered around camera Translation = XMMatrixTranslation( XMVectorGetX(camPosition), XMVectorGetY(camPosition), XMVectorGetZ(camPosition) ); //Set sphereWorld's world space using the transformations sphereWorld = Scale * Translation; ///////////////**************new**************//////////////////// meshWorld = XMMatrixIdentity(); //Define cube1's world space matrix Rotation = XMMatrixRotationY(3.14f); Scale = XMMatrixScaling( 1.0f, 1.0f, 1.0f ); Translation = XMMatrixTranslation( 0.0f, 0.0f, 0.0f ); meshWorld = Rotation * Scale * Translation; ///////////////**************new**************//////////////////// light.pos.x = XMVectorGetX(camPosition); light.pos.y = XMVectorGetY(camPosition); light.pos.z = XMVectorGetZ(camPosition); light.dir.x = XMVectorGetX(camTarget) - light.pos.x; light.dir.y = XMVectorGetY(camTarget) - light.pos.y; light.dir.z = XMVectorGetZ(camTarget) - light.pos.z; } void RenderText(std::wstring text, int inInt) { d3d11DevCon->PSSetShader(D2D_PS, 0, 0); //Release the D3D 11 Device keyedMutex11->ReleaseSync(0); //Use D3D10.1 device keyedMutex10->AcquireSync(0, 5); //Draw D2D content D2DRenderTarget->BeginDraw(); //Clear D2D Background D2DRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); //Create our string std::wostringstream printString; printString << text << inInt; printText = printString.str(); //Set the Font Color D2D1_COLOR_F FontColor = D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f); //Set the brush color D2D will use to draw with Brush->SetColor(FontColor); //Create the D2D Render Area D2D1_RECT_F layoutRect = D2D1::RectF(0, 0, Width, Height); //Draw the Text D2DRenderTarget->DrawText( printText.c_str(), wcslen(printText.c_str()), TextFormat, layoutRect, Brush ); D2DRenderTarget->EndDraw(); //Release the D3D10.1 Device keyedMutex10->ReleaseSync(1); //Use the D3D11 Device keyedMutex11->AcquireSync(1, 5); //Use the shader resource representing the direct2d render target //to texture a square which is rendered in screen space so it //overlays on top of our entire scene. We use alpha blending so //that the entire background of the D2D render target is "invisible", //And only the stuff we draw with D2D will be visible (the text) //Set the blend state for D2D render target texture objects d3d11DevCon->OMSetBlendState(d2dTransparency, NULL, 0xffffffff); //Set the d2d Index buffer d3d11DevCon->IASetIndexBuffer( d2dIndexBuffer, DXGI_FORMAT_R32_UINT, 0); //Set the d2d vertex buffer UINT stride = sizeof( Vertex ); UINT offset = 0; d3d11DevCon->IASetVertexBuffers( 0, 1, &d2dVertBuffer, &stride, &offset ); WVP = XMMatrixIdentity(); cbPerObj.WVP = XMMatrixTranspose(WVP); d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 ); d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer ); d3d11DevCon->PSSetShaderResources( 0, 1, &d2dTexture ); d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState ); d3d11DevCon->RSSetState(CWcullMode); d3d11DevCon->DrawIndexed( 6, 0, 0 ); } void DrawScene() { //Clear our render target and depth/stencil view float bgColor[4] = { 0.1f, 0.1f, 0.1f, 1.0f }; d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor); d3d11DevCon->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0); constbuffPerFrame.light = light; d3d11DevCon->UpdateSubresource( cbPerFrameBuffer, 0, NULL, &constbuffPerFrame, 0, 0 ); d3d11DevCon->PSSetConstantBuffers(0, 1, &cbPerFrameBuffer); //Set our Render Target d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, depthStencilView ); //Set the default blend state (no blending) for opaque objects d3d11DevCon->OMSetBlendState(0, 0, 0xffffffff); //Set Vertex and Pixel Shaders d3d11DevCon->VSSetShader(VS, 0, 0); d3d11DevCon->PSSetShader(PS, 0, 0); //Set the cubes index buffer d3d11DevCon->IASetIndexBuffer( squareIndexBuffer, DXGI_FORMAT_R32_UINT, 0); //Set the cubes vertex buffer UINT stride = sizeof( Vertex ); UINT offset = 0; d3d11DevCon->IASetVertexBuffers( 0, 1, &squareVertBuffer, &stride, &offset ); //Set the WVP matrix and send it to the constant buffer in effect file WVP = groundWorld * camView * camProjection; cbPerObj.WVP = XMMatrixTranspose(WVP); cbPerObj.World = XMMatrixTranspose(groundWorld); d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 ); d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer ); d3d11DevCon->PSSetShaderResources( 0, 1, &CubesTexture ); d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState ); d3d11DevCon->RSSetState(CCWcullMode); //d3d11DevCon->DrawIndexed( 6, 0, 0 ); ///////////////**************new**************//////////////////// //Draw our model's NON-transparent subsets for(int i = 0; i < meshSubsets; ++i) { //Set the grounds index buffer d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0); //Set the grounds vertex buffer d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset ); //Set the WVP matrix and send it to the constant buffer in effect file WVP = meshWorld * camView * camProjection; cbPerObj.WVP = XMMatrixTranspose(WVP); cbPerObj.World = XMMatrixTranspose(meshWorld); cbPerObj.difColor = material[meshSubsetTexture[i]].difColor; cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture; d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 ); d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer ); d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer ); if(material[meshSubsetTexture[i]].hasTexture) d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] ); d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState ); d3d11DevCon->RSSetState(RSCullNone); int indexStart = meshSubsetIndexStart[i]; int indexDrawAmount = meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i]; if(!material[meshSubsetTexture[i]].transparent) d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 ); } ///////////////**************new**************//////////////////// /////Draw the Sky's Sphere////// //Set the spheres index buffer d3d11DevCon->IASetIndexBuffer( sphereIndexBuffer, DXGI_FORMAT_R32_UINT, 0); //Set the spheres vertex buffer d3d11DevCon->IASetVertexBuffers( 0, 1, &sphereVertBuffer, &stride, &offset ); //Set the WVP matrix and send it to the constant buffer in effect file WVP = sphereWorld * camView * camProjection; cbPerObj.WVP = XMMatrixTranspose(WVP); cbPerObj.World = XMMatrixTranspose(sphereWorld); d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 ); d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer ); //Send our skymap resource view to pixel shader d3d11DevCon->PSSetShaderResources( 0, 1, &smrv ); d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState ); //Set the new VS and PS shaders d3d11DevCon->VSSetShader(SKYMAP_VS, 0, 0); d3d11DevCon->PSSetShader(SKYMAP_PS, 0, 0); //Set the new depth/stencil and RS states d3d11DevCon->OMSetDepthStencilState(DSLessEqual, 0); d3d11DevCon->RSSetState(RSCullNone); d3d11DevCon->DrawIndexed( NumSphereFaces * 3, 0, 0 ); //Set the default VS, PS shaders and depth/stencil state d3d11DevCon->VSSetShader(VS, 0, 0); d3d11DevCon->PSSetShader(PS, 0, 0); d3d11DevCon->OMSetDepthStencilState(NULL, 0); ///////////////**************new**************//////////////////// //Draw our model's TRANSPARENT subsets now //Set our blend state d3d11DevCon->OMSetBlendState(Transparency, NULL, 0xffffffff); for(int i = 0; i < meshSubsets; ++i) { //Set the grounds index buffer d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0); //Set the grounds vertex buffer d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset ); //Set the WVP matrix and send it to the constant buffer in effect file WVP = meshWorld * camView * camProjection; cbPerObj.WVP = XMMatrixTranspose(WVP); cbPerObj.World = XMMatrixTranspose(meshWorld); cbPerObj.difColor = material[meshSubsetTexture[i]].difColor; cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture; d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 ); d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer ); d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer ); if(material[meshSubsetTexture[i]].hasTexture) d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] ); d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState ); d3d11DevCon->RSSetState(RSCullNone); int indexStart = meshSubsetIndexStart[i]; int indexDrawAmount = meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i]; if(material[meshSubsetTexture[i]].transparent) d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 ); } ///////////////**************new**************//////////////////// RenderText(L"FPS: ", fps); //Present the backbuffer to the screen SwapChain->Present(0, 0); } int messageloop(){ MSG msg; ZeroMemory(&msg, sizeof(MSG)); while(true) { BOOL PeekMessageL( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg ); if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else{ // run game code frameCount++; if(GetTime() > 1.0f) { fps = frameCount; frameCount = 0; StartTimer(); } frameTime = GetFrameTime(); DetectInput(frameTime); UpdateScene(frameTime); DrawScene(); } } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch( msg ) { case WM_KEYDOWN: if( wParam == VK_ESCAPE ){ DestroyWindow(hwnd); } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wParam, lParam); } Effects.fx: struct Light { float3 pos; float range; float3 dir; float cone; float3 att; float4 ambient; float4 diffuse; }; cbuffer cbPerFrame { Light light; }; cbuffer cbPerObject { float4x4 WVP; float4x4 World; float4 difColor; bool hasTexture; }; Texture2D ObjTexture; SamplerState ObjSamplerState; TextureCube SkyMap; struct VS_OUTPUT { float4 Pos : SV_POSITION; float4 worldPos : POSITION; float2 TexCoord : TEXCOORD; float3 normal : NORMAL; }; struct SKYMAP_VS_OUTPUT //output structure for skymap vertex shader { float4 Pos : SV_POSITION; float3 texCoord : TEXCOORD; }; VS_OUTPUT VS(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL) { VS_OUTPUT output; output.Pos = mul(inPos, WVP); output.worldPos = mul(inPos, World); output.normal = mul(normal, World); output.TexCoord = inTexCoord; return output; } SKYMAP_VS_OUTPUT SKYMAP_VS(float3 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL) { SKYMAP_VS_OUTPUT output = (SKYMAP_VS_OUTPUT)0; //Set Pos to xyww instead of xyzw, so that z will always be 1 (furthest from camera) output.Pos = mul(float4(inPos, 1.0f), WVP).xyww; output.texCoord = inPos; return output; } float4 PS(VS_OUTPUT input) : SV_TARGET { input.normal = normalize(input.normal); //Set diffuse color of material float4 diffuse = difColor; //If material has a diffuse texture map, set it now if(hasTexture == true) diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord ); float3 finalColor = float3(0.0f, 0.0f, 0.0f); //Create the vector between light position and pixels position float3 lightToPixelVec = light.pos - input.worldPos; //Find the distance between the light pos and pixel pos float d = length(lightToPixelVec); //Add the ambient light float3 finalAmbient = diffuse * light.ambient; //If pixel is too far, return pixel color with ambient light if( d > light.range ) return float4(finalAmbient, diffuse.a); //Turn lightToPixelVec into a unit length vector describing //the pixels direction from the lights position lightToPixelVec /= d; //Calculate how much light the pixel gets by the angle //in which the light strikes the pixels surface float howMuchLight = dot(lightToPixelVec, input.normal); //If light is striking the front side of the pixel if( howMuchLight > 0.0f ) { //Add light to the finalColor of the pixel finalColor += diffuse * light.diffuse; //Calculate Light's Distance Falloff factor finalColor /= (light.att[0] + (light.att[1] * d)) + (light.att[2] * (d*d)); //Calculate falloff from center to edge of pointlight cone finalColor *= pow(max(dot(-lightToPixelVec, light.dir), 0.0f), light.cone); } //make sure the values are between 1 and 0, and add the ambient finalColor = saturate(finalColor + finalAmbient); //Return Final Color return float4(finalColor, diffuse.a); } float4 SKYMAP_PS(SKYMAP_VS_OUTPUT input) : SV_Target { return SkyMap.Sample(ObjSamplerState, input.texCoord); } float4 D2D_PS(VS_OUTPUT input) : SV_TARGET { float4 diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord ); return diffuse; }