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

光线追踪高效渲染优化:避免全场景渲染

Python光线追踪优化:视锥体剔除与动态分辨率实现

1. 视锥体剔除(Frustum Culling)实现

核心思路

光线追踪中,视锥体剔除的本质是提前过滤相机视野外的物体,避免对这些物体执行无意义的光线相交检测。针对你找到的基础Python光线追踪代码,核心是给每个可渲染物体添加轴对齐包围盒(AABB),再判断包围盒是否在相机视锥体内。

实现步骤与代码片段

步骤1:给物体添加包围盒

以原代码中的Sphere类为例,新增AABB属性:

class Sphere:
    def __init__(self, center, radius, material):
        self.center = center
        self.radius = radius
        self.material = material
        # 计算轴对齐包围盒的最小/最大顶点
        self.aabb_min = center - Vec3(radius, radius, radius)
        self.aabb_max = center + Vec3(radius, radius, radius)

步骤2:计算相机视锥体的6个平面

在Camera类中添加视锥体平面计算逻辑,平面用(A,B,C,D)表示(满足平面方程Ax+By+Cz+D=0):

import math

class Camera:
    def __init__(self, lookfrom, lookat, vup, vfov, aspect_ratio):
        # 保留原相机初始化逻辑(u/v/w向量、origin等)
        self.fov_rad = math.radians(vfov)
        self.half_height = math.tan(self.fov_rad / 2)
        self.half_width = aspect_ratio * self.half_height
        self.near_plane_z = 0.01  # 近裁剪面距离
        self.far_plane_z = 100.0  # 远裁剪面距离
        # 计算世界空间下的视锥体平面
        self.frustum_planes = self._calc_world_frustum_planes()

    def _calc_world_frustum_planes(self):
        # 相机空间下的6个平面
        cam_planes = [
            (0.0, 0.0, 1.0, -self.near_plane_z),    # 近平面
            (0.0, 0.0, -1.0, self.far_plane_z),     # 远平面
            (1.0, 0.0, self.half_width/self.near_plane_z, 0.0),  # 左平面
            (-1.0, 0.0, self.half_width/self.near_plane_z, 0.0), # 右平面
            (0.0, -1.0, self.half_height/self.near_plane_z, 0.0),# 上平面
            (0.0, 1.0, self.half_height/self.near_plane_z, 0.0)  # 下平面
        ]
        # 转换为世界空间(利用相机正交矩阵的转置作为逆矩阵)
        u, v, w, origin = self.u, self.v, self.w, self.origin
        world_planes = []
        for A,B,C,D in cam_planes:
            new_A = A*u.x + B*v.x + C*w.x
            new_B = A*u.y + B*v.y + C*w.y
            new_C = A*u.z + B*v.z + C*w.z
            new_D = D + A*origin.x + B*origin.y + C*origin.z
            world_planes.append((new_A, new_B, new_C, new_D))
        return world_planes

步骤3:包围盒与视锥体相交判断

添加工具函数,判断AABB是否在视锥体内:

def is_aabb_in_frustum(aabb_min, aabb_max, frustum_planes):
    # 检查每个平面,若AABB完全在平面外侧则剔除
    for A,B,C,D in frustum_planes:
        # 计算AABB在平面法线上的最大投影值
        max_proj = max(
            A*aabb_min.x + B*aabb_min.y + C*aabb_min.z,
            A*aabb_max.x + B*aabb_min.y + C*aabb_min.z,
            A*aabb_min.x + B*aabb_max.y + C*aabb_min.z,
            A*aabb_max.x + B*aabb_max.y + C*aabb_min.z,
            A*aabb_min.x + B*aabb_min.y + C*aabb_max.z,
            A*aabb_max.x + B*aabb_min.y + C*aabb_max.z,
            A*aabb_min.x + B*aabb_max.y + C*aabb_max.z,
            A*aabb_max.x + B*aabb_max.y + C*aabb_max.z,
        )
        # 最大投影值 < -D → AABB完全在平面外侧
        if max_proj < -D:
            return False
    return True

步骤4:整合到光线追踪主逻辑

修改原代码的trace函数,先过滤视锥体外的物体:

def trace(ray, objects, camera, depth):
    hit_record = None
    closest_t = float('inf')
    # 过滤可见物体
    visible_objects = [obj for obj in objects if is_aabb_in_frustum(obj.aabb_min, obj.aabb_max, camera.frustum_planes)]
    # 仅对可见物体执行相交检测
    for obj in visible_objects:
        record = obj.hit(ray, 0.001, closest_t)
        if record:
            closest_t = record.t
            hit_record = record
    # 保留原有的反射/折射逻辑
    return hit_record

2. 动态分辨率缩放(Dynamic Resolution Scaling)实现

核心思路

针对离相机较远的物体,降低对应区域的渲染分辨率(如1/2或1/4分辨率),再通过双线性插值上采样还原到目标分辨率,减少光线发射的总数量。

实现步骤与代码片段

步骤1:定义分辨率等级与距离阈值

在主渲染函数中配置距离阈值与对应缩放比例:

# 动态分辨率规则:距离越远,缩放比例越小
DRS_CONFIG = [
    (20.0, 1.0),   # 距离<20 → 1x分辨率
    (50.0, 0.5),   # 20≤距离<50 → 0.5x分辨率
    (float('inf'), 0.25)  # 距离≥50 → 0.25x分辨率
]

步骤2:预渲染低分辨率图层并上采样

修改原渲染函数,先生成距离图,再按规则渲染低分辨率图层,最后上采样:

def render(camera, objects, image_width, image_height):
    # 第一步:生成距离图(记录每个像素对应的世界空间距离)
    distance_map = [[float('inf')]*image_width for _ in range(image_height)]
    for j in range(image_height-1, -1, -1):
        for i in range(image_width):
            u = (i + random.random()) / (image_width-1)
            v = (j + random.random()) / (image_height-1)
            ray = camera.get_ray(u, v)
            hit = trace(ray, objects, camera, 0)
            if hit:
                distance_map[j][i] = (hit.p - camera.origin).length()
    
    # 第二步:预渲染各分辨率的图层
    drs_layers = {}
    for _, scale in DRS_CONFIG:
        if scale not in drs_layers:
            drs_w = int(image_width * scale)
            drs_h = int(image_height * scale)
            layer = []
            for j in range(drs_h-1, -1, -1):
                row = []
                for i in range(drs_w):
                    u = (i + random.random()) / (drs_w-1)
                    v = (j + random.random()) / (drs_h-1)
                    ray = camera.get_ray(u, v)
                    color = trace_color(ray, objects, camera, 50)  # 原代码的颜色追踪函数
                    row.append(color)
                layer.append(row)
            drs_layers[scale] = layer
    
    # 第三步:双线性插值上采样到目标分辨率
    final_image = []
    for j in range(image_height-1, -1, -1):
        row = []
        for i in range(image_width):
            dist = distance_map[j][i]
            # 匹配对应缩放比例
            scale = 1.0
            for threshold, s in DRS_CONFIG:
                if dist < threshold:
                    scale = s
                    break
            layer = drs_layers[scale]
            drs_w, drs_h = len(layer[0]), len(layer)
            # 计算插值坐标
            x = (i / image_width) * drs_w
            y = (j / image_height) * drs_h
            x0, x1 = int(math.floor(x)), min(int(math.ceil(x)), drs_w-1)
            y0, y1 = int(math.floor(y)), min(int(math.ceil(y)), drs_h-1)
            dx, dy = x - x0, y - y0
            # 双线性插值计算颜色
            c00 = layer[y0][x0]
            c01 = layer[y1][x0]
            c10 = layer[y0][x1]
            c11 = layer[y1][x1]
            color = (1-dx)*(1-dy)*c00 + dx*(1-dy)*c10 + (1-dx)*dy*c01 + dx*dy*c11
            row.append(color)
        final_image.append(row)
    return final_image

内容的提问来源于stack exchange,提问作者user23205098

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 13:24:52