LoginSignup
37
29

More than 5 years have passed since last update.

[Unity] Plane Meshを別のモデルの形状に沿わせる

Posted at

落下地点をマーキングするなど、なにかしらのインジケータとして地面に沿うようになにかを表示したい、という要望があるかと思います。(というかまんまそれがやりたかったんですがw)

こちらの記事(WORKING WITH UNITY3D ENGINE – DECAL EDITOR)を見つけ、Raycastを使ってやってるのかなーと思いたち、実装してみました。

動きとしてはこんな感じになります。

Gyazo]
実際に地面に沿うところ

処理の流れ

今回作成したものの流れは以下です。

  1. ランタイムで新しくGameObjectを生成する
  2. 各種、表示に必要なコンポーネントを追加
  3. スクリプトをアタッチされたオブジェクトのMesh情報をコピー
  4. Update内で頂点情報を更新

という流れになります。

コード

コードを順番に見ていきます。

GameObjectを生成する

まずは空のGameObjectを生成します。
最終的にこのオブジェクトの頂点情報を更新していきます。

// Create a projected mesh base.
void CreateProjectedObject() {
    m_projectedOject = new GameObject("ProjectedObject");

    MeshRenderer newRenderer = m_projectedOject.AddComponent<MeshRenderer>();
    MeshFilter newMeshFilter = m_projectedOject.AddComponent<MeshFilter>();

    // Copy a mesh to the game object.
    MeshFilter meshFilter = GetComponent<MeshFilter>();
    Mesh       mesh       = meshFilter.mesh;

    newMeshFilter.mesh = mesh;

}

new GameObject() で新規にゲームオブジェクトを生成します。これを実行した時点で自動的にSceneのルートに配置されます。
今回は親子関係を作る必要がないため親は設定しません。

次に、画面に表示するのに必要なコンポーネントを追加します。
具体的には MeshRendererMeshFilter です。

そして追加された MeshFilterMesh に生成元のオブジェクトのメッシュ情報をコピーします。
(頂点情報やindex、レンダリングタイプなど諸々設定する必要があるため)

以上でセットアップは終わりです。

Updateのタイミングで頂点情報を更新

あとは Update のタイミングで頂点情報を更新してやれば完成です。

計算の手順としては以下です。

  1. 元のオブジェクトの頂点を取り出す
  2. 取り出した頂点をワールド空間座標に変換する
  3. 各頂点をいったん上空に起き、そこから下向きに Ray を飛ばす
  4. ヒットしたらその位置を頂点の高さに設定(ヒットしなかったら 0 にする)
  5. 再計算された頂点情報を、投影用オブジェクトの頂点として再設定

という流れです。

頂点をワールド空間座標に変換する

頂点を変換するには以下のようにします。

// 変換用行列を取り出す
Matrix4x4  matrix = meshFilter.transform.localToWorldMatrix;

// 行列を頂点に適用する
Vector3 vec = matrix.MultiplyPoint(vertex);

Rayを飛ばして位置を算出する

頂点が変換できたら、全頂点に対して以下のようにRayを飛ばし、位置を判定します。

foreach (Vector3 vertex in verticies) {
    Vector3 vec = matrix.MultiplyPoint(vertex);
    vec.y = 1000;

    RaycastHit hit;
    if (Physics.Raycast(vec, Vector3.down, out hit)) {
        Vector3 newVert = vec;
        newVert.y = hit.point.y;
        newVerticies[i] = newVert;
    }
    else {
        vec.y = 0;
        newVerticies[i] = vec;
    }

    i++;
}

Updateメソッドコード全文

まとめると、頂点位置計算の処理は以下のようになります。

// Update is called once per frame
void Update () {
    MeshFilter meshFilter   = GetComponent<MeshFilter>();
    Matrix4x4  matrix       = meshFilter.transform.localToWorldMatrix;
    Mesh       mesh         = meshFilter.mesh;
    Vector3[]  verticies    = mesh.vertices;
    Vector3[]  newVerticies = new Vector3[verticies.Length];

    int i = 0;
    foreach (Vector3 vertex in verticies) {
        Vector3 vec = matrix.MultiplyPoint(vertex);
        vec.y = 1000;

        RaycastHit hit;
        if (Physics.Raycast(vec, Vector3.down, out hit)) {
            Vector3 newVert = vec;
            newVert.y = hit.point.y;
            newVerticies[i] = newVert;
        }
        else {
            vec.y = 0;
            newVerticies[i] = vec;
        }

        i++;
    }

    Mesh projectedMesh = m_projectedOject.GetComponent<MeshFilter>().mesh;
    projectedMesh.vertices = newVerticies;

    // 法線情報の更新が必要な場合は以下を実行する
    // projectedMesh.RecalculateNormals();
}

最終コード

動作するコード全文を載せておきます。

ProjectedMesh.cs
using UnityEngine;
using System.Collections;

public class ProjectedMesh : MonoBehaviour {

    // Use for projected mesh.
    private GameObject m_projectedOject;

    // Use this for initialization
    void Start () {
        CreateProjectedObject();
        GetComponent<MeshRenderer>().enabled = false;
    }


    // Update is called once per frame
    void Update () {
        MeshFilter meshFilter   = GetComponent<MeshFilter>();
        Matrix4x4  matrix       = meshFilter.transform.localToWorldMatrix;
        Mesh       mesh         = meshFilter.mesh;
        Vector3[]  verticies    = mesh.vertices;
        Vector3[]  newVerticies = new Vector3[verticies.Length];

        int i = 0;
        foreach (Vector3 vertex in verticies) {
            Vector3 vec = matrix.MultiplyPoint(vertex);
            vec.y = 1000;

            RaycastHit hit;
            if (Physics.Raycast(vec, Vector3.down, out hit)) {
                Vector3 newVert = vec;
                newVert.y = hit.point.y;
                newVerticies[i] = newVert;
            }
            else {
                vec.y = 0;
                newVerticies[i] = vec;
            }

            i++;
        }

        Mesh projectedMesh = m_projectedOject.GetComponent<MeshFilter>().mesh;
        projectedMesh.vertices = newVerticies;
        // projectedMesh.RecalculateNormals();
    }

    // Create a projected mesh base.
    void CreateProjectedObject() {
        m_projectedOject = new GameObject("ProjectedObject");

        MeshRenderer newRenderer = m_projectedOject.AddComponent<MeshRenderer>();
        MeshFilter newMeshFilter = m_projectedOject.AddComponent<MeshFilter>();

        // Copy a mesh to the game object.
        MeshFilter meshFilter = GetComponent<MeshFilter>();
        Mesh       mesh       = meshFilter.mesh;

        newMeshFilter.mesh = mesh;
    }
}

参考にした記事

37
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
29