Search Unity

  1. Unity wants to learn about your experiences working on a Unity project today. We'd like to hear from you via this survey.
    Dismiss Notice
  2. Read here for Unity's latest plans on OpenXR.
    Dismiss Notice
  3. All Pro and Enterprise subscribers: find helpful & inspiring creative, tech, and business know-how in the new Unity Success Hub. Sign in to stay up to date.
    Dismiss Notice
  4. Dismiss Notice

Mirror Reflections in VR

Discussion in 'AR/VR (XR) Discussion' started by BLMSTN, Jul 18, 2016.

  1. BLMSTN

    BLMSTN

    Joined:
    Jul 18, 2016
    Posts:
    1
    Hej there,

    I'm currently working on a little VR project that needs to have mirror reflections. I'm using this script: http://wiki.unity3d.com/index.php/MirrorReflection4 for the reflections.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. // This is in fact just the Water script from Pro Standard Assets,
    5. // just with refraction stuff removed.
    6.  
    7. [ExecuteInEditMode] // Make mirror live-update even when not in play mode
    8. public class MirrorReflection : MonoBehaviour {
    9.     public bool m_DisablePixelLights = true;
    10.     public int m_TextureSize = 256;
    11.     public float m_ClipPlaneOffset = 0.07f;
    12.  
    13.     public LayerMask m_ReflectLayers = -1;
    14.  
    15.     private Hashtable m_ReflectionCameras = new Hashtable(); // Camera -> Camera table
    16.  
    17.     private RenderTexture m_ReflectionTexture = null;
    18.     private int m_OldReflectionTextureSize = 0;
    19.  
    20.     private static bool s_InsideRendering = false;
    21.  
    22.     // This is called when it's known that the object will be rendered by some
    23.     // camera. We render reflections and do other updates here.
    24.     // Because the script executes in edit mode, reflections for the scene view
    25.     // camera will just work!
    26.     public void OnWillRenderObject() {
    27.         var rend = GetComponent<Renderer>();
    28.         if (!enabled || !rend || !rend.sharedMaterial || !rend.enabled)
    29.             return;
    30.  
    31.         Camera cam = Camera.current;
    32.         if (!cam)
    33.             return;
    34.  
    35.         // Safeguard from recursive reflections.      
    36.         if (s_InsideRendering)
    37.             return;
    38.         s_InsideRendering = true;
    39.  
    40.         Camera reflectionCamera;
    41.         CreateMirrorObjects(cam, out reflectionCamera);
    42.  
    43.         // find out the reflection plane: position and normal in world space
    44.         Vector3 pos = transform.position;
    45.         Vector3 normal = transform.up;
    46.  
    47.         // Optionally disable pixel lights for reflection
    48.         int oldPixelLightCount = QualitySettings.pixelLightCount;
    49.         if (m_DisablePixelLights)
    50.             QualitySettings.pixelLightCount = 0;
    51.  
    52.         UpdateCameraModes(cam, reflectionCamera);
    53.  
    54.         // Render reflection
    55.         // Reflect camera around reflection plane
    56.         float d = -Vector3.Dot(normal, pos) - m_ClipPlaneOffset;
    57.         Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
    58.  
    59.         Matrix4x4 reflection = Matrix4x4.zero;
    60.         CalculateReflectionMatrix(ref reflection, reflectionPlane);
    61.         Vector3 oldpos = cam.transform.position;
    62.         Vector3 newpos = reflection.MultiplyPoint(oldpos);
    63.         reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
    64.  
    65.         // Setup oblique projection matrix so that near plane is our reflection
    66.         // plane. This way we clip everything below/above it for free.
    67.         Vector4 clipPlane = CameraSpacePlane(reflectionCamera, pos, normal, 1.0f);
    68.         //Matrix4x4 projection = cam.projectionMatrix;
    69.         Matrix4x4 projection = cam.CalculateObliqueMatrix(clipPlane);
    70.         reflectionCamera.projectionMatrix = projection;
    71.  
    72.         reflectionCamera.cullingMask = ~(1 << 4) & m_ReflectLayers.value; // never render water layer
    73.         reflectionCamera.targetTexture = m_ReflectionTexture;
    74.         GL.invertCulling = true;
    75.         reflectionCamera.transform.position = newpos;
    76.         Vector3 euler = cam.transform.eulerAngles;
    77.         reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
    78.         reflectionCamera.Render();
    79.         reflectionCamera.transform.position = oldpos;
    80.         GL.invertCulling = false;
    81.         Material[] materials = rend.sharedMaterials;
    82.         foreach (Material mat in materials) {
    83.             if (mat.HasProperty("_ReflectionTex"))
    84.                 mat.SetTexture("_ReflectionTex", m_ReflectionTexture);
    85.         }
    86.  
    87.         // Restore pixel light count
    88.         if (m_DisablePixelLights)
    89.             QualitySettings.pixelLightCount = oldPixelLightCount;
    90.  
    91.         s_InsideRendering = false;
    92.     }
    93.  
    94.  
    95.     // Cleanup all the objects we possibly have created
    96.     void OnDisable() {
    97.         if (m_ReflectionTexture) {
    98.             DestroyImmediate(m_ReflectionTexture);
    99.             m_ReflectionTexture = null;
    100.         }
    101.         foreach (DictionaryEntry kvp in m_ReflectionCameras)
    102.             DestroyImmediate(((Camera)kvp.Value).gameObject);
    103.         m_ReflectionCameras.Clear();
    104.     }
    105.  
    106.  
    107.     private void UpdateCameraModes(Camera src, Camera dest) {
    108.         if (dest == null)
    109.             return;
    110.         // set camera to clear the same way as current camera
    111.         dest.clearFlags = src.clearFlags;
    112.         dest.backgroundColor = src.backgroundColor;
    113.         if (src.clearFlags == CameraClearFlags.Skybox) {
    114.             Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
    115.             Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
    116.             if (!sky || !sky.material) {
    117.                 mysky.enabled = false;
    118.             } else {
    119.                 mysky.enabled = true;
    120.                 mysky.material = sky.material;
    121.             }
    122.         }
    123.         // update other values to match current camera.
    124.         // even if we are supplying custom camera&projection matrices,
    125.         // some of values are used elsewhere (e.g. skybox uses far plane)
    126.         dest.farClipPlane = src.farClipPlane;
    127.         dest.nearClipPlane = src.nearClipPlane;
    128.         dest.orthographic = src.orthographic;
    129.         dest.fieldOfView = src.fieldOfView;
    130.         dest.aspect = src.aspect;
    131.         dest.orthographicSize = src.orthographicSize;
    132.     }
    133.  
    134.     // On-demand create any objects we need
    135.     private void CreateMirrorObjects(Camera currentCamera, out Camera reflectionCamera) {
    136.         reflectionCamera = null;
    137.  
    138.         // Reflection render texture
    139.         if (!m_ReflectionTexture || m_OldReflectionTextureSize != m_TextureSize) {
    140.             if (m_ReflectionTexture)
    141.                 DestroyImmediate(m_ReflectionTexture);
    142.             m_ReflectionTexture = new RenderTexture(m_TextureSize, m_TextureSize, 16);
    143.             m_ReflectionTexture.name = "__MirrorReflection" + GetInstanceID();
    144.             m_ReflectionTexture.isPowerOfTwo = true;
    145.             m_ReflectionTexture.hideFlags = HideFlags.DontSave;
    146.             m_OldReflectionTextureSize = m_TextureSize;
    147.         }
    148.  
    149.         // Camera for reflection
    150.         reflectionCamera = m_ReflectionCameras[currentCamera] as Camera;
    151.         if (!reflectionCamera) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
    152.         {
    153.             GameObject go = new GameObject("Mirror Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox));
    154.             reflectionCamera = go.GetComponent<Camera>();
    155.             reflectionCamera.enabled = false;
    156.             reflectionCamera.transform.position = transform.position;
    157.             reflectionCamera.transform.rotation = transform.rotation;
    158.             reflectionCamera.gameObject.AddComponent<FlareLayer>();
    159.             go.hideFlags = HideFlags.HideAndDontSave;
    160.             m_ReflectionCameras[currentCamera] = reflectionCamera;
    161.         }
    162.     }
    163.  
    164.     // Extended sign: returns -1, 0 or 1 based on sign of a
    165.     private static float sgn(float a) {
    166.         if (a > 0.0f) return 1.0f;
    167.         if (a < 0.0f) return -1.0f;
    168.         return 0.0f;
    169.     }
    170.  
    171.     // Given position/normal of the plane, calculates plane in camera space.
    172.     private Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign) {
    173.         Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
    174.         Matrix4x4 m = cam.worldToCameraMatrix;
    175.         Vector3 cpos = m.MultiplyPoint(offsetPos);
    176.         Vector3 cnormal = m.MultiplyVector(normal).normalized * sideSign;
    177.         return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
    178.     }
    179.  
    180.     // Calculates reflection matrix around the given plane
    181.     private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane) {
    182.         reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
    183.         reflectionMat.m01 = (-2F * plane[0] * plane[1]);
    184.         reflectionMat.m02 = (-2F * plane[0] * plane[2]);
    185.         reflectionMat.m03 = (-2F * plane[3] * plane[0]);
    186.  
    187.         reflectionMat.m10 = (-2F * plane[1] * plane[0]);
    188.         reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
    189.         reflectionMat.m12 = (-2F * plane[1] * plane[2]);
    190.         reflectionMat.m13 = (-2F * plane[3] * plane[1]);
    191.  
    192.         reflectionMat.m20 = (-2F * plane[2] * plane[0]);
    193.         reflectionMat.m21 = (-2F * plane[2] * plane[1]);
    194.         reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
    195.         reflectionMat.m23 = (-2F * plane[3] * plane[2]);
    196.  
    197.         reflectionMat.m30 = 0F;
    198.         reflectionMat.m31 = 0F;
    199.         reflectionMat.m32 = 0F;
    200.         reflectionMat.m33 = 1F;
    201.     }
    202. }
    Everything is working fine until i use my HTC Vive. The Reflections in the mirror seem to drift apart depending on my SteamVR's CameraPosition. The further I move away from the mirror the more the reflections are drifting apart and vise versa. I googled for a while and read something about the vergence of the eyes but i don't know how to implement such things in the script.

    -Any help is appreciated
     
    Pillo likes this.
  2. dariony

    dariony

    Joined:
    May 16, 2015
    Posts:
    18
    for stereo mirror reflection see https://www.assetstore.unity3d.com/en/#!/content/71255

     
    jk15, osmoberlin and fffMalzbier like this.
  3. aeroson

    aeroson

    Joined:
    Jul 20, 2013
    Posts:
    23
    Since this was the first post about VR mirror I found when Googling this issue.
    Here is modified http://wiki.unity3d.com/index.php/MirrorReflection4 that works acceptably in VR, it uses ideas from: https://github.com/AADProductions/VR-Mirrors/blob/master/Assets/MirrorEffect/MirrorCamera.cs

    ShaderLab - Mirror.shader
    Code (CSharp):
    1. // original source from: http://wiki.unity3d.com/index.php/MirrorReflection4
    2. Shader "FX/MirrorReflection"
    3. {
    4.     Properties
    5.     {
    6.         _MainTex ("_MainTex", 2D) = "white" {}
    7.         _ReflectionTexLeft ("_ReflectionTexLeft", 2D) = "white" {}
    8.         _ReflectionTexRight ("_ReflectionTexRight", 2D) = "white" {}
    9.     }
    10.     SubShader
    11.     {
    12.         Tags { "RenderType"="Opaque" }
    13.         LOD 100
    14.         Pass {
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.             #include "UnityCG.cginc"
    19.             struct v2f
    20.             {
    21.                 float2 uv : TEXCOORD0;
    22.                 float4 refl : TEXCOORD1;
    23.                 float4 pos : SV_POSITION;
    24.             };
    25.             float4 _MainTex_ST;
    26.             v2f vert(float4 pos : POSITION, float2 uv : TEXCOORD0)
    27.             {
    28.                 v2f o;
    29.                 o.pos = UnityObjectToClipPos (pos);
    30.                 o.uv = TRANSFORM_TEX(uv, _MainTex);
    31.                 o.refl = ComputeScreenPos (o.pos);
    32.                 return o;
    33.             }
    34.             sampler2D _MainTex;
    35.             sampler2D _ReflectionTexLeft;
    36.             sampler2D _ReflectionTexRight;
    37.             fixed4 frag(v2f i) : SV_Target
    38.             {
    39.                 fixed4 tex = tex2D(_MainTex, i.uv);
    40.                 fixed4 refl;
    41.                 if (unity_StereoEyeIndex == 0) refl = tex2Dproj(_ReflectionTexLeft, UNITY_PROJ_COORD(i.refl));
    42.                 else refl = tex2Dproj(_ReflectionTexRight, UNITY_PROJ_COORD(i.refl));
    43.                 return tex * refl;
    44.             }
    45.             ENDCG
    46.         }
    47.     }
    48. }
    C# - MirrorReflection.cs
    Code (CSharp):
    1. // original source from: http://wiki.unity3d.com/index.php/MirrorReflection4
    2.  
    3. using UnityEngine;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. // This is in fact just the Water script from Pro Standard Assets,
    8. // just with refraction stuff removed.
    9.  
    10. [ExecuteInEditMode] // Make mirror live-update even when not in play mode
    11. public class MirrorReflection : MonoBehaviour
    12. {
    13.     public bool m_DisablePixelLights = true;
    14.     public int m_TextureSize = 256;
    15.     public float m_ClipPlaneOffset = 0.07f;
    16.     public int m_framesNeededToUpdate = 0;
    17.  
    18.     public LayerMask m_ReflectLayers = -1;
    19.  
    20.     private Dictionary<Camera, Camera> m_ReflectionCameras = new Dictionary<Camera, Camera>();
    21.  
    22.     private RenderTexture m_ReflectionTextureLeft = null;
    23.     private RenderTexture m_ReflectionTextureRight = null;
    24.     private int m_OldReflectionTextureSize = 0;
    25.  
    26.     private int m_frameCounter = 0;
    27.  
    28.     private static bool s_InsideRendering = false;
    29.  
    30.     // This is called when it's known that the object will be rendered by some
    31.     // camera. We render reflections and do other updates here.
    32.     // Because the script executes in edit mode, reflections for the scene view
    33.     // camera will just work!
    34.     public void OnWillRenderObject()
    35.     {
    36.         if (m_frameCounter > 0)
    37.         {
    38.             m_frameCounter--;
    39.             return;
    40.         }
    41.  
    42.         var rend = GetComponent<Renderer>();
    43.         if (!enabled || !rend || !rend.sharedMaterial || !rend.enabled)
    44.             return;
    45.  
    46.         Camera cam = Camera.current;
    47.         if (!cam)
    48.             return;
    49.  
    50.         // Safeguard from recursive reflections.    
    51.         if (s_InsideRendering)
    52.             return;
    53.         s_InsideRendering = true;
    54.  
    55.         m_frameCounter = m_framesNeededToUpdate;
    56.  
    57.         RenderCamera(cam, rend, Camera.StereoscopicEye.Left, ref m_ReflectionTextureLeft);
    58.         if (cam.stereoEnabled)
    59.             RenderCamera(cam, rend, Camera.StereoscopicEye.Right, ref m_ReflectionTextureRight);
    60.     }
    61.  
    62.     private void RenderCamera(Camera cam, Renderer rend, Camera.StereoscopicEye eye, ref RenderTexture reflectionTexture)
    63.     {
    64.         Camera reflectionCamera;
    65.         CreateMirrorObjects(cam, eye, out reflectionCamera, ref reflectionTexture);
    66.  
    67.         // find out the reflection plane: position and normal in world space
    68.         Vector3 pos = transform.position;
    69.         Vector3 normal = transform.up;
    70.  
    71.         // Optionally disable pixel lights for reflection
    72.         int oldPixelLightCount = QualitySettings.pixelLightCount;
    73.         if (m_DisablePixelLights)
    74.             QualitySettings.pixelLightCount = 0;
    75.  
    76.         CopyCameraProperties(cam, reflectionCamera);
    77.  
    78.         // Render reflection
    79.         // Reflect camera around reflection plane
    80.         float d = -Vector3.Dot(normal, pos) - m_ClipPlaneOffset;
    81.         Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
    82.  
    83.         Matrix4x4 reflection = Matrix4x4.zero;
    84.         CalculateReflectionMatrix(ref reflection, reflectionPlane);
    85.  
    86.         Vector3 oldEyePos;
    87.         Matrix4x4 worldToCameraMatrix;
    88.         if (cam.stereoEnabled)
    89.         {
    90.             worldToCameraMatrix = cam.GetStereoViewMatrix(eye) * reflection;
    91.  
    92.             var eyeOffset = SteamVR.instance.eyes[(int)eye].pos;
    93.             eyeOffset.z = 0.0f;
    94.             oldEyePos = cam.transform.position + cam.transform.TransformVector(eyeOffset);
    95.         }
    96.         else
    97.         {
    98.             worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
    99.             oldEyePos = cam.transform.position;
    100.         }
    101.  
    102.         Vector3 newEyePos = reflection.MultiplyPoint(oldEyePos);
    103.         reflectionCamera.transform.position = newEyePos;
    104.  
    105.         reflectionCamera.worldToCameraMatrix = worldToCameraMatrix;
    106.  
    107.         // Setup oblique projection matrix so that near plane is our reflection
    108.         // plane. This way we clip everything below/above it for free.
    109.         Vector4 clipPlane = CameraSpacePlane(worldToCameraMatrix, pos, normal, 1.0f);
    110.  
    111.         Matrix4x4 projectionMatrix;
    112.         if (cam.stereoEnabled) projectionMatrix = HMDMatrix4x4ToMatrix4x4(SteamVR.instance.hmd.GetProjectionMatrix((Valve.VR.EVREye)eye, cam.nearClipPlane, cam.farClipPlane));
    113.         else projectionMatrix = cam.projectionMatrix;
    114.      
    115.         //projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);
    116.         MakeProjectionMatrixOblique(ref projectionMatrix, clipPlane);
    117.  
    118.         reflectionCamera.projectionMatrix = projectionMatrix;
    119.         reflectionCamera.cullingMask = m_ReflectLayers.value;
    120.         reflectionCamera.targetTexture = reflectionTexture;
    121.         GL.invertCulling = true;
    122.         //Vector3 euler = cam.transform.eulerAngles;
    123.         //reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
    124.         reflectionCamera.transform.rotation = cam.transform.rotation;
    125.         reflectionCamera.Render();
    126.         //reflectionCamera.transform.position = oldEyePos;
    127.         GL.invertCulling = false;
    128.         Material[] materials = rend.sharedMaterials;
    129.         string property = "_ReflectionTex" + eye.ToString();
    130.         foreach (Material mat in materials)
    131.         {
    132.             if (mat.HasProperty(property))
    133.                 mat.SetTexture(property, reflectionTexture);
    134.         }
    135.  
    136.         // Restore pixel light count
    137.         if (m_DisablePixelLights)
    138.             QualitySettings.pixelLightCount = oldPixelLightCount;
    139.  
    140.         s_InsideRendering = false;
    141.     }
    142.  
    143.  
    144.     // Cleanup all the objects we possibly have created
    145.     void OnDisable()
    146.     {
    147.         if (m_ReflectionTextureLeft)
    148.         {
    149.             DestroyImmediate(m_ReflectionTextureLeft);
    150.             m_ReflectionTextureLeft = null;
    151.         }
    152.         if (m_ReflectionTextureRight)
    153.         {
    154.             DestroyImmediate(m_ReflectionTextureRight);
    155.             m_ReflectionTextureRight = null;
    156.         }
    157.         foreach (var kvp in m_ReflectionCameras)
    158.             DestroyImmediate(((Camera)kvp.Value).gameObject);
    159.         m_ReflectionCameras.Clear();
    160.     }
    161.  
    162.  
    163.     private void CopyCameraProperties(Camera src, Camera dest)
    164.     {
    165.         if (dest == null)
    166.             return;
    167.         // set camera to clear the same way as current camera
    168.         dest.clearFlags = src.clearFlags;
    169.         dest.backgroundColor = src.backgroundColor;
    170.         if (src.clearFlags == CameraClearFlags.Skybox)
    171.         {
    172.             Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
    173.             Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
    174.             if (!sky || !sky.material)
    175.             {
    176.                 mysky.enabled = false;
    177.             }
    178.             else
    179.             {
    180.                 mysky.enabled = true;
    181.                 mysky.material = sky.material;
    182.             }
    183.         }
    184.         // update other values to match current camera.
    185.         // even if we are supplying custom camera&projection matrices,
    186.         // some of values are used elsewhere (e.g. skybox uses far plane)
    187.         dest.farClipPlane = src.farClipPlane;
    188.         dest.nearClipPlane = src.nearClipPlane;
    189.         dest.orthographic = src.orthographic;
    190.         dest.fieldOfView = src.fieldOfView;
    191.         dest.aspect = src.aspect;
    192.         dest.orthographicSize = src.orthographicSize;
    193.     }
    194.  
    195.     // On-demand create any objects we need
    196.     private void CreateMirrorObjects(Camera currentCamera, Camera.StereoscopicEye eye, out Camera reflectionCamera, ref RenderTexture reflectionTexture)
    197.     {
    198.         reflectionCamera = null;
    199.  
    200.  
    201.         // Reflection render texture
    202.         if (!reflectionTexture || m_OldReflectionTextureSize != m_TextureSize)
    203.         {
    204.             if (reflectionTexture)
    205.                 DestroyImmediate(reflectionTexture);
    206.             reflectionTexture = new RenderTexture(m_TextureSize, m_TextureSize, 16);
    207.             reflectionTexture.name = "__MirrorReflection" + eye.ToString() + GetInstanceID();
    208.             reflectionTexture.isPowerOfTwo = true;
    209.             reflectionTexture.hideFlags = HideFlags.DontSave;
    210.             m_OldReflectionTextureSize = m_TextureSize;
    211.         }
    212.  
    213.         // Camera for reflection
    214.         if (!m_ReflectionCameras.TryGetValue(currentCamera, out reflectionCamera)) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
    215.         {
    216.             GameObject go = new GameObject("Mirror Reflection Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox));
    217.             reflectionCamera = go.GetComponent<Camera>();
    218.             reflectionCamera.enabled = false;
    219.             reflectionCamera.transform.position = transform.position;
    220.             reflectionCamera.transform.rotation = transform.rotation;
    221.             reflectionCamera.gameObject.AddComponent<FlareLayer>();
    222.             go.hideFlags = HideFlags.DontSave;
    223.             m_ReflectionCameras.Add(currentCamera, reflectionCamera);
    224.         }
    225.     }
    226.  
    227.     // Extended sign: returns -1, 0 or 1 based on sign of a
    228.     private static float sgn(float a)
    229.     {
    230.         if (a > 0.0f) return 1.0f;
    231.         if (a < 0.0f) return -1.0f;
    232.         return 0.0f;
    233.     }
    234.  
    235.     // Given position/normal of the plane, calculates plane in camera space.
    236.     private Vector4 CameraSpacePlane(Matrix4x4 worldToCameraMatrix, Vector3 pos, Vector3 normal, float sideSign)
    237.     {
    238.         Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
    239.         Vector3 cpos = worldToCameraMatrix.MultiplyPoint(offsetPos);
    240.         Vector3 cnormal = worldToCameraMatrix.MultiplyVector(normal).normalized * sideSign;
    241.         return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
    242.     }
    243.  
    244.     // Calculates reflection matrix around the given plane
    245.     private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
    246.     {
    247.         reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
    248.         reflectionMat.m01 = (-2F * plane[0] * plane[1]);
    249.         reflectionMat.m02 = (-2F * plane[0] * plane[2]);
    250.         reflectionMat.m03 = (-2F * plane[3] * plane[0]);
    251.  
    252.         reflectionMat.m10 = (-2F * plane[1] * plane[0]);
    253.         reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
    254.         reflectionMat.m12 = (-2F * plane[1] * plane[2]);
    255.         reflectionMat.m13 = (-2F * plane[3] * plane[1]);
    256.  
    257.         reflectionMat.m20 = (-2F * plane[2] * plane[0]);
    258.         reflectionMat.m21 = (-2F * plane[2] * plane[1]);
    259.         reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
    260.         reflectionMat.m23 = (-2F * plane[3] * plane[2]);
    261.  
    262.         reflectionMat.m30 = 0F;
    263.         reflectionMat.m31 = 0F;
    264.         reflectionMat.m32 = 0F;
    265.         reflectionMat.m33 = 1F;
    266.     }
    267.  
    268.     // taken from http://www.terathon.com/code/oblique.html
    269.     private static void MakeProjectionMatrixOblique(ref Matrix4x4 matrix, Vector4 clipPlane)
    270.     {
    271.         Vector4 q;
    272.  
    273.         // Calculate the clip-space corner point opposite the clipping plane
    274.         // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and
    275.         // transform it into camera space by multiplying it
    276.         // by the inverse of the projection matrix
    277.  
    278.         q.x = (sgn(clipPlane.x) + matrix[8]) / matrix[0];
    279.         q.y = (sgn(clipPlane.y) + matrix[9]) / matrix[5];
    280.         q.z = -1.0F;
    281.         q.w = (1.0F + matrix[10]) / matrix[14];
    282.  
    283.         // Calculate the scaled plane vector
    284.         Vector4 c = clipPlane * (2.0F / Vector3.Dot(clipPlane, q));
    285.  
    286.         // Replace the third row of the projection matrix
    287.         matrix[2] = c.x;
    288.         matrix[6] = c.y;
    289.         matrix[10] = c.z + 1.0F;
    290.         matrix[14] = c.w;
    291.     }
    292.  
    293.     protected Matrix4x4 HMDMatrix4x4ToMatrix4x4(Valve.VR.HmdMatrix44_t input)
    294.     {
    295.         var m = Matrix4x4.identity;
    296.  
    297.         m[0, 0] = input.m0;
    298.         m[0, 1] = input.m1;
    299.         m[0, 2] = input.m2;
    300.         m[0, 3] = input.m3;
    301.  
    302.         m[1, 0] = input.m4;
    303.         m[1, 1] = input.m5;
    304.         m[1, 2] = input.m6;
    305.         m[1, 3] = input.m7;
    306.  
    307.         m[2, 0] = input.m8;
    308.         m[2, 1] = input.m9;
    309.         m[2, 2] = input.m10;
    310.         m[2, 3] = input.m11;
    311.  
    312.         m[3, 0] = input.m12;
    313.         m[3, 1] = input.m13;
    314.         m[3, 2] = input.m14;
    315.         m[3, 3] = input.m15;
    316.  
    317.         return m;
    318.     }
    319.  
    320. }
     
  4. YouDesign

    YouDesign

    Joined:
    Jun 21, 2017
    Posts:
    55
    Version for Oculus Rift:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using UnityEngine.XR;
    6.  
    7.  
    8. // This is in fact just the Water script from Pro Standard Assets,
    9. // just with refraction stuff removed.
    10.  
    11. [ExecuteInEditMode] // Make mirror live-update even when not in play mode
    12. public class MirrorReflection : MonoBehaviour
    13. {
    14.     public bool m_DisablePixelLights = true;
    15.     public int m_TextureSize = 256;
    16.     public float m_ClipPlaneOffset = 0.07f;
    17.     public int m_framesNeededToUpdate = 0;
    18.  
    19.     public LayerMask m_ReflectLayers = -1;
    20.  
    21.     private Dictionary<Camera, Camera> m_ReflectionCameras = new Dictionary<Camera, Camera>();
    22.  
    23.     private RenderTexture m_ReflectionTextureLeft = null;
    24.     private RenderTexture m_ReflectionTextureRight = null;
    25.     private int m_OldReflectionTextureSize = 0;
    26.  
    27.     private int m_frameCounter = 0;
    28.  
    29.     private static bool s_InsideRendering = false;
    30.  
    31.     // This is called when it's known that the object will be rendered by some
    32.     // camera. We render reflections and do other updates here.
    33.     // Because the script executes in edit mode, reflections for the scene view
    34.     // camera will just work!
    35.     public void OnWillRenderObject()
    36.     {
    37.         if (m_frameCounter > 0)
    38.         {
    39.             m_frameCounter--;
    40.             return;
    41.         }
    42.  
    43.         var rend = GetComponent<Renderer>();
    44.         if (!enabled || !rend || !rend.sharedMaterial || !rend.enabled)
    45.             return;
    46.  
    47.         Camera cam = Camera.current;
    48.         if (!cam)
    49.             return;
    50.  
    51.         // Safeguard from recursive reflections.  
    52.         if (s_InsideRendering)
    53.             return;
    54.         s_InsideRendering = true;
    55.  
    56.         m_frameCounter = m_framesNeededToUpdate;
    57.  
    58.         RenderCamera(cam, rend, Camera.StereoscopicEye.Left, ref m_ReflectionTextureLeft);
    59.         if (cam.stereoEnabled)
    60.             RenderCamera(cam, rend, Camera.StereoscopicEye.Right, ref m_ReflectionTextureRight);
    61.     }
    62.  
    63.     private void RenderCamera(Camera cam, Renderer rend, Camera.StereoscopicEye eye, ref RenderTexture reflectionTexture)
    64.     {
    65.         Camera reflectionCamera;
    66.         CreateMirrorObjects(cam, eye, out reflectionCamera, ref reflectionTexture);
    67.  
    68.         // find out the reflection plane: position and normal in world space
    69.         Vector3 pos = transform.position;
    70.         Vector3 normal = transform.up;
    71.  
    72.         // Optionally disable pixel lights for reflection
    73.         int oldPixelLightCount = QualitySettings.pixelLightCount;
    74.         if (m_DisablePixelLights)
    75.             QualitySettings.pixelLightCount = 0;
    76.  
    77.         CopyCameraProperties(cam, reflectionCamera);
    78.  
    79.         // Render reflection
    80.         // Reflect camera around reflection plane
    81.         float d = -Vector3.Dot(normal, pos) - m_ClipPlaneOffset;
    82.         Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
    83.  
    84.         Matrix4x4 reflection = Matrix4x4.zero;
    85.         CalculateReflectionMatrix(ref reflection, reflectionPlane);
    86.  
    87.         Vector3 oldEyePos;
    88.         Matrix4x4 worldToCameraMatrix;
    89.         if (cam.stereoEnabled)
    90.         {
    91.             worldToCameraMatrix = cam.GetStereoViewMatrix(eye) * reflection;
    92.             Vector3 eyeOffset;
    93.             if (eye == Camera.StereoscopicEye.Left)
    94.                 eyeOffset = InputTracking.GetLocalPosition(XRNode.LeftEye);
    95.             else
    96.                 eyeOffset = InputTracking.GetLocalPosition(XRNode.RightEye);
    97.             eyeOffset.z = 0.0f;
    98.             oldEyePos = cam.transform.position + cam.transform.TransformVector(eyeOffset);
    99.         }
    100.         else
    101.         {
    102.             worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
    103.             oldEyePos = cam.transform.position;
    104.         }
    105.  
    106.         Vector3 newEyePos = reflection.MultiplyPoint(oldEyePos);
    107.         reflectionCamera.transform.position = newEyePos;
    108.  
    109.         reflectionCamera.worldToCameraMatrix = worldToCameraMatrix;
    110.  
    111.         // Setup oblique projection matrix so that near plane is our reflection
    112.         // plane. This way we clip everything below/above it for free.
    113.         Vector4 clipPlane = CameraSpacePlane(worldToCameraMatrix, pos, normal, 1.0f);
    114.  
    115.         Matrix4x4 projectionMatrix;
    116.  
    117.  
    118.  
    119.         //if (cam.stereoEnabled) projectionMatrix = HMDMatrix4x4ToMatrix4x4(cam.GetStereoProjectionMatrix(Camera.StereoscopicEye.Left));
    120.         //else
    121.         //if (cam.stereoEnabled)
    122.         //    projectionMatrix = HMDMatrix4x4ToMatrix4x4(SteamVR.instance.hmd.GetProjectionMatrix((Valve.VR.EVREye)eye, cam.nearClipPlane, cam.farClipPlane));
    123.         //else
    124.         if (cam.stereoEnabled)
    125.             projectionMatrix = cam.GetStereoProjectionMatrix(eye);
    126.         else
    127.             projectionMatrix = cam.projectionMatrix;
    128.         //projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);
    129.         MakeProjectionMatrixOblique(ref projectionMatrix, clipPlane);
    130.  
    131.         reflectionCamera.projectionMatrix = projectionMatrix;
    132.         reflectionCamera.cullingMask = m_ReflectLayers.value;
    133.         reflectionCamera.targetTexture = reflectionTexture;
    134.         GL.invertCulling = true;
    135.         //Vector3 euler = cam.transform.eulerAngles;
    136.         //reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
    137.         reflectionCamera.transform.rotation = cam.transform.rotation;
    138.         reflectionCamera.Render();
    139.         //reflectionCamera.transform.position = oldEyePos;
    140.         GL.invertCulling = false;
    141.         Material[] materials = rend.sharedMaterials;
    142.         string property = "_ReflectionTex" + eye.ToString();
    143.         foreach (Material mat in materials)
    144.         {
    145.             if (mat.HasProperty(property))
    146.                 mat.SetTexture(property, reflectionTexture);
    147.         }
    148.  
    149.         // Restore pixel light count
    150.         if (m_DisablePixelLights)
    151.             QualitySettings.pixelLightCount = oldPixelLightCount;
    152.  
    153.         s_InsideRendering = false;
    154.     }
    155.  
    156.  
    157.     // Cleanup all the objects we possibly have created
    158.     void OnDisable()
    159.     {
    160.         if (m_ReflectionTextureLeft)
    161.         {
    162.             DestroyImmediate(m_ReflectionTextureLeft);
    163.             m_ReflectionTextureLeft = null;
    164.         }
    165.         if (m_ReflectionTextureRight)
    166.         {
    167.             DestroyImmediate(m_ReflectionTextureRight);
    168.             m_ReflectionTextureRight = null;
    169.         }
    170.         foreach (var kvp in m_ReflectionCameras)
    171.             DestroyImmediate(((Camera)kvp.Value).gameObject);
    172.         m_ReflectionCameras.Clear();
    173.     }
    174.  
    175.  
    176.     private void CopyCameraProperties(Camera src, Camera dest)
    177.     {
    178.         if (dest == null)
    179.             return;
    180.         // set camera to clear the same way as current camera
    181.         dest.clearFlags = src.clearFlags;
    182.         dest.backgroundColor = src.backgroundColor;
    183.         if (src.clearFlags == CameraClearFlags.Skybox)
    184.         {
    185.             Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
    186.             Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
    187.             if (!sky || !sky.material)
    188.             {
    189.                 mysky.enabled = false;
    190.             }
    191.             else
    192.             {
    193.                 mysky.enabled = true;
    194.                 mysky.material = sky.material;
    195.             }
    196.         }
    197.         // update other values to match current camera.
    198.         // even if we are supplying custom camera&projection matrices,
    199.         // some of values are used elsewhere (e.g. skybox uses far plane)
    200.         dest.farClipPlane = src.farClipPlane;
    201.         dest.nearClipPlane = src.nearClipPlane;
    202.         dest.orthographic = src.orthographic;
    203.         dest.fieldOfView = src.fieldOfView;
    204.         dest.aspect = src.aspect;
    205.         dest.orthographicSize = src.orthographicSize;
    206.     }
    207.  
    208.     // On-demand create any objects we need
    209.     private void CreateMirrorObjects(Camera currentCamera, Camera.StereoscopicEye eye, out Camera reflectionCamera, ref RenderTexture reflectionTexture)
    210.     {
    211.         reflectionCamera = null;
    212.  
    213.  
    214.         // Reflection render texture
    215.         if (!reflectionTexture || m_OldReflectionTextureSize != m_TextureSize)
    216.         {
    217.             if (reflectionTexture)
    218.                 DestroyImmediate(reflectionTexture);
    219.             reflectionTexture = new RenderTexture(m_TextureSize, m_TextureSize, 16);
    220.             reflectionTexture.name = "__MirrorReflection" + eye.ToString() + GetInstanceID();
    221.             reflectionTexture.isPowerOfTwo = true;
    222.             reflectionTexture.hideFlags = HideFlags.DontSave;
    223.             m_OldReflectionTextureSize = m_TextureSize;
    224.         }
    225.  
    226.         // Camera for reflection
    227.         if (!m_ReflectionCameras.TryGetValue(currentCamera, out reflectionCamera)) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
    228.         {
    229.             GameObject go = new GameObject("Mirror Reflection Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox));
    230.             reflectionCamera = go.GetComponent<Camera>();
    231.             reflectionCamera.enabled = false;
    232.             reflectionCamera.transform.position = transform.position;
    233.             reflectionCamera.transform.rotation = transform.rotation;
    234.             reflectionCamera.gameObject.AddComponent<FlareLayer>();
    235.             go.hideFlags = HideFlags.DontSave;
    236.             m_ReflectionCameras.Add(currentCamera, reflectionCamera);
    237.         }
    238.     }
    239.  
    240.     // Extended sign: returns -1, 0 or 1 based on sign of a
    241.     private static float sgn(float a)
    242.     {
    243.         if (a > 0.0f) return 1.0f;
    244.         if (a < 0.0f) return -1.0f;
    245.         return 0.0f;
    246.     }
    247.  
    248.     // Given position/normal of the plane, calculates plane in camera space.
    249.     private Vector4 CameraSpacePlane(Matrix4x4 worldToCameraMatrix, Vector3 pos, Vector3 normal, float sideSign)
    250.     {
    251.         Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
    252.         Vector3 cpos = worldToCameraMatrix.MultiplyPoint(offsetPos);
    253.         Vector3 cnormal = worldToCameraMatrix.MultiplyVector(normal).normalized * sideSign;
    254.         return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
    255.     }
    256.  
    257.     // Calculates reflection matrix around the given plane
    258.     private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
    259.     {
    260.         reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
    261.         reflectionMat.m01 = (-2F * plane[0] * plane[1]);
    262.         reflectionMat.m02 = (-2F * plane[0] * plane[2]);
    263.         reflectionMat.m03 = (-2F * plane[3] * plane[0]);
    264.  
    265.         reflectionMat.m10 = (-2F * plane[1] * plane[0]);
    266.         reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
    267.         reflectionMat.m12 = (-2F * plane[1] * plane[2]);
    268.         reflectionMat.m13 = (-2F * plane[3] * plane[1]);
    269.  
    270.         reflectionMat.m20 = (-2F * plane[2] * plane[0]);
    271.         reflectionMat.m21 = (-2F * plane[2] * plane[1]);
    272.         reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
    273.         reflectionMat.m23 = (-2F * plane[3] * plane[2]);
    274.  
    275.         reflectionMat.m30 = 0F;
    276.         reflectionMat.m31 = 0F;
    277.         reflectionMat.m32 = 0F;
    278.         reflectionMat.m33 = 1F;
    279.     }
    280.  
    281.     // taken from http://www.terathon.com/code/oblique.html
    282.     private static void MakeProjectionMatrixOblique(ref Matrix4x4 matrix, Vector4 clipPlane)
    283.     {
    284.         Vector4 q;
    285.  
    286.         // Calculate the clip-space corner point opposite the clipping plane
    287.         // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and
    288.         // transform it into camera space by multiplying it
    289.         // by the inverse of the projection matrix
    290.  
    291.         q.x = (sgn(clipPlane.x) + matrix[8]) / matrix[0];
    292.         q.y = (sgn(clipPlane.y) + matrix[9]) / matrix[5];
    293.         q.z = -1.0F;
    294.         q.w = (1.0F + matrix[10]) / matrix[14];
    295.  
    296.         // Calculate the scaled plane vector
    297.         Vector4 c = clipPlane * (2.0F / Vector3.Dot(clipPlane, q));
    298.  
    299.         // Replace the third row of the projection matrix
    300.         matrix[2] = c.x;
    301.         matrix[6] = c.y;
    302.         matrix[10] = c.z + 1.0F;
    303.         matrix[14] = c.w;
    304.     }
    305.  
    306. }
    307.  
     
    Last edited: Oct 12, 2018
    Shizola, P_Jong, krougeau and 3 others like this.
  5. raiwin

    raiwin

    Joined:
    Jun 24, 2016
    Posts:
    7
    I got this error:

    Assets/Scripts/MirrorReflection.cs(305,49): error CS0246: The type or namespace name `Valve' could not be found. Are you missing an assembly reference?

    Any clue?
     
    YouDesign likes this.
  6. YouDesign

    YouDesign

    Joined:
    Jun 21, 2017
    Posts:
    55
    raiwin likes this.
  7. raiwin

    raiwin

    Joined:
    Jun 24, 2016
    Posts:
    7
  8. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    25
    Has anyone been able to get this reflection script working in Single-Pass?
     
    ROBYER1 and P_Jong like this.
  9. Needle0

    Needle0

    Joined:
    Jul 20, 2013
    Posts:
    10
    I have another question: this solution seems to work by rendering reflections twice, once for each eye, which may be computationally expensive. In the use case I am working with, most of the reflected objects are very far away, making the stereo parallax between L/R negligible. In this case the second render would be redundant and making a mono reflection sufficient in theory. However, when I try to modify the script/shader so that the same rendertexture is applied to both eyes, the reflections appear incorrect when seen from inside the Oculus Rift (the two images appear offset wrong, especially when tilting your head to the right/left). This seems to specifically occur in Unity 2017.4.11 and later (The release notes for this version mention asymmetric FOV support for Rift; perhaps this is related). Is there a way to make mono reflections appear OK?
     
    Last edited: Jun 9, 2019
  10. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    25
    The shader offsets the UVs for the reflection based on the current Stereo Eye Index, because of the assumption that the images are different. You could try removing the offset altogether, or changing it to always use the offset of whatever eye you chose to use twice.
     
  11. Needle0

    Needle0

    Joined:
    Jul 20, 2013
    Posts:
    10
    I later found out that the asymmetric FOV rendering feature is indeed the cause, as it is specifically mentioned in Oculus documentation that some shaders will break: https://developer.oculus.com/blog/tech-note-asymmetric-field-of-view-faq/ Unfortunately, unlike some other shader-breaking optimizations like single pass stereo, I cannot find a way to disable asymmetric FOV.

    Pardon my not being very bright with shader code, but is it correct to assume the "UVs for the reflection" you mention is
    i.refl
    in the Mirror.shader code? Or is it some other value? I experimented around modifying the
    i.refl.xy
    values, but they never seem to appear right.
     
  12. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    25
    oh, my mistake! I've been using my own variant of this shader for a while now and I didn't realize that I had changed my versikon of the shader a bunch. The version of this shader that i've been using renders a single double-wide texture for the reflection and adjusts the UVs for single-pass stereo. The only issue is for some reason it's unable to reflect the skybox correctly, so I've been using a gigantic sphere for my skybox instead. Asymmetric FOV should only be needed for multi-pass, right?
     
  13. Needle0

    Needle0

    Joined:
    Jul 20, 2013
    Posts:
    10
    Ideally, everyone should benefit if it works both in single and multi-pass, but for the time being, the project I am working on is in multi-pass since more stuff begins to break if I switch it to single-pass.
     
  14. ROBYER1

    ROBYER1

    Joined:
    Oct 9, 2015
    Posts:
    1,168
    Is there an example of the working Single-pass version?
     
  15. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    25
    I'm having trouble locating the project files from when i did single-pass stereo mirrors, but I'm going to try to do it again in the next few days! If I do, I'll share the code here.
     
    AlessandroElifani likes this.
  16. ROBYER1

    ROBYER1

    Joined:
    Oct 9, 2015
    Posts:
    1,168
    You are an absolute hero, please do share your findings!
     
  17. LiminalTeam

    LiminalTeam

    Joined:
    Sep 17, 2019
    Posts:
    5
    Did anyone have a single pass version of mirrors working?
     
  18. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    25
    I do, somewhere! check back here again after the weekend, i'm hoping to re-do it by then.
     
    ROBYER1 likes this.
  19. Jichaels

    Jichaels

    Joined:
    Dec 27, 2018
    Posts:
    237
    I don't really undestand what you are trying to achieve, but can't you simply use a Camera with appropriate culling to render to a RenderTexture ? At least that's what I'm doing and it's working fine
     
  20. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    25
    update: I've been extremely busy! I'll do the thing... at some point. Sorry fam ;-;

    The normal method of planar reflections - with an oblique near clip plane on a second camera - works just fine for Multi-pass VR and non-VR. Multi-pass is the easiest and least performant of the stereo rendering modes; it renders the whole scene for left eye, and then again for the right eye, so each eye renders like a normal scene.

    In single-pass stereo, however, the left and right eye are done at the same time, using a double wide render texture containing the image for both eyes. This is nearly twice as fast - or multi-pass is twice as slow - making single-pass stereo the only real option for performance-constrained VR games, especially those on the Oculus Quest, or any PC VR that aims for a high graphical fidelity and realtime lighting.
    The problem is, the unusual projection required for that double-wide texture needs to be built into custom shaders and image effects manually. It's not too bad for most shaders, but for ray marching, reflection, projection, and pretty much anything that moves pixels around in screen-space, it's difficult or impossible to implement. Lots of effects don't work without being made specifically for single-pass stereo rendering, and planar reflections are one of them, due both the positioning of the reflection camera and the later projection of the reflected render texture.
     
    Tom-Goethals, jeromeWork and ROBYER1 like this.
  21. jeromeWork

    jeromeWork

    Joined:
    Sep 1, 2015
    Posts:
    380
    Thanks @swanickj That's a great explanation of why a second camera to a render texture just won't cut it for VR.

    Eagerly awaiting that single-pass stereo mirror magic :)
     
    swanickj likes this.
  22. KORTEN123

    KORTEN123

    Joined:
    Feb 19, 2019
    Posts:
    1
    Did you do something similar to this tutorial?

    That's essentially what I did but am having trouble getting the camera FOV and near clipping planes right. Keep seeing the backside of the wall the mirror is attached to and the reflection seems to have an improper perspective when I get too close.
     
  23. Nikud

    Nikud

    Joined:
    Dec 10, 2019
    Posts:
    2
    I have the same problem. I used the code from the very beginning of this tutorial and my mirror reflects the wall it is attached to. Has anyone a solution for this by now? Or would you recommend another vr mirror apporach?
     
  24. CheeseOmelette

    CheeseOmelette

    Joined:
    Jan 22, 2019
    Posts:
    10
    Hi, just to add to this... I was able to get the above code and shader to (somewhat) work in VR on the Oculus Rift, but it only works when the mirror is at a 90 degree angle. If I rotate it, the images on the left and right eye are completely different and make it impossible to see.

    Is there any suggestion for how to fix it? I am not familiar with rendering at all... I'm using multi-pass since others have had success with that.
     
    Last edited: Feb 6, 2020
  25. Leovc1

    Leovc1

    Joined:
    Mar 19, 2019
    Posts:
    1
    I'm having the same problem, any solutions?
     
  26. avandenberg

    avandenberg

    Joined:
    Nov 22, 2018
    Posts:
    23
    Same, still looking for a solution... :/
     
  27. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,256
    As @swanickj said ...


    ... 1. if you're doing multipass it should be easy (google it: there are a large number of people who have written tutorials on this specifically (ie non-VR) and there are many free implementations floating around the web, plus many cheap/paid solutions on the Asset Store). NB: some of those I've seen are just ... broken. So try a few!

    ... 2. If you're trying to do single-pass, better get good at writing hardcore shaders, my friend :). Because no-one else's shader is going to support this (unless they specifically design for it, and charge $$$ for it - it's a horrible chunk of code to write and maintain).

    (I'm currently doing option 2 for myself in my own water/lighting shader. I understand the math, but debugging it gives me headaches :))
     
  28. avandenberg

    avandenberg

    Joined:
    Nov 22, 2018
    Posts:
    23
    thank you so much for the reply :) I feel really stupid now. I misinterpreted aeroson's post. Applied those scripts and it works now! HELL YEAH <3

    The issue mentioned by @CheeseOmelette still persists though. I'll be diving a little deeper into the code this week, If I happen to find out why it happens I'll post it here.

    Edit: This is a screenshot of the issue: https://imgur.com/a/BGOfPFQ
     
    a436t4ataf likes this.
  29. avandenberg

    avandenberg

    Joined:
    Nov 22, 2018
    Posts:
    23
    Figured out the problem: upload_2020-3-4_16-54-26.png
    You need to make sure the plane on which the mirror effect is being rendered is in a seperate layer. Then you want to exclude this layer from rendering in the MirrorReflection Script. This fixes the issue entirely.
     
    a436t4ataf and codestage like this.
  30. avandenberg

    avandenberg

    Joined:
    Nov 22, 2018
    Posts:
    23
    Does anyone know how to port this to URP?

    Error message:
    Recursive rendering is not supported in SRP (are you calling Camera.Render from within a render pipeline?).
    UnityEngine.Camera:Render()

    When I do so anyway the scene view trips out hard. On the HMD (HTC Vive) Nothing at all is shown.

    Pfff just when I thought I had the solution >.<
    I need it in URP unfortunately :/
     
    Last edited: Mar 4, 2020
  31. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,256
    Honestly, any time I see someone using URP, I walk away. It doesn't work, it's not stable, it's not production-ready yet, it's not documented, and the shaders are horribly horribly messed up.

    (there's a lot of active discussion and complaints on this at the moment elsehwere on the forums. Personally: I'm about to launch a major graphical asset explicitly with NO SUPPORT For URP/HDRP because Unity has messed up the shader side so badly)

    You can make it work, but you will have to potentially rewrite your code for every single point release of Unity (that's how much they are breaking / changing it right now).

    /rant.

    TL;DR: friends don't let friends do non-trivial graphics in URP ... yet. Wait for Unity to fix it :).
     
    Hobodi and codestage like this.
  32. avandenberg

    avandenberg

    Joined:
    Nov 22, 2018
    Posts:
    23
    I actually feel URP is pretty close to done and it performs quite well with a lot of new and useful features, it does take some time to get used to though but I think it's def worth it as its going to be the new default in the near future anyway.

    Anyway, I got it to work. The key was to change the Camera.Render() to UniversalRenderPipeline.RenderSingleCamera(SRC, mirrorCamera);
    where SRC is the ScriptableRenderContext passed from the RenderPipeline.beginCameraRendering function.
     
  33. Tom-Goethals

    Tom-Goethals

    Joined:
    Mar 6, 2015
    Posts:
    97
    VRChat has it working..
    Found their shader on Github, made it work on quest. (non-commercial project)
    Perfect stereo mirror rendering.. heavy hit on performance though.
    Single Pass Double Wide Forward Rendered.
    Bit hesitant to share the link as i'm not sure the VRChat code is supposed to be open on the internet.
    Might just be from the open VRChat SDK.. not sure.
     
  34. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    25
    I agree. What i love about URP though is that the non-builtin pipelines are fully visible to me, and i can figure out or modify how anything works when i need to. URP makes some compromises for a few things in order to achieve greater performance than built-in can produce, and those make things tricky sometimes, but i haven't found anything I couldn't hack, yet. ... except double-grab-pass shaders, like a gaussian blur filter plane. I managed a really nice gaussian blur with some fancy blue noise scattering in one pass, though, so i have forgiven URP for this one
     
  35. AkilliMum

    AkilliMum

    Joined:
    Dec 6, 2014
    Posts:
    90
    I know you are looking for free scripts but i wrote mirroring for both standard and SRP. You can look here: Works on all passes with StandardRP, LWRP and URP (works on Quest, Rift and Rİft-S). They are more than just mirror :)
    https://assetstore.unity.com/publishers/14806
     
  36. codestage

    codestage

    Joined:
    Jul 27, 2012
    Posts:
    1,519
    It's a pity it does not works with Vive =/
     
  37. AkilliMum

    AkilliMum

    Joined:
    Dec 6, 2014
    Posts:
    90
    I could not get vive. I have to order it from outside of my country, but this corona thing broke my schedules. Please follow the thread, i will update when i get a vive .)
     
    codestage likes this.
  38. mcmount

    mcmount

    Joined:
    Nov 15, 2015
    Posts:
    60
    Sad thing is, I posted single pass reflection while back with custom features I added, like transparency, but I apparently the code I used from a public source was somehow "borrowed". So my post got deleted without any explanation.
     
  39. codestage

    codestage

    Joined:
    Jul 27, 2012
    Posts:
    1,519
    Oh, that's pretty confusing! Did you tried to share code somewhere else, like on github \ gist \ etc?

    Found this one as well, it works in Vive Single Pass but it has some strange behaviour on custom non-rectangular meshes with specific rotation \ scaling in my case.
    I'm not sure it's legal to share it as well, I also not sure it's legal to use it in own projects outside the VRChat SDK so not posting it here.
     
  40. Aaron-Meyers

    Aaron-Meyers

    Joined:
    Dec 8, 2009
    Posts:
    297
    I wonder if HDRP's Planar Reflection Probe will be brought to URP at some point. It makes adding mirror reflections incredibly easy...
     
  41. avandenberg

    avandenberg

    Joined:
    Nov 22, 2018
    Posts:
    23
    This would be absolutely amazing. I'm really wish they would allow the implementation of a lot of these HDRP functionality in URP, even if it'd be a simple port
     
    Shizola likes this.
  42. TheVirtualMunk

    TheVirtualMunk

    Joined:
    Sep 6, 2019
    Posts:
    52
    Any chance you could share the code? I can't seem to figure out how to get the render context - also I'm trying to get this to work with OVR instead of steam so the code from the previous posts is already messed up even more.
     
    ROBYER1 likes this.
  43. TheVirtualMunk

    TheVirtualMunk

    Joined:
    Sep 6, 2019
    Posts:
    52
    Meanwhile, if anyone is interested in an alternative solution, I've made a script for mirroring any gameobject along a plane. This method doesn't use any shaders and basically just clones the object and mirrors its transform relative to a plane. I used some information from this thread.

    I think its worth doing it like this when talking VR as it can be much more performant (especially on Quest which I'm aiming for). While it does create a layer for "the mirror world", baking lighting doesn't work (old unity bug), and setting up correct realtime lighting is a bit finicky, but for mobile VR realtime lighting is out the window anyways sooo....

    Anyways, here's the code. (remove any attributes/imports creating errors, they are from our internal repository):

    EDIT: Fixed child transforms not being updated - also disclaimer - this won't work for complex stuff like skinned mesh renderers without a bit of manual synchronization.

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEditor;
    5. using UnityEngine;
    6. using System;
    7. using emotitron.Compression;
    8.  
    9. [ExecuteInEditMode]
    10. public class MirroredObject : MonoBehaviour
    11. {
    12.  
    13.     [Foldout("SetUp", true)]
    14.     public bool ExecuteInEditor = true;
    15.  
    16.     public Transform Mirror;
    17.  
    18.     [Layer]
    19.     public int MirrorWorldLayer;
    20.  
    21.     [Foldout("Debug", true)]
    22.     public bool mirrorRoot = true;
    23.  
    24.     public bool syncChildren = true;
    25.  
    26.     [SerializeField, ReadOnly]
    27.     private GameObject mirroredObject;
    28.  
    29.     [SerializeField, ReadOnly]
    30.     private List<Transform> MirroredTransformChilds, OriginTransformChilds;
    31.  
    32.     Plane mirrorPlane;
    33.  
    34.     [SerializeField, ReadOnly]
    35.     private GameObject MirroredObjectsHolder;
    36.  
    37.     void Update()
    38.     {
    39.         if (!ExecuteInEditor && Application.isEditor)
    40.             return;
    41.  
    42.         if (!mirroredObject && Mirror)
    43.         {
    44.             InstantiateClone();
    45.         }
    46.  
    47.         if(mirroredObject && Mirror)
    48.             MirrorObject();
    49.     }
    50.     private void Start()
    51.     {
    52.         if (!mirroredObject && Mirror)
    53.             InstantiateClone();
    54.     }
    55.  
    56.     [ButtonMethod]
    57.     private void InstantiateClone()
    58.     {
    59.         //Clear previous instances
    60.         DeleteMirrorInstance();
    61.  
    62.         //Create or find MirrorWorldHolder
    63.         if (!MirroredObjectsHolder)
    64.         {
    65.             if (!GameObject.Find("MirrorWorld"))
    66.                 MirroredObjectsHolder = new GameObject("MirrorWorld");
    67.  
    68.             MirroredObjectsHolder = GameObject.Find("MirrorWorld");
    69.         }
    70.      
    71.         //Create Instance
    72.         mirroredObject = Instantiate(gameObject, MirroredObjectsHolder.transform);
    73.      
    74.         //Mirror X Axis
    75.         mirroredObject.transform.localScale = new Vector3(mirroredObject.transform.localScale.x * -1, mirroredObject.transform.localScale.y, mirroredObject.transform.localScale.z);
    76.  
    77.         //Removes this script from the cloned object
    78.         mirroredObject.GetComponent<MirroredObject>().enabled = false;
    79.         DestroyImmediate(mirroredObject.GetComponent<MirroredObject>());
    80.  
    81.         //Setup lists for mirroring child transforms
    82.         OriginTransformChilds = new List<Transform>(GetComponentsInChildren<Transform>(true));
    83.         MirroredTransformChilds = new List<Transform>(mirroredObject.GetComponentsInChildren<Transform>(true));
    84.  
    85.         //Assign layer to mirror world
    86.         mirroredObject.layer = MirrorWorldLayer;
    87.         foreach (var item in mirroredObject.GetComponentsInChildren<Transform>(true))
    88.         {
    89.             item.gameObject.layer = MirrorWorldLayer;
    90.         }
    91.     }
    92.  
    93.     private void OnDisable()
    94.     {
    95.         DeleteMirrorInstance();
    96.     }
    97.  
    98.     private void DeleteMirrorInstance()
    99.     {
    100.        if (mirroredObject)
    101.             DestroyImmediate(mirroredObject);
    102.     }
    103.  
    104.     private void MirrorObject()
    105.     {
    106.         mirrorPlane = new Plane(Mirror.forward, Mirror.position);
    107.  
    108.         Vector3 closestPoint;
    109.         float distanceToMirror;
    110.         Vector3 mirrorPos;
    111.  
    112.         #region Children
    113.         if (syncChildren)
    114.         {
    115.             for (int i = 0; i < OriginTransformChilds.Count; i++)
    116.             {
    117.                 Transform trans = OriginTransformChilds[i];
    118.  
    119.                 closestPoint = mirrorPlane.ClosestPointOnPlane(trans.position);
    120.                 distanceToMirror = mirrorPlane.GetDistanceToPoint(trans.position);
    121.  
    122.                 mirrorPos = closestPoint - mirrorPlane.normal * distanceToMirror;
    123.  
    124.                 MirroredTransformChilds[i].position = mirrorPos;
    125.                 MirroredTransformChilds[i].rotation = ReflectRotation(trans.rotation, mirrorPlane.normal);
    126.             }
    127.         }
    128.         #endregion
    129.  
    130.         if (!mirrorRoot)
    131.             return;
    132.  
    133.         //Update the transform:
    134.         closestPoint = mirrorPlane.ClosestPointOnPlane(transform.position);
    135.         distanceToMirror = mirrorPlane.GetDistanceToPoint(transform.position);
    136.  
    137.         mirrorPos = closestPoint - mirrorPlane.normal * distanceToMirror;
    138.  
    139.         mirroredObject.transform.position = mirrorPos;
    140.         mirroredObject.transform.rotation = ReflectRotation(transform.rotation, mirrorPlane.normal);
    141.  
    142.     }
    143.  
    144.  
    145.     private Quaternion ReflectRotation(Quaternion source, Vector3 normal)
    146.     {
    147.         return Quaternion.LookRotation(Vector3.Reflect(source * Vector3.forward, normal), Vector3.Reflect(source * Vector3.up, normal));
    148.     }
    149.  
    150. #if UNITY_EDITOR
    151.     private void OnDrawGizmos()
    152.     {
    153.         if (!Mirror)
    154.             return;
    155.  
    156.         Gizmos.color = Color.blue;
    157.  
    158.         Vector3 closestPoint = mirrorPlane.ClosestPointOnPlane(transform.position);
    159.         Gizmos.DrawLine(transform.position, closestPoint);
    160.  
    161.         Gizmos.color = Color.cyan;
    162.         float distanceToMirror = mirrorPlane.GetDistanceToPoint(transform.position);
    163.  
    164.         Vector3 mirrorPos = closestPoint - mirrorPlane.normal * -1;
    165.  
    166.  
    167.         Gizmos.DrawLine(closestPoint, mirrorPos);
    168.  
    169.         Gizmos.color = Color.green;
    170.         Gizmos.DrawRay(new Ray(Mirror.position, Mirror.up));
    171.  
    172.         Gizmos.color = Color.red;
    173.         Gizmos.DrawRay(new Ray(Mirror.position, Mirror.forward * -1));
    174.  
    175.         Gizmos.color = Color.yellow;
    176.         Gizmos.DrawRay(new Ray(Mirror.position, mirrorPlane.normal));
    177.     }
    178.  
    179. #endif
    180. }
    181.  
    182.  
     
    Last edited: Jul 8, 2020
    wpetillo and jeromeWork like this.
  44. jeromeWork

    jeromeWork

    Joined:
    Sep 1, 2015
    Posts:
    380
    TheVirtualMunk likes this.
  45. alap18

    alap18

    Joined:
    Oct 31, 2014
    Posts:
    3
    @jeromeWork I've got the mirror working using a modified version of the package shared by the FinalIK developer. I'm now trying to wrap my head around how I would actually implement this on a wall where I don't have space behind the actual wall, which is what this sort of solution needs. It's proving to be quite a big constraint and really restricts where I can actually have a mirror.

    Is there a way to get around that? Or a simple solution that's somehow evaded me?
     
  46. Albino_Burrito

    Albino_Burrito

    Joined:
    Jun 13, 2018
    Posts:
    3
    Hi, I entered this code in my scene and it works for my left eye but when close my left eye in vr the mirror on my right eye is moving weird.
    upload_2021-1-27_14-30-2.png
    Anyone think they can help?
     
  47. ngdangha

    ngdangha

    Joined:
    Oct 27, 2020
    Posts:
    1
    upload_2021-1-27_23-27-10.png

    After using the script, I get this and do not know how to fix, can anybody help me with this please? Thank you in advance.
     
  48. Sheynes

    Sheynes

    Joined:
    Mar 27, 2017
    Posts:
    35
    I've been looking for an HDRP VR mirror for half a year now. I'm willing to pay for it, but I can't find any.
    The planar reflection prob do not work for VR. The eye offset is bugged out.
     
  49. gabrielmukobi

    gabrielmukobi

    Joined:
    Aug 11, 2019
    Posts:
    2
    If anyone like me wanted a working mirror in Unity 2020 using the SteamVR XR plugin and single-pass instanced rendering, I made some modifications to the shader and script posted above by aeroson that seems to work:

    Shader:
    Code (CSharp):
    1. // original source from: http://wiki.unity3d.com/index.php/MirrorReflection4
    2. Shader "FX/MirrorReflection"
    3. {
    4.     Properties
    5.     {
    6.         _MainTex ("_MainTex", 2D) = "white" {}
    7.         _ReflectionTexLeft ("_ReflectionTexLeft", 2D) = "white" {}
    8.         _ReflectionTexRight ("_ReflectionTexRight", 2D) = "white" {}
    9.     }
    10.     SubShader
    11.     {
    12.         Tags { "RenderType"="Opaque" }
    13.         LOD 100
    14.         Pass {
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.             #include "UnityCG.cginc"
    19.             struct appdata
    20.             {
    21.                 float4 pos : POSITION;
    22.                 float2 uv : TEXCOORD0;
    23.              
    24.                 UNITY_VERTEX_INPUT_INSTANCE_ID // For Single-Pass Instanced Rendering
    25.             };
    26.             struct v2f
    27.             {
    28.                 float2 uv : TEXCOORD0;
    29.                 float4 refl : TEXCOORD1;
    30.                 float4 pos : SV_POSITION;
    31.              
    32.                 UNITY_VERTEX_OUTPUT_STEREO // For Single-Pass Instanced Rendering
    33.             };
    34.             float4 _MainTex_ST;
    35.             v2f vert(appdata v)
    36.             {
    37.                 v2f o;
    38.  
    39.                 UNITY_SETUP_INSTANCE_ID(v); // For Single-Pass Instanced Rendering
    40.                 UNITY_INITIALIZE_OUTPUT(v2f, o); // For Single-Pass Instanced Rendering
    41.                 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); // For Single-Pass Instanced Rendering
    42.  
    43.                 o.pos = UnityObjectToClipPos (v.pos);
    44.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    45.                 o.refl = ComputeScreenPos (o.pos);
    46.                 return o;
    47.             }
    48.             sampler2D _MainTex;
    49.             sampler2D _ReflectionTexLeft;
    50.             sampler2D _ReflectionTexRight;
    51.             UNITY_INSTANCING_BUFFER_START(Props)
    52.                 // Define instance props here
    53.             UNITY_INSTANCING_BUFFER_END(Props)
    54.             fixed4 frag(v2f i) : SV_Target
    55.             {
    56.                 UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); // Enables unity_StereoEyeIndex variable
    57.  
    58.                 fixed4 tex = tex2D(_MainTex, i.uv);
    59.                 fixed4 refl;
    60.                 if (unity_StereoEyeIndex == 0) refl = tex2Dproj(_ReflectionTexLeft, UNITY_PROJ_COORD(i.refl));
    61.                 else refl = tex2Dproj(_ReflectionTexRight, UNITY_PROJ_COORD(i.refl));
    62.                 return tex * refl;
    63.             }
    64.             ENDCG
    65.         }
    66.     }
    67. }
    C# Script that goes on the same game object using a material with the above shader:
    Code (CSharp):
    1. // original source from: http://wiki.unity3d.com/index.php/MirrorReflection4
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using Valve.VR;
    6. // This is in fact just the Water script from Pro Standard Assets,
    7. // just with refraction stuff removed.
    8. [ExecuteInEditMode] // Make mirror live-update even when not in play mode
    9. public class MirrorReflection : MonoBehaviour
    10. {
    11.     public bool m_DisablePixelLights = true;
    12.     public int m_TextureSize = 256;
    13.     public float m_ClipPlaneOffset = 0.07f;
    14.     public int m_framesNeededToUpdate = 0;
    15.     public Vector3 normal = new Vector3(1, 0, 0);
    16.     public LayerMask m_ReflectLayers = -1;
    17.     private Dictionary<Camera, Camera> m_ReflectionCameras = new Dictionary<Camera, Camera>();
    18.     private RenderTexture m_ReflectionTextureLeft = null;
    19.     private RenderTexture m_ReflectionTextureRight = null;
    20.     private int m_OldReflectionTextureSize = 0;
    21.     private int m_frameCounter = 0;
    22.     private static bool s_InsideRendering = false;
    23.     // This is called when it's known that the object will be rendered by some
    24.     // camera. We render reflections and do other updates here.
    25.     // Because the script executes in edit mode, reflections for the scene view
    26.     // camera will just work!
    27.     public void OnWillRenderObject()
    28.     {
    29.         if (m_frameCounter > 0)
    30.         {
    31.             m_frameCounter--;
    32.             return;
    33.         }
    34.         var rend = GetComponent<Renderer>();
    35.         if (!enabled || !rend || !rend.sharedMaterial || !rend.enabled)
    36.             return;
    37.         Camera cam = Camera.current;
    38.         if (!cam)
    39.             return;
    40.         // Safeguard from recursive reflections.  
    41.         if (s_InsideRendering)
    42.             return;
    43.         s_InsideRendering = true;
    44.         m_frameCounter = m_framesNeededToUpdate;
    45.         RenderCamera(cam, rend, Camera.StereoscopicEye.Left, ref m_ReflectionTextureLeft);
    46.         if (cam.stereoEnabled)
    47.             RenderCamera(cam, rend, Camera.StereoscopicEye.Right, ref m_ReflectionTextureRight);
    48.     }
    49.     private void RenderCamera(Camera cam, Renderer rend, Camera.StereoscopicEye eye, ref RenderTexture reflectionTexture)
    50.     {
    51.         Camera reflectionCamera;
    52.         CreateMirrorObjects(cam, eye, out reflectionCamera, ref reflectionTexture);
    53.         // find out the reflection plane: position and normal in world space
    54.         Vector3 pos = transform.position;
    55.         // Optionally disable pixel lights for reflection
    56.         int oldPixelLightCount = QualitySettings.pixelLightCount;
    57.         if (m_DisablePixelLights)
    58.             QualitySettings.pixelLightCount = 0;
    59.         CopyCameraProperties(cam, reflectionCamera);
    60.         // Render reflection
    61.         // Reflect camera around reflection plane
    62.         float d = -Vector3.Dot(normal, pos) - m_ClipPlaneOffset;
    63.         Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
    64.         Matrix4x4 reflection = Matrix4x4.zero;
    65.         CalculateReflectionMatrix(ref reflection, reflectionPlane);
    66.         Vector3 oldEyePos;
    67.         Matrix4x4 worldToCameraMatrix;
    68.         if (cam.stereoEnabled)
    69.         {
    70.             worldToCameraMatrix = cam.GetStereoViewMatrix(eye) * reflection;
    71.             var eyeOffset = SteamVR.instance.eyes[(int)eye].pos;
    72.             eyeOffset.z = 0.0f;
    73.             oldEyePos = cam.transform.position + cam.transform.TransformVector(eyeOffset);
    74.         }
    75.         else
    76.         {
    77.             worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
    78.             oldEyePos = cam.transform.position;
    79.         }
    80.         Vector3 newEyePos = reflection.MultiplyPoint(oldEyePos);
    81.         reflectionCamera.transform.position = newEyePos;
    82.         reflectionCamera.worldToCameraMatrix = worldToCameraMatrix;
    83.         // Setup oblique projection matrix so that near plane is our reflection
    84.         // plane. This way we clip everything below/above it for free.
    85.         Vector4 clipPlane = CameraSpacePlane(worldToCameraMatrix, pos, normal, 1.0f);
    86.         Matrix4x4 projectionMatrix;
    87.         if (cam.stereoEnabled) projectionMatrix = HMDMatrix4x4ToMatrix4x4(SteamVR.instance.hmd.GetProjectionMatrix((Valve.VR.EVREye)eye, cam.nearClipPlane, cam.farClipPlane));
    88.         else projectionMatrix = cam.projectionMatrix;
    89.    
    90.         //projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);
    91.         MakeProjectionMatrixOblique(ref projectionMatrix, clipPlane);
    92.         reflectionCamera.projectionMatrix = projectionMatrix;
    93.         reflectionCamera.cullingMask = m_ReflectLayers.value;
    94.         reflectionCamera.targetTexture = reflectionTexture;
    95.         GL.invertCulling = true;
    96.         //Vector3 euler = cam.transform.eulerAngles;
    97.         //reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
    98.         reflectionCamera.transform.rotation = cam.transform.rotation;
    99.         reflectionCamera.Render();
    100.         //reflectionCamera.transform.position = oldEyePos;
    101.         GL.invertCulling = false;
    102.         Material[] materials = rend.sharedMaterials;
    103.         string property = "_ReflectionTex" + eye.ToString();
    104.         foreach (Material mat in materials)
    105.         {
    106.             if (mat.HasProperty(property))
    107.                 mat.SetTexture(property, reflectionTexture);
    108.         }
    109.         // Restore pixel light count
    110.         if (m_DisablePixelLights)
    111.             QualitySettings.pixelLightCount = oldPixelLightCount;
    112.         s_InsideRendering = false;
    113.     }
    114.     // Cleanup all the objects we possibly have created
    115.     void OnDisable()
    116.     {
    117.         if (m_ReflectionTextureLeft)
    118.         {
    119.             DestroyImmediate(m_ReflectionTextureLeft);
    120.             m_ReflectionTextureLeft = null;
    121.         }
    122.         if (m_ReflectionTextureRight)
    123.         {
    124.             DestroyImmediate(m_ReflectionTextureRight);
    125.             m_ReflectionTextureRight = null;
    126.         }
    127.         foreach (var kvp in m_ReflectionCameras)
    128.             DestroyImmediate(((Camera)kvp.Value).gameObject);
    129.         m_ReflectionCameras.Clear();
    130.     }
    131.     private void CopyCameraProperties(Camera src, Camera dest)
    132.     {
    133.         if (dest == null)
    134.             return;
    135.         // set camera to clear the same way as current camera
    136.         dest.clearFlags = src.clearFlags;
    137.         dest.backgroundColor = src.backgroundColor;
    138.         if (src.clearFlags == CameraClearFlags.Skybox)
    139.         {
    140.             Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
    141.             Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
    142.             if (!sky || !sky.material)
    143.             {
    144.                 mysky.enabled = false;
    145.             }
    146.             else
    147.             {
    148.                 mysky.enabled = true;
    149.                 mysky.material = sky.material;
    150.             }
    151.         }
    152.         // update other values to match current camera.
    153.         // even if we are supplying custom camera&projection matrices,
    154.         // some of values are used elsewhere (e.g. skybox uses far plane)
    155.         dest.farClipPlane = src.farClipPlane;
    156.         dest.nearClipPlane = src.nearClipPlane;
    157.         dest.orthographic = src.orthographic;
    158.         dest.fieldOfView = src.fieldOfView;
    159.         dest.aspect = src.aspect;
    160.         dest.orthographicSize = src.orthographicSize;
    161.     }
    162.     // On-demand create any objects we need
    163.     private void CreateMirrorObjects(Camera currentCamera, Camera.StereoscopicEye eye, out Camera reflectionCamera, ref RenderTexture reflectionTexture)
    164.     {
    165.         reflectionCamera = null;
    166.         // Reflection render texture
    167.         if (!reflectionTexture || m_OldReflectionTextureSize != m_TextureSize)
    168.         {
    169.             if (reflectionTexture)
    170.                 DestroyImmediate(reflectionTexture);
    171.             reflectionTexture = new RenderTexture(m_TextureSize, m_TextureSize, 16);
    172.             reflectionTexture.name = "__MirrorReflection" + eye.ToString() + GetInstanceID();
    173.             reflectionTexture.isPowerOfTwo = true;
    174.             reflectionTexture.hideFlags = HideFlags.DontSave;
    175.             m_OldReflectionTextureSize = m_TextureSize;
    176.         }
    177.         // Camera for reflection
    178.         if (!m_ReflectionCameras.TryGetValue(currentCamera, out reflectionCamera)) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
    179.         {
    180.             GameObject go = new GameObject("Mirror Reflection Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox));
    181.             reflectionCamera = go.GetComponent<Camera>();
    182.             reflectionCamera.enabled = false;
    183.             reflectionCamera.transform.position = transform.position;
    184.             reflectionCamera.transform.rotation = transform.rotation;
    185.             reflectionCamera.gameObject.AddComponent<FlareLayer>();
    186.             go.hideFlags = HideFlags.DontSave;
    187.             m_ReflectionCameras.Add(currentCamera, reflectionCamera);
    188.         }
    189.     }
    190.     // Extended sign: returns -1, 0 or 1 based on sign of a
    191.     private static float sgn(float a)
    192.     {
    193.         if (a > 0.0f) return 1.0f;
    194.         if (a < 0.0f) return -1.0f;
    195.         return 0.0f;
    196.     }
    197.     // Given position/normal of the plane, calculates plane in camera space.
    198.     private Vector4 CameraSpacePlane(Matrix4x4 worldToCameraMatrix, Vector3 pos, Vector3 normal, float sideSign)
    199.     {
    200.         Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
    201.         Vector3 cpos = worldToCameraMatrix.MultiplyPoint(offsetPos);
    202.         Vector3 cnormal = worldToCameraMatrix.MultiplyVector(normal).normalized * sideSign;
    203.         return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
    204.     }
    205.     // Calculates reflection matrix around the given plane
    206.     private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
    207.     {
    208.         reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
    209.         reflectionMat.m01 = (-2F * plane[0] * plane[1]);
    210.         reflectionMat.m02 = (-2F * plane[0] * plane[2]);
    211.         reflectionMat.m03 = (-2F * plane[3] * plane[0]);
    212.         reflectionMat.m10 = (-2F * plane[1] * plane[0]);
    213.         reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
    214.         reflectionMat.m12 = (-2F * plane[1] * plane[2]);
    215.         reflectionMat.m13 = (-2F * plane[3] * plane[1]);
    216.         reflectionMat.m20 = (-2F * plane[2] * plane[0]);
    217.         reflectionMat.m21 = (-2F * plane[2] * plane[1]);
    218.         reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
    219.         reflectionMat.m23 = (-2F * plane[3] * plane[2]);
    220.         reflectionMat.m30 = 0F;
    221.         reflectionMat.m31 = 0F;
    222.         reflectionMat.m32 = 0F;
    223.         reflectionMat.m33 = 1F;
    224.     }
    225.     // taken from http://www.terathon.com/code/oblique.html
    226.     private static void MakeProjectionMatrixOblique(ref Matrix4x4 matrix, Vector4 clipPlane)
    227.     {
    228.         Vector4 q;
    229.         // Calculate the clip-space corner point opposite the clipping plane
    230.         // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and
    231.         // transform it into camera space by multiplying it
    232.         // by the inverse of the projection matrix
    233.         q.x = (sgn(clipPlane.x) + matrix[8]) / matrix[0];
    234.         q.y = (sgn(clipPlane.y) + matrix[9]) / matrix[5];
    235.         q.z = -1.0F;
    236.         q.w = (1.0F + matrix[10]) / matrix[14];
    237.         // Calculate the scaled plane vector
    238.         Vector4 c = clipPlane * (2.0F / Vector3.Dot(clipPlane, q));
    239.         // Replace the third row of the projection matrix
    240.         matrix[2] = c.x;
    241.         matrix[6] = c.y;
    242.         matrix[10] = c.z + 1.0F;
    243.         matrix[14] = c.w;
    244.     }
    245.     protected Matrix4x4 HMDMatrix4x4ToMatrix4x4(Valve.VR.HmdMatrix44_t input)
    246.     {
    247.         var m = Matrix4x4.identity;
    248.         m[0, 0] = input.m0;
    249.         m[0, 1] = input.m1;
    250.         m[0, 2] = input.m2;
    251.         m[0, 3] = input.m3;
    252.         m[1, 0] = input.m4;
    253.         m[1, 1] = input.m5;
    254.         m[1, 2] = input.m6;
    255.         m[1, 3] = input.m7;