An efficient way to handle 1000+ Vectors in a shader? (Custom FullScreenPass)

Viewed 26

So I wrote a simple proximity shader that takes a point and applies a color depending on the distance. This shader is applied to a Global Custom Pass Volume and is rendered with FullScreenPass. I have since expanded it to receive two VectorArrays: one that contains a list of positions and one that contains a matching list of shader information (fall off, intensity, etc) that goes along with the corresponding index in the Position array.

ProximityShader.shader:

uniform float4 _Positions[MAX_RENDERS];
uniform float4 _ShadingDataArray[MAX_RENDERS];

This data is then processed via a for loop and combined using the maximum value of the given iteration. This means I am effectively calculating the distance of up to 256 (MAX_RENDERS) positions every time the shader executes. See the whole FullScreenPass method below.

This works pretty well, but the problem is that on lower-to-mid-range machines if MAX_RENDERS is much bigger than 256, the framerate takes hit on the CPU. Even on a 2019 i9 it will still run at around 30-35 FPS at 2k, which I suppose this is acceptable for now, but my concern is that this won't scale very well and even 256 might be taxing later on once my scenes have more complex geometry. It would be nice to be able to efficiently render thousands of such effects to give myself enough headroom. But it's just too much memory to instantiate with the CPU inside the shader. And besides, the maximum array size is 1023 on a shader so it might be worth considering some other way to get the shader the data it needs..

So my questions is: of the multiple approaches I can take to optimize my shader, which are worth considering and which make the most sense and how do I do them?

Possible approaches:

  1. Lower the resolution of the RTHandles buffer (This might be acceptable as the gradient is smooth enough that it probably won't look terrible)
  2. Use GPU instancing (How? The examples in the tutorials I've seen don't seem to support HDRP or FullScreenPasses)
  3. Use multiple passes (Would thousands of passes really make sense? How does one do this?)

Any feedback is appreciated. Thanks!

FullScreenPass:

 float4 FullScreenPass(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);
        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
 
        float4 color = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);
         
        // Initialize color data that will be blended with result of for loop
        float4 baseColor = _BaseColor;
        float4 colorData = baseColor;
     
        for(int index = 0; index < _NumPositions; index++) {
            // Read incident data
            float4 _Position = _Positions[index];
            float4 _ShaderData = _ShadingDataArray[index];
         
            // Read shader data
            float4 falloff = _ShaderData[0];
            float4 falloffWidth = _ShaderData[1];
            float4 maxIntensity = _ShaderData[2];
         
            // Create inner and outer falloff
            float innerFalloff = max(falloff - falloffWidth, 0);
            float outerFalloff = falloff;
         
            /* Calculate the object's distance from the current fragment (or whatever it is I still don't really
            understand lol) */
            float objDist = distance(posInput.positionWS, _Position);
         
            // Create two gradients. One bigger than the other
            float4 outerFalloffGradient = clamp((outerFalloff - objDist) / outerFalloff, 0, 1);
            float4 innerFalloffGradient = clamp((1 - (innerFalloff - objDist)) / innerFalloff, 0, 1);
         
            // Subtract the smaller gradient from the bigger one.
            float4 gradient = min(outerFalloffGradient, innerFalloffGradient);
                       
            // Now interpolate the color data using the donut gradient
            float4 donutData = max(colorData, lerp(baseColor, color, gradient));
         
            // Calculate the percent intensity decrease
            float distanceFalloff = 1 - (outerFalloff / maxIntensity);
         
            // Fade back to black if we're far from the max intensity point
            colorData = lerp(baseColor, donutData, distanceFalloff);
        }
     
        return colorData;
    }
0 Answers
Related