Geometry Shaderのサンプルを解読する（初級編）

## はじめに
- Unityでかっこいいエフェクトを作りたい！！
- [keijiroさん](https://github.com/keijiro)のエフェクトがめっちゃカッコ良いから参考にしたい！！
- でもテクニックが上級すぎてソース見ても全然わからない！！！！（ごめんなさい）

という経験、Unity勢のみなさんなら一度は通ったことがあるのではないでしょうか…？（暴論）

この記事では、（主に自分が）そんな状態を脱するべく
エフェクトの中でよく使われている **Geometry Shader** というシェーダーを理解して、
カッコいいエフェクトを作れるようになるための一歩を踏み出します！

※ 備忘録的に書いているので、説明を一部省略してしまっているところがあります。ご了承ください。

## Geometry Shader とは
- 簡単に言うと、プリミティブ(メッシュを構成する基本形状)の増減や変換ができるシェーダーです。
- Vertex Shader の後、Fragment Shader の前に実行されます。
- **メッシュの頂点数をいじれたり、頂点を増やしてポリゴンにしたり、増やした頂点の位置や回転などを制御することができる**ため、エフェクトの幅をかなり広げることができます。

以下の記事や本の説明がとてもわかりやすいので、ぜひご一読ください。

- [ジオメトリシェーダ入門](http://edom18.hateblo.jp/entry/2018/07/11/140455)
- [Unity Graphics Programming vol.1 - 第6章 ジオメトリシェーダーで草を生やす](https://indievisuallab.stores.jp/items/59edf11ac8f22c0152002588)

## サンプル
- 今回はサンプルとして、[keijiroさん](https://github.com/keijiro)の [StandardGeometryShader](https://github.com/keijiro/StandardGeometryShader) を拝借させていただきます。
![img](https://camo.githubusercontent.com/d0b0313beca46cb444a027a8ec7f0e6aacf7cddf/68747470733a2f2f692e696d6775722e636f6d2f684774586b41372e676966)
すでにもうなんかカッコいいですよね。

- この時点でも結構シンプルなものになっていますが、シェーダーの中で [Deferred Rendering](https://docs.unity3d.com/jp/460/Manual/RenderTech-DeferredLighting.html) を行なっているので、そこも削った最小限のサンプルへと改変します。
    - ※ Deferred Rendering の説明はここでは割愛しますが、以下の記事がわかりやすいです。
        - [【Unity】ForwardレンダリングとDeferredレンダリングの違いを軽くまとめてみた](https://qiita.com/r-ngtm/items/a78ca2f72a36e2005da3)
        - [oFでDeferred Renderingしてみよう](https://qiita.com/y_UM4/items/7647fd9fc19e60ec5822)

## コード
改変したコードを以下にアップしています。
https://github.com/genkitoyama/StandardGeometryShader

## 解説
上記のサンプルにおいて、特にGeomrtry Shaderでは大きく3つのことが行われています。

**1. 球を構成している三角形の頂点を取得する**
**2. 取得した三角形の頂点を法線方向に押し出す**
**3. 押し出した上面と側面のプリミティブを出力する**

これらについて、他のシェーダーも合わせて、解説をしていきます。

--

### 事前準備など
まず事前に、各シェーダに渡す構造体や、CPU側から渡すプロパティなどを設定します。

#### プロパティ
エフェクトをアニメーションさせるための変数を設定します。
（これをスクリプトやタイムラインからシェーダーに送る）

```
_LocalTime("Animation Time", Float) = 0.0
```

#### 構造体 (Struct)

- Vertex Shader → Geometry Shader に渡すための構造体 `Attributes`
- Geometry Shader → Fragment Shader に渡すための構造体 `Varyings`

の2種類の構造体を用意します。（今回はどちらも`position`と`normal`のみ）

```glsl

struct Attributes
{
    float4 position : POSITION;
    float3 normal : NORMAL;
};
```

```glsl

struct Varyings
{
    float4 position : SV_POSITION;
    float3 normal : NORMAL;
};
```

### Vertex Shader
Vertex Shaderでは、設定した構造体の各パラメータに関して、
Unityのオブジェクト座標からワールド座標系への変換だけを行います。

```glsl

Attributes Vertex(Attributes input)
{
    // Only do object space to world space transform.
    input.position = mul(unity_ObjectToWorld, input.position);
    input.normal = UnityObjectToWorldNormal(input.normal);
    return input;
}
```

### Geometry Shader

Geometry Shaderでは、最初に**「入出力するプリミティブの型」**と、**「最大で出力する頂点の数」**を設定する必要があります。

今回は、**三角形を押し出して三角柱（ただし底面なし）をつくりたい**ので、

- 入力のプリミティブの型は `triangle` で 長さ3の配列
- 出力のプリミティブの型は `TriangleStream`
- 最大で出力する頂点の数 `maxvertexcount` は `3 * 1(上面) + 4 * 3(側面) = 15`

とします。

また、今回は**プリミティブごとにそれぞれ異なった押し出し量にしたい**ので、
引数に各プリミティブのID `uint pid : SV_PrimitiveID` も入れています。

```glsl

[maxvertexcount(15)]
void Geometry(triangle Attributes input[3], uint pid : SV_PrimitiveID, inout TriangleStream<Varyings> outStream)
{
    ...
}
```

#### 1. 球を構成している三角形の頂点を取得する
三角形で構成されている球(Icosphere)に対して、それぞれの三角形を取得します。
**Geometry Shaderはプリミティブ（今回でいうと三角形）ごとに実行される**ので、
引数で設定した配列を順番に設定していけばOKです。

```glsl

float3 wp0 = input[0].position.xyz;
float3 wp1 = input[1].position.xyz;
float3 wp2 = input[2].position.xyz;
```

このように、Vertex Shaderではできなかった**隣接した頂点を参照できる**のがGeometry Shaderの強みだったりします。

#### 2.取得した三角形の頂点を法線方向に押し出す
まず、押し出す量を設定します。
`saturate()` は、引数を[0,1]でクランプして出力してくれる便利関数です。
（0未満の場合は0に、1より大きい時は1に、その間ならそのまま）
※ マジックナンバーに関しては、実際にいじりながら動きを見てみて、気持ち良いところになればOKだと思います。

```glsl

float ext = saturate(0.4 - cos(_LocalTime * UNITY_PI * 2) * 0.41);
ext *= 1 + 0.3 * sin(pid * 832.37843 + _LocalTime * 88.76);
```

上記で設定した押し出し量と、 `ConstructNormal()` という関数で求めた法線ベクトルとを
掛け合わせたものを各頂点に加算することで、押し出し後の頂点を設定していきます。

```glsl

float3 offs = ConstructNormal(wp0, wp1, wp2) * ext;
float3 wp3 = wp0 + offs;
float3 wp4 = wp1 + offs;
float3 wp5 = wp2 + offs;
```

#### 3. 押し出した上面と側面のプリミティブを出力する

Geometry Shaderでは、Vertex Shaderで設定した頂点も改めて全て出力し直す必要があります。
そのため、押し出した上面と、押し出したことで出現した側面x3の頂点情報を出力していきます。

出力の仕方はメッシュを作るときと似ていて、 

- `outStream.Append()` で現在のストリームに頂点情報を出力
- `outStream.RestartStrip()` で現在のストリームを終了

を繰り返して出力していきます。

`VertexOutput()` は、Geometry Shaderの出力用の構造体を用意するための関数です。
具体的には、`position`は Fragment Shaderに渡すためにクリッピング座標系に変換し、
`normal`はそのまま出力しています。

```glsl

// Cap triangle
float3 wn = ConstructNormal(wp3, wp4, wp5);
float np = saturate(ext * 10);
float3 wn0 = lerp(input[0].normal, wn, np);
float3 wn1 = lerp(input[1].normal, wn, np);
float3 wn2 = lerp(input[2].normal, wn, np);
outStream.Append(VertexOutput(wp3, wn0));
outStream.Append(VertexOutput(wp4, wn1));
outStream.Append(VertexOutput(wp5, wn2));
outStream.RestartStrip();
```

```glsl

// Side faces
wn = ConstructNormal(wp3, wp0, wp4);
outStream.Append(VertexOutput(wp3, wn));
outStream.Append(VertexOutput(wp0, wn));
outStream.Append(VertexOutput(wp4, wn));
outStream.Append(VertexOutput(wp1, wn));
outStream.RestartStrip();
```

### Fragment Shader
最後にFragment Shaderで色をつけていきます。

本来はここでDeferred Renderingの処理を書いて質感を高めていますが、
ここでは簡単のためにnormalの値だけRGBに乗算するようにします。
（一色だと動いているかがわかりにくいので）

```glsl

float4 Fragment(Varyings input) : COLOR
{
    float4 col = _Color;
    col.rgb *= input.normal;
    return col;
}
```

## 実行結果
実行すると、こんな感じになります。
タイムラインから送られている `_Localtime` の値に合わせて、
表面の三角形がボコボコ動くことが確認できます。

![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109722/5567a560-402c-0503-7939-00e0b7cf12d5.gif)

（最初の状態と色味以外何も変わっていないように見えますが、Geometry Shaderでこのようなエフェクトが作れることがわかったかなと思います…！）



## おわりに
- Geometry Shaderの簡単なサンプルを解読してみました。
    - （初級編と言いつつボリュームが多くてすみません…）
    - （そして勝手に解説してしまってkeijiroさんすみません…）
- 次回は、中級編としてもっと複雑なGeometry Shaderのエフェクトを解説する予定です！

## 参考にしたリンク集
- [ジオメトリシェーダ入門](http://edom18.hateblo.jp/entry/2018/07/11/140455)
- [【Unity】Geometry Shader（ジオメトリシェーダー）の超入門サンプル【初心者向け】](https://gurutaka-log.com/geometry-shader-intro)
- [Geometry Shaderの書き方](https://www.techry.net/blogs/1791)
- [Unity Graphics Programming vol.1](https://indievisuallab.stores.jp/items/59edf11ac8f22c0152002588)
- [Unityシェーダープログラミングの教科書 ShaderLab言語解説編](https://booth.pm/ja/items/660001)
- [Unity Shader Programming Vol.01 (v.2.0.0)](https://booth.pm/ja/items/931290)
- [StandardGeometryShader](https://github.com/keijiro/StandardGeometryShader)