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的算法过程如下: ...
随机推荐
- Nginx for windows 访问路径包含中文
转载自http://blog.csdn.net/five824/article/details/48261213 Nginx for windows 访问路径包含中文 原创 2015年09月07日 0 ...
- Windows下使用swoole的环境搭建
Cygwin 官方地址:http://www.cygwin.com/ swoole 官方下载地址:https://github.com/swoole/swoole-src/releases 方法/步骤 ...
- GYOJ_1812_股票(stock)
题目描述 2130年,股神巴菲特投胎了!他投胎到你身上! 你作为股神转世,能力比原股神还要强,你可以预测到今后n天的股价.假设刚开始你的手上有1元钱,你想知道n天后你最多可以赚到多少钱.作为股神转世, ...
- 【转】Android Monkey 命令行可用的全部选项
常规 事件 约束限制 调试 原文参见:http://www.douban.com/note/257030384/ 常规 –help 列出简单的用法. -v 命令行的每一个 -v 将增加反馈信息的级别. ...
- 有关终端的一些tips
reg.exe是用于操作注册表的命令,可以通过reg /?来查看所有参数,在pentest中有两个很实用的参数 reg query 读取注册表信息, reg add 添加或修改注册表内容. 设想如下场 ...
- 安卓权威编程指南-笔记(第27章 broadcast intent)
本章需求:首先,让应用轮询新结果并在有所发现时及时通知用户,即使用户重启设备后还没有打开过应用.其次,保证用户在使用应用时不出现新结果通知. 1. 一般intent和broadcast intent ...
- 为何银行愿为收购supercell做无权追索融资?
无追索权融资又称纯粹的项目融资,是指贷款人对项目主办人没有任何追索权的项目融资.简单来说,这是一种项目失败,也无法追尝的承诺,一般发生在石油.天然气.煤炭.铜.铝等矿产资源开发等相对较为保值的项目融资 ...
- Node学习(二) --使用http和fs模块实现一个简单的服务器
1.创建一个www目录,存储静态文件1.html.1.jpg. * html文件内容如下: 12345678910111213 <html lang="en">< ...
- FormsAuthenticationTicket身份验证通过后无法登陆---可能存在的问题
这是我自己遇到过的,FormsAuthenticationTicket身份验证通过后还是存在无法登录的问题,调试了很长时间还是没有发现问题,最后突然想到是否是因为cookie长度限制,导致不能将信息存 ...
- Matplotlib数据可视化(4):折线图与散点图
In [1]: from matplotlib import pyplot as plt import numpy as np import matplotlib as mpl mpl.rcParam ...