111 lines
5.2 KiB
C#
111 lines
5.2 KiB
C#
|
|
using UnityEngine;
|
||
|
|
|
||
|
|
namespace Unity.Cinemachine.Samples
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// This behaviour implements a custom algorithm to provide smooth blends between perspective
|
||
|
|
/// and ortho cameras. Drop it into your scene, and while enabled it will override the default
|
||
|
|
/// blending algorithms when appropriate.
|
||
|
|
///
|
||
|
|
/// Specifically, if one camera is perspective and the other is orthographic, and if the orthographic
|
||
|
|
/// camera has a LookAt target, the custom blend will be used.
|
||
|
|
///
|
||
|
|
/// Because it's not possible to lerp between perspective and ortho cameras, we approximate the ortho
|
||
|
|
/// camera with a perspective camera placed far away with a small fov, and blend to that. Afterwards,
|
||
|
|
/// we simply cut to the ortho camera.
|
||
|
|
///
|
||
|
|
/// This class illustrates the use of the CinemachineCore.GetCustomBlender hook, and shows how to
|
||
|
|
/// implement the CinemachineBlend.IBlender interface.
|
||
|
|
/// </summary>
|
||
|
|
[ExecuteAlways]
|
||
|
|
public class PerspectiveToOrthoCustomBlender : MonoBehaviour, CinemachineBlend.IBlender
|
||
|
|
{
|
||
|
|
[Tooltip("Minimum distance at which to place the perspective camera which will mimic the orthographic one. \n"
|
||
|
|
+ "Changing this distance may affect the feel of the blend: a large distance will produce a better approximation "
|
||
|
|
+ "of the ortho camera, but will also make the FOV change happen more quickly at the start of the blend. \n"
|
||
|
|
+ "Keep this distance as small as you can tolerate, to avoid precision errors which can be present at "
|
||
|
|
+ "large camera distances.")]
|
||
|
|
public float FakeOrthoCameraDistance = 100;
|
||
|
|
|
||
|
|
void OnEnable() => CinemachineCore.GetCustomBlender += GetCustomBlender;
|
||
|
|
void OnDisable() => CinemachineCore.GetCustomBlender -= GetCustomBlender;
|
||
|
|
|
||
|
|
// CinemachineCore.GetCustomBlender handler
|
||
|
|
CinemachineBlend.IBlender GetCustomBlender(ICinemachineCamera camA, ICinemachineCamera camB)
|
||
|
|
{
|
||
|
|
// Use the custom blender if and only if we're transitioning between ortho and perspective cameras
|
||
|
|
if (camA != null && camB != null)
|
||
|
|
{
|
||
|
|
var stateA = camA.State;
|
||
|
|
var stateB = camB.State;
|
||
|
|
if (IsBlendToOrthoCandidate(ref stateA, ref stateB))
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
// Use default blender
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// CinemachineBlend.IBlender implementation
|
||
|
|
public CameraState GetIntermediateState(ICinemachineCamera camA, ICinemachineCamera camB, float t)
|
||
|
|
{
|
||
|
|
var stateA = camA.State;
|
||
|
|
var stateB = camB.State;
|
||
|
|
|
||
|
|
// This can happen if we're blending intermediate states due to interrupted blend
|
||
|
|
if (!IsBlendToOrthoCandidate(ref stateA, ref stateB))
|
||
|
|
return CameraState.Lerp(stateA, stateB, t);
|
||
|
|
|
||
|
|
if (!stateA.Lens.Orthographic)
|
||
|
|
return BlendToOrtho(ref stateA, ref stateB, t);
|
||
|
|
|
||
|
|
return BlendToOrtho(ref stateB, ref stateA, 1-t);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsBlendToOrthoCandidate(ref CameraState stateA, ref CameraState stateB)
|
||
|
|
{
|
||
|
|
bool orthoA = stateA.Lens.Orthographic;
|
||
|
|
bool orthoB = stateB.Lens.Orthographic;
|
||
|
|
|
||
|
|
// A lookAt target is required on the ortho camera in order to establish the mimic fov
|
||
|
|
return orthoA != orthoB && ((orthoA && stateA.HasLookAt()) || (orthoB && stateB.HasLookAt()));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Replaces stateB with a fake ortho camera which is a far-away perspective camera with a small fov
|
||
|
|
CameraState BlendToOrtho(ref CameraState stateA, ref CameraState stateB, float t)
|
||
|
|
{
|
||
|
|
var lensB = stateB.Lens;
|
||
|
|
var orthoSize = lensB.OrthographicSize;
|
||
|
|
|
||
|
|
var lookAt = stateB.ReferenceLookAt;
|
||
|
|
if (!stateA.HasLookAt())
|
||
|
|
stateA.ReferenceLookAt = lookAt;
|
||
|
|
|
||
|
|
var distanceFromTarget = Vector3.Distance(lookAt, stateB.GetCorrectedPosition());
|
||
|
|
|
||
|
|
// We want it to be far compared to the ortho size
|
||
|
|
var extraDistance = Mathf.Max(0, Mathf.Max(FakeOrthoCameraDistance, orthoSize * 20) - distanceFromTarget);
|
||
|
|
|
||
|
|
var rotB = stateB.GetFinalOrientation();
|
||
|
|
stateB.RawPosition = stateB.GetCorrectedPosition() + rotB * Vector3.back * extraDistance;
|
||
|
|
stateB.PositionCorrection = Vector3.zero;
|
||
|
|
stateB.ReferenceUp = rotB * Vector3.up;
|
||
|
|
|
||
|
|
// Force a spherical position algorithm
|
||
|
|
stateB.BlendHint |= CameraState.BlendHints.SphericalPositionBlend;
|
||
|
|
|
||
|
|
// The fov should be such as to produce the ortho size at the target's position
|
||
|
|
var lens = stateA.Lens;
|
||
|
|
lens.FieldOfView = 2f * Mathf.Atan(orthoSize / (extraDistance + distanceFromTarget)) * Mathf.Rad2Deg;
|
||
|
|
|
||
|
|
// Lerp the clip planes to reduce popping
|
||
|
|
lens.NearClipPlane = Mathf.Max(lens.NearClipPlane, extraDistance + lensB.NearClipPlane);
|
||
|
|
lens.FarClipPlane = extraDistance + lensB.FarClipPlane;
|
||
|
|
stateB.Lens = lens;
|
||
|
|
|
||
|
|
// We square t to spend more time at the start of the blend, producing a smoother result
|
||
|
|
// when the fake ortho camera is far away. This could potentially be tweaked.
|
||
|
|
return CameraState.Lerp(stateA, stateB, t * t);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|