You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何提升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实现大幅加速。

修改示例:

  1. 替换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]]
  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)
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 20:55:37