Search code examples
c#unity-game-engineshaderhlsl

How to take every vertex of a mesh and make a cube spawn and move to each one? (Unity C#)


Here is a script I currently have for object emptsh to use a compute shader for making a point appear on a screen where every vertex on the BoyMesh mesh is.

Now I'm trying to, instead of make simple points appear on screen for each BoyMesh vertex position, spawn cubes from a different position and then have them fly over to each vertex position (one cube per vertex position). Any ideas?

using UnityEngine;
using System.Collections;

public class emptsh : MonoBehaviour
{
    public BoyMesh BoyMesh;
    public Mesh meshdata;

    public ComputeShader cshader;
    public Material mat;

    private int kernel;
    private int num4pos;

    private ComputeBuffer posbuffer;
    private int num4vertex;
    private ComputeBuffer vertexbuffer;


    private void meshInfo()
    {
        Vector3[] vertics = meshdata.vertices;
        int[] triangles = meshdata.triangles;
        num4vertex = triangles.Length;

        Vector3[] newVertics = new Vector3[num4vertex];
        for (int i = 0; i < num4vertex; ++i)
        {
            newVertics[i] = vertics[triangles[i]];
        }

        vertexbuffer = new ComputeBuffer(num4vertex, 12);
        vertexbuffer.SetData(newVertics);
    }

    void Start()
    {   
        meshdata = BoyMesh.Mesh;

        kernel = cshader.FindKernel("CSMain");
        num4pos = 1; //this determines how many appear
        //num4vertex = ;
        meshInfo();
        //float3 
        posbuffer = new ComputeBuffer(num4pos, 12); 
    }

    private void BufferSet()
    {
        cshader.SetBuffer(kernel, "posbuffer", posbuffer); 
        mat.SetBuffer("posbuffer", posbuffer);     
        mat.SetBuffer("vertexbuffer", vertexbuffer);             
    }

    private void OnRenderObject()
    {
        BufferSet();
        //1
        cshader.Dispatch(kernel, 1, 1, 1);
        //2
        mat.SetPass(0);
        //3
        Graphics.DrawProceduralNow(MeshTopology.Points, num4vertex, num4pos);      
    }

    private void OnDestroy()
    {
        posbuffer.Release();
        vertexbuffer.Release();
    }
}

And then the actual shader code:

Shader "Unlit/cshader4"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    CGINCLUDE
        #define time _Time.y 
        float3 Rotx(in float3 p,in float a){
            float c,s; float3 q = p;
            c = cos(a); s = sin(a);
            q.y = c * p.y - s * q.z;
            q.z = s * p.y + c * q.z;
            return q;
        }

        float random(float id){
            return frac(sin(id)*678.342231);
        }
    ENDCG
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass{
            CGPROGRAM
                #pragma target 5.0
                #pragma vertex vert
                #pragma fragment frag 
                #include "UnityCG.cginc"

                sampler2D _MainTex;

                StructuredBuffer<float3> vertexbuffer;
                StructuredBuffer<float3> posbuffer;
                struct vertIN{
                    uint vID : SV_VertexID;
                    uint ins : SV_InstanceID;
                };

                struct vertOUT{
                    float4 pos : SV_POSITION;
                };

                vertOUT vert(vertIN i){
                    vertOUT o = (vertOUT)0;
                        float4 position = float4(vertexbuffer[i.vID],1);
                        position.xyz = Rotx(position.xyz,time*(1+random(i.ins)));
                        position.xyz += posbuffer[i.ins];
                        o.pos = UnityObjectToClipPos(position);
                    return o;
                }

                fixed4 frag(vertOUT ou):SV_Target{

                    return 1;
                }
            ENDCG
        }
    }
}

Solution

  • There are two parts to this.

    First, you have to change to rendering triangles instead of points, and give your shader the vertices of all of the triangles that make up your cubes.

    Change your MeshInfo to create a vertex buffer of 36 * the number of vertices in the mesh. This is because you need to make 12 triangles for each vertex (2 triangles for each of the 6 sides of the cube). For each of the 36 vertices, you'll need to alter its position to move it from the center of the cube to one of its corners:

    private void meshInfo()
    {
        float cubeHalfWidth = 0.01f; // determines how big the cube is.
        num4vertex = 36 * meshdata.vertices;
    
        Vector3[] newVertics = new Vector3[num4vertex];
        for (int i = 0; i < meshdata.vertices; ++i)
        {
            Vector3 curVertex = meshdata.vertices[i];
    
            // find corner positions
            Vector3 bottomBackLeftCorner = new Vector3(curVertex.x - cubeHalfWidth, curVertex.y - cubeHalfWidth, curVertex.z - cubeHalfWidth);
            Vector3 bottomFrontLeftCorner = new Vector3(curVertex.x - cubeHalfWidth, curVertex.y - cubeHalfWidth, curVertex.z + cubeHalfWidth_;
            Vector3 bottomFrontRightCorner = new Vector3(curVertex.x + cubeHalfWidth, curVertex.y - cubeHalfWidth, curVertex.z + cubeHalfWidth);
            Vector3 bottomBackRightCorner = new Vector3(curVertex.x + cubeHalfWidth, curVertex.y - cubeHalfWidth, curVertex.z - cubeHalfWidth);
            Vector3 topBackLeftCorner = new Vector3(curVertex.x - cubeHalfWidth, curVertex.y + cubeHalfWidth, curVertex.z - cubeHalfWidth);
            Vector3 topFrontLeftCorner = new Vector3(curVertex.x - cubeHalfWidth, curVertex.y + cubeHalfWidth, curVertex.z + cubeHalfWidth);
            Vector3 topFrontRightCorner = new Vector3(curVertex.x + cubeHalfWidth, curVertex.y + cubeHalfWidth, curVertex.z + cubeHalfWidth);
            Vector3 topBackRightCorner = new Vector3(curVertex.x + cubeHalfWidth, curVertex.y + cubeHalfWidth, curVertex.z - cubeHalfWidth)};
    
            // create triangles, clockwise looking at visible side
            int o=i*36;
            // back Face
            newVertics[o++] = bottomBackLeftCorner;
            newVertics[o++] = topBackLeftCorner;
            newVertics[o++] = topBackRightCorner;
    
            newVertics[o++] = topBackRightCorner;
            newVertics[o++] = bottomBackRightCorner;
            newVertics[o++] = bottomBackLeftCorner;
    
            // bottom Face
            newVertics[o++] = bottomFrontRightCorner;
            newVertics[o++] = bottomFrontLeftCorner;
            newVertics[o++] = bottomBackLeftCorner;
    
            newVertics[o++] = bottomBackLeftCorner;
            newVertics[o++] = bottomBackRightCorner;
            newVertics[o++] = bottomFrontRightCorner;
    
            // front Face
            newVertics[o++] = bottomFrontRightCorner;
            newVertics[o++] = topFrontRightCorner;
            newVertics[o++] = topFrontLeftCorner;
    
            newVertics[o++] = topFrontLeftCorner;
            newVertics[o++] = bottomFrontLeftCorner;
            newVertics[o++] = bottomFrontRightCorner;
    
            // top Face
            newVertics[o++] = topBackRightCorner;
            newVertics[o++] = topBackLeftCorner;
            newVertics[o++] = topFrontLeftCorner;
    
            newVertics[o++] = topFrontLeftCorner;
            newVertics[o++] = topFrontRightCorner;
            newVertics[o++] = topBackRightCorner;
    
            // left Face
            newVertics[o++] = bottomFrontLeftCorner;
            newVertics[o++] = topFrontLeftCorner;
            newVertics[o++] = topBackLeftCorner;
    
            newVertics[o++] = topBackLeftCorner;
            newVertics[o++] = bottomBackLeftCorner;
            newVertics[o++] = bottomFrontLeftCorner;
    
            // right Face
            newVertics[o++] = bottomBackRightCorner;
            newVertics[o++] = topBackRightCorner;
            newVertics[o++] = topFrontRightCorner;
    
            newVertics[o++] = topFrontRightCorner;
            newVertics[o++] = bottomFrontRightCorner;
            newVertics[o] = bottomBackRightCorner;
        }
    
        vertexbuffer = new ComputeBuffer(num4vertex, Marshal.SizeOf(newVertics.GetType().GetElementType()));
    
        vertexbuffer.SetData(newVertics);
    }
    

    You'll also need to change the MeshTopology to Triangles:

    Graphics.DrawProceduralNow(MeshTopology.Triangles, num4vertex, num4pos); 
    

    The second part is getting the cubes to move. This is the easier step.

    In the shader, add a float _moveCubeT parameter to the shader, and a lerping from some starting position to the position you have already based on the _moveCubeT parameter:

    Shader "Unlit/cshader4"
    {
        Properties
        {
            _moveCubeT ("MoveCubeT", Float) = 0
            _MainTex ("Texture", 2D) = "white" {}
        }
        CGINCLUDE
            #define time _Time.y 
            float3 Rotx(in float3 p,in float a){
                float c,s; float3 q = p;
                c = cos(a); s = sin(a);
                q.y = c * p.y - s * q.z;
                q.z = s * p.y + c * q.z;
                return q;
            }
    
            float random(float id){
                return frac(sin(id)*678.342231);
            }
        ENDCG
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass{
                CGPROGRAM
                    #pragma target 5.0
                    #pragma vertex vert
                    #pragma fragment frag 
                    #include "UnityCG.cginc"
    
                    sampler2D _MainTex;
    
                    StructuredBuffer<float3> vertexbuffer;
                    StructuredBuffer<float3> posbuffer;
                    float _moveCubeT;
    
                    struct vertIN{
                        uint vID : SV_VertexID;
                        uint ins : SV_InstanceID;
                    };
    
                    struct vertOUT{
                        float4 pos : SV_POSITION;
                    };
    
                    vertOUT vert(vertIN i){
                        vertOUT o = (vertOUT)0;
                            float3 startingpos = float3(0,0,0); //set starting pos for each cube here
                            float4 position = float4(vertexbuffer[i.vID],1);
                            position.xyz = Rotx(position.xyz,time*(1+random(i.ins)));
                            position.xyz += posbuffer[i.ins];
    
                            position.xyz = lerp(startingpos, position.xyz, _moveCubeT); // lerp based on time
    
                            o.pos = UnityObjectToClipPos(position);
                        return o;
                    }
    
                    fixed4 frag(vertOUT ou):SV_Target{
                        return 1;
                    }
                ENDCG
            }
        }
    }
    

    Then back in your C# code, set this _moveCubeT float according to where the cubes are in their transit:

    private void BufferSet()
    {
        // Move cubes for 2 seconds and pause for 8 seconds, repeat.
        float t = Mathf.Clamp( (Time.time % 10f) / 2f, 0f, 1f); 
        cshader.SetBuffer(kernel, "posbuffer", posbuffer); 
        mat.SetBuffer("posbuffer", posbuffer);     
        mat.SetBuffer("vertexbuffer", vertexbuffer);
        mat.SetFloat("_moveCubeT", t);
    } 
    

    Altogether, this is intended to give you completely unlit, untextured, white cubes that move to where the vertices are on the mesh. If you want to light, texture, or color these cubes, some changes will have to be made but that is more appropriate for another question.

    I've never used DrawProceduralNow so there may be some missing pieces but this should be considered at least a partial answer.