Рисование куба рендерингом методом Гуро
В ВУЗе на лабораторных по компьютерной графике мы рисовали куб методом плоского рендеринга. Захотелось попробовать изменить код, чтобы нарисовать куб методом Гуро.
Я начал рисовать куб попиксельно, для каждой вершины рассчитал интенсивность(решил брать интенсивность грани из прошлого кода, но теперь беру 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;
}
