ComputeShader基础用法系列之一

Compute Shader是Unity5.0之后推出的功能,主要的作用就是利用GPU的大规模并行计算的特性进行一些适合大规模数据的计算,即SIMD(单指令多数据)模式。

在编写Compute Shader之前,首先要了解Compute Shader的基本原理。

传统的Shader编程基本上都是在渲染管线的框架中进行的,而Compute Shader是一段独立的GPU程序,不需要借助渲染管线的框架。但这也意味着更加灵活,需要掌握更偏向底层的一些知识。在Shader编程中,我们无需指定shader使用多少线程,但是再Compute Shader中则需要。GPU线程组结构如下图(这里白嫖一张图):

 线程组是由一层套娃操作形成的,外层的三个维度的线程组里面又套了一个三维度的线程组,外面一层的线程的指定由Dispatch时候传入的参数决定,而内层的线程指定由numthreads()方法指定。

Compute Shader使用的语言用hlsl就可以。一下先给出一个简单的例子来描述Compute Shader原理。

Compute Shader文件主要由一下组成部分:

1.Kernel,声明ComputeShader的接口函数(即宿主程序调用的函数)

2.定义变量

3.实现Kernel指定的方法,如下:

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

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

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    Result[id.xy] = float4(id.xy,0.0, 0.0);
}

任何GPU程序都是需要一个宿主程序(即CPU程序)的,在Unity中通过ComputeShader类进行操作,compute是一个ComputeShader类的对象:

int kernel = compute.FindKernel("CSMain");
compute.Dispatch(kernel,32,32,1);

FindKernel中找的就是ComputeShader中的接口函数名字。

为了方便显示执行效果,我们申请一张RenderTexture查看执行结果,最后如下:

void Start()
    {
        int kernel = compute.FindKernel("CSMain");
        RenderTexture rt = new RenderTexture(512,512,24);
        rt.enableRandomWrite = true;
        rt.Create();
        compute.SetTexture(kernel,"Result",rt);
        compute.Dispatch(kernel,32,32,1);
        display.material.SetTexture("_BaseMap",rt);
    }

申请一张RT,然后设置到compute shader中,调用dispatch,最后将RT给到材质的纹理查看结果。

结果如下:

 我们发现几个现象:

1.图片的大小与写入的颜色范围不一致

2.基本上看不到其他的颜色,在边缘处有很细的绿色线和红色线

我们首先来看一下为什么写入的颜色范围有问题呢?这就得回到我们ComputeShader中的一个非常重要的东西:ThreadID。

我们可以看到对于接口函数的声明:void CSMain (uint3 id : SV_DispatchThreadID) 中有个语义SV_DispatchThreadID,这个语义代表了id这个参数指的是当前执行程序的线程ID,id是一个uint3类型,分别制定了线程的xyz,这个xyz就是上面白嫖的那张图中的SV_DispatchThreadID。然后再看看我们ComputeShader是如何返回的:

Result[id.xy] = float4(id.xy,0.0, 0.0);

其中我们通过id.xy作为纹理纹素的序列写入到纹理当前执行的线程ID的xy,反应在颜色上就是RG通道,这就是为什么大面积黄色,xy在大面积部分都是>=1的,只有在边缘当x=0时,只剩G通道,所以是绿色,当y=0时,只剩R通道,所以是红色。而线条的宽度正体现了这个线程处理的范围——一个线程处理一个像素。

那么这样也好理解为什么我们的颜色写入范围不对了,我们申请的RT大小是512*512的,而我们Dispatch的线程是32*32,ComputeShader中的线程是8*8,那么最后处理的范围就是32*8=256的范围,即512贴图的四分之一。所以当我们把Dispatch的线程指定为64时,就把范围纠正过来了,如下:

 这样,就可以进行一些简单的实际操作了,比如要隔64个像素画一条线,就可以如下:

float2 uv = float2(id.x%64,id.y%64);
Result[id.xy] = float4(uv,0.0, 0.0);

 如果画个圆,就算个中心距离中心的距离,如下:

float2 uv = float2(id.x%64,id.y%64);
    float dis = 1-distance(uv,float2(32,32))/32.0;
    Result[id.xy] = float4(dis,0.0,0.0, 0.0);

效果如下:

 具体其他的实现可以自己研究。之后会接着介绍ComputeShader的其他用法。

原文地址:https://www.cnblogs.com/shenyibo/p/13712440.html