20200218のUnityに関する記事は8件です。

【UnityでOculus Quest向けのアプリを作る】ハンドトラッキングで物を掴む

◆ はじめに

前回、ハンドトラッキングの実装しました。
なので、今回は物を掴む方法について書きます。

手の動き、指の取得は
OculusQuest ハンドトラッキングSDKから、指Boneの情報を取得し分析する
こちらの記事を参考にしてください。

必要あれば前の記事を参考してください。
Oculus Quest向けのAPPをビルドして、動作確認する
物を掴む為には(物体の制作)
ハンドトラッキングの実装

今回の記事ですが、
完全に自己流の方法でやっているので、無理矢理感がある為、要注意です。

◆ 開発環境

macOS Mojave バージョン 10.14.6
Unity 2018.4.12f1
Android SDK
ハンドトラッキング機能を使用する為、
Oculusのバージョンは(Ver12)以上、にアップデートする必要があります。

◆ 手順

  • 準備した物の説明
  • 掴む実装方法説明
  • 指動作の判断
  • 論理およびコード説明]
  • 動作確認

準備した物の説明

スクリーンショット 2020-02-17 18.43.04.png
まず、必要なObjectを準備しました。
ハンドトラッキングを実装して、物体(Cube)とテーブルを用意します。
Cubeのサイズは自由に設置しても構いません。

プログラムがちゃんと動いてるかどうか、
結果を見やすくする為に、Canvasを作りました。必須ではないですが、あった方が便利です。
また、テーブルも必須ではないです。

掴む実装方法説明

以前の物を掴む為にはでは、
無事に手を表示後普通に物体を掴む事ができますが、ハンドトラッキングではできないらしいです。
02.png
なので、
今回やるべき事は手が物体を当たった状態で、掴むという動作をした時、物体を手の階層に入れる。
要するに、物体を手の子要素にするという事です。

勿論、
子要素にするのではなく、掴む判定の時、物体のポジションを手と同じにするというのも方法の一つです。

指動作の判断

ovrHand.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ovrHand : MonoBehaviour
{
    private OVRHand _ovrHand;
    // 掴む状態
    public bool _catching;

    // Start is called before the first frame update
    void Start()
    {
        _catching = false;
        _ovrHand = this.gameObject.GetComponent<OVRHand>();
    }
    // Update is called once per frame
    void Update()
    {
        if (_ovrHand.GetFingerIsPinching(OVRHand.HandFinger.Index))
        {
            _catching = true;
        }
        else
        {
            _catching = false;
        }
    }
}

上記のコードは「OVRHandPrefab」に追加します。
手を取得し、変数「_ catching」を作ります。「_ catching」の説明は後で

GetFingerIsPinchingは、指先と指先が触れているかどうかを取得する事ができます。
OVRHand.HandFingerは、親指とどの指が触れているかを判定できます。今回は人差し指にします。

今回は、指先と指先が触れてたら「_catching」は真にします。
状態はCanvas上に表示します。
(Canvas作ってないなら書く必要はないです、この後にCanvas関する説明も全部省略します)

論理およびコード説明

cubeTouch.cs
    private ovrHand _ovrHand;
    private bool _touchIN;      //当たり
    private bool _catch;        //掴む
    private bool _hold;         //取れる

    // Start is called before the first frame update
    void Start()
    {
        _ovrHand = FindObjectOfType<ovrHand>();
    }
void Update()
    {
        // 手は選択状態なのかを常に取得する
        _catch = _ovrHand._catching;
        touchCube();
        CatchCube();
    }
void touchCube()
    {
        if (_touchIN == true && _catch == false)
        {
            _hold = true;
        }
    }

    void CatchCube()
    {
        if (_hold == true && _catch == true)
        {
            _touchIN = false;
        }
        else if (_touchIN = false && _touchIN == false)
        {
            _hold = false;
        }
    }

まず、掴むという動作について考えます。

手は握った状態で物体に当たったら、物体をつかめる。
物体に当たった後、手を握ると、物体をつかめる。

このことを、上記のコードで表しています。

cubeTouch.cs
void OnCollisionStay(Collision other)
    {
        // 物体をタッチした時 タッチ変数は 真になる
        if (other.gameObject.name == "Hand_Index3_CapsuleRigidBody" ||
            other.gameObject.name == "Hand_Index2_CapsuleRigidBody" ||
            other.gameObject.name == "Hand_Thumb3_CapsuleRigidBody" ||
            other.gameObject.name == "Hand_Thumb2_CapsuleRigidBody" 
            )
        {
            _touchIN = true;
            // 位置固定し物理削除する
            gameObject.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeAll;
            gameObject.GetComponent<Rigidbody>().isKinematic = true;
        }
    }

上の説明に合わせ、
まず、物体をタッチしないと始まらないので、当たり判定書きます。

それぞれの指の名前は一番上の参考リンクを参照してください。
タッチしたら、「 _touchIN」は真になります。

cubeTouch.cs
void CatchCube()
    {
        if (_hold == true && _catch == true)
        {
            Debug.Log("_catch = " + _catch + " 物を掴みました!");
            // 右手の子供になる
            gameObject.transform.SetParent(_rightHandAnchor.gameObject.transform);
            // 重力抜き 当たり判定しない
            gameObject.GetComponent<Rigidbody>().useGravity = false;
            gameObject.GetComponent<BoxCollider>().isTrigger = true;
            _touchIN = false;
        }
        else if (_touchIN = false && _touchIN == false)
        {
            // 親関係解除
            gameObject.transform.parent = null;
            // 重力 当たり判定 復帰
            gameObject.GetComponent<Rigidbody>().useGravity = true;
            gameObject.GetComponent<BoxCollider>().isTrigger = false;
            // 位置移動可 物理復帰
            gameObject.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.None;
            gameObject.GetComponent<Rigidbody>().isKinematic = false;
            _hold = false;
        }
    }

今回のCubeは物理演算を入れているので、掴む時は物理演算を抜く必要があります。
「CatchCube()」の中に必要な処理を追加します。
コードの意味は 、コメントアウトで書いているので参考にしてください。

最後に、「cubeTouch.cs」をCubeの中に入れば完成です。

動作確認

com.oculus.vrshell-20200218-204038_1_1.gif

ビルドして、動作確認するとこうなります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OculusQuestのハンドトラッキングで指ジェスチャに色々つけたら大混乱

はじめに

Oculus Quest がアップデートでハンドトラッキングに対応してます。
ハンドトラッキングでは、コントローラ等一切無しで自分の手・指のみでVR操作ができるようになります。
SDKとしては各手・各指関節の曲がり具合が取得できるのですが、
これをアプリの操作に利用すれば 各指の伸ばす・曲げるにボタン割当すれば10指10ボタンまでいける

じゃ指10本を思うがままに個別に動かせるのかというとそんなわけないだろと..
この記事はそんなハンドトラッキングとアプリ操作について考察・試行錯誤した事例を紹介するものです。

(なおこの記事は 2020/1月から書いていた記事 "ついにでたOculusQuestハンドトラッキング×VRMでアバター空間満喫してみた" からの派生記事です)

ハンドトラッキング・指操作についての考察

ハンドトラッキングで指に操作割り当てしようとすると、色々課題にぶつかります。
大きく人間側起因の課題と、ハンドトラッキング起因の課題があるなかで気づいたものを整理しておきます。
(なお摘まむジェスチャをピンチと呼んでます)

  • 人が自然に独立して動かせる指と・そうでない指がある
    • 人によってできる・できない違いそうなのですが、私の場合こんな具合
      • 親指+他指のピンチ
        • 人差指 : いける。
        • 中指 : 一緒に薬指・小指まがっちゃう
        • 薬指 : 一緒に中指曲がっちゃう
        • 小指 : できるけどちょっと苦しい
      • 親指+他指2本のピンチ
        • 人差指・中指 : 一緒に薬指曲がっちゃう
        • 中指・薬指 : いける
        • 薬指・小指 : 一緒に中指曲がっちゃう
      • 親指+他指3本のピンチ
        • 人差指・中指・薬指 : いける
        • 人差指・薬指・小指 : 苦しい
        • 人差指・中指・小指 : 苦しい
        • 中指・薬指・小指 : いける
  • OculusQuest のハンドトラッキングで得意・苦手な手がある
    • 手を静止してジェスチャ
      • 視界から奥のほうの隠れた指がプルプルする..
    • ジェスチャしたまま手を動かす
      • 薬指ピンチしようとするとプルプル..
    • 他、様々 ”普通やらないでしょ” ってジェスチャほどプルプル..
    • LeapMotion同様、ハンドトラッキングもののデバイスあるあるだと思うので、これはそういうものと 割り切ったうえでアプリデザイン考えることに。

指の操作をつたえるの、とても大変

サンプルアプリ作って試してもらったら..

自分以外の人に使ってみて貰おうとしたら、これがとても大変。
なにせ、各種指ジェスチャを呼ぶ名前が無くって、さらにはユーザーとの概念の共通認識もなくって。

  • ”右手の五本の指を全部開いて、そこから人差し指と親指だけ閉じてくっつけて”=ピンチ
  • ”目の前に見えてる四角い箱をピンチすると掴めて”=ピンチしたままドラッグ?
  • "ピンチしてたの離す”=ピンチ..アウト??
  • 指で摘まむ=ピンチイン, 摘まんだまま動かす=ピンチドラッグ, 摘まんだの離す=ピンチアウト、とか?

基本のクリック、ドラッグ操作すら伝えるの難しい。
それが済むと今度 ”それを指でピンチして” というと手5指でグリップしちゃって別操作になったり。

今回のサンプルアプリは使える操作として、

  • "左手の中指をロングピンチするとメニューon/off"
  • "左手の人差指・中指でピンチドラッグするとワールド全体移動”
  • "左手・右手を視界正面からちょっと外側でピンチドラッグするとワールド全体拡大縮小"

なんてのも付けてみたのだけど、ここまでくると解説動画でもないと理解してもらえる気がしない..。
上手なアナウンスと操作に対する習熟が必用か..。

ここまでくるとハンドトラッキングめんどくさくない?コントローラ使えばいいじゃんってなりそうなんですが、
しかしコントローラから解き放たれたVR-HMDの使うの簡易さといったら、もうこれなしじゃ嫌なわけですよ。
なのでなんとかこれに慣れていきたい・慣らしていきたい次第です。

今回のサンプルの使ってるとこ動画(twitter)

まとめ

  • ハンドトラッキングは指操作できる素敵夢技術
  • 指でおこなう空間操作にまだ馴染みがないので、どの指操作は多数の人が普通にできるのか知見が欲しい。
  • インサイドアウトのデバイスでのハンドトラッキングには、手の姿勢によって検出の得意・不得意あるので、 どの指操作は検出しやすいのかの知見が欲しい。
  • 指で行うジェスチャー群に一般的な名称がついて、みんな分かるくらいまで普及してほしい。
    • マウスが出て右クリック・ダブルクリック・ドラッグ&ドロップが普及、スマホでフリック入力が普及したように。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VRoidモデルの表情調整と表情データの再利用方法

VRoidの標準の表情が強すぎる

VRoidの標準で設定されている表情がこちらなのですが……
Vroid_表情一覧.png
なんというか、こう、強 い 。
全体的にもう少しナチュラルな表情にするためにUnityを起動して調整していきます。
また、VRMファイルはVRoid Studioで再読み込みすることはできないため、着替えなどを行った際に今回作った表情を再利用する方法についても説明していきます。

準備

  • 新規プロジェクトの作成
    Unityを起動して3Dの新規プロジェクトを作成します。
    スクリーンショット 2020-02-18 16.19.51.png

  • UniVRMのインポート
    こちらから UniVRM-x.xx.x_ce1c.unitypackageというファイルを選択してダウンロードします。
    x.xx.xの部分はバージョン番号です。基本的に最新版で問題ないかと思います。
    ダウンロードが完了したらファイルをダブルクリックすると下記のような画面が出るのでImportボタンを押します。
    スクリーンショット 2020-02-18 16.28.08.png

  • VRMファイルの読み込み
    UniVRMのインポートが完了したら、UnityのAssetsフォルダへVRMファイルをドラッグ&ドロップして読み込みます。読み込みには数秒〜数分かかりますのでファイルが生成されるまで少しだけ待ちましょう。
    スクリーンショット 2020-02-18 16.31.53.png

表情の調整

  • BlendShapesの表示
    Assetsに生成された[モデル名].BlendShapesを選択し、中にあるBlendShape.assetを選択してください。
    Inspectorに表示されたSelect BlendShapeClip下のボタンを押すとその下に表示された画面がボタンに対応した表情に切り替わります。
    スクリーンショット 2020-02-18 16.46.57.png

  • 表情の調整
    Select BlendShapeClipから編集したい表情を選択します。今回は怒った顔を変更するのでAngryを選択しています。
    下の方にあるFaceという文字をクリックすると表情パラメータのリストが見えるようになるので、このパラメータを修正していきます。
    Angryの初期値はFace.M_F00_000_00_Fcl_ALL_Angryが100になっています。
    スクリーンショット 2020-02-18 17.13.34.png

今回は下記のようにFace.M_F00_000_00_Fcl_ALL_Angryを0にして眉、目、口のパラメータをそれぞれ下記画像のように変更しました。
スクリーンショット 2020-02-18 17.12.59.png

調整した結果は下記のようになります。
スクリーンショット 2020-02-18 17.10.43.png

この調整を変更したい表情全てに対して行っていきます。
Select BlendShapeClipのAddBlendShapeClipを選択することで独自の表情を設定することも可能です。
LOOKUP/LOOKDOWN/LOOKLEFT/LOOKRIGHTについては調整の方法があるそうなのですが、ボクもまだ試せていないので実際にやってみたらまた記事を書きます。

VRMファイルの書き出し

調整が完了したらVRMとして書き出します。
Assets下の[モデル名].prefabをHierarchyにドラッグアンドドロップします。
スクリーンショット 2020-02-18 17.29.15.png

Hierarchyのモデルを選択した状態でメニューからVRMUniVRM-x.xx.x→Export humanoidを選択します。

スクリーンショット 2020-02-18 17.31.09.png

VRM Exporterという画面が出てくるため、変更が必要な項目があれば書き換え、Exportボタンを押すと保存フォルダを選択する画面が出てくるので、保存場所を選んで保存します。(数秒〜数分ほどかかります)
スクリーンショット 2020-02-18 17.32.09.png

書き出されたVRMファイルと3teneで表示したり、Vroid hubにアップロードしてみると表情が変更されていることを確認できます。

表情データの再利用

今回書き出したVRMファイルはVRoid Studioで読み込むことはできません。
そのため着替えなどをした際には再度表情の調整をする必要があります。
しかしパラメータの調整をもう一度するのは面倒……ということで、先ほど編集した表情のデータをエクスポートして再利用してみたいと思います。
この時表情データを書き出したVRMファイルとそれを適用したいVRMファイルの名称が同じだと正常に動作しないので同一のものにならないよう注意してください。

  • 表情データの書き出し Assetsフォルダ等で右クリックをし、出てきたメニューの中からExport Packageを選択します。 スクリーンショット 2020-02-18 17.44.53.png

Exporting pakageという画面が出てくるので、一度Noneボタンを押して全てのチェックを外し、[モデル名].BlendShapesにチェックを付け直しExportボタンを押します。
保存するフォルダの選択とファイル名を入力する画面が出てくるので分かりやすい名前をつけて保存してください。[記入したファイル名].unitypackageというファイルが保存されます。
スクリーンショット 2020-02-18 17.45.14.png

  • 表情データの適用 まず適用したいモデルを準備(プロジェクトの作成、UniVRMのインポート、VRMのインポート)をしてください。 完了したら先ほど保存したBlendShapesのunitypackageファイルをダブルクリックもしくはAssetsフォルダへドラッグアンドドロップして出てきた画面でImportボタンを押します。

スクリーンショット 2020-02-18 17.59.46.png

次にAssetsフォルダから該当モデルのprefabを選択し、InspectorからVRM Blend Shape ProxyBlend Shape Avatarの右にあるボタンを選択します。
スクリーンショット 2020-02-18 18.06.13.png

BlendShapesのunitypackageが正常にインポートできていた場合、Select BlendShapes画面に2つのBlendShapeというファイルが表示されていると思いますので、最初に選択されていたのとは別のBlendShapeファイルを選択することで表情データが適用されます。
スクリーンショット 2020-02-18 18.05.26.png

最後に

今回はVRoidモデルの表情変更についてまとめてみました。
こちらの方法で書き出したVRMファイルを3teneで動かした際には表情が変わることが確認できましたが、VRoid mobileでは特に変化が見られませんでした。
利用するアプリケーションによって差異があると思われますのでご注意ください。

最後までご覧いただきありがとうございました!
次回はVRoidモデルにアクセサリーを付ける記事も書いてみようかな……!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidの戻るキー対応をなるべく簡単にする提案

意外と面倒くさいAndroid戻るキー対応

ね。面倒くさいですよね。(iOSには無いわけですし・・・)
さしあたり一番簡単なのは「画面上の『ネガティブ的』なボタンと同等の機能をつける」事です。

ダイアログを開いたのであれば、戻るキーが「閉じるボタン」や「戻るボタン」相当になればよいですし。
image.png

画面遷移したのであれば、戻るキーが「Backボタン」(前の画面に戻る)相当になればよいわけです。
image.png

そう書くと簡単のように聞こえますが、
画面遷移した次の画面で、ダイアログを表示した場合は?
image.png

戻るキーを押したら、ダイアログも閉じてしまう+画面も前の画面に戻ってしまう

では困るわけです。

これをまともに対応しようとすると、

  • 優先順位スタックマネージャ的なクラスを作成
  • 画面遷移したら、「戻るボタン」の処理(Actionとか?)を(上記)スタックマネージャにPush
  • ダイアログを開いたら「ダイアログ閉じるボタン」の処理(Actionとか?)をスタックマネージャにPush
  • 戻るキーを押したら、スタックマネージャにスタックされている処理の一番上(Peek)を処理
  • ダイアログを閉じたら「ダイアログ閉じるボタン」の処理をスタックマネージャからRemove
  • もう一度戻るキーを押したら・・・・

といった、管理が必要になります。 はい面倒臭いですね!

もっとシンプルに考える

そもそも、上記例の「画面遷移した次の画面で、ダイアログを表示した場合」って、普通はダイアログがモーダル的に表示されていて、後ろの「戻るボタン」は押せないようにしているのがほとんどのはず。(わざわざ後ろのボタンのintaractiveをfalseにしているのか、「タッチガード」的な全画面Panelを一枚噛ませてタッチイベントを遮断しているかのどちらかがほとんどでしょう)

問題なのは、

単純に戻るキーとボタンが押されたときの処理を関連付けてしまうと、uguiのイベントとは関係無しに処理が呼ばれてしまう

ことです。

なので、徹底的にuguiのイベントを倣い、戻るキーの押下を指定ボタンへのマウスクリックへとすり替えてあげれば解決です。

作ってみた

KeyBind.cs
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[RequireComponent(typeof(Button))]
public class KeyBind : MonoBehaviour
{
    [SerializeField]
    private Button _targetButton;

    public KeyCode _bindKey;

    private void Reset()
    {
        _targetButton = GetComponent<Button>();
    }

    private static List<RaycastResult> raycastResultList = new List<RaycastResult>();
    private PointerEventData _pointerEventData;

    private void Update()
    {
        //指定したキーの押下
        if (Input.GetKeyDown(_bindKey))
        {
            _pointerEventData = new PointerEventData(EventSystem.current)
            {
                button = PointerEventData.InputButton.Left,
                position = _targetButton.transform.position //指定したボタンの位置にマウスがある体
            };
            EventSystem.current.RaycastAll(_pointerEventData , raycastResultList);
            var validGameObject = raycastResultList.Select(result => result.gameObject).FirstOrDefault(gameObject => gameObject != null);//一番最初にぶつかっている有効なGameObject取得
            raycastResultList.Clear();
            if (validGameObject == null)
            {
                return;
            }
            var currentPointerDownHandlerObject = ExecuteEvents.GetEventHandler<IPointerDownHandler>(validGameObject); //ボタン位置にあるGameObjectからIPointerDownHandlerを保持しているGameObjectを取得
            if (currentPointerDownHandlerObject != _targetButton.gameObject){
                return;    //ボタン位置から得られたGameObjectとボタンのGameObjectが異なる=別のもので遮られている ので処理しない
            }

            _pointerEventData.pointerPress = currentPointerDownHandlerObject;
            ExecuteEvents.Execute(currentPointerDownHandlerObject, _pointerEventData, ExecuteEvents.pointerDownHandler);
        }

        //指定したキーの押上
        if (_pointerEventData != null && _pointerEventData.pointerPress != null && Input.GetKeyUp(_bindKey))
        {
            ExecuteEvents.Execute(_pointerEventData.pointerPress, _pointerEventData, ExecuteEvents.pointerUpHandler);
            ExecuteEvents.Execute(_pointerEventData.pointerPress, _pointerEventData, ExecuteEvents.pointerClickHandler);
            _pointerEventData = null;
        }
    }
}

(よくわからんなりに調べて作ったので、大分力業ですが・・・)

使い方

このScriptをButtonコンポーネントが乗っているGameObjectに追加します。
image.png

Target Button は勝手に同GameObjectButtonがセットされます。
そして
Bind Key には割り当てたいハードキー を指定します(KeyCode の一覧が候補で出ます)
Androidの戻るキーは KeyCode.Escape で割り当たります。
image.png

なんと、これだけで、ボタンのタップとAndroidの戻るキーが同等になります! シンプル!!

注意

ボタンを疑似的にクリックした相当なので、(利点でも欠点でもあるんですが)ボタンのTransitionがそのまま効きます。
↑の動画をよく見ると分かるんですが、戻るキーを押した時でもボタンの色が変化しています(戻るキーを押しっぱなしにすると、ボタンも押されっぱなしになる)
それが嫌! という場合には使えないです。 悪しからず・・・。

補足

今回、 Androidの戻るキー対応 と銘打っては居ますが、既に書いた通りボタンには KeyCodeで割り当てるキーを指定することができます。
もう一つの使い道として、入力処理の一元化があります。

こちらの動画のゲームで今回のKeyBind.csが実際に使われており、前半はマウスでボタンをクリックして操作ですが、後半はそれぞれのボタンに割り当てられたキーボードで操作しています。

このように「複数方法の入力処理を制御」するには

  • ベタでボタンがクリックされた場合の処理とキーボード入力の処理の場合を分けて書いてしまうスタイル
  • IInput のような入力処理を抽象化したインタフェースを切り、IInput を実装した KeyboardInputButtonInput のようなクラスをそれぞれ実装するスタイル
  • 神Inputクラスに想定される全インプット処理分の条件分岐をぶち込んでいくGODスタイル

などなど。方法はありますがそれなりに面倒で。

対して、↑の動画のゲームでは入力制御は「ボタン処理」のみ対象に記述しています。
そして、KeyBind.csはあくまでもボタンの疑似クリック処理なので、キーボード操作を増やしても入力制御処理は何も手を入れずに済んでいます。 つまり、ボタンによる入力処理で「処理の一元化」がされている状態です。

もちろん、これは画面上にバーチャルパッド的なものをuguiで置いているから出来るだけなので適用範囲はそう広くは無いですが、使える人も少なくないのではないでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでAndroidの戻るキー対応をなるべく簡単にする提案

意外と面倒くさいAndroid戻るキー対応

ね。面倒くさいですよね。(iOSには無いわけですし・・・)
さしあたり一番簡単なのは「画面上の『ネガティブ的』なボタンと同等の機能をつける」事です。

ダイアログを開いたのであれば、戻るキーが「閉じるボタン」や「戻るボタン」相当になればよいですし。
image.png

画面遷移したのであれば、戻るキーが「Backボタン」(前の画面に戻る)相当になればよいわけです。
image.png

そう書くと簡単のように聞こえますが、
画面遷移した次の画面で、ダイアログを表示した場合は?
image.png

戻るキーを押したら、ダイアログも閉じてしまう+画面も前の画面に戻ってしまう

では困るわけです。

これをまともに対応しようとすると、

  • 優先順位スタックマネージャ的なクラスを作成
  • 画面遷移したら、「戻るボタン」の処理(Actionとか?)を(上記)スタックマネージャにPush
  • ダイアログを開いたら「ダイアログ閉じるボタン」の処理(Actionとか?)をスタックマネージャにPush
  • 戻るキーを押したら、スタックマネージャにスタックされている処理の一番上(Peek)を処理
  • ダイアログを閉じたら「ダイアログ閉じるボタン」の処理をスタックマネージャからRemove
  • もう一度戻るキーを押したら・・・・

といった、管理が必要になります。 はい面倒臭いですね!

もっとシンプルに考える

そもそも、上記例の「画面遷移した次の画面で、ダイアログを表示した場合」って、普通はダイアログがモーダル的に表示されていて、後ろの「戻るボタン」は押せないようにしているのがほとんどのはず。(わざわざ後ろのボタンのintaractiveをfalseにしているのか、「タッチガード」的な全画面Panelを一枚噛ませてタッチイベントを遮断しているかのどちらかがほとんどでしょう)

問題なのは、

単純に戻るキーとボタンが押されたときの処理を関連付けてしまうと、uguiのイベントとは関係無しに処理が呼ばれてしまう

ことです。

なので、徹底的にuguiのイベントを倣い、戻るキーの押下を指定ボタンへのマウスクリックへとすり替えてあげれば解決です。

作ってみた

KeyBind.cs
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[RequireComponent(typeof(Button))]
public class KeyBind : MonoBehaviour
{
    [SerializeField]
    private Button _targetButton;

    public KeyCode _bindKey;

    private void Reset()
    {
        _targetButton = GetComponent<Button>();
    }

    private static List<RaycastResult> raycastResultList = new List<RaycastResult>();
    private PointerEventData _pointerEventData;

    private void Update()
    {
        //指定したキーの押下
        if (Input.GetKeyDown(_bindKey))
        {
            _pointerEventData = new PointerEventData(EventSystem.current)
            {
                button = PointerEventData.InputButton.Left,
                position = _targetButton.transform.position //指定したボタンの位置にマウスがある体
            };
            EventSystem.current.RaycastAll(_pointerEventData , raycastResultList);
            var validGameObject = raycastResultList.Select(result => result.gameObject).FirstOrDefault(gameObject => gameObject != null);//一番最初にぶつかっている有効なGameObject取得
            raycastResultList.Clear();
            if (validGameObject == null)
            {
                return;
            }
            var currentPointerDownHandlerObject = ExecuteEvents.GetEventHandler<IPointerDownHandler>(validGameObject); //ボタン位置にあるGameObjectからIPointerDownHandlerを保持しているGameObjectを取得
            if (currentPointerDownHandlerObject != _targetButton.gameObject){
                return;    //ボタン位置から得られたGameObjectとボタンのGameObjectが異なる=別のもので遮られている ので処理しない
            }

            _pointerEventData.pointerPress = currentPointerDownHandlerObject;
            ExecuteEvents.Execute(currentPointerDownHandlerObject, _pointerEventData, ExecuteEvents.pointerDownHandler);
        }

        //指定したキーの押上
        if (_pointerEventData != null && _pointerEventData.pointerPress != null && Input.GetKeyUp(_bindKey))
        {
            ExecuteEvents.Execute(_pointerEventData.pointerPress, _pointerEventData, ExecuteEvents.pointerUpHandler);
            ExecuteEvents.Execute(_pointerEventData.pointerPress, _pointerEventData, ExecuteEvents.pointerClickHandler);
            _pointerEventData = null;
        }
    }
}

(よくわからんなりに調べて作ったので、大分力業ですが・・・)

使い方

このScriptをButtonコンポーネントが乗っているGameObjectに追加します。
image.png

Target Button は勝手に同GameObjectButtonがセットされます。
そして
Bind Key には割り当てたいハードキー を指定します(KeyCode の一覧が候補で出ます)
Androidの戻るキーは KeyCode.Escape で割り当たります。
image.png

なんと、これだけで、ボタンのタップとAndroidの戻るキーが同等になります! シンプル!!

注意

ボタンを疑似的にクリックした相当なので、(利点でも欠点でもあるんですが)ボタンのTransitionがそのまま効きます。
↑の動画をよく見ると分かるんですが、戻るキーを押した時でもボタンの色が変化しています(戻るキーを押しっぱなしにすると、ボタンも押されっぱなしになる)
それが嫌! という場合には使えないです。 悪しからず・・・。

補足

今回、 Androidの戻るキー対応 と銘打っては居ますが、既に書いた通りボタンには KeyCodeで割り当てるキーを指定することができます。
もう一つの使い道として、入力処理の一元化があります。

こちらの動画のゲームで今回のKeyBind.csが実際に使われており、前半はマウスでボタンをクリックして操作ですが、後半はそれぞれのボタンに割り当てられたキーボードで操作しています。

このように「複数方法の入力処理を制御」するには

  • ベタでボタンがクリックされた場合の処理とキーボード入力の処理の場合を分けて書いてしまうスタイル
  • IInput のような入力処理を抽象化したインタフェースを切り、IInput を実装した KeyboardInputButtonInput のようなクラスをそれぞれ実装するスタイル
  • 神Inputクラスに想定される全インプット処理分の条件分岐をぶち込んでいくGODスタイル

などなど。方法はありますがそれなりに面倒で。

対して、↑の動画のゲームでは入力制御は「ボタン処理」のみ対象に記述しています。
そして、KeyBind.csはあくまでもボタンの疑似クリック処理なので、キーボード操作を増やしても入力制御処理は何も手を入れずに済んでいます。 つまり、ボタンによる入力処理で「処理の一元化」がされている状態です。

もちろん、これは画面上にバーチャルパッド的なものをuguiで置いているから出来るだけなので適用範囲はそう広くは無いですが、使える人も少なくないのではないでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity Companion Licenseについて調べてみた

先日Unity社が提供するライブラリを改変したいと思ったのですが、そのライブラリのライセンス(Unity Companion License)が一体どういうものなのかを調べてみたところ、あまり情報がまとまっていなかったのでここにログを残します。

最初にお断りしますが、私は法律の専門家ではなく、英語スキルも自信がないので、このページは参考程度に留め、誤っている点にお気付きの方はぜひ編集リクエストを送っていただけるとありがたいです。

ここに書いてある意訳・まとめは、あくまで私個人の見解であり、UnityCompanionLicenseの取り扱いについてはご自身で判断してくださるようお願いいたします。

今回の目的であるライブラリの改変に焦点を当て、いくつかの項目をピックアップし、
[原文]→[Google翻訳]→[意訳(手で修正)]したものを掲載し、ページの最後に全文に対する対訳をまとめます。
法律関係の単語は難しいですね・・・。

Unity Companion Licenseの全文はこちらから参照できます。
https://unity3d.com/jp/legal/licenses/Unity_Companion_License

翻訳パート

原文

Unity Technologies ApS (“Unity”) grants to you a worldwide, non-exclusive, no-charge, and royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute the software that accompanies this License (“Software”), subject to the following terms and conditions:

意訳

Unity Technologies ApS(「Unity」)は、このライセンス(「ソフトウェア」)に付随するソフトウェアの複製、派生物の作成、公開、展示、および配布を行うための、世界規模での排他的かつ恒久的な著作権使用料無料のライセンスを付与します。
このライセンスには次の条件が適用されます。

原文

  1. Unity Companion Use Only. Exercise of the license granted herein is limited to exercise for the creation, use, and/or distribution of applications, software, or other content pursuant to a valid Unity content authoring and rendering engine software license (“Engine License”).

意訳

  1. Unityでの利用のみ(許可されます)。ここで付与されるライセンスの行使は、有効なUnityコンテンツのオーサリングおよびレンダリングエンジンソフトウェアライセンス(「エンジンライセンス」)に従って、アプリケーション、ソフトウェア、またはその他のコンテンツの作成、使用、および/または配布の行使に限定されます。

原文

\3. Ownership; Derivative Works.
3.1 You own your content. In this License, “derivative works” means derivatives of the Software itself--works derived only from the Software by you under this License (for example, modifying the code of the Software itself to improve its efficacy); “derivative works” of the Software do not include, for example, games, apps, or content that you create with the Software. You keep all right, title, and interest to your own content.

意訳

\3. 派生物の所有権について。
3.1 お客様のコンテンツはお客様に帰属します。 このライセンスでは、「派生物」とは、ソフトウェア自体の派生物を意味し、このライセンスに基づいてお客様がソフトウェアからのみ派生した作品(たとえば、ソフトウェア自体のコードを変更してその有効性を向上させるなど)を指します。 ソフトウェアの「派生物」には、たとえば、ソフトウェアで作成したゲーム、アプリ、またはコンテンツは含まれません。 お客様は自身のコンテンツに対するすべての権利、権原、利益を保有します。

原文

3.2 Unity owns its content. While you keep all right, title, and interest to your own content per the above, as between Unity and you, Unity will own all right, title, and interest to all intellectual property rights (including patent, trademark, and copyright) in the Software and derivative works of the Software, and you hereby assign and agree to assign all such rights in those derivative works to Unity. Should assignment be invalid for any reason, you grant to Unity an irrevocable, perpetual, worldwide, non-exclusive, no-charge, and royalty-free license (with the right to grant sublicenses) under those intellectual property rights to those derivative works. You also agree to waive or refrain from asserting any author’s right, moral rights, or like rights to the extent necessary to permit exploitation as contemplated under this License

3.2 コンテンツそのものはUnityに帰属します。 Unityとお客様の間で、お客様は上記に従ってお客様自身のコンテンツに対するすべての権利、権原、および利益を保持しますが、すべての知的財産権(特許、商標、および著作権を含む) ソフトウェアおよびソフトウェアの派生物はUnityに帰属し、これらの派生物におけるすべての権利をUnityに譲渡することに同意したものと見做します。
何らかの理由で譲渡が無効になった場合、それらの派生著作物に対する知的財産権の下で、世界規模での著作権使用料無料の恒久的かつ排他的、取消不能なライセンス(サブライセンスを付与する権利を含む)をUnityに付与します。また、お客様は、このライセンスの下で想定される開発を可能にするのに必要な範囲で、著者の権利、著作者人格権、または同様の権利を放棄する、または控えることに同意します。

原文

3.3 Your right to use derivative works. You will always have the right to use derivative works of the Software you create, consonant with this License.

3.3 「派生物」を使用する権利。このライセンスに従い、お客様は常に作成したソフトウェアの派生物を使用することができます。

まとめ

今回の目的であるコードの改変については、Unity上で利用する分にはライセンスで認められているようです。
またUnityCompanionLicenseであるライブラリを使用して作成したコンテンツの権利はユーザーに帰属するようですが、改変したコード(派生物)の権利はUnityに帰属するので、そこだけ注意が必要そうです。

参考にしたサイト

http://tsubakit1.hateblo.jp/entry/2019/01/21/210756

全文の意訳


Unity Companion License ("License")

Unity Technologies ApS(「Unity」)は、このライセンス(「ソフトウェア」)に付随するソフトウェアの複製、派生物の作成、公開、展示、および配布を行うための、世界規模での排他的かつ恒久的な著作権使用料無料のライセンスを付与します。
このライセンスには次の条件が適用されます。

  1. Unityでの利用のみ(許可されます)。ここで付与されるライセンスの行使は、有効なUnityコンテンツのオーサリングおよびレンダリングエンジンソフトウェアライセンス(「エンジンライセンス」)に従って、アプリケーション、ソフトウェア、またはその他のコンテンツの作成、使用、および/または配布の行使に限定されます。
    つまり、エンジンライセンスに基づいてライセンスされたソフトウェアでの行使に限定されませんが、エンジンライセンスに依存するアプリケーション、ソフトウェア、またはその他のコンテンツの作成、使用、および/または配布以外の目的で行使することはできません。 本契約で付与されるライセンスのその他の行使は許可されません。また、競合分析または競合製品またはサービスの開発にソフトウェアを使用することはできません。

  2. エンジンライセンスの変更はありません。 このライセンスも、ここで付与されたライセンスの行使も、エンジンライセンスを変更するものではありません。

  3. 派生物の所有権について。
    3.1 お客様のコンテンツはお客様に帰属します。 このライセンスでは、「派生物」とは、ソフトウェア自体の派生物を意味し、このライセンスに基づいてお客様がソフトウェアからのみ派生した作品(たとえば、ソフトウェア自体のコードを変更してその有効性を向上させるなど)を指します。 ソフトウェアの「派生物」には、たとえば、ソフトウェアで作成したゲーム、アプリ、またはコンテンツは含まれません。 お客様は自身のコンテンツに対するすべての権利、権原、利益を保有します。
    3.2 コンテンツそのものはUnityに帰属します。 Unityとお客様の間で、お客様は上記に従ってお客様自身のコンテンツに対するすべての権利、権原、および利益を保持しますが、すべての知的財産権(特許、商標、および著作権を含む) ソフトウェアおよびソフトウェアの派生物はUnityに帰属し、これらの派生物におけるすべての権利をUnityに譲渡することに同意したものと見做します。
    何らかの理由で譲渡が無効になった場合、それらの派生著作物に対する知的財産権の下で、世界規模での著作権使用料無料の恒久的かつ排他的、取消不能なライセンス(サブライセンスを付与する権利を含む)をUnityに付与します。また、お客様は、このライセンスの下で想定される開発を可能にするのに必要な範囲で、著者の権利、著作者人格権、または同様の権利を放棄する、または控えることに同意します。
    3.3 「派生物」を使用する権利。このライセンスに従い、お客様は常に作成したソフトウェアの派生物を使用することができます。

  4. 商標について。このライセンスの下で、Unityまたはその関連会社の商標、サービスマーク、商号、製品名、またはブランド(「商標」)を使用する権利またはライセンスは付与されません。商標の記述的な使用は許可されています。たとえば、https://unity3d.com/public-relations/brandにあるUnityのブランド使用ガイドラインを参照してください。

  5. 通知およびサードパーティの権利について。ソフトウェアに関連する著作権表示を含むこのライセンスは、ソフトウェアおよびその「派生物」のすべての実質的な部分(または、実行できない場合、そのような通知が慣習的に配置されるその他の場所)で提供する必要があります。さらに、ソフトウェアにUnityの「サードパーティの通知」または同様のファイルが添付されている場合、そのファイルで特定されたソフトウェアはそれらの個別のライセンス条件に準拠することを認め、同意します。

  6. 免責事項、責任の制限について。本ソフトウェアおよびその「派生物」は「現状有姿」で提供され、商品性、特定の目的への適合性、および/または非侵害の保証を含め、明示または黙示を問わず、いかなる種類の保証もなしに提供されます。いかなる場合においても、著作権所有者または著者は如何なる(直接的、間接的、偶発的、特別、模範的、または結果的であるかどうか、代替品またはサービスの調達、使用の損失、データ、または利益、および事業の中断を含む)不利益や損失の責任を負いません。またはその他の責任、契約、不法行為、またはその他の行為のいずれにおいても、本ソフトウェアまたは本ソフトウェアの派生的作品から派生したもの、派生した作品、またはその中のその他の取引の使用も含みます。

  7. "USE IS ACCEPTANCE"およびライセンスバージョンについて。ソフトウェアへのアクセスと使用は、このライセンスとその契約条件への同意を意味します。このライセンスは変更または更新できます。このような変更または更新が行われた場合、更新されたライセンスに基づいてソフトウェアを使用する場合、更新されたライセンスの条件に従うことになります。

  8. 法令遵守での利用と解除について。ここで付与されるライセンスの行使は、常に法律を遵守し、所有権(知的財産権を含む)を侵害しません。(i)このライセンスの違反により、 (ii)ソフトウェアが直接的または二次的/間接的な特許侵害を構成すると主張する者に対して、相互請求または反訴を含む何らかの形態の特許訴訟を開始した場合にこのライセンスは直ちに終了します。

  9. 可分性について。本ライセンスのいずれかの規定が施行不能または無効であると判断された場合、その規定は可能な限り最大限に施行され、その他の規定は完全に効力を持ち続けます。

  10. 準拠法および裁判地。このライセンスは、法の抵触に関する規則を除き、デンマークの法律に準拠し、これに従って解釈されます。国際物品売買契約に関する国連条約は適用されません。米国内に居住している場合(または主要な事業所がある場合)、お客様とUnityは、発生する紛争に関して、カリフォルニア州サンフランシスコ郡にある州裁判所および連邦裁判所の対人および専属管轄権および裁判地に提出することに同意します本ライセンスの「紛争」。米国外に居住している場合(または主たる事業所がある場合)、お客様とUnityは、紛争に関してデンマークのコペンハーゲンにある裁判所の対人的かつ排他的な管轄権および裁判地に提出することに同意します。


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで使えるBGMクロスフェード、ループポイント指定機能を公開

はじめに

この度はBGMをループする方法を探していました。
ただ基本機能にはそのような機能がなく、有料プラグインを入れたり皆さん工夫して実装されている様子。
そこで今回、簡易なプラグインを作成して共有することにしました。

プラグイン

ここにアップしています。
https://github.com/Tadashi-Honjo/Unity-BgmManager

プロジェクトへの取り込みは
「BgmManager.unitypackage」をインポートするか
zipを落としてもらって「BgmManager」フォルダを直接Assetsフォルダに放り込んでもらっても大丈夫です。

機能

説明はざっくりとしたものとなりますが、基本的にはBGMや環境音(風の音など)に使う処理を管理するプレハブなどを用意しています。

基本的な機能としては下記となります。

  • 指定したBGMへのクロスフェードでの移行(デフォルト0.5秒移行)
  • BGMと環境音を並列して再生する
  • 各音源でループを開始するポイントを指定する(秒指定)

ゲームを作る上で最低限必要なBGM管理機能となります。

利用手順

非常にシンプルですが
順を追って説明していきたいと思います。

データを用意

まず再生するためにデータを用意する必要があります。
Projectウィンドウで右クリックして
「Craete→MyGame→CreateMusicTable」を実行します。

create_music_data.png

すると再生に必要な情報を設定するファイルが出来るので
設定してファイルを作成してください。

各設定項目は下記のようになります。

Clip

再生するファイル

LoopBeginTime

ループポイントを指定する場合に設定(秒指定)

Volume

再生するボリューム(データ単位で微調整する時などご活用ください)

BGM管理プレハブを配置

では次は実際に再生していきます。
まず下記のプレハブをシーンに配置してください。

BgmManager/Prefabs/System/MusicManager.prefab

そして「BgmManager/SetDefaultBGM」を呼び出して
さきほど作成したサウンドデータを引数で渡してあげます。

これで一応音楽は再生されるはず。
またこれに別の音楽を指定するとクロスフェードして音楽が切り替わります。

余談

ここからは実装意図などを念の為記述しておきたいと思います。
不要な方は飛ばしていただいても大丈夫です。

ループ処理について

ループポイントについては基本的にOnUpdateでポーリングする形を取っています。
正直あまりきれいではないのですがイベントなどが見当たらなかったためできるだけシンプルな実装に敢えてしています。

BgmManagerの立ち位置

BgmManagerはあくまで複数のMusicManagerを管理するための
統括みたいなものです。
今回のケースだとBGMと環境音などのループ音源が同時再生されることを考慮してこのような実装になっています。

MusicManagerの実装について

来たリクエストをできるだけ処理するため
リクエストをプールして順次移行していく形を取っています。
実際の再生は2つのAudioSourceをクロスフェードさせているだけです。

注意点

ただあまり連続でリクエストするシチュエーションがあるとプール最大数分を越えてしまう可能性があるので注意が必要です。

開発ゲームのPR

話が変わりますが
主に共有している内容は現在開発中のゲームのものとなります。
もしよろしければ応援していただけますと嬉しいです。

https://www.youtube.com/watch?v=5f9chpPc6OM

終わりに

いかがでしたでしょうか?
こちらを使えば簡単なBGMの再生やループ再生などは利用できるようになります。

必要最低限しか要らない方が高いプラグインを買う必要はないと思いますのでぜひご活用いただけましたら幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ComputeShaderを使ってUVマップ画像を作成する

はじめに

自作のEditor拡張でUVマップを表示しました。
そのときにComputeShaderを使用したのでその詳細を記していきます。

ComputeShaderとは

HLSLやGLSLといった描画用のシェーダーがありますが、ComputeShaderはGPUを使った数値計算をする仕組みです。
GPUは単純な計算を並列実行できるので、処理によってはCPUに比べて高速に処理が実行できます。
Unityで使う場合には事前にC#のプログラム上で必要なデータや出力先を指定してComputeShaderを実行させます。
https://docs.unity3d.com/ja/2018.4/Manual/class-ComputeShader.html

メッシュのUVマップを取得する実際のコードを見ながら簡単に解説します。

実際のコード

実際のコードです。
今回のComputeShaderではポリゴン単位で並列で計算させています。

getUVMap.compute
#pragma kernel CSMain

// 出力先テクスチャ
RWTexture2D<float4> UVMap;

// 入力データ
StructuredBuffer<float2> UVs;
StructuredBuffer<int> Triangles;
int Width;
int Height;

CGPROGRAM
// 2点間に線を引く
void drawline(uint2 p1, uint2 p2, float4 color) {
    int2 diffp12 = int2(p2.x-p1.x, p2.y-p1.y);
    float distp12 = distance(p1, p2);
    for (int i = 0; i < distp12; i++) 
    {
        UVMap[p1 + diffp12 / distp12 * i] = color;  
    }
}
ENDCG

[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    // 3角ポリゴンをつくる3頂点のインデックスを取得
    int p1Index = Triangles[id.x * 3];
    int p2Index = Triangles[id.x * 3 + 1];
    int p3Index = Triangles[id.x * 3 + 2];

    // 3頂点に対応したuv座標を取得
    float2 uv1 = UVs[p1Index];
    float2 uv2 = UVs[p2Index];
    float2 uv3 = UVs[p3Index];

    // テクスチャの座標に変換
    uint2 p1Pos = uint2(uv1.x * Width, uv1.y * Height);
    uint2 p2Pos = uint2(uv2.x * Width, uv2.y * Height);
    uint2 p3Pos = uint2(uv3.x * Width, uv3.y * Height);

    float4 color = float4(1, 1, 1, 1);

    // 3頂点が示すテクスチャ上の点間に線を引く
    drawline(p1Pos, p2Pos, color);
    drawline(p2Pos, p3Pos, color);
    drawline(p3Pos, p1Pos, color);
}
cprog.cs
private Texture2D GetUVMap(Mesh mesh, int subMeshIndex, Texture2D texture)
{
    var triangles = mesh.GetTriangles(subMeshIndex);
    var uvs = mesh.uv;

    if (uvs.Count() <= 0) return null;

    ComputeShader cs = Instantiate(Resources.Load<ComputeShader>("getUVMap")) as ComputeShader;
    int kernel = cs.FindKernel("CSMain");

    RenderTexture uvMapRT = new RenderTexture(texture.width, texture.height, 0);
    uvMapRT.enableRandomWrite = true;
    uvMapRT.Create();

    var triangleBuffer = new ComputeBuffer(triangles.Count(), sizeof(int));
    var uvBuffer = new ComputeBuffer(uvs.Count(), Marshal.SizeOf(typeof(Vector2)));
    triangleBuffer.SetData(triangles);
    uvBuffer.SetData(uvs);

    cs.SetTexture(kernel, "UVMap", uvMapRT);
    cs.SetInt("Width", texture.width);
    cs.SetInt("Height", texture.height);
    cs.SetBuffer(kernel, "Triangles", triangleBuffer);
    cs.SetBuffer(kernel, "UVs", uvBuffer);

    cs.Dispatch(kernel, triangles.Length / 3, 1, 1);

    triangleBuffer.Release();
    uvBuffer.Release();

    var uvMapTex = new Texture2D(texture.width, texture.height, TextureFormat.RGB24, false);
    uvMapTex.name = texture.name;

    // RenderTextureからTexture2Dに変換
    var original = RenderTexture.active;
    RenderTexture.active = uvMapRT;
    uvMapTex.ReadPixels(new Rect(0, 0, uvMapRT.width, uvMapRT.height), 0, 0);
    uvMapTex.Apply();
    RenderTexture.active = original;

    uvMapRT.Release();

    return uvMapTex;
}

処理の解説

compute shader

このComputeShaderで実行される部分はCSMainの部分です。

[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)

上についているnumthreadsは処理単位のブロックみたいなものですが、今回は特に考えないのですべて1にしています。

そうした場合、引数のidには並列処理ごとに割り振られた異なるidが入力されます。
[numthreads(1,1,1)]としたのでid.xが異なる値でyとzはすべて同じ値になっています。

そのidを元にメッシュの3頂点を特定するためのインデックスを取得します。

// 3角ポリゴンをつくる3頂点のインデックスを取得
int p1Index = Triangles[id.x * 3];
int p2Index = Triangles[id.x * 3 + 1];
int p3Index = Triangles[id.x * 3 + 2];

取得したインデックスを元にUV座標を取得してテクスチャ座標に変換します。

// 3頂点に対応したuv座標を取得
float2 uv1 = UVs[p1Index];
float2 uv2 = UVs[p2Index];
float2 uv3 = UVs[p3Index];

// テクスチャの座標に変換
uint2 p1Pos = uint2(uv1.x * Width, uv1.y * Height);
uint2 p2Pos = uint2(uv2.x * Width, uv2.y * Height);
uint2 p3Pos = uint2(uv3.x * Width, uv3.y * Height);

そして、2頂点間に線を引いていきます。

drawline(p1Pos, p2Pos, color);
drawline(p2Pos, p3Pos, color);
drawline(p3Pos, p1Pos, color);

2頂点間に線を引くコードはこちらです。

// 2点間に線を引く
void drawline(uint2 p1, uint2 p2, float4 color) {
    int2 diffp12 = int2(p2.x-p1.x, p2.y-p1.y);
    float distp12 = distance(p1, p2);
    for (int i = 0; i < distp12; i++) 
    {
        UVMap[p1 + diffp12 / distp12 * i] = color;  
    }
}

cshape

C#コード側ではこのComputeShaderに必要なデータを渡して、実行させています。

始めに使用するComputeShaderをResourcesフォルダから読み込んで、実行するKernelを取得します。

ComputeShader cs = Instantiate(Resources.Load<ComputeShader>("getUVMap")) as ComputeShader;
int kernel = cs.FindKernel("CSMain");

次に使用するデータを渡すためにBufferを確保して、データを設定します。

var triangleBuffer = new ComputeBuffer(triangles.Count(), sizeof(int));
var uvBuffer = new ComputeBuffer(uvs.Count(), Marshal.SizeOf(typeof(Vector2)));
triangleBuffer.SetData(triangles);
uvBuffer.SetData(uvs);

cs.SetTexture(kernel, "UVMap", uvMapRT);
cs.SetInt("Width", texture.width);
cs.SetInt("Height", texture.height);
cs.SetBuffer(kernel, "Triangles", triangleBuffer);
cs.SetBuffer(kernel, "UVs", uvBuffer);

そして、ComputeShaderを実行します。
Dispatch(kernel, x, y, z)はComputeShaderの[numthreads(x, y, z)]に対応しています。

cs.Dispatch(kernel, triangles.Length / 3, 1, 1);

これで計算結果がテクスチャとしてuvMapTexに出力されています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む