// Tunnel Overlay|Locomotion|20140
|
|
namespace VRTK
|
|
{
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Applys a tunnel overlay effect to the active VR camera when the play area is moving or rotating to reduce potential nausea caused by simulation sickness.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// **Script Usage:**
|
|
/// * Place the `VRTK_TunnelOverlay` script on any active scene GameObject.
|
|
///
|
|
/// > This implementation is based on a project made by SixWays at https://github.com/SixWays/UnityVrTunnelling
|
|
/// </remarks>
|
|
[AddComponentMenu("VRTK/Scripts/Locomotion/VRTK_TunnelOverlay")]
|
|
public class VRTK_TunnelOverlay : MonoBehaviour
|
|
{
|
|
[Header("Movement Settings")]
|
|
|
|
[Tooltip("Minimum rotation speed for the effect to activate (degrees per second).")]
|
|
public float minimumRotation = 0f;
|
|
[Tooltip("Maximum rotation speed for the effect have its max settings applied (degrees per second).")]
|
|
public float maximumRotation = 45f;
|
|
[Tooltip("Minimum movement speed for the effect to activate.")]
|
|
public float minimumSpeed = 0f;
|
|
[Tooltip("Maximum movement speed where the effect will have its max settings applied.")]
|
|
public float maximumSpeed = 1f;
|
|
|
|
[Header("Effect Settings")]
|
|
|
|
[Tooltip("The color to use for the tunnel effect.")]
|
|
public Color effectColor = Color.black;
|
|
[Tooltip("An optional skybox texture to use for the tunnel effect.")]
|
|
public Texture effectSkybox;
|
|
[Tooltip("The initial amount of screen coverage the tunnel to consume without any movement.")]
|
|
[Range(0f, 1f)]
|
|
public float initialEffectSize = 0f;
|
|
[Tooltip("Screen coverage at the maximum tracked values.")]
|
|
[Range(0f, 1f)]
|
|
public float maximumEffectSize = 0.65f;
|
|
[Tooltip("Feather effect size around the cut-off as fraction of screen.")]
|
|
[Range(0f, 0.5f)]
|
|
public float featherSize = 0.1f;
|
|
[Tooltip("Smooth out radius over time.")]
|
|
public float smoothingTime = 0.15f;
|
|
|
|
protected Transform headset;
|
|
protected Camera headsetCamera;
|
|
protected Transform playarea;
|
|
protected VRTK_TunnelEffect cameraEffect;
|
|
protected float angularVelocity;
|
|
protected float angularVelocitySlew;
|
|
protected Vector3 lastForward;
|
|
protected Vector3 lastPosition;
|
|
protected Material matCameraEffect;
|
|
protected int shaderPropertyColor;
|
|
protected int shaderPropertyAV;
|
|
protected int shaderPropertyFeather;
|
|
protected int shaderPropertySkyboxTexture;
|
|
protected Color originalColor;
|
|
protected float originalAngularVelocity;
|
|
protected float originalFeatherSize;
|
|
protected Texture originalSkyboxTexture;
|
|
protected float maximumEffectCoverage = 1.15f;
|
|
protected bool createEffectSkybox = false;
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
matCameraEffect = Resources.Load<Material>("TunnelOverlay");
|
|
shaderPropertyColor = Shader.PropertyToID("_Color");
|
|
shaderPropertyAV = Shader.PropertyToID("_AngularVelocity");
|
|
shaderPropertyFeather = Shader.PropertyToID("_FeatherSize");
|
|
shaderPropertySkyboxTexture = Shader.PropertyToID("_SecondarySkyBox");
|
|
VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
|
|
}
|
|
|
|
protected virtual void OnEnable()
|
|
{
|
|
headset = VRTK_DeviceFinder.HeadsetCamera();
|
|
if (headset != null)
|
|
{
|
|
headsetCamera = headset.GetComponent<Camera>();
|
|
cameraEffect = headset.GetComponent<VRTK_TunnelEffect>();
|
|
}
|
|
playarea = VRTK_DeviceFinder.PlayAreaTransform();
|
|
originalAngularVelocity = matCameraEffect.GetFloat(shaderPropertyAV);
|
|
originalFeatherSize = matCameraEffect.GetFloat(shaderPropertyFeather);
|
|
originalColor = matCameraEffect.GetColor(shaderPropertyColor);
|
|
CheckSkyboxTexture();
|
|
if (effectSkybox != null)
|
|
{
|
|
originalSkyboxTexture = matCameraEffect.GetTexture(shaderPropertySkyboxTexture);
|
|
matCameraEffect.SetTexture("_SecondarySkyBox", effectSkybox);
|
|
}
|
|
|
|
if (cameraEffect == null && headset != null)
|
|
{
|
|
cameraEffect = headset.gameObject.AddComponent<VRTK_TunnelEffect>();
|
|
cameraEffect.SetMaterial(matCameraEffect);
|
|
}
|
|
}
|
|
|
|
protected virtual void OnDisable()
|
|
{
|
|
headset = null;
|
|
headsetCamera = null;
|
|
playarea = null;
|
|
|
|
if (cameraEffect != null)
|
|
{
|
|
matCameraEffect.SetTexture("_SecondarySkyBox", originalSkyboxTexture);
|
|
originalSkyboxTexture = null;
|
|
|
|
SetShaderFeather(originalColor, originalAngularVelocity, originalFeatherSize);
|
|
matCameraEffect.SetColor(shaderPropertyColor, originalColor);
|
|
Destroy(cameraEffect);
|
|
}
|
|
|
|
if (createEffectSkybox)
|
|
{
|
|
effectSkybox = null;
|
|
createEffectSkybox = false;
|
|
}
|
|
}
|
|
|
|
protected virtual void OnDestroy()
|
|
{
|
|
VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
|
|
}
|
|
|
|
protected virtual void FixedUpdate()
|
|
{
|
|
Vector3 fwd = playarea.forward;
|
|
Vector3 pos = playarea.position;
|
|
|
|
float newAngularVelocity = Vector3.Angle(lastForward, fwd) / Time.fixedDeltaTime;
|
|
newAngularVelocity = (newAngularVelocity - minimumRotation) / (maximumRotation - minimumRotation);
|
|
|
|
if (maximumSpeed > 0)
|
|
{
|
|
float speed = (pos - lastPosition).magnitude / Time.fixedDeltaTime;
|
|
speed = (speed - minimumSpeed) / (maximumSpeed - minimumSpeed);
|
|
|
|
if (speed > newAngularVelocity)
|
|
{
|
|
newAngularVelocity = speed;
|
|
}
|
|
}
|
|
|
|
float actualInitialSize = (initialEffectSize * maximumEffectCoverage);
|
|
float actualMaxSize = (maximumEffectSize * maximumEffectCoverage) - actualInitialSize;
|
|
|
|
newAngularVelocity = Mathf.Clamp01(newAngularVelocity) * actualMaxSize;
|
|
angularVelocity = Mathf.SmoothDamp(angularVelocity, newAngularVelocity, ref angularVelocitySlew, smoothingTime);
|
|
|
|
SetShaderFeather(effectColor, angularVelocity + actualInitialSize, featherSize);
|
|
|
|
lastForward = fwd;
|
|
lastPosition = pos;
|
|
|
|
if (effectSkybox != null)
|
|
{
|
|
matCameraEffect.SetMatrixArray("_EyeToWorld", new Matrix4x4[2]
|
|
{
|
|
headsetCamera.GetStereoViewMatrix(Camera.StereoscopicEye.Left).inverse,
|
|
headsetCamera.GetStereoViewMatrix(Camera.StereoscopicEye.Right).inverse
|
|
});
|
|
|
|
Matrix4x4[] eyeProjection = new Matrix4x4[2];
|
|
eyeProjection[0] = headsetCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Left);
|
|
eyeProjection[1] = headsetCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Right);
|
|
eyeProjection[0] = GL.GetGPUProjectionMatrix(eyeProjection[0], true).inverse;
|
|
eyeProjection[1] = GL.GetGPUProjectionMatrix(eyeProjection[1], true).inverse;
|
|
eyeProjection[0][1, 1] *= -1f;
|
|
eyeProjection[1][1, 1] *= -1f;
|
|
|
|
matCameraEffect.SetMatrixArray("_EyeProjection", eyeProjection);
|
|
}
|
|
}
|
|
|
|
protected virtual void SetShaderFeather(Color givenTunnelColor, float givenAngularVelocity, float givenFeatherSize)
|
|
{
|
|
matCameraEffect.SetColor(shaderPropertyColor, givenTunnelColor);
|
|
matCameraEffect.SetFloat(shaderPropertyAV, givenAngularVelocity);
|
|
matCameraEffect.SetFloat(shaderPropertyFeather, givenFeatherSize);
|
|
}
|
|
|
|
protected virtual void CheckSkyboxTexture()
|
|
{
|
|
if (effectSkybox == null)
|
|
{
|
|
Cubemap tempTexture = new Cubemap(1, TextureFormat.ARGB32, false);
|
|
tempTexture.SetPixel(CubemapFace.NegativeX, 0, 0, Color.white);
|
|
tempTexture.SetPixel(CubemapFace.NegativeY, 0, 0, Color.white);
|
|
tempTexture.SetPixel(CubemapFace.NegativeZ, 0, 0, Color.white);
|
|
tempTexture.SetPixel(CubemapFace.PositiveX, 0, 0, Color.white);
|
|
tempTexture.SetPixel(CubemapFace.PositiveY, 0, 0, Color.white);
|
|
tempTexture.SetPixel(CubemapFace.PositiveZ, 0, 0, Color.white);
|
|
effectSkybox = tempTexture;
|
|
createEffectSkybox = true;
|
|
}
|
|
else if (effectColor.r < 0.15f && effectColor.g < 0.15 && effectColor.b < 0.15)
|
|
{
|
|
VRTK_Logger.Warn("`VRTK_TunnelOverlay` has an `Effect Skybox` texture but the `Effect Color` is too dark which will tint the texture so it is not visible.");
|
|
}
|
|
}
|
|
}
|
|
}
|