【Unity2018】Google Cloud Visionを使用する

はじめに

Unityで画像認識したい!
Unityで文字認識したい!

そんな欲望を叶えてくれるのが、Google Cloud APIです。
今回は、このCloud Vision APIUnityで利用する方法についてみていきたいと思います。

◆必要なもの
* Unity(本記事では2018を使用)
* Googleアカウント
* クレジットカード(面倒くさいけど、無料アカウント登録に必要)

◆作るもの
* ウェブカメラに写っているもののデータを取得する。

調べたらこんなものもありました。。。 assetstore.unity.com

Google Cloud Vision とは

Google Cloud Visionは、Googleが提供している画像認識APIです。
手書き文字の認識や対象となる画像になにが写っているかなど、たくさんの情報を引き出すことができます。

Cloud Vision APIでできること

具体的にCloud Vision APIでできることは、以下のようになります。

項目 説明
ラベル検出 画像に写っているさまざまなカテゴリを検出できる
ウェブ検出 類似の画像をインターネットで検索できる
光学式文字認識(OCR 画像内のテキストを検出、抽出できる
対応言語の詳細はこちらから
手書き入力認識 手書き入力の認識ができる
ロゴ検出 画像ないの一般的な商品ロゴの検出ができる
Object Localizer 画像内の物体の位置と個数を確認できる
REST APIの統合 REST APIを使用できる?
ランドマーク検出 自然ランドマークや人工建造物の検出ができる
顔検出 画像内の人物の顔を検出できる。
感情、帽子の着用などの顔の属性も検出できる
※個人を特定する顔認識には対応していない
コンテンツの管理 アダルト、暴力など、画像内に含まれる不適切なコンテンツを検出できる
ML Kitの統合 モバイルSDKであるML Kitとの結合
商品検索 カタログに記載されている商品を認識できる
画像の属性 ドミナントカラーや切り抜きのヒントなど、画像の一般的な属性を検出できる
料金について

1〜1000ユニットであれば、無料で使用できます。
1000ユニットを超えた場合は、1000ユニットごとに1.5ドル請求されます。
アカウント登録すると300ドル相当のクレジットももらえるので、一人で試す分には問題ないでしょう。

その他詳しい料金体制はこちらをご参照ください。

Cloud Vision APIを触ってみる

なにはともあれ、まずは試しに触ってみましょう。
以下のページにアクセスしてください。
cloud.google.com

ページ中央付近、Try The APIの右横にある点線の長方形内にお好きな画像をD&Dします。
f:id:gunjyousky:20190125115648p:plain:w800

著作権的に大丈夫な、最近撮った霜柱の画像をD&Dしてみます。
『私はロボットではありません』にチェックを入れると、検出結果*1が表示されました。 f:id:gunjyousky:20190125115944p:plain:w800

上部のタブをクリックすると、その他の検出結果を見ることもできます。
『プロパティ』タブの中にある支配的な色なんて、ちょっとおもしろそうですよね。
f:id:gunjyousky:20190125121138p:plain:w800

Google Cloud Vision の無料トライアルに登録する

Google Cloud Vision APIを利用するにはm無料トライアルに登録する必要があります。
前述の通り、クレジットカードが必要になりますので用意してください。

Google Cloud Vision公式サイトにアクセス

cloud.google.com

無料アカウントを作成
  1. 『無料トライアル』ボタンをクリック
    f:id:gunjyousky:20190123182159p:plain:w800

  2. ステップ1/2

    • 国を洗濯
    • 利用規約を読んで同意する
    • 最新情報のメール通知を受け取るかどうかチェックする
    • 『AGREE AND CONTINUE』をクリック

    f:id:gunjyousky:20190123175132p:plain:w800

  3. ステップ2/2

    • お支払いプロファイルの設定
    • お客様情報を入力
    • お支払いタイプを選択
    • お支払い方法を選択
    • 『START MY FREE TRIAL』をクリック

    f:id:gunjyousky:20190124135433p:plain:w800

  4. 登録完了
    ・『OK』を押したら登録完了(やったね)

    f:id:gunjyousky:20190124144544p:plain:w800

APIキーを発行する

Google Cloud Vision APIをUnityで利用するには、APIキーは発行する必要があります。
発行したAPIキーをUnity側に適用することで、初めてGoogle Cloud Visionが使用できるようになります。

下記『Cloud APIサービスに対する認証』ページを開いてください。

cloud.google.com

開いた画面の中央付近いあるAPIキーを設定』をクリック
f:id:gunjyousky:20190125151817p:plain:w800

リンク先の画面中央、『[API Manager] → [認証情報] を使用する』をクリック
f:id:gunjyousky:20190125152108p:plain:w800

リンク先の画面上部、 『[API Manager] → [認証情報] 』をクリック
f:id:gunjyousky:20190125152438p:plain:w800

リンク先のAPIとサービスページ内にある、
『認証情報』タブ>『認証情報を作成』ボタンから『APIキー』を選択します。
f:id:gunjyousky:20190125152810p:plain:w800

APIキーを作成しました』モーダルが表示されれば成功です。
『閉じる』ボタンでモーダルを閉じてください。 f:id:gunjyousky:20190125154344p:plain:w800

以降このAPIキーを使用することになるので、ページを忘れないようにしてください。

Unityでpng/jpg画像認識をしてみる

それではさっそくUnityでGoogleCloudVisionを使用してみます。

CloudVisionAPI.csの作成

まず、Unityを起動したら、Assetsの中に新規フォルダ構成を作成し、Sceneを保存しましょう。
ここでは、CloudVisionTextと名前をつけてScenesフォルダ内に保存しています。

f:id:gunjyousky:20190225161214p:plain

次に、Scriptsフォルダ内に新規C#スクリプトを作成します。 スクリプト名をCloudVision.csとし、下記ソースコードを貼り付けて保存します。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Networking;
using System;
using System.Text;

public class CloudVisionAPI : MonoBehaviour
{
    public string url = "https://vision.googleapis.com/v1/images:annotate?key=";
    public string apiKey = "";
    public FeatureType featureType;
    public Texture2D texture2D;


    [System.Serializable]
    public class requestBody
    {
        public List<AnnotateImageRequest> requests;
    }

    [System.Serializable]
    public class AnnotateImageRequest
    {
        public Image image;
        public List<Feature> features;
        //public string imageContext;
    }

    [System.Serializable]
    public class Image
    {
        public string content;
        //public ImageSource source;
    }

    [System.Serializable]
    public class ImageSource
    {
        public string gcsImageUri;
    }

    [System.Serializable]
    public class Feature
    {
        public string type;
        public int maxResults;
    }

    public enum FeatureType
    {
        TYPE_UNSPECIFIED,
        FACE_DETECTION,
        LANDMARK_DETECTION,
        LOGO_DETECTION,
        LABEL_DETECTION,
        TEXT_DETECTION,
        SAFE_SEARCH_DETECTION,
        IMAGE_PROPERTIES
    }

    [System.Serializable]
    public class ImageContext
    {
        public LatLongRect latLongRect;
        public string languageHints;
    }

    [System.Serializable]
    public class LatLongRect
    {
        public LatLng minLatLng;
        public LatLng maxLatLng;
    }

    [System.Serializable]
    public class LatLng
    {
        public float latitude;
        public float longitude;
    }

    [System.Serializable]
    public class responseBody
    {
        public List<AnnotateImageResponse> responses;
    }

    [System.Serializable]
    public class AnnotateImageResponse
    {
        public List<EntityAnnotation> labelAnnotations;
    }

    [System.Serializable]
    public class EntityAnnotation
    {
        public string mid;
        public string locale;
        public string description;
        public float score;
        public float confidence;
        public float topicality;
        public BoundingPoly boundingPoly;
        public List<LocationInfo> locations;
        public List<Property> properties;
    }

    [System.Serializable]
    public class BoundingPoly
    {
        public List<Vertex> vertices;
    }

    [System.Serializable]
    public class Vertex
    {
        public float x;
        public float y;
    }

    [System.Serializable]
    public class LocationInfo
    {
        LatLng latLng;
    }

    [System.Serializable]
    public class Property
    {
        string name;
        string value;
    }

    // Use this for initialization
    void Start()
    {
        if (apiKey == null || apiKey == "")
            Debug.LogError("No API key.");

        //画像を送り解析する
        StartCoroutine("RequestVisionAPI");

    }
    /*-----------------------------------------------------------*
     * ◆GoogleCloudAPIと接続して画像を渡しJsonを返す
     *-----------------------------------------------------------*/
    private IEnumerator RequestVisionAPI()
    {

        if (this.apiKey == null) yield return null;

        // 画像をbase64Imageに変換する(GPUで処理できるようにするため)
        byte[] jpg = texture2D.EncodeToJPG();
        string base64Image = System.Convert.ToBase64String(jpg);

        // requestBodyを作成
        var requests = new requestBody();
        requests.requests = new List<AnnotateImageRequest>();

        var request = new AnnotateImageRequest();
        request.image = new Image();
        request.image.content = base64Image;

        request.features = new List<Feature>();
        var feature = new Feature();
        feature.type = featureType.ToString();
        feature.maxResults = 10;
        request.features.Add(feature);

        requests.requests.Add(request);

        // JSONに変換
        string jsonRequestBody = JsonUtility.ToJson(requests);

        // ヘッダを"application/json"にして投げる
        var webRequest = new UnityWebRequest(url + apiKey, "POST");
        byte[] postData = Encoding.UTF8.GetBytes(jsonRequestBody);
        webRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(postData);
        webRequest.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        webRequest.SetRequestHeader("Content-Type", "application/json");

        yield return webRequest.SendWebRequest();

        if (webRequest.isNetworkError)
        {
            // エラー時の処理
            Debug.Log("Error");
        }
        else
        {
            // 成功時の処理
            Debug.Log(webRequest.downloadHandler.text);

            responseBody responses = JsonUtility.FromJson<responseBody>(webRequest.downloadHandler.text.Replace("\n", "").Replace(" ", ""));

            foreach (var label in responses.responses[0].labelAnnotations)
            {
                Debug.Log("\ndescriptopn : " + label.description);
            }

        }
    }

}

この作成したスクリプトが、画像認識をしてくれるメインのスクリプトになります。
Hierarchyビューで右クリックしてCreateEmptyを選択します。
作成した空のゲームオブジェクトにAPIDirectorと名前をつけて、CloudVisionAPI.csをアタッチしましょう。

f:id:gunjyousky:20190225171403p:plain

CloudVisionAPIコンポーネントの値を設定する

CloudVisionAPI.csコンポーネントの値を以下のように調整します。

パラメータ名 意味
Url 通信を行うためのURL https://vision.googleapis.com/v1/images:annotate?key=
Api Key 発行したAPIキーをコピペする APIキーの取得方法
Feature Type 画像認識の方法を選択する
ラベル認識以外を使用する際は、CloudVisionAPI.cs上部の必要になてくるクラスが不足していると思いますので、各自修正してください
LABEL_DETECTION
Texture 2D テクスチャを画像認識させたい場合は、ここにD&Dする 認識対象となる画像

f:id:gunjyousky:20190228183518p:plain

Texture2Dとして使用する画像の設定

Texture2Dで使用できる画像の拡張子はjpgまたはpngになります。
(その他の拡張子は試していませんごめんなさい)
注意事項として、以下の2項目を設定する必要があります。

パラメータ名
Texture Type Default
Advanced > Read and Write Enabled ☑️

f:id:gunjyousky:20190225174338p:plain

エラーのヒント

ここまできたらあとは実行するだけです!
画像認識の結果はConsoleLogに出力されますので、さっそく実行してみましょう!

おっと、なにやらエラー文が表示されました。

{
  "error": {
    "code": 403,
    "message": "Cloud Vision API has not been used in project 813196382142 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/vision.googleapis.com/overview?project=813196382142 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.Help",
        "links": [
          {
            "description": "Google developers console API activation",
            "url": "https://console.developers.google.com/apis/api/vision.googleapis.com/overview?project=813196382142"
          }
        ]
      }
    ]
  }
}

UnityEngine.Debug:Log(Object)
<RequestVisionAPI>c__Iterator0:MoveNext() (at Assets/WebCamTextureCloudVision2.cs:191)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)

message部分を読んでみると、APIキーごとにはじめて実行したタイミングで認証手順が必要になるようです。 エラー文内にあるURLのページから、認証手続きをしてください。
認証手続き完了後、再度Unityを実行すればうまくいっているはずです。

実行結果

f:id:gunjyousky:20190225175928j:plain

ようやく環境が整ったので、上記の画像をUnity上で認識させてみたいと思います。
実行結果がこちら↓

f:id:gunjyousky:20190225180222p:plain

Freezing, Water, Hand, Ice, Winter

実行結果をみる限り、問題なくプログラムは動いているようです。
consoleの一番上に表示されているものが認識結果の生データです。
以降、認識結果のラベルのみを抽出して表示してみました。

霜柱(Frost Piller)が出なかったのは少し残念ですが、可能性は十分に確認できたかと思います。

Unityでウェブカメラの映像を画像認識してみる

最後に、ウェブカメラの映像を使って画像認識をしてみましょう。

Planeにウェブカメラの映像を表示させる

下準備として、Unity上に配置したPlaneにウェブカメラの映像を表示してみたいと思います。
詳細に関しては下記記事を参照してください。

linemarker.hatenablog.com

ウェブカメラの映像を渡す

ウェブカメラの映像を表示させることができましたか?
続いて、ウェブカメラの映像をCloudVisionAPI.csに渡すための対応をしていきます。
WebCamController.cs内にある、ウェブカメラの映像を渡す関数を追加します。
Texture2D型で渡すと都合がいいので、WebCamTexture型の生データをTexture2D型に変換して引き渡したいと思います。

完成したソースコードがこちら↓

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WebCamController : MonoBehaviour {
    public int width = 640;
    public int height = 480;
    public int fps = 30;
    WebCamTexture webCamTexture;
    Texture2D texture2D;


    public Texture2D GetWebCamTexture2D()
    {
        Color[] pixels = webCamTexture.GetPixels();
        if (pixels.Length == 0)
        {
            Debug.Log("webCam Error: no pixels");
        }
        else
        {
            if (texture2D == null || webCamTexture.width != texture2D.width || webCamTexture.height != texture2D.height)
            {
                texture2D = new Texture2D(webCamTexture.width, webCamTexture.height, TextureFormat.RGBA32, false);
            }
        }
        texture2D.SetPixels(pixels);
        return texture2D;
    }

    // Use this for initialization
    void Awake()
    {
        WebCamDevice[] devices = WebCamTexture.devices;
        for (var i = 0; i < devices.Length; i++)
        {
            Debug.Log(devices[i].name);
        }
        if (devices.Length > 0)
        {
            webCamTexture = new WebCamTexture(devices[0].name, this.width, this.height, this.fps);
            GetComponent<Renderer>().material.mainTexture = webCamTexture;
            texture2D = new Texture2D(webCamTexture.width, webCamTexture.height, TextureFormat.RGBA32, false);
            webCamTexture.Play();
        }
    }

}

13行目のGetWebCamTexture2D関数が引き渡し部分の本体になります。
34行目のウェブカメラを起動する関数がStartからAwakeに変更されている点にも注意してください。

ウェブカメラの映像を受け取る

ウェブカメラの映像を引き渡す処理は完成したので、受け取る側の記述もしていきましょう。
CloudVisionAPI.cs内の画像認識用Texture2DにGetWebCamTexture2D()関数を代入するだけで問題ありません。 ウェブカメラの画像を指定時間ごとに更新できるように修正します。

完成版のソースコードがこちら↓

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Networking;
using System;
using System.Text;

public class CloudVisionAPI : MonoBehaviour
{
    public string url = "https://vision.googleapis.com/v1/images:annotate?key=";
    public string apiKey = "";
    public FeatureType featureType;
    public Texture2D texture2D;
    public WebCamController webCamController;
    public float captureIntervalSeconds = 5.0f;


    [System.Serializable]
    public class requestBody
    {
        public List<AnnotateImageRequest> requests;
    }

    [System.Serializable]
    public class AnnotateImageRequest
    {
        public Image image;
        public List<Feature> features;
        //public string imageContext;
    }

    [System.Serializable]
    public class Image
    {
        public string content;
        //public ImageSource source;
    }

    [System.Serializable]
    public class ImageSource
    {
        public string gcsImageUri;
    }

    [System.Serializable]
    public class Feature
    {
        public string type;
        public int maxResults;
    }

    public enum FeatureType
    {
        TYPE_UNSPECIFIED,
        FACE_DETECTION,
        LANDMARK_DETECTION,
        LOGO_DETECTION,
        LABEL_DETECTION,
        TEXT_DETECTION,
        SAFE_SEARCH_DETECTION,
        IMAGE_PROPERTIES
    }

    [System.Serializable]
    public class ImageContext
    {
        public LatLongRect latLongRect;
        public string languageHints;
    }

    [System.Serializable]
    public class LatLongRect
    {
        public LatLng minLatLng;
        public LatLng maxLatLng;
    }

    [System.Serializable]
    public class LatLng
    {
        public float latitude;
        public float longitude;
    }

    [System.Serializable]
    public class responseBody
    {
        public List<AnnotateImageResponse> responses;
    }

    [System.Serializable]
    public class AnnotateImageResponse
    {
        public List<EntityAnnotation> labelAnnotations;
    }

    [System.Serializable]
    public class EntityAnnotation
    {
        public string mid;
        public string locale;
        public string description;
        public float score;
        public float confidence;
        public float topicality;
        public BoundingPoly boundingPoly;
        public List<LocationInfo> locations;
        public List<Property> properties;
    }

    [System.Serializable]
    public class BoundingPoly
    {
        public List<Vertex> vertices;
    }

    [System.Serializable]
    public class Vertex
    {
        public float x;
        public float y;
    }

    [System.Serializable]
    public class LocationInfo
    {
        LatLng latLng;
    }

    [System.Serializable]
    public class Property
    {
        string name;
        string value;
    }

    // Use this for initialization
    void Start()
    {
        if (apiKey == null || apiKey == "")
            Debug.LogError("No API key.");

        //画像を送り解析結果を受け取る
        StartCoroutine("RequestVisionAPI");

    }
    /*-----------------------------------------------------------*
     * ◆GoogleCloudAPIと接続して画像を渡しJsonを返す
     *-----------------------------------------------------------*/
    private IEnumerator RequestVisionAPI()
    {
        do
        {
            if (this.apiKey == null) yield return null;

            if (webCamController != null)
            {
                //更新遅延
                yield return new WaitForSeconds(captureIntervalSeconds);
                texture2D = webCamController.GetWebCamTexture2D();
            }

            // 画像をbase64Imageに変換する(GPUで処理できるようにするため)
            byte[] jpg = texture2D.EncodeToJPG();
            string base64Image = System.Convert.ToBase64String(jpg);

            // requestBodyを作成
            var requests = new requestBody();
            requests.requests = new List<AnnotateImageRequest>();

            var request = new AnnotateImageRequest();
            request.image = new Image();
            request.image.content = base64Image;

            request.features = new List<Feature>();
            var feature = new Feature();
            feature.type = featureType.ToString();
            feature.maxResults = 10;
            request.features.Add(feature);

            requests.requests.Add(request);

            // JSONに変換
            string jsonRequestBody = JsonUtility.ToJson(requests);

            // ヘッダを"application/json"にして投げる
            var webRequest = new UnityWebRequest(url + apiKey, "POST");
            byte[] postData = Encoding.UTF8.GetBytes(jsonRequestBody);
            webRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(postData);
            webRequest.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
            webRequest.SetRequestHeader("Content-Type", "application/json");

            yield return webRequest.SendWebRequest();

            if (webRequest.isNetworkError)
            {
                // エラー時の処理
                Debug.Log("Error");
            }
            else
            {
                // 成功時の処理
                Debug.Log(webRequest.downloadHandler.text);

                responseBody responses = JsonUtility.FromJson<responseBody>(webRequest.downloadHandler.text.Replace("\n", "").Replace(" ", ""));

                foreach (var label in responses.responses[0].labelAnnotations)
                {
                    Debug.Log("\ndescriptopn : " + label.description);
                }

            }
        } while (webCamController != null);
    }

}

UnityのInspectorビューに戻り、CloudVisionAPI.csコンポーネントに新たに追加された2つの変数を調整します。
Web Cam Controllerには、WebCamController.csのついたPlaneオブジェクトをD&Dしてください。
Capture Interval Secondsはウェブカメラの映像更新間隔ですので、とりあえず5秒にしておきましょう。
※Texture2DとWebCamController両方がセットされていた場合、ウェブカメラの方が優先されるプログラムになっています。

パラメータ名 意味
Web Cam Controller ウェブカメラの画像を認識させたい場合は、WebCamController.csを持つオブジェクトをD&Dする なし
Capture Interval Seconds ウェブカメラの画像を認識させる際の更新秒数 5

f:id:gunjyousky:20190228184128p:plain

実行結果

Unityの実行結果がこちら↓

f:id:gunjyousky:20190228185946p:plain

なるほどなるほど。
これで私に髪の毛が生えていることがバレてしまいましたね。

GoogleCloudVisionAPIの使い方は以上になります。

参考文献

qiita.com

tokyoz.koozyt.com https://assetstore.unity.com/packages/add-ons/machinelearning/google-cloud-vision-74812

*1:検出結果が日本語なのは、ページ内を日本語に翻訳しています。

【Unity2018】オブジェクトにWebCameraの映像を貼り付ける

はじめに

UnityでPlaneにウェブカメラの映像を貼り付ける方法についてです。
映像にエフェクトをつけたり3Dキャラを走らせたり夢を広げるための下準備です。

Let’s プログラミング

では、実際にプログラムを書いていきましょう。

スクリプトの作成

ウェブカメラの映像を取得して、テクスチャにセットするスクリプトを作成していきます。
ProjectウィンドウのAssetフォルダ内で右クリックしてCreate>C# Scriptを選択します。
新しく作成したC#スクリプトのファイル名をWebCamController.csにします。
作成したスクリプトに以下のプログラムを記述します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WebCamController : MonoBehaviour {
    public int width = 640;
    public int height = 480;
    public int fps = 30;
    WebCamTexture webCamTexture;

    // Use this for initialization
    void Start()
    {
        WebCamDevice[] devices = WebCamTexture.devices;
        for (var i = 0; i < devices.Length; i++)
        {
            Debug.Log(devices[i].name);
        }
        if (devices.Length > 0)
        {
            webCamTexture = new WebCamTexture(devices[0].name, this.width, this.height, this.fps);
            GetComponent<Renderer>().material.mainTexture = webCamTexture;
            webCamTexture.Play();
        }
    }

}

このプログラム内容は以下の通りです。

1. devicesにWebCamTexture.devicesで取得したカメラデバイスの配列情報を格納する。

大抵のパソコンにはカメラは一つしかないので、devices[0]に格納されます。
スマホの場合、外カメと内カメがあるので、取得したいカメラの要素数を確認してください。

2. webCamTextureに、取得したいカメラの画像データを受け渡す。

パラメータとして設定した幅、高さ、フレームレートが選択されたカメラにサポートされていない場合、最も近い使用可能な値が返される。

3. アタッチされたオブジェクトのマテリアルに設定されたmainTextureにウェブカメラのセットします。

4. ウェブカメラを再生します。

スクリプトのアタッチ

Hierarchyビュー上で右クリックして3D Object > Planeを作成します。
Planeに作成したスクリプトをD&Dすれば完成です。

映像が上下もしくは左右反転している場合
Plane>Transform>Scaleの値をマイナスにして反転させる。

映像が暗い場合
ProjectウィンドウのAssetフォルダ内で右クリックしてCreate>Materialを選択。
作成したマテリアルを選択し、Inspectorビュー>Shader>Unlit/Textureに変更。
Planeに適用する。

参考文献

こちらの記事がわかりやすいです。 nn-hokuson.hatenablog.com

【Unity2018】EmissionとBloomを使用してオブジェクトを光らせる

f:id:gunjyousky:20190107150720p:plain

はじめに、

あけましておめでとうございます。
Unity技術メモ始めました。
なにから書こうか迷ったのですが、年の初めということもあり『旧約聖書』にならって世界に光を実装する方法を記載したいと思います。

すばらしい先神たちによる福音書参考文献はこちら

初めに、神は天地を創造された。 地は混沌であって、闇が深淵の面にあり、神の霊が水の面を動いていた。 神は言われた。「光あれ。」こうして、光があった。 神は光を見て、良しとされた。神は光と闇を分け、光を昼と呼び、闇を夜と呼ばれた。夕べがあり、朝があった。第一の日である。

神は天地を創造された。

Unityで世界を創造してみましょう。
Unityを起動して、File > New Sceneから新しい世界を作ることができます。
ここでは試しにNewWorldという名前をつけて保存しています。
f:id:gunjyousky:20190102174348p:plain
Hierarchyビューをみてください。
すでにこの世界には2つの存在を確認することできますね。
これらは新しく世界を創造すると自動的に作られるものになります。

存在 詳細
Main Camera 世界をみるための(神の)目
Directional Light 世界を照らす光

さて、天地を創造するからには、地面をつくる必要があります。
Hierarchyビュー上で以下の処理をしてください。

右クリック > 3D Object > Plane 
InspectorビューでPlaneの位置を原点(0, 0, 0)にする

これで地面をつくることができました。
Hierarchyビューに新しくPlaneができていれば成功です。
ん?Gameビューに写っていないって?
Sceneビューに切り替えて、MainCameraの画角を調節しましょう。

Transform X Y Z
Position 0 2 -5
Rotation 30 0 0
Scale 1 1 1

f:id:gunjyousky:20190102181546p:plain

GameビューでもPlaneを確認できるようになりました。これで、下準備は完了です。

こうして、闇ができた。

さて、次はこの世界を真っ暗にする方法です。
いくつか手順があるので、順にみていきましょう。

1. Directional Lightを非アクティブにする

まず、Hierarchyビューの中に光となるオブジェクトが存在しますね。
このDirectional Lightを非アクティブにしてみましょう。

f:id:gunjyousky:20190101222535p:plain

おや、思っていたよりも暗くなりませんでした。 f:id:gunjyousky:20190102192453p:plain
Directional Lightの影響はなくなったものの、まだ何かしらの光の影響を受けているようです。
しかも、Hierarchyビュー上には存在しないソースのようです。
探してみましょう。

2. 拡散環境光の設定を変更する

調べてみると、どうやら拡散環境光(アンビエントライト)のせいで明るくなっているようです。

docs.unity3d.com

拡散環境光は、光源を持たず、世界全体を照らす光です。
そのため、特定のソースオブジェクトを持っていません。
設定は下記ウィンドウより変更できます。
Window > Rendering > Lighting Settings

f:id:gunjyousky:20190102212950p:plain

Lighting SettingsウィンドウのSceneタブ内にあるEnvironment配下の設定を変更します。

Environment 変更前の値 変更後の値
Environment Lighting > Intensity Multiplier 1 0
Environment Reflections > Intensity Multiplier 1 0

Environment Lightingはライトを配置する前のSceneの明るさを決める拡散環境光を表しており、Intensity Multiplierで明るさの度合いを変更することができます。
Environment Reflectionsは拡散環境光の反射光を表しており、Intensity Multiplierで反射光の映り込み具合を変更することができます。

f:id:gunjyousky:20190102213942p:plain

これでPlaneにかかる拡散環境光とその反射光は0になり、大地は暗闇に覆われました。
あとは大空を暗くするだけです。

3. カメラの背景設定を変更する

Scene上で奥行き方向に最も遠い空間に何を描画するかは、カメラから設定することができます。
Main CameraをクリックしてInspectorビューからCameraコンポーネントを確認すると、一番上にClear Flags という設定があります。
これが、カメラの背景を設定している項目です。
今回は世界を暗闇で覆いたいので、何も描画しないDon't Clearを選択肢します。
その他の項目の意味を理解したい場合は、下記を参照してください。

Clear Flags 意味
Skybox スカイボックスを使用する
Solid Color 特定の色で塗りつぶす
Depth Only 前のフレームに残っていた色か、前に表示されていた何かを残す
Don't Clear 何も表示しない

docs.unity3d.com

これで、世界は闇に包まれました。 f:id:gunjyousky:20190102220418p:plain

Emissonによって、オブジェクトに光が宿った。

ゲームオブジェクトを光らせる方法についてみていきます。
Sceneが暗いと作業しにくいので、<u><b>Lighting Settingsウィンドウ > Sceneタブ > Environment配下の設定を元に戻しましょう。
これまでの作業はなんだったのか・・・

Environment デフォルトの値
Environment Lighting > Intensity Multiplier 1
Environment Reflections > Intensity Multiplier 1

次に、光らせるオブジェクトとそれを取り囲むような左右奥3面の壁を用意します。

光らせるオブジェクト(Cube)

Transform X Y Z
Position 0 0.5 0
Rotation 0 0 0
Scale 1 1 1

左右奥の3面の壁
反射光のテストに使用するため、適当な大きさで光らせるCubeを取り囲むように配置する。
もし画角が悪いようであれば、カメラをX軸回転させて調節してください。
※図のカメラはX軸に20度回転させています。

f:id:gunjyousky:20190103014437p:plain

では実際のCubeを自己発光させたいと思います。

1. 自己発光用マテリアルを作成する

Cubeを光らせるには、自己発光用のマテリアルを作成してアタッチする必要があります。
Projectビュー > 右クリック > Create > Materialを選択して、新しくマテリアルを作成してください。
f:id:gunjyousky:20190103015358p:plain

ここでは新しく作成したマテリアルに、仮にM_Emissionという名前をつけました。
さて、マテリアルとはどのような質感の素材なのかを設定するためのものになります。
作成したマテリアルをクリックし、Inspectorビュー > Emissionにチェックをいれましょう。
f:id:gunjyousky:20190103015919p:plain

これで、マテリアルに自己発光(Emission)という質感を追加することができました。
どんな色で自己発光するかは、Emission下のColorから設定できます。
また、Colorの左のアイコンから白黒の画像を選択すると、そのテクスチャの白い箇所のみを発光させることができます。
f:id:gunjyousky:20190103140815p:plain

ここではテクスチャを使わず、RGB =(1, 1, 1) の白色を設定したいと思います。
これで、マテリアルの設定は完了です。

Sceneビューを開き、光らせたいCubeにマテリアルをD&Dしましょう。
Cubeの色が白色になれば、うまく適用できています。
f:id:gunjyousky:20190103021046p:plain

さて、Gameビューをみてみましょう。
f:id:gunjyousky:20190103021152p:plain

・・・おっと、拡散環境光を消し忘れていたため壁と床がはっきりと見えてしまっています。
拡散環境光の影響を受けないようにしてみましょう。

Window > Rendering > Lighting Settings > Sceneタブ > Environment

Environment 変更後の値
Environment Lighting > Intensity Multiplier 0
Environment Reflections > Intensity Multiplier 0

f:id:gunjyousky:20190103021802p:plain

Cubeのみ白く光っていますね。
無事オブジェクトを自己発光させることができました。

2. 反射光を設定する

Cubeの反射光を設定していきましょう。
反射光とは、Cubeを光源としたときの周りのオブジェクトへの映り込みになります。
Cubeを光源として壁や床を照らし出してみましょう。

設定は簡単です。
光源となるオブジェクトと、その影響を受けるオブジェクトをStaticにするだけです。
f:id:gunjyousky:20190103134525p:plain

光源となるCube、床、壁オブジェクトをStaticに変更しました。
f:id:gunjyousky:20190103134748p:plain

床や壁を照らし出すことができました。

最後に、staticな球体を空間に追加してみたいと思います。 f:id:gunjyousky:20190103135515p:plain

Cubeの光が綺麗に球体に写り込んでいますね。
左側の壁に球体の影も確認できます。

これで、反射光に関する設定は完了です。

Bloomによって、光が滲んだ。

表現をより豊かにしてみましょう。
自己発光している物体は、眩しくて光が滲んで見えますよね。
この光の滲みを実装してみたいと思います。
f:id:gunjyousky:20190104013111j:plain

1. HDRを有効にする

Bloom処理を行うには、カメラのHDR設定をアクティブにする必要があります。 docs.unity3d.com Main Camera > Cameraコンポーネント > Allow HDR をアクティブにしてください。

f:id:gunjyousky:20190107113218p:plain

2. Legacy Image Effectsをインポートする

光を滲ませるには、Bloomという処理が必要になってきます。
このBloom処理は、スクリプトから制御できます。
AssetStoreからLegacy Image Effectsをインポートしてください。

f:id:gunjyousky:20190104015019p:plain

3. Bloomエフェクトをアタッチする

インポートが終了したらカメラにBloomスクリプトを追加しましょう。

docs.unity3d.com

Main Camera > InspectorビューのAdd Componentをクリック > 検索欄に『Bloom』と入力します。
出てきた『Bloom』スクリプトをクリックしてcomponentに追加しましょう。 f:id:gunjyousky:20190107141818p:plain

ちなみにBloomと名のつくスクリプトは3種類ありますが、違いは以下のようになります。

スクリプト 内容
Bloom 今回使用する最も拡張されたスクリプト
Bloom Optimized 高速に動くよう最適化されたスクリプト
Bloom And Flares 同時にレンズフレアも自動生成の設定も出来るスクリプト

Bloomの設定項目についてみていきましょう。
今回はMode:Basicでの設定項目をみていきます。 f:id:gunjyousky:20190107141937p:plain

設定項目 デフォルト設定 説明
Quality High High で高品質、高周波数を維持し、エイリアシング低減します。
Mode Basic 高度なオプションを表示するには Complex モードを選択します。
Blend Add カラーバッファに Bloom を追加するために使用するメソッド。
HDR Auto Bloomの計算結果によってはHDRバッファを使用することがあるので、Onにしておく。
Intensity 0.5 Bloomの強度。
Threshold 0.5 Bloomの閾値。画像領域でこの値よりも明るい領域にBloomが適用される。
Blur iterations 2 ガウスブラーが適用される回数。
Sample distance 2.5 ブラーの最大半径。
4. Bloomとマテリアルの値を調節する

最後に、Bloomの設定とマテリアルを調節していい感じに仕上げましょう。

マテリアルの設定

設定項目
R 1
G 1
B 1
Intensity 0

※MaterialのIntensityを調節することで反射光を制御できます。

Bloomの設定

設定項目
Quality High
Mode Basic
Blend Add
HDR On
Intensity 0.5
Threshold 0.5
Blur iterations 2
Sample distance 2.5

※BloomのIntensityを調節することでボカシ具合の強度を制御できます。

Bloomなし
f:id:gunjyousky:20190107115906p:plain

Bloomあり
f:id:gunjyousky:20190107120033p:plain

以上、これで光のボカシを実装することができました。

参考文献

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

gametukurikata.com