我的OpenGL学习进阶之旅介绍一下 绘制图元
Posted 欧阳鹏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅介绍一下 绘制图元相关的知识,希望对你有一定的参考价值。
目录
一、绘制图元
在上一篇博客
中,我们介绍了 图元的类型:三角形、直线和点精灵,现在我们来介绍一下如何绘制图元。
图元是可以用OpenGL ES
中的glDrawArrays
、glDrawElements
、glDrawRangeElements
、glDrawArraysInstanced
和glDrawElementsInstanced
命令绘制的几何形状对象。下面我们来介绍一下这几个API。
1.1 glDrawArrays
1.1.1 glDrawArrays
API说明
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 glDrawArrays
API示例
glDrawArrays
用元素索引为first
到first + 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 glDrawElements
API说明
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
中保存的元素索引类型,有效值为:- GL_UNSIGNED_BYTE
- GL_UNSIGNED_SHORT
- GL_UNSIGNED_INT
-
indices
指向元素索引存储位置的指针
1.3 glDrawRangeElements
1.3.1 glDrawRangeElements
API说明
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
中保存的元素索引类型,有效值为:- GL_UNSIGNED_BYTE
- GL_UNSIGNED_SHORT
- 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
绘制三角形,用glDrawArrays
和glDrawElements
绘制一个三角扇形,我们的应用程序在GPU上运行的也比glDrawArrays
更快。
1.5 图元重启
1.5.1 使用图元重启的作用
使用图元重启的作用:
- 可以在一次绘图调用中渲染多个不相连的图元(例如三角扇形或者条带)。这对于降低绘图API调用的开销是有利的。
- 图元重启的另一种方法是生成退化三角形,这种方法较不简洁。
1.5.2 在索引列表中插入一个特殊索引来重启一个用于索引绘图调用
使用图元,可以通过在索引列表中插入一个特殊索引来重启一个用于索引绘图调用(如glDrawElements
、glDrawRangeElements
、glDrawElementsInstanced
)的图元。
这个特殊索引是该索引类型的最大可能索引(例如,索引类型为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
个图元实例的驱动顶点选择,顶点的编号从1
到n
,n
是绘制的顶点数量
图元i 的类型 | 驱动顶点 |
---|---|
GL_POINTS | i |
GL_LINES | 2i |
GL_LINE_LOOP | 如果 i < n , 则为 i + 1 |
GL_LINE_STRIP | 如果 i = n , 则为 1 |
GL_TRIANGLES | i + 1 |
GL_TRIANGLE_STRIP | 3i |
GL_TRIANGLE_FAN | i + 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
),有效值为:- GL_UNSIGNED_BYTE
- GL_UNSIGNED_SHORT
- 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的 光栅化 : 剔除多边形偏移