Мне было поручено реализовать версию алгоритма сканирования линии для назначения. Эта программа работает путем чтения списка вершин и цветов из текстового файла. Три вершины и три цвета выталкиваются из очереди за раз, затем рисуются края треугольника. До сих пор моя программа делает это в значительной степени отлично. Перед этим нам было поручено задание, которое рисовало прямоугольники, и сразу после запуска тестов стало ясно, что программе необходимо перевернуть координаты 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;
}
}
Чтобы обеспечить некоторую перспективу, это результирующее изображение, когда ничего не заполнено: http://imgur.com/a/VOpWJ
Это происходит, когда выполняется только fillFlatBottom: http://imgur.com/a/nexR9
Это происходит, когда выполняется только fillFlatTop: http://imgur.com/a/flRCK
Что именно я делаю не так? Очевидно, что линейный алгоритм не вызывает этой проблемы. Либо я неправильно вычисляю точку напротив средней точки (это vNew), либо я как-то испортил алгоритмы заполнения.
Давайте просто посмотрим на одну из функций: fillFlatTop
теперь. Идея в том, что для двух точек CurV0
а также curV1
двигаться по двум сторонам треугольника от самой нижней вершины v2
в верхние вершины v0
а также v1
соответственно.
Давайте проверим это:
За curV0
как y
идет от того из v2
в v0
, y
изменения по v0.y - v2.y
, Но вы хотите x
изменить на равный v0.x - v2.x
так что для каждого 1
изменить в y
хочешь иметь (v0.x - v2.x) / (v0.y - v2.y)
За curV1
так же, как указано выше, но заменяет 0
с 1
s.
Если вы посмотрите на свой код, наклоны верны (есть двойной отрицательный результат, но он работает правильно).
Теперь давайте посмотрим на fillFlatBottom
, То же, что и выше:
За curV0
двигаясь от v0
в v1
изменить в y
является v1.y - v0.y
соответствует v1.x - v0.x
изменить в x
так что для каждого 1
изменить в y
ты хочешь x
изменить на (v1.x - v0.x) / (v1.y - v0.y)
,
За curV1
так же, как и выше, с 2
с вместо 1
s
Сравнивая это с вашим кодом, кажется, что у вашего склона неправильный знак, или если вы хотите использовать то же соглашение, что и 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;}
Если ваше изображение или холст для рисования перевернуты, перед вызовом функции TriangleFilled инвертируйте координаты y. Это проще сделать до вызова функции, затем внутри функции:
y1 = img->Height()-1 - y1;
y2 = img->Height()-1 - y2;
y3 = img->Height()-1 - y3;
Но, от характера вашего задания
Эта программа работает, читая в списке вершин и цветов из
текстовый файл.
кажется, что вам нужно выполнить заливку / заливку Гуро, и это немного более длинный кусок кода.