我正在为教育目的构建一个简单的光线跟踪器,并希望为对象添加折射。使用Snells Law,我能够在交叉点处递归地创建新的光线。光线跟踪器目前仅支持球体,我使用的场景中我有多个球体彼此嵌套,具有不同的折射率。
如果我从球体外部开始射线,一切看起来都很简单。从场景的折射率开始,一旦击中第一个球体,使用前面的折射率和球体材质的折射率折射光线,直到你击中下一个球体,依此类推。使用交点的法线我可以确定我是进入还是离开球体。
但是,我不明白我应该如何处理球体叶子以及如果光线没有在场景的外部开始时该怎么办。
示例
你有三个球体,从外到内的折射率分别为0.9,1.1和0.8。空气指数为1.0
您的相机位于球体外部并指向球体的中心:
现在问题,当相机在球体内时。您不会知道必须切换的折射率。
答案 0 :(得分:9)
我有一个类似的光线追踪器(用Python编写)并且偶然发现了相同的问题:为了正确地计算出物理学,人们必须知道交叉点边界两侧的折射率。这花了很长时间才能优雅地解决,但最后我选择了这个解决方案/设计:
<强>设计强>
1)场景 - 我有一个主场景对象(基本上是场景中所有对象的数组),你可能会有类似的东西。它存储几何对象。
方法:
intersection_points(ray)
- 返回所有交叉点的列表,按照与光线的距离排序。intersection_objects(ray)
- 返回所有交叉点对象的列表,按照与光线的距离排序。containing_object(ray)
- 返回包含光线的对象。objects()
- 以任意顺序返回所有对象的列表。注意:场景向列表中添加了一个额外的对象: Scene_Boundary 。这是一个封装整个场景的巨型盒子(或Sphere),即一切都在这个边界内。
2)对象 - 使几何对象(例如你的球体)实现这些方法。
方法:
contains(ray)
- 返回True是光线原点在对象内部,如果在表面上则为False,如果在外面则为False ray_is_on_surface(ray)
- 返回True表示光线仅在曲面上,否则为False。intersection_points(ray)
- 返回光线与对象的交叉点surface_normal(ray)
- 返回光线撞击表面的曲面法线向量(这将有助于菲涅耳反射和折射)对于光学计算,物体也必须具有折射率。
实例变量:
refractive_index
边界问题
我们想要解决的问题:边界内部(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)通过在iobject列表中查找前面的一个对象来找到n2,例如,在伪代码中:
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)
您拥有计算正确反射和折射所需的所有信息。即使光线在对象之外,这通常也足以工作,但偶尔我需要实现一些逻辑过滤以使算法更加健壮,但基本上就是这样!
反思和折射
只需知道曲面法线即可反射矢量。在使用numpy的Python中,我这样做,
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
答案 1 :(得分:1)
从物理学的角度发布这个,而不是光线追踪实现的观点:P。
斯内尔定律指出,入射角和折射角的正弦比等于边界两侧两种介质折射率之比的倒数。
因此,当您有一条光线接近新材料而您想知道新材质中的角度时,您需要知道光线撞击新材料的角度,即新材料的折射率,以及光线当前所在材料的折射率。
正如你所说,折射可以很好地移动到球体中,你必须知道每个球体和你的场景的折射率。
我会说创建一叠折射率是处理一堆嵌套材料的好方法,因为你将不得不触摸你再次推入堆叠的所有折射率。你移出了嵌套的球体。
关于确定离开球体时必须开始的折射率,你总是说sin(theta1)/ sin(theta2)= [折射率为2] / [折射率为1]。因此,您需要您当前所在材料的折射率以及您将要移动的材料的折射率。
如果我误解了你的问题,请道歉,但我希望有所帮助!