Реализация алгоритма сканирования строк

Мне было поручено реализовать версию алгоритма сканирования строки для задания. Эта программа работает путем считывания списка вершин и цветов из текстового файла. Из очереди извлекаются по три вершины и три цвета, затем рисуются края треугольника. Пока моя программа делает это почти идеально. Перед этим нам было поручено рисовать прямоугольники, и сразу после выполнения тестов стало ясно, что программе необходимо перевернуть координаты 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://imgur.com/a/VOpWJ

Это происходит, когда выполняется только fillFlatBottom: https://imgur.com/a/nexR9

Это происходит, когда выполняется только fillFlatTop: https://imgur.com/a/flRCK

Что именно я делаю не так? Очевидно, что линейный алгоритм не вызывает этой проблемы. Я либо неправильно вычисляю точку напротив средней точки (это vNew), либо я как-то испортил алгоритмы заливки.


person user3308219    schedule 17.02.2017    source источник


Ответы (2)


Давайте просто посмотрим на одну из функций: 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 то же, что и выше, но вместо 0s на 1s.

Если вы посмотрите на свой код, наклоны правильные (есть двойной отрицательный результат, но он работает правильно).

Теперь давайте посмотрим на 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 то же, что и выше, с 2s вместо 1s

Сравнивая это с вашим кодом, кажется, что наклон, который у вас есть, имеет неправильный знак, или, если вы хотите придерживаться того же соглашения, что и fillFlatTop, вы должны сохранить наклоны такими, какие они есть, но изменить приращения (x += ...) на уменьшение ( x -= ...), опять же, на двойное отрицание :-)

person triple_r    schedule 17.02.2017

Вот код, который я использую для рисования закрашенного треугольника. Он основан на алгоритме, описанном на этой странице: Заполнители треугольников . Код является частью класса на основе шаблона и использует библиотеку 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;

Но по характеру вашего задания

Эта программа работает путем считывания списка вершин и цветов из текстового файла.

кажется, что вам нужно выполнить заливку / затенение по Гуро, а это немного более длинный фрагмент кода.

person S. Petrić    schedule 18.02.2017
comment
Что мне нравится в этом посте: лаконичный, четко сформулированный. Затем SO (StackOverflow (& SE: Stack Exchange)) посвящен вопросам и ответам. Хотя многие сообщения с вопросами вряд ли соответствуют основному требованию Один вопрос, на который можно ответить, а заголовок этого сообщения не совсем полезен, есть один явный вопрос: What exactly am I doing wrong?. Ваш пост не отвечает на это. - person greybeard; 18.02.2017
comment
Да, ты прав. Однако я разместил этот код в качестве рабочего примера. На самом деле это тот же подход, поэтому плакат может легко обнаружить ошибку, сравнив исходный код с этим. Это не прямой ответ на вопрос «Что я делаю не так?» но это может быть полезно. - person S. Petrić; 19.02.2017