准备工作
在上一章中,我们了解了如何获取Compute Shader Buffer的数据,在本章中,我们将结合前两张所了解的内容,将一个立方体变成球形的效果
首先,我们需要使用blender制作一个立方体的模型,效果如下图:
这个立方体与Unity中自带的立方体不同,在Blender中制作的立方体会分很多段,是为了将其变换为球形时能有更平滑的表面
将制作好的立方体模型导入unity中后,需要勾选Model标签页的 Read/Write
选项
获取Mesh数据并传输、接收
接下来创建C#和Compute Shader脚本,名称均为 MeshDeform
,C#脚本代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| using UnityEditor.Performance.ProfileAnalyzer; using UnityEngine;
public class MeshDeform : MonoBehaviour { public ComputeShader shader; public float radius;
private int kernelhandle; private Mesh mesh;
//存储球形的每个顶点信息 private Vertex[] vertexArray; //存储立方体的每个顶点信息 private Vertex[] initialArray;
//接收GPU数据的buffer private ComputeBuffer vertexBuffer; //初始化的数据传输给GPU的buffer private ComputeBuffer initialBuffer;
private void Start() {
}
private void Update() {
}
private void InitVertexArrays(Mesh _mesh) {
}
private void InitGPUBuffers() {
}
private void GetVertexFromGPU() {
} }
public struct Vertex { public Vector3 vPosition; public Vector3 vNormal;
public Vertex(Vector3 _p, Vector3 _n) { vPosition.x = _p.x; vPosition.y = _p.y; vPosition.z = _p.z;
vNormal.x = _n.x; vNormal.y = _n.y; vNormal.z = _n.z; } }
|
在以上代码中,我们创建了两个ComputeBuffer,initialBuffer
用来存储立方体中初始状态下每个顶点的位置和法线信息,vertexBuffer
是用来获取Compute Shader计算后的结果
在 Start
方法中需要初始化自身和Compute Shader中各类的参数,代码如下:
1 2 3 4 5 6 7 8 9 10 11
| private void Start() { MeshFilter _mf = gameObject.GetComponent<MeshFilter>(); mesh = _mf.mesh; kernelhandle = shader.FindKernel("CSMain"); shader.SetFloat("radius", radius); shader.SetFloat("radius", radius);
InitVertexArrays(_mf.mesh); InitGPUBuffers(); }
|
初始化完成后,我们需要使用立方体mesh中的顶点数据来初始化 vertexArray
和 initialArray
数组,这两个数组在初始化时,里面的数据是完全相同的,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private void InitVertexArrays(Mesh _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; } }
|
数组初始化完成后,接下来初始化 vertexBuffer
和 initialBuffer
,
这两个字段总长度和元素长度都相同,只是 initialBuffer
用来保存立方体状态下mesh的顶点信息数据,vertexBuffer
用来接收数据并对当前立方体mesh进行变换,代码如下
1 2 3 4 5 6 7 8 9 10 11
| 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); }
|
ComputeBuffer初始化完成后,我们需要完成 GetVertexFromGPU
方法,作用是将GPU中的数据保存到 vertexArray
中,并按照顺序赋值给立方体mesh中的每个顶点,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private void GetVertexFromGPU() { 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].vPosition; _normals[i] = vertexArray[i].vNormal; }
mesh.vertices = _vertices; mesh.normals = _normals; }
|
最后是在Update方法中,传入一个在0到1之间的浮点数,使Compute Shader可以进行周期性的变化,也就是在球形和立方体之间来回变换,代码如下
1 2 3 4 5 6 7 8
| private void Update() { float _delta = (Mathf.Sin(Time.time) + 1) / 2; shader.SetFloat("delta", _delta); shader.Dispatch(kernelhandle, vertexArray.Length, 1, 1);
GetVertexFromGPU(); }
|
Compute Shader中计算顶点
完成了C#部分,我们接下来完成Compute Shader部分,这部分相对简单很多,主要是如何计算立方体表面顶点到球面的运动过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #pragma kernel CSMain
struct Vertex { float3 position; float3 normal; };
RWStructuredBuffer<Vertex> vertexBuffer; StructuredBuffer<Vertex> initialBuffer;
float delta; float radius;
[numthreads(8, 8, 1)] void CSMain(uint3 id : SV_DispatchThreadID) { float3 initialPos = initialBuffer[id.x].position; float3 initialNormal = initialBuffer[id.x].normal; float3 s = float3(normalize(initialPos) * radius); float3 pos = lerp(initialPos, s, delta);
float3 snormal = normalize(initialPos); float3 norm = lerp(initialNormal, snormal, delta);
vertexBuffer[id.x].position = pos; vertexBuffer[id.x].normal = norm; }
|
在Compute Shader中,我们先通过 initialBuffer
获取到立方体上每个顶点的信息,然后对每个定点进行归一化,再乘以球形的半径,这样做是因为当前顶点从立方体表面到球形表面,实际上是沿着归一化向量的方向运动的,运动的目的地就是归一化向量乘以半径的位置
计算出了每个顶点的初始位置和目标位置,我们就可以用lerp方法求出当前顶点的位置,最后将所有变换后的顶点信息放入 vertexBuffer
等待C#脚本来获取
最终我们就实现了一个立方体变为球体的功能了,如下图:
在下一章中,我们将了解如何生成噪点图,以及一些经典噪点图的生成算法,并在Compute Shader中导入和使用它们