24-透视投影算法

透视类

class Projection  // 透视投影
{
public:
    Point3 Eye;   // 视点
    double R;     // 视点球坐标
    double Phi;   // 
    double Theta; // 
    double D;     // 
    double K[8];  // 透视常数
public:
    Projection()
    {       
        R = 1000;
        D = 800;
        Phi = 90;
        Theta = 0;      // 世界坐标z轴正上方 看向原点
        InitialParameter();
    }
    ~Projection(){}

    void InitialParameter() // 初始化参数
    {
        K[0] = sin(PI * Theta / 180);
        K[1] = sin(PI * Phi / 180);
        K[2] = cos(PI * Theta / 180);
        K[3] = cos(PI * Phi / 180);
        K[4] = K[1] * K[2];
        K[5] = K[0] * K[1];
        K[6] = K[2] * K[3];
        K[7] = K[0] * K[3];
        Eye  = Point3(R * K[5], R * K[5], R * K[4]); // 设置视点
    }

    void SetEye(double r, double phi, double theta)   // 设置视点
    {
        R = r;
        Phi = phi;
        Theta = theta;
        InitialParameter();
    }

    Point3 GetEye()
    {
        return Eye;
    }

    Point2 PerspectiveProjection2(Point3 worldPoint) // 二维透视投影
    {
        Point3 viewPoint;  // 观察坐标系
        viewPoint.x =  K[2] * worldPoint.x - K[0] * worldPoint.z;
        viewPoint.y = -K[7] * worldPoint.x + K[1] * worldPoint.y - K[6] * worldPoint.z;
        viewPoint.z = -K[5] * worldPoint.x - K[3] * worldPoint.y - K[4] * worldPoint.z + R;

        Point2 screenPoint; // 屏幕坐标
        screenPoint.x = D * viewPoint.x / viewPoint.z;
        screenPoint.y = D * viewPoint.y / viewPoint.z;

        return screenPoint;
    }
};

完整代码

// 24-透视投影算法
// 参考 https://www.bilibili.com/video/BV1Ne4y1m7MF

#define UNICODE
#include <Windows.h>
#include <Windowsx.h>
#include <math.h>
#define WINDOW_TEXT L"24-透视投影算法"
#define ROUND(d) int(floor(d)+0.5)  // 四舍五入
#define PI        3.1415926

struct Point2  // 二维点
{
    double x;
    double y;
    double w;  // 齐次坐标
    Point2() :x(0), y(0), w(0) {}
    Point2(double x, double y) :x(x), y(y), w(0) {}
};

struct Point3  // 三维点
{
    double x;
    double y;
    double z;
    double w;  // 齐次坐标
    Point3() :x(0), y(0), z(0), w(0) {}
    Point3(double x, double y, double z) :x(x), y(y), z(z), w(0) {}
};

struct Face        // 面
{
    int ptIndex[4] = { 0,0,0,0 }; // 面顶点索引
    Face() {};
    ~Face() {};
};

class Transform3
{
public:
    Point3* _P;          // 顶点数组
    int     _ptNumber;   // 顶点数量
    double  _M[4][4];    // 变换矩阵
public:
    Transform3() :_P(NULL), _ptNumber(0)
    {
        memset(_M, 0, sizeof(_M));
    };
    ~Transform3() {};

    void MultiplyMatrix()  //矩阵相乘
    {
        Point3* PTemp = new Point3[_ptNumber];
        for (int i = 0; i < _ptNumber; i++)
        {
            PTemp[i] = _P[i];
        }
        for (int i = 0; i < _ptNumber; i++)
        {
            _P[i].x = _M[0][0] * PTemp[i].x + _M[0][1] * PTemp[i].y + _M[0][2] * PTemp[i].z + _M[3][0]; // * PTemp[i].w
            _P[i].y = _M[1][0] * PTemp[i].x + _M[1][1] * PTemp[i].y + _M[1][2] * PTemp[i].z + _M[3][1]; // * PTemp[i].w
            _P[i].z = _M[2][0] * PTemp[i].x + _M[2][1] * PTemp[i].y + _M[2][2] * PTemp[i].z + _M[3][2]; // * PTemp[i].w
            _P[i].w = _M[3][0] * PTemp[i].x + _M[3][1] * PTemp[i].y + _M[3][2] * PTemp[i].z + _M[3][3]; // * PTemp[i].w
        }

        delete[]PTemp;
    }

    void SetMatrix(Point3* P, int ptNumber) //顶点数组初始化
    {
        _P = P;
        _ptNumber = ptNumber;
    }
    void Identity()
    {
        _M[0][0] = 1.0, _M[0][1] = 0.0, _M[0][2] = 0.0, _M[0][3] = 0.0;
        _M[1][0] = 0.0, _M[1][1] = 1.0, _M[1][2] = 0.0, _M[1][3] = 0.0;
        _M[2][0] = 0.0, _M[2][1] = 0.0, _M[2][2] = 1.0, _M[2][3] = 0.0;
        _M[3][0] = 0.0, _M[3][1] = 0.0, _M[3][2] = 0.0, _M[3][3] = 1.0;
    }

    void Translate(double tx, double ty, double tz)//平移变换
    {
        Identity();
        _M[3][0] = tx;
        _M[3][1] = ty;
        _M[3][2] = tz;
        MultiplyMatrix();
    }

    void Scale(double sx, double sy, double sz)//缩放变换
    {
        Identity();
        _M[0][0] = sx;
        _M[1][1] = sy;
        _M[2][2] = sz;
        MultiplyMatrix();
    }  

    void RotateX(double beta)//绕X轴旋转变换
    {
        Identity();
        beta = beta * PI / 180;
        _M[1][1] = cos(beta), _M[1][2] = -sin(beta);
        _M[2][1] = sin(beta), _M[2][2] = cos(beta);
        MultiplyMatrix();
    }

    void  RotateY(double beta)//绕Y轴旋转变换
    {
        Identity();
        beta = beta * PI / 180;
        _M[0][0] = cos(beta), _M[0][2] = sin(beta);
        _M[2][0] = -sin(beta), _M[2][2] = cos(beta);
        MultiplyMatrix();
    }

};

class Projection  // 透视投影
{
public:
    Point3 Eye;   // 视点
    double R;     // 视点球坐标
    double Phi;   // 
    double Theta; // 
    double D;     // 
    double K[8];  // 透视常数
public:
    Projection()
    {       
        R = 1000;
        D = 800;
        Phi = 90;
        Theta = 0;      // 世界坐标z轴正上方 看向原点
        InitialParameter();
    }
    ~Projection(){}

    void InitialParameter() // 初始化参数
    {
        K[0] = sin(PI * Theta / 180);
        K[1] = sin(PI * Phi / 180);
        K[2] = cos(PI * Theta / 180);
        K[3] = cos(PI * Phi / 180);
        K[4] = K[1] * K[2];
        K[5] = K[0] * K[1];
        K[6] = K[2] * K[3];
        K[7] = K[0] * K[3];
        Eye  = Point3(R * K[5], R * K[5], R * K[4]); // 设置视点
    }

    void SetEye(double r, double phi, double theta)   // 设置视点
    {
        R = r;
        Phi = phi;
        Theta = theta;
        InitialParameter();
    }

    Point3 GetEye()
    {
        return Eye;
    }

    Point2 PerspectiveProjection2(Point3 worldPoint) // 二维透视投影
    {
        Point3 viewPoint;  // 观察坐标系
        viewPoint.x =  K[2] * worldPoint.x - K[0] * worldPoint.z;
        viewPoint.y = -K[7] * worldPoint.x + K[1] * worldPoint.y - K[6] * worldPoint.z;
        viewPoint.z = -K[5] * worldPoint.x - K[3] * worldPoint.y - K[4] * worldPoint.z + R;

        Point2 screenPoint; // 屏幕坐标
        screenPoint.x = D * viewPoint.x / viewPoint.z;
        screenPoint.y = D * viewPoint.y / viewPoint.z;

        return screenPoint;
    }
};


class Cube        // 立方体
{
public:
    Point3      _V[8]; // 点表
    Face        _F[6]; // 面表
    Projection  _projection; // 透视
public:
    Cube() {};
    ~Cube() {};

    void ReadVertex() // 读取点表
    {
        _V[0].x = 0; _V[0].y = 0; _V[0].z = 0;
        _V[1].x = 1; _V[1].y = 0; _V[1].z = 0;
        _V[2].x = 1; _V[2].y = 1; _V[2].z = 0;
        _V[3].x = 0; _V[3].y = 1; _V[3].z = 0;
        _V[4].x = 0; _V[4].y = 0; _V[4].z = 1;
        _V[5].x = 1; _V[5].y = 0; _V[5].z = 1;
        _V[6].x = 1; _V[6].y = 1; _V[6].z = 1;
        _V[7].x = 0; _V[7].y = 1; _V[7].z = 1;

    }
    void ReadFace()  // 读取面表
    {
        _F[0].ptIndex[0] = 0; _F[0].ptIndex[1] = 4; _F[0].ptIndex[2] = 7; _F[0].ptIndex[3] = 3; // 左
        _F[1].ptIndex[0] = 1; _F[1].ptIndex[1] = 2; _F[1].ptIndex[2] = 6; _F[1].ptIndex[3] = 5; // 右
        _F[2].ptIndex[0] = 0; _F[2].ptIndex[1] = 1; _F[2].ptIndex[2] = 5; _F[2].ptIndex[3] = 4; // 底
        _F[3].ptIndex[0] = 2; _F[3].ptIndex[1] = 3; _F[3].ptIndex[2] = 7; _F[3].ptIndex[3] = 6; // 顶
        _F[4].ptIndex[0] = 0; _F[4].ptIndex[1] = 3; _F[4].ptIndex[2] = 2; _F[4].ptIndex[3] = 1; // 后
        _F[5].ptIndex[0] = 4; _F[5].ptIndex[1] = 5; _F[5].ptIndex[2] = 6; _F[5].ptIndex[3] = 7; // 前
    }

    void Draw(HDC hdc)
    {
        Point2 screenPoint[4];                             // 二维点

        for (int nFace = 0; nFace < 6; nFace++)            // 面循环
        {

            for (int nVertex = 0; nVertex < 4; nVertex++)  // 点循环
            {
                int vertex = _F[nFace].ptIndex[nVertex];

                screenPoint[nVertex] = _projection.PerspectiveProjection2(_V[vertex]);
            }

            MoveToEx(hdc, ROUND(screenPoint[0].x), ROUND(screenPoint[0].y), NULL); // 绘制多边形            
            LineTo(hdc, ROUND(screenPoint[1].x), ROUND(screenPoint[1].y));
            LineTo(hdc, ROUND(screenPoint[2].x), ROUND(screenPoint[2].y));
            LineTo(hdc, ROUND(screenPoint[3].x), ROUND(screenPoint[3].y));
            LineTo(hdc, ROUND(screenPoint[0].x), ROUND(screenPoint[0].y));
        }

    }
};

static bool     m_Play = false;
static wchar_t* m_str = L"左键 - 旋转 | 暂停";

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rect;
    static double alpha;
    static double beta;


    switch (uMsg)
    {
    case WM_TIMER:
        alpha += 1;
        beta += 1;
        InvalidateRect(hwnd, NULL, FALSE);
        return 0;

    case WM_LBUTTONDOWN:
        m_Play = !m_Play;
        if (m_Play)
        {
            SetTimer(hwnd, 0, 0, NULL);
        }
        else
        {
            KillTimer(hwnd, 0);
        }
        return 0;

    case WM_PAINT:
    {
        hdc = BeginPaint(hwnd, &ps);
        GetClientRect(hwnd, &rect);

        SetMapMode(hdc, MM_ANISOTROPIC);
        SetWindowExtEx(hdc, rect.right, rect.bottom, NULL);
        SetViewportExtEx(hdc, rect.right, -rect.bottom, NULL);
        SetViewportOrgEx(hdc, rect.right / 2, rect.bottom / 2, NULL);

        // 内存DC
        HDC     memDC = CreateCompatibleDC(hdc);                               // 创建兼容DC 画板
        HBITMAP newBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);  // 创建画布
        HGDIOBJ oldBitmap = SelectObject(memDC, newBitmap);                    // 将画布选入画板
        FillRect(memDC, &rect, (HBRUSH)(COLOR_WINDOW + 1));
        DrawText(memDC, m_str, wcslen(m_str), &rect, NULL);

        SetMapMode(memDC, MM_ANISOTROPIC);
        SetWindowExtEx(memDC, rect.right, rect.bottom, NULL);
        SetViewportExtEx(memDC, rect.right, -rect.bottom, NULL);
        SetViewportOrgEx(memDC, rect.right / 2, rect.bottom / 2, NULL);


        // 绘制图形
        {
            static Cube cube;
            static Transform3 transform;

            cube.ReadVertex();
            cube.ReadFace();

            transform.SetMatrix(cube._V, 8);  // 设置顶点

            double scale = 300;

            transform.Scale(scale, scale, scale);                     // 放大

            transform.Translate(-scale / 2, -scale / 2, -scale / 2);    // 平移
            transform.RotateX(alpha);
            transform.RotateY(beta);

            cube.Draw(memDC);

        }

        // 内存dc复制到设备
        BitBlt(hdc, ROUND(-rect.right / 2), ROUND(-rect.bottom / 2), rect.right, rect.bottom, memDC, ROUND(-rect.right / 2), ROUND(-rect.bottom / 2), SRCCOPY);

        SelectObject(memDC, oldBitmap);

        DeleteObject(newBitmap);
        DeleteDC(memDC);

        EndPaint(hwnd, &ps);
    }
    return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    // Register the window class.
    const wchar_t CLASS_NAME[] = L"CAG";
    WNDCLASS wc = { };

    wc.style = CS_HREDRAW | CS_VREDRAW; // 重新绘制整个工作区
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0,                              // Optional window styles.
        CLASS_NAME,                     // Window class
        WINDOW_TEXT,                    // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

        NULL,       // Parent window    
        NULL,       // Menu
        hInstance,  // Instance handle
        NULL        // Additional application data
    );

    if (hwnd == NULL)
    {
        return 0;
    }
    ShowWindow(hwnd, nCmdShow);
    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}


透视投影矩阵推导

透视投影三要素:视点、屏幕、物体

世界坐标原点平移至观察坐标系原点

绕y轴旋转

绕x轴旋转

仿射变换,(从右手系变为左手洗)新坐标系与观察坐标系重合

世界坐标右手系统,屏幕坐标左手系,观察坐标左系

原点平移到视点的矩阵

绕y轴旋转

变换矩阵

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值