Skip to content

【提取游戏模型】

有时候为了学习,需要借助一些其他游戏的模型和素材来做练习。

  1. 安装 Intel GPA
  2. 使用GPA进行渲染数据抓帧。
  3. 导出各种csv数据进行备用。
  4. 导出需要的各种图片素材资源备用。
  5. 在Unity使用自己写的生产fbx工具进行转换模型。
  6. 把转换出来的模型保存为fbx。
  7. 重新写Shader还原模型原本的样子。

需要导出这些csv数据

  • pos.csv(顶点坐标位置)
  • posindex.csv(顶点坐标索引)
  • normal.csv(法线向量)
  • tangent.csv(切线向量)
  • uv1.csv(UV坐标)

以下是转换工具ExportFbxToolWindow.cs文件源码:

CSharp
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class ModelCsvTransform
{
    Hashtable posMap = new Hashtable();

    Hashtable normalMap = new Hashtable();

    Hashtable tangentMap = new Hashtable();

    Hashtable uvMap = new Hashtable();

    List<int> posIndexList;

    int maxVertexCount = 0;

    public Mesh getMesh()
    {
        // 顶点数据
        Vector3[] vertices = new Vector3[maxVertexCount+1];
        for (int i = 0; i < (maxVertexCount+1);i++)
        {
            if(posMap.ContainsKey(i)){
                Vector3 v = (Vector3) posMap[i];
                vertices[i] = v;
            }
        }

        // 构成三角形的顶点索引数组
        int[] triangles = posIndexList.ToArray();

        // 法线
        Vector3[] normalVertices = null;
        if(normalMap.Count > 0){
            normalVertices = new Vector3[maxVertexCount+1];
            for (int i = 0; i < (maxVertexCount+1);i++)
            {
                if(normalMap.ContainsKey(i)){
                    Vector3 v = (Vector3) normalMap[i];
                    normalVertices[i] = v;
                }
            }
        }
        
        // 切线
        Vector4[] tangentVertices = null;
        if(tangentMap.Count > 0){
            tangentVertices = new Vector4[maxVertexCount+1];
            for (int i = 0; i < (maxVertexCount+1);i++)
            {
                if(tangentMap.ContainsKey(i)){
                    Vector4 v = (Vector4) tangentMap[i];
                    tangentVertices[i] = v;
                }
            }
        }

        // uv
        Vector2[] uvVertices = null;
        if(uvMap.Count > 0){
            uvVertices = new Vector2[maxVertexCount+1];
            for (int i = 0; i < (maxVertexCount+1);i++)
            {
                if(uvMap.ContainsKey(i)){
                    Vector2 v = (Vector2) uvMap[i];
                    uvVertices[i] = v;
                }
            }
        }

        Mesh mesh = new Mesh();
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        if(normalVertices != null){
            mesh.normals = normalVertices;
        }
        if(tangentVertices != null)
        {
            mesh.tangents = tangentVertices;
        }
        if(uvVertices != null)
        {
            mesh.uv = uvVertices;
        }
        mesh.hideFlags = HideFlags.HideAndDontSave;
        
        return mesh;
    }

    public void setPosCsv(string data)
    {
        posMap = new Hashtable();
        string[] strArray = data.Split('\r');
        for (int i = 1; i < strArray.Length; i = i + 3)
        {
            try
            {
                if(strArray[i].Length > 1)
                {
                    string[] lineArrX = strArray[i].Split(',');
                    string[] lineArrY = strArray[i+1].Split(',');
                    string[] lineArrZ = strArray[i+2].Split(',');

                    if(lineArrX.Length >= 2 && lineArrY.Length >= 2 && lineArrZ.Length >= 2){
                        string indexStr = lineArrX[0].Replace("\n","");
                        int index = int.Parse(indexStr);// 索引
                        float px = float.Parse(formatNumString(lineArrX[1]));
                        float py = float.Parse(formatNumString(lineArrY[1]));
                        float pz = float.Parse(formatNumString(lineArrZ[1]));
                        posMap.Add(index,new Vector3(px,py,pz));
                    }
                }  
            }
            catch (System.Exception)
            {
                throw;
            }
        }
        Debug.Log("posMapCount:" + posMap.Count);
    }

    public void setPosIndexCsv(string data)
    {
        maxVertexCount = 0;
        posIndexList = new List<int>();
        string[] strArray = data.Split('\r');
        for (int i = 1; i < strArray.Length; i++)
        {
            string[] lineArr = strArray[i].Split(',');
            if(lineArr.Length > 1)
            {
                int index = int.Parse(lineArr[1]);// 索引
                if(index > maxVertexCount){
                    maxVertexCount = index;
                }
                posIndexList.Add(index);
            }
        }
        Debug.Log("posIndexList Length:" + posIndexList.Count);
    }

    public void setNormalCsv(string data)
    {
        normalMap = new Hashtable();
        if(data == "")return ;
        string[] strArray = data.Split('\r');
        for (int i = 1; i < strArray.Length; i = i + 3)
        {
            try
            {
                if(strArray[i].Length > 1)
                {
                    string[] lineArrX = strArray[i].Split(',');
                    string[] lineArrY = strArray[i+1].Split(',');
                    string[] lineArrZ = strArray[i+2].Split(',');

                    if(lineArrX.Length >= 2 && lineArrY.Length >= 2 && lineArrZ.Length >= 2){
                        string indexStr = lineArrX[0].Replace("\n","");
                        int index = int.Parse(indexStr);// 索引
                        float px = float.Parse(formatNumString(lineArrX[1]));
                        float py = float.Parse(formatNumString(lineArrY[1]));
                        float pz = float.Parse(formatNumString(lineArrZ[1]));
                        normalMap.Add(index,new Vector3(px,py,pz));
                    }
                }
            }
            catch (System.Exception)
            {
                throw;
            }
             
        }
    }

    public void setTangentCsv(string data)
    {
        tangentMap = new Hashtable();
        if(data == "")return ;
        string[] strArray = data.Split('\r');
        for (int i = 1; i < strArray.Length; i = i + 4)
        {
            try
            {
                if(strArray[i].Length > 1)
                {
                    string[] lineArrX = strArray[i].Split(',');
                    string[] lineArrY = strArray[i+1].Split(',');
                    string[] lineArrZ = strArray[i+2].Split(',');
                    string[] lineArrW = strArray[i+3].Split(',');

                    if(lineArrX.Length >= 2 && lineArrY.Length >= 2 && lineArrZ.Length >= 2 && lineArrW.Length >= 2){
                        string indexStr = lineArrX[0].Replace("\n","");
                        int index = int.Parse(indexStr);// 索引
                        float px = float.Parse(formatNumString(lineArrX[1]));
                        float py = float.Parse(formatNumString(lineArrY[1]));
                        float pz = float.Parse(formatNumString(lineArrZ[1]));
                        float pw = float.Parse(formatNumString(lineArrW[1]));
                        tangentMap.Add(index,new Vector4(px,py,pz,pw));
                    }
                }
            }
            catch (System.Exception)
            {
                throw;
            }
        }
    }

    public void setUv1Csv(string data)
    {
        uvMap = new Hashtable();
        if(data == "")return ;
        string[] strArray = data.Split('\r');
        for (int i = 1; i < strArray.Length; i = i + 2)
        {
            try
            {
                if(strArray[i].Length > 1)
                {
                    string[] lineArrX = strArray[i].Split(',');
                    string[] lineArrY = strArray[i+1].Split(',');

                    if(lineArrX.Length >= 2 && lineArrY.Length >= 2){
                        string indexStr = lineArrX[0].Replace("\n","");
                        int index = int.Parse(indexStr);// 索引
                        float px = float.Parse(formatNumString(lineArrX[1]));
                        float py = float.Parse(formatNumString(lineArrY[1]));
                        uvMap.Add(index,new Vector2(px,py));
                    }
                }
            }
            catch (System.Exception)
            {
                throw;
            }
             
        }
    }

    public string formatNumString(string numStr)
    {
        if(numStr == "inf")
        {
            return "0";
        }
        if(numStr == null || numStr == ""){
            return "0";
        }
        if(numStr.IndexOf('e') != -1 || numStr.IndexOf('E') != -1)
        {
            return float.Parse(numStr).ToString();
        }
        if(numStr.Length > 15){
            return numStr.Substring(0,15);
        }
        return numStr;
    }
}

public class ExportFbxToolWindow : EditorWindow {

    [MenuItem("vpTools/ExportFbxTool")]
    private static void ShowWindow() {
        var window = GetWindow<ExportFbxToolWindow>();
        window.titleContent = new GUIContent("ExportFbxTool");
        window.Show();
    }

    string vertexPosPathTextField = "F:/svn_weipengRepository/extractAssets/wangzhe/daji/images/pos.csv";
    string vertexPosIndexPathTextField = "F:/svn_weipengRepository/extractAssets/wangzhe/daji/images/posindex.csv";
    string normalPathTextField = "F:/svn_weipengRepository/extractAssets/wangzhe/daji/images/normal.csv";
    string tangentPathTextField = "F:/svn_weipengRepository/extractAssets/wangzhe/daji/images/tangent.csv";
    string uv1PathTextField = "F:/svn_weipengRepository/extractAssets/wangzhe/daji/images/uv1.csv";

    string dirName = "DefaultObject";
    private void OnGUI() {

        GUILayout.Label("顶点坐标位置csv文件路径");
        vertexPosPathTextField = GUILayout.TextField(vertexPosPathTextField);
        GUILayout.Space(10);

        GUILayout.Label("顶点索引csv文件路径");
        vertexPosIndexPathTextField = GUILayout.TextField(vertexPosIndexPathTextField);
        GUILayout.Space(10);

        GUILayout.Label("法线csv文件路径");
        normalPathTextField = GUILayout.TextField(normalPathTextField);
        GUILayout.Space(10);

        GUILayout.Label("切线csv文件路径");
        tangentPathTextField = GUILayout.TextField(tangentPathTextField);
        GUILayout.Space(10);

        GUILayout.Label("UV1 csv文件路径");
        uv1PathTextField = GUILayout.TextField(uv1PathTextField);
        GUILayout.Space(10);

        if(GUILayout.Button("创建在场景里",GUILayout.Width(200),GUILayout.Height(35)))
        {
            createModelOnScene();
        }

        GUILayout.Space(10);
        if(GUILayout.Button("清空路径",GUILayout.Width(200),GUILayout.Height(35)))
        {
            vertexPosPathTextField = "";
            vertexPosIndexPathTextField = "";
            normalPathTextField = "";
            tangentPathTextField = "";
            uv1PathTextField = "";
        }

        GUILayout.Space(10);
        GUILayout.Label("normal.csv pos.csv posindex.csv tangent.csv uv.csv");
        if(GUILayout.Button("自动路径",GUILayout.Width(200),GUILayout.Height(35)))
        {
            // 找到根目录
            string rootPath = EditorUtility.OpenFolderPanel("","C:/","");

            vertexPosPathTextField = "";
            vertexPosIndexPathTextField = "";
            normalPathTextField = "";
            tangentPathTextField = "";
            uv1PathTextField = "";

            bool b1 = false;
            bool b2 = false;
            if(rootPath != null && rootPath != ""){
                DirectoryInfo directory = new DirectoryInfo(rootPath);
                dirName = directory.Name;
                FileInfo[] files = directory.GetFiles();
                for (int i = 0; i < files.Length; i++)
                {
                    if(files[i].Extension == ".csv")
                    {
                        if(files[i].Name == "normal.csv")
                        {
                            normalPathTextField = files[i].FullName;
                        }else if(files[i].Name == "pos.csv")
                        {
                            vertexPosPathTextField = files[i].FullName;
                            b1 = true;
                        }else if(files[i].Name == "posindex.csv")
                        {
                            vertexPosIndexPathTextField = files[i].FullName;
                            b2 = true;
                        }else if(files[i].Name == "tangent.csv")
                        {
                            tangentPathTextField = files[i].FullName;
                        }else if(files[i].Name == "uv.csv")
                        {
                            uv1PathTextField = files[i].FullName;
                        }
                        
                    }
                }
            }

            if(b1 && b2)
            {
                createModelOnScene();
            }
        }
    }

    /// <summary>
    /// 在场景上创建模型
    /// </summary>
    private void createModelOnScene()
    {
        // 顶点数据
        string vertexPosData = readFile(vertexPosPathTextField);

        // 构成面的顶点索引数组
        string posIndexData = readFile(vertexPosIndexPathTextField);

        // 法线数据(非必须)
        string normalData = "";
        if(normalPathTextField != ""){
            normalData = readFile(normalPathTextField);
        }

        // 切线数据
        string tangentData = "";
        if(tangentPathTextField != "")
        {
            tangentData = readFile(tangentPathTextField);
        }

        // uv1
        string uv1Data = "";
        if(uv1PathTextField != "")
        {
            uv1Data = readFile(uv1PathTextField);
        }

        ModelCsvTransform csvTF = new ModelCsvTransform();
        csvTF.setPosCsv(vertexPosData);
        csvTF.setPosIndexCsv(posIndexData);
        csvTF.setNormalCsv(normalData);
        csvTF.setTangentCsv(tangentData);
        csvTF.setUv1Csv(uv1Data);

        Mesh m = csvTF.getMesh();
        GameObject go = new GameObject(dirName);
        MeshFilter mf = go.AddComponent<MeshFilter>();
        mf.mesh = m;
        MeshRenderer mr = go.AddComponent<MeshRenderer>();
        Material mat = new Material(Shader.Find("Standard"));
        mat.hideFlags = HideFlags.HideAndDontSave;
        mr.material = mat;
        Debug.Log("生成网格成功,请在场景上查看");
    }

    /// <summary>
    /// 创建网格
    /// </summary>
    /// <param name="vertices"> 顶点列表 </param>
    /// <param name="triangles"> 顶点索引数组 </param>
    public Mesh CreateMesh(Vector3[] vertices,int[] triangles)
    {
        Mesh mesh = new Mesh();
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.hideFlags = HideFlags.HideAndDontSave;
        return mesh;
    }

    private string readFile(string path)
    {
        while (path.IndexOf('"') != -1)
        {
            path = path.Remove(path.IndexOf('"'),1);
        }
        return File.ReadAllText(path);
    }
}

MIT Licensed