Unityの2DRendererにおけるOpaqueTextureで剣閃エフェクト（2DRenderer中編）

#　もくじ

2DRenderer前編

https://qiita.com/dieblondebestie/items/4f1bb99b49775ea10fb3

2DRenderer後編

https://qiita.com/dieblondebestie/items/57b6e618490ba7b21c0c

# 概要
ゲームのよくある演出として剣閃の部分をゆがませる表現があります。
UnityURPの3DではOpaqueTextureというRenderEventでAfterRenderingOpaquesくらいまで描画したRenderTextureを別のRenderTextureにBlitしといて後のTransparentの描画時やCustomPostProcessなどで使用できるものです。

2DRendererでも似たような機能としてCameraSortingLayerTextureがあり、それを利用して剣閃エフェクトを作成した事例を説明します。

# 環境
- Unity 6000.0.23f1

# 実装
### CameraSortingLayerTexture

2DRendererは3DRendererと違ってOpaqueObject（不透明オブジェクト）とTransparentObject（透過オブジェクト）と分かれておらず、すべてTransparentObjectの扱いなのでCameraSortingLayerTextureの生成タイミングをSortingLayerで指定してあげる必要があります。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054619/d41c2eda-4f36-4fba-b6a2-9458992544fb.png)

3Dのレンダリング順（色々省略版）
```plantuml
start
:カメラ更新;
:SkyBox描画;
:OpaqueObject描画;
#AADDAA:OpaqueTexture生成;
:TransparentObject描画;
:PostProcess適用;
stop
```
2Dのレンダリング順（色々省略版）
```plantuml
start
:カメラ更新;
:LightTexture描画;
:Object描画（CameraSortingLayerTextureの生成タイミングに設定したSortingLayerまで）;
#AADDAA:CameraSortingLayerTexture生成;
:Object描画（CameraSortingLayerTextureの生成タイミングに設定したSortingLayerから）;
:PostProcess適用;
stop
```

OpaqueTextureがOpaqueObject描画時に使えないのと同様にCameraSortingLayerTextureは生成タイミング以前のSortingLayerの描画時には使えません。

CameraSortingLayerTextureの使い方はOpaqueTexture同じで2DTextureのプロパティ名を`_CameraSortingLayerTexture`にすることで受け取れます。

### 剣閃エフェクト

剣閃のMeshはVisualEffectGraphのParticleStripで作成しており、刃の刃先と根本のPositionを常に渡し刃の長さ、角度、向きを計算してます。後はParticleStripは何もしないとParticleが常に繋がってしまうので、過去の剣閃は消したかったので、剣閃ごとにStripIdを変えて過去IDだったらParticleを削除するようにしてます。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054619/e63a2f8a-0903-4525-8bc5-1babe5ed7e23.png)

後はShaderでCameraSortingLayerTextureをゆがませるだけです。基本的にはノイズテクスチャをUVスクロールさせて、`NormalFromHeight`のノードで剣閃のMeshのUVをゆがませScreenPotisionからスクリーンのUVに加算、それでCameraSortingLayerTextureをサンプルすることで背景をゆがませた表現を描画します。自分は3DTextureを3軸にUVスクロールできるSubGraphを作成してより自然なノイズのアニメーションを実現、Normalへの変換も備え付けのノードは荒くなってしまうのでどこかで拝見した式を3DTexture用に改良してCustomFunctionで実装しました。

ちゃんと変換されてますが、正しい計算なのかはちょっと自信ないです。
```hlsl
void HeightToNormal3D_float(UnityTexture3D heightMap, UnitySamplerState samplesState,  float3 uv, float3 heightMapResolution, float normalStrength, out float4 OutColor)
{
    OutColor = 0;

    float3 s = 1/heightMapResolution;
    
    float p = SAMPLE_TEXTURE3D(heightMap, samplesState, uv).x;
    float h1 = SAMPLE_TEXTURE3D(heightMap, samplesState, uv + s * float3(1, 0, 0)).x;
    float v1 = SAMPLE_TEXTURE3D(heightMap, samplesState, uv + s * float3(0, 1, 0)).x;
       
    float2 normal = (p - float2(h1, v1));
    
    normal *= normalStrength;
    
    OutColor = float4(normal, 1, 1);
    
}
```

そのほか刃に近いところほど歪みを強くしたり発光するエフェクトを追加してたりしてます。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054619/5ec2c3f9-6a3f-4746-8b15-f99c859aee9f.png)

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054619/53fdfc09-1017-4325-88ac-860d22c50ef9.png)

# まとめ

CameraSortingLayerTextureですがRenderLoopの中で1回しか生成できないのでそれが不便です。透過表現をしたいとき必ず必要になる機能なので複数回生成できるようにしてくれてもいいような気がします。しかも2DRendererは2DLightなどのクラスでinternalでアクセスできないものが多数ありRendererFeatureなどで自前でCameraSortingLayerTextureのようなテクスチャを用意することもできません。