【问题标题】:Line rasterisation: Cover all pixels, regardless of line gradient?线光栅化:覆盖所有像素,无论线渐变如何?
【发布时间】:2020-10-25 18:02:26
【问题描述】:

基本上,我想使用线算法来确定要为我的 raycaster 检查哪些单元格的碰撞。

Bresenham 不是很好,因为它使用统一厚度方法,这意味着它会忽略至少没有覆盖线一半的单元格。一点都不好,因为这意味着我的线的某些部分没有被检查是否与单元格相交,从而导致错误。

我似乎找不到任何“粗线”算法,谁能帮我找到一个?


格林:我想要什么。
红色:我目前拥有的和不想要的。

【问题讨论】:

  • 只使用包含行的任何部分的单元格肯定很简单吗?
  • 这正是我想要的。但我不知道/不了解它背后的数学原理。
  • 行是如何定义的?作为斜率长度截距?作为坡度长度起始点?作为两个端点?

标签: c# algorithm line gradient pixel


【解决方案1】:

我遇到了与您完全相同的问题,并找到了一个非常简单的解决方案。通常,Bresenham 有两个连续的 if 来确定是否应该增加两个维度的坐标:

public void drawLine(int x0, int y0, int x1, int y1, char ch) {
    int dx =  Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
    int dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
    int err = dx + dy, e2; // error value e_xy

    for (;;) {
        put(x0, y0, ch);

        if (x0 == x1 && y0 == y1) break;

        e2 = 2 * err;

        // horizontal step?
        if (e2 > dy) {
            err += dy;
            x0 += sx;
        }

        // vertical step?
        if (e2 < dx) {
            err += dx;
            y0 += sy;
        }
    }
}

现在您所要做的就是在第二个if 之前插入一个else

public void drawLineNoDiagonalSteps(int x0, int y0, int x1, int y1, char ch) {
    int dx =  Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
    int dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
    int err = dx + dy, e2;

    for (;;) {
        put(x0, y0, ch);

        if (x0 == x1 && y0 == y1) break;

        e2 = 2 * err;

        // EITHER horizontal OR vertical step (but not both!)
        if (e2 > dy) { 
            err += dy;
            x0 += sx;
        } else if (e2 < dx) { // <--- this "else" makes the difference
            err += dx;
            y0 += sy;
        }
    }
}

现在算法不再同时改变两个坐标。 我还没有彻底测试过,但它似乎工作得很好。

【讨论】:

  • 我会试一试,并对其进行基准测试 - 如果它有效并且比我当前的方法更快,我会接受它。
  • 这对我来说似乎很容易出错。查看 Wikipedia 文章,通常只有一个 if if (e2 &lt; dx)。而dy 被初始化为绝对值的负数!?删除 dy 的否定仍然容易出错。水平线会丢失交替的像素,而稍微偏离水平线的线会导致无限循环。
  • @JamesMorris 负值避免了下游代码中的负号也有两个 if ...
  • 此修改绘制的线,因为算法更喜欢水平步骤,并且仅在绝对必要时才执行垂直步骤以将错误指示符保持在范围内。类似的修改会绘制一条更接近原始线的线,根据使误差最小化的方式选择水平或垂直步长。有关类似问题,请参阅 my answer
【解决方案2】:

这个帖子很旧,但我认为值得把它放到互联网上:

// This prints the pixels from (x, y), increasing by dx and dy.
// Based on the DDA algorithm (uses floating point calculations).
void pixelsAfter(int x, int y, int dx, int dy)
{
    // Do not count pixels |dx|==|dy| diagonals twice:
    int steps = Math.abs(dx) == Math.abs(dy)
            ? Math.abs(dx) : Math.abs(dx) + Math.abs(dy);
    double xPos = x;
    double yPos = y;
    double incX = (dx + 0.0d) / steps;
    double incY = (dy + 0.0d) / steps;
    System.out.println(String.format("The pixels after (%d,%d) are:", x, y));
    for(int k = 0; k < steps; k++)
    {
        xPos += incX;
        yPos += incY;
        System.out.println(String.format("A pixel (%d) after is (%d, %d)",
            k + 1, (int)Math.floor(xPos), (int)Math.floor(yPos)));
    }
}

【讨论】:

  • 不错!不是专门封装steps 以有效地提取 45 度情况下的对角线,而是很容易调整它以找到所有角与 45 度线接触的像素?我想得到所有 3 个对角线,好像考虑到仅仅触摸一个像素角就足以算作击中它。
【解决方案3】:

不失一般性,假设 x2 >= x1,则

int x = floor(x1);
int y = floor(y1);
double slope = (x2 - x1) / (y2 - y1);
if (y2 >= y1) {
  while (y < y2) {
    int r = floor(slope * (y - y1) + x1);
    do {
      usepixel(x, y);
      ++x;
    } while (x < r);
    usepixel(x, y);
    ++y;
  }
}
else {
  while (y > y2) {
    int r = floor(slope * (y - y1) + x1);
    do {
      usepixel(x, y);
      ++x;
    } while (x < r);
    usepixel(x, y);
    --y;
  }
}

发言权可以写成一个整数。

【讨论】:

  • 我想,如果 x2 小于 x1,我会交换两个轴?
  • 你会交换两个端点,是的。你的测试工作正常吗?
  • (x1, y1) 和 (x2, y2) 到底是什么数据类型?从您的图形中,我假设它们是浮点数,但如果一切都是整数,则可以进一步优化。
  • 嗯,它们是浮点数,但还没有到整数会不利的地步。
  • 如果y2 == y1,那么当你定义slope时你会被零除。
【解决方案4】:

GPU Gems 中有一篇有趣的文章,也许对你有帮助:Chapter 22. Fast Prefiltered Lines

【讨论】:

【解决方案5】:

如果 Bresenham 有一个额外的限制,即不允许对角线移动:使用传统算法生成点,然后作为后处理步骤插入仅进行正交移动所需的额外步骤。

【讨论】:

  • 这将每秒执行大约 60 次。我负担不起开销:/
【解决方案6】:

您可以找到您的射线与水平网格线的所有交点,然后标记一行上的所有单元格,这些单元格要么在一侧有一个交点,要么在两个单元格之间,并在该行上有交叉点。

找到交叉点可以通过从原点开始,将点推进到第一个交叉点(并标记过程中的单元格),找出带你从一个交叉点到下一个交叉点的向量(这两个操作都是基本相似三角形(或三角)),然后逐列推进,直到你走得足够远。逐列推进涉及每列添加一个向量,以及一个小循环来填充具有交叉点的单元格之间的单元格。如果您正在动态处理单元格,请将“mark”替换为“process” - 此算法保证每个单元格只标记一次。

垂直线也可以这样做,但网格通常存储在水平切片中,所以我选择了它。如果您使用三角函数,则需要处理带有特殊情况的水平直线。

顺便说一下,据我所知,这就是旧的基于网格的 raycaster“3D”游戏(如 Wolfenstein 3D)的制作方式。我第一次从this book 读到这个算法,很久以前。

【讨论】:

  • 嗯?我对此感到困惑。我检查每个矩形是否与线相交?
  • @Ruirize:不,你更喜欢,从原点一步一步去寻找交叉点。检查 Ben Voigt 的答案,了解一段(基本上)是这样的代码......也许它更容易理解。
猜你喜欢
  • 2021-06-21
  • 1970-01-01
  • 2014-09-01
  • 2015-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-21
相关资源
最近更新 更多