購読中です 読者をやめる 読者になる 読者になる

凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

ワイヤレスなブロック同士を簡単に連携できる MESH を手に入れたので現状で出来ることをまとめてみた

はじめに

MESHSONY によるプロジェクトで、タグと呼ばれるワイヤレスのブロックに備わった機能を Canvas というタブレット上のアプリでつなぎ合わせるだけで、タグ同士が連携する仕組みを簡単に作ることが出来るプラットフォームです。

今年はじめに Indiegogo 上で資金調達が開始され、目標の $50,000 に対し、$64,875 の出資が集まりました。私は $149 の Advanced Set(Early Bird)に Back していたので、GPIO タグを含む 4 つのタグが届きました。

f:id:hecomi:20150516125042j:plain

似たコンセプトのプラットフォームとして littleBits がありますが、littleBits はマグネットで接続することに対して、MESH はワイヤレスであるため、それぞれ異なる使い方が出来ると思います(両者を組み合わせてる方も既にいるようです)。

indiegogo で出資した人には届きましたが、販売はまだされておらず、現在準備中とのことです。

関連サイト

スペック

  • 大きさ
    • 24 mm x 12 mm x 48 mm
  • 通信
  • バッテリー
  • 動作温度
    • 0 ~ 35 ℃
  • 同時接続可能数
  • 動作環境

注意が必要なのは現在は iPad でしか動かない点です。Android 対応は検討中のようです。

Getting Started

詳細は省略しますが、セットアップは簡単で、手順に従ってタグ本体のアイコンを長押しするとペアリングが出来ます。レシピを作成し、サイドバーから好きなコンポーネントをドラッグドロップして配置、機能のソケットをつなぎ合わせると、つないだ直後からそれらが連携するようになります。また、発火したイベントはハイライト(本体 LED / Canvas 両方)されるので何が起きたかもリアルタイムに把握することが出来ます。

MESH タグはまだ 4 種類しかありませんが、タブレットの機能と組み合わせたりすれば、アイディア次第でいろいろな使い方ができると思います。例えば以下は Motion タグでなくて iPad のマイクを利用してタップを取得してみた例です。

Canvas

タグの種類

Canvas では MESH Tags、タブレット機能、ソフトウェアタグが利用でき、以下のものが現在用意されています。

  • MESH Tags

    • Button
      • Pressed / Hold / Double
    • Move
      • Shake / Flip / Tap / Orientation
      • Sensitivity や向きの設定が可能
    • LED
      • Light UP / Firefly(ゆっくり明滅)/ Blink / Off
      • 色や明るさ、長さ、明滅間隔が設定可能
    • GPIO
      • Vout Supply / Digital In / Digital Out / Analog In / PWM Out
      • ピンソケットの番号やトリガの種類、スレッショルドなどが設定可能
  • タブレット機能

    • Camera
      • Take Picture
      • フロントカメラか背面カメラ
    • Mic
      • Sound Trigger
      • Sensitivity が設定可能
    • Speaker
      • Play / Stop
      • プリセットから音を選んだり、別途録音した音を使用可能
  • ソフトウェアタグ

    • And
      • Simultaneous(2 つの入力が ON になっているかを調べる)
      • Window(同時と判断する時間の長さ)が設定可能
    • Timer
      • Wait(一定時間後) / Interval(一定間隔)
      • 時間を指定可能
    • Toggle
      • Alternate(順番に出力を切り分ける)

f:id:hecomi:20150517180116j:plain

今後の展開

公式サイトを見ると、カメラやアクションカムといったソニー製の製品との連携や WEB サービスとの連携も現在作成中のようです。

また将来的に自分でソフトウェアタグを追加できる仕組みも検討されているとのことです。

GPIO タグ

いろいろとハックしたいとなったときに面白いのが GPIO タグです。GPIO タグの仕様は以下に公開されています。

2.54 mm ピッチの 10 ピンソケット(Vout、GND、Digital Input x 3、Digital Output x 3、Analog Input x 1、PWMout x 1)が底部についていて、出力は Vo: 3.3 V / 100 mA(電源接続時は 250 mA)です。温度や気圧などのセンサや、サーボやバイブレータなどのアクチュエータを接続して好きな機能を作ることが出来ます。例えばシンプルな例として Vo と Digital Input 1 をリードスイッチで接続して見ると、以下のように接触センサが作れます。

他にも Vo と GND にモータをつないで回転させたり、PWMout を使ってサーボを制御したりも可能です。

PC との連携

Arduino やラズパイと連携したり、これらを経由して PC と連携することも出来ます。例えば Arduino の例として以下の様なスケッチを書き込んで、GPIO タグの Dout と Arduino の Din をつないでみます。

void setup()
{
  Serial.begin(9600);
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(7, INPUT);
}

void loop()
{
  Serial.print(digitalRead(2));
  Serial.print("\t");
  Serial.print(digitalRead(3));
  Serial.print("\t");
  Serial.print(digitalRead(4));
  Serial.println("");
}

この Arudino の出力を何かしらで取得してみます。以下は Node.js から取得した例です。

var serialport = require('serialport');
var SerialPort = serialport.SerialPort;
var parsers    = serialport.parsers;

var sp = new SerialPort('/dev/tty.usbserial-XXXXXXXX', {
    baudrate : 9600,
    parser   : parsers.readline('\r\n')
});

sp.on('data', function(data) {
    console.log(data);
});

ここまで来れば自前のシステムや IFTTT などの好きなサービスとの連携はし放題だと思います。試しに hue と連携させてみました。

ただし結構回りくどいので、はやく公式にウェブ連携やソフトウェアタグの追加をサポートして欲しいところです(HTTP アクセス出来るだけでも大分楽なのでなにとぞ...)。

個人的に欲しい機能

常時 ON

現状は Canvas アプリでレシピを立ち上げてないと機能が発火しませんが、常時 ON にしておける仕組みがほしいです。直接 PC と接続できるとか、レシピを書き込めるハブとなる親タグがいるとか、Android に対応してサーバとして裏で動かしておけるとか、そういった仕組みが出来るとプロトタイプにとどまらず日常生活を便利にする所まで延長できると思います。

状態取得

イベントは検知できるのですが、状態が欲しいなと思いました。例えば、傾き + ボタン押下で出す音を変えたいと思ってこんなものを作ってみました。

f:id:hecomi:20150517191615j:plain

が、これはうまく動かなくて Orientation が取れるのは傾けた直後なので、傾けた直後にボタンを押す、とやらないと所望の音が発火しません。ボタンも Pressing / Not Pressed みたいな状態が欲しかったりします。例えば傾きとボタン、後は音のピッチを変えるコンポーネントとか組み合わせて、オリジナルの楽器が作れる、とかそういうところまでカスタム出来るようになってくれると良いなと思いました。

(Pressing 後に Not Pressed が飛んでこなかったらステートが崩れたりする可能性があったり、状態を取得しようとするとアクセスが頻繁になり電池食うとか色々と悩ましいところではあるとは思いますが。。)

生値取得

上記のことを出来るように、ソフトウェアタグで生値を使えるようにして欲しいです。

おわりに

現状でもアイディア次第で色んな面白いものが作れますし、もっとたくさんの MESH タグやソフトウェアタグが増えると、複雑なシステムも10分くらいで簡単に作れるようになると思います。ワイヤレスで小さいのは本当に素晴らしいですね。今後のアップデートに大いに期待したいです。

Unity のトゥーンシェーダについて調べてみた

はじめに

自前のトゥーンシェーダを作りたくて、まずは Unity 付属のトゥーンシェーダについて調べてみました。Unity のシェーダについては以前の記事をサラッと読んでいただけると理解しやすいと思います。

環境

  • Unity 5.0.1f1

Toon/Basic

トゥーンシェーダは Assets > Import Package > Effects アセットについてきます。まずは一番シンプルな Toon/Basic を見てみます。

f:id:hecomi:20150425144236p:plain

コードを見てみます。

Shader "Toon/Basic" {
    Properties {
        _Color ("Main Color", Color) = (.5,.5,.5,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _ToonShade ("ToonShader Cubemap(RGB)", CUBE) = "" { }
    }


    SubShader {
        Tags { "RenderType"="Opaque" }
        Pass {
            Name "BASE"
            Cull Off
            
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #pragma multi_compile_fog

           #include "UnityCG.cginc"

            sampler2D _MainTex;
            samplerCUBE _ToonShade;
            float4 _MainTex_ST;
            float4 _Color;

            struct appdata {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                float3 normal : NORMAL;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float2 texcoord : TEXCOORD0;
                float3 cubenormal : TEXCOORD1;
                UNITY_FOG_COORDS(2)
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.cubenormal = mul (UNITY_MATRIX_MV, float4(v.normal,0));
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = _Color * tex2D(_MainTex, i.texcoord);
                fixed4 cube = texCUBE(_ToonShade, i.cubenormal);
                fixed4 c = fixed4(2.0f * cube.rgb * col.rgb, col.a);
                UNITY_APPLY_FOG(i.fogCoord, c);
                return c;
            }
            ENDCG           
        }
    } 

    Fallback "VertexLit"
}

とてもシンプルなコードです。何をやっているかというと、シンプルな頂点 / フラグメントシェーダに以下の様なキューブマップテクスチャを入力として与えている形になります。

f:id:hecomi:20150425144830p:plain

上記は標準の Effects アセットをインポートした時についてくるテクスチャです。Unity 5 からはキューブマップテクスチャとして入力された画像のアスペクト比を見て、インポート時に自動的にキューブマップテクスチャにしてくれます。上記は 128x128 と 1:1 なテクスチャなのでスフィアマップなテクスチャと認識されます。

スフィアマップとして扱われるので、中心の色が上側、周辺の色が下側として描画されて良い感じに影ができるわけです。Toon/Basic は計算量的には軽量ですが、ライトの位置に関係なく下側に影が描画される形になります(試しにディレクショナルライトをグリグリしてみても影の位置は変わりません)。

ちなみに本エントリの主題からは外れますが、#pragma multi_compile_fogUNITY_FOG_COORDSUNITY_TRANSFER_FOG は Unity 5 から Vertex / Fragment シェーダへ Fog の効果を追加する便利機能のようです。

Toon/Lit

ライトの影響を受けるトゥーンシェーダとして Toon/Lit が用意されています。

f:id:hecomi:20150425172923p:plain

Toon Ramp というプロパティに以下の様なテクスチャを与えています。

f:id:hecomi:20150425173027p:plain

コードを見てみます。

Shader "Toon/Lit" {
    Properties {
        _Color ("Main Color", Color) = (0.5,0.5,0.5,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Ramp ("Toon Ramp (RGB)", 2D) = "gray" {} 
    }

    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
CGPROGRAM
#pragma surface surf ToonRamp

sampler2D _Ramp;

// custom lighting function that uses a texture ramp based
// on angle between light direction and normal
#pragma lighting ToonRamp exclude_path:prepass
inline half4 LightingToonRamp (SurfaceOutput s, half3 lightDir, half atten)
{
   #ifndef USING_DIRECTIONAL_LIGHT
    lightDir = normalize(lightDir);
   #endif
    
    half d = dot (s.Normal, lightDir)*0.5 + 0.5;
    half3 ramp = tex2D (_Ramp, float2(d,d)).rgb;
    
    half4 c;
    c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
    c.a = 0;
    return c;
}


sampler2D _MainTex;
float4 _Color;

struct Input {
    float2 uv_MainTex : TEXCOORD0;
};

void surf (Input IN, inout SurfaceOutput o) {
    half4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;
    o.Alpha = c.a;
}
ENDCG

    } 

    Fallback "Diffuse"
}

Surface Shaderカスタムライティングモデルを利用した実装になっています。

Surface Shader は、表面の材質を記述する Surface Function と、ライティングとの相互作用を記述する Lighting Model からなるシェーダです。通常は Lighting Model は組み込みの Lambert(Diffuse)か Blingphone(Specular)を指定しますが、これらで表現できない今回のような場合に、カスタムライティングモデルを利用する形になります。カスタムライティングは Lighting* 接頭辞から始まる関数を作成し、#pragma surface でその関数を使用するライティングモデルとして指定します。

それではコードを見てみます。ミソは以下の行で、ここで光のあたっている量を計算しています。

half d = dot (s.Normal, lightDir)*0.5 + 0.5;

簡単に説明すると、面の法線方向と入射光の方向を比較し、光源方向と表面の法線方向が一致しているほど 1.0 に近づき、一致していない(逆を向いている)ほど 0.0 に近づきます。図解した詳細は以下のページが詳しいです。

そして、この値 d をテクスチャの UV 座標 (d, d) として与え、影の強さとして取り出し、これと表面の色、ライトの色、減衰率を掛けて最終的な色として採用します。

half3 ramp = tex2D (_Ramp, float2(d,d)).rgb;
c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);

なお、Forward 用に #pragma lighting で Diferred で効かないように記述されています。また、減衰率(attenuation)を示す atten が 2 倍で与えられるのは、Unity 4 系までの負債で Unity 5 からは要らないのですが、おそらく修正し忘れではないかと思います(どっかで報告できないのかな)。

ちなみに影ありのディレクショナルライトと一緒にそのまま使うと結構汚い感じになってしまいます。

f:id:hecomi:20150425173338p:plain

影を消せば綺麗にはなるのですが、影を表示したい場合の解決方法はちょっと思いつかないので保留です。。

Toon/Basic Outine

f:id:hecomi:20150425213730p:plain

Toon/Basic OutlineToon/Lit Outline がありますが、両方共 2-Pass なシェーダを利用して再現しています。

Shader "Toon/Basic Outline" {
    Properties {
        _Color ("Main Color", Color) = (.5,.5,.5,1)
        _OutlineColor ("Outline Color", Color) = (0,0,0,1)
        _Outline ("Outline width", Range (.002, 0.03)) = .005
        _MainTex ("Base (RGB)", 2D) = "white" { }
        _ToonShade ("ToonShader Cubemap(RGB)", CUBE) = "" { }
    }
    
    CGINCLUDE
   #include "UnityCG.cginc"
    
    struct appdata {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
    };

    struct v2f {
        float4 pos : SV_POSITION;
        UNITY_FOG_COORDS(0)
        fixed4 color : COLOR;
    };
    
    uniform float _Outline;
    uniform float4 _OutlineColor;
    
    v2f vert(appdata v) {
        v2f o;
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

        float3 norm   = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
        float2 offset = TransformViewToProjection(norm.xy);

        o.pos.xy += offset * o.pos.z * _Outline;
        o.color = _OutlineColor;
        UNITY_TRANSFER_FOG(o,o.pos);
        return o;
    }
    ENDCG

    SubShader {
        Tags { "RenderType"="Opaque" }
        UsePass "Toon/Basic/BASE"
        Pass {
            Name "OUTLINE"
            Tags { "LightMode" = "Always" }
            Cull Front
            ZWrite On
            ColorMask RGB
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #pragma multi_compile_fog
            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_APPLY_FOG(i.fogCoord, i.color);
                return i.color;
            }
            ENDCG
        }
    }
    
    Fallback "Toon/Basic"
}

UsePass "Toon/Basic/BASE" で先ほどの Toon/Basic シェーダを使って描画します。そして次に2つ目の Pass でアウトラインを描画します。やっていることとしては WebGL の解説ですが、アルゴリズムは同じなので wgld.org さんの図を見ると分かりやすいと思います。

ざっくり言うと、ちょっと拡大して裏側だけ描画するようにして指定されたアウトライン色で塗りつぶす、という感じです。拡大する部分のコードを見てみます。

float3 norm   = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
float2 offset = TransformViewToProjection(norm.xy);

ここの計算はちょっと複雑なのですが、要は法線の方向を求めています。v.normal は元々のモデルの法線方向ですが、ライティングするためには回転・スケーリングされた後の法線が欲しいわけです。回転は単純に回転すればよいのですが、スケーリングに関しては面倒で、例えば球をペチャンコに潰すと法線は上面は (0, 1, 0)、下面は (0, -1, 0) となり、方向が変わるのが想像できると思います。また法線の大きさも正規化しないと行けません。このあたりを良い感じに計算しているのが上記2行なわけですね。ちなみに UNITY_MATRIX_IT_MV はモデルビュー転置行列の逆行列(IT = Inverse Transposed)です。

z 方向は拡大しても描画に関係ないので、得られた xy 成分だけを offset として取り出し o.pos に加えます。

o.pos.xy += offset * o.pos.z * _Outline;

ここで、o.pos.z をかけているのは、物体を遠ざけても同じサイズのアウトラインの太さを得るためです。o.pos.z を取り除くと近づくほど太く、遠ざかるほど細くなるアウトライン(= 物体の縁をマーカで黒く塗ったような感じ)になります。

これで横と縦に膨らんだモデルが得られます。後は _OutlineColor で塗りつぶして描画すれば、めでたくアウトラインが描画できるわけです。ただし、この方法には弱点があって、法線方向に膨らめているだけなので直角に交わる面付近などで線が途切れてしまいます。

f:id:hecomi:20150425200922p:plain

Toon/Lit Outline

f:id:hecomi:20150425213848p:plain

Toon/Lit Outline では、ここで作った OUTLINEUsePass してる実装になってます。再利用出来るの良いですね。

Shader "Toon/Lit Outline" {
    Properties {
        _Color ("Main Color", Color) = (0.5,0.5,0.5,1)
        _OutlineColor ("Outline Color", Color) = (0,0,0,1)
        _Outline ("Outline width", Range (.002, 0.03)) = .005
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Ramp ("Toon Ramp (RGB)", 2D) = "gray" {} 
    }

    SubShader {
        Tags { "RenderType"="Opaque" }
        UsePass "Toon/Lit/FORWARD"
        UsePass "Toon/Basic Outline/OUTLINE"
    } 
    
    Fallback "Toon/Lit"
}

改造してみる

色々分かったところで自前でトゥーンシェーダを書いてみます。以下のページで紹介されている方法で 1 Pass でアウトラインを描画してみます。

具体的には、Toon/Lit では法線 - 光源方向の内積の値のみ使ってテクスチャから1次元で色を取り出していましたが、法線 - 視線方向の内積の値も使って2次元でテクスチャから色を取り出します。

Shader "Toon/LitWithOutline" {
    Properties {
        _Color ("Main Color", Color)  = (0.5, 0.5, 0.5, 1)
        _MainTex ("Base (RGB)", 2D)   = "white" {}
        _Ramp ("Toon Ramp (RGB)", 2D) = "gray" {} 
    }

    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
CGPROGRAM
#pragma surface surf ToonRamp

sampler2D _Ramp;
sampler2D _MainTex;
half4     _Color;

inline half4 LightingToonRamp(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
   #ifndef USING_DIRECTIONAL_LIGHT
    lightDir = normalize(lightDir);
   #endif
    
    half u = dot(s.Normal, lightDir) * 0.5 + 0.5;
    half v = dot(s.Normal, viewDir);
    half3 ramp = tex2D(_Ramp, half2(u, v)).rgb;
    
    half4 c;
    c.rgb = s.Albedo * _LightColor0.rgb * ramp;
    c.a = 0;
    return c;
}

struct Input {
    half2 uv_MainTex : TEXCOORD0;
};

void surf (Input IN, inout SurfaceOutput o) {
    half4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;
    o.Alpha = c.a;
}
ENDCG

    } 

    Fallback "Diffuse"
}

カスタムライティングモデルで指定する Lighting Function では、引数を追加すれば視線方向である half3 viewDir を受け取ることができるので、これを利用しています。入力する画像は以下の様なものです。

f:id:hecomi:20150425224417p:plain

結果がコレです。

f:id:hecomi:20150425205427p:plain

...うーん、微妙ですね。

おまけ:Unity 4.x から Unity 5.x へのトゥーンシェーダの移行

これまで Unity 4.x 系でトゥーンシェーダを使っていたプロジェクトを Unity 5 に持ってくるとアウトラインが極太で変になると思います。これは以下の移行ガイドに書いてあるように、法線方向へのふくらめ度合いの計算時に normalize するように修正してみてください。

// float3 norm   = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
float3 norm   = normalize(mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal));

おわりに

仕組みがわかると色々と工夫しがいがあると思いますので、みなさんも色々と工夫したトゥーンシェーダ作ってみてください。

Unity でキャラクタの足の位置を地面の形状に合わせてみた

はじめに

段差や坂を登る時などに単にアニメーションさせているだけではコライダの位置に合わせてキャラクタが上下するだけで、片方の足は地面に付いているけど、もう片方の足の位置は地面から離れているといった不自然な表現になってしまいます。

これを解決するには、アニメーションした後に地面の形状に応じて足の位置と傾きを上書きしてあげれば良いわけです。Unity では MonoBehaviourOnAnimatorIK() などのタイミングで IK(Inverse Kinematics)を利用することで、手前の関節も位置も考慮して両手両足の位置を上書きをすることが出来ます。これを利用すると簡単に足を地面に沿わせることが出来ます。前半はこれについて解説します。

地面の形状に合うようにつけるのは簡単なのですが、動いている時も自然な見た目になるようにしようとすると色々と面倒になります。自分で作るのも大変なので、有料になってしまいますが Unity の有名なアセットの一つである Final IK を使ってセットアップする方法について後半解説します。

スクリーンショット

未使用時

f:id:hecomi:20150422222028p:plain

Unity 標準の Animator IK

f:id:hecomi:20150422222001p:plain

Final IK(Grounder FBBIK)

f:id:hecomi:20150422222440p:plain

デモ

Final IK のデモです。

Unity 標準の Animator IK の利用

4.x では IK はプロ版の機能でしたが 5.x からフリー版でも使えるようになりました。

Unity では MonoBehaviour.OnAnimatorIK() および Unity 5 から導入された StateMachineBehaviour.OnStateIK() のタイミングで、IK を利用して両手両足の位置をアニメーション更新後に上書きして指定することが出来ます。

以下、地面に設置させるサンプルです。

using UnityEngine;
using System.Collections;

public class FootIK : MonoBehaviour 
{
    private Animator animator_;

    public bool  isDrawDebug = false; 
    public float heelOffsetZ = 0f;
    public float toeOffsetZ  = 0f;
    public float rayLength   = 0.2f;

    // 身体の各ボーンの位置は Animator から取れるのでエイリアスを作っておくと便利
    private Transform leftFoot  { get { return animator_.GetBoneTransform(HumanBodyBones.LeftFoot);  } }
    private Transform rightFoot { get { return animator_.GetBoneTransform(HumanBodyBones.RightFoot);  } }
    private Transform leftToe   { get { return animator_.GetBoneTransform(HumanBodyBones.LeftToes); } }
    private Transform rightToe  { get { return animator_.GetBoneTransform(HumanBodyBones.RightToes); } }


    void Start()
    {
        animator_ = GetComponent<Animator>();
    }


    void OnAnimatorIK()
    {
        // IK の位置から踵・つま先のオフセットを設定
        var heelOffset   = Vector3.up * heelOffsetZ;
        var toeOffset    = Vector3.up * toeOffsetZ;
        var leftHeelPos  = leftFoot.position  + heelOffset;
        var leftToePos   = leftToe.position   + toeOffset;
        var rightHeelPos = rightFoot.position + heelOffset;
        var rightToePos  = rightToe.position  + toeOffset;

        // 足の位置を IK に従って動かす
        var leftIkMoveLength  = UpdateFootIk(AvatarIKGoal.LeftFoot,  leftHeelPos,  leftToePos);
        var rightIkMoveLength = UpdateFootIk(AvatarIKGoal.RightFoot, rightHeelPos, rightToePos);

        // 身体の位置を下げないと IK で移動できないので
        // IK で移動させた差分だけ身体を下げる
        animator_.bodyPosition += Mathf.Max(leftIkMoveLength, ightIkMoveLength) * Vector3.down;
    }


    float UpdateFootIk(AvatarIKGoal goal, Vector3 heelPos, Vector3 toePos)
    {
        // レイを踵から飛ばす(めり込んでた時も平気なようにちょっと上にオフセットさせる)
        RaycastHit ray;
        var from   = heelPos + Vector3.up * rayLength;
        var to     = Vector3.down;
        var length = 2 * rayLength;

        if (Physics.Raycast(from, to, out ray, length)) {
            // レイが当たった場所を踵の場所にする
            var nextHeelPos = ray.point - Vector3.up * heelOffsetZ;
            var diffHeelPos = (nextHeelPos - heelPos);

            // Animator.SetIKPosition() で IK 位置を動かせるので、
            // 踵の移動分だけ動かす
            // 第1引数は AvatarIKGoal という enum(LeftFoot や RightHand など)
            animator_.SetIKPosition(goal, animator_.GetIKPosition(goal) + diffHeelPos);
            // Animator.SetIKPositionWeight() では IK のブレンド具合を指定できる
            // 本当は 1 固定じゃなくて色々フィルタ掛けると良いと思う
            animator_.SetIKPositionWeight(goal, 1f);

            // 踵からつま先の方向に接地面が上になるように向く姿勢を求めて
            // IK に反映させる
            var rot = GetFootRotation(nextHeelPos, toePos, ray.normal);
            animator_.SetIKRotation(goal, rot);
            animator_.SetIKRotationWeight(goal, 1f);

            // レイを確認用に描画しておくと分かりやすい
            if (isDrawDebug) {
                Debug.DrawLine(heelPos, ray.point, Color.red);
                Debug.DrawRay(nextHeelPos, rot * Vector3.forward, Color.blue);
            }

            return diffHeelPos.magnitude;
        }

        return 0f;
    }


    Quaternion GetFootRotation(Vector3 heelPos, Vector3 toePos, Vector3 slopeNormal)
    {
        // つま先の位置からレイを下に飛ばす
        RaycastHit ray;
        if (Physics.Raycast(toePos, Vector3.down, out ray, 2 * rayLength)) {
            if (isDrawDebug) {
                Debug.DrawLine(toePos, ray.point, Color.red);
            }
            var nextToePos = ray.point + Vector3.up * toeOffsetZ;
            // つま先方向に接地面の法線を上向きとする傾きを求める
            return Quaternion.LookRotation(nextToePos - heelPos, slopeNormal);
        }
        // レイが当たらなかったらつま先の位置はそのままで接地面方向に回転だけする
        return Quaternion.LookRotation(toePos - heelPos, slopeNormal);
    }
}

これで冒頭のスクショのようになります。

ただ、このままでは動かすと地面に近づいた時に足がアニメーションで地面につく前に IK で地面についてしまうのでビッタンビッタンした動きになってしまいます。これを防ぐために足の上下速度に応じてフィルタを掛けたりしてあげたり、StateMachineBehaviour.OnStateIK() の方で特定のモーションの時だけ接地するようにしてあげたりする必要があります。また走っていて足が垂直になるようなときに、上記スクリプトでは踵-つま先方向を保ったまま地面につけようとする結果、すごい角度で地面に接地した感じになってしまいます。これを防ぐために足の角度制限をつけてあげたりする必要があると思います。また、Animator IK は膝位置を調整できないので、やけに内股になってしまったりします。

Final IK を利用する

こういった問題点は、Final IK に同梱されている Grounder FBBIK を使うと解決します。Final IK は $90 のアセットで Full Body な IK や人以外の IK も出来たりと Unity の標準の IK と比べるとかなり高機能になっています。動画も沢山上がっているので見てると買いたくなると思います。

Grounder FBBIK はこんな感じで使えます。

基本的には Full Body Biped IK(人型の全身の IK)と Grounder FBBIK をアタッチして、Grounder FBBIKIkFull Body Biped IK をドラッグ&ドロップするだけで良いのですが、セットアップが失敗することがあります。

f:id:hecomi:20150423013639p:plain

赤や黄色のジョイントがダメなところです。このまま動かすととても残念な感じになります。

f:id:hecomi:20150423014041p:plain

原因はピンと伸びていることのようで、以下の動画の後半部のように微妙に関節を動かしてあげると解決します。

f:id:hecomi:20150423014135p:plain

とても簡単です。

おわりに

Animator 周りの知識まだまだ乏しいのですが、勉強しながら、動きが綺麗でかつ色々出来る面白いキャラクタコントローラ作りたいです。