OpenGL中的原语组装和光栅化

时间:2024-04-04 13:38:59

转自:http://blog.csdn.net/myarrow/article/details/7747733

 

一、什么是原语?

       原语就是可以用glDrawArrays和glDrawElements来进行画图的几何对象。原语由一系列顶点来描述,每个顶点包含位置、颜色、法线和纹理坐标。

       原语包括:点、线、三角行。

二、原语类型

1. 三角形原语类型

      1)GL_TRIANGLES:三角形顶点互不重用,如顶点{V0,V1,V2,V3,V4,V5},则描述了2个三角形,三角形顶点分别为:(V0,V1,V2)和(V3,V4,V5)。

      2)GL_TRIANGLE_STRIP:三角形顶点被重用,画一系列连接的三角形,三角形的最后2个顶点为下一个三角形的前面2个顶点。如顶点{V0,V1,V2,V3,V4},则描述了3个三角形,三角形顶点分别为:(V0,V1,V2),(V2,V1,V3)<注意此顺序,应该为逆时针方向> 和 (V2,V3,V4)。

      3)GL_TRIANGLE_FAN:三角形顶点被重用,画一系列连接的三角形,前面三角形的第1和3个顶点,为后面三角形的第1和2个顶点。如顶点{V0,V1,V2,V3,V4},则描述了3个三角形,三角形顶点分别为:(V0,V1,V2), (V0,V2,V3)和(V0,V3,V4)。

2. 线原语类型

      1)GL_LINES:线的顶点互不重用。如顶点{V0,V1,V2,V3,V4,V5},则描述了3条线,线顶点分别为:(V0,V1),(V2,V3)和 (V4,V5)。

      2)GL_LINE_STRIP:线的顶点被重用,前面线的最后一个顶点为下一条线的第一个顶点。如顶点{V0,V1,V2,V3},则描述了3条线,线顶点分别为:(V0,V1),(V1,V2)和 (V2,V3)。

      3)GL_LINE_LOOP:线的顶点被重用,与GL_LINE_STRIP类似,只是最后一条线的最后一个顶点与第一条线的第一个顶点相连。如顶点{V0,V1,V2,V3,V4},则描述了5条线,线顶点分别为:(V0,V1), (V1,V2), (V2,V3), (V3,V4)和 (V4,V0)。

      线的宽度可以通过函数“void glLineWidth(GLfloat width)”来设置。

3. 顶点原语

       GL_POINTS:对每个顶点进行画图。

       注:窗口坐标(0,0)位于窗口左下角。而点的坐标(0,0)位于窗口左上角。

       gl_PointSize是Vertex Shader中的一个内嵌变量,gl_PointCoord是Fragment Shader中的一个内嵌变量,其值取值范围为:[0.0,1.0],并且从左到右或从上到下地增加。

三、画图原语

       OpenGL ES 2.0有以下两个画图原语:

       1)glDrawArrays

       2)glDrawElements.

1. glDrawArrays

     void glDrawArrays(GLenum mode, GLint first, GLsizei count)
     • mode:指定要画的原语类型,有效值如下:
                GL_POINTS
                GL_LINES
                GL_LINE_STRIP
                GL_LINE_LOOP
                GL_TRIANGLES
                GL_TRIANGLE_STRIP
                GL_TRIANGLE_FAN

     • first:起始顶点在enabled vertex arrays中的索引

     • count:将被用于画图的顶点个数

     举例:glDrawArrays(GL_TRIANGLES, 0, 6)画了两个三角形,顶点的顶点数组索引分别为:(0, 1, 2)和(3, 4, 5)。

2. glDrawElements

    void glDrawElements(GLenum mode, GLsizei count,GLenum type, const GLvoid *indices)
    • mode:指定要画的原语类型,有效值如下:
                GL_POINTS
                GL_LINES
                GL_LINE_STRIP
                GL_LINE_LOOP
                GL_TRIANGLES
                GL_TRIANGLE_STRIP
                GL_TRIANGLE_FAN

    • count:指示在*indices中的顶点索引个数

       • type:指示在*indices中元素数据类型,有效值如下:

                GL_UNSIGNED_BYTE
                GL_UNSIGNED_SHORT
                GL_UNSIGNED_INT(可选,只有OES_element_index_uint扩展被实现时,才可使用)

     indices:存储顶点索引的数组或指针

3. glDrawArrays &glDrawElements各自用武之地

        如果Enbaled Vertex Array中的顶点不被多个原语所共享,则使用glDrawArrays较高效,因为省去了顶点索引数组;否则使用glDrawElements高效,因为同一个顶点在Enabled Vertex Array中只有一份数据,不存在多个数据copy,节省了内存空间。

        举例如下(画一个立方体):

OpenGL中的原语组装和光栅化

1) 使用glDrawArrays的代码如下:

[cpp] view plaincopy
 
  1. #define VERTEX_POS_INDX 0  
  2. #define NUM_FACES 6  
  3. GLfloat vertices[] = { … }; // (x, y, z) per vertex  
  4. glEnableVertexAttribArray(VERTEX_POS_INDX);  
  5. glVertexAttribPointer(VERTEX_POS_INDX, 3, GL_FLOAT, GL_FALSE,0, vertices);  
  6. for (i=0; i<NUM_FACES; i++)  
  7. {  
  8.     glDrawArrays(GL_TRIANGLE_FAN, first, 4);  
  9.     first += 4;  
  10. }  
  11.   
  12. //or glDrawArrays(GL_TRIANGLES, 0, 36);  

注:总共8个顶点,却保存了24个顶点或36个顶点的数据。

2)使用glDrawElements的代码如下:

[cpp] view plaincopy
 
  1. #define VERTEX_POS_INDX 0  
  2. GLfloat vertices[] = { … };// (x, y, z) per vertex  
  3. GLubyte indices[36] = { 0, 1, 2, 0, 2, 3,  
  4.                         0, 3, 4, 0, 4, 5,  
  5.                         0, 5, 6, 0, 6, 1,  
  6.                         7, 6, 1, 7, 1, 2,  
  7.                         7, 4, 5, 7, 5, 6,  
  8.                         7, 2, 3, 7, 3, 4 };  
  9. glEnableVertexAttribArray(VERTEX_POS_INDX);  
  10. glVertexAttribPointer(VERTEX_POS_INDX, 3, GL_FLOAT, GL_FALSE,0, vertices);  
  11. glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(GLubyte),GL_UNSIGNED_BYTE, indices);  

4. 总结

       只要有顶点重用,使用glDrawElements 的性能比glDrawArrays要好,因为不仅CPU与GPU之间传递数据的内存带宽更小,且在GPU中占用的内存也更少。

四、原语组装(Primitive Assembly)

       OpenGL ES 2.0原语组装阶段如下图所示:

OpenGL中的原语组装和光栅化

1. 坐标系统

OpenGL中的原语组装和光栅化

2. 裁剪体(clip volume)

    裁剪体由近、远、左、右、上、下共6个裁剪面组成。如下图所示:

OpenGL中的原语组装和光栅化

      在clipping阶段,每个原语都被裁剪体进行裁剪。并对每种原语执行如下操作来进行裁剪:

      1)Clipping triangles

            • 如果全部在裁剪体内,什么都不做

            • 如果全部不在裁剪体内,则丢弃

            • 如果部分在裁剪体内,则产生新的顶点并形成三角形FAN

      2)Clipping lines 

            • 如果全部在裁剪体内,什么都不做

            • 如果全部不在裁剪体内,则丢弃

            • 如果部分在裁剪体内,则产生新的顶点并形成新的LINE

      3)Clipping point sprites

            • 如果全部在裁剪体内,什么都不做

            • 如果全部不在裁剪体内,则丢弃

3. 透视除法(PerspectiveDivision 

        它的目标是把裁剪坐标变为归一化设备坐标,其取值范围为:[-1,1]。其实现方法为把坐标的每个分量除以Wc。裁剪坐标(Xc,Yc,Zc,Wc) 经过透视除法(Xc/Wc),(Yc/Wc),(Zc/Wc)则变成了归一化设备坐标(Xd,Yd,Zd)。

4. 视口变换(Viewport Transformation)

        它把归一化设备坐标(Xd,Yd,Zd)变换为窗口坐标或屏幕坐标(Xw,Yw,Zw)。

1)视口变换视口通过以下API设置:

        void glViewport(GLint x, GLint y, GLsizei w, GLsizei h)
        • x, y:指明视口左下角屏幕坐标(以像素为单位)

         w, h:指明视口的宽度和高度(以像素为单位)     

2)视口变换深度通过以下API设置:    

        void glDepthRange(GLclampf n, GLclampf f)
         n, f:指明要求的深度范围。默认值:n=0.0,f=1.0,值的范围为[0.0,1.0]。 
        

3)视口变换方法为:

OpenGL中的原语组装和光栅化

      Ox = (x + w)/2,Oy = (y + h)/2,n 和 f 表示要求的深度范围。

  
五、光栅化(Rasterization)

OpenGL中的原语组装和光栅化

        光栅化获取每个原语(如:点、线、三角形),然后为这个原语产生合适的Fragment。一个Fragment表示在屏幕空间中的一个像素位置(x,y),且fragment数据将被Fragment Shader处理以产生一个Fragment Color。

 六、选择(Culling)

        在三角形被光栅化之前,我需要确定哪些面对观察者,哪些面背对观察者。Culling操作丢弃哪些背对观察者的面,从而光栅化这些看不见的三角形,从而节约了GPU的时间,并提高了GPU的性能。相关函数如下:   

        1)void glFrontFace(GLenum dir)
        • dir:指示面对观察者的三角形的方向(GL_CW<顺时针>或GL_CCW<逆时针>),默认值为:GL_CCW。

                  CW: Clockwise,CCW:Counter-Clockwise

        2)void glCullFace(GLenum mode)

        • mode:指示三角形的哪个面被选择(GL_FRONT、GL_BACK、GL_FRONT_AND_BACK),默认值是GL_BACK。

 

        3)void glEnable(GLenum cap)
              void glDisable(GLenum cap)
        • cap:设置为GL_CULL_FACE,初始时,Culling被disabled掉了。