い、い、、いっくし!!

各記事はkwskってコメントすると詳しく書き直します

Shader Graphを触ってみる

この記事は、ボイゲ Advent Calendar 2019 - Adventar 5日目の記事です。

ひとつ前(4日目): www.nicovideo.jp ひとつ後(6日目): hassakulab.com

まとめ

  • 背景
    • Shaderとか難しい、触ったことない、でも触れたらなんかすごそう
  • 手法
    • UnityでShader Graphというのが実装されたから使ってみる
  • 結果
    • Shader Graph怖くない

Shaderとは

普通のプログラムが使うCPUではなく、GPUで描画させるために必要な処理を記述したもの

Shader Graphって何がいいの

今までのShaderは、専用の(Cライクな)言語を覚える必要があった。 しかもShaderを記述するためには、行列演算を主とした幾何・代数への知識が必要だった。 そのため、実際に映像表現を作る人にとってハードルが高かった。

Shader Graphでは、Shaderのすべてをグラフ記述することが可能になり、学習コストが下がった。

// Shader Graphのスクリーンショット貼る

なので今まで足踏みしてた人も、使ってみようよ!という記事です。

Shader Graphを習う

あくまでも自分の場合だけど、

Youtubeにサンプル解説動画があるので、たくさん見よう ほとんど英語だけど、動画にUnity Editorの操作画面が出てるから、とりあえずトレースする

実は、Shader Graphを触る前に、WebGLを使ったレンダリングフルスクラッチで書いてて、 その時の知見がいくつか有用だったので紹介。

VertexとFragmentシェーダー

WebGLOpenGLDirectXでも)では、GPUでの描画プロセスで、Vertexシェーダーを実行したあとに、Fragmentシェーダーを実行される。 Vertexシェーダーでは、描画される座標が計算されて、Fragmentシェーダーでは、その座標における色が計算される。

下の図では、Scene上にビームサーベルが配置されている。プレビュー画面では、カメラに映ったビームサーベルが表示される。 何気なく受け入れてしまうが、ビームサーベル(のメッシュ)の座標(x,y,z)を画面座標(X,Y)に変換しなければならない。 これがVertexシェーダーの役割だ。

f:id:ixsiid:20191204223719p:plain
シェーダーの役割

普通に、Unityを触ってる限り、Vertexシェーダーはテンプレからいじる必要はない。 画面上にうまく表示されるようにカメラやオブジェクトの位置を調整するからで、 Vertexシェーダーを変にいじると、表示するオブジェクトが歪んだり、ぶっ飛んで行ったり、形が崩れたりする。

Fragmentシェーダーについてはいったん置いといて、Shader Graphでは、Vertex, Fragmentシェーダーを同一のグラフ上で記述する。 Unlit Shader Graphを作成すると、グラフ上にUnlit Masterというノードが追加される。 このMasterノードが最終的な描画内容を受けるアウトプットに相当する。

f:id:ixsiid:20191203233901p:plain
Unlit Master

Vertexシェーダーの役割は、このMasterのPositionに相当する。 普通はいじる必要がないから、デフォルト値である「Object Space」を接続しておけばいい。

逆に、Vertexシェーダーを使う場合を考えてみる。

f:id:ixsiid:20191206225126p:plain
座標変換

左のPositionノードは各頂点の座標(Transformで指定するやつ)、これが実描画の時に通常は、Object→Viewへと座標変換される。 ここでTransformノードで、View→Objectと逆変換を先に入れておくと、座標変換がキャンセルされ、常に同じ座標に表示されることになる。1

f:id:ixsiid:20191206225437p:plain
キューブのレンダリング位置(赤い四角)が、見る視点によらず、Transform座標上に表示される

更に、timeノードを使うと、時間経過に応じた動きを追加することもできる。 試しに回転行列を作り、先ほどのPositionに掛けてみる。

f:id:ixsiid:20191206230218p:plain
回転行列を掛ける

ゲーム画面上でも、レンダリングされるものが回転しだす。

f:id:ixsiid:20191206230325p:plain
回転するキューブ

シェーダーでできることは、基本的にスクリプトでも実現可能だ。 回転するキューブであれば、FixedUpdate内でTransformにAddRotationし続ければいい。 シェーダーで実行することができれば、その独立度合が高くなる。 各スクリプトのタイミング問題や、処理速度の問題に頭を悩ませなくていい。

// 閉会式動画が作られるらしいので、やっつけでもうちょっと見た目のインパクトがあるものを作ってみる

f:id:ixsiid:20191211212125p:plain
のこぎり波倍するシェーダー

上の図はのこぎり波をフーリエ変換したものを8次までシェーダー内で再現した。

f:id:ixsiid:20191211212333p:plain
のこぎり波

のこぎり波のように、びょーん、びょーん、びょーんと拡大するシェーダーになる。ほんとはビートを刻むようなエフェクトっぽくしたかったのだが諦めた。2

f:id:ixsiid:20191211213135g:plain
拡縮するキューブ

Fragmentシェーダーは、Shader GraphではColorとAlphaでMasterに接続される。 簡単な例として、先ほどのキューブを、アルファグラデーションをかけてみる。

f:id:ixsiid:20191206231511p:plain
アルファグラデーション

UVノードは、メッシュ内での(x,y)座標位置、Splitノードによりメッシュ状のX座標だけを抽出している。3 ゲーム画面上では、X座標に応じて透明になるレンダリングがされる。

f:id:ixsiid:20191206231947p:plain
グラデーションのかかったキューブ

先ほどのTimeノード、それにランダム性を加えるNoiseノードを組み合わせれば、ちょっとした登場エフェクトなども作れる。

f:id:ixsiid:20191206232542p:plain
透明度をランダムに変化させる

f:id:ixsiid:20191206233246g:plain
ゲーム上でのエフェクト

ここでは、単色ばかりの簡単な例を出したが、特に2Dゲームであればテクスチャの扱い方がわかれば、応用はさせやすい。

冒頭でもいったように実用的なものは、Youtubeなどを参照してほしい。 Shader Graphの場合、ある程度のノードの塊でどんな処理か意識できるようになるのがポイントだと思う。

ゲームジャムでのシェーダーづくり

ゲームジャムをするときに、シェーダーまで手を出せないよ!という人も多いと思う。 ただ、ちょっとした座標の動き、表現の動きをシェーダーで作れると「やり方」に幅がうまれる。

例えば、最初の企画が「ここで、うわー!ってやるとババーンとなって!ドかーってなるかんじ!」って時に 当たり判定をいれて、ダメージ計算をして、破壊判定をさせるといった処理系は手を出しやすい。

それを実装する人をしり目に「じゃあ、おれそのババーンがドかーんとなるシェーダー作ります。」とか言えるとかっこいい。かっこよくない?

スクリプト+パーティクルシステムで実装すると、インターフェースを相談したり、パラメータ例を伝えたり、 結合時にタイミングを見ながら、そのパラメータを調整したりとなるが、シェーダーであれば、マテリアルまで用意しておいて入れ替えるだけで済む。

システム処理系と描画処理系の独立性が高い分、結合が簡単になるのである。

// なんか面白いオチ


  1. 簡略化のためにカメラはOrthographics(パースが効いていないカメラ)を前提にしている

  2. ほんとにのこぎり波倍で表示したいだけなら、先に倍率係数をバッファしてTransformで計算するほうがはやい気がする(未確認)

  3. Splitノードの出力がRGBAとなっているが、座標ベクトルであればXYZWにそのまま対応する