├── .gitignore ├── Assets ├── Resources │ └── Shaders │ │ └── Tunnelling.shader └── Scripts │ └── ImageEffects │ └── Tunnelling.cs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # =============== # 2 | # Unity generated # 3 | # =============== # 4 | /[Ll]ibrary/ 5 | /[Tt]emp/ 6 | /[Oo]bj/ 7 | /[Uu]nity[Gg]enerated/ 8 | /[Pp]roject[Ss]ettings/ 9 | *.meta 10 | 11 | # ===================================== # 12 | # Visual Studio / MonoDevelop generated # 13 | # ===================================== # 14 | ExportedObj/ 15 | *.svd 16 | *.userprefs 17 | *.csproj 18 | *.pidb 19 | *.suo 20 | *.sln 21 | *.user 22 | *.unityproj 23 | *.booproj 24 | .vscode/ 25 | 26 | # ============ # 27 | # OS generated # 28 | # ============ # 29 | .DS_Store 30 | .DS_Store? 31 | ._* 32 | .Spotlight-V100 33 | .Trashes 34 | Icon 35 | ehthumbs.db 36 | Thumbs.db 37 | Thumbs.db.meta 38 | 39 | # ===== # 40 | # Other # 41 | # ===== # 42 | *.unitypackage 43 | /[Bb]in/ 44 | /Assets/StreamingAssets/ 45 | /Assets/[Tt][Ee][Ss][Tt]/ -------------------------------------------------------------------------------- /Assets/Resources/Shaders/Tunnelling.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Tunnelling" { 2 | Properties { 3 | _MainTex ("Texture", 2D) = "white" {} 4 | _AV ("Angular Velocity", Float) = 0 5 | _Feather ("Feather", Float) = 0.1 6 | } 7 | SubShader { 8 | // No culling or depth 9 | Cull Off ZWrite Off ZTest Always 10 | 11 | Pass { 12 | CGPROGRAM 13 | #pragma vertex vert 14 | #pragma fragment frag 15 | #pragma multi_compile __ TUNNEL_SKYBOX 16 | 17 | #include "UnityCG.cginc" 18 | 19 | struct appdata { 20 | float4 vertex : POSITION; 21 | float2 uv : TEXCOORD0; 22 | }; 23 | 24 | struct v2f { 25 | float2 uv : TEXCOORD0; 26 | float4 vertex : SV_POSITION; 27 | }; 28 | 29 | v2f vert (appdata v) { 30 | v2f o; 31 | o.vertex = UnityObjectToClipPos(v.vertex); 32 | o.uv = v.uv; 33 | return o; 34 | } 35 | 36 | sampler2D _MainTex; 37 | float4 _MainTex_ST; 38 | float _AV; 39 | float _Feather; 40 | 41 | float4x4 _EyeProjection[2]; 42 | float4x4 _EyeToWorld[2]; 43 | 44 | #if TUNNEL_SKYBOX 45 | samplerCUBE _Skybox; 46 | 47 | inline fixed3 sampleSkybox(float4 vPos){ 48 | float3 dir = normalize(mul(_EyeToWorld[unity_StereoEyeIndex], vPos).xyz); 49 | return texCUBE(_Skybox, dir).rgb; 50 | } 51 | #endif 52 | 53 | inline float4 screenCoords(float2 uv){ 54 | float2 c = (uv - 0.5) * 2; 55 | float4 vPos = mul(_EyeProjection[unity_StereoEyeIndex], float4(c, 0, 1)); 56 | vPos.xyz /= vPos.w; 57 | return vPos; 58 | } 59 | 60 | fixed4 frag (v2f i) : SV_Target { 61 | float2 uv = UnityStereoScreenSpaceUVAdjust(i.uv, _MainTex_ST); 62 | fixed4 col = tex2D(_MainTex, uv); 63 | 64 | float4 coords = screenCoords(i.uv); 65 | float radius = length(coords.xy / (_ScreenParams.xy / 2)) / 2; 66 | float avMin = (1 - _AV) - _Feather; 67 | float avMax = (1 - _AV) + _Feather; 68 | float t = saturate((radius - avMin) / (avMax - avMin)); 69 | 70 | #if TUNNEL_SKYBOX 71 | fixed4 effect = fixed4(sampleSkybox(coords),0); 72 | #else 73 | fixed4 effect = fixed4(1,0,0,0); 74 | #endif 75 | 76 | return lerp(col, effect, t); 77 | } 78 | ENDCG 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Assets/Scripts/ImageEffects/Tunnelling.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | namespace Sigtrap.ImageEffects { 5 | [RequireComponent(typeof(Camera))] 6 | public class Tunnelling : MonoBehaviour { 7 | #region Public Fields 8 | [Tooltip("Remove for plain black effect.")] 9 | public Cubemap skybox; 10 | 11 | [Header("Angular Velocity")] 12 | /// 13 | /// Angular velocity calculated for this Transform. DO NOT USE HMD! 14 | /// 15 | [Tooltip("Angular velocity calculated for this Transform.\nDO NOT USE HMD!")] 16 | public Transform refTransform; 17 | 18 | /// 19 | /// Below this angular velocity, effect will not kick in. Degrees per second 20 | /// 21 | [Tooltip("Below this angular velocity, effect will not kick in.\nDegrees per second")] 22 | public float minAngVel = 0f; 23 | 24 | /// 25 | /// At/above this angular velocity, effect will be maxed out. Degrees per second 26 | /// 27 | [Tooltip("At/above this angular velocity, effect will be maxed out.\nDegrees per second")] 28 | public float maxAngVel = 180f; 29 | 30 | /// 31 | /// Below this speed, effect will not kick in. 32 | /// 33 | [Tooltip("Below this speed, effect will not kick in.")] 34 | public float minSpeed = 0f; 35 | 36 | /// 37 | /// At/above this speed, effect will be maxed out. 38 | /// 39 | [Tooltip("At/above this speed, effect will be maxed out.\nSet negative for no effect.")] 40 | public float maxSpeed = -1f; 41 | 42 | [Header("Effect Settings")] 43 | /// 44 | /// Screen coverage at max angular velocity. 45 | /// 46 | [Range(0f,1f)][Tooltip("Screen coverage at max angular velocity.\n(1-this) is radius of visible area at max effect (screen space).")] 47 | public float maxEffect = 0.75f; 48 | 49 | /// 50 | /// Feather around cut-off as fraction of screen. 51 | /// 52 | [Range(0f, 0.5f)][Tooltip("Feather around cut-off as fraction of screen.")] 53 | public float feather = 0.1f; 54 | 55 | /// 56 | /// Smooth out radius over time. 0 for no smoothing. 57 | /// 58 | [Tooltip("Smooth out radius over time. 0 for no smoothing.")] 59 | public float smoothTime = 0.15f; 60 | #endregion 61 | 62 | #region Smoothing 63 | private float _avSlew; 64 | private float _av; 65 | #endregion 66 | 67 | #region Shader property IDs 68 | private int _propAV; 69 | private int _propFeather; 70 | #endregion 71 | 72 | #region Eye matrices 73 | Matrix4x4[] _eyeToWorld = new Matrix4x4[2]; 74 | Matrix4x4[] _eyeProjection = new Matrix4x4[2]; 75 | #endregion 76 | 77 | #region Misc Fields 78 | private Vector3 _lastFwd; 79 | private Vector3 _lastPos; 80 | private Material _m; 81 | private Camera _cam; 82 | #endregion 83 | 84 | #region Messages 85 | void Awake () { 86 | _m = new Material(Shader.Find("Hidden/Tunnelling")); 87 | 88 | if (refTransform == null){ 89 | refTransform = transform; 90 | } 91 | 92 | _propAV = Shader.PropertyToID("_AV"); 93 | _propFeather = Shader.PropertyToID("_Feather"); 94 | 95 | _cam = GetComponent(); 96 | } 97 | 98 | void Update(){ 99 | Vector3 fwd = refTransform.forward; 100 | float av = Vector3.Angle(_lastFwd, fwd) / Time.deltaTime; 101 | av = (av - minAngVel) / (maxAngVel - minAngVel); 102 | 103 | Vector3 pos = refTransform.position; 104 | 105 | if (maxSpeed > 0) { 106 | float speed = (pos - _lastPos).magnitude / Time.deltaTime; 107 | speed = (speed - minSpeed) / (maxSpeed - minSpeed); 108 | 109 | if (speed > av) { 110 | av = speed; 111 | } 112 | } 113 | 114 | av = Mathf.Clamp01(av) * maxEffect; 115 | 116 | _av = Mathf.SmoothDamp(_av, av, ref _avSlew, smoothTime); 117 | 118 | _m.SetFloat(_propAV, _av); 119 | _m.SetFloat(_propFeather, feather); 120 | 121 | _lastFwd = fwd; 122 | _lastPos = pos; 123 | } 124 | 125 | void OnPreRender(){ 126 | // Update eye matrices 127 | Matrix4x4 local; 128 | #if UNITY_2017_2_OR_NEWER 129 | if (UnityEngine.XR.XRSettings.enabled) { 130 | #else 131 | if (UnityEngine.VR.VRSettings.enabled) { 132 | #endif 133 | local = _cam.transform.parent.worldToLocalMatrix; 134 | } else { 135 | local = Matrix4x4.identity; 136 | } 137 | 138 | _eyeProjection[0] = _cam.GetStereoProjectionMatrix(Camera.StereoscopicEye.Left); 139 | _eyeProjection[1] = _cam.GetStereoProjectionMatrix(Camera.StereoscopicEye.Right); 140 | _eyeProjection[0] = GL.GetGPUProjectionMatrix(_eyeProjection[0], true).inverse; 141 | _eyeProjection[1] = GL.GetGPUProjectionMatrix(_eyeProjection[1], true).inverse; 142 | 143 | _eyeProjection[0][1, 1] *= -1f; 144 | _eyeProjection[1][1, 1] *= -1f; 145 | 146 | // Hard-code far clip 147 | _eyeProjection[0][3, 3] = 0.001f; 148 | _eyeProjection[1][3, 3] = 0.001f; 149 | 150 | _eyeToWorld[0] = _cam.GetStereoViewMatrix(Camera.StereoscopicEye.Left); 151 | _eyeToWorld[1] = _cam.GetStereoViewMatrix(Camera.StereoscopicEye.Right); 152 | 153 | _eyeToWorld[0] = local * _eyeToWorld[0].inverse; 154 | _eyeToWorld[1] = local * _eyeToWorld[1].inverse; 155 | 156 | _m.SetMatrixArray("_EyeProjection", _eyeProjection); 157 | _m.SetMatrixArray("_EyeToWorld", _eyeToWorld); 158 | 159 | // Update skybox 160 | if (skybox){ 161 | _m.SetTexture("_Skybox", skybox); 162 | _m.EnableKeyword("TUNNEL_SKYBOX"); 163 | } else { 164 | _m.DisableKeyword("TUNNEL_SKYBOX"); 165 | } 166 | } 167 | 168 | void OnRenderImage(RenderTexture src, RenderTexture dest){ 169 | Graphics.Blit(src, dest, _m); 170 | } 171 | 172 | void OnDestroy(){ 173 | Destroy(_m); 174 | } 175 | #endregion 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SixWays 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnityVrTunnelling 2 | Simple Unity implementation of *Tunnelling* for VR - dynamic reduction of FOV to combat sim-sickness 3 | 4 | ## DEPRECATED 5 | This package is now deprecated - our advanced plugin [VR Tunnelling Pro is now open source](https://github.com/sigtrapgames/VrTunnellingPro-Unity) and is strongly recommended over this repo. 6 | 7 | ## Tunnelling 8 | 9 | *Tunnelling*, as seen in Ubisoft's Eagle Flight, is dynamic vignetting to reduce FOV in VR while rotating. This reduces perceived motion for players and can be highly effective in reducing sim-sickness. 10 | 11 | ## Use 12 | Just attach to a camera and link the Ref Transform field to the player's vehicle or body (i.e. something which determines their gross rotation in the world - NOT the HMD). The default settings should work ok for most situations. 13 | 14 | Note that Tunnelling.shader must be in the Resources folder, or the shader will work in editor but not in builds. 15 | 16 | ## Cubemaps 17 | Supports drawing a cubemap in the vignette for a "cage" effect. 18 | 19 | ## VR Tunnelling Pro 20 | For many more features, including masking, 3D cage, full cross-platform support, presets and a mobile-friendly version, see our upcoming Unity Asset Store plugin VR Tunnelling Pro: [www.sigtrapgames.com/vrtp](http://www.sigtrapgames.com/vrtp) 21 | 22 | ## Example 23 | ![Tunnelling in Sublevel Zero](https://gifyu.com/images/tunnelling.gif) 24 | --------------------------------------------------------------------------------