Appearance
【提取游戏模型】
有时候为了学习,需要借助一些其他游戏的模型和素材来做练习。
- 安装 Intel GPA。
- 使用GPA进行渲染数据抓帧。
- 导出各种csv数据进行备用。
- 导出需要的各种图片素材资源备用。
- 在Unity使用自己写的生产fbx工具进行转换模型。
- 把转换出来的模型保存为fbx。
- 重新写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);
}
}