@desert root Hey, I would like to thank you for all the help you recently gave me 😄 . Currently I have something close to the Valve system working but unfortunatly it looks like it will not fit our use case very well. https://advances.realtimerendering.com/s2020/RenderingDoomEternal.pdf I am now looking at the system in Doom slide 54 but thats even less info to go on. I hope you would be willing to help me get a grasp on it as well. The only thing I currently understand is packing the clip and blood weights(or at least I think I understand it) I have no idea how to read a instanced buffer in shadergraph, or how the hell they compute the clip mask outside of the VS/PS. Like sure we can store a number from 0 to 15 in lets say the first 4 bits of a byte indicating the weight of the mask but then what ?
#Making a gore system in HDRP
1 messages · Page 1 of 1 (latest)
I think I can read a structured buffer with a custom function node, but I'm not sure how to dispatch a CS that writes to the same buffer the shader will read.
I must admit that this simple slide is a bit cryptic to me :/
What is a "clip" in that context ?
As for the buffer, I'd says that it is updated in the compute shader before the rendering occurs, and then simply passed to the shader, in Unity using Material.SetBuffer ? https://docs.unity3d.com/ScriptReference/Material.SetBuffer.html
I suppose clip is texture for each wound that does alpha clipping.
I dont think there is a way they would directly clip vertices
Then that 1 byte data is maybe something like the clip index and the weight split between bits ?
Then mention 12+ active wounds, so that's 4 bits for the clip index, leaving 4 bits (16 values) for the vertex weight, plenty enough I think ?
so you think that the clip masks are in a TextureArray for example
and the buffer controls sampling the array
That's my guess yes
If you have the game, you could take a renderdoc capture and analyse it 😅
I do have the game haha
I know they use a completly bindless pipeline so it makes sense for the clip masks to be in a texture array ( I think)
How would I dispatch the compute shader and pass the buffer to the material before rendering happens ?
So, if I'm not wrong, if you create the buffer once, and "store" the cpu object on an component or a script that isn't destroyed from one frame to the other, the buffer should always exist.
So you only have to assign it to the material once and it should always be available.
To update with the compute shader, you could using camera/frame rendering event to dispatch the compute at the start of rendering.
ah yes from the RenderPipelineManager
hmm I'm still very confused as to what the compute shader actually does and what data we feed it. So for example we detect a hit on the head collider, then what? I dont get this byte per vertex thing if their wounds are authored with their own clip and blood masks cant I just flip the corresponding index to a 1 and let the shader sample from that...
I'm still installing Doom btw 🙂
If the clip mask is already a texture why the need for the whole buffer.
A guess:
The whole buffer is basically a bunch of booleans that say: this vertex is on or off.
The compute shader running per character holds that array and when you send it a hit position and radius - the compute shader chews through all of the positions and checks if they're within and should be flagged as off.
Because vertex data is interpolated you can blend that so that when you clip it you get smooth transitions.
When you get to the edges you've basically got a single noise texture that roughens up the clipping to make it look natural for wounds.
Similar idea is the half life alyx bottle: https://gamedev.stackexchange.com/questions/182989/how-can-i-achieve-simulated-fluid-surface-deformation-in-a-bottle
This looks promising: https://github.com/mgstauffer/UnityComputeBufferExample/tree/master
But it seems to be limited in platform support
You could also just store it in the TexCoords or vertex colors
instead of a RW buffer
using the compute shader i think
with a texture
The way I got this running (and its not perfect by any means just a starting point) was starting with this repo:
https://github.com/mgstauffer/UnityComputeBufferExample/tree/master
Then adding
#pragma target 5.0 to the shader
changing
uniform StructuredBuffer<float> buffer : register(t1); to uniform RWStructuredBuffer<float> _IsAlive : register(u1);
removing the update loop from Main.cs and adding Graphics.SetRandomWriteTarget(1, computeBuffer, false); to allow the shader to write to the buffer.
Making the data array as large as the amount of vertices in my mesh by grabbing that variable from the meshfilter & initializing all of the floats in that array to 1.0f (bc each vertex is alive on start).
setting isAlive in the vert function
_IsAlive[v.id] = false;
}
and sending it to the fragment function through an interpolator I added to v2f:
float isAlive : TEXCOORD1;
like this:
o.isAlive = _IsAlive[v.id];
From there the logic in the fragment function is basically just multiplying the alpha by o.isAlive
ComputeBuffers dont need to be textures, they can be most kinds of primitive data. The cool thing about them is they can also be structs so you can be pretty organized.
The craziest thing about ComputeBuffers is the idea of RWStructuredBuffer which is capable of reading and writing to an array within a shader.
This has potential to run stupid fast because it never has to communicate with the CPU
One note:
In my brief research I couldn't figure out how to make it 1bit per vertex so I ended up using floats. Unity wants the stride to be a multiple of 4bytes when you make the compute buffer and 1 is too small.
here r my modified files
they're very very rough
I was hestitant to share bc i didnt see a license but they're mit lol
Thank you for contributing, your idea is neat but I dont think it is very close to what is described in the presentation. I think we can make the buffer be of uint and the 4 stride is not a problem
Oh sorry! I was just running off of the doom eternal slide! I got ahead of myself. 😅
Sorry the toddler needed me haha.
So I gave it some thinking. Your idea might not be far off
It can definitely work as the procedural blood on bullet hits. I wonder if we bake the preskinned vert positions if we can do the distance check in a separate compute shader.
How ever the presentation specifically mentions a artist made clip mask that goes along with each wound mesh
And they somehow pack that info in a buffer as well
But i fail to understand why is it per vertex. Unless every enemy in doom has a completely wounded model that gets procedurally exposed
Then it kinda makes sense since we can pack a clip weight and blood weight in the byte per vertex, but then what do we need the authored mask for
Im going to renderdoc Doom tomorrow and see if I can get a glimpse
In the meantime I have contacted the people from the presentation haha
Awesome please post what they share with you! I'm invested now haha.
I think the part I didn't think through is how the mask relates to the parts that are hidden inside the surface of the model (& the artist masks I think are part of that)
but yeah I think you could totally use the process I outlined above to do blood and to cut through the shell model
you could start w/ them as separate variables then pack them together later if you wanted
that's my theory and I think something renderdoc will tell also
hmm trying to connect renderdoc or Nsight to a steam game is proving to be rather difficult
So I manged to get captures working, but I'm getting kinda lost . This is the first instance of the enemy being rendered and it already has a hole in it
this is the output of the vertex shader at this point
So the presentation mentioned that the work is being done in a compute shader, the first mention of compute is Compute Pass #1 way up at the beggining of the frame.
now this torso has 17339 verts according to the vs input
if it is 1byte/vertex I expect to find a buffer somewhere in the compute pass #1 that has 17339 bytes in it
is there a way to sort buffers by size in the Resource Inspector ?
wait the buffers seem to be uint so that is 4 bytes so I'm really looking for a buffer with roughly the size of 4335 or something.
but nothing is close to that as well. All the ssbo buffers seem really huge and the uniform ones are tiny like 64 bytes
I'm very lost on how to proceed.
@lament yew I can confirm that the wound is indeed a seperate mesh, so there is no fully wounded model being exposed
I am now convinced that the specific compute shader is dispatched only on the frame the wound is applied and this will be almost impossible to capture
So the question remains how does the computer shader calculate the clip/blood weight for each vertex. I mean its possible to sample a texture in a cs but how will that be cheaper than doing it in just the vs ?
I think doing it in the cs you can control when it's dispatched (like u said above) maybe that's the advantage?
The intersection math doesn't seem that heavy tho. The reason I used a vertex shader w/ rwbuffer is because I didn't want to think about skinning (which I rly should have tested).
But my assumption (could b wrong) was that unity does skinning before the vertex shader executes.
So by the time you get there you don't need to think about it.
I'm sure there's ways to time that w/ c# tho
I suppose the cs effectively bakes the sample into the buffer, then all the vs does is interpolate the data to the ps.
That makes sorta sense, but Im thinking what the weights actually are, as far as I can tell the wounds dont have damage stages they are on or off
Idk at this point I need and adult haha
I think you could still make that and not use damage stages!
You have a weight for clip that maybe subtracts alpha so it pushes fragments into below the threshold for discard. That's modulated by the clip texture.
You have a weight for the blood which you could use to reveal the blood texture either with a lerp or another threshold.
Somehow they mix them together but the hardest remaining part imo is how they authored the clip texture and the blood texture for each monster - both of which I think rly sell the effect.
It being weights I think has the benefit of more smoothly introducing those alpha thresholds.
Authoring the masks is the easy part a character artist does it by hand alongside the wound model
I just feel that if I have to pass in the uvs to the compute shader in order to sample the masks as well it is kinda redundant. Then again the fact that we sample once per wound and then just have a buffer with the data for the ps vs shaders to read might actually be a win. I just dont want to throw time at this implementation until Im certain it’s close to what they are doing
Oh! Don't sample the masks in the compute shader that seems like a waste. Just do that in the fragment shader.
You lose a lot of the perks of a texture when you sample it per vertex rather than in a fragment function.
Might as well just be vertex attributes you painted in in blender or something.
Then the whole compute shader is useless I can just as easily, if not more so, pack the weights directly into the buffer from a job
Usually the advantage of the compute shader if you set things up right is that you don't need to copy much data back and forth between cpu and gpu. The gpu can be really fast if it has all the data it needs.
I think you're right though you probably could do that.
The other piece to consider IMO is the skinning. If you did it on the cpu you'd have to copy the skinned mesh from the gpu to the cpu, run operations, then send it back to the gpu.
Where if you had it all in shaders you're just sending commands to the gpu for data it already has.
That's my understanding at least.
But Idk sounds like it's already going to have a very small footprint if you're only doing it every once and a while so it might be worth at least trying in jobs.
I got it working https://streamable.com/00tdw7
I will make a breakdown of my solution but I need to add some more code and cleanup
but in the end I ended up sampling the masks in the compute shader, still not sure about the actual weights, right now it's either 0 or 1
So lets start off with the compute shader
#pragma enable_d3d11_debug_symbols
RWByteAddressBuffer _woundBuffer;
StructuredBuffer<float2> _uvBuffer;
Texture2D<float4> _clipMaskTexture;
SamplerState pointClampSampler;
Texture2DArray<float3> _clipMaskTextures;
int _slice;
int _bufferLenght;
[numthreads(64,1,1)]
void ComputeMasks(uint3 id : SV_DispatchThreadID)
{
if(id.x < _bufferLenght)
{
float clip = _clipMaskTextures.SampleLevel(pointClampSampler, float3(_uvBuffer[id.x].r, _uvBuffer[id.x].g, _slice), 0).r;
float blood = _clipMaskTextures.SampleLevel(pointClampSampler, float3(_uvBuffer[id.x].r, _uvBuffer[id.x].g, _slice), 0).g;
if(blood >0)
{
uint clipScaled = clip * 0xffff;
uint bloodScaled = blood * 0xffff;
uint packedValue = (clipScaled << 16) | (bloodScaled & 0xffff);
_woundBuffer.Store(id.x * 4, packedValue);
}
}
}
It's very simple all it does is sample a texture array based on a given slice and then packs the two mask values into the uint .The slice index corresponds to a specific wound so like abdomen, left shoulder, head etc. We also need to pass in the mesh uv's to the compute shader so we can do our sampling.
Then to unpack them I have this custom function : ```ByteAddressBuffer _woundBuffer;
void ReadBuffer_float(float i, out float clip, out float blood)
{
uint packedValue = _woundBuffer.Load(i * 4);
clip = (packedValue >> 16) / 65535.0;
blood = (packedValue & 0xffff) / 65535.0;
#ifdef SHADERGRAPH_PREVIEW
clip = 1;
blood =0;
#endif
}```
After that the two values simply get interpolated to the fragment shader to be used as we please