opengl读取obj模型并绘制2.0

此2.0版本是相较于我第一篇读取的文章而言的,第一篇文章只是一个简单的读取绘制程序,只能绘制单一物体,不实用。

2.0版本采用链式方式,绘制多个物体。链式方法的好处就是能够方便的添加材质数据(本文未讲),能够更好的实现其效果。

首先进行准备工作,数据类型的定义及基类(链表类)的定义

struct Float3
{
	float Data[3];         //三个float元素的结构体,用于存储数据
};

struct Face
{
	int vertex[3][3];       //三个点构成一个面  每个点有三个索引信息
};

class List                               //链表操作
{
public:
	List() :mNext(nullptr) {}
	void* mNext;                       //下一个节点
	template
	T* Next()                            //得到下一个节点
	{
		return(T*)mNext;
	}
	void PushBack(List*);              //链表尾部插入节点
};

用链表类去派生出objModel类,也可以利用该链表类派生其他类,方便以后操作。

下面就是objModel类的定义

class objModel :public List
{
public:
	objModel() {}
	void loadObjModel(const char*);   //加载obj模型文件
	void objDraw();                   //绘制obj模型
private:
	vector normal, texcoord, position;     //vector容器存储法线,纹理坐标及点的位置信息
	vector face;       //储存面信息
	int vertnum;             //储存 点的个数
	int coordnum;            //储存纹理坐标个数
};

需要注意的就是点的个数和纹理坐标个数的储存。因为obj模型文件的face索引不是说每个物体都从1号索引开始,而是紧接着上一个物体去索引。但是vector库的索引需要从0号开始,故要减去相应的个数。本人一开始没注意face的索引数据,总是导致vector越界,最终才发现是face索引数据我没搞懂,遂加入个数来解决此问题。

下面就是List类的实现,即链表的插入操作。

void List::PushBack(List* node)
{
	List* mNode = this;
	while (mNode->mNext != nullptr)
	{
		mNode = mNode->Next();
	}
	mNode->mNext = node;

}

objModel类的实现,需要注意的就是添加了对#信息的处理,及点/纹理个数,以及创建新的物体,并插入链表。其余的与第一篇文章无异。

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);   //找到第二个/   即找到点的纹理坐标信息
					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;
					}
					numtemp = temp;

				}
			}
		}//end 该行非空
	}//end while
	delete tempModel;
	delete fileContent;
}

objModel绘制函数的实现

void objModel::objDraw()
{
	if (position.size() != 0)
	{
		glBegin(GL_TRIANGLES);
		for (unsigned int i = 0; i < face.size(); ++i)         //循环遍历face信息
		{

			//第一个点的法线,纹理,位置信息
			glNormal3fv(normal[face[i].vertex[0][2] - 1 - vnum].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 - vnum].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 - vnum].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;
	if (Next() != nullptr)
	{
		Next()->objDraw();
	}
	else
		return;
}

绘制前定义两个变量,vnum(存储点/法线的个数),cnum(存储纹理坐标的个数),并初始化为0,绘制完成后重新赋值为0;

下面是绘制结果,本文没有加纹理,所以就使用线框来绘制,纹理及材质信息的完善要通过读取mtl文件来实现。

opengl读取obj模型并绘制2.0_第1张图片

你可能感兴趣的:(opengl读取obj模型并绘制2.0)