Search Unity

Supersample Rendering

Discussion in 'General Graphics' started by yuzu_drink, Jun 2, 2015.

  1. yuzu_drink

    yuzu_drink

    Joined:
    Jan 15, 2012
    Posts:
    14
    Hello,

    I am trying to do supersample rendering (render a higher resolution than the screen, then scale it down to the screen resolution) so that a shader that stretches pixels looks better. (Otherwise, one pixel gets spread across several when the view stretches.)

    I have accomplished this by rendering the effect to a RenderTexture and then drawing that texture to the screen during OnGUI, but this technique has the downside of not preserving colorspace properly (linear/gamma). (I think it's probably also slower...) And it also doesn't draw Gizmos.

    I've spent all day so far trying to work around this, and nothing seems to work:
    - Creating a larger rendertexture and blitting to that first (just draws the low-res image to the texture)
    - Set the camera's target texture to the render texture, blit to that in OnRenderImage, then set RenderTexture.active to null (and camera's target texture to null, or else Unity crashes) and to a blit to null (the screen buffer) -- this blits whatever fits of the larger texture onto the screen buffer and doesn't scale it to fit.

    Graphics.DrawTexture has the same problems as GUI.DrawTexture.

    Has anyone gotten this to work?
     
  2. Chman

    Chman

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    721
    Here's how I do it :
    • Create an empty GameObject on the scene, zero'd the transform, add a Camera to it (culling mask to none, clear flags to solid color) and a new component (lets name it Supersampling).
    • Disable all the cameras on your scene (either manually or in the Supersampling component, see below).
    • In Supersampling, have a list of all cameras that should be rendered.
    • In Supersampling.OnRenderImage() create/recycle your supersampling render texture and render all the cameras from the list to this RT.
    Code (CSharp):
    1. for (int i = 0; i < Cameras.Count; i++)
    2. {
    3.     Camera cam = Cameras[i];
    4.     cam.targetTexture = supersamplingRT;
    5.     cam.Render();
    6.     cam.targetTexture = null;
    7. }
    • Finally, blit the RT to screen. For supersampling 2x2, a simple bilinear filter works fine, but with sizes bigger than 2x2 you'll want to use a lowpass or lanczos filter.
    I'll try to find some time to clean up my code and post it on Github.
     
    SAOTA likes this.
  3. yuzu_drink

    yuzu_drink

    Joined:
    Jan 15, 2012
    Posts:
    14
    Thanks, this is awesome!

    So when you blit to the screen, do you use a shader to downsample for you? Whenever I've tried blitting large textures to the screen, they just copy whatever can be shown at native resolution to the screen and clip all of the extra (so for a 2x2 supersample, I only get the bottom 1/4 of the texture drawn to screen).
     
  4. BloodMarked

    BloodMarked

    Joined:
    Dec 11, 2013
    Posts:
    28
    i thought id upload my script as it should make things easier for others

    so as described, you
    1) deactivate your main camera
    2) create a new camera and set culling mask to none and clear flags to solid color
    3) attach this script
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using UnityStandardAssets.ImageEffects;
    5.  
    6. public class supersampling : MonoBehaviour {
    7.  [ExecuteInEditMode]
    8.  
    9.  RenderTexture supersamplingRT;
    10.  public Camera cam;
    11.  const int factor = 2;
    12.  
    13.  void Start () {
    14.  supersamplingRT = new RenderTexture (Screen.width*factor,Screen.height*factor,24, RenderTextureFormat.ARGB32);
    15.  }
    16.  
    17.  void OnRenderImage (RenderTexture source, RenderTexture destination) {
    18.  cam.targetTexture = supersamplingRT;
    19.  cam.Render();
    20.  cam.targetTexture = null;
    21.  
    22.  Graphics.Blit (supersamplingRT, destination);
    23.  }
    24. }
    25.  
    4) link your main camera to the script

    in the script you can change the amount of supersampling and you could include an array if you render multiple cameras

    note that this does not work well with resizing the window, the image will be distorted.
    sadly the size of rendertextures cant be changed later, if someone has a solution thatd be cool
     
    UziMonkey and SAOTA like this.
  5. eco_bach

    eco_bach

    Joined:
    Jul 8, 2013
    Posts:
    1,601
    whats the advantage of writing your own custom code over using the SSAA asset?
     
  6. GMM

    GMM

    Joined:
    Sep 24, 2012
    Posts:
    301
    Thank for explaining how you achieved this effect, i made a very similar system for Unity 4 that no longer works in Unity 5 because reasons.

    Here is a sample of a system that checks the current main camera in the scene and applies a scaling factor to it, this system makes it possible to keep the effect active across of cameras with no work involved in switching the actual camera.

    It does have some performance issues right now since the way a new camera is picked using the Camera.Main function, but the dummy camera should be the one tagged as "MainCamera". If someone has a good suggestion on how to fix this in a way that allows the script to automatically pick the camera, i would like to hear about it.

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ScreenIndependenRenderer : MonoBehaviour
    6. {
    7.     RenderTexture viewPortRT;
    8.     public float scale = 0.1f;
    9.     private float lastScale;
    10.     private Camera currentCamera;
    11.     private Vector2 currentResolution;
    12.     private bool shouldUpdateRenderTexture = false;
    13.  
    14.     Camera dummyCam;
    15.  
    16.     // How big or small the scale can be
    17.     private float minScale = 0.1f;
    18.     private float maxScale = 4.0f;
    19.  
    20.     void Awake()
    21.     {
    22.         // Get the current resolution
    23.         currentResolution = new Vector2(Screen.width, Screen.height);
    24.  
    25.         // Set the last scale to the current scale to avoid creating multiple render textures on create
    26.         lastScale = scale;
    27.  
    28.         // Set up the temporary camera
    29.         dummyCam = gameObject.AddComponent<Camera>();
    30.         dummyCam.cullingMask = 0;
    31.         dummyCam.backgroundColor = Color.black;
    32.         dummyCam.clearFlags = CameraClearFlags.SolidColor;
    33.  
    34.         // Create a render texture
    35.         CreateRenderTexture();
    36.     }
    37.  
    38.     void Update()
    39.     {
    40.         // Checks if the resolution has changed
    41.         CheckResolution();
    42.  
    43.         // Checks if the camera has changed
    44.         CheckCamera();
    45.  
    46.         // Checks if the scale has changed
    47.         CheckScale();
    48.  
    49.  
    50.         if (shouldUpdateRenderTexture)
    51.         {
    52.             CreateRenderTexture();
    53.         }
    54.  
    55.         shouldUpdateRenderTexture = false;
    56.     }
    57.  
    58.     // Checks for resolution changes
    59.     void CheckResolution()
    60.     {
    61.         // Get the current screen resolution
    62.         Vector2 tempRes = new Vector2(Screen.width, Screen.height);
    63.  
    64.         // Check if the resolution has changed
    65.         if (tempRes != currentResolution)
    66.         {
    67.             // Save the new resolution
    68.             currentResolution = tempRes;
    69.  
    70.             // Set the correct aspect ratio for the camera
    71.             currentCamera.aspect = currentResolution.x / currentResolution.y;
    72.  
    73.             // Mark the render texture for updating
    74.             shouldUpdateRenderTexture = true;
    75.         }
    76.     }
    77.  
    78.     void CheckCamera()
    79.     {
    80.         // Get the main camera
    81.         Camera mainCamera = Camera.main;
    82.  
    83.         // Check if there is a main camera
    84.         if (mainCamera)
    85.         {
    86.             // Check if the main camera is the same as the current camera
    87.             if (mainCamera != currentCamera)
    88.             {
    89.                 // Camera is not the same, set the new camera
    90.                 currentCamera = mainCamera;
    91.  
    92.                 // Set the correct aspect ratio for the camera
    93.                 currentCamera.aspect = currentResolution.x / currentResolution.y;
    94.             }
    95.         }
    96.         else
    97.         {
    98.             // Set to null
    99.             currentCamera = null;
    100.         }
    101.     }
    102.  
    103.     void CheckScale()
    104.     {
    105.         if (lastScale != scale && currentCamera)
    106.         {
    107.             // Check if the scale is below zero
    108.             if (scale <= minScale)
    109.             {
    110.                 scale = minScale;
    111.             }
    112.  
    113.             // Check if the scale is above the max resolution
    114.             else if (scale >= maxScale)
    115.             {
    116.                 scale = maxScale;
    117.             }
    118.  
    119.             // Log the scale
    120.             lastScale = scale;
    121.  
    122.             // Mark the render texture for updating
    123.             shouldUpdateRenderTexture = true;
    124.         }
    125.     }
    126.  
    127.     // Creates the rendertexture
    128.     void CreateRenderTexture()
    129.     {
    130.         viewPortRT = new RenderTexture((int)(Screen.width * scale), (int)(Screen.height * scale), 24, RenderTextureFormat.ARGB32);
    131.     }
    132.  
    133.     void OnRenderImage(RenderTexture source, RenderTexture destination)
    134.     {
    135.         // Check if we have a camera before rendering
    136.         if (currentCamera && viewPortRT)
    137.         {
    138.             currentCamera.targetTexture = viewPortRT;
    139.             currentCamera.Render();
    140.             currentCamera.targetTexture = null;
    141.  
    142.             Graphics.Blit(viewPortRT, destination);
    143.         }
    144.     }
    145. }
    146.  
     
    Last edited: Sep 11, 2015
    SAOTA likes this.
  7. GMM

    GMM

    Joined:
    Sep 24, 2012
    Posts:
    301
    Just a very small update to my previous post.

    I implemented a class that i attach to my scene cameras that also handles toggling various effects on and off, making a static reference to the current main camera and referencing that in the screen independent rendering system instead of Camera.Main does work.

    I won't share the the updated solution as it also references some of my other camera control code, but it should be very simple to implement.