這一篇接着來一步步實現。
本篇主要是介紹矩陣,並推導渲染流水管線中使用的縮放、旋轉、平移矩陣,然後利用C++實現它,並且做一個簡單的小遊戲(使用平移、旋轉、縮放)
1.首先介紹矩陣並實現它
理論:
C++代碼實現:matrix3
#pragma once
#include "vector2.h"
//此處的宏定義主要是爲了讀者能區分矩陣元素的位置
#define _M_11 0
#define _M_12 1
#define _M_13 2
#define _M_21 3
#define _M_22 4
#define _M_23 5
#define _M_31 6
#define _M_32 7
#define _M_33 8
class Matrix3
{
public:
float m[9];
//構造
Matrix3()
{
//默認爲單位矩陣
Identity();
}
//單位化
Matrix3& Identity()
{
for (int i = 0; i < 9; ++i)
m[i] = 0.0f;
m[_M_11] = m[_M_22] = m[_M_33] = 1.0f;
return *this;
}
//平移
Matrix3& Translate(float x, float y)
{
m[_M_11] = 1.0f; m[_M_12] = 0.0f; m[_M_13] = 0.0f;
m[_M_21] = 0.0f; m[_M_22] = 1.0f; m[_M_23] = 0.0f;
m[_M_31] = x; m[_M_32] = y; m[_M_33] = 1.0f;
return *this;
}
Matrix3& Translate(Vector2 v)
{
m[_M_11] = 1.0f; m[_M_12] = 0.0f; m[_M_13] = 0.0f;
m[_M_21] = 0.0f; m[_M_22] = 1.0f; m[_M_23] = 0.0f;
m[_M_31] = v.x; m[_M_32] = v.y; m[_M_33] = 1.0f;
return *this;
}
//旋轉
Matrix3& Rotation(float angle)
{
m[_M_11] = cos(angle); m[_M_12] = sin(angle); m[_M_13] = 0.0f;
m[_M_21] = -m[_M_12]; m[_M_22] = m[_M_11]; m[_M_23] = 0.0f;
m[_M_31] = 0.0f; m[_M_32] = 0.0f; m[_M_33] = 1.0f;
return *this;
}
//縮放
Matrix3& Scale(float x, float y)
{
m[_M_11] = x; m[_M_12] = 0.0f; m[_M_13] = 0.0f;
m[_M_21] = 0.0f; m[_M_22] = y; m[_M_23] = 0.0f;
m[_M_31] = 0.0f; m[_M_32] = 0.0f; m[_M_33] = 1.0f;
return *this;
}
Matrix3& Scale(Vector2 v)
{
m[_M_11] = v.x; m[_M_12] = 0.0f; m[_M_13] = 0.0f;
m[_M_21] = 0.0f; m[_M_22] = v.y; m[_M_23] = 0.0f;
m[_M_31] = 0.0f; m[_M_32] = 0.0f; m[_M_33] = 1.0f;
return *this;
}
};
inline Vector2* V2_x_M3(const Vector2* v, const Matrix3* m, Vector2* r)
{
float temp[] =
{
v->x * m->m[_M_11] + v->y * m->m[_M_21] + m->m[_M_31],
v->x * m->m[_M_12] + v->y * m->m[_M_22] + m->m[_M_32],
v->x * m->m[_M_13] + v->y * m->m[_M_23] + m->m[_M_33],
};
r->x = temp[0] / temp[2];
r->y = temp[1] / temp[2];
return r;
}
inline Vector2 operator * (const Vector2& v, const Matrix3& m)
{
Vector2 r;
return *V2_x_M3(&v, &m, &r);
}
inline Matrix3* M3_x_M3(const Matrix3* m1, const Matrix3* m2, Matrix3* r)
{
r->m[_M_11] =
m1->m[_M_11] * m2->m[_M_11] +
m1->m[_M_12] * m2->m[_M_21] +
m1->m[_M_13] * m2->m[_M_31];
r->m[_M_12] =
m1->m[_M_11] * m2->m[_M_12] +
m1->m[_M_12] * m2->m[_M_22] +
m1->m[_M_13] * m2->m[_M_32];
r->m[_M_13] =
m1->m[_M_11] * m2->m[_M_13] +
m1->m[_M_12] * m2->m[_M_23] +
m1->m[_M_13] * m2->m[_M_33];
r->m[_M_21] =
m1->m[_M_21] * m2->m[_M_11] +
m1->m[_M_22] * m2->m[_M_21] +
m1->m[_M_23] * m2->m[_M_31];
r->m[_M_22] =
m1->m[_M_21] * m2->m[_M_12] +
m1->m[_M_22] * m2->m[_M_22] +
m1->m[_M_23] * m2->m[_M_32];
r->m[_M_23] =
m1->m[_M_21] * m2->m[_M_13] +
m1->m[_M_22] * m2->m[_M_23] +
m1->m[_M_23] * m2->m[_M_33];
r->m[_M_31] =
m1->m[_M_31] * m2->m[_M_11] +
m1->m[_M_32] * m2->m[_M_21] +
m1->m[_M_33] * m2->m[_M_31];
r->m[_M_32] =
m1->m[_M_31] * m2->m[_M_12] +
m1->m[_M_32] * m2->m[_M_22] +
m1->m[_M_33] * m2->m[_M_32];
r->m[_M_33] =
m1->m[_M_31] * m2->m[_M_13] +
m1->m[_M_32] * m2->m[_M_23] +
m1->m[_M_33] * m2->m[_M_33];
return r;
}
inline Matrix3 operator * (const Matrix3& m1, const Matrix3& m2)
{
Matrix3 r;
return *M3_x_M3(&m1, &m2, &r);
}
2.遊戲部分:
#include <iostream>
#include <windows.h>
#include <vector>
#include "vector2.h"
#include "matrix3.h"
#pragma comment(lib, "Msimg32.lib")
//定義一些窗口常量
#define _CLIENT_PW 640 //屏幕像素寬
#define _CLIENT_PH 480 //屏幕像素高
#define _FRAME_PER_SECOND 40 //屏幕每秒刷新幀數
#define _FRAME_SLEEPTIME (1000/_FRAME_PER_SECOND) //每幀休息時間
#define _PI 3.1415 //pai
//全局窗口句柄
HWND g_hWnd = nullptr;
//窗口活動
BOOL g_Active = FALSE;
//函數聲明
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void GameInit();
void GameRun();
void GameEnd();
//程序入口點
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//填充窗口類別結構體
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = nullptr;
wc.lpszClassName = __TEXT("My3D");
//註冊窗口類別結構體
RegisterClass(&wc);
//根據客戶區矩形計算窗口矩形
int screen_pw = GetSystemMetrics(SM_CXSCREEN);
int screen_ph = GetSystemMetrics(SM_CYSCREEN);
RECT r =
{
(screen_pw - _CLIENT_PW) / 2,
(screen_ph - _CLIENT_PH) / 2,
r.left + _CLIENT_PW,
r.top + _CLIENT_PH
};
//校準窗口矩形
AdjustWindowRect(&r, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);
//創建窗口
g_hWnd = CreateWindow(
wc.lpszClassName,
__TEXT("My3D"),
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
r.left, r.top, r.right - r.left, r.bottom - r.top, //窗口左上角位置和寬高
HWND_DESKTOP,
NULL,
wc.hInstance,
NULL);
//更新窗口
UpdateWindow(g_hWnd);
//顯示窗口
ShowWindow(g_hWnd, nCmdShow);
//遊戲初始化
GameInit();
//消息循環
MSG msg = {};
while (WM_QUIT != msg.message)
{
//下面的代碼表示,一旦有消息就調用消息處理函數來處理消息
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//當沒有消息且窗口激活那麼就執行遊戲循環
else if (TRUE == g_Active)
{
//遊戲運行
GameRun();
}
//當沒有消息且窗口未激活就等待消息
else
WaitMessage();
}
//遊戲結束
GameEnd();
return (int)msg.wParam;
}
//消息回調函數
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_ACTIVATEAPP:
{
g_Active = static_cast<BOOL>(wParam);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(1);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//主顯示設備
HDC g_MainDC = nullptr;
//後備顯示設備
HDC g_BackDC = nullptr;
//英雄位置
Vector2 HeroPos(_CLIENT_PW / 2, _CLIENT_PH / 2);
//目的地位置
Vector2 Des = HeroPos;
//移動速度(標量)
float speed = 5.0f;
//移動方向
Vector2 Dir(0.0f, 0.0f);
//是否達到目的地
BOOL isArrive = true;
//矩形模型:頂點數據
Vector2 rect[4];
//角度
float angle = 0.0f;
//縮放比例
Vector2 scale(1.0f, 1.0f);
Vector2 multiple(0.1f, 0.1f);
void GameInit()
{
//得到主顯示設備
g_MainDC = GetDC(g_hWnd);
//創建後備顯示設備
g_BackDC = CreateCompatibleDC(g_MainDC);
//創建和主顯示設備兼容的位圖
HBITMAP hbmp = CreateCompatibleBitmap(g_MainDC, _CLIENT_PW, _CLIENT_PH);
//將兼容位圖選入兼容設備,刪除老的位圖
DeleteObject(SelectObject(g_BackDC, hbmp));
//初始化模型頂點數據
rect[0].Set(15, 15);
rect[1].Set(-15, 15);
rect[2].Set(-15, -15);
rect[3].Set(15, -15);
}
// 設備 起點x,y 終點x,y
void DrawLine(HDC hdc, float b_x1, float b_y1, float e_x2, float e_y2)
{
MoveToEx(hdc, b_x1, b_y1, 0);
LineTo(hdc, e_x2, e_y2);
}
void GameRun()
{
//得到從操作系統啓動一瞬間到目前的毫秒數
ULONGLONG begin_time = GetTickCount64();
//清屏
BitBlt(g_BackDC, 0, 0, _CLIENT_PW, _CLIENT_PH, nullptr, 0, 0, WHITENESS);
//創建變換矩陣:先縮放,再旋轉,最後平移
Matrix3 M[4];
M[0].Scale(scale);
M[1].Rotation(angle);
M[2].Translate(HeroPos);
M[3] = M[0] * M[1] * M[2];
//創建模型接受變換後的數值:原始數據不能隨意修改
Vector2 temp[4];
for (int i = 0; i < 4; ++i)
{
temp[i] = rect[i] * M[3];
}
//繪製
DrawLine(g_BackDC, temp[0].x, temp[0].y, temp[1].x, temp[1].y);
DrawLine(g_BackDC, temp[1].x, temp[1].y, temp[2].x, temp[2].y);
DrawLine(g_BackDC, temp[2].x, temp[2].y, temp[3].x, temp[3].y);
DrawLine(g_BackDC, temp[3].x, temp[3].y, temp[0].x, temp[0].y);
if (GetAsyncKeyState(VK_LBUTTON) & 0x8000)
{
//得到當前光標相對於桌面的座標
POINT p;
GetCursorPos(&p);
//將桌面的座標轉換到客戶區的座標
ScreenToClient(g_hWnd, &p);
Des.Set(p.x, p.y);
//計算移動方向
Vector2 t = Des - HeroPos;
Dir = t.Normalize();
}
scale += multiple;
if (scale.x >= 3.0f || scale.x <= 1.0f)
multiple = -multiple;
angle += 0.05f;
if (angle >= 2*_PI)
angle = 0.0f;
if ((HeroPos - Des).Length() <= 5)
isArrive = true;
else
isArrive = false;
//位置改變
if (isArrive == false)
{
HeroPos += (Dir * speed);
}
//後備顯示設備顏色傳輸給主顯示設備
BitBlt(
g_MainDC,
0, 0, _CLIENT_PW, _CLIENT_PH,
g_BackDC,
0, 0,
SRCCOPY);
//得到本次遊戲循環總的消耗時間
ULONGLONG frame_time = GetTickCount() - begin_time;
//進行本次遊戲循環休息
if (_FRAME_SLEEPTIME > frame_time)
Sleep(_FRAME_SLEEPTIME - frame_time);
else
Sleep(1); //必須休息一下
}
void GameEnd()
{
//刪除後備設備,釋放主設備
DeleteDC(g_BackDC);
ReleaseDC(g_hWnd, g_MainDC);
}
遊戲效果: