文章

Signed Distance Fields

什么是有符号距离场?

想象一下,有一个黑白图像,其中黑色部分被视为内部白色部分被视为外部。想要的是一种快速的方法来查找从任何给定点到内部的距离。

SDF 只是一个图像,其中每个像素包含到边界上最近点的距离。

因此,如果一个像素在外部,那么如果距离 10 个像素,它可能会包含 +10。如果它在里面,它将包含-10。

  • 原始图像

    img

  • SDF

    img

实现

有一个简单的线性时间算法来计算 SDF。(如果你想在 GPU 上执行,还有一个 n-log-n 算法>,但我在这里只给出简单的 CPU 情况)。该算法称为8SSEDT ,这里有一篇关于它的论文。不过要小心,因为论文中存在一些错误。

定义像素网格

1
2
3
4
5
6
7
8
9
10
11
struct Point
{
    int dx, dy;

    int DistSq() const { return dx*dx + dy*dy; }
};

struct Grid
{
    Point grid[HEIGHT][WIDTH];
};

dx/dy 包含从该像素到相对侧最近像素的偏移量。

我们实际上需要 两个 Grid,因为每个 Grid 仅包含正距离。为了获得真实的有符号距离,我们必须执行两次并合并结果。

如果像素

  • 位于“内部”,则我们将网格初始化为 (0,0),
  • 位于”外部”,(+INF,+INF)

注意:不要让你的+INF值太大,否则当你平方它时它会溢出。

1
2
3
4
5
6
7
8
if ( g < 128 )
{
    Put( grid1, x, y, inside );
    Put( grid2, x, y, empty );
} else {
    Put( grid2, x, y, inside );
    Put( grid1, x, y, empty );
}

现在我们要做的就是运行传播算法。请参阅论文,了解这里到底发生了什么,但基本上的想法是查看相邻像素的 dx/dy,然后尝试将其添加到我们的像素中,看看它是否比我们已有的更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void Compare( Grid &g, Point &p, int x, int y, int offsetx, int offsety )
{
    Point other = Get( g, x+offsetx, y+offsety );
    other.dx += offsetx;
    other.dy += offsety;

    if (other.DistSq() < p.DistSq())
        p = other;
}

void GenerateSDF( Grid &g )
{
    // Pass 0
    for (int y=0;y<HEIGHT;y++)
    {
        for (int x=0;x<WIDTH;x++)
        {
            Point p = Get( g, x, y );
            Compare( g, p, x, y, -1,  0 );
            Compare( g, p, x, y,  0, -1 );
            Compare( g, p, x, y, -1, -1 );
            Compare( g, p, x, y,  1, -1 );
            Put( g, x, y, p );
        }

        for (int x=WIDTH-1;x>=0;x--)
        {
            Point p = Get( g, x, y );
            Compare( g, p, x, y, 1, 0 );
            Put( g, x, y, p );
        }
    }

    // Pass 1
    for (int y=HEIGHT-1;y>=0;y--)
    {
        for (int x=WIDTH-1;x>=0;x--)
        {
            Point p = Get( g, x, y );
            Compare( g, p, x, y,  1,  0 );
            Compare( g, p, x, y,  0,  1 );
            Compare( g, p, x, y, -1,  1 );
            Compare( g, p, x, y,  1,  1 );
            Put( g, x, y, p );
        }

        for (int x=0;x<WIDTH;x++)
        {
            Point p = Get( g, x, y );
            Compare( g, p, x, y, -1, 0 );
            Put( g, x, y, p );
        }
    }
}

就是这样!之后您所要做的就是运行一次快速传递,以根据两个正值重建最终的有符号距离值:

1
2
3
int dist1 = (int)( sqrt( (double)Get( grid1, x, y ).DistSq() ) );
int dist2 = (int)( sqrt( (double)Get( grid2, x, y ).DistSq() ) );
int dist = dist1 - dist2;

这是本文的完整源代码:>> URL

本文由作者按照 CC BY 4.0 进行授权