Unity3D关于ComputeShader

2023-11-15

由于最近在实验中需要大量循环计算产生网格,所以可能需要GPU的加速。

对于compute shader学习下,可能对于做GPU加速有帮助。以下补充修改了转载文章的内容

原文链接:https://blog.csdn.net/csharpupdown/article/details/79385544 

https://blog.csdn.net/weixin_38884324/article/details/80570160

 

目录

一、简介

二、使用方法

三、关于numthreads

四、C#代码解析

五、注意事项


一、简介

从此图可以看出 CPU和GPU之间的数据传输是瓶颈。故当使用GPU时,对Texture的逐像素处理不需要传回CPU,因而速度比较快。

 ComputeShader就是这样的可称为GPGPU编程的东西。顾名思义,它是一段shader程序。它有shader的特性,比如是在GPU上运行的,但它又是脱离于正常的渲染管线之外的,即不对渲染结果造成影响。这跟我们普通的shader不同。

这种shader扩展名为.compute,是DX11的HLSL风格的shader,当然,也可以转为GLSL,使其能兼容OpenGL(目前还没测试,只在DX11上测试过)。ComputeShader目前能运行的图形库包括DX11、DX12、OpenGL4.3+、OpenGLES3.

    那么它的好处就是只要是涉及到大量的数学计算,并且是可以并行的没有很多分支的计算,都可以采用ComputeShader。它的缺点就是,如上图所示的,我们需要把数据从CPU传到GPU,然后GPU运算完之后,又从GPU传到CPU,这样的话可能会有点延迟,而且他们之间的传输速率也是一个瓶颈。

      不过,如果你真的有大量的计算需求的话,还是不要犹豫地使用ComputeShader,它能带来的性能提升绝对是值得的。

 

二、使用方法

我们通过一个例子来逐渐地解释ComputeShader的用法。这个例子是在Unity平台下实现的,是计算两个相等大小的Vector3数组相加的结果,得到另一个大小一样的Vector3数组。

我们先贴出ComputeShader的代码:

//shader脚本
// Each #kernel tells which function to compile; you canhave many kernels
 
#pragma kernel CSMain
 
 
 
#define thread_group_x 2
 
#define thread_group_y 2
 
#define thread_x 2
 
#define thread_y 2
 
 
 
RWStructuredBuffer<float3> Result;
 
RWStructuredBuffer<float3> preVertices;
 
RWStructuredBuffer<float3> nextVertices;
 
//单个线程组包含的线程数
 
[numthreads(2,2,1)]
 
void CSMain (uint3 id : SV_DispatchThreadID)
 
{
 
int index = id.x + (id.y * thread_x * thread_group_x) +(id.z * thread_group_x * thread_group_y * thread_x * thread_y);
 
Result[index] = preVertices[index] + nextVertices[index];
 
}

代码行数不多,我们一句句来。

          首先第一句,#pragma kernel CSMain 定义了该shader的入口函数。这也是C#文件访问该kernel的接口,下面会提到。当然,一个.compute文件可以包含多个kernel,只需要写多个#pragma kernel xxx即可。但是必须至少包含一个kernel,否则会报编译错误。

          接下来的四句#define 宏定义,跟C++没什么两样,至于它们的意思,前面说了,shader都是运行在多线程上的。而这里又多一个线程与线程组的概念。先按字面意思记一下,后面我们细讲。

         然后三句RWStructuredBuffer<float3>定义了三个buffer,pre和next是我们要从CPU传进来的,result是在GPU计算好之后,传回到CPU的。RW的意思是Read and Write.这里我们用的类型是float3,因为我们是计算两个Vector3数组的加法。也有很多别的类型可以套上去,就像泛型一样。也可以自定义结构体。比如:

//C#脚本 
      struct MyVector
 
   {
 
      float x;
 
      float y;
 
      float z;
 
  };
 
  RWStructuredBuffer<MyVector> data;

然后在C#端创建一个同等类型的结构体。

  [numthreads(2,2,1)] 这句话就比较重要,也能解释了上面的那四句宏定义。我们就来说说这个线程是怎么切分的。

 numthreads的意思就是单个线程组中线程数有多少,并且是怎么分割的。它指定的就是上图中右下的格子。那么线程组的分割又是在哪里指定的呢?这是在C#代码里指定的,下一节会说明。

然后是函数的签名。这里说一下参数uint3 id :SV_DispatchThreadID 。它是指该线程所在的总的线程结构中的索引,还是以上面这个例子来说。如果我们把单个线程组的线程都压缩到这个线程组中,那么就会成为一个4*4的线程方格矩阵。所以id的取值范围就(0,0,0)~(3,3,0)。

其实id的取值范围为(0,0,0)~(threadx*thread_groupx-1,thready*thread_groupy-1,threadz*thread_groupz-1)其中numthreads指定的就是(threadx,thready,threadz).(thread_groupx,thread_groupy,thread_groupz)是在C#脚本里指定的。
 

三、关于numthreads

在C#脚本中我们会用到一个 Dispatch 函数,这个函数就是定义线程组的数量。

在二维中,如果图片的解析度是(256*128),那么Dispatch和numthreads的分配可以如下:

  1. Dispatch (k, 256, 128, 1)         [numthreads (1, 1, 1)]
  2. Dispatch (k, 32, 16, 1)           [numthreads (8, 8, 1)]
  3. Dispatch (k, 256/8, 128/8, 1)     [numthreads (8, 8, 1)]
  4. Dispatch (k, 8, 4, 1)             [numthreads (32, 32, 1)]

从中我们可以发现 Dispatch.x * numthreads.x = 256 ( 图片宽度 ),Dispatch.y * numthreads.y = 128 ( 图片高度 ),只要 Dispatch * numthreads 是图片大小,那么图片就能完全显示,不会少漏一个像素。

(但是numthreads有上限,numthreads.x * numthreads.y * numthreads.z 必须小于等于 1024)

PS:关于Group 与 Thread 的关系如下图

假设有一张 4X4的图像,使用参数 Dispatch(k, 2, 2, 1) 与 [numthreads(2, 2, 1)]

Group 的 长、宽、高 是由 numthreads 所设定的,同理一个Group的大小是不能超过 1024 。

同理 [numthreads(32, 32, 1)] 的 Group 大小就是 32 * 32 * 1 ( 32 * 32 像素 )

然而如果你的 GPU 有 64 核心,而你的 Group 也有 64 个,那么每个核心将能处理一个 Group。

假设 ThreadSize = ( numthreads.x * numthreads.y * numthreads.z )

那么不同的 ThreadSize 在不同硬件厂下分配的资源是不同的

建议: 
AMD:ThreadSize 使用 64 的倍数 ( wavefront 架构 ) 
NVIDIA:ThreadSize 使用 32 的倍数 ( SIMD32 (Warp) 架构 )

另外使用 Group 有个好处,就是可以等待所有 Thread 同步。不过不同的 Group 是无法同步的。

如果你要在一个 Group 中同步所有 Thread,你可以用下面这语法

GroupMemoryBarrierWithGroupSync();

 

四、C#代码解析

using UnityEngine;
 
 
 
public class TestCalcMesh:MonoBehaviour
 
{
 
   public ComputeShader calcMeshShader;
 
 
 
   private ComputeBuffer preBuffer;
 
   private ComputeBuffer nextBuffer;
 
   private ComputeBuffer resultBuffer;
 
 
 
   public Vector3[] array1;
 
   public Vector3[] array2;
 
   public Vector3[] resultArr;
 
  
 
   public int length = 16;
 
 
 
   private int kernel;
 
   // Use this for initialization
 
   void Start ()
 
   {
 
       array1 = new Vector3[length];
 
       array2 = new Vector3[length];
 
       resultArr = new Vector3[length];
 
       for (int i = 0; i < length; i++)
 
       {
 
            array1[i] = Vector3.one;
 
            array2[i] = Vector3.one * 2;
 
       }
 
 
 
       InitBuffers();
 
       kernel = calcMeshShader.FindKernel("CSMain");
 
       calcMeshShader.SetBuffer(kernel, "preVertices", preBuffer);
 
       calcMeshShader.SetBuffer(kernel, "nextVertices", nextBuffer);
 
       calcMeshShader.SetBuffer(kernel, "Result", resultBuffer);
 
   }
 
 
 
   void InitBuffers()
       preBuffer = new ComputeBuffer(array1.Length, 12);
 
       preBuffer.SetData(array1);
 
       nextBuffer = new ComputeBuffer(array2.Length, 12);
 
       nextBuffer.SetData(array2);
 
       resultBuffer = new ComputeBuffer(resultArr.Length, 12);
 
   }
 
 
 
   // Update is called once per frame
 
   void Update ()
 
   {
 
   if(Input.GetKeyDown(KeyCode.KeypadEnter))
 
       {
 
            calcMeshShader.Dispatch(kernel,2,2,1);
 
            resultBuffer.GetData(resultArr);
 
            //do something with resultArr.
 
            resultBuffer.Release();
 
       }
 
}
 
 
 
   void OnDestroy()
 
   {
 
       preBuffer.Release();
 
       nextBuffer.Release();
 
   }
 
}

其实C#的代码能说的地方不多,只要用Visual Studio的智能感知就知道大概的意思了。

首先是变量定义部分,定义了一个Compute Shader,还有三个Compute Buffer,Compute Buffer就是在shader计算时传入传出数据的缓存。可以存储任何的数据类型,对应shader中的RWStruceturedBuffer,因为我们没有指定传入传出的类型,所以在初始化这个buffer的时候,我们需要指定stride的值,在这个例子中,stride=12,因为Vector3有3个float值,每个float值是4个字节。所以这里stride的值是指单个类型的字节数

然后就是ComputeShader.SetBuffer函数,这个其实比较好理解。

比较重要的是ComputeShader.Dispatch函数,它就是指定线程组是如何划分的,即指定(thread_groupx,thread_groupy,thread_groupz)。

最后记得在使用完之后,将Buffer释放掉。
 

五、注意事项

1、numthreads的数值是有大小限制的,具体如下表所示:

Compute Shader

Maximum Z

Maximum Threads (X*Y*Z)

cs_4_x

1

768

cs_5_0

64

1024

你可以选中一个ComputeShader,然后点击Show CompiledShader,里面有说明是哪个版本。即是cs_4_x还是cs_5_0.

2、DX和OpenGL是有不同的布局规则的,如果将compute shader自动转换成GLSL的compute shader的话,用的是std430标准。因此如果是用float3数组的话,在DX里是会自动对齐位数的,而在OpenGL里的话,则会强制转成float4.所以就有可能产生兼容的问题,但是标量、二维、四维的数组就不会有这个问题。所以在使用float3数组时,需要注意一下。

 

3、main函数里,索引不能是常数,会报编译错误。比如

int a = 0;

Result[a] = preVertices[0] +nextVertices[0];

它这样的规则可能是为了线程的安全性,毕竟ComputeShader是多线程的。不能写入一个常数索引里,估计是担心多线程会有数据的冲突问题。

 

4、Don't use get data in real time! (took me 2months to find the reason, which I can explain another time if you like)

Instead:

Make an array of compute buffers, aminimum of 2: one for read, one for write.

Do a rw structure in compute, filledwith junk data to be overridden in the compute

On the next frame (this is import - Isuggest doing an enum to 'waitforenfoframe' yield )

Lastly copy the Contents of the writebuffer into read.

Then use these cloned buffers withwhatever you need - get data does work on static buffers in this case.

这段话描述的问题,我还没有碰到过,所以还不太清楚,暂且留着

 

来源网址 https://forum.unity.com/threads/check-if-a-computeshader-dispatch-command-is-completed-on-gpu-before-doing-second-kernel-dispatch.369631/
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity3D关于ComputeShader 的相关文章

  • Unity3D方向键控制人物移动的代码

    代码 var v Input GetAxis Vertical var h Input GetAxis Horizontal transform Translate transform forward Time deltaTime move
  • Unity3d中使用OnGUI()函数判断“键盘按下抬起”功能的新方法。

    private bool flagJudgeDownAllow true 开始值为true void OnGUI key Event current FunctionKeyCodeV1 key private void FunctionKe
  • Unity的Text Mesh Pro文字显示重叠处理

    在使用Text Mesh Pro的时候 出现文字重叠 如图 在编辑器内显示是正确的 最后发现是换行造成的 原本的文字是从pdf中复制过来 就会重叠 在记事本中删除换行用回车再次换行就能正确显示
  • Vuforia Virtual Button(虚拟按钮)使用技巧

    最近一直在摸索Vuforia的使用 Virtual Button算是一个比较容易吸引人眼球的功能 在经过一些测试后 我来总结下自己在探索过程中得到的一些经验 1 如何新建一个Virtual Button工程 导入Vuforia sdk 后把
  • 我的和unity3d的小故事1——恶魔射手之鼠标控制移动之input.getaxis("Horizontal")与input.getaxis("Vertical")

    跟着恶魔射手视频学习的过程中遇到的第一个问题是怎么也移动不了 那么问题来了 打印出来是获得的下面两个值都是0 怎么办呢 改edit下面的projectsetting里面的input属性下面两个值的type都改成mouse movement
  • Unity3d提升效率的高级技巧(二)

    11 在层次视图中选中某个游戏对象 按快捷键 Cmd Ctrl D 即可复制该对象 对于检视面板中的数组字段也可已同样的方式来复制元素 12 检视面板中所有的颜色字段都是支持复制和粘贴的 只需右键点击颜色字段既可选择操作 13 如果觉得在场
  • JPush极光推送Unity插件iOS设备无法获取DeviceToken

    前言 最近在使用JPush进行极光推送 Unity插件GitHub地址https github com jpush jpush unity3d plugin 问题描述 但是发现了一个问题 按照官方文档操作 最终仍然无法获取DeviceTok
  • 游戏开发unity编辑器扩展知识系列:一个方法添加至多个MenuItem

    代码如下 用多个MenuItem标记方法就可以了 MenuItem GameObject 生成带图片的Image false 100 MenuItem Assets 生成带图片的Image static void GenImageGameO
  • 【Unity-学习-021】异步实现HTTP请求

    对Http访问操作 Unity中一般使用协程操作 但是协程有一个比较要命的要求就是所在Mono必须在场景中是激活的 所以一些操作就会被限制 所以我们就找办法替代掉协程做一些异步的操作 那就用异步方法 首先扩展一下AsyncOperation
  • unity本地分数排行榜简单解决方案(Json)

    具体效果 大体方法 创建一个分数类Score和一个分数类的容器List
  • unity粒子特效附上贴图后播放动画

    转自 http jingyan baidu com article f96699bbb1a0d6894f3c1b77 html 参考 http www unitymanual com thread 2993 1 1 html dsign a
  • Unity3D之Rigidbody

    目录 常用的Rigidbody属性和方法 rigidbody AddForce rigidbody AddTorque rigidbody velocity rigidbody angularVelocity rigidbody Sleep
  • Unity使用spine动画

    Unity使用spine动画 在 Unity 中 常常使用 Spine 来制作一些动画 引擎本身并不能直接播放 Spine 动画 需要额外导入一个 RunTime 插件库才能支持 官网插件导入 当然 也可以到 Spine 官网关于 Unit
  • Unity中UI框架的使用1-添加面板、显示Loading页面

    其中BasePanel和Canvas都是挂在面板的预制物上的 1 导入我们的UI框架 本篇文章中有用的是两个UIPanelType NUIManager和NBasePanel 会放在文章最后供大家使用 2 先将我们做好的Panel设置成预制
  • Mecanim Any State

    Any State表示任意状态 任意状态是 一个一直存在的特殊状态 他的存在是为了保证你在无意转移至某个你当前正处于的特殊状态而准备的 为你的状态机中的每个状态设置相同的对外转移是一个快捷的方式 假如有Walk Run Fly Die这四个
  • NO.6——Unity3D中两种绘制小地图的方法

    在玩游戏时 你经常会发现 在游戏窗口的右上角或者左下角通常会有一个小地图 里边实时反馈角色的移动信息甚至是世界地图 那么这个小地图是如何绘制的呢 我目前掌握了两种方法 一种是以GUI方法重新绘制一个小窗口 另一种是新建一个正交投影的摄像机机
  • Unity3d中脚本无法编译问题(Monodevelop)

    使用Monodevelop打开脚本 编译时报错 具体错误忘记了 原因是 net框架引起 升级到 net框架4 5后解决
  • 对 thread_position_in_grid 感到困惑

    我正在 macOS 上的 Metal 中开发计算着色器 我正在尝试做一些非常基本的事情来了解它们是如何工作的 我看到一些我不明白的输出 我想我应该首先尝试生成一个简单的二维渐变 红色通道将沿宽度从 0 增加到 1 绿色通道将沿高度从 0 增
  • 在 directx 11 中一次渲染到多个纹理

    我正在尝试使用 C directx 11 SDK 一次渲染到两个纹理 我希望一个纹理包含结果图像的每个像素的颜色 渲染 3D 场景时我通常在屏幕上看到的颜色 另一个纹理包含每个像素的法线和深度 3 个浮点表示法线 1 个浮点表示法线 为深度
  • GLSL memoryBarrierShared() 有用吗?

    我想知道 memoryBarrierShared 的用处 事实上 当我查找屏障功能的文档时 我读到 对于计算着色器中任何给定的静态屏障实例 单个工作组内的所有调用都必须进入该实例 然后才能允许任何调用继续超出该实例 这确保了在给定的屏障静态

随机推荐

  • QFile清空原来文件内容的方法

    QFile清空原来文件内容的方法 Qt 清空文件方法 Qt 清空文件方法 方法一 void DataOperate clearFileInfos QString fileName QFile file fileName file resiz
  • LeetCode1823.找出游戏的胜利者

    共有 n 名小伙伴一起做游戏 小伙伴们围成一圈 按 顺时针顺序 从 1 到 n 编号 确切地说 从第 i 名小伙伴顺时针移动一位会到达第 i 1 名小伙伴的位置 其中 1 lt i lt n 从第 n 名小伙伴顺时针移动一位会回到第 1 名
  • 机器学习之加州房价预测(一)

    加州房价预测实例 任务 基于加州房价数据集建立一个预测模型 使之可以在给定的条件下 预测加州任何地点的房价的中位数 一 定义问题 1 公司要如何利用我的模型 模型的输出将作为另一个机器学习算法的输入 该算法在综合考虑其他因素之后 决定是否值
  • 推荐一本书——《The Scientist and Engineer's Guide to Digital Signal Processing》

    突然在国外的网站上看到一本非常好的数字信号处理的书籍 讲解简介明白 清晰易懂 书籍为免费电子版 地址为 http www dspguide com pdfbook htm
  • day05-编程题

    知识点 方法 题目1 训练 定义一个方法 该方法能够找出两个小数中的较小值并返回 在主方法中调用方法进行测试 训练提示 根据方法的功能描述 方法的参数应该是两个小数 要返回两个小数的较小值 所以返回值类型也是小数类型 解题方案 操作步骤 定
  • QT中学习Opengl---(GLSL简单的使用)

    前言 本文的代码是 LearnOpenGL 中对应代码 这里提供学习 大家喜欢的可去官方网站去看看 https learnopengl cn readthedocs io zh latest https learnopengl cn rea
  • C++的模板特例化template<>

    C 的模板特例化是指当我们定义了一个通用的模板类或模板函数时 如果特定输入参数类型或值需要进行不同的处理 我们可以为这些特定情况提供单独的实现 这就是模板特例化 下面我们将详细介绍C 的模板特例化 假设我们有以下的一个模板类 templat
  • java自学笔记12:java中的集合框架(下)List

    一 学生选课 判断List中课程是否存在 思考 在课程序列中 如何判断是否包含某门或者某几门课程 如果课程序列包含某门课程 如何判断该课程的索引位置 在学生映射表中 如何判断是否包含某个学生ID 又该如何判断是否包含某个学生对象 如果想把课
  • 解读随着教育改革的深入steam教育

    STEAM鼓励孩子勇于创新和探索 打破思维的第三面墙 自古以来 大家都是教育孩子纠正错误 而STEAM可以让孩子们通过与小组实践学习 探索讨论 交流思想和相互帮助 来发现自己的缺点和不足 通过团队合作来弥补自己的劣势 可以说 STEAM是一
  • Pandas 返回Nan值的行索

    Pandas 返回Nan值的行索 通过np where函数查找 gt gt gt df Out 1 0 1 0 0 450319 0 062595 1 0 673058 0 156073 2 0 871179 0 118575 3 0 59
  • Mysql大小写敏感设置(Docker版)

    应用场景 本人由于项目前期使用windows版国产数据库开发 默认就是大小写不敏感的 加上代码规范约束不够 导致代码中SQL大小写不统一 后期有需求要更换数据库 改用Mysql 因为在Linux系统中Mysql默认是大小写敏感的 所以需要对
  • java多线程同步的实现方式

    java多线程同步的实现方式 1 什么时候会出现线程安全问题 2 使用synchronized关键字 2 1修饰方法 2 2 修饰代码块 3 使用重入锁实现线程同步 4 wait与notify方法 5 使用原子变量实现线程同步 关于vola
  • vmospro启动黑屏_VMOS Pro,安卓手机上的虚拟机

    应用名称 VMOS Pro 应用包名 com vmos pro 应用版本 1 1 26 应用大小 28 0MB 适用平台 Android 5 1 版本说明 1 优化电量同步问题 2 优化游戏断触问题 3 设备信息修改加入随机按钮 4 增加横
  • Docker导入导出镜像(镜像迁移)

    打包现有镜像 docker images 命令查看已有镜像列表 docker save命令打包镜像 docker save使用说明 o 选项 用来指定输出文件 将alpine ffmpeg 3 15打包 docker save o alpi
  • Linux定时任务

    Linux定时任务 at命令 语法 at 选项 日期时间 选项 f 指定包含具体指令的任务文件 q 指定新任务的队列名称 l 显示待执行任务的列表 d 删除指定的待执行任务 m 任务执行完成后向用户发送 E mail 日期时间 指定任务执行
  • 通过XSD文件生成JAVA对象

    c Program Files Java jdk1 8 0 101 bin gt xjc exe p io xsd xml encoding UTF 8 xsdTOxml MyField xsd d xsdTOxml Picked up J
  • 如何在uni中实现一个路由守卫

    在uni app中实现路由守卫 可以使用全局的router beforeEach方法来拦截路由导航 以下是一个简单的示例 展示了如何在uni app中实现路由守卫 在上述代码中 我们通过监听beforeRouterEnter事件来实现路由守
  • java.util.LinkedHashMap cannot be cast to Entity

    前后端数据传输转换问题 java util LinkedHashMap cannot be cast to Entity 问题场景 项目前端使用json传输方式 后台接收后对象变成了LinkedHashMap ResponseBody Re
  • 方向包围盒OBB(oriented bounding box)

    制造几何仿真中的碰撞检测通常视为针对刚体对象间的碰撞检测 这样的话可以把非刚体即软体的建模和变形算法对碰撞检测的影响减少到最小 常见成熟的基于包围盒的碰撞检测 box intersection test 算法如 1 沿坐标轴的包围盒AABB
  • Unity3D关于ComputeShader

    由于最近在实验中需要大量循环计算产生网格 所以可能需要GPU的加速 对于compute shader学习下 可能对于做GPU加速有帮助 以下补充修改了转载文章的内容 原文链接 https blog csdn net csharpupdown