Unity 基于Cinemachine计算透视摄像机在地图中的移动范围
Unity中Cinemachine的基础功能介绍可详见之前写的博客:
https://www.cnblogs.com/koshio0219/p/11820654.html
本篇的重点是讨论,在给定规则地图的长宽和中心点坐标的情况下,如何动态生成一个透视摄像机的碰撞盒子以限定摄像机的视野永远不会超出地图的边界。
例如,下面这种规则地图:(或者其他用程序生成的单位块地图)
在输入一些参数后:
可以自动创建形如:
这样的摄像机运动范围,且输出的范围能够适配到屏幕的分辨率,考虑到相机绕某一轴向的旋转等问题。
其实基本都是纯粹的数学运算,开始之前,必须先弄清楚透视摄像机的一些基本原理,它的视窗大小和屏幕分辨率之间到底是什么关系:
1.FOV:这是透视摄像机区别于正交摄像机最重要的一个特性——视口大小,它表示的是当前摄像机视野范围的开口角度,也因该角度大小的不同,使得透视摄像机的近裁剪平面和远裁剪平面大小不一,从而产生三维空间中近大远小的特点。
2.Aspect:当前摄像机的宽高比。为什么要设置这样一个东西呢?理由就是屏幕有不同的分辨率,而相机映照出来的画面最终是要在屏幕当中显示的,当我们的屏幕分辨率发生变化时,相机的视口面积也会对应的发生变化,这时,仅仅只有一个FOV没办法满足不同类型的屏幕分辨率,于是就需要额外设置相机的宽高比来对最终呈现的摄像机视口大小进行辅助调整。
在Unity中,是以视口的高为基准进行计算的,也就是说,Unity中的透视摄像机的Fov角度其实是按照屏幕分辩率的高度进行对应的,而宽度对应的Fov则随着Aspect的变化而变化,不是面板设置的Fov大小。
试比较下面两张图,分别是摄像机的宽和高的Fov:
设置的Fov为40度,当前的屏幕分辨率为2960*1440:
很显然,只有高度对应的Fov为面板中显示的值,而宽度对应的Fov明显大于40度。实际宽的的Fov应该是82度左右(40*2960/1440)。
知道了上面这些后我们才能更愉快的进行接下来的计算,不然只会计算出许多错误也搞不清是什么原因。
在Cinemachine中,一般会设置一个跟随目标,且跟踪该目标的距离是一个常量,可以从面板中取得:
我们先分析摄像机的左右运动范围是如何计算的:(本例中的摄像机只在X轴向上存在旋转值,一般斜向的摄像机也只需要旋转一个轴即可,左右看上去一般追求对称性)
观察上图,假设现在摄像机位于空中的P点,已知AB为地图的边缘围墙高度,BC为角色的高度,CP为跟踪的摄像机到角色的距离,现在我们需要求出摄像机所在的X轴向的坐标,关键就是要求出AD的距离。
我们还知道一个数据就是摄像机的Fov,但是由于该Fov并非高度对应的值,所以我们先要进行一次转换,以得到摄像机宽度视口的Fov角度。以下均为弧度计算:
//计算的角度均为弧度值,传入纵向的(高)Fov的一半得到横向的(宽)Fov的一半
public float GetHorizontalFovHalf(float vhfov, float aspect)
{
return Mathf.Atan(Mathf.Tan(vhfov) * aspect);
}
上面已经讲过原理了这里就不在进行过多叙述了,简单来说就是利用摄像机的深度值进行了一次转换,因为无论是纵向还是横向的Fov,它们的深度值都是相同的,读者可以自行画图或脑补一下。
通过上面的方法我们就可以求得∠DPA的大小了,它正好就是横向Fov的一半,那个∠α的大小就可以轻易求出,现在问题的关键就是要求出边AP的长度,AP的长度得出的话,就可以利用∠α余弦求得AD,DP等。
利用正弦定理可以非常快速的解决上面的问题,当然你也可以设未知数利用勾股定律解一元二次方程,但当你写程序的时候你可能会有想吐的冲动:
//计算轴向偏移值
private float GetSizeOffse(float fbangel, float distance, float wh, float followy)
{
//直角弧度值
var rightangel = * Mathf.Deg2Rad;
//∠PAC
var disangel = fbangel + rightangel;
//求出正弦定理的比值
var sin = distance / Mathf.Sin(disangel);
//求∠APC的正弦值
var angelo = (wh - followy) / sin;
//三角形内角和求∠ACP
var angel = rightangel * - Mathf.Asin(angelo) - disangel;
//计算AP利用α余弦返回AD
return sin * angel * Mathf.Cos(fbangel);
}
fbangel即为上图中的∠α,distance即为上图中的CP,wh即为上图中的AB,followy即为上图中的CB。
X轴向的偏移计算完毕后,Z轴的偏移也是类似的,只不过需要考虑旋转值而已,接下来就是摄像机的高度(注意摄像机的高度是一个变量),这个很容易计算。下面给出生成摄像机运动区域的参考:
//计算并生成透视摄像机的运动区域
public void GenZone()
{
Camera = Camera.main; //计算从地图中心到边缘的向量
var toedge = WidthHeight * UnitLength * .5f;
//左后
var lb = CenterPoint - toedge;
//右前
var rf = CenterPoint + toedge;
//墙高
var wh = WallHeight; zone = new GameObject("CameraZone"); var box = zone.AddComponent<BoxCollider>();
var cvc = GetComponent<CinemachineVirtualCamera>();
var cft = cvc.GetCinemachineComponent<CinemachineFramingTransposer>(); var cvcs = cvc.m_Lens;
//摄像机跟踪目标的高度
var followy = cvc.m_Follow.position.y;
//跟踪距离
var distance = cft.m_CameraDistance;
//屏幕高对应的Fov的一半(真实Fov)
var hfov = cvcs.FieldOfView * .5f * Mathf.Deg2Rad;
//摄像机视口宽高比
var aspect = Camera.aspect;
//摄像机轴向旋转值
var rotation = Camera.transform.eulerAngles.x * Mathf.Deg2Rad;
var rightangel = * Mathf.Deg2Rad;
//屏幕宽对应的Fov的一半(转化后的Fov)
var whfov = GetHorizontalFovHalf(hfov, aspect); //摄像机当前高度
var height = Mathf.Sin(rotation) * distance + followy; //计算左右偏移(对称)
var lrangel = rightangel - whfov;
var widthh = GetSizeOffse(lrangel, distance, wh, followy);
var left = lb.x + widthh;
var right = rf.x - widthh;
var sizex = Mathf.Abs(left - right); //计算前后偏移(带旋转值,非对称)
var fangel = rotation - hfov;
var front = rf.y - GetSizeOffse(fangel, distance, wh, followy); var bangel = rotation + hfov;
var back = lb.y - GetSizeOffse(bangel, distance, wh, followy); var sizez = Mathf.Abs(front - back); //设置摄像机运动范围的大小,因为在XZ平面上,盒子的高度可以为一个常量
box.size = new Vector3(sizex, , sizez);
zone.transform.position = new Vector3((left + right) * .5f, height, (front + back) * .5f); CC.m_BoundingVolume = zone.GetComponent<BoxCollider>();
}
生成该盒子后,只需要将它赋值给CinemachineConfiner的BoundingVolume属性即可:
为了更方便的进行测试和调试,可以写一个Editor脚本在编辑器模式下生成:
using UnityEditor;
using UnityEngine; [CustomEditor(typeof(CameraZoneCtrl))]
public class CameraZoneEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
CameraZoneCtrl ctrl = (CameraZoneCtrl)target;
if (GUILayout.Button("创建摄像机范围"))
{
ctrl.GenZone();
}
}
}
Unity 基于Cinemachine计算透视摄像机在地图中的移动范围的更多相关文章
- Unity 通过NGUI 完成单摄像机 制作地图
本次思想主要是通过 Ngui的Scroll View 主要是UIPanel的Clipping属性的Alipha Clip 调节窗口大小,遮蔽地图试地图实现在屏幕的部分显示.此方法的好处是不用担心sha ...
- unity 基于scrollRect实现翻页显示
unity 基于scrollRect实现翻页显示,并定为到某一页,而不是某一页的中间方法(第二个脚本采用实际位置计算,并在update里实现平滑过渡): 组场景时,经常需要获取鼠标(或者点击)开始结束 ...
- Unity 3d 实现物体跟随摄像机视野运动
https://blog.csdn.net/qq_31411825/article/details/61623857 Unity 3d 实现物体跟随摄像机视野运动Created by miccall ...
- 基于ArcEngine与C#的鹰眼地图实现
鹰眼图是对全局地图的一种概略表达,具有与全局地图的空间参考和空间范围.为了更好起到空间提示和导航作用,有些还具备全局地图中重要地理要素,如主要河流.道路等的概略表达.通过两个axMapControl控 ...
- Spark 介绍(基于内存计算的大数据并行计算框架)
Spark 介绍(基于内存计算的大数据并行计算框架) Hadoop与Spark 行业广泛使用Hadoop来分析他们的数据集.原因是Hadoop框架基于一个简单的编程模型(MapReduce),它支持 ...
- 跨平台移动开发_PhoneGap 使用Geolocation基于所在地理位置坐标调用百度地图API
使用Geolocation基于所在地理位置坐标调用百度地图API 效果图 示例代码 <!DOCTYPE html> <html> <head> <title& ...
- 10-THREE.JS perspective透视摄像机和orthographic正交摄像机区别
<!DOCTYPE html> <html> <head> <title></title> <script src="htt ...
- 一元建站-基于函数计算 + wordpress 构建 serverless 网站
前言 本文旨在通过 快速部署一个 wordpress 网站到阿里云函数计算平台 这个示例来展示 serverless web 新的开发模式, 包括 FUN 工具一键初始化 NAS, 同步网站到 NAS ...
- 基于函数计算 + TensorFlow 的 Serverless AI 推理
前言概述 本文介绍了使用函数计算部署深度学习 AI 推理的最佳实践, 其中包括使用 FUN 工具一键部署安装第三方依赖.一键部署.本地调试以及压测评估, 全方位展现函数计算的开发敏捷特性.自动弹性伸缩 ...
随机推荐
- Beta版是什么意思
外部测试版的意思. 软件会出现三种版本 1.alpha内部测试版本,极不稳定,一般也不会出现的公众视线,仅供内部测试人员测试用. 2.beta公共测试版,就是对外发布软件的测试版,收集公众的意见和建议 ...
- Spring Security 学习笔记-授权控制过滤器
FilterSecurityInterceptor 是比较核心的过滤器,主要负责授权工作.SecurityMetadataSource 需要安全授权的元数据资源 AuthenticationMana ...
- node第一个参数必须是err
Node.js 约定回调函数第一个参数必须是错误对象err: 问题:Node.js约定回调函数第一个参数必须是错误对象err,如果没有错误该参数就是null 原因:异步执行分成两段,在两段之间抛出异常 ...
- vue-learning:33 - component - 内置组件 - 过渡组件transition
vue内置过渡组件transition 目录 什么是过渡 基本过渡或动画实现的语法 css过渡动画:transition / animation js过渡:特定事件钩子函数 各种情形下的过渡实现,使用 ...
- jq 技巧汇总
1,jQuery方法$()实际上是拥有两个参数的 $('li','.firstEl').onclick(function(){.......}) 这里,第二个参数用来限制第一个参数给定的查找结果 ...
- html5中的audio和video属性和事件汇总
<audio> 标签属性: src:音乐的URL preload:预加载 autoplay:自动播放 loop:循环播放 controls:浏览器自带的控制 <video> 标 ...
- 【Docker】镜像基本操作
1.镜像获取/生成 docker pull <image_name:tag> docker pull mysql:5.6 docker build -t <镜像名> <D ...
- 北京联通盒子-数码视讯Q7-破解
准备: 1.数码视讯Q7盒子 2.电焊笔 细电线4跟不同色(可以直接用废旧USB的线) 3.TTL 转 USB线 ,型号: CH340g(自行淘宝购买) 4.安装TTL线的驱动到电脑上(找淘宝商家要 ...
- Python自定义函数的参数
在Python中自定义的函数可以有三类不同的参数 formal parameters positional arguments Keyword Arguments When a final forma ...
- bug(一)环境问题
1.项目开发完成,部署到服务器进行测试,遇到一个问题: 同样的接口,同样的数据库表,同样的功能,得到不同的查询结果 具体如下图: 可以看到一个是有值的,一个是无值的,断点调试会发现所以的状态都是对的, ...