|
/************************************************************************************
|
|
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
|
|
|
Licensed under the Oculus Utilities SDK License Version 1.31 (the "License"); you may not use
|
|
the Utilities SDK except in compliance with the License, which is provided at the time of installation
|
|
or download, or which otherwise accompanies this software in either electronic or hard copy form.
|
|
|
|
You may obtain a copy of the License at
|
|
https://developer.oculus.com/licenses/utilities-1.31
|
|
|
|
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
ANY KIND, either express or implied. See the License for the specific language governing
|
|
permissions and limitations under the License.
|
|
************************************************************************************/
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
/// <summary>
|
|
/// A component to apply a Colored vignette effect to the camera
|
|
/// </summary>
|
|
[RequireComponent(typeof(Camera))]
|
|
[ExecuteInEditMode]
|
|
public class OVRVignette : MonoBehaviour {
|
|
|
|
/// <summary>
|
|
/// Controls the number of triangles in the vignette mesh.
|
|
/// </summary>
|
|
public enum MeshComplexityLevel
|
|
{
|
|
VerySimple,
|
|
Simple,
|
|
Normal,
|
|
Detailed,
|
|
VeryDetailed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Controls the falloff appearance.
|
|
/// </summary>
|
|
public enum FalloffType
|
|
{
|
|
Linear,
|
|
Quadratic
|
|
}
|
|
|
|
private static readonly string QUADRATIC_FALLOFF = "QUADRATIC_FALLOFF";
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private Shader VignetteShader;
|
|
|
|
// These are only used at startup.
|
|
[SerializeField]
|
|
[Tooltip("Controls the number of triangles used for the vignette mesh." +
|
|
" Normal is best for most purposes.")]
|
|
private MeshComplexityLevel MeshComplexity = MeshComplexityLevel.Normal;
|
|
[SerializeField]
|
|
[Tooltip("Controls how the falloff looks.")]
|
|
private FalloffType Falloff = FalloffType.Linear;
|
|
|
|
// These can be controlled dynamically at runtime
|
|
[Tooltip("The Vertical FOV of the vignette")]
|
|
public float VignetteFieldOfView = 60;
|
|
[Tooltip("The Aspect ratio of the vignette controls the " +
|
|
"Horizontal FOV. (Larger numbers are wider)")]
|
|
public float VignetteAspectRatio = 1f;
|
|
[Tooltip("The width of the falloff for the vignette in degrees")]
|
|
public float VignetteFalloffDegrees = 10f;
|
|
[ColorUsage(false)]
|
|
[Tooltip("The color of the vignette. Alpha value is ignored")]
|
|
public Color VignetteColor;
|
|
|
|
private Camera _Camera;
|
|
private MeshFilter _OpaqueMeshFilter;
|
|
private MeshFilter _TransparentMeshFilter;
|
|
private MeshRenderer _OpaqueMeshRenderer;
|
|
private MeshRenderer _TransparentMeshRenderer;
|
|
|
|
private Mesh _OpaqueMesh;
|
|
private Mesh _TransparentMesh;
|
|
private Material _OpaqueMaterial;
|
|
private Material _TransparentMaterial;
|
|
|
|
private int _ShaderScaleAndOffset0Property;
|
|
private int _ShaderScaleAndOffset1Property;
|
|
|
|
private Vector4[] _TransparentScaleAndOffset0 = new Vector4[2];
|
|
private Vector4[] _TransparentScaleAndOffset1 = new Vector4[2];
|
|
private Vector4[] _OpaqueScaleAndOffset0 = new Vector4[2];
|
|
private Vector4[] _OpaqueScaleAndOffset1 = new Vector4[2];
|
|
|
|
private bool _OpaqueVignetteVisible = false;
|
|
private bool _TransparentVignetteVisible = false;
|
|
|
|
#if UNITY_EDITOR
|
|
// in the editor, allow these to be changed at runtime
|
|
private MeshComplexityLevel _InitialMeshComplexity;
|
|
private FalloffType _InitialFalloff;
|
|
#endif
|
|
|
|
private int GetTriangleCount()
|
|
{
|
|
switch(MeshComplexity)
|
|
{
|
|
case MeshComplexityLevel.VerySimple: return 32;
|
|
case MeshComplexityLevel.Simple: return 64;
|
|
case MeshComplexityLevel.Normal: return 128;
|
|
case MeshComplexityLevel.Detailed: return 256;
|
|
case MeshComplexityLevel.VeryDetailed: return 512;
|
|
default: return 128;
|
|
}
|
|
}
|
|
|
|
private void BuildMeshes()
|
|
{
|
|
#if UNITY_EDITOR
|
|
_InitialMeshComplexity = MeshComplexity;
|
|
#endif
|
|
int triangleCount = GetTriangleCount();
|
|
|
|
Vector3[] innerVerts = new Vector3[triangleCount];
|
|
Vector2[] innerUVs = new Vector2[triangleCount];
|
|
Vector3[] outerVerts = new Vector3[triangleCount];
|
|
Vector2[] outerUVs = new Vector2[triangleCount];
|
|
int[] tris = new int[triangleCount * 3];
|
|
for (int i = 0; i < triangleCount; i += 2)
|
|
{
|
|
float angle = 2 * i * Mathf.PI / triangleCount;
|
|
|
|
float x = Mathf.Cos(angle);
|
|
float y = Mathf.Sin(angle);
|
|
|
|
outerVerts[i] = new Vector3(x, y, 0);
|
|
outerVerts[i + 1] = new Vector3(x, y, 0);
|
|
outerUVs[i] = new Vector2(0, 1);
|
|
outerUVs[i + 1] = new Vector2(1, 1);
|
|
|
|
innerVerts[i] = new Vector3(x, y, 0);
|
|
innerVerts[i + 1] = new Vector3(x, y, 0);
|
|
innerUVs[i] = new Vector2(0, 1);
|
|
innerUVs[i + 1] = new Vector2(1, 0);
|
|
|
|
int ti = i * 3;
|
|
tris[ti] = i;
|
|
tris[ti + 1] = i + 1;
|
|
tris[ti + 2] = (i + 2) % triangleCount;
|
|
tris[ti + 3] = i + 1;
|
|
tris[ti + 4] = (i + 3) % triangleCount;
|
|
tris[ti + 5] = (i + 2) % triangleCount;
|
|
}
|
|
|
|
if (_OpaqueMesh != null)
|
|
{
|
|
DestroyImmediate(_OpaqueMesh);
|
|
}
|
|
|
|
if (_TransparentMesh != null)
|
|
{
|
|
DestroyImmediate(_TransparentMesh);
|
|
}
|
|
|
|
_OpaqueMesh = new Mesh()
|
|
{
|
|
name = "Opaque Vignette Mesh",
|
|
hideFlags = HideFlags.HideAndDontSave
|
|
};
|
|
_TransparentMesh = new Mesh()
|
|
{
|
|
name = "Transparent Vignette Mesh",
|
|
hideFlags = HideFlags.HideAndDontSave
|
|
};
|
|
|
|
_OpaqueMesh.vertices = outerVerts;
|
|
_OpaqueMesh.uv = outerUVs;
|
|
_OpaqueMesh.triangles = tris;
|
|
_OpaqueMesh.UploadMeshData(true);
|
|
_OpaqueMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 10000);
|
|
_OpaqueMeshFilter.sharedMesh = _OpaqueMesh;
|
|
|
|
_TransparentMesh.vertices = innerVerts;
|
|
_TransparentMesh.uv = innerUVs;
|
|
_TransparentMesh.triangles = tris;
|
|
_TransparentMesh.UploadMeshData(true);
|
|
_TransparentMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 10000);
|
|
_TransparentMeshFilter.sharedMesh = _TransparentMesh;
|
|
}
|
|
|
|
private void BuildMaterials()
|
|
{
|
|
#if UNITY_EDITOR
|
|
_InitialFalloff = Falloff;
|
|
#endif
|
|
if (VignetteShader == null)
|
|
{
|
|
VignetteShader = Shader.Find("Oculus/OVRVignette");
|
|
}
|
|
if (VignetteShader == null)
|
|
{
|
|
Debug.LogError("Could not find Vignette Shader! Vignette will not be drawn!");
|
|
return;
|
|
}
|
|
|
|
if (_OpaqueMaterial == null)
|
|
{
|
|
_OpaqueMaterial = new Material(VignetteShader)
|
|
{
|
|
name = "Opaque Vignette Material",
|
|
hideFlags = HideFlags.HideAndDontSave,
|
|
renderQueue = (int)RenderQueue.Background
|
|
};
|
|
_OpaqueMaterial.SetFloat("_BlendSrc", (float)BlendMode.One);
|
|
_OpaqueMaterial.SetFloat("_BlendDst", (float)BlendMode.Zero);
|
|
_OpaqueMaterial.SetFloat("_ZWrite", 1);
|
|
}
|
|
_OpaqueMeshRenderer.sharedMaterial = _OpaqueMaterial;
|
|
|
|
if (_TransparentMaterial == null)
|
|
{
|
|
_TransparentMaterial = new Material(VignetteShader)
|
|
{
|
|
name = "Transparent Vignette Material",
|
|
hideFlags = HideFlags.HideAndDontSave,
|
|
renderQueue = (int)RenderQueue.Overlay
|
|
};
|
|
|
|
_TransparentMaterial.SetFloat("_BlendSrc", (float)BlendMode.SrcAlpha);
|
|
_TransparentMaterial.SetFloat("_BlendDst", (float)BlendMode.OneMinusSrcAlpha);
|
|
_TransparentMaterial.SetFloat("_ZWrite", 0);
|
|
}
|
|
|
|
if (Falloff == FalloffType.Quadratic)
|
|
{
|
|
_TransparentMaterial.EnableKeyword(QUADRATIC_FALLOFF);
|
|
}
|
|
else
|
|
{
|
|
_TransparentMaterial.DisableKeyword(QUADRATIC_FALLOFF);
|
|
}
|
|
_TransparentMeshRenderer.sharedMaterial = _TransparentMaterial;
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
#if UNITY_2019_1_OR_NEWER
|
|
RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
|
|
#elif UNITY_2018_1_OR_NEWER
|
|
UnityEngine.Experimental.Rendering.RenderPipeline.beginCameraRendering += OnBeginCameraRendering;
|
|
#endif
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
#if UNITY_2019_1_OR_NEWER
|
|
RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
|
|
#elif UNITY_2018_1_OR_NEWER
|
|
UnityEngine.Experimental.Rendering.RenderPipeline.beginCameraRendering -= OnBeginCameraRendering;
|
|
#endif
|
|
DisableRenderers();
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
_Camera = GetComponent<Camera>();
|
|
_ShaderScaleAndOffset0Property = Shader.PropertyToID("_ScaleAndOffset0");
|
|
_ShaderScaleAndOffset1Property = Shader.PropertyToID("_ScaleAndOffset1");
|
|
|
|
GameObject opaqueObject = new GameObject("Opaque Vignette") { hideFlags = HideFlags.HideAndDontSave };
|
|
opaqueObject.transform.SetParent(_Camera.transform, false);
|
|
_OpaqueMeshFilter = opaqueObject.AddComponent<MeshFilter>();
|
|
_OpaqueMeshRenderer = opaqueObject.AddComponent<MeshRenderer>();
|
|
|
|
_OpaqueMeshRenderer.receiveShadows = false;
|
|
_OpaqueMeshRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
|
_OpaqueMeshRenderer.lightProbeUsage = LightProbeUsage.Off;
|
|
_OpaqueMeshRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
|
|
_OpaqueMeshRenderer.allowOcclusionWhenDynamic = false;
|
|
_OpaqueMeshRenderer.enabled = false;
|
|
|
|
GameObject transparentObject = new GameObject("Transparent Vignette") { hideFlags = HideFlags.HideAndDontSave };
|
|
transparentObject.transform.SetParent(_Camera.transform, false);
|
|
_TransparentMeshFilter = transparentObject.AddComponent<MeshFilter>();
|
|
_TransparentMeshRenderer = transparentObject.AddComponent<MeshRenderer>();
|
|
|
|
_TransparentMeshRenderer.receiveShadows = false;
|
|
_TransparentMeshRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
|
_TransparentMeshRenderer.lightProbeUsage = LightProbeUsage.Off;
|
|
_TransparentMeshRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
|
|
_TransparentMeshRenderer.allowOcclusionWhenDynamic = false;
|
|
_TransparentMeshRenderer.enabled = false;
|
|
|
|
BuildMeshes();
|
|
BuildMaterials();
|
|
}
|
|
|
|
private void GetTanFovAndOffsetForStereoEye(Camera.StereoscopicEye eye, out float tanFovX, out float tanFovY, out float offsetX, out float offsetY)
|
|
{
|
|
var pt = _Camera.GetStereoProjectionMatrix(eye).transpose;
|
|
|
|
var right = pt * new Vector4(-1, 0, 0, 1);
|
|
var left = pt * new Vector4(1, 0, 0, 1);
|
|
var up = pt * new Vector4(0, -1, 0, 1);
|
|
var down = pt * new Vector4(0, 1, 0, 1);
|
|
|
|
float rightTanFovX = right.z / right.x;
|
|
float leftTanFovX = left.z / left.x;
|
|
float upTanFovY = up.z / up.y;
|
|
float downTanFovY = down.z / down.y;
|
|
|
|
offsetX = -(rightTanFovX + leftTanFovX) / 2;
|
|
offsetY = -(upTanFovY + downTanFovY) / 2;
|
|
|
|
tanFovX = (rightTanFovX - leftTanFovX) / 2;
|
|
tanFovY = (upTanFovY - downTanFovY) / 2;
|
|
}
|
|
|
|
private void GetTanFovAndOffsetForMonoEye(out float tanFovX, out float tanFovY, out float offsetX, out float offsetY)
|
|
{
|
|
// When calculating from Unity's camera fields, this is the calculation used.
|
|
// We can't use this for stereo eyes because VR projection matrices are usually asymmetric.
|
|
tanFovY = Mathf.Tan(Mathf.Deg2Rad * _Camera.fieldOfView * 0.5f);
|
|
tanFovX = tanFovY * _Camera.aspect;
|
|
offsetX = 0f;
|
|
offsetY = 0f;
|
|
}
|
|
|
|
private bool VisibilityTest(float scaleX, float scaleY, float offsetX, float offsetY)
|
|
{
|
|
// because the corners of our viewport are the furthest from the center of our vignette,
|
|
// we only need to test that the farthest corner is outside the vignette ring.
|
|
return new Vector2((1 + Mathf.Abs(offsetX)) / scaleX, (1 + Mathf.Abs(offsetY)) / scaleY).sqrMagnitude > 1.0f;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (MeshComplexity != _InitialMeshComplexity)
|
|
{
|
|
// rebuild meshes
|
|
BuildMeshes();
|
|
}
|
|
|
|
if(Falloff != _InitialFalloff)
|
|
{
|
|
// rebuild materials
|
|
BuildMaterials();
|
|
}
|
|
#endif
|
|
|
|
// The opaque material could not be created, so just return
|
|
if (_OpaqueMaterial == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float tanInnerFovY = Mathf.Tan(VignetteFieldOfView * Mathf.Deg2Rad * 0.5f);
|
|
float tanInnerFovX = tanInnerFovY * VignetteAspectRatio;
|
|
float tanMiddleFovX = Mathf.Tan((VignetteFieldOfView + VignetteFalloffDegrees) * Mathf.Deg2Rad * 0.5f);
|
|
float tanMiddleFovY = tanMiddleFovX * VignetteAspectRatio;
|
|
|
|
_TransparentVignetteVisible = false;
|
|
_OpaqueVignetteVisible = false;
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
float tanFovX, tanFovY, offsetX, offsetY;
|
|
if (_Camera.stereoEnabled)
|
|
{
|
|
GetTanFovAndOffsetForStereoEye((Camera.StereoscopicEye)i, out tanFovX, out tanFovY, out offsetX, out offsetY);
|
|
}
|
|
else
|
|
{
|
|
GetTanFovAndOffsetForMonoEye(out tanFovX, out tanFovY, out offsetX, out offsetY);
|
|
}
|
|
|
|
float borderScale = new Vector2((1 + Mathf.Abs(offsetX)) / VignetteAspectRatio, 1 + Mathf.Abs(offsetY)).magnitude * 1.01f;
|
|
|
|
float innerScaleX = tanInnerFovX / tanFovX;
|
|
float innerScaleY = tanInnerFovY / tanFovY;
|
|
float middleScaleX = tanMiddleFovX / tanFovX;
|
|
float middleScaleY = tanMiddleFovY / tanFovY;
|
|
float outerScaleX = borderScale * VignetteAspectRatio;
|
|
float outerScaleY = borderScale;
|
|
|
|
// test for visibility.
|
|
_TransparentVignetteVisible |= VisibilityTest(innerScaleX, innerScaleY, offsetX, offsetY);
|
|
_OpaqueVignetteVisible |= VisibilityTest(middleScaleX, middleScaleY, offsetX, offsetY);
|
|
|
|
_OpaqueScaleAndOffset0[i] = new Vector4(outerScaleX, outerScaleY, offsetX, offsetY);
|
|
_OpaqueScaleAndOffset1[i] = new Vector4(middleScaleX, middleScaleY, offsetX, offsetY);
|
|
_TransparentScaleAndOffset0[i] = new Vector4(middleScaleX, middleScaleY, offsetX, offsetY);
|
|
_TransparentScaleAndOffset1[i] = new Vector4(innerScaleX, innerScaleY, offsetX, offsetY);
|
|
}
|
|
|
|
// if the vignette falloff is less than or equal to zero, we don't need to draw
|
|
// the transparent mesh.
|
|
_TransparentVignetteVisible &= VignetteFalloffDegrees > 0.0f;
|
|
|
|
_OpaqueMaterial.SetVectorArray(_ShaderScaleAndOffset0Property, _OpaqueScaleAndOffset0);
|
|
_OpaqueMaterial.SetVectorArray(_ShaderScaleAndOffset1Property, _OpaqueScaleAndOffset1);
|
|
_OpaqueMaterial.color = VignetteColor;
|
|
_TransparentMaterial.SetVectorArray(_ShaderScaleAndOffset0Property, _TransparentScaleAndOffset0);
|
|
_TransparentMaterial.SetVectorArray(_ShaderScaleAndOffset1Property, _TransparentScaleAndOffset1);
|
|
_TransparentMaterial.color = VignetteColor;
|
|
}
|
|
|
|
private void EnableRenderers()
|
|
{
|
|
_OpaqueMeshRenderer.enabled = _OpaqueVignetteVisible;
|
|
_TransparentMeshRenderer.enabled = _TransparentVignetteVisible;
|
|
}
|
|
|
|
private void DisableRenderers()
|
|
{
|
|
_OpaqueMeshRenderer.enabled = false;
|
|
_TransparentMeshRenderer.enabled = false;
|
|
}
|
|
|
|
// Objects are enabled on pre cull and disabled on post render so they only draw in this camera
|
|
private void OnPreCull()
|
|
{
|
|
EnableRenderers();
|
|
}
|
|
|
|
private void OnPostRender()
|
|
{
|
|
DisableRenderers();
|
|
}
|
|
|
|
#if UNITY_2019_1_OR_NEWER
|
|
private void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
|
|
#else
|
|
private void OnBeginCameraRendering(Camera camera)
|
|
#endif
|
|
{
|
|
if (camera == _Camera)
|
|
{
|
|
EnableRenderers();
|
|
}
|
|
else
|
|
{
|
|
DisableRenderers();
|
|
}
|
|
}
|
|
}
|