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