Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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

参考にした記事

edo_m18
現在はUnity ARエンジニア。 主にARのコンテンツ制作をしています。 最近は機械学習にも興味が出て勉強中です。 Unityに関するブログは別で書いています↓ https://edom18.hateblo.jp/
http://edom18.hateblo.jp/
unity-game-dev-guild
趣味・仕事問わずUnityでゲームを作っている開発者のみで構成されるオンラインコミュニティです。Unityでゲームを開発・運用するにあたって必要なあらゆる知見を共有することを目的とします。
https://unity-game-dev-guild.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away