一、关于3D

我们看见,这两个三角形是相似的,因此计算很简单
若相对物体的方向是斜的,计算三角函数即可
不会的看代码
二、EasyX简介
initgraph(长,宽) 打开绘图 或initgraph(长,宽,窗口设置-见下文)
closegraph() 关闭绘图
cleardevice() 清屏
setlinestyle() 设置线条样式
setfillstyle() 设置填充样式
setcolor() 设置前景色 包括setlinecolor()&settextcolor()
setbkcolor() 设置背景色
setfillcolor() 设置填充色
setbkmode() 设置背景(不)透明
RGBtoGRAY() & RGBtoHSV() &RGBtoHSL() 转换 也可以将前后调换
EGERGB() & EGEGET_R() & EGEGET_G() & EGEGET_B() 将R/G/B与color数据转换
putpixel(x坐标,y坐标,颜色) 画点
line(起点x,起点y,终点x,终点y) 画线
rectangle(起点x,起点y,终点x,终点y) 画空心矩形 包括fill前缀(实心)
roundrect(起点x,起点y,终点x,终点y) 画空心矩形 包括fill前缀(实心)
circle(中心x,中心y,半径) 画圆 包括fill前缀(实心) & f后缀(快速)
ellipse(中心x,中心y,半径1,半径2) 画椭圆 包括fill前缀(实心) & f后缀(快速)
polygon() 画多边形 包括fill前缀(实心)
outtextxy(x坐标,y坐标,文本) 输出文本
mousemsg() 有没有鼠标信息
getmouse() 返回鼠标信息(没有则等待)
keymsg() 有没有键盘信息
getkey() 返回键盘信息(没有则等待)
newimage() 分配图片地址
getimage(指针,文件名)[仅EGE]、loadimage()[仅Easyx] 获取图像(从文件)
getimage(起点x,起点y,终点x,终点y) 获取图像(从屏幕)
putimage(指针,起点x,起点y) 输出图像(到屏幕)
saveimage() 输出图像(到文件
GetImageBuffer() 获取缓冲区指针(快速绘图、读图)
......
三、C++实现
cpp
#include <graphics.h>
#include <conio.h>
#include <math.h>
#include <time.h>
#include <windows.h>
const int WIDTH = 1200;
const int HEIGHT = 1000;
const float PI = 3.1415926535f;
// 摄像机参数
struct Camera {
float x = 0, y = 1, z = 0; // 初始位置
float yaw = 0, pitch = 0; // 视角角度
float speed = 0.1f; // 移动速度
float sensitivity = 0.008f; // 鼠标灵敏度
} camera;
// 3D点结构体
struct Point3D { float x, y, z; };
// 立方体面结构体
struct Face {
int points[4]; // 顶点索引
COLORREF color; // 颜色
float depth; // 深度用于排序
Point3D normal; // 法向量
};
// 立方体顶点
Point3D cubePoints[] = {
{-1, -1, -1}, {1, -1, -1}, {1, 1, -1}, {-1, 1, -1},
{-1, -1, 1}, {1, -1, 1}, {1, 1, 1}, {-1, 1, 1}
};
// 立方体面定义
Face cubeFaces[] = {
{{0,1,2,3}, RED}, // 前
{{4,5,6,7}, BLUE}, // 后
{{0,3,7,4}, GREEN}, // 左
{{1,5,6,2}, YELLOW}, // 右
{{3,2,6,7}, CYAN}, // 上
{{0,4,5,1}, MAGENTA} // 下
};
// 计算面的法向量
void calculateNormals() {
for (auto& face : cubeFaces) {
Point3D p1 = cubePoints[face.points[0]];
Point3D p2 = cubePoints[face.points[1]];
Point3D p3 = cubePoints[face.points[2]];
// 计算两个向量
Point3D v1 = {p2.x - p1.x, p2.y - p1.y, p2.z - p1.z};
Point3D v2 = {p3.x - p1.x, p3.y - p1.y, p3.z - p1.z};
// 计算叉积(法向量)
face.normal = {
v1.y * v2.z - v1.z * v2.y,
v1.z * v2.x - v1.x * v2.z,
v1.x * v2.y - v1.y * v2.x
};
// 归一化法向量
float len = sqrt(face.normal.x * face.normal.x + face.normal.y * face.normal.y + face.normal.z * face.normal.z);
face.normal.x /= len;
face.normal.y /= len;
face.normal.z /= len;
}
}
// 将3D坐标转换为屏幕坐标
POINT project(Point3D p) {
// 相对摄像机的位置
float dx = p.x - camera.x;
float dy = p.y - camera.y;
float dz = p.z - camera.z;
// 旋转(绕Y轴和X轴)
float cosY = cos(camera.yaw), sinY = sin(camera.yaw);
float cosP = cos(camera.pitch), sinP = sin(camera.pitch);
// 旋转计算
float x = dx*cosY - dz*sinY;
float z = dz*cosY + dx*sinY;
float y = dy*cosP - z*sinP;
z = z*cosP + dy*sinP;
// 透视投影
if(z <= 0) z = 0.0001f;
float f = 400 / z;
return { (int)(x*f + WIDTH/2), (int)(-y*f + HEIGHT/2) };
}
void drawScene() {
POINT screenPoints[8];
// 投影所有顶点
for(int i=0; i<8; i++)
screenPoints[i] = project(cubePoints[i]);
// 计算每个面的深度并排序
for(auto& face : cubeFaces) {
float avgZ = 0;
for(int i : face.points)
avgZ += cubePoints[i].z - camera.z;
face.depth = avgZ/4;
}
// 按深度排序(远到近)
qsort(cubeFaces, 6, sizeof(Face), [](const void* a, const void* b) {
return (int)(((Face*)b)->depth - ((Face*)a)->depth);
});
// 绘制每个面
for(auto& face : cubeFaces) {
POINT poly[4];
for(int i=0; i<4; i++)
poly[i] = screenPoints[face.points[i]];
// 直接使用面的颜色填充
setfillcolor(face.color);
fillpoly(4, (int*)poly);
}
}
// 绘制准星
void drawCrosshair() {
setcolor(BLACK);
line(WIDTH / 2 - 10, HEIGHT / 2, WIDTH / 2 + 10, HEIGHT / 2);
line(WIDTH / 2, HEIGHT / 2 - 10, WIDTH / 2, HEIGHT / 2 + 10);
}
// 绘制建筑物
void drawBuilding(float x, float y, float z, float width, float height, float depth, COLORREF color) {
Point3D buildingPoints[] = {
{x - width / 2, y - height / 2, z - depth / 2},
{x + width / 2, y - height / 2, z - depth / 2},
{x + width / 2, y + height / 2, z - depth / 2},
{x - width / 2, y + height / 2, z - depth / 2},
{x - width / 2, y - height / 2, z + depth / 2},
{x + width / 2, y - height / 2, z + depth / 2},
{x + width / 2, y + height / 2, z + depth / 2},
{x - width / 2, y + height / 2, z + depth / 2}
};
Face buildingFaces[] = {
{{0,1,2,3}, color}, // 前
{{4,5,6,7}, color}, // 后
{{0,3,7,4}, color}, // 左
{{1,5,6,2}, color}, // 右
{{3,2,6,7}, color}, // 上
{{0,4,5,1}, color} // 下
};
POINT screenPoints[8];
for(int i=0; i<8; i++)
screenPoints[i] = project(buildingPoints[i]);
for(auto& face : buildingFaces) {
POINT poly[4];
for(int i=0; i<4; i++)
poly[i] = screenPoints[face.points[i]];
setfillcolor(face.color);
fillpoly(4, (int*)poly);
}
}
// 绘制树
void drawTree(float x, float y, float z) {
// 树干
drawBuilding(x, y, z, 0.2f, 1.0f, 0.2f, RGB(139, 69, 19));
// 树冠
drawBuilding(x, y + 0.8f, z, 1.0f, 0.5f, 1.0f, RGB(34, 139, 34));
}
// 处理输入
void processInput() {
// 处理键盘输入
float moveX = 0, moveZ = 0;
if (GetAsyncKeyState('W') & 0x8000) moveX = 1; // 前
if (GetAsyncKeyState('S') & 0x8000) moveX = -1; // 后
if (GetAsyncKeyState('A') & 0x8000) moveZ = -1; // 左
if (GetAsyncKeyState('D') & 0x8000) moveZ = 1; // 右
// 计算移动方向(基于当前视角)
float speed = camera.speed;
float dx = moveX * cos(camera.yaw) - moveZ * sin(camera.yaw);
float dz = moveZ * cos(camera.yaw) + moveX * sin(camera.yaw);
camera.z += dx * speed;
camera.x += dz * speed;
// 处理鼠标输入
MOUSEMSG m;
while(MouseHit()) {
m = GetMouseMsg();
if(m.uMsg == WM_MOUSEMOVE) {
// 计算相对鼠标移动量
static int lastX = WIDTH / 2, lastY = HEIGHT / 2;
int dx = m.x - lastX;
int dy = m.y - lastY;
lastX = m.x;
lastY = m.y;
// 更新视角
camera.yaw += dx * camera.sensitivity;
camera.pitch -= dy * camera.sensitivity;
// 限制垂直视角
if(camera.pitch > PI/2.5) camera.pitch = PI/2.5;
if(camera.pitch < -PI/2.5) camera.pitch = -PI/2.5;
}
}
}
int main() {
initgraph(WIDTH, HEIGHT);
setbkcolor(WHITE);
BeginBatchDraw();
ShowCursor(FALSE); // 隐藏鼠标
// 计算法向量
calculateNormals();
// 将鼠标初始位置设置为窗口中心
SetCursorPos(WIDTH / 2, HEIGHT / 2);
while(true) {
// 检测 ESC 键退出
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
break;
}
cleardevice(); // 清屏
processInput();
//drawScene();
drawBuilding(0, 0, 5, 2, 3, 2, RGB(128, 128, 128)); // 绘制建筑物
drawBuilding(0, 0, 5, 2, 3, 2, RGB(128, 128, 128)); // 绘制建筑物
drawTree(3, 0, 5); // 绘制树
drawTree(5, 0, 3); // 绘制树
drawCrosshair();
FlushBatchDraw(); // 刷新缓冲区
Sleep(10); // 控制帧率
}
EndBatchDraw();
closegraph();
ShowCursor(TRUE); // 恢复鼠标显示
return 0;
}
效果:

编译:
bash
g++ -o 3d 3d.cpp -std=c++11 -leasyx
w a s d可以行走