// Tunnel Overlay|Locomotion|20140
namespace VRTK
{
using UnityEngine;
///
/// 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.
///
///
/// **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
///
[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("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();
cameraEffect = headset.GetComponent();
}
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();
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.");
}
}
}
}