Notch で Custom Shader Post Effect を使ってみる

「Notch #madewithnotch Advent Calendar 2019」 23日目の記事です。

あまり語られない、Notch でのカスタムシェーダーの活用。（豊富なノードが用意されているので、そもそも使う場面が無いのかもしれないですが…）

そこでこの投稿では、Notch X シェーダーにどんなものが用意されているかについて整理しつつ、その中で「__Custom Shader Post Effect__」の使い方をまとめていきたいと思います。加えて、PostFx以外の活用方法も紹介できればと。


# Notch のシェーダーは「HLSL」
High Level Shading Language の略で、DirectX で使われるシェーダー言語。

ちなみにゲームエンジン Unity のShaderLabの中で利用する「Cg/HLSL」は、Cgという別のシェーダー言語と共に、HLSLを元にしているので、Notchへの移植がしやすいかも。

ただ、GLSLを触ったことがある人なら、型や関数名が違うくらいの差のイメージで触れるので、kitasenjudesign さんの「[GLSLをHLSLに書き換える](https://qiita.com/kitasenjudesign/items/89297f239059662cd38e)」などを見ながら触れば簡単に移植できると思います。



# Notch で使える HLSL系機能 とは？
Notch でHLSLを使える場面は、大きく分けると以下の3つ。

### 1. 「__Custom Shader Post Effect__」 ノード　（PostFx - Image Processing）
自身で PostFx をHLSLで実装できるノード（[マニュアル](http://manual.notch.one/0.9.23/en/topic/nodes-post-fx-image-processing-custom-shader-post-effect?q=HLSL)）




### 2. Proceduralの Signed Distance Fields
Procedural - Generatorsにある「__Editable Code__」 ノード（[マニュアル](http://manual.notch.one/0.9.23/en/topic/nodes-procedural-generators-editable-code?q=HLSL)）や、ProceduralsのGenerators系やCloning系ノードにある「__Custom CSG Code__」パラメーターでは、Signed Distance Fields（ある座標から輪郭までの最短の距離の関数）で、ブール演算のコントロールや形状の変形をコントロールできるようです。

マニュアルの中でも「[Techniques](http://manual.notch.one/0.9.23/en/topic/techniques-hlsl-scripting?q=HLSL)」にページを割いて説明されています。
（まだ深掘りできていないので、使っている方がいたらぜひシェアしてほしいです）
 
  

__今回は Custom Shader Post Effect にフォーカス。簡単に活用する上での情報を整理していこうと思います。__


# Custom Shader Post Effect の基本
Custom Shader Post Effect の[マニュアル](http://manual.notch.one/0.9.23/en/topic/nodes-post-fx-image-processing-custom-shader-post-effect?q=HLSL) にはサンプルコードが書いてるので、それをベースに手を加えていくのが最初は簡単かと思います。ということで、サンプルコードを見ていきましょう。

### マニュアルのサンプルコードをPostFxに使ってみる
最初に活用するレベルで知っておきたいコメントを追記していきます。

```glsl


Texture2D <float4> InputBuffer : INPUTBUFFER; // 親ノードになる Video Processing が入ってくるイメージ

float CurrentTime : CURRENTTIME; // 現在時刻をNotchが入れてくれる。
float BlendAmount : BLENDAMOUNT; // デフォルトattribute。パラメータに標準表示され、ブレンド量をコントロールする。

float MyCustomParameter; // カスタムのパラメータを追加できる。ここではMyCustomParameterという名前のパラメータを追加。

// ↓ Vertex Shader周りは一旦このままでOK ↓
sampler LinearClampSampler
{
    Filter = Min_Mag_Linear_Mip_Point;
    AddressU = Clamp;
    AddressV = Clamp;
    AddressW = Clamp;
};

struct VS_OUTPUT // Vertexシェーダーから渡ってくる情報
{
    float4 Position  : SV_POSITION;
	float2 Uv : TEXCOORD0;
};

VS_OUTPUT VS_Fullscreen(float4 Position : POSITION) // Vertexシェーダー
{
 	VS_OUTPUT Out = (VS_OUTPUT)0;
 	Out.Position = float4(Position.xy,0,1);
	Out.Uv = Position.xy * 0.5f + 0.5f;

	return Out;    
}
// ↑ Vertex Shader周りは一旦このままでOK ↑

//  -------- エフェクト部分 -----------
float4 PS_ApplyPostProcess(VS_OUTPUT In) : SV_TARGET0
{
	// 処理対象のピクセルのカラー情報を取得
	float4 sourceValue = InputBuffer.Load(uint3(In.Position.xy,0));

	// エフェクト処理を行う
	
	float4 rslt = float4(sourceValue.xyz * abs(sin((In.Uv.y+CurrentTime*0.2f)*30.0f * (1.0f+MyCustomParameter))),sourceValue.w);

	// lerp関数で、BlendAmountの量に応じてsourceValue（元映像）とrslt（エフェクト後）の比率を変える
	rslt = lerp(sourceValue, rslt, BlendAmount); 

	return rslt;
}
//  -------- エフェクト部分 -----------


BlendState NoBlend {
  AlphaToCoverageEnable = FALSE;
  BlendEnable[0] = FALSE;
  BlendEnable[1] = FALSE;
  BlendEnable[2] = FALSE;
  BlendEnable[3] = FALSE;
};
DepthStencilState NoDepthState {
  DepthEnable = FALSE;
  DepthWriteMask = All;
  DepthFunc = Less;
  StencilEnable = FALSE; 
};
RasterizerState DefaultRasterState 
{
	CullMode = None;
	FillMode = Solid;
	DepthBias = 0;
	ScissorEnable = false;
};

// ここでVertexシェーダー、Pixelシェーダーとなる関数を指定、マルチパスも組める
technique11 ApplyPostProcess
{
	pass p0
	{
		SetVertexShader( CompileShader( vs_4_0, VS_Fullscreen() ) );
		SetPixelShader( CompileShader( ps_4_0, PS_ApplyPostProcess() ) );

		SetBlendState( NoBlend, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
		SetDepthStencilState( NoDepthState, 0);
		SetRasterizerState( DefaultRasterState );
	}
}
```


### デフォルトのセマンティクスは2つ、オリジナルのfloatパラメータも追加可能

| Name | Description |
|:--|:--|
| CURRENTTIME | ノードの現在時刻 |
| BLENDAMOUNT | エフェクトのブレンド量をコントロールすることを想定された |

グローバルなfloat型の変数は、ノードのattributeプロパティに自動で追加してくれる。それらをModifierで動かすと、アニメーション化できます。
（サンプルの例では「MyCustomParameter」というパラメータが作られており、エフェクト処理のスピードコントロールに使用）


### 使い方は他の PostFx ノードと同じ
書いたシェーダーは「*.fx」という拡張子で保存します。
あとはノードグラフ上にドラッグ&ドロップすると、「Custom Shader Post Effect」として配置されるので、他のPostFxのようにVideoProcessing系のノードにぶら下げると、エフェクトが適用されます。
![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115077/c424a93d-0eb2-669c-96f9-d4f78af2e10e.gif)



# 応用的（？）な活用法
このようにPostFxが作れるのでスクリーン全体にかける以外にも、色々と使える幅はありそう、ということで活用方法を考えてみました。

ちなみにPostFxなので、ライブカメラの映像にエフェクトをかけたものを以下のようにするも良いですが、エフェクトでは無い「シェーダーアート」系のものであれば、「Flat Colour」ノードで作った全白にエフェクトをかけてあげると良いと思います。

※ 下の例もDistance Fieldのシェーダーを全白へのPostFxとしてかけています。


## 1. MaterialのColorに使ってみる
Materialノードの「Colour Texture」インプットにはVideo Processingノードが挿せるようになっている。そこに、全白にエフェクトをかけたものを挿してみると結構面白いかも。
![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115077/75b913eb-f5bc-3bed-79df-a0d6d92b9de2.gif)



## 2. プロジェクション／ゴボ的なLight
Lightノードの「Projection Image」インプットにVideo Processingを挿すと、プロジェクション／ゴボ（ライトの前に模様のついたガラスなどを差し込んで、形のついた光と投影するもの）的な表現ができます。
![3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115077/e07041f9-e052-3383-f4f5-d87f57d48afb.gif)


# さいごに
これからシェーダーを触ってみよう／手軽にPostFxで何か書いてみたいという方は、Unityで板ポリにシェーダーで絵を書く、という setchi氏さんの「[楽しい！Unityシェーダー お絵描き入門！](http://tsubakit1.hateblo.jp/entry/2018/04/12/000956)」のスライドを激推しします。UnityなのでほぼHLSLとして書けるので移植も楽です。

注意点としては、やはり自分で実装する分、安定性も自分次第になる点。まずはノードで組めないか考えた上で、それでも難しい／過去のGLSL資産を使いたい場合には良いんじゃないかと思います。