TECH PLAY

電通総研

電通総研 の技術ブログ

822

はじめに Azure AI Studio とは Azure AI Studioのトレース機能 Azure AI Studioのトレース機能の使い方 Azure AI Studioの準備 ローカル環境での準備 トレース機能の使い方 エージェントワークフローでトレース機能を使ってみる 実装コード トレース結果を確認 トレース機能の便利な点まとめ こんにちは!X イノベーション 本部AIトランスフォーメーションセンター(AITC)の後藤です。 本記事では最近盛り上がりつつあるLLMエージェントの開発に便利なAzure AI Studioと Prompt flow を使ったトレース機能について紹介します。 はじめに LLMエージェントは一度の実行で内部的に複数のLLMがさまざまなプロンプトで呼び出されるため、処理結果の追随が単純なLLMのチャットと比較して困難です。 このような課題に対してAzure AI Studio(Prompt flow)のトレース機能を使えば簡単にLLMの実行ログが集められると知り、今回記事にしました。 ちなみにこのようなLLMの実行結果管理ツールとしてはLangSmithというツールもあります。こちらは個人利用であればすぐに使えて便利です。今回はなるべくAzureに情報をすべて集めた方が管理しやすいのでAzure AI Studioのトレース機能を調査しました。 そもそもエージェントとは何かや、エージェントに関するより細かな内容を知りたい場合はAITCのコラムもご参照ください。 RAGによる質問応答を生成AIエージェントへ 生成AIエージェントのワークフローの実装方法と関連ライブラリについて理解する 生成AI活用のトレンド:生成AI/LLMエージェントコラムまとめ AIエージェントは何から取り組む?社内取り組み紹介 ICLR2024から見るLLMエージェントの研究動向 Azure AI Studio とは トレース機能の前にAzure AI Studioの説明をします。Azure AI Studio は2024年5月にGAされたばかりのサービスです。 Azure AI Studio は、AI 開発を簡単に行える統合プラットフォームです。開発者やデータサイエンティスト向けに設計されており、AIモデルの構築、評価、デプロイをシームレスに行えます。 特徴としてはAzureでこれまで提供されてきたAI系のサービスを接続し、Azure AI Studioからそれらの機能を使用可能な点です。 また今年のMiscrosoft Buildでも発表があったようにAzure AI Studioからデプロイできるモデルも今後どんどん増えていく予定です。 これまでAzure上のLLMを用いた開発ではAzure OpenAI ServiceでOpenAIモデルのデプロイやPlayGroundでの動作確認を行い、Azure AI Searchで検索インデックスを作り、Azure Machien LearningのPrompt flowで精度検証を行い、、、のようにAzureの複数リソースをまたがって管理する必要がありました。 Azure AI Studioを用いると上記の関連サービスをAzure AI Studioを通して使えるようになります。 Azure AI Studioのトレース機能 Azure AI Studioのトレース機能とは何かは公式ドキュメントに記載があります。 トレースは、エージェント、AutoGen、取得拡張生成 (RAG) ユース ケースなどの生成 AI アプリケーションの実行プロセスを開発者が深く理解できるようにする強力なツールです。 参考: https://learn.microsoft.com/ja-jp/azure/ai-studio/how-to/develop/trace-local-sdk?tabs=python 書いてある通り、LLM関連の開発時に便利な実行プロセスの追随・可視化機能を提供してくれます。 なおAzure AI StudioはGAになりましたが、トレース機能はまだプレビュー段階の機能になります。 Azure AI Studioのトレース機能の使い方 実際にAzure AI Studioのトレース機能を試してみます。 今回はLLMとしてAzure OpenAI Serviceの API を使用します。 Azure AI Studioの準備 Azure AI Studio側の準備を行います。この手順は公式ドキュメントに詳細が記載されているため簡単に説明します。 参考: https://learn.microsoft.com/ja-jp/azure/ai-studio/how-to/create-azure-ai-resource Azure AI Studioはざっくりいうとハブで複数のAzure上のAI関連リソースとの接続を定義し、ハブの中のプロジェクトという単位でハブで接続しているリソースを用いて様々な実験・開発を行います。 したがって、まずハブを作成し次に作成したハブの中にプロジェクトを作成します。 作成したプロジェクトを選択すると以下の画面になります。ここまででAzure AI studio側の準備は完了です。 ローカル環境での準備 作業用のフォルダに移動し、以下のコマンドを実行します。<>の部分は各々の環境に応じて書き換えてください。 az login pf config set trace.destination=azureml://subscriptions/<your_subscription_id>/resourcegroups/<your_resourcegroup_name>/providers/Microsoft.MachineLearningServices/workspaces/<your_studio_project_name> 上記コマンドを実行すると自分の場合は以下のerrorが発生しました。 pf.config.set failed with MissingAzurePackage: "promptflow[azure]" is required for this functionality, please install it by running "pip install promptflow-azure" with your version. 言われた通りパッケージをインストールして再実行します。 [2024-05-30 15:56:28 +0900][promptflow.azure._restclient.flow_service_caller][INFO] - start polling until Cosmos DB setup finished... このコマンド実行が終われば準備完了です。 トレース機能の使い方 Prompt flow(promptflow-tracing)をインストールし、以下のコードを実行するだけでLLMの実行箇所については自動で記録されます。(※ Azure OpenAI Serviceのgptモデルでしか試していません) from promptflow.tracing import start_trace # start a trace session, and print a url for user to check trace start_trace(collection= "trace-openai-test" ) ノートブックで上記内容を実行すると以下のように出力されます。 Starting prompt flow service... Start prompt flow service on port 23333, version: 1.11.0. You can stop the prompt flow service with the following command:'pf service stop'. Alternatively, if no requests are made within 1 hours, it will automatically stop. 出力にあるようにこのコードを実行すると以下のようにローカルでブラウザからcollectionごとにトレースの確認が可能になります。 ここでいうcollectionはコード内の trace-openai-test のことであり、実行結果はこのcollection単位でまとめられます。したがって、collectionは意味のあるまとまりの単位で都度生成した方が後から確認しやすいです。 実際にLLMを実行してどのように記録されるか確認します。上記のコードを実行後にAzure OpenAIの API を叩いてみます。 import os from openai import AzureOpenAI client = AzureOpenAI( api_key= os.getenv( "AZURE_OPENAI_API_KEY" ), api_version= "2024-05-01-preview" , azure_endpoint = os.getenv( "AZURE_OPENAI_ENDPOINT" ) ) response = client.chat.completions.create( model= "gpt-4o-202405-13" , temperature= 0.7 , max_tokens= 500 , messages=[ { "role" : "system" , "content" : "あなたは優秀なアシスタントです" }, { "role" : "user" , "content" : "夏の俳句を詠んでください" }, ] ) 以下がcollectionの中身です。LLMの実行ごとに1行 追加されます(何度か実行した後なので複数結果があります) それぞれの中身は以下のようになっており、ロールごとの生成結果と右上に実行時間と トーク ン数も記載されます。 ここまで見せたのはローカルから確認した結果です。次にAzure AI Studioにいき、作成したプロジェクトのtraceをみるとローカルと同様の内容で記録されていることがわかります。 エージェントワークフローでトレース機能を使ってみる エージェントワークフローについては以下の記事で解説しておりますのでご参照ください。 生成AIエージェントのワークフローの実装方法と関連ライブラリについて理解する 今回は以下のような簡単なフローでトレース機能の効果を見てみます。 処理の流れとしては最初にタスクの計画を立てて、計画内容の数だけタスクを実行して最後にタスクの実行結果を踏まえて最終回答を生成する流れです。 エージェントワークフローの実装部分はLangGraphで行っています。LangGraphでフローを組んだ場合、ノードの各処理とノード間を条件付きのエッジで接続する際には関数を定義します。 トレース機能にはデコレータも用意されており、LLMの入出力だけでなく @trace デコレータが付けられた関数の入出力についても記録してくれます。 この機能はLangGraphと非常に相性が良く結果を見た際にフローの流れがわかりやすいです。 実装コード まずはフローを定義するLangGraphの実装部分を紹介します。長くなるのでプロンプトやモデル定義部分は割愛します。 import operator from typing import TypedDict, List, Tuple,Annotated from langgraph.graph import StateGraph, END from promptflow.tracing import start_trace, trace # start a trace session, and print a url for user to check trace start_trace(collection= "trace-langgraph-workflow" ) # Agent(Node)の状態を記録するためのクラス # 基本的に各ノードにこのクラスが引数に渡される class AgentState (TypedDict): input : str answer: str plan: List[ str ] past_steps: Annotated[List[Tuple], operator.add] exec_subtask_idx: int # 現在実行中のサブタスクのインデックス # Graph全体を定義 workflow = StateGraph(AgentState) # 使用するNodeを追加。名前はこの後も使うので一意である必要がある workflow.add_node( "planner" , create_plan) workflow.add_node( "agent_executor" , execute_action) workflow.add_node( "answer_creator" , create_answer) # エントリーポイントを定義。これが最初に呼ばれるNode workflow.set_entry_point( "planner" ) # Nodeをつなぐエッジを追加 workflow.add_edge( "planner" , "agent_executor" ) workflow.add_edge( "answer_creator" , END) # 条件付きエッジを追加。reflection処理に入るか否かを判定する workflow.add_conditional_edges( "agent_executor" , # つなぎ元のNode should_continue_execute_action, # 条件判定関数 { # 結果が"continue"ならactionにつなぐ "continue" : "agent_executor" , # 結果が"end"なら終了 "end" : "answer_creator" , }, ) # 最後にworkflowをコンパイルする。これでLangChainのrunnnableな形式になる app = workflow.compile() 上記コードでは .add_node でノード名とノードに対応する関数が渡されています。この関数がそのノードで実行される処理に該当します。 それらを記述したのが以下になります。 @trace デコレータはこれらの関数に付けます。 @ trace def should_continue_execute_action (state): # 実行すべきアクションが残っているかを判定 past_steps = state[ "past_steps" ] if len (past_steps) == len (state[ "plan" ]): print ( "end agent execution" ) return "end" else : print ( "continue agent execution" ) return "continue" @ trace def create_plan (state): # 回答のため実行計画を作成 plan = planner_chain.invoke({ "input" : state[ "input" ]}) return { "plan" : plan.steps, "past_steps" : [], "exec_subtask_idx" : 0 , "reflection_try_count" : 0 , "reflection_advise" : "" , "reflection_result" : False , } @ trace def execute_action (state): # stateからメッセージを取得 exec_subtask_idx = state[ "exec_subtask_idx" ] subtask = state[ "plan" ][exec_subtask_idx] # サブタスクを実行 result = agent_executor.invoke({ "input" : state[ "input" ], "plan" : state[ "plan" ], "sabtask" : subtask}) return { "past_steps" : [(subtask, result[ "output" ])], "exec_subtask_idx" : exec_subtask_idx + 1 , } @ trace def create_answer (state): # 最終回答を作成 answer = answer_chain.invoke( { "input" : state[ "input" ], "past_steps" : state[ "past_steps" ]} ) return { "answer" : answer} トレース結果を確認 上記のフローを実行してみました。ローカルで結果を確認すると以下のような表示になります。 これまでとは異なり一番左のkindがFunctionになっています。また先ほどはName部分がopenai_chatに統一されていましたが、今回は関数名です。inputとoutputに関しても関数の入出力に対応しています。 最初に実行されるcreate_plan関数の実行結果をみてみます。 Functionが外側にあり、中にLLMの実行結果が表示されています。 toolの定義についてもちゃんと確認できます。 ここまでは関数内部でLLMが使われたパターンですが、LLMが使われないパターンに関しても記録されています。 以下の画像はshould_continue_execute_action関数の実行結果です。こちらの関数はstateに蓄積された値を参照し、未実行のタスクがないかを確認する関数になります。 入力に与えた状態とその結果として出力される値が表示されており、こちらも処理フローの デバッグ に大変便利です。 Azure AI Studio側の結果も見てみます。 ローカルと同様の内容があります。中身の実行結果部分に関しても正しく記録されていることがわかります。 また関数でLLMが2回呼ばれた場合もちゃんとトレースできています。 トレース機能の便利な点まとめ Azure AI Studio(Prompt flow)のトレース機能を使ってみてわかった便利な点まとめです。 わずか2行という少ないコード記述量で自動でローカルとAzure AI Studio両方に記録をとってくれる エージェントワークフローのような複雑な処理の実験管理が楽になる。特にLangGraphとの相性も良い Azure AI Studioと連携することで結果を他人にURLで共有可能で説明がしやすい。特にtoolを使用した呼び出し部分は可視化されると格段に説明がしやすい エージェントワークフローのような長い処理の デバッグ に便利。途中の入出力をコピペして処理の途中から実行してみたりなども記録が残ってるとやりやすい 執筆: @goto.yuki 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
こんにちは、 電通 総研金融ソリューション事業部の岡崎です。 今回は Mixamo という Adobe が提供している、3Dキャ ラク ター用のアニメーションを使用して、UE5内のキャ ラク ターにいろいろなアニメーションを適応させる方法をご紹介します。 さらに Mixamo で見つけたアニメーションを、MetaHumanにも適応させる方法も併せてご紹介します。 検証環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 4070 Ti Game Engine: Unreal Engine 5.2.1 実装手順 Mixamo Converterのダウンロード Mixamoでアニメーションをダウンロード UEへアニメーションをインポート MixamoアニメーションをMetaHuman用に変更 1. Mixamo Converterのダウンロード Mixamo は Adobe が提供している3Dキャ ラク ターのカスタマイズからアニメーション付けができる Webサービス です。 3Dキャ ラク ターデータがあれば、簡単にリギング(3Dキャ ラク ターを動かすための仕組み”リグ” を作ること)やアニメーション付けが可能で、Mixamo内で提供しているさまざまな動きを3Dキャ ラク ターに付与することができます。 今回はMixamoのリギング機能などを使用せず、UEのコンテンツパック内にあるサードパーソンテンプレートの「Quinn」を使用します。 「Quinn」を使用するために、まずは Miximo converter というアプリを使用します。 Miximo converter のサイトに行き、右上の「DIRECT DOWNLOAD」からダウンロードします。 ZIPファイルを解凍して「Mixamo_Converter.exe」をダブルクリックで実行します。 起動したら「LEFT CLICK TO CONTINUE」を左クリックして進みます。 アプリ画面右側の「Enter the conversion process」をクリックします。 「CHOOSE A CHARACTER」という項目が出てくるので、ここでMixamoのアニメーションを適応させたいキャ ラク ターを選びます。 今回は先ほど記述した通り、コンテンツパック内のサードパーソンテンプレートの「Quinn」を使用するので、「Quinn」を選択します。 「Quinn」をクリックするとフォルダが開きます。 サードパーソンテンプレートではSimpleの方が使われているので、「_SKM_Quinn_Simple.FBX」を使います。 次の手順で、この「_SKM_Quinn_Simple.FBX」を使用してアニメーションのダウンロードを行っていきます。 2. Mixamoでアニメーションをダウンロード 次に、 Mixamo のサイトを開きます。 サインインには Adobe のアカウントが必要です。Mixamoは無料で使用できるので、アカウントがない場合はアカウントを作成します。 Mixamo を開いたら、右側の「UPLOAD CHARACTER」をクリックし、先ほど「Mixamo Converter」で開いた「_SKM_Quinn_Simple.FBX」を ドラッグ&ドロップ します。 画面右側のキャ ラク ターがQuinnに変更されます。 次に使いたいアニメーションを選んでいきます。今回私は、「JabCross」を選びました。 ダウンロードボタンを押して、設定を変更します。 Frames per Second(フレームレート)は60にして、Skinは「Without Skin」を選択します。 次にダウンロードされたFBXファイルをUE用にコンバートします。 再び「Mixamo Converter」を開き、「Open the folder with original animations」をクリックします。 フォルダが開かれるので、先ほどMixamoからダウンロードしたFBXファイルを格納します。 「Click here to convert the animations」をクリックするとコンバートを開始されます。 コンバート完了後、右の「Open the folder with converted animations」を押すとフォルダが開かれて、FBXファイルをダウンロード出来ます。 (コンバート作業は数秒で完了します) 3. UEへアニメーションをインポート UEを開き、適当なプロジェクトを作成して、作成したアニメーションをインポートします。 まずはコンテンツブラウザ内で右クリックを行いインポートを選択して、先ほど生成されたアニメーションのFBXファイルをインポートします。 FBXインポートオプション画面が開かれるので、まずは「デフォルトにリセット」ボタンを押下します。 Mixamo Converterを使用する場合は設定を変更する必要があるので、下記項目も設定します。 ・「メッシュ > Skeleton」を「SK_Mannequin」にする ・「Animation > 詳細設定 > Use Default Sample Rate」のチェックを入れる ・「Animation > 詳細設定 > Import Attributes as Curves or Animation Attributes」のチェックを外す ・「Animation > 詳細設定 > Import Bone Tracks」のチェックを入れる ・「Animation > 詳細設定 > Do not import curves with only 0 values」のチェックを外す 最後にインポートボタンを押してUE上にインポートをします。 コンテンツブラウザ内にQuinnのアニメーションとしてインポート出来たことを確認します。 このアニメーションは、Quinnに使用されている「SK_Mannequin」をベースとしたアニメーションなので、 サードパーソンテンプレートで用意されているアニメーションと同じように使用することができます。 次の工程では、インポートしてきたアニメーションをMetaHumanに適応させる方法を紹介します。 4. MixamoアニメーションをMetaHuman用に変更 ここでは、インポートしてきたアニメーションをMetaHuman用に変更して使用する方法をご紹介します。 変更するためにはアニメーションのリターゲティングという処理が必要になります。 コンテンツブラウザ内でインポートしてきたアニメーションファイルを右クリックします。 「アニメーションアセットのリターゲティング>アニメーションアセット・ブループリントを複製してリターゲティング」を押します。 「アニメーションアセットを複製しリターゲット」という画面が開かれるので、右上の「IKリターゲッタ」の項目を「 RTG _Metahuman」に変更します。 IKリターゲッタとは、IKリグというアセットを使い、アニメーションリターゲットを行う機能です。 IKリグという、ボーンの関係性を示したファイルを作成すれば、異なるボーン名や骨構造を持ったス ケルト ンの間でリターゲットを行うことができます。 MetaHumanの場合は既にIKリグを設定した「 RTG _Metahuman」というファイルが用意されているので、使用すればいいだけになっています。 設定画面左側の「ソーススケルタルメッシュ」のセレクトボックスを、インポートしてきたアニメーションのベースとなっている「SKM_Manny」に変更します。 「ターゲットスケルタルメッシュ」はそのままにしておきます。 (「ソーススケルタルメッシュ」を変更した時に、自動的に「ターゲットスケルタルメッシュ」の項目は「f_med_new_body」に変更されます) 最後に右側の設定画面でアニメーションのファイル名やファイルの格納先を設定して、「リターゲット」を押下します。 するとMetaHumanに使用できるようにリターゲットされたアニメーションが生成されます。 これでMetaHumanもMixamoの好きなアニメーションを再生できるようになります。 試しにレベルシーケンス内でMetaHumanにアニメーションを再生させてみます。 詳細は割愛しますが、シーケンス作成画面からレベルシーケンスを作成します。 まずはアウトライナ上にMetaHumanを配置します。 シーケンスを作成してトラック追加から アウトライナー に配置済みの任意のMetaHumanを追加します。 「Body」の横のプラスボタンから「アニメーション>JabCross」を選択します。 これでレベルシーケンス上でアニメーションを再生させることができます。 以上がMixamoからインポートしてきたアニメーションを、UE内で使用する方法やMetaHumanに適応させる方法の紹介でした。 おわりに 今回はUE5.2を使用して、Mixamoからインポートしてきたアニメーションを、UE内で使用する方法や、MetaHumanに適応させる方法を紹介しました。 先日アップデートがあったUE5.4では、アニメーションのリターゲティングがさらにやりやすく、簡単になったそうなのでそちらも学習していきたいです。 また、Mixamoだけではなく、独自で簡単なアニメーションを作成して、UE内で使用したい場面も出てきたので、そちらの学習も行っていき、またご紹介できればと思います。 最後までご覧いただきありがとうございました! 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! 電通総研採用ページ 参考 https://styly.cc/ja/tips/mixamo_3d/ https://zenn.dev/daichi_gamedev/books/unreal-engine-5/viewer/mixamo-to-ue https://ue5exp0.com/mixamo-ue5/ https://www.youtube.com/watch?v=tCkAo0rcmF8 執筆: @okazaki.wataru 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
こんにちは、 電通 総研金融ソリューション事業部の岡崎です。 今回は Live Link Face for Unreal Engine という iPhone のアプリを使用して iPhone のカメラで撮影した顔のアニメーションをキャプチャし、UnrealEngineのMetaHumanに適応させる方法を ご紹介します。 また、こちらの 山下さんの記事 では、Livelinkを用いてリアルタイムにMetaHumanを動かす方法が紹介されていますので、ぜひそちらもご覧ください。 本記事は前編後編に分かれており、この記事では手順4の「MetaHumanIdentityを使用してLiveLink動画を調整」までをご紹介します。 検証環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 4070 Ti Game Engine: Unreal Engine 5.2.1 iPhone14pro:IOS16.4.1 実装手順 LiveLinkを使用した撮影 UnrealEngineの準備 iPhone で撮影した動画をインポート MetaHumanIdentityを使用してLiveLink動画を調整 LiveLinkで撮影した動画からアニメーションを作成 レベルシーケンスアニメーションをMetaHumanに付与 MetaHumanの体のアニメーションをリターゲティングで作成 MetaHumanの顔と体のアニメーションを連結 1. LiveLinkを使用した撮影 まずは iPhone を利用して作成したい顔の動きの動画を撮影します。 今回は自分の顔で驚いた表情をしたり、何かを喋ったりする顔を作成していきます。 iPhone の App Store から「Live Link Face」というアプリをダウンロードします。 その後MetaHuman Animatorを選択します。 動画を撮る画面になるので、まずはカリブレーションテイクを撮影していきます。 カリブレーションとは、LiveLinkで撮影した顔をMetaHumanの顔と一致させるために必要な調整のことです。 撮影前に動画の名前を「calibration」に変更します。 撮影ボタンを押した後に、まっすぐ前を向き、左右を向き、最後に歯を大きく見せる動きを撮影します。 (キャプチャはモザイクを追加しています) モザイクでわかりづらいですが、歯がよく見えるシーンも撮影します。 (キャプチャに関しては、画像素材を合成しています。出典元: 歯科素材.com から「 きれいな歯 」を利用) 一連の動作が全てが終わったら撮影停止ボタンを押します。 次に、 モーションキャプチャ 用の動画を撮っていきます。 動画の名前を「main」に変更して撮影をします。 動画は割愛しますが、 このショットでは驚いた表情をしたり、なにかを喋ったりする動画を撮影してください。 ここからはUnrealEngineでの作業に移ります。 2. UnrealEngineの準備 まずは、メタヒューマンを使用するために、EpicGamesLauncherから「MetaHuman」「MetaHuman Plugin」の2つを 使用するエンジン(今回の場合は Unreal Engine 5.2.1)にインストールします。 インストールが完了したら、プロジェクトを作成し、任意のレベルを配置します。 「Quixel Bridge」からプロジェクトへMetaHumanをダウンロードします。 今回はこのMetaHumanを使用していきます。 ダウンロードが完了するとコンテンツブラウザ内の「コンテンツ>MetaMumans>BP_Hudson」にファイルが追加されます。 3. iPhone で撮影した動画をインポート コンテンツブラウザ内で右クリックを行い、「MetaHumanAnimator>CaptureSouce」を選択します。 今回私は「MetaHumanActor_Source」と 命名 します。 次に「MetaHumanActor_Source」を開き、「CaptureSourceType」のセレクトボックスから「LiveLinkFaceConnection」を選びます。 「DeviceAddress」の欄には、使用しているPCと iPhone が同じネットワークに接続しているのを確認して、 wifi の IPアドレス を記載します。 確認方法は、 iPhone で「LiveLink」アプリを開き、歯車の設定ボタンからOSCサーバーを押し IPアドレス を確認して、「DeviceAddress」の欄に記載します。 次に「MetaHumanActor_Source」を閉じ、画面上部のツールから「CaptureManager」を選択して、左側の「MetaHumanActorSource」を選択すると、 iPhone で撮影した動画を表示できます。 画面下の「AddToQueue」を押してインポートします。 次の工程で、キャプチャした動画をMetaHumanに適応させるために調整をしていきます。 4. LiveLinkの動画の調整 コンテンツブラウザから、「MetaHumanAnimator>MetaHumanIdentity」を選択して作成します。 「MetaHumanActor_Identity」と 命名 します。 「MetaHumanActor_Identity」を開き左上から「CreateComponents>FromFootage」を押下し、先ほどインポートしてきた動画のうち、「calibration」の動画を選択します。 下記キャプチャのように iPhone で撮影した動画が出てくるので、カメラに対して正面を向いている箇所で動画を停止させます。 左下のプラスマークを押すと、顔の眉毛、目、ほうれい線、唇の辺りに緑色の線が表示されます。 (ぼかしが入っているためわかりづらいですが、緑色の線が出現しています) 調整する必要がないことがほとんどですが、もし顔の眉毛、目、ほうれい線、唇の辺りに緑色の線が、実際の顔とずれている場合は、緑色の線をドラッグで移動させて調整します。 左下のカメラのアイコンを押して、動画を再び動かし、今度は左を向いている部分で停止させます。 先ほどと同じ様にプラスマークを押して、緑色の線を調整します。 右向きも同様の作業を行います。 正面、左、右が終わったら画面上部の「MetaHumanIdentityの解決」を押します。 次に左側のツリーから「Body」の選択欄から任意のメッシュを選択します。 体を選び終わったら、画面上部の「MeshToMetaHuman」から「Auto-rigIdentity(SkeletalMesh + FullMetaHuman)」を選択します。 ここまでで、顔の動きが、ボーンのアニメーション情報に変更できたので、最後に歯も追加します。 左側のツリーから「Poses」を選択して「Add」ボタンを押下します。 「AddPose>AddTeeth」を押します。 動画を再び動かし、歯を見せている部分まで移動して左下のプラスマークを押し緑色の線が歯を覆っているか確認します。 正常に緑の線が作成されていたら、画面上部の「FitTeeth」を押し、処理を進めます。 最後の画面右上の「PrepareForPerformance」をおして、処理が完了するのを待ちます。 ここまででMetaHumanIdentityを使用した調整作業が完了しました。 コンテンツブラウザに戻り「SK_MetaHumanActor_Identity」というファイルが生成されていることを確認出来たら MetaHumanIdentityを使用した調整作業が無事完了です。 おわりに 今回は iPhone を使用して撮影した動画をUE内にインポートを行い、MetaHumanに適応させるためにカリブレーション(調整)を行う手順まで紹介しました。 後編 では、いよいよMetaHumanを モーションキャプチャ で動かして、 自分の動画の動きを再現させる方法をご紹介します。 ぜひそちらもご覧いただけますとうれしいです。 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! 電通総研採用ページ 参考 歯科素材.com https://dev.epicgames.com/documentation/ja-jp/unreal-engine/recording-face-animation-on-ios-device-in-unreal-engine?application_version=5.2 https://www.unrealengine.com/ja/blog/new-live-link-face-ios-app-now-available-for-real-time-facial-capture-with-unreal-engine 執筆: @okazaki.wataru 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
こんにちは、 電通 総研金融ソリューション事業部の岡崎です。 前回の記事 ではMetaHumanIdentityを使用してLiveLink動画を調整する手順まで説明しました。 この記事は前回の続きからになりますので、まだご覧になっていない方は、 前編 も是非ご一読ください! 検証環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 4070 Ti Game Engine: Unreal Engine 5.2.1 iPhone14pro:IOS16.4.1 実装手順 LiveLinkを使用した撮影 UnrealEngineの準備 iPhone で撮影した動画をインポート MetaHumanIdentityを使用してLiveLink動画を調整 LiveLink動画からアニメーションを作成 レベルシーケンスアニメーションをMetaHumanに適用 MetaHumanの体のアニメーションをリターゲティングで作成 MetaHumanの顔と体のアニメーションを連結 5. LiveLink動画からアニメーションを作成 まずはコンテンツブラウザから「MetaHumanAnimator>MetaHumanPerformance」を作成します。 「MetaHumanPerformance」を開きます。 右上の「FootageCaptureData」から iPhone で撮影した「main」の動画を選択します。 次に右上の「MetaHumanIdentity」から「MetaHumanActor_Identity」を選択します。 さらにその下のHeadMovementModeから「ControlRig」を選択して、左上の「プロセス」ボタンを押下します。 LiveLinkで撮影した動画と、同じ動きをしているアニメーションが再生されるので、再生が終わるまで待ちます。 最後に作成されたアニメーションをMetaHumanで使用するためにエクスポートします。 画面上部の「ExportLevelSequence」を押し、レベルシーケンスとして動画を書き出します。 設定画面が出ますがデフォルトのままで大丈夫です。 エクスポートが完了すると、コンテンツブラウザ内にレベルシーケンスファイルが出力されているのが確認できます。 6. レベルシーケンスアニメーションをMetaHumanに適用 次に、作成されたレベルシーケンスファイルをMetaHumanに適用させていきます。 作成されたレベルシーケンスファイルを開くと、ビューポート上にLiveLinkの動画と先ほど作成したidentityで作られた顔のメッシュが出現します。 identityで作成された顔のメッシュをビューポート上で選択して、詳細パネルから「Mesh>SkeletalMeshAsset」を「Hudson_FaceMesh」に変更します。 すると顔のアセットがMetaHumanの顔に変更されます。 ここでレベルシーケンスを再生させるとMetaHumanの顔でLiveLinkで作成した動きができていることが確認できます。 ただこのままでは、まだMetaHumanのアセットに現在作成したアニメーションを使用することができないので、レベルシーケンスの動きをアニメーションにベイクする必要があります。 レベルシーケンス上で「Face」アセットを選択して、右クリックから「アニメーションをベイク」を選択します。 本来であればこれでアニメーションが作成されるはずですが、おそらくUE5.2のバグで、「アニメーションをベイク」の処理を始めるとアプリが落ちてしまいます。 そのため、以下の対策方法でバグを回避する必要があります。 以下、アプリが落ちてしまうバグの対策方法です。 プロジェクト設定から「RHI」と検索して、デフォルトのRHIの設定を「DirectX12」から「Vulkan」に変更して、UEを再起動します。 設定を変更したUEではアプリが落ちるバグが発生しないので「アニメーションをベイク」を行います。 今回は「Baked_Animation_MetaHuman_LiveLink」と 命名 しました。 無事アニメーションがベイクできたら、先ほどの設定画面から「DirectX12」に戻しておきます。 次にエクスポートしたアニメーションをMetaHumanに付与していきます。 先ほどまで作業していたレベルシーケンスファイルを閉じ、レベル上に「BP_Hudson」を配置します。 次に新規でレベルシーケンスファイルを作ります。 画面上部の動画マークからレベルシーケンスを作成します。 シーケンサー エディターの左側にある「+トラック」ボタンから「 シーケンサ へアクタ>BP_Hudson」を選択して、レベルシーケンス上でMetaHumanへのアクセスができる状態にします。 次にシーケンスエディタ内の「BP_Hudson」の配下にある「Face」を広げて「MetaHuman_ControlRig」を削除します。 (このリグはMetaHumanにデフォルトでついているもので、これがあると任意のアニメーションを付与できないので削除) 再び「Face」を選択して右側のプラスアイコンから「Animation>Baked_Animation_MetaHuman_LiveLink」を選択します。 これでMetaHumanにLiveLinkで作成した動きを付与することができました。 ただ、このままだと体と顔が連結されておらず、体にアニメーションをつけた時に分裂してしまうので、顔と体を連結させる必要があります。 次の工程で説明していきます。 7. MetaHumanの体のアニメーションをリターゲティングで作成 まずは顔と体のアニメーションを連結する前に、MetaHumanの体用のアニメーションを用意します。 今回用意する方法は、UEのデフォルトThirdPersonPackに内包されているアニメーションをMetaHuman用にリターゲティングして使用します。 プロジェクト内にThirdPersonPackを追加していない場合は、コンテンツブラウザの左上「Add」ボタンから「コンテンツパックを追加>サードパーソン」を選択して追加します。 コンテンツブラウザ内に「Characters」というフォルダができるので、「Characters>Mannequins>Animations>Manny」から任意のアニメーションを選びます。 今回は「MM_Walk_Fwd」を使います。 「MM_Walk_Fwd」を右クリックして「アニメーションアセットのリターゲティング>アニメーションアセット・ブループリントを複製してリターゲティング」を押下します。 編集画面がでるので右上の「IKリターゲッタ」を「 RTG _Metahuman」に変更します。 左側の「ソーススケルタルメッシュ」を「SKM_Manny」にして、右側の「TargetSkeletalMesh」は変更せずに右下の「リターゲット」ボタンを押します。 これによりMetaHumanの体のアニメーションが作成されました。 8. MetaHumanの顔と体のアニメーションを連結 シーケンサー エディターを再び開き、「BP_Hudson」配下のBodyを選択し、Faceの時と同様に「コン トロール リグ」を削除して、右側のプラスアイコンから「Animation」を選択して、先ほどリターゲティングした「MM_Walk_Fwd」を選択します。 これでMetaHumanの体と顔が同時に再生されているのが確認できます。 (画質が悪くわかりづらいですが、表情も動いています) しかし、よく見ると体と顔の間に隙間がありうまく連動されていないのがわかります。 これはUE5.2ではアニメーションのポストプロセス処理が原因しており、現状だとポストプロセスを無効化しないと解消できないのでこれを解除していきます。 シーケンサー エディターでBodyを選択し、詳細パネルから「Animation>AnimationMode」の右にあるキーフレーム追加ボタンを押します。 追加ボタンをおすとシーケンス上にひし形をしたキーが追加されたのを確認できます。 キーフレームが打てたことを確認したら、「DisablePostProcessBlueprint」にチェックを入れて無効化します。 同様にFaceでもキーフレームを追加してポストプロセスを無効化します。 これで顔と体が連結することができました。 作成したMetaHumanによっては顔のメッシュが崩れてしまうことがあります。 (このキャプチャはほかのMetaHumanを試していた時に起きた現象です) これは顔のメッシュのLODが関係しているので、もし崩れてしまった人は次の工程を参考にしてみてください。 アウトライナー 上の「BP_Hudson」を選択し「詳細」の「LODSync」をクリックします。 詳細の下に「NumLODs/ ForcedLOD /MinLOD 」の欄があるので、キャプチャの通りに設定を行ってください。 これにより、顔のメッシュの崩れは解消できます。 以上が、LiveLinkを使ってMetaHumanの顔の動きを自由に生成する方法の紹介でした。 おわりに 今回はLiveLinkという スマホ アプリを使うことで、顔のアニメーションを自由に作成する方法を学びました。 学習を通じて、工程は多いものの、とても高品質なアニメーションが作れることにとても驚きました。 この技術を使うことで NPC やムービーのキャ ラク ターなどのアニメーションでできることが大幅に増えるのではないかと感じます。 体のアニメーションについても、UE搭載のデフォルトの動き以外を作成する方法を次回以降で紹介する予定なので、 より自由な表現ができるのではないかと思います。 最後まで読んでいただきありがとうございました! 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! 電通総研採用ページ 参考 https://dev.epicgames.com/documentation/ja-jp/unreal-engine/recording-face-animation-on-ios-device-in-unreal-engine?application_version=5.2 https://www.unrealengine.com/ja/blog/new-live-link-face-ios-app-now-available-for-real-time-facial-capture-with-unreal-engine 執筆: @okazaki.wataru 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
こんにちは。コミュニケーションIT事業部 ITソリューション部の英です。 普段はWebアプリや スマホ アプリの案件などを担当しています。あと、趣味でAIを勉強しています。 前回に引き続き IoTデ バイス (温度センサー)の異常検知 をテーマに記事を書いてみます。 前回の記事はManaged Apache Flinkを使用した 閾値 でのリアルタイム異常検知でした。 → 前回の記事 今回の記事では 閾値 (正解)を与えずに異常を検出する仕組みをご紹介します。 使用するのはSageMakerに搭載されているRCF(ランダムカットフォレスト)という アルゴリズム です。 ランダムカットフォレストとは? ランダムカットフォレスト(Random Cut Forest)は、異常検知を目的とした 機械学習 アルゴリズム の一つです。 異常検知とはデー タセット 内の”異常”なデータポイントを見つけ出すことを指します。 例えば、ネットワークの トラフィック データから サイバー攻撃 を検出したり、製造ラインのセンサーデータから機械の異常を発見したりする際に用いられます。 また、 AWS の公式 チュートリアル では タクシーの乗車数データからイベントを検出する 検証をされています。 仕組み ランダムカットの生成 データをランダムに分割します。データ空間に対してランダムに位置と方向を選び、データを二分します。例えば、温度データがあれば、その値に基づいて適当な位置でデータを分割します。 ツリーの構築 分割したデータ群で木を作ります。ランダムに選ばれた位置と方向でデータを分割する操作を繰り返します。これにより、データがどんどん細かく分割されていきます。この分割の結果がツリー状の構造を形成します。 異常スコアの計算 対象データがツリー上のどの深さにあるかを測定し、異常度をスコア化します。ツリーの深さは、データポイントがどのくらいの回数分割されたかを表します。データポイントがツリーの浅い位置にある場合、それは他のデータポイントから大きく外れた異常値である可能性が高いとされます。 かなり直感的でわかりやすい アルゴリズム だと思います。 例えば、100冊の本があったとして、ほとんどの本は150ページから250ページあるとします。しかし、1冊だけ500ページの分厚い本があるとしましょう。この500ページの本はデータをランダムに分割する初期の段階で他のデータポイントから孤立しやすいことが想像できますよね。500ページある本はツリーの浅い位置に配置され、150ページから250ページの本は、何度も分割されてツリーの深い位置に配置されます。 ポイントとしては 異常スコアがいくつだったら異常とみなすか は人が 閾値 を設定する必要があることです。 これだけ聞くと 閾値 で異常を検知していた前回の記事と同じじゃないのか?と思うかもしれません。 全然違います。 仮に温度で 閾値 を設定した場合、季節の変化に耐えられるでしょうか?地理的変化に耐えられるでしょうか? "30℃を超えたら異常とするセンサー"を埼玉の 熊谷市 にでも設定してみてください。夏場は毎日異常扱いです。 「 熊谷市 だったら35℃くらいから異常かも!」「軽井沢なら30℃くらいから異常かも!」のように設置した場所ごとに異常さは異なって当たり前なのです。 つまり、今回の アルゴリズム では 正常か異常かをリアルなデータを用いて自動的に識別できる ということ。 これがRCFの画期的なところです。 ランダムフォレストと名前は似ていますが、また違った アルゴリズム ですね。 ランダムフォレストについては過去の記事で解説しています。 → 過去の記事 ここから本題 STEP1:学習用データの作成 以下のように学習用のデモデータを生成します。 通常時は正弦波で温度が流れてきて、異常時にはそこにランダムな数値を加算するものとします。今回は1/100の確率で異常データが生成されます。 実際の温度センサーはこれほど綺麗な波形にはなりませんが、今回は検証をシンプルにするためにこのようなデータを使用するものとします。 STEP2:学習用データの確認 学習用データをプロットしてみると以下のようなグラフになります。 人間の目は良くできていますから、どこが異常かは一目見ればわかります。 STEP3:学習用データのアップロード S3のセッション バケット に学習データをアップロードしておきます。 STEP4:学習 先ほど生成した学習データを使用してト レーニン グを行います。 S3を経由する意味はほぼありませんが、お作法に則って学習データはS3から取得してきます。 ハイパーパラメータ 説明 role モデルの実行に必要な権限を定義する AWS IAMロール instance_count ト レーニン グや推論を行うための インスタンス の数 instance_type 使用する インスタンス のタイプ num_samples_per_tree 各ツリーあたりのサンプル数。これにより、各決定木が何回のランダムなカット操作を実行するかが決まります。数値が大きいほど、モデルの精度が上がりますが、計算コストも増加します。異常データの比率を1/num_samples_per_treeに設定するのが最適とされています。 num_trees ツリーの数。ツリーの数が多いほど、モデルの異常検知能力が向上しますが、計算リソースも多く必要になります。 → ハイパーパラメータについて STEP5:推論の実行 モデルをデプロイし、新規のデータポイントを500個生成して異常スコアを算出してみます。 算出したスコアは CSV 形式で保存しておきます。 以下のような CSV ファイルが出力されました。一番右の列が異常スコアです。 STEP6:結果のプロット 今回は異常スコアが3.0以上のものを異常として赤色の「×」マークでプロットしてみます。 以下のようにプロットされました。 しかし、この設定ではグラフの下部(18℃付近)が赤く染まってしまっていることが分かります。 異常スコアが3.0以上なら異常とする 閾値 を見直す必要がありそうです。 次は異常スコアが3.5以上なら異常とするように 閾値 を変更してみます。 以下のようにプロットされました。 このグラフは人間の目で見たときに感じた異常さと同じ結果を示しているかと思います。成功ですね。 まとめ 異常検知はさまざまな産業やアプリケーションにおいて重要な役割を果たしています。今回紹介したランダムカットフォレスト(RCF)は、事前に 閾値 (正解)を設定することなく、データの異常を効果的に検出できる強力な アルゴリズム です。 RCFの最大の利点は、その適応性です。異常が環境や状況に依存する場合に特に有用です。従来の 閾値 ベースの方法では、適切な 閾値 を設定するのが困難であるため、この適応性は大きなアドバンテージとなります。 複雑なデー タセット から有意な異常パターンを自動的に抽出できるため、データ分析の専門知識がなくても利用できる強力な分析ツールです。実際のデータ分析や業務改善に役立てていただければ幸いです。 これからも AWS ×AIの検証記事をたくさん書いていきます。 ↓ のスターを押していただけると嬉しいです。励みになります。 最後まで読んでいただき、ありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! <電通×IT>電通グループ基幹システムプロジェクトマネージャー <電通×IT>顧客DX案件プロジェクトマネージャー <電通×IT>クラウドアーキテクト <電通×IT>アプリケーションアーキテクト 執筆: 英 良治 (@hanabusa.ryoji) 、レビュー: @takami.yusuke ( Shodo で執筆されました )
アバター
thumbnail こんにちは!金融ソリューション事業部WEB3グループの山下です。 本記事は「SpatialComputing入門」シリーズのPart2となります。 前回のPart1 で追加した3Dオブジェクトに対して複数のGestureによるインタ ラク ションを実装していきます。 「SpatialComputing入門」シリーズ: Part1:3Dオブジェクトを生成して空間に配置する Part2:3DオブジェクトにGestureを追加する Part3:現実空間の平面に3Dオブジェクトを配置する 動作環境 Apple M1 Max ( macOS 14.5) Xcode 15.4 VisionPro (visionOS 1.2) 実装手順 TapGestureの追加 VisionPro動作確認 DragGestureの追加 VisionPro動作確認 1. TapGestureの追加 Part1 で作成したVolumeView.SwiftのStructを、以下に修正します。 オブジェクトをTapをすると、3Dコンテンツのスケールを増加する処理を実装します。 struct VolumeView : View { @State private var scale : Float = 1.0 var body : some View { RealityView { content in // Generate ModelEntity let model = ModelEntity( mesh : .generateCone(height : 0.1 , radius : 0.1 ), materials : [ SimpleMaterial ( color: .lightGray , isMetallic: true )] ) // Add components model.components. set (InputTargetComponent()) model.components. set (CollisionComponent(shapes : [ .generateSphere ( radius: 0.1 )] )) // Add ModelEntity to RealityView content content.add(model) } update : { content in if let model = content.entities.first { model.transform.scale = [scale, scale, scale] } }.gesture(TapGesture().targetedToAnyEntity().onEnded{ _ in scale += 0.1 }) } 変更点は以下のとおりです。 State変数の追加 @State private var scale:Float = 1.0 :スケール率を管理するState変数を追加 .update: クロージャ を追加: RealityViewで最初に取得するcontentに対して、scaleを変更する State変数が変更されたら毎回呼び出される処理 .gestureイベントを追加: TapGester()が終了したら、scaleを0.1増加 2. VisionPro動作確認 実行すると、以下のような挙動になります。 オブジェクトを見てTapすると、スケールが増加するインタ ラク ションを確認できます。 3. DragGestureの追加 次に、Dragをすることで3Dコンテンツが回転する処理を実装します。 VolumeView.Swiftを、以下に修正します。 struct VolumeView : View { @State private var scale : Float = 1.0 @State private var rotationAngleX : Float = 0.0 @State private var rotationAngleY : Float = 0.0 @State private var lastTranslation : CGSize = .zero var body : some View { RealityView { content in // Generate ModelEntity let model = ModelEntity( mesh : .generateCone(height : 0.1 , radius : 0.1 ), materials : [ SimpleMaterial ( color: .lightGray , isMetallic: true )] ) // Add components model.components. set (InputTargetComponent()) model.components. set (CollisionComponent(shapes : [ .generateSphere ( radius: 0.1 )] )) // Add ModelEntity to RealityView content content.add(model) } update : { content in if let model = content.entities.first { model.transform.scale = [scale, scale, scale] model.transform.rotation = simd_quatf(angle : rotationAngleY , axis : [ 0, 1, 0 ] ) * simd_quatf(angle : rotationAngleX , axis : [ 1, 0, 0 ] ) } } .gesture(TapGesture().targetedToAnyEntity().onEnded{ _ in scale += 0.1 }) .simultaneousGesture( DragGesture(minimumDistance : 0 ) .onChanged { value in let translation = value.translation if abs(translation.height) > 0 { rotationAngleX += Float(translation.height - lastTranslation.height) * 0.01 } if abs(translation.width) > 0 { rotationAngleY += Float(translation.width - lastTranslation.width) * 0.01 } lastTranslation = translation } .onEnded { _ in // ドラッグ終了時に変化量をリセット lastTranslation = .zero } ) } 変更点は以下のとおりです。 State変数の追加 @State private var rotationAngleX: Float = 0.0 : @State private var rotationAngleY: Float = 0.0 : @State private var lastTranslation: CGSize = .zero : .update クロージャ の修正 Drag ジェスチャー で取得したrotationAngleを用いて、モデルを回転させる処理を追加 .simultaneousGestureイベントの追加 Drag ジェスチャー の上下/左右の動きに対して、それぞれrotationAngleXとrotationAngleYを割り当てる 既にTap ジェスチャー があるので、非同期での ジェスチャー を有効にする為に.gestureではなく.simultaneousGestureを追加 以下の回転処理のコードが少し特殊なので、補足します。 model.transform.rotation = simd_quatf(angle: rotationAngleY, axis: [0, 1, 0]) * simd_quatf(angle: rotationAngleX, axis: [1, 0, 0]) ここでは simd_quatf(angle:axis)関数 を用いてmodelを回転させています。 引数であるangle:では、Float値で回転角度を指定しています。 axis:では回転させる座標軸を指定([0, 1, 0]の場合はY軸周り、[1, 0, 0]の場合はX軸周り)しています。 上記により、DagGestureで取得した上下左右の動きをモデルの回転に変換することで、直接物体をつかんで回転させているようなジェスチャを実現しています。 4. VisionPro動作確認 実行すると、以下のような挙動になります。 Dragしている手の動きに応じて、モデルが回転していることを確認できました。 終わりに 今回はSpatialComputingの入門編Part2として、VisionProにて導入された目および手を用いたインタ ラク ションを実装しました。 今回はシステムが提供する ジェスチャー を用いましたが、ARKitを用いることで独自のカスタム ジェスチャー を実装することも可能です。ARKitの空間認識機能やアンカー機能を用いるには、今回実装したようなWindowやVolumeではなく、ImmersiveViewでの実装が必要になります。 次回 Part3 はImmersiveViewの実装方法をご紹介します。 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちは一緒に働いてくれる仲間を募集しています! 電通総研の採用ページ 参考文献 Design for spatial input Gestures simd_quatf(angle:axis)関数 執筆: @yamashita.yuki 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
thumbnail こんにちは!金融ソリューション事業部WEB3グループの山下です。 本記事は「SpatialComputing入門」シリーズのPart3となります。 前回の Part1 および Part2 にて紹介したWindowやVolumeとは別の、現実世界とインタ ラク ションが可能なImmersiveViewを用いて現実世界の平面に対してオブジェクトを配置します。 「SpatialComputing入門」シリーズ: Part1:3Dオブジェクトを生成して空間に配置する Part2:3DオブジェクトにGestureを追加する Part3:現実空間の平面に3Dオブジェクトを配置する WorldTrackingについて 実装に入る前に、今回のデモ実装で利用するWorldTracking機能、およびARKitのプライバシーについて解説します。 画像参考: Meet ARKit for spatial computing iOS などと異なりvisionOSでは、カメラや加速度センサーのデータがアプリに対して直接開放されていません。 その代わり、ARKit daemonsによって適切に処理された結果をアプリが利用できるようになっています。 また基本的に、ARKitを用いた空間認知やハンドト ラッキング にアクセスする為にはユーザーの アクセス許可 が必要になります。 画像参考: Meet ARKit for spatial computing ただ今回用いる AnchorEntity に関してはWorldTrackingのデータを用いており、ユーザーによるアクセス許可は不要になります。 動作環境 Apple M1 Max ( macOS 14.5) Xcode 15.4 VisionPro (visionOS 1.2) 実装手順 App.swiftにImmersiveViewを追加 ContentViewにButtonを追加 ImmersiveView.swiftを新規作成 VisionPro動作確認 1. App.swiftにImmersiveViewを追加 Part2 との変更点のみ記載します。 ModelEntityTestApp.swiftのbody内に、以下を追記します。 ImmersiveSpace(id : "ImmersiveSpace" ) { ImmersiveView() } Part1 でVolumeを追加した際と同様、ImmersiveSpaceでもidを指定して呼びだす際に利用します。 2. ContentViewにButtonを追加 ContentView.swiftのbody内に、ImmersiveViewを開く為のButtonを追加します。 Button(action : { showImmersiveSpace = true }) { Text( "Show ImmersiveSpace" ) } 今回は、既にVolumeを開く為のButton Viewがあった為、VStack内で上記Buttonを追加しました。 3. ImmersiveView.swiftを新規作成 新規にImmersiveView.swiftを作成して、以下を記載します。 import SwiftUI import RealityKit import RealityKitContent struct ImmersiveView : View { @State private var contentReference : RealityViewContent? @Environment ( \.dismiss ) private var dismiss var body : some View { RealityView { content in self .contentReference = content setupTimer() } .ignoresSafeArea() } private func setupTimer () { Timer.scheduledTimer(withTimeInterval : 0.01 , repeats : true ) { _ in DispatchQueue.main.async { addAnchor() } } } @MainActor private func addAnchor () { guard let content = contentReference else { return } let anchorEntity = AnchorEntity(plane : .vertical) let model = ModelEntity( mesh : .generateCone(height : 0.01 , radius : 0.01 ), materials : [ SimpleMaterial ( color: .yellow , isMetallic: true )] ) anchorEntity.addChild(model) content.add(anchorEntity) removeAnchor(after : 3 , anchor : anchorEntity ) } private func removeAnchor (after seconds : Double , anchor : AnchorEntity ) { DispatchQueue.main.asyncAfter(deadline : .now() + seconds) { anchor.removeFromParent() } } } #Preview { ImmersiveView() } 概要は、以下のとおりです。 変数宣言 @State private var contentReference: RealityViewContent? RealityViewのcontent参照を保持するための変数 外部に定義した関数でRealityViewのcontentにアクセスする際に利用します @Environment(\.dismiss) private var dismiss ビューを閉じるための 環境変数 addAncor関数 AnchorEntity(plane: .vertical) で垂直な面を検出して、アンカーを取得します RealityViewでは、画面に描画されるUIやEntityの変更はメインスレッドで行う必要があります。その為、addAnchor関数に@MainActorを付与しています setupTimer関数 Timerクラスを用いて、0.01秒毎にaddAncor()を呼び出します VisionProのフレームレートは90FPXの為、描画回数の限界を試すために約0.01を指定してみました DispatchQueue.main.asyncを用いることで、メインスレッドで行う必要のあるAddAnchorの呼び出しを、非同期にQuereとして追加しています removeAnchor関数 引数で受け取ったAnchorの削除処理を、同じく引数で受け取った秒数を経過後に実行します DispatchQueue.main.asyncAfterにより、非同期で削除処理を予約しています 4. VisionPro動作確認 実行すると、以下のような挙動になります。 ヘッドセットの動きに合わせて、 AnchorEntity(plane: .vertical) で壁面が自動的に検出され、3Dオブジェクトが配置されることを確認できました。また天井には描画がされないことからも、正しく垂直面を取得できていることが分かります。 終わりに 今回はSpatialComputingの入門編Part3として、現実空間の平面に3Dオブジェクトを配置しました。 今回はシンプルなWorldTrackingを利用しましたが、ARKitを活用することでより詳細な空間の認知やハンドト ラッキング を用いることも可能です。 SwiftUI, RealityKit, ARKitといった馴染みのある開発 フレームワーク を利用することで、驚くほど簡単にVisionProおよびvisionOSのパワフルな機能にアクセス可能です。 今週の WWDC2024 でもvisionOS 2が発表されていますので、引き続き今後のvisionOSの進化にも注目していきたいと思います。 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちは一緒に働いてくれる仲間を募集しています! 電通総研の採用ページ 参考文献 Meet ARKit for spatial computing Tracking specific points in world space WorldAnchor 執筆: @yamashita.yuki 、レビュー: @takami.yusuke ( Shodo で執筆されました )
アバター
ModelEntityで3Dオブジェクトを空間に配置 こんにちは!金融ソリューション事業部WEB3グループの山下です。 本記事では、 Apple Vision Pro 上で、 ModelEntity クラスで生成した3Dオブジェクトを空間に表示する実装例をご紹介します。 本記事は「SpatialComputing入門」シリーズのPart1となります。 以下3つのPartに分けて、visionOSの基礎的なアプリケーション実装方法を解説していきます。 Part1:3Dオブジェクトを空間に配置する Part2:3DオブジェクトにGestureを追加する Part3:現実空間の平面に3Dオブジェクトを配置する visionOSについて初の記事となりますので、初めて Xcode に触れる方でも実装できるよう丁寧に解説していきます。 動作環境 Apple M1 Max ( macOS 14.5) Xcode 15.4 VisionPro (visionOS 1.2) 実装手順 Xcode プロジェクトの作成 Xcode の設定 VisionProの設定 VisionPro動作確認 App.swiftの修正 ContentView.swiftの修正 VolumeView.swiftの作成 VisionPro動作確認 1. Xcode プロジェクトの作成 Xcode プロジェクトを作成します。 Xcode のインストールが未済の方は、 公式サイト より事前にインストールを行ってください。また、開発には Apple のDeveloperアカウントも必要になります。もしまだアカウントをお持ちでない方は 公式サイト より登録を行ってください。 Xcode 起動画面にて、「Create New Project...」を押下します。 テンプレートで「visionOS > App」を選択します。 オプションを設定します。 visionOSの新規追加要素として、以下を設定しています。 initial Scene: 「Window」 Immersive Space Renderer:「None」 プロジェクト作成が完了すると、以下の画面が立ち上がります。 2. Xcode の設定 上部メニューの Setting > Platformを開いて、「visionOS」「visionOS Sumulator」がインストールされていることを確認します。もしまだであれば、右側の「Get」ボタンを押下してインストールします。 上部メニューの Window > Devices and Simulatorsを開いて、VisionProを追加します。 VisionProが Mac と同じネットワークに繋がれており、同じ iCloud アカウントでログインされていれば表示されるので、「Pair」ボタンを押下します。 Xcode 上部メニューから、Run Destination として「 Apple VisionPro」を選択します。 3.VisionProの設定 VisionProで以下の設定を行います。 (基本的に iOS やiPadOSの実機テストと同様の手順になります) Settingアプリを開き、Privacy & Security > Developer ModeをONにします。 画像参考: Apple公式ドキュメント General > VPN & Device Managementから、「Developer名を信頼」をタップします。 画像参考: Apple公式ドキュメント 4.VisionPro動作確認 Xcode で三角の再生ボタンを押下します。 ビルドが完了すると、VisionProで以下のアプリが立ち上がります。 5. App.swiftの修正 ModelEntityTestApp.swiftを、以下に書き換えます。 import SwiftUI @main struct ModelEntityTestApp: App { var body: some Scene { WindowGroup { ContentView() } WindowGroup(id: "volume") { VolumeView() } .windowStyle(.volumetric) .defaultSize(width: 0.5, height: 0.5, depth: 0.5, in: .meters) } } 概要は以下のとおりです。 一つ目のWindowGroupで、ContentViewを表示 Sceneの最初に定義されるViewが、アプリ起動時に表示される画面となる デフォルトでは、VolumeではなくWindowとして扱われる 二つ目のWindowGroup(id: "volume")で、VolumeView()を定義 .windowStyle(.volumetric) で、Volume Sceneとして定義 .defaultSize(width: 0.5, height: 0.5, depth: 0.5, in: .meters) で、Volumeのサイズを指定 idは、後で別画面から呼びだす際のIdentifierとなる 6. ContentView.swiftの修正 ContentView.swiftを、以下に書き換えます。 import SwiftUI import RealityKit import RealityKitContent struct ContentView : View { @Environment ( \.openWindow ) private var openWindow var body : some View { VStack { Text( "Hello, world!" ) Button( "Open Volume" ){ openWindow(id : "volume" ) } } .padding() } } #Preview(windowStyle : .automatic) { ContentView() } 概要は以下のとおりです。 @Environment(\.openWindow) でWindowを開く為の 環境変数 を定義 VStackでTextとButtonを設置 Button Actionで openWindow(id: "volume") としてVolumeViewを定義 7. VolumeView.swiftの作成 Xcode のFile > New > File...から、新規Swiftファイルを作成します。 Swift Fileを選択します。ファイル名はVolumeView.Swiftとします。 VolumeView.swiftを、以下に書き換えます。 import SwiftUI import RealityKit import RealityKitContent struct VolumeView : View { var body : some View { RealityView { content in // Generate ModelEntity let model = ModelEntity( mesh : .generateSphere(radius : 0.1 ), materials : [ SimpleMaterial ( color: .lightGray , isMetallic: true )] ) // Add components model.components. set (InputTargetComponent()) model.components. set (CollisionComponent(shapes : [ .generateSphere ( radius: 0.1 )] )) // Add ModelEntity to RealityView content content.add(model) } } } 概要は以下のとおりです。 RealityView内で、以下を定義 ModelEntityの作成 mesh: :メッシュの定義。今回は半径0.1mの Sphere を指定 materials: :マテリアルの定義。今回はライトグレーかつメタリックな質感を指定 コンポーネント の追加 InputTargetComponent() と CollisionComponent を追加。 今回は実装しないが、インタ ラク ションを行う際に必須となる コンポーネント Entitiyを追加 content.add(model) RealityViewにModelEntityを追加 8.VisionPro動作確認 ビルドが完了すると、ContentViewが表示されます。 "Open Volume"ボタンを押下することで追加のVolume Sceneで Sphere が表示されます。 ちなみに今回紹介したコードでは Sphere を指定しましたが、RealityKitが提供している別の形状も指定可能です。 以下はConeを指定した結果です。 終わりに 今回はSpatialComputingの入門編として、VisionPro上に3Dコンテンツを表示する実装例をご紹介しました。 全く新しいインターフェースでありつつも、これまで iOS や iPad などで使われてきたSwiftUIやRealityKitの仕組みの延長線上で実装が可能な為、開発者体験が非常に洗練されていることが分かります。 今回紹介したWindowやVolumeは、まだ"枠"が存在するSceneになります。 今後のテーマとしては、"枠"の存在しないFull Spaceや、空間認識が可能なARKitを活用したVisionProらしい没入体験についても知見を紹介していければ幸いです。 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちは一緒に働いてくれる仲間を募集しています! 電通総研の採用ページ 参考文献 Meet SwiftUI for spatial computing Adding 3D content to your app 執筆: @yamashita.yuki 、レビュー: @nagamatsu.yuji ( Shodo で執筆されました )
アバター
こんにちは、 電通 総研金融ソリューション事業部の岡崎です。 前回の記事 では群衆の服の色をランダムにする手順まで説明しました。 この記事は前回の続きからになりますので、まだご覧になっていない方は、前編も是非ご一読ください! 検証環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 4070 Ti Game Engine: Unreal Engine 5.2.1 実装手順 群衆で使用する人型のメッシュの用意 人型のメッシュにアニメーションを付与する 群衆のアセットをPCGで配置する 群衆の服の色をランダムにする 群衆の髪型をランダムにする 群衆のアニメーションをランダムにする 群衆の性別をランダムにする 5. 群衆の髪型をランダムにする 後編では初めにMetaHumanのアセットに髪を生やしていきます。 「BP_Cheering_metamale」を開き、 コンポーネント 追加ボタンから「StaticMesh」を追加して「hair1」と 命名 します。 MetaHumanCreatorからダウンロードした際に、髪の毛のメッシュも入っているのでそれを使っていきます。 群衆という事で負荷を考えて、できるだけ軽いLOD7で使うための髪型を使いました。 「hair1」のスタティックメッシュのセレクトボックスで 「コンテンツ>Crowd>Character>Male>m_001>Hair>Helmets」の中にある「Hair_S_AfroFade_Helmet_LOD7」を選択します。 このままでは追加した髪の毛と、人間の本体が連動していないので、ソケットというものを使って連動していきます。 「hair1」を選択した状態で、詳細タブにあるソケットの欄から親ソケットを「HeadTop_End」に変更します。 これはスケルタルメッシュのボーンの名前で、頭の頂点にある点と髪の毛を連動させるために変更しました。 次に髪の毛の位置を変更して頭にの位置に合わせます。 コンパイル と保存をする事で、ビューポート上のMetaHumanに髪の毛を生やす事が出来ました。 ここから髪型をランダムにするために、他の髪型も追加していきます。 「BP_Cheering_metamale」を開きます。 一旦「hair1」の「Visible」のチェックを外し、 レンダリング されない様に変更します。 次に「hair1」の時と同じ要領で「hair2」、「hair3」を作成します。 髪型は「コンテンツ>Crowd>Character>Male」内に入っているスタティックメッシュを自由に使います。 「hair2」、「hair3」も同様に「Visible」のチェックを外し、 レンダリング されない様に変更しておきます。 イベントグラフに移動し、ランダムに髪型を レンダリング する処理を作っていきます。 先ほどまで作っていたノードに追加して、「整数型でスイッチ」ノードを追加します。 「Selection」のピンには0から2までの整数がランダムで生成される「RandomIntegerInRange」ノードをつなげます。 「0」のピンに「SetVisibility」ノードをつなぎ、ターゲットを「Hair1」とします。 同様に「hair2」、「hair3」を レンダリング する用のノードも作成します。 これにより、髪型もランダムに生成される様になりました。 現段階では、アニメーションが全て同じものを使用しており、再生のタイミングも一緒なので 次の工程ではアニメーションを複数用意して、タイミングもずらす処理を行っていきます。 6. 群衆のアニメーションをランダムにする まずはアニメーションの開始タイミングをずらす処理を作成します。 先ほど「BP_Cheering_metamale」に作成したノードにつづけて「Delay」のノードをつなぎます。 「Duration」ピンには「RandomFloatInRange」のノードをつなげて、0~3の間でランダムにFloat型の数値の秒数待機させます。 これにつなげて「PlayAnimation」ノードを作成して、ターゲットを「SkeletalMesh」にして、「NewAnimToPlay」のセレクトボックスで「Cheering 1 Anim_mixamo_com」を選択します。 これによりアニメーションの開始タイミングをずらす事ができます。 今作成したノードの一覧がこちらになります。 次に違うアニメーションを複数用意していきます。 Mixamo に移動して、使用したいアニメーションを検索します。 ダウンロードが完了したら、今まで行ってきたことと同じ手順で新規のブループリントアクターを作成します。 今回私は「 HipHop 」というアニメーションを使って「BP_ hiphop _metamale」を作成しました。 まだこの新規アクターはPCGグラフ上では使用していないので、もちろんビューポートには出てきません。 PCGグラフを編集していきます。 先ほど作成した「SpawnActor」の接続を一度解除して、代わりに「TransformPoints」ノードをつなぎます。 ここでは作成するPCGコンテンツの大きさや向きなどを調整できます。 私は「ScaleMin/Max」で90%~110%の大きさにランダム性を持たせて生成させています。 また、向きに関しても前方に対して70~120度のランダム性を持たせています。 次に「DensityFilter」ノードを追加します。 「Lower/Upper Bound」の値を編集することで、PCGコンテンツをフィルターする事ができます。 具体的には「LowerBound」と「UpperBound」に「0」と「0.25」と入力すると、 PCGコンテンツ内のBound値が0~0.25までの間のコンテンツにのみ干渉する事ができます。 デバッグ モードで見るとボックスの色の濃い黒いものだけをフィルターする事ができます。 (Bound値でフィルターする前) (Bound値でフィルターした後) 「DensityFilter」から「SpawnActor」に接続する事で、 デバッグ ボックスの黒い位置だけに人間を生成できます。 次に、同様に「Lower/Upper Bound」の値を「0.26」と「0.5」に設定した「DensityFilter」ノードを追加します。 デバッグ モードで見ると濃いグレーのボックスに干渉できる事が確認できます。 この「DensityFilter」にも「SpawnActor」を接続し、新規で作成した「BP_ hiphop _metamale」をセットします。 これにより、PCGの黒いボックスが表示された場所は「BP_Cheering_metamale」を表示して 濃いグレーのボックスが表示された場所には「BP_ hiphop _metamale」が表示できる様になります。 同様に他のアニメーションのブループリントアクターを作成する事で、3つのアニメーションがランダムで配置されたPCGを作成する事ができます。 7. 群衆の性別をランダムにする 最後に群衆の性別をランダムにするために女性のアセットを追加していきます。 とはいえ、ここでは今まで紹介してきた内容を繰り返し行うだけの内容になるので、詳細な説明は割愛いたします。 まず「MetaHumanCreator」を使用して女性を作成し、「Quixel Bridge」からプロジェクトへ追加します。 MetaHumanをスタティックメッシュ化してMixamoを使いアニメーションを追加する事で、 任意のブループリントアクターを作成し、PCGに追加する事ができます。 「DensityFilter」の数を増やすことによってアクターを好きな種類だけ増やすことができます。 おわりに 今回はPCGを使ってアニメーションのあるキャ ラク ターをランダムに生成する方法をご紹介しました。 スケルタルメッシュにルールを作成して、いろいろなアニメーションをつける事ができるので、いろいろな場面で応用できそうな技術です。 しかし、木などのスタティックメッシュを配置するよりも、アクターを配置する方がCPU, GPU ともに負荷がかかるので 大量に配置する際は負荷対策もしっかりと学ばないといけないと感じました。 記事をご覧いただきまして、ありがとうございました! 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! 電通総研採用ページ 参考 https://mozpaca.hatenablog.com/entry/20170816/1502873390 https://www.youtube.com/watch?v=ZXh6oesGTGg https://gam0022.net/blog/2024/01/01/ue5-pcg-introduction-tips/ https://www.youtube.com/watch?v=PwEDb84sxi8 執筆: @okazaki.wataru 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
電通 総研 X(クロス) イノベーション 本部 の三浦です。 現在開催されている AWS re:Inforce 2024 の Keynote にて、 AWS IAMのrootユーザーおよびIAMユーザーのMFA(多要素認証)としてPasskeyのサポートが発表されました。 以下、公式文書です。 AWS adds passkey multi-factor authentication (MFA) for root and IAM users AWS Identity and Access Management now supports passkey as a second authentication factor 概要 本アップデートで、IAMユーザーのMFAデ バイス としてPasskeyが利用できるようになりました。 わずらわしいTOTPともおさらばです。6桁の数字を残り時間を見ながら入れるの結構ストレスだったのではないでしょうか? ということで設定 では、サクサク試していきましょう。 『MFAデ バイス の割り当て』を選び MFAデ バイス を選択で、「パスキー、または、セキュリ ティー キー」を選択 で、下記のようなダイアログが私の環境では出てきたので(環境によって動作が異なります)、 ここでは スマートフォン 、 タブレット を選択 そうすると、下記のようなバーコードが出てきてカメラで読み込ませるだけで パスキーが登録されました。 という感じで設定が終わりました。とても簡単ですね。 残課題 Windows Helloのpin認証、顔認証で利用できるとさらに便利になるかと考えました。しかし、筆者の持っている端末では Windows Hello、または外部セキュリ ティー キー」を選択すると、セキュリ ティー キーの使用を強制されてしまいました。この辺は利便性、セキュリ ティー 強度に大きく関係しますので何が最適かよく検討する必要がありそうです。 使いどころ 社としては、認証をIDP( アイデンティティ プロバイダー)に依存させるべきですが、どうしてもその標準から外れる部分が出てきます。筆者は全社レベルで管理者業務を行っており、IDPのトラブル時にも対応できる必要があります。しかし、これをTOTP(タイムベース ワンタイムパスワード )で管理していると、数百ものTOTPエントリが必要になり、非常に煩雑に感じていました。 が、Passkeyを使うことにより、モバイルや Windows Helloで簡易に認証を集約できるメリットが得られます。 私たちは一緒に働いてくれる仲間を募集しています! クラウドアーキテクト 執筆: @miura.toshihiko 、レビュー: @akutsu.masahiro ( Shodo で執筆されました )
アバター
こんにちは、 電通 総研金融ソリューション事業部の岡崎です。 今回はUE5でPCGを使用した群衆を作成します。 作成する群衆は、ライブ会場などに配置するためのアセットとして使用する想定ですので、 後ろから見た姿の、服の色や髪、背丈や性別、動きのアニメーションなどをランダムになるように作成します。 また、今回は後ろ姿のみが映る予定ですので、顔の違いは考慮せずに作成していきます。 本記事は前編後編に分かれており、この記事では手順4の「群衆の服の色をランダムにする」までをご紹介します。 検証環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 4070 Ti Game Engine: Unreal Engine 5.2.1 実装手順 群衆で使用する人型のメッシュの用意 人型のメッシュにアニメーションを付与する 群衆のアセットをPCGで配置する 群衆の服の色をランダムにする 群衆の髪型をランダムにする 群衆のアニメーションをランダムにする 群衆の性別をランダムにする 1. 群衆で使用する人型のメッシュの用意 まずは群衆で使う人型のアセットを用意します。 今回は、EPICが提供しているデジタルヒューマンの MetaHuman を使用します。 別記事ではありますが、写真からMetahumanを生成する方法については 松崎さんの記事 が参考になるので、ぜひご覧ください。 まずはUnrealEngineのコンテンツ追加タブから「Quixel Bridge」を選択します。 その後出てくるウィンドウからMetaHumanCreatorで作成した任意のMetaHumanを選択し、「Download」ボタンを押下します。 ダウンロードが完了したらMetaHumanをプロジェクトに追加するために「Add」ボタンを押下します。 コンテンツブラウザ内の「コンテンツ>MetaMumans>meta_male(作成したMetaHumanの名前)」にファイルが追加されます。 追加された「BP_meta_male」を アウトライナー に配置する事で、MetaHumanが正常にインポートできた事を確認します。 追加されたMetaHumanはスケルタルメッシュを使用しているのですが、 大量に配置した際のパフォーマンス向上の為スタティックメッシュに変換して使用したいので変換作業を行います。 画面上部のアクタタブから「スタティックメッシュへ変換」を押下します。 追加したい場所を選び、保存を押すとスタティックメッシュが生成されます。 髪の毛は変換元のスケルタルメッシュには含まれていないため、スタティックメッシュに変換した際に消えてしまいますが、 後々追加するので問題はありません。 さらに作成したスタティックメッシュのファイルを右クリックして「アセットアクション」から「エクスポート」を選択してFBXファイルを作成します。 次の工程で、作成したスタティックメッシュのFBXファイルにアニメーションをつけていきます。 2. 人型のメッシュにアニメーションを付与する ここではMetaHumanを元にしたスタティックメッシュに群衆のアニメーションをつけていきます。 アニメーションを作成するにあたり Adobe 社が提供している Mixamo というサービスを利用します。 Mixamo は、二足歩行のアセットに対して、自動でス ケルト ンを作成して、 用意してある、歩行やダンスなど、さまざまなアニメーションをアセットに付与する事ができるサービスです。 Mixamoを開いたら、まずFBXファイルをアップロードします。 画面右側の「Upload character」を選択し、FBXファイルを選びます。 キャプチャのように、作成したMetaHumanのスタティックメッシュが表示されるので「Next」を押します。 次にアニメーションで使用するためのリグ付けを行っていきます。 キャプチャのように右側のサンプルを参考に、顎や腕、膝などの場所を登録します。 完了して生成されるまで少し待つと、リグのついたMetaHumanが完成します。 次に群衆に使いたいアニメーションを検索します。 私は今回、ライブ会場で盛り上がる人を作りたかったので「Cheering」のアニメーションを使いました。 選択したら、右側の「Download」ボタンを押します。 「Skin」を「With Skin」にしてダウンロードします。 ダウンロードしたファイルをUE上にインポートする事で、アニメーションに対応したスケルタルメッシュを使用する事ができます。 アウトライナー に配置する事でMetaHumanがCheeringのアニメーションをしているのが確認できます。 今回は、アニメーション毎に、それぞれ異なるスケルタルメッシュを用いてアニメーションを作成します。 ちなみにアニメーションを付与する方法として、 共通のスケルタルメッシュに対してアニメーションシーケンスを複数切り替える方法もありますが、 こちらは後日別の記事で紹介する予定です。 (Mixamoからアニメーションをダウンロードして、UE上でスケルタルメッシュにリターゲティングを行い、アニメーションを再現する) 次の手順で、現在作成したアニメ付きのMetaHumanをPCGを利用して複数配置し、群衆にしていきます。 3. 群衆のアセットをPCGで配置する まずは以前の私の PCGの記事 を参考にPCGを作成していきます。 PCG作成の詳しい方法は本記事では割愛するので、 PCGの記事 を参考にしてください。 「PCGVolume」と「PCGグラフ」を作成します。 「PCGグラフ」を開き、「Input」の「Landscape」から「SurfaceSampler」をつなぎます。 群衆を作成するにあたり、大体の人数のあたりをここでつけておきます。 次に群衆を配置するために「SpawnActor」ノードを作成します。 前回のこちらの記事 では「StaticMeshSpawner」を使用しましたが、 アニメーションをさせるために必要なスケルタルメッシュアセットは、「StaticMeshSpawner」では配置できないので、「SpawnActor」ノードを使用します。 本来はスケルタルメッシュスポナーのようなものを使いたいですが、そのようなノードはないので「SpawnActor」を使用します。まずは「SpawnActor」で使用するブループリントアクターを作成します。 Mixamoで出力されたアセットはスケルタルメッシュのアセットなのでまずはこれをブループリントアクター上に配置します。 コンテンツブラウザ上でブループリントアクターを「BP_Cheering_metamale」という名前で生成します。 コンポーネント 追加ボタンから「SkeletalMesh」を選択して追加します。 追加したら右側の詳細パネルの「メッシュ>SkeletalMeshAsset」のセレクトボックスから、先ほどMixamoで作成したUE上にインポートしてきたスケルタルメッシュのファイル「Cheering_1」を選択します。 さらに詳細パネルの「Animation>AnimationMode」の項目を「アニメーションアセット」にセットします。 アニメーションアセットを選択するタブが出てくるので同時にインポートしてきた「Cheering_1_Anim_mixamo_com」を選択します。 これにより、MetaHumanが手を挙げているアニメが再生されているブループリントアクターが出来ました。 PCGグラフに戻り、「SpawnActor」の「TemplateActorClass」から現在作成したブループリントアクター「BP_Cheering_metamale」を選択します。 また「Option」を「MergePCGOnly」を選びます。 この設定は後述する「ConstructionScript」を使用する際の条件になります。 これで アウトライナー 上にPCGで作成した群衆が配置できます。 それでは次の工程で、配置した群衆の服の色をランダムにしていきます。 4. 群衆の服の色をランダムにする まずは服の色をランダムにするためのマテリアルを作成します。 コンテンツブラウザでマテリアルを作成します。 私は「M_RandomColor」と 命名 しました。 作成したらベー スカラー のピンに「VectorParameter」を追加して「basecolor」と 命名 します。 パラメーターは全てゼロで黒のままで大丈夫です。 私の場合は服の素材を表すために「ラフネス」のピンに「10」の値を接続して反射をなくしています。 次にMixamoから持ってきたスケルタルメッシュのファイル「Cheering__1」を選択します。 編集画面上で服のマテリアルが入っているエレメント2の「M_Lights_Shorts」のマテリアルを 今作成した「M_RandomColor」に変更します。 ツヤの消えた黒い服を着ていることを確認します。 次に「BP_Cheering_metamale」を開き、「Construction Script」タブを開きます。 「CreateDynamicMaterialInstance」ノードを作成してターゲットを「SkeletalMesh」にします。 「Construction Script」に書いた処理は、ゲーム中には実行されずに、レベルにそのブループリントを配置、変更した時点で処理が実行されます。 また「Element index」の値を「2」にします。 (これは「M_Lights_Shorts」のマテリアルがエレメント2に格納されているため) 次に「ReturnValue」を変数に昇格させ、変数名を「DMI_random」とします。 「BP_Cheering_metamale」の「イベントグラフ」に戻ります。 「BeginPlay」の時にシャツのマテリアルの値を変更するために「SetVectorParameterValue」ノードをつなぎます。 ターゲットは先ほど作成した「DMI_random」にして、「ParameterName」は、マテリアル作成時に 命名 した「basecolor」と書きます。 次に「MakeColor」ノードを作成し、「RGB」にそれぞれ「RandomFloat」の値を接続します。 (「A」はデフォルトのまま「1.0」にしておきます) これでイベントが起こるたびに服の色のマテリアルの「RGB」の値が変わり、服の色がランダムに変更される様になります。 ビューポートに戻ると服の色がランダムになっているのが確認できます。 おわりに 前編では、MetaHumanをPCGで配置する方法や、mixamoの使用方法、マテリアルの変更方法などを紹介しました。 今回は要件的にmixamoから出力したスケルタルメッシュとアニメーションをそのまま使いましたが、 アニメーションだけを書き出して既存のキャ ラク ターの動きを上書きするためのリターゲティング処理などもあります。 そちらも今後使う機会がたくさんありそうなので勉強をしてみたいと思います。 後編では、髪型や性別、アニメーションをランダムに振り分ける方法などもご紹介しています。 近日中に公開するので、ぜひそちらもご覧ください! 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! 電通総研採用ページ 参考 https://mozpaca.hatenablog.com/entry/20170816/1502873390 https://www.youtube.com/watch?v=ZXh6oesGTGg https://gam0022.net/blog/2024/01/01/ue5-pcg-introduction-tips/ https://www.youtube.com/watch?v=PwEDb84sxi8 執筆: @okazaki.wataru 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
こんにちは。コミュニケーションIT事業部 ITソリューション部の英です。 普段はWebアプリや スマホ アプリの案件などを担当しています。あと、趣味で AI を勉強しています。 今回は IoTデ バイス (温度センサー)の異常検知 をテーマに記事を書いてみます。 温度センサーから Amazon Kinesis Data Streamsにデータが送信され、送信されたデータに対して Amazon Managed Service for Apache Flinkでリアルタイムにデータを処理します。 今回は 閾値 で異常を検知し、 SNS に通知するシンプルな仕組みにしています。 Amazon Kinesis Data Streams(KDS)とは Amazon Kinesis Data Streams(KDS)は、リアルタイムで大量のデータを収集、処理、および分析するためのサービスです。データのストリーミング処理を簡単に行うことができます。 高いスケーラビリティ:必要に応じて スループット を調整可能で、大規模なデータ処理にも対応します。 データの分割:データを複数のシャードに分けることで、並列処理が可能です。 データ保持:デフォルトで24時間、最大8760 時間(365日)までデータを保持できます。 Amazon Managed Service for Apache Flinkとは? Apache Flink は、リアルタイムでデータを処理・分析するためのソリューションです。 マネージドサービスなのでインフラの管理が不要で、ユーザーはコードの記述に集中できます。 Kinesis Data Streams、 Kinesis Data Firehose、 Amazon S3 などの AWS サービスと簡単に統合できます。 ここから本題 STEP1:データストリームの作成 Kinesis Data Streamsを選択した状態で「データストリームの作成」を押下します。 以下のように設定します。今回はシャード分割なしとします。 STEP2:通知用の SNS を作成する Amazon SNS のトピックを作成し、Eメールでサブスクライブしておきます。 とくに難しい手順はないのでこの記事では解説しません。 STEP3:Cloud9に Kinesis への権限を与える Cloud9から先ほどのデータストリームに送信できるように権限を設定します。 新規のIAMポリシーを作成して、新規ロールにアタッチしましょう。 Cloud9用のデフォル トロール にポリシーをアタッチすることはおすすめしません。 特定のデータストリームにだけ権限を与えればよいのでARNを指定しましょう。 その後、Cloud9が稼働しているEC2のIAMロールを切り替えます。 STEP4:テストデータの送信 Cloud9を立ち上げ、以下の スクリプト を実行します。 1秒ごとに温度データが正弦波で連携され、0.5%の確率で異常データが生成されます。 以下のようにデータが流れ始めます。 正常なデータ:20~30℃の範囲内 異常なデータ:35~50℃の範囲内 送信されたデータは Kinesis のデータビューワーでも確認できます。 STEP5: Apache Flinkのノートブックを作成 データ分析タブからノートブックを新規作成できます。 ボタン一つでGlueのDBも自動生成されます。 作成が完了すると「Open in Apache Zeppelin」からノートブックを開くことができます。 STEP6:サンプル SQL の確認 初期状態から全文SELECTの SQL が作成されています。 connectorに kinesis を指定し、rawデータを取得しています。 STEP7:新規ノートブックを作成する(カスタマイズ用) Managed Apache Flinkの画面で「Studio ノートブックを作成」を押下します。 先ほど自動生成されたDBを流用します。 ノートブックに紐づいているIAMロールに以下の権限を追加します。 Kinesis Data Streamsの読み書き Glueの読み書き ※すでにGlueを利用している場合はリソースを指定してください SNS の発行 { "Effect": "Allow", "Action": [ "sns:Publish" ], "Resource": "arn:aws:sns:ap-northeast-1:(アカウントID):(トピック名)" }, { "Effect": "Allow", "Action": [ "kinesis:ListShards", "kinesis:DescribeStream", "kinesis:GetRecords", "kinesis:GetShardIterator", "kinesis:PutRecord", "kinesis:PutRecords" ], "Resource": "arn:aws:kinesis:ap-northeast-1:(アカウントID):stream/TemperatureStream" }, { "Effect": "Allow", "Action": [ "glue:GetPartitions", "glue:GetTable", "glue:GetTables", "glue:GetDatabase", "glue:GetDatabases", "glue:DeleteTable", "glue:CreateTable" ], "Resource": [ "arn:aws:glue:ap-northeast-1:(アカウントID):catalog", "arn:aws:glue:ap-northeast-1:(アカウントID):database/*", "arn:aws:glue:ap-northeast-1:(アカウントID):table/*" ] } STEP8: SQL を定義していく まずは、ストリームデータが取得できるか、デフォルトの SQL を投げてみます。 Cloud9からダミーデータを流してみると、 JSON 形式でrawデータが返ってきました。 次に JSON 内の要素を分割してみます。 temperatureとtimestampの2つに分割することができました。 STEP10:異常検知のジョブを定義する 以下をノートブックに貼り付けて実行する。 <>内は環境に合わせて書き換えてください。 %flink.pyflink import boto3 from pyflink.table import TableEnvironment, EnvironmentSettings, DataTypes from pyflink.table.udf import udf from pyflink.datastream import StreamExecutionEnvironment from pyflink.table import StreamTableEnvironment # ストリーミング実行環境とテーブル環境の設定 exec_env = StreamExecutionEnvironment.get_execution_environment() exec_env.set_parallelism(1) t_env = StreamTableEnvironment.create(exec_env, environment_settings=EnvironmentSettings.new_instance().in_streaming_mode().build()) # データソースの作成 t_env.execute_sql(""" CREATE TABLE raw_single_column_table ( single_column_output STRING ) WITH ( 'connector' = 'kinesis', 'stream' = '<ストリーム名>', 'aws.region' = '<リージョン名>', 'scan.stream.initpos' = 'LATEST', 'format' = 'raw' ) """) # データ解析ビューの作成 t_env.execute_sql(""" CREATE VIEW parsed_data AS SELECT CAST(JSON_VALUE(single_column_output, '$.temperature') AS DOUBLE) AS temperature, TO_TIMESTAMP(CAST(JSON_VALUE(single_column_output, '$.timestamp') AS STRING), 'yyyy-MM-dd''T''HH:mm:ss.SSSSSS''Z''') AS `timestamp` FROM raw_single_column_table """) # UDF(ユーザー定義関数)を作成して、SNS通知を送信 @udf(result_type=DataTypes.BOOLEAN()) def notify_sns(temperature, timestamp): if temperature >= 30: sns_client = boto3.client('sns', region_name='ap-northeast-1') message = f"Temperature alert: {temperature}℃ at {timestamp}" sns_client.publish( TopicArn='arn:aws:sns:ap-northeast-1:<アカウントID>:<トピック名>', Message=message ) print(message) return True return False # UDFをテーブル環境に登録 t_env.create_temporary_function("notify_sns", notify_sns) # 結果を出力するテーブルを作成 t_env.execute_sql(""" CREATE TABLE alert_table ( temperature DOUBLE, `timestamp` TIMESTAMP(3), notified BOOLEAN ) WITH ( 'connector' = 'print' ) """) # 温度が30℃以上のデータをフィルタリングし、通知を送信するSQLクエリを実行 result_table = t_env.sql_query(""" SELECT temperature, `timestamp`, notify_sns(temperature, `timestamp`) AS notified FROM parsed_data WHERE temperature >= 30 """) # 結果をalert_tableに保存 result_table.execute_insert("alert_table") # ストリーミングジョブの実行 exec_env.execute("Temperature Alert Job") ジョブの実行状況はノートブックの右上の「FLINK JOB」を押下することで確認できます。 ログやチェックポイントなど詳細なジョブの実行状況を監視できます。 Cloud9からダミーデータを流すと、Eメールで通知されることが確認できました。 温度センサーのデータが30℃以上ならばメールで通知される仕組みです。↓は45.61℃。 まとめ IoTデ バイス の温度センサーから送信されるデータをリアルタイムで処理し、異常を検知するシステムの構築方法を紹介しました。 今回はメールで通知するだけでしたが、 AWS の様々なサービスと統合できるので、業務シナリオに合わせて柔軟に組み替えてリアルタイム監視システムを構築していきましょう。 また今回は 閾値 で異常を検知しましたが、時系列データの異常検知にはRCF(ランダムカットフォレスト)を用いることが一般的になっています。 これはまた別の記事で紹介しようと思います。 ↓ のスターを押していただけると嬉しいです。励みになります。 最後まで読んでいただき、ありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! <電通×IT>電通グループ基幹システムプロジェクトマネージャー <電通×IT>顧客DX案件プロジェクトマネージャー <電通×IT>クラウドアーキテクト <電通×IT>アプリケーションアーキテクト 執筆: 英 良治 (@hanabusa.ryoji) 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
こんにちは、 電通 総研金融ソリューション事業部の岡崎です。 今回はUE5.2の機能であるプロシージャルコンテンツ生成 フレームワーク (PCG:Procedural Content Generation Framework)に関して、 前回の記事 に引き続き、より詳しい機能の紹介を行っていきます。 今回はPCGのスプラインという機能を使用して、PCGで草を配置し、さらに道を生成する方法を紹介します。 検証環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 4070 Ti Game Engine: Unreal Engine 5.2.1 実装手順 PCGを使用して草を配置 スプラインを作成し、レベル上に配置 PCGグラフを変更してスプライン上の草を排除 スプラインで任意の範囲を指定して草を配置 1. PCGを使用して森を生成 前回の記事 と同様、まずはPCGを使用して森を生成します。 今回は簡潔な手順のみ説明します。詳細な説明は前回の記事を参考にしてください。 「PCGVolume」を作成し、レベル上のViewPort上に配置します。 次に「PCG グラフ」を作成します。 今回、PDGグラフ名は「Tech1PCGGraph」と 命名 しました。 アウトライナー 上で先ほど範囲を決めるために作成した「PCGVolume」を選択し、子要素の「PCGComponent」を選択します。 インスタンス 直下に Graph を選択する欄があるので、先ほど作成した「PCG グラフ」を選択します。 次に森を作成するためのノードを作成します。 PCGグラフを開き、「Input」の「Landscape」から「SurfaceSampler」ノードを作成して繋ぎます。 さらに「TransformPoints」を接続し、地上に生やしていく草の大きさや向きなどを指定します。 PCGグラフを編集した際に、リアルタイムにViewportに変更が入るので、 同時に変更点を見る事ができるよう、下記キャプチャのようにエディターを配置をして編集をする方法もおすすめです。 次に「StaticMeshSpawner」ノードを作成し、草のStatic Meshを配置します。 「Descriptor > Static Mesh」の順に開き、配置したい草のアセットを選択します。 ここまででPCGを使って任意のアセットをランダムに配置する事ができました。 より詳しいPCGの作成方法は、 前回の記事 に記載しています。 次は、現在作成した草原に道を作成して、道の上には草を生やさないルールを作成します。 2. スプラインを作成し、レベル上に配置 ここでは道を作成するためにスプラインを使用します。 UEのスプラインとは、ポイントとポイントをラインで繋いだ形状のアクターのことを指します。 まずはブループリントアクターを作成し、「BP_PathSpline」と 命名 します。 「BP_PathSpline」を開き、左上のAddボタンから「Spline」を追加します。 レベル上でスプラインを見やすくするために「ScaleVisualizationWidth」を300に設定します。 スプラインも少し伸ばします。 次に画面上部の「クラスのデフォルト」を押下し、詳細パネルから「タグ」を検索し、プラスボタンからインデックスを「Path」と 命名 します。 コンパイル とセーブをして、「BP_Path」をViewPortに配置します。 作りたい道の向きに合わせて「Alt」キーを押しながら頂点をドラッグしてPathを伸ばします。 (スプラインが見えづらかったので、草を一度非表示に変更しています) PCGで配置した草の上に道にしたいスプラインを配置できたら、PCGグラフに戻ります。 3. PCGグラフを変更してスプライン上の草を排除 先ほどのPCGグラフ「Tech1PCGGraph」を開いて、PCGグラフを編集します。 「GetSplineData」ノードを作成し、詳細パネルの「ActorSelectionTag」を先ほど設定した「Path」にします。 また、スプラインを複数配置した時にも使用したいので「SelectMultiple」にもチェックを入れておきます。 次に「SplineSampler」ノードを作成し、「Dimension」のセレクトボックスから「OnSpline」を選択します。 次に「BoundsModifier」ノードを作成し、「SplineSampler」のアウトプットに繋ぎます。 詳細パネルから「BoundsMin / Max」のY/Zを300にして デバッグ モード(ノード上で「D」キー押下)にするとキャプチャのように、スプラインの範囲が白い棒状に表示されます。 次に、この棒状のスプラインと重なっている草を排除するために「Difference」ノードを作成します。 草を生やしている「 Surface Sampler」ノードのアウトプットから「Difference」の「Source」に繋ぎ、 「BoundsModifier」のアウトプットを「Difference」の「Differences」に接続します。 キャプチャのようにノードをつなげる事で、 スプライン上の道に草のメッシュが排除された形で、ランダム生成された形状を作成する事ができます。 4. スプラインで任意の範囲を指定して草を配置 ここでは、スプラインを使用した応用として、スプラインで任意の範囲を指定して草を配置する方法を紹介します。 この記事では、草の生成を「 Surface Sampler」を使用して行いましたが、 スプラインを使用して、任意の形の内部にのみ、草を生やすことなどができます。 まずは草を生やす形を決めるために新しいブループリントアクターを「BP_LandSpline」で作成します。 「BP_PathSpline」の時と同様に、スプラインを作成し、キャプチャのように中心点以外の頂点を削除します。 残った頂点を選択し、右クリックで「スプライン生成パネル」を選択し、サークルを作成します。 頂点の数は6にして、半径を2000ほどにしておきます。 選択されている頂点をキャプチャのように上に移動して削除して、途切れた円形にします。 その後、詳細パネルの「ClosedLoop」にチェックを入れて閉じた丸いスプラインに変更します。 完成したら「BP_PathSpline」と同様にタグに「Land」と 命名 して作成します。 作成した円状のスプラインをViewPortに配置します。 これから配置したスプラインの内部のみ草を生やします。 PCGグラフに戻ります。 「GetSplineData」と、「SplineSampler」を作成し、ノードをつなぎます。 「GetSplineData」はPathの時と同様に、Tagに「Land」を設定します。 「SplineSampler」の「Dimension」のセレクトボックスから「OnInterior」を選択します。 これで円の内側にPCGキューブが配置されました。 「InteriorSampleSpacing」の値を200に、「InteriorBorderSampleSpacing」の値を50程度に調整してPCGキューブの表示間隔を調整します。 また、キャプチャのように大きく隙間が空いている時は、「TreatSplineAsPolyline」にチェックを入れることで均等に配置されます。 続いて、PCGキューブの向きや配置をランダムにするために「TransformPoints」を追加します。 「Projection」のアウトプットから「TransformPoints」に接続して、「RotationMax」のZ値を360に変更し 「Offset Min/Max」のX/Yの値を-200と200に変更し、キューブの重なりなどを排除するために「SelfPruning」ノードを追加します。 当初使用していた「SurfaceSampler」を使用してメッシュを配置していた方法と、今回の「SplineSampler」を使用したメッシュを入れ替える事で、 Landのスプライン上に生成した草から、Pathのスプラインで生成した道上の草を排除する事もできます。 さらに、LandのスプラインやPathのスプラインはコピー生成する事もできるので、下記キャプチャのように草や道を自由に配置する事ができるようになります。 (円形のLandのスプラインを2つに複製したパターン) (円形のLandのスプラインを変形して複製し、Pathのスプラインも複製して配置したパターン) 今回の機能の紹介はこれで以上になります。 おわりに 今回は、PCGで使用できるスプラインを利用して、より複雑なルールの作成方法を紹介しました。 どのような仕様でメッシュを配置したいかを、PCGグラフ上でノードに落とし込む方法を調査するのは時間がかかりますが、一度ノードを作成できれば複製や編集が容易になるため、UEで何かを制作する際にはとても有効な方法だと改めて感じました。 また、スプラインを使用したスタティックメッシュの配置では、スプラインの形や大きさ、数によってさまざまな地形を作ることができそうです。 ひきつづき、PCGに関する知識を学習していきたいと思います。 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! 電通総研採用ページ 参考文献 https://historia.co.jp/archives/34360/ https://ue5exp0.com/pcg/ https://www.youtube.com/watch?v=KZHNyESAyw4 執筆: @okazaki.wataru 、レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
アバター
みなさんこんにちは!  電通 総研 金融ソリューション事業部の松崎です。 今回から、 Unreal Editor for Fortnite(以下UEFN)の初学者向け紹介記事を執筆していきます。 本記事では UEFNってそもそも何なのか Fortniteのクリエイティブモードと何が違うのか Unreal Engine とはどう違うのか 具体的にどうやって導入・操作するのか といった部分を紹介します。 これからUEFNの導入を考えている方は、是非最後までチェックしてください。 目次 UEFN概要 UEFN vs Fortnite(クリエイティブモード) UEFN導入方法 基礎概念と操作方法 目次 1 UEFN概要 1.1 UEFNとは 1.2 UEFN最新情報(2024年4月現在) ① Metahuman to UEFN ② 外部ライセンスIP利用(レゴブランド) 1.3 UEFNの企業活用事例 ① NIKE 「Airphoria」 ② ソニー 「SHIBUYA MULTIVERSE」 2 UEFN vs Fortnite(クリエイティブモード) 2.1 外部アセット利用 2.2 UE機能 ①ランドスケープモード ②ポストプロセスボリューム ③レベルシーケンス 2.3 Verse 2.4 チームでの共同開発 3 UEFN導入方法 3.1 動作環境 3.2 インストール手順 4 基本概念と操作方法 4.1 プロジェクト ①チーム作成方法 ②プロジェクト作成方法 4.2 エディタ 4.3 アセット ①外部アセットのインポート方法 ②UEからUEFNへのアセット移行方法 ③FABマーケットプレイスからの購入方法 4.4 デバイス(仕掛け) ①島設定デバイスの設定方法 ②通常デバイスの設置方法 4.5 Verse おわりに 参考文献 1 UEFN概要 1.1 UEFNとは UEFN( Unreal Editor for Fortnite)とは、その名の通り「 Unreal Engine の機能を使用して、Fortnite上で動くゲームが作成できるツール」です。 この説明を見ると、 Fortnite上で動くゲームを作るってどういうこと? Unreal Engine の機能を使用する とは? という疑問が出てくると思いますので、これらを説明していきます。 Fortniteは Epic Games によって開発されたゲームです。 プレイ可能なプラットフォームは、 スマホ , PC, PlayStation , Nintendo Switch , Xbox と多岐に渡ります。 一般的にはバトルロワイヤルゲームとして有名ですが、他にも「クリエイティブモード」という、プレイヤーが独自にゲーム(マップ)を制作して公開できるモードがあります。 このクリエイティブモードでは、既に8万を超えるゲームが制作されています。(2024年現在) また、 Epic Games はクリエイティブモードでのゲーム制作者に対するエンゲージメント配当プログラム(作成したゲームが多く遊ばれると配当金を貰える仕組み)を実施しており、1年目にして総額3億2千万ドル以上配当されております。 Fortniteのユーザー数は5億人を越えており、Fortniteクリエイティブモードは「ユーザー数5億人のゲーム制作プラットフォーム」となっています。 ※配当プログラムの詳細は こちら をご参照ください クリエイティブモードでのゲーム制作はFortnite上で行うことが可能ですが、外部アセットの利用や細かいロジック設定ができないなど、様々な制限があります。 そこで登場した拡張ツールがUEFNです。UEFNは、本格的な ゲームエンジン である Unreal Engine (以下UE)の機能をクリエイティブモードのゲーム制作に使用できるようにしたツールです。 これにより、クリエイティブモード向けのリッチなゲーム制作が可能となりました。(具体的な拡張要素は2章で記載します) なお、UEの開発もFortniteと同様に Epic Games が行っております。最新版であるUE5では以下の特徴的な機能があり、これらを使用できるのもUEFNの強みとなります。 Lumen(リアルな光照射効果を提供するソリューション) Nanite(非常に詳細な視覚効果を実現する仮想 幾何学 技術) UEの一部機能に関しては 以前の記事 にて説明しておりますので、興味のある方はご参照ください。 1.2 UEFN最新情報(2024年4月現在) 2024年3月 20日 開催の「State of Unreal 」の基調講演において、 Epic Games から新機能紹介が行われました。その中から、UEFNに関する2つを紹介します。 ① Metahuman to UEFN MetahumanをUEFNにインポートし、Fortniteのあら ゆるキャラ クターへ適用できるようになりました。Metahumanとは、人間の顔を忠実度高く再現したデジタルヒューマンを作成できるソリューションです。 Metahumanの詳細や作成方法は 以前の記事 にて紹介しておりますので、興味のある方はご参照ください。 Metahuman Creator やMetahuman Animatorを活用し、自分の顔にそっくりなキャ ラク ター( NPC )を作成することもできます。( 作成方法の紹介記事 ) ② 外部ライセンスIP利用(レゴブランド) レゴブランドのテンプレート、小道具、消耗品、アイテムなどを使用できるようになりました。また、ミニフィギュアやレゴスタイルのコスチュームをFortniteのプレイヤーへ適用できるようになりました。 今後はレゴブランド以外にも「Rocket Racing」や「Fall Guys」のテンプレート・アセットが使えるようになり、 Epic Games はさまざまなライセンスIPがFortnite上で使用できる世界を目指している、とのことです。 今後も様々なライセンスIPアセットが利用できるようになりそうで、続報が楽しみですね。 1.3 UEFNの企業活用事例 企業におけるUEFN(Fortniteクリエイティブモード)活用事例を紹介します。 ① NIKE 「Airphoria」 Airphoriaは、 NIKE が Epic Games イノベーション ラボのサポートのもと、フォートナイトスタジオBeyondと共同制作したマップです。Airphoriaでは、 Air Maxをモチーフにした雲上都市でのスニーカーハントゲームが提供されていました。 ゲームを一定以上クリアするとFortnite上のバックアクセサリーが貰えたり、Airphoriaにインスピレーションを受けたコレクションが NIKE .com(北米地域)で発売されるなど、UEFNを活用した マーケティング の試みが展開されました。 ※日本では2023/6/28までの限定公開 ② ソニー 「SHIBUYA MULTIVERSE」 株式会社 ソニー・ピクチャーズ・エンタテインメント 配給の映画『 スパイダーマン :アクロス・ザ・スパイダーバース』の公開を記念し、 ソニー が Sony Esports Projectとして制作したマップです。 UEFNを活用して渋谷の街並みが再現されています。 またその後、SHIBUYA MULTIVERSEの上空でバトルが可能な「Sky Battle in Shibuya Multiverse」マップも公開されました。 最初に公開されたSHIBUYAマップを軸に、追加で様々なマップ(ゲーム)を展開する試みを行っています。 ※2024/7/30までの公開 (マップコードは こちら をご参照ください) 2 UEFN vs Fortnite(クリエイティブモード) 本章では、UEFNの登場によって拡張された要素を紹介します。 UEFNによって拡張された機能は、大きくまとめると以下になります。 外部アセット利用(3Dモデル・アニメーション・テクスチャのインポート等) UE機能( ランドスケープ 構築、レベルシーケンス等) Verse( プログラミング言語 ) チームでの共同開発( Unreal Revision Control) 上記に関して、次節以降で具体的に説明します。 2.1 外部アセット利用 Fortniteクリエイティブモードでは Epic Games 側が用意したアセットのみに利用が制限されていましたが、UEFNでは自分のアセットをインポートして利用できます。 ここでは、外部サイトで購入した3DモデルやBlederなどで自作したものを使うこともできます。 3DモデルはFBX,OBJ,glTF,GLB形式に対応しています。また、テクスチャ( PNG , JPEG , TIFF 形式など)やオーディオ(WAV,AIF,FALC, OGG 形式)のインポートにも対応しており、アセットの自由度が格段に高くなりました。 (画像:Fortniteクリエイティブモードで用意されているアセット一覧) (画像:UEFNで自作アセットをインポートした画面) 外部アセットのインポート方法や、UEからUEFN用にモデルをアウトプットする方法は4章にて紹介します。 2.2 UE機能 UEFNでは、UEの主要な機能をクリエイティブモード向けに利用できます。 UEFNで使用可能なUE機能は様々にありますが、ここでは目玉となる機能を紹介します。 ① ランドスケープ モード ランドスケープ モードでは、マップの土台となる島の地形を操作できます。 地形の特定部分を盛り上がらせて山のようにしたり、逆にへこませて谷のようにすることも可能です。 また、土や水の動きによる地面の浸食を再現させることもできます。 (画像:UEFNの ランドスケープ モード画面) ランドスケープ モードに関しては 以前の記事 にて紹介しておりますので、こちらもご参照ください。 その他、フォリッジモードで木などを簡単に敷き詰めることも可能です。 ②ポストプロセスボリューム ポストプロセスボリュームを使用することで、特定の領域内にポストプロセスを適用できます。 プレイヤーが特定の領域に入った際に色調や明るさを変えさせるなど、ゲームプレイにおける映像表現の幅が広がりました。 (画像:配置したポストプロセスボリューム) (画像:ポストプロセスボリューム内でのホワイトバランス変化) ③レベルシーケンス UEFNでは、レベルシーケンスを使用してゲーム内のシネマティクス(リアルタイムで レンダリング される動画)を作成できます。UEと同様、レベル シーケンサー 上にオブジェクト・キャ ラク ター・カメラを追加し、キーフレームを打っていくことでシネマティクスを作成します。 2.3 Verse Verseは Epic Games が開発した プログラミング言語 です。Verseを使用してプログラミングを行うことで、クリエイティブモードで使う各種ロジックをカスタマイズできるようになりました。 UEでゲーム制作を行う際はBPでプログラミングを行いますが、UEFNではBPは使用できません。また、逆にUEでVerseを使用することも不可となります Verseによる基本的な開発方法に関しては4章にて紹介します。 2.4 チームでの共同開発 UEFNでは、ユーザー同士でチームを組んでマップを共同編集するための「 Unreal Revision Control」機能が備わっています。 Unreal Revision Controlによって制作中のマップはバージョン管理がされており、「どの変更が」「いつ」「誰」によって行われたのかを常に確認できます。また、ファイル編集時にはチェックアウト(ロック)が掛かるため、編集の競合や デグレード が防止されています。 UEFNでは上記機能がデフォルトで備わっており、チームでの共同開発が行いやすい環境となります。 ※2024年4月現在 Unreal Revision ControlはUEFN専用の機能となり、UEでは使用不可です チーム設定や Unreal Revision Controlの設定方法に関しては4章にて紹介します。 3 UEFN導入方法 本章では、UEFNの具体的な導入方法を紹介します。 3.1 動作環境 UEFN導入に際し、動作環境に求められるスペックを紹介します。 Epic Gamesアプリサイト を参照に、UEFNの必須スペックと推奨スペックは以下になります。 ※OSは Windows のみ対応 必須スペック OS : Windows 10(64ビット) バージョン1703 CPU : Core i3 -3225 3.3GHz メモリ : 8GB RAM GPU : Intel HD4000, AMD RadeonVega8 推奨スペック OS : Windows10/11(64ビット) CPU : Core i5 -7300U 3.5 GHz、 AMD Ryzen 3 3300Uまたは同等のプロセッサ メモリ : 16GB RAM以上 GPU : NVIDIA GTX960, AMD R9280, または同等のDX11対応 GPU VRAM : 8GB VRAM以上 追加要件 : NVMe SSD なお、Fortniteの動作環境に求められるスペックも上記と同様になります。 3.2 インストール手順 UEFNのインストール手順を紹介します。UEFNで作成したマップをテストプレイする際にFortniteが必要になるため、Fortniteのインストール手順も合わせて紹介します。 最初に、 Epic Games が提供している Epic Games Launcherをインストールします。 Epic Games Launcherは こちらのサイト から インストーラ をダウンロードし、起動してインストールします。 インストールが完了したら、Epic Game Launcherを開き Epic Games アカウントでサインインします。 Epic Games アカウントを所持していない場合は、新規サインアップを行います。 サインインが完了すると Epic Games Launcherのメイン画面が開きます。 ストア上部の検索欄から「Fortnite」を検索します。 Fortniteのストアページへ移動し、画面右の「入手」から注文を行います。(無料です) ※下記画像では既に注文済みのため、「ライブラリに所有」と記載されております。 同様にUEFNも注文します。 ライブラリに移動し、Fortniteをクリックしてインストールします。 Fortniteのインストールが完了しましたら、同様にUEFNもクリックしてインストールします。 ※下記画像はインストール後の状態です。 両方のインストールが完了した後は、ライブラリのFortnite・UEFNからそれぞれのアプリケーションを起動できます。以上にてUEFN・Fotniteの導入は完了です。 4 基本概念と操作方法 この章では、UEFNでマップ制作を行うにあたって基本となる概念と、関連する操作手順を紹介します。 4.1 プロジェクト プロジェクトとは、アセットの集まりとそのアセットの共有やテストに必要なデータが含まれた1つのまとまりを指します。プロジェクトには複数のマップ(レベル)を含めることも可能です。 UEFNでマップ制作を行う際は、まず初めにプロジェクトの作成を行います。 また、プロジェクトをチームで共同編集したい場合は事前にチームを作成しておき、プロジェクト作成時に Unreal Rivision Controlを設定する必要があります。 チーム作成方法とプロジェクトの作成方法を紹介します。 ①チーム作成方法 フォートナイトクリエイターポータル へアクセスします。 Epic Games アカウントでログインします。 画面左上の「選択中のチーム」をクリックすると、UEFNのチーム選択画面が表示されます。 「新規チームを作成する」をクリックし、チーム作成画面を表示します。 チーム名や追加するチームメンバーを記載します。既に作成済みのプロジェクトをチームに移管する場合は、ここで移管対象のプロジェクトを選択しましょう。 記載できましたら、「チームを作成」をクリックし、作成完了です。 なお、チーム名称やチームメンバー・役割などは後から変更可能です。 ②プロジェクト作成方法 Epic Games Launcherの「ライブラリ」を開きます。 UEFNを起動すると、UEFNプロジェクトブラウザが表示されます。 「ISLAND TEMPLATES」などからテンプレートとする島を選び、「プロジェクトの場所」「プロジェクト名」を記載します。 この際、画面右の「 Unreal Revision Control」にチェックを入れ、チームで開発する際は対象とするチームを選択します。 「create」をクリックするとプロジェクトが作成され、UEFNエディタが表示されます。 4.2 エディタ UEFNにおけるゲーム制作は、UEと同様にエディタ画面上で行います。 以下に、 Epic Gamesが公開しているUEFNとUEの比較ドキュメント から抜粋したエディタ画面比較を示します。 上記の見ての通り、エディタ画面上の各ツールはUEFNとUEで基本的には同じです。 分かりやすい違いとして、UEFNではアウトライナの部分に追加で「ISLAND SETTINGS(島設定)」が記載されていますが、こちらに関しては4.4で紹介します。 UEFNとUEにおける各ツール内の詳細な機能差は、 Epic Games公式ドキュメント をご参照ください。 4.3 アセット アセットとは、ゲームを制作する際に用いる各種構成要素を指します。例えば、「スタティックメッシュ」「マテリアル」「テクスチャ」「 サウンド キュー」などが含まれます。 UEFNにおける各種アセットはコンテンツブラウザに格納されています。 UEFNでプロジェクトを作成した際、コンテンツブラウザにはデフォルトで「Fortnite」「Epic」フォルダが作成されています。これらのフォルダには、 Epic Games が用意したアセット(Forniteクリエイティブモードでの既存アセットを含む)などが格納されており、自由に使うことが可能です。 ここからは、各種外部アセットのインポート方法と、公式 マーケットプレイス (FAB)からの購入方法を紹介します。 ①外部アセットのインポート方法 コンテンツブラウザ上に、インポートしたアセットを格納するフォルダを新規作成します。 コンテンツブラウザの「インポート」をクリックします。 インポートするアセットを選択し、「開く」をクリックします。 インポートオプションを設定します。 FBX形式の3Dモデルをインポートする際は、以下のように設定しましょう。 メッシュ > 欠落している コリジョン を生成:on メッシュ > 詳細設定 > メッシュを結合:on メッシュ > 詳細設定 > メッシュLODをインポート:on マテリアル > Search Location (検索場所): Local (ローカル) マテリアル > Material Import Method (マテリアルのインポート方法): Create New Materials (新規マテリアルを作成) 「全てをインポート」をクリックし、インポートします。 UEと同様、インポートしたアセットは ドラッグ&ドロップ でビューポート上に設置可能です。 ②UEからUEFNへのアセット移行方法 UEで利用しているアセットをUEFNに移行したい場合は、UEの機能を用いて簡単に行うことができます。 UEのプロジェクトを開き、移行したいアセットのフォルダを右クリックして「移行」を選択します。 送り先となるUEFNプロジェクトフォルダ内の「Content」フォルダを選択します。 UEFNプロジェクトにアセットが移行されました。 ③FAB マーケットプレイス からの購入方法 FAB マーケットプレイス (以下、FAB)では、UEFNプロジェクトで使用できるカスタム仕様のアセットが販売されています。(無料アセット存在) FABを開くには、 ツールバー の「Fab」をクリックします。 別ウィンドウでFABが開きます。 購入したいアセットを選択したら、「コンテンツブラウザに追加」をクリックします。 なお、今回は参照アセットとして追加します。追加したアセットの中身を編集したい場合は、変更可能なアセットとして追加しましょう。 参照アセットとして追加したため、コンテンツブラウザ内の「Referenced Content」に追加されました。 ドラッグ&ドロップ でビューポート上に設置します。 4.4 デ バイス (仕掛け) UEFNのコンテンツブラウザ(「Fortnite > Devices」フォルダ配下)には、デ バイス (仕掛け)と呼ばれる特殊なアセットが格納されています。 デ バイス とは、ゲームの仕組みを構築をするために使用される一定のロジックを持ったアセットです。 デ バイス を配置することで、ゲームプレイを行うプレイヤーとのインタ ラク ション要素を定義できます。 デ バイス の具体例 タイマーの仕掛け:カウントダウン(時間が0になったときに別イベントを起動)や、ストップウォッチとして表示 アイテムスポーンの仕掛け:特定のアイテムをスポーンさせたり、一定時間毎にランダムにアイテムをスポーンさせる エンドゲームの仕掛け:現在のラウンドやゲーム全体を終了させ、勝利/敗北の判定やスコアボードを表示させる 2024年4月現在、デ バイス は約200種類ほど用意されています。各種デ バイス の詳細に関しては Epic Gamesの公式ドキュメント をご参照ください。 以下では、デ バイス の中で重要となる「島設定(Island Setting)」の設定方法と、その他の通常デ バイス の設置方法を紹介します。 ①島設定デ バイス の設定方法 デ バイス の種類は数多くありますが、その中で必ず使用されるものは「島設定(Island Setting)」です。 島設定デ バイス では、「ゲームルール」「ゲーム設定」「UI設定」「 デバッグ 設定」を調整できます。そのため、UEFNゲーム制作における「ゲームとしての基盤要素」はすべて島設定デ バイス で行うことになります。 島設定デ バイス は、UEFNプロジェクト作成時にビューポート内にデフォルトで生成されます。(アウトライナから確認可能) アウトライナから島設定デ バイス の インスタンス を選択し、詳細画面から各種設定を変更できます。 島設定デ バイス の設定項目詳細に関しては、 Epic Gamesの公式ドキュメント をご参照ください。 ②通常デ バイス の設置方法 通常デ バイス は、「Fortnite > Devices」内のデ バイス をビューポート上に ドラッグ&ドロップ することで設置( インスタンス を作成)できます。 ゲーム制作にあたっては、自分の作りたいゲーム内容に合わせて各種デ バイス の設置・設定を行いましょう。 4.5 Verse 2章にて軽く紹介した通り、UEFN内ではVerseを使用してプログラミングを行います。 2024年4月現在、Verseプログラミングでは「デ バイス (仕掛け)ロジック」「 NPC ロジック」をカスタマイズすることが可能です。 本記事では、Verseによるプログラミング開発の基本的な進め方を紹介します。 Verse言語の詳細に関しては今後別記事にて紹介予定となるため、興味のある方は是非 そちら もご参照ください。 メニューバーから「Verse > Verse Explorer 」をクリックし、Verse Explorer を表示させる。 Verse Explorer の最上段にあるプロジェクト名を右クリックし、「Add new Verse file to project」をクリック。 カスタマイズしたいロジックのテンプレートを選択し、Verseファイルを作成する。 今回は画面上に「 Hello World 」を表示するVerseファイルを作成していくため、デ バイス 用のテンプレートを使います。 Verseファイルが作成されたら、ファイル名を右クリックして「Open in Visual Studio Code 」をクリック。 ※ VS Code をインストールしていない方は、事前に こちら からインストールが必要です。なおUEFNから VS Code を起動すると、 VS Code に自動で「Verse」Extensionがインストールされます。 VS Code にてVerseファイルが開かれました。 画像で表示されているテンプレートファイルでは、以下の処理が記述されています。 hello_world_device というデ バイス 名で新規デ バイス を定義 ゲーム開始時に動くロジックを定義(OnBegin関数) 画面に「Hello, world!」「2 + 2 = 4」を表示  ※Verseにおける標準出力(Print関数)は、ゲーム画面左上に表示されます。 テンプレートから少しだけ編集します。 計算式の部分を、「3 x 5 = 15」となるように編集しました。 Verseファイルを VS Code 上で保存し、UEFNエディタに戻ります。 メニューバーから「Verse > Verseコードをビルド」をクリックし、ビルド( コンパイル )します。 ビルドに成功したら、コンテンツブラウザ上のVerseクラスをビューポート内に ドラッグ&ドロップ します。 デ バイス ロジックテンプレートで作成したVerseクラスは「(プロジェクト名)コンテンツ > Creative Devices」に格納されています。 Verseクラスの インスタンス が作成され、ゲーム内に配置されました。 デ バイス のオブジェクト自体はゲームに不要なため、今回は「ゲーム中に表示」をオフにします。 FortniteでのテストプレイでVerseファイルの動作を確認します。 ツールバー の「セッションを開始」をクリックします。 しばらく待つとセッションが開始され、同時にFortniteがクリエイティブモードの状態で起動します。 Fortniteの画面でEscを押下し、「ゲームをスタート」をクリックします。 ゲーム開始と同時に、画面左上にVerseで出力した文字を確認できました。 また、Mキーを押下して「ログ」をクリックすると、ログ上からも文字の出力を確認できます。 以上が、Verseによるプログラミング開発の基本的な進め方です。 既存デ バイス 間の連動や NPC ロジックの変更方法は別記事にて紹介予定となりますので、是非 そちら もご参照ください。 おわりに 本記事では、UEFNの基礎となる部分を広く紹介しました。 いままでFortniteクリエイティブモードだけでマップ制作をしていた方や、UEでゲーム制作をしていた方たちが、UEFNに踏み出す一助となれれば幸いです。 UEFNは特にVerse部分の奥が深いので、今後はそちらに焦点を当てて記事を執筆する予定です。 UEFNには、リーチできる広いユーザー規模やクリエイターに対しての収益還元など、他のプラットフォームにはない魅力があると思います。今後、世界中で愛されている日本のアニメやゲーム、音楽などの様々なコンテンツ・IPにおけるUEFN参入支援を展開していければと考えております。 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! 電通総研の採用ページ 参考文献 ・ Fortniteクリエイティブモード エンゲージメント配当 ・ Unreal Engine5 を使ってワールドの地形を作成してみました ・ フォトグラメトリ×Mesh to Metahumanで人物そっくりな3Dキャラクターを作成してみた(前編) ・ GDC2024のState of Unrealの重要ニュース ・ フォートナイト クリエイティブおよび UEFN で独自のレゴ® の島を制作しましょう ・ UEFN を使用して構築された Nike のバーチャル スニーカー ハント Airphoria の全貌を探る ・ 「フォートナイト」×「NIKE」がコラボレーション!UEFNを活用した“Airphoria”がフォートナイト内に登場! ・ SHIBUYA MULTIVERSE ・ Sky Battle in Shibuya Multiverse ・ Epic Games Store UEFN ・ Epic Games公式ドキュメント UEFN と UE の比較 ・ Epic Games公式ドキュメント Unreal Editor for Fortnite で共同作業を行う ・ Epic Games公式ドキュメント アセットをインポートする ・ UEのマーケットプレイスでダウンロードしたアセットをUEFNのプロジェクトにインポートするにはどうしたらよいでしょうか? ・ Epic Games公式ドキュメント 仕掛けの使用 ・ Epic Games公式ドキュメント 島設定 ・ 写真フィルターのようにシーンの雰囲気を大きく変えるUE5「ポストプロセス」入門 ・ Epic Games公式ドキュメント Verse を使用して独自の仕掛けを作成する 執筆: @matsuzaki.shota 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
みなさんこんにちは!  電通 総研 金融ソリューション事業部の松崎です。 本記事は、 Unreal Editor for Fortnite(以下UEFN)の初学者向け紹介記事の第2弾です。 前回の記事 ではUEFN全体の基礎編として紹介しました。今回はUEFN特有のプログラム言語である「Verse」に注目して紹介します。 「UEFNを使い始めたけど、Verseというものがイマイチ分からない」という方は、是非最後までチェックしてください。 目次 Verseとは 実装の流れ 実装例 目次 1 Verseとは 1.1 Verseの言語的特性 1.2 Verseの特徴的要素 ①失敗コンテキスト ②並行処理 1.3 基本概念と記述方法 2 実装の流れ 2.1 クラスの定義 ① デバイス(仕掛け)ロジック ② NPCロジック 2.2 デバイス参照の定義 2.3 処理の記述 ① creative_device クラス ② npc_behavier クラス ③ fort_playspace インタフェース ④ player クラス(agent の子クラス) ⑤ fort_charcter インタフェース 2.4 イベント処理 ① Awaitable インタフェース ② Subscribable インタフェース 3 実装例 おわりに 参考文献 1 Verseとは Verseに関しては 前回の記事 でも軽く触れていましたが、改めて紹介します。 Verseは Epic Games が開発した プログラミング言語 です。Verseを使用してプログラミングを行うことで、Fortniteクリエイティブモードで使う各種ロジックをカスタマイズできるようになりました。 UEでゲーム制作を行う際はBlueprint(以下、BP)や C++ を用いますが、UEFNではBPは使用できません。また、逆にUEでVerseを使用することも不可となります 2024年4月現在のVerseプログラミングでは、主に「デ バイス (仕掛け)ロジック」と「 NPC ロジック」をカスタマイズすることが可能です。 「デ バイス (仕掛け)ロジック」に関しては、既存のデ バイス 同士を連動させるロジックを作成したり、特定の条件(キャ ラク ターの状態変化、時間経過など)でデ バイス を起動させるロジックを作成できます。 また、「 NPC ロジック」に関しては、 NPC スポナーデ バイス からスポーンさせる NPC の挙動ロジックをカスタマイズすることが可能です。 1.1 Verseの言語的特性 Epic Games公式ドキュメント を参照に、Verseの言語的な特性は以下のように示されます。 静的な型付け マルチ パラダイム 言語(関数型・ オブジェクト指向 ・命令型プログラミング) 式で記述(文の記述はなし) 失敗コンテキスト 言語レベルでの並行処理 Epic Games は、Verseを「 メタバース 開発の際の共通言語」とさせることを目的としており、将来的には現在のWeb開発における JavaScript の立ち位置を目指しています。 そのため、Verseは設計思想として「シンプル」「汎用的」「ハイパフォーマス」「長期的な利用可能性」などを掲げており、上記の特性はその思想が反映されたものです。 1.2 Verseの特徴的要素 1.1で示した中で、Verseの大きな特徴である「失敗コンテキスト」「並行処理」に関して解説します。 ①失敗コンテキスト 失敗コンテキストとは「失敗する可能性がある式を実行できるコンテキスト」です。 失敗する可能性がある式とは、具体的には「成功して何かしらの値を生み出すか、失敗して何も値を返さない式」を意味します。つまり、失敗を前提として式を実行させることができる、ということです。 注意点として、失敗コンテキストで例外処理が走ることはありません。そもそもVerseの言語仕様として例外処理が存在しません。失敗コンテキスト内で式が失敗した場合は、コンテキスト内の処理がすべて ロールバック されます。 失敗コンテキストにより、特定の式をコミットすることなく試し実行することが可能になります。 以下に、失敗コンテキストを利用した記述例を示します。 if (Element := MyArray[Index]): Log(Element) 「:=」は変数や定数の初期化 演算子 ifの()内にて、MyArrayt配列のインデックスが有効か否かをチェック インデックスが有効であれば、ElementへアクセスしてLog出力 インデックスが無効の際は ロールバック (ElementへのアクセスやLog出力は実行されない) ②並行処理 ここで紹介する「並行処理」に関して、 Epic Games公式ドキュメント では「並列処理」と記載されています。 プログラミングにおける一般的な認識として、「並行処理」と「並列処理」は以下のように説明されます。 並行処理: 処理を細かく分割し、それぞれの処理を切り替えながら実行することで複数の処理を並行で進めること。時間を「範囲」で見ると複数の処理が実行されているが「点」で見るとどれか1つの処理のみが実行されている状態。 並列処理: マルチコアCPUなどにより、複数の処理を同時に実行すること。時間を「点」で見た際も複数の処理が実行されている状態。 Verseでは言語レベルにて並行処理の制御が可能であり、これはノン プリエンプティブマルチタスク で動作させていることを意味します。(逆にVerseからマルチコアCPUを制御できるわけではないため、本記事では「並列処理」とは呼称しません) 次に、Verseにおける並行処理の制御方法を説明します。 Verseのすべての式は、「immediate式」と「async式(以下、非同期式)」に分けられます。 Epic Games公式ドキュメント にて、それぞれの式は以下のように説明されています。 immediate式:遅延なく評価され、現在の「シミュレーションアップデート」内にて評価が完了する 非同期式:評価が後回しになる可能性があり、現在または後続の「シミュレーションアップデート」で完了することもあれば、完了しないこともある ※ 「シミュレーションアップデート」という概念が出てきていますが、これはVerseにおける最小実行単位です。3DCGにおける レンダリング の最小単位は「1フレーム」ですので、基本的にはこれと同じになります。 (ただし、「シミュレーションアップデート」と「フレーム」の時間を意図的にズラすことは可能です。また、オンラインのゲームサーバーが同期されなくなった場合など、「フレーム」の更新前に「シミュレーションアップデート」が完了することはあります。詳細は こちら をご参照ください) Verseでは、上記の「immediate式」と「非同期式」を使い分けることによって、並行処理を言語側から制御することが可能です。 1.3 基本概念と記述方法 Verseはマルチ パラダイム 言語として設計されており、主に以下の パラダイム 要素を取り入れています。 オブジェクト指向 プログラミング 関数型プログラミング 命令型プログラミング Epic Games公式ドキュメント によると、Verseにおけるマルチ パラダイム は「 決定論 的プログラミング」を可能な限り追求する形で取り入れられているとのことです。 Verse プログラミング言語 としての基本的な構文方法や機能は、 Epic Games公式ドキュメント(Verse言語のクイックリファレンス) に記載されております。実装する際はご参照ください。 2 実装の流れ Verseファイルを実装する際の流れを紹介します。 2.1 クラスの定義 Verseファイルを作成する際は、最初にクラスの定義を行います。 この際、Verseファイルの目的によって継承するクラスが変わります。 ① デ バイス (仕掛け)ロジック デ バイス (仕掛け)ロジックを作成する場合は、[creative_device]クラスを継承させます。 このクラスは、カスタムしたデ バイス (仕掛け)ロジック作成用に Epic Games が用意したクラスです。 [creative_device]クラスが継承されたクラスでVerseファイルを作成すると、 コンパイル される際に自動でUEFNコンテンツブラウザに表示されます。また、Verseファイル(クラス)の インスタンス はUEFNにてコンテンツブラウザからビューポート上にドラッグすることで配置されます。 なお、[creative_device]クラスは[/Fortnite.com/Devices]モジュールにて定義されているため、事前にこのモジュールをインポートする必要があります。 インポート式 using { /Fortnite.com/Devices } クラス定義式例(継承含む) hello_world_device := class(creative_device): ② NPC ロジック NPC ロジックを作成する場合は、[ npc _behavior]クラスを継承します。 このクラスはカスタム NPC 動作の作成用に Epic Games が用意したクラスです。 また、[ npc _behavior]クラスを継承したクラスでは、CharacterDefinitionアセットか npc _spawner_deviceで生み出されるキャ ラク ターに対しての挙動ロジックを定義できます。 なお、[ npc _behavior]クラスは[/Fortnite.com/AI]モジュールにて定義されているため、事前にこのモジュールをインポートする必要があります。 インポート式 using { /Fortnite.com/AI } クラス定義式例(継承含む) npc_custom_action := class(npc_behavior): Verseにてインポート可能なモジュール一覧は こちら をご参照ください。 2.2 デ バイス 参照の定義 デ バイス 間の連動ロジック作成時などに、Verse内で既存デ バイス (仕掛け)を参照する必要があるときは、そのデ バイス 型の変数を「UEFNから編集可能な形」で定義する必要があります。 「UEFNから編集可能な形」で定義する理由としては、実際のデ バイス インスタンス との紐づけをUEFNで行う必要があるためです。 Verseでは、紐づけ用の箱を作っておくイメージになります。 デ バイス 用の変数を作成する際に用いる各種デ バイス 型名は こちら をご参照ください。 デ バイス 参照の定義式例 @editable MyDevice:timer_device = timer_device{} 上記では、「タイマーの仕掛け」紐づけ用の箱を作成しています。 「@editable」を記述することでUEFNから編集可能な形にしています。 上記にモジュールインポートとクラス定義の式を加え、以下のように記述します。 using { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } teckblog_device := class(creative_device): @editable MyDevice:timer_device = timer_device{} 試しにこの記述のVerseファイルを コンパイル してUEFNのビューポート内に配置すると、 インスタンス の詳細から「タイマーの仕掛け インスタンス 」との紐づき設定が行えるようになっていることを確認できます。 2.3 処理の記述 デ バイス 参照の定義まで完了しましたら、Verseで行う処理を記述します。 ここでは、Verse特有の機能をいくつか紹介します。 機能の網羅的な詳細に関しては、 Verse モジュールリスト から対象となるクラスを探してご参照ください。 (例:creative_classの機能 → /Fortnite.com/Devices/creative_device/Functions) ① creative_device クラス OnBegin/OnEnd:クリエイティブモードのゲーム終了/開始時に呼び出されるメソッド Show/Hide:デ バイス インスタンス のオブジェクトをゲーム内で表示/非表示 GetPlayspace:fort_playspaceインタフェースへアクセス ② npc _behavier クラス OnBegin/OnEnd: NPC の追加/削除時に呼び出されるメソッド GetAgent:ゲーム内の全 NPC 情報を取得(agentクラス) ③ fort_playspace インタフェース GetPlayers:ゲーム内の全プレイヤー情報を取得(playerクラス) PlayerAddedEvent/PlayerRemovedEvent:ゲーム内にプレイヤーが参加/離脱した際に信号を送信 ④ player クラス(agent の子クラス) GetFortCharacter:fort_characterインタフェーズへアクセス ⑤ fort_charcter インタフェース GetAgent:キャ ラク ターに紐づいたエージェントの状態を取得 EliminatedEvent:キャ ラク ターが試合から除外された際に信号を送信 2.4 イベント処理 Verseでは、イベント処理を定義することが可能です。イベント処理とは、「特定のイベントが発生した際に動きだす」処理のことを指します。 イベント処理の際に使用する機能を紹介します。 ① Awaitable インタフェース Await:信号が送信されるまで実行中のタスクを待機 以下は、プレイヤー追加時に特定のメッセージが出される処理例です。 プレイヤー追加時にPlayerAddedEventによって信号が送信され、Await関数が解除されることによって後続処理(Print)が動き出します。 PlayerAddedEvent.Await() Print("プレイヤーが追加されました") ② Subscribable インタフェース Subscribe(t):信号が送信された際に(t)に登録したメソッドを呼びだす 以下は、プレイヤー追加時にAddWeaponメソッドが呼び出される処理例です。 プレイヤー追加時にPlayerAddedEventによって信号が送信され、Subscribe関数がAddWeaponメソッドを呼び出します。 (AddWeaponメソッドは別で定義されている前提になります) PlayerAddedEvent.Subscribe(AddWeapon) ①のAwait関数を用いたイベント処理では、「待機状態を解除する」というトリガーを用いているため、1回処理が完了すると2回に信号が送信されても起動しません。(「ループ処理を組んで再度待機状態に戻す」などで、再利用すること自体は可能です) 一方、②のSubscribe関数を用いたイベント処理では信号が送信される度にイベント処理が起動します。 そのため、起動されるイベント処理の用途に合わせて使い分けが必要です。 なお、上記で紹介しているイベント処理用インタフェースは、PlayAddedEventのように特定条件で信号を送信してくれる機能を使うことが前提になります。このような機能が用意されていない条件をトリガーに設定したい際は、[event(t)]クラスを用いて、「信号を送信するロジック」から実装する必要があります。 こちらに関しては別記事にて紹介予定です。 3 実装例 2章で紹介した各種実装方法などを用いて、例となるロジックを実装していきます。 今回はデ バイス 間の連携ロジックを作成します。 具体的には、 タイマーのカウントダウンが0になるごとに、1つずつバリアを消すロジック を実装します。 このロジック内で連携するデ バイス は「バリアの仕掛け」と「タイマーの仕掛け」です。 ※バリアとはUEFNがデフォルトで提供しているデ バイス の一つで、主に「キャ ラク ターや銃弾を通さない半透明な壁」として利用されます。 最初に、デ バイス ロジックテンプレートからVerseファイルを作成します。 以下のようにVerseを記述しました。 using { /Fortnite.com/Devices } using { /Verse.org/Native } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } timer_barrier_connect_device := class<concrete>(creative_device): @editable Barriers : []barrier_device = array{barrier_device{}} @editable Timers : []timer_device = array{timer_device{}} OnBegin<override>()<suspends>:void= for(Index -> Timer : Timers): Timer.SuccessEvent .Subscribe(event_handler{Device := Self,Timer_num := Index} .TimerCompleatedHandler) DropBarriers(Index: int):void= if(Barrier := Barriers[Index]): Barrier.Disable() event_handler := class: Device : timer_barrier_connect_device Timer_num : int TimerCompleatedHandler(PlayerAgent:?agent): void = Device.DropBarriers(Timer_num) 処理の流れをまとめると以下になります。 Onbeginメソッド内でタイマーカウントダウンの完了イベント(SuccessEvent)を、待つ タイマー完了イベントが発生したら、Subscribe関数に登録されている[event_handler]クラスのTimerCompleatedHandlerメソッドが呼び出される。 (この際、完了した「タイマーの仕掛け」のIndex数値が[event_handler]クラスへ渡される) TimerCompleatedHandlerメソッドの中で、メインクラスのDropBarriersメソッドが呼び出される。 (「2.」で渡されたIndex数値をDropBarriersメソッド呼び出しの引数に用いる) DropBarriersメソッドにて、Index数値に対応する「バリアの仕掛け」を無効化 以下、各部に分けて説明します。 最初はモジュールのインポートです。 using { /Fortnite.com/Devices } using { /Verse.org/Native } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } 次に、メインクラスとなる[timer_barrier_connect_device]クラスを定義します。 [creative_device]クラスを継承する形で定義しています。 timer_barrier_connect_device := class<concrete>(creative_device): メインクラスの中に、「バリアの仕掛け」と「タイマーの仕掛け」用の変数を定義します。 今回はそれぞれの仕掛けを複数個用いるので、配列で定義しています。 @editable Barriers : []barrier_device = array{barrier_device{}} @editable Timers : []timer_device = array{timer_device{}} OnBeginメソッドの中身を記述します。(OnBeginはゲーム開始と同時に動きだすメソッドです) for文を利用し、「タイマーの仕掛け」の個数分だけイベント処理が登録されるようにしています。 Subscribe関数にて呼び出されているのは[event_handler]クラスのTimerCompleatedHandlerメソッドです。(後で定義します) OnBegin<override>()<suspends>:void= for(Index -> Timer : Timers): Timer.SuccessEvent .Subscribe(event_handler{Device := Self,Timer_num := Index} .TimerCompleatedHandler) DropBarriersメソッドです。名前の通り「バリアの仕掛け」を無効化するためのメソッドです。 引数のIndexに対応した「バリアの仕掛け」を無効化します。 無効化の処理がif文に入っている理由は、配列インデックスの存在チェックのためです。(1.2 ①で紹介した失敗コンテキストを利用しています) DropBarriers(Index: int):void= if(Barrier := Barriers[Index]): Barrier.Disable() メインクラスとは別で、[event_handler]クラスを定義します。 クラス内のTimerCompleatedHandlerメソッドでは、メインクラスのDropBarriersメソッドが呼び出されるように定義しています。 event_handler := class: Device : timer_barrier_connect_device Timer_num : int TimerCompleatedHandler(PlayerAgent:?agent): void = Device.DropBarriers(Timer_num) 各部の説明は以上となりますが、ここで 「Subscribe関数から、なぜ直接DropBarriersメソッドを呼び出していないのか?」 を説明します。 [event_handler]という別クラスを経由してDropBarriersメソッドを呼び出している理由は、 「Subscribe関数からメソッド呼び出しを行う際は、呼び出しメソッドに対してパラメータ(Index数値)を渡すことができないから」です。 Subscribe関数からメソッドを呼び出す際、ほとんどの場合はagentパラメータしか渡すことができません。 渡せるパラメータは、Subscribe関数に信号を送っているイベント関数に依存します。 (今回の場合は[timer_device]クラスのSuccessEventが該当し、以下の通りagentパラメータしか渡せません) Subscribe関数に上記の制約があるため、 「 イベントハンドラ 用のクラスを別で作成し、(メソッド呼び出しではなく)クラス呼び出しの部分でパラメータを渡す」 という手段を取っています。詳細には UEFNフォーラム投稿 もご参照ください。 Verseの実装が完了したので、ビルドした後にVerseクラスをビューポートへ ドラッグ&ドロップ し、Verse インスタンス を作成します。 UEFNの「Fortnite/Devices」から、「タイマーの仕掛け」と「バリアの仕掛け」を3つずつ配置します。 この際、「タイマーの仕掛け」は以下のように設定します。 持続時間:30秒(タイマー1) / 60秒(タイマー2) / 90秒(タイマー3) ゲーム開始時にスタート:オン また、「バリアの仕掛け」は見分けがつくよう「バリアの素材」を変化させています。 Verse インスタンス の詳細から、「バリアの仕掛け」と「タイマーの仕掛け」をVerseに紐づけます。 アウトライナからVerse インスタンス を選択し、詳細画面でデ バイス 名の欄を開きます。(今回は「Timer Barrier Connect Device」) BarriersとTimersの欄を開き、「+」を押してインデックスの配列要素を3つに増やします。 ビューポートの左から順に対応するよう、各インデックス配列要素に「バリアの仕掛け」「タイマーの仕掛け」を設定(紐付け)しました。 Fortniteにてテストプレイを行います。 ゲーム開始時 30秒経過後 60秒経過後 90秒経過後 想定通りにロジックが動いていることを確認できました。 おわりに 本記事では、UEFNにおけるVerseの基礎となる概念を実装例と共に紹介しました。 細かい構文方法や API に関しては、本文中に紹介した Epic Games 公式ドキュメント( これ とか これ )にまとまっております。実際にVerseのコードを書く際はそれらをじっくり参照しながら記述すると、深い学びになると思います。 Verseでは NPC の挙動ロジックをカスタマイズすることも可能ですので、次はそちらの実装例を紹介したいと思います。 現在、 電通 総研は web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間をお待ちしております! 電通総研の採用ページ 参考文献 ・ Epic Games公式ドキュメント Verse 言語のリファレンス ・ Epic Games公式ドキュメント Verse 言語のクイック リファレンス ・ Epic Games公式ドキュメント Verse を使用したプログラミングの詳細 ・ Epic Games公式ドキュメント モジュールとパス ・ Epic Games公式ドキュメント Verse API Reference ・ Epic Games公式ドキュメント Verse の仕掛けのプロパティをカスタマイズする ・ Epic Games公式ドキュメント 3.プレイヤー イベントをサブスクライブする ・ Epic Games公式ドキュメント クラス ・ Epic Games公式ドキュメント 失敗 ・ Epic Games公式ドキュメント 並列処理の概要 ・ Epic Games公式ドキュメント frame ・ Epic Gamesフォーラム 追加パラメーターを使用したイベント サブスクライブのガイド (ハンドラー関数) ・ Epic Games公式ドキュメント 4. 仕掛けをリンクする ・ Verse Concurrency—Time Flow: Everything, Everywhere in UEFN, All at Once | Unreal Fest 2023 ・ UEデザイナーがUEFNで遊んでみた ・ verse言語の設計思想を読み解きたい(2)失敗コンテキストとは何か ・ Verse言語の設計思想を読み解きたい(9)非同期処理① 並行処理と並列処理 ・ Verse言語の設計思想を読み解きたい(10)非同期処理② 非同期式と非同期コンテキスト ・ [UEFN][verse] UEFNのカスタムイベント実装を考える[1] Await()の使い方 ・ [UEFN][verse] UEFNのカスタムイベント実装を考える[2] 「イベントリスナ」と「イベントハンドラ」についておさらい ・ 【UEFN】 VerseでDeviceを連携させる(Verseの学習①) ・ 【UEFN】Verseでイベント発生時に追加でパラメータを渡す ・ 【UEFN】Verseでcreative_deviceクラスの多用をやめてみる 執筆: @matsuzaki.shota 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
こんにちは!HCM事業部の小林です。 今回は、私が2024年3月に受験したUX検定基礎についてご紹介します。 最近はUIUXが重要視されつつあり、力を入れていきたいと思っている組織も多いのではないでしょうか。UIUXに少しでも興味がある人はぜひ最後まで読んでみてください! UX検定基礎とは なぜ受けようと思ったのか UX検定基礎の勉強方法 受験と結果 今回の試験を終えてみて UX検定基礎はこんな人におすすめ! おわりに UX検定基礎とは UX検定基礎は、UXを学び始めた人がUX向上の取組みに向けた マインドセット と基礎スキルを学ぶことができる資格です。 IT系の資格で例えるとITパスポートやG検定と同じような位置づけです。 以下の図は、UX向上に必要な3段階のスキルを示しています。 UX検定基礎では、一番基礎となるUXジェネラリストに達しているか確かめる問題が出題されます。受験資格の制限がないため、興味がある人であれば誰でも受けられます! 将来UIUXデザイナーになりたい人は受けておいて損はないと思います。 (UX検定公式HPより引用) 試験時間は100分です。4択の中から選ぶ形式で100問が出題されます。 合格ラインは2024年3月時点では公表されていません。 オンライン実施(自宅受験)なので自分のパソコンを使って受験しました。 なぜ受けようと思ったのか 以前からUIUXに興味があり、独学で学んでいたときに先輩社員におすすめされたのがきっかけです。この機会を通じて、UIUXについて学びつつ、自分がこの分野に適性があるのか知れればいいなと思って受験しました。 UX検定基礎の勉強方法 2024年3月時点では、UX検定基礎の過去問は公開されていません。 そのため、公式サイトにある シラバス や学習推奨図書をもとに勉強を進めました。 試験勉強期間中にやったこととしては以下のとおりです。 ① シラバス にある単語の意味を調べる 公式サイトには、出題範囲として シラバス が掲載されています。小カテゴリや重要ワードにある単語について1つ1つ意味を調べ、 スプレッドシート にまとめていきました。文字だけでなく図も一緒にのせておくと、あとで見直すときに思い出しやすかったです。 ②学習推奨図書を読む 公式サイトでは勉強方法として4冊の学習推奨図書が掲載されています。 本を読みながら シラバス にあった単語があれば単語帳に追記していきました。また、 シラバス に載っていない単語でも分からない単語があったらすぐに調べることを意識していました。 ・アフターデジタル2 UXと自由 海外のサービスの事例を踏まえて解説しています。日本にはあまりないサービスやビジネスモデルがたくさん紹介されていて読むのが楽しかったです。日本企業のあるべき姿について学ぶことができました。 ・UXグロースモデル アフターデジタルを生き抜く実践方法論 UX業務を組織に浸透させる方法について書いてある本です。UXの知識というよりも、考え方が中心に書いてある印象でした。アフターデジタル2の続編なので、まずそちらを読んでからのほうが理解しやすいと思います。 ・人間中心設計入門(HCDライブラリー第0巻) UXの考え方が定義されるまでの歴史やHCD(人間中心設計)のプロセスなど幅広く紹介されていました。イラストがたくさんあって読みやすかったです。 専門用語の解説がたくさん掲載されているため、他の本を読んで分からなかった単語はこの本で調べるという使い方もできます。UXを学ぶにあたっての入門書としてぴったりだと思うので、受験するか迷っている人はまずこの本を読んでから決めてもいいのかなと思いました。 ・ ユーザビリティ エンジニアリング: ユーザエクス ペリエ ンスのための調査、設計、評価手法 タイトルの通り、調査・設計・評価まで実際にどのように進めていくのか詳しく書いてありました。実務で行うことになったとしても、この本を持っておけば自分でもできそう!と思わせてくれるような本でした。 「業務が忙しくて本を読む時間がない!」「本を読むのが苦手、、」という方は、本の難易度と合格に向けて読むべき本の優先度を考えてみたので、そちらを参考にしてみてください。(あくまで個人の主観です) <難易度> アフターデジタル2< ユーザビリティ エンジニアリング≒人間中心設計入門<UXグロースモデル <優先度> 人間中心設計入門< ユーザビリティ エンジニアリング<アフターデジタル2≒UXグロースモデル ③webサイトを見て10 ヒューリスティック 評価をしてみる 上の2つは他の体験談でも紹介されていた勉強法ですが、これは独自で考えた勉強法です。 いろんなサイトを見漁って「このサイト見やすい!」と思ったサイトをピックアップして10 ヒューリスティック 評価を行いました。 ④(時間があれば)動画コンテンツ視聴 UX検定の公式サイトでは「UXインテリジェンス基礎コース」という動画コンテンツも紹介されています。8時間ほどで出題範囲を学ぶことが可能です。プラスアルファで学びたい方はこちらも勉強に取り入れてみてもいいと思います。 試験勉強のスケジュール 試験勉強の全体スケジュールとしてはこんな感じでした。 学習推奨図書を読み始めたのは2か月前からで、全て読了したのが1か月前でした。 試験日の1か月前からは学習推奨図書の2周目を読みつつも単語帳に割く時間を増やしました。 全て含めた勉強時間は40時間ほどでした。 受験と結果 結果は100点中92点で合格でした!(ぱちぱち) 問題は70分で解き終わり、残りの30分間で不安な問題を見直す時間に充てました。問題数が多く、ボリューミーだったので30分では全ての問題を見直すことができませんでした。。ただひねった問題はなく、 シラバス の単語の意味を覚えていれば分かる印象でした。 合否は1か月後と書いてありましたが約2週間で来ました。 最初に述べた通り合格ラインは分かりませんが、 SNS を見てると80点台で合格している人がいたので80点台が取れていればいいのかなと思います。 今回の試験を終えてみて いつもは勉強している途中でモチベーションが下がることが多かったのですが、UX検定基礎は新しい学びがたくさんあり、勉強期間全体を通じて楽しみながら勉強できました。 学んだ成果として、UIUX関係の本の内容がすらすら読めるようになったのがすごくうれしかったです。また、デザイナーさんの話が頭に入ってきやすくなった気がしています。 試験後もUIUXに関する本は読んでいるのですが、個人的に試験前に読みたかったと思える本があったので載せておきます。UXについて書いてあるところだけでも読んでみてください。 UX検定基礎はこんな人におすすめ! ・UIUXに興味がある人 ・デザイナーさんと話す機会がある人 ・デザイン思考を社内に広めていきたい人 ・UXの考え方を業務に活かしたい人 おわりに 今回はUX検定基礎の勉強方法や難易度、受けてみての感想などを紹介しました。UIUXに少しでも興味があったら受けてみてはいかがでしょうか!この体験記が参考になれば幸いです。 HCM事業部では技術職を募集しています。職種ごとの詳しい募集要項は以下のリンクからご覧ください。 私たちは一緒に働いてくれる仲間を募集しています! 新卒採用ページ 【オープンポジション】自社パッケージ製品の導入プロジェクトマネージャ/リーダ 統合HCMソリューション導入におけるクラウドチームリーダー (東京)統合HCMソリューション 導入プロジェクトマネージャー/リーダー (名古屋)統合HCMソリューション 導入プロジェクトマネージャー/リーダー (大阪)統合HCMソリューション  導入プロジェクトマネージャー/リーダー 執筆: @kobayashi.hinami 、レビュー: @nagamatsu.yuji ( Shodo で執筆されました )
アバター
こんにちは、 電通 総研の瀧川亮弘です。 現在、Flutter(FlutterFlow)とSupabaseによる アプリ開発 を行っています。 本記事ではSupabaseのEdge FunctionsでDrizzle ORMを利用して トランザクション に対応する実装について記載します。 Edge FunctionsからDBへのアクセスには、Supabase Javascript Client(以下supabase-js)とDrizzle ORM(以下drizzle)という二つのライブラリを併用しています。 drizzleはsupabase-jsに備わっていない トランザクション 機能を補う目的で利用しています。 https://github.com/orgs/supabase/discussions/526 クライアントの初期化 依存ライブラリ supabase-jsの初期化 drizzleの初期化 トランザクション処理の実装 ユーザー退会 ユーザー登録 終わりに クライアントの初期化 それぞれのクライアントの インスタンス 化処理です。 依存ライブラリ まずは必要な依存ライブラリをインポートします。 { "imports": { "supabase": "https://esm.sh/@supabase/supabase-js@2.40.0", "drizzle-orm": "npm:drizzle-orm", "drizzle-orm/": "npm:/drizzle-orm/", "postgres": "https://deno.land/x/postgresjs@v3.4.4/mod.js", } } supabase-jsの初期化 デフォルトで用意されている 環境変数 からDBの接続情報を取得し インスタンス を生成します。 import { createClient } from "supabase"; // 環境変数から必要な情報を取得 const supabaseUrl = Deno.env.get("SUPABASE_URL") as string; const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") as string; // Supabaseのクライアントを初期化 export const supabase = createClient(supabaseUrl, supabaseKey); drizzleの初期化 自身で設定した 環境変数 CUSTOM_SUPABASE_DB_URL からSupervisorの接続情報を取得し インスタンス を生成します。 Supervisor経由でDBに接続することでmax connectionsエラーを抑制できます。 詳細は以下をご参照ください。 https://supabase.com/docs/guides/database/connecting-to-postgres import { drizzle as createDrizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; // 環境変数から必要な情報を取得 const connectionString = Deno.env.get("CUSTOM_SUPABASE_DB_URL") as string; // Postgresのクライアントを初期化 const pg = postgres(connectionString, { prepare: false, ssl: true, }); // Drizzleのクライアントを初期化 export const drizzle = createDrizzle(pg, { logger: true }); トランザクション 処理の実装 まず基本方針としてauth スキーマ に対してはsupabase-js、public スキーマ に対してはdrizzleでアクセスするという使い分けをしています。 auth スキーマ はSupabaseがデフォルトで用意している スキーマ であり、開発者は スキーマ の変更を行いません。 supabase-jsを通して、ユーザー登録やメールアドレス変更など認証関連の処理を実行することで、間接的にauth スキーマ にアクセスします。 具体的な処理はsupabase-jsが隠蔽しているため、開発者は スキーマ の詳細を知る必要がありません。 public スキーマ ではアプリケーションに必要な任意のテーブルを管理しています。 supabase-jsでもpublic スキーマ へのアクセスは可能ですが、 トランザクション をサポートしていないため、drizzleでアクセスしています。 Supabaseで開発を行う場合、2つの スキーマ (auth, public)にまたがり、副作用のある処理を実行したいケースがあります。 しかし、supabase-jsとdrizzleで トランザクション を共有できないため、若干の工夫が必要です。 例としてユーザー退会とユーザー登録について記載します。 ユーザー退会 ユーザー退会では、auth.users(auth スキーマ のusersテーブル)とpublic.profilesのレコードを同時に 論理 削除します。 まずは、Edge Functionsでpublic スキーマ に対する処理のみを実装します。 import { drizzle, supabase } from "../_shared/client.ts"; // ユーザー退会 const handleDelete = async (req: Request): Promise<Response> => { // 省略 await drizzle.transaction(async (tx) => { // お気に入り削除 await tx.delete(favorite).where(eq(favorite.userId, uid)); // プロフィール削除 await tx.update(profiles) .set({ isDeleted: true, }) .where(eq(users.authUserId, uid)); }); // 省略 }; Deno.serve((req) => { return handleDelete(req); }); 次にauth スキーマ に対する処理を追加します。 auth.usersへの削除処理に失敗した場合、明示的に tx.rollback(); でpublic側も ロールバック する点がポイントです。 import { drizzle, supabase } from "../_shared/client.ts"; // ユーザー退会 const handleDelete = async (req: Request): Promise<Response> => { // 省略 await drizzle.transaction(async (tx) => { // お気に入り削除 await tx.delete(favorite).where(eq(favorite.userId, uid)); // プロフィール削除 await tx.update(profiles) .set({ isDeleted: true, }) .where(eq(profiles.authUserId, uid)); // ユーザー削除 const { error: errorDeleteAuthUser } = await supabase .auth .admin .deleteUser( uid, true, // 論理削除 ); if (errorDeleteAuthUser) { tx.rollback(); // 明示的にロールバック // 省略 } }); // 省略 }; Deno.serve((req) => { return handleDelete(req); }); ユーザー登録 ユーザー登録では、auth.usersとpublic.profilesのレコードを同時に追加する必要があります。 外部キーの依存関係の都合上、auth.usersから先にインサートします。 public側の処理に失敗した場合に、drizzleの トランザクション 機能でauth側を ロールバック することはできないため、ユーザー退会と同様の方法は取れません。 結果、Edge Functionsでのpublic.profilesへのインサートは諦め、公式ページの通りの実装としました。 https://supabase.com/docs/guides/auth/managing-user-data#advanced-techniques auth.usersのインサートをトリガーに PostgreSQL の関数でpublic.profilesへのインサート処理を行っています。 関数内でエラーが発生した場合、auth側の処理も ロールバック されることは確認できました。 おおよそ公式の通りですが、一応ソースも掲載します。 -- ユーザー登録関数 CREATE OR REPLACE FUNCTION public .handle_new_user() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE role public . " Role " ; BEGIN role := (NEW.raw_user_meta_data ->> ' role ' ):: public . " Role " ; INSERT INTO public .profiles (auth_user_id, role, is_deleted) VALUES (NEW.id, role, false ); RETURN NEW; END ; $$; -- ユーザー登録関数を実行するトリガー DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public .handle_new_user(); 終わりに SupabaseもDrizzleも公式ページが充実しているため、基本そちらで事足りそうです。 それでは素敵なSupabase生活を^^ 私たちと一緒に働いてくれる仲間を、是非お待ちしております! 電通総研の採用ページ 執筆: @takigawa.akihiro 、レビュー: @kobayashi.hinami ( Shodo で執筆されました )
アバター
こんにちは!X イノベーション 本部プロダクト イノベーション センターの 米久 保 剛です。 弊社のテックブログ上では今回が初めての記事執筆となります。 アーキテクチャ 設計やアプリケーション設計の話を中心に、 不定 期に情報発信していきたいと考えています。 YAGNI 原則 YAGNI 原則をご存知でしょうか。 エクストリーム・プログラミング (XP)の重要な原則の一つであるこの原則は、You Ain't Gonna Need Itのアクロニム(頭字語)から 命名 されています。日本語にすると「どうせ要らないって」というニュアンスでしょうか。推測に基づいて余計な機能を作り込んだところで将来実際に使われる可能性は低く、時間と労力を無駄にするばかりかコードの複雑化などのリスクさえあります。ですから、現時点でわかっている要件をちょうど満たすだけの機能を実装すべきであると YAGNI 原則は主張します。 YAGNI 原則は機能(振る舞い)の大小に限った話ではなく、設計についても言えるものです。つまり、不確定な将来の要件に対応するための仕組みをあらかじめ作っておくという、無駄になる可能性のある過剰設計を抑制するのです。推測に基づいた設計は、無駄なだけでなく、間違った設計や良くない設計を生んでしまう危険性もはらみます。正しい設計を行えるのに十分な情報が得られる時点(つまり、本当にその要件が必要となるとき)まで、設計判断を遅らせるのです。 サンプル サンプルコードで確認しましょう。(本記事中の完全な ソースコード は、筆者の GitHubリポジトリ で確認可能です)。 最初の設計 次のコードは、商品リストを CSV ファイルに出力するプログラムです。サンプルコードなので CLI ですが、Webアプリケーションの場合は、クライアントからのリク エス トを受け付けて処理を行うコントローラーに該当すると考えてください。 // main_1.ts import { Product , getProducts } from "./products.js" ; import { outputCsv } from "./output_csv_1.js" ; const products: Product [] = getProducts (); // 商品リスト取得 outputCsv ( products ); // CSVファイル出力 実際の CSV 出力処理は、別のファイルに関数を定義し、 csv-writer というNodeモジュールを利用して実装しています。 // output_csv_1.ts import { createObjectCsvWriter } from "csv-writer" ; import { Product } from "./products.js" ; const outputCsv = ( products: Product [] ) : void => { const csvWriter = createObjectCsvWriter ( { path: "./dist/products.csv" , header: [ { id: "productCode" , title: "商品コード" } , { id: "productName" , title: "商品名" } , { id: "price" , title: "単価" } , { id: "category" , title: "カテゴリー" } , { id: "notes" , title: "備考" } , ] , } ); csvWriter.writeRecords ( products ) .then (() => { console .log ( "...CSVを出力しました" ); } ); } ; export { outputCsv } ; この関数内に CSV のレイアウト情報がベタ書きされていることに、不安を抱いたという方は、 プログラマー のセンスとして正しいです。なぜなら、運用に入って以下のような仕様変更要求が発生した場合に、現在の設計だと容易に対応できないケースがあるからです。 カラムの追加や削除、順序変更 データの加工 レコードのソート順の変更 条件に応じたレイアウトの切り替え 仮にこれらの要求にすべて応えられるような設計をしようとすると、プログラムの数は増え、処理も複雑化するでしょう。また、現時点でリアルな要件が決まっていないのならば、何らかの仮定に基づいて仕様を決めて進めることになります。この仮定が外れていた場合は、結局設計を見直す必要が生じ、大きな手戻りとなってしまうのです。 ですので、素敵な設計をしたいという欲求をぐっとこらえて、現時点で十分な設計にとどめることが YAGNI の考え方となります。現在の設計でも、一つ目の仕様変更要求「カラムの追加や削除、順序変更」であれば問題なく対応可能です。 設計の見直し 次に、「条件に応じたレイアウトの切り替え」の仕様変更要求が実際に発生したとしましょう。具体的には次の内容とします。 ログインユーザーが管理者の場合は CSV にすべての列を出力するが、通常のユーザーの場合は列を限定する 当初の設計は、 CSV のレイアウトは固定で一つであることを前提としているため、この仕様変更要求を満たすことができません。設計を見直すときです。 まず、 CSV 出力関数を修正し、出力対象のカラム情報を引数で受け取るようにします。 // output_csv_2.ts import { createObjectCsvWriter } from "csv-writer" ; import { Product } from "./products.js" ; const outputCsv = ( products: Product [] , columns: { id: string ; title: string }[] ) : void => { const csvWriter = createObjectCsvWriter ( { path: "./dist/products.csv" , header: columns , } ); csvWriter.writeRecords ( products ) .then (() => { console .log ( "...CSVを出力しました" ); } ); } ; export { outputCsv } ; 呼び出し元のプログラムを修正します。 // main_2.ts import { Product , getProducts } from "./products.js" ; import { User , getUser } from "./users.js" ; import { outputCsv } from "./output_csv_2.js" ; const userId = 1 ; // 1: Alice(管理者), 2: Bob const loginUser: User = getUser ( userId ); // ログインユーザー情報取得 const products: Product [] = getProducts (); // 商品リスト取得 let columns ; if ( loginUser.isAdmin ) { columns = [ { id: "productCode" , title: "商品コード" } , { id: "productName" , title: "商品名" } , { id: "supplier" , title: "仕入先" } , { id: "price" , title: "単価" } , { id: "category" , title: "カテゴリー" } , { id: "notes" , title: "備考" } , ] ; } else { columns = [ { id: "productCode" , title: "商品コード" } , { id: "productName" , title: "商品名" } , { id: "price" , title: "単価" } , { id: "category" , title: "カテゴリー" } , ] ; } outputCsv ( products , columns ); // CSVファイル出力 これで仕様変更要求に対応することができました。 ただ、 if-else による条件分岐は少し気になりますね。 リファクタリング この条件分岐が放つ「不吉な匂い」は、このままでよいでしょうか。 この程度の単純な条件分岐であれば一旦よしとし、次の条件分岐が発生するときに リファクタリング するという考え方もあるでしょう。しかしながら、次の変更時に もリフ ァクタリングが先送りされ、そのときは永久にやってこないということが往々にしてあります。 設計判断は YAGNI 原則によって先送りしますが、 リファクタリング は先送りすべきでない のです。 コントローラーの役割を担うプログラムは、処理フローを記述すべきであって、詳細なルールを記述すべきではありません。そう考えると、条件分岐を記述する場所が適切ではないようです。 条件に応じて CSV レイアウトを決定する役割を分離して、新しい関数を導入します。 // csv_layout_1.ts import { User } from "./users.js" ; type Column = { id: string ; title: string ; } ; const getProductsCsvLayout = ( user: User ) : Column [] => { if ( user.isAdmin ) { return [ { id: "productCode" , title: "商品コード" } , { id: "productName" , title: "商品名" } , { id: "supplier" , title: "仕入先" } , { id: "price" , title: "単価" } , { id: "category" , title: "カテゴリー" } , { id: "notes" , title: "備考" } , ] ; } else { return [ { id: "productCode" , title: "商品コード" } , { id: "productName" , title: "商品名" } , { id: "price" , title: "単価" } , { id: "category" , title: "カテゴリー" } , ] ; } } ; export { Column , getProductsCsvLayout } ; 呼び出し側は次のようにすっきりし、処理フローの見通しがよくなりました。 // main_3.ts import { Product , getProducts } from "./products.js" ; import { User , getUser } from "./users.js" ; import { Column , getProductsCsvLayout } from "./csv_layout_1.js" ; import { outputCsv } from "./output_csv_2.js" ; const userId = 1 ; // 1: Alice(管理者), 2: Bob const loginUser: User = getUser ( userId ); // ログインユーザー情報取得 const products: Product [] = getProducts (); // 商品リスト取得 const columns: Column [] = getProductsCsvLayout ( loginUser ); // CSVレイアウト取得 outputCsv ( products , columns ); // CSVファイル出力 拡張性 拡張性(extensibility) は、ソフトウェアに機能を追加したり、機能を拡張したりすることが容易に行えることを表す品質特性です。 YAGNI 原則は、不確かな推測に基づいた早すぎる拡張性の導入を抑制します。本当にそれが必要となるときまで、拡張性を導入するという設計判断を遅延させるのです。 ただし、建前はそうであっても、実際にはあらかじめ拡張性をもたせた設計をするケースもあります。例えば以下のようなケースです。 今回は見送ったが、将来対応すべき要求として バックログ アイテムが登録されており、具体的な要求もある程度見えている パッケージソフト として、拡張性を持たせておいた方がよいことを経験上わかっている もちろん、先を見越して拡張性を導入することが誤った設計を生んでしまうリスクとの トレードオフ であることを認識する必要はあります。 ふたたび、サンプル 拡張性を先に考慮しておくという設計判断を妥当だと考えたならば、最初の実装段階で、先のサンプルの最終形まで持っていくことは「あり」です。 パッケージソフト における拡張性の重要さについて少し言及しましたので、さらに掘り下げてみましょう。 パッケージソフト の機能を拡張する手段は様々ですが、例えば以下のような方法があります。 設定ファイルによって振る舞いを変更する 設定テーブルによって振る舞いを変更する アドオン開発によって振る舞いを変更する 製本本体の ソースコード に手を入れるモディフィケーションという方法がアドオン開発と呼ばれることもあるのですが、モディフィケーションにはデメリットもあります。Add-onの文字通り、追加モジュールを製品に足す方法が優れていると言えるでしょう。 今回のサンプルコードに以下の要件が与えられたという前提で、対応方法の例を見てみましょう。 アドオン開発によって、出力する CSV ファイルのレイアウトを変更できること アドオンのインターフェース設計 アドオンモジュールを製品に組み込むためには、接合面としてのインターフェースを定める必要があります。 オブジェクト指向設計 ならばその名のとおり「インターフェース」を定義することになりますが、今回は関数型のアプローチで設計しているため、インターフェースの役割となる関数の型を定義しました。 (TypeScriptでは type 文で型名を宣言できますが、関数もその対象となります)。 // csv_layout_2.ts(一部抜粋) type AddonFunction = ( user: User ) => Column [] ; アドオンの実装 アドオンモジュールには、 AddonFunction 型の関数の実体を実装します。製品本体から利用できるように、 default キーワードを付けてエクスポートしています。 // addon.js import { User } from "./users.js" ; import { Column , AddonFunction } from "./csv_layout_2.js" ; const getProductsCsvLayout: AddonFunction = ( user: User ) : Column [] => { return [ { id: "category" , title: "カテゴリー" } , { id: "productCode" , title: "商品コード" } , { id: "productName" , title: "商品名" } , { id: "price" , title: "単価" } , ] ; } ; export default getProductsCsvLayout ; アドオンの呼び出し 今回のサンプルコードでは、es2020のDynamic import機能を用いて、アドオンモジュールを動的に読み込んで関数を呼びだすように実装しました。 // csv_layout_2.ts(一部抜粋) const getProductsCsvLayout = async ( user: User ) : Promise < Column [] > => { const addonModulePath = "./addon.js" ; return import( addonModulePath ) .then ((module) => { // アドオンモジュールが存在する場合はそれを利用 console .log ( "アドオンモジュールを読み込みました" ); return module . default( user ); } ) . catch (( error ) => { // アドオンモジュールが存在しない場合は標準の振る舞い if ( user.isAdmin ) { return [ { id: "productCode" , title: "商品コード" } , { id: "productName" , title: "商品名" } , { id: "supplier" , title: "仕入先" } , { id: "price" , title: "単価" } , { id: "category" , title: "カテゴリー" } , { id: "notes" , title: "備考" } , ] ; } else { return [ { id: "productCode" , title: "商品コード" } , { id: "productName" , title: "商品名" } , { id: "price" , title: "単価" } , { id: "category" , title: "カテゴリー" } , ] ; } } ); } ; サンプルのため、所定の場所に所定のファイル名でアドオンモジュールが格納されていることを前提としています。実際のプロダクトでは、設定ファイルにパスやファイル名を記述したり、管理画面から登録できるようにしたりすることになるでしょう。 マイクロカーネル アーキテクチャ アドオンモジュールを用いた拡張性の実現方法について、要点をまとめると以下のとおりです。 ソフトウェアの機能を拡張できる箇所(拡張点)を定める 拡張点のインターフェースを定義する 拡張点においてアドオンモジュールを読み込んで実行する仕組みを作る これは、 マイクロカーネル アーキテクチャ と呼ばれる アーキテクチャ スタイルです。 まとめ YAGNI 原則に従い、不用意な過剰設計を避け、設計をシンプルに保っておくことは開発のベロシティを向上させます いつで もリフ ァクタリングによって設計を洗練させることができます 必要な場合は、 YAGNI 原則から離れて拡張性を事前に設計することも間違いではありません。つまるところ設計とは常に トレードオフ なのです 執筆: @tyonekubo 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
こんにちは。コミュニケーションIT事業部 ITソリューション部の英です。 普段はWebアプリや スマホ アプリの案件などを担当しています。あと、趣味で AI を勉強しています。 XGBoostは 機械学習 で非常に人気のある アルゴリズム の一つで、特に表データにおける予測問題で高い性能を発揮します。 "非 ディープラーニング の手法"の中ではとても高い性能を発揮するとされています。用途としては売上予測や顧客の離脱予測など、分類問題や回帰問題に用いられます。 なんだか難しい言葉がいっぱい出てきました。 今回は初歩から丁寧に解説していこうと思います。まずXGBoostは「eXtreme Gradient Boosting」の略です。 "エクストリーム勾配決定木"、かっこいいですね。 そもそも決定木とは? データを分類を行うためのシンプルで強力な 機械学習 の手法です。質問を繰り返し選択することで答えを導く「 木構造 」のモデルです。入力値を根から投入すると、内部ノード(if文)をたくさんくぐって最終的に1つの葉に落ちます。 アプリに課金するかどうかの決定木(イメージ) 葉の部分を良くみると、最後の分岐で必ず「Yes/No」の2択に落ちていますよね。 古いレコメンドシステムだとこのようにif文をベタ打ちだったりしますよね。 ランダムフォレストとはなにか? 先ほどの決定木では 過学習 (Overfitting)に陥ることがあり、モデルの汎用性が低い状態です。 過学習 とはト レーニン グデータに過剰にフィットしてしまい、検証データで良い性能が出ない状態のことです。 そこで登場したのがランダムフォレストです。複数の決定木を同時に作成し、全ての木の出力結果をもとに最終的な出力が決まります。 このように、複数の学習モデル(分類器や回帰器)を組み合わせて、単一のモデルよりも優れた予測性能を得る 機械学習 の手法をアンサンブル学習と言います。これにより予測の安定性が増し、 過学習 のリスクを抑えることが期待できます。 以下は同じテーマですが、木の構造が先ほどと異なります。このように、最終的なYes / Noへのルートや分岐が木ごとに異なるわけです。分類モデルの場合、最終的な結論(Yes / No)はすべての木が出力する予測結果の多数決で決まります。 勾配決定木とは何か? 先ほどの木を見ていただきたいのですが、「フレンド数 ≦ 3」がNoって少し"適当"だと思いませんか? このように、各Yes / Noは同じ価値ではないわけです。そこで各葉にスコアリングし、決定木が前の決定木の残差(誤差)を逐次的に学習することで、モデルの予測性能を段階的に向上させます。 勾配決定木は、以下の手順で構築されます。 初期モデル : 最初に簡単なモデルを作成する 残差の計算 : 予測値(確率)と正解ラベル(1 / 0)の差(残差)を計算する。 新しい木を作成 : 残差を減らすように新しい決定木を作成する モデルの更新 : 新しい決定木をモデルに追加する 繰り返し : このプロセスを繰り返して、モデルを改善する XGBoostとは何か? 勾配ブースティングの手法を高度に最適化した実装であり、 機械学習 コンペティション や実用的なデータ分析で広く使われている非常に人気のあるライブラリです。そう、ライブラリです。モデルと言っても良いです。 ここから本題 今回はSageMakerのXGBoostを使って分類モデルの学習を行い、k分割交差検証を使って混合行列でモデルの性能を評価してみます。 「 オンラインゲームのユーザーがゲームに課金するかどうか 」を予測させるモデルを作ります。ゲームのプレイ時間や実績の達成状況、年齢などを学習データとして与えてみます。検証をシンプルにするために特徴量は少なめにします。 STEP1:学習データの準備 Python の スクリプト で学習用データの CSV を一括で作成します。10000レコード作成しましょう。 ゲーム時間(hours_played)と実績数(achievement_count)は 正規分布 を使って割合を調整しています。これはゲームにハマっているユーザーと、そうでないユーザーの傾向を表現するためです。完全なランダムで生成すると予測精度に影響が出てしまいます。ゲーム時間と実績数を主成分だと仮定して、これらが多ければpaid(正解ラベル)が1を取りやすくします。係数は適宜調整をしてください。 課金する確率を操作しているだけであるため、ゲームのプレイ時間が長く、実績数が多いユーザーでも 無課金 ユーザーになる可能性があるということです。これは現実でもそうですね。 年齢(age)とフレンド数(friends_count)は課金確率に影響を与えない仕組みになっているため今回はノイズ要素になります。 生成された CSV は以下のようなかたちです。 今回は全体の約38%のユーザーが課金したと仮定したデータになりました。 今回使用するXGBoostは一般的に正規化や 正則化 が不要なモデルのため、前処理を施さずにこのまま学習データとして使用します。 列名 説明 age ユーザーの年齢 hours_played プレイ時間 ※paidの確率に影響 friends_count フレンド数 achievement_count 実績数 ※paidの確率に影響 paid 目的変数 ※1が課金ユーザー、0が 無課金 ユーザー STEP2:各種ライブラリのインポート 以下の通り、各種ライブラリをインポートします。 ※下線は未使用警告なので無視していただいて構いません STEP3:データの読み込みと準備 paidは"課金したユーザーかどうか"を示す正解ラベルですから、yにセットします。それ以外を特徴量としてxにセットします。 STEP4:k分割交差検証の準備 今回は全データを5つのサブセットに分割します。サブセット4つで学習し、残り1つで評価することを5回繰り返します。shuffle(True)にすることで、データの並び順によって生じるデータの偏り、それによる学習結果への影響を抑えることができます。random_stateはシャッフル時の乱数生成のシード値を設定するものです。再現性のために指定しています。 STEP5:検証 以下の通り繰り返し検証を行い、そのたびに推論エンドポイントをdeleteします。推論エンドポイントは放置すると課金されてしまうので、必ず削除するようにしてください。 今回のデータでは課金ユーザーが少数派であり学習結果に影響を与える可能性があるため、SMOTE(Synthetic Minority Over-sampling Technique)を使用してオーバーサンプリングしています。SMOTEは少数派のサンプルとその近傍のサンプルを組み合わせて新しいサンプルを作成し、データのバランスを改善します。 ト レーニン グが開始されました。 結果を待っている間に少しハイパーパラメーターについて解説します。 RandomizedSearchCVを用いることで、最適なハイパーパラメーターを見つけながら、最適なモデルを構築します。 パラメータ 説明 max_depth 各決定木の最大の深さです。モデルの複雑さを制御します。 eta 学習率です。各学習ステップでの重みの更新量を調整して学習の安定性を向上させます。 gamma 分割が必要かどうかを決めるためのノード分割コストに対する 閾値 です。予測の正確性が少なくとも設定値以上改善されない限り、新しい分岐は作りません。 min_child_weight 子ノードが持つべき最小のサンプル数の合計重みです。 過学習 を防ぐために小さなサンプルの分割を抑制します。 subsample 各決定木を構築する際に使用するランダムに選択されたサンプルの割合です。 objective='binary:logistic' バイナリ分類のためのロジスティック回帰を目的関数として設定し、確率的な出力を生成します。 num_round 構築する決定木の数です。 STEP6:評価 confusion_matrixの結果は以下の並びで表示されます。TP(True Positive)が左上にくるイメージがありますよね。TN(True Negative)が左上に来るので注意してください。 Actual / Predicted Negative Positive Negative TN FP Positive FN TP 学習と検証が完了したので評価を表示してみます。 一番性能が良さそうなFold5をピックアップして中身を読み解きましょう。 全体的な精度(accuracy)は70% であり、比較的良好な結果と言えます。 クラス0の適合率が82%で、これはモデルがクラス0を予測する際に正確であることを示しています。 クラス1の適合率が58%と低めであり、これはクラス1を予測する際に誤った予測が多いことを示しています。 クラス1の再現率が77%で、これはモデルがクラス1を見逃す確率が低いことを示しています。 クラス0の再現率が65%と低めであり、クラス0を見逃すケースが一定数存在することを示しています。 全体的に良好な結果ですが、クラス1の適合率が低い ことが分かります。 なぜ、クラス1の適合率が低いのかはテストデータを見れば明らか です。今回は学習用データの作成の際に"ゲームに課金する確率"を操作して、"ゲームに課金する素質がある人"でも課金する(paid=1)確率は約4割、課金しない(paid=0)確率が約6割存在するためです。このような不確実性を持ったデータに対する学習にしては、モデルの品質はかなり良い結果であると考えられます。 機械学習 の分類手法は他にもたくさんあるので、気が向いたら検証してみようと思います。 最後まで読んでいただき、ありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! <電通×IT>電通グループ基幹システムプロジェクトマネージャー <電通×IT>顧客DX案件プロジェクトマネージャー <電通×IT>クラウドアーキテクト <電通×IT>アプリケーションアーキテクト 執筆: 英 良治 (@hanabusa.ryoji) 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 クラウド イノベーション センターの柴田です。 本記事ではDatadog Logsにてホストタグが継承されない問題の原因と解決方法をご紹介します。 発生した問題 Amazon EKS クラスタ &マネージド型ノードグループを構築し、その上のPodのログをDatadog Logsで収集・管理していました。 あるとき一部のログに AWS Integration 関連のタグ( region 、 availability-zone 、 autoscaling_group 、EC2 インスタンス のタグなど)が付与されていないことがわかりました。 以下は期待通り先述のタグが付与されたログ(以降は正常なログと呼称)です。 { " msg ":" structured log without host attribute " } 以下は期待に反して先述のタグが付与されなかったログ(以降は問題のあったログと呼称)です。 正常なログと比べてタグの件数が少ないことがわかります。 { " msg ":" structured log with host attribute "," host ":" localhost " } 原因 先述の問題の原因は、問題のあったログの host 属性が誤って設定されたせいでした。 ログの host 属性について Datadogの公式ドキュメント ではログの host 属性について以下のように説明しています。 メトリクスで定義された送信元ホストの名前。Datadog で一致したホストから、対応するホストタグが自動的に取得され、ログに適用されます。Agent では、この値が自動的に設定されます。 正常なログの場合 正常なログの host 属性の値はマネージド型ノードグループのEC2 インスタンス の インスタンス IDです。 そのため一致したホストのホストタグがログに適用されます。 問題のあったログの場合 一方、問題のあったログの host 属性の値は localhost です。 Datadog上には localhost に一致するホストが存在しなかったため、ホストタグの継承が行われませんでした。 なぜログの host 属性の値は インスタンス IDではなく localhost だったのでしょうか? Datadog Logsの Pipeline の Preprocessing for JSON logs は以下のとおり設定されています。 そのため、ログが host hostname syslog.hostname のいずれかの属性を含む構造化ログの場合、その値がログの host 属性として設定されます。 問題のあったログは host 属性を含む構造化ログでした。 そのため、ログの host 属性の値が インスタンス IDから localhost へ上書きされていました。 解決方法 先述の問題はログの host 属性をDatadog上のホストと一致するように設定することで解決できます。 具体的には2つの方法が挙げられます。 Pipeline の Preprocessing for JSON logs の Host attributes から問題となっている構造化ログの属性( host )を削除する。 ログから問題となっている構造化ログの属性( host )のキーをリネームまたは削除する。 前者の方法はDatadog Organization全体に影響を及ぼすため、今回は後者の方法を採用しました。 その結果、問題のあったログに対して、期待通り AWS Integration関連のタグが付与されるようになりました。 おわりに 本記事ではDatadog Logsにてホストタグが継承されない問題の原因と解決方法をご紹介しました。 最後までお読みいただき、ありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! クラウドアーキテクト 執筆: @shibata.takao 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター