这一节跟渲染好像关系不大,但是值得看一看,我觉得更像是对游戏引擎和渲染的一些非常浅显的摸索和思考,以前用unity都是直接调的标准资源包里的摄像机类,很少从底层原理来考虑摄像机的实现方法。GameObject也是,unity都封装好了。
本节的demo里一共介绍了3种摄像机
- 第一人称
- 第三人称
- 自由视角
其中自由视角和第一人称差距不大。
摄像机基类
先放一个以前用过的unity的非官方的第一人称幽灵视角摄像机类。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour {
public float zoomSensitivity = 10f;
public float mouseSensitivity = 5f;
public float speedSensitivity = 20f;
private float m_deltX = 0f;
private float m_deltY = 0f;
private Camera mainCamera;
void Start () {
mainCamera = GetComponent<Camera>();
}
// Update is called once per frame
void Update ()
{
if (Input.GetMouseButton(0))
{
LockCursor(true);
UFOMove();
ZoomMove();
}
else LockCursor(false);
}
private void FixedUpdate()
{
if (Input.GetMouseButton(0))
{
LookRotation();
}
}
private void ZoomMove()
{
if (Input.GetAxis("Mouse ScrollWheel") != 0)
{
mainCamera.transform.localPosition = mainCamera.transform.position + mainCamera.transform.forward * Input.GetAxis("Mouse ScrollWheel") * zoomSensitivity; ;
}
}
private void UFOMove()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
if (Input.GetKey(KeyCode.LeftShift))
{
horizontal *= 3; vertical *= 3;
}
mainCamera.transform.Translate(Vector3.forward * vertical * speedSensitivity * Time.deltaTime);
mainCamera.transform.Translate(Vector3.right * horizontal * speedSensitivity * Time.deltaTime);
}
private void LookRotation()
{
m_deltX += Input.GetAxis("Mouse X") * mouseSensitivity;
m_deltY -= Input.GetAxis("Mouse Y") * mouseSensitivity;
m_deltX = ClampAngle(m_deltX, -360, 360);
m_deltY = ClampAngle(m_deltY, -70, 70);
mainCamera.transform.rotation = Quaternion.Euler(m_deltY, m_deltX, 0);
}
private void LockCursor(bool b)
{
//Cursor.lockState = b ? CursorLockMode.Locked : Cursor.lockState = CursorLockMode.None;
Cursor.visible = b ? false : true;
}
float ClampAngle(float angle, float minAngle, float maxAgnle)
{
if (angle <= -360)
angle += 360;
if (angle >= 360)
angle -= 360;
return Mathf.Clamp(angle, minAngle, maxAgnle);
}
}
//原文链接:https://blog.youkuaiyun.com/MaxLykoS/article/details/72801979
unity中摄像机需要的一些有用的属性
只看重要的部分,首先得有Transform摄像机的坐标,位置旋转缩放,然后是,透视类型,FOV,剪裁距离和视口。
接下来就要写我们自己的摄像机类了,但是在写之前,得先弄清楚一些事情。
DirectXMath数学库
这个数学库实际上之前算世界变换矩阵的时候用到过,只不过当时只是矩阵计算,而在这里会添加一些向量计算。
可以参考这篇博客
做一些补充
- XMVECTOR和XMFloat3
首先XMVECTOR里有很多属性,但是看float那一行就够了。其次,XMVECTOR的float有4个数,而XMFloat3有三个数,当然可以用XMFloat4,但是很多情况下用3就够了。另外,XMFloat3这种类似的类型只是用来赋值存储,不能用于计算,用于计算的是XMVECTOR。
那二者如何转化呢?
float3 to vector -> XMLoadFloat3(float3)
vector to float3 -> XMStoreFloat3(float3, vector)
matrix to float4x4 -> XMStoreFloat4x4(float4x4, matrix)
float4x4 to matrix -> XMLoadFloat4x4(float4x4)
取单值
float3.x
XMVectorGetX(vector)
最后,假如两个vector点乘,结果仍是vector(应该是个常数),但是这个vector的xyzw都一样(都是结果常数)。 - XMVector3Transform和XMVector3TransformNormal
具体可以查微软文档,其中前者将认为输入向量的w为1,而返回的向量w可能不为1。后者将会忽略矩阵的最后一行计算。 - 常数乘向量时,要把常数保存到xyzw都一样的vector中再跟其他vector计算,用XMVectorReplicate函数。
实现第一人称Camera
前面提到,unity中摄像机的一些属性如下
摄像机的坐标属性,透视类型,FOV,剪裁距离和视口。
- 坐标属性
// 摄像机的观察空间坐标系对应在世界坐标系中的表示
DirectX::XMFLOAT3 m_Position;
DirectX::XMFLOAT3 m_Right;
DirectX::XMFLOAT3 m_Up;
DirectX::XMFLOAT3 m_Look;
坐标属性非常重要,关系到很多方法。
移动
平面步行
void FirstPersonCamera::Walk(float d)
{
XMVECTOR Pos = XMLoadFloat3(&m_Position);
XMVECTOR Right = XMLoadFloat3(&m_Right);
XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMVECTOR Front = XMVector3Normalize(XMVector3Cross(Right, Up));
XMVECTOR Dist = XMVectorReplicate(d);
// DestPos = Dist * Front + SrcPos
XMStoreFloat3(&m_Position, XMVectorMultiplyAdd(Dist, Front, Pos));
//步行只能在同一水平面上,所以up是固定的,front是用up和right叉乘的
//最后d乘front再加上原位置即可
}
//调用,第一人称视角
if (keyState.IsKeyDown(Keyboard::W))
cam1st->Walk(dt * 3.0f);
if (keyState.IsKeyDown(Keyboard::S))
cam1st->Walk(dt * -3.0f);
UFO式飞行
void FirstPersonCamera::MoveForward(float d)
{
XMVECTOR Pos = XMLoadFloat3(&m_Position);
XMVECTOR Look = XMLoadFloat3(&m_Look);
XMVECTOR Dist = XMVectorReplicate(d);
// DestPos = Dist * Look + SrcPos
XMStoreFloat3(&m_Position, XMVectorMultiplyAdd(Dist, Look, Pos));
//look就是front,直接叠加即可
}
//调用 自由视角
if (keyState.IsKeyDown(Keyboard::W))
cam1st->MoveForward(dt * 3.0f);
if (keyState.IsKeyDown(Keyboard::S))
cam1st->MoveForward(dt * -3.0f);
左右移动
void FirstPersonCamera::Strafe(float d)
{
XMVECTOR Pos = XMLoadFloat3(&m_Position);
XMVECTOR Right = XMLoadFloat3(&m_Right);
XMVECTOR Dist = XMVectorReplicate(d);
// DestPos = Dist * Right + SrcPos</