树莓派上的高效3d

时间:2017-05-01 17:13:37

标签: python 3d raspberry-pi2

我正在尝试在10x10x10网格中对覆盆子pi(型号2B)上的3D 3D阵列进行建模。 我只是想让它们根据模式生成算法打开和关闭。

我已经在pi3d中编写了一些基本代码来模拟1000个球体,并保存在一个数组中。它循环通过阵列,并通过将球体的颜色更改为蓝色或黑色来打开或关闭每个LED。

代码的核心部分如下:

spheres = [[[pi3d.Sphere(x=x-5,y=y-5,z=z-5,radius=0.1) for x in range(dim)] for y in range(dim)] for z in range(dim)]
i = 0

while DISPLAY.loop_running():
    k = mykeys.read()
    if k == 27:
        mykeys.close()
        ISPLAY.destroy()
        break

    CAM.update(mymouse)
    for x in range (dim):
        for y in range(dim):
            for z in range(dim):
                colour=0.1
                if(((x-dim/2.0) * (x-dim/2.0)) + ((y-dim/2.0) * (y-dim/2.0)) + ((z-dim/2.0) * (z-dim/2.0)) <= i * dim):
                    colour = 1.0
                spheres[x][y][z].set_material((0.0,0.0,colour))
                spheres[x][y][z].draw()
    i=i+0.1
    if i > 4:
        i=0

这很好用,但给了我大约5 fps。将球体更改为立方体可以略微改善这一点,但至少我真的想要提高一个数量级的性能。我知道我可以在数学方面取得一些效率提升,但我经历了类似的性能随机打开和关闭,所以我暂时不关注它。

我可能认为这只是要求太多的覆盆子pi,但随后玩了与它捆绑在一起的Minecraft游戏,并发现它在渲染过程中具有更大的复杂性。

我想知道是否有其他方法,或者甚至是另一种语言,我可以用它来给我那种我正在寻找的表现。

我对3D编程知之甚少,所以任何人都可以指出的任何建议或教程都可能有用。

3 个答案:

答案 0 :(得分:1)

在您执行任何操作之前,请对代码进行分析,以查看其运行缓慢的位置。值得注意的是,pi3D不一定能像Minecraft的调整3D引擎那样快速运行。

一个球体需要很多多边形来绘制光滑的边缘。即使保守估计每个球体只有32个多边形,你的总多边形数量也会变为:

10 * 10 * 10 * 32 = 32000

一个简单的优化是用立方体替换球体:

10 * 10 * 10 * 6 = 6000

如果想要球体的外观,可以通过渲染面向相机(也就是:广告牌)的1个多边形平面来进一步减少多边形数量,并使用球体纹理。

10 * 10 * 10 * 1 = 1000

尝试乘法而不是分割10 / 210 * 0.5相同,并且不要做同样的工作两次:

x_dim = x - dim * 0.5
y_dim = y - dim * 0.5
z_dim = z - dim * 0.5

if((x_dim * x_dim) + (y_dim * y_dim) + (z_dim * z_dim) <= i * dim):

最后,尝试只在整个场景上调用draw()一次,而不是在每个球体上调用。

答案 1 :(得分:1)

问题是有一个python代码为每个pi3d.Shape一次做一个矩阵乘法。虽然这是使用numpy完成的,并且尽可能快,但它仍然很慢。

您可以将所有球体制作成一个pi3d.MergeShape,然后每帧只需要一次绘制()并且速度非常快......但是

  1. 你的球体对象使用12边x 12个切片的默认值,给出288个面和864个顶点,这样你的MergeShape将有864,000个顶点,这可能会使GPU慢下来。

  2. 捆绑着色器只对整个Shape使用一个材质RGB值,您想为每个需要黑客着色器的球体指定不同的颜色(如果您习惯于黑客着色器,则很容易做到) )您在缓冲区数组的纹理坐标字段中指定RGB值。

  3. 你的代码没有显示你正在使用的着色器,默认将是mat_light,这将为每个球体提供平滑的3D效果,但如果你可以使用点进行管理(参见演示SpriteBalls)那么你可能有数千个快速运行的球体...但您仍需要修改着色器以改变每个顶点的漫反射颜色。

    或者你可以制作半蓝色,半黑色的纹理,并调整每帧各种球体的纹理坐标。假设你已经将所有球体合并为一个形状,这将非常快(虽然将涉及一个笨拙的numpy公式来重现你的x,y,z嵌套循环的效果)

    在接下来的几天里,我将尝试设计演示如何执行这些选项并将其添加到https://github.com/pi3d/pi3d_demos

    编辑我刚才记得Starfield.py演示版使用了可变颜色&#39;广告牌&#39;点。这可以在每帧渲染数千点,但它有各种复杂功能,模糊了相对简单的结构,正如我上面提到的,我将制作一个更简单的版本来演示你的10x10x10阵列,使用欧几里德距离中心的颜色变化。

    2nd EDIT这是使用pi3d_demos/shaders/star_point

    的广告牌或精灵版
    import pi3d
    import numpy as np
    
    DIM = 10
    half_d = DIM/2.0
    arr_len = DIM ** 3
    
    disp = pi3d.Display.create()
    shader = pi3d.Shader('shaders/star_point')
    cam = pi3d.Camera()
    spheres = pi3d.Points(camera=cam, point_size=400.0, z=15.0,
              vertices=[[x - half_d, y - half_d, z - half_d] for x in range(DIM) for y in range(DIM) for z in range(DIM)],
              normals=np.zeros((arr_len, 3)), tex_coords=np.full((arr_len, 2), 1.0))
    spheres.set_shader(shader)
    arr_buf = spheres.buf[0].array_buffer # shortcut to numpy array shape (1000,8) [[vx,vy,vz,nx,ny,nz,u,v]]
    # the star_point shader uses nx,ny,nz as RGB values, only the B value is being
    # changed here i.e. arr_buff[:,5]
    i = 0
    while disp.loop_running():
      spheres.draw()
      ix = np.where(np.sum((arr_buf[:,:3] - [half_d, half_d, half_d]) ** 2, axis=1) <= i * DIM)[0]
      arr_buf[:,5] = 0.1 # set all to midnight blue first
      arr_buf[ix,5] = 1.0 # set ones within (i * DIM) ** 0.5 to blue
      spheres.re_init() # have to update buffer
      i += 0.1
      if i > 4.0:
        i = 0.0
    

    这是一个使用MergeShape的版本,然后调整uv坐标

    import pi3d
    import numpy as np
    
    DIM = 10
    half_d = DIM/2.0
    arr_len = DIM ** 3
    
    disp = pi3d.Display.create()
    shader = pi3d.Shader('uv_light')
    cam = pi3d.Camera()
    tex_array = np.zeros((16,16,3), dtype=np.uint8)
    tex_array[:8,:8] = [0, 0, 25] # top left midnight blue
    tex_array[8:, 8:] = [0, 0, 255] # bottom right bright blue
    tex = pi3d.Texture(tex_array, mipmap=False)
    spheres = pi3d.MergeShape(camera=cam, z=15.0)
    spheres.merge([[pi3d.Sphere(radius=0.1, sides=6, slices=6), x - half_d, y - half_d, z - half_d, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0] 
                                          for x in range(DIM) for y in range(DIM) for z in range(DIM)])
    spheres.set_draw_details(shader, [tex])
    arr_buf = spheres.buf[0].array_buffer # shortcut to numpy array shape (1000,8) [[vx,vy,vz,nx,ny,nz,u,v]]
    arr_buf[:,6:8] *= 0.5 # scale uv to just use top left part of texture
    base_tex_c = arr_buf[:,6:8].copy()
    i = 0
    while disp.loop_running():
      spheres.draw()
      ix = np.where(np.sum((arr_buf[:,:3] - [half_d, half_d, half_d]) ** 2, axis=1) <= i * DIM)[0]
      arr_buf[:,6:8] = base_tex_c # set uv to base (top left)
      arr_buf[ix,6:8] += 0.5 # set index ix to bottome right
      spheres.re_init() # have to update buffer
      i += 0.1
      if i > 4.0:
        i = 0.0
    

    我发现使用默认的Sphere,数组缓冲区的大小变得太大,因此将其减少到6x6版本。希望这可以在某个阶段帮助某人。

答案 2 :(得分:-1)

一个非常好的开始是将所有这些代码放入一个函数中,然后调用该函数。就像现在一样,你正在使用的所有内容都被放入全局命名空间,这无疑是slow

然后,取出当前在循环内的所有内容,不需要在循环之外。例如,您可以在绘制球体之前尝试着色/纹理化球体,或者至少在循环之外进行颜色计算(如果可能的话)。

如果您想更改语言,请尝试使用cython,它会将此代码编译为C,同时保留所有库调用等。