Compute shader not working, should be changing mesh vertices positions

So, currently I am trying to change vertex positions in a mesh using a Compute Shader and Compute Buffer. As you can see in my compute shader, I’m just trying to make the vertices move around on the y axis. My goal for this is just to learn how it is done, however regardless of what I do nothing seems to work. I feel like there is a very obvious flaw that I am not seeing.

When I run the code, absolutely nothing happens.

My main C# Script below

 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MeshDeformScript : MonoBehaviour
{
     public struct MyTestVertexData
     {
         public uint id;
         public Vector4 pos;
         public Vector3 nor;
         public Vector4 tan;
         public Vector4 uv;
     }
    
     public GameObject go;
     public ComputeShader shader;
     //Compute
     private int _kernel;
     private int dispatchCount = 0;
     private ComputeBuffer vertexBuffer;
     private MyTestVertexData[] meshVertData;
    
     private Mesh mesh;
    
     // Start is called before the first frame update
     void Start()
     {
         //The Mesh
         mesh = go.GetComponent<MeshFilter>().mesh;
         mesh.name = "My Mesh";
        
         //MeshVertexData array
         meshVertData = new MyTestVertexData[mesh.vertexCount];
         for (int j=0; j< mesh.vertexCount; j++)
         {
             meshVertData[j].id = (uint)j;
             meshVertData[j].pos = mesh.vertices[j];
             meshVertData[j].nor = mesh.normals[j];
             meshVertData[j].uv = mesh.uv[j];
             meshVertData[j].tan = mesh.tangents[j];
         }
        
         //Compute Buffer
         vertexBuffer = new ComputeBuffer(mesh.vertexCount, 16*4);
         vertexBuffer.SetData(meshVertData);
         //Compute Shader kernel
         _kernel = shader.FindKernel ("CSMain");
         uint threadX = 0;
         uint threadY = 0;
         uint threadZ = 0;
         shader.GetKernelThreadGroupSizes(_kernel, out threadX, out threadY, out threadZ);
         dispatchCount = Mathf.CeilToInt(meshVertData.Length / threadX)+1;
         shader.SetBuffer(_kernel, "vertexBuffer", vertexBuffer); // read & write
         shader.SetInt("_VertexCount",meshVertData.Length);
     }
     // Update is called once per frame
     void Update()
     {
         shader.SetFloat("_Time",Time.time);
         shader.Dispatch (_kernel, dispatchCount , 1, 1);
     }
    
     void OnDestroy()
     {
         vertexBuffer.Release();
     }
}

My Compute Shader

 #pragma kernel CSMain
struct MyTestVertexData
{
     uint id;
     float4 pos;
     float3 nor;
     float4 tan;
     float4 uv;
};
//This is only for vertex kernel
RWStructuredBuffer<MyTestVertexData> vertexBuffer;
float _Time;
uint _VertexCount;
//----------------------VERTEX-----------------------------
[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
     //Real id
     uint rid = vertexBuffer[id.x].id;
     vertexBuffer[rid].pos.y = sin(_Time);
}

Are you expecting the mesh itself to be modified by this code? It doesn’t seem like you’re doing anything except modifying an array of a custom struct type. At no point are you copying that data into the mesh. Or have you checked that array and found no data in it is changing?

1 Like

wait, how am I supposed to copy it back into the mesh? The array is being modified just fine

At some point you need to copy the data back into the mesh… all of those properties you are accessing like mesh.vertices, mesh.normals etc… all create a copy of the mesh data array. In fact my understanding of how those properties work, your code here actually is copying each one of these arrays every iteration of this loop:

         for (int j=0; j< mesh.vertexCount; j++)
         {
             meshVertData[j].id = (uint)j;
             meshVertData[j].pos = mesh.vertices[j];
             meshVertData[j].nor = mesh.normals[j];
             meshVertData[j].uv = mesh.uv[j];
             meshVertData[j].tan = mesh.tangents[j];
         }

So it would be better to pull each of those arrays out into a variable first, then do this iteration.

Anyway, the way you copy data back into the mesh is like this…

// Get a copy of the mesh vertex data.
Vector3[] vertices = mesh.vertices;

// Do your transformations. For example, shift all the vertices right by one unit:
for (int i = 0; i < vertices.length; i++) {
  vertices[i] = vertices[i] + Vector3.right;
}

// Then copy data back
mesh.vertices = vertices;

How or if this is possible to do with a compute shader, I have no idea. Can you get your results data back into CPU-land as arrays on the main thread? If so, just copy it back into the mesh at that point.

1 Like

By the way, if your intention is to move the position of the vertices around, isn’t that something you can just do with a normal vertex shader in the graphics pipeline? Is it something that matters for more than just rendering?

1 Like

I got it to work! Thank you!

I also had to add the last line

     void Update()
     {
         shader.SetFloat("_Time",Time.time);
         shader.Dispatch (_kernel, dispatchCount , 1, 1);
         meshVertData = vertexBuffer.GetData(meshVertData);
     }

Oh, honestly I just wanted to learn how to do it both ways. I’ve done it with a regular vertex shader before, but I wanted to understand how to do the same with a compute shader.

1 Like

If it’s just for learning, no problem, but be aware that using GetData has a huge performance cost. For starters, it causes the CPU to stall waiting for the GPU to flush all outstanding work. Then the GPU itself has to stop everything while copying the data back to CPU memory (Unity doesn’t allow compute shaders to modify mesh data yet, so it has to be copied to the CPU and back).