OpenGL basics: calling glDrawElements once per object
- by Bethor
Hi all,
continuing on from my explorations of the basics of OpenGL (see this question), I'm trying to figure out the basic principles of drawing a scene with OpenGL.
I am trying to render a simple cube repeated n times in every direction.
My method appears to yield terrible performance : 1000 cubes brings performance below 50fps (on a QuadroFX 1800, roughly a GeForce 9600GT).
My method for drawing these cubes is as follows:
done once:
set up a vertex buffer and array buffer containing my cube vertices in model space
set up an array buffer indexing the cube for drawing as 12 triangles
done for each frame:
update uniform values used by the vertex shader to move all cubes at once
done for each cube, for each frame:
update uniform values used by the vertex shader to move each cube to its position
call glDrawElements to draw the positioned cube
Is this a sane method ? If not, how does one go about something like this ? I'm guessing I need to minimize calls to glUniform, glDrawElements, or both, but I'm not sure how to do that.
Full code for my little test : (depends on gletools and pyglet)
I'm aware that my init code (at least) is really ugly; I'm concerned with the rendering code for each frame right now, I'll move to something a little less insane for the creation of the vertex buffers and such later on.
import pyglet
from pyglet.gl import *
from pyglet.window import key
from numpy import deg2rad, tan
from gletools import ShaderProgram, FragmentShader, VertexShader, GeometryShader
vertexData = [-0.5, -0.5, -0.5, 1.0,
-0.5, 0.5, -0.5, 1.0,
0.5, -0.5, -0.5, 1.0,
0.5, 0.5, -0.5, 1.0,
-0.5, -0.5, 0.5, 1.0,
-0.5, 0.5, 0.5, 1.0,
0.5, -0.5, 0.5, 1.0,
0.5, 0.5, 0.5, 1.0]
elementArray = [2, 1, 0, 1, 2, 3,## back face
4, 7, 6, 4, 5, 7,## front face
1, 3, 5, 3, 7, 5,## top face
2, 0, 4, 2, 4, 6,## bottom face
1, 5, 4, 0, 1, 4,## left face
6, 7, 3, 6, 3, 2]## right face
def toGLArray(input):
return (GLfloat*len(input))(*input)
def toGLushortArray(input):
return (GLushort*len(input))(*input)
def initPerspectiveMatrix(aspectRatio = 1.0, fov = 45):
frustumScale = 1.0 / tan(deg2rad(fov) / 2.0)
fzNear = 0.5
fzFar = 300.0
perspectiveMatrix = [frustumScale*aspectRatio, 0.0 , 0.0 , 0.0 ,
0.0 , frustumScale, 0.0 , 0.0 ,
0.0 , 0.0 , (fzFar+fzNear)/(fzNear-fzFar) , -1.0,
0.0 , 0.0 , (2*fzFar*fzNear)/(fzNear-fzFar), 0.0 ]
return perspectiveMatrix
class ModelObject(object):
vbo = GLuint()
vao = GLuint()
eao = GLuint()
initDone = False
verticesPool = []
indexPool = []
def __init__(self, vertices, indexing):
super(ModelObject, self).__init__()
if not ModelObject.initDone:
glGenVertexArrays(1, ModelObject.vao)
glGenBuffers(1, ModelObject.vbo)
glGenBuffers(1, ModelObject.eao)
glBindVertexArray(ModelObject.vao)
initDone = True
self.numIndices = len(indexing)
self.offsetIntoVerticesPool = len(ModelObject.verticesPool)
ModelObject.verticesPool.extend(vertices)
self.offsetIntoElementArray = len(ModelObject.indexPool)
ModelObject.indexPool.extend(indexing)
glBindBuffer(GL_ARRAY_BUFFER, ModelObject.vbo)
glEnableVertexAttribArray(0) #position
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ModelObject.eao)
glBufferData(GL_ARRAY_BUFFER, len(ModelObject.verticesPool)*4, toGLArray(ModelObject.verticesPool), GL_STREAM_DRAW)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(ModelObject.indexPool)*2, toGLushortArray(ModelObject.indexPool), GL_STREAM_DRAW)
def draw(self):
glDrawElements(GL_TRIANGLES, self.numIndices, GL_UNSIGNED_SHORT, self.offsetIntoElementArray)
class PositionedObject(object):
def __init__(self, mesh, pos, objOffsetUf):
super(PositionedObject, self).__init__()
self.mesh = mesh
self.pos = pos
self.objOffsetUf = objOffsetUf
def draw(self):
glUniform3f(self.objOffsetUf, self.pos[0], self.pos[1], self.pos[2])
self.mesh.draw()
w = 800
h = 600
AR = float(h)/float(w)
window = pyglet.window.Window(width=w, height=h, vsync=False)
window.set_exclusive_mouse(True)
pyglet.clock.set_fps_limit(None)
## input
forward = [False]
left = [False]
back = [False]
right = [False]
up = [False]
down = [False]
inputs = {key.Z: forward, key.Q: left, key.S: back, key.D: right,
key.UP: forward, key.LEFT: left, key.DOWN: back, key.RIGHT: right,
key.PAGEUP: up, key.PAGEDOWN: down}
## camera
camX = 0.0
camY = 0.0
camZ = -1.0
def simulate(delta):
global camZ, camX, camY
scale = 10.0
move = scale*delta
if forward[0]:
camZ += move
if back[0]:
camZ += -move
if left[0]:
camX += move
if right[0]:
camX += -move
if up[0]:
camY += move
if down[0]:
camY += -move
pyglet.clock.schedule(simulate)
@window.event
def on_key_press(symbol, modifiers):
global forward, back, left, right, up, down
if symbol in inputs.keys():
inputs[symbol][0] = True
@window.event
def on_key_release(symbol, modifiers):
global forward, back, left, right, up, down
if symbol in inputs.keys():
inputs[symbol][0] = False
## uniforms for shaders
camOffsetUf = GLuint()
objOffsetUf = GLuint()
perspectiveMatrixUf = GLuint()
camRotationUf = GLuint()
program = ShaderProgram(
VertexShader('''
#version 330
layout(location = 0) in vec4 objCoord;
uniform vec3 objOffset;
uniform vec3 cameraOffset;
uniform mat4 perspMx;
void main()
{
mat4 translateCamera = mat4(1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
cameraOffset.x, cameraOffset.y, cameraOffset.z, 1.0f);
mat4 translateObject = mat4(1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
objOffset.x, objOffset.y, objOffset.z, 1.0f);
vec4 modelCoord = objCoord;
vec4 positionedModel = translateObject*modelCoord;
vec4 cameraPos = translateCamera*positionedModel;
gl_Position = perspMx * cameraPos;
}'''),
FragmentShader('''
#version 330
out vec4 outputColor;
const vec4 fillColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
void main()
{
outputColor = fillColor;
}''')
)
shapes = []
def init():
global camOffsetUf, objOffsetUf
with program:
camOffsetUf = glGetUniformLocation(program.id, "cameraOffset")
objOffsetUf = glGetUniformLocation(program.id, "objOffset")
perspectiveMatrixUf = glGetUniformLocation(program.id, "perspMx")
glUniformMatrix4fv(perspectiveMatrixUf, 1, GL_FALSE, toGLArray(initPerspectiveMatrix(AR)))
obj = ModelObject(vertexData, elementArray)
nb = 20
for i in range(nb):
for j in range(nb):
for k in range(nb):
shapes.append(PositionedObject(obj, (float(i*2), float(j*2), float(k*2)), objOffsetUf))
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
glFrontFace(GL_CW)
glEnable(GL_DEPTH_TEST)
glDepthMask(GL_TRUE)
glDepthFunc(GL_LEQUAL)
glDepthRange(0.0, 1.0)
glClearDepth(1.0)
def update(dt):
print pyglet.clock.get_fps()
pyglet.clock.schedule_interval(update, 1.0)
@window.event
def on_draw():
with program:
pyglet.clock.tick()
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glUniform3f(camOffsetUf, camX, camY, camZ)
for shape in shapes:
shape.draw()
init()
pyglet.app.run()