Unity
Shader
HLSL

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

はじめに

Unityにて、ポリゴンの頂点の座標値を直交座標系から円柱座標系(円筒座標系)に変換する必要があったので、そのためのShaderファイルを作成しました。なぜShaderなのかというと、リアルタイムで描画したかったからです。

円柱座標系に変換したものを描画してみるとこんな感じになりました。(正確には直交座標系から円柱座標系へ変換した座標値を直交座標系に射影したものを描画しています。)
cylindrical.gif

上にあるのが本来のメッシュ形状で、下にあるのがそれを円柱座標系に変換したものです。Shader以外はすべて同一データを与えています。

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

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

円柱の方向について

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

円柱座標系の座標値について

今回の座標値は以下の3つを想定しています。
- r: 円柱の中心軸からの半径方向。
- θ: 周方向。単位はラジアン[rad]
- y: 円柱の軸方向。

サンプルコード

以下が作成したShaderファイルです。

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は円柱座標系を直交座標系に変換する機能を有効にするかどうかのチェックボックスです。

直交座標系から円柱座標系へ変換

数式は以下のようになります。

\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としています。このような座標を特異点と呼びます。

円柱座標系から直交座標系へ変換

数式は以下のようになります。

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

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

頂点シェーダー

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

フラグメントシェーダー

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

おわりに

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