【Unity】【ComputeShader】使用ComputeShader计算相机视野

前言:

        设一个1024*1024的地块,需要实时计算有哪些地块在相机范围内。为了提高运行效率,这里用ComputeShader来计算。

1、准备工作

        先在Unity右键创建一个ComputeShader,然后再创建一个Canvas,放一张RawImage(用于接受调试用的图片)。

        然后创建一个控制类,添加以下控制组件:

public class ComputeTest : MonoBehaviour
{
    #region 外部赋值属性

    /// <summary>
    /// 用于显示用的调试图片;
    /// </summary>
    public RawImage ShowImage;

    /// <summary>
    /// 当前计算Shader
    /// </summary>
    public ComputeShader shader;

    /// <summary>
    /// 当前主相机
    /// </summary>
    public Camera mainCamera;

    #endregion
}

2、写ComputeShader代码:

        原理就是,由客户端传过来摄像机的VP矩阵,然后在Shader里计算当前格子是否显示。然后写入到目标图片里就可以了。

        (具体的ComputeShader的介绍网上很多了,这里就不赘述了)

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

// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;

float4x4 WorldToCameraMatrix;

//使用VP矩阵计算是否可见
bool IsVisableInCamera (float x,float z)
{
    float4 clipPos =mul(WorldToCameraMatrix, float4(x,0,z,1));
    float3 ndcPos = float3(clipPos.x / clipPos.w, clipPos.y / clipPos.w, clipPos.z / clipPos.w);

    float view_x = 0.5f + 0.5f * clipPos.x / clipPos.w;
    float view_y = 0.5f + 0.5f * clipPos.y / clipPos.w;

    return view_x>=0 && view_x<=1 && view_y>=0 && view_y<=1 && clipPos.w>0;
}

[numthreads(8,8,1)]
void WolrdMapVisable (uint3 id : SV_DispatchThreadID)
{
    float x=id.x;//0~1024
    float z=id.y;//0~1024

    bool IsVisialbe = IsVisableInCamera(x,z);

    float retValue = IsVisialbe?1:0;    
    //如果可见显示白色,不可见显示黑色
    half4 col=half4(retValue,retValue,retValue,1);

    Result[id.xy] =col;
}

        这里的图片是外部传过来,定好的大小(1024*1024);

3、编写Unity C#控制代码


    private const int TextureSize = 1024;//整个地图大小
    private const int ThreadGroup = 8;
    private const int ThreadGroupSize = TextureSize / ThreadGroup;

    //各种属性名;
    private const string ComputeShaderName = "WolrdMapVisable";
    private const string ComputeTextureName = "Result";
    private const string ComputeMatrixName = "WorldToCameraMatrix";

    private int kID;
    private int matixNameID;
    private RenderTexture texture;

    // Start is called before the first frame update
    void Start() { RunComputeShader(); }

    private void RunComputeShader()
    {
        //设置用于ComputeShader的贴图;
        texture = new RenderTexture(TextureSize, TextureSize, 24);
        texture.enableRandomWrite = true;
        texture.filterMode = FilterMode.Point;
        texture.Create();

        //将图片显示在UI上,调试用;
        ShowImage.texture = texture;

        //获取Shader的一些属性;
        kID = shader.FindKernel(ComputeShaderName);
        matixNameID = Shader.PropertyToID(ComputeMatrixName);

        //启动Shader:
        shader.SetTexture(kID, ComputeTextureName, texture);
        UpdateComputeShader();
    }

    void Update() { UpdateComputeShader(); }

    /// <summary>
    /// 每帧调用一次,设置相机矩阵
    /// </summary>
    private void UpdateComputeShader()
    {
        shader.SetMatrix(matixNameID, mainCamera.projectionMatrix * mainCamera.worldToCameraMatrix);
        shader.Dispatch(kID, ThreadGroupSize, ThreadGroupSize, 1);
    }

        这个C#代码就比较简单了,简单看一看就OK了。

        之后运行起来看看:

         可以看到可视范围(白色)就随着相机的变化而改变了。

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:

        通过ComputeBuffer读取其实有一定的性能消耗,如果数据太大则需要考虑一下是否合算。

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值