为什么这个 glDrawElements 调用失败了?
Why is this glDrawElements call failing?
我正在尝试整合 https://gamedev.stackexchange.com/questions/10727/fastest-way-to-draw-quads-in-opengl-es and How to draw with Vertex Array Objects and glDrawElements in PyOpenGL 的建议,在 GLFW 提供的上下文中使用 OpenGL.arrays.vbo.VBO
实现的索引缓冲区并使用助手 class 来渲染四边形。 (我也是,至少现在,依赖 ctypes
获取原始数据,而不是使用 Numpy。)
我生成了以下最小示例(如果我能解决这个问题,我应该能够解决实际代码):
import ctypes
import glfw
from OpenGL.arrays import vbo
from OpenGL.GL import *
from OpenGL.GL import shaders
def setup_window():
glfw.init()
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(256,256,'test',None,None)
glfw.make_context_current(window)
glfw.set_input_mode(window, glfw.STICKY_KEYS, True)
return window
def get_program():
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)
return shaders.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER)
window = setup_window()
glUseProgram(get_program())
vertices = vbo.VBO(
(ctypes.c_float * 16)(
-1, -1, 0, 0,
-1, 1, 0, 0,
1, 1, 0, 0,
1, -1, 0, 0
)
)
indices = vbo.VBO(
(ctypes.c_float * 6)(
0, 1, 2, 1, 2, 3
),
target=GL_ELEMENT_ARRAY_BUFFER
)
while (
glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS
and not glfw.window_should_close(window)
):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
with vertices, indices:
glVertexAttribPointer(0, 4, GL_FLOAT, False, 0, None)
glEnableVertexAttribArray(0)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, None)
glfw.swap_buffers(window)
glfw.poll_events()
glDrawElements
调用失败,出现完全无用的 "invalid operation" 异常。注释掉后,其他一切似乎都可以正常工作(当然除了没有绘制任何内容)。
这里到底出了什么问题?我明白使用 VBO
实例作为上下文管理器(with
块)应该确保数据是绑定的,因此应该可供 glDrawElements
渲染。
我也尝试了以下方法,但没有效果:
- 在 VBO 上使用显式
.bind()
调用而不是 with
块
- 使用空数据
- 将
glDrawElements
调用中的 size
参数更改为我认为正确的所有其他数字
您正在使用核心配置文件OpenGL context:
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
在核心配置文件上下文中,您必须使用 Vertex Array Object,因为默认的 VAO (0) 无效。
切换到兼容性配置文件 (glfw.OPENGL_COMPAT_PROFILE
) 或创建顶点数组对象。我建议创建一个 VAO。
无论如何,必须在绑定 VAO 之后绑定缓冲区:
indices.bind()
vertices.bind()
并且索引的类型必须是整数,并且在 glDrawElements
处指定的类型必须对应于此类型。例如 c_int16
和 GL_UNSIGNED_SHORT
:
indices = vbo.VBO(
(ctypes.c_int16 * 6)(
0, 1, 2, 0, 2, 3
),
target=GL_ELEMENT_ARRAY_BUFFER
)
请注意,Index buffers 已在 VAO 中说明,因此必须在绑定 ELEMENT_ARRAY_BUFFER
之前绑定 VAO。
glVertexAttribPointer
associates, the currently bound ARRAY_BUFFER
, to the specified attribute index in the currently bound VAO. THus the VAO and the Vertex Buffer Object之前必须绑定
示例:
window = setup_window()
program = get_program()
glUseProgram(program)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
vertices = vbo.VBO(
(ctypes.c_float * 16)(
-1, -1, 0, 1,
-1, 1, 0, 1,
1, 1, 0, 1,
1, -1, 0, 1
)
)
indices = vbo.VBO(
(ctypes.c_int16 * 6)(
0, 1, 2, 0, 2, 3
),
target=GL_ELEMENT_ARRAY_BUFFER
)
indices.bind()
vertices.bind()
glVertexAttribPointer(0, 4, GL_FLOAT, False, 0, None)
glEnableVertexAttribArray(0)
while (
glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS
and not glfw.window_should_close(window)
):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, None)
glfw.swap_buffers(window)
glfw.poll_events()
总结讨论的结果和我的测试:
VBO class 工作正常; with
块工作正常。
必须创建 VAO 才能在 OPENGL_CORE_PROFILE 中工作。我一直对如何让 VAO 与索引缓冲区一起工作感到困惑,但事实证明它并没有什么特别之处——只需设置 VAO,其余代码是相同的。它可以像 vao = glGenVertexArrays(1); glBindVertexArray(vao)
一样简单。这需要在任何需要 VAO 的函数之前发生;在此代码中,这实际上意味着 "before the with
block is entered"。但当然,我也希望它在 while
循环之外,因为不断地重新创建它是没有意义的(而且我还必须清理它)。
在为 SSCCE 生成数据时,我在几个地方搞砸了。索引缓冲区需要有 ctypes.c_int16
类型的数据(它原来有);缓冲区中的实际索引是错误的(同样,我按照 gamedev.stackexchange 示例最初让它们正确);并且顶点的 W 坐标需要 1.0
而不是 0.0
(在我的实际代码库中,Z 和 W 组件由着色器填充,因为我试图只做 2D渲染,顶点数据将使用这些值作为纹理坐标)。
我正在尝试整合 https://gamedev.stackexchange.com/questions/10727/fastest-way-to-draw-quads-in-opengl-es and How to draw with Vertex Array Objects and glDrawElements in PyOpenGL 的建议,在 GLFW 提供的上下文中使用 OpenGL.arrays.vbo.VBO
实现的索引缓冲区并使用助手 class 来渲染四边形。 (我也是,至少现在,依赖 ctypes
获取原始数据,而不是使用 Numpy。)
我生成了以下最小示例(如果我能解决这个问题,我应该能够解决实际代码):
import ctypes
import glfw
from OpenGL.arrays import vbo
from OpenGL.GL import *
from OpenGL.GL import shaders
def setup_window():
glfw.init()
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(256,256,'test',None,None)
glfw.make_context_current(window)
glfw.set_input_mode(window, glfw.STICKY_KEYS, True)
return window
def get_program():
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)
return shaders.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER)
window = setup_window()
glUseProgram(get_program())
vertices = vbo.VBO(
(ctypes.c_float * 16)(
-1, -1, 0, 0,
-1, 1, 0, 0,
1, 1, 0, 0,
1, -1, 0, 0
)
)
indices = vbo.VBO(
(ctypes.c_float * 6)(
0, 1, 2, 1, 2, 3
),
target=GL_ELEMENT_ARRAY_BUFFER
)
while (
glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS
and not glfw.window_should_close(window)
):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
with vertices, indices:
glVertexAttribPointer(0, 4, GL_FLOAT, False, 0, None)
glEnableVertexAttribArray(0)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, None)
glfw.swap_buffers(window)
glfw.poll_events()
glDrawElements
调用失败,出现完全无用的 "invalid operation" 异常。注释掉后,其他一切似乎都可以正常工作(当然除了没有绘制任何内容)。
这里到底出了什么问题?我明白使用 VBO
实例作为上下文管理器(with
块)应该确保数据是绑定的,因此应该可供 glDrawElements
渲染。
我也尝试了以下方法,但没有效果:
- 在 VBO 上使用显式
.bind()
调用而不是with
块 - 使用空数据
- 将
glDrawElements
调用中的size
参数更改为我认为正确的所有其他数字
您正在使用核心配置文件OpenGL context:
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
在核心配置文件上下文中,您必须使用 Vertex Array Object,因为默认的 VAO (0) 无效。
切换到兼容性配置文件 (glfw.OPENGL_COMPAT_PROFILE
) 或创建顶点数组对象。我建议创建一个 VAO。
无论如何,必须在绑定 VAO 之后绑定缓冲区:
indices.bind()
vertices.bind()
并且索引的类型必须是整数,并且在 glDrawElements
处指定的类型必须对应于此类型。例如 c_int16
和 GL_UNSIGNED_SHORT
:
indices = vbo.VBO(
(ctypes.c_int16 * 6)(
0, 1, 2, 0, 2, 3
),
target=GL_ELEMENT_ARRAY_BUFFER
)
请注意,Index buffers 已在 VAO 中说明,因此必须在绑定 ELEMENT_ARRAY_BUFFER
之前绑定 VAO。
glVertexAttribPointer
associates, the currently bound ARRAY_BUFFER
, to the specified attribute index in the currently bound VAO. THus the VAO and the Vertex Buffer Object之前必须绑定
示例:
window = setup_window()
program = get_program()
glUseProgram(program)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
vertices = vbo.VBO(
(ctypes.c_float * 16)(
-1, -1, 0, 1,
-1, 1, 0, 1,
1, 1, 0, 1,
1, -1, 0, 1
)
)
indices = vbo.VBO(
(ctypes.c_int16 * 6)(
0, 1, 2, 0, 2, 3
),
target=GL_ELEMENT_ARRAY_BUFFER
)
indices.bind()
vertices.bind()
glVertexAttribPointer(0, 4, GL_FLOAT, False, 0, None)
glEnableVertexAttribArray(0)
while (
glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS
and not glfw.window_should_close(window)
):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, None)
glfw.swap_buffers(window)
glfw.poll_events()
总结讨论的结果和我的测试:
VBO class 工作正常;
with
块工作正常。必须创建 VAO 才能在 OPENGL_CORE_PROFILE 中工作。我一直对如何让 VAO 与索引缓冲区一起工作感到困惑,但事实证明它并没有什么特别之处——只需设置 VAO,其余代码是相同的。它可以像
vao = glGenVertexArrays(1); glBindVertexArray(vao)
一样简单。这需要在任何需要 VAO 的函数之前发生;在此代码中,这实际上意味着 "before thewith
block is entered"。但当然,我也希望它在while
循环之外,因为不断地重新创建它是没有意义的(而且我还必须清理它)。在为 SSCCE 生成数据时,我在几个地方搞砸了。索引缓冲区需要有
ctypes.c_int16
类型的数据(它原来有);缓冲区中的实际索引是错误的(同样,我按照 gamedev.stackexchange 示例最初让它们正确);并且顶点的 W 坐标需要1.0
而不是0.0
(在我的实际代码库中,Z 和 W 组件由着色器填充,因为我试图只做 2D渲染,顶点数据将使用这些值作为纹理坐标)。