我正在尝试用 C++ 绘制一个立方体,但它看起来不太像我想要的那样。我正在使用公式将球坐标转换为笛卡尔(直角)坐标。这个想法是找到光线从窗口的每个像素照射到的位置。如果光线击中物体,则绘制该像素。
以下是代码的主要部分:
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);
}
}
}
12/23/2023:我们通过三角形的相似性取得了一些结果。到目前为止我只在 2D 中尝试过。它看起来像这样:

使用该算法的面积仅减少。视角会影响中心光线的分组,但我不能无限地减小角度(增加焦距、腿)。你能为这个做什么?(没有算法的代码)
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
只需 1 行即可转换为 3D:
double L = sqrt(Focus * Focus + x * x + y * y);
一个非常好的结果,我不喜欢的是立方体的波纹度,不均匀的边缘。
我无法在 3D 中添加 Bresenham 算法,我不明白为什么。但该步骤提高了清晰度,但性能却显着下降。现在步长是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 (чуть выше) на какой-то коэффициент, однако не преуспел. Меняется пропорция по высоте или ширине (цифры, где я указал количество пикселей), однако левый-нижний угол и диагональ правого-верхнего угла продолжают различаться, что плохо. Пока что я не понимаю, что и на что требуется умножить?















