zoukankan      html  css  js  c++  java
  • Focus On 3D Model之OBJ格式加载

          加载模型在我看来从来都不是一件容易的事情,尤其是我学习3D是从D3D入手的,模型加载直接一个函数就搞定了,对于内部细节从来都没有深入研究过。 .x文件用的比较少,所以对其理解也很肤浅。

          这本书使用的渲染API是OpenGL,对于习惯使用D3D的读者来书,这可能是件比较头大的事情。在读这本书之前,我稍微补了补OpenGL的基础,发现就以经完全够用了。所以D3D党完全不用担心,更何况懂点OpenGL也不是坏事,在学点OpenGL之后,我才明白原来的自己是多么的一叶障目,因为一种API就放弃一本书,太幼稚了。

    1. OBJ格式解释浅析:

           言归正传,我们本节介绍的是OBJ文件模型加载,本节主要加载OBJ文件的4种内容,分别是顶点坐标,纹理坐标,法线以及面。

           这四种信息在OBJ文件中表示如下(以下内容只显示部分OBJ文件):

          #This is an obj 3d model file

           v -0.134214 1.696017 -0.152522   //这行是顶点

           v -0.128631 1.666882 -0.149329   //这行是顶点

     

           vn -0.456288 0.869882 0.138733   //这行是法线

           vn -0.460052 0.847785 0.244615   //这行是法线

     

           f 1//1 2//2 3//3                  //这行是三角形面

           f 3//3 4//4 1//1                  //这行是三角形面

           #end of file

           顶点和法线永远只有一种格式,如上。但是三角形面存在有好几种格式。表述面的行的通用格式一般为:

           f 1/2/3 4/5/6 7/8/9

           这行的意思就是:

    1. 该面由顶点数组中的第1个,第4个,第7个顶点构成,注意在写代码的时候注意,对应数组中的索引应该是0,3,6,因为OBJ中的索引好是从1开始的,而计算机语言种数组中是从0开始的,一般在赋值的时候会减1。
    2. 该面的顶点对应的纹理坐标是由纹理坐标数组种的第2个,第5个,第8个元素组成,注意在写代码的时候注意,对应数组中的索引应该是1,4,7,因为OBJ中的索引好是从1开始的,而计算机语言种数组中是从0开始的,一般在赋值的时候会减1。
    3. 该面的顶点对应的法线是由法线数组种的第3个,第6个,第9个元素组成,意在写代码的时候注意,对应数组中的索引应该是2,5,8,因为OBJ中的索引好是从1开始的,而计算机语言种数组中是从0开始的,一般在赋值的时候会减1。

            也就是说,对应三角形面行的含义如下:

                  f                                1/2/3                                             4/5/6                                              7/8/9

            面描述符    顶点索引1/纹理坐标索引1/法线索引1     顶点索引2/纹理坐标索引2/法线索引2    顶点索引3/纹理坐标索引3/法线索引3

            这样,其他几种面的描述方式也就很容易理解了:

            f 1 2 3              //只有顶点索引

            f 1/4 2/5 3/6         //顶点索引+纹理坐标索引

            f 1/4/7 2/5/8 3/6/9    //顶点索引+纹理坐标索引+法线索引

            f 1//4 2//5 3//6       //顶点索引+法线索引

            OBJ格式解释就到这里。

    2. OBJ格式加载:    

          OBJ加载类维护了4个向量,其中分别是点,纹理坐标,法线,面,其中点、纹理坐标、法线都是以buffer块的形式存储的,面中存储的是点,纹理坐标以及法线的索引,每次在绘制面的时候,跟面中所给的三个索引值,分别在buffer块中查找对应的点、纹理坐标以及法线值,然后传入OpenGL流水线,进行绘制,整个加载函数代码如下:

            加载思路:1.  每次加载一个字符,

                           2.  如果是该字符是'v',则该行可能是‘vt’,‘vn’也可能是‘v’需进一步判断。

              2.1  读入第二个字符;

              2.2  如果字符是‘t’,则读取纹理坐标;并设置包含纹理的bool变量为true;

                                2.2  如果字符是‘n’  则读取法线;并设置包含法线的bool变量为true;

                                2.3  如果字符是‘  ‘  则读取顶点;

                                2.4  如果不以上都不是,读取该行,并不做处理(认为是丢弃了);

                           3.  如果是'f'开头,则肯定是面数据行:

                                3.1  判断是否包含法线及纹理坐标:

                                3.2  均含,则读取纹理坐标索引 与 法线索引 + 顶点索引; 

                                3.3  含纹理索引不含法线索引则读取 纹理索引 + 顶点索引;

                                3.4  不含纹理索引含法线索引则读取 法线索引 + 顶点索引;

                                3.5  均不包含,则只读取顶点索引;

                            4.  返回1,继续循环,直到文件结束。

                            5.  讲维护的四个向量的首地址赋值给四个将要进行绘制用的数据指针(方便OpenGL调用)。

                            完。

    void COBJ::Load(char * ModelName)
    {
    	FILE * fp;
    	if(NULL == (fp = fopen(ModelName,"rt")))
    	{
    		cout<<"Open Obj Model File Failed"<<endl;
    		return;
    	}
    
    	while (!feof(fp))
    	{
    		char LineBuffer[256];   //store every line of the obj file
    		char iFirst, iNext;     //store the 1st and 2nd letter of the line
    
    		float fTemp[3] = {0.0f, 0.0f, 0.0f};
    
    		iFirst = fgetc(fp);
    		if (iFirst == 'v')
    		{
    			iNext = fgetc(fp);
    			if (iNext == ' '||iNext == '	')    //test if the 2nd letter is space, if yes, then the line is vertex coordinate
    			{
    				fgets(LineBuffer, 256, fp);
    				sscanf(LineBuffer, " %f %f %f", &fTemp[0], &fTemp[1], &fTemp[2]);
    				m_VertexBuffer.push_back(fTemp);
    			}
    
    			else if (iNext == 't') //test if the 2nd letter is 't', if yes, then the texture coordinate
    			{
    				fgets(LineBuffer, 256, fp);
    				sscanf(LineBuffer, " %f %f", &fTemp[0], &fTemp[1]);
    				m_TexcoordBuffer.push_back(fTemp);
    				isThereTexture = true;
    			}
    
    			else if (iNext == 'n') //test if the 2nd letter is 'n', if yes, then the normal
    			{
    				fgets(LineBuffer, 256, fp);
    				sscanf(LineBuffer, " %f %f %f", &fTemp[0], &fTemp[1], &fTemp[2]);
    				m_NormalBuffer.push_back(fTemp);
    				isThereNormal = true;
    			}
    			else                   //we do not deal with such situation.
    			{
    				fgets(LineBuffer, 256, fp);
    			}
    		}
    		else if (iFirst == 'f')
    		{
    			int temp[3][3];
    
    			if(isThereTexture && isThereNormal)       //both texture and normal
    			{
    				fgets(LineBuffer, 256, fp);
    				sscanf(LineBuffer, " %d/%d/%d %d/%d/%d %d/%d/%d",&temp[0][0],&temp[1][0],&temp[2][0],
    				                                                 &temp[0][1],&temp[1][1],&temp[2][1],
    																 &temp[0][2],&temp[1][2],&temp[2][2]);
    			    m_FaceBuffer.push_back(&temp[0][0]);
    			}
    			else if (!isThereTexture && isThereNormal) //only normal
    			{
    				fgets(LineBuffer, 256, fp);
    				sscanf(LineBuffer, " %d//%d %d//%d %d//%d",&temp[0][0],&temp[2][0],
    														   &temp[0][1],&temp[2][1],
    														   &temp[0][2],&temp[2][2]);
    				m_FaceBuffer.push_back(&temp[0][0]);
    			}
    			else if ( isThereTexture && !isThereNormal) //only texture
    			{
    				fgets(LineBuffer, 256, fp);
    				sscanf(LineBuffer, " %d/%d %d/%d %d/%d",&temp[0][0],&temp[1][0],
    				                                        &temp[0][1],&temp[1][1],
    				                                        &temp[0][2],&temp[1][2]);
    				m_FaceBuffer.push_back(&temp[0][0]);
    			}
    			else                                       //neither texture nor normal, only vertex
    			{
    				fgets(LineBuffer, 256, fp);
    				sscanf(LineBuffer, " %d %d %d",&temp[0][0],
    											   &temp[0][1],
    											   &temp[0][2]);
    				m_FaceBuffer.push_back(&temp[0][0]);
    			}
    		}
    		else
    		{
    			fgets(LineBuffer, 256, fp);
    		}
    	}
    	m_pVertex   = &m_VertexBuffer[0];
    	m_pTexcoord = &m_TexcoordBuffer[0];
    	m_pNormal   = &m_NormalBuffer[0];
    	m_pFace     = &m_FaceBuffer[0];
    }
    

     3. 代码错误Q&A:

         3.1   重写的时候改了一下构成顶点,法线,纹理坐标的向量结构,使之既可以按照x,y,z访问向量子成员,也可以按照数组来访问子成员。但是出现了一个问题,就是打印顶点数据,xyz均相同,仔细看了下共用体代码,一下子就明白了:

    struct VECTOR3
    {
    	union
    	{
    		float vec[3];
    		struct  
    		{
    			float x,y,z;
    		};
    	};
    	VECTOR3(float * pData)
    	{
    		memcpy(vec,pData,sizeof(float)*3);
    	}
    };
    
    struct VECTOR2
    {
    	union
    	{
    		float vec[2];
    		struct
    		{
    			float x,y;
    		};
    	};
    	VECTOR2(float * pData)
    	{
    		memcpy(vec,pData,sizeof(float)*2);
    	}
    };
    

     当时写的时候,float x,y,z的外边把struct给掉了。

          3.2.   关于面数据行打印数据错误:最后传递给OBJ_FACE(int * pData)的参数是&fTemp[0][0],而非&fTemp[3][3],这个错误实在是太不应该了,直接导致数组越界,但是数组并不检查,所以打印出来的面数据行的索引非常奇怪。

          3.3.   读入和导出OBJ,注意OBJ格式的索引是从0开始,因此读入索引时,要减1;而导出索引时,要加1.

          3.4    同时使用fgets()和fgetc()读取同一个文件,内部只维护一个文件内部指针

  • 相关阅读:
    C++ 虚函数表解析(转载)
    javaWeb中的/路径问题
    java创建多线程(转载)
    JSP中pageEncoding和charset区别,中文乱码解决方案(转载)
    Class.forName()的作用与使用总结(转载)
    Java内存模型
    java-锁膨胀的过程
    java对象头信息和三种锁的性能对比
    并发容器
    synchronized和volatile以及ReentrantLock
  • 原文地址:https://www.cnblogs.com/infintyward/p/3462383.html
Copyright © 2011-2022 走看看