unity3d 从零开始compute shader

开始用compute shader

我喜欢vertex/fragment shaders的简单,他们只是做一件事(把顶点和颜色输出到屏幕上去),他们做得非常好,但是有时这种简单限制了你,当你的cpu拼了命的循环那些矩阵,算出并储存在贴图上。。。

Compute Shader 解决了这个问题,我将在今天说明它的基础,我将通过一个unity自己的例子告诉你,使用structured buffer的数据在compute shader中工作

Compute Shader能用来控制粒子群的位置

什么是compute shader?

简单地说, compute shader是一个在GPU中执行的程序,不需要操作网格mesh和贴图texture数据,在OpenGL或DirectX存储空间中工作(不像OpenCL有自己的存储空间),并且能输出数据的缓冲或贴图,跨线程的执行分享存储。

现在unity只支持DirectX11的compute shader,如果等到了OpenGL4.3,苹果mac用户也能用他们了。

这意味着这是一个仅用于windows的教程。如果不是windows的机器,也许会没有用。

用了它有什么好处与坏处?

两个词:数学与并行化。任何问题都包含相同的(没有条件转移conditional branching

)计算对于每一个元素

在数据中的设置都是完美的。在GPU中,你进行的计算越多,越有好处。

条件转移conditional branching确实降低你的性能,因为GPU不能做这个不能达到最佳,但是这和写一个vertex和fragment shaders没有区别,所以如果你有过写shader的经历这将非常简单。

但是它也有潜在问题。从GPU中获取存储到你的CPU中需要时间,当compute shader工作时这就像是你的瓶颈。确保优化你的内核kernel 去工作在最小的缓存可以减轻这个问题,但是这个问题将永远不会消除。

懂了?那我们开始吧

当我们使用DirectX时,unity的compute shader需要使用HLSL编程语言,但是他几乎不能辨别其他shader语言,所以如果你能写CG或者GLSL你也会用好的。

你需要做的第一件事是创建一个新的compute shader。Unity的工程面板中有一个这个选项(博主注:Project->Create->Compute Shader),这一步很简单。如果你把它打开,你会看到像下面这样自动产生的代码(取消了注释)

#pragma kernel CSMain

RWTexture2D<float4> Result;

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    Result[id.xy] = float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
}

这是一个搞清楚compute shader的好例子,所以我们一行一行的看

#pragma kernel CSMain

这指定了这个程序的入口函数(compute shader的main函数),一个compute shader能定义许多函数,你能从脚本中随意调用。

RWTexture2D<float4> Result;

这声明了一个变量,它包含的数据是shader程序将要用到的。我们不需要用到网格mesh数据,你需要像这样明确的声明,你的compute shader需要写入读出什么数据。数据类型名前面的“RW”指定了shader可以进行读写操作。

[numthreads(8,8,1)]

这一行指定了compute shader创建的线程组的大小。GPU利用了大量的并行处理使得GPU创建的一些线程可以同时运行。线程组指定如何组织线衍生程们 spawned threads,在上面的代码中,我们指定我们想要每组线程组包含64个线程,可以使用像一个二维数组。

决定你线程组的最佳大小是个复杂的问题,与你的目标硬件有很大关系。一般的,把你的GPU想成一些流处理器,每一个都能同时执行X个线程,每个处理器同一时间运行一个线程组,所以理论上你想你的线程组去包含x个线程来利用处理器。我用这个值来准备控制他们,所以而不是建议怎样更好地去设置这些值,想了解更多你可以去google。

放置shader是一个很普通的代码。核心函数决定--基于线程的id执行函数,哪一个像素应该被使用, 并且在result缓冲中写入一些数据

实际运行shader

显然我们不能把compute shader加入到网格中导出运行,尤其是它没有网格数据。compute shader实际上要用脚本来调用,看起来像这样:

public ComputeShader shader;

void RunShader()
{
int kernelHandle = shader.FindKernel("CSMain");

RenderTexture tex = new RenderTexture(256,256,24);
tex.enableRandomWrite = true;
tex.Create();

shader.SetTexture(kernelHandle, "Result", tex);
shader.Dispatch(kernelHandle, 256/8, 256/8, 1);
}

这里标记了一些东西。首先在你创建renderTexture前先设置了enableRandomWrite标记。这使你的compute shader 有权写入贴图。如果你不设置这个标记你就不能在你的shader中作为一个写入目标使用贴图。

然后我们需要一种方法来确定我们想要在compute shader中调用什么函数。FindKernel函数使用一个字符串类的名字,与我们一开始在compute shader中设置的核心kernel的名字相同。记住,在一个compute shader文件中, 可以有多个核心kernel (函数)。

ComputeShader.SetTexture让我们把CPU的数据传到GPU。在不同的存储空间中移动数据会在你的程序中产生延迟,你传值越多延迟越明显。对于这个原因,如果你想每帧都执行compute shader,你需要优化实际操作的数据。

三个整数通过Dispatch指定需要产生的线程组的数量,在compute shader中 numthreads块中指定取消每个线程组的大小,所以在上面的例子中,我们产生的线程的总数是:

32*32个线程组 * 64个线程每组 = 65536个线程。

这结束了相当于在render texture中的一个线程一个像素,使场景核心函数调用只能操作一个像素。

所以现在我们知道写一个compute shader能操作贴图内存,让我们看看我们能让他去做什么。

Structured Buffers非常惊异

修饰贴图数据很像vert/frag shader,是时候释放我们的GPU让他操作数据了,是的,这是可行的,听起来非常好。

一个Structured Buffer 只是一种数据类型的一个数组的数据。可以设置conditional branching为浮点或整型。你可以在comepute shader中像这样声明:

StructuctedBuffer<float> floatBuffer;
RWStructuredBuffer<int> readWriteIntBuffer;

数据类型也可以是结构体,在本文的第二个例子会讲到

在我们的例子中,我们将通过我们的compute shader中的一组点,每个都有一个矩阵被我们变换。我们可以用两个分开的缓冲完成它(一个是Vector3s另一个是Matrix4x4s), 但是将一个点或矩阵在一个结构体中,处理起来会很简单

struct VecMatPair
{
public Vector3 point;
public Matrix4x4 matrix;
}

我们也在shader中需要定义数据类型,但是HLSL没有Matrix4x4 或 Vector3类型。

然而,它有相同的存储布局的数据类型。我们的shader结束时看起来是这样的:

#pragma kernel Multiply

struct VecMatPair
{
	float3 pos;
	float4x4 mat;
};

RWStructuredBuffer<VecMatPair> dataBuffer;

[numthreads(16,1,1)]
void Multiply (uint3 id : SV_DispatchThreadID)
{
    dataBuffer[id.x].pos = mul(dataBuffer[id.x].mat,
    				float4(dataBuffer[id.x].pos, 1.0));
}

注意我们的线程组现在组织成一个空间的数组。这对于线程组的维数没有性能影响,所以你可以在你的程序中自由的选择。

在我们刚才的贴图例子中,在一个脚本中构建一个structured buffer有点困难。对于一个buffer,你需要指定在这个buffer中一个元素要多大的字节,并且储存信息和数据本身一起在一个compute buffer物体中。在我们的结构体例子中,字节数大小仅仅是我们存储的 float值的大小(3个vector,16个matrix)乘以一个float的大小(4bytes),对于在一个结构体中一个总数达到76bytes。在compute shader 中设置他看起来是这样的:

public ComputeShader shader;

void RunShader()
{
	VecMatPair[] data = new VecMatPair[5];
	//INITIALIZE DATA HERE

	ComputeBuffer buffer = new ComputeBuffer(data.Length, 76);
	int kernel = shader.FindKernel("Multiply");
	shader.SetBuffer(kernel, "dataBuffer", buffer);
	shader.Dispatch(kernel, data.Length, 1,1);
}

现在我们需要让这个改进数据回到一个格式,使我们能在脚本中使用。不像我们上面render

Texture的例子,structured buffers 需要明确的从GPU的存储空间中被转移到CPU中。在我的经验中,当使用compute shader时,你要注意这是一个最大的性能消耗,我找到的只有一种方法来减轻它是优化你的缓冲,所以他们要尽可能的小,直到存在可用,并且只有从你的shader中把拉数据出来时你才完全需要它。

得到数据到你的CPU中的实际的代码很简单。你需要的只是一个有着相同的数据类型で数组并且大小和写入缓冲的数据相同,如果我们改进上面的脚本去写数据到一个第二数组,看起来像是这样:

public ComputeShader shader;

void RunShader()
{
VecMatPair[] data = new VecMatPair[5];
VecMatPair[] output = new VecMatPair[5];

//INITIALIZE DATA HERE

ComputeBuffer buffer = new ComputeBuffer(data.Length, 76);
int kernel = shader.FindKernel("Multiply");
shader.SetBuffer(kernel, "dataBuffer", buffer);
shader.Dispatch(kernel, data.Length, 1,1);
buffer.GetData(output);
}

你需要看profiler去得到你操作的最大的数据移动到CPU的的精确时间,但是我发现这么做确实非常消耗

原文链接:http://kylehalladay.com/blog/tutorial/2014/06/27/Compute-Shaders-Are-Nifty.html

译:wolf96  http://blog.csdn.net/wolf96

时间: 2024-11-05 15:59:10

unity3d 从零开始compute shader的相关文章

Unity3D Compute shader 新解粒子堆积矩阵【二】

Unity3D Compute shader 新解粒子堆积矩阵[二] 1.Compute Shader都出来了好些年了,幸好Unity3D 加入了该特性,当然U3D本着易开发操作平台,在u3d中实现compute shader起来是相当轻松的. 2.除了Compute Shader,还有 Vertex shader,Geometry shader,Fragment shader ; Tesselation shader,Domain shader,Hull Shader ,如果你觉得有一种都想学

Unity3D Compute Shader 新解 简单体绘制技术与点吸引【三】

Unity3D Compute Shader 新解体绘制技术与吸收[三] 1.Compute Shader是一个DX11的大特点,显然它真的开放了GPU的运算 2.Compute Shader的价值也是非常大的,在科学.工程.医学.各种应用上显而易见 3.GPU的浮点运算和精度比CPU高几个数量级,而且GPU的并行运算的结构.指令不会冗长的等待 这几天听了恩雅的 "Waters Show The Hidden Heart"音乐,当时耳机烂成一坨,我就像他们放Hi歌一样放了出来,于是有人

聊聊如何正确向Compute Shader传递数组

0x00 前言 前一段时间去英国出差,发现Unity Brighton 办公室的手绘地图墙很漂亮,在这里分享给大家. 在这篇文章中,我们选择了过去几周Unity官方社区交流群以及UUG社区群中比较有代表性的几个问题,总结在这里和大家进行分享.主要涵盖了UGUI.Lighting.Profiler.Shader Graph.SRP.Compute Shader.GLES等领域. 同时,也欢迎大家加入我们这个讨论干货的官方技术群,交流看法分享经验. Unity官方社区交流群:629212643 0x

【原创翻译】初识Unity中的Compute Shader

一直以来都想试着自己翻译一些东西,现在发现翻译真的很不容易,如果你直接把作者的原文按照英文的思维翻译过来,你会发现中国人读起来很是别扭,但是如果你想完全利用中国人的语言方式来翻译,又怕自己理解的不到位,反而与作者的愿意相悖.所以我想很多时候,国内的译者也是无奈吧,下次再看到译作也会抱着一些感同身受的态度去读.这是我第一次翻译整篇文章,能力有限,望见谅,翻译不好的地方也希望大家指出来. 其实ComputeShader在Unity中出现已经有蛮长的一段时间了,因为自己一直对Shader比较感兴趣,所

使用Compute Shader加速Irradiance Environment Map的计算

Irradiance Environment Map基本原理 Irradiance Environment Map(也叫Irradiance Map或Diffuse Environment Map),属于Image Based Lighting技术中的一种. Irradiance Map的详细定义可参考GPU Gems 2  Chapter 10."Real-Time Computation of Dynamic Irradiance Environment Maps".简单说来就是一

Unity3D中的Shader

简单的说,Shader是为渲染管线中的特定处理阶段提供算法的一段代码.Shader是伴随着可编程渲染管线出现的,从而可以对渲染过程加以控制. 1. Unity提供了很多内建的Shader,这些可以从官网下载,打开looking for older version的链接就能看到Build-in shaders.选择合适的Shader很重要,以下是开销从低到高的排序: (1)Unlit:仅使用纹理颜色,不受光照影响 (2)VertexLit:顶点光照 (3)Diffuse:漫反射 (4)Specul

Unity3D中的shader基础知识

1.Unity中配备了强大的阴影和材料的语言工具称为ShaderLab,以程式语言来看,它类似于CgFX和Direct3D的效果框架语法,它描述了材质所必须要的一切咨询,而不仅仅局限于平面顶点/像素着色. 2.在Unity3D中创建一个Shader:Assets -> Create -> Shader 3.创建了Shader就可以应用到各个材质Material中,创建材质:Assets -> Create -> Material.然后就可以在材质的Inspector面板中,shad

OpenGL 之 Compute Shader(通用计算并行加速)

平常我们使用的Shader有顶点着色器.几何着色器.片段着色器,这几个都是为光栅化图形渲染服务的,OpenGL 4.3之后新出了一个Compute Shader,用于通用计算并行加速,现在对其进行介绍. 介绍Compute Shader之前需要先介绍一下ImageTexture: 普通的Texture在GLSL中只能进行读取(sampler采样获取数据),写入则必须在Fragment Shader中写入帧缓冲绑定的附件Texture当前像素中,不能随意指定位置写入,并且不能同时读写同一张纹理(我

Unity3D 5.3.5 Compute Shader粒子测试

http://pan.baidu.com/s/1miCuFTA mitk