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

How do I adding a texture to my mirror reflection shader?

Discussion in 'Shaders' started by MikalSaltveit, Aug 17, 2018.

  1. MikalSaltveit

    MikalSaltveit

    Joined:
    Sep 6, 2013
    Posts:
    6
    I have a mirror reflection shader that is working perfectly. Except...

    Now I want to add a texture on top to make it look like the mirror is dirty.

    Any advice / help would be most appreciated.

    Thanks!
    -Mikal Saltveit

    Code (CSharp):
    1. Shader "FX/MirrorReflection"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Base (RGB)", 2D) = "white" {}
    6.         [HideInInspector] _ReflectionTex ("", 2D) = "white" {}
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 100
    12.         Pass {
    13.             CGPROGRAM
    14.             #pragma vertex vert
    15.             #pragma fragment frag
    16.             #include "UnityCG.cginc"
    17.             struct v2f
    18.             {
    19.                 float2 uv : TEXCOORD0;
    20.                 float4 refl : TEXCOORD1;
    21.                 float4 pos : SV_POSITION;
    22.             };
    23.             float4 _MainTex_ST;
    24.             v2f vert(float4 pos : POSITION, float2 uv : TEXCOORD0)
    25.             {
    26.                 v2f o;
    27.                 o.pos = UnityObjectToClipPos (pos);
    28.                 o.uv = TRANSFORM_TEX(uv, _MainTex);
    29.                 o.refl = ComputeScreenPos (o.pos);
    30.                 return o;
    31.             }
    32.             sampler2D _MainTex;
    33.             sampler2D _ReflectionTex;
    34.             fixed4 frag(v2f i) : SV_Target
    35.             {
    36.                 fixed4 tex = tex2D(_MainTex, i.uv);
    37.                 fixed4 refl = tex2Dproj(_ReflectionTex, UNITY_PROJ_COORD(i.refl));
    38.                 return tex * refl;
    39.             }
    40.             ENDCG
    41.         }
    42.     }
    43. }
    It uses this class as well to work

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

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Sounds like maybe you want to look at using a surface shader? You can use the screenPos Input parameter to get the same thing you’re calculating for the refl value. You’ll want to output the reflection as part of the o.Emission value, and the dirt texture as the o.Albedo. If your dirt texture has an alpha, multiply the reflection by one minus the alpha, and the Albedo by the alpha.

    https://docs.unity3d.com/Manual/SL-SurfaceShaders.html
     
  3. MikalSaltveit

    MikalSaltveit

    Joined:
    Sep 6, 2013
    Posts:
    6
    Yes! That's good info. as a follow up, i'm noticing that my reflections have a point where they "shift" and have a completely different texture if your standing in a specific location and looking a specific way. I can fix this by moving the mirror plane closer to the camera... but it messes up the perspecitve.

    Also, any advice on how to make the reflection fuzzy or blurry?

    Thanks again!
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    I don't have an answer to this one. The script appears to do one reflection for one plane and unless you have multiple scripts attached to one object there's no reason I can immediately see for why there would be any shift or change textures.

    You'd need to use a shader to blur the render texture after it's been rendered with one of many techniques. You could write this yourself, or instead of using MirrorReflection4 you could use the planar reflection Unity published here:
    https://github.com/keijiro/AdamPlaneReflection

    It tries to match the effect the Standard shader's roughness values have on the specular highlights and reflection probes, but for blurring a planar reflection. However unlike MirrorReflection4 it uses a fixed sized reflection texture which means it won't match the screen resolution automatically and getting really sharp reflections is more difficult.