#Plexus effect implementation
1 messages · Page 1 of 1 (latest)
Let's continue in here. I can first describe the idea broadly and then go into implementation details if that is ok.
That works!
I wont be able to follow along for now tho, that'll have to wait for a few hours
So starting from the VFX graph with the points only, we would want to create some connections between them. What I figured would be the easiest is to create a second set of particles that would become the connections. In the current implementation every particle has one connection which then connects to one other particle. We could connect to some random particle but that would look very messy so what I did instead was to find the closest particle from each particle and connect to that one.
Originally the code to find the closest particle just looped through the whole buffer of particle positions and find the closest one. This approach though led to this problem where the particles A and B (in the SS) are so close to each other that the closest particle from A is B and the other way around. This would cause the connection from A to connect straight to B and from B to A which would mean one of the connections is doubled. This could lead to some visual artefacts with partially transparent connections but even more so it is an issue of wasting resources and getting less actual connections. This would also cause very predictable connections where these longer connections for example would never happen because both of the particles at the ends would find closer particles to connect to.
What I came up with was to actually not loop through all the particles but approximately half of them (more specifically particleCount / 2 - 1 particles). This ensures that if there is connection from A to B, there cannot be one from B to A since they are guaranteed to not be included in both search ranges. This is the reason the for loop that I showed earlier looked like this:
for (uint i = (index + ((bufferSize / 2) + 1)) % bufferSize; i != index; i = (i + 1) % bufferSize)
The use of the remainder operator (%) is there to not go beyond the range of the buffer but rather start from the start once the end is reached. The other benefit of this approach is that we don't find the absolutely closest particle so we only get relatively short connections but sometimes get little longer ones too (we basically find the closest particle out of about half of them). Oh and this way we can cut the number of operations in half.
What is this buffer you may ask? In order to find the shortest one, we need to store the particle positions somewhere. I'm sure we could access the buffer unity uses internally somehow but there doesn't seem to be any guarantees about it's order etc. so iterating over it may not be trivial. What I did instead was to have a C# script that constructs a GraphicsBuffer of Vector3s/float3s that has the same size as the amount of particles in the system. This buffer is then provided to the effect via VisualEffect.SetGraphicsBuffer. To keep these positions in the buffer up to date, in the Update Particles block of the main particle system, I make each particle write their position to the buffer in their respective index. Unfortunately there is no block in the VFX Graph that writes to a buffer so I had to make one with a Custom HLSL Block (shown in the SS), it isn't more complicated than this though:
void WriteBuffer(inout VFXAttributes attributes, in RWStructuredBuffer<float3> buffer, in uint index, in float3 value)
{
buffer[index] = value;
}
note that inout VFXAttributes attributes is not used at all, it just has to be always there for custom blocks. Usually that would be used to change the properties of the particle in question but we don't want to do any of that here
The earlier referred for loop which finds the "closest particle" is then used here to figure out the position of the connection particle (custom HLSL Operator). Btw ReadBuffer is a custom operator too which just reads the buffer at index. There is the Sample Graphics Buffer node built-in, but unfortunately that won't work together with our own buffer handling methods due to technical limitations (the graph gets confused with the buffer types). Just know I figured I should probably just remove that block and integrate it to the Closest Particle one which has to sample the position at the index anyways. Currently it looks like the second image. The alpha calculation is added later to the function to make the connections fade out when they are about to switch to different position. I can explain that further later
More stress testing. 10k particles running in slightly over 2ms. Definitely not an issue then. Only at 20k particles my pc starts struggling.
This is so good!
Thanks for the explanation too, im working on implementing it right, though i have a few issues, mostly due to me being inexperienced
I havent used vfx graph all that much before, so i dont know how to utilize line particles, how to use 2 sets of particles on the same graph, nor have i worked with HLSL & graphics buffers, so if you could provide me with a step-by-step explanation to all of that, i'd be really grateful!
Im mostly having issues with setting up the 2nd set of particles, as i dont know how to configure it, i'll probably be able to wing creating a graphics buffer & the hlsl scripts!
You can see how I set it up here #1410577592485941371 message. You could even have two different systems side by side in the graph but what I have is just one spawner which spawns two different kinds of particles. If you have bursts of 50 for example, you will get 50 of each type. The spawner just tells each connected system how many to spawn and when. Each system generally needs the initializer connected to update connected to output block. You can reuse a spawner though to synchronize the systems.
When it comes to the line particles, there are couple solutions. If you only want exactly one pixel wide lines, you should use the Output Particle Line block at the end of the system. That is the easiest to use from what I can gather when you just want a line between two points but it is always 1 pixel wide which is why I didn't want that. Maybe the most flexible of options would be to use strips but I personally couldn't figure out how to use them in this case, it is definitely possible though (and they would allow to add more points to the lines to make them curvy and stuff). What I went with was bit weird solution of using simple quads and stretching them to look like lines. I made this Subgraph Block that stretches the quad and places it between position and a target. If you remember from earlier, here #1410577592485941371 message (first image) I set the calculated positions to the position and target position in every update so that I can then place it where it should be when rendering at the Output block. Please do ask if anything is unclear, I probably didn't do great job explaining that
One way to create a Subgraph Block is to right click on any block on the Output context and convert that to Subgraph Block. In case you didn't know, subgraphs are basically analogous to functions in programming. You could do everything in the main graph, but subgraphs allow you to separate some logic into smaller graphs and reuse the subgraphs in multiple places
Custom HLSL code is basically just subgraphs but with pure code. With HLSL you can do some stuff that is not possible with the graph editor alone (like for loops). Noteworthy that there are two types of subgraphs/HLSL code: Blocks and Operators. Operators are these nodes that take in some input and produce an output. Blocks are these nodes that attach to a context (like update or output). They do not produce an output but they can directly modify the particles data like the To Target Line subgraph block does (which is very poorly named by me)
Well, i got the lines working!
I implemented all the custom HLSL, i think? The the thing i need help with is the C# creation of the buffer!
I can show mine, it's bad, sometimes doesn't work and I'm not sure why. I wanted it to work in editor too and was too lazy to figure out how to do it properly. If you just initialize the buffer in Start and free the memory in OnDestroy or something, it should always work in play mode. Getting it to work properly in editor I haven't quite figured out. GraphicsBuffers are something that needs to be released once they are not used (which the release method does) or you will cause a memory leak:
using UnityEngine;
using UnityEngine.VFX;
[ExecuteAlways]
public class BufferSetter : MonoBehaviour
{
public int bufferSize;
GraphicsBuffer buffer;
void OnEnable()
{
if (buffer != null && buffer.IsValid())
{
print("Already exists"); //doesn't seems to ever be called
return;
}
buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, bufferSize, sizeof(float) * 3);
GetComponent<VisualEffect>().SetGraphicsBuffer("PositionBuffer", buffer);
}
void OnDisable()
{
buffer?.Release(); //? makes it only execute if not null
}
}```
It is created as a structured buffer which is basically just an array of structs (which could be vectors for example). Since we want to store 3d positions in the buffer, we use `sizeof(float) * 3` as the stride size (in bytes) which would always evaluate to 12 bytes. I don't know your level of knowledge on this so I might be stating the obvious, but graphics buffers are basically just arrays of data that are located in the GPU memory meaning they can be accessed and modified by the GPU. They are very similar to textures just without the 2d sampling of data so the data must be accessed by index.
Makes sense! So it's basically the space allocated for GPU accessible data
I've tried to implement it, but im now getting this error flooding my console, and im not exactly sure whats causing it
CardSelectionBurst is the VFX asset
Exactly. Just an array (of 12 bytes in this case) in GPU memory
Most likely one of your custom HLSL code. I got the same error for basically any compiler error in the shader code. You could use an HLSL code editor which can point out the errors. I just pasted my code here: https://shader-playground.timjones.io/, it is bit slow but at least it gives sensible compiler errors
Really unfortunate unity hasn't implemented any logging for the compiler errors (at least I couldn't find them)
Btw any HLSL editor will scream about inout VFXAttributes attributes in custom blocks since VFXAttributes isn't defined there so you could just remove that parameter in the editor to get rid of that error. In unity you still need that
I think this code i copied contains an error? At least that's the one Shader playground complains about
"Expected declaration
Parsing failed, aborting"
Oh, let me see. I'm not sure if I even used that exact code myself
Mine works fine. Can you take a screenshot of the whole playground page?
Actually i may have misunderstood how the site works
tho, it still gives me an error, a diffirent one this one
It gives this one for any of the custom hlsl scripts though
Im not entirely sure which one of the hlsl scripts is causing the erorro in the unity editor itself
Forgot to mention that it expects an HLSL version of a main method (entry point). You should leave the original code in there and just add the own method at the top
so this:
struct PSInput
{
float4 color : COLOR;
};
float4 PSMain(PSInput input) : SV_TARGET
{
return input.color;
}```
Okay everything seems fine?
But i've failed to notice another error in the unity editor before,
The shader playground does give an error when i paste in this one, but only because it doesnt have a defenition for the "VFXAttributes"
{
buffer[index] = value;
}```
Could be something else then. Do you seen any errors in the graph itself?
I dont think so?
Perhaps i could send the graph over, maybe you'll catch something
feel free
One more into the list of ridiculous VFX custom node bugs. If you double click the error, it will get you to the generated code which shows this (the given line number is bit wrong for some reason). Your code doesn't have that so why would this happen? Oh boy. It turns out custom HLSL blocks cannot start with more than 1 consecutive comments. If you remove the comments at the front, it should work
. Btw one problem that I noticed is that you have the two incoming integers mixed up. The Buffer Count should go to the buffer size etc.
I'll file a bug report on this commenting matter
That sounds so ridiculous oh my god
and now when i run it unity literally imploded hah
thanks for pointing ot the mistake btw! I swaped them to the correct spots now
This is caused by the vfx asset being present in my project at all, after i removed it finally works again
That is due to your GPU not responding in reasonable time. Happened to me quite many times too. It is most likely an infinitely loop or an issue with trying to access something outside the buffer range. Make sure the buffer is the same size as the amount of particles you are going to have. To avoid these, I actually added this at the start of the ClosestPoint method just in case. It may affect the performance ever so slightly so it may be good to remove it once the effect is ready:
if (index >= bufferSize) //safeguard against incorrect setup which would cause crash
{
return float3(0,0,0);
}```
Also, another question i had
my vfx graph spanws 12 particles every time the effect is triggered, to a maximum 128 being present at a time
does my buffer size would have to be 128?
To address that I would like to know little bit more about your case. Why is the capacity set to 128? From what I can see, it doesn't seem to spawn more than 12 particles at a time
It may be triggered again before all the previous particles despwan
hold on, i'll share a vid
This could potentially be a bit of a difficult issue. What do you want to happen if two sets of 12 particles are present at the same time? Do you want the two sets to interact with each other by making connections between them or would it be fine if the two were separate systems only connecting to their own particles?
It's fine if they're completely disconnected
i guess i can just use multiple sets of particles then
Yeah. If that is fine, you should probably just set the capacity to 12 and buffer size to the same. Every time you select a card, you would either spawn a new system (from a prefab that has the buffer initializer script in it) or reuse the systems from a pool (should be faster). At least for now we don't have GPUEvents used so GPU instancing the systems should happen automatically making copies of the system run very fast
Actually, the 128 buffer works surprisingly well!
If I remember correctly, extra capacity makes the systems also run slower since even the inactive ones have to be processed so it could save on performance too a bit.
It might do fine but I believe there is an slight issue due to how we are using the buffer size in the loop in term looping over particles that don't even exist. If you spawned more than 12 particles simultaneously we would also have to do something more sophisticated with the indexing of the data (The Spawn indices are always between 0 and 11)
Solving those problems properly is not an easy one. I would just go with the multiple instances of the system approach and the size of 12 used throughout
Thanks, thats what i'll go with then!
As for now, the effect works wonderfully
Since my particles have random lifetime, it creates a really interesting effect when one of two connected particles disappears first, but it's position is still in the buffer, so the line still appears
i havent yet implemented the pooling you suggested, but i'll get to that now
thanks a ton for the help with the effect!
Yup. My solution to that is quite wacky, but it works. I believe the solution breaks the early mentioned GPU instancing but it is still likely fine (+ I'm surprised if there is not a better way to do that)
would it be possible to implement a second buffer?
that keeps track of if particle wit hthat index still exists
and if it tries to connect to a particle that disappeared, it just destroys the connection with it
I just used the same buffer but made it of float4's and stored the alpha of the particle in the fourth channel. In your case the stars seems to shrink to oblivion so you would likely want to store the size instead. In the ClosestPoint method I then checked for the stored alpha values of the connection points and took the smaller of them (using min) to make the lines fade out according to the dimmer of the two connection points
Oh thats clever
That would be, but unfortunately it wasn't quite as easy. When storing the alpha values in the Update of the main particles, I ran into issue where the alpha value wouldn't be updated in the last update where the particle dies (or at least it didn't store the right value) so the alpha value was always left slightly over 0 when the particle died so the connections would be left floating there slightly visible when the particles died. The only solution that I found is the part that I called "wacky" and wacky it is. In order to set the alpha to 0 when the particle dies, I had to spawn a new particle in OnDie, in that particles initialize context I set the value in the buffer to 0 and kill the particle. Note that the spawn index is derived from the dying particle so the index should be therefore correct.
Huh i see, interesting
Would just rounding the value down below an arbitrary threshold work too?
Though, i know that wouldnt be 100% accurate
Probably but I wasn't too confident on that and I wanted to have faint connections too to fade connections in and out when it is about to change what it connects to. That is a part that I haven't explained yet. Basically I added an alpha output to the ClosestPoint method and made it return the just described min(start.a, target.a) combined with the effect of fading in and out the connections when about to change
If you want the connections to just snap between the particles, that is fine and can look cool too but if you want the fading effect, I can show that too. That is just not too complicated modification to the ClosestPoint Operator which should work out of the box without any extra modification elsewhere (except to connect that alpha port to the update context)
Yes, that sounds really cool!
i'd love to learn about it
i see, makes sense
The idea is quite simple really. The problem is that how we know when the connection is about to change? We kind of cannot know for sure since we don't know where the points are moving (unless we store the velocities too but that would be quite the mess). What I did instead was to not only find the closest distance to a particle but also find the distance to the second closest particle. We do this because we know that a connection cannot change if the second closest particle is much further away than the closest. Only when the two distances are close to each other, can we be close to switching to another particle.
If the distance to the closest particle is closest and distance to the second closest particle is sClosest, we can now calculate the alpha value by (sClosest - closest) / sClosest which will get closer to 0 when the values are similar and closer to 1 when the closest particle is much closer than the second closest. You can obviously do different mappings to this value to get it behaving exactly as you want (e.g. if you want the connections to stay visible for longer and only at the very end start fading) but this alone gives us quite nice estimation of how close we are to switching to another particle. If you want to have a go with it yourself, feel free. I can also share the current version of my ClosestPoint Operator immediately if you want.
I'm just now realizing that this whole things is very stupid when I could simply make one Custom HLSL Block instead of an operator which would set the position, target position and the alpha automatically.
This is what it looks now with the HLSL Block. I also added the Fade Curve there to make it easy to adjust how much and at what point should the fading in and out happen
Btw what I said about the errors wasn't true, I just didn't find them at first. You do actually get proper error messages in the editor but you have to click at the error in the console and then select the pointed compute shader within the asset. That will reveal the actual error message in the proper code (at least for me when I use separete HLSL file for the custom nodes). You can see how I'm not the most experienced with the VFX graph myself either 🙂
In case I wasn't clear about this, I do still set the alpha for each particle in the buffer in every update to allow smooth transitions but in addition I have the wacky particle initialization trick to get the value to exactly 0. Oh and when outputting multiple values from custom HLSL Operators (like the ClosestPoint method), remember this bug #✨┃vfx-and-particles message. You basically cannot return a value as well as have output parameters even though the documentation shows an example like that. if you want to get multiple values out, you must use an return type of "void" (return nothing) and have everything as out parameters
Oh, aaand... the wacky trick only works when Experimental Nodes are enabled from the preferences (Edit / Preferences / Visual Effects / Experimental Operators/Blocks). Otherwise you won't find the GPUEvent block. GPU Events have been experimental forever but they should be totally fine to use. Only downside is that it used to break GPU instancing but now that I look at it, it seems since unity 6.1, GPU Events work totally fine with instancing so there shouldn't be any performance considerations there either assuming you are using unity 6.1 or newer. Sorry for failing to mention this too
I assumed the instancing problem was the main reason GPU Events were still experimental but for some reason it is still experimental even without that
GPU Events are very standard way to implement many effects. Even the default templates like the Firework one uses them
Here is yet another stress test to see the GPU instancing in action (enabled by default, you don't have to do anything). Here's 1000 VFX objects each having 100 particles and connections running at stable 55fps. If I disable GPU instancing, the fps drops to 20 but even that wouldn't be an issue in your case/particle counts
Oooh how did you turn it into a block with a curve?
i think this might work!
Whenever the loop encounters the new closest value, it stores the previous as the second closest, and then does the operation after the loop's over
Also, sinze im working with 2D, i can utilize both Z and W values of the buffer vectors to store whatever info i may need
You can create in parameter of type VFXCurve and sample it via SampleCurve(theCurve, tBetween0And1)
Interesting i see
also, i had a syntax question for hlsl
im trying to implement 4th float of the vector as the particle size, but i cant figure out how to output it
let me rephrase that actually
i need to output a vector4 that uses the position coordinates for the xyz, but uses the separately calculated "alpha" value as it's w component
how does that work out syntax-wise?
you can create a vector4 like this: float4(anyFloat3, float) is that what you are asking?
yes, thanks
This is almost correct. The only issue is that if closest < dist < sclosest (happen to find value that is smaller than second closest but not closest), sclosest should get the value of dist but with your code the values will only change if you find the absolute closest. I have exactly the same code as you but I also have this conditioncs else if (dist < sclosest) { //found new second closest sclosest = dist; }
{
float4 pos = pointBuffer[index]; //position of the starting point
float closest = 10000000; //there must be a constant somewhere too right?
uint closestIdx = 0;
float sclosest = 1;
float alpha = 0;
float4 output = 0;
if (index >= bufferSize) //safeguard against incorrect setup which would cause crash
{
return origin;
}
//loop through the particle positions without counting the starting point and avoiding duplicate connections
for (uint i = (index + ((bufferSize/ 2) + 1)) % bufferSize; i != index; i = (i + 1) % bufferSize)
{
float dist = distance((float2)pos, (float2)pointBuffer[i]); //this could be optimized by calculating the squared distance instead
if (dist < closest)
{
//found new closest point
sclosest = closest;
closest = dist;
closestIdx = i;
}
else if (dist < sclosest)
{
//found new second closest
sclosest = dist;
}
}
alpha = (sclosest - closest) / sclosest;
output = float4 ((float3)pointBuffer[closestIdx], alpha);
return output;
}```
This is the code i have now, it compiles and works, but the alpha value doesnt seem to be correct
i've also cast the vectors to 2d in the distance calculation, sinse im working in 2D
Btw you can utilize swizzling to not have to cast float4's to float2's etc. For example output = float4(pointbuffer[closestIdx].xyz, alpha);. If you only want float2, you can do pos.xy instead of (float2)pos. That shouldn't make the output any different though I don't think. I usually don't use the casts at all so I'm not sure how they work but I assume they do the same.
You can output float4 as described but much cleaner way might be to have two output parameters like this:
void ClosestPoint(...inParameters..., out float3 outPos, out float alpha)
{
//calculate whatever here
//...
//set the out parameters here or anywhere above
outPos = whatever;
alpha = whatever;
}```
This way you can output many parameters which will look like here: [#1410577592485941371 message](/guild/489222168727519232/channel/1410577592485941371/)
Oh thanks!
Also i seem to have fixed it
Do you know what it was? Putting the effect in slow motion might help seeing whether it works correctly if there is any doubt
yeah! I used it a bit
Might as well share what I have. It is very similar but basically just has the fade out based on particle alphas and uses out parameters instead of single return. Mine also outputs the starting position, so I don't have to get it separately from the buffer. I did use Custom Block eventually, but this is the Custom Operator version:
void ClosestPoint(in RWStructuredBuffer<float4> pointBuffer, in uint index, in uint bufferSize, in VFXCurve fadeCurve, out float3 startPos, out float3 targetPos, out float alpha)
{
if (index >= bufferSize) //safeguard against incorrect setup which would cause crash
{
//JUST ADDED THESE TWO LINES NEEDED FOR THE OPERATOR SOLUTION
startPos = targetPos = float3(0, 0, 0);
alpha = 0;
return;
}
float4 startValue = pointBuffer[index];
float3 pos = startValue.xyz;
float closest = 10000000;
uint closestIdx = 0;
float sClosest = 10000000; //what we initialize this with shouldn't matter since it will get instantly overwritten in the first loop
for (uint i = (index + ((bufferSize / 2) + 1)) % bufferSize; i != index; i = (i + 1) % bufferSize)
{
float dist = distance(pos, pointBuffer[i].xyz);
if (dist < closest)
{
//found new closest
sClosest = closest;
closest = dist;
closestIdx = i;
}
else if (dist < sClosest)
{
//found new second closest
sClosest = dist;
}
}
startPos = pos;
float4 targetValue = pointBuffer[closestIdx];
targetPos = targetValue.xyz;
//note the use of min function to get the fade based on particle alphas
alpha = SampleCurve(fadeCurve, (sClosest - closest) / sClosest) * min(startValue.a, targetValue.a);
}```
When it comes to the fadeCurve, when it is linear, it will work solely based on the formula as if there was no curve used. If it is more like in the second image, the particles would stay visible for longer and start fading out bit later (but faster obviously) which is probably what you want to do if anything. If the graph curves the other way, the effect would be the opposite: the connections would fade in slowly and potentially never even reach fully opaque. You could of course do all sorts of squiggles in there to even make the connections flash in and out even when not transitioning (that could be achieved easier by other means though). You should keep the curves starting from (0, 0) and ending in (1, 1) though regardless of the profile
Oh yeah i've used the graphs before!
they're really handy when you want to customise your lerps especially
they sure are
I tried to use your code, but for some reason im having issues with the alpha value again
it always outputs alpha of 0
i do write the relevant alpha value into the buffer, but it doesnt seem to affect anything
Can you show what that looks like? Are you sure you are not overwriting the alpha value in the code that sets the xyz?
I replaced the alpha value with a red to blue gradient, and all the lines are red, which is at 0
(i used a random 0-1 float for testing purposes)
and then the alpha from the operator is outputed right into the set colour node
What is the curve set to. If it is always 0, it would explain it
Right of course
for some reason my brain decided that this was the default curve for testing stuff, but that would the linear one
Though, actually, that didnt fix it
but it's a step in the right direction
Is the buffer constructed correctly in the C# script with stride size of sizeof(float) * 4?
yeah!
and actually, intrestingly enough, the output didnt change at all between the flat & linear curves
even tho the flat curve should've always output 1, not 0
is it still always 0?
yeah
try to make the alpha = startValue.a in the function just to make sure there's nothing more in play than the values in the buffer
Still always 0
huh
Perhaps i could send the graph over again, if you're willing to look through it
because im genuenly so confused
Please do, I'm confused too. Mine seems to work fine with the same set up. Provide the C# script too if you don't mind
Btw you should add the two lines to the function that I added later on here #1410577592485941371 message
Completely unrelated but one thing that I might say now before I forget too is that you should probably move this custom HLSL block to the end of the Update to try and get the most up to date data to the buffers (with gravity, drag etc. already set). I'm not 100% sure the order even makes a difference but in case it does, why not
done
im pretty sure thats how it works!
Yeah I think I have seen the order mattering a lot and it makes sense given how it generates a compute shader for each context but I did see some weird comment by a unity staff member in a forum post regarding this which confused me but I think I just misunderstood what they meant.
I will have to go to bed now, but if you find where the issue lies please tell me, because i have seemingly tried everything i could, andi genuenly have no idea what could potentially be the cause
Similar case. It is 2:45 am here so my brain isn't braining anymore. I will come back to this tomorrow.
thanks a ton for all the help!
I found the issue
i plugged it into the output node instead of the update
that kinda worked, but it's still not functionaing entirely as its supposed to
yeah, it worked when i did that with my own implementation
The lines get stuck when the particles fade away
It turns out you can't read a value from RWBuffer in the Output Particle context. The reason this works for the positions here #1410577592485941371 message is that the positions connect to the Update where it is fine. It also works if you make the input type StructuredBuffer without RW at the start (which stands for read and write). That almost makes sense but I assume this is a bug too
the value to determine alpha here that i use is age over lifetime, which should be a 1-0 float, so it should always be 0 by the time the particles fade away
Did you reverse that? If you just used that for the alpha, it would give transparent at the start of the particles lifetime and opaque at the end
I don't think the wacky solution of initalizing new particlese causes any problems but if you wanna do something more proper, a solution that I thought yesterday was that if you really have the 2 channels of the buffer unused, you could just make each particle write their lifetime to the Z channel and spawn time to the W channel in the start (you wouldn't have to update them at all so you wouldn't have the last frame update problem). From those you could always reconstruct the Age Over Lifetime value given the current time. This is what I should have probably done myself to begin with. Only issue with this would be if you messed with the time scale, you would need to take that into account somehow
I think it works fine enough as it is!
Even without pooling the vfx, i'll still attempt that tho
i'll attempt that too, sinze the Z value is unused in my case
i've learned a lot about vfx graph & working with hlsl in these past few days
Same. I'm just tired of filing all the bug reports 😅
Especially the custom HLSL nodes seem to be bugged every which way
That doesn't really limit anything though. You can always make your own structs that have more than 5 elements and create StructuredBuffer<YourStructType>. This would generally be the preferred way to replace the float4 solution too to make the code cleaner by using names to access the parts something like this:
struct ParticleData
{
float3 position;
float ageOverLife;
};
RWStructuredBuffer<ParticleData> buffer;
float3 pos = buffer[index].position;```
I'm not convinced anymore this is possible at all. Probably is but I just cannot figure out how to make the custom HLSL code recognize the custom struct type (whether defined in the same HLSL file or in C#)
Obviously using multiple buffers would be possible though
0h i see
damn, thats weird
luckly i have the Z float unused, so i'll be using that
@patent canopy Couple extra notes that I saw from the system you sent. The way you send the position of the system via property is totally reasonable, but even more intuitive solution would probably be to just set each of the systems to use local space (click the button that says World at the top right). This way you don't have to pass it via property but can instead just move the transform of the gameobject and the system will move along.
Second thing that I noticed was the capacity which was still at 128 even though you don't need nearly that much (only as much as you burst at once in your case). That is probably not a big deal but it is an extra calculations the GPU must do which goes to waste.
About the wacky solution in case you go for that, you should probably use the Output Particle Point context for minimal overhead (basically just draws single pixel) and disable sorting by clicking on the output context and turning it off on the inspector. Even though the particle should be immediately killed, at least the performance indicator showed that the sorting was still happening for at least one frame.
One thing that I forgot to mention was that you probably want the connections to draw first and then draw the main particles on top. That order you can change by selecting the whole VFX asset on the Project window and drag the particles in Output Render Order around.
Last thing that I forgot to mention about Custom HLSL nodes is that you can have at most 4 in parameters. It seems like the out parameters don't count to that and neither does the inout VFXAttributes attributes for blocks. If you used the less wacky solution, you may find yourself overshooting that limit if you for example need to pass in the current time too. Unfortunately Custom HLSL doesn't support uint2, but you could for example pass in the bufferSize and index as float2 constructed by float2(asfloat(index), asfloat(bufferSize)) and then unpack via asuint.