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的其他用法。