Python光线追踪代码RecursionError:递归深度超限问题求助
Python光线追踪项目RecursionError问题排查与修复
问题背景
开发Python光线追踪项目时,get_raycolor函数触发RecursionError: maximum recursion depth exceeded,错误来自折射材质的透射光线递归调用。调试发现某特定立方体碰撞体被重复检测上千次,递归无法终止。
相关代码片段
get_raycolor函数
def get_raycolor(ray): distances = [] hit_orientation = [] for s in Sc['scene_primitives']: if isinstance(s,list): if s[0] == 'sphere': a = sphere_intersect(s,ray['origin'],ray['dir']) elif s[0] == 'plane': a = plane_intersect(s,ray['origin'],ray['dir']) else: a = cuboid_intersect(s,ray['origin'],ray['dir']) else: a = s.intersect(ray['origin'], ray['dir']) distances.append(a[0]) hit_orientation.append(a[1]) nearest = np.minimum.reduce(distances) color = vec3(0., 0., 0.) for (coll, dis , orient) in zip(Sc['scene_primitives'], distances, hit_orientation): hit_check = (nearest != FARAWAY) & (dis == nearest) if np.any(hit_check): if isinstance(coll,list): if coll[0] == 'sphere': color += coll[4].get_color({'origin':ray['origin'].extract(hit_check),'dir':ray['dir'].extract(hit_check),'depth':ray['depth'],'n':ray['n'].extract(hit_check),'reflections':ray['reflections'],'transmissions':ray['transmissions'],'diffuse_reflections':ray['diffuse_reflections']}, {'distance':extract(hit_check,dis),'orientation':extract(hit_check,orient),'collider':coll,'u':None,'v':None,'point':None}).place(hit_check) elif coll[0] == 'cuboid': color += coll[5].get_color({'origin':ray['origin'].extract(hit_check),'dir':ray['dir'].extract(hit_check),'depth':ray['depth'],'n':ray['n'].extract(hit_check),'reflections':ray['reflections'],'transmissions':ray['transmissions'],'diffuse_reflections':ray['diffuse_reflections']}, {'distance':extract(hit_check,dis),'orientation':extract(hit_check,orient),'collider':coll,'u':None,'v':None,'point':None}).place(hit_check) else: color += coll[6].get_color({'origin':ray['origin'].extract(hit_check),'dir':ray['dir'].extract(hit_check),'depth':ray['depth'],'n':ray['n'].extract(hit_check),'reflections':ray['reflections'],'transmissions':ray['transmissions'],'diffuse_reflections':ray['diffuse_reflections']}, {'distance':extract(hit_check,dis),'orientation':extract(hit_check,orient),'collider':coll,'u':None,'v':None,'point':None}).place(hit_check) else: color += coll.material.get_color({'origin':ray['origin'].extract(hit_check),'dir':ray['dir'].extract(hit_check),'depth':ray['depth'],'n':ray['n'].extract(hit_check),'reflections':ray['reflections'],'transmissions':ray['transmissions'],'diffuse_reflections':ray['diffuse_reflections']}, {'distance':extract(hit_check,dis),'orientation':extract(hit_check,orient),'collider':coll,'u':None,'v':None,'point':None}).place(hit_check) return color
Refractive材质类
class Refractive: def __init__(self, n, **kwargs): self.n = n def get_color(self, ray, hit): hit['point'] = (ray['origin'] + ray['dir'] * hit['distance']) if isinstance(hit['collider'],list): if hit['collider'][0] == 'sphere': N = (hit['point'] - hit['collider'][3]) * (1. / hit['collider'][5])*hit['orientation'] max_depth = hit['collider'][2] elif hit['collider'][0] == 'cuboid': N = cuboid_get_Normal(hit['collider'],hit) max_depth = hit['collider'][8] else: N = hit['collider'][4] max_depth = hit['collider'][3] else: N = hit['collider'].get_Normal(hit)*hit['orientation'] max_depth = hit['collider'].max_ray_depth color = vec3(0.,0.,0.) V = ray['dir']*-1. nudged = hit['point'] + N * .000001 if ray['depth'] <max_depth: n1 = ray['n'] n2 = vec3(np.where(hit['orientation']== UPWARDS, self.n.x, Sc['n'].x),np.where(hit['orientation']== UPWARDS, self.n.y, Sc['n'].y),np.where(hit['orientation']== UPWARDS, self.n.z, Sc['n'].z)) n1_div_n2 = vec3(np.real(n1.x), np.real(n1.y), np.real(n1.z))/vec3(np.real(n2.x), np.real(n2.y), np.real(n2.z)) cosdeltai = V.dot(N) sin2deltat = (n1_div_n2)**2 * (1.-cosdeltai**2) vv = 1. - (n1/n2)**2 * (1.-cosdeltai**2) cosdeltat = vec3(np.sqrt(vv.x) , np.sqrt(vv.y) ,np.sqrt(vv.z)) r_per = (n1*cosdeltai - n2*cosdeltat)/(n1*cosdeltai + n2*cosdeltat) r_par = -1.*(n1*cosdeltat - n2*cosdeltai)/(n1*cosdeltat + n2*cosdeltai) F = (np.abs(r_per)**2 + np.abs(r_par)**2)/2. reflected_ray_dir = (ray['dir'] - N * 2. * ray['dir'].dot(N)).normalize() color += (get_raycolor({'origin':nudged, 'dir':reflected_ray_dir, 'depth':ray['depth'] + 1, 'n':ray['n'], 'reflections':ray['reflections'] + 1, 'transmissions':ray['transmissions'], 'diffuse_reflections':ray['diffuse_reflections']}))*F n1_div_n2_aver = (n1_div_n2.x + n1_div_n2.y + n1_div_n2.z)/3 sin2deltat = (n1_div_n2_aver)**2 * (1.-cosdeltai**2) non_TiR = (sin2deltat <= 1.) if np.any(non_TiR): # 错误触发行:depth未递增 color += (get_raycolor( {'origin':(hit['point'] - N * .000001).extract(non_TiR),'dir':(ray['dir']*(n1_div_n2_aver) + N*(n1_div_n2_aver * cosdeltai - np.sqrt(1-np.clip(sin2deltat,0,1)))).normalize().extract(non_TiR),'depth':ray['depth'],'n':n2.extract(non_TiR),'reflections':ray['reflections'],'transmissions':ray['transmissions'] + 1,'diffuse_reflections':ray['diffuse_reflections']}) )*(1. - F).extract(non_TiR).place(non_TiR) vv = -2.*vec3(np.imag(ray['n'].x), np.imag(ray['n'].y), np.imag(ray['n'].z))*2.*np.pi/vec3(630,550,475) * 1e9* hit['distance'] color = color *vec3(np.exp(vv.x) , np.exp(vv.y) ,np.exp(vv.z)) return color
根本原因分析
- 递归终止条件失效:在透射光线的
get_raycolor调用中,depth参数直接使用ray['depth']而非ray['depth'] + 1。结合立方体的max_depth=5,递归调用的depth始终为0,永远满足ray['depth'] < max_depth,导致无限递归。 - 潜在自相交问题:透射光线的原点偏移方向是否正确需要验证,若偏移方向错误,可能导致光线刚进入物体就再次检测到与自身相交,加剧递归次数。
修复方案
- 修复递归深度递增:修改透射光线的
depth参数为ray['depth'] + 1,确保递归能在达到max_depth时终止:color += (get_raycolor( {'origin':(hit['point'] - N * .000001).extract(non_TiR),'dir':(ray['dir']*(n1_div_n2_aver) + N*(n1_div_n2_aver * cosdeltai - np.sqrt(1-np.clip(sin2deltat,0,1)))).normalize().extract(non_TiR),'depth':ray['depth'] + 1,'n':n2.extract(non_TiR),'reflections':ray['reflections'],'transmissions':ray['transmissions'] + 1,'diffuse_reflections':ray['diffuse_reflections']}) )*(1. - F).extract(non_TiR).place(non_TiR) - 验证偏移方向:确认透射光线的原点偏移方向(
hit['point'] - N * .000001)是否正确。当光线进入介质时(hit['orientation']为向下),应向物体内部偏移;若方向错误,调整为hit['point'] + N * .000001或根据法线方向动态判断。 - 增加全局递归限制:在
get_raycolor开头添加全局最大深度检查,防止单个物体max_depth设置过大:GLOBAL_MAX_DEPTH = 10 def get_raycolor(ray): if ray['depth'] >= GLOBAL_MAX_DEPTH: return vec3(0.,0.,0.) # 原有代码...
调试建议
- 打印递归深度:在
get_raycolor开头添加print(f"Recursion depth: {ray['depth']}"),确认深度是否递增。 - 断点调试:使用
pdb.set_trace()在错误触发行设置断点,查看每次递归的ray参数和max_depth值。 - 简化场景测试:先创建仅包含单个折射立方体的场景,验证递归终止逻辑,再逐步添加其他物体。
- 统计碰撞次数:给每个碰撞体添加计数,打印被检测的次数,确认是否存在重复检测或自相交。
代码结构优化建议
- 用类替代列表存储场景图元:将球体、立方体、平面定义为类(如
Sphere、Cuboid),统一intersect、get_normal等方法,避免大量isinstance判断,提高代码可读性。 - 提取光线创建工具函数:将反射、透射光线的创建逻辑封装为函数,减少重复代码,比如:
def create_reflected_ray(origin, dir, normal, depth, n, reflections): reflected_dir = (dir - normal * 2. * dir.dot(normal)).normalize() nudged_origin = origin + normal * 0.000001 return {'origin': nudged_origin, 'dir': reflected_dir, 'depth': depth+1, 'n': n, 'reflections': reflections+1, 'transmissions':0, 'diffuse_reflections':0} - 添加类型提示:为函数参数和返回值添加类型提示,提升代码可维护性,比如:
def get_raycolor(ray: dict[str, Any]) -> vec3: # 代码... - 缓存相交结果:对同一光线与场景图元的相交结果进行缓存,避免重复计算,提升性能。
内容的提问来源于stack exchange,提问作者FrostDream
相关产品推荐
相关产品推荐

