Unityで直交座標系のポリゴンを円柱座標系にリアルタイム変換してみた

![cylindrical.gif](https://qiita-image-store.s3.amazonaws.com/0/221845/55d17034-fe22-bc0f-9cd7-350f73671cb3.gif)

# はじめに
Unityで、ポリゴンの頂点の座標値を、直交座標系から円柱座標系（円筒座標系）へリアルタイムに変換する必要があったので、そのためのShaderを作りました。

サンプルのUnityプロジェクトはこちらです。
https://github.com/segurvita/UnityCylindricalCoordinate


# できること
上の動画が実際の描画結果です。こんな感じのことができます。

上に浮いているのが本来のメッシュ形状で、下の地面に転がっているのがそれを円柱座標系に変換したものです。（正確には直交座標系から円柱座標系へ変換した座標値を直交座標系に射影したものを描画しています。）

Shader以外はすべて同一データを与えています。

この動画では物体が静止しているので伝わりずらいですが、リアルタイムで座標変換しているので、アニメーション等で元のMeshが変形しても、ちゃんと反映されます。

ついでに、円柱座標系から直交座標系へ逆変換する処理も書きました。


# 円柱の方向について
Unityの場合、 `z` ではなく `y` が高さを表します。そのため、今回はy軸を中心軸とした円柱座標系を想定します。一般的には、z軸を中心軸とするため、数式が他のサイトと異なるかもしれません。何卒ご了承ください。


# 円柱座標系の座標値について
今回の座標値は以下の３つを想定しています。

- `r` : 円柱の中心軸からの半径方向。
- `θ` : 周方向。単位はラジアン[rad]
- `y` : 円柱の軸方向。

# サンプルコード
以下が作成したShaderファイルです。

```c++
Shader "Custom/CylindricalCoordinate"
{
    Properties {
        _MainTex ("Main Tex", 2D) = "white" {}
        [MaterialToggle] _canR2C ("Enable Rectangular To Cylindrical", Float) = 1 // 0 is false, 1 is true
        [MaterialToggle] _canC2R ("Enable Cylindrical To Rectangular", Float) = 1 // 0 is false, 1 is true
    }
    SubShader {
        Tags {
            "Queue"="Geometry"
        }
        Pass {
            CGPROGRAM
            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                half2 uv   : TEXCOORD0;
            };

            struct v2f {
                float4 vertex : SV_POSITION;
                half2 uv   : TEXCOORD0;
            };

            //直交座標系を円柱座標系に変換する関数
            float4 r2c (float4 rec){
                float4 cyl;

                //円柱座標系のrを計算
                cyl.x = sqrt(rec.x * rec.x + rec.z * rec.z);

                //円柱座標系のθを計算
                if(rec.x==0.0 && rec.z==0.0){
                    //特異点ではθは定まらないが、仮にθ＝0とする
                    cyl.z = 0.0;
                }else{
                    cyl.z = atan2(rec.z, rec.x);        
                }

                //そのまま
                cyl.y = rec.y;      
                cyl.w = rec.w;

                return cyl;
            }

            //円柱座標系を直交座標系に変換する関数
            float4 c2r (float4 cyl){
                float4 rec;

                rec.x = cyl.x * cos(cyl.z);
                rec.y = cyl.y;
                rec.z = cyl.x * sin(cyl.z);
                rec.w = cyl.w;

                return rec;
            }

            uniform sampler2D _MainTex;
            float _canR2C;
            float _canC2R;

            #pragma vertex vert
            #pragma fragment frag

            v2f vert (appdata v) {
                v2f o;

                //直交座標系を円柱座標系に変換
                if(_canR2C){
                    v.vertex = r2c(v.vertex);
                }

                //円柱座標系を直交座標系に変換
                if(_canC2R){
                    v.vertex = c2r(v.vertex);
                }

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i): SV_Target {
                return tex2D(_MainTex, i.uv);
            }

            ENDCG
        }
    }
}
```

# 使い方
Shaderファイルを新規作成し、上記のサンプルコードをコピペします。
Materialファイルを新規作成し、Shader欄のドロップダウンリストから、 `Custom/CylindricalCoordinate` を選択します。
ObjectのMesh RendererのMaterialsに上記のMaterialファイルを設定します。

`Enable Rectangular To Cylindrical` にチェックを入れると、直交座標系を円柱座標系に変換します。
`Enable Cylindrical To Rectangular` にチェックを入れると、円柱座標系を直交座標系に変換します。

両方をONにした場合、直交座標系を円柱座標系に変換したものを直交座標へ戻すので、描画上の見た目は変化しないと思います。

# Propertiesについて
Propertiesに記述したパラメータは、UnityのEditorから制御できるようになります。
`_MainTex` ではテクスチャ画像を指定できます。
`_canR2C` は直交座標系を円柱座標系に変換する機能を有効にするかどうかのチェックボックスです。
`_canC2R` は円柱座標系を直交座標系に変換する機能を有効にするかどうかのチェックボックスです。

# 直交座標系から円柱座標系へ変換
数式は以下のようになります。

```math
\begin{align}
r&=&\sqrt{x^2 + z^2}\\
y&=&y\\
θ&=&\arctan(z/x)
\end{align}
```
`arctan` はアークタンジェントです。符号も含める必要があるため、実際のShaderでは `atan2` を利用します。

Shaderでは `r2c` という関数に書いています。 `rec`が直交座標系の座標値、 `cyl` が円柱座標系の座標値です。コーディングの便宜上、 `cyl.x` を `r` 、 `cyl.z` を `θ` としています。

また、 `x=0` かつ `z=0` のとき、 `θ` は定まらないため、仮に `θ=0` としています。このような座標を特異点と呼びます。

# 円柱座標系から直交座標系へ変換
数式は以下のようになります。

```math
\begin{align}
x&=&r\cos{θ}\\
y&=&y\\
z&=&r\sin{θ}
\end{align}
```

Shaderでは `c2r` という関数に書いています。

# 頂点シェーダー
`vert` という関数が頂点シェーダーです。バーテックスシェーダーとも呼ばれます。頂点シェーダーでは、ポリゴンの頂点の座標値を変更することができます。ここで、先ほどの `r2c` と `c2r` を呼んでいます。チェックボックスの入力に応じて、どちらの関数を実行するか変更ができるようになっています。

# フラグメントシェーダー
`frag` という関数がフラグメントシェーダーです。ピクセルシェーダーとも呼ばれます。今回の座標変換には関係ありませんが、描画には最低限必要な関数なので、記載します。入力されたテクスチャをそのままのUV座標で描画します。

# おわりに
Windows, Mac, iOSにてリアルタイムに動作することを確認しました。今回、初めてシェーダーに触れたのですが、計算の速さに驚きました。今後は色々と作ってみようと思います。