Shadow Mapping与光线追踪
Shadow Mapping本质是一种图像空间的算法,不需要知道场景的几何信息。
关键思想:
不在阴影中的点必须可以同时被摄像机和光源看到。
Shadow Mapping步骤
从光源渲染
假设光源有个虚拟相机,从光源看向场景,做光栅化,记录深度信息的图像,生成深度图。
从摄像机渲染
从摄像机看场景,对于每一个看到的点,将其映射回光源渲染的深度图上,连接光源和这个点,计算点到光源的距离, 比较从光源到这个看到的点的深度和其在深度图上对应的深度,若深度一致,说明这个点可以同时被光源和摄像机看到。
Shadow Mapping结果
如下图非绿色的是阴影应该在的地方
Shadow Mapping的问题:
- 生成的是硬阴影(只对点光源)。
- 阴影的效果取决于shadow map的分辨率。
- 涉及浮点精度比较的问题。
软阴影和硬阴影
如下图,左边是硬阴影(界限比较清晰),右边是软阴影。
软阴影的原理:由于点光源有大小,会形成如图所示的(Umbra)本影区域和半影(Penumbra)区域。所以会形成阴影的过渡。
进行光线追踪(Ray Tracing)的原因
光栅化不能很好的处理全局效果(如下图所示)。
- 难以做软阴影。
- 难以表现光线多次弹射,比如间接光照。
- 光栅化很快但是质量较低
- 光线追踪很准确,但是很慢。光栅化:实时(游戏),光线追踪:离线(动画)。光线追踪一帧就要花10K个计算机小时
光线追踪算法
光线模型的假设
- 光线沿直线传播。
- 光线和光线不会发生碰撞。
- 光线会从光源发出,打到场景中,经过反射折射等最终进入人眼(光线具有可逆性)。
- 对于能够被相机看见的物体,可以认为相机发射出光线。
光线投射(Ray Casting)
光线投射是光线追踪中用于生成初始光线的第一步。
- 通过从每一个像素发射光线来生成图像。
- 通过从光源发射光线来检测阴影。
从视点开始经过像素获得与场景中物体最近的交点。
和光源连线,判断是否对光源可见从而确定该点是否在阴影中。如果物体交点到光源的线中间没有交点,则被相机和光源同时可见,交点被照亮。
然后更加光源能量计算着色,写回像素结果。
Recursive(Whitted- Style) Ray Tracing
此算法要考虑反射和折射的光线。
此算法在79年渲染1帧用了74min、06年PC需要6s、12年的GPU需要1/30s。
对每一个弹射点都要计算着色的值,然后把所有着色的值都加到像素的值里面去(光线是可逆的,你在看物体的同时物体也能看到你)。
光线有不同的分类:primary ray、secondary ray、shadow ray。
结果:
光线与物体的交点 Ray-Object intersections
光线
光线在数学上是有起点和方向的射线,表达式如下,t代表时间。
光线与球求交
光线与球的交点p,必须要满足p既在光线上也在球上。球上任意一点到球心都为半径。
光线和隐式表面求交
解出来结果t,即可得到交点(t必须是实数,也要是正数)。
光线和三角形面求交
对于显示表面与光线求交的关键是求三角形与光线求交。
光线和三角形求交有如下作用:
- 渲染:可见性、阴影、光照…
- 几何:可以判断点是再物体内还是物体外。
- 对任何一个封闭的曲面,在内部找一个点,如果点在形状内,那么从这个点发出的任意方向的射线与此物体的交点数量一定是奇数。
算法(与平面求交再判断是否在三角形内)
简单的办法是让场景中的每个三角形与光线进行求交计算,但是有如下严重问题。
- 简单但是运算量大。
- 可以有0或1个交点。
如果想要计算光线和三角形面的交点,因为三角形在平面内,所以可以把光线和三角形求交拆成两个问题:
- 让光线与平面求交(目前与平面求交比与三角形求交容易)。
- 判断与平面的交点是否在三角形内。
Moller Trumbore算法
Moller Trumbore算法是一种使用重心坐标计算的一种更快的方法。在下面的式子中,左边是光线的表达式,右边是三角形上一点的重心坐标形式。
使用克莱默法则,如果解出b1,b2,1-b1-b2都大于0且t大于0,则说明解是有意义的,说明光线与三角形相交。
这个图像有10.7M个三角形,对于4k的屏幕,每个像素都要发生一条光线,每条光线要计算10.7M个三角形,计算量巨大,需要做加速,解决办法包围盒。
包围盒(Bounding Volumes)
原始的做法是让每个像素的光线要和所有三角形面求交点,计算量太大,那么如何让这个过程加速呢?如果我们圈定包围盒,连与包围盒都不相交的三角形就不用接着算了,那么就可以很大的减少计算量。
基本思想 :使用一个简单的几何体来包围一个复杂的物体,如果光线不饿能碰到包围盒,那么就不用再计算其与里面物体的交点。
求光线与长方体的交点
3D物体通常的包围盒是长方体。
一个重要理解:长方体是三对不同的面形成的交集。
我们使用的包围盒通常是轴对齐(AABB)包围盒。Axis-Aligned Bounding Box(AABB)
经过以上分析,现在问题就i变成了求光线和轴对齐包围盒的交点
求光线和轴对齐包围盒的交点
2维的情况
二维长方形盒子,实质是两对平行线的交叉部分,分别求光线与这两对平行线的交点,对生成的两条线段求交集,即计算tenter=max{tmin},texit=min{tmax},具体过程如下:
先计算二维的长方形。由于前面提到的光线方程是参数方程,所以只要我们计算光线什么时候会和一对平行的边有交点,就可以求出光线进入和离开这个边的时间。
因为一个长方形有两对边,所以会得到两对解,然后求这两对解的交集,tmin,tmax就分别代表着光线进入和离开长方体的时间
简单理解就是进入了所有的边才叫进入,出去了一个边就离开了,下面三维的情况也是同样如此。
3维的情况
根据2维的结果,我们观察发现:
- 只有当光线进入了所有的对面,才能说光线进入了盒子。
- 只要光线离开任意一个对面,光线就离开了盒子。dcscdsdcdscccccccccccccccccccccc
三维立方体是三对平面相交的部分,光线求交推广到三维,则是光线与三对平面分别求进入和出去的时间t,并将这三组t时间求交,最后判断光线是否与盒子相交。
计算tenter(光线进入物体时的t)与texit(光线离开物体时的t):
- 计算每个面的tmin,tmax
- 对于3D盒子,得到光线进入盒子和离开盒子的时间,即计算tenter=max{tmin},texit=min{tmax}
- 如果tenter<texit就说明光线与盒子有交点。
- t为负时的意义:如果texit<0,说明盒子在光线背后,说明没有交点。如果texit>=0并且tenter<0,说明光线的起点在盒子里,一定有交点。
结论:
如果满足tenter<texit且texit>=0,则说明光线与AABB包围盒有交点。
使用轴对齐(AABB)概念的原因
主要目的是简化计算。轴对齐的情况下计算t(只需要考虑各方向的分量即可):
Uniform Spatial Partitions (Grids)均匀空间划分
建立加速格子
- 找到包围盒
- 创建格子
- 标记与物体相交的格子
光线与场景求交点
- 按照光线穿梭的顺序遍历格子
- 对于每个格子,如果盒子格子里有物体,计算存储在格子里的所有物体是否与光线有交点。
加速效果
格子太多和格子太少都是不好的。只有一个格子:没有加速效果格子。特别密集:需要做很多光线与格子求交,加大开销。
即想要获得比较好的加速效果,格子不能太稀疏,也不能太密集。下面是人们经验得到的规律。
- #cells = C * #objs。格子的数量是物体的数量乘以常数
- C ≈ 27 in 3D
格子划分法适用于大小和空间均匀分布的大型对象集合(如下图所示)。
但是此方法不适合于有很多空旷区域的场景(如下图所示),下面又称为“teapot in stadium”问题。teapot in stadium是指如果在空旷的操场放置一个茶壶
空间划分(Spatial Partitions)
把空间划分成不重叠的区域。一个物体可能呗包含在多个区域中。
空间划分的例子
- Oct-Tree 八叉树 把场景包起来,然后把正方体切成八份,横竖割一刀,上下一刀,可以分为八块,二维对应4叉树,一维二叉树。不断的对子节点递归进行此过程,放格子里面是空的或者物体足够少就停下来。(但是有个严重的问题,随着维度的升高,节点数量指数型增长)
- KD-Tree 与八叉树几乎相同,但是对每次划分的格子,总是沿着某一个轴切割(通常是交替的),且对每个格子仅切割一次。(好处是相比于八叉树,节点数量的复杂度不会随着维度指数型增长。)
- BSP-Tree 对空间二分,每次划分时选择一个方向,与KD-Tree的区别在于它的切割不一定是与轴平行的。(存在的问题是在维度高的时候不好计算,切割的几何体,根据几何体的维度,切割的几何体从点到线到面到超平面。)
KD-Tree
建立空间划分在光线追踪之前。
总体来说是一颗二叉树,如下图所示(为了简化没画全,实际上1、2等节点都有子节点)。
对于非叶子节点:
- 划分的轴:x、y或z轴。
- 划分的位置:分割的平面沿轴的坐标。
- 子节点:一定有两个子节点。
- 在非叶子节点不会存储物体。
- 对于叶子节点存储物体列表。
计算光线与物体交点的步骤
首先和最大的包围盒A求交,有交点说明光线与其子节点有交点,访问其孩子节点:
找到叶子节点1,与区域内的所有物体求交。
光线与B包围盒求交,有交点说明光线与其子节点有交点,访问其孩子节点:
找到叶子节点2,与区域内的所有物体求交。
光线与C包围盒求交,有交点说明光线与其子节点有交点,访问其孩子节点:
找到叶子节点3,与区域内的所有物体求交。
光线与D包围盒求交,有交点说明光线与其子节点有交点,访问其孩子节点:
KD-Tree的问题:
- 很难判断物体的三角形和AABB包围盒是否有交集。
- 一个物体可能会与很多AABB包围盒有交集。即一个物体可能存储在多个叶子节点里
引入物体划分避免上述的问题。
物体划分
把物体划分成不相交的子集。每个子集的包围盒在空间上可能是重叠的。
与BVH加速结构(Object Partition & Bounding Volume Hierarchy)
建立过程:
- 找到包围盒
- 递归的把物体集合划分成两个子集
- 重新计算子集的包围盒
- 当满足一定要求的时候停止
- 在每个叶子节点里存储所有物体
首先对于所有物体找到包围盒,形成根节点
然后把物体分成两部分,并重新计算包围盒,形成根节点的子节点。
不断的递归重复这个过程,直达满足一定的条件后停止,最后在叶子节点存储物体列表。
BVH的优缺点:
BVH避免了KD-Tree的问题,具有如下优点。
- 一个物体只能出现在一个格子里。
- 不涉及三角形和包围盒求交的问题。
- BVH并没有把空间严格的划分开(包围盒有可能相交)。
建立中的细节问题
如何划分节点?
每次都需要选择一个维度来分割(经验上有下面两种做法),使树接近平衡。
- 选择节点中最长的轴。比如长长条形的物体。
- 取中间的物体的位置来划分节点。
划分终止条件?
当节点包含少于特定元素时停止(比如5)。
BVH树的存储结构?
- 中间节点存储包围盒和子节点的指针。
- 叶子节点存储包围盒和物体列表。
- 节点代表场景中图元(primitives)的子集,所有物体都在子树中。