Python光学仿真:光线追迹中反射与折射的算法实现
本文详细解析利用Python实现光线追迹中反射与折射的数学原理与代码逻辑。内容涵盖平面与弧面上的光线交互,通过几何对称与垂足计算推导反射角与折射角,并处理全反射边界条件。文章提供完整的Python函数实现,包括点线对称、垂足求解及通用接口封装,适用于光学仿真开发参考。
折射与反射
光线与光学元件发生相互作用时,主要表现为人射光的反射与透射(折射)两种现象。针对当前常用的光学元件,其表面形态主要简化为平面与球面两类。在二维几何模型中,这种关系进一步简化为射线与线段(平面)或射线与劣弧(球面)之间的几何交互问题。
平面反射
从计算复杂度来看,平面上的反射与折射处理比球面更为简单,而其中反射问题的求解又优于折射问题。因此,在构建仿真模型时,优先处理平面反射问题有助于建立基础逻辑。
根据反射定律,入射角等于反射角。最直接的解题思路是计算入射光线与平面之间的夹角,结合平面在二维空间中的斜率,推导出射光线的斜率,进而得到出射光线的方程表达式。
上述直接计算法的主要缺陷在于需要频繁调用三角函数及其反函数。由于三角函数与反三角函数并非严格意义上的互为逆运算,在参数传递过程中容易因象限判断或定义域限制引发数值误差或逻辑错误。
相比之下,一种更稳健且不易出错的方法是几何对称法:先找到入射点关于平面法线的对称点,该对称点与光线交点的连线即为出射光线的方程。这种方法避免了复杂的三角运算,提高了数值稳定性。


平面折射
折射问题的解决思路与反射类似,初始想法同样是获取入射角,依据折射定律计算折射角,再根据折射角推导光线方程。然而,这一思路同样面临三角函数与反函数转换带来的数值处理难点。



进一步分析发现,折射与反射在表达形式上具有内在一致性。若从入射点向法线作垂线,垂足记为C,并约定该垂线与出射光线的交点为出射点B,则出射点到垂足的距离BC与入射点到垂足的距离AC之间存在比例关系。当入射光线与反射光线的折射率相等时,该比例为1;否则,比例系数为λ。
值得注意的是,λ值并非总是存在实数解。由于计算过程中分母包含根号表达式,若根号内数值小于0,则无实数解。这与物理直觉一致:并非所有入射光线都能产生折射光线,当满足特定条件导致折射光线消失时,即发生全反射现象。
因此,当前的关键步骤是根据入射点确定垂足坐标,其计算公式如下(见下文代码逻辑)。

基于上述推导,对于常规折射问题,可通过两次对称变换求解:首先将入射点关于反射平面进行对称,随后关于法线进行定比延长对称,从而精确得到出射点位置。
python实现
至此,我们已建立完整的反射与折射几何关系模型,具体Python实现代码如下:
#得到点关于直线的对称点,k为比例系数
def getSymDot(point,line,k=1):
return tuple((np.array(getPedal(point,line))*(1+k)-point)/k)
#得到直线的垂足
def getPedal(point,line):
a,b,c=line
x0,y0 = point
y1 = (a**2*y0-a*b*x0-b*c)/(a**2+b**2)
x1 = (b**2*x0-a*b*y0-a*c)/(a**2+b**2)
return (x1,y1)
函数getSymDot通过输入点和直线参数来求解对称点,其核心逻辑是将点关于直线的对称问题转化为点关于垂足的对称问题。为此,代码中调用了getPedal函数,该函数接收一点一线作为输入,返回过该点所作直线的垂足坐标。
所有代码逻辑均是对前述数学公式的直接复现,确保几何关系的准确映射。
def cataDioLine(abc=[1,-1,1],line=[2,-1,1],
sPoint=[],cross=[],n1=1,n2=1.5):
normal = [-line[1],line[0],line[1]*cross[0]-line[0]*cross[1]]#法线
flecDot = getSymDot(sPoint,normal)
flec=getABC([cross,flecDot])
dPara = np.sqrt(line[0]**2+line[1]**2)
dNormal = np.abs(np.array(normal).dot(list(sPoint)+[1]))/dPara#到法线距离
dPane = np.abs(np.array(line).dot(list(sPoint)+[1]))/dPara#到反射面距离
if dNormal == 0:
return flec,abc
delt = (n2/n1)**2*(1+(dPane/dNormal)**2)-1#判定全反射
if delt>0:
k =dPane/dNormal/np.sqrt(delt)
fracDot = getSymDot(sPoint,normal,k)
fracDot = getSymDot(fracDot,line)
frac = getABC([cross,fracDot])
return flec,frac
return flec,[0,0,0]
函数cataDioLine实现了反射与折射的综合计算。需注意,此处引入的getABC并非前文定义的通过点和角度求直线方程的函数,而是通过两点坐标转换为[a,b,c](即直线一般式系数[a,b,c])的辅助函数。
鉴于Python语言不支持传统意义上的函数重载,若需实现同名但不同功能的操作,只能将不同逻辑封装在同一函数内部,通过参数类型或数量进行区分:
def getABC(*par):
if len(par)==1: #此时传入的参数为点对dots=[(x0,y0),(x1,y1)]
dots = par[0]
abc = [dots[1][1]-dots[0][1],
dots[0][0]-dots[1][0],
-np.linalg.det(dots)]
return np.array(abc)/(np.sqrt(abc[0]**2+abc[1]**2))
elif len(par)==2: #此时传入的参数为点和角度(x0,y0),theta
theta,sPoint = par
a,b = [np.sin(theta),-np.cos(theta)]
c = -(a*sPoint[0]+b*sPoint[1])
return [a,b,c]
观察输入参数(*par),部分开发者可能会产生误解,但这仅是Python的一种可变参数传参语法。其中,(*args)表示将传入的位置参数打包为列表args;(**kargs)表示将传入的关键字参数打包为字典kargs。
弧面问题
光线在弧面上的反射问题属于“看似复杂实则简单”的典型场景。只要能够准确找到弧面在交点处的法线,即可将该问题转化为前述的平面反射问题进行求解。
因此,核心问题被简化为求解圆的切线方程,该切线即视为反射平面。由于数学推导过程较为直接,此处省略具体公式,读者可通过阅读代码反推其几何原理。
#获取过交点的圆弧的切线
def getTangent(corss=[0,1],circle=[0,0,1]):
a = corss[0]-circle[0]
b = corss[1]-circle[1]
c = -a*corss[0]-b*corss[1]
return [a,b,c]
综上所述,我们得到了一套完整的折射与反射问题求解方案,具体实现如下:
#光在直线或弧线表面的反折射
def cataDio(abc=[1,-1,1],dots=[(0,2),(2,2)],
sPoint=[-2,-1],n1=1,n2=1.5):
cross = getCross(abc,dots,sPoint) #获取交点
if cross == []:
return [],[],[]
if len(dots)==3:
line = getTangent(cross,arc2cir(dots)) #圆上切线
elif len(dots)==2:
line = getABC(dots)
flec,frac = cataDioLine(abc,line,sPoint,cross,n1,n2)
return cross,flec,frac
在此方案中,getCross函数也需要进行重构,使其不仅适用于直线,也能适配弧线场景:
def getCross(abc=[1,-1,0],dots=[[0,-1],[0,1],[0.5,0]],point=[]):
if len(dots)==3:
return getCrossArc(abc,dots,point)
if len(dots)==2:
return getCrossDots(abc,dots,point)
通过实践发现,使用两个点表示线段、三个点表示弧线是一种较为直观且舒适的数据表达方式。这种表达形式上的统一,有助于简化代码逻辑并提升可读性。
以上就是python光学仿真实现光线追迹折射与反射的实现的详细内容,更多关于python光线追迹的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









