簡單渲染流水管線C++代碼實現(三)---矩陣

這一篇接着來一步步實現。
本篇主要是介紹矩陣,並推導渲染流水管線中使用的縮放、旋轉、平移矩陣,然後利用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);
}

遊戲效果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章