简体   繁体   中英

How to get dynamic vertex coordinates from Unity animation?

I have a question about generating point cloud from 3D model mesh in animation.

I try it following the steps below.

  1. I get free assets. ( assets url )
  2. I set up an animation that the 3D model walking with "FreeVoxelGril-walk" from "Inspector > Select Motion".
  3. I have the 3D model walk using a C# script. This script has functions that count vertexes on the mesh and get vertex coordinates. In addition, it exports these data to a.csv file.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerControl : MonoBehaviour
{
    // 移動スピード
    public float speed;

    // 前進or後退
    bool FronFlag = true;

    // 保存ファイル名
    public string filename = "sample";

    // 原点にあるオブジェクトの頂点を数えるか?
    bool OriginSaveFlag = false;

    // 時間計測のための変数
    // private float time; //時間計測のための変数

    // Start is called before the first frame update
    void Start() {
        if (OriginSaveFlag) {
            // 頂点の数を数える
            SkinnedMeshRenderer skin = GetComponentInChildren<SkinnedMeshRenderer>();
            Mesh mesh = skin.sharedMesh;
            int vtx_num = 0;
            for (int i = 0; i < mesh.vertices.Length; i++) {
                vtx_num++;
                Vector3 item = mesh.vertices[i];
                Debug.Log("No." + vtx_num + ", " + "Vertex Coordinates: " + item.ToString("F6"));
            }
            Debug.Log("Number of vertexes: " + vtx_num);
            Vector3[] vtx_posi_array = new Vector3[vtx_num];
        
            // 頂点座標を取得
            int count = 0;
            for(int i = 0; i < mesh.vertices.Length; i++) {
                float x = mesh.vertices[i].x;
                float y = mesh.vertices[i].y;
                float z = mesh.vertices[i].z;
                vtx_posi_array[count] = new Vector3(x, y, z);
                count++;
            }

            // csvファイルに書き込む
            try {
                filename = filename + ".csv";
                bool append = false;
                Debug.Log("vtx_posi_array.Length: " + vtx_posi_array.Length);
                using(var sw = new System.IO.StreamWriter(@filename, append)) {
                    for(int i = 0; i < vtx_posi_array.Length; ++i) {
                        sw.WriteLine("{0},{1},{2}", vtx_posi_array[i].x, vtx_posi_array[i].y, vtx_posi_array[i].z);
                    }
                }
            }
            catch(System.Exception e) {
                Debug.Log(e.Message);
            }
        }
    }

    // Update is called once per frame
    void Update() {
        // プレイヤの位置情報
        Transform PlayerTransform = this.transform;
        
        // 座標を取得
        Vector3 pos = PlayerTransform.position;
        // Debug.Log("座標: " + pos);

        if (FronFlag) {
            // 前に移動する
            transform.position += transform.forward * speed * Time.deltaTime;
            if (pos.z <= -10.0){
                FronFlag = false;
            }
        } else {
            // 後ろに移動する
            transform.position -= transform.forward * speed * Time.deltaTime;
                if (pos.z >= 0){
                FronFlag = true;
            }
        }

        SkinnedMeshRenderer skin = GetComponentInChildren<SkinnedMeshRenderer>();
        Mesh mesh = skin.sharedMesh;
        // Debug.Log("法線: " + mesh.uv.Length);
        // Debug.Log("三角形: " + mesh.triangles.Length);

        if (Input.GetKeyDown(KeyCode.S)) { // Game画面にて、キーボードを入力
            Debug.Log("Since you put the S key, vertex coordinates are saved.");

            // 頂点の数を数える
            int vtx_num = 0;
            for (int i = 0; i < mesh.vertices.Length; i++) {
                vtx_num++;
                Vector3 item = mesh.vertices[i];
                Debug.Log("No." + vtx_num + ", " + "Vertex Coordinates: " + item.ToString("F6"));
            }
            Debug.Log("Number of vertexes: " + vtx_num);
            Vector3[] vtx_posi_array = new Vector3[vtx_num];
        
            // 頂点座標を取得
            int count = 0;
            for(int i = 0; i < mesh.vertices.Length; i++) {
                float x = mesh.vertices[i].x;
                float y = mesh.vertices[i].y;
                float z = mesh.vertices[i].z;
                vtx_posi_array[count] = new Vector3(x, y, z);
                count++;
            }

            // csvファイルに書き込む
            try {
                filename = filename + ".csv";
                bool append = false;
                Debug.Log("vtx_posi_array.Length: " + vtx_posi_array.Length);
                using(var sw = new System.IO.StreamWriter(@filename, append)) {
                    for(int i = 0; i < vtx_posi_array.Length; ++i) {
                        sw.WriteLine("{0},{1},{2}", vtx_posi_array[i].x, vtx_posi_array[i].y, vtx_posi_array[i].z);
                    }
                }
            }
            catch(System.Exception e) {
                Debug.Log(e.Message);
            }
        }
    }
}

  1. I convert the.csv file to a.pcd file.

The method can generate static point cloud. In other words, the point cloud does not reflect a walking posture, eg, movement of hands and feet, head.

In Step 4, I found a problem in that vertex coordinates had not changed when the 3D model is moving using Unity animation.

What should I do to solve the problem?

That is exactly the purpose of a SkinnedMeshRenderer , that you can animate its bones without actually changing its underlying mesh data.

You could use BakeMesh to create

a snapshot of SkinnedMeshRenderer and stores it in mesh.

The vertices are relative to the SkinnedMeshRenderer Transform component.

like eg (simplyfied a lot)

var targetMesh = new Mesh();
skin.BakeMesh(targetMesh);

be aware though that of course this is not the most performant method depending on the complexity of your mesh.


Regarding performance you have some other improvement potentials here like eg

  • use private Mesh targetMesh = new Mesh(); so always the same one is re-used
  • cash the skin into a class field only once

also note that this

 int vtx_num = 0;
 for (int i = 0; i < mesh.vertices.Length; i++) 
 {
     vtx_num++;
     Vector3 item = mesh.vertices[i];
     Debug.Log("No." + vtx_num + ", " + "Vertex Coordinates: " + item.ToString("F6"));
 }
 Debug.Log("Number of vertexes: " + vtx_num);
 Vector3[] vtx_posi_array = new Vector3[vtx_num];
    
 // 頂点座標を取得
 int count = 0;
 for(int i = 0; i < mesh.vertices.Length; i++) {
     float x = mesh.vertices[i].x;
     float y = mesh.vertices[i].y;
     float z = mesh.vertices[i].z;
     vtx_posi_array[count] = new Vector3(x, y, z);
     count++;
 }

can/should be simplified to

var vtx_posi_array = mesh.vertices;
Debug.Log("Number of vertexes: " + vtx_posi_array.Length);

for(var i = 0; i < vtx_posi_array.Length; i++)
{
    var item = vtx_posi_array[i];
    Debug.Log($"No. {i}, Vertex Coordinates: {item.ToString("F6")}");
}

Reasons:

  • Vector3 is a struct and anyway always a copy by value , it is only more overhead to first store each component in a float and then construct a Vector3 again
  • In both loops i already is your index. You have redundant vtx_num and count variables
  • mesh.vertices is a property that every time returns a new copy of an Vector3[] -> access it only once , then directly work on the returned array since it is just a copy

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM