如何提升Python中scene._render_pygame_def_update()的运行速度
3D渲染器性能优化方案
1. 预计算相机旋转的三角函数值
当前project_and_rotate函数中,每处理一个顶点都会重复计算cos(rotx)、sin(rotx)等同帧内固定的值,这是巨大的计算浪费。可以在每一帧渲染前预计算这些值,再传入投影函数复用。
修改示例:
调整projected_des方法预计算三角函数:
def projected_des(self, cam): # 预计算当前相机旋转的三角函数值 cos_rx, sin_rx = cos(cam.rx), sin(cam.rx) cos_ry, sin_ry = cos(cam.ry), sin(cam.ry) cos_rz, sin_rz = cos(cam.rz), sin(cam.rz) def project_func(coord): return project_and_rotate(coord.x, coord.y, coord.z, cos_rx, sin_rx, cos_ry, sin_ry, cos_rz, sin_rz, cam.x, cam.y, cam.z, cam.cx, cam.cy, cam.cz, 10, cam.render_distance) return map(project_func, self.verts.get_coords([0,-1]))
修改project_and_rotate函数直接使用预计算值:
@lru_cache(maxsize=None) def project_and_rotate(x, y, z, cos_rx, sin_rx, cos_ry, sin_ry, cos_rz, sin_rz, posx, posy, posz, cx, cy, cz, scale, render_distance): x, y, z = x - posx, y - posy, z - posz if abs(x) > render_distance or abs(z) > render_distance: return None # 复用子计算结果,减少重复运算 xz_rot = x * cos_rz - y * sin_rz yz_rot = y * cos_rz + x * sin_rz temp1 = xz_rot * cos_ry - z * sin_ry temp2 = z * cos_ry + xz_rot * sin_ry z_depth = temp2 * cos_rx + yz_rot * sin_rx + 5 + cz if z_depth <= 0: # 避免除以0或负深度导致的异常 return None px = temp1 * (315 / z_depth) * scale + cx py = (yz_rot * cos_rx - temp2 * sin_rx) * (315 / z_depth) * scale + cy return [round(px), round(py)]
2. 用Numpy批量处理顶点运算
Python循环逐个处理顶点的效率极低,改用Numpy对整个顶点数组进行批量矩阵运算,利用其底层C实现大幅加速。
修改示例:
- 替换
coordinate类,直接用Numpy数组存储顶点:
class verticies_structure: def __init__(self): self._verts = numpy.array([], dtype=numpy.float32).reshape(0, 3) def add_vert(self, x, y, z): new_vert = numpy.array([[x, y, z]], dtype=numpy.float32) self._verts = numpy.vstack([self._verts, new_vert]) def get_coords(self, indexes): return self._verts[indexes[0]:indexes[1]]
- 重构旋转投影为矩阵运算:
def get_rotation_matrix(cos_rx, sin_rx, cos_ry, sin_ry, cos_rz, sin_rz): # 构建Z->Y->X顺序的旋转矩阵 rot_z = numpy.array([ [cos_rz, -sin_rz, 0], [sin_rz, cos_rz, 0], [0, 0, 1] ], dtype=numpy.float32) rot_y = numpy.array([ [cos_ry, 0, sin_ry], [0, 1, 0], [-sin_ry, 0, cos_ry] ], dtype=numpy.float32) rot_x = numpy.array([ [1, 0, 0], [0, cos_rx, -sin_rx], [0, sin_rx, cos_rx] ], dtype=numpy.float32) return rot_x @ rot_y @ rot_z def project_vertices(vertices, rot_matrix, cam_pos, cx, cy, cz, scale, render_distance): # 平移顶点到相机空间 cam_space = vertices - cam_pos # 剔除超出渲染距离的顶点 mask = (numpy.abs(cam_space[:, 0]) <= render_distance) & (numpy.abs(cam_space[:, 2]) <= render_distance) cam_space = cam_space[mask] # 应用旋转 rotated = cam_space @ rot_matrix.T # 计算深度,剔除负深度顶点 z_depth = rotated[:, 2] + 5 + cz mask = z_depth > 0 rotated = rotated[mask] z_depth = z_depth[mask] # 透视投影 proj_factor = 315 / z_depth px = rotated[:, 0] * proj_factor * scale + cx py = rotated[:, 1] * proj_factor * scale + cy # 转为整数屏幕坐标 return numpy.round(numpy.column_stack([px, py])).astype(numpy.int32)
- 修改
projected_des方法使用批量运算:
def projected_des(self, cam): cos_rx, sin_rx = cos(cam.rx), sin(cam.rx) cos_ry, sin_ry = cos(cam.ry), sin(cam.ry) cos_rz, sin_rz = cos(cam.rz), sin(cam.rz) rot_matrix = get_rotation_matrix(cos_rx, sin_rx, cos_ry, sin_ry, cos_rz, sin_rz) cam_pos = numpy.array([cam.x, cam.y, cam.z], dtype=numpy.float32) vertices = self.verts.get_coords([0, -1]) return project_vertices(vertices, rot_matrix, cam_pos, cam.cx, cam.cy, cam.cz, 10, cam.render_distance)
3. 替换set_at为批量绘制
Pygame的set_at是单像素操作,效率极低。收集所有有效顶点后,用pygame.draw.points批量绘制,可大幅提升渲染速度。
修改示例:
调整_render_pygame_def_update中的绘制逻辑:
def _render_pygame_def_update(self): self._pygame_screen.fill((0,70,0)) self.regulate_camera() all_points = [] screen_w, screen_h = self._window_wh for idx, vclass in self._model.items(): for model in vclass: points = model.projected_des(self._camera) if len(points) > 0: # 过滤超出屏幕范围的点 mask = (points[:, 0] >= 0) & (points[:, 0] < screen_w) & (points[:, 1] >= 0) & (points[:, 1] < screen_h) valid_points = points[mask].tolist() all_points.extend(valid_points) # 批量绘制所有有效顶点 if all_points: pygame.draw.points(self._pygame_screen, (255,255,255), False, all_points, 1) if self._super_ls: self._pygame_screen.blit(self._lss_hdri_file_jpg_surf,(0,0))
4. 提前剔除不可见模型/顶点
在投影运算前,通过包围盒判断模型是否完全在视锥体之外,直接跳过整个模型的计算,减少无效运算量。
修改示例:
在mesh类中添加包围盒和可见性判断:
class mesh(object): def __init__(self, file_obj, cam): # ... 原有代码 ... size=100 for x in range(size): for z in range(size): self.verts.add_vert(x-size//2,0,z-size//2) # 计算模型包围盒 self.min_vert = numpy.min(self.verts._verts, axis=0) self.max_vert = numpy.max(self.verts._verts, axis=0) # ... 原有代码 ... def is_visible(self, cam): # 简单包围盒视锥体剔除 cam_pos = numpy.array([cam.x, cam.y, cam.z]) box_center = (self.min_vert + self.max_vert) / 2 distance = numpy.linalg.norm(box_center - cam_pos) box_radius = numpy.linalg.norm(self.max_vert - box_center) # 若包围盒完全在渲染距离外,判定为不可见 return distance - box_radius <= cam.render_distance
渲染时先判断模型可见性:
def _render_pygame_def_update(self): self._pygame_screen.fill((0,70,0)) self.regulate_camera() all_points = [] screen_w, screen_h = self._window_wh for idx, vclass in self._model.items(): for model in vclass: if not model.is_visible(self._camera): continue # 跳过不可见模型 points = model.projected_des(self._camera) if len(points) > 0: mask = (points[:, 0] >= 0) & (points[:, 0] < screen_w) & (points[:, 1] >= 0) & (points[:, 1] < screen_h) valid_points = points[mask].tolist() all_points.extend(valid_points) if all_points: pygame.draw.points(self._pygame_screen, (255,255,255), False, all_points, 1) if self._super_ls: self._pygame_screen.blit(self._lss_hdri_file_jpg_surf,(0,0))
内容的提问来源于stack exchange,提问作者Roberto E. Torres
相关产品推荐
相关产品推荐

