在opengl中绘制基本的集合原语可以使用诸如
gl_begin(type)
。。。
gl_end
的方式,逐个顶点进行绘制,但是如果想绘制一个大的模型或是一个完整的场景,里面的顶点数目几十上百万,这时就不能这样逐个顶点绘制了,为此,在opengl中有从数组绘制的方式。
数组绘制的基本思想:
就是把所所有顶点的位置、法向等信息装入数组,并且按照一定的序列(预先排好的)绘制他们就行了,这只需要几步操作。这里面一共涉及到两种数组,顶点数组(vertex array)与序列数组(indice array)。顶点数组就是将各顶点的位置、法向等装入(可单独也可联合),序列数组就好比一个目录,上面记录了先绘制哪个顶点,在绘制哪一个。图例
有了这个数组就可以进行绘制了
在opengl的数组绘制中,一共分三步:
第一步:用glEnableClientState(type)激活一个类型的数组type=GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_INDEX_ARRAY, GL_NORMAL_ARRAY, GL_TEXTURE_COORD_ARRAY, and GL_EDGE_FLAG_ARRAY,表示要进行那种数据的绘制
第二步: 用glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)/glNormalPointer…指定定点数组,size为分量数(位置为2或3,法向为3等),type是GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE的一种,为数组中数据的类型,stride是指在定点数组中两个连续顶点的数据间的间隔(byte为单位),这只在联合的形式中有用,在上图的顶点的联合数组中,GL_VERTEX_ARRAY的stride为3*四儿总分(GLfloat),因为要跨国3个向量的数据,POINTE为指向第一个数据的指针,上图中联合类型中GL_VERTEX_ARRAY的为pointer,而GL_NORMAL_ARRAY的为pointer+3。
第三步:用glDrawElements(GLenum mode, GLsizei count, GLenum type, void *indices)进行绘制,其中mode为绘制的集合原语类型(三角形等),count为绘制的顶点个数,type为索引数组中数据的类型,indeces为索引数组。另外有函数glArrayElement()一次绘制一个点。
使用以上三步可以从数组中绘制图形了,但是通常我们不直接在程序中直接定义这些长数组,而是将一个图形的数据保存在文件中,常用的如OBJ文件,这是就需要先解析文件,然后从中得到这些数组
解析OBJ文件的过程的主要思想为:
Obj文件包含了所有顶点的信息,和所有面片所包含的顶点的信息。
如
V 0.1 0.2 0.3
V 1.1 1 2.1
……
F 1 2 3
F2 3 5
首先将其中的所有的顶点信息读入到我们的顶点数组中;
然后解读面片信息,将所有的面片按照顺序读入到索引数组中,如上面的例子在
顶点的位置数组中将是{0.1 0.2 0.3 1.1 1 2.1。。。。。。}
索引数组将是{1,2,3,2,3,5。。。。。。}
当然实际的OBJ可能还有很多其他的顶点信息,如法向、贴图、颜色等,过称相同
这样构建好数组后,就可以用opengl的三步绘制了
注意,索引数组中的个数和总定点数是不等的,因为一个顶点可能会被几个面共有,这时,他在索引数组中会出现多次,索引数组就是绘制顶点的顺序。
Nvidia opengl sdk辅助
应用中有很多外部的库实现了对obj文件的解析,其中NVIDIA opengl sdk是很好的一个opengl辅助库,他其中实现了很多类,都是较有用的工具。其中的nv::Model类就是一个可以解析obj文件的类。
Nv::Mode类
该类描述了一个模型的信息。使用该类的过程通常是这样的
首先用loadModel从obj文件读入一个模型信息,此时里面包含的信息是最原始的obj中的数据,如果obj文件中未定义法向等,可以调用computeNormals()进行计算。
读入后就可以用opengl的三个步骤绘制了,如这段代码
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3,GL_FLOAT,0,model->getPositions());
glNormalPointer(GL_FLOAT,0,model->getNormals());
glDrawElements(GL_TRIANGLES, model->getIndexCount(), GL_UNSIGNED_INT, model->getPositionIndices());
glDrawElements(GL_TRIANGLES, model->getIndexCount(), GL_UNSIGNED_INT, model-> getNormals());
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
这只是一种绘制方法,这种方法中,顶点的位置、发相等分别在不同的数组中,各自的索引也可能是不同的,该类有另一种方法可以将所有的数据都归结到一个数组中(也就是联合形式),然后索引也是唯一的。
这种方式更加推荐
首先调用compileModel()将数组编辑归结到一起,然后就可以用下面代码绘制
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
int stride=model->getCompiledVertexSize()*sizeof(GLfloat);
glVertexPointer(3,GL_FLOAT,stride,model->getCompiledVertices());
glNormalPointer(GL_FLOAT,stride,model->getCompiledVertices()+model->getCompiledNormalOffset());
glDrawElements(GL_TRIANGLES, model->getCompiledIndexCount(), GL_UNSIGNED_INT, model->getCompiledIndices());
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
这里面stride是大数组中两个同类型数据间的跨度,所有类型的都是相等的
然后在glVertexPointer/ glNormalPointer时,就要加上一个位移就可以了。
(我在用这个类时发现最后进行delete时会出问题,而且nvidia的demo中也只new,不delete,不知其中是否有其他机制在里面)
归结一下nv::Model类的函数
初始化:
读文件
loadModelFromFile
查询是否有法向(。。。)特性和计算:
NVSDKENTRY bool hasNormals() const;
NVSDKENTRY bool hasTexCoords() const;
NVSDKENTRY bool hasTangents() const;
NVSDKENTRY bool hasColors() const;
NVSDKENTRY void computeTangents();
NVSDKENTRY void computeNormals();
用原始OBJ绘制:
得到顶点数组
NVSDKENTRY const float* getPositions() const;
NVSDKENTRY const float* getNormals() const;
NVSDKENTRY const float* getTexCoords() const;
NVSDKENTRY const float* getTangents() const;
NVSDKENTRY const float* getColors() const;
得到序列数组
NVSDKENTRY const GLuint* getPositionIndices() const;
NVSDKENTRY const GLuint* getNormalIndices() const;
NVSDKENTRY const GLuint* getTexCoordIndices() const;
NVSDKENTRY const GLuint* getTangentIndices() const;
NVSDKENTRY const GLuint* getColorIndices() const;
得到数组的数目
NVSDKENTRY int getPositionCount() const;
NVSDKENTRY int getNormalCount() const;
NVSDKENTRY int getTexCoordCount() const;
NVSDKENTRY int getTangentCount() const;
NVSDKENTRY int getColorCount() const;
NVSDKENTRY int getIndexCount() const;
用联合的大数组进行绘制:
首先重编数组
compileModel()
得到联合的顶点数组:
NVSDKENTRY const float* getCompiledVertices() const;
得到联合的索引数组
NVSDKENTRY const GLuint* getCompiledIndices( PrimType prim = eptTriangles) const;
得到在联合数组中各特性数据的起始位移
NVSDKENTRY int getCompiledPositionOffset() const;
NVSDKENTRY int getCompiledNormalOffset() const;
NVSDKENTRY int getCompiledTexCoordOffset() const;
NVSDKENTRY int getCompiledTangentOffset() const;
NVSDKENTRY int getCompiledColorOffset() const;
得到联合数组中一个顶点所包含的分量数
// returns the size of the merged vertex in # of floats
NVSDKENTRY int getCompiledVertexSize() const;
得到顶点数和索引数
NVSDKENTRY int getCompiledVertexCount() const;
NVSDKENTRY intgetCompiledIndexCount( PrimType prim = eptTriangles) const;