试图在 OpenGL 中更新 500 多个顶点位置会导致读取访问冲突?

Attempting to update 500+ vertex positions in OpenGL causes a read access violation?

我正在尝试绘制大量顶点(类似于点云)。

同时我也在尝试学习如何使用 VBO 并将 Numpy 数组传递给 GLSL 程序并为顶点位置设置动画。

我认为我已经取得了一些进展,但是我对 array_size 参数的设置有限制,任何超过 500 的参数通常都会导致错误:WindowsError: exception: access violation reading 0x0999B000

有时它可以(随机地)工作,我只是想知道我是否在分配内存或缓冲数组的方式上犯了错误?

补充一下,以后我希望一次更新2500+个顶点位置。想知道我如何才能做到这一点?

#!/bin/env python
# coding: utf-8

import time
import numpy as np
from textwrap import dedent

from OpenGL.GL import *
from OpenGL.GL.shaders import compileShader, compileProgram

import pygame
from pygame.locals import *

##############################################################################
# OpenGL funcs
##############################################################################
buffers=None
shader = None
def init_gl():
    glEnable(GL_VERTEX_PROGRAM_POINT_SIZE) #allow the program to specify the point size

    global shader, buffers

    vertex_shader = compileShader(dedent('''

        uniform mat4 Projection = mat4(1);
        uniform mat4 ModelView = mat4(1);

        varying out vec3 _color;

        void main() {
            _color = gl_Color;
            gl_Position =  Projection * ModelView * gl_ModelViewProjectionMatrix * gl_Vertex;

            vec3 ndc = gl_Position.xyz / gl_Position.w ; // perspective divide.
            float zDist = 1.0-ndc.z ; // 1 is close (right up in your face,)
            // 0 is far (at the far plane)
            gl_PointSize = 25*zDist ; // between 0 and 50 now.

        }
        '''), GL_VERTEX_SHADER)
    fragment_shader = compileShader(dedent('''

        in vec3 _color;

        void main() {
            gl_FragColor = vec4(_color, 1.0); //gl_Color;
        }
        '''), GL_FRAGMENT_SHADER)
    shader = compileProgram(vertex_shader, fragment_shader)

    buffers=create_vbo()

yaw=0
pitch=0
def draw():
    global yaw, pitch
    glClear(GL_COLOR_BUFFER_BIT)# | GL_DEPTH_BUFFER_BIT)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    yaw+=0.39
    pitch+=0.27
    glTranslatef(0.0, 0.0, 0.0)
    glRotatef(yaw, 0, 1, 0)
    glRotatef(pitch, 1, 0, 0)

    setPoints()
    glFlush()

##############################################################################
# vertices
##############################################################################
array_size = 1000
scale = 0.15

#create dataset https://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html
theta = np.linspace(-4 * np.pi, 4 * np.pi, array_size)
z = np.linspace(-2, 2, array_size)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)

vertices = np.dstack((x,y,z)) * scale
colors = np.tile(np.array([0.0, 1.0, 0.0]), (array_size,1)) #a bunch of green vertices
indices=np.arange(array_size)


def create_vbo():
    buffers = glGenBuffers(3)

    glBindBuffer(GL_ARRAY_BUFFER, buffers[0])
    glBufferData(GL_ARRAY_BUFFER,
            vertices.nbytes,  # byte size
            (ctypes.c_float*len(vertices.flat))(*vertices.flat),
            GL_STREAM_DRAW)

    glBindBuffer(GL_ARRAY_BUFFER, buffers[1])
    glBufferData(GL_ARRAY_BUFFER,
            colors.nbytes, # byte size
            (ctypes.c_float*len(colors.flat))(*colors.flat),
            GL_STATIC_DRAW)

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[2])
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
            indices.nbytes, # byte size
            (ctypes.c_uint*len(indices.flat))(*indices.flat),
            GL_STATIC_DRAW)
    return buffers

def draw_vbo():
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);

    glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
    glVertexPointer(3, GL_FLOAT, 0, None);

    glBindBuffer(GL_ARRAY_BUFFER, buffers[1]);
    glColorPointer(3, GL_FLOAT, 0, None);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[2]);
    glDrawElements(GL_POINTS, indices.size, GL_UNSIGNED_INT, None);

    glDisableClientState(GL_COLOR_ARRAY)
    glDisableClientState(GL_VERTEX_ARRAY);


def setPoints():
    global shader

    glUseProgram(shader)
    draw_vbo()

    projection = np.array([#the matrix generated captured while using HTC Vive
        [ 0.75752085,  0.        ,  0.        ,  0.],
        [ 0.        ,  0.68160856,  0.        ,  0.],
        [ 0.05516453, -0.00299519, -1.00040019, -1.],
        [ 0.        ,  0.        , -0.20008004,  0.]
    ])
    modelview = np.array([#the matrix generated captured while using HTC Vive
        [ 0.99030989,  0.04490654,  0.13141415,  0.],
        [-0.01430531,  0.9742285 , -0.22510922,  0.],
        [-0.13813627,  0.22104797,  0.9654305 ,  0.],
        [-0.12975544, -0.9294402 , -1.06236947,  1.]
    ])

    glUniformMatrix4fv(glGetUniformLocation(shader, "Projection"), 1, False, projection)
    glUniformMatrix4fv(glGetUniformLocation(shader, "ModelView"), 1, False, modelview)

    glUseProgram(0)

##############################################################################
if __name__ == '__main__':

    pygame.init()
    pygame.display.set_mode((800, 600), HWSURFACE|OPENGL|DOUBLEBUF)

    init_gl()

    start_time = time.time()
    while time.time() - start_time < 5: #5 second animation
        draw()
        pygame.display.flip()

[更新]

当我意识到我没有正确转换我的 Numpy 数组的数据类型时,我相信我已经回答了我自己的问题。因此,通过在我创建数组时将 .astype() 添加到我的数组中,我设法获得了 2000 多个顶点来显示和动画:)

vertices = (np.dstack((x,y,z)) * scale).astype(np.float32)
colors = (np.tile(np.array([0.0, 1.0, 0.0]), (array_size,1))).astype(np.float32) #a bunch of green vertices
indices = np.arange(array_size).astype(np.uint32)

这里是固定的例子:

#!/bin/env python
# coding: utf-8

import time
import numpy as np
from textwrap import dedent

from OpenGL.GL import *
from OpenGL.GL.shaders import compileShader, compileProgram

import pygame
from pygame.locals import *

##############################################################################
# OpenGL funcs
##############################################################################
buffers=None
shader = None
def init_gl():
    glEnable(GL_VERTEX_PROGRAM_POINT_SIZE) #allow the program to specify the point size

    global shader, buffers

    vertex_shader = compileShader(dedent('''

        uniform mat4 Projection = mat4(1);
        uniform mat4 ModelView = mat4(1);

        varying out vec3 _color;

        void main() {
            _color = gl_Color;
            gl_Position =  Projection * ModelView * gl_ModelViewProjectionMatrix * gl_Vertex;

            vec3 ndc = gl_Position.xyz / gl_Position.w ; // perspective divide.
            float zDist = 1.0-ndc.z ; // 1 is close (right up in your face,)
            // 0 is far (at the far plane)
            gl_PointSize = 25*zDist ; // between 0 and 50 now.

        }
        '''), GL_VERTEX_SHADER)
    fragment_shader = compileShader(dedent('''

        in vec3 _color;

        void main() {
            gl_FragColor = vec4(_color, 1.0); //gl_Color;
        }
        '''), GL_FRAGMENT_SHADER)
    shader = compileProgram(vertex_shader, fragment_shader)

    buffers=create_vbo()

yaw=0
pitch=0
def draw():
    global yaw, pitch
    glClear(GL_COLOR_BUFFER_BIT)# | GL_DEPTH_BUFFER_BIT)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    yaw+=0.39
    pitch+=0.27
    glTranslatef(0.0, 0.0, 0.0)
    glRotatef(yaw, 0, 1, 0)
    glRotatef(pitch, 1, 0, 0)

    setPoints()
    glFlush()

##############################################################################
# vertices
##############################################################################
array_size = 2000
scale = 0.15

#create dataset https://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html
theta = np.linspace(-4 * np.pi, 4 * np.pi, array_size)
z = np.linspace(-2, 2, array_size)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)

vertices = (np.dstack((x,y,z)) * scale).astype(np.float32)
colors = (np.tile(np.array([0.0, 1.0, 0.0]), (array_size,1))).astype(np.float32) #a bunch of green vertices
indices = np.arange(array_size).astype(np.uint)


def create_vbo():
    buffers = glGenBuffers(3)

    glBindBuffer(GL_ARRAY_BUFFER, buffers[0])
    glBufferData(GL_ARRAY_BUFFER,
            vertices.nbytes,  # byte size
            (ctypes.c_float*len(vertices.flat))(*vertices.flat),
            GL_STREAM_DRAW)

    glBindBuffer(GL_ARRAY_BUFFER, buffers[1])
    glBufferData(GL_ARRAY_BUFFER,
            colors.nbytes, # byte size
            (ctypes.c_float*len(colors.flat))(*colors.flat),
            GL_STATIC_DRAW)

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[2])
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
            indices.nbytes, # byte size
            (ctypes.c_uint*len(indices.flat))(*indices.flat),
            GL_STATIC_DRAW)
    return buffers

def draw_vbo():
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);

    glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
    glVertexPointer(3, GL_FLOAT, 0, None);

    glBindBuffer(GL_ARRAY_BUFFER, buffers[1]);
    glColorPointer(3, GL_FLOAT, 0, None);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[2]);
    glDrawElements(GL_POINTS, indices.size, GL_UNSIGNED_INT, None);

    glDisableClientState(GL_COLOR_ARRAY)
    glDisableClientState(GL_VERTEX_ARRAY);


def setPoints():
    global shader

    glUseProgram(shader)
    draw_vbo()

    projection = np.array([#the matrix generated captured while using HTC Vive
        [ 0.75752085,  0.        ,  0.        ,  0.],
        [ 0.        ,  0.68160856,  0.        ,  0.],
        [ 0.05516453, -0.00299519, -1.00040019, -1.],
        [ 0.        ,  0.        , -0.20008004,  0.]
    ])
    modelview = np.array([#the matrix generated captured while using HTC Vive
        [ 0.99030989,  0.04490654,  0.13141415,  0.],
        [-0.01430531,  0.9742285 , -0.22510922,  0.],
        [-0.13813627,  0.22104797,  0.9654305 ,  0.],
        [-0.12975544, -0.9294402 , -1.06236947,  1.]
    ])

    glUniformMatrix4fv(glGetUniformLocation(shader, "Projection"), 1, False, projection)
    glUniformMatrix4fv(glGetUniformLocation(shader, "ModelView"), 1, False, modelview)

    glUseProgram(0)

##############################################################################
if __name__ == '__main__':

    pygame.init()
    pygame.display.set_mode((800, 600), HWSURFACE|OPENGL|DOUBLEBUF)

    init_gl()

    start_time = time.time()
    while time.time() - start_time < 5: #5 second animation
        draw()
        pygame.display.flip()

问题是您总体上没有指定 dtype,这导致 float64 被选为 dtype

print(vertices.dtype)
print(colors.dtype)
print(indices.dtype)

那应该打印 "float32", "float32", "uint32"。但是它打印 "float64", "float64", "int32".

问题是现在您的数据是原来的两倍。 因此,当您调用 glBufferData() 时,您告诉 datasize 是它实际大小的两倍(与您实际给它的大小相反)。


这就是触发 access violation reading 0x0999B000 的原因,因为您的驱动程序正试图读取您提供的数据范围之外的数据。

修复它的最简单方法是在设置顶点后执行以下操作:

vertices = vertices.astype(np.float32)
colors = colors.astype(np.float32)
indices = indices.astype(np.uint32)

现在您可以在 RAM 和 VRAM 允许的范围内添加尽可能多的顶点!


最后,为了避免将来出现麻烦,请在使用 vertices 调用 glBufferData() 之前添加以下内容:

assert vertices.dtype == np.float32

colorsindices 同样如此(使用 np.uint32 代替)。

如果数据类型不是所需数据类型,这将触发断言。


对于小于 231-1 的索引,无论您使用 int32 还是 uint32 都不会发生变化。然而,选择预期的类型可能是明智的,这样可以避免将来不小心绊倒自己。 尽管您可能达不到那么多指数或其他任何人。但安全总比后悔好。