Search Unity

Converting a mirror script into a portal script

Discussion in 'Scripting' started by RiokuTheSlayer, Jan 6, 2016.

  1. RiokuTheSlayer

    RiokuTheSlayer

    Joined:
    Aug 22, 2013
    Posts:
    356
    Heya everyone, Rioku here.

    Had a bit of a suggestion/request.

    I found this script a few days ago, and have been trying to convert it into a portal script, so you can sorta see into other areas of levels.

    I've nearly gotten it, but I really don't know what I'm doing. I've just been messing around with variables and stuff. I don't really know how matrices work.

    That script also has a few errors, here's a Unity 5 version that works correctly from the start.

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

    But that doesn't work, since the matrix we're using for rendering will "align" itself to the opposite mirror's position and such, and the clipping for the camera is set to match the other one.

    Alright, so we add the difference in position of the mirrors to the pos variable. That breaks everything, including culling and the like, which I want to keep.

    Instead, I added a new variable in CalculateReflectionMatrix, a Vector3 called offset, and added that's x,y,z to the matrix's m03,m13,and m23 respectively.

    Alright that sorta works. It correctly aligns the mirrors to each other's views.

    That is, until you rotate one of them. And that's where I'm stuck. Once you rotate one of them, it doesn't rotate the relative view of the matrix to match the new direction of the current mirror. So you get this effect.




    This is fine if both of your "Portals" are going to face the same direction, but mine aren't. I need to be able to have them facing any direction.

    So what I need help with is changing up this script so that the projection matrix is aligned with the rotation of the current mirror so that it orients itself correctly.

    Here's the modified script with all the changes I've mentioned above.

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

    Anyway, thanks for any help! Hope someone can help me get this working. If we can, then there'll be another free method of making portals around the internet.
     
  2. RiokuTheSlayer

    RiokuTheSlayer

    Joined:
    Aug 22, 2013
    Posts:
    356
    Well I've been at it all day. Haven't gotten any progress.

    I have absolutely no idea how to change the matrix. I've found out that the 1F-2F...(ect) entries in CalculateReflectionMatrix are the scale.

    That leads me to believe that one of the other ones is rotation, but that's not the case, since I've added test values to each of them, to no avail. None of them seem to align to any rotational axis. There's a sorta skew one and another rotation&scale(?) one.

    I'm surprised it's so hard to rotate this view so it doesn't look like it changes.