Почему куб отрисовывается неправильно?
Пытаюсь отрисовать куб на С++, однако выглядит это не совсем так, как хотелось бы. Я использую формулы для преобразования сферических координат в декартовы (прямоугольные) координаты. Идея заключается в том, чтобы найти куда попадёт луч из каждого пикселя окна. Если луч встретил объект отрисовать этот пиксель.
Вот основные части кода:
int Screen[320 * 180 * 3] = { 0 };
int World[160][90][90] = { 0 };
int X1 = 0;
int Y1 = 0;
int BasicAngle = 0;
World[10][10][0] = 1;
World[10][10][10] = 1;
World[20][20][20] = 1;
World[40][40][40] = 1;
World[40][20][10] = 1;
createWindow(0, 0, 320, 180);
for (int y = 0; y < 90; y++)
{
double B = (90.0 / 90.0) * y;
for (int x = 0; x < 160; x++)
{
double A = (90.0 / 160.0) * x + BasicAngle;
for (int R = 0; R < 100; R++)
{
int X = X1 + R * sin(B * M_PI / 180) * cos(A * M_PI / 180);
int Y = Y1 + R * sin(B * M_PI / 180) * sin(A * M_PI / 180);
int Z = R * cos(B * M_PI / 180);
if (X < 160 && Y < 90 && X > -1 && Y > -1 && Z > -1 && Z < 90)
{
if (World[X][Y][Z] == 1)
{
R = 100;
Screen[y * 320 * 3 + (x + 160) * 3 + 0] = 255; // red правый
Screen[y * 320 * 3 + (x + 160) * 3 + 1] = 0; // green правый
Screen[y * 320 * 3 + (x + 160) * 3 + 2] = 0; // blue правый
Screen[Y * 320 * 3 + X * 3 + 0] = 255; // red левый
Screen[Y * 320 * 3 + X * 3 + 1] = 0; // green левый
Screen[Y * 320 * 3 + X * 3 + 2] = 0; // blue левый
}
else
{
Screen[Y * 320 * 3 + X * 3 + 0] = 0; // red левый
Screen[Y * 320 * 3 + X * 3 + 1] = 255; // green левый
Screen[Y * 320 * 3 + X * 3 + 2] = 0; // blue левый
}
}
}
}
}
Из-за чего происходит такое искажение? Если есть другой способ выпускать луч, я могу рассмотреть эту возможность. Может проблема в определении пересечения луча и объекта? Я хочу хранить объекты в виде вокселей, после уже из кубиков собрать нечто интересное, без полигонов.
Дополнение.
Скриншоты будут делаться при нажатии на пробел.
#include <Windows.h>
#include <iostream>
#include <fstream>
#include <cmath>
double M_PI = 3.1415926535;
void save1dArrayToBMP(const char* path, int* buffer, int width, int height)
{
std::ofstream file;
file.open(path, std::ios::out | std::ios::binary);
unsigned char bmpPadding[3] = { 0, 0, 0 };
const int paddingAmount = (4 - (width * 3) % 4) % 4;
const int fileHeaderSize = 14;
const int informationHeaderSize = 40;
const int fileSize = fileHeaderSize + informationHeaderSize + width * height * 4;
unsigned char fileHeader[fileHeaderSize];
//File type
fileHeader[0] = 'B';
fileHeader[1] = 'M';
// File size
fileHeader[2] = fileSize;
fileHeader[3] = fileSize >> 8;
fileHeader[4] = fileSize >> 16;
fileHeader[5] = fileSize >> 24;
//Reserved 1 (not used)
fileHeader[6] = 0;
fileHeader[7] = 0;
//Reserved 2 (not used)
fileHeader[8] = 0;
fileHeader[9] = 0;
//Pixel data offset
fileHeader[10] = fileHeaderSize + informationHeaderSize;
fileHeader[11] = 0;
fileHeader[12] = 0;
fileHeader[13] = 0;
unsigned char informationHeader[informationHeaderSize];
//Header size
informationHeader[0] = informationHeaderSize;
informationHeader[1] = 0;
informationHeader[2] = 0;
informationHeader[3] = 0;
//Image width
informationHeader[4] = width;
informationHeader[5] = width >> 8;
informationHeader[6] = width >> 16;
informationHeader[7] = width >> 24;
//Image height
informationHeader[8] = height;
informationHeader[9] = height >> 8;
informationHeader[10] = height >> 16;
informationHeader[11] = height >> 24;
//Planes
informationHeader[12] = 1;
informationHeader[13] = 0;
//Bits per pixels (RGB)
informationHeader[14] = 24;
informationHeader[15] = 0;
//Compression (no compression)
informationHeader[16] = 0;
informationHeader[17] = 0;
informationHeader[18] = 0;
informationHeader[19] = 0;
//Image size (no compression)
informationHeader[20] = 0;
informationHeader[21] = 0;
informationHeader[22] = 0;
informationHeader[23] = 0;
//X pixels per meter (not specified)
informationHeader[24] = 0;
informationHeader[25] = 0;
informationHeader[26] = 0;
informationHeader[27] = 0;
//Y pixels per meter (not specified)
informationHeader[28] = 0;
informationHeader[29] = 0;
informationHeader[30] = 0;
informationHeader[31] = 0;
//Total colors (colors palette not used)
informationHeader[32] = 0;
informationHeader[33] = 0;
informationHeader[34] = 0;
informationHeader[35] = 0;
//Important colors (Generally ignored)
informationHeader[36] = 0;
informationHeader[37] = 0;
informationHeader[38] = 0;
informationHeader[39] = 0;
file.write(reinterpret_cast<char*>(fileHeader), fileHeaderSize);
file.write(reinterpret_cast<char*>(informationHeader), informationHeaderSize);
//file.write(reinterpret_cast<char*>(buffer), width * height * 4);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
unsigned char r = static_cast<unsigned char>(buffer[(height - y - 1) * width * 3 + x * 3 + 0]);
unsigned char g = static_cast<unsigned char>(buffer[(height - y - 1) * width * 3 + x * 3 + 1]);
unsigned char b = static_cast<unsigned char>(buffer[(height - y - 1) * width * 3 + x * 3 + 2]);
unsigned char color[] = { b, g, r };
file.write(reinterpret_cast<char*>(color), 3);
}
file.write(reinterpret_cast<char*>(bmpPadding), paddingAmount);
}
file.close();
std::cout << "File created\n";
}
int World[160][90][90] = { 0 };
int X1 = 0;
int Y1 = 0;
int BasicAngle = 0;
int Screen[320 * 180 * 3] = { 0 };
void drawLine(int x1, int y1, int z1, int x2, int y2, int z2, int ScreenX, int ScreenY) {
int dx = x2 - x1;
int dy = y2 - y1;
int dz = z2 - z1;
int steps = max(max(abs(dx), abs(dy)), abs(dz)); // Выбираем максимальное изменение по координатам как количество шагов
int xInc = dx / steps;
int yInc = dy / steps;
int zInc = dz / steps;
int x = x1;
int y = y1;
int z = z1;
for (int i = 0; i <= steps; i++) {
if (x < 160 && y < 90 && x > -1 && y > -1 && z > -1 && z < 90)
{
if (World[x][y][z] == 1)
{
i = steps;
Screen[ScreenY * 320 * 3 + (ScreenX + 160) * 3 + 0] = 255; // red
Screen[ScreenY * 320 * 3 + (ScreenX + 160) * 3 + 1] = 0; // green
Screen[ScreenY * 320 * 3 + (ScreenX + 160) * 3 + 2] = 0; // blue
Screen[y * 320 * 3 + x * 3 + 0] = 255; // red
Screen[y * 320 * 3 + x * 3 + 1] = 0; // green
Screen[y * 320 * 3 + x * 3 + 2] = 0; // blue
}
else
{
Screen[y * 320 * 3 + x * 3 + 0] = 0; // red
Screen[y * 320 * 3 + x * 3 + 1] = 255; // green
Screen[y * 320 * 3 + x * 3 + 2] = 0; // blue
}
}
x += xInc;
y += yInc;
z += zInc;
}
}
int main()
{
World[10][10][0] = 1;
World[10][10][10] = 1;
World[20][20][20] = 1;
World[40][40][40] = 1;
World[40][20][10] = 1;
while (true)
{
for (int i = 0; i < 320 * 180 * 3; i++)
{
Screen[i] = 0;
}
for (int x = 0; x < 320; x++)
{
Screen[90 * 320 * 3 + x * 3 + 0] = 255; // red
Screen[90 * 320 * 3 + x * 3 + 1] = 255; // green
Screen[90 * 320 * 3 + x * 3 + 2] = 255; // blue
}
for (int y = 0; y < 180; y++)
{
Screen[y * 320 * 3 + 160 * 3 + 0] = 255; // red
Screen[y * 320 * 3 + 160 * 3 + 1] = 255; // green
Screen[y * 320 * 3 + 160 * 3 + 2] = 255; // blue
}
for (int y = 0; y < 90; y++)
{
double B = (90.0 / 90.0) * y;
for (int x = 0; x < 160; x++)
{
double A = (90.0 / 160.0) * x + BasicAngle;
int R = 100;
int X = X1 + R * sin(B * M_PI / 180) * cos(A * M_PI / 180);
int Y = Y1 + R * sin(B * M_PI / 180) * sin(A * M_PI / 180);
int Z = R * cos(B * M_PI / 180);
drawLine(X1, Y1, 0, X, Y, Z, x, y);
}
}
if (GetAsyncKeyState(VK_SPACE))
{
save1dArrayToBMP("Frame.bmp", Screen, 320, 180);
Sleep(100);
}
}
}
23.12.2023:
Получилось добиться некоторого результата с подобием треугольников. Пока что попробовал только в 2Д. Выглядит это следующим образом:

Площадь с алгоритмом только уменьшилась. Угол обзора влияет на кучность лучей в центре, однако я не могу уменьшать угол бесконечно (увеличивая фокусное расстояние, катет). Что можно с этим сделать? (Код без алгортима)
int Focus = 32;
for (int x = -15; x < 16; x++)
{
double L = sqrt(Focus * Focus + x * x);
for (double Lenght = 0; Lenght < 100; Lenght += 0.1)
{
double coeff = Lenght / L;
int X = x * coeff;
int Y = Focus * coeff;
Screen[Y * 320 * 3 + (X + 160) * 3 + 1] = 255;
}
}
Тут в коде от -15 до 16, из-за этого пустоты, а надо от -159 до 160. С этим разобрался и перешёл в 3D
Перевёл в 3D 1 строкой:
double L = sqrt(Focus * Focus + x * x + y * y);
Очень хороший результат, что мне не нравится - волнистость кубика, неровные грани.
Алгоритм Брезенхема в 3D не могу добавить, не понимаю почему. А вот шаг повысил чёткость, однако сильно упала производительность. Теперь шаг 0.01, а хотелось бы 0.5
Я не могу понять следующее:
- Почему не работает алгоритм Брезенхема?
- Как умножать координаты на матрицу (чтобы повернуть камеру), если мне нужно в результате получить опять координаты, а не матрицу?
- Как можно обойти искажение без шага? Очень сильно упала производительность.
30.12.23 Последние результаты такие, для разрешения 640х480 дальность прорисовки от 130 до примерно 150 единиц, слева направо:
- 8.323 с. (метод на подобии треугольников, шаг: 0.01)
- 9.648 с. (метод по уравнению прямой, шаг на умножении: 0.00001)
- 6.824 с. (метод по уравнению прямой, шаг на сложении: 0.00001)
- 0.479 с. (код Станислава, принцип я пока что не понял)
во втором варианте я использую следующий способ:
в циклах for меняю endX и endY:
int aX = endX - startX;
int aY = endY - startY;
int aZ = endZ - startZ;
и после в цикле for меняю множитель (шаг):
int aX1 = aX * multiplier;
int aY1 = aY * multiplier;
int aZ1 = aZ * multiplier;
Шаг приходится уменьшать, чтобы избежать ошибок отрисовки. Чтобы отказаться от шага - нужно ориентироваться по сетке. Но тогда требуется для этого некоторая формула, которую я не знаю.
При этом сетка проходит по 0, по 1, по 2 и т.д., а куб находится между 0 и 1. В идеале сетка должна проходить по 0.5, по 1.5, 2.5 и т.д. - это уже задел на будущее
31.12:
Попробовал я демонстрационный пример. Попробовал пример - не изучил, очень постараюсь с ним разобраться, т.к. хотелось бы менять размер сетки, для оптимизации по типу "Voxel Octrees". С примером работает в 20 раз быстрее, чем просто с шагом. При разрешении 640х480 всё хорошо, потому что соотношение сторон почти одинаковое, а вот при FHD и соотношении 16:9 уже начинаются проблемы:

Жёлтым это призма (угол обзора), красным сам куб. Пытался домножать aX (чуть выше) на какой-то коэффициент, однако не преуспел. Меняется пропорция по высоте или ширине (цифры, где я указал количество пикселей), однако левый-нижний угол и диагональ правого-верхнего угла продолжают различаться, что плохо. Пока что я не понимаю, что и на что требуется умножить?
Ответы (2 шт):
#include <stdbool.h>
#include <stdio.h>
typedef struct {
bool busy;
unsigned char r, g, b;
} voxel_t;
#define GRID_SIZE 10
voxel_t grid[GRID_SIZE][GRID_SIZE][GRID_SIZE];
int main() {
grid[0][0][1].busy = true;
grid[0][0][1].r = 0;
grid[0][0][1].g = 0;
grid[0][0][1].b = 0;
grid[0][0][5].busy = true;
grid[0][0][5].r = 0;
grid[0][0][5].g = 0;
grid[0][0][5].b = 127;
grid[0][0][9].busy = true;
grid[0][0][9].r = 0;
grid[0][0][9].g = 0;
grid[0][0][9].b = 255;
grid[9][0][1].busy = true;
grid[9][0][1].r = 255;
grid[9][0][1].g = 0;
grid[9][0][1].b = 0;
grid[9][0][5].busy = true;
grid[9][0][5].r = 255;
grid[9][0][5].g = 0;
grid[9][0][5].b = 127;
grid[9][0][9].busy = true;
grid[9][0][9].r = 255;
grid[9][0][9].g = 0;
grid[9][0][9].b = 255;
grid[0][9][1].busy = true;
grid[0][9][1].r = 0;
grid[0][9][1].g = 255;
grid[0][9][1].b = 0;
grid[0][9][5].busy = true;
grid[0][9][5].r = 0;
grid[0][9][5].g = 255;
grid[0][9][5].b = 127;
grid[0][9][9].busy = true;
grid[0][9][9].r = 0;
grid[0][9][9].g = 255;
grid[0][9][9].b = 255;
grid[9][9][1].busy = true;
grid[9][9][1].r = 255;
grid[9][9][1].g = 255;
grid[9][9][1].b = 0;
grid[9][9][5].busy = true;
grid[9][9][5].r = 255;
grid[9][9][5].g = 255;
grid[9][9][5].b = 127;
grid[9][9][9].busy = true;
grid[9][9][9].r = 255;
grid[9][9][9].g = 255;
grid[9][9][9].b = 255;
const int dimx = 640, dimy = 480;
// camera origin position
double origin_x = 5;
double origin_y = 5;
double origin_z = -15;
// screen plane dimensions
double screen_z_offset = 0.5;
double screen_dimx = 0.4;
double screen_dimy = 0.3;
FILE *fp = fopen("temp.ppm", "wb");
fprintf(fp, "P6\n%d %d\n255\n", dimx, dimy);
// (i, j) - pixel on screen (pixel in image)
for (int i = 0; i < dimy; ++i) {
for (int j = 0; j < dimx; ++j) {
// pixel_* - координаты пиксела в мировой системе координат
// [0, dimx - 1] -> [-0.5, 0.5]
double sx = (double)j / (dimx - 1) - 0.5;
// [-0.5, 0.5] ->
// [origin_x - screen_dimx / 2, origin_x + screen_dimx / 2]
double pixel_x = origin_x + sx * screen_dimx;
// [0, dimy - 1] -> [0.5, -0.5]
double sy = 0.5 - (double)i / (dimy - 1);
// [-0.5, 0.5] ->
// [origin_y - screen_dimy / 2, origin_y + screen_dimy / 2]
double pixel_y = origin_y + sy * screen_dimy;
double pixel_z = origin_z + screen_z_offset;
// направление луча из камеры в мир
double dx = pixel_x - origin_x;
double dy = pixel_y - origin_y;
double dz = pixel_z - origin_z;
printf("%lf %lf %lf\n", dx, dy, dz);
// текущая точка на луче
double x = origin_x;
double y = origin_y;
double z = origin_z;
// индексы текущего вокселя
int vx = (int)x;
int vy = (int)y;
int vz = (int)z;
unsigned char color[3] = {127, 127, 127};
// трассировка вдоль луча
while (vz < GRID_SIZE) {
double dtz = (vz + 1 - z) / dz;
double dtx = dtz + 1;
if (dx > 0) {
dtx = (vx + 1 - x) / dx;
} else if (dx < 0) {
dtx = (vx - x) / dx;
}
double dty = dtz + 1;
if (dy > 0) {
dty = (vy + 1 - y) / dy;
} else if (dy < 0) {
dty = (vy - y) / dy;
}
double dt = dtx;
if (dty < dt) {
dt = dty;
}
if (dtz < dt) {
dt = dtz;
}
if (dt == dtx) {
if (dx > 0) {
++vx;
} else if (dx < 0) {
--vx;
}
}
if (dt == dty) {
if (dy > 0) {
++vy;
} else if (dy < 0) {
--vy;
}
}
if (dt == dtz) {
++vz;
}
x += dt * dx;
y += dt * dy;
z += dt * dz;
if (
0 <= vx && vx < GRID_SIZE &&
0 <= vy && vy < GRID_SIZE &&
0 <= vz && vz < GRID_SIZE &&
grid[vx][vy][vz].busy
) {
color[0] = grid[vx][vy][vz].r;
color[1] = grid[vx][vy][vz].g;
color[2] = grid[vx][vy][vz].b;
break;
}
}
fwrite(color, 1, 3, fp);
}
}
fclose(fp);
}
Демонстрационный пример, который показывает как по отрезку вывести индексы всех кубов, которые отрезок пересекает:
#include <math.h>
#include <stdio.h>
typedef struct {
int i; // текущий индекс ячейки решётки
double dt; // шаг между пересечениями
double next_t; // момент следующего пересечения решётки
int di; // как меняется индекс при пересечении решётки
} range_t;
void init_range(double start, double end, range_t *r) {
r->i = (int)floor(start);
r->dt = 1. / fabs(end - start);
if (start < end) {
r->next_t = r->dt * (floor(start) + 1. - start);
r->di = 1;
} else if (start > end) {
r->next_t = r->dt * (start - floor(start));
r->di = -1;
} else {
r->next_t = 2; // заведомо большое значение
r->di = 0;
}
}
int main() {
double sx = 3.5;
double sy = 9.9;
double sz = 8.9;
double ex = 9.9;
double ey = 0.1;
double ez = 5.2;
range_t rx;
range_t ry;
range_t rz;
init_range(sx, ex, &rx);
init_range(sy, ey, &ry);
init_range(sz, ez, &rz);
double t = 0;
while (t < 1) {
// печать координат куба
printf("%i %i %i\n", rx.i, ry.i, rz.i);
// rx->next_t - параметр t, когда отрезок пересечёт целый x
// ry->next_t - параметр t, когда отрезок пересечёт целый y
// rz->next_t - параметр t, когда отрезок пересечёт целый z
// выбираем из них наименьший
range_t *nearest = ℞
if (ry.next_t < nearest->next_t) {
nearest = &ry;
}
if (rz.next_t < nearest->next_t) {
nearest = &rz;
}
t = nearest->next_t; // увеличиваем параметр t
nearest->next_t += nearest->dt; // запоминаем следующее пересечение
nearest->i += nearest->di; // исправляем координату куба
}
}
$ gcc rasterization.c -lm $ ./a.out 3 9 8 4 9 8 4 8 8 4 7 8 5 7 8 5 7 7 5 6 7 6 6 7 6 5 7 6 4 7 6 4 6 7 4 6 7 3 6 8 3 6 8 2 6 8 2 5 8 1 5 9 1 5 9 0 5













