線をなぞる表現の作り方 (進捗判定付き)
シェーダー
積木製作の北村です。
プレイヤーが線をなぞる操作を作る場合、皆さんはどのようなアプローチで実装しますか?
弊社のブログでも過去に以下の方法をご紹介したことがあります。
一筆書きの簡易表現
今回は、なぞる操作の進捗を取得したい場合の実装アプローチをご紹介します。
なぞり具合の進捗を取得できるようにすることで、操作の終了判定を行う際などに利用することが出来ます。
今回作ったもの
先に完成図をお見せすると、こんな感じです。
正面のコンクリートの塊に薄い赤の線が表示されていますが、
これを左トリガーの操作でなぞっていきます。
赤のスプレーでペイントするイメージです。
前方のTextMeshでなぞり具合の進捗をパーセンテージで確認することが出来ます。
メッシュの準備
今回は線の形状のメッシュを用意しておきます。
どの部分をなぞっているか判定するのにMeshColliderとRaycastを利用したいためにこのようにしました。

UVも重なる部分が無いように開いておきます。

テクスチャの用意
メッシュのUVレイアウトを元に、線のメッシュの領域を識別するためのマスク用のテクスチャも用意します。
マテリアルの描画に使用するものと進捗判定に使用するものとで解像度を分けて2種類用意します。
こちらが描画用(512*512px)、

こちらが判定用(32*32px)です。

インタラクションの実装
1. 下準備
まずは線をなぞった結果を記録する為、RenderTextureを生成します。
今回は、描画用・判定用に対してそれぞれ読み取り用、書き込み用のRenderTextureを用意しました
テクスチャを黒一色に初期化する為、個別にGL.Clear()を実行しています。
また、コードサンプルは割愛しますが、線を塗る処理を行うComputeShaderを使うための下準備も併せて行います。
2. 線を塗る
線を塗るには、まずは線のメッシュ状の塗りたい場所のUV座標を取得する必要があります。
今回は以下のようにPhysics.RaycastAll()で行いました。
メッシュの塗りたい場所を取得するイメージとしては下の図のような感じです。
上の赤い点が塗る操作を行う手の位置で、
そこから最も近いメッシュ上の点がUV座標を取得する部位になります。
線のMeshColliderを検出出来たら、RaycastHitから線のメッシュのUV座標を取得して
ComputeShaderに渡します。
ComputeShaderでは1で用意したRenderTextureを塗っていきます。
受け取ったUV座標から一定の範囲内のピクセルを塗っていきます。
この時のRenderTextureの状態を図にすると下のようになります。
下の図の赤い点が取得したUV座標で、その周囲の薄い赤の範囲がRenderTexture上で色を塗る範囲になるイメージです。
今回は描画用・判定用のRenderTextureを一つの関数でまとめて塗る為、
判定用のRenderTextureを塗る前にピクセルの位置に応じて処理を弾いています。
塗った後のRenderTextureは線のマテリアルに渡し、色を塗り分ける為のマスクとして使用します。
マテリアルにRenderTextureが適用されると、下の図のように対応する部分だけが赤く塗られます。
3. 進捗判定
進捗判定は、判定用RenderTextureのピクセルを全探索して塗られた割合をチェックしていきます。
事前に用意した判定用マスクテクスチャの内、線のメッシュが存在する範囲だけを確認します。
その中で何個塗られたピクセルがあるかを集計すれば割合を求めることが出来ます。
再度RenderTextureの図を見てみましょう。
線の領域の上の赤く塗られたマス目の数を数え、そこから塗られた割合を求めます。
進捗判定は毎フレーム安定して動作するようにしたいので、
判定用のテクスチャ類は全て解像度を落として用意しました。
今回は32*32ピクセルのテクスチャを使用しております。
また、2でRenderTextureを塗る際も、実は若干半径を広げてペイントしています。
C#で計算結果を受け取りComputeBuffre.GetData()を使用し、長さ1の配列に格納しました。
実装例
実際にテストシーンで使ったComputeShaderはこちらです。
上記のComputeShaderを呼び出すC#スクリプトは以下の物を使いました。
その他、テストシーンでの入力取得に以下のスクリプトも使用しています。
ControllerInputHandler.cs
MarkingInputHandler_Demo.cs
ここに各種シェーダー、マテリアル、テクスチャ等を加えて完成すると以下のようになります。