Unity Compute Shader - 6 获取 Compute Shader Buffer 的数据

准备工作

在上一章中,我们学习了如何将自定义数据传递给Compute Shader Buffer并进行计算,在本章中,我们来学习如何获取Compute Shader Buffer中的数据在C#脚本中使用
首先创建一个C#脚本,名称为 StarsTrans,创建一个Compute Shader脚本,名称为 OrbitingStars
这里,我们准备向Compute Shader传输多个Vecotr3的位置信息,经过Compute Shader计算,然后取回这些计算结果,每个Vecotr3对应一个场景中圆球的位置,让这些圆球像星球一样围绕着中心店转动,StarsTrans 代码如下:

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
using UnityEngine;

public class StarsTrans : MonoBehaviour
{
//球的数量
public int starCount;
public ComputeShader shader;

//球的预制体
public GameObject prefab;

private ComputeBuffer resultBuffer;
private int kernelHandle;
private uint threadGroupSizeX;
private int groupSizeX;
private Vector3[] output;

//球的实例
private Transform[] stars;

void Start()
{
kernelHandle = shader.FindKernel("OrbitingStars");
shader.GetKernelThreadGroupSizes(kernelHandle, out threadGroupSizeX, out _, out _);
//根据圆球数量和线程组中的X线程数,计算出调用次数
groupSizeX = (int)((starCount + threadGroupSizeX - 1) / threadGroupSizeX);

//填充Buffer数据
resultBuffer = new ComputeBuffer(starCount, sizeof(float) * 3);
shader.SetBuffer(kernelHandle, "Result", resultBuffer);
output = new Vector3[starCount];

//根据圆球数量创建对应实例
stars = new Transform[starCount];
for (int i = 0; i < starCount; i++)
{
stars[i] = Instantiate(prefab, transform).transform;
}
}
}

Compute Shader中可读写的buffer

C#脚本中的内容,与上一章的基本相同,都是创建buffer并填充。但在Compute Shader脚本中有些许不同
在上一章中我们只是将数据从C#脚本传输到了Compute Shader中,这里我们需要在Compute Shader中计算一些假随机值,然后在C#脚本中获取buffer中的结果
这里就要使用到 RWStructuredBuffer,代码如下:

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
#pragma kernel OrbitingStars

RWStructuredBuffer<float3> Result;
float time;

float random(float value, float seed = 0.546)
{
float res = frac(sin(value + seed) * 143758.5453);
return res;
}

float3 random3(float value)
{
return float3(random(value, 3.9812), random(value, 7.1536), random(value, 5.7241));
}

[numthreads(64, 1, 1)]
void OrbitingStars(uint3 id : SV_DispatchThreadID)
{
float3 sinDir = normalize(random3(id.x) - 0.5);
float3 vec = normalize(random3(id.x + 7.1393) - 0.5);
float3 cosDir = normalize(cross(sinDir, vec));

float scaledTime = time * 0.5 + random(id.x) * 712.131234;

float3 pos = sinDir * sin(scaledTime) + cosDir * cos(scaledTime);

Result[id.x] = pos * 2;
}

这里要注意,我们使用了 RWStructuredBuffer,这是一个可读写的缓冲区,可以让C#脚本获取到这个缓冲区中的计算结果数据
OrbitingStars方法中,我们做了上一章中圆环相似的伪随机,实现了球形围绕中心点旋转的效果

获取Buffer的计算结果

回到C#脚本,在Update方法中,我们先要将数据传输给Compute Shader,然后使用 ComputeBuffer.GetData 方法从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
using UnityEngine;

public class StarsTrans : MonoBehaviour
{
...

private void Update()
{
shader.SetFloat("time", Time.time);
//将数据传输给Compute Shader进行计算
shader.Dispatch(kernelHandle, groupSizeX, 1, 1);
//获取计算结果
resultBuffer.GetData(output);

//将计算结果依次赋值给每个球形
for (int i = 0; i < stars.Length; i++)
{
stars[i].localPosition = output[i];
}
}

private void OnDestroy()
{
resultBuffer.Dispose();
}
}

此处要注意 OnDestroy中要主动释放掉ComputeBuffer,不然会造成内存泄漏,Unity会有警告

至此我们就完成了若干个小球围绕着一个中心点运动的效果,如下图:

在下一章中,我们尝试使用这一章与前一章所了解的Compute Shader Buffer内容,制作一个球形与立方体之间变换的效果

相关链接

RWStructuredBuffer