透视类
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轴旋转
变换矩阵