精确的子像素线绘制算法(光栅化算法)

时间:2014-07-10 15:02:10

标签: algorithm math graphics raytracing bresenham

我需要一种比Bresenham line drawing algorithm慢一点的算法,但必须更准确。使用' exact'我的意思是:应该打印每个触摸的像素。不多也不少!这意味着使用更粗的线或类似物不是一种选择,因为将涉及太多的像素。此外,我不需要像asked before这样的图形框架或类似框架,我需要算法!该应用程序并非真正属于' graphics'它位于geography area,其中像素是' tiles'。

对我来说,主要的问题是我需要亚像素精度,这意味着一条线可以从0.75 / 0.33开始,而不仅仅是0/0,就像整数值的情况一样。我尝试在过去几个小时内创建一个可行的解决方案,但无法使其正常工作 - 边缘情况太多了。

首先,我认为像Wu的算法那样的抗锯齿版本应该可以实现,但它会打印太多像素(特别是对于起点和终点),并且在某些情况下它仍会遗漏某些像素,例如非常短的。

然后我试着让Bresenham工作,我替换了第二个' if'与'否则如果'正如here指出的那样,它更接近但仍然不存在。然后我尝试将Bresenham从整数精度移动到浮点精度,这导致无限循环(因为x,y值跳过完成条件if (y1 == y2 && x1 == x2))。

我可以使用naive line drawing解决方案,但我应该使用哪个delta?例如。如果我使用0.1,我仍会遗漏一些像素,使用较小的值可能会花费太长时间(仍然会错过像素)。

C / Java / ...中的工作解决方案将不胜感激。至少它应该适用于octant 1,但是一个完整的解决方案会更好。

更新:我提出了以下想法:使用朴素行光栅化,您可以为每个点计算4个像素候选项。然后检查这4个像素,如果该线真正穿过它们。但我不确定线/盒交叉点是否足够快。

2 个答案:

答案 0 :(得分:7)

如果您只需要恒定的颜色(不按像素的使用区域进行插值),请使用 DDA

void line_DDA_subpixel(int x0,int y0,int x1,int y1,int col) // DDA subpixel -> thick
    {
    int kx,ky,c,i,xx,yy,dx,dy;
    x1-=x0; kx=0; if (x1>0) kx=+1; if (x1<0) { kx=-1; x1=-x1; } x1++;
    y1-=y0; ky=0; if (y1>0) ky=+1; if (y1<0) { ky=-1; y1=-y1; } y1++;
    if (x1>=y1)
     for (c=x1,i=0;i<x1;i++,x0+=kx)
        {
        pnt(x0,y0,col); // this is normal pixel the two below are subpixels 
        c-=y1; if (c<=0) { if (i!=x1-1) pnt(x0+kx,y0,col); c+=x1; y0+=ky; if (i!=x1-1) pnt(x0,y0,col); }
        }
    else
     for (c=y1,i=0;i<y1;i++,y0+=ky)
        {
        pnt(x0,y0,col); // this is normal pixel the two below are subpixels 
        c-=x1; if (c<=0) { if (i!=y1-1) pnt(x0,y0+ky,col); c+=y1; x0+=kx; if (i!=y1-1) pnt(x0,y0,col); }
        }
    }

其中:

void pnt(int x,int y,int col);

是使用颜色col光栅化像素(x,y)的例程。源代码是C ++

我认为它是向前的,但无论如何

DDA 使用参数线方程y=k*x+qx=ky+q取决于差异(如果更大xy差异,那么就没有孔)。 kdy/dxdx/dy,整个除法减少为内部循环中的减法+加法(每个循环的最后一行)。这可以很容易地修改为任意数量的维度(我通常使用 7D 或更多)。在现代机器上,速度有时比Bresenham更好(取决于平台和用途)。

与简单的 DDA

相比,这就是它的样子

DDA and DDA_subpixel lines

[edit2]双坐标 //最初[edit1]

好的,这是新代码:

void line_DDA_subpixel1(double x0,double y0,double x1,double y1,int col)    // DDA subpixel -> thick
    {
    int i,n,x,y,xx,yy;
    // prepare data n-pixels,x1,y1 is line dx,dy step per pixel
    x1-=x0; i=ceil(fabs(x1));
    y1-=y0; n=ceil(fabs(y1));
    if (n<i) n=i; if (!n) n=1;
    x1/=double(n);
    y1/=double(n); n++;
    // rasterize DDA line
    for (xx=x0,yy=y0,i=0;i<=n;i++,x0+=x1,y0+=y1)
        {
        // direct pixel
        pnt(x,y,col);
        // subpixels on change in both axises
        x=x0; y=y0;
        if ((i<n)&&(x!=xx)&&(y!=yy)) { pnt(xx,y,col); pnt(x,yy,col); }
        xx=x; yy=y;
        }
    }

这就是它的样子:

DDA and DDA_subpixel lines double coordinates

角度现在应该是double精度,但 pnt(x,y,col)仍然是整数!!!

[edit3]像素网格交叉

void DDAf_line_subpixel(float x0,float y0,float x1,float y1,int col)    // DDA subpixel -> thick
    {
    int i,n; float a,a0,a1,aa,b,d;
    // end-points
    pnt(x0,y0,col);
    pnt(x1,y1,col);
    // x-axis pixel cross
    a0=1; a1=0; n=0;
    if (x0<x1) { a0=ceil(x0); a1=floor(x1); d=(y1-y0)/(x1-x0); a=a0; b=y0+(a0-x0)*d; n=fabs(a1-a0); } else
    if (x0>x1) { a0=ceil(x1); a1=floor(x0); d=(y1-y0)/(x1-x0); a=a0; b=y1+(a0-x1)*d; n=fabs(a1-a0); }
    if (a0<=a1) for (aa=a,i=0;i<=n;i++,aa=a,a++,b+=d) { pnt(aa,b,col); pnt( a,b,col); }
    // y-axis pixel cross
    a0=1; a1=0; n=0;
    if (y0<y1) { a0=ceil(y0); a1=floor(y1); d=(x1-x0)/(y1-y0); a=a0; b=x0+(a0-y0)*d; n=fabs(a1-a0); } else
    if (y0>y1) { a0=ceil(y1); a1=floor(y0); d=(x1-x0)/(y1-y0); a=a0; b=x1+(a0-y1)*d; n=fabs(a1-a0); }
    if (a0<=a1) for (aa=a,i=0;i<=n;i++,aa=a,a++,b+=d) { pnt(b,aa,col); pnt(b, a,col); }
    }

最后还有一些时间,所以我稍微调整了 DDA ,但是导致很多if的ID,所以我更改了光栅化。现在计算所有像素网格交叉(交叉点),然后为每个添加右子像素。这就是它的样子(没有错误的子像素):

line pixel crossing subpixel

对于每个xy网格线,第一个交叉点计算(a,b)step位于一个轴1像素中,其余为第二个根据{{​​1}}或dy/dx。在此之后,for循环填充子像素......

答案 1 :(得分:4)

如果您的线很细并且像素是矩形(正方形):

enter image description here

然后考虑使用体素网格遍历算法,例如,请参阅Woo和Amanatides的文章“Fast Voxel Traversal Algorithm...”。

Practical implementation(在网格遍历部分中)

回答评论:
正确初始化X坐标变量(Y相同)

  DX = X2 - X1
  tDeltaX = GridCellWidth / DX
  tMaxX = tDeltaX * (1.0 - Frac(X1 / GridCellWidth)) 
  //Frac if fractional part of float, for example, Frac(1.3) = 0.3

我的回答here

中的示例