Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Resolved Strange vertex behaviour in the vertex shader?

Discussion in 'Shaders' started by CinnamonCereals, Jan 28, 2022.

  1. CinnamonCereals

    CinnamonCereals

    Joined:
    Jan 19, 2022
    Posts:
    23
    What I'm trying to do is raise the whole mesh up by x amount each frame in the vertex shader. But this happens: (from pic 1 to 2)
    1.jpeg 2.jpeg 1.jpeg 2.jpeg

    Note: the first picture should be a flat plane but it starts off that way. While the base vertices still move, some vertices raise way more than others every frame. What I am doing is using a RWStructuredBuffer to update and read the current position of the vertex in the vertex shader, and add x amount to it each frame. If I do it in the CPU, no problems whatsoever.

    Why am I doing this in a shader? I am currently working on a cloth mechanic and have to manipulate the vertices in the vertex shader. I am quite new to all this. But long story short, I was trying to debug something and came across this interesting behaviour that I think may be the root problem.

    It looks like to some vertices the amount is added more than to others but I can't understand why. Maybe the read/write from the buffer is not in the proper order? Here's the simplified code:

    C#:
    Code (CSharp):
    1.     Mesh mesh;
    2.     Renderer render;
    3.     Material material;
    4.     ComputeBuffer buffer;
    5.  
    6.     void Start()
    7.     {
    8.         mesh = GetComponent<MeshFilter>().mesh;
    9.         buffer = new ComputeBuffer(mesh.vertices.Length, sizeof(float)*3);
    10.         render = GetComponent<Renderer>();
    11.         material = render.material;
    12.         buffer.SetData(mesh.vertices);
    13.         material.SetBuffer("_CurrentVertices", buffer);
    14.     }
    15. private void OnDisable() {
    16. buffer.Release();
    17. buffer = null;
    18. Destroy(material);
    19. Destroy(render);
    20. }
    21.  
    Shader code:
    Code (CSharp):
    1.  
    2.  
    3.  
    4. struct VertexPosition
    5. {
    6. float3 position;
    7. };
    8.  
    9. uniform RWStructuredBuffer<VertexPosition> _CurrentVertices : register(u1);
    10.  
    11. VertexOutput Vertex(Attributes input, uint indexID: SV_VertexID) {
    12.     VertexOutput output;
    13.     _CurrentVertices[indexID].position.y += 0.00005;
    14.     output.positionCS = TransformObjectToHClip(_CurrentVertices[indexID].position);
    15.     VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS);
    16.     output.normalWS = normalInput.normalWS;
    17.     output.uv = TRANSFORM_TEX(input.uv, _MainTexture);
    18.     return output;
    19. }
    I have tried doing the same in objectspace and world space. Have tried using different meshes, even only the unity built in ones, some are worse than others but in the end It is exactly the same. The longer the object goes up the worse it gets. The buffer is not used elsewhere besides the c# script to send data and the shader. Am I not understanding something or is this a bug?
     
    Last edited: Jan 28, 2022
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,242
    This probably isn't a bug per se, but rather exposing an unexpected side effect of that GPU's internal vertex handling. It would appear that the GPU on that device processes some vertices multiple times per frame. There could be many reasons for that. For example some mobile GPUs that use a tile based deferred renderer, the vertices of some triangles may be processed multiple times due to appearing in multiple tiles. This doesn't look like that specific behavior, but does look similar.

    I would suggest one of two options:
    Option A: Update the buffer using a compute shader rather than doing it in the vertex shader. That complicates things slightly but will solve the problem. It might also be faster.

    Option B: Use two buffers you swap back and forth between reading from and writing to.
    Code (csharp):
    1. // c#
    2. ComputeBuffer bufferA, bufferB;
    3.  
    4. void Start()
    5. {
    6.   bufferA = new ComputeBuffer(mesh.vertices.Length, sizeof(float)*3);
    7.   bufferB = new ComputeBuffer(mesh.vertices.Length, sizeof(float)*3);
    8.   bufferA.SetData(mesh.vertices);
    9.   material.SetBuffer("_PreviousVertices", bufferA);
    10.   material.SetBuffer("_CurrentVertices", bufferB);
    11.   // don't set bufferB with any data
    12.   // also do the rest of the stuff you were doing
    13. }
    14.  
    15. void Update()
    16. {
    17.   // swap buffers
    18.   ComputeBuffer temp = bufferA;
    19.   bufferA = bufferB;
    20.   bufferB = temp;
    21.   material.SetBuffer("_PreviousVertices", bufferA);
    22.   material.SetBuffer("_CurrentVertices", bufferB);
    23. }
    24.  
    25. // shader
    26. _CurrentVertices[indexID].position.y = _PreviousVertices[indexID].position.y = 0.00005;
    This will guarantee that even if some vertices are processed multiple times per frame by the GPU they won't accumulate more offset than other vertices.
     
    CinnamonCereals likes this.
  3. CinnamonCereals

    CinnamonCereals

    Joined:
    Jan 19, 2022
    Posts:
    23
    Thank you @bgolus , amazing response as always. I tried your second solution out and while it works (I think you miss clicked on '=' when it should be '+' in the shader equation), I'll get my hands dirty with the compute shaders, can't run from them forever. Will let you know how it goes.
     
    bgolus likes this.
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,242
    Yep! oops ... :p