使用3d模型
引用:http://www.ategpu.com/2009/06/25/%E4%BD%BF%E7%94%A83d%E6%A8%A1%E5%9E%8B.html
通过前6章的学习你应该完全掌握了使用OpenGL绘制物体的所有内容。这包括对基本图元的渲染,颜色和光照以及上一章所讲述的纹理贴图。运用上面的知识,我们已经具备了完整的渲染任何一个3D模型的能力。但在真正的游戏中,我们要绘制的3D物体往往十分复杂,对于这样的情况,使用代码来创建模型显得即复杂又不直观,难以调试。因此,我们通常的做法都是在专门的建模工具(如3DS Max, Maya等)中创建模型,再将模型导出为特定的格式,然后在我们的程序载入这个模型。这正是本章的主题:3D模型。
在计算机平台上,常用的3D模型有3DS、OBJ、MD2、MD3、MDL等多种格式。这些格式在存储方式存在很大的差别,但基本思想大同小异。3D模型又分为动态模型和静态模型。在动态模型中除了保存模型的定点和表面数据之外,还有与动画相关的信息。这些动态模型在渲染起来较为复杂。在这里,我们只介绍一种简单的静态模型格式–OBJ模型。
学习本章,你将了解:
-
使用建模工具创建模型并输出为OBJ格式
-
OBJ模型文件的文件格式
-
用于存储材质的MTL文件格式
-
载入OBJ模型
-
渲染模型
7.1 使用建模工具创建模型
要渲染一个模型,首先要有一个模型文件。目前绝大多数流行的建模软件都支持将模型输出为OBJ文件。这里以3DS Max为例,介绍创建OBJ模型的方法。
首先,启动3DS Max,并向往常一样创建模型。如图7.1-1所示。
模型创建完后,执行文件(File)菜单->导出(Export)命令。在弹出的保存对话框中,输入文件名,并在”保存类型”项中,选则”Wavefront Object (*.obj)”项。
如图7.1-2所示。
图7.1-1 使用3DS Max创建模型 |
图7.1-2 保存文件 |
单击”保存”按钮后,会弹出如图7.1-3所示的选项对话框。
|
图7.1-3 设置模型文件参数 |
在这里,请注意在Faces栏中,选择Triangles以使用三角形作为基本图元。还要注意钩去Relative Vertex一项,禁止使用相对顶点索引。这是因为在后面的程序中,我们将不考虑相对顶点索引以简化代码。
单击”OK”按钮后,会紧接着弹出另一个选项对话框,如图7.1-4所示。
|
图7.1-4 设置材质文件参数 |
这一步是在设置与此OBJ文件对应的MTL文件(用于保存材质信息),注意要选择”Scene”并勾选”Export”复选框。然后单击”OK”按钮完成设置。这样,在指定的文件夹下,我们得到了两个文件:一个*.OBJ文件,存储了模型的顶点、法线和纹理坐标信息;一个*.MTL文件,存储了该模型所用的材质信息。
7.2 OBJ文件格式
OBJ文件格式是非常简单的。这种文件以纯文本的形式存储了模型的顶点、法线和纹理坐标和材质使用信息。OBJ文件的每一行,都有极其相似的格式。在OBJ文件中,每行的格式如下:
前缀 参数1 参数2 参数3 … |
其中,前缀标识了这一行所存储的信息类型。参数则是具体的数据。OBJ文件的前缀可以有:
表7.2-1 OBJ文件中的前缀 | |
前缀 |
说明 |
v | 表示本行指定一个顶点。
此前缀后跟着3个单精度浮点数,分别表示该定点的X、Y、Z坐标值 |
vt | 表示本行指定一个纹理坐标。
此前缀后跟着两个单精度浮点数。分别表示此纹理坐标的U、V值 |
vn | 表示本行指定一个法线向量。
此前缀后跟着3个单精度浮点数,分别表示该法向量的X、Y、Z坐标值 |
f | 表示本行指定一个表面(Face)。
一个表面实际上就是一个三角形图元。此前缀行的参数格式后面将详细介绍。 |
usemtl | 此前缀后只跟着一个参数。该参数指定了从此行之后到下一个以usemtl开头的行之间的所有表面所使用的材质名称。该材质可以在此OBJ文件所附属的MTL文件中找到具体信息。 |
mtllib | 此前缀后只跟着一个参数。该参数指定了此OBJ文件所使用的材质库文件(*.mtl)的文件路径 |
我们使用3DS Max创建了一个长方体,并保存为OBJ格式。用写字板打开这个OBJ文件,可以看到如下内容:
#
Max2Obj Version 4.0 Mar 10th, 2001 # mtllib ./Box.mtl g # object (null) to come … # v -46.508743 -45.052959 50.796341 v 49.442947 -45.052959 50.796341 v -46.508743 -45.052959 -48.019585 v 49.442947 -45.052959 -48.019585 v -46.508743 48.034504 50.796341 v 49.442947 48.034504 50.796341 v -46.508743 48.034504 -48.019585 v 49.442947 48.034504 -48.019585 # 8 vertices vt 0.000000 0.000000 0.000000 vt 1.000000 0.000000 0.000000 vt 0.000000 1.000000 0.000000 vt 1.000000 1.000000 0.000000 vt 0.000000 0.000000 0.000000 vt 1.000000 0.000000 0.000000 vt 0.000000 1.000000 0.000000 vt 1.000000 1.000000 0.000000 vt 0.000000 0.000000 0.000000 vt 1.000000 0.000000 0.000000 vt 0.000000 1.000000 0.000000 vt 1.000000 1.000000 0.000000 # 12 texture vertices g (null) s 2 f 1/10 3/12 4/11 f 4/11 2/9 1/10 s 4 f 5/9 6/10 8/12 f 8/12 7/11 5/9 s 8 f 1/5 2/6 6/8 f 6/8 5/7 1/5 s 16 f 2/1 4/2 8/4 f 8/4 6/3 2/1 s 32 f 4/5 3/6 7/8 f 7/8 8/7 4/5 s 64 f 3/1 1/2 5/4 f 5/4 7/3 3/1 # 12 faces g |
仔细观察,你会发现这个文件中包含了一些我们没有提到的前缀,如以”#”开头的注释,以g开头的表示组的前缀等等。但这些前缀并不影响模型的外观,因此我们可以忽略它们。
在解释以f为前缀的行的格式之前,我们不得不提一个新的概念,这就是顶点索引(Vertex Indices)。我们知道,对于每一个三角形,都需要用3个顶点来表示。例如在上面的立方体模型中,一共有6×2×3=36个顶点。仔细想想就会知道,在这36个顶点中,又相当数量的顶点是重合的。如果把这些重合的顶点都一一表示出来,就太浪费存储空间了。于是,我们提出了顶点索引的想法,解决空间占用问题。顶点索引的思想是建立两个数组,一个数组用于存储模型中所有的顶点坐标值,另一个数组则存储每一个表面所对应的三个顶点在第一个数组中的索引。图 7.2-1显示了这种一一对应的关系。
图7.2-1 顶点索引与顶点数组的对应 |
建立这样的顶点索引显然更加节约存储空间。
假设Indices:array of Integer是顶点索引数组,Vertices:array of TVertex是顶点数组,使用下面的代码段就可以把整个顶点索引对应的所有三角形绘制出来:
procedure DrawIndex(Indices:array of Integer;Vertices:array of TVertex);
var i :Integer;
begin
glBegin(GL_TRIANGLES);
for i := 0 to (High(Vertices)+1) div 3 -1 do
begin
glVertex3fv(@Vertices[Indices[i*3]]);
glVertex3fv(@Vertices[Indices[i*3+1]]);
glVertex3fv(@Vertices[Indices[i*3+2]]);
end;
glEnd;
end;
以此类推,我们可以为模型中所有的法线、纹理坐标都建立起相应的索引,以节省更多的空间。而事实上,OBJ文件就是这么做的。
现在,我们再来看一下OBJ文件的结构。在一个OBJ文件中,首先有一些以v、vt或vn前缀开头的行指定了所有的顶点、纹理坐标、法线的坐标。然后再由一些以f开头的行指定每一个三角形所对应的顶点、纹理坐标和法线的索引。在顶点、纹理坐标和法线的索引之间,使用符号”/”隔开的。一个f行可以以下面几种格式出现:
f 1 2 3
这样的行表示以第1、2、3号顶点组成一个三角形。
f 1/3 2/5 3/4
这样的行表示以第1、2、3号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,第二个顶点的纹理坐标的索引值为5,第三个顶点的纹理坐标的索引值为4。
f 1/3/4 2/5/6 3/4/2
这样的行表示以第1、2、3号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,其法线的索引值是4;第二个顶点的纹理坐标的索引值为5,其法线的索引值是6;第三个顶点的纹理坐标的索引值为6,其法线的索引值是2。
f 1//4 2//6 3//2
这样的行表示以第1、2、3号顶点组成一个三角形,且忽略纹理坐标。其中第一个顶点的法线的索引值是4;第二个顶点的法线的索引值是6;第三个顶点的法线的索引值是2。
值得注意的是文件中的索引值是以1作为起点的,这一点与Delphi中以0作为起点有很大的不同。在渲染的时候应注意将从文件中读取的坐标值减去1。
7.3 用于存储材质信息的MTL文件
MTL文件与OBJ文件极其相似。只是用于标识行的前缀有所不同。这些前缀的意义如表7.3-1所示。
表7.3-1 MTL文件中的前缀 | |
前缀 |
说明 |
newmtl | 表示新建一个材质。
此前缀后跟一个字符串,表示此材质的名称。 此行之后的信息都是对这个材质进行设定。 |
Ka | 指定最后建立的材质的环境光成分。此行包含3个单精度浮点参数。 |
Kd | 指定最后建立的材质的漫射光成分。此行包含3个单精度浮点参数。 |
Ks | 指定最后建立的材质的镜面光成分。此行包含3个单精度浮点参数。 |
map_Kd | 指定最后建立的材质的反射贴图。如果此行的第一个参数为字符串”-s”,则此行将包括5个参数。其中第二、第三和第四个参数为此纹理贴图在U、V、W方向的缩放值,第五个参数为纹理图片的文件名。如果第一个字符串不是”-s”,那么第一个参数就是此纹理图片的文件名。 |
map_Ks | 指定最后建立的材质的镜面贴图。如果此行的第一个参数为字符串”-s”,则此行将包括5个参数。其中第二、第三和第四个参数为此纹理贴图在U、V、W方向的缩放值,第五个参数为纹理图片的文件名。如果第一个字符串不是”-s”,那么第一个参数就是此纹理图片的文件名。 |
由这些信息,我们就足以写一个类,用于读取和渲染OBJ模型了。
7.4 读取OBJ模型
为了保存这些模型数据,你最好建立6个动态数组,分别存储顶点数据、顶点索引、法线数据、法线索引、纹理坐标数据、纹理坐标索引。读取文件时,把文件中的数据存入数组中。
要读取一个文本文件,我们可以使用Delphi中自带的TStringList类。TStringList的用法十分简单,创建实例之后,执行 TStringList.LoadFromFile(FileName)就可以载入文本文件了。
载入完之后,可以使用TStringList的Strings属性来获取文本文件每一行的值。如
Line5:=StringList.Strings[4]; //读取第5行
载入模型的重点是读取这些数据,我们可以建立一个循环,循环判别每一行的前缀,然后根据不同的前缀执行不同的操作。你可以这样写代码:
procedure LoadFromFile(FileName:String);
var i:Integer; sl:TStringList; prefix:String;
begin
sl:=TStringList.Create;
for i := 0 to sl.Count-1 do
begin
prefix := ReadPrefix(SL.Strings[i]);
if prefix = ‘v’ then //处理顶点数据
… //用于读取顶点数据的代码
else if prefix = ‘vt’ then //处理纹理坐标数据
… //用于读取纹理坐标数据的代码
… //处理其他前缀行
end;
sl.Free;
end;
此外,你应该建立一个函数用于分割和提取每一行中的各个参数。对于这个函数,你可以参考下面的代码。
function GetSubString(Line: String; ValueCount: Integer): String;
//ValueCount参数指定要提取第几个词,如果是0则表示提取前缀。
var fP,eP:Integer; vn:Integer;
i:Integer;
function WordCount(Line:String):Integer; //读取此行中的单词个数
var i :Integer;
begin
Line:=' '+Line;
Result:=0;
for i := 1 to Length(Line)-1 do
begin
if (Line[i]=' ') and (Line[i+1]<>' ') then
Result := Result+1;
end;
end;
begin
vn:=0;
fp:=1;
ep:=0;
Line:=Line+' ';
if ValueCount>0 then
for i := 2 to Length(Line)-1 do
begin
if (Line[i+1]<>' ')and(Line[i] = ' ') then
Inc(vn);
if vn=ValueCount then
begin
fp := i+1;
Break;
end;
end;
if ValueCount在OBJ文件中,与其关联的MTL文件是由一个以mtllib开头的行指定的。当读到mtllib时,你应该开始载入mtllib行所指定的MTL文件。你可以参考下面的做法:
首先,定义
TMaterial = record
Name:String; //名称
TextureFile:String; //纹理路径
Texture:THyTexture; //这就是第6章我们创建的纹理贴图类
Tiling:T3DVector; //纹理缩放因子
Ambient,Diffuse,Specular:array[1..3] of Single; //材质
end;TMaterials = array of TMaterial;
用于存储所有的材质。然后使用下面的代码来读取MTL文件:
7.7 TObjMdl类的完整代码
以下代码已包含在示例程序中。其中引用的单元(GLInit,RapidUI)全部可以在http://program.stedu.net上下载。
unit untObjModel;
interface
uses Classes, SysUtils, OpenGL, GLinit, RapidUI;
type
T3DVector = record
X,Y,Z:Single;
end;
T2DVector = record
X,Y:Single;
end;
TFace = record
MaterialName:String;
HasNormal:Boolean;
NormalInfo:record case Integer of
1:(NormalIdx:array[1..3] of Integer);
2:(Normal:T3DVector);
end;
Vertices:array[1..3] of Integer;
TexCoords:array[1..3] of Integer;
end;
TMaterial = record
Name:String;
TextureFile:String;
Texture:THyTexture;
Tiling:T3DVector;
Ambient,Diffuse,Specular:array[1..3] of Single;
end;
TMaterials = array of TMaterial;
TObjModel = class(TObject)
private
Vertices:array of T3DVector;
Normals: array of T3DVector;
TexCoords: array of T2DVector;
Faces:array of TFace;
Materials:TMaterials;
FMaterialCount:Integer;
FHasNormals: Boolean;
FTextured: Boolean;
FVertexCount,FFaceCount,FNormalCount,FTexCoordCount:Integer;
Textures : array of THyTexture;
TextureCount:Integer;
CommingMaterialName:String;
procedure ReadLine(Line:String);
procedure ProcessMtl(Line:String);
procedure ReadVertex(Line:String);
procedure ReadNormal(Line:String);
procedure ReadTexCoord(Line:String);
procedure ReadFace(Line:String);
procedure LoadMaterials(FileName:String);
procedure ReadMTLine(Line:String);
function GetLinePrefix(Line:String):String;
function GetSubString(Line:String;ValueCount:Integer):String;
function GetSingleValue(Line:String;ValueCount:Integer):Single;
function GetIntegerValue(Line:String;ValueCount:Integer):Integer;
function UseMaterial(Name: String):Integer;
public
constructor Create;
procedure LoadFromFile(FileName:String);
procedure Render;
procedure FreeModel;
procedure Free;
property HasNormals:Boolean read FHasNormals;
property Textured:Boolean read FTextured;
end;
function CalcTriangleNormal(P1,P2,P3:T3DVector):T3DVector;
implementation
function CalcTriangleNormal(P1,P2,P3:T3DVector):T3DVector;
var V1,V2:T3DVector;
function GenVector(EndPos,StartPos:T3DVector):T3DVector;
begin
result.X := EndPos.X - StartPos.X;
Result.Y := EndPos.Y - StartPos.Y;
Result.Z := EndPos.Z - StartPos.Z;
end;
function VectorCross(U,V:T3DVector):T3DVector;
begin
Result.X := U.Y*V.Z - U.Z*V.Y;
Result.Y := U.Z*V.X - U.X*V.Z;
Result.Z := U.X*V.y - U.Y*V.X
end;
function VectorMulLanda(Landa:Single;V:T3DVector):T3DVector;
begin
result.X := Landa*V.X;
Result.Y := Landa*V.Y;
Result.Z := Landa*V.Z;
end;
function VectorLength(V:T3DVector):Single;
begin
Result := SQRT(V.X*V.X + V.Y *V.Y + V.Z *V.Z);
end;
function Normalize(V:T3DVector):T3DVector;
begin
Result := VectorMulLanda(1/VectorLength(V),V);
end;
begin
V1:=GenVector(P2,P1);
V2:=GenVector(P3,P1);
Result := Normalize(VectorCross(v1,v2));
end;
{ TObjModel }
constructor TObjModel.Create;
begin
FNormalCount:=0;
FVertexCount:=0;
FFaceCount:=0;
FTexCoordCount:=0;
end;
procedure TObjModel.Free;
var i :Integer;
begin
SetLength(Vertices,0);
SetLength(Normals,0);
SetLength(TexCoords,0);
SetLength(Faces,0);
for i := 0 to TextureCount-1 do
Textures[i].Free;
end;
function TObjModel.GetIntegerValue(Line: String;
ValueCount: Integer): Integer;
var Vs:String;
begin
vs:=GetSubString(Line,ValueCount);
Result := StrToInt(vs);
end;
function TObjModel.GetLinePrefix(Line: String): String;
begin
Result := GetSubString(Line,0);
end;
function TObjModel.GetSingleValue(Line: String;
ValueCount: Integer): Single;
var vs:String; V:Single;Code:Integer;
begin
vs:=GetSubString(Line,ValueCount);
Val(vs,V,code);
if Code = 0 then
result := V
else
Result:=0;
end;
function TObjModel.GetSubString(Line: String; ValueCount: Integer): String;
var fP,eP:Integer; vn:Integer;
i:Integer;
function WordCount(Line:String):Integer;
var i :Integer;
begin
Line:=' '+Line;
Result:=0;
for i := 1 to Length(Line)-1 do
begin
if (Line[i]=' ') and (Line[i+1]<>' ') then
Result := Result+1;
end;
end;
begin
vn:=0;
fp:=1;
ep:=0;
Line:=Line+' ';
if ValueCount>0 then
for i := 2 to Length(Line)-1 do
begin
if (Line[i+1]<>' ')and(Line[i] = ' ') then
Inc(vn);
if vn=ValueCount then
begin
fp := i+1;
Break;
end;
end;
if ValueCount0);
FTextured := (TextureCount>0);
for i := 0 to FFaceCount-1 do
begin
if not Faces[i].HasNormal then
begin
Faces[i].NormalInfo.Normal := CalcTriangleNormal(
Vertices[Faces[i].Vertices[1]-1],Vertices[Faces[i].Vertices[2]-1],
Vertices[Faces[i].Vertices[3]-1]);
end;
end;
finally
Sl.Free;
end;
end;
procedure TObjModel.LoadMaterials(FileName: String);
var sl:TStringList;
i:Integer;
begin
sl:=TStringList.Create;
try
sl.LoadFromFile(FileName);
for i := 0 to sl.Count-1 do
begin
ReadMTLine(sl.Strings[i]);
end;
finally
Sl.Free;
end;
end;
procedure TObjModel.ProcessMtl(Line: String);
begin
CommingMaterialName:=GetSubString(Line,1);
end;
procedure TObjModel.ReadFace(Line: String);
var i,j:Integer; subS:String;
vc:Integer;
nvs:array[1..3] of String;
function GetValueCount(S:String):Integer;
var i:Integer;
begin
Result := 1;
for i := 1 to Length(S) do
begin
if S[i] = '/' then
Inc(Result);
end;
end;
function GetValue(S:String;n:Integer):String;
var i,fp:Integer;ep:Integer;sc:Integer;sp:array[1..2] of Integer;
begin
fp := 1;
ep := 0;
sc := 0;
for i := 1 to Length(S) do
begin
if S[i]='/' then
begin
inc(SC);
sp[sc] := i;
end;
end;
if n=1 then
begin
Result := Copy(s,1,sp[1]-1);
end
else if n=2 then
begin
Result := Copy(s,sp[1]+1,sp[2]-sp[1]-1);
end
else if n= 3 then
begin
Result := Copy(s,sp[2]+1,Length(s)-sp[2]);
end;
end;
begin
Inc(FFaceCount);
if FFaceCount-1>High(Faces) then
SetLength(Faces,FFaceCount+50);
if CommingMaterialName<>'' then
begin
Faces[FFaceCount-1].MaterialName := CommingMaterialName;
CommingMaterialName:='';
end;
for i := 1 to 3 do
begin
SubS:=GetSubString(Line,i);
vc := GetValueCount(SubS);
for j := 1 to vc do
begin
nvs[j]:= GetValue(SubS,j);
if nvs[j]<>'' then
begin
if j= 1 then
begin
Faces[FFaceCount-1].Vertices[i] := StrToInt(Nvs[1]);
end
else
if j=2 then
begin
Faces[FFaceCount-1].TexCoords[i] := StrToInt(Nvs[2]);
end
else
begin
Faces[FFaceCount-1].HasNormal :=True;
Faces[FFaceCount-1].NormalInfo.NormalIdx[i] := StrToInt(Nvs[3]);
end;
end;
end;
end;
end;
procedure TObjModel.ReadLine(Line: String);
var fp:String;
begin
fp:=GetLinePrefix(Line);
if fp = 'mtllib' then
LoadMaterials(GetSubString(Line,1))
else
if fp = 'v' then
ReadVertex(Line)
else
if fp = 'vn' then
ReadNormal(Line)
else
if fp = 'vt' then
ReadTexCoord(Line)
else
if fp = 'f' then
ReadFace(Line)
else
if fp = 'usemtl' then
ProcessMtl(Line);
end;
procedure TObjModel.ReadMTLine(Line: String);
var pf:String;
begin
pf := Lowercase(GetLinePreFix(Line));
if pf = 'newmtl' then
begin
Inc(FMaterialCount);
SetLength(Materials,FMaterialCount);
Materials[FMaterialCount-1].Name := GetSubString(Line,1);
end
else if pf = 'ka' then
begin
Materials[FMaterialCount-1].Ambient[1] := GetSingleValue(Line,1);
Materials[FMaterialCount-1].Ambient[2] := GetSingleValue(Line,2);
Materials[FMaterialCount-1].Ambient[3] := GetSingleValue(Line,3);
end
else if pf = 'kd' then
begin
Materials[FMaterialCount-1].Diffuse[1] := GetSingleValue(Line,1);
Materials[FMaterialCount-1].Diffuse[2] := GetSingleValue(Line,2);
Materials[FMaterialCount-1].Diffuse[3] := GetSingleValue(Line,3);
end
else if pf = 'ks' then
begin
Materials[FMaterialCount-1].Specular[1] := GetSingleValue(Line,1);
Materials[FMaterialCount-1].Specular[2] := GetSingleValue(Line,2);
Materials[FMaterialCount-1].Specular[3] := GetSingleValue(Line,3);
end
else if pf = 'txt' then
begin
Materials[FMaterialCount-1].TextureFile := GetSubString(Line,1);
if FileExists(Materials[FMaterialCount-1].TextureFile) then
begin
Inc(TextureCount);
SetLength(Textures,TextureCount);
Textures[TextureCount-1]:=THyTexture.Create;
Textures[TextureCount-1].LoadFromFile(Materials[FMaterialCount-1].TextureFile);
Materials[FMaterialCount-1].Texture := Textures[TextureCount-1];
end
else
Materials[FMaterialCount-1].TextureFile := '';
end
else if pf = 'map_kd' then
begin
if GetSubString(Line,1) = '-s' then
begin
Materials[FMaterialCount-1].Tiling.X:= GetSingleValue(Line,2);
Materials[FMaterialCount-1].Tiling.Y:= GetSingleValue(Line,3);
Materials[FMaterialCount-1].Tiling.Z:= GetSingleValue(Line,4);
ReadMTLine('txt ' + GetSubString(Line,5));
end
else
begin
Materials[FMaterialCount-1].Tiling.X:= 1;
Materials[FMaterialCount-1].Tiling.Y:= 1;
Materials[FMaterialCount-1].Tiling.Z:= 1;
ReadMTLine('txt ' + GetSubString(Line,1));
end;
end;
end;
procedure TObjModel.ReadNormal(Line: String);
begin
Inc(FNormalCount);
if FNormalCount-1>High(Normals) then
SetLength(Normals,FNormalCount+50);
Normals[FNormalCount-1].X := GetSingleValue(Line,1);
Normals[FNormalCount-1].Y := GetSingleValue(Line,2);
Normals[FNormalCount-1].Z := GetSingleValue(Line,3);
end;
procedure TObjModel.ReadTexCoord(Line: String);
begin
Inc(FTexCoordCount);
if FTexCoordCount-1>High(TexCoords) then
SetLength(TexCoords,FTexCoordCount+50);
TexCoords[FTexCoordCount-1].X := GetSingleValue(Line,1);
TexCoords[FTexCoordCount-1].Y := GetSingleValue(Line,2);
end;
procedure TObjModel.ReadVertex(Line: String);
begin
Inc(FVertexCount);
if FVertexCount-1>High(Vertices) then
SetLength(Vertices,FVertexCount+50);
Vertices[FVertexCount-1].X := GetSingleValue(Line,1);
Vertices[FVertexCount-1].Y := GetSingleValue(Line,2);
Vertices[FVertexCount-1].Z := GetSingleValue(Line,3);
end;
function TObjModel.UseMaterial(Name:String):Integer;
var idx,i:Integer;
begin
for i := 0 to FMaterialCount-1 do
begin
if Materials[i].Name = Name then
begin
idx:=i;
Result :=i;
break;
end;
end;
if Materials[idx].TextureFile <>'' then Materials[idx].Texture.Bind;
glMaterialfv(GL_FRONT,GL_AMBIENT,@Materials[idx].Ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,@Materials[idx].Diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,@Materials[idx].Specular);
end;
procedure TObjModel.Render;
var i,j:Integer; CurMtlIdx:Integer; curMtlTiling:T3DVector;
begin
for i := 0 to FFaceCount -1 do
begin
curMtlTiling.X:=1;
curMtlTiling.Y:=1;
curMtlTiling.Z:=1;
if Faces[i].MaterialName <>'' then
begin
CurMtlIdx:=UseMaterial(Faces[i].MaterialName);
curMtlTiling:=Materials[CurMtlIdx].Tiling;
end;
glBegin(GL_TRIANGLES);
for j := 1 to 3 do
begin
if Faces[i].HasNormal then
glNormal3fv(@Normals[Faces[i].NormalInfo.NormalIdx[j]-1])
else
glNormal3fv(@Faces[i].NormalInfo.Normal);
if FTextured then
glTexCoord2d(TexCoords[Faces[i].TexCoords[j]-1].X*curMtlTiling.X,
TexCoords[Faces[i].TexCoords[j]-1].Y*curMtlTiling.Y);
glVertex3fv(@Vertices[Faces[i].Vertices[j]-1]);
end;
glEnd;
end;
end;
procedure TObjModel.FreeModel;
var i : Integer;
begin
for i := 0 to TextureCount-1 do
begin
Textures[i].Free;
end;
FNormalCount:=0;
FVertexCount:=0;
FFaceCount:=0;
TextureCount:=0;
FTexCoordCount:=0;
FMaterialCount:=0;
SetLength(Normals,FNormalCount);
SetLength(Vertices,FVertexCount);
SetLength(Faces,FFaceCount);
SetLength(TexCoords,FTexCoordCount);
SetLength(Materials,FMaterialCount);
SetLength(Textures,TextureCount);
end;
end.