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

のしメモ アプリ開発ブログ

Unityアプリとかロボットとか作ってるときに困ったこととかメモ

Unityで学ぶ画像処理【色の変換編】

Unityにはピクセルごとに色を取得することができるので、Unityは画像処理もできるのです。
以前の記事はこちらです。Texture2D書き換え周りの基本的な処理はこちらをご参考に。

今回はInterfaceの5月号の画像処理特集のアルゴリズムを読みながら、Unityでコードを書いて学んでいきたいと思います。

こんな人におすすめ

・画像処理の仕組みを学びたい
・様々なプラットフォームで、スタンドアロンで動作する画像処理がしたい

今回はUnityのTexture2Dを使った画像処理について紹介していきます。
リアルタイムで行う場合はPostProcessingやShaderで処理するとよいですが、今回は説明しません。

ちなみに、360度画像からおっさんを消す「VANISH360」もUnityのTexture2Dクラスを使っておっさんを消しています。

RGB色入れ替え

実行結果

R->G, G->B, B->R

f:id:noshipu:20170524215431p:plain

R->B, G->R, B->G

f:id:noshipu:20170524215920p:plain

コード

R->G, G->B, B->R

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        outputColors[(width * y) + x] = new Color(color.g, color.b, color.r);
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

RGBの色を入れ替えるので、結構大きく色調が変わるものの、大きくバランスが崩れることはない感じです。

モノクロエフェクト

実行結果

f:id:noshipu:20170524222618p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        float average = (color.r + color.g + color.b) / 3;
        outputColors[(width * y) + x] = new Color(average, average, average);
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

RGB色の平均値を計算し、全ての色の同一の強さにすることでモノクロにすることができます。
画素値が同一の強さになることで、強調する色がなく、モノクロの状態にすることができます。

セピアエフェクト

実行結果

f:id:noshipu:20170524223254p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        float average = (color.r + color.g + color.b) / 3;
        outputColors[(width * y) + x] = new Color(average, average * 0.8f, average * 0.55f);
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

モノクロの拡張版です。
モノクロの状態である平均値に対して、セピアの明度を各色に乗算してあげることでセピアな感じを出すことができます。
モノクロ画像をセピアにするには(Rx1, Gx0.8, Bx0.55)を乗算する。

ポスタリゼーションエフェクト

実行結果

split=5

f:id:noshipu:20170524231006p:plain

split=2

f:id:noshipu:20170524231114p:plain

split=3 && モノクロ

f:id:noshipu:20170524231607p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];

int split = 3;
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        for (int i = 0; i < split; i++)
        {
            float col1 = i * (1f / (float)split);
            float col2 = (i + 1f) * (1f / (float)split);
            if(col1 <= color.r && color.r <= col2)
            {
                color.r = (col1 + col2) / 2f;
            }
            if (col1 <= color.g && color.g <= col2)
            {
                color.g = (col1 + col2) / 2f;
            }
            if (col1 <= color.b && color.b <= col2)
            {
                color.b = (col1 + col2) / 2f;
            }
        }
        outputColors[(width * y) + x] = color;
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

ポスターっぽく。漫画エフェクトみたいな画像処理です。
色の段階を減らすことによって、色のバリエーションが少なくなり、ポスターやイラストっぽい雰囲気を出すことができます。
splitで0から1で設定できる色調を区切りをつけて設定させます。

ソラリゼーションエフェクト

実行結果

f:id:noshipu:20170525020839p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        if(color.r <= 0.5f)
        {
            color.r = 2 * color.r;
        }
        else
        {
            color.r = 1 * 2 - 2 * color.r;
        }
        if (color.g <= 0.5f)
        {
            color.g = 2 * color.g;
        }
        else
        {
            color.g = 1 * 2 - 2 * color.g;
        }
        if (color.b <= 0.5f)
        {
            color.b = 2 * color.b;
        }
        else
        {
            color.b = 1 * 2 - 2 * color.b;
        }
        outputColors[(width * y) + x] = color;
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

画素値の差を強調できる処理で、画素値が高いものは色の反転を起こし一部がネガっぽい感じになっています。
複数回を折り返すパターンもできるので、結果がどのよう状態になるのか、予測がなかなかつかないので楽しいですね。

モザイクエフェクト

実行結果

size=11

f:id:noshipu:20170525025152p:plain

size=27

f:id:noshipu:20170525025333p:plain

size=151

f:id:noshipu:20170525025435p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];

int size = 151;
for (int y = (size - 1) / 2; y < height; y = y + size)
{
    for (int x = (size - 1) / 2; x < width; x = x + size)
    {
        float colorR = 0f;
        float colorG = 0f;
        float colorB = 0f;

        for (int j = y - (size - 1) / 2; j <= y + (size - 1) / 2; j++)
        {
            for (int i = x - (size - 1) / 2; i <= x + (size - 1) / 2; i++)
            {
                if (i >= 0 && j >= 0 && i < width && j < height)
                {
                    colorR += inputColors[(width * j) + i].r;
                    colorG += inputColors[(width * j) + i].g;
                    colorB += inputColors[(width * j) + i].b;
                }
            }
        }

        colorR = colorR / (size * size);
        colorG = colorG / (size * size);
        colorB = colorB / (size * size);

        for (int j = y - (size - 1) / 2; j <= y + (size - 1) / 2; j++)
        {
            for (int i = x - (size - 1) / 2; i <= x + (size - 1) / 2; i++)
            {
                if (i >= 0 && j >= 0 && i < width && j < height)
                {
                    outputColors[(width * j) + i] = new Color(colorR, colorG, colorB);
                }
            }
        }
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

指定のピクセルから周辺の色を取得し、平均値を計算して周辺のピクセルに割り当ててあげることで、ブロック単位で色が変わりモザイクになります。

コードはGithubで公開してます

今回使用したUnityプロジェクトはGithubにのっけています。今後も更新予定です。

最後に

画像処理楽しい!
続きも近いうちにやります。

GearVRコントローラーをUnityで使う

GearVRコントローラーを使う方法についてメモです。

GearVRコントローラーがついに日本で発売となり、そのGearVRコントローラーに対応した「ZOMBIE ELEVATOR」が発売されました!
リアル系なゾンビシューティングゲームです。是非やってみてください。

OculusStoreリンクはこちら

【本題】GearVRコントローラーをUnityで使う

1. GearVR用にプロジェクト設定をしておく

PlayerSettingsでPlatformがAndroidになっていることを確認するなど、色々設定項目を確認。
今回は説明をスキップします。

2. OculusUtilities.unitypackageをダウンロード

OculusのサイトからSDKをダウンロードしてくる
https://developer.oculus.com/downloads/package/oculus-utilities-for-unity-5/

3. OculusUtilities.unitypackageをインポートする

ダウンロードしたファイルを解凍し、対象のプロジェクトにインポートする

4. OVRCameraRigを配置

既存のメインカメラを削除し、
Assets/OVR/Prefabs/OVRCameraRigをシーンに配置
これだけでLeftHandAnchor、RightHandAnchorのGameObjectにコントローラーの動きがトラッキングされるようになる

5. GearVRコントローラーのモデルを表示する


GearVRのコントローラーを表示させたい場合

LeftHandAnchorにAssets/OVR/Prefabs/GearVrControllerを配置して、OVRGearVRControllerのControllerの項目をLTrackedRemoteに変更

右手の方も同様にRightHandAnchorにAssets/OVR/Prefabs/GearVrControllerを配置して、OVRGearVRControllerのControllerの項目をRTrackedRemoteに変更する

Hierarchyはこんな感じに

※ 他のモデルを利用する場合は他のモデルをHandAnchorの子供に入れたり、HandAnchorの位置をスクリプトで追従させるなどで対応できる

6. 入力周りのコード

Assets/OVR/Scenes/GearVrControllerTestのシーンを見るとデモシーンが見れる
デモシーンのOVRGearVrControllerTest.csでは様々な数値の取得していることが確認できる

・単純なクリックだけであればFire1等で取得することも可能

・OVRInputからOculusRiftと同じような形で入力を受け取ることが可能

// 右利き用のコントローラーが接続されているか
OVRInput.IsControllerConnected(OVRInput.Controller.RTrackedRemote);

// トリガーの取得
OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger);

・コントローラーを検知して分岐処理を入れたりも

// コントローラーの検知
var controller = OVRInput.GetActiveController();
if(controller == OVRInput.Controller.LTrackedRemote)
{
    // 左利き処理
}
else if(controller == OVRInput.Controller.RTrackedRemote)
{
    // 右利き処理
}
else
{
    // コントローラーが接続されていない場合の処理
}

7. デバッグについて

OculusRift+OculusTouchでUnityEditor上での動作確認ができる
GearVRの実機で見るのが一番だが、通常の確認はOculusRift+OculusTouchでOK

8. GearVRコントローラーが使える

すごい便利。Gaze卒業!

GearVRコントローラーを利用したアプリを作る上で気をつけたいことメモ

・GearVR含め、コントローラーはポジショントラッキングはしていないので、歩き回るようなコンテンツは作れない。
・360度回転するようなコンテンツは、コントローラーのトラッキングがずれていく。ゾンビエレベーターも真後ろは出現しないようにしている。
・コントローラーを全員が持っている訳ではない。まだまだ持ってないGearVRユーザーが多いので、コントローラーがない場合の対応も入れたほうがいい。
・コントローラーは2つ同時に接続できない。
・右利き、左利きの場合の見え方を検討しないといけない。右手のモデルが標準のゾンビエレベーターでは、左利きの場合は手のスケールを-1して反転させている。

Unityで作ったVRアプリをViveportで配信する手順

OculusRift向けにリリースしていたMakeboxをHTC Viveに対応し、Viveportでリリース対応したときのメモ。
既に作っているVRアプリをViveportでリリースしたい人は参考にしてみてください。

Makeboxとは

ViveportにてMakeboxをリリース
Makeboxは簡単にVoxelモデリングができるVRアプリケーションです。

ダウンロードはこちらから

【本題】Viveportへの配信手順

おおまかな流れ

1. 開発者登録する
2. SteamVRを入れる(SteamVR必須ではないかも?要確認)
3. DRM対応をする
4. 申請する
5. 配信される

ざっくりと説明していきます。

続きを読む

Unity5.6でDayDreamのデモを動かす

Daydreamのコントローラーを使ったデモを見るための手順をメモ

環境

Unity 5.6.1f1
gvr-unity-sdk 1.40.0

手順

1. gvr-unity-sdkをダウンロード

リリースしている1.40.0をダウンロードします
https://github.com/googlevr/gvr-unity-sdk/releases

2. サンプルプロジェクトを開く

Unityで`gvr-unity-sdk-1.40.0\Samples\DaydreamLabsControllerPlayground`のプロジェクトを開く

3. Target PlatformをAndroidに変更

Build SettingsからAndroidにSwitchPlatformをする

4. AndroidAPIレベルを設定

PlayerSettingsからMinimum API Level of 24に設定する

5. AndroidManifestを差し替え

Assets/Plugins/Android/AndroidManifest-Daydream.xmlをAssets/Plugins/Android/AndroidManifest.xmlに書き換え

6. ビルドをして端末で確認する

デモ動作する