左上填充规则

左上填充规则

by Goncely

 

  :对左上填充规则进行详细介绍的资料很少,而理解左上填充规则对于编写光栅化器至关重要。本文对光栅化的原理和左上填充规则进行了详细描述,描述的时候尽可能做到易懂、详尽,如果读者还是有不明白的地方请给我留言,我会将没有论述清楚的地方补充完善。

1 、概述

光栅化的时候通常会遇到一个术语,那就是左上填充规则( top-left rule ),它的作用是为了避免相邻的多边形重复绘制邻边。如图 1 所示,如果没有左上填充规则,绘制三角形 ABD BCD 的时候,有可能同时绘制 BD 边,导致 BD 被绘制 2 次。乍一看,除了稍微影响点效率外,重复绘制好像也没什么坏处,不过在处理透明度和模板( stenciling )的时候,重复渲染三角形的邻边会让结果出现严重的瑕疵。

 

图1 相邻的三角形

2 、定义

说到左上填充规则,不得不先说光栅化。光栅化的本质是确定绘制哪些离散的像素点(更确切的说是像素块),如图 2 所示,填充图中的三角形时,光栅化器会决定对哪些像素着色。

一般来说,光栅化器的输入是多边形的顶点坐标,这些顶点坐标构成一个封闭的多边形。当需要填充该多边形时,一个直观的想法便是绘制所有位于多边形内部的像素。那又如何判断像素块位于多边形的内部呢?通常以像素块的中心点作为判断依据,只要像素块的中心点(图 2 中的叉叉)位于多边形内部,便认为该像素块也位于多边形内部。那么当像素块的中心点恰好在多边形的边上怎么办?如图 2 中三角形,有 6 个像素块的中心点和底边重合,在填充这个三角形的时候是否要对这 6 个像素块着色呢?这就是问题的关键所在,也是左上填充规则的由来之处。如果简单的认为像素都应着色或都不着色,会致使重复填充或出现漏点。

图2  三角形光栅化的示意图

 

左上填充规则约定,当像素块中心点和多边形的左边或者上边重合时,该像素块位于多边形内部,填充多边形时需要着色;相反,当像素块的中心点和多边形的右边或者底边重合时,该像素块位于多边形外部,填充多边形时不需要着色。比如,当需要光栅化一个 [0 5] 区间的正方形时,需要渲染 [0 4] 区间的所有像素块,而第 5 行(底边)和第 5 列(右边)的像素则不渲染,结果如图 3 所示。运用左上填充规则光栅化连续的多边形时,既不会有漏点,也避免了重复绘制。

图3  四边形的光栅化图

3 、左边和上边

首先看一下图 4 ,四个三角形的深黑色边是左边或上边,灰色变是右边或底边。图中对于左边和上边的确定有的很形象也很好理解,有的可能就不那么容易理解了,比如右上方的三角形,它的下面那条边更像是底边,而不是左边。其实除了右下方三角形的水平边是上边外,其余的黑色边都是左边,而所有的灰色边都是右边。

 

 

图4  左边和上边的示意图

 

为了深刻理解左边和上边的定义,需要简单了解下光栅化的流程,其基本算法如下:

 

// 输入: fymin fymax ,均为浮点数,分别代表多边形的最小纵坐标,和最大纵坐标

int ymin = compute_ymin(fymin);

int ymax = compute_ymax(fymax);

for(int y = ymin; y<ymax; y++)

{

       float fxmin, fxmax;

       ComputeXRange(y, fxmin, fxmax); // 计算 y 轴对应的扫描线区域

       int xmin = compute_xmin(fxmin);

       int xmax = computer_xmax(fxmax);

       for(int x = xmin; x<xmax; x++)

              SetPixel(x, y, color);

}

 

代码很好理解,不多解释,需要强调的是,上述代码使用的就是左上填充规则。其中 ymin 对应的水平线便是上边, ymax 对应的水平线是底边。 y 轴扫描循环内部的( xmin, y )点集构成了左边,( xmax, y )点集构成了右边。代码中绘制了上边( y = ymin ),但没有绘制底边( y<ymax );绘制了左边( x = xmin ),却没有绘制右边( x<xmax )。根据以上分析可以得出结论:水平边要们是上边,要么是底边;斜边要么是左边,要么是右边。这样就很好理解图 4 中左上边的定义了。

类似的,对上述代码稍加修改,便可以实现右下填充规则:

 

// 输入: fymin fymax ,均为浮点数,分别代表多边形的最小纵坐标,和最大纵坐标

int ymin = compute_ymin(fymin);

int ymax = compute_ymax(fymax);

for(int y = ymin+1; y<=ymax; y++)

{

       float fxmin, fxmax;

       ComputeXRange(y, fxmin, fxmax); // 计算 y 轴对应的扫描线区域

       int xmin = compute_xmin(fxmin);

       int xmax = computer_xmax(fxmax);

       for(int x = xmin+1; x<=xmax; x++)

              SetPixel(x, y, color);

}


4 、实现

根据对像素块中心点的坐标定义不同,左上填充规则的实现有两种模式(即样例代码中 compute_ymin 等函数的实现)。首先介绍下像素块中心点的坐标定义,一种定义是将第一个像素块的中心点坐标定义为( 0, 0 ),另一种定义则是将它的坐标定义为( 0.5, 0.5 ),详见图 5

 

图5  像素块中心点的坐标定义

 

在第一种定义中,屏幕左上角的坐标为( -0.5, -0.5 ),相当于把整个屏幕坐标系向左上方平移了( 0.5, 0.5 )个单位;第二种定义则保留了屏幕坐标系的原始位置,屏幕左上角的坐标为( 0, 0 )。下面着重以第一种中心点坐标定义为例讲解 compute_ymin 等函数的实现。

从样例代码可以看出, compute_ymin compute_ymax compute_xmin compute_xmax 四个函数的本质是将浮点数变换为整数。根据左上填充规则的定义,我们希望左上边在 (-1, 0] 区域时取 0 ,右下边在 (0, 1] 区域时取 0 ;类似的,左上边在 (0, 1] 区域时取 1 ,右下边在 (1, 2] 区域时取 1…… 。由上述分析,可以得出 compute_ymin compute_xmin 的函数定义如下:

 

int compute_ymin(float f)

{

return ceil(f);

}

 

int compute_xmin(float f)

{

return ceil(f);

}

 

另外,可以看出右下边相比左上边的转换函数始终相差 1 ,所以理论上来说 compute_ymax compute_xmax 的函数定义应该为 ceil(f)-1 ,但是光栅化的时候代码使用了 y<ymax x<xmax ,没有使用小于等于比较浮,因而 compute_ymax compute_xmax 的函数定义同 compute_ymin compute_xmin 的函数定义一致:

 

int compute_ymax(float f)

{

return ceil(f);

}

 

int compute_xmax(float f)

{

return ceil(f);

}

 

将转换函数的代码代入,可以得到运用左上填充规则的光栅化代码如下:

 

// 输入: fymin fymax ,均为浮点数,分别代表多边形的最小纵坐标,和最大纵坐标

int ymin = ceil(fymin);

int ymax = ceil(fymax);

for(int y = ymin; y<ymax; y++)

{

       float fxmin, fxmax;

       ComputeXRange(y, fxmin, fxmax); // 计算 y 轴对应的扫描线区域

       int xmin = ceil(fxmin);

       int xmax = ceil(fxmax);

       for(int x = xmin; x<xmax; x++)

              SetPixel(x, y, color);

}

 

当像素块中心点取第二种坐标定义时,四个转换函数的定义皆为 ceil(f-0.5f) ,读者可自行分析。

5 、例子

下面以 (1.5, 0.5) (0.5, 1.5) (1.5, 1.5) 三点构成的三角形为例,讨论左上填充规则在光栅化中的应用。假设像素块的中心点使用第二种定义,那么三角形的 fymin fymax 分别为 0.5 1.5 ,应用转换函数 ceil(f-0.5f) ,可以得出 ymin ymax 分别是 0 1 ,根据左上填充规则只绘制 0 轴的扫描线; 0 轴对应的扫描线区域为 fxmin = fxmax = 1.5 ,转换成整数得到 xmin = xmax = 1 。在 for(int x = xmin; x<xmax; x++) 循环中,初始条件便不满足,循环体不会执行,所以该三角形不会渲染任何像素块,结果如图 6 所示。更多的例子请参考图 7 。其中灰色和深灰色均为着色的像素块,如果读者想测试自定义三角形或三角形集合的光栅化结果,可到我的 下载空间下载 我编写的演示程序,演示程序中的深灰色方块代表重复着色的像素块。

图6  一个三角形的光栅化例子

 

图7  左上填充规则的光栅化示图

6 、小结

左上填充规则处理的是那些位于边上的像素点,不影响多边形内部的像素。其中左边和上边对应于光栅化时的起始条件,右边和底边则对应于终止条件。为了便于理解坐上填充规则,特意做了一个演示程序,感兴趣的读者可以到我的 下载空间下载 ,使用比较方便,只要输入三角形顶点就可以看到渲染结果。我用她来测试我的软件光栅化器,帮了我很大忙,解决了一个困扰我近半个月的问题,本文中的很多图例也是她生成的,希望能给大家带来帮助。如果有人想对该程序进行完善和扩充(有很多想到的实用功能没有实现),我很乐意提供源代码,只需要给我留言即可。

 

 

 

posted @ 2010-05-13 15:32  Goncely  阅读(410)  评论(0编辑  收藏  举报