Unity開発にMayaでDirectX11Shaderを使う〜導入編〜

こんにちは。はじめまして。
この記事は [Maya Advent Calender 2018](https://qiita.com/advent-calendar/2018/maya) の2日目の記事です。
AdventCalender初参加です :blush: Qiitaに投稿するのも初です :blush: 

# はじめに
今回の内容は Maya で DirectX11Shader を導入した話です。
DirectX11Shader のテクニック的な高等な物ではありませのでご容赦ください :bow: 
Unity でShaderを書いてて DirectX とかあんまり詳しくないけど Maya 上でも同じ結果を見たいと考えてる人向けのゆる目の記事になります。

# 導入にあたって
さて、いざ Direct11XShader を書こうとしたところで困った点は、あんまり参考記事がネットに転がってなかったことでした。
DirectX をもともと触っている人からしたら何のことは無いのかもしれませんが、木っ端TAの自分はこれが大変苦労しました :innocent: 
最終的に参考にしたのはこの辺り↓です。

- Maya インストールフォルダ内のサンプルファイル
  - `C:\Program Files\Autodesk\Maya20XX\presets\HLSL11\examples`
    - 特に `MayaUberShader.fx` ←これ。怒涛の情報量に目眩がしますが、大体これに答えが載ってます。
- MayaKnowledge の [このへん](https://knowledge.autodesk.com/ja/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2015/JPN/Maya/files/GUID-13229A83-B6A0-4280-840C-F9C6F40BB13D-htm.html)
  - アトリビュートエディタの UI 整理のための情報が乗ってます。
- MSDN の [このへん](https://msdn.microsoft.com/ja-jp/library/ee416106(v=vs.85).aspx) 等
  - 本家 Microsoft のリファレンスです。

# サンプル
テクスチャを貼るだけのミニマム構成のサンプルです。
（※Unity から移植してくることを意識しています。）

```glsl:Sample.fx
// 変換行列。行優先宣言。
row_major float4x4 MATRIX_MVP : WorldViewProjection < string UIWidget = "None"; >;


// テクスチャ
texture gInputTex : InputTexture
<
    string UIName = "Main Texture";
    string UIGroup = "Texture"; // UIをまとめるグループ名
    int UIOrder = 001; // UIの表示順優先度
    int UVEditorOrder = 1;
>;
sampler2D _MainTex = sampler_state
{
    Texture = <gInputTex>;
    AddressU = Wrap;
    AddressV = Wrap;
    AddressW = Wrap;
};
// UVタイル＆オフセットパラメータ
float2 _MainTexTiling
<
    string UIName = " - Tiling";
    string UIGroup = "Texture";
    int UIOrder = 002;
> = {1.0f, 1.0f};
float2 _MainTexOffset
<
    string UIName = " - Offset";
    string UIGroup = "Texture";
    int UIOrder = 003;
> = {0.0f, 0.0f};


// 頂点シェーダ入力構造体
struct appdata
{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};

// 頂点シェーダ出力構造体
struct v2f
{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};


// 頂点シェーダ
v2f vert( appdata v )
{
        v2f o;

        o.vertex = mul( MATRIX_MVP,v.vertex );
        o.uv = float2( v.uv.x, 1.0f - v.uv.y ) * _MainTexTiling + _MainTexOffset;

        return o;
}

// フラグメントシェーダ
float4 frag( v2f i ) : SV_Target
{
    float4 col = tex2D(_MainTex, i.uv);
    return col;
}


// BlendState
BlendState BlendState_AlphaBlending
{
    AlphaToCoverageEnable = FALSE;
    BlendEnable[0] = TRUE;
    SrcBlend = SRC_ALPHA;
    DestBlend = INV_SRC_ALPHA;
    BlendOp = ADD;
    SrcBlendAlpha = ONE;
    DestBlendAlpha = ONE;
    BlendOpAlpha = ADD;
    RenderTargetWriteMask[0] = 0x0F;
};


// DepthStencilState
DepthStencilState DepthStencilState_ZWriteOn
{
    DepthEnable = true;
    DepthWriteMask = 1;
    DepthFunc = LESS_EQUAL;
    StencilEnable = false;
};

DepthStencilState DepthStencilState_ZWriteOff
{
    DepthEnable = true;
    DepthWriteMask = 0;
    DepthFunc = LESS_EQUAL;
    StencilEnable = false;
};


// Technique
technique11 Opaque
<
    bool overridesDrawState = false;
    int isTransparent = 0; // always Opaque
    bool supportsAdvancedTransparency = false; 
>
{
    pass p0
    <
        string drawContext = "colorPass";
    >
    {
        SetDepthStencilState( DepthStencilState_ZWriteOn, 0 );

        SetVertexShader(CompileShader(vs_4_0, vert()));
        SetHullShader(NULL);
        SetDomainShader(NULL);
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, frag()));
    }
}

technique11 AlphaBlend
<
    bool overridesDrawState = false;
    int isTransparent = 1; // always transparent
    string transparencyTest = "true";
    bool supportsAdvancedTransparency = false; 
>
{
    pass p0
    <
        string drawContext = "colorPass";
    >
    {
        SetBlendState( BlendState_AlphaBlending, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF );
        SetDepthStencilState( DepthStencilState_ZWriteOff, 0 );

        SetVertexShader(CompileShader(vs_4_0, vert()));
        SetHullShader(NULL);
        SetDomainShader(NULL);
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, frag()));
    }
}
```

![プレビュー画像](https://qiita-image-store.s3.amazonaws.com/0/308883/db77341a-9163-4d7f-4106-16138d032cfc.png)
本当は最初にコレを作るところから始めるべきだったんですが、 **説明書はわかんなくなってから読むタイプ** なので、欲しいシェーダを作ってから逆算で作りました :innocent: 

# Unity と見た目を合わせるにあたって
「あっれー、Unity と見た目合わないぞー :persevere:」となった点がいくつかあったのでメモを残します。

- テクスチャ座標の基点
  - Unity は左下基点（OpenGLあわせ）、DirectXは左上基点なので、反転させます。
- 行列の向き
  - Unity と同じ順で行列計算式 `mul(Matrix, vector)` を書くために、変換行列に行優先宣言 `row_major` をしておきます。
- _Time実装
  - 上記サンプルには無いですが、UVスクロール等で使う Unity の `_Time` 変数は Maya の再生時間から下のように実装することが出来ます。

```
float _Time_Y : TIME < string UIWidget = "None"; >;
float4 _Time < string UIWidget = "None"; > = {1.0f, 1.0f, 1.0f, 1.0f};

~~中略~~

{
    _Time = float4( _Time_Y/20, _Time_Y, _Time_Y*2, _Time_Y*3 );
}
```

# おまけ ～CgFxShader～
実ははじめのうちは DirectX11Shader ではなく CgFxShader を使っていました。
が、使ってく中でいくつか問題点が確認されたので、 DirectX11Shader に乗り換えをしました。

- 同じシェーダファイルを同一シーン内複数マテリアルに当てると正常に動作しなくなる（ことがある）。
- Position、NormalなどAttributeの設定が保存されないのか、シーンを開き直すと初期化される。
- シェーダファイルを更新してリロードをしてもうまく反映されない（ことがある）。
- **Autodeskが非推奨としている**
  - MayaKnowledge で「 CgFx シェーダは旧式のテクノロジに基づいているため、使用することはお勧めできなくなりました。」とされています。

# おわりに
結構最適ではないところも多いと思いますが、冒頭に書いた通り Maya で DirectX11Shader を導入しようとしてもあまり「こうすればいいよ！」って記事が少なかったので、
（もちろん、 DirextX 自体のサンプルはいっぱいあると思いますが、個人的にちょっと難易度が高い。。。）
これから導入を考えているどこかの誰かの助けになれればこれ幸いです。

# ユニティちゃんライセンス
![ユニティちゃんライセンス](http://unity-chan.com/images/imageLicenseLogo.png)
この記事は[ユニティちゃんライセンス条項](http://unity-chan.com/contents/license_jp/)の元に提供されています。