Unity Compute Shader入門初探

在寫shader的時(shí)候糖儡,我們通常會(huì)創(chuàng)建一個(gè)unlit shader或者standard surface shader伐坏,但是,至少在我的工作中休玩,從來沒有創(chuàng)建過compute shader著淆,這個(gè)東西是干嘛用的呢劫狠?帶著這個(gè)疑問,我們今天來一探究竟永部。

創(chuàng)建shader面板

談起compute shader独泞,我們要先了解一個(gè)概念,叫GPGPU(General-purpose computing on graphics processing units)苔埋。根據(jù)Wikipedia的介紹

它是利用處理圖形任務(wù)的圖形處理器來計(jì)算原本由中央處理器處理的通用計(jì)算任務(wù)懦砂。這些通用計(jì)算任務(wù)通常與圖形處理沒有任何關(guān)系。

那么组橄,專門為圖形任務(wù)所設(shè)計(jì)的圖形處理器是如何怎么能處理通用計(jì)算任務(wù)的呢荞膘?這不是在搶CPU的飯碗么?的確玉工,早期的顯卡是沒有這種功能的羽资。在很久很久以前,那時(shí)老黃還沒成立NVIDIA遵班,顯卡市場(chǎng)還是Voodoo稱霸的時(shí)候屠升,顯卡里有兩種單元,一種專門處理頂點(diǎn)狭郑,叫做vertex unit腹暖,另一種專門處理像素,稱作pixel unit翰萨。然而脏答,隨著渲染場(chǎng)景越來越復(fù)雜(想想游戲史,是不是游戲看起來越來越逼真了亩鬼?)殖告,這種模式不利于負(fù)載均衡,而且這時(shí)候人們也希望顯卡能做一些通用計(jì)算的需求辛孵,圖形渲染不再是唯一的需求丛肮,所以GPGPU在這時(shí)開始浮出水面。到了2006年底及2007年初魄缚,老黃拿出了他的GeForce 8800 GTX,AMD也拿出了Radeon HD 2800焚廊,unified shaders的時(shí)代來臨了冶匹。什么叫unified shaders?之前專門處理頂點(diǎn)的vertex unit我們需要為它寫vertex shader咆瘟,專門處理像素的pixel unit我們需要為它寫pixel shader嚼隘,到了unified shaders時(shí)代,不管vertex shader也好袒餐,pixel shader也好飞蛹,顯卡都會(huì)用一種unit來處理谤狡,這時(shí),GPGPU變成了可能卧檐。

現(xiàn)在回到compute shader墓懂,它是一種運(yùn)行在顯卡上卻不在普通渲染管線上的程序,利用它可以做大型并行的GPGPU算法霉囚,以此來獲得比CPU快很多倍的計(jì)算能力捕仔。不過在獲得這種能力之前,我們先來看看如何使用這種shader盈罐。

首先是C#腳本榜跌,用于掌控全局

public Texture inputTex;
public ComputeShader computeShader;
public RawImage image;

void Start(){
        RenderTexture t = new RenderTexture(inputTex.width,inputTex.height,24);
        t.enableRandomWrite = true;
        t.Create();
        image.texture = t;
        image.SetNativeSize();

        int kernel = computeShader.FindKernel("Gray");
        computeShader.SetTexture(kernel,"inputTexture",inputTex);
        computeShader.SetTexture(kernel,"outputTexture",t);
        computeShader.Dispatch(kernel,inputTex.width / 8, inputTex.height / 8,1);
}  

其次是compute shader,所有的計(jì)算都放在這里

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

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



[numthreads(8,8,1)]
void Gray (uint3 id : SV_DispatchThreadID)
{
    float r = inputTexture[id.xy].r;
    float g = inputTexture[id.xy].g;
    float b = inputTexture[id.xy].b;

    float res = r * 0.299 + g * 0.587 + b * 0.114;
    outputTexture[id.xy] = float4(res,res,res,1);
}

這里我實(shí)現(xiàn)的功能是將一張彩色圖轉(zhuǎn)成灰階圖盅粪,雖然體現(xiàn)不出GPU的強(qiáng)大并行計(jì)算能力钓葫,但能起到如何使用compute shader的作用O(∩_∩)O~

首先我們?yōu)橐敵龅幕译A圖準(zhǔn)備一個(gè)地方,名叫t票顾,t的屬性enableRandomWrite必須為true础浮,這樣才能將數(shù)據(jù)寫入。然后通過FindKernel方法找kernel库物,里面?zhèn)魅氲膕tring就是compute shader中#pragma kernel Gray定義的名字Gray霸旗,當(dāng)然你想起什么名字就起什么名字,可以隨便改的戚揭,不過相應(yīng)的void Gray (uint3 id : SV_DispatchThreadID)這個(gè)地方的名字要一致诱告。然后用SetTexture方法將數(shù)據(jù)設(shè)置好,由于inputTexture是讀取數(shù)據(jù)用的民晒,所以對(duì)應(yīng)在compute shader里面的變量類型是只讀型的Texture2D精居,而outputTexture的輸出數(shù)據(jù)用的,所以用讀寫型的RWTexture2D潜必。有朋友可能要問了靴姿,RWTexture2D這個(gè)怎么沒見過啊。原來磁滚,unity中的compute shader是遵循DirectX 11語法的佛吓,所以這個(gè)RWTexture2D是HLSL里的類型,詳見微軟文檔https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-rwtexture2d垂攘。

最后到了調(diào)用Dispatch方法的時(shí)候维雇,也意味著整段程序都設(shè)置完畢,要開始工作了晒他。從文檔里可以看出需要傳四個(gè)參數(shù)進(jìn)去吱型,分別是kernelIndex、threadGroupsX陨仅、threadGroupsY津滞、threadGroupsZ铝侵,這些是什么意思呢?

文檔圖片1

kernelIndex很好解釋触徐,就是剛剛FindKernel方法返回的值咪鲜。其余的參數(shù),從淺層次上講锌介,就是要讓threadGroupsX * numthreads.x = 圖片寬嗜诀,threadGroupsY * numthreads.y = 圖片高,threadGroupsZ大部分時(shí)間下都是1孔祸。這里的numthreads就是compute shader里的[numthreads(8,8,1)]隆敢。
另外有個(gè)限制條件是在shader model 5的平臺(tái)下numthreads.x *numthreads.y * numthreads.z <= 1024,numthreads.z <= 64崔慧,(在shader model 4.5的平臺(tái)下這個(gè)數(shù)字是768拂蝎,numthreads.z <=1,再往下的shader model則不支持compute shader了)惶室。
還有要注意的是由于架構(gòu)問題温自,一個(gè)線程組里有幾個(gè)線程需要結(jié)合硬件,NVIDIA的架構(gòu)下最好是32的倍數(shù)個(gè)線程皇钞,AMD架構(gòu)下最好為64的倍數(shù)個(gè)線程悼泌。

更進(jìn)一步,我們來看微軟文檔里的一張圖夹界。

文檔圖片2

threadGroupsX馆里、threadGroupsY、threadGroupsZ代表著你要開多少組線程可柿,每個(gè)線程組里面有多少個(gè)線程是由numthreads里的參數(shù)決定的鸠踪。拿我寫的這段代碼舉例,Dispatch的時(shí)候開了128 * 128 * 1組的線程組复斥,每組線程組里面有8 * 8 * 1個(gè)線程营密,128 * 8 = 1024,這里我用的圖片的長和寬都是1024目锭,即每個(gè)線程都在處理圖片上的某一個(gè)像素评汰。void Gray (uint3 id : SV_DispatchThreadID)這邊的id即為每個(gè)線程的index。那如果numthreads設(shè)為[numthreads(64,4,1)]痢虹,那么Dispatch的時(shí)候可以設(shè)為Dispatch(kernel,inputTex.width / 64, inputTex.height / 4,1);键俱。

文檔圖片2中還提到了SV_GroupThreadID,SV_GroupID,SV_GroupIndex,這些也是用來索引線程的世分,具體關(guān)系看彥霖大佬的圖就明白了。

Group ID 一看就懂 :

Group Thread ID 一看就懂 :

Group Index 一看就懂 :

接下來我們來看如何用compute shader進(jìn)行簡單計(jì)算任務(wù)缀辩,而不是處理貼圖臭埋。代碼如下

CS腳本

public ComputeShader csBuffer;
ComputeBuffer buffer;
struct MyInt{
        public int val;
        public int index;
    };

void Start()
    {
       
            CSFib();
        
    }

public void CSFib(){
        MyInt[] total = new MyInt[32];
        buffer = new ComputeBuffer(32,8);
        int kernel = csBuffer.FindKernel("Fibonacci");
        csBuffer.SetBuffer(kernel,"buffer",buffer);
        csBuffer.Dispatch(kernel,1,1,1);
        buffer.GetData(total);
        for (int i = 0; i < total.Length; i++)
        {
            Debug.Log(total[i].val);
        }

    }

    private void OnDestroy() {
            buffer.Release();
    }

compute shader

#pragma kernel Fibonacci

struct MyInt{
    int val;
    int index;
};

RWStructuredBuffer<MyInt> buffer;

int Fib(int n){
    int a = 0;
    int b = 1;
    int res = 0;
    for(int i=0;i<n;i++){
        res = a + b;
        a = b;
        b = res;
    }
    return a;
}

[numthreads(32,1,1)]
void Fibonacci (uint3 id : SV_DispatchThreadID)
{
    buffer[id.x].val = Fib(id.x);
    buffer[id.x].index = id.x;
    
}

菲波那切數(shù)列我想大家都應(yīng)該知道吧踪央?那么用GPU來算菲波那切數(shù)列就是以上代碼在實(shí)現(xiàn)的內(nèi)容了。為了讓大家看看在compute shader中如何使用自定義結(jié)構(gòu)體瓢阴,我舍棄了int類型而使用了自定義結(jié)構(gòu)體MyInt畅蹂,其中val存菲波那切數(shù)列中每一項(xiàng)的值,index存的是值所對(duì)應(yīng)的索引荣恐。
這里多出來了一個(gè)ComputeBuffer類型的buffer液斜,用于存儲(chǔ)計(jì)算得到的值,可以看到后面要用
GetData方法把數(shù)值從GPU里面拿出來叠穆。在new這個(gè)ComputeBuffer的時(shí)候我們需要傳入兩個(gè)參數(shù)少漆,從文檔上來看第一個(gè)是count,我需要輸出32個(gè)菲波那切數(shù)列硼被,就填32示损;第二個(gè)是stride,代表每個(gè)元素的長度嚷硫,由于自定義結(jié)構(gòu)體MyInt有兩個(gè)int類型的屬性检访,所以這里的stride為8。其余內(nèi)容通過上面的講解仔掸,我想應(yīng)該不難理解了脆贵。

文檔圖片3

當(dāng)然,compute shader能做的遠(yuǎn)遠(yuǎn)不止這些起暮,來看看大佬們把compute shader玩出了什么花樣卖氨。

GPU粒子系統(tǒng)
GPU布料系統(tǒng)
圖片壓縮
模型曲面細(xì)分
戰(zhàn)地3中使用compute shader對(duì)點(diǎn)光源、探照燈等進(jìn)行剔除
知乎大V MaxwellGeng實(shí)現(xiàn)的GPU Occlusiong Culling鞋怀,他使用了Hiz的方法双泪,對(duì)Cluster進(jìn)行遮擋剔除(
刺客信條大革命,在這部游戲中使用了GPUDRP技術(shù)密似,并在Siggraph 2015: Advances in Real-Time Rendering in Games course中發(fā)表

還有很多很多應(yīng)用在這就不一一列舉了焙矛,以及,以上的我都無力實(shí)現(xiàn)残腌,這么菜讓大家見笑了(⊙o⊙)…

項(xiàng)目地址

參考
Introduction to compute shaders
【風(fēng)宇沖】Shader:二十八ComputeShaders
Unity 使用 GPGPU 計(jì)算村斟,使用 ComputeShader 將圖片轉(zhuǎn)成灰階圖
numthreads
Unity 3D : ComputeShader 全面詳解
Compute Shader次世代優(yōu)化方案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抛猫,隨后出現(xiàn)的幾起案子蟆盹,更是在濱河造成了極大的恐慌,老刑警劉巖闺金,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逾滥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡败匹,警方通過查閱死者的電腦和手機(jī)寨昙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門讥巡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舔哪,你說我怎么就攤上這事欢顷。” “怎么了捉蚤?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵抬驴,是天一觀的道長。 經(jīng)常有香客問我缆巧,道長布持,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任盅蝗,我火速辦了婚禮鳖链,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘墩莫。我一直安慰自己芙委,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布狂秦。 她就那樣靜靜地躺著灌侣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪裂问。 梳的紋絲不亂的頭發(fā)上侧啼,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音堪簿,去河邊找鬼痊乾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛椭更,可吹牛的內(nèi)容都是我干的哪审。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼虑瀑,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼湿滓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起舌狗,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤叽奥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后痛侍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朝氓,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膀篮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘹狞。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖誓竿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谈截,我是刑警寧澤筷屡,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站簸喂,受9級(jí)特大地震影響毙死,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喻鳄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一扼倘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧除呵,春花似錦再菊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泛豪,卻和暖如春稠诲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诡曙。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工臀叙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人价卤。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓劝萤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親荠雕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子稳其,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容