DirectX11笔记9:第一人称摄像机
效果

注意i,以下所有内容一定一定要结合我的源码阅读。
然后希望我的程序可以运行成功
1.个人坐标的创建:
在第一人称摄像机中,摄像机需要以下几个元素去描述它的状态:
DirectX::XMFLOAT4 Position; //位置 DirectX::XMFLOAT4 Pos_Up; //指定向上的位置 DirectX::XMFLOAT4 Target; //看的方向单位向量 DirectX::XMFLOAT4 Pos_Right;//指定“右侧”的方向
记录相机相对于世界坐标的位置,向上的方向(现在没用),看向眼睛看向方向的单位向量,以及”右侧“的方向
他们的初始化赋值:
//在这里初始化相机信息 //建立,描述 摄像机与视角坐标 Position = DirectX::XMFLOAT4(0.0f, 1.0f, 4.0f, 0.0f); Pos_Up = DirectX::XMFLOAT4(0.0f, 1.0f, 0.0f, 0.0f); Target = DirectX::XMFLOAT4(0.0f, 0.0f, -1.0f,0.0f); Pos_Right = DirectX::XMFLOAT4(-1.0f, 0.0, 0.0, 0.0);
建立初始转化的矩阵:
View_World = DirectX::XMMatrixIdentity();//XMMatrixIdentity构建单位矩阵 View_View = DirectX::XMMatrixLookAtLH(DirectX::XMLoadFloat4(&Position), DirectX::XMLoadFloat4(&(Target + Position)), DirectX::XMLoadFloat4(&Pos_Up)); //XMMatrixLookAtLH函数返回的是世界->视图变换矩阵 View_Projection = DirectX::XMMatrixPerspectiveFovLH(DirectX::XM_PIDIV2, 800 / (FLOAT)600, 0.01f, 100.0f);
这里需要自己写一个关于XMFLOAT4相加的重载函数:
///////////// /////////////////////XNA_MATH_CODE//////////////////////////////////////////////////// DirectX::XMFLOAT4 operator+(DirectX::XMFLOAT4 ¶meter1, DirectX::XMFLOAT4 ¶meter2) { return DirectX::XMFLOAT4( parameter1.x + parameter2.x, parameter1.y + parameter2.y, parameter1.z + parameter2.z, parameter1.w + parameter1.w ); } ///////////////////////////////////////////////////////////////////////////////////////// ////////////
这样,初始化好了摄像机,使用了一个位置,一个眼睛方向,一个向右方向,一个向上方向,固定了它。
相当于 位置坐标。Z轴,X轴,Y轴。在原世界坐标系中建立了一个新的坐标系,然后获得 世界————》局部 的坐标转换矩阵.
2.相机的旋转变幻
旋转变化实际上就是2个绕着轴旋转。
左右旋转:眼睛看向的方向与向右的方向以Y轴为轴线旋转:
代码:
void FirstCamera::RotateBy_Y(float xx) { DirectX::XMMATRIX Angle_RotateY = DirectX::XMMatrixRotationY(xx);//计算角度 //角度变幻 DirectX::XMVECTOR after = DirectX::XMVector4Transform(DirectX::XMLoadFloat4(&Target), Angle_RotateY); DirectX::XMStoreFloat4(&Target, after); //right 也要变化 after = DirectX::XMVector4Transform(DirectX::XMLoadFloat4(&Pos_Right), Angle_RotateY); DirectX::XMStoreFloat4(&Pos_Right, after); IsCameraMove = TRUE; }
上下旋转:眼睛看向的方向与头顶的方向以 向右的方向为轴线旋转,
这里有一点注意,由于人的头是无法上下旋转超过90度的,就是说你无法通过向上转头,最后看到自己的脚后跟。需要一个限制。
void FirstCamera::RotateBy_X(float xx) { DirectX::XMFLOAT4 Restrore = Target; //DirectX::XMFLOAT4 Restore = Target;//想一想又没其它办法? DirectX::XMMATRIX Angle_RotateX = DirectX::XMMatrixRotationAxis(XMLoadFloat4(&Pos_Right), xx);//计算角度a DirectX::XMVECTOR after = DirectX::XMVector4Transform(DirectX::XMLoadFloat4(&Target), Angle_RotateX); //使用 //XMMatrixRotationAxis DirectX::XMStoreFloat4(&Target, after); //判断一下,锁定上下的视角 if (Target.y<-0.9 || Target.y>0.9) { Target = Restrore; return; } /* if (Target.z<1.0 && Target.z>-1.0) { Target = Restore; return; }*/ DirectX::XMVECTOR after_up = DirectX::XMVector4Transform(DirectX::XMLoadFloat4(&Pos_Up), Angle_RotateX); //注意这里 //DirectX::XMStoreFloat4(&Pos_Up, after_up); IsCameraMove = TRUE; }
这里看会发现,我没有对像向上的方向进行变换,你们可以尝试打开那句注释,会有奇怪的事情发生。。。。我是发现为啥。。。
3.平移变换
很简单就可以想到,向前平移就是沿着眼睛方向在地面投影的方向移动(人不会飞,也不会钻地)。左右平移就是沿着向右方向在地面的投影方向平移。
代码:
void FirstCamera::MoveCamera_LeftRight(float xx) { float normaliz_Length = (Pos_Right.x)*(Pos_Right.x) + (Pos_Right.z)*(Pos_Right.z); float normaliz_X = Pos_Right.x / normaliz_Length; float normaliz_Z = Pos_Right.z / normaliz_Length; horizonMove.x += normaliz_X*xx; horizonMove.z += normaliz_Z*xx; IsCameraMove = TRUE; } void FirstCamera::MoveCamera_UpDown(float xx) { IsCameraMove = TRUE; } void FirstCamera::MoveCamera_ForwadBack(float xx) { float normaliz_Length = (Target.x)*(Target.x) + (Target.z)*(Target.z); float normaliz_X = Target.x / normaliz_Length; float normaliz_Z = Target.z / normaliz_Length; horizonMove.x += normaliz_X*xx; horizonMove.z += normaliz_Z*xx; IsCameraMove = TRUE; }
没什么好说的。。。
最后是接受修改之后改变相机相对位置:
void FirstCamera::ApplyCameraChange() { Position = DirectX::XMFLOAT4( Position.x + horizonMove.x, Position.y + horizonMove.y, Position.z + horizonMove.z, Position.w ); View_View = DirectX::XMMatrixLookAtLH(DirectX::XMLoadFloat4(&Position), DirectX::XMLoadFloat4(&(Target + Position)), DirectX::XMLoadFloat4(&Pos_Up)); //update IsCameraMove = FALSE; horizonMove = DirectX::XMFLOAT3(0, 0, 0); //clean }
至此,所有的原理都解释完毕了。
然后是实现方式
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
键盘与鼠标的输入输出。
这里我本来是要使用DirectInput的功能区做键鼠模块的,后来google了一些资料,发现Iput系列也是用类似hook实现的,而且评价不好。
那就用windows自带的消息机制实现,也不难。
先是鼠标:
鼠标接受一个MouseMove消息,并且将他固定在屏幕中央(使用SendInput),每次MouseMove后计算出它的移动值,以此来进行改变视角:
(这里有个没解决的问题,窗口化游戏时,鼠标移动太大会鼠标弹出窗口外。。。。)
case WM_MOUSEMOVE: POINT Mouse_Move; Mouse_Move.x = LOWORD(lParam); Mouse_Move.y = HIWORD(lParam); SendInput(1, &mouseInput, sizeof(INPUT)); ClientToScreen(Main_Window_HWND, &Mouse_Move); //printf("Mouse Move :%ld, %ld\n", Mouse_Move.x - Window_Center_X, Mouse_Move.y - Window_Center_Y); Mouse_Move.x = Mouse_Move.x - Window_Center_X; Mouse_Move.y = Mouse_Move.y - Window_Center_Y; RotateBy_Y((float)(0.0015*Mouse_Move.x)); RotateBy_X((float)(0.0015*Mouse_Move.y)); return 0;
在开始的时候使用ShowCursor(FALSE);把鼠标隐藏掉。
每次程序窗口移动都要重新计算窗口中心点:
case WM_MOVE:
if (FALSE == SetMouseMoveCenter())
printf("Error in Set Mouse Move Center\n");
return 0;
计算窗口中心的代码:
BOOL My_D3d_APP::SetMouseMoveCenter() { BOOL ret; RECT wndRect1; ret = GetWindowRect(Main_Window_HWND, &wndRect1); printf("+====== SetMouseCenter =====+\n"); printf("window Rect: %d, %d , %d, %d\n", wndRect1.bottom, wndRect1.left, wndRect1.right, wndRect1.top); Window_Center_X = wndRect1.left + 400; Window_Center_Y = wndRect1.top + 300; //Window_Center_X = 65535 * Window_Center_X / GetSystemMetrics(SM_CXSCREEN); //Window_Center_Y = 65535 * Window_Center_Y / GetSystemMetrics(SM_CYSCREEN); printf("Set Window Center :%d %d\n", Window_Center_X, Window_Center_Y); mouseInput.mi.dx = 65535 * Window_Center_X / GetSystemMetrics(SM_CXSCREEN); mouseInput.mi.dy = 65535 * Window_Center_Y / GetSystemMetrics(SM_CYSCREEN); return ret; }
然后是键盘:
在Keydown的时候 记录一个按下的记号,在KeyUp的时候记录一个放开的记号,在每一帧之前计算一次是否移动,怎么移动。
case WM_KEYDOWN: switch (wParam) { case 38://up if (Mouse_Move_Forwad_Back != 1) Mouse_Move_Forwad_Back = 1; //MoveCamera_ForwadBack((float)+0.2); //RotateBy_X((float)+0.05); break; case 40://down if (Mouse_Move_Forwad_Back != -1) Mouse_Move_Forwad_Back = -1; //MoveCamera_ForwadBack((float)-0.2); //RotateBy_X((float)-0.05); break; case 37://left if (Mouse_Move_Righr_Left != -1) Mouse_Move_Righr_Left = -1; //MoveCamera_LeftRight((float)-0.2); break; case 39://right if (Mouse_Move_Righr_Left != 1) Mouse_Move_Righr_Left = 1; //MoveCamera_LeftRight((float)+0.2); break; default: break; } return 0; case WM_KEYUP: switch (wParam) { case 38://up if (Mouse_Move_Forwad_Back != -1) Mouse_Move_Forwad_Back = 0; break; case 40://down if (Mouse_Move_Forwad_Back != 1) Mouse_Move_Forwad_Back = 0; break; case 37://left if (Mouse_Move_Righr_Left != 1) Mouse_Move_Righr_Left = 0; break; case 39://right if (Mouse_Move_Righr_Left != -1) Mouse_Move_Righr_Left = 0; break; default: break; } return 0;
还有:
if (Mouse_Move_Forwad_Back != 0) MoveCamera_ForwadBack(Mouse_Move_Forwad_Back*((float)+0.04)); if (Mouse_Move_Righr_Left != 0) MoveCamera_LeftRight(Mouse_Move_Righr_Left*((float)+0.04)); RenderScene(); Sleep(15);
至此,所有的内容完毕。
后面是一些注意点。
1.顶点输入顺序。
在输入顶点时以先下后上的方式输入。
比如我的程序中有一个箱子与一个100X100的草地,那么箱子在草地上面,必须先输入草地的顶点,再输入箱子的顶点
2.Y轴的问题:
这个程序中相机的Y轴不会改变,否则会出现奇怪的事情。。。。
浙公网安备 33010602011771号