技術にっき

気になる技術について呟いてます

UnityのURPプロジェクトでカメラ映像を反転させる

Built-in Render Pipeline から Universal Render Pipeline(以降URPと記載)に移行した際、カメラ映像を反転させる処理が動かなくなりました。
今回は、URP プロジェクトでカメラ映像を反転させる方法のひとつをお話しします。


確認環境

  • Unity バージョン:2021.3.25f1
  • Universal RP バージョン:12.1.11


まとめ


Built-in Render Pipeline でのカメラ映像の反転

今回はカメラ映像を反転させる方法の一つとして、Camera の ProjectionMatrix を変更する方法を用います。
Built-in Render Pipeline での実装は、Unity公式ドキュメントのカメラ反転のサンプルを参考にします。
MonoBehaviour-OnPreCull() - Unity スクリプトリファレンス


解説は下記の記事が参考になりました。
rdrgn.hatenablog.com
ポイントは OnPreCull(), OnPreRender(), OnPostRender() といったイベントを利用していることです。


URP でのカメラ映像の反転

Built-in Render Pipeline の実装を URP で動かすと、カメラ映像を反転する処理が動かなくなります。
この問題は OnPreCull(), OnPreRender(), OnPostRender() のイベントが URP のプロジェクトで呼ばれなくなったことが原因で発生します。
そのため使用しているイベントを差し替えることで、この問題は解決できます。

URP における同様なイベントとしては、RenderPipelineManager のイベントがあります。
Rendering.RenderPipelineManager - Unity スクリプトリファレンス
これらのイベントで差し替えることで、カメラ映像の反転を実現することができます。

  • イベント差し替えの例
    • OnPreCull() -> RenderPipelineManager.beginCameraRendering
    • OnPreRender() -> RenderPipelineManager.beginCameraRendering
    • OnPostRender() -> RenderPipelineManager.endCameraRendering

(参考) zenn.dev


実装例

最後に、URP プロジェクトでカメラ映像を左右反転させる実装例を掲載します。
こちらをカメラにアタッチして、動作を確認してみてください。
インスペクターから IsInvert の値を操作することで反転の有無を切り替えることができます。

using UnityEngine;
using UnityEngine.Rendering;

[RequireComponent(typeof(Camera))]
public class InvertableCamera : MonoBehaviour
{
    [SerializeField] bool IsInvert = true;
    private Camera _targetCamera = null;

    void Awake()
    {
        _targetCamera = GetComponent<Camera>();
    }

    void OnEnable()
    {
        RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
        RenderPipelineManager.endCameraRendering += OnEndCameraRendering;
    }

    void OnDisable()
    {
        RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
        RenderPipelineManager.endCameraRendering -= OnEndCameraRendering;
    }

    void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
    {
        if (IsInvert)
        {
            GL.invertCulling = true;
        }

        _targetCamera.ResetWorldToCameraMatrix();
        _targetCamera.ResetProjectionMatrix();
        
        var vec = IsInvert ? new Vector3(-1f, 1f, 1f) : Vector3.one;
        _targetCamera.projectionMatrix = _targetCamera.projectionMatrix * Matrix4x4.Scale(vec);
    }

    void OnEndCameraRendering(ScriptableRenderContext context, Camera camera)
    {
        GL.invertCulling = false;
    }
}


以上。

※当ブログに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。
 当サイトで掲載している画像や動画の著作権・肖像権等は各権利所有者に帰属致します。

背景画像とカメラ左右反転の組み合わせで遭遇した問題の対応

Unity上でアバターの背景に画像を表示したい!といろいろやっていた時に遭遇した問題の対応についてメモを残します。
具体的には、カメラを左右反転させた際、そのカメラをRender Cameraに設定しているCanvasのImageが表示されなかった問題についてです。

Unity バージョン:Unity 2021.3.25f1

準備

まず、アバターの背景に画像を表示するために行ったことは下記の通り。

  • Scene上にアバターを配置

  • 背景画像を表示するImageを含むCanvasを作成

    • CanvasのRenderModeをScreen Space - Cameraに設定

      • Render CameraにMainCameraを設定
    • Imageに表示したい画像を設定

問題

この状態で、アバターの動きのみを左右反転させたいと思い 下記の記事を参考に実装してみたところ、反転時にImageが表示されなくなりました。
rdrgn.hatenablog.com

解決策

背景画像、アバターそれぞれ用のカメラを用意して アバター用のカメラのみ反転させるようにしました。
アバター用カメラは下記のように設定。

  • Depth値は、アバター用カメラ>背景画像用カメラ

  • Clear FlagsはDepth OnlyまたはDon't Clear

TargetDisplayはともに表示したいものを設定。

以上。

※当ブログに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。
 当サイトで掲載している画像や動画の著作権・肖像権等は各権利所有者に帰属致します。

Googleマイマップのルート情報をUnityで扱う方法

地図上のある移動経路における各地点の地理座標系(緯度・経度)を取得して
Unityで扱う方法
を調べてみました。

移動経路における各地点のイメージ図
今回は、Googleマップのマイマップの機能を利用する方法の一つを紹介します。

Unity バージョン:Unity 2020.3.32f1


マイマップとは

マイマップGoogleマップの機能のひとつで、好きな場所にマーカーを置いたり
メモやルートを作成するなど自分用のマップを作ることができる機能です。
また、他の人にマイマップを共有して同時編集することも可能です。

ルート情報をUnityで扱う方法

下記の手順を行うことで、マイマップで作成したルートにおける
各地点の地理座標系をUnityで扱うことができます。

1. マイマップでルートを作成する
2. マイマップを公開する
3. マイマップのKMLファイルをUnityで取得する
4. 特定のルートにおける地理座標系の情報を取得する

各手順の方法を下記で説明します。
Googleアカウント保有を前提としています。
 活用の際はGoogleマップの利用規約をご確認ください。

1. マイマップでルートを作成する

保有するマイマップがない場合は、My Mapsのサイトから「+ 新しい地図を作成」
ボタンを押して新しいマイマップを作成します。

マイマップを作成

その後、マイマップでルートを作成します。具体的な作成方法は
割愛しますが、移動経路における目的地と移動方法、ルート名を設定します。

ルートの作成

2. マイマップを公開する

マイマップ名の下にある「共有」を押して、地図タイトルと説明を
記載後、「OK」ボタンを押します。
さらに地図の共有で、図のようにリンクを知っている人に公開する
設定を行い、リンクをコピーしておきましょう。

リンクを知っている人へのマイマップの公開

3. マイマップのKMLファイルをUnityで取得する

UnityWebRequestを使い、マイマップのKMLファイルを取得します。

KML(Keyhole Markup Language)はGoogleマップなどで地理データの表示に
使用されるファイル形式です。

Unity内で下記のようなC#スクリプトを作成し、GameObjectにアタッチ
することで、UnityでKMLファイルを取得し、ログにてKMLファイルの中身を
確認することができます。
※ midは手順2でコピーしたURLのリクエストパラメータの値を参照

// MyBehaviour.cs
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
 
public class MyBehaviour : MonoBehaviour {

    // コピーしたURL中のmidの値を設定する
    [SerializeField] string mid = "******************************";

    void Start() {
        StartCoroutine(GetText());
    }
 
    IEnumerator GetText() {
        UnityWebRequest www = UnityWebRequest.Get("https://www.google.com/maps/d/u/0/kml?mid=" + mid + "&forcekml=1");
        yield return www.SendWebRequest();
 
        if (www.result != UnityWebRequest.Result.Success) {
            Debug.Log(www.error);
        }
        else {
            Debug.Log(www.downloadHandler.text);
        }
    }
}

KMLファイルをログに出力

4. 特定のルートにおける地理座標系の情報を取得する

3で取得したKMLファイルのデータから特定ルートの部分を切り出して
Unityで扱えるようリストの状態に変換します。
3で作成したC#スクリプトを下記の様に修正することで
ルートの地理座標系情報をリストで扱えるようになりました!

// MyBehaviour.cs
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using System.Collections.Generic;

public class MyBehaviour : MonoBehaviour {

    // ルート名とコピーしたURL中のmidの値を設定する
    [SerializeField] string mid = "******************************";
    [SerializeField] string route = "***********";

    void Start() {
        StartCoroutine(GetText());
    }
 
    IEnumerator GetText() {
        UnityWebRequest www = UnityWebRequest.Get("https://www.google.com/maps/d/u/0/kml?mid=" + mid + "&forcekml=1");
        yield return www.SendWebRequest();
 
        if (www.result != UnityWebRequest.Result.Success) {
            Debug.Log(www.error);
        }
        else {
            //Debug.Log(www.downloadHandler.text);
            string areaText;
            List<List<double>> coordinateList;
            areaText = GetRouteArea(www.downloadHandler.text);
            if(areaText!=""){
                coordinateList = CreateCoordinateList(areaText);
                DisplayCoordinateList(coordinateList);
            } 
        }
    }

    // ルート名に対応する地理座標系を抽出する
    private string GetRouteArea(string text){
        string returnStr = "";        
        System.Text.RegularExpressions.Regex r =
            new System.Text.RegularExpressions.Regex(
                    @"<Placemark>\s*<name>"
                    + System.Text.RegularExpressions.Regex.Escape(route) 
                    + @"</name>.*?<(coordinates)>(.*?)</\1>",
                System.Text.RegularExpressions.RegexOptions.IgnoreCase
                | System.Text.RegularExpressions.RegexOptions.Singleline);
        System.Text.RegularExpressions.MatchCollection mc = r.Matches(text);

        if(mc.Count==0){
            Debug.Log("No match.");
            return returnStr;
        }
        foreach (System.Text.RegularExpressions.Match m in mc){
            returnStr = m.Groups[2].Value;
        }  
        return returnStr;
    }

    // 地理座標系のリストを作成する
    private List<List<double>> CreateCoordinateList(string text){
        var list = new List<List<double>>();
        int i = 0;
        System.Text.RegularExpressions.Regex r =
            new System.Text.RegularExpressions.Regex(
                    @"\s+([^\s]+?),([^\s]+?),0\s",
                System.Text.RegularExpressions.RegexOptions.IgnoreCase
                | System.Text.RegularExpressions.RegexOptions.Singleline);
        System.Text.RegularExpressions.MatchCollection mc = r.Matches(text);
                      
        foreach (System.Text.RegularExpressions.Match m in mc){
            // 緯度:list[x][0] - Groups[2].Value
            // 経度:list[x][1] - Groups[1].Value
            list.Add(new List<double>());
            list[i].Add(double.Parse(m.Groups[2].Value));
            list[i].Add(double.Parse(m.Groups[1].Value));
            i++;
        }      
        return list;
    }

    // 地理座標系のリストを一覧表示する
    private void DisplayCoordinateList(List<List<double>> list){
        int i=0;
        string displayStr="";
        foreach (List<double> li in list){
                displayStr +=(i+1) + ":緯度: " + li[0] + " 経度: " +  li[1] + "\n";
                i++;
        }     
        Debug.Log(displayStr);
    }
}



参考:
HTTP サーバーからテキストやバイナリデータを取得 (GET)/Unity Documentation
UnityでHTMLからテキスト出力/Sirohood

※当ブログに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。
 当サイトで掲載している画像や動画の著作権・肖像権等は各権利所有者に帰属致します。

日本の「新型コロナウイルス接触確認アプリ」の話

2020年6月19日(金)に 日本の新型コロナウイルス接触確認アプリ」がリリースされました。
この記事では アプリの概要実際にダウンロードして気になった点 についてお話しします。


アプリの概要

このアプリは、厚生労働省のサイトにて下記の様に説明されています。

○ 厚生労働省は、新型コロナウイルス感染症の拡大防止に資するよう、新型コロナウイルス感染症対策テックチームと連携して、新型コロナウイルス接触確認アプリ(COCOA※)を開発しました。ご自身のスマートフォンにインストールして、利用いただきますようお願いします。 
 ※COVID-19 Contact Confirming Application

○ 本アプリは、利用者ご本人の同意を前提に、スマートフォンの近接通信機能(ブルートゥース)を利用して、お互いに分からないようプライバシーを確保して、新型コロナウイルス感染症の陽性者と接触した可能性について、通知を受けることができます。

○ 利用者は、陽性者と接触した可能性が分かることで、検査の受診など保健所のサポートを早く受けることができます。利用者が増えることで、感染拡大の防止につながることが期待されます。


詳細については厚生労働省のサイトに掲載されていますが、
要するに個人情報・位置情報を取得することなく "新型コロナウイルスの陽性者との接触の可能性" を知ることができるアプリだと私は認識しています。
"接触の可能性" を知ることで、利用者が感染拡大の防止に役立てたり
アプリを通じて検査の受診などの案内を受けたりできるので、このコロナ禍において有用なアプリといえるでしょう。


気になった点

私は Google Play からこのアプリをインストールして、利用してみました。
(公開日から1か月間は試行版とのこと)
   Google Play ダウンロードサイト
   Apple Play ダウンロードサイト

利用をしてみて、気になった2点についてお話しします。

 ① スマホの「デバイスの位置情報」の設定
 ② 「接触」の定義の時間



スマホの「デバイスの位置情報」の設定

アプリ利用時にGPSなどの位置情報を使用しないのに
バイスの位置情報の利用許可を付与しなければならない理由について疑問に思いました。
アプリを利用した際に、Android の位置情報の設定を ON にしていない場合、下記のようなポップアップがでます。

f:id:OtyA:20200621154014j:plain
バイスの位置情報に関する権限

こちらの "ONにする"を押すとスマホBluetooth と位置情報の利用許可 が付与にされます。

この理由について調べた結果、Bluetooth を利用するためにデバイスの位置情報の利用許可を付与しなければならないようです。(ポップアップにも書いてありましたが…)
より具体的には、Android 6.0 以上からBluetooth通信でのスキャン結果を取得するために利用許可が必要となるようです。
このアプリの動作可能OSバージョンは、AndroidAndroid 6.0 以上であるため、設定をする必要があります。

参考:Android デベロッパー > Bluetooth Low Energy の概要



② 「接触」の定義の時間

このアプリでは1メートル以内で15分以上近接した状態を「接触」と定義していますが、この15分という時間をどのように決めたのかが気になりました。
こちらについては、恐らく厚生労働省が定める「濃厚接触」の基準を適用しているのだと思います。

厚生労働省は以下の様に濃厚接触について説明しています。

濃厚接触者は、新型コロナウイルスに感染していることが確認された方と近距離で接触、或いは長時間接触し、感染の可能性が相対的に高くなっている方を指します。
濃厚接触かどうかを判断する上で重要な要素は上述のとおり、1.距離の近さと2.時間の長さです。必要な感染予防策をせずに手で触れること、または対面で互いに手を伸ばしたら届く距離(1m程度以内)で15分以上接触があった場合に濃厚接触者と考えられます。


ただし、上記引用サイトにも記載があるように、マスクや接触の有無、「3密」の状況によって感染の可能性は変わるようなので、あくまで参考程度に考えておくのがよさそうです。



最後に

今回は、日本における「新型コロナウイルス接触確認アプリ」の概要と私が気になった点についてお話いたしました。
現在のコロナ禍において、このアプリは感染拡大防止の目的で大いに役立つでしょう。
しかし、アプリの機能上 利用している人が少なければ大きな効果は見込めません。
こちらのアプリの利用者が増えて、国内での感染拡大抑止につながればと思います。
 ※アプリの利用は任意です。




※当ブログのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、
 誤情報が入り込んだり、情報が古くなっていることもございます。
 当ブログに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。


通話時のノイズキャンセル機能の話

2019年から続くコロナ禍のなか、社会ではテレワークで業務を行う機会が増えています。
テレワークでは、通話でコミュニケーションをとる人も多いでしょう。
そんな中、家族の声や家外の環境音など通話ノイズが気になる方も多いのではないでしょうか。

今回は、通話ノイズを除去する機能 ノイズキャンセル機能についてお話いたします。


通話ノイズの除去方法

私が知るなかで、通話のノイズを除去する方法は以下の2つがあります。
 ① 複数のマイクによる差分をとる方法
 ② 1つの音声を信号処理する方法


① 複数のマイクによる差分をとる方法

①の方法は「周囲の環境音」を拾うマイクと「環境音を含む声」を拾う口元付近のマイクとで収集した音声の差分をとることで、通話者の声を取り出します。
例えば、iPhone では①の方法で通話時のノイズを除去しています。

この方法では 単一マイクの機材で通話を行う場合と比べて、通話ノイズを大きく減らすことができるようです。

参考:CVC6.0・CVC8.0ノイズキャンセル機能はハンズフリー機器やイヤホンの通話中の騒音軽減機能; マイナーナビ


② 1つの音声を信号処理する方法

②の方法では、収集した音声信号をソフトウェア的に処理することでノイズを低減します。
従来からの技術としては、Qualcomm 社のcVc (clear voice capture) ノイズキャンセル機能があります。
cVc Noise Cancellation Technology; Qualcomm

また、最近では GoogleAI を活用したノイズキャンセリング機能を実装しました。
gigazine.net
この AI によるノイズキャンセリング機能は、ビデオ会議ツールGoogle Meet』のウェブブラウザ版に活用されています。
紹介動画では、高いノイズキャンセル性能が見られました。
オンライン環境は必要なものの、AI を活用したノイズキャンセリング機能は単一マイクしか持たない機材を活用する場面においては、大きな力を発揮できそうです。



最後に

今回は、ノイズキャンセリング機能についてお話ししました。
複数マイク内蔵の機材を活用したりソフトウェア的な信号処理をしたりすることで、通話者の声以外のノイズを除去することが可能です。
通話において環境音などの通話ノイズに困っている方は、複数マイクを内蔵した機材や、強力なノイズキャンセリング機能が組み込まれているアプリを検討してみてはいかがでしょうか。




※当ブログのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、
 誤情報が入り込んだり、情報が古くなっていることもございます。
 当ブログに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。


QRコードでWiFiにログインする技術の話

イベントや旅行先、宿泊先で WiFi を利用したいとき、スマホなどの端末上で WiFiSSID とパスワードを入力する必要があります。
すぐに WiFi でインターネットにつなぎたいのにログインするまでが面倒くさい、そんな経験があるのではないでしょうか。
今回、お話しするサイト「WiFi Login Card」を利用することで、そんな面倒な作業を省略することができます。


gigazine.net
WiFi Login Card サイト wifi.dev.bdw.to

このサイトを活用することで、ユーザーはQRコードを読み込むだけで WiFi にアクセスできるようになります。
このQRコードは、上記サイトにアクセスし WiFi のネットワーク名とパスワードを入力することで作成することができます。
また、Print ボタンを押下することで、QRコード、ネットワーク名、パスワードが印字された紙を印刷できるようです。

この「WiFi Login Card」のような技術を活用することで、WiFi を提供する施設では施設利用者が簡単に WiFi を利用できるようになり、顧客満足度向上に貢献できそうですね。




※当ブログのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、
 誤情報が入り込んだり、情報が古くなっていることもございます。
 当ブログに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。


Web AR で物件を内覧できるサービスの話

専用アプリ不要で、AR で物件をバーチャル内覧できるサービスを株式会社 YONDE が開始しました。

プレスリリース prtimes.jp



従来の内覧サービスでは 360 度カメラで撮影した映像を見せるものが多い印象でしたが、こちらのサービスでは 物件の 3D (室内)を AR で見られるようにしています。

室内を等倍で内覧できるほか、俯瞰視点でも物件を確認できるようです。

f:id:OtyA:20200531175816g:plain
物件を上から見た図( プレスリリースより引用)



このサービスでは Web AR 技術を使っているため、Google Store や Apple Store からアプリをダウンロードする手間を必要とせず、ブラウザから閲覧が可能です。
この機能により、さらに気軽にコンテンツを体験することができます。


このように、Web AR を活用することで、ユーザーはより簡単に AR を体験できるようになります。
AR を体験するハードルが下がることで、製品の魅力や雰囲気を伝えやすくなり、販売促進につながることが期待されるでしょう。

上記のような理由から、今後は一般ユーザー向けの AR サービスは Web AR が中心になっていくことが予想されます。
これからの Web AR 技術の最新動向に注目です。




※当ブログのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、
 誤情報が入り込んだり、情報が古くなっていることもございます。
 当ブログに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。