问题描述
继续我对OpenGL基础的探索(请参阅此问题),我正在尝试弄清使用OpenGL绘制场景的基本原理.
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.
我正在尝试绘制一个在各个方向上重复n次的简单立方体.
I am trying to render a simple cube repeated n times in every direction.
我的方法似乎产生了可怕的性能:1000个多维数据集使性能低于50fps(在QuadroFX 1800上,大致为GeForce 9600GT).
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:
完成一次:
- 在模型空间中设置包含我的多维数据集顶点的顶点缓冲区和数组缓冲区
- 设置一个数组缓冲区,将要绘制的多维数据集索引为12个三角形
每帧都完成
- 更新顶点着色器使用的统一值以一次移动所有多维数据集
针对每个立方体,针对每一帧完成
done for each cube, for each frame:
- 更新顶点着色器用于将每个立方体移至其位置的统一值
- 调用glDrawElements绘制定位的立方体
这是理智的方法吗?如果没有,那么如何处理这样的事情?我猜想我需要最小化对glUniform,glDrawElements或两者的调用,但是我不确定如何做到这一点.
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.
我的小测试的完整代码:(取决于 gletools 和pyglet)
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()
推荐答案
基本上,由于每帧循环一次数据,您可能开始达到Python的性能极限.如果您用C重写代码,则性能可能会好得多.
Basically, as you're looping through data once per frame, you're probably starting to hit Python's performance limits. It's probable that if you rewrote your code in, say, C you'd have much better performance.
无论如何,要独立于语言而提高OpenGL的性能,必须限制绘图调用(对glDrawElements
的调用)的数量,以限制CPU使用率并改善CPU与GPU之间的通信.
Anyway, to increase performance in OpenGL, independently of the language, you have to limit the number of draw calls (calls to glDrawElements
), to limit the CPU usage and improve the communication between CPU and GPU.
根据您的目标,您有多种选择来加快测试速度:
Depending of your target, you have multiple options to speed up your test :
-
如果所有立方体都保持静态,则可以通过预变换顶点将它们的几何形状组合到单个VBO中,并对所有立方体发出单个绘制调用.
if all your cubes are to remain static, you could combine their geometries into a single VBO, by pretransforming the vertices, and issue a single draw call for all your cubes.
如果要单独对所有多维数据集进行动画处理,则可以使用硬件实例化(如果硬件允许),或者可以使用伪硬件实例化,可以找到一些指针此处.使用这些技术,您基本上可以通过一次绘制调用来绘制多个多维数据集,然后由着色器根据其原始ID来获取多维数据集位置.
if all your cubes are to be animated independently, you could use hardware instancing (if your hardware allows it), or pseudo-hardware instancing, you can find some pointers here. Using these techniques, you basically can draw multiple cubes with a single draw call, it's then up to the shader to fetch the cube position according its primitive id.
这篇关于OpenGL基础:每个对象调用一次glDrawElements的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!