I would suggest you try doing it without textures. Think of each point where the blood start as a circle. Then you can make a function for it.
See IQuilez’s blog (or bible…): https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
In this case, a circle is a 2D spere so:
float sdSphere( float2 p, float s )
return length( p ) - s;
From there you can feed a position p and a size s to the function above each loop of your marching instead of sampling a texture… You can then color the sphere based on how far from it’s center that pixel is, calculate normals if you need using the following (replace function with sdSphere from above)
float2 calcNormal( in vec2 x, in float eps )
vec2 e = vec2( eps, 0.0 );
return normalize( vec2( function(x+e.xyy) - function(x-e.xyy),
function(x+e.yxy) - function(x-e.yxy) ) );
Now all you need to do is run this 2-3 times for the origin points, add a pseudo random jitter to the size of the sphere each loop, maybe some offsets. etc…
Not having a texture to run in your loop could speed it up a lot since that sphere function is super simple.
Also, note that when you get into marching the cost is directly scaling with the number of pixel used, so even if you reduce steps as it gets further away, you are likely not going to have great return compared to the reduction in quality as the number of pixels to process is already going down. So in reality, this optim is giving you quality problems, while not addressing your primary area of problem, when this thing is close to camera and taking a lot of screen space.