Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Projected texture read warps skybox?

Discussion in 'Shaders' started by swanijam, Jan 7, 2019.

  1. swanijam

    swanijam

    Joined:
    Nov 14, 2016
    Posts:
    23
    So I'm playing with reflection rendertextures for a lake scene, and running into a strange issue of the the skybox warping in the reflection, but not the other objects:


    I tried the same scene but with the skybox on a gigantic inverted sphere, and there is no warping:


    and if I add 'ZWrite Off' to the skybox-sphere shader, the warping comes back.

    The shader is relatively simple:

    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 = ComputeNonStereoScreenPos (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.                 // i.uv = TransformStereoScreenSpaceTex(i.uv, 1);
    41.                 fixed4 refl;
    42.                 float4 projCoords = UNITY_PROJ_COORD(i.refl);
    43.                
    44.                 if (unity_StereoEyeIndex == 0) {
    45.                     refl = tex2Dproj(_ReflectionTexLeft, projCoords) * tex2Dproj(_MainTex, projCoords);
    46.                 }
    47.                 else {
    48.                     refl = tex2Dproj(_ReflectionTexRight, projCoords) * tex2Dproj(_MainTex, projCoords);
    49.                 }
    50.                
    51.                 return refl;
    52.             }
    53.             ENDCG
    54.         }
    55.     }
    56. }
    and here's the script that creates the reflection cameras, from this thread :

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

    here's the plane just showing 1/i.refl.w. It's continuous, so there's nothing special about the Skybox's w component.
    upload_2019-1-7_11-37-37.png

    and here's the rest of i.refl:
    upload_2019-1-7_11-40-2.png
    also continuous. I'm not sure why it's skewed to the left, or why the skew isn't symmetrical but leftward in both eyes, but although this is weird it still doesn't explain what's weird about the skybox pixels.

    Any idea what causes the warping of the skybox, or why setting ZWrite Off causes warping on the skybox-sphere?