【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

news2024/11/16 18:36:58

转载请注明出处: https://blog.csdn.net/weixin_44013533/article/details/138909256

作者:CSDN@|Ringleader|

目录

    • Quaternion API 速览
    • FromToRotation在Transform中的应用
    • LookRotation 中upwards取Vector3.up和 transform.up的区别
    • 旋转时如何保持Y轴不变,但朝向目标旋转呢?
    • 不同旋转方法案例
    • lerp与LerpUnclamped区别
    • Quaternion.Slerp 、Quaternion.RotateTowards区别
    • Lerp、Slerp比较
    • Quaternion * operator
    • 总结

主要参考:

  1. Unity手册 & Quaternion API
  2. Unity3D - 详解Quaternion类(二)
  3. 【Unity编程】Unity中关于四元数的API详解

Quaternion API 速览

创建旋转:

  • FromToRotation 创建一个从 fromDirection 旋转到 toDirection 的旋转。
  • LookRotation 使用指定的 forward 和 upwards 方向创建旋转。
  • AngleAxis 创建一个围绕 axis 旋转 angle 度的旋转。
  • Angle 返回两个旋转 a 和 b 之间的角度(以度为单位)。(180°以内)

操作旋转:

  • Lerp 在 a 和 b 之间插入 t,然后对结果进行标准化处理。参数 t 被限制在 [0, 1] 范围内。
  • Slerp 在四元数 a 与 b 之间按比率 t 进行球形插值。参数 t 限制在范围 [0, 1] 内。
  • RotateTowards 将旋转 from 向 to 旋转。

Transform 类还提供了一些方法可用于处理 Quaternion 旋转:Transform.Rotate & Transform.RotateAround

FromToRotation在Transform中的应用

Transform中有很多Quaternion的应用,比如获取对象的xyz轴向量在世界坐标系下的表示,就用到了FromToRotation:

   ///<para>The red axis of the transform in world space.</para>
    public Vector3 right
    {
      get => this.rotation * Vector3.right;
      set => this.rotation = Quaternion.FromToRotation(Vector3.right, value);
    }
    ///<para>The green axis of the transform in world space.</para>
    public Vector3 up
    {
      get => this.rotation * Vector3.up;
      set => this.rotation = Quaternion.FromToRotation(Vector3.up, value);
    }

    ///<para>Returns a normalized vector representing the blue axis of the transform in world space.
    public Vector3 forward
    {
      get => this.rotation * Vector3.forward;
      set => this.rotation = Quaternion.LookRotation(value);
    }

LookRotation 中upwards取Vector3.up和 transform.up的区别

public static Quaternion LookRotation (Vector3 forward, Vector3 upwards= Vector3.up);

因为LookRotation 会使对象Z轴与forward参数向量对齐,X 轴与Vector3.Cross(upwards,forward)这个叉乘结果对齐,Y 轴与 Z 和 X 的叉乘(Vector3.Cross(transform.forward,transform.right) )对齐。(注意unity左手坐标系,叉乘方向)

所以会看到当upwards取世界空间的向上和模型空间的向上是有区别的。

或者说,upwards取模型空间的向上时对象可以绕自身z轴旋转,对象状态并不固定。
在这里插入图片描述

public class LookRotationTest : MonoBehaviour
{
    public Transform obt_forward;
    public Transform obt_worldUp;
    public Transform obt_selfUp;
    
    public bool showCrossResult = false;// 验证叉乘方向用

    private void Update()
    {
        // upwards取世界空间的向上
        LookForward(obt_worldUp, obt_forward.position - obt_worldUp.position, Vector3.up);
        // upwards取模型空间的向上
        LookForward(obt_selfUp,obt_forward.position - obt_selfUp.position, obt_selfUp.up);
    }

    private void LookForward(Transform obt, Vector3 forward,Vector3 upwards)
    {
        obt.rotation = Quaternion.LookRotation(forward, upwards);
        var position = obt.position;
        Debug.DrawLine(obt_forward.position, position);

        Debug.DrawLine(position, position + obt.right * 5, Color.red);
        Debug.DrawLine(position, position + obt.up * 5, Color.green);
        Debug.DrawLine(position, position + obt.forward * 5, Color.blue);
        
        // 验证叉乘方向, 叉乘结果与LookFoward结果一致
        if (showCrossResult)
        {
            var X_cross = Vector3.Cross(upwards,forward);
            var Y_cross = Vector3.Cross(obt.forward,obt.right);
        
            Debug.DrawLine(position, position + X_cross * 10, Color.cyan);
            Debug.DrawLine(position, position + Y_cross * 10, Color.yellow);
        }
    }
}

旋转时如何保持Y轴不变,但朝向目标旋转呢?

使用Vector3.ProjectOnPlane将目标方向投影到xz平面。
在这里插入图片描述

Lookat, FromToRotation and LookRotation?

不同旋转方法案例

下面代码比较了transform.forward 、Transform.LookAt、Quaternion.FromToRotation、LookRotation、Quaternion.RotateTowards、Vector3.ProjectOnPlane等不同方法

public class CompareImmediateAndStep : MonoBehaviour
{
    public Transform turret;
    public Transform enemy;
    private string str;

    private void Update()
    {
        var targetTowards = enemy.position - turret.position;
        // 立刻跟随敌人
        if (Input.GetKey(KeyCode.Alpha1))
        {
            turret.forward = targetTowards;
            str = "使用turret.forward = targetTowards;";
        }

        // 上面本质也是使用FromToRotation方法
        if (Input.GetKey(KeyCode.Alpha2))
        {
            turret.rotation = Quaternion.FromToRotation(Vector3.forward, targetTowards);
            str = "使用turret.rotation = Quaternion.FromToRotation(Vector3.forward, targetTowards);";
        }

        if (Input.GetKey(KeyCode.Alpha3))
        {
            //当FromToRotation的fromDirection参数是forward轴时,可以用LookRotation
            turret.rotation = Quaternion.LookRotation(targetTowards);
            str = "turret.rotation = Quaternion.LookRotation(targetTowards);";
        }
        if (Input.GetKey(KeyCode.Alpha4))
        {
            turret.LookAt(enemy.transform);
            str = "turret.LookAt(enemy.transform);";
        }

        // 插值方式,加入旋转速度
        if (Input.GetKey(KeyCode.Alpha5))
        {
            var fromToRotation = Quaternion.LookRotation(targetTowards);
            turret.rotation = Quaternion.RotateTowards(turret.rotation, fromToRotation, 45 * Time.deltaTime);

            str = "使用worldUp的Quaternion.RotateTowards";
        }
        // 保持对象Y轴朝向不变,将targetTowards进行投影
        if (Input.GetKey(KeyCode.Alpha6))
        {
            var fromToRotation = Quaternion.LookRotation(Vector3.ProjectOnPlane(targetTowards,Vector3.up),Vector3.up);
            turret.rotation = Quaternion.RotateTowards(turret.rotation, fromToRotation, 45 * Time.deltaTime);
            
            str = "保持Y轴朝向不变,将targetTowards进行投影";
        }

        DrawAxis(turret);
        DrawAxis(enemy);
    }

    private void DrawAxis(Transform obt)
    {
        DrawAxis(obt, Color.red, Color.green, Color.blue, 5);
    }

    private void DrawAxis(Transform obt, Color xc, Color yc, Color zc, float length)
    {
        if (obt.gameObject.activeInHierarchy)
        {
            var position = obt.position;
            Debug.DrawLine(position, position + obt.right * length, xc);
            Debug.DrawLine(position, position + obt.up * length, yc);
            Debug.DrawLine(position, position + obt.forward * length, zc);
        }
    }
    
    private Rect rect = new Rect(100, 100, 600, 50);

    private void OnGUI()
    {
        DrawLabel(rect, str);
    }

    private static void DrawLabel(Rect rect1, String str)
    {
        var style = new GUIStyle
        {
            fontSize = 38,
            wordWrap = true
        };
        GUI.Label(rect1, str, style);
    }
}

lerp与LerpUnclamped区别

区别就是Unclamp不会钳值,还能取负值。
在这里插入图片描述

Vector3: Lerp vs LerpUnclamped

Quaternion.Slerp 、Quaternion.RotateTowards区别

RotateTowards本质也是使用了SlerpUnclamped方法,但其旋转速度恒定,不会因target变化而改变。

public static Quaternion RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta)
    {
      float num = Quaternion.Angle(from, to);
      return (double) num == 0.0 ? to : Quaternion.SlerpUnclamped(from, to, Mathf.Min(1f, maxDegreesDelta / num));
    }

这篇帖子 Use Quaternion.RotateTowards() instead of Quaternion.Slerp() 提到了:

如果旋转操作的from和to都已知,使用 Quaternion.Slerp() - 例如打开一扇门或箱子盖。
如果要以恒定角速度转向某物,则使用 Quaternion.RotateTowards() - 例如,在塔防类游戏中要转向塔的炮塔。

Lerp、Slerp比较

Quaternion的插值分析及总结
Lerp求得的是四元数在圆上的弦上的等分,而Slerp求得的是四元数载圆上的圆弧的等分
在这里插入图片描述

进行代码验证:

public class SlerpTest : MonoBehaviour
{
    public Transform obtLerp;
    public Transform obtSlerp;
    public Transform towardsObt;
    public bool towardsNotChanged;
    private Quaternion currentLookRotation,lastLookRotation;
    private Quaternion initRotationLerp, initRotationSlerp;
    
    public float speed = 0.1f;
    float total = 0.0f;

    private void Start()
    {
        lastLookRotation = Quaternion.LookRotation(towardsObt.position-obtLerp.position);
        initRotationLerp = obtLerp.rotation;
        initRotationSlerp = obtSlerp.rotation;
    }

    void Update()
    {
        CompareSlerpAndLerp();
    }

    private void CompareSlerpAndLerp()
    {
        currentLookRotation = Quaternion.LookRotation(towardsObt.position - obtLerp.position);
        towardsNotChanged = currentLookRotation == lastLookRotation;

        // 改变朝向时,重置初始位置、重置total
        if (!towardsNotChanged)
        {
            lastLookRotation = currentLookRotation;
            // 重置执行Lerp、Slerp时的初始旋转
            initRotationLerp = obtLerp.rotation;
            initRotationSlerp = obtSlerp.rotation;
            total = 0;
            return;
        }

        lastLookRotation = currentLookRotation;
        total += Time.deltaTime * speed;
        if (total >= 1.0f)
            total = 1.0f;

        obtLerp.rotation = Quaternion.Lerp(initRotationLerp, currentLookRotation, total);
        obtSlerp.rotation = Quaternion.Slerp(initRotationSlerp, currentLookRotation, total);
        DrawAxis(obtLerp);
        DrawAxis(obtSlerp, Color.cyan, Color.magenta, Color.yellow, 10);
    }

    private void DrawAxis(Transform obt)
    {
        DrawAxis(obt, Color.red, Color.green, Color.blue, 5);
    }
    private void DrawAxis(Transform obt,Color xc,Color yc,Color zc,float length)
    {
        if (obt.gameObject.activeInHierarchy)
        {
            var position = obt.position;
            Debug.DrawLine(position, position + obt.right * length, xc);
            Debug.DrawLine(position, position + obt.up * length, yc);
            Debug.DrawLine(position, position + obt.forward * length, zc);
        }
    }
    private Rect rect = new Rect(100, 100, 600, 50);
    private void OnGUI()
    {
        DrawLabel(rect,"Total:"+total);
    }

    private void DrawLabel(Rect rect1, String str)
    {
        var style = new GUIStyle
        {
            fontSize = 38,
            wordWrap = true
        };
        GUI.Label(rect1, str, style);
    }
}

如下图,RGB颜色的轴是Lerp方法,青紫黄轴是Slerp方法。可以看到Lerp相对Slerp来说先慢,中间快,最后慢。和上面的弦等分和弧等分理论一致。
请添加图片描述

Quaternion * operator

在这里插入图片描述
关于Quaternion 左乘右乘和坐标系的关系看我这篇:【Unity学习笔记】第十六 World space、Parent space和Self space及Quaternion左乘右乘辨析

总结

本文主要辨析Quaternion中Lerp、Slerp、RotateTowards等方法,并进行代码验证。至此,对Quaternion核心方法的理解已比较清晰,但其中的数学原理比如四元数、和欧拉角的关系、万向锁、逆和共轭等问题还是有待进一步学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1684489.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软管的高速非接触外径测量方案!单双轴测径仪多种类型!

一、传统测量方式的局限 在软管外径的测量领域&#xff0c;传统方式往往面临多重挑战&#xff1a; 1、挤压变形&#xff1a;传统的测量方式可能导致软管因挤压而变形&#xff0c;进而影响测量数据的准确性。 2、人为误差&#xff1a;测量结果常因人为因素而有所差异&#xff0c…

文本转语音软件-TTSMaker

一、TTSMaker介绍 TTSMaker&#xff08;马克配音&#xff09;是一款免费的文本转语音工具&#xff0c;提供语音合成服务&#xff0c;支持多种语言&#xff0c;包括中文、英语、日语、韩语、法语、德语、西班牙语、阿拉伯语等50多种语言&#xff0c;以及超过300种语音风格。 可…

春秋云境CVE-2023-50564

简介 Pluck-CMS v4.7.18 中的 /inc/modules_install.php 组件&#xff0c;攻击者可以通过上传一个精心制作的 ZIP 文件来执行任意代码。 正文 1.进入靶场 2.弱口令进入 admin123 3.找上传点 4.将木马打包&#xff0c;上传一句话木马 5.蚁剑连接6.得到flag

前端 CSS 经典:filter 滤镜

前言&#xff1a;什么叫滤镜呢&#xff0c;就是把元素里的像素点通过一套算法转换成新的像素点&#xff0c;这就叫滤镜。而算法有 drop-shadow、blur、contrast、grayscale、hue-rotate 等。我们可以通过这些算法实现一些常见的 css 样式。 1. drop-shadow 图片阴影 可以用来…

程序包org.springframework.boot不存在

springBoot项目启动报错 程序包org.springframework.boot不存在 1、检查依赖 首先检查pom文件判断依赖是否存在 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.4.5…

基于微信小程序的校园捐赠系统的设计与实现

校园捐赠系统是一种便捷的平台&#xff0c;为校园内的各种慈善活动提供支持和便利。通过该系统&#xff0c;学生、教职员工和校友可以方便地进行捐赠&#xff0c;并了解到相关的项目信息和捐助情况。本文将介绍一个基于Java后端和MySQL数据库的校园捐赠系统的设计与实现。 技术…

OFDM 802.11a的FPGA实现(二十一)发射主控模块MCU(含代码)

目录 1.前言 2.主控逻辑 3.Matlab 4.verilog 5.ModelSim 6.ModelSim仿真结构与Matlab自动化对比 完整工程链接&#xff08;含verilog和Matlab代码&#xff09;&#xff1a; https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzkxNjM0NDk2Nw&actiongetalbum&album…

MySql:多表设计-关联查询

目录 多表设计 代码 运行 数据库设计范式 设计三范式 1、第一范式&#xff1a; 2、第二范式&#xff1a; 3、第三范式&#xff1a; 多表设计_关联查询 外键 外键约束 代码 运行 注意&#xff1a; 应用 代码 运行 代码 运行 关联查询 含义&#xff1a; …

<项目> 云备份

目录 一、简单认识 二、实现目标 三、服务端程序负责功能及功能模块划分 四、客户端程序负责功能及功能模块划分 五、环境搭建 &#xff08;一&#xff09;gcc 7.3 &#xff08;二&#xff09;安装jsoncpp库 &#xff08;三&#xff09;下载bundle数据压缩库 &#xf…

通义千问AI免费批量写文章

简数采集器支持调用阿里通义千问AI大模型API接口&#xff0c;对采集的数据进行研究分析&#xff0c;内容创作等。 通义千问AI快速使用方法&#xff08;不用编程对接&#xff09; 目录 1. 注册申请通义千问API 2. 简数对接通义千问AI 3. 设置通义千问AI指令 4. 通义千问AI…

微软Edge

微软Edge浏览器概述 功能介绍 微软Edge是一款基于Chromium开源项目的网页浏览器&#xff0c;旨在提供更快的网页加载速度、更高的安全性和更好的用户体验。它支持多种操作系统&#xff0c;包括Windows、macOS、Android和iOS&#xff0c;能够满足不同用户的需求。Edge浏览器拥…

【论文极速读】 LLava: 指令跟随的多模态大语言模型

【论文极速读】 LLava: 指令跟随的多模态大语言模型 FesianXu 20240331 at Tencent WeChat Search Team 前言 如何将已预训练好的大规模语言模型&#xff08;LLM&#xff09;和多模态模型&#xff08;如CLIP&#xff09;进行融合&#xff0c;形成一个多模态大语言模型&#xf…

「云渲染课堂」3dmax渲染影响时间的原因有哪些

在3ds Max使用过程中经验丰富的用户普遍了解&#xff0c;渲染大型场景往往需要消耗更多的时间&#xff0c;尤其是在硬件配置相同的情况下。但有时候&#xff0c;我们可能会遇到一个反直觉的现象&#xff1a;在相同的硬件配置下&#xff0c;渲染小型场景所需的时间竟然超过了大型…

数据挖掘与机器学习——机器学习概述

一、什么是机器学习 机器学习的英文名称叫Machine Learning&#xff0c;简称ML&#xff0c;该领域主要研究的是如何使计算机能够模拟人类的学习行为从而获得新的知识。 机器学习与数据挖掘的联系&#xff1a;简单来说&#xff0c;机器学习就是让计算机从大量 的数据中学习到相关…

大创项目推荐 深度学习手势识别 - yolo python opencv cnn 机器视觉

文章目录 0 前言1 课题背景2 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 3 YOLOV53.1 网络架构图3.2 输入端3.3 基准网络3.4 Neck网络3.5 Head输出层 4 数据集准备4.1 数据标注简介4.2 数据保存 5 模型训练5.1 修…

MagicAnimate: Temporally Consistent Human Image Animation using Diffusion Model

show lab NUS&bytedancehttps://github.com/magic-research/magic-animate 问题引入 输入参考图片 I r e f I_{ref} Iref​和动作序列 p 1 : N [ p 1 , ⋯ , p N ] p^{1:N}[p_1,\cdots,p_N] p1:N[p1​,⋯,pN​]&#xff0c;其中 N N N表示的是帧数&#xff0c;输出的是 …

MySQL主从复制+读写分离(ShardingJDBC)

MySQL主从复制读写分离 MySQL主从复制介绍二进制日志&#xff1a; MySQL的主从复制原理如下搭建主从复制准备工作主库配置从库配置 测试 读写分离案例ShardingJDBC介绍数据库环境初始工程导入读写分离配置测试1). 保存数据2). 修改数据3). 查询数据4). 删除数据 MySQL主从复制 …

ipad air6电容笔推荐,2024十大高性价比电容笔排行榜!

​电容笔作为ipad的最佳拍档&#xff0c;为学生党和打工人带来了极大的便利&#xff0c;二者搭配效率真的大大提升&#xff0c;但是&#xff0c;如何选购一支适合自己的电容笔呢&#xff1f;作为一个对数码设备非常感兴趣并且有一定了解的人&#xff0c;我根据自己多年的使用经…

deepseek是哪家公司

deepblue是什么公司 DeepSeek是杭州深度求索人工智能基础技术研究有限公司的简称。12 杭州深度求索人工智能基础技术研究有限公司&#xff0c;成立于2023年&#xff0c;位于浙江省杭州市&#xff0c;是一家专注于研究和试验发展的企业。该公司的注册资本为1000万人民币&…

安装和使用图像处理软件GraphicsMagick @FreeBSD

GraphicsMagick是一个用于处理图像的读取、写入和操作的工具软件。它被誉为图像处理领域的“瑞士军刀”&#xff0c;短小精悍&#xff0c;支持超过88种图像格式&#xff0c;包括DPX、GIF、JPEG、JPEG-2000、PNG、PDF、PNM和TIFF等。 GraphicsMagick的主要特点包括&#xff1a;…