实现扫描线算法

Implementing a scanline algorithm

本文关键字:扫描线算法 实现      更新时间:2023-10-16

我的任务是为作业实现扫描线算法的一个版本。 该程序通过从文本文件中读取顶点和颜色列表来工作。 一次从队列中弹出三个顶点和三种颜色,然后绘制三角形的边。 到目前为止,我的程序几乎完美地做到了这一点。 在这样做之前,我们接到了一项绘制矩形的任务,在运行测试后立即清楚,程序需要翻转 y 坐标才能正确绘制图像。

这就是问题所在。 我在这里找到了扫描线三角形填充函数的一些算法,但我注意到我必须修改它们才能解释图像翻转的事实。 在计算机图形网格中,较高的 y 值朝向底部。 在组织给定三角形的三个顶点时,正常的做法是将 y 值最低的顶点指定为顶部,将 y 值最高的顶点指定为底部。 我颠倒了这一点,以解释图像翻转。

无论我做什么,我都无法让我的图像正确填充。 它将按原样使用这些函数,只要我在绘制单个像素时不翻转 y 值,并且顶点按预期方式排序,其中较低的 y 值朝顶部。 但是,生成的图像是垂直翻转的。

以下功能几乎是程序的全部,除了我认为不需要任何更正的线条绘制功能。

void FrameBuffer::drawTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Color& c0, const Color& c1, const Color& c2, Functional status) {
//Start by organizing vertices from top to botttom (need to rearrange colors as well)
Vertex vTop, vMid, vLow;
Color cTop, cMid, cLow;
vTop = v0;  cTop = c0;
if (v1[Y] > vTop[Y]) {
vMid = vTop;    cMid = cTop;
vTop = v1;      cTop = c1;
}
else {
vMid = v1;      cMid = c1;
}
if (v2[Y] > vTop[Y]) {
vLow = vMid;    cLow = cMid;
vMid = vTop;    cMid = cTop;
vTop = v2;      cTop = c2;
}
else if (v2[Y] > vMid[Y]) {
vLow = vMid;    cLow = cMid;
vMid = v2;      cMid = c2;
}
else {
vLow = v2;      cLow = c2;
}
//Draw the edges of the triangle
drawLine(vTop, vMid, cTop, cMid, status);
drawLine(vTop, vLow, cTop, cLow, status);
drawLine(vMid, vLow, cMid, cLow, status);
//Fill the triangle; first case is flat bottom
if (vMid[Y] == vLow[Y]) {
fillFlatBottom(vTop, vMid, vLow, status);
}
//Second case is flat top
else if (vTop[Y] == vMid[Y]) {
fillFlatTop(vTop, vMid, vLow, status);
}
//General case; triangle can be split into flat bottom above a flat top
else {
Vertex vNew;        //This represents the point on the triangle across from the "midpoint"
vNew[Y] = vMid[Y];
vNew[X] = vTop[X] + ((vMid[Y] - vTop[Y]) / (vLow[Y] - vTop[Y])) * (vLow[X] - vTop[X]);
vNew[Z] = (vLow[Z] * (vNew[X] - vTop[X]) * (vNew[Y] - vMid[Y]) + vTop[Z] * (vNew[X] - vMid[X]) * (vNew[Y] - vLow[Y]) + vMid[Z] * (vNew[X] - vLow[X]) * (vNew[Y] - vTop[Y]) - vMid[Z] * (vNew[X] - vTop[X]) * (vNew[Y] - vLow[Y]) - vLow[Z] * (vNew[X] - vMid[X]) * (vNew[Y] - vTop[Y]) - vTop[Z] * (vNew[X] - vLow[X]) * (vNew[Y] - vMid[Y])) / ((vNew[X] - vTop[X]) * (vNew[Y] - vMid[Y]) + (vNew[X] - vMid[X]) * (vNew[Y] - vLow[Y]) + (vNew[X] - vLow[X]) * (vNew[Y] - vTop[Y]) - (vNew[X] - vTop[X]) * (vNew[Y] - vLow[Y]) - (vNew[X] - vMid[X]) * (vNew[Y] - vTop[Y]) - (vNew[X] - vLow[X]) * (vNew[Y] - vMid[Y]));
fillFlatBottom(vTop, vMid, vNew, status);
fillFlatTop(vMid, vNew, vLow, status);
}
}
//Draw from bottom up (something is happening here that causes errors)
void FrameBuffer::fillFlatTop(const Vertex& v0, const Vertex& v1, const Vertex& v2, Functional status) {
double xSlope1 = -(v2[X] - v0[X]) / (v2[Y] - v0[Y]);
double xSlope2 = -(v2[X] - v1[X]) / (v2[Y] - v1[Y]);
Vertex curV0 = v2;
Vertex curV1 = v2;
//v0 is the highest point with the highest y value and v2 is the lowest (for this case) with the lowest y value
while (curV0[Y] < v0[Y] && curV1[Y] < v0[Y]) {
Color curC0 = image.get(curV0[X], curV0[Y]);
Color curC1 = image.get(curV1[X], curV1[Y]);
drawLine(curV0, curV1, curC0, curC1, status);
curV0[Y] += 1;  curV0[X] -= xSlope1;
curV1[Y] += 1;  curV1[X] -= xSlope2;
}
}
//Draw from top down (something is happening here that causes errors)
void FrameBuffer::fillFlatBottom(const Vertex& v0, const Vertex& v1, const Vertex& v2, Functional status) {
double xSlope1 = -(v1[X] - v0[X]) / (v1[Y] - v0[Y]);
double xSlope2 = -(v2[X] - v0[X]) / (v2[Y] - v0[Y]);
Vertex curV0 = v0;
Vertex curV1 = v0;
//v0 is the highest point with the highest y value and v1 (for this case) is the lowest with the lowest y value
while (curV0[Y] >= v1[Y] && curV1[Y] >= v1[Y]) {
Color curC0 = image.get(curV0[X], curV0[Y]);
Color curC1 = image.get(curV1[X], curV1[Y]);
drawLine(curV0, curV1, curC0, curC1, status);
curV0[Y] -= 1;  curV0[X] += xSlope1;
curV1[Y] -= 1;  curV1[X] += xSlope2;
}
}

为了提供一些视角,这是未填充任何内容时生成的图像:https://i.stack.imgur.com/av26e.jpg

当只有 fillFlatBottom 运行时会发生这种情况:https://i.stack.imgur.com/PjBRx.jpg

当只有 fillFlatTop 运行时,会发生这种情况:https://i.stack.imgur.com/lW4pG.jpg

我到底做错了什么? 很明显,行算法没有导致此问题。 我要么错误地计算了中点(即 vNew)对面的点,要么以某种方式搞砸了填充算法。

让我们看一下其中一个函数:现在fillFlatTop。这个想法是,对于两个点,CurV0curV1沿着三角形的两条边分别从最底部的顶点v2移动到顶部顶点v0v1

让我们检查一下:

  • 对于curV0,当yv2v0y变化v0.y - v2.y。但是,您希望x变化等于v0.x - v2.x,因此对于y中的每一个1变化,您希望有(v0.x - v2.x) / (v0.y - v2.y)

  • 对于curV1,与上述相同,但将0s替换为1s。

如果你看一下你的代码,斜率是正确的(有一个双重否定,但它是正确的)。

现在,让我们看看fillFlatBottom.同上:

  • 对于从v0移动到v1curV0y的变化v1.y - v0.y对应于xv1.x - v0.x变化,所以对于y中的每一个1变化,你都希望x改变(v1.x - v0.x) / (v1.y - v0.y)

  • 对于curV1,同上,用2s 而不是1s

将此与您的代码进行比较,似乎您的斜率具有错误的符号,或者如果您想采用与fillFlatTop相同的约定,则应保持斜率不变,但将增量(x += ...)更改为递减(x -= ...),再次具有双负:-)

这是我用来绘制填充三角形的代码。它基于此页面上描述的算法:三角形填充物 .该代码是基于模板的类的一部分,它使用 std 库和一些图像容器,但您可以轻松修改代码以满足您的需求:

struct spFPoint
{
float x;
float y;
};
//-----------------------
// draw triangle filled
//-----------------------
template <class T>
static void TriangleFilled(spImage<T> *img, T val, int x1, int y1, int x2, int y2, int x3, int y3)
{
// this piece of code is used only for sorting  
spFPoint pt;
pt.x = x1;
pt.y = y1;
vector <spFPoint> triangle;
triangle.push_back(pt);
pt.x = x2;
pt.y = y2;
triangle.push_back(pt);
pt.x = x3;
pt.y = y3;
triangle.push_back(pt);
stable_sort(triangle.begin(), triangle.end(), yAscFPoint);
// here comes the actual algorithm
spFPoint A, B, C;
float   dx1, dx2, sx1, sx2, y;
A = triangle[0];
B = triangle[1];
C = triangle[2];
B.y-A.y > 0 ? dx1 = (B.x-A.x)/(B.y-A.y) : dx1=0;
C.y-A.y > 0 ? dx2 = (C.x-A.x)/(C.y-A.y) : dx2=0;
sx1 = sx2 = A.x;
y = A.y;
// upper half
for (; y <= B.y; y++, sx1+=dx1, sx2+=dx2)
ScanLine(img, val, y, sx1, sx2);  // or your function for drawing horizontal line 
// lower half
C.y-B.y > 0 ? dx1 = (C.x-B.x)/(C.y-B.y) : dx1=0;
sx1 = B.x;
y   = B.y;
for (; y <= C.y; y++, sx1+=dx1, sx2+=dx2)
ScanLine(img, val, y, sx1, sx2); // or your function for drawing horizontal line
}
//----------------------
// draw scanline (single color)
//----------------------
template <class T>
static void ScanLine(spImage<T> *img, T val, int y, int x1, int x2)
{
if (y < 0 || y > img->Height()-1) return;
if ((x1 < 0 && x2 < 0) || (x1 > img->Width()-1 && x2 > img->Width()-1 )) return;
if (x1 > x2)   // swap coordinates
{
x1 = x1^x2;
x2 = x1^x2;
x1 = x1^x2;
}
if (x1 < 0) x1 = 0;
if (x2 > img->Width()-1) x2 = img->Width()-1;
for (int j = x1; j <= x2; j++)
img->Pixel(y, j) = val;     // draw point
}
//----------------------
// sorting function
//----------------------
inline static bool yAscFPoint(spFPoint lhs, spFPoint rhs) { return lhs.y < rhs.y;}  

如果图像或绘图画布是颠倒的,则在调用 TriangleFill 函数之前,请反转 y 坐标。在函数调用之前更容易做到这一点,然后在函数内部:

y1 = img->Height()-1 - y1;
y2 = img->Height()-1 - y2;
y3 = img->Height()-1 - y3;

但是,从您的任务性质来看

该程序的工作原理是读取来自 文本文件。

似乎您需要执行 Gouraud 填充/着色,这是一段更长的代码。