Search Unity

Support for UI elements in RenderToCubemap

Discussion in 'General Graphics' started by JohnRossitter, Sep 2, 2016.

  1. JohnRossitter

    JohnRossitter

    Joined:
    Dec 18, 2013
    Posts:
    1,027
    I know this is not presently possible, but I would like to see an option in the RenderToCubemap() function that would allow you to capture UI elements. I use this function for non reflection probe based work, and process cubemap data to create 360 equirectangular images.

    I dont want to open a BUG ticket in Unity, so I thought maybe someone on the graphics team at Unity could see this.

    Ideally, I would want to have the UI captured in the world forward camera direction for worldspace items.

    Thanks
     
  2. theANMATOR2b

    theANMATOR2b

    Joined:
    Jul 12, 2014
    Posts:
    7,790
  3. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
  4. Mnstrspeed

    Mnstrspeed

    Joined:
    Feb 9, 2017
    Posts:
    7
    I ran into the same problem trying to render a world-space UI to a cubemap in 2017.2. I ended up throwing together a script to manually draw all UI elements in the scene. I couldn't find any documentation on how the UI system does its rendering, so don't expect this to work if you're using non-standard UI components. It's also pretty slow and uses some hacks that might not work on other versions.

    Code (csharp):
    1.  
    2. using System.Linq;
    3. using UnityEngine;
    4. using UnityEngine.Rendering;
    5. using UnityEngine.UI;
    6.  
    7. /// <summary>
    8. /// Component that manually renders world-space UI Graphics to a Camera. Intended for
    9. /// use with Camera.RenderToCubemap which seems to ignore the built-in UI system
    10. /// </summary>
    11. [RequireComponent(typeof(Camera))]
    12. public class ManualUIRenderer : MonoBehaviour
    13. {
    14.     private static readonly int MainTexProperty = Shader.PropertyToID("_MainTex");
    15.     private static readonly int TextureSampleAddProperty = Shader.PropertyToID("_TextureSampleAdd");
    16.     private static readonly int ColorProperty = Shader.PropertyToID("_Color");
    17.  
    18.     private Camera targetCamera;
    19.     private CommandBuffer commandBuffer;
    20.  
    21.     private void Awake()
    22.     {
    23.         this.targetCamera = this.GetComponent<Camera>();
    24.  
    25.         this.commandBuffer = new CommandBuffer();
    26.         this.commandBuffer.name = "Manual UI rendering";
    27.         this.targetCamera.AddCommandBuffer(CameraEvent.AfterSkybox, commandBuffer);
    28.     }
    29.  
    30.     private void OnDisable()
    31.     {
    32.         this.commandBuffer.Clear();
    33.     }
    34.  
    35.     private void Update()
    36.     {
    37.         this.commandBuffer.Clear();
    38.         AddUiDrawingCommands(this.targetCamera, this.commandBuffer);
    39.     }
    40.  
    41.     private static void AddUiDrawingCommands(Camera cam, CommandBuffer buffer)
    42.     {
    43.         // Root canvases ordered by screen space depth
    44.         var rootCanvases = FindObjectsOfType<Canvas>()
    45.             .Where(canvas => canvas.isRootCanvas && canvas.renderMode == RenderMode.WorldSpace)
    46.             .OrderByDescending(canvas => cam.WorldToScreenPoint(canvas.transform.position).z);
    47.  
    48.         foreach (var canvas in rootCanvases)
    49.         {
    50.             // Graphics after culling sorted by depth
    51.             var graphics = canvas.GetComponentsInChildren<Graphic>()
    52.                 .Where(graphic => TestCullingMask(graphic, cam.cullingMask))
    53.                 .OrderBy(graphic => graphic.depth);
    54.  
    55.             foreach (Graphic graphic in graphics)
    56.             {
    57.                 AddGraphicDrawingCommands(graphic, buffer);
    58.             }
    59.         }
    60.     }
    61.  
    62.     private static bool TestCullingMask(Graphic graphic, int cullingMask)
    63.     {
    64.         return ((1 << graphic.gameObject.layer) & cullingMask) != 0;
    65.     }
    66.  
    67.     private static void AddGraphicDrawingCommands(Graphic graphic, CommandBuffer buffer)
    68.     {
    69.         // Probably not needed, but let's call it anyway
    70.         graphic.Rebuild(CanvasUpdate.PreRender);
    71.  
    72.         // Determine effective alpha from CanvasGroups (probably not how Unity does this)
    73.         float effectiveAlpha = graphic.GetComponentsInParent<CanvasGroup>()
    74.             .Aggregate(1f, (alpha, group) => alpha * group.alpha);
    75.  
    76.         var material = new Material(graphic.materialForRendering); // material with IMaterialModifiers already applied
    77.         material.SetTexture(MainTexProperty, graphic.mainTexture);
    78.         material.SetColor(ColorProperty, graphic.color * new Color(1f, 1f, 1f, effectiveAlpha));
    79.         if (graphic is Text)
    80.         {
    81.             // Not sure how/when Unity decides to set _TextureSampleAdd, but Text is
    82.             // the only component I've encountered that needs it
    83.             material.SetVector(TextureSampleAddProperty, new Vector3(1, 1, 1));
    84.         }
    85.  
    86.         var mesh = new Mesh();
    87.         // Call protected member Graphic.OnPopulateMesh through reflection to
    88.         // populate the mesh. Probably really slow and skipping quite a few steps
    89.         // in Unity's UI render pipeline, but it seems to work
    90.         using (VertexHelper vh = new VertexHelper())
    91.         {
    92.             graphic.GetType().InvokeMember("OnPopulateMesh",
    93.                 System.Reflection.BindingFlags.Instance |
    94.                 System.Reflection.BindingFlags.InvokeMethod |
    95.                 System.Reflection.BindingFlags.NonPublic,
    96.                 null, graphic, new object[] { vh });
    97.  
    98.             vh.FillMesh(mesh);
    99.         }
    100.  
    101.         buffer.DrawMesh(mesh, graphic.rectTransform.localToWorldMatrix, material);
    102.     }
    103. }
    104.  
    Attach the component to the camera you're using for RenderToCubemap and you should be good to go.

    Edit: updated to work with CanvasGroup alpha
     
    Last edited: Mar 19, 2018
    playaley, tigme and dtamay3 like this.
  5. pChandhok

    pChandhok

    Joined:
    Mar 6, 2018
    Posts:
    4
    Not working anymore with Unity 2018.2 any temporary fixes?
     
  6. Mnstrspeed

    Mnstrspeed

    Joined:
    Feb 9, 2017
    Posts:
    7
    I just did a quick test with 2018.2 and it's still working here. What kind of components are you using in your UI? I only needed the bare basics for my purposes (basically only
    Text
    and
    Image
    ), so it's possible this code never worked with your UI, even before 2018.2. For example, I know TextMesh Pro components don't work right now, but I'm not using those in my project so I haven't looked into why that is.
     
  7. pChandhok

    pChandhok

    Joined:
    Mar 6, 2018
    Posts:
    4
    I am using the same with Unity Recorder to record a 360 video which uses targetCamera.RenderToCubemap(m_Cubemap1, 63, Camera.MonoOrStereoscopicEye.Left); to capture the cubemap in Camera360Input.cs but I am unable to get UI in the output. I am only using basic UI Text and Image.
     
  8. Mnstrspeed

    Mnstrspeed

    Joined:
    Feb 9, 2017
    Posts:
    7
    I'm not sure what it could be then. It's possible the images are being rendered but at the wrong scale/position; like I said it's a very hacky solution. I attached my working test project in case you want to investigate further.
     

    Attached Files:

  9. pChandhok

    pChandhok

    Joined:
    Mar 6, 2018
    Posts:
    4
    Thanks got it working in a fresh project.
     
  10. Chris-KillerSnails

    Chris-KillerSnails

    Joined:
    Oct 6, 2017
    Posts:
    4
    In order to get the text to appear using these scripts, I had to change

    Code (CSharp):
    1.  this.targetCamera.AddCommandBuffer(CameraEvent.AfterSkybox, commandBuffer);
    to:

    Code (CSharp):
    1. this.targetCamera.AddCommandBuffer(CameraEvent.AfterEverything, commandBuffer);
    After that, text appeared! :) Thanks so much!
     
  11. fherbst

    fherbst

    Joined:
    Jun 24, 2012
    Posts:
    802
    Has anyone here reported a bug about this? If yes, could you please post the case number here?
    Also tagging @phil_lira for URP support of this and @LeonhardP as this is a problem in current alpha/beta still.
     
  12. vadimtihonyuk

    vadimtihonyuk

    Joined:
    Feb 26, 2018
    Posts:
    32
    Doesn't work with TextMeshPro UI
     
  13. tigme

    tigme

    Joined:
    Jul 1, 2014
    Posts:
    37
    Thanks for the script... I tested it U2021.1.16f1 and works for normal UI text but not TMP Pro. TMP Pro readers a solid block. So this is perfect for rendering normal text.

    Result:
    The red rectangle is TMP Pro
    The Top text above the Cube is normal UI Text
     

    Attached Files:

  14. tigme

    tigme

    Joined:
    Jul 1, 2014
    Posts:
    37
    Would love it if Unity can fix this because it is something I intend to use.

    Does any know if any of the commercial assets work properly with 360 renders?
     
  15. jumpgate_lewis

    jumpgate_lewis

    Joined:
    Mar 24, 2020
    Posts:
    1
    If anyone stumbles across this looking for a fix for TextMeshProUGUI elements (Unsure about TextMeshPro mesh text) I was able to modify Mnstrspeed's script with the following to get it to render correctly:


    var mesh = new Mesh();

    // Call protected member Graphic.OnPopulateMesh through reflection to
    // populate the mesh. Probably really slow and skipping quite a few steps
    // in Unity's UI render pipeline, but it seems to work
    using (VertexHelper vh = new VertexHelper())
    {
    graphic.GetType().InvokeMember("OnPopulateMesh",
    System.Reflection.BindingFlags.Instance |
    System.Reflection.BindingFlags.InvokeMethod |
    System.Reflection.BindingFlags.NonPublic,
    null, graphic, new object[] { vh });

    vh.FillMesh(mesh);
    }

    if (graphic is TextMeshProUGUI)
    {
    var textMeshProUGUI = graphic as TextMeshProUGUI;
    mesh = textMeshProUGUI.mesh;
    material = textMeshProUGUI.fontMaterial;
    }

    buffer.DrawMesh(mesh, graphic.rectTransform.localToWorldMatrix, material);
     
    tigme and fherbst like this.
  16. BlaideFedorowytsch

    BlaideFedorowytsch

    Joined:
    Nov 28, 2022
    Posts:
    2
    UI Buttons that are tinted black are showing up white... Perhaps the button tints aren't rendered?


    Edit:

    For anyone else running into this issue, I Modfied Mnstrspeed's script with jumpgate_lewis's modification and then further modified it to resolve the above, I did also have to make a second script to keep track of UnityEngine.UI.Selectables selection states, because their selection state is protected, and i didn't want to replace button with a class that inherits from button or selectable.

    My SelectableStateTracker:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5. using UnityEngine.UI;
    6.  
    7. /// <summary>
    8. /// Unitys default buttons in the normal UI  has selectionstate as a protected variable, can't check it to work around tint not showin in video correctly. so alternatively i track it here.
    9. /// </summary>
    10. public class SelectableStateTracker : MonoBehaviour, IPointerDownHandler,IPointerEnterHandler,IPointerExitHandler,IPointerUpHandler,ISelectHandler,IDeselectHandler
    11. {
    12.  
    13.     private bool hovered = false;
    14.     private bool pressed = false;
    15.     private bool selected = false;
    16.     public Selectable selectable;
    17.  
    18.     private void Awake()
    19.     {
    20.         selectable = GetComponent<Selectable>();
    21.  
    22.     }
    23.  
    24.     public enum SelectionState {highlighted,normal,pressed,disabled,selected};
    25.  
    26.     public SelectionState CurrentSelectionState = SelectionState.normal;
    27.  
    28.     public void OnPointerDown(PointerEventData eventData)
    29.     {
    30.         pressed = true;
    31.     }
    32.  
    33.     public void OnPointerEnter(PointerEventData eventData)
    34.     {
    35.         hovered = true;
    36.    
    37.     }
    38.  
    39.     public void OnPointerExit(PointerEventData eventData)
    40.     {
    41.         hovered = false;
    42.    
    43.     }
    44.  
    45.     public void OnPointerUp(PointerEventData eventData)
    46.     {
    47.         pressed = false;
    48.  
    49.     }
    50.  
    51.     public SelectionState evaluate()
    52.     {
    53.         if (!selectable.interactable)
    54.         {
    55.             return SelectionState.disabled;
    56.         }
    57.      
    58.  
    59.         return (hovered, pressed,selected) switch
    60.         {
    61.  
    62.             (false, false,false) => SelectionState.normal,
    63.             (true, false, false) => SelectionState.highlighted,
    64.             (false, true, false) => SelectionState.pressed,
    65.             (false, true, true) => SelectionState.pressed,
    66.             (true, true, false) => SelectionState.pressed,
    67.             (true, true, true) => SelectionState.pressed,
    68.             _ => SelectionState.selected
    69.         };
    70.     }
    71.  
    72.     public void OnSelect(BaseEventData eventData)
    73.     {
    74.         selected = true;
    75.     }
    76.  
    77.     public void OnDeselect(BaseEventData eventData)
    78.     {
    79.         selected = false;
    80.     }
    81. }
    82.  


    And The Modified version of the ManualUIRenderer
    Code (CSharp):
    1.  
    2. using System.Linq;
    3. using TMPro;
    4. using UnityEngine;
    5. using UnityEngine.EventSystems;
    6. using UnityEngine.Rendering;
    7. using UnityEngine.UI;
    8.  
    9. /// <summary>
    10. /// Component that manually renders world-space UI Graphics to a Camera. Intended for
    11. /// use with Camera.RenderToCubemap which seems to ignore the built-in UI system
    12. /// </summary>
    13. [RequireComponent(typeof(Camera))]
    14. public class ManualUIRenderer : MonoBehaviour
    15. {
    16.     private static readonly int MainTexProperty = Shader.PropertyToID("_MainTex");
    17.     private static readonly int TextureSampleAddProperty = Shader.PropertyToID("_TextureSampleAdd");
    18.     private static readonly int ColorProperty = Shader.PropertyToID("_Color");
    19.  
    20.     private Camera targetCamera;
    21.     private CommandBuffer commandBuffer;
    22.  
    23.    
    24.  
    25.     private void Awake()
    26.     {
    27.         this.targetCamera = this.GetComponent<Camera>();
    28.  
    29.         this.commandBuffer = new CommandBuffer();
    30.         this.commandBuffer.name = "Manual UI rendering";
    31.         this.targetCamera.AddCommandBuffer(CameraEvent.AfterSkybox, commandBuffer);
    32.         //this.targetCamera.AddCommandBuffer(CameraEvent.AfterEverything, commandBuffer);
    33.  
    34.        
    35.  
    36.     }
    37.  
    38.     private void OnDisable()
    39.     {
    40.         this.commandBuffer.Clear();
    41.     }
    42.  
    43.     private void Update()
    44.     {
    45.         this.commandBuffer.Clear();
    46.         AddUiDrawingCommands(this.targetCamera, this.commandBuffer);
    47.     }
    48.  
    49.     private static void AddUiDrawingCommands(Camera cam, CommandBuffer buffer)
    50.     {
    51.         // Root canvases ordered by screen space depth
    52.         var rootCanvases = FindObjectsOfType<Canvas>()
    53.             .Where(canvas => canvas.isRootCanvas && canvas.renderMode == RenderMode.WorldSpace)
    54.             .OrderByDescending(canvas => cam.WorldToScreenPoint(canvas.transform.position).z);
    55.  
    56.         foreach (var canvas in rootCanvases)
    57.         {
    58.             // Graphics after culling sorted by depth
    59.             var graphics = canvas.GetComponentsInChildren<Graphic>()
    60.                 .Where(graphic => TestCullingMask(graphic, cam.cullingMask))
    61.                 .OrderBy(graphic => graphic.depth);
    62.  
    63.             foreach (Graphic graphic in graphics)
    64.             {
    65.                 AddGraphicDrawingCommands(graphic, buffer);
    66.             }
    67.         }
    68.     }
    69.  
    70.     private static bool TestCullingMask(Graphic graphic, int cullingMask)
    71.     {
    72.         return ((1 << graphic.gameObject.layer) & cullingMask) != 0;
    73.     }
    74.  
    75.     private static void AddGraphicDrawingCommands(Graphic graphic, CommandBuffer buffer)
    76.     {
    77.         // Probably not needed, but let's call it anyway
    78.         graphic.Rebuild(CanvasUpdate.PreRender);
    79.  
    80.         // Determine effective alpha from CanvasGroups (probably not how Unity does this)
    81.         float effectiveAlpha = graphic.GetComponentsInParent<CanvasGroup>()
    82.             .Aggregate(1f, (alpha, group) => alpha * group.alpha);
    83.  
    84.         var material = new Material(graphic.materialForRendering); // material with IMaterialModifiers already applied
    85.         material.SetTexture(MainTexProperty, graphic.mainTexture);
    86.         material.SetColor(ColorProperty, graphic.color * new Color(1f, 1f, 1f, effectiveAlpha));
    87.  
    88.         if(graphic.GetComponent<SelectableStateTracker>() != null)
    89.         {
    90.             SelectableStateTracker s = graphic.GetComponent<SelectableStateTracker>();
    91.             if (s.selectable.transition == Selectable.Transition.ColorTint)
    92.             {
    93.                 SelectableStateTracker.SelectionState state = s.evaluate();
    94.  
    95.                 Color c = (state) switch
    96.                 {
    97.                     SelectableStateTracker.SelectionState.highlighted => s.selectable.colors.highlightedColor,
    98.                     SelectableStateTracker.SelectionState.normal => s.selectable.colors.normalColor,
    99.                     SelectableStateTracker.SelectionState.pressed => s.selectable.colors.pressedColor,
    100.                     SelectableStateTracker.SelectionState.disabled => s.selectable.colors.disabledColor,
    101.                     SelectableStateTracker.SelectionState.selected => s.selectable.colors.selectedColor,
    102.                     _ => s.selectable.colors.normalColor
    103.                 };
    104.  
    105.  
    106.                 material.SetColor(ColorProperty,c * new Color(1f, 1f, 1f, effectiveAlpha));
    107.             }
    108.  
    109.         }
    110.  
    111.         if (graphic is Text)
    112.         {
    113.             // Not sure how/when Unity decides to set _TextureSampleAdd, but Text is
    114.             // the only component I've encountered that needs it
    115.             material.SetVector(TextureSampleAddProperty, new Vector3(1, 1, 1));
    116.         }
    117.  
    118.         var mesh = new Mesh();
    119.  
    120.  
    121.  
    122.         // Call protected member Graphic.OnPopulateMesh through reflection to
    123.  
    124.         // populate the mesh. Probably really slow and skipping quite a few steps
    125.  
    126.         // in Unity's UI render pipeline, but it seems to work
    127.  
    128.         using (VertexHelper vh = new VertexHelper())
    129.  
    130.         {
    131.  
    132.             graphic.GetType().InvokeMember("OnPopulateMesh",
    133.  
    134.                 System.Reflection.BindingFlags.Instance |
    135.  
    136.                 System.Reflection.BindingFlags.InvokeMethod |
    137.  
    138.                 System.Reflection.BindingFlags.NonPublic,
    139.  
    140.                 null, graphic, new object[] { vh });
    141.  
    142.  
    143.  
    144.             vh.FillMesh(mesh);
    145.  
    146.         }
    147.  
    148.  
    149.  
    150.         if (graphic is TextMeshProUGUI)
    151.  
    152.         {
    153.  
    154.             var textMeshProUGUI = graphic as TextMeshProUGUI;
    155.  
    156.             mesh = textMeshProUGUI.mesh;
    157.  
    158.             material = textMeshProUGUI.fontMaterial;
    159.  
    160.         }
    161.  
    162.  
    163.  
    164.         buffer.DrawMesh(mesh, graphic.rectTransform.localToWorldMatrix, material);
    165.  
    166.     }
    167. }
    168.  
     
    Last edited: Oct 6, 2023