我的OpenGL学习进阶之旅介绍一下 绘制图元

Posted 欧阳鹏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅介绍一下 绘制图元相关的知识,希望对你有一定的参考价值。

一、绘制图元

在上一篇博客

中,我们介绍了 图元的类型:三角形、直线和点精灵,现在我们来介绍一下如何绘制图元。

图元是可以用OpenGL ES中的glDrawArraysglDrawElementsglDrawRangeElementsglDrawArraysInstancedglDrawElementsInstanced命令绘制的几何形状对象。下面我们来介绍一下这几个API。

1.1 glDrawArrays

1.1.1 glDrawArraysAPI说明

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
    指定启用的顶点数组中的起始顶点索引

  • count
    指定要绘制的顶点数量

1.1.2 glDrawArraysAPI示例

glDrawArrays用元素索引为firstfirst + count - 1的元素指定的顶点绘制mode指定的图元。

调用

glDrawArrays(GL_TRIANGLES, 0, 6 );

将绘制两个三角形:一个三角形由元素索引(0,1,2)指定,另外一个三角形由元素索引(3,4,5)指定。


类似的,调用

glDrawArrays(GL_TRIANGLE_STRIP,  0,  5 );

将绘制三个三角形:一个三角形由元素索引(0,1,2)指定,第二个三角形由元素索引(2,1,3)指定,最后一个三角形由元素索引(2,3,4)指定

1.2 glDrawElements

1.2.1 glDrawElementsAPI说明

void glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices);

参数说明:

  • mode
    指定要渲染的图元,有效值为:

    // 点精灵
    GL_POINTS
    // 直线
    GL_LINES
    GL_LINE_STRIP
    GL_LINE_LOOP
    // 三角形
    GL_TRIANGLES
    GL_TRIANGLE_STRIP
    GL_TRIANGLE_FAN
    
  • count
    指定要绘制的索引数量

  • type
    指定indices中保存的元素索引类型,有效值为:

    1. GL_UNSIGNED_BYTE
    2. GL_UNSIGNED_SHORT
    3. GL_UNSIGNED_INT
  • indices
    指向元素索引存储位置的指针

1.3 glDrawRangeElements

1.3.1 glDrawRangeElementsAPI说明

void glDrawRangeElements (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices);

参数说明:

  • mode
    指定要渲染的图元,有效值为:

    // 点精灵
    GL_POINTS
    // 直线
    GL_LINES
    GL_LINE_STRIP
    GL_LINE_LOOP
    // 三角形
    GL_TRIANGLES
    GL_TRIANGLE_STRIP
    GL_TRIANGLE_FAN
    
  • start
    指定indices中的最小数组索引

  • end
    指定indices中的最大数组索引

  • count
    指定要绘制的索引数量

  • type
    指定indices中保存的元素索引类型,有效值为:

    1. GL_UNSIGNED_BYTE
    2. GL_UNSIGNED_SHORT
    3. GL_UNSIGNED_INT
  • indices
    指向元素索引存储位置的指针

1.4 如何选择?

如果你又一个由一系列顺序元素索引描述的图元,且几何形状的顶点不共享,则glDrawArrays很好用。但是,游戏或者其他3D应用程序使用的典型对象由多个三角形网格组成,其中的元素索引可能不一定按照顺序,顶点通常在网格的三角形之间共享。

考虑如下图所示的立方体:

1.4.1 用glDrawArrays绘制

如果我们用glDrawArrays绘制,则代码如下:

#include <GLUtils.h>

#define VERTEX_POS_INDX 0
#define NUM_FACES 6

GLfloat vertivces[] =  ...  ;  // (x,y,z) per vertex

glEnableVertexAttribArray ( VERTEX_POS_INDX );
glVertexAttribPointer( VERTEX_POS_INDX , 3, GL_FLOAT, GL_FALSE, 0 ,vertivces);

for(int i = 0; i< NUM_FACES; i++)

	glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4 );


// or 

glDrawArrays(GL_TRIANGLES , 36 );

为了用glDrawArrays绘制这个立方体,需要为立方体的每一面调用glDrawArrays
共享的顶点必须重复,这意味着

  • 如果将每面当做GL_TRIANGLE_FAN绘制,则需要分配24个顶点
  • 如果将每面当做GL_TRIANGLES绘制,则需要分配36个顶点

而不是8个顶点,这显然不是一个高效的方法。

1.4.2 用glDrawElements绘制

glDrawElements绘制同一个立方体的代码如下:

#define VERTEX_POS_INDX 0
GLfloat vertivces[] =    ;  // (x,y,z) per vertex
GLubyte indices[36] = 
        0,1,2,  0,2,3,
        0,3,4,  0,4,5,
        0,5,6,  0,6,1,

        7,1,6,  7,2,1,
        7,5,4,  7,6,5,
        7,3,2,  7,4,3
;
glEnableVertexAttribArray ( VERTEX_POS_INDX );
glVertexAttribPointer( VERTEX_POS_INDX , 3, GL_FLOAT, GL_FALSE, 0 ,vertivces);
glDrawElements( GL_TRIANGLES, sizeof(indices) / sizeof( GLubyte),
                GL_UNSIGNED_BYTE , indices);

即时我们用glDrawElements绘制三角形,用glDrawArraysglDrawElements绘制一个三角扇形,我们的应用程序在GPU上运行的也比glDrawArrays更快。

1.5 图元重启

1.5.1 使用图元重启的作用

使用图元重启的作用:

  • 可以在一次绘图调用中渲染多个不相连的图元(例如三角扇形或者条带)。这对于降低绘图API调用的开销是有利的。
  • 图元重启的另一种方法是生成退化三角形,这种方法较不简洁。

1.5.2 在索引列表中插入一个特殊索引来重启一个用于索引绘图调用

使用图元,可以通过在索引列表中插入一个特殊索引来重启一个用于索引绘图调用(如glDrawElementsglDrawRangeElementsglDrawElementsInstanced)的图元。

这个特殊索引是该索引类型的最大可能索引(例如,索引类型为GL_UNSIGNED_BYTE是为255,索引类型为GL_UNSIGNED_SHORT时为65535)。

例如,假定两个三角形条带分别有元素索引 (0,1,2,3)和 (8,9,10,11)。如果我们想利用图元重启在一次调用glDrawElement***中绘制两个条带,索引类型为GL_UNSIGNED_BYTE,则组合的元素索引列表为(0,1,2,3 ,255, 8,9,10,11)

1.5.3 启用和禁用图元重启

可以用如下代码启用和禁用图元重启:

// 启用图元重启
glEnable( GL_PRIMITIVE_RESTART_FIXED_INDEX );

// Draw primitives

// 禁用图元重启
glDisable( GL_PRIMITIVE_RESTART_FIXED_INDEX );

1.6 驱动顶点

如果没有限定符,那么顶点着色器的输出值在图元中使用线性插值。
但是,使用平面着色时没有发生插值。因为没有发生插值,所以片段着色器中只有一个顶点值可用。
对于给定的图元实例,这个驱动顶点确定使用顶点着色器的哪一个顶点输出,因为只能使用一个顶点。

下表展示了驱动顶点选择的规则:

表:第 i 个图元实例的驱动顶点选择,顶点的编号从1nn是绘制的顶点数量

图元i的类型驱动顶点
GL_POINTSi
GL_LINES2i
GL_LINE_LOOP如果 i < n, 则为 i + 1
GL_LINE_STRIP如果 i = n, 则为 1
GL_TRIANGLESi + 1
GL_TRIANGLE_STRIP3i
GL_TRIANGLE_FANi + 2

1.7 几何形状实例化

1.7.1 几何形状实例化的作用

  • 几何形状实例化很高效,可以用一次API调用多次渲染具有不同属性(例如不同的变换矩阵、颜色或者大小)的一个对象。
    这一功能在渲染大量类似对象时很有用,例如对人群的渲染。
  • 几何形状实例化降低了向OpenGL ES 引擎发送许多API调用的CPU处理开销。

1.7.2 使用几何形状实例化

要使用几何形状实例化绘图调用渲染,可以使用如下命令:

  • glDrawArraysInstanced
  • glDrawElementsInstanced
void glDrawArraysInstanced (GLenum mode, GLint first, 
                       GLsizei count, GLsizei instancecount);

void glDrawElementsInstanced (GLenum mode, GLsizei count, 
						GLenum type, const void *indices, 
						GLsizei instancecount);

参数说明:

  • mode
    指定要渲染的图元,有效值为:

    // 点精灵
    GL_POINTS
    // 直线
    GL_LINES
    GL_LINE_STRIP
    GL_LINE_LOOP
    // 三角形
    GL_TRIANGLES
    GL_TRIANGLE_STRIP
    GL_TRIANGLE_FAN
    
  • fisrt
    指定启用的顶点数组中的起始顶点索引(仅限glDrawArraysInstanced

  • count
    指定要绘制的索引数量

  • type
    指定indices中保存的元素索引类型(仅限glDrawElementsInstanced ),有效值为:

    1. GL_UNSIGNED_BYTE
    2. GL_UNSIGNED_SHORT
    3. GL_UNSIGNED_INT
  • indices
    指向元素索引存储位置的指针(仅限glDrawElementsInstanced

  • instancecount
    指定绘图的图元实例数量

可以使用两种方法访问每个实例的数据。

1.7.2.1 glDrawArraysInstanced

第一个方法是用如下命令指示OpenGL ES对每个实例读取一次或者多次顶点属性:

void glVertexAttribDivisor (GLuint index, GLuint divisor);

参数说明:

  • index
    指定通用顶点属性索引
  • divisor
    指定index位置的通用属性更新之间传递的实例数量

默认情况下,如果没有指定glVertexAttribDivisor 或者顶点属性的divisor等于0,对每个顶点将读取一次顶点属性。如果divisor等于1,对每个图元实例读取一次顶点属性


1.7.2.2 glDrawElementsInstanced

第二个方法是使用内建输入变量 gl_InstanceID 作为顶点着色器中的缓冲区索引,以访问每个实例的数据。使用前面提到的几何形状实例化API调用时,gl_InstanceID将保存当前图元实例的索引。使用非实例化绘图调用时,gl_InstanceID将返回0.

1.7.3 实战一下

下面两个代码片段说明如何用一次实例化绘图调用绘制多个几何形状(例如立方体),其中每个立方体实例的颜色不同。

首先,我们创建一个颜色缓冲区,用于保存以后用于实例化绘图调用的多种颜色数据(每个实例一个颜色)。

// Random color for each instance
   
      GLubyte colors[NUM_INSTANCES][4];
      int instance;

      srandom ( 0 );

      for ( instance = 0; instance < NUM_INSTANCES; instance++ )
      
         colors[instance][0] = random() % 255;
         colors[instance][1] = random() % 255;
         colors[instance][2] = random() % 255;
         colors[instance][3] = 0;
      

      glGenBuffers ( 1, &userData->colorVBO );
      glBindBuffer ( GL_ARRAY_BUFFER, userData->colorVBO );
      glBufferData ( GL_ARRAY_BUFFER, NUM_INSTANCES * 4, colors, GL_STATIC_DRAW );
   

创建和填充颜色缓冲区之后,我们可以绑定颜色缓冲区,将其作为几何形状的顶点属性之一。然后,指定顶点属性因数1,为每个图元实例读取颜色。最后,用一次实例化绘图调用绘制立方体。

  // Load the instance color buffer
   glBindBuffer ( GL_ARRAY_BUFFER, userData->colorVBO );
   glVertexAttribPointer ( COLOR_LOC, 4, GL_UNSIGNED_BYTE,
                           GL_TRUE, 4 * sizeof ( GLubyte ), ( const void * ) NULL );
   glEnableVertexAttribArray ( COLOR_LOC );
   
   // Set one color per instance
   glVertexAttribDivisor ( COLOR_LOC, 1 ); 

   // 省略其他代码
 
   // Bind the index buffer
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->indicesIBO );

   // Draw the cubes
   glDrawElementsInstanced ( GL_TRIANGLES, userData->numIndices, 
   						GL_UNSIGNED_INT, ( const void * ) NULL, NUM_INSTANCES );

完整代码为:

#include <stdlib.h>
#include <math.h>
#include "esUtil.h"

#ifdef _WIN32
#define srandom srand
#define random rand
#endif


#define NUM_INSTANCES   100
#define POSITION_LOC    0
#define COLOR_LOC       1
#define MVP_LOC         2

typedef struct

   // Handle to a program object
   GLuint programObject;

   // VBOs
   GLuint positionVBO;
   GLuint colorVBO;
   GLuint mvpVBO;
   GLuint indicesIBO;

   // Number of indices
   int       numIndices;

   // Rotation angle
   GLfloat   angle[NUM_INSTANCES];

 UserData;

///
// Initialize the shader and program object
//
int Init ( ESContext *esContext )

   GLfloat *positions;
   GLuint *indices;

   UserData *userData = esContext->userData;
   const char vShaderStr[] =
      "#version 300 es                             \\n"
      "layout(location = 0) in vec4 a_position;    \\n"
      "layout(location = 1) in vec4 a_color;       \\n"
      "layout(location = 2) in mat4 a_mvpMatrix;   \\n"
      "out vec4 v_color;                           \\n"
      "void main()                                 \\n"
      "                                           \\n"
      "   v_color = a_color;                       \\n"
      "   gl_Position = a_mvpMatrix * a_position;  \\n"
      "                                           \\n";

   const char fShaderStr[] =
      "#version 300 es                                \\n"
      "precision mediump float;                       \\n"
      "in vec4 v_color;                               \\n"
      "layout(location = 0) out vec4 outColor;        \\n"
      "void main()                                    \\n"
      "                                              \\n"
      "  outColor = v_color;                          \\n"
      "                                              \\n";

   // Load the shaders and get a linked program object
   userData->programObject = esLoadProgram ( vShaderStr, fShaderStr );

   // Generate the vertex data
   userData->numIndices = esGenCube ( 0.1f, &positions,
                                      NULL, NULL, &indices );

   // Index buffer object
   glGenBuffers ( 1, &userData->indicesIBO );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->indicesIBO );
   glBufferData ( GL_ELEMENT_ARRAY_BUFFER, sizeof ( GLuint ) * userData->numIndices, indices, GL_STATIC_DRAW );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, 0 );
   free ( indices );

   // Position VBO for cube model
   glGenBuffers ( 1, &userData->positionVBO );
   glBindBuffer ( GL_ARRAY_BUFFER, userData->positionVBO );
   glBufferData ( GL_ARRAY_BUFFER, 24 * sizeof ( GLfloat ) * 3, positions, GL_STATIC_DRAW );
   free ( positions );

   // Random color for each instance
   
      GLubyte colors[NUM_INSTANCES][4];
      int instance;

      srandom ( 0 );

      for ( instance = 0; instance < NUM_INSTANCES; instance++ )
      
         colors[instance][0] = random() % 255;
         colors[instance][1] = random() % 255;
         colors[instance][2] = random() % 255;
         colors[instance][3] = 0;
      

      glGenBuffers ( 1, &userData->colorVBO );
      glBindBuffer ( GL_ARRAY_BUFFER, userData->colorVBO );
      glBufferData ( GL_ARRAY_BUFFER, NUM_INSTANCES * 4, colors, GL_STATIC_DRAW );
   

   // Allocate storage to store MVP per instance
   
      int instance;

      // Random angle for each instance, compute the MVP later
      for ( instance = 0; instance < NUM_INSTANCES; instance++ )
      
         userData->angle[instance] = ( float ) ( random() % 32768 ) / 32767.0f * 360.0f;
      

      glGenBuffers ( 1, &userData->mvpVBO );
      glBindBuffer ( GL_ARRAY_BUFFER, userData->mvpVBO );
      glBufferData ( GL_ARRAY_BUFFER, NUM_INSTANCES * sizeof ( ESMatrix ), NULL, GL_DYNAMIC_DRAW );
   
   glBindBuffer ( GL_ARRAY_BUFFER, 0 );

   glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
   return GL_TRUE;



///
// Update MVP matrix based on time
//
void Update ( ESContext *esContext, float deltaTime )

   UserData *userData = ( UserData * ) esContext->userData;
   ESMatrix *matrixBuf;
   ESMatrix perspective;
   float    aspect;
   int      instance = 0;
   int      numRows;
   int      numColumns;


   // Compute the window aspect ratio
   aspect = ( GLfloat ) esContext->width / ( GLfloat ) esContext->height;

   // Generate a perspective matrix with a 60 degree FOV
   esMatrixLoadIdentity ( &perspective );
   esPerspective ( &perspective, 60.0f, aspect, 1.0f, 20.0f );

   glBindBuffer ( GL_ARRAY_BUFFER, userData->mvpVBO );
   matrixBuf = ( ESMatrix * ) glMapBufferRange ( GL_ARRAY_BUFFER, 0, sizeof ( ESMatrix ) * NUM_INSTANCES, GL_MAP_WRITE_BIT );

   // Compute a per-instance MVP that translates and rotates each instance differnetly
   numRows = ( int ) sqrtf ( NUM_INSTANCES );
   numColumns = numRows;

   for ( instance = 0; instance < NUM_INSTANCES; instance++ )
   
      ESMatrix modelview;
      float translateX = ( ( float ) ( instance % numRows ) / ( float ) numRows ) * 2.0f - 1.0f;
      float translateY = ( ( float ) ( instance / numColumns ) / ( float ) numColumns ) * 2.0f - 1.0f;

      // Generate a model view matrix to rotate/translate the cube
      esMatrixLoadIdentity ( &modelview );

      // Per-instance translation
      esTranslate ( &modelview, translateX, translateY, -2.0f );

      // Compute a rotation angle based on time to rotate the cube
      userData->angle[instance] += ( deltaTime * 40.0f );

      if ( userData->angle[instance] >= 360.0f )
      
         userData->angle[instance] -= 360.0f;
      

      // Rotate the cube
      esRotate ( &modelview, userData->angle[instance], 1.0, 0.0, 1.0 );

      // Compute the final MVP by multiplying the
      // modevleiw and perspective matrices together
      esMatrixMultiply ( &matrixBuf[instance], &modelview, &perspective );
   

   glUnmapBuffer ( GL_ARRAY_BUFFER );


///
// Draw a triangle using the shader pair created in Init()
//
void Draw ( ESContext *esContext )

   UserData *userData = esContext->userData;

   // Set the viewport
   glViewport ( 0, 0, esContext->width以上是关于我的OpenGL学习进阶之旅介绍一下 绘制图元的主要内容,如果未能解决你的问题,请参考以下文章

我的OpenGL学习进阶之旅介绍一下OpenGL ES的图元装配:坐标系统透视分割视口变化

我的OpenGL学习进阶之旅介绍一下OpenGL ES的图元装配:坐标系统透视分割视口变化

我的OpenGL学习进阶之旅介绍一下 图元的类型:三角形直线和点精灵

我的OpenGL学习进阶之旅介绍一下OpenGL ES的 光栅化 : 剔除多边形偏移

我的OpenGL学习进阶之旅介绍一下OpenGL ES的 光栅化 : 剔除多边形偏移

我的OpenGL学习进阶之旅介绍一下 图元的类型:三角形直线和点精灵