raspberry pi 上的高效 3d

Efficient 3d on raspberry pi

我正在尝试在 10x10x10 网格中的 raspberry pi(模型 2B)上模拟 3d LED 阵列。 我只是想让它们根据模式生成算法打开和关闭。

我已经在 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。将球体更改为立方体会略微改善这一点,但我真的希望至少能将性能提高一个数量级。我知道我可以在数学上取得一些效率提升,但我在随机打开和关闭它们时遇到了类似的性能,所以我现在不关注它。

虽然我可能只是对 raspberry pi 提出了太多要求,但后来玩了捆绑它的 minecraft 游戏,发现它在渲染流畅的同时具有更大的复杂性。

我想知道是否有另一种方法,甚至是另一种语言,我可以用它来获得我正在寻找的那种性能。

我对 3d 编程知之甚少,因此任何人向我指出的任何建议或教程都可能对我有用。

在你做任何事情之前,分析你的代码,看看它在哪里运行宁慢。值得注意的是,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(),而不是对每个球体调用一次。

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

您可以将所有球体合并为一个 pi3d.MergeShape,这样每帧只需要一次 draw() 并且速度会非常快...但是

  1. 您的 Sphere 对象使用 12 边 x 12 切片的默认值,提供 288 个面和 864 个顶点,因此您的 MergeShape 将有 864,000 个顶点,这可能开始减慢 GPU 速度。

  2. 捆绑的着色器只对整个形状使用一个 material RGB 值,你想为每个球体指定不同的颜色,这需要一个 hacked 着色器(如果你很容易做到习惯于破解着色器),您可以在缓冲区数组的纹理坐标字段中指定 RGB 值。

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

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

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

编辑 我只记得 Starfield.py 演示使用可变颜色 'billboard' 点。这可以每帧渲染数千个点,但它具有各种复杂性,使相对简单的结构变得模糊,正如我上面提到的,我将制作一个更简单的版本来演示你的 10x10x10 阵列,使用距中心的欧几里得距离改变颜色。

第二次编辑这是使用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 版本。希望这在某个阶段对某人有所帮助。