1 前言

场景缩放、平移、旋转有两种实现方案,一种是对场景中所有物体进行同步变换,另一种方案是对相机的位置和姿态进行变换。

对于方案一,如果所有物体都在同一个根对象下(其子对象或孙子对象),那么只需要对根对象施加变换就可以实现场景变换;如果有多个根对象,那就需要对所有根对象施加变换。该方案实现简单,但是会破坏场景中对象的尺寸、位置、姿态,不符合现实世界的规则。如:对场景施加缩放变换后,又新增了一个对象,但是该对象不是放在同一个根目录下,就会让用户感觉新增对象的尺寸超出意外;如果有多个根对象,就会存在多个参考系(每个根对象一个参考系),增加场景中对象的控制难度。

对于方案二,通过变换相机的位置和姿态,让用户感觉场景中所有对象在同步缩放、平移、旋转。该方案实现较困难,但是不会破环场景中对象的尺寸、位置、姿态,更贴近真实世界的规则,也不需要将所有对象都放在同一个根对象下。

方案二明显优于方案一,本文将详细介绍其原理和实现。原理如下:

1)场景缩放原理

利用相机的透视原理(详见→透视变换原理),即相机拍摄到的图片呈现近大远小的效果,将相机靠近和远离场景,从而实现放大和缩小场景的效果。

2)场景平移原理

相机成像是在近平面上,如果扩展近平面的范围,相机拍摄的范围也就越大,将近平面平移到相机位置上,记为平面 S,将相机在 S 平面上平移,就会实现场景平移效果。

3)场景旋转原理

在 Unity3D Scene 窗口,通过按 Alt 键 + 鼠标拖拽,可以旋转场景。场景旋转包含两种情况,鼠标沿水平方向拖拽、鼠标沿竖直方向拖拽。

当鼠标沿水平方向拖拽时,笔者通过多次实验观察,发现如下规律:当场景缩放到某个值时,旋转场景时,屏幕中心位置的物体(在相机的正前方)在场景旋转过程中始终处在屏幕中心,并且旋转轴的方向始终是 Y 轴方向。因此可以得出结论:旋转中心在相机正前方(forward),旋转轴沿 Y 轴方向。

旋转中心的 y 值最好与地图的 y 值相等,如果场景中没有地图,可以取旋转中心为:cam.position + cam.forward * (nearPlan + 1 / nearPlan),当然,用户也可以取其他值。已知旋转中心的 y 值,可以按照以下公式推导出 x、z 值:

当鼠标沿竖直方向拖拽时,旋转中心在相机位置,旋转轴沿相机的左边(-right)。

本文代码资源见→缩放、平移、旋转场景。

2 代码实现

SceneController.cs

using UnityEngine;public class SceneController : MonoBehaviour {private Texture2D[] cursorTextures; // 鼠标样式: 箭头、小手、眼睛private Transform cam; // 相机private float nearPlan; // 近平面private Vector3 preMousePos; // 上一帧的鼠标坐标private int cursorStatus = 0; // 鼠标样式状态private bool isDraging = false; // 是否在拖拽中private void Awake() {string[] mouseIconPath = new string[]{"MouseIcon/0_arrow", "MouseIcon/1_hand", "MouseIcon/2_eye"};cursorTextures = new Texture2D[mouseIconPath.Length];for(int i = 0; i < mouseIconPath.Length; i++) {cursorTextures[i] = Resources.Load<Texture2D>(mouseIconPath[i]);}cam = Camera.main.transform;Vector3 angle = cam.eulerAngles;cam.eulerAngles = new Vector3(angle.x, angle.y, 0); // 使camp.right指向水平方向nearPlan = Camera.main.nearClipPlane;}private void Update() {cursorStatus = GetCursorStatus();// 更新鼠标样式, 第二个参数表示鼠标点击位置在图标中的位置, zero表示左上角Cursor.SetCursor(cursorTextures[cursorStatus], Vector2.zero, CursorMode.Auto);UpdateScene(); // 更新场景(Ctrl+Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)}private int GetCursorStatus() { // 获取鼠标状态(0: 箭头, 1: 小手, 2: 眼睛)if (isDraging) {return cursorStatus;}if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.LeftControl)) {return 1;}if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.LeftAlt)) {return 2;}return 0;}private void UpdateScene() { // 更新场景(Ctrl+Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)float scroll = Input.GetAxis("Mouse ScrollWheel");if (!isDraging && cursorStatus == 1 && Mathf.Abs(scroll) > 0) { // 缩放场景ScaleScene(scroll);} else if (Input.GetMouseButtonDown(0)) {preMousePos = Input.mousePosition;isDraging = true;} else if (Input.GetMouseButtonUp(0)) {isDraging = false;} else if (Input.GetMouseButton(0)) {Vector3 offset = Input.mousePosition - preMousePos;if (cursorStatus == 1) { // 移动场景MoveScene(offset);} else if (cursorStatus == 2) { // 旋转场景RotateScene(offset);}preMousePos = Input.mousePosition;}}private void ScaleScene(float scroll) { // 缩放场景cam.position += cam.forward * scroll;}private void MoveScene(Vector3 offset) { // 平移场景cam.position -= (cam.right * offset.x / 100 + cam.up * offset.y / 100);}private void RotateScene(Vector3 offset) { // 旋转场景Vector3 rotateCenter = GetRotateCenter(0);cam.RotateAround(rotateCenter, Vector3.up, offset.x / 3); // 水平拖拽分量cam.LookAt(rotateCenter);cam.RotateAround(rotateCenter, -cam.right, offset.y / 5); // 竖直拖拽分量}private Vector3 GetRotateCenter(float planeY) { // 获取旋转中心if (Mathf.Abs(cam.forward.y) < float.Epsilon || Mathf.Abs(cam.position.y) < float.Epsilon) {return cam.position + cam.forward * (nearPlan + 1 / nearPlan);}float t = (planeY - cam.position.y) / cam.forward.y;float x = cam.position.x + t * cam.forward.x;float z = cam.position.z + t * cam.forward.z;return new Vector3(x, planeY, z);}
}

说明:SceneController 脚本组件挂在相机下,鼠标图标如下,需要放在 Resouses/MouseIcon 目录下, 并且需要在 Inspector 窗口将其 Texture Type 属性调整为 Cursor。

3 运行效果

通过 Ctrl+Scroll 缩放场景,Ctrl+Drag 平移场景,Alt+Drag 旋转场景 ,效果如下:

4 优化

第 2 节中场景变换存在以下问题,本节将对这些问题进行优化。

  • 竖直方向平移场景时,会抬高或降低相机高度;
  • 竖直方向旋转场景时,如果相机垂直朝向地面,就会出现窗口急速晃动问题,因为旋转中心出现了跳变。

针对问题一,将相机的上方向量(camera.up)投影到水平面上,再用投影向量计算相机前后平移的偏移量。

针对问题二,使用一个全局变量实时保存并更新旋转中心的位置,并通过相机周转和自传(两者旋转角度和方向相等)实现水平和竖直方向旋转场景,避免使用 LookAt,因为相机不一定一直朝向旋转中心(如:相机焦点不在地图里)。

SceneController.cs

using UnityEngine;public class SceneController : MonoBehaviour {private const float MAX_HALF_EDGE_X = 5f; // 地图x轴方向半边长private const float MAX_HALF_EDGE_Z = 5f; // 地图z轴方向半边长private Texture2D[] cursorTextures; // 鼠标样式: 箭头、小手、眼睛private Transform cam; // 相机private float planeY = 0f; // 地面高度private Vector3 rotateCenter; // 旋转中心private Vector3 focusCenter; // 相机在地面上的焦点中心private bool isFocusInMap; // 相机焦点是否在地图里private Vector3 preMousePos; // 上一帧的鼠标坐标private int cursorStatus = 0; // 鼠标样式状态private bool isDraging = false; // 是否在拖拽中private void Awake() {string[] mouseIconPath = new string[] { "MouseIcon/0_arrow", "MouseIcon/1_hand", "MouseIcon/2_eye" };cursorTextures = new Texture2D[mouseIconPath.Length];for (int i = 0; i < mouseIconPath.Length; i++) {cursorTextures[i] = Resources.Load<Texture2D>(mouseIconPath[i]);}cam = Camera.main.transform;Vector3 angle = cam.eulerAngles;cam.eulerAngles = new Vector3(angle.x, angle.y, 0); // 使camp.right指向水平方向rotateCenter = new Vector3(0, planeY, 0);focusCenter = new Vector3(0, planeY, 0);}private void Update() {cursorStatus = GetCursorStatus();// 更新鼠标样式, 第二个参数表示鼠标点击位置在图标中的位置, zero表示左上角Cursor.SetCursor(cursorTextures[cursorStatus], Vector2.zero, CursorMode.Auto);UpdateScene(); // 更新场景(Ctrl+Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)}private int GetCursorStatus() { // 获取鼠标状态(0: 箭头, 1: 小手, 2: 眼睛)if (isDraging){return cursorStatus;}if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.LeftControl)){return 1;}if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.LeftAlt)){return 2;}return 0;}private void UpdateScene() { // 更新场景(Ctrl+Scroll: 缩放场景, Ctrl+Drag: 平移场景, Alt+Drag: 旋转场景)float scroll = Input.GetAxis("Mouse ScrollWheel");if (!isDraging && cursorStatus == 1 && Mathf.Abs(scroll) > 0) { // 缩放场景ScaleScene(scroll);}else if (Input.GetMouseButtonDown(0)) {preMousePos = Input.mousePosition;UpdateRotateCenter();isDraging = true;}else if (Input.GetMouseButtonUp(0)) {isDraging = false;}else if (Input.GetMouseButton(0)) {Vector3 offset = Input.mousePosition - preMousePos;if (cursorStatus == 1) { // 移动场景MoveScene(offset);}else if (cursorStatus == 2) { // 旋转场景RotateScene(offset);}preMousePos = Input.mousePosition;}}private void ScaleScene(float scroll) { // 缩放场景cam.position += cam.forward * scroll;}private void MoveScene(Vector3 offset) { // 平移场景Vector3 horVec = Vector3.ProjectOnPlane(cam.right, Vector3.up).normalized;Vector3 verVec = Vector3.ProjectOnPlane(cam.up, Vector3.up).normalized;cam.position -= (horVec * offset.x / 100 + verVec * offset.y / 100);}private void RotateScene(Vector3 offset) { // 旋转场景float hor = offset.x / 3;float ver = -offset.y / 5;cam.RotateAround(rotateCenter, Vector3.up, hor); // 相机绕旋转中心水平旋转cam.RotateAround(rotateCenter, cam.right, ver); // 相机绕旋转中心竖直旋转// 由于transform.RotateAround方法中已经进行了物体姿态调整, 因此以下语句是多余的// cam.RotateAround(cam.position, Vector3.up, hor); // 相机自转, 使其朝向旋转中心// cam.RotateAround(cam.position, cam.right, ver); // 相机自转, 使其朝向旋转中心}private void UpdateRotateCenter() { // 更新旋转中心UpdateFocusStatus();if (!isFocusInMap) {return;}rotateCenter.x = Mathf.Clamp(focusCenter.x, -MAX_HALF_EDGE_X, MAX_HALF_EDGE_X);rotateCenter.z = Mathf.Clamp(focusCenter.z, -MAX_HALF_EDGE_Z, MAX_HALF_EDGE_Z);}private void UpdateFocusStatus() { // 更新焦点状态isFocusInMap = true;Vector3 vec1 = new Vector3(0, planeY - cam.position.y, 0);Vector3 vec2 = cam.forward;if (Mathf.Abs(vec1.y) < float.Epsilon || Mathf.Abs(vec2.y) < float.Epsilon) {isFocusInMap = false;return;}float angle = Vector3.Angle(vec1, vec2);if (angle >= 90) { // 相机在地面以上并且朝天, 或在地面以下并且朝下isFocusInMap = false;return;}float t = (planeY - cam.position.y) / vec2.y;focusCenter.x = cam.position.x + t * vec2.x;focusCenter.z = cam.position.z + t * vec2.z;if (Mathf.Abs(focusCenter.x) > MAX_HALF_EDGE_X || Mathf.Abs(focusCenter.z) > MAX_HALF_EDGE_Z) { // 相机焦点不在地图区域内isFocusInMap = false;}}
}

【Unity3D】缩放、平移、旋转场景相关推荐

  1. 20P46 Premiere预设800种干扰缩放平移旋转分割拉伸全景透视扭曲炫光视频无缝转场

    20P46 Premiere预设800种干扰缩放平移旋转分割拉伸全景透视扭曲炫光视频无缝转场 模版信息: 适用软件:Premiere Pro CC 2015.3 或更高版本 使用插件:无需外置插件 分 ...

  2. Unity3D使用鼠标旋转缩放平移视角

    Unity使用鼠标旋转缩放平移视角 用代码在Game界面完美实现Scene界面的操作方法. 使用方法:把脚本挂在相机上,把跟踪的target拖到脚本上. 视角跟踪的是一个空物体,当然如果你是做RPG游 ...

  3. OSG仿真案例(5)——模型的平移、缩放、旋转

    这个程序显示的是一头牛,牛的身子朝向屏幕. image.png 旋转后,让牛头朝向屏幕外.如下图所示. image.png OSG中通过旋转模型来改变节点的姿态,使用类 MatrixTransform ...

  4. OpenCasCade学习笔记(三):加载显示STEP格式图片,并实现平移、缩放和旋转操作

    OpenCasCade学习笔记(三):加载显示STEP格式图片,并实现平移.缩放和旋转操作 C3DWidget.h #pragma once#include <QtWidgets/QApplic ...

  5. 基于python的图像变换(翻转、平移、缩放、旋转、仿射和透视变换)

    图像变换 翻转 平移 旋转和缩放 仿射和透视变换矩阵 仿射 透视变换 翻转 import cv2 from matplotlib import pyplot as plt image = cv2.im ...

  6. 图像算法二:【图像几何变换】平移、镜像、转置、缩放、旋转、插值

    作为一个强大的科学计算软件,MATLAB广泛运用于较多领域,以其简单的编程风格著称.这篇文章便通过matlab语言来讲述如何进行图像的各种几何变换. 图像几何变换又称为图像空间变换,它是将一幅图像中的 ...

  7. opencv 图像平移、缩放、旋转、翻转 图像仿射变换

    图像几何变换 图像几何变换从原理上看主要包括两种:基于2x3矩阵的仿射变换(平移.缩放.旋转.翻转).基于3x3矩阵的透视变换. 图像平移 opencv实现图像平移 实现图像平移,我们需要定义下面这样 ...

  8. Android单点触控技术,对图片进行平移,缩放,旋转操作

    转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/42833893),请尊重他人的辛勤劳动成果,谢谢! 相信大家 ...

  9. 图像增广 || 通过仿射变换实现图像的平移、缩放、旋转、翻转、错切,及MATLAB实现与分析

    1.仿射变换理论 仿射变换(Affine Transformation或 Affine Map)是一种二维坐标(x,y)到二维坐标(u,v)之间的线性变换,它保持了二维图形的"平直性&quo ...

最新文章

  1. 另类的 高版本数据库 转换到 低版本数据库
  2. 一致性 Hash 算法学习(分布式或均衡算法)
  3. C# Regex类详解
  4. 【转帖】配置管理计划(CM Plan)
  5. aws sqs_JMS和AWS SQS的更多高级内容
  6. linux安装python_Python - 爱豆
  7. 网游Server端开发基础
  8. react classname多个_React全家桶简介
  9. 使用google analytics(分析)监测百度竞价关键词效果(网址构建工具)
  10. [Excel函数]--intercept函数
  11. windows 10 Docker Desktop TeamTalk 安装笔记
  12. 曾维沛全网营销推广如何做?微商精准引流,让客户主动找上门
  13. SSL1284压岁钱
  14. 台式计算机的打印机端口,台式电脑怎么连接网络打印机
  15. Linux 下 va_start、va_end 学习及使用
  16. MySQL必知必会的知识点
  17. 蓝牙解码格式哪个最好_让有源音箱飞起来,让汽车音响硬起来,飞傲BTR5蓝牙HiFi解码品评...
  18. C语言再学习 -- 常用快捷键
  19. 2-10 CAD基础 偏移(offset)
  20. 递归算法到非递归算法的转换

热门文章

  1. 基于摄像直读原理的 OCR智能采集仪表拍照采集终端
  2. android 调用锁屏,Android反射调用goToSleep实现一键锁屏、亮屏
  3. 5G、物联网和人工智能:2021年的艺术和技术工作
  4. HTML+CSS基础知识3
  5. 三叶草生物上市首日破发:累计亏损超过18亿,梁朋父子均为美国籍
  6. 论药品包装机械的概念设计 Comment on medicines and chemical reagents package machinery conceptual design
  7. vmware vconverter 从物理机迁移系统到Esxi
  8. Matlab利用M_map和mapshow绘制网格地图
  9. 展会预告 | 我们相约宁波——2019世界数字经济大会
  10. notepad 记事本的问题