Search Unity

Any optimizations to be made on large for loop?

Discussion in 'General Graphics' started by millercarso01, Oct 16, 2021.

  1. millercarso01

    millercarso01

    Joined:
    Nov 22, 2019
    Posts:
    3
    Hello, I have been developing this image effect for quite some time now and it has gone through many iterations to get to this point (no pun intended). I was wondering though if there were any ways to speed it up still? It is pretty taxing on my system (it is only a laptop ATM) and I feel it could still use some work. I have tried using GameObjects, then object pooling, then the Graphics Library, then Compute buffers, and now here where I am using the GL library. This is the image effect:
    https://drive.google.com/file/d/1vQMX9NivEGINE0Pxep29yRt3I8Gnt1LS/view?usp=sharing
    and these are the two scripts:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class GLRect : MonoBehaviour
    6. {
    7.     public int speed;
    8.     public float width = .1f;
    9.     public float height = .1f;
    10.     public float xMult;
    11.     public float yMult;
    12.     static Material lineMaterial;
    13.     public Camera cam;
    14.  
    15.     private void Start()
    16.     {
    17.         if (!lineMaterial)
    18.         {
    19.             Shader shader = Shader.Find("Hidden/Internal-Colored");
    20.             lineMaterial = new Material(shader);
    21.             //lineMaterial.hideFlags = HideFlags.HideAndDontSave;
    22.             // Turn on alpha blending
    23.             //lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
    24.             //lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
    25.             // Turn backface culling off
    26.             //lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
    27.             // Turn off depth writes
    28.             lineMaterial.SetInt("_ZWrite", 0);
    29.         }
    30.  
    31.     }
    32.  
    33.     void startGL()
    34.     {
    35.         lineMaterial.SetPass(0);
    36.         GL.MultMatrix(transform.localToWorldMatrix);
    37.         GL.Begin(GL.QUADS);
    38.     }
    39.  
    40.     void OnRenderObject()
    41.     {
    42.         startGL();
    43.         for (int i = 0; i < speed; i++)
    44.         {
    45.             Vector2 v = getRandCoords();
    46.             Vector2 colorGenCoord = new Vector2((v.x / xMult + 1) * xMult, (v.y / yMult + 1) * yMult);
    47.             Color32 c = textureHandler.getPixelColor(colorGenCoord.x, colorGenCoord.y, xMult * 2, yMult * 2);
    48.             GL.Color(c);
    49.             GL.Vertex3(v.x, v.y, 1);
    50.             GL.Vertex3(v.x, v.y + height, 1);
    51.             GL.Vertex3(v.x + width, v.y + height, 1);
    52.             GL.Vertex3(v.x + width, v.y, 1);
    53.         }
    54.     }
    55.     private Vector2 getRandCoords()
    56.     {
    57.             Vector2 v;
    58.             float theta = Random.Range(0, 2 * Mathf.PI);
    59.             float a = Random.Range(0, 1f);
    60.             v.x = a * Mathf.Cos(theta) * xMult;
    61.             v.y = a * Mathf.Sin(theta) * yMult;
    62.         return v;
    63.     }
    64.  
    65. }
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class textureHandler : MonoBehaviour
    6. {
    7.     public static int resolution = 256;
    8.     public RenderTexture rt;
    9.     private static Texture2D tex;
    10.     private static byte[] colorArray = new byte[resolution*resolution*4];
    11.     public static int abberation = 4;
    12.     // Start is called before the first frame update
    13.     void Start()
    14.     {
    15.         rt = new RenderTexture(resolution,resolution,0);
    16.         GetComponent<Camera>().targetTexture = rt;
    17.         RenderTexture.active = rt;
    18.         tex = new Texture2D(rt.width, rt.height);
    19.     }
    20.     private void OnRenderImage(RenderTexture source, RenderTexture destination)
    21.     {
    22.  
    23.         tex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
    24.         colorArray = tex.GetRawTextureData();
    25.     }
    26.     public static Color32 getPixelColor(float x, float y, float camWidth, float camHeight)
    27.     {
    28.         byte iX = (byte)Mathf.RoundToInt((x / camWidth) * tex.width - 1);
    29.         byte iY = (byte)Mathf.RoundToInt((y / camHeight) * tex.height - 1);
    30.         int index = (iX + iY * resolution) * abberation;
    31.         Color32 color = new Color32(colorArray[index],colorArray[index + 1],colorArray[index + 2], 255);
    32.         return color;
    33.     }
    34. }
    35.  
    As you can see, the first one is the main script and is attached to the camera you actually see, the second script is attached to a camera that gets input and renders it to an array. I don't really know how else to optimize this and I'm sure you guys know something. Thanks!
     
    Last edited: Oct 17, 2021
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The first improvement you’d get us by not doing this. Render your quads using a material that takes the source render texture. Then calculate the UV position for the center of the quad and assign that for all 4 vertices. Reading the render texture back to the CPU is brutally expensive.

    After that the next best option might be to not construct a new mesh every frame on the GPU. Make it once with the number of quads you want and jitter them with their vertex shader.

    After that, stop using a bunch of quads and use a compute shader or vertex fragment shader using texture bombing to get the jittered positions.
     
    halley likes this.
  3. millercarso01

    millercarso01

    Joined:
    Nov 22, 2019
    Posts:
    3
    Thank you for the reply! I had an idea that something like this may be the case since I've heard reading pixels is extremely intensive and the larger the texture the exponentially larger the task is especially every frame. Quick question though, how would I go about doing this? I'm slightly familiar with shaders but how would you get one to render quads like that? Also how would you get them to calculate vertex position? (I'm guessing a vertex shader of some sort but just making sure).
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    For that first option, a basic transparent unlit textured shader or particle shader would work. No custom shader would be needed if the mesh data you're rendering is appropriately setup.

    For the second option, yes, you'd need a custom vertex fragment shader with the position of the quads being adjusted in the vertex shader part of the vertex fragment shader. The idea is you'd still be rendering a mesh that's a bunch of quads, just one you generate once when you first need it or the number of quads you need changes, with a regular grid of positions & UVs, and reuse rather than one you're creating on the CPU and reuploading every frame. The later is what you're doing when you use the
    GL.Vertex()
    calls. You can write very similar code to what you already have to take the UV and
    _Time.y
    or some other shader uniform float you update every frame to jitter the positions. Then it's only a
    Graphics.DrawMeshNow()
    every frame instead of the massive loop of
    GL.Vertex()
    calls and you're done.

    The last option and to answer the question:
    Search for the term "texture bombing" and you'll find a ton of resources. Admittedly they're old and I don't think any are Unity specific, but they'll describe the concept both using mesh quads like you currently are doing and using a shader that can make a single triangle look like it's covered in hundreds or millions of "quads" (and the number doesn't significantly affect how expensive the shader is).
     
  5. millercarso01

    millercarso01

    Joined:
    Nov 22, 2019
    Posts:
    3
    Hey, I've been trying to work at this for awhile, I got the RenderTexture part to work, however, unluckily, it seems like the most intensive part of the program is drawing the Quads. I was wondering however, since it seems like you're much more experienced than I if you could had an example of how exactly to accomplish what you're saying? I've been working on trying to get this to work for awhile and it seems to just not want to. Any help is appreciated, thanks.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I would write a texture bombing shader that uses _Time as a random hash seed and skip almost all of the c# side of things.