两种默认产生噪音的方式

Nosie阶段的Component

  Component在流水线中主要通过MuteCameraState来处理对State的计算。

  对于Noise类型的Component来说,就是在MuteCameraState中,通过将噪音数据应用到State中的PositionCorrection和OrientationCorrection两个字段上,来提供相机的抖动功能(比如Cinemachine提供的BasicMultiChannelPerlin)。

  没有开始和停止的概念。有Nosie文件的时候就会产生噪音,没有就停止。

监听ImpulseManager的Extension

  通过对Impulse Sourse发出的震动事件(这个事件十分完善,有位置、半径、持续时间等参数,模拟一个真实的震动)监听的处理来产生震动。

噪音类

ISignalSource6D

  ISignalSource6D就是Cinemachine提供的用来描述噪音数据的接口,主要提供三个能力:

  1. 保存噪音的数据。
  2. 获取噪音的总时长,用来判断噪音是否结束。
  3. 获取某一时间点的噪音数据。

NoiseSettings

  可作为ImpulseDefinition和BasicMultiChannelPerlin的噪音数据使用。



  最上面两行是NoiseSettings在Inspector面板中预览的参数,分别是预览的时间长度、图像高度、是否动画。

  NoiseSetting中对旋转、位置的每一个轴的震动都分别描述。

  每个震动都可以由多个波叠加而成。

  每个波由频率和振幅描述,后面那个Toggle勾选上代表这个波是非随机波(实际上就是使用Mathf.Cos函数计算),不勾选就是随机的(Mathf.PerlinNoise函数)。

CinemachineFixedSignal

  只能用于ImpulseDefinition的噪声文件。



  这个是可以用在冲击(Impulse Source)中使用的噪音。只能对位置产生影响。

  三个参数分别代表x、y、z轴的噪音曲线。

Tips

  1. BasicMultiChannelPerlin所产生噪声在开始生效的时候会通过ReSeed对x、y、z轴初始数据做随机偏移,导致每次开始震动的时机都不一样。
  2. ImpulseListener产生的冲击是可以选择是否做随机偏移的。

存在问题及扩展思路

  Cinemachine自带的两种产生噪声的方式比较单一,可能会不满足复杂的噪音需求。

  比如项目组之前已经有一套成熟的通过表格配置来描述一个噪音的方案。我们希望可以直接把这个表格的配置直接用在Cinemachine中怎么办。

  这里提供两个思路:

  1. 写一个可以使用表格数据的Component。
  2. 通过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中噪音的应用的更多相关文章

  1. Unity 基于Cinemachine计算透视摄像机在地图中的移动范围

    Unity中Cinemachine的基础功能介绍可详见之前写的博客: https://www.cnblogs.com/koshio0219/p/11820654.html 本篇的重点是讨论,在给定规则 ...

  2. [翻译]:Cinemachine 官方文档(0)

    目录 Overview : Installation and Getting Started :安装并开始 User Guide :用户指南 What is Cinemachine? : 什么是Cin ...

  3. Unity - Cinemachine实现相机抖动

    普通相机抖动脚本较易实现,但在使用cinemachine相机下,其Transform组件不可被代码改变,那么Cinemachine的相机抖动如何实现呢?本文结合实际项目,对实现相机抖动的三大步骤进行系 ...

  4. 主元分析PCA理论分析及应用

    首先,必须说明的是,这篇文章是完完全全复制百度文库当中的一篇文章.本人之前对PCA比较好奇,在看到这篇文章之后发现其对PCA的描述非常详细,因此迫不及待要跟大家分享一下,希望同样对PCA比较困惑的朋友 ...

  5. 基于ArcGIS的栅格图像平滑处理(转)

    基于ArcGIS的栅格图像平滑处理 栅格数据获取的途径多种多样,造成了栅格数据质量的很大差异,一些质量较差的栅格数据存在大量“噪音”象元,即在表达同类型的地理要素时,出现个别像元值与周边像元不一致的情 ...

  6. NLP︱LDA主题模型的应用难题、使用心得及从多元统计角度剖析

    将LDA跟多元统计分析结合起来看,那么LDA中的主题就像词主成分,其把主成分-样本之间的关系说清楚了.多元学的时候聚类分为Q型聚类.R型聚类以及主成分分析.R型聚类.主成分分析针对变量,Q型聚类针对样 ...

  7. 什么是tcp/ip

    在了解Tcp /Ip之前.我们需要了解几个名词的含义: 什么是IP? IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层---TCP或UDP层:相反,IP层也 ...

  8. 从锅炉工到AI专家(11)(END)

    语音识别 TensorFlow 1.x中提供了一个语音识别的例子speech_commands,用于识别常用的命令词汇,实现对设备的语音控制.speech_commands是一个很成熟的语音识别原型, ...

  9. 机器学习总结(二)bagging与随机森林

    一:Bagging与随机森林 与Boosting族算法不同的是,Bagging和随机森林的个体学习器之间不存在强的依赖关系,可同时生成并行化的方法. Bagging算法 bagging的算法过程如下: ...

随机推荐

  1. Qt char * 与 const char * 的转换

    char *ch1="hello11"; const char *ch2="hello22"; ch2 = ch1;//不报错,但有警告 ch1 = (char ...

  2. group compare vs pair compare

    成对总体检验是令y1=x11-x12:y2=x21-x22等,令新的随机变量y去做假设检验.此方法适用于排除物理因素影响,对差异更敏感,所以适用于小样本.而使用两个总体均值比较的方法适用于大样本.

  3. Activiti 5.16 用户手册

    http://www.mossle.com/docs/activiti/index.html#bpmnNoneStartEvent   Activiti 5.16 用户手册

  4. tomcat一闪而过

    JAVA_HOME should point to a JDK not a JRE |vista tomcat 有的朋友在启动Tomcat的时候会出现问题. 1例如: 手动点击startup.bat ...

  5. axios统一封装

    本文代码参考了网上别人的资料,经过修改而来 /** * Created by zxf on 2017/9/6. * 封装统一的ajax请求,统一拦截请求,对不同的请求状态封装 * 通常说, ajax ...

  6. iOS中如何实现准确的倒计时程序 · 九十里

    iOS中倒计时程序,考虑线程暂停场景. iOS App进入后台时,GCD线程也会跟着暂停.当程序进入前台后,GCD线程恢复.因而倒计时程序需要考虑这一点,通过加入时间的比对来实现. + (void)c ...

  7. 关于sql拼接的知识点 where1=1

    String sql="select * from tab_route where 1 = 1 "; 这样不会报错,而且可以根据情况,再去拼接sql 可以使用if(){}else{ ...

  8. 全栈新视觉——前后端分离

    1234 前端工程化从单纯的 HTML/CSS/JavaScript,到 gulp/webpack 以及 node.js.可能还需要其他的插件 sass.less.vue.react.angular. ...

  9. Qt 隐藏标题栏 窗口移动 鼠标事件

    摘要 隐藏标题栏 头文件声明鼠标移动虚函数 .cpp文件实现功能 1 setWindowFlags(Qt::FramelessWindowHint | windowFlags()); 无标题栏移动窗体 ...

  10. RPi.GPIO 和 HM

    后续笔记不再记录导入的模块和硬件的连接方法,请根据关键词自行搜索. RPi.GPIO模块 GPIO:General Purpose Input Output 即 通用输入/输出 RPi.GPIO是一个 ...