北条ゲームズ Hojo Games

錦の北条の開発ブログ

スポンサーサイト 

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

【Unity】カメラに滴る雨をコンピュートシェーダで【解説未完】 

2017y01m21d_225040536.png 
こんな感じの表示をコンピュートシェーダでやってみました。

正確には、ウォープの適当な倍数(今回は128)のスレッド単位で動かし、一つ目のスレッドグループでは水滴のシミュレーションを行い、更に同時にレンダーテクスチャの解像度を満たす分スレッドグループを走らせ、1ピクセル1スレッド担当で画面を歪ませるためのrgb情報と水滴の現在の情報の保持であるaを合わせたrgbaレンダ―テクスチャ出力するためのコンピュートシェーダで、実際に歪ませているシェーダはピクセルシェーダです。(品質が結構いいので意外かもしれないですが、そちらはちょっとuvをいじるだけのイメージエフェクトでほとんど大したものではありません。)

本稿のコンピュートシェーダでは水滴シミュレーションと歪ませる情報の生成を同時に走らせているので、多分常に現在のフレームの確定後の見栄えは現れないと思います(前フレームのものと混ざるか、完全に前フレームで描画するべきだったものになると思う)。現実問題見栄えとしては影響はあまり現れないので、速度とのトレードオフとして実施しています。

ちなみに一応、滴る雨シェーダはコンピュートシェーダでなくても大量のPassを許容すれば、僕の実装したロジックそのままだときつそうですが(例えばサイズを持った点を打つのはキツそうなので、そこら辺をやめれば)頑張ればピクセルシェーダだけでも実装できるはずです。その場合はハードウェア要件は下がると思いますが、重そうですね。最初からハイエンドの方を向く感じでやるならコンピュートシェーダでやった方が素直で、適切に実装すればかなり高速になるんじゃないかなと思います。

突然必要に駆られてやっただけなのでGPUの仕組みとか全く知らなかったので、GPUプログラミング的に適切でない部分あるかもしれません。(というか、ツッコミがもらえたらうれしいのでアップした)
別に大した量のコードではないんですがかなり汚いです、ご注意


・コンピュートシェーダ部分

#pragma kernel GenerateDropletMap

#define NaturalDropletReductionPerDispatch 0.005
#define ThreadNumberOneAxis 128

//kuso
#define OutOfScreenY -1000000

struct DroppingWater
{
int X;
int Y;
int RadiasSize;
bool NeverMove;
};

//in
float2 outputTextureResolution;
Texture2D<float4> frictionTexture;
float2 frictionTextureResolution;

//inout
RWTexture2D<float4> ResultNormal;
RWStructuredBuffer<DroppingWater> droppingWaters;

//アンチエイリアス対応の点うつくん
void DrawPoint( int x, int y, float col )
{
  ResultNormal[int2(x,y)] += float4(0,0,0,col);
  ResultNormal[ int2(x,y) + float2(-1,-1)] += float4(0,0,0,ResultNormal[int2(x,y)].a * 0.1f);
ResultNormal[ int2(x,y) + float2(-1,0)] +=  float4(0,0,0,ResultNormal[int2(x,y)].a * 0.15f);
ResultNormal[ int2(x,y) + float2(-1,1)] +=  float4(0,0,0,ResultNormal[int2(x,y)].a * 0.1f);
ResultNormal[ int2(x,y) + float2(0,-1)] +=  float4(0,0,0,ResultNormal[int2(x,y)].a * 0.15f);
ResultNormal[ int2(x,y) + float2(0,1)] +=   float4(0,0,0,ResultNormal[int2(x,y)].a * 0.15f);
ResultNormal[ int2(x,y) + float2(1,-1)] +=  float4(0,0,0,ResultNormal[int2(x,y)].a * 0.1f);
ResultNormal[ int2(x,y) + float2(1,0)] +=   float4(0,0,0,ResultNormal[int2(x,y)].a * 0.15f);
ResultNormal[ int2(x,y) + float2(1,1)] +=   float4(0,0,0,ResultNormal[int2(x,y)].a * 0.1f);
}

//ソリッド円を描く君 http://fussy.web.fc2.com/algo/algo2-1.htm参考に作成
void DrawSolidCircle( int x0, int y0, int r)
{
int currentRadius = r;
DrawPoint( x0,y0,1.0f);
while(currentRadius > 0){
 int x = currentRadius;
 int y = 0;
 int F = -2 * r + 3;

 while ( x >= y ) {
  float currentColor =  (0.9f - ((float)x / r) * 0.9f);
   DrawPoint( x0 + x, y0 + y, currentColor );
   DrawPoint( x0 - x, y0 + y, currentColor );
   DrawPoint( x0 + x, y0 - y, currentColor );
   DrawPoint( x0 - x, y0 - y, currentColor );
   DrawPoint( x0 + y, y0 + x, currentColor );
   DrawPoint( x0 - y, y0 + x, currentColor );
   DrawPoint( x0 + y, y0 - x, currentColor );
   DrawPoint( x0 - y, y0 - x, currentColor );
   if ( F >= 0 ) {
     x--;
     F -= 4 * x;
   }
   y++;
   F += 4 * y + 2;
 }
 currentRadius --;
 }
 }


[numthreads(ThreadNumberOneAxis,1,1)]
void GenerateDropletMap (uint3 id : SV_DispatchThreadID,
uint3 groupIDx : SV_GroupID)
{
//水滴は128個まで(こう書けばdivergeしないと信じている)
if(groupIDx.x == 0){

uint dropletID = id.x;

if(!droppingWaters[dropletID].NeverMove){

//闇のif無し水滴分岐術
float resizeToFrictionMapCoefficient = (frictionTextureResolution.x / outputTextureResolution.x);
float2 resizedCurrentDropletPosition = float2(droppingWaters[dropletID].X,droppingWaters[dropletID].Y) * resizeToFrictionMapCoefficient;
droppingWaters[dropletID].X +=  (int)(droppingWaters[dropletID].RadiasSize * (frictionTexture[resizedCurrentDropletPosition + float2(1,-1)].r + frictionTexture[resizedCurrentDropletPosition + float2(0,-1)].g * 2.0f - frictionTexture[resizedCurrentDropletPosition + float2(0,-1)].b * 2.0f - frictionTexture[resizedCurrentDropletPosition + float2(-1,-1)].r ));
droppingWaters[dropletID].Y -= 1;

}
DrawSolidCircle(droppingWaters[dropletID].X,droppingWaters[dropletID].Y ,droppingWaters[dropletID].RadiasSize);
if(droppingWaters[dropletID].NeverMove){
//NeverMoveは一回だけ画面上に描画したらあとは一瞬でハけて消滅させられるというもの
droppingWaters[dropletID].Y -= OutOfScreenY;
}
}else{

//頭の1個以外のスレッドグループはもうさっさと並列に法線マップを作ることに執心してもらう
//今回の上の書き換えが結果に反映されるかどうかなどは全く分からないが、速度重視で、見栄えにはそんなに問題ないのでトレードオフ

//generate normal map
int yPosition = (id.x -  ThreadNumberOneAxis) / outputTextureResolution.x;
int xPosition = (id.x -  ThreadNumberOneAxis) - outputTextureResolution.x * yPosition;
uint2 normalMapID = uint2(xPosition,yPosition);

float upHeight = ResultNormal[normalMapID +  float2(0,-1)].a + ResultNormal[normalMapID +  float2(0,-2)].a +  ResultNormal[normalMapID +  float2(0,-3)].a;
float downHeight = ResultNormal[normalMapID +  float2(0,1)].a +  ResultNormal[normalMapID +  float2(0,2)].a + ResultNormal[normalMapID +  float2(0,3)].a;
float rightHeight = ResultNormal[normalMapID +  float2(1,0)].a + ResultNormal[normalMapID +  float2(2,0)].a + ResultNormal[normalMapID +  float2(3,0)].a;
float leftHeight = ResultNormal[normalMapID +  float2(-1,0)].a + ResultNormal[normalMapID +  float2(-2,0)].a + ResultNormal[normalMapID +  float2(-3,0)].a;

float heightX = 0.25f * (leftHeight - rightHeight);
float heightY = 0.25f * (downHeight - upHeight);

ResultNormal[normalMapID] = float4(heightX,heightY,0.5f,ResultNormal[normalMapID].a - NaturalDropletReductionPerDispatch );

}
}





これによって生成されるResultNormalのR,G成分を利用して画面を歪ませるピクセルシェーダと組み合わせて水滴が実現できます。
.cs側でピクセル数+128を包み込む数のスレッドグループでdispatchしてあげて使う感じです。
(そちらのピクセルシェーダとこれを適切に利用する.csはあとで載せます)


記事中のfrictionTextureに使っているもの(あなたのプロダクトに入れてはいけない 参考に作成してください)
noise.png

Comment

Add your comment

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。