Cinemachine中噪音的应用
两种默认产生噪音的方式
Nosie阶段的Component
  Component在流水线中主要通过MuteCameraState来处理对State的计算。
  对于Noise类型的Component来说,就是在MuteCameraState中,通过将噪音数据应用到State中的PositionCorrection和OrientationCorrection两个字段上,来提供相机的抖动功能(比如Cinemachine提供的BasicMultiChannelPerlin)。
  没有开始和停止的概念。有Nosie文件的时候就会产生噪音,没有就停止。
监听ImpulseManager的Extension
  通过对Impulse Sourse发出的震动事件(这个事件十分完善,有位置、半径、持续时间等参数,模拟一个真实的震动)监听的处理来产生震动。

噪音类
ISignalSource6D
ISignalSource6D就是Cinemachine提供的用来描述噪音数据的接口,主要提供三个能力:
- 保存噪音的数据。
 - 获取噪音的总时长,用来判断噪音是否结束。
 - 获取某一时间点的噪音数据。
 
NoiseSettings
  可作为ImpulseDefinition和BasicMultiChannelPerlin的噪音数据使用。

  最上面两行是NoiseSettings在Inspector面板中预览的参数,分别是预览的时间长度、图像高度、是否动画。
  NoiseSetting中对旋转、位置的每一个轴的震动都分别描述。
  每个震动都可以由多个波叠加而成。
  每个波由频率和振幅描述,后面那个Toggle勾选上代表这个波是非随机波(实际上就是使用Mathf.Cos函数计算),不勾选就是随机的(Mathf.PerlinNoise函数)。
CinemachineFixedSignal
  只能用于ImpulseDefinition的噪声文件。

  这个是可以用在冲击(Impulse Source)中使用的噪音。只能对位置产生影响。
  三个参数分别代表x、y、z轴的噪音曲线。
Tips
- BasicMultiChannelPerlin所产生噪声在开始生效的时候会通过ReSeed对x、y、z轴初始数据做随机偏移,导致每次开始震动的时机都不一样。
 - ImpulseListener产生的冲击是可以选择是否做随机偏移的。
 
存在问题及扩展思路
  Cinemachine自带的两种产生噪声的方式比较单一,可能会不满足复杂的噪音需求。
  比如项目组之前已经有一套成熟的通过表格配置来描述一个噪音的方案。我们希望可以直接把这个表格的配置直接用在Cinemachine中怎么办。
  这里提供两个思路:
- 写一个可以使用表格数据的Component。
 - 通过ImpulseManager和Extension来产生和处理这种表格所描述的噪音。
 
通过Component产生噪音
  这里的例子是实现一个简单的相机震屏效果,相机的震动是在相机空间内的,和相机当前的世界坐标和旋转都无关。
  首先我们需要一个可以描述表格数据的噪音类
  假如我们的噪音在表格中是这么描述的:
| 延迟开始的时间 | xyz轴的震动强度 | 震动一次的时间 | 震动持续的总时间 | 
|---|---|---|---|
| delay | strength | cycleTime | duration | 
  我们这个噪音类只是用来对表格中的噪音数据做一次转换,来供ImpulseManager或Component来使用,并不是用来存储噪音数据的,所以我们直接继承ISignalSource6D就可以,不用继承SignalSourceAsset。
  因为功能很简单,所以就直接贴一下代码:
public class GameShakeSource : ISignalSource6D
{
    public float Delay;
    public Vector3 Strength;
    public float CycleTime;
    public float Duration;
    public GameShakeSource(float delay, Vector3 strength, float cycleTime, float duration)
    {
        Delay = delay;
        Strength = strength;
        CycleTime = cycleTime;
        Duration = duration;
    }
    //噪音持续的总时间,用于判断这个噪音是否结束
    public float SignalDuration
    {
        get
        {
            return Delay + Duration;
        }
    }
    //根据当前噪音经过的时间,获取噪音产生的位置和旋转偏移量。
    //因为表格中没有旋转相关的数据,所以直接返回identity值。
    public void GetSignal(float timeSinceSignalStart, out Vector3 pos, out Quaternion rot)
    {
        if(timeSinceSignalStart <= Delay)
        {
            pos = Vector3.zero;
        }
        else
        {
            float times = timeSinceSignalStart / (CycleTime / 4);
            int cycle25Count = Mathf.FloorToInt(times);
            float inCycle25Time = times - cycle25Count;
            if(cycle25Count % 4 == 0)
            {
                pos = Vector3.Lerp(Vector3.zero, Strength, inCycle25Time);
            }
            else if(cycle25Count % 4 == 1)
            {
                pos = Vector3.Lerp(Strength, Vector3.zero, inCycle25Time);
            }
            else if (cycle25Count % 4 == 2)
            {
                pos = Vector3.Lerp(Vector3.zero, -Strength, inCycle25Time);
            }
            else
            {
                pos = Vector3.Lerp(-Strength, Vector3.zero, inCycle25Time);
            }
        }
        rot = Quaternion.identity;
    }
}
使用这个噪音文件的Component:
public class CinemachineShake : CinemachineComponentBase
{
    public ISignalSource6D ShakeSetting;
    public override bool IsValid { get { return enabled; } }
    public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Noise; } }
    private float mNoiseTime;
    private Matrix4x4 shakeMatrix = new Matrix4x4();
    //VirtualCamera用来在流水线中计算State的接口
    public override void MutateCameraState(ref CameraState curState, float deltaTime)
    {
        if (!IsValid || deltaTime < 0)
            return;
        if (ShakeSetting == null || mNoiseTime > ShakeSetting.SignalDuration)
            return;
        mNoiseTime += deltaTime;
        ShakeSetting.GetSignal(mNoiseTime, out Vector3 pos, out Quaternion rot);
        //因为这里是希望实现的是震屏功能,所以需要将ShakeSetting计算出的相机空间中的偏移量,转化为世界坐标中的偏移量。
        //直接用相机的旋转生成的矩阵乘一下就可以了
        shakeMatrix.SetTRS(Vector3.zero, curState.FinalOrientation, Vector3.one);
        //把位置偏移量应用到State上
        curState.PositionCorrection += shakeMatrix.MultiplyPoint(-pos);
        rot = Quaternion.SlerpUnclamped(Quaternion.identity, rot, -1);
        //把旋转偏移量应用到State上
        curState.OrientationCorrection = curState.OrientationCorrection * rot;
    }
    public void Shake(ISignalSource6D shakeSetting)
    {
        ShakeSetting = shakeSetting;
        mNoiseTime = 0;
    }
    public void Shake(float delay, Vector3 strength, float cycleTime, float duration)
    {
        Shake(new GameShakeSource(delay, strength, cycleTime, duration));
    }
    public void Shake()
    {
        mNoiseTime = 0;
    }
}
使用的时候调这个Component的Shake接口即可。
通过Extension产生噪音
  噪音类就直接用上面的那个。
  先提供一个新的Chanel用于这个震屏,用来和普通冲击产生的震动做区分。

  写一个ShakeManager代替ImpulseSource产生Impulse事件,直接生成事件加到ImpulseManager中。
public class ShakeManager
{
    public static void Test()
    {
        AddShake(0, new Vector3(0.3f, 0.3f, 0), 0.2f, 0.1f);
    }
    public static void AddShake(float delay, Vector3 strength, float cycleTime, float duration)
    {
        CinemachineImpulseManager.ImpulseEvent e
                = CinemachineImpulseManager.Instance.NewImpulseEvent();
        e.m_Envelope = new CinemachineImpulseManager.EnvelopeDefinition();
        //开始和衰减阶段的时间都填0,只留下中间一段时间
        e.m_Envelope.m_AttackTime = 0;
        e.m_Envelope.m_DecayTime = 0;
        e.m_Envelope.m_SustainTime = delay + duration;
        e.m_SignalSource = new GameShakeSource(delay, strength, cycleTime, duration);
        //产生冲击的位置和影响半径,这里填Vector3.zero和float.MaxValue,
        //获取的震动数据的时候从Vector3.zero这个位置获取就可以获取全量没有衰减的数据。
        e.m_Position = Vector3.zero;
        e.m_Radius = float.MaxValue;
        //2就是刚定义的gameShakeChannel
        e.m_Channel = 2;
        //选Fixed,不希望震动的方向对相机产生额外影响
        e.m_DirectionMode = CinemachineImpulseManager.ImpulseEvent.DirectionMode.Fixed;
        //衰减方式随便填,这里用不到
        e.m_DissipationMode = CinemachineImpulseManager.ImpulseEvent.DissipationMode.LinearDecay;
        //这个也用不到
        e.m_DissipationDistance = 0;
        CinemachineImpulseManager.Instance.AddImpulseEvent(e);
    }
}
写一个处理这类震动数据的Extension。
public class CinemachineShakeListener : CinemachineExtension
{
    [Tooltip("Impulse events on channels not included in the mask will be ignored.")]
    [CinemachineImpulseChannelProperty]
    public int m_ChannelMask = 1;
    [Tooltip("Gain to apply to the Impulse signal.  1 is normal strength.  Setting this to 0 completely mutes the signal.")]
    public float m_Gain = 1;
    [Tooltip("Enable this to perform distance calculation in 2D (ignore Z)")]
    public bool m_Use2DDistance = false;
    private Matrix4x4 shakeMatrix = new Matrix4x4();
    //VirtualCamera用来在流水线中计算State的接口
    protected override void PostPipelineStageCallback(
        CinemachineVirtualCameraBase vcam,
        CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
    {
        //由于这个接口在么个阶段后都会调用,所以要加这个判断。
        //保证只在Aim结束后指调用一次
        if (stage == CinemachineCore.Stage.Aim)
        {
            Vector3 impulsePos = Vector3.zero;
            Quaternion impulseRot = Quaternion.identity;
            //直接调ImpulseManager的接口获取gameShakeChannel产生的震动数据,
            //位置填zero,保证噪音不会衰减
            if (CinemachineImpulseManager.Instance.GetImpulseAt(
                Vector3.zero, m_Use2DDistance, m_ChannelMask, out impulsePos, out impulseRot))
            {
                //转换到世界坐标
                shakeMatrix.SetTRS(Vector3.zero, state.FinalOrientation, Vector3.one);
                //增加强度参数的影响后,应用到当前State上
                state.PositionCorrection += shakeMatrix.MultiplyPoint(impulsePos * -m_Gain);
                impulseRot = Quaternion.SlerpUnclamped(Quaternion.identity, impulseRot, -m_Gain);
                state.OrientationCorrection = state.OrientationCorrection * impulseRot;
            }
        }
    }
}
其他方案
  也可以选择不通过将自己组装的ImpulseEvent传给ImpulseManager来产生震动。
  直接单独写一个Manager来专门管理这一类震动。通过Extension直接从这个Manager中获取震动数据。就可以避免ImpulseManager中的一些比如范围判断、强度衰减等无效计算。
效果

小结
  Cinemachine中的噪音的核心思路其实就是在相机的基本位置旋转(也就是流水线中的Aim阶段之后)确定后,为相机添加一个额外的偏移量(OrientationCorrection,PositionCorrection参数)。
  不管是通过Compoent、Extension或者其他什么奇妙的操作来添加这个偏移量都可以。
项目链接:https://github.com/blueberryzzz/Cinemachine-Shake
Cinemachine中噪音的应用的更多相关文章
- Unity 基于Cinemachine计算透视摄像机在地图中的移动范围
		
Unity中Cinemachine的基础功能介绍可详见之前写的博客: https://www.cnblogs.com/koshio0219/p/11820654.html 本篇的重点是讨论,在给定规则 ...
 - [翻译]:Cinemachine 官方文档(0)
		
目录 Overview : Installation and Getting Started :安装并开始 User Guide :用户指南 What is Cinemachine? : 什么是Cin ...
 - Unity - Cinemachine实现相机抖动
		
普通相机抖动脚本较易实现,但在使用cinemachine相机下,其Transform组件不可被代码改变,那么Cinemachine的相机抖动如何实现呢?本文结合实际项目,对实现相机抖动的三大步骤进行系 ...
 - 主元分析PCA理论分析及应用
		
首先,必须说明的是,这篇文章是完完全全复制百度文库当中的一篇文章.本人之前对PCA比较好奇,在看到这篇文章之后发现其对PCA的描述非常详细,因此迫不及待要跟大家分享一下,希望同样对PCA比较困惑的朋友 ...
 - 基于ArcGIS的栅格图像平滑处理(转)
		
基于ArcGIS的栅格图像平滑处理 栅格数据获取的途径多种多样,造成了栅格数据质量的很大差异,一些质量较差的栅格数据存在大量“噪音”象元,即在表达同类型的地理要素时,出现个别像元值与周边像元不一致的情 ...
 - NLP︱LDA主题模型的应用难题、使用心得及从多元统计角度剖析
		
将LDA跟多元统计分析结合起来看,那么LDA中的主题就像词主成分,其把主成分-样本之间的关系说清楚了.多元学的时候聚类分为Q型聚类.R型聚类以及主成分分析.R型聚类.主成分分析针对变量,Q型聚类针对样 ...
 - 什么是tcp/ip
		
在了解Tcp /Ip之前.我们需要了解几个名词的含义: 什么是IP? IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层---TCP或UDP层:相反,IP层也 ...
 - 从锅炉工到AI专家(11)(END)
		
语音识别 TensorFlow 1.x中提供了一个语音识别的例子speech_commands,用于识别常用的命令词汇,实现对设备的语音控制.speech_commands是一个很成熟的语音识别原型, ...
 - 机器学习总结(二)bagging与随机森林
		
一:Bagging与随机森林 与Boosting族算法不同的是,Bagging和随机森林的个体学习器之间不存在强的依赖关系,可同时生成并行化的方法. Bagging算法 bagging的算法过程如下: ...
 
随机推荐
- 10X genomics|cell base|in-vivo based|model organisms|SBI|
			
生命组学-药物基因组学 精准医学的内容有个人全基因组测序,移动可穿戴设备,它可以实时监测,深度学习模型预测疾病,对疾病预测做到有效.安全和可控. 药物基因组学就是研究疾病.化合物和靶点之间的关系,关键 ...
 - S2SH项目实现分页功能
			
javaWEB项目实现分页的方法很多,网上也有很多列子,最近工作中S2SH框架项目中需要一个分页的功能,查看了很多用一下方式实现,功能思路很清晰,觉得是很好的一种实现方法,记录下便多学习. 刚开始得到 ...
 - Spring Boot 学习笔记(六) 整合 RESTful 参数传递
			
Spring Boot 学习笔记 源码地址 Spring Boot 学习笔记(一) hello world Spring Boot 学习笔记(二) 整合 log4j2 Spring Boot 学习笔记 ...
 - 吴裕雄--天生自然HTML学习笔记:HTML 元素
			
HTML 文档由 HTML 元素定义. HTML 元素 开始标签 * 元素内容 结束标签 * <p> 这是一个段落 </p> <a href="default. ...
 - haproxy笔记之六:负载均衡MySQL服务的配置示例
			
#--------------------------------------------------------------------- # Global settings #---------- ...
 - pipe 导致的 CLOSE_WAIT :: Utop's Blog
			
历时一周总算把导致服务大量 CLOSE_WAIT 的原因给找到了.打印任务调用栈果然的必备手段啊! 问题描述 Python 服务 A,用于接收心跳包确认其他服务是否存活.其他服务每 5 分钟向 A 发 ...
 - Python实现链表倒序(带头指针)
			
class ListNode(object): def __init__(self, x): self.val = x self.next = None def reverseList(self, h ...
 - 机器学习的盛宴:NIPS 2015
			
作者:微软亚洲研究院实习生:林添 冰雪王国的浪漫 机器学习的盛宴 NIPS(Advances in Neural Information Processing Systems,神经信息处理系统进展大会 ...
 - Jun
			
Contents 数据来源 代码演示 讨论 一.数据来源 为了节省时间,我直接用了官方所给的数据,分别是雄性和雌性小鼠的肝脏芯片数据 Female Data Male Data 二.代码演示 数据输入 ...
 - Liferay7 Intellij IDEA 开发环境搭建
			
一.安装Liferay插件 安装过程不在赘述,推荐两种安装方式: 通过Intellij插件市场安装 通过下载插件zip包安装 安装完成后,在项目板块中点鼠标右键,会出现Liferay菜单. 二.安装L ...