Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Reflection probe renders only back face of planar objects

Discussion in 'Universal Render Pipeline' started by dot_entity, Dec 14, 2022.

  1. dot_entity

    dot_entity

    Joined:
    Nov 2, 2012
    Posts:
    79
    I am trying to make an interior scene and make the floor reflective using the kMirror method (based on texture rendering with a second camera) since I was not able to achieve correct reflections with reflection probes. In addition I would like to have the walls for example, which are planar objects (not closed geometry), to be affected by reflection probes.
    I managed to do the reflective floor, but I noticed a glitch: Since the kMirror script has been added in the scene, a reflection probe placed in the same scene does not render the front face of the planar the objects, i.e. the walls or a plane GO (strangely enough, the "3d" objects render fine. I figured that if I set the render face property of the material on the planar object to render the back face (or both) or, of course, if I rotate a plane's back face to "look" at the camera the reflection probe render is accurate.
    I know I am asking for a wild guess here, but do you have any thoughts of when this odd behaviour can happen?
    It's hard for me to figure out the source of the issue (reflection probe's camera confusion?, mess with normals?), so I share the mirror script in hope it can help (it's a public project, so I guess it's alright to post it here too):

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.Rendering;
    4. using UnityEngine.Rendering.Universal;
    5.  
    6. namespace kTools.Mirrors
    7. {
    8.     /// <summary>
    9.     /// Mirror Object component.
    10.     /// </summary>
    11.     [AddComponentMenu("kTools/Mirror"), ExecuteInEditMode]
    12.     [RequireComponent(typeof(Camera), typeof(UniversalAdditionalCameraData))]
    13.     public class Mirror : MonoBehaviour
    14.     {
    15. #region Enumerations
    16.         /// <summary>
    17.         /// Camera override enumeration for Mirror properties
    18.         /// <summary>
    19.         public enum MirrorCameraOverride
    20.         {
    21.             UseSourceCameraSettings,
    22.             Off,
    23.         }
    24.  
    25.         /// <summary>
    26.         /// Scope enumeration for Mirror output destination
    27.         /// <summary>
    28.         public enum OutputScope
    29.         {
    30.             Global,
    31.             Local,
    32.         }
    33. #endregion
    34.  
    35. #region Serialized Fields
    36.         [SerializeField]
    37.         float m_Offset;
    38.  
    39.         [SerializeField]
    40.         int m_LayerMask;
    41.  
    42.         [SerializeField]
    43.         OutputScope m_Scope;
    44.  
    45.         [SerializeField]
    46.         List<Renderer> m_Renderers;
    47.  
    48.         [SerializeField]
    49.         float m_TextureScale;
    50.  
    51.         [SerializeField]
    52.         MirrorCameraOverride m_AllowHDR;
    53.  
    54.         [SerializeField]
    55.         MirrorCameraOverride m_AllowMSAA;
    56. #endregion
    57.  
    58. #region Fields
    59.         const string kGizmoPath = "Packages/com.kink3d.mirrors/Gizmos/Mirror.png";
    60.         Camera m_ReflectionCamera;
    61.         UniversalAdditionalCameraData m_CameraData;
    62.         RenderTexture m_RenderTexture;
    63.         RenderTextureDescriptor m_PreviousDescriptor;
    64. #endregion
    65.  
    66. #region Constructors
    67.         public Mirror()
    68.         {
    69.             // Set data
    70.             m_Offset = 0.01f;
    71.             m_LayerMask = -1;
    72.             m_Scope = OutputScope.Global;
    73.             m_Renderers = new List<Renderer>();
    74.             m_TextureScale = 1.0f;
    75.             m_AllowHDR = MirrorCameraOverride.UseSourceCameraSettings;
    76.             m_AllowMSAA = MirrorCameraOverride.UseSourceCameraSettings;
    77.         }
    78. #endregion
    79.  
    80. #region Properties
    81.         /// <summary>Offset value for oplique near clip plane.</summary>
    82.         public float offest
    83.         {
    84.             get => m_Offset;
    85.             set => m_Offset = value;
    86.         }
    87.  
    88.         /// <summary>Which layers should the Mirror render.</summary>
    89.         public LayerMask layerMask
    90.         {
    91.             get => m_LayerMask;
    92.             set => m_LayerMask = value;
    93.         }
    94.  
    95.         /// <summary>
    96.         /// Global output renders to the global texture. Only one Mirror can be global.
    97.         /// Local output renders to one texture per Mirror, this is set on all elements of the Renderers list.
    98.         /// </summary>
    99.         public OutputScope scope
    100.         {
    101.             get => m_Scope;
    102.             set => m_Scope = value;
    103.         }
    104.  
    105.         /// <summary>Renderers to set the reflection texture on.</summary>
    106.         public List<Renderer> renderers
    107.         {
    108.             get => m_Renderers;
    109.             set => m_Renderers = value;
    110.         }
    111.  
    112.         /// <summary>Scale value applied to the size of the source camera texture.</summary>
    113.         public float textureScale
    114.         {
    115.             get => m_TextureScale;
    116.             set => m_TextureScale = value;
    117.         }
    118.  
    119.         /// <summary>Should reflections be rendered in HDR.</summary>
    120.         public MirrorCameraOverride allowHDR
    121.         {
    122.             get => m_AllowHDR;
    123.             set => m_AllowHDR = value;
    124.         }
    125.  
    126.         /// <summary>Should reflections be resolved with MSAA.</summary>
    127.         public MirrorCameraOverride allowMSAA
    128.         {
    129.             get => m_AllowMSAA;
    130.             set => m_AllowMSAA = value;
    131.         }
    132.  
    133.         Camera reflectionCamera
    134.         {
    135.             get
    136.             {
    137.                 if(m_ReflectionCamera == null)
    138.                     m_ReflectionCamera = GetComponent<Camera>();
    139.                 return m_ReflectionCamera;
    140.             }
    141.         }
    142.  
    143.         UniversalAdditionalCameraData cameraData
    144.         {
    145.             get
    146.             {
    147.                 if(m_CameraData == null)
    148.                     m_CameraData = GetComponent<UniversalAdditionalCameraData>();
    149.                 return m_CameraData;
    150.             }
    151.         }
    152. #endregion
    153.  
    154. #region State
    155.         void OnEnable()
    156.         {
    157.             // Callbacks
    158.             RenderPipelineManager.beginCameraRendering += BeginCameraRendering;
    159.    
    160.             // Initialize Components
    161.             InitializeCamera();
    162.         }
    163.  
    164.         void OnDisable()
    165.         {
    166.             // Callbacks
    167.             RenderPipelineManager.beginCameraRendering -= BeginCameraRendering;
    168.  
    169.             // Dispose RenderTexture
    170.             SafeDestroyObject(m_RenderTexture);
    171.         }
    172. #endregion
    173.  
    174. #region Initialization
    175.         void InitializeCamera()
    176.         {
    177.             // Setup Camera
    178.             reflectionCamera.cameraType = CameraType.Reflection;
    179.             reflectionCamera.targetTexture = m_RenderTexture;
    180.  
    181.             // Setup AdditionalCameraData
    182.             cameraData.renderShadows = false;
    183.             cameraData.requiresColorOption = CameraOverrideOption.Off;
    184.             cameraData.requiresDepthOption = CameraOverrideOption.Off;
    185.         }
    186. #endregion
    187.  
    188. #region RenderTexture
    189.         RenderTextureDescriptor GetDescriptor(Camera camera)
    190.         {
    191.             // Get scaled Texture size
    192.             var width = (int)Mathf.Max(camera.pixelWidth * textureScale, 4);
    193.             var height = (int)Mathf.Max(camera.pixelHeight * textureScale, 4);
    194.  
    195.             // Get Texture format
    196.             var hdr = allowHDR == MirrorCameraOverride.UseSourceCameraSettings ? camera.allowHDR : false;
    197.             var renderTextureFormat = hdr ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default;
    198.             return new RenderTextureDescriptor(width, height, renderTextureFormat, 16) { autoGenerateMips = true, useMipMap = true };
    199.         }
    200. #endregion
    201.  
    202. #region Rendering
    203.         void BeginCameraRendering(ScriptableRenderContext context, Camera camera)
    204.         {
    205.             // Never render Mirrors for URP Overlay Camera
    206.             var camData = camera.GetUniversalAdditionalCameraData();
    207.             if (camData.renderType == CameraRenderType.Overlay)
    208.                 return;
    209.  
    210.             // Profiling command
    211.             CommandBuffer cmd = CommandBufferPool.Get($"Mirror {gameObject.GetInstanceID()}");
    212.             using (new ProfilingScope(cmd, new ProfilingSampler($"Mirror {gameObject.GetInstanceID()}")))
    213.             {
    214.                 ExecuteCommand(context, cmd);
    215.  
    216.                 // Test for Descriptor changes
    217.                 var descriptor = GetDescriptor(camera);
    218.                 if (!descriptor.Equals(m_PreviousDescriptor))
    219.                 {
    220.                     // Dispose RenderTexture
    221.                     if (m_RenderTexture != null)
    222.                     {
    223.                         SafeDestroyObject(m_RenderTexture);
    224.                     }
    225.  
    226.                     // Create new RenderTexture
    227.                     m_RenderTexture = new RenderTexture(descriptor);
    228.                     m_PreviousDescriptor = descriptor;
    229.                     reflectionCamera.targetTexture = m_RenderTexture;
    230.                 }
    231.  
    232.                 // Execute
    233.                 RenderMirror(context, camera);
    234.                 SetShaderUniforms(context, m_RenderTexture, cmd);
    235.             }
    236.  
    237.             ExecuteCommand(context, cmd);
    238.         }
    239.  
    240.         void RenderMirror(ScriptableRenderContext context, Camera camera)
    241.         {
    242.             // Mirror the view matrix
    243.             var mirrorMatrix = GetMirrorMatrix();
    244.             reflectionCamera.worldToCameraMatrix = camera.worldToCameraMatrix * mirrorMatrix;
    245.  
    246.             // Make oplique projection matrix where near plane is mirror plane
    247.             var mirrorPlane = GetMirrorPlane(reflectionCamera);
    248.             var projectionMatrix = camera.CalculateObliqueMatrix(mirrorPlane);
    249.             reflectionCamera.projectionMatrix = projectionMatrix;
    250.    
    251.             // Miscellanious camera settings
    252.             reflectionCamera.allowHDR = allowHDR == MirrorCameraOverride.UseSourceCameraSettings ? camera.allowHDR : false;
    253.             reflectionCamera.allowMSAA = allowMSAA == MirrorCameraOverride.UseSourceCameraSettings ? camera.allowMSAA : false;
    254.             reflectionCamera.enabled = false;
    255.  
    256.             // Render reflection camera with inverse culling
    257.             GL.invertCulling = true;
    258.             UniversalRenderPipeline.RenderSingleCamera(context, reflectionCamera);
    259.             GL.invertCulling = false;
    260.         }
    261. #endregion
    262.  
    263. #region Projection
    264.         Matrix4x4 GetMirrorMatrix()
    265.         {
    266.             // Setup
    267.             var position = transform.position;
    268.             var normal = transform.forward;
    269.             var depth = -Vector3.Dot(normal, position) - offest;
    270.  
    271.             // Create matrix
    272.             var mirrorMatrix = new Matrix4x4()
    273.             {
    274.                 m00 = (1f - 2f * normal.x  * normal.x),
    275.                 m01 = (-2f     * normal.x  * normal.y),
    276.                 m02 = (-2f     * normal.x  * normal.z),
    277.                 m03 = (-2f     * depth     * normal.x),
    278.                 m10 = (-2f     * normal.y  * normal.x),
    279.                 m11 = (1f - 2f * normal.y  * normal.y),
    280.                 m12 = (-2f     * normal.y  * normal.z),
    281.                 m13 = (-2f     * depth     * normal.y),
    282.                 m20 = (-2f     * normal.z  * normal.x),
    283.                 m21 = (-2f     * normal.z  * normal.y),
    284.                 m22 = (1f - 2f * normal.z  * normal.z),
    285.                 m23 = (-2f     * depth     * normal.z),
    286.                 m30 = 0f,
    287.                 m31 = 0f,
    288.                 m32 = 0f,
    289.                 m33 = 1f,
    290.             };
    291.             return mirrorMatrix;
    292.         }
    293.  
    294.         Vector4 GetMirrorPlane(Camera camera)
    295.         {
    296.             // Calculate mirror plane in camera space.
    297.             var pos = transform.position - Vector3.forward * 0.1f;
    298.             var normal = transform.forward;
    299.             var offsetPos = pos + normal * offest;
    300.             var cpos = camera.worldToCameraMatrix.MultiplyPoint(offsetPos);
    301.             var cnormal = camera.worldToCameraMatrix.MultiplyVector(normal).normalized;
    302.             return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
    303.         }
    304. #endregion
    305.  
    306. #region Output
    307.         void SetShaderUniforms(ScriptableRenderContext context, RenderTexture renderTexture, CommandBuffer cmd)
    308.         {
    309.             var block = new MaterialPropertyBlock();
    310.             switch(scope)
    311.             {
    312.                 case OutputScope.Global:
    313.                     // Globals
    314.                     cmd.SetGlobalTexture("_ReflectionMap", renderTexture);
    315.                     ExecuteCommand(context, cmd);
    316.  
    317.                     // Property Blocm
    318.                     block.SetFloat("_LocalMirror", 0.0f);
    319.                     foreach(var renderer in renderers)
    320.                     {
    321.                         renderer.SetPropertyBlock(block);
    322.                     }
    323.                     break;
    324.                 case OutputScope.Local:
    325.                     // Keywords
    326.                     Shader.EnableKeyword("_BLEND_MIRRORS");
    327.  
    328.                     // Property Block
    329.                     block.SetTexture("_LocalReflectionMap", renderTexture);
    330.                     block.SetFloat("_LocalMirror", 1.0f);
    331.                     foreach(var renderer in renderers)
    332.                     {
    333.                         renderer.SetPropertyBlock(block);
    334.                     }
    335.                     break;
    336.             }
    337.         }
    338. #endregion
    339.  
    340. #region CommandBufer
    341.         void ExecuteCommand(ScriptableRenderContext context, CommandBuffer cmd)
    342.         {
    343.             context.ExecuteCommandBuffer(cmd);
    344.             cmd.Clear();
    345.         }
    346. #endregion
    347.  
    348. #region Object
    349.         void SafeDestroyObject(Object obj)
    350.         {
    351.             if(obj == null)
    352.                 return;
    353.    
    354.             #if UNITY_EDITOR
    355.             DestroyImmediate(obj);
    356.             #else
    357.             Destroy(obj);
    358.             #endif
    359.         }
    360. #endregion
    361.  
    362. #region AssetMenu
    363. #if UNITY_EDITOR
    364.         // Add a menu item to Mirrors
    365.         [UnityEditor.MenuItem("GameObject/kTools/Mirror", false, 10)]
    366.         static void CreateMirrorObject(UnityEditor.MenuCommand menuCommand)
    367.         {
    368.             // Create Mirror
    369.             GameObject go = new GameObject("New Mirror", typeof(Mirror));
    370.    
    371.             // Transform
    372.             UnityEditor.GameObjectUtility.SetParentAndAlign(go, menuCommand.context as GameObject);
    373.    
    374.             // Undo and Selection
    375.             UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Create " + go.name);
    376.             UnityEditor.Selection.activeObject = go;
    377.         }
    378. #endif
    379. #endregion
    380.  
    381. #region Gizmos
    382. #if UNITY_EDITOR
    383.         void OnDrawGizmos()
    384.         {
    385.             // Setup
    386.             var bounds = new Vector3(1.0f, 1.0f, 0.0f);
    387.             var color = new Color32(0, 120, 255, 255);
    388.             var selectedColor = new Color32(255, 255, 255, 255);
    389.             var isSelected = UnityEditor.Selection.activeObject == gameObject;
    390.  
    391.             // Draw Gizmos
    392.             Gizmos.matrix = transform.localToWorldMatrix;
    393.             Gizmos.color = isSelected ? selectedColor : color;
    394.             Gizmos.DrawIcon(transform.position, kGizmoPath, true);
    395.             Gizmos.DrawWireCube(Vector3.zero, bounds);
    396.         }
    397. #endif
    398. #endregion
    399.     }
    400. }
    401.  
     
    Last edited: Jan 9, 2023
    tomekkie2 likes this.
  2. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    949
  3. dot_entity

    dot_entity

    Joined:
    Nov 2, 2012
    Posts:
    79
    Hello and thanks for the response! It seems to be a similar issue. I also changed the material to 2-sided, but this is not so convenient in my case for another reason. Since I am working on a building interior, having planar objects for walls, with non-2-sided mat, helps with the view in the scene, because the back face is culled and I can see "inside" the building easier. I also did not find any easy way for culling the back face of objects in scene view, either a single or a 2-sided material is used. I guess, by design, the cameras (included the one in the scene) only render the objects based on their material/shader properties as they apply on geometry and not on their geometry properties (normals) directly.
     
  4. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    949
    I am doing the same in case of typical interior scenes.
    But my scene this time includes both interior and exterior with walls covered with separate materials on outside and inside and I am probing only the inside with the probe.

    But the problem might arise because as I guess we both might be using a script with similar code using beginCameraRendering and endCameraRendering functions, and manipulate with Camera.projectionMatrix. The problem disappears when the script gets detached from camera. The script affects also the lens flares, which get occluded the wrong way.
     
    Last edited: Jan 8, 2023
    dot_entity likes this.
  5. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    949
  6. dot_entity

    dot_entity

    Joined:
    Nov 2, 2012
    Posts:
    79
    Well done! However, and please correct me if I am mistaken, isn't this method creates an extra draw call to flip the already flipped faces?
    Wouldn't it be more efficient if the scripts/shaders that gave us the odd behaviour in the first place, were "fixed"?
    I am currently trying to find another (free) method for planar reflections, preferably on shader level if possible, without a camera object involved. I am yet too amateur though to figure out enough.

    Correction on my first post: It was my confusion, since for every object the back faces are rendered (by the reflection probe), unless the material is set to 2-sided, without differentiation among planar or volumetric/"3d" objects, which makes of course more sense.
     
    Last edited: Jan 11, 2023
  7. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    949
    Yes, it does create an extra draw call, but in my case I am doing this as an alternative.

    This behaviour is not anything odd, because both my previous script and your solution from github use
    GL.invertCulling = true;
    tellig GL to cull the other way round.

    Possibly my solution could also work for you, but I am not simulating mirror, but flipping 3d scenes, and then I like the rendering probe to update properly, when user puts textures or colors on these exhibition stand models.
    https://fairs.d2.pl/stoiskaStandard/galleries/
     
  8. dot_entity

    dot_entity

    Joined:
    Nov 2, 2012
    Posts:
    79
    I appreciate the reply and am happy that this solution works for you. You have been helpful already and I don't wish to bother you further, but you (or anyone else) may be able to explain me in a simple manner some of the following?

    From the theoretical point of view,
    I find it hard to understand at which level the GL api is involved. Without digging much I found this picture online (copied from this site https://www3.ntu.edu.sg):
    Graphics3D_CoordTransform.png
    My question is how/where the
    GL.invertCulling = true;
    command affects the rendering pipeline?
    How is it possible that the main camera view (not the one used for the reflections) is correct, but the reflection probe is affected?

    From the practical point of view,
    and generally speaking, what should be done, in order the invertCulling flag to affect the geometry only as processed by the reflection camera and not by the rest of transformations (eg. by the reflection probe)? Is it correct to say that a completely other approach is needed, because the scripts we have used will have this outcome by design and no easy (script) editing will fix the issue?
     
  9. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    949
    My guess is the reflection probes - if they are realtime - might get rendered for both cameras. Possibly you could block them from being rendered when the mirror camera is being rendered, and use the texture generated for the normal camera for that time, with
    GL.invertCulling = false;

    ________________________________
    Do you also use the lens flares? They get occluded the wrong way for me. I was hoping this might be a result of using
    GL.invertCulling = true;
    , but I was wrong. The lens flares get occluded the other way round in builds (webgl and android), the right way in editor only, unaffected by any invertCulling changes. (I don't like to bother you too much with this, either). https://forum.unity.com/threads/len...reversed-order-in-webgl.1374015/#post-8716413
     
  10. dot_entity

    dot_entity

    Joined:
    Nov 2, 2012
    Posts:
    79
    Either real time or baked the same result.
    Not sure how to apply this, but thanks anyway! I don't currently use post processing at all.
    I will have a look into the other link you posted for extra info.
     
  11. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    949
    I have found that the beginCameraRendering event is being fired for each camera and rendering probe.
    Means if - as an example - I have two cameras and a reflection probe in the scene it is fired 10 times:
    • 6 times for 6 sides of reflection cubemap
    • scene view camera
    • scene camera
    • 2 game cameras
    The code including
    GL.invertCulling = false;
    should get executed for the mirror camera only.

    So I would replace this code:
    Code (CSharp):
    1.             if (camData.renderType == CameraRenderType.Overlay)
    2.                 return;
    from the lines 207, 208 from the script in your first post
    with:
    Code (CSharp):
    1.             if (camData.renderType != reflectionCamera)
    2.                 return;
    You can also take a look at a similar script I was using from here:
    https://answers.unity.com/questions/20337/flipmirror-camera.html?page=1&pageSize=5&sort=votes
    and adding
    if (camera != Camera) return;
    to beginCameraRendering and endCameraRendering functions fixed the issues.
     
    Last edited: Jan 13, 2023
    dot_entity likes this.
  12. dot_entity

    dot_entity

    Joined:
    Nov 2, 2012
    Posts:
    79
    You are amazing @tomekkie2 !! Your persistence to provide help really moves me! Adding to my appreciation is the fact that your simple and straightforward solution has fixed the issue!
    Now, to be specific, I am not sure if I have set everything right yet, but the probe bakes fine. The
    if (camData.renderType != reflectionCamera)
    is not compiling right away and I had to do a small, although irrational I believe, edit. I will explain more when I'll have a better understanding, but I did not want to be late before I thank you. And of course, I cannot thank you enough!
    I'm back soon with more details....
     
    tomekkie2 likes this.