Search Unity

Question Sharing vertex data between shader passes in URP

Discussion in 'Shaders' started by Buttermilch, Mar 19, 2023.

  1. Buttermilch

    Buttermilch

    Joined:
    Nov 23, 2016
    Posts:
    33
    Using Unit 2021.3.6f1

    I'm using GPU instancing for grass and my material is using a custom unlit shader.

    I'm using two shader passes. The first one calculates the grass position and moves it with some noise for a wind effect. The second one is only needed to render the shadows.
    Now the problem is I have to do the same vertex calculations in the 2nd pass again to get shadows that also move.

    To solve this I've tried to create a RWStructuredBuffer for storing the vertex position of the first pass.
    Then retrieve that data with C# and sent it back to the shader for the second pass.
    I can see that the RWStructuredBuffer contains the correct values and is not empty. But my second pass is now rendering nothing.

    In both passes I've declared the buffer like this:
    RWStructuredBuffer<float3> finalPosBuffer : register(u1);


    The first pass then fills it with this:
    finalPosBuffer[instanceID] = float3(positionWorldSpace);
    (positionWorldSpace contains vertex position with distortion)

    The second pass simply does this:

    v2f o;
    float3 positionWorldSpace = finalPosBuffer[instanceID];
    UNITY_TRANSFER_FOG(o,o.vertex);
    o.vertex = mul(UNITY_MATRIX_VP, float4(positionWorldSpace, 1));


    On the C# side I init the buffer like this:


    _finalPositionBuffer = new ComputeBuffer(_trsList.Count, 3 * sizeof(float), ComputeBufferType.Default);
    List<Vector3> posBuffer = new List<Vector3>();
    for (int i = 0; i < _trsList.Count; i++) {
    posBuffer.Add(Vector3.zero);
    }
    _finalPosData = posBuffer.ToArray();
    _finalPositionBuffer.SetData(_finalPosData);
    _material.SetBuffer("finalPosBuffer", _finalPositionBuffer);
    (trsList is a list of transformation matrices for the instancing)

    End then simply sends it back every frame like this:

    _finalPositionBuffer.GetData(_finalPosData);
    _material.SetBuffer("finalPosBuffer", _finalPositionBuffer);
     
  2. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    ShadowCaster pass (and DepthOnly in URP) generally run first before other passes. You should be doing it first, with the calculations in it, and then using that result in later passes.

    Though you may find it's cheaper to just do the calculation again instead of introducing a UAV read. I would bind the buffer as just StructuredBuffer for subsequent passes at least instead of RW.
     
  3. Buttermilch

    Buttermilch

    Joined:
    Nov 23, 2016
    Posts:
    33
    Thanks for the info. But now I have more questions. Can you change the execution order of shader passes? And how do I access the structured buffer in other passes (I tried that already as described in my post)?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Pass order is determined by the render pipeline, and the order is the way it is due to dependencies.

    For example the forward pass needs the shadow maps to be rendered first, as it needs to read from them to show shadows (assuming the object casts shadows, or is visible).
     
  5. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    That's a pipeline stall. You're sending the entire buffer data from GPU to the CPU, stalling the whole application until the data is ready, and then not using that data for anything at all. That quite literally can cut your framerate in half, going from 60 FPS to 30 FPS.