这次要完成的是使用计算着色器将立方体转换为球体。
要确保立方体的面数足够多,这样才会有更好的效果。
首先要将网格数据传递到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()
{
}
}