线光栅化:覆盖所有像素,无论线条渐变?

时间:2010-12-07 20:30:58

标签: c# algorithm line gradient pixel

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

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

我似乎无法找到任何“粗线”算法,任何人都可以帮我找到一个吗?

Red: Bad. Green: Good!
格林:我想要什么。
红色:我目前拥有和不想要的东西。

6 个答案:

答案 0 :(得分:7)

我和你有完全相同的问题,并找到了一个非常简单的解决方案。通常,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;
        }
    }
}

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

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;
        }
    }
}

现在算法不再同时改变两个坐标。 我没有对此进行过彻底的测试,但似乎效果很好。

答案 1 :(得分:5)

这个帖子很老了,但我认为将它放在互联网上是值得的:

// 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)));
    }
}

答案 2 :(得分:2)

不失一般性,假设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;
  }
}

楼层调用可能只是作为一个整数写入。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

您可以找到光线与水平网格线相交的所有交点,然后标记一行上有交点的行上的所有单元格,或者在行上有交点的两个单元格之间。

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

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

顺便说一下,据我所知,这是基于网格的raycaster“3D”游戏(如Wolfenstein 3D)的完成程度。我在很久以前就从this book开始阅读这个算法。