一、说明
我们常用的着色器绘制函数是glDrawArray和glDrawElements,glDrawArray我们已经使用的很熟练,不需要重提,那么glDrawElements就需要重点提示它的用法,本篇就是对这个函数用法详细记载。
二、顶点顺序渲染和选择渲染
在一般的顶点画出,有glDrawArray()函数,此函数是将VOB的顶点顺序发出就算OK,但是,用glDrawElements不是按照VOB顺序完成,而是将顶点按照index数组进行画出。这种渲染大大增加渲染的灵活性和多样性。
本篇我们将尝试使用这种渲染方法。
2.1 基本方法函数
OpenGL提供的画图函数可以分为两大类:non-indexed draw和indexed draw。下面列举出了几个最常用的画图函数。
//1.基本方法:non-indexed draw
void glDrawArrays( GLenum mode, GLint first, GLsizei count);
//mutidraw + non-indexed draw
void glMultiDrawArrays( GLenum mode, GLint *first, GLsizei *count, GLsizei primcount);
//instance + non-indexed draw
void glDrawArraysInstanced( GLenum mode, GLint first,GLsizei count, GLsizei instancecount);
//indirect + non-indexed draw
void glDrawArraysIndirect(GLenum mode, const void *indirect);
//2.基本方法:indexed draw
void glDrawElements(GLenum mode, GLsizei count, GLenum type, void * indices);
//mutidraw + indexed draw
void glMultiDrawElements( GLenum mode, GLsizei *count, GLenum type, void **indices, GLsizei primcount);
//instance + indexed draw
void glDrawElementsInstanced( GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount);
//indirect + indexed draw
void glDrawElementsIndirect(GLenum mode, GLenum type, const void *indirect);
glDrawArrays()和glDrawElements()是最基础的2个画图函数,每调用1次画图函数,CPU和GPU会进行一次数据通信,我们把这个过程叫做drawcall,drawcall是一个非常耗时的操作,为了减少drawcall,提高绘制效率,在上述2个函数的基础上,引入了intancing draw,multiDraw和indirect draw。但是,对于现如今PC端GPU,减少drawcall对于性能提升基本不管用了。
在调用上面的画图函数前,我们需要做一些准备工作,包括3点:
- 提供顶点数据来源;
- 提供顶点数据的解析方式;
- 告诉函数怎样把顶点组装成图元。
2.2.顶点数据管理
画图函数需要的顶点数据来自当前绑定的VAO,VAO提供了画图需要的所有顶点数据。
OpenGL中涉及到管理顶点数据的对象有VAO,VBO,EBO,IBO。这些对象之间的层级关系如下图所示:
2.3 层级关系
VAO处于最高层级,通过【绑定】操作,可以建立起VAO与VBO,EBO,IBO之间的关系;
VBO,EBO,IBO处于第二层级,用于管理顶点数据,VBO负责管理顶点属性数据,EBO负责管理顶点索引数据,IBO负责管理画图指令数据。
第三层级,VBO,EBO,IBO这些对象内部不存放数据,我们需要额外分配内存,把数据存放到这段内存中,并和上述3种对象关联起来。
其中VAO和VBO是必须的;EBO和IBO是可选的,根据我们的需求决定是否创建,调用indexed draw则必须创建EBO,调用indirect draw则必须创建IBO。
三、测试EBO的代码
from OpenGL.GL import shaders
from OpenGL.arrays import vbo
from OpenGL.GL import *
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, \
glBindVertexArray
import pygame
import numpy as np
def get_index_vetices(N ):
lst = list(range(100))
indexs = np.array([lst], dtype=np.int32)
return indexs
def get_primery_vetices(N,dim):
vertex = np.zeros([N, dim], dtype=np.float32)
for i in vertex:
for Z in range(dim-1):
i[Z] = 2*np.random.rand()-1
return vertex
def main():
pygame.init()
screen = pygame.display.set_mode((800,600), pygame.OPENGL|pygame.DOUBLEBUF)
# Create the VBO
# vertices = np.array([[0, 1, 0], [-1, -1, 0], [1, -1, 0]], dtype='f')
vertices = get_primery_vetices(100,3)
global VBO,EBO
VBO = glGenBuffers(1) # 创建缓存
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW) # 输入数据
# Create the index buffer object
indices = np.array([[0, 1, 2],[3,2,5],[13,9,15],[33,14,16],[99,3,2]], dtype=np.int32)
EBO = glGenBuffers(1 )
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW)
# Now create the shaders
VERTEX_SHADER = shaders.compileShader("""
#version 330
layout(location = 0) in vec4 position;
void main()
{
gl_Position = position;
}
""", GL_VERTEX_SHADER)
FRAGMENT_SHADER = shaders.compileShader("""
#version 330
out vec4 outputColor;
void main()
{
outputColor = vec4(0.0f, 1.0f, 0.0f, 1.0f);
}
""", GL_FRAGMENT_SHADER)
shader = shaders.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER)
#The draw loop
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glUseProgram(shader)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO)
glDisable(GL_PROGRAM_POINT_SIZE)
glPointSize(33.0)
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None) # 这里的None不能写为0
# glDrawArrays(GL_TRIANGLES, 0, 6) #This line still works
# glDrawElements(GL_TRIANGLES, 11, GL_UNSIGNED_INT, indices) # This line does work too!
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, None)
# Show the screen
pygame.display.flip()
try:
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
break
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE or event.unicode == "q":
break
# pygame.display.flip()
finally:
pygame.quit()
main()
结果
四、总结
EBO使用方法:
1 创建并绑定索引
# Create the index buffer object
indices = np.array([[0, 1, 2],[3,2,5],[13,9,15],[33,14,16],[99,3,2]], dtype=np.int32)
EBO = glGenBuffers(1 )
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW)
2 在glutDisplayFunc回调函数中
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);
# 调用绘制点的索引
五、后记
还有一些其它的函数,我们将在后文中逐渐实现。