婆娑梵净山 2026-02-14 21:03 采纳率: 88.3%
浏览 5

c++三维渲染小数点困惑


#include <graphics.h>
#include <cmath>
#include <vector>
#include<stdio.h>
#include<easyx.h>

// 定义三维点
struct Point3D {
    double x, y, z;
    Point3D(double x = 0, double y = 0, double z = 0) : x(x), y(y), z(z) {}
};

// 定义二维点(屏幕坐标)
struct Point2D {
    int x, y;
    Point2D(int x = 0, int y = 0) : x(x), y(y) {}
};

// 球体参数
const double PI = 3.1415926535;
const int radius = 150;          // 球体半径
const int centerX = 400;         // 屏幕中心 X 坐标
const int centerY = 300;         // 屏幕中心 Y 坐标
const int stacks = 30;           // 经线(纬度圈)数量,越多越精细
const int slices = 30;           // 纬线(经度圈)数量,越多越精细

// 存储所有顶点的三维坐标和投影后的二维坐标
std::vector<std::vector<Point3D>> vertices3D;
std::vector<std::vector<Point2D>> vertices2D;

// 旋转角度
double angleX = 0, angleY = 0, angleZ = 0;

// 三维旋转函数 (绕X轴)
Point3D rotateX(const Point3D& p, double angle) {
    double cosA = cos(angle), sinA = sin(angle);
    return Point3D(p.x, p.y * cosA - p.z * sinA, p.y * sinA + p.z * cosA);
}

// 三维旋转函数 (绕Y轴)
Point3D rotateY(const Point3D& p, double angle) {
    double cosA = cos(angle), sinA = sin(angle);
    return Point3D(p.x * cosA + p.z * sinA, p.y, -p.x * sinA + p.z * cosA);
}

// 三维旋转函数 (绕Z轴)
Point3D rotateZ(const Point3D& p, double angle) {
    double cosA = cos(angle), sinA = sin(angle);
    return Point3D(p.x * cosA - p.y * sinA, p.x * sinA + p.y * cosA, p.z);
}

// 初始化球体顶点
void initSphere() {
    vertices3D.resize(stacks + 1);
    for (int i = 0; i <= stacks; ++i) {
        vertices3D[i].resize(slices + 1);
        double theta = PI * i / stacks;          // 极角 (0 到 PI)
        double sinTheta = sin(theta);
        double cosTheta = cos(theta);

        for (int j = 0; j <= slices; ++j) {
            double phi = 2 * PI * j / slices;    // 方位角 (0 到 2PI)
            double sinPhi = sin(phi);
            double cosPhi = cos(phi);

            // 你提供的第一组公式就在这里!
            double x = radius * sinTheta * cosPhi;
            double y = radius * sinTheta * sinPhi;
            double z = radius * cosTheta;

            vertices3D[i][j] = Point3D(x, y, z);
        }
    }
}

// 投影并绘制
void draw() {
    // 应用旋转并投影到2D
    for (int i = 0; i <= stacks; ++i) {
        for (int j = 0; j <= slices; ++j) {
            Point3D p = vertices3D[i][j];
            // 应用旋转
            p = rotateX(p, angleX);
            p = rotateY(p, angleY);
            p = rotateZ(p, angleZ);
            // 平行投影到2D (忽略Z),并平移到屏幕中心
            int screenX = centerX + static_cast<int>(p.x);
            int screenY = centerY + static_cast<int>(p.y);
            vertices2D[i][j] = Point2D(screenX, screenY);
        }
    }

    // 绘制经线 (连接纬度圈上的点)
    for (int i = 0; i < stacks; ++i) {
        for (int j = 0; j < slices; ++j) {
            // 绘制四边形,这里简化为绘制两条线来形成网格
            line(vertices2D[i][j].x, vertices2D[i][j].y,
                vertices2D[i][j + 1].x, vertices2D[i][j + 1].y);
            line(vertices2D[i][j].x, vertices2D[i][j].y,
                vertices2D[i + 1][j].x, vertices2D[i + 1][j].y);
        }
        // 绘制最后一根经线
        line(vertices2D[i][slices].x, vertices2D[i][slices].y,
            vertices2D[i + 1][slices].x, vertices2D[i + 1][slices].y);
    }
    // 绘制最后一圈纬线
    for (int j = 0; j < slices; ++j) {
        line(vertices2D[stacks][j].x, vertices2D[stacks][j].y,
            vertices2D[stacks][j + 1].x, vertices2D[stacks][j + 1].y);
    }
}

int main() {
    // 创建绘图窗口
    initgraph(1800, 1600);
    // 设置坐标原点为左上角 (EasyX默认),y轴向下为正。我们已经在draw函数中手动处理了屏幕中心和平移。
    // 如果想用标准的数学坐标系,可以取消下面的注释
    // setorigin(centerX, centerY); // 设置原点为屏幕中心
    // setaspectratio(1, -1);       // 翻转y轴,使其向上为正

    initSphere();
    // 为投影后的2D点分配空间
    vertices2D.resize(stacks + 1);
    for (int i = 0; i <= stacks; ++i) {
        vertices2D[i].resize(slices + 1);
    }

    // 游戏主循环
    BeginBatchDraw(); // 开始批量绘图,防止闪烁
    while (1) { // 按下任意键退出
        cleardevice();

        // 更新旋转角度
        angleX += 0.01;
        angleY += 0.02;
        // angleZ += 0.005;

        // 绘制球体
        setlinecolor(WHITE); // 设置线条颜色为白色
        draw();

        FlushBatchDraw(); // 刷新画布,显示绘制内容
        Sleep(10);        // 短暂延时,控制帧率
    }
    EndBatchDraw();       // 结束批量绘图
    closegraph();         // 关闭窗口
    return 0;
}

img

img


疑问为什么用easyx无法对两条直线的那个交点进行颜色渲染

  • 写回答

1条回答 默认 最新

  • 檀越@新空间 2026-02-14 21:04
    关注

    晚上好🌙🌙🌙
    本答案参考通义千问

    你提供的 C++ 代码用于在 EasyX 图形库中绘制一个旋转的球体,但你提到存在 “小数点困惑” 的问题。这可能指的是以下几种情况:

    1. 投影时的小数点精度问题(例如,p.xp.y 是浮点数,直接强制转换为 int 可能导致坐标不准确)。
    2. 渲染结果出现锯齿或不平滑(由于坐标未正确处理)。
    3. 球体形状不正确(可能是公式或旋转逻辑的问题)。

    🧠 问题分析

    1. 投影时的坐标精度问题

    draw() 函数中,你使用了:

    int screenX = centerX + static_cast<int>(p.x);
    int screenY = centerY + static_cast<int>(p.y);
    

    这里将浮点数 p.xp.y 强制转换为整数,可能导致 坐标丢失精度,从而造成图形模糊、失真或错位。

    2. 球体形状问题

    你使用了标准的球面参数化公式:

    x = radius * sin(theta) * cos(phi)
    y = radius * sin(theta) * sin(phi)
    z = radius * cos(theta)
    

    这是正确的球面参数化方式,没有问题。

    3. 旋转逻辑是否正确

    你分别对 X、Y、Z 轴进行旋转,逻辑是正确的,但注意:旋转顺序会影响最终效果,建议保持一致性。


    ✅ 解决方案

    1. 精确投影:使用四舍五入而不是截断

    修改 draw() 中的投影部分,使用 round() 函数来避免因强制转换导致的误差:

    int screenX = centerX + static_cast<int>(round(p.x));
    int screenY = centerY + static_cast<int>(round(p.y));
    

    加粗重点: 使用 round() 而不是 static_cast<int> 可以避免因为浮点数的小数部分被丢弃而导致的视觉失真。


    2. 修复线段绘制逻辑

    你的当前线段绘制逻辑只绘制了部分线段,导致网格不完整。建议使用更清晰的方式绘制球体的经纬线。

    以下是优化后的 draw() 函数:

    void draw() {
        // 应用旋转并投影到2D
        for (int i = 0; i <= stacks; ++i) {
            for (int j = 0; j <= slices; ++j) {
                Point3D p = vertices3D[i][j];
                // 应用旋转
                p = rotateX(p, angleX);
                p = rotateY(p, angleY);
                p = rotateZ(p, angleZ);
                // 投影到2D,并四舍五入
                int screenX = centerX + static_cast<int>(round(p.x));
                int screenY = centerY + static_cast<int>(round(p.y));
                vertices2D[i][j] = Point2D(screenX, screenY);
            }
        }
    
        // 绘制经线 (连接纬度圈上的点)
        for (int i = 0; i <= stacks; ++i) {
            for (int j = 0; j < slices; ++j) {
                line(vertices2D[i][j].x, vertices2D[i][j].y,
                    vertices2D[i][j + 1].x, vertices2D[i][j + 1].y);
            }
        }
    
        // 绘制纬线 (连接经度圈上的点)
        for (int j = 0; j <= slices; ++j) {
            for (int i = 0; i < stacks; ++i) {
                line(vertices2D[i][j].x, vertices2D[i][j].y,
                    vertices2D[i + 1][j].x, vertices2D[i + 1][j].y);
            }
        }
    }
    

    加粗重点: 修正了线段绘制逻辑,确保所有经纬线都被正确绘制,使球体更完整。


    3. 添加帧率控制和退出条件

    目前你的主循环没有退出机制,可以添加按键退出功能:

    while (!kbhit()) { // 按下任意键退出
        cleardevice();
    
        // 更新旋转角度
        angleX += 0.01;
        angleY += 0.02;
    
        // 绘制球体
        setlinecolor(WHITE); // 设置线条颜色为白色
        draw();
    
        FlushBatchDraw(); // 刷新画布,显示绘制内容
        Sleep(10);
    }
    

    加粗重点: 增加了 kbhit() 检测,使程序可以在按下任意键后退出,提高用户体验。


    📝 修改后的完整代码片段(关键部分)

    // 投影并绘制
    void draw() {
        // 应用旋转并投影到2D
        for (int i = 0; i <= stacks; ++i) {
            for (int j = 0; j <= slices; ++j) {
                Point3D p = vertices3D[i][j];
                // 应用旋转
                p = rotateX(p, angleX);
                p = rotateY(p, angleY);
                p = rotateZ(p, angleZ);
                // 投影到2D,并四舍五入
                int screenX = centerX + static_cast<int>(round(p.x));
                int screenY = centerY + static_cast<int>(round(p.y));
                vertices2D[i][j] = Point2D(screenX, screenY);
            }
        }
    
        // 绘制经线 (连接纬度圈上的点)
        for (int i = 0; i <= stacks; ++i) {
            for (int j = 0; j < slices; ++j) {
                line(vertices2D[i][j].x, vertices2D[i][j].y,
                    vertices2D[i][j + 1].x, vertices2D[i][j + 1].y);
            }
        }
    
        // 绘制纬线 (连接经度圈上的点)
        for (int j = 0; j <= slices; ++j) {
            for (int i = 0; i < stacks; ++i) {
                line(vertices2D[i][j].x, vertices2D[i][j].y,
                    vertices2D[i + 1][j].x, vertices2D[i + 1][j].y);
            }
        }
    }
    
    int main() {
        initgraph(1800, 1600);
    
        initSphere();
        vertices2D.resize(stacks + 1);
        for (int i = 0; i <= stacks; ++i) {
            vertices2D[i].resize(slices + 1);
        }
    
        BeginBatchDraw();
        while (!kbhit()) { // 按下任意键退出
            cleardevice();
    
            angleX += 0.01;
            angleY += 0.02;
    
            setlinecolor(WHITE);
            draw();
    
            FlushBatchDraw();
            Sleep(10);
        }
    
        closegraph();
        return 0;
    }
    

    ✅ 总结

    | 问题 | 解决方法 | |------|----------| | 小数点精度问题 | 使用 round() 替代 static_cast<int> | | 线段绘制不完整 | 修正经纬线绘制逻辑 | | 无退出机制 | 添加 kbhit() 控制退出 |

    通过以上改进,你可以获得更精确、更平滑的三维球体渲染效果。

    如果你还有其他问题,比如球体旋转方向不对、光照效果等,也可以继续提问!

    评论

报告相同问题?

问题事件

  • 创建了问题 2月14日