区域遮挡-视野

Partial Occlusion Field-of-View

by pixelpracht on August 5th, 2012

It seems like there are problems that I have to solve over and over again.Pathfinding is one of them. Another is visibility determination.

看起来有些问题得反反复复地解决,自动寻路是如此,另外一个是视野的计算。

 

When you need to know if there’s an unobstructed line of sight betweenpoint A and point B you typically just cast a ray from A to B and see if ithits something. The set of all points that can be reached with an unobstructedline from A are referred to as A’s Field of View. Calculating itaccurately would require an unlimited amount of rays, so you typicallysacrifice accuracy by partitioning space in some discrete data structure.Grids, Trees, Pixels, Voxels… that kind of thing.

当你想知道光从A能否畅通无阻的传播到B点,你只要换一条A到B的直线,然后看看是否中间有阻碍物体。那么从A点发射出去的环绕中心360度的直线 所能到达的点就形成了一块饼状的域,我们叫它视野Field of View.如果要精确的计算就需要无限条射线。所以你只能用一些最终导致降低精确度的办法:像素采样 格子 树形(像素和格子可以理解,trees 和 voxels在视野算法里是怎么回事?求解) 等等。

 

In this post I’ll focus on FoV calculations on squaregrids. Using a low resolution square grid is a pretty huge abstraction, andthe whole point of this article is to show how to get some accuracy backwithout losing too much performance! But let’s startsimple…

在这篇文章里,我将围绕着FoV算法在方格上的应用。用低度的方格是非常抽象的,本文的重点是展示怎样去准确无误而又不失太多观赏性!^_^但是让我们从简单的开始..

 

Calculate Field of View with Raycasting

以光线投射法计算视野

 

As long as you only care for a Boolean result (is a square B visible fromsquare A in the center) it’s straight forward to calculate using raycasting.
First of all consider how many rays you need: Just enough so that every fieldthat could potentially be part of the Field of View is passed by atleast one ray. So better define a max range or you would still have to cast anunlimited number of rays despite working on a low resolution squaregrid. Now select fields at maximum range so that those fields completelyenclose your center. Draw lines to them and all closer fields are automaticallytested too!

 

只要你关心true or false (对于以A点为光源来看方块B是否可见).只要直接使用光线投射算法。

首先考虑多少射线你需要:经可能多的这样所有的区域可能有一条射线穿过。这样你最好定义射线条数的上限值,否则你可能得到了无穷多的线条了,尽管你 采用的是低密度方格。现在选择一个靠近A的一块区域(10*10的格子怎么样).从A点画线到他们然后线进过的地方将被自动测试。


 

Raycasting only towards fields at max-range reduces the algorithmscomplexity from O(n³) to O(n²). Thtat’s a huge difference! Imagine howthe illustration would look like if all cells would be target of  araycast…

光线投射算法只计算了最大范围次,将算法复杂度从O(n^3)降低到了O(n^2)。这是一个巨大的不同点!想象下如果所有的格子都成了射线的目的地,那成什么样子了(蜘蛛网吗)

 

For line-drawing you can just use Bresenham’s Line Algorithm. All clear fieldscovered by the line are part of the field of view. As soon as you hit an opaquefield abort the raycast. It’s not part of the field of view and all otherfields that follow along the direction of the ray are occluded by it.

画线的方法你可以用下贝里森汉直线算法。所有可以被直线覆盖到的地方都是视野区域。当直线撞到了遮挡物,光线就被主档掉了。就不再是视野的可见范围了,所有直线延续的那段都被阻挡了。

 

There’s room for improvement

还可有所突破

When I tried to use the raycasting approach to implement dynamic lightingfor an isometric 2D game engine I found that I would have to find an improvedalgorithm to make it look great.

当我用了辐射靠近去实现动态的光照效果为一个2d游戏引擎,我发现我不得不对算法采取点措施让效果看起来更好点

Consider an opaque field casting a shadow. A lot of fields willneither be fully lit nor fully in shadow. From light source’s point of viewthose fields are partially occluded! So I wanted my fields to specifytheir membership in the Field of View as a percentage (0 – 100%) and notjust a Boolean (true or false).

考虑到一个遮挡块会投射下阴影。许多块将不是全亮也不是全在阴影中的。从光源的点投射过来的光在被某个块遮挡了之后将会导致其旁边的块处在半明半暗之中。因为我们是以块为单位计算他的亮度值得,所以我们希望他是一个半分比的值,代表了他是只接受到一半的光照还是1/3的光照。

The other limitation I hoped to overcome were integer coordinates. Itdidn’t suffice to say a light-source is on Field (x,y) – I wanted to specifythe exact position on the field the light would come from. So I neededto be able to precisely define the center of my Field of View by using non-integercoordinates.

另外一个局限是我希望把光源的整数坐标改成double型,因为我不想只是说光源是在那个块上的,我希望光源是一个点出发,所以他可能是在1.333,1.444这样的,这样我才能更精确的计算出光辐角度。所以我需要一个非整型坐标。

The Mission

任务

I hope I managed to convey an idea of what problem the algorithm I’m aboutto present is trying to solve. To be sure let’s sum up the situation:

我想传达下我对于算法有哪些不足之处,然后尽力去解决它。让我们先设定一个场景吧

  • We operate on a square grid that partitions a 2D plane in equally sized fields.
  • 我们在2d平面上分隔出若干个等同大小的格子
  • Some squares are clear (will allow light to pass through) while others are opaque (will block light)
  • 一些格子是完全通透的光能直接穿过去。有一些是不透明的光纤不能传过去。
  • We define a point for which we want to calculate the Field of View.
  • 我先定义一个光源点。
  • We want to know the degree of occlusion for each square closer then the max range.
  • 我们要在光照范围内计算每个格子的被遮挡值。

My research didn’t yield any established solution so I accepted thechallenge to find a new one (or reinvent the wheel, who knows). Here’s ademonstration of my solution in action:

我研究了半天也没有发现可以突破的办法所以我只好挑战下自己创造一个算法(重造轮子,谁知道呢).这里是一个我写的demo

点击打开链接

The full source of the demo isavailable under the MIT license. However, deducting an algorithm from poorlydocumented AS3 source code might be a little inconvenient if you want tounderstand the principles behind it. So I conclude the post with a explanationof the algorithm.

完整版本受MIT  license保护。可是算法AS3源代码的文档有限,如果你想去理解很不方便。所以我归纳了这篇文章来解释这个算法。

Partial Occlusion Field of View

It helps to think of center of the Field of View as a light source fromwhich light is spreading equally in all directions. The spreading light willexit a square on the edges facing away from the light source and enter aneighbouring square. So for each square you can clearly identify one or twoneighbouring cells that all the light is coming from.

Now all you have to do to know how much light is going through aparticular square is to ask the relevant neighbours to describe theircontribution. If a neighbouring square is opaque it will contribute no light.Otherwise it will contribute a portion of the light it itself received – whichcan be anything between zero and the width of the shared edge.

Circular sectors to represent beams of light

A simple way to express the “portion of light” is a circular sector. Withthe center known we just have to store two angles to define such a sector.

This illustration shows how the light passing between two occluders can bedescribed by specifying two angles: Alpha and Theta.

Due to the nature of the grid one sector (e.g. two angles) per neighbouris enough to describe it’s contribution! This is because occluders are alwaysone square in size and thus the shadows they cast can’t be smaller then onesquare. With this method of describing lightbeams it’s easy to form unions andintersections of lightbeams, too. Unions, when we want to combine the lightcoming from two different neighbouring squares and Intersections when weconstrain the result to the size of our current square.

The Algorithm

The algorithm can be summed up as follows:

FOR EACH non-blocking square:
  1: Identify all neighbours that are not opaque
     AND share an edge AND are closer to the center.
  2: Store the contribution from the first neighbour
     as circular sector A.
  3: IF a second neighbour exists: store the contribution
     from the second neighbour as circular sector B.
  4: Calculate a circular sector C that describes the maximum
     amount of light that could possibly pass the currentsquare.
  5: Form the UNION of A and B and INTERSECT with C to receive D.
     D describes the amount of light actually passing thecurrent square.
     The smaller the ratio of D to C the bigger the occlusion.

The following illustration demonstrates the process on a square thathappens to be half occluded. Only one neighbour contributes light, while theother is opaque.


This animated illustration shows how to calculate the occlusion of asquare based on the occlusion of the two of it’s neighbours closer to thecenter.

Calculating a Squares circular Sector


But how to calculate a circular sector that describes the maximum widthlight beam that could go through a particular square (labeled ‘C’ in the aboveillustration)?
To find it you simply calculate alpha for all four corners of the squareand pick the largest and the smallest angle to form the circular sector. Theresulting sector will enclose the full square.

The illustration shows that there’s a distinct pattern in what corners youwould have to pick. So you don’t have to calculate four values and then discardtwo. Instead you can evaluate the sign of the x and y component of the vectorpointing from lightsource to the square and to predict the correct ones. And ifyou’re going through the sourcecode of my reference implementation and noticethese weird looking lines now you know what they do! dx and dy describe theposition of the square in relation to the center and (x1, y1) and (x2, y2) arethe corner’s that define the sector.

var sx:Number = (dx == 0) ? 0 :(dx < 0) ? -1 : 1;

var sy:Number = (dy == 0) ? 0 :(dy < 0) ? -1 : 1;

var ox:Number = (sy == 0) ? sx: 0

var oy:Number = (sx == 0) ? sy: 0

var x1:Number = x + 0.5 * (1 -sy + ox);

var y1:Number = y + 0.5 * (1 +sx + oy);

var x2:Number = x + 0.5 * (1 +sy + ox);

var y2:Number = y + 0.5 * (1 -sx + oy);

Recursion vs Iteration

Another difficulty worth mentioning is that to calculate a particularsquare’s occlusion the occlusion of it’s relevant neighbours must be knownfirst. How can we make sure they are already calculated?

The obvious approach would be to use recursion, which means that wheneverwe query the sector of a cell that hasn’t yet been evaluated it’s sector willbe calculated before the query returns the correct value. To calculate the FoV(e.g. the degree of occlusion of all squares within max-range of the center) usingrecursion, trigger the evaluation in the four corners of the boundingrectangle. All squares enclosed by the rectangle will be visited before thecalls return. There’s just no other way for the recursion to terminate than byreaching the center tile for which the occlusion is always known. This is whenthe evaluation-method ceases to calling itself on neighboring cells and beginsto return values. It’s a good idea to implement it so that each cell is onlycalculated the first time it is queried and cache the result. That waysubsequent queries don’t incur an unnecessary reevaluation.

There’s nothing wrong with that approach as long the stack space suffices.But if this could be a problem it’s also possible to devise an iterator patternthat will visit squares in such an order that all relevant neighbours havealready been calculated.


The demonstration I’ve linked implements both the recursive and theiterator based version of the algorithm as well as some raycasting based FoV asa reference. Here’s the source code of thedemo! It’s released under the MIT License and includes all relevantdependencies. I hope you enjoyed the lengthy read, feel free to post questionsor comments!

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值