おもちゃラボ

Unityで遊びを作ってます

Unityで始めるARKit超入門 平面をシェーダで描画する

UnityのARKitについてくる平面検出マーカーって、見やすいですけどイケてないですよね。そこで、Unityのシェーダを使って、ARKitの平面検出マーカーを少しだけかっこよくする方法を紹介します。今回は上のようなドットで書かれた平面マーカーを作ってみましょう。

f:id:nn_hokuson:20181001192028j:plain

平面描画用の水玉シェーダを作成する

 上のようなドット柄の平面検出マーカーを作るだけなら、ドット模様(水玉模様)のテクスチャを用意して貼り付ければ良い気もしますね。ただ、平面が大きくなるとテクスチャも引き伸ばされるため、あまりきれいな表示になりません。

f:id:nn_hokuson:20181001185311p:plain:w300

シェーダを使って描画することで、平面の大きさによらず一定の間隔でドットを打てるのでシェーダを使った方法をオススメします。

 

水玉シェーダを作成する

まずはシーンビューで右クリックして「Create」→「Shader」→「Standard Surface Shader」を選択してシェーダファイルを作成します。ファイル名はARPlaneShaderにしておきます。

次のプログラムを入力してください。

Shader "Custom/ARPlaneShader"
{
  properties
  {
    _Ratio("Ratio", float) = 1
  }
  SubShader {
    Blend SrcAlpha OneMinusSrcAlpha
    Pass {
      Tags { "Queue"="Transparent" "RenderType" = "Transparent" }

      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
       #include "UnityCG.cginc"

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

      struct v2f
      {
        float4 position : SV_POSITION;
        float2 uv     : TEXCOORD0;
      };

      v2f vert (appdata input)
      {
        v2f output;
        output.position = UnityObjectToClipPos(input.vertex);
        output.uv = input.uv;
        return output;
      }

     float _Ratio;
      fixed4 frag (v2f input) : SV_Target
      {
        float2 v = float2(input.uv.x * 200 * _Ratio, input.uv.y * 200) ;

        float  f = 5 * ( (sin(v.x) * 0.5 + 0.5) + (sin(v.y) * 0.5 + 0.5) );
        return fixed4(fixed3(0.1, 0.9, 1.0),  1-f);        
      }
      ENDCG
    }
  }
}

 
 

シェーダで水玉模様を作る原理

ここでは、ドットを打たないところ以外は透明にしたいのでBlendのタイプとQueue、Render Typeを指定しています。この辺は次のUnity Shaderの記事も参考にしてください。
 
nn-hokuson.hatenablog.com

重要なのはfragメソッドの中に書いているフラグメントシェーダです。少しややこしく見えるかもしれませんが、キモはsin(v.x)+sin(v.y)を計算しているところです。z = sin(x)+ sin(y)の計算結果は次のようになります。

f:id:nn_hokuson:20181001190113j:plain:w300

この山の高さを透明度にすることで頂上付近のみがドットとして表示されるという仕組みです。山の頂上だけをピックアップするからドットに見える、というのは上の図を横から見て考えたほうが分かりやすいかもしれません。

f:id:nn_hokuson:20181001185859p:plain:w400

Unityでシェーダを使って、その他の図形を書きたい場合は次の記事も参考にして下さい。

nn-hokuson.hatenablog.com

Prefabのシェーダを差し替える

シェーダができたところで、既存の平面検出シェーダと差し替えましょう。まずは今作成したシェーダをアタッチしたマテリアルを作成します。ARPlaneShaderを選択した状態で、右クリックして「Create」→「Material」を選択してください。

f:id:nn_hokuson:20181001190557p:plain:w200

このマテリアルをARKItの検出平面のPrefabに設定します。UnityARKitPlugin/Examples/Common/Prefabs/の中にあるdebugPlane→Planeオブジェクトのシェーダを今作成したCustom/ARPlaneShaderに変更してください。

f:id:nn_hokuson:20181001190801j:plain

これで、ビルドしてARKitを実行して平面を検出すると、次のように水玉模様が描かれた平面が検出されます。

f:id:nn_hokuson:20181001192028j:plain

Unityで始めるARKit入門 光源推定編

前回はUnityとARKitを使って、タップしたところにリンゴを配置するサンプルを作りました🍎

nn-hokuson.hatenablog.com

今回はARKitで光源推定をする方法を紹介します。ARでライトを配置する場合、そのままライトをおいただけでは光源の強さや色合いが決まっているため、モデルが映像になじまず違和感がある絵になってしまいます。

ARKitでは撮影された映像から光源の強さと色合いを推定できる機能があるので、それを使用してみましょう。

f:id:nn_hokuson:20180925231804j:plain

ARKitの光源推定でできること

 ARは様々な環境で撮影するため、その環境に応じたライトの設定をすることで、より自然なARに見せることができるようになります。ARKitでもARKit1.5からは光源推定ができる機能が提供されるようになりました。

このARKitの光源推定で推定できる値は次の2つになります。

  • ライトの強さ
  • ライトの色温度

どちらからライトが当たっているか、というライトの位置と方向は推定できないことに注意してください。ARKitで得られる光源の推定値をUnityのライトに設定することで、より自然なARを実現できます。

f:id:nn_hokuson:20180925233222p:plain:w450

光源推定を使ってみる

といってもUnityの場合はデフォルトで光源推定が有効になっているので、やることはありません(笑)1からシーンを作る場合を考えて、設定する項目を見ておきましょう。必要なのは次の二点です。

  1. Enable Light Estimationのチェックを入れる
  2. ライトにUnityARAmbientスクリプトをアタッチする

まずはヒエラルキーウインドウのARCameraManagerの中の「Enable Light Estimation」のチェックボックスにチェックを入れます。

f:id:nn_hokuson:20180925204013p:plain:w350

続いてヒエラルキーウインドウのDirectional lightに、Assets/UnityARKitPlugin/Plugins/iOS/UnityARKitHelpers/に含まれるUnityARAmbientスクリプトをアタッチします。

f:id:nn_hokuson:20180925204042p:plain:w350

このスクリプトの中身を見ておきましょう。

using System.Runtime.InteropServices;
using UnityEngine.XR.iOS;

namespace UnityEngine.XR.iOS
{
  public class UnityARAmbient : MonoBehaviour
  {

    private Light l;

    public void Start()
    {
      l = GetComponent<Light>();
			UnityARSessionNativeInterface.ARFrameUpdatedEvent += UpdateLightEstimation;
    }

    void UpdateLightEstimation(UnityARCamera camera)
    {
      if (camera.lightData.arLightingType == LightDataType.LightEstimate) {
        float newai = camera.lightData.arLightEstimate.ambientIntensity;
        l.intensity = newai / 1000.0f;
        l.colorTemperature = camera.lightData.arLightEstimate.ambientColorTemperature;
      }
    }

    void OnDestroy() {
      UnityARSessionNativeInterface.ARFrameUpdatedEvent -= UpdateLightEstimation;
    }
  }
}

Startメソッドの中では、ARKit側でARFrameUpdatedEventが呼びだれたタイミング(フレーム更新時)でUnityのUpdateLightEstimationが実行されるようにイベントを追加しています。

UpdateLightEstimationではARKitで得られたライトの推定強度(ambientIntensity)と推定色温度(ambientColorTemperature)をUnityのDirectional lightに設定しています。

実行結果

Unityでビルドして実行してみましょう。部屋のライトをつけたときと消したときで見え方が変わったら成功です。

f:id:nn_hokuson:20180925232049g:plain:w500

Unityで始めるARKit入門 モデル配置編

前回はUnityからARKitを使う方法と、デモを動かすところまで実行しました。今回はタッチしたところにモデルを配置するプログラムを作ってみましょう。

f:id:nn_hokuson:20180920201828j:plain

シーンを作成する

今回も前回ダウンロードしたARKitのUnity用プラグインを使用します。
まだ、入手していない方はこちらからダウンロードしてください。

https://bitbucket.org/Unity-Technologies/unity-arkit-plugin/get/arkit2.0_beta.zip

次に今回作るARKitのアプリ用にシーンを用意します。
/Assets/UnityARKitPlugin/Examples/ARKitSceneを一度開きます。メニューから「File」→「Save Scene as...」を選択して、「Test」という名前で保存しましょう。

f:id:nn_hokuson:20180920195520p:plain:w350

これでTestというシーンが新しく作られます。これを使って実験していきましょう。

不要なオブジェクトを削除する

ヒエラルキービューには色々なARKitで使うオブジェクトがすでに配置されています。それぞれに役割がある(各内容は前回の記事参照)のですが、ヤヤコシイので不要なものは削除しましょう。

nn-hokuson.hatenablog.com

今回は次のオブジェクトが不要ですので、ヒエラルキービューから削除してください。

  • RandomCube
  • HitCubeParent
  • GeneratePlanes
  • ARKitControl
  • PointCloudExample
  • AR3DOFCameraManager

次のような状態になっていればOKです。PointCloudParticleExampleは検出された特徴点を表示するもので、デバッグ用に便利なので残しています。

f:id:nn_hokuson:20180920195539p:plain:w270

タッチを検出するプログラムを作る

続いてタッチを検出するプログラムを作ります。プロジェクトウィンドウで右クリックし、「Create」→「C# Script」を選択してください。ファイル名は「TouchController」にしました。

f:id:nn_hokuson:20180920195549p:plain:w270

スクリプトファイルが作成できたら、次のスクリプトを入力してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.iOS;

public class TouchController : MonoBehaviour
{
    public GameObject cubePrefab;

    void HitTest(ARPoint point)
    {
        List<ARHitTestResult> hitResults = UnityARSessionNativeInterface
            .GetARSessionNativeInterface()
            .HitTest(point, ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent);

        // 平面とあたっていた場合
        if (hitResults.Count > 0)
        {
            GameObject g = Instantiate(cubePrefab);
            g.transform.position = UnityARMatrixOps.GetPosition(hitResults[0].worldTransform);
            g.transform.rotation = UnityARMatrixOps.GetRotation(hitResults[0].worldTransform);
        }            
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.touchCount > 0 )
        {
            var touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                var screenPosition = Camera.main.ScreenToViewportPoint(touch.position);
                ARPoint point = new ARPoint
                {
                    x = screenPosition.x,
                    y = screenPosition.y
                };

                // 平面との当たり判定
                HitTest(point);
            }
        }
    }
}

Updateメソッドの中でタッチの検出を行っています。タッチされた場合は、ScreenToViewportPointメソッドを使ってタッチ座標をスクリーン座標からビュー座標に変換しています。

平面との当たり判定はHitTestメソッドの中で行っています。当たり判定に関してはUnityの機能ではなくARKitに用意されているHitTestメソッドを使います。

HitTestメソッドの第二引数には当たり判定を行う対象を指定します。指定できるオプションには次のようなものがあります。

オプション名 役割
ARHitTestResultTypeFeaturePoint ARKittで検出された特徴点
ARHitTestResultTypeEstimatedHorizontalPlane 平面の推定値
ARHitTestResultTypeExistingPlane 検出された平面
ARHitTestResultTypeExistingPlaneUsingExtent 検出された平面の内側領域


ここでは、検出された面に対して当たり判定を行いたいので、ARHitTestResultTypeExistingPlaneUsingExtentを指定しています。

タッチした座標が平面とあたっていた場合は、Prefabに登録したモデルのインスタンスを作成しています。SwiftでARKitを使う場合は、SceneKitでNodeを登録・・・ということが必要になりますが、ここらへんが簡単にできるのがUnityの強みですね!

配置する3DモデルのPrefabを作る

配置するモデルは何でもかまわないので、今回はAsset Storeから次のものを利用させていただきました。

Prefabを登録する際は、そのスケールに注意してください。Unityでは標準の立方体(Create→3D Object→Cube)の1辺を1mとして扱うので、その大きさのままARKitで表示してしまうと、大きすぎます。

大きすぎるだけならまだしも、モデルの内側に入っちゃって、あれ表示されてない??なんてこともあるので注意です・・・

最後にスクリプトをアタッチしてPrefabを登録しましょう。ヒエラルキーウインドウのCreate→Create Emptyで空のゲームオブジェクト(Game)を作成し、そこに先程作ったTouchControllerをアタッチしてください。次にインスペクタのPrefab欄に表示したいモデルのPrefabを登録します。

f:id:nn_hokuson:20180920200407j:plain

書き出して実行する

これで今回のアプリは完成です。メニューバーから「File」→「BuildSettings」を選択してビルドウインドウを開いてください。Scene In Buildに「Test.scene」をドラッグ&ドロップします。TargetをiOSにしてから、Buildボタンで書き出してください。

f:id:nn_hokuson:20180920200538j:plain:w400

実機で実行して画面をタップすると、どんどんリンゴが生成されます🍎🍎🍎

f:id:nn_hokuson:20180920202146g:plain:w500

次回は、ARで表示したモデルをより自然に見せるための「光源推定」について紹介します。

nn-hokuson.hatenablog.com

参考書

SwiftでARKitの基本的な機能を学べる書籍「SwiftでつくるARKit超入門」を執筆しました!
128ページ、2000円でBOOTHで絶賛発売中です!
booth.pm