Using texture as buffer

I’m trying to draw circles that will grow and fade away on a model at arbitrary points picked by a ‘raycast to UV’ operation.

My idea was to create a procedural texture that will draw circles on a model based on a texture map. I’m drawing individual pixels to the texture map and I’m trying to write a shader that will draw circles anywhere it sees pixels of a certain color.

I was able to figure out a formula for drawing circles at arbitrary coordinates. But looping through the texture map in the shader to find points doesn’t work. I’m guessing far too many operations per pixel and per frame? If the camera looks at the model Unity freezes.

CODE SNIPPET:

for(int p = 0; p < _MainTex_TexelSize.z; p++) {
  for(int j = 0; j < _MainTex_TexelSize.w; j++) {
     if( isWhite(tex2D(_MainTex, float2(p,j))) ) {
       cc1 = circle(float2(i.uv.x, i.uv.z), 3., float2(p, j), _Color2);
       if(!isWhite(cc1)) result = cc1;
     } else {
       result = _Color;
     }
  }
}

My guess was that this was way too much to be doing per pixel but is there an efficient way to do this? Or maybe I’m thinking about this the wrong way?

Some questions:

  1. How many circles are you planning on drawing?
  2. Are the circles going to cover the entire model or be limited to smaller areas?
  3. How dynamic are the circles?
  4. Do you ever remove circles, or just add new ones?

Some suggestions before knowing more:
Use arrays instead of a texture to hold your data. Sampling a texture has a not insignificant cost. Most of the time the GPUs are good at hiding that cost. However if you stack a few dozen samples on top of each other they queue up and that cost becomes more obvious. Also, something as “small” as a 64x64 texture is 4096 pixels, which is a ton of samples to do per pixel.

Sort your data so there are no holes and pass a value that lets the shader know the max number of circles. This will cost a little more on the CPU, but result is large potential savings on the GPU. That said if you have 1000+ circles you want to draw you may need to reconsider how you’re approaching the problem to begin with.

1.) Probably less than 20 or so at a time, I think.
2.) Could be anywhere
3.) Relatively dynamic, each will have a ‘strength’ value that will determine the circles size and transparency that will constantly decrease until it’s gone.
4.) Definitely both

For more context, I’m creating circular ripples on a pond but a sort of simple, ‘vector art’-ish stylized version.

After making this post this morning I’d found this resource: Arrays & shaders: heatmaps in Unity - Alan Zucconi that I think is essentially suggesting exactly what you’re saying and I’d begun to try and implement.

So instead of having the water surface have a texture, it’s got a vector3 list with x and y world coordinates and a strength value in z that gets sent by any object with a raycast with a sendmessage. Then, in the same update function that decrements strengths in the array, send the array to the shader at the end.

Is that what you were suggesting with your approach of an array with no holes?

Is it possible to re-send an array of a different length to a shader dynamically, though?
*Edit: For this last question, the answer is right in the article I linked. No. He’s setting an array with a max length, then setting a variable for how many of them are used.
*Another Edit: It doesn’t look like he’s sending an updating list of points. So would this example still work for me, where I’m needing to quickly and constantly send a new vector list each frame?

Read that tutorial by Alan, it’s a good place to start, though it’s slightly out of date. You will need to use SetVectorArray instead of individually numbered SetVector calls. This is a good thing for performance, but does mean his tutorial doesn’t work anymore. There are some comments on that page that have some fixes, but I don’t think they’re complete. There’s an update here though:

And here’s the documentation for the function you want to use.

Some key stuff:
No, you cannot dynamic change the length of the arrays. You need to set a fixed array size in the shader, and you’ll want to pass an array of that same size to the shader via SetVectorArray. A quote from the documentation above:

By no holes I mean something like this. Imagine you have an array with this data:

Vector3(0.0, 1.0, 1.0),
Vector3(1.0, 0.0, 2.0),
Vector3(0.0, 1.0, 0.0),
Vector3(1.0, 1.0, 3.0)

That third element has a range of zero. If you iterate all 4 elements that third one isn’t doing anything but making your shader slower to render. If you instead sort the array or remove elements that are effectively “empty” you can pass a value to the shader to limit the number of values to iterate over. Alan’s tutorial has some of that.

As usual you were spot on and helped fix my problem. I actually had two problems, besides the massive inefficiency I first described, I’d also carelessly copy-pasted a for loop that was decrementing instead of incrementing, so looking at the texture caused an infinite loop.

Using your advice I re-coded the shader and script and it’s both working and running buttery smooth.

Thank you for your help, bgolus!