-
Notifications
You must be signed in to change notification settings - Fork 15
3.3 Model
Now it is time to get our hands dirty and start creating the actual loading code. The goal of this tutorial is to create another class that represents a model in its entirety, that is, a model that contains multiple meshes, possibly with multiple objects. A house, that contains a wooden balcony, a tower and perhaps a swimming pool could still be loaded as a single model. We'll load the model from a OBJ file and turn it into TMesh objects we've created in the [last](3.2 Mesh) tutorial.
Without further ado, I present you the class structure of the IModel interface and corresponding TModel class:
type
IModel = interface
['{29B5AB25-129C-4CA6-B368-146577F298F6}']
procedure Draw;
end;
type
TModel = class(TInterfacedObject, IModel)
private
FMeshes: TObjectList<TMesh>;
FDirectory: String;
FShader: IShader;
private
procedure LoadModel(const APath: String);
...
protected
{ IModel }
procedure Draw;
public
constructor Create(const APath: String; const AShader: IShader);
destructor Destroy; override;
end;The TModel class contains a list of TMesh objects and requires us to give it a file location (in assets.zip) in its constructor. It then loads the file right away via the LoadModel method that is called in the constructor. We also store the directory of the file path that we'll later need when loading the material library and textures.
The Draw methodn is nothing special and basically loops over each of the meshes to call their respective Draw method:
procedure TModel.Draw;
var
I: Integer;
begin
for I := 0 to FMeshes.Count - 1 do
FMeshes[I].Draw;
end;ℹ️ instead of a
for..toloop, you can also use afor..inloop here (as infor Mesh in FMeshes do). However, using enumerators is a bit less efficient than using plainforloops, so I tend to avoid them in places where performance is important (although that is arguably not the case here).
With the helper of the TParser class we created in the last tutorial, the LoadModel method can look like this:
procedure TModel.LoadModel(const APath: String);
var
Parser: TParser;
begin
FDirectory := TPath.GetDirectoryName(APath) + '/';
Parser := TParser.Create(APath);
try
ParseOBJ(Parser);
finally
Parser.Free;
end;
end;Note that we extract the directory name from the file path since we need it later to locate material libraries and textures. (Remember to use forward slashes (/) instead of back slashes, since forward slashes work on all platforms).
Parsing an OBJ file now reduces to reading commands and arguments until the end of the file has been reached, and handling each command we want to support:
procedure TModel.ParseOBJ(const AParser: TParser);
var
Command, Arg1, Arg2, Arg3: String;
begin
while AParser.ReadLine(Command, Arg1, Arg2, Arg3) do
begin
case Command.Chars[0] of
'f': if (Command = 'f') then
{ Parse face vertex. };
'm': if (Command = 'mtllib') then
LoadMtlLib(Arg1);
'o': if (Command = 'o') then
{ Start a new object/mesh. Store previous mesh if any. }
StoreMesh;
'u': if (Command = 'usemtl') then
{ Assign material from material library to mesh. }
'v': if (Command = 'v') then
{ Add position }
else if (Command = 'vt') then
{ Add texture coordinate }
else if (Command = 'vn') then
{ Add normal }
end;
StoreMesh;
end;A little trick I often use when recognizing a string (like a Command in this example) is the use the first character of the string in a case-statement. This is faster than creating one long if-then-else chain for each possible value. (You could also use a dictionary to look up strings, but this method is even faster).
I did not show the source code for each specific command here. There are a few helper records and methods involved in handling these commands, and it would be too much code to show here. Besides, we want to focus this tutorial series on OpenGL and not on file parsing. But if you are interested in the inner workings, then by all means consult the source code in the Sample.Classes unit.
| ⬅️ [3.2 Mesh](3.2 Mesh) | Contents | [4.1 Depth Testing](4.1 Depth Testing) ➡️ |
|---|
Learn OpenGL(ES) with Delphi
-
- Getting Started
- OpenGL (ES)
- [Creating an OpenGL App](Creating an OpenGL App)
- [1.1 Hello Window](1.1 Hello Window)
- [1.2 Hello Triangle](1.2 Hello Triangle)
- [1.3 Shaders](1.3 Shaders)
- [1.4 Textures](1.4 Textures)
- [1.5 Transformations](1.5 Transformations)
- [1.6 Coordinate Systems](1.6 Coordinate Systems)
- [1.7 Camera](1.7 Camera)
- [Review](Getting Started Review)
-
- Lighting
- [2.1 Colors](2.1 Colors)
- [2.2 Basic Lighting](2.2 Basic Lighting)
- [2.3 Materials](2.3 Materials)
- [2.4 Lighting Maps](2.4 Lighting Maps)
- [2.5 Light Casters](2.5 Light Casters)
- [2.6 Multiple Lights](2.6 Multiple Lights)
- [Review](Lighting Review)
-
- Model Loading
- [3.1 OBJ Files](3.1 OBJ Files)
- [3.2 Mesh](3.2 Mesh)
- [3.3 Model](3.3 Model)
-
- Advanced OpenGL
- [4.1 Depth Testing](4.1 Depth Testing)
- [4.2 Stencil Testing](4.2 Stencil Testing)
- [4.3 Blending](4.3 Blending)
- [4.4 Face Culling](4.4 Face Culling)
- [4.5 Framebuffers](4.5 Framebuffers)
- [4.6 Cubemaps](4.6 Cubemaps)
- [4.7 Advanced Data](4.7 Advanced Data)
- [4.8 Advanced GLSL](4.8 Advanced GLSL)
- [4.9 Geometry Shader](4.9 Geometry Shader)
- 4.10Instancing
- [4.11 Anti Aliasing](4.11 Anti Aliasing)
-
- Advanced Lighting
- [5.1 Advanced Lighting](5.1 Advanced Lighting)
- [5.2 Gamma Correction](5.2 Gamma Correction)
- [5.3 Shadows](5.3 Shadows)
- [5.3.1 Shadow Mapping](5.3.1 Shadow Mapping)
- [5.3.2 Point Shadows](5.3.2 Point Shadows)
- [5.3.3 CSM](5.3.3 CSM)
- [5.4 Normal Mapping](5.4 Normal Mapping)
- [5.5 Parallax Mapping](5.5 Parallax Mapping)
- [5.6 HDR](5.6 HDR)
- [5.7 Bloom](5.7 Bloom)
- [5.8 Deferred Shading](5.8 Deferred Shading)
- [5.9 SSAO](5.9 SSAO)
-
- PBR
-
- In Practice
- [7.1 Debugging](7.1 Debugging)
- [Text Rendering](Text Rendering)
- [2D Game](2D Game)
- Breakout
- [Setting Up](Setting Up)
- [Rendering Sprites](Rendering Sprites)
- Levels
-
Collisions
- Ball
- [Collision Detection](Collision Detection)
- [Collision Resolution](Collision Resolution)
- Particles
- Postprocessing
- Powerups
- Audio
- [Render Text](Render Text)
- [Final Thoughts](Final Thoughts)