写这篇的缘由是最近老师给了一个UE4的工程,是一个海水模拟的Demo,实现了二十年前一篇paper的算法,paper的地址是:

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.161.9102&rep=rep1&type=pdf​citeseerx.ist.psu.edu

不过由于我还不够熟悉UE4,加之UE4各种版本满天飞,不同插件基于不同版本开发实在混乱。。。老师给的工程跑不起来,但至少看看代码还是能知道在干啥的,于是尝试在Unity里复现一下这个工程。这篇文章主要介绍Unity Compute Shader的用法流程,而不是IFFT模拟海水的算法,因为受限于贫瘠的数学水平,目前我还没理解这个算法到底在干什么。。。

阅读此文前你应该知道

  • 基本的Unity操作及编写C#脚本
  • 一种Shader语言,GLSL / HLSL / Unity 的Shaderlab也行

阅读此文后你会了解

  • 在Unity中使用Compute Shader的流程
  • 使用Compute Shader时要注意的问题

此文环境

  • Unity 2019.4.11f1
  • Windows 10

什么是Compute Shader

这要从GPU开始说起。GPU相比于CPU的一个特点是它特化了向量、矩阵和浮点运算能力,以众多核心数和大显存来实现极高的并行程度从而完成光栅化流程渲染。因此,我们可以利用GPU的能力做一些别的事情,不是要它的完整光栅化流程,而是它的高性能浮点和向量运算能力。由此诞生了Compute Shader,即专注于运算的Shader。

使用Compute Shader的基本流程

要使用Compute Shader的能力,朴素的想法让我们关注三件事情:

  • 如何进行数据传输,这包括把我要进行运算的数据传给Compute Shader和取出运算结果
  • 如何编写Computer Shader里的具体运算指令
  • 如何用C#脚本控制Compute Shader运行

下面就来分别讲述这三个事情

数据传输

如果你写过普通的shader,就知道传统光栅化管线的shader往往是对输入的texture等数据进行操作,结果输出一个fixed4颜色。在Compute Shader中也是一样。要运算的数据要存放在texture、float等数据结构中传入shader,但输出是一张texture。因此,每个compute shader至少需要一张输出texture(不然你写这个shader干啥?)。然后,如果你需要输入的话,也至少需要一张输入texture。将texture与Compute Shader中的变量绑定的方法,是ComputeShader.SetTexture(string shader中texture的变量名, Texture 要绑定的贴图)。以贴图方式输入的数据相当于是每个线程不同的数据。对于那些各个线程相同的输入数据,也就是一些uniform的数据(texture其实也是uniform的,只不过每个线程从中取出自己需要的那个像素而已),使用ComputeShader.SetFloats之类的方法。具体的代码将在下个段落里一起说明,因为有些需要结合Compute Shader的结构才能说明白。

编写ComputeShader

直接在资源里新建一个Compute Shader,里面会包含一段官方示例代码。不过这里我们使用下面这些代码解释一下各个部分是做什么的(代码都是完整的,只是劈开一段一段放而已)。

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

一上来是一个#pragma kernel 指令,这个指令指示一个类似main函数一样的东西。之后我们就会通过这个指令来在C#脚本中控制Compute Shader的运行。

uniform float4 width_height_Lx_Lz;Texture2D <float4> InputTexture;
RWTexture2D<float4> OutputSurface0;

接下来就声明一些自己需要的变量,例如代码中声明了一个flaot4,一个InputTexture,一个outputTexture。这个input就是我们给shader输入的要计算的数据,output就是计算结果;我们将在脚本中获取它们的内容。

[numthreads(32, 32, 1)]
void CSMain(uint3 ThreadId : SV_DispatchThreadID)
{ float2 pos = ThreadId.xy; //screen space position // 一些具体运算OutputSurface0[pos] = float4(1,0,0,0);
}

接下来是main函数,这里的函数名字叫CSMain。名字你可以自定义,只要和 #pragma kernel 命令匹配即可,就像平常写光栅化shader时 #pragma vertex vert 一样。但注意到这里有一个[numthreads(32, 32, 1)]的命令。这个命令是用来指示每个组里有多少线程运行的。那么什么是分组呢?画个图会比较好理解一些:

比如上面这张texture,假设它像素数量是16*16,我们要对它进行分组,每个组里分派一些线程计算。比如我们将它分成4*4共16个组,那么每个组里就有4*4个像素点,所以我们在numthreads命令里就要写4*4*1(numthreads控制的是一个组内线程分配情况)。分组方式与每个组里的线程数不必相同,这里都是4*4仅是举例巧合。在上面给出的Shader代码中,numthread(32,32,1) 是因为后面我们会看到C#代码中传过来的输入输出texture都是512*512的,所以可知共分了8*8=64组(每组中有32*32个线程,因此就有(512 / 32) * (512 / 32)个组)。这里要注意的是,如何分组没有限制,但要结合GPU的核心数进行设计以最大化效率,且Compute Shader中numthreads的三个参数乘积不能大于1024。

而语义SV_DispatchThreadID就是当前线程对应的整张texture中的像素位置(而不是它在自己组内的位置)。因此上面这段代码的意思就是将输出中的所有位置都填写上(1,0,0,0)这个向量。还有一些其他的语义可以使用,比如SV_DispatchIndex就是当前的线程在哪个组里。

使用C# 控制脚本运行

代码如下:

 // get the handle of the main function in computer shaderint kernel = H0Generation.FindKernel("CSMain");

第一步是获得操纵Compute Shader的句柄。这里H0Generation是一个ComputeShader类型的变量(就是上面展示的那段代码),你通过public声明后在Inspector中绑定也好什么方法,反正你把你的ComputeShader和脚本中的ComputeShader变量绑定到一起,然后调用FindKernel函数,参数就是你刚才在Compute Shader里#pragma kernel 的参数,也就是你Compute Shader的main函数的名字。

 // H0Generation is called every frame, so release the texture of last frameif (H0Output)H0Output.Release();// declare a new rt for this frame with a size of 512 * 512 and ARGB32H0Output = new RenderTexture(512, 512, 1, RenderTextureFormat.ARGB32);// enable write otherwise it cannot be modified in the shaderH0Output.enableRandomWrite = true;// actually create the rtH0Output.Create();

接下来我们创建一张输出贴图。H0Output是一个RenderTexture类型的变量,我们把它声明成512*512,格式是ARGB32。这些参数你都可以随你需要设定,只要保证texture的尺寸、分组数量和每个组内线程数量匹配就可以了。注意这里关键的一点是要设置 RenderTexture.enableRandomWrite=true,你的Compute Shader才有向纹理中写入数据的权限。

 // bind rt and some uniform variables with those in the shaderH0Generation.SetTexture(kernel, "OutputSurface0", H0Output);H0Generation.SetTexture(kernel, "InputTexture", GaussTexture);H0Generation.SetFloats("width_height_Lx_Lz", new float[] { width_height_Lx_Lz.x, width_height_Lx_Lz.y, width_height_Lx_Lz.z, width_height_Lx_Lz.w });

接下来我们进行数据绑定。直接用SetXXX的函数将Shader中参数的名字和C#中的变量绑定起来即可,注意SetTexture命令需要指定操纵Compute Shader的句柄。这里就需要解释一下,上面一直将Kernel比作main函数,但main函数只能由一个,而Compute Shdaer里的kernel是可以有多个的。因此我们可以声明多个函数#pragma kernel,然后再C#中取得相应的句柄,这样设置texture时就会给对应的函数分配它该访问的texture,接下来执行Compute Shader时也使用这个句柄去运行对应的函数。

 // run the compute shader, wtih 32 * 32 groups. this is the maximum, it cannot exceeds 1024H0Generation.Dispatch(kernel, 512 / 32, 512 / 32, 1);

接下来就是执行了。Dispatch函数将执行你传入的句柄对应的Shader中的函数,后面传入的三个数据,就是你对Texture分组的方式。就像上一节提到过的,因为我们这张texture是512*512的,而且Compute Shader里的numthreads定义的每组线程数量是(32, 32, 1),所以我们这里的分组数就应该是 (512/32, 512/32, 1)。运行完后,Compute Shader的运算结果就会存在你指定的output里面啦!接下来你就可以像访问普通texture那样获取里面的数据了!

这里我直接把输出的texture赋给一个材质,然后用材质在世界中渲染一个平面来查看效果;感觉这种方式还是十分方便的(果然printf是最经典的调试方法了)。使用的Compute Shader中有时间变量,所以图案会一直改变,运行效果如下:

知乎视频​www.zhihu.com

就到这里啦✌

使用示例_在Unity中使用ComputeShader示例相关推荐

  1. ios开发中计算代码运算时间_理解Unity中的优化(二):内存

    内存: 内存消耗是一个关键的性能指标,尤其是在内存资源有限的平台上,比如低端移动设备. 内存消耗分析: 在Unity中诊断内存问题,Unity介绍了一款开元的可视化内存分析工具--MemoryProf ...

  2. 在Unity中使用ComputeShader

    目前Unity中有两种并行运算的方式 1)C# Job System,在CPU上以多线程的方式并行运算,通常用来处理逻辑相关内容 2)Compute Shader,从DirectX11.OpenGL4 ...

  3. unity保存游戏数据_在Unity中保存和加载玩家游戏数据

    unity保存游戏数据 In this tutorial, we'll learn to implement Save/Load game functionality in our game. We ...

  4. unity项目源码_在Unity中使用protobuf

    Protocol Buffers (通常简称为protobuf) 是Google开发的一种格式,这种格式与开发语言无关.与运行平台无关,用于序列化结构数据,并且很容易扩展.这种格式可以用于通信协议.数 ...

  5. ugui unity 取消选择_关于Unity中的UGUI优化,你可能遇到这些问题

    ​关键字 界面制作 ​网格重建 ​界面切换 ​加载相关 ​字体 ​ 一.界面制作 Q1:UGUI里的这个选项 ,应该是ETC2拆分Alpha通道的意思,但是在使用中并没起作用?请问有没有什么拆分的标准 ...

  6. unity中创建游戏场景_在Unity中创建Beat Em Up游戏

    unity中创建游戏场景 Learn how to use Unity to create a 3D Beat Em Up game in this full tutorial from Awesom ...

  7. unity 控制点 贝塞尔曲线_在Unity中使用贝塞尔曲线(转)

    鼎鼎大名的贝塞尔曲线相信大家都耳熟能详.这两天因为工作的原因需要将贝塞尔曲线加在工程中,那么MOMO迅速的研究了一下成果就分享给大家了哦.贝塞尔曲线的原理是由两个点构成的任意角度的曲线,这两个点一个是 ...

  8. 雄迈sdk 使用示例_使用增强的API,示例代码和SDK自定义支付解决方案

    雄迈sdk 使用示例 As developers build sites for merchants, we know that payment processing capabilities are ...

  9. unity 下一帧执行_理解Unity中的优化(三):协程(Coroutines)

    Coroutines: Coroutines与其他脚本代码的执行方式不同.在性能分析中,大多数的脚本代码只会在Unity的生命周期方法下出现一次.但是协程总是会在两个地方出现. 在性能分析中,Coro ...

最新文章

  1. Oracle入门(七)之表空间
  2. spring boot 调试 - 热部署
  3. windows安装多个maven_全网最容易理解的Maven安装、配置、集成演示
  4. zookeeper选举机制及相关概念
  5. win7程序员御用主题包制作
  6. 复合存储引擎的设计和实现(包含ORM和内容存储)
  7. 2017-2018-1 20155317《信息安全系统设计基础》 实验五 通讯协议设计
  8. KaTeX数学公式语法
  9. ROS下usb_cam的安装
  10. MP3固件升级(转)
  11. 2022-2028年中国美容美发行业现状调研与未来前景趋势报告
  12. excel 如何删除有颜色的行
  13. JAVA图片加水印(电子奖状填充名字)
  14. 计算机网络层之 P2P
  15. Python爬虫 - 统计自己读过小说的字数
  16. 圆周率怎么计算来的?教你利用欧拉恒等式,生成圆周率万能公式!
  17. 安装 Black Duck
  18. 高级语言编译/解释流程
  19. centos7 zabbix短信告警(阿里短信平台)
  20. 浅谈雷达与其火灾探测功能

热门文章

  1. 积性函数与线性筛(还不会)
  2. 抽象代数学习笔记(5) 运算
  3. hibernate查询-基本查询
  4. MySoft.Data从入门到精通系列(五)【数据更新】
  5. .net 读word中文字方法
  6. UA OPTI501 电磁波5 电磁场的基本物理量:电磁场的源与电磁场的强度
  7. UA MATH564 概率分布1 二项分布下
  8. 案例实作图解Asp.Net MVC教程
  9. VMware Workstation 与 Hyper-V 不兼容。请先从系统中移除 Hyper-V 角色
  10. WPF 自定义控件的坑(蠢的:自定义控件内容不显示)