本文已参与[新人创作礼]活动,一起开启掘金创作之路。
书接上文,我们已经成功使用 ComputeShader 计算了视野,先运行起来看看效果:
可以看到可视范围(白色)就随着相机的变化而改变了。
4、将计算结果返回
然而,以上通过ComputeShader计算的结果仅仅是一张图片而已,C# 里面并不能获取到具体的值。在某些情况下(如显示小地图),这种情况就已经OK了。但如果我们要知道哪些坐标点是在相机范围内的,这个就需要使用ComputeBuffer来处理了。
在C#代码里做以下补充:
……
private const string ComputeBufferName = "VisableCellBuffer";
private int ComputeBufferID;
private ComputeBuffer AppendBuffer;
……
private void RunComputeShader()
{
……
//这里的初始化 AppendBuffer 的时候就要传入理论最大值,不然后续读取的时候会失败;
AppendBuffer = new ComputeBuffer(TextureSize * TextureSize, sizeof(int), ComputeBufferType.Append);
ComputeBufferID = Shader.PropertyToID(ComputeBufferName);
……
}
private void UpdateComputeShader()
{
……
SetAppedBuffer();
……
}
private void SetAppedBuffer()
{
AppendBuffer.SetCounterValue(0);
shader.SetBuffer(kID, ComputeBufferID, AppendBuffer);
}
在ComputeShader里需要做以下补充:
……
AppendStructuredBuffer<int2> VisableCellBuffer;
……
[numthreads(8,8,1)]
void WolrdMapVisable (uint3 id : SV_DispatchThreadID)
{
……
if(IsVisialbe)
{
//把可见的格子转换成int值,然后存进VisableCellBuffer里面。
int2 index=x*10000+z;
VisableCellBuffer.Append(index);
}
……
这样就可以把显示个格子转成一个坐标ID传出来了。
然后读取方法如下:
private int[] ArrRet = new int[ThreadGroupSize];
private void ReadAppendBuffer()
{
var countBuffer = new ComputeBuffer(1, sizeof(int), ComputeBufferType.IndirectArguments);
ComputeBuffer.CopyCount(AppendBuffer, countBuffer, 0);
//通过这个方法拿到第一个数据,就是AppendBuffer的数量
int[] counter = new int[1] { 0 };//ToDo,这里还可以继续优化;
countBuffer.GetData(counter);
int leftCount = counter[0];
int startIndex = 0;
while (leftCount > 0)
{
int leftSize = Mathf.Min(leftCount, ThreadGroupSize);
AppendBuffer.GetData(ArrRet, 0, startIndex, leftSize);//分几次读取出来;
for (int i = 0; i < ThreadGroupSize; i++)
{
//Debug.Log($"[{startIndex}|{i}] {ArrRet[i]}");
}
leftCount = leftCount - ThreadGroupSize;
startIndex = startIndex + ThreadGroupSize;
}
}
这样就可以读取ComputeShader的计算结果了。
PS :
上面的读取方法,其实是做了ComputeShader里面最忌讳的一件事之一:GPU的运算数据下行。这个读取数据的效率是很低的,低到可以计划将效率拉回到传统方法。 所以到一定要慎用! 其实也有不下行的方法,但不在本文的讨论范围之内。