第三版本相较于前两个版本,添加了对 f 1//1 类型的支持(面 由点坐标和法向量组成),以及对mtl文件的读取。
准备工作,准备一个结构体来保存材质信息,然后由vector保存即可
struct mMaterial
{
float Ns; //shinness
float Ka[3];
float Kd[3];
float Ks[3];
string mTextureName;
GLuint mTextureId;
};
ka,kd,ks即相应的环境光,漫反射及镜面反射的参数,mTextureName保存着该材质的名称,由mtl文件读入,mTextureId即纹理贴图。
objModel类新加入一个成员变量mtlName,用于存储材质名称,方便从vector中查询材质信息。
class objModel :public List
{
public:
objModel() {}
void loadObjModel(const char*);
void print();
void objDraw();
private:
vector normal, texcoord, position; //vector容器存储法线,纹理坐标及点的位置信息
vector face;
int vertnum;
int coordnum;
int nornum;
std::string mtlName;
};
修改loadObjModel函数,使其能够对mtl文件进行读写。定义一个全局变量来存储mtl信息,std::vector
CreateTexture函数自己实现就好了。
void objModel::loadObjModel(const char* objFileName)
{
int nFileSize = 0;
unsigned char* fileContent = LoadFileContent(objFileName, nFileSize); //读取文件内容
if (fileContent == nullptr) //文件为空
{
return;
}
objModel* tempModel = new objModel;
stringstream ssFileContent((char*)fileContent); //流读取文件内容
string temp; //接受无关信息
char szoneLine[256]; //读取一行的数据
while (!ssFileContent.eof())
{
memset(szoneLine, 0, 256); // 每次循环初始化数组szoneLine
ssFileContent.getline(szoneLine, 256); //流读取一行
if (strlen(szoneLine) > 0) //该行不为空
{
if (szoneLine[0] == 'v') //v开头的数据
{
stringstream ssOneLine(szoneLine); //数据存储到流中 方便赋值
if (szoneLine[1] == 't') //纹理信息
{
ssOneLine >> temp; //接受标识符 vt
Float3 tempTexcoord;
ssOneLine >> tempTexcoord.Data[0] >> tempTexcoord.Data[1]; //数据存入临时变量中
tempModel->texcoord.push_back(tempTexcoord); //存入容器
}
if (szoneLine[1] == 'n') //法线信息
{
ssOneLine >> temp; //接收标识符vn
Float3 tempNormal;
ssOneLine >> tempNormal.Data[0] >> tempNormal.Data[1] >> tempNormal.Data[2];
tempModel->normal.push_back(tempNormal);
}
else //点的位置信息
{
ssOneLine >> temp;
Float3 tempLocation;
ssOneLine >> tempLocation.Data[0] >> tempLocation.Data[1] >> tempLocation.Data[2];
tempModel->position.push_back(tempLocation);
}
}
else if (szoneLine[0] == 'f') //面信息
{
stringstream ssOneLine(szoneLine); //流读取一行数据
ssOneLine >> temp; //接收标识符f
// f信息 exp: f 1/1/1 2/2/2 3/3/3 位置索引/纹理索引/法线索引 三角面片 三个点构成一个面
string vertexStr; //接收流的内容
Face tempFace;
for (int i = 0; i < 3; ++i) //每个面三个点
{
ssOneLine >> vertexStr; //从流中读取点的索引信息
size_t pos = vertexStr.find_first_of('/'); //找到第一个/的位置 //即找到点的位置信息
string locIndexStr = vertexStr.substr(0, pos); //赋值点的位置信息
size_t pos2 = vertexStr.find_first_of('/', pos + 1); //找到第二个/ 即找到点的纹理坐标信息
if (pos2 - pos == 1)
{
string norIndexStr = vertexStr.substr(pos2 + 1, vertexStr.length() - pos2 - 1);
tempFace.vertex[i][0] = atoi(locIndexStr.c_str()); //将索引信息从 srting转换为 int //位置索引信息赋值
tempFace.vertex[i][2] = atoi(norIndexStr.c_str());
tempModel->coordnum = 0;
}
else
{
string texIndexSrt = vertexStr.substr(pos + 1, pos2 - 1 - pos); //赋值点的纹理坐标信息
string norIndexSrt = vertexStr.substr(pos2 + 1, vertexStr.length() - 1 - pos2); //赋值点的法线信息
tempFace.vertex[i][0] = atoi(locIndexStr.c_str()); //将索引信息从 srting转换为 int //位置索引信息赋值
tempFace.vertex[i][1] = atoi(texIndexSrt.c_str()); //纹理坐标索引信息赋值
tempFace.vertex[i][2] = atoi(norIndexSrt.c_str()); //法线信息赋值
}
}
tempModel->face.push_back(tempFace);
}
else if (szoneLine[0] == '#') //处理注释内容
{
stringstream ssOneLine(szoneLine); //流读取一行数据
ssOneLine >> temp; //接收#
string numtemp;
while (ssOneLine)
{
ssOneLine >> temp;
if (temp == "faces")
{
PushBack(tempModel);
tempModel = new objModel;
break;
}
else if (temp == "vertices")
{
tempModel->vertnum = atoi(numtemp.c_str());
break;
}
else if (temp == "texture")
{
tempModel->coordnum = atoi(numtemp.c_str());
break;
}
else if (temp == "vertex")
{
tempModel->nornum = atoi(numtemp.c_str());
break;
}
numtemp = temp;
}
}
else if (szoneLine[0] == 'u')
{
stringstream ssOneLine(szoneLine);
ssOneLine >> temp >> temp; //temp接收材质名
tempModel->mtlName = temp;
}
else if (szoneLine[0] == 'm') //处理材质库
{
mMaterial *tempMtl=new mMaterial;
stringstream ssOneLine(szoneLine);
ssOneLine >> temp; //接受mtllib
ssOneLine >> temp; //接收材质名
string fileName = "Res/" + temp;
const char* mtlFileName = fileName.c_str();
fstream mtlFile;
mtlFile.open(mtlFileName);
if (mtlFile.fail())
cout << "mtl file open fail" << std::endl;
while (!mtlFile.eof())
{
string temp;
mtlFile >> temp;
if (temp == "newmtl")
{
mtlFile >> tempMtl->mTextureName;
}
else if (temp == "Ns")
{
mtlFile >> tempMtl->Ns;
}
else if (temp == "Ka")
{
mtlFile >> tempMtl->Ka[0] >> tempMtl->Ka[1] >> tempMtl->Ka[2];
}
else if (temp == "Kd")
{
mtlFile >> tempMtl->Kd[0] >> tempMtl->Kd[1] >> tempMtl->Kd[2];
}
else if (temp == "Ks")
{
mtlFile >> tempMtl->Ks[0] >> tempMtl->Ks[1] >> tempMtl->Ks[2];
}
else if (temp == "map_Kd")
{
mtlFile >> temp;
size_t pos = temp.find_last_of('\\');
temp = temp.substr(pos + 1, temp.length() - 1 - pos);
string tempF = "Res/" + temp;
const char* texFile = tempF.c_str();
tempMtl->mTextureId = CreateTexture(texFile);
mMtl.push_back(*tempMtl);
tempMtl = new mMaterial;
}
}
}
}//end 该行非空
}//end while
delete tempModel;
delete fileContent;
}
对照obj文件,及mtl文件,代码很容易理解了,不多赘述。
需要注意的就是 代码中的 string fileName = "Res/" + temp; 。因为在我的项目中,所有的资源文件都保存在了Res文件下,所以我文件前缀为Res/,大家在使用代码的时候,根据需求自行修改即可,也可以将其作为变量传入函数,随时修改前缀即可。
下面进行objDraw的修改,使其能够处理材质信息。
void objModel::objDraw()
{
glEnable(GL_TEXTURE_2D);
if (position.size() != 0)
{
if (mtlName.length()!=0)
{
for (auto i = mMtl.begin(); i < mMtl.end(); ++i)
{
if (i->mTextureName == mtlName)
{
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, i->Ka);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, i->Kd);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, i->Ks);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, i->Ns);
glBindTexture(GL_TEXTURE_2D, i->mTextureId);
break;
}
}
}
glBegin(GL_TRIANGLES);
if (coordnum == 0)
{
for (unsigned int i = 0; i < face.size(); ++i)
{
//第一个点的法线,位置信息
glNormal3fv(normal[face[i].vertex[0][2] - 1 - nnum].Data);
glVertex3fv(position[face[i].vertex[0][0] - 1 - vnum].Data);
//第二个点的法线,位置信息
glNormal3fv(normal[face[i].vertex[1][2] - 1 - nnum].Data);
glVertex3fv(position[face[i].vertex[1][0] - 1 - vnum].Data);
//第三个点的法线,位置信息
glNormal3fv(normal[face[i].vertex[2][2] - 1 - nnum].Data);
glVertex3fv(position[face[i].vertex[2][0] - 1 - vnum].Data);
}
}
else
{
for (unsigned int i = 0; i < face.size(); ++i) //循环遍历face信息
{
//第一个点的法线,纹理,位置信息
glNormal3fv(normal[face[i].vertex[0][2] - 1 - nnum].Data);
glTexCoord2fv(texcoord[face[i].vertex[0][1] - 1 - cnum].Data);
glVertex3fv(position[face[i].vertex[0][0] - 1 - vnum].Data);
//第二个点的法线,纹理,位置信息
glNormal3fv(normal[face[i].vertex[1][2] - 1 - nnum].Data);
glTexCoord2fv(texcoord[face[i].vertex[1][1] - 1 - cnum].Data);
glVertex3fv(position[face[i].vertex[1][0] - 1 - vnum].Data);
//第三个点的法线,纹理,位置信息
glNormal3fv(normal[face[i].vertex[2][2] - 1 - nnum].Data);
glTexCoord2fv(texcoord[face[i].vertex[2][1] - 1 - cnum].Data);
glVertex3fv(position[face[i].vertex[2][0] - 1 - vnum].Data);
}
}
glEnd();
}
vnum += vertnum;
cnum += coordnum;
nnum += nornum;//cout << "end model" << std::endl;
if (Next() != nullptr)
{
Next()->objDraw();
}
else
return;
}
相当简单的修改,从vector中查找该材质信息,进行设置即可。
效果图:
没办法,建模功底差的要死,所以效果图很惨。但是代码是没问题的