Learn ComputeShader 06 Mesh deformation

这次要完成的是使用计算着色器将立方体转换为球体。

要确保立方体的面数足够多,这样才会有更好的效果。

首先要将网格数据传递到GPU

我们需要定义一个struct来存储一个顶点的数据

    public struct Vertex
    {
        public Vector3 position;
        public Vector3 normal;
        public Vertex(Vector3 p, Vector3 n)
        {
            position = p;
            normal = n;
        }
    }

在shader中也声明结构体存储顶点数据

struct Vertex{
	float3 position;
	float3 normal;
}

接着声明顶点数组存储所有顶点信息,另一个数组也是存储顶点位置,这样可以保证我们修改顶点数组以后还保留初始顶点信息。然后声明对应的buffer存储数据

    Vertex[] vertexArray;
    Vertex[] initiaArray;
    ComputeBuffer vertexBuffer;
    ComputeBuffer InitialBuffer;

然后获取组件MeshFIlter以获取顶点数据.并且将顶点数据填充到数组中,mesh.vertices 返回的是模型的局部顶点坐标,这些坐标是在模型的局部空间中定义的,并且不包括任何缩放、旋转或平移的变换

    private void InitVertexArrays(Mesh mesh)
    {
        vertexArray = new Vertex[mesh.vertices.Length];
        initialArray = new Vertex[mesh.vertices.Length];

        for(int i=0;i<vertexArray.Length;i++)
        {
            Vertex v1 = new Vertex(mesh.vertices[i], mesh.normals[i]);
            vertexArray[i] = v1;
            Vertex v2 = new Vertex(mesh.vertices[i], mesh.normals[i]);
            initialArray[i] = v2;
        }
    }

接着顶点数据传递到GPU

    private void InitGPUBuffers()
    {
        vertexBuffer = new ComputeBuffer(vertexArray.Length, sizeof(float) *6);
        vertexBuffer.SetData(vertexArray);
        initialBuffer = new ComputeBuffer(initialArray.Length, sizeof(float) * 6);
        initialBuffer.SetData(initialArray);
        shader.SetBuffer(kernelHandle, "vertexBuffer",  vertexBuffer);
        shader.SetBuffer(kernelHandle, "initialBuffer", initialBuffer);
    }

接着在shader中声明对应的缓冲区,这里initialbuffer是只读的

RWStructuredBuffer<Vertex> vertexBuffer;
StructuredBuffer<vertex> initialBuffer;

接着在shader中完成立方体和球体之间的过渡,这里主要修改位置和法线。

位置的话先将立方体的位置向量归一化长度变为1,这样就有了一个半径为1的球体,接着乘上半径,然后乘上0.01是因为向量归一化以后长度放大了一百倍,如果不乘就会导致出现的球体非常大。然后根据时间进行插值,这里delta的取值范围是0-1,越靠近0形状越接近于球体

法线可以简单的设置成顶点位置,因为立方体的中心位于原点。

[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
	float3 initialPos = initialBuffer[id.x].position;
	float3 s = float3(normalize(initialPos )*radius*0.01);
	float3 pos = lerp( initialPos, s, delta);

	float3 initialNormal = initialBuffer[id.x].normal;
	float3 snormal = normalize(initialPos);
	float3 norm = lerp( initialNormal, snormal, delta);

	vertexBuffer[id.x].position = pos;
	vertexBuffer[id.x].normal = norm;
}

接着在cpu上接收修改后的顶点信息。并且赋值给mesh

    void GetVerticesFromGPU() 
    {
        vertexBuffer.GetData(vertexArray);
        Vector3[] vertices = new Vector3[vertexArray.Length]; 
        Vector3[] normals = new Vector3[vertexArray.Length];
        for (int i = 0; i < vertexArray.Length; i++)
        { 
            vertices[i] = vertexArray[i].position; 
            normals[i] = vertexArray[i].normal;
         }
        mesh.vertices = vertices;
        mesh.normals = normals;

    }

最后就是在每一帧都调用dispatch更改顶点数据然后传递到cpu

效果如下

完整代码

MeshDeform.compute

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain

float delta;
float radius;

struct Vertex{
	float3 position;
	float3 normal;
};

RWStructuredBuffer<Vertex> vertexBuffer;
StructuredBuffer<Vertex> initialBuffer;

[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
	float3 initialPos = initialBuffer[id.x].position;
	float3 s = float3(normalize(initialPos )*radius*0.01);
	float3 pos = lerp( initialPos, s, delta);

	float3 initialNormal = initialBuffer[id.x].normal;
	float3 snormal = normalize(initialPos);
	float3 norm = lerp( initialNormal, snormal, delta);

	vertexBuffer[id.x].position = pos;
	vertexBuffer[id.x].normal = norm;
}

MeshDeform.cs

using UnityEngine;
using System.Collections;


public class MeshDeform : MonoBehaviour
{
    public ComputeShader shader;
    [Range(0.5f, 2.0f)]
	public float radius;
	
    int kernelHandle;
    Mesh mesh;

    public struct Vertex
    {
        public Vector3 position;
        public Vector3 normal;
        public Vertex(Vector3 p, Vector3 n)
        {
            position = p;
            normal = n;
        }
    }

    Vertex[] vertexArray;
    Vertex[] initialArray;
    ComputeBuffer vertexBuffer;
    ComputeBuffer initialBuffer;

    // Use this for initialization
    void Start()
    {
    
        if (InitData())
        {
            InitShader();
        }
    }

    private bool InitData()
    {
        kernelHandle = shader.FindKernel("CSMain");

        MeshFilter mf = GetComponent<MeshFilter>();

        if (mf == null)
        {
            Debug.Log("No MeshFilter found");
            return false;
        }

        InitVertexArrays(mf.mesh);
        InitGPUBuffers();

        mesh = mf.mesh;

        return true;
    }

    private void InitShader()
    {
       shader.SetFloat("radius", radius);

    }
    
    private void InitVertexArrays(Mesh mesh)
    {
        vertexArray = new Vertex[mesh.vertices.Length];
        initialArray = new Vertex[mesh.vertices.Length];

        for(int i=0;i<vertexArray.Length;i++)
        {
            Vertex v1 = new Vertex(mesh.vertices[i], mesh.normals[i]);
            vertexArray[i] = v1;
            Vertex v2 = new Vertex(mesh.vertices[i], mesh.normals[i]);
            initialArray[i] = v2;
        }
    }

    private void InitGPUBuffers()
    {
        vertexBuffer = new ComputeBuffer(vertexArray.Length, sizeof(float) *6);
        vertexBuffer.SetData(vertexArray);
        initialBuffer = new ComputeBuffer(initialArray.Length, sizeof(float) * 6);
        initialBuffer.SetData(initialArray);
        shader.SetBuffer(kernelHandle, "vertexBuffer",  vertexBuffer);
        shader.SetBuffer(kernelHandle, "initialBuffer", initialBuffer);
    } 

    void GetVerticesFromGPU() 
    {
        vertexBuffer.GetData(vertexArray);
        Vector3[] vertices = new Vector3[vertexArray.Length]; 
        Vector3[] normals = new Vector3[vertexArray.Length];
        for (int i = 0; i < vertexArray.Length; i++)
        { 
            vertices[i] = vertexArray[i].position; 
            normals[i] = vertexArray[i].normal;
         }
        mesh.vertices = vertices;
        mesh.normals = normals;

    }

        void Update(){
        if (shader)
        {
        	shader.SetFloat("radius", radius);
            float delta = (Mathf.Sin(Time.time) + 1)/ 2;
            shader.SetFloat("delta", delta);
            shader.Dispatch(kernelHandle, vertexArray.Length, 1, 1);
            
            GetVerticesFromGPU();
        }
    }

    void OnDestroy()
    {
        
    }
}

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值