从0到实时光线追踪(三):自发光物体与光线的进一步优化

在上一章我们实现了一个简陋的基于光线的光栅化渲染器,在这一章我们将对其进行优化,并实现自发光物体、阴影等功能,使其更加完善。看起来我们似乎完成了一个基于光线的光栅化器,他在2K大小的屏幕上能有80左右的fps,在1080p的屏幕上能呈现140fps,而当窗口大小进一步降低到760p时则能呈现250左右fps的效率。这些数字看起来很大,然而,相较于常规光栅化在4k屏幕上动辄200fps+的渲染效率而言,这一过程还有很大的优化空间。出于对性能的考虑,我们不得不在这里多停留一会儿,对光线求交的过程做出更进一步的优化。我们的预期目标是,在包括光照计算在内的所有光栅化结束时能在1080p下维持至少200fps的效率。这个不断优化的过程十分枯燥,不过好处是,我们将收获一个和寻常光栅化方法在小场景下渲染速度相差无几的、具有物理特性的独特光栅化方法。

实现自发光物体

自发光

自发光的实现很简单,只需要在处理物体表面颜色时,将物体固有色和自发光色、自发光强度相乘即可。

1
2
3
4
5
if hit:
if scene_objects[index].mtl.emission_intensity == 0:
image_pixels[i, j] = scene_objects[index].mtl.albedo * illumination(ray, pos, index)
else:
image_pixels[i, j] = scene_objects[index].mtl.emission_intensity * scene_objects[index].mtl.emission_color

这样我们就得到了具有颜色的自发光物体。

ic7ctc.png

更多光源形状

要计算更多的光源形状,我们只需要使用更准确的距离公式。在最初的光源强度计算公式中,我们假定了光源是一个球体。事实上,我们现在只需要将这一假设改为“光源是一个SDF物体”即可。这样,由于光源是线性变化的,我们只需要使用SDF物体的距离公式去计算点与光源的距离,并将光源向照射点位移这么多的距离,即可得到新光源的表面顶点。得到了新光源的表面顶点,就可以很容易地得到具有更多形状的光源了。

1
2
3
to_light = scene_objects[flag].trans.position - pos
i = normalize(to_light)
to_light = i * sdf_dis(pos, scene_objects[flag])

icz3Py.png

然而,这一处理似乎存在一定的问题。很容易发现,虽说我们考虑了物体的几何形体,但光源目前仍然是从物体的中心点发射出来的,这导致了靠近物体几何中心的地方具有更大的光照强度,显示在now图像上的效果就是离几何中心更近的区域看起来更亮。不过,这是一个很普遍的误区。事实上,一个物体如果离发光物体的中心越近,就越能接收到更多的光照——毕竟物体的两端都会同时发光,而两端的光线都会作用到同一物体上。

不过,我们仍然会认为,这一光照处理是有问题的。观察红色的墙壁,我们可以发现,似乎它的亮度太大了点。这是因为发光物体对其它物体造成的光照效果应当是物体上每个点发出的光照的总和,而这里我们只考虑了光照物体离其它物体最近的的点的距离,这显然是不合理的。作为一种纠正措施,我们在计算光照物体与其它物体的距离时,同时考虑光源中心离“光源与其他物体最近一点”之间的距离。这一纠正措施的基本思想是,由于发光物体的每个点都会向被照明点P发光,且发光物体是SDF,其高度对称,不妨设发光物体所有点指向物体的方向之平均正是物体中心点指向点P的方向。而物体不可能在中心点处受光,因此我们将光源平移到物体上距离点P最近的一点,在该点以既定方向发射光源,即可在模拟物体所有点一起向点P发光的同时。模拟自发光物体的形状导致的光照强度大小变化。

1
2
3
4
5
6
7
8
9
10
11
12
# preparation
ori2pos = (scene_objects[flag].trans.position - pos)
pos2light = -get_SDF_normal(scene_objects[flag], pos) * (sdf_dis(pos, scene_objects[flag]))

i = normalize(normalize(pos2light) + normalize(pos2light + ori2pos))

i_dist = length(pos2light) + (length(pos2light + ori2pos) / scene_objects[flag].mtl.emission_intensity)
i_intensity = scene_objects[flag].mtl.emission_intensity / (4.0 * pi * (i_dist ** 2))

# diffuse reflection
ndl = max(0.0, dot(normal, pos2light), dot(normal, ori2pos)) # N dot L
ld += i_intensity * ndl

此时我们得到了更好的光照效果。

imTNJc.png

光线求交的进一步优化