Рисование куба рендерингом методом Гуро

В ВУЗе на лабораторных по компьютерной графике мы рисовали куб методом плоского рендеринга. Захотелось попробовать изменить код, чтобы нарисовать куб методом Гуро.

Я начал рисовать куб попиксельно, для каждой вершины рассчитал интенсивность(решил брать интенсивность грани из прошлого кода, но теперь беру 3 смежных грани у вершины и нахожу среднее значение), а потом с помощью интерполяции для каждого пикселя определяю свою интенсивность. Под определенными ракурсами куб выглядит неплохо(его можно крутить с помощью стрелок), но обычно на кубе есть артефакты, и всегда присутствуют тонкие горизонтальные полосы другого цвета. Не знаю, как исправить.

Извините за глупый вопрос. Спасибо за то, что прочитали!

Вот мой код:

#define _USE_MATH_DEFINES
#include <windows.h>
#include <tchar.h>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <math.h>

using namespace std;

const int WIDTH = 400;
const int HEIGHT = 300; // Ширина и высота окна

float v11, v12, v13, v21, v22, v23, v31, v32, v33, ν43;
float rho = 300., thetta = 75., phi = 30., ScreenDist = 250.;
float A, B, C, D, An, Bn, Cn;
float xt[3], yt[3], zt[3];
float Al, Bl, Cl;
float alpha;
float th, ph, costh, cosph, sinth, sinph;
float factor = atan(1.0) / 45.;
PAINTSTRUCT ps;
static HBRUSH hBrush;

class TFPoint // Класс определяет простую трехмерную точку с координатами X, Y и Z
{
public:
    float X;
    float Y;
    float Z;
    float intensity;  // Новая переменная для интенсивности
    vector<int> adjacentFaces;  // Список смежных граней
    void addAdjacentFace(int faceIndex) {
        adjacentFaces.push_back(faceIndex);
    }
};

TFPoint CubePoints[] = { // Массив содержит координаты восьми вершин куба
    {-50, -50, -50},
    {50, -50, -50},
    {50, 50, -50},
    {-50, 50, -50},
    {-50, 50, 50},
    {-50, -50, 50},
    {50, -50, 50},
    {50, 50, 50}
};

int Gran[6][4] = { // Этот массив определяет индексы вершин для каждой грани куба
    {0, 3, 4, 5},
    {0, 5, 6, 1},
    {2, 7, 4, 3},
    {7, 6, 5, 4},
    {0, 1, 2, 3},
    {2, 1, 6, 7}
};

void VidMatCoeff(float rho, float thetta, float phi) // Функция вычисляет коэффициенты матрицы обзора на основе заданных параметров
{
    // Преобразование углов из градусов в радианы
    th = thetta * factor;
    ph = phi * factor;

    // Вычисление косинусов и синусов углов
    costh = cos(th);
    sinth = sin(th);
    cosph = cos(ph);
    sinph = sin(ph);

    // Заполнение матрицы обзора (видовой матрицы)
    v11 = -sinth;
    v12 = -cosph * costh;
    v13 = -sinph * costh;

    v21 = costh;
    v22 = -cosph * sinth;
    v23 = -sinph * sinth;

    v31 = 0.;
    v32 = sinph;
    v33 = -cosph;

    // Установка координаты z камеры (вектора обзора)
    ν43 = rho;
}

POINT Perspective(float x, float y, float z) // Функция выполняет перспективную проекцию для трехмерной точки и 
{                                                                   // возвращает соответствующую двумерную точку
    // Создание переменных для хранения результатов перспективной проекции
    POINT point;
    float xe, ye, ze;

    // Вызов функции VidMatCoeff для обновления коэффициентов матрицы обзора
    VidMatCoeff(rho, thetta, phi);

    // Применение матрицы обзора для выполнения перспективной проекции
    xe = v11 * x + v21 * y;
    ye = v12 * x + v22 * y + v32 * z;
    ze = v13 * x + v23 * y + v33 * z + ν43;

    // Преобразование 3D-координат в 2D-координаты на экране
    point.x = ScreenDist * xe / ze + WIDTH;
    point.y = ScreenDist * ye / ze + HEIGHT;

    // Возвращение результатов перспективной проекции
    return point;
}

// Объявление прототипа функции обработки сообщений окна
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
TCHAR WinName[] = _T("MainFrame");

// Основная функция WinMain, точка входа в приложение для Windows
int APIENTRY WinMain(HINSTANCE This, HINSTANCE Prev, LPSTR cmd, int mode)
{
    HWND hWnd;         // Дескриптор окна
    // Дескриптор используется для отправки сообщений окну, 
    // управления его свойствами и, в целом, взаимодействия с окном в процессе выполнения программы
    MSG msg;           // Структура для хранения сообщений
    WNDCLASS wc;       // Структура класса окна

    // Заполнение структуры класса окна
    wc.hInstance = This;
    wc.lpszClassName = WinName;
    wc.lpfnWndProc = WndProc;
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszMenuName = NULL;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    // Регистрация класса окна
    if (!RegisterClass(&wc))
        return 0;

    // Создание окна приложения
    hWnd = CreateWindow(WinName,
        _T("Приложение"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        HWND_DESKTOP,
        NULL,
        This,
        NULL);

    // Отображение окна
    ShowWindow(hWnd, mode);

    // Цикл обработки сообщений
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // Возвращение нулевого значения при завершении программы
    return 0;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    // Массив для хранения координат вершин многоугольника
    POINT point1[4];
    HDC hdc; // Контекст устройства для рисования
    int sx, sy, xPos, yPos, zDelta; // Переменные для обработки сообщений
    int minY = 999;
    int maxY = -999;
    int minX = 999;
    int maxX = -999;

    switch (message)
    {
    case WM_KEYDOWN:
        // Обработка нажатий клавиш
        switch (wParam) {
        case VK_LEFT:
            // Поворот влево
            thetta -= 5.0;
            break;
        case VK_RIGHT:
            // Поворот вправо
            thetta += 5.0;
            break;
        case VK_UP:
            // Поворот вверх
            phi += 5.0;
            break;
        case VK_DOWN:
            // Поворот вниз
            phi -= 5.0;
            break;
        }
        // Перерисовка окна
        InvalidateRect(hWnd, NULL, TRUE);
        break;
    case WM_MOUSEWHEEL:
        // Обработка вращения колеса мыши
        zDelta = (int)wParam;
        ScreenDist -= zDelta / 1000000.;

        // Перерисовка окна
        InvalidateRect(hWnd, NULL, TRUE);
        break;
    case WM_SIZE:
        // Получение размеров окна
        sx = LOWORD(lParam); // Ширина окна
        sy = HIWORD(lParam); // Высота окна
        break;
    case WM_PAINT:
        // Начало рисования
        hdc = BeginPaint(hWnd, &ps);

        // Вычисление углов и других параметров для преобразования координат
        th = thetta * factor;
        ph = phi * factor;
        costh = cos(th);
        sinth = sin(th);
        cosph = cos(ph);
        sinph = sin(ph);
        A = rho * sinph * costh; 
        B = rho * sinph * sinth; // Сферические координаты в декартовы
        C = rho * cosph; 
        Al = A / (sqrt(A * A + B * B + C * C));
        Bl = B / (sqrt(A * A + B * B + C * C)); // Нормализация вектора
        Cl = C / (sqrt(A * A + B * B + C * C));

        // Проходим по всем вершинам куба
        for (int i = 0; i < 8; ++i) {
            for (int j = 0; j < 6; ++j) {
                for (int k = 0; k < 4; ++k) {
                    if (Gran[j][k] == i) {
                        CubePoints[i].addAdjacentFace(j);
                    }
                }
            }
        }
        // Цикл по граням куба
        for (int i = 0; i < 6; i++) 
        {
            // Преобразование координат вершин в 2D с использованием перспективы
            for (int j = 0; j < 4; j++)
            {
                point1[j] = Perspective(CubePoints[Gran[i][j]].X,
                    CubePoints[Gran[i][j]].Y,
                    CubePoints[Gran[i][j]].Z);
            }

            // Вычисление ориентации грани и рисование ее в соответствующем цвете
            D = point1[0].x * (point1[1].y - point1[2].y) -
                point1[1].x * (point1[0].y - point1[2].y) +
                point1[2].x * (point1[0].y - point1[1].y);
            // Если ориентация отрицательна, рисуем грань
            if (D < 0)
            {
                for (int e = 0; e < 4; e++) {
                    for (int k = 0; k < 3; k++) {
                        int indexFace = CubePoints[Gran[i][e]].adjacentFaces[k];
                        for (int j = 0; j < 3; j++)
                        {
                            xt[j] = CubePoints[Gran[indexFace][j]].X;
                            yt[j] = CubePoints[Gran[indexFace][j]].Y;
                            zt[j] = CubePoints[Gran[indexFace][j]].Z;
                        }
                        // Вычисление нормали к грани куба
                        // V1 = (x1 - x0, y1 - y0, z1 - z0)
                        // V2 = (x2 - x0, y2 - y0, z2 - z0)
                        // N = V1 * V2
                        A = yt[0] * (zt[1] - zt[2]) - yt[1] * (zt[0] - zt[2]) + yt[2] * (zt[0] - zt[1]);
                        B = -(xt[0] * (zt[1] - zt[2]) - xt[1] * (zt[0] - zt[2]) + xt[2] * (zt[0] - zt[1]));
                        // - добавляется для того, чтобы сохранить правильное направление нормали
                        C = xt[0] * (yt[1] - yt[2]) - xt[1] * (yt[0] - yt[2]) + xt[2] * (yt[0] - yt[1]);
                        // Нормализация вектора нормали
                        An = A / (sqrt(A * A + B * B + C * C));
                        Bn = B / (sqrt(A * A + B * B + C * C));
                        Cn = C / (sqrt(A * A + B * B + C * C));
                        // Вычисление интенсивности
                        alpha = (An * Al + Bn * Bl + Cn * Cl);
                        // Скалярное произведения нормализованного вектора нормали грани и нормализованного вектора направления обзора
                        // alpha = cosθ
                        CubePoints[Gran[i][e]].intensity += alpha;
                    }
                    CubePoints[Gran[i][e]].intensity = CubePoints[Gran[i][e]].intensity / 3;
                }

                for (int j = 0; j < 4; j++) {
                    minY = min(minY, point1[j].y);
                    maxY = max(maxY, point1[j].y);
                    minX = min(minX, point1[j].x);
                    maxX = max(maxX, point1[j].x);
                }
                for (int y = minY; y <= maxY; y++) {
                    // Поиск пересечения с ребрами
                    vector<int> intersections;
                    for (size_t i = 0; i < 4; ++i) {
                        int x1 = point1[i].x;
                        int y1 = point1[i].y;
                        int x2 = point1[(i + 1) % 4].x;
                        int y2 = point1[(i + 1) % 4].y;
                        if ((y1 <= y && y <= y2) || (y2 <= y && y <= y1)) {
                            // Если текущее ребро пересекает текущую строку
                            if (y1 != y2) {
                                // Уравнение прямой для нахождения x пересечения
                                int x_intersection = x1 + (y - y1) * (x2 - x1) / (y2 - y1);
                                intersections.push_back(x_intersection);
                            }
                        }
                    }

                    // Сортировка пересечения по возрастанию x
                    sort(intersections.begin(), intersections.end());

                    for (size_t i = 0; i < intersections.size(); i++) {
                        int x_start = intersections[i];
                        int x_end = (i + 1 < intersections.size()) ? intersections[i + 1] : intersections[i];
                        // Добавление координат всех пикселей внутри текущего отрезка
                        for (int x = x_start; x <= x_end; x++) {
                            // Интерполяция интенсивности на текущей строке
                            
                            //AQ = sqrt((xq - xa) * (xq - xa) + (yq - ya) * (yq - ya))
                            float AQ = sqrt((x_start - point1[0].x) * (x_start - point1[0].x) 
                                + (y - point1[0].y) * (y - point1[0].y));
                            //AB = sqrt((xb - xa) * (xb - xa) + (yb - ya) * (yb - ya))
                            float AB = sqrt((point1[1].x - point1[0].x) * (point1[1].x - point1[0].x) 
                                + (point1[1].y - point1[0].y) * (point1[1].y - point1[0].y));
                            float t = AQ/AB; // Коэффициент интерполяции
                            float intensityStart = (1.0f - t) * CubePoints[Gran[i][1]].intensity + t * CubePoints[Gran[i][0]].intensity;

                            //BR = sqrt((xr - xb) * (xr - xb) + (yr - yb) * (yr - yb))
                            float BR = sqrt((x_end - point1[1].x) * (x_end - point1[1].x)
                                + (y - point1[1].y) * (y - point1[1].y));
                            //BC = sqrt((xc - xb) * (xc - xb) + (yc - yb) * (yc - yb))
                            float BC = sqrt((point1[2].x - point1[1].x) * (point1[2].x - point1[1].x)
                                + (point1[2].y - point1[1].y) * (point1[2].y - point1[1].y));
                            float w = BR / BC; // Коэффициент интерполяции
                            float intensityEnd = (1.0f - w) * CubePoints[Gran[i][2]].intensity + w * CubePoints[Gran[i][1]].intensity;

                            //QP = sqrt((xp - xq) * (xp - xq) + (yp - yq) * (yp - yq))
                            float QP = sqrt((x - x_start) * (x - x_start)
                                + (y - y) * (y - y));
                            //QR = sqrt((xr - xq) * (xr - xq) + (yr - yq) * (yr - yq))
                            float QR = sqrt((x_end - x_start) * (x_end - x_start)
                                + (y - y) * (y - y));
                            float u = QP / QR; // Коэффициент интерполяции
                            float intensity = (1.0f - u) * intensityEnd + u * intensityStart;
                            // Расчет цвета пикселя с учетом интерполированной интенсивности
                            COLORREF pixelColor = RGB((1 - intensity) * 255, (1 - intensity) * 255, (1 - intensity) * 255);
                            SetPixel(hdc, x, y, pixelColor);
                        }
                    }
                }
            }
        }

        // Завершение рисования
        EndPaint(hWnd, &ps);
        break;
    case WM_DESTROY:
        // Завершение работы программы при закрытии окна
        PostQuitMessage(0);
        break;
    default:
        // Обработка остальных сообщений по умолчанию
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    // Возврат нулевого значения после обработки сообщения
    return 0;
}

Куб, который получается(я его покрутил)


Ответы (0 шт):