Search Unity

Question Any way to run the vertex shader for all submeshes first, then run the geom&fragment shaders after?

Discussion in 'Shaders' started by Milun, Nov 3, 2020.

  1. Milun

    Milun

    Joined:
    Nov 30, 2012
    Posts:
    12
    Hello! I'm working on a Vectrex-style shader for Unity that makes use of a
    RWStructuredBuffer that's hit a bit of a snag once I introduced a mesh with multiple submeshes.

    Basically, the way my shader works is, during the vertex shader step, each vertice's clip pos is stored in a RWStructuredBuffer:

    v2g vert (appdata v)
    {
    v2g o;
    o.pos = UnityObjectToClipPos(v.vertex);
    vertsPosRWBuffer[v.vertexId] = o.pos;
    return o;
    }


    ...and then during the geom shader, that buffer is accessed to get the clip pos's of adjacent vertices.

    Now, when my mesh only has one submesh, this works exactly as I want it to (in the geom, each vertex is able to determine the clip pos of it's neighbours and render based on it).

    However... when the mesh has more than one submesh, my mesh starts rendering incorrectly.


    (In the above image, I set each vertex to render a line to the vertexes belonging to its adjacent tris. The clipPos of the adjacent tri's vertices are determined using the values in vertsPosRWBuffer, and work find when there's only one submesh).

    I assume this is happening because the geom shader is being executed for submesh[0] before submesh[1] has had it's vector shader executed, meaning that the vertsPosRWBuffer hasn't been fully populated with the clipPos of every single vertex yet. I could be wrong about this however (I couldn't find much documentation on Unity shader + submesh interaction).

    So, is there any way I can populate my RWStructuredBuffer with the clipPos of every vertex from every submesh before the geom shader is entered?

    Thanks in advance! If you want to see the project (and/or try it for yourself), it can be found here (I'll be updating it soon once I get this bug resolved).
     
  2. URoland

    URoland

    Unity Technologies

    Joined:
    Nov 4, 2020
    Posts:
    9
    Hi Milun,

    from the underlying API's perspective, each submesh is a separate draw call, so there is not going to be an easy way to get the exact behavior you are looking for.

    Here are some ideas for working around this
    • Can you use just one submesh? If you need the submeshes for different materials, could there be a way to use just one material and pass different shading parameters in a different way, e.g. through vertex colors?
    • You could try to generate this information in a compute shader prior to rendering.
     
  3. Milun

    Milun

    Joined:
    Nov 30, 2012
    Posts:
    12
    I was afraid of that. Alright then, thanks for the advice! I had considered using vertex colours as an alternative, but with the style of game I'm working on, I'd rather just have the 64 different "allowed" colours each be their own material, and mix and match them with subshaders. Looks like it's time for me to learn how a Computer Shader works!
     
  4. URoland

    URoland

    Unity Technologies

    Joined:
    Nov 4, 2020
    Posts:
    9
    I guess one more option would be to provide a static buffer with the object space positions (assuming they don't change), and then transform them into clip space in the geometry shader each frame. This buffer could be generated on the CPU on load, and you would save a dispatch for the compute shader each frame.
     
  5. Milun

    Milun

    Joined:
    Nov 30, 2012
    Posts:
    12
    Unfortunately, that one's not an option (I need this to work on SkinnedMeshes). I was even tempted to do a BakeMesh each frame, but I know the cost of that.

    Also, having looked into Compute Shaders, it looks like I'm going the Vertex color route after all! I found a lot of examples of meshes being generated with Compute Shaders, but none of them being used to read in the deformed vertices of a Skinned Mesh.
     
    Last edited: Nov 4, 2020
  6. ArminRigo

    ArminRigo

    Joined:
    May 19, 2017
    Posts:
    20
    For anyone reading this much later: even with a single submesh, I am not sure if there is a guarantee that all vertices are shaded before any geometry (or fragment) shader runs. As far as I understand things, if you are reading array items in the geometry shader produced by the vertex shader of an unrelated vertex, you might not always get the up-to-date value.

    One solution that might work (fixing this problem as well as the multiple-submeshes problem): put two materials on every submesh. The first material's vertex shader fills in the RWStructuredBuffer and then discards all triangles, which you can do by having the vertex shader output being ``struct v2f { float4 pos : SV_POSITION; float cull : SV_CULLDISTANCE0; }`` and writing the value -1 to ``cull``. No fragment shader will be invoked, but I guess you still need to write a dummy one. The second material has a higher rendering queue to make sure it runs later, and it can read from the same buffer (which can be a ``StructuredBuffer`` instead at this point). It's similar to the idea of a separate ComputeShader, but done with a regular shader instead, which simplifies various things a lot and should work for skinned meshes too.