如何根据高度图计算可见区域?

时间:2011-08-22 22:57:10

标签: graphics geometry

我有一个高度图。我想有效地计算在任何给定位置和高度从眼睛可见的哪些瓷砖。

This paper表示高度图表现优于将地形转换为某种网格,但他们使用Bresenhams对网格进行采样。

如果我采用它,我必须为地图上的每一块瓷砖做一条视线Bresenham线。在我看来,如果你从眼睛向外填充,它应该可以重复使用大部分计算并在一次通过中计算高度图 - 扫描线填充方法可能是什么?

但逻辑让我感到震惊。逻辑是什么?

这是一个高度图,其中有一个特定的观景点(绿色立方体)的可见性(在“分水岭”中的“视域”?)画在它上面:

enter image description here

这是我提出的O(n)扫描;我在下面的答案How to compute the visible area based on a heightmap?富兰克林和雷的方法中看起来与我在论文中给出的相同,只是在这种情况下,我从眼睛向外走,而不是走向外围,朝着中心做一个bresenhams;在我看来,我的方法会有更好的缓存行为 - 即更快 - 并使用更少的内存,因为它不必跟踪每个磁贴的向量,只记住扫描线的价值:

typedef std::vector<float> visbuf_t;

inline void map::_visibility_scan(const visbuf_t& in,visbuf_t& out,const vec_t& eye,int start_x,int stop_x,int y,int prev_y) {
    const int xdir = (start_x < stop_x)? 1: -1;
    for(int x=start_x; x!=stop_x; x+=xdir) {
        const int x_diff = abs(eye.x-x), y_diff = abs(eye.z-y);
        const bool horiz = (x_diff >= y_diff);
        const int x_step = horiz? 1: x_diff/y_diff;
        const int in_x = x-x_step*xdir; // where in the in buffer would we get the inner value?
        const float outer_d = vec2_t(x,y).distance(vec2_t(eye.x,eye.z));
        const float inner_d = vec2_t(in_x,horiz? y: prev_y).distance(vec2_t(eye.x,eye.z));
        const float inner = (horiz? out: in).at(in_x)*(outer_d/inner_d); // get the inner value, scaling by distance
        const float outer = height_at(x,y)-eye.y; // height we are at right now in the map, eye-relative
        if(inner <= outer) {
            out.at(x) = outer;
            vis.at(y*width+x) = VISIBLE;
        } else {
            out.at(x) = inner;
            vis.at(y*width+x) = NOT_VISIBLE;
        }
    }
}

void map::visibility_add(const vec_t& eye) {
    const float BASE = -10000; // represents a downward vector that would always be visible
    visbuf_t scan_0, scan_out, scan_in;
    scan_0.resize(width);
    vis[eye.z*width+eye.x-1] = vis[eye.z*width+eye.x] = vis[eye.z*width+eye.x+1] = VISIBLE;
    scan_0.at(eye.x) = BASE;
    scan_0.at(eye.x-1) = BASE;
    scan_0.at(eye.x+1) = BASE;
    _visibility_scan(scan_0,scan_0,eye,eye.x+2,width,eye.z,eye.z);
    _visibility_scan(scan_0,scan_0,eye,eye.x-2,-1,eye.z,eye.z);
    scan_out = scan_0;
    for(int y=eye.z+1; y<height; y++) {
        scan_in = scan_out;
        _visibility_scan(scan_in,scan_out,eye,eye.x,-1,y,y-1);
        _visibility_scan(scan_in,scan_out,eye,eye.x,width,y,y-1);
    }
    scan_out = scan_0;
    for(int y=eye.z-1; y>=0; y--) {
        scan_in = scan_out;
        _visibility_scan(scan_in,scan_out,eye,eye.x,-1,y,y+1);
        _visibility_scan(scan_in,scan_out,eye,eye.x,width,y,y+1);
    }
}

这是一种有效的方法吗?

  • 使用中心点而不是查看“内部”像素与LoS通过的一侧的邻居之间的斜率
  • 可以使用trig来缩放向量,这样可以用因子乘法来代替吗?
  • 它可以使用一个字节数组,因为高度本身是字节
  • 它不是一个径向扫描,它一次做一条完整的扫描线但远离该点;它只使用了几条扫描线 - 额外的内存,这是整洁的
  • 如果它有效,你可以想象你可以使用径向扫描块来很好地分配它;你必须首先计算最中心的区块,然后你可以从中分配所有紧邻的区块(它们只需要给出边缘最中间的值),然后又越来越多的并行性。

那么如何最有效地计算这个视域?

1 个答案:

答案 0 :(得分:3)

您想要的是扫描算法。基本上你将光线(Bresenham's)投射到每个周边细胞,但是在你去的时候跟踪地平线并将你在路上传递的任何细胞标记为可见或不可见(如果可见则更新光线的地平线)。这会让你从原始方法的O(n ^ 3)(单独测试nxn DEM的每个单元格)到O(n ^ 2)。

paper第5.1节中对算法的更详细描述(如果您希望使用真正巨大的高度图,您可能会因其他原因而感到有趣)。