我有一个类似的光线追踪器(用 Python 编写)并且偶然发现了同样的问题:为了正确计算物理,必须知道交叉边界每一侧的折射率。这需要很长时间才能优雅地解决,但最后我采用了这个解决方案/设计:
设计
1)场景——我有一个主场景对象(基本上是场景中所有对象的数组),你可能会有类似的东西。它存储几何对象。
方法:
intersection_points(ray)
- 返回所有交点的列表,按与射线的距离排序。
intersection_objects(ray)
- 返回所有相交对象的列表,按与射线的距离排序。
containing_object(ray)
- 返回包含射线的对象。
objects()
- 以任意顺序返回所有对象的列表。
注意:场景向列表中添加了一个额外的对象:Scene_Boundary。这是一个巨大的盒子(或球体),它封装了整个场景,即一切都在这个边界内。
2) 对象——使几何对象(例如你的球体)实现这些方法。
方法:
contains(ray)
- 返回 True 是射线原点在对象内部,如果在表面上,则返回 False,如果在外部,则返回 False
ray_is_on_surface(ray)
- 如果光线仅在表面上,则返回 True,否则返回 False。
intersection_points(ray)
- 返回光线与对象的交点
surface_normal(ray)
- 返回光线撞击表面的表面法线向量(这将有助于菲涅耳反射和折射)
对于光学计算,物体还必须具有折射率。
实例变量:
边界问题
我们要解决的问题:边界内(n1)和外(n2)的折射率是多少?为此,我们遵循以下程序:
1) 在整个场景中追踪光线:
sphere # origin = (0,0,0), radius = 1
ray # origin = (0,0,0), direction = (0,0,1) Note: the ray is inside the sphere
scene.add_object(sphere)
ipoints = scene.intersection_points(ray) # [ (0,0,1), (0,0,10) ]
iobjects = scene.intersection_objects(ray) # [ Sphere, Scene_Boundary]
请记住,这些是按距射线原点的距离排序的。ipoints 和 iobjects 中的最后一项是光线与场景边界的交点。我们稍后将使用它!
2) n1 只需通过查找包含对象即可找到,例如:
obj1 = scene.containing_object(ray) # Scene_Boundary
n1 = obj1.refractive_index() # n1 = 1. Scene_Boundary always has refractive index of Air
3) n2 是通过在 iobject 列表中向前查看一个对象来找到的,例如在伪代码中:
index = iobjects.index_of_object(obj1)
obj2 = iobjects[index+1]
n2 = obj2.refractive_index() # n2 = 1.5 e.g. Glass
4)获取表面法线以备后用:
normal = obj1.surface_normal(ray)
您拥有计算正确反射和折射所需的所有信息。即使光线在对象之外,这也足够通用,但偶尔我确实需要实现一些逻辑过滤以使算法更健壮,但基本上就是这样!
反射和折射
您只需知道表面法线就可以反射矢量。在 Python 中使用 numpy 我这样做,
def reflect_vector(normal, vector):
d = numpy.dot(normal, vector)
return vector - 2 * d * normal
折射(如所讨论的)需要 n1 和 n2 值:
def fresnel_refraction(normal, vector, n1, n2):
n = n1/n2
dot = np.dot(norm(vector), norm(normal))
c = np.sqrt(1 - n**2 * (1 - dot**2))
sign = 1
if dot < 0.0:
sign = -1
refraction = n * vector + sign*(c - sign*n*dot) * normal
return norm(refraction)
最后,您需要计算光线的反射系数,其中角度是光线方向与表面法线之间的角度(假设您的光线是“非偏振的”)。将此与 0 到 1 之间的随机数进行比较,以确定是否发生反射。
def fresnel_reflection(angle, n1, n2):
assert 0.0 <= angle <= 0.5*np.pi, "The incident angle must be between 0 and 90 degrees to calculate Fresnel reflection."
# Catch TIR case
if n2 < n1:
if angle > np.arcsin(n2/n1):
return 1.0
Rs1 = n1 * np.cos(angle) - n2 * np.sqrt(1 - (n1/n2 * np.sin(angle))**2)
Rs2 = n1 * np.cos(angle) + n2 * np.sqrt(1 - (n1/n2 * np.sin(angle))**2)
Rs = (Rs1/Rs2)**2
Rp1 = n1 * np.sqrt(1 - (n1/n2 * np.sin(angle))**2) - n2 * np.cos(angle)
Rp2 = n1 * np.sqrt(1 - (n1/n2 * np.sin(angle))**2) + n2 * np.cos(angle)
Rp = (Rp1/Rp2)**2
return 0.5 * (Rs + Rp)
最后的评论
这一切都来自我尚未发布的 Python 光学光线追踪项目(!),但您可以在此处查看一些详细信息: http: //daniel.farrell.name/freebies/pvtrace。我喜欢Python!此处列出了许多 Python 光线追踪项目,http ://groups.google.com/group/python-ray-tracing-community/web/list-of-python-statistical-ray-tracers 。最后,请注意示例中的分数折射率,等式将失效。
更新
在我的光线追踪器中实现的屏幕截图,可在http://github.com/danieljfarrell/pvtrace获得
