CS409/CS809 Lab 4: Loading OBJ Models

Setting up an OpenGL Project with the Obj Library of Files

1. Start Microsoft Visual Studio 2013. Select File / New / Project. Under “Project Type”, choose Templates / Visual C++. Then choose a Win32 Console Application. Change the name to Lab4 or some suitable name and change the location to a hard drive located on the computer (typically C or D). You will need to remember this location for steps 3 and 5. Make sure the “Create folder for project” checkbox is not checked and that the “Add to source control” is not checked. Click OK.

· If the Chose Location window keeps popping up again, you aren’t allowed to save files at the location you chose. Select a different location, such as the desktop.

· Do not put your program on one of the external drives like H. The connection is slow, and your program may not compile correctly.

2. Click Next> or Application Settings (they go to the same place). Check the “Empty Project” box. Make sure the “Security development lifecycle check” is not checked. Click Finish.

3. Go to http://www2.cs.uregina.ca/~anima/408/Terms/201710/Labs/index.html and download the Lab4.zip and ObjLibrary.zip files to your project directory.

4. Unzip the 2 zip files. You will have a folder called ObjLibrary and 17 other files.

5. In Visual C++, go to Project/Add Existing Item… and select main4.cpp (and holding control, continue to select), Sleep.h, Sleep.cpp, and GetGlut.h.

6. Ensure that Solution Explorer is visible (View / Solution Explorer). In the Solution Explorer, select Lab4 (the project name) and right click it. Choose Add / New Filter. A new folder icon should appear in the Solution Explorer. Name it ObjLibrary. (This creates an imaginary folder in the project, but it would not create a Windows folder on the computer).

7. In the Solution Explorer, right click on the ObjLibrary folder icon and select Add / Existing item. Go into the ObjLibrary folder on Windows and select all files. The files should appear in the Solution Explorer inside the ObjLibrary folder. You should get 26 files.

8. Compile and run the original program. You should see 2 windows: a text console and a graphical window labeled “Loading OBJ Models”. There is a set of positive X/Y/Z axes and a wire-frame cube in the middle of this second window. Exit the windows. Closing the text window will close the graphical window, and usually vice versa.

9. For the remainder of the instructions, it is assumed that you compile and run regularly so that you can see what each change does.

Load and display an OBJ model without a material

10. Add an #include for "ObjLibrary/ObjModel.h"
#include "ObjLibrary/ObjModel.h"
Add a using directive for ObjLibrary after the using directive for std:
using namespace ObjLibrary;

11. Declare a global variable (before the main function) of the ObjModel type:
ObjModel spiky;

12. In the init function, use the load function in the ObjModel class to load the OBJ model called Spiky.obj:
spiky.load("Spiky.obj");

13. In the display function, use the draw function in the ObjModel class to display the OBJ model:
spiky.draw();
You should see a spiky ball at the origin.

14. Change the color of the spiky object to orange by adding a glColor line in front of the draw line.

15. Change the display function in the program to draw a total of three spiky orange objects along the positive X axis, starting at (1.0, 0.0, 0.0) and ending at (3.0, 0.0, 0.0). Make these spiky objects only 0.45 times the size of the original spiky model. Don’t forget to use Push/Pop matrix commands. Use a for loop instead of just copying the drawing code.

Load and display an OBJ model with a material

A material is a general term for everything that controls how a model should appear like instead of its geometry. Materials include colour, texture, and other more obscure things. Many models have one material, but some are divided into meshes, each with its own material.

16. To load an OBJ model with one or more materials, we need an .obj file, a .mtl file, and zero or more .bmp files. If you ever get a .mtl file that includes a .jpg or other format of image file, you will have to convert it to a .bmp file for use with the ObjLibrary provided in this exercise, as explained later in this lab.

· There are instructions on the Labs page for how to also load .png images.

17. Declare a global variable (before the main function) of the ObjModel type:
ObjModel bucket;

18. In the init function, use the load function in the ObjModel class to load an OBJ model from a file named firebucket.obj:
bucket.load("firebucket.obj");

19. In the display function, use the draw function in the ObjModel class to display the OBJ model:
bucket.draw();
You will not see the bucket because it is too big.

20. Change the scale of the bucket to 0.005 times the size of the original bucket model. Don’t forget to use Push/Pop matrix commands.

21. Change the program to draw a total of three buckets along the negative X axis, starting at the (-1.0, 0.0, 0.0) and ending at (-3.0, 0.0, 0.0). The buckets sit on the X axis, while the spiky objects are centered along the X axis, so the buckets seem to be sitting higher than the spiky objects.

Create a DisplayList for the Bucket

A display list is a series of commands stored in the video memory, i.e., the memory on the graphics card. Display lists are no longer used in commercial games, but are much easier to use than their replacements. Here the demonstration simply shows how to use functions from the ObjLibrary to use display lists; to see how they are done using OpenGL directly, please see the source code in the ObjLibrary or OpenGL documentation.

22. Add an #include for "ObjLibrary/DisplayList.h"
#include "ObjLibrary/DisplayList.h"

23. Declare a global variable (before the main function) of the DisplayList type:
DisplayList bucket_list;

24. In the init function, after loading the bucket, use the getDisplayList function in the ObjModel class to create a display list for bucket:
bucket_list = bucket.getDisplayList();
(Note: this command copies the information needed to draw the bucket into video memory. All we get back from OpenGL is an identifier number for the chunk of video memory where this information is stored. As such, display lists can only be created after glutInit has been called—not when initializing global variables.)

25. In the display function, replace the call to draw for bucket with a call to the draw function in the DisplayList class:
bucket_list.draw();
The bucket should display as before. However, the commands are now being stored and run from video memory. The exact increase in speed will depend on many factors, including your graphics hardware, the complexity of the model, and the area on the screen in covers. At typical speed increase is 100 times faster.

26. Change the for loop that draws the three buckets to draw 50 buckets instead.

Add a Skybox

A skybox is a special cube that contains the scene background. This background is displayed behind everything else. Using a skybox allows the background to have terrain and landmarks on it instead of just being a solid colour. Internally, a skybox is a cube with the faces reversed (so they can only be seen from inside) drawn around the camera.

27. Declare a global variable (before the main function) of the ObjModel type:
ObjModel skybox;

28. In the init function, use the load function in the ObjModel class to load an OBJ model from a file named Skybox2.obj:
skybox.load("Skybox.obj");

29. In the display function, immediately after the gluLookAt command, use the draw function in the ObjModel class to display the skybox:
skybox.draw();
A strange-looking textured cube will appear at the origin, sticking through the side of the nearest bucket. It looks unusual because you are seeing the far side of it instead of the near side.

· Note: The skybox must be drawn first. Anything drawn before will not be visible.

30. Move the skybox to be at the same position as the camera. Don’t forget to use Push/Pop matrix commands. You should now see sand blocking your view of everything else.

· You can determine the position of the camera by looking at the first three numbers following gluLookAt. So, if you see gluLookAt(2.0, 1.0, 4.0, …) then the camera is located at (2, 1, 4) in X/Y/Z space.

· Optional: Create one or more global variables to represent the camera and initialize it or them to these values. Then use the variables to set the camera position with gluLookAt and the skybox position with glTranslated.

31. Add lines before and after the draw call for the skybox to disable and re-enable writing to the depth buffer:
glDepthMask(GL_FALSE);
skybox.draw();
glDepthMask(GL_TRUE);
Since the depth buffer is not being used, the distance to the skybox will not be recorded internally, so everything else will be displayed in front of it. The effect is that the skybox will now appear to be behind everything else.

32. Move the camera and skybox down by 2 in the Y direction. You should now be able to see mountains above the sand.

33. Scale the skybox until to be 60% of the size of your far clipping plane (the last parameter to the gluPerspective function called by the reshape function):
glScaled(600.0, 600.0, 600.0);
There should be no visible effect. However, a larger skybox will have less "jiggle" effects due to floating point rounding when the camera moves. This is especially important if the camera is far from the origin.

How to Obtain OBJ Models

1. You can make OBJ models with Maya, Blender, 3D Studio Max, or other animation packages. Maya is available in the CL 105 lab but it is not easy to use (instructions given in the CS 408 Notes—same login and password).

2. You can find many OBJ models without textures on the web. Typically the .mtl file is not provided and thus no texturing is produced.

3. You can find a collection of OBJ models with textures from the web (details follow).

Downloading OBJ Models from ShareCG

1. The largest collection of OBJ models with textures that we have found is at the ShareCG website: http://www.sharecg.com/. You will have to join the sharecg group to obtain the models (the “JOIN” button is along the top edge of the window). It will send you an email message and you can then login in.

2. Do a search in the search bar on the upper right for “.mtl”. Experience has shown that where there is an .mtl file there is an .obj file, but not vice versa. You should see a list of a few hundred models.

3. Pick a model and download it. Typically, you get .obj, .mtl, and .jgp/.png/.bmp/etc. files. Some models don’t have image files. Open the .mtl file with WordPad and see if it refers to any image files: such references are typically in lines that start with “map” or “bump”. If it refers to a file that you don’t have, you ordinarily should give up and find another model. However, if it refers to something like “wood.jpg” you may be able to find your own image to use instead.

4. Use Start / All Programs / Accessories / Paint (or other software) to convert other kinds of image files to .bmp files. If your texture is larger than 1024x1024 pixels, you may also have to scale it down, but nearly all graphics cars have a limit many times that. OpenGL originally required textures sizes to be a power of 2, but has not for more than a decade. They do recommend that texture sizes be multiples of 4.

5. Change the .mtl file(s) to refer to the .bmp files that you have created instead of any other types of files.

4. Declare a global variable (before the main function) of the ObjModel type for your new object, e.g., for a model called yourchoice:
ObjModel yourchoice;

5. In the init function, use the load function in the ObjModel class to load the new OBJ model from its .obj file:
yourchoice.load("yourchoice.obj");

6. In the display function, use the draw function in the ObjModel class to display the OBJ model:
yourchoice.draw();

7. It is unfortunately common that you don’t see the object at this point. The next section discusses some typical fixes.

Typical Fixes for an OBJ Model from the Web

8. Try changing the scale from 1 to 0.1, and if that doesn’t show anything, then 0.01, and if that doesn’t show anything, then 0.001, and then 10. Don’t forget to use Push/Pop matrix commands.

9. Try inserting glDisable(GL_CULL_FACE) before the yourchoice.draw line and glEnable(GL_CULL_FACE) after it. This tells OpenGL to draw both sides of every face. Culling refers to not drawing something. This fix also applies if you see only parts of the model.
glDisable(GL_CULL_FACE);
yourchoice.draw();
glEnable(GL_CULL_FACE);

10. If the above change helps, you may want to switch to drawing the opposite face (from the default) instead of both faces. To do so, remove the glDisable/glEnable lines and use:
glFrontFace(GL_CW);
yourchoice.draw();
glFrontFace(GL_CCW);

11. Check in the .mtl file that transparency is not set to 0. When transparency is 0, it means that you cannot see the object. Look for and remove any of the following lines:
d 0.0
Tr 0.0
You should also get a warning in the console window if your model has an entirely transparent material.

12. If the model displays but the texture is upside down, or mirror imaged from left to right, or both, make appropriate changes to the image file using Paint. The texture must be saved as a 24-bit or 32-bit bmp file.

13. If the .mtl file for an object is missing, you should first see what .mtl file(s) is/are mentioned in the .obj file. Use the printMtlLibraries() function in the ObjModel class to print a list of every material file that the .obj file mentions. Also, use the printBadMaterials() function in the ObjModel class to print a list of the materials that cannot be found. To do so, add these lines to your init function after loading the model called yourmodel:
yourmodel.printMtlLibraries();
yourmodel.printBadMaterials();