文章仅供个人学习使用,如果对内容感兴趣的,可以直接参考最后的链接查看原文学习。
Visibility Determination is the process of deciding whether there is a clear & unimpeded line of sight from one game entity or point in space to another.
可见性判断是确定从一个游戏实体或空间点到另一游戏实体是否存在清晰且不受阻碍的视线的过程。
- Hit detection---确定一个实体是否应该受到类似射线的游戏效果的影响,例如射线拾取,光束武器或快速/短程子弹(忽略使子弹偏离直线较长距离的射弹物理特性)。
- Stealth gameplay mechanics---确定角色或安全设备是否可以看到可疑或敌对的事物。 在这种情况下,可见性可以考虑照明,部分遮挡或伪装,以表示某物是否“引人注目”,而不仅仅是视线可达。
- Light propagation and fog of war(战争迷雾)---确定从特定角色的角度来说,游戏世界的哪些区域以及其中的哪些实体是可见的,或者从特定光源接收光。 在2D游戏中,这通常是通过计算可见性多边形(visibility polygons)来实现的。
- Occlusion Culling---确定哪些对象在渲染的场景中不可见,因此可以从最密集的处理中将其省略。
补充:2d Visibility (in grid)
在2D top-down的地图中,有时候从给定点计算哪些区域可见是有用的。例如,您可能想隐藏从玩家位置不可见的内容,或者您想知道手电筒将照亮哪些区域。
链接中的作者实现了一个程序,可以拖动玩家位置来查看玩家的可见度:
https://www.redblobgames.com/articles/visibility/
该算法还可以计算给定光源下哪些区域被照亮。 每个灯光运行一次,都可以构建一个光照图,以显示哪些区域被照亮,感兴趣的可以去看下。
正文:
为了以合理和正确的方式查看场景,必须省略从特定角度不可见的所有部分。 特别地,这些是不透明物体或被其他物体遮挡的部分的背面。 这称为hidden-line或hidden-surface-removal。根据场景的复杂性,对象的类型和数据结构,可用的硬件以及应用程序的要求,可以使用不同的可见性确定算法。使用对象空间object-space方法时,将对象的位置相互比较,并且仅绘制前(可见)部分。 使用图像空间image-space方法时,对于图像的每个部分,可见度都是单独确定的。 以下说明未考虑透明对象。
一、Backface Detection (Backface Culling)
Backface Culling不是完整的可见性判断方法。有了它,所有法线都指向远离观察者,因此不可见的多边形将被消除,以减少后续步骤的成本。平均而言,此过程会删除50%的多边形。为了计算使用正交投影时要删除的多边形,需要计算视图矢量与表面法线的标量积(Vview * N> 0→不可见);对于透视投影,视点(x,y,z)为 代入平面方程式(Ax + By + Cz + D <0→不可见)
二、Z-Buffering (Depth Buffering)
z-buffering算法通过以下方式解决了特定图像分辨率的可见性问题:对于图像的每个点,除了颜色信息之外,有关显示对象位置的信息还存储在自己的存储器中。由于视角方向通常是(负)z方向,因此x和y值对应于像平面坐标,这样我们仅需要存储z值。
因此,除了图像缓冲区(帧缓冲区)之外,还需要一个额外的缓冲区,该缓冲区可以存储每个像素的z值。
此缓冲区称为z-buffer或depth-buffer。使用z缓冲区,可以按任意顺序绘制所有对象。计算要绘制的对象的z值(在大多数情况下为多边形),并将其与要在其上绘制对象的像素的z值进行比较。如果新的z值离viewer更近(因此通常更大),则将在此处绘制新的对象颜色,替换旧的图像值,并且z缓冲区中的z值也会更新。否则,该对象将被遮挡,并且无需采取任何措施:
for all (x,y) (* initializing the background *)
depthBuff(x,y) = - ∞
frameBuff(x,y) = backgroundColor
for each polygon P (* loop over all polygons *)
for each position (x,y) on polygon P
calculate depth z
if z > depthBuff(x,y) then
depthBuff(x,y) = z
frameBuff(x,y) = surfColor(x,y)
(* else nothing! *)
可以为平面多边形有效地增量计算z值。 z-buffer的最大优点是不必对对象(多边形)进行排序。
三、Scan-Line Method
使用扫描线方法,将为每个水平线计算正确的可见性(在示例中,从上到下,因此y减小)。这样,可以利用的是,两条连续的像素线(扫描线)通常具有相似的可见性属性。
示例中,一共有三个多边形,分别为ABC(对应颜色),我们对扫描线和与其相交的边记下来,形成我们图中的表。
从包含按最大y值排序的多边形所有边的表和关联的多边形表开始,为每条扫描线创建一个活动边列表。
从最后一条扫描线的边缘开始逐步进行此操作:
消除已结束的边缘,并检查已排序边缘表的下一条边缘(如果已开始)。用这种方法计算出一条扫描线与所有边缘的所有交点后,将按其x值(从左到右)对它们进行排序。 在每对相交点之间,必须确定哪个多边形最接近viewer; 这是可见的,将沿着此扫描线绘制。
四、Depth-Sorting Method
深度排序算法的原理是从后到前对所有多边形进行排序,然后按此顺序绘制它们。由于所有隐藏(被遮挡的)部分都比遮挡的部分远,因此生成的图像具有正确的可见性(painter’s algorithm)。
主要花费是排序,必须以一种方式进行,即没有多边形(部分)遮挡列表中后来出现(因此更靠近viewer)的任何其他多边形。
这分两个步骤完成:首先快速进行近似排序,然后检查排序是否正确。 如果不是,将使用该列表。
具体:
1.近似排序--Approximate sorting:根据最小的z值(最远的多边形)对多边形进行排序。
2.将每个多边形S与每个(!)其他多边形S’进行比较:
(*假设S位于S'*后面)
(*现在进行一系列测试,并且测试的复杂度不断提高,直到可以接受排序为正确*为止)
a.S的最大z值小于S’的最小z值→排序正确。
b.x或y间隔不交叉→排序正确
c.S的所有顶点都在S’的平面后面→排序正确
d.S’的所有顶点都在S平面的前面→排序正确
e.S和S’在xy平面上的投影不相交(请参见下面的图片)→排序正确
f.排序可能是错误的情况(请参见S隐藏在S后面的图片)→交换并再次检查排序。 如果排序再次错误,则发生了一种特殊情况(参见图片),必须通过分割一个多边形来解决:
五、Area-Subdivision Method
与图像的四叉树表示类似,通过将区域细分为四个四分之一,可以以低分辨率解决简单问题,并简化更复杂的问题。 如果递归地应用到图像分辨率,可以实现可见度问题的像素精确解决方案。
为此,需要一种方法来快速发现多边形投影在(二次)图像窗口上的位置。 有四种可能性:(1)多边形覆盖整个窗口,(2)多边形部分位于窗口的内部和部分外部,(3)多边形完全位于窗口的内部,或者(4)多边形完全位于窗口的外部 。
有三个简单的visibility decisions:
- 1. 所有多边形都在窗口外 → done
- 2.只有一个多边形与窗口相交→ draw this polygon
- 3.一个多边形覆盖整个窗口,并位于窗口区域中所有其他多边形的前面→ draw this polygon
如果所有三个测试均失败,则将窗口分为四个quarters,然后将其递归处理。
请注意,已经完全位于窗口外部的多边形也位于其子窗口之外。 同样,如果多边形已覆盖整个窗口,则其所有子窗口也将被该多边形覆盖。 如果子窗口仅达到一个像素的大小,则选择最近的多边形。 从示例中可以看出,这发生在可见性发生变化的所有边缘。
六、Octree
如果将场景表示为八叉树而不是多边形,那么对于每个视图方向,数据结构都已经知道哪一侧在前面,哪一侧在后面。
可以按以下方式递归地呈现数据结构:首先,在一个多维数据集中,呈现离viewer最远的子多维数据集,然后呈现下一个近三个子多维数据集,然后是下三个,最后是最近的子多维数据集。 在下面的示例中可以看到一种可能的排序方式:
或者,可以从前到后进行渲染。 然后,必须已经存储了绘制了某些东西的所有区域,以便仅绘制那些将保持可见的区域。 这种数据结构相对于其他数据结构的优势在于,它隐式知道哪些部分在前面,哪些部分在后面。
七、Ray-Casting
Ray-Casting是一种可见性判断的方法,可以为每个像素显式计算在那里可见的东西。为此,从每个像素向场景中射出一条直线,即一条直线。向后思考(Thinking backwards),通过该射线,光从场景传输到图像平面或分别传输到viewer。
如果此射线与场景的所有对象或多边形相交,则将获得一组交点,并选择最接近viewer的交点。在此相交点处的表面颜色决定了射线所穿过的像素的颜色。 如果对所有像素都执行此操作,则每个像素的结果就是最近的对象的颜色,即可见的对象。
通过射线投射,不仅可以以简单的方式渲染多边形,而且还可以计算与直线相交的其他类型的表面(例如,自由曲面)。如后面所述,通常在交点处需要表面法线,以便能够计算出有用的阴影。 相反,射线投射非常昂贵,因为必须为每个对象(每个像素可能要成千上万个)计算一个交点(每个像素全屏需要几百万个)。 这就是为什么必须有效执行交集测试和进一步优化的原因。
Ray-casting 是ray-tracing的简单变体,可以使用它模拟许多其他光学效果。 这将在后面的章节中进行解释。
Ray-Casting =
for each pixel of the image-plane:
generate a line through the pixel in viewing-direction (“viewing-ray”)
intersect the ray with all objects
choose the nearest intersection point to the viewer
color the pixel with the color of the surface at this intersect. point
八 总结
让我们最后对上面提到的算法做一个分类:
Object-space methods:backface culling, depth sorting, octree
Image-space methods: z-buffering, scanline method, area subdivision method, ray-casting
[1] Visibility Determination---Tag Info https://gamedev.stackexchange.com/tags/visibility-determination/info
[2]visibility polygons https://www.redblobgames.com/articles/visibility/
[3]Visibility-Determination https://www.cg.tuwien.ac.at/courses/CG1/textblaetter/englisch/05%20Visibility%20Determination%20(engl).pdf