Unity3.4.2升级3.5.2之lightmap数据

简介: 问题描述:   Unity3.5的升级日志中,描述了不少新的功能和一些纠错,所以想将项目的引擎版本从之前的3.4.2升级到3.5.2。   但是,从U3D2.4.2升级到U3D3.5.2之后,发现游戏场景物体出现了大量的贴图错乱,下图所示为升级前后,场景中一棵树的效果图对比,感觉问题挺严重...

 

问题描述:

  Unity3.5的升级日志中,描述了不少新的功能和一些纠错,所以想将项目的引擎版本从之前的3.4.2升级到3.5.2。

  但是,从U3D2.4.2升级到U3D3.5.2之后,发现游戏场景物体出现了大量的贴图错乱,下图所示为升级前后,场景中一棵树的效果图对比,感觉问题挺严重的,头疼啊~

问题原因:

  引用一段Unity官方升级日志,可以解释这个问题出现的原因:

  "Fixed the "Generate Lightmap UVs" reducing the difference between mac/win. Not there yet, but should be fixed for the most part. Please reimport meshes and rebake lightmaps."

  "Even more fixes to "Generate Lightmap UVs" to make it generate same UVs across Mac/Win."

  翻译过来就是,3.5的Generate Lightmap UVs的算法变了。

  之前在3.4.2中工作时,发现在Mac上打的lightmap,放到Win32上后,效果不太一样,原因就是在3.4版本中,Mac和Win32上生成光照贴图UV的算法不一样。话说,虽然不一样,但最终效果差别也不是太大,整体效果还是不错的。

  在3.5中,Generate Lightmap UVs的算法变了,以使得mac和win平台上的生成算法基本一致(不是完全一致), 这样,在mac中打的lightmap在win上就能直接使用,并且效果一样了。这是升级所带来的好处,但同时带来的坏处就是,3.4中打的lightmap,在3.5中将不能用了,悲情啊,并且官方给的建议是"rebake lightmaps"(太不负责人了)。

  PS:升级后并不是所有资源的光照贴图都错乱了,只有那些在模型导入设置中勾选了 "Generate Lightmap UVs" 的模型有问题。换句话说,就是所有依赖Unity生成光照贴图UV的模型都悲剧了。所以在此建议,正在使用3.4开发的项目组,让美术不要依赖此选项生成UV2,而是在max中直接做好,再导入Unity。

 

解决方案:

  最简单的解决方案就是rebake lightmaps(官方建议)。但考虑到美术工作量的问题,我用了另外一个方法来解决这个问题。

  基本想法是,将3.4中正确的数据备份到本地,然后重写到3.5的工程中。变化的只是模型的UV2,所以只需备份每个模型的UV2即可。

  所以代码分为2个部分:(1)拿到模型UV2的数据,并序列化到本地;(2)读取序列化数据,并将数据写入到对应的模型。

  

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

// (1)拿到模型UV2的数据,并序列化到本地;
public class UVConverterWindow : EditorWindow
{
    static EditorWindow window;
    static Vector2[] s_uv;

    public static Dictionary<string, List<List<Vector2>>> SceneUVs = new Dictionary<string, List<List<Vector2>>>();

    [MenuItem("Editor/UVConverterWindow")]
    static void Execute()
    {
        if (window == null)
            window = (UVConverterWindow)GetWindow(typeof(UVConverterWindow));
        window.Show();
    }

    void OnGUI()
    {
        if (GUILayout.Button("GetCurrentSceneUVs", GUILayout.Width(200), GUILayout.Height(50)))
        {
            GetAndPrintAllGameobject();
        }
        EditorGUILayout.Space();
        if (GUILayout.Button("SaveData", GUILayout.Width(200), GUILayout.Height(50)))
        {
            SaveData("SceneUVs.xml");
        }
        EditorGUILayout.Space();
        if (GUILayout.Button("SceneUVs.count", GUILayout.Width(200), GUILayout.Height(50)))
        {
            Debug.Log("SceneUVs.count = " + SceneUVs.Count);
        }
        EditorGUILayout.Space();
        if (GUILayout.Button("SceneUVs.Clear", GUILayout.Width(200), GUILayout.Height(50)))
        {
            SceneUVs.Clear();
        }

    }

    // 得到所有物体的UV2数据,并存进SceneUVs
    void GetAndPrintAllGameobject()
    {
        Object[] objects = GameObject.FindObjectsOfTypeAll(typeof(GameObject));
        List<GameObject> LstObjs = new List<GameObject>();
        foreach (Object obj in objects)
        {
            if ((obj as GameObject).GetComponentsInChildren(typeof(MeshFilter)).Length != 0)
                LstObjs.Add((obj as GameObject));
        }
        List<string> LstObjNames = new List<string>();
        foreach (GameObject go in LstObjs)
        {
            if (go.name.Contains("Rain") || go.name.Equals("flower_red01") || go.name.Equals("Snowflakes") || go.name.Equals("Snowbox") || go.name.Equals("levelmeshes_1")
                || go.name.Equals("Envi") || go.name.Equals("shrub") || go.name.Equals("bush") || go.name.Equals("EnVironments"))
            {
                continue;
            }

            if (SceneUVs.ContainsKey(go.name) || LstObjNames.Contains(go.name) )
                continue;
            else
                LstObjNames.Add(go.name);
        }

        foreach (string name in LstObjNames)
        {
            Debug.Log("name = " + name);
            // new GameObject
            List<List<Vector2>> newGoLst = new List<List<Vector2>>();
            GameObject asset = GameObject.Find(name) as GameObject;
            foreach (MeshFilter meshFilter in asset.GetComponentsInChildren(typeof(MeshFilter)))
            {
                // new MeshFilter
                List<Vector2> newMeshFilter = new List<Vector2>();
                Vector2[] uvs = meshFilter.sharedMesh.uv2;
                for (int i = 0; i < uvs.Length; i++)
                {
                    newMeshFilter.Add(new Vector2(uvs[i].x, uvs[i].y));
                }
                newGoLst.Add(newMeshFilter);
            }
            SceneUVs.Add(name, newGoLst);
        }
    }

    // 将SceneUVs中的数据存到本地文件
    public void SaveData(string strFilename)
    {
        XmlDocument XmlDoc = new XmlDocument();
        XmlElement XmlRoot = XmlDoc.CreateElement("SceneData");
        XmlDoc.AppendChild(XmlRoot);

        foreach (KeyValuePair<string, List<List<Vector2>>> pair in SceneUVs)
        {
            XmlElement xmlGameObject = XmlDoc.CreateElement("GameObject");
            XmlRoot.AppendChild(xmlGameObject);
            xmlGameObject.SetAttribute("name", pair.Key);

            foreach (List<Vector2> meshUVS in pair.Value)
            {
                XmlElement xmlMeshFilter = XmlDoc.CreateElement("Mesh");
                xmlGameObject.AppendChild(xmlMeshFilter);

                foreach (Vector2 uv in meshUVS)
                {
                    XmlElement xmlUVValues = XmlDoc.CreateElement("UV");
                    xmlMeshFilter.AppendChild(xmlUVValues);

                    xmlUVValues.SetAttribute("U", XmlConvert.ToString(uv.x));
                    xmlUVValues.SetAttribute("V", XmlConvert.ToString(uv.y));
                }
            }
        }

        string strPath = "Assets/Resources/DB/" + strFilename;
        if (strPath != "")
            XmlDoc.Save(strPath);

    }

}

 

  在3.4中加入上述代码,编译后,会多出一个菜单项【Editor/UVConvertorWindow】,在需要拿数据的场景中通过此菜单弹出脚本对话框,然后先通过“GetCurrentSceneUVs”按钮获取数据,再点击"SaveData"将数据存到本地。

  遍历游戏中的是几个场景,得到了12M多的数据,下面为得到的一部分数据:

<SceneData>
  <GameObject name="stone_2">
    <Mesh>
      <UV U="0.9810646" V="0.201751187" />
      <UV U="0.570682466" V="0.5148572" />
      <UV U="0.0608001873" V="0.9120108" />
      <UV U="0.224167913" V="0.982134938" />
      <UV U="0.140530467" V="0.630211532" />
      <UV U="0.0273421481" V="0.8217057" />
      <UV U="0.912159264" V="0.209614545" />
      <UV U="0.04156696" V="0.703405" />
      <UV U="0.8343242" V="0.220369309" />
      <UV U="0.6729313" V="0.498295426" />
      <UV U="0.9025791" V="0.167751029" />
      <UV U="0.6711328" V="0.469830871" />
      <UV U="0.7272404" V="0.466777444" />
...

  好的,拿到数据~\(≧▽≦)/~,接下来就是在3.5中将这些数据写入模型了。

  重写这些数据的原理是,让那些打了勾的模型重新导入,然后在导入的过程中对模型的UV2进行一个重写。下面是作为工具提供的一个窗口类:

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

// 升级窗口类
public class UpgradeUnity3D352 : EditorWindow
{
    public static Dictionary<string, List<List<Vector2>>> SceneUVs = new Dictionary<string,List<List<Vector2>>>();
    public static bool PostprocessOn = false;

    static UpgradeUnity3D352 window;
    [MenuItem("Editor/UpgradeUnity3D352")]
    static void Execute()
    {
        if (window == null)
            window = (UpgradeUnity3D352)GetWindow(typeof(UpgradeUnity3D352));
        window.Show();
    }

    void OnGUI()
    {
        if (GUILayout.Button("LoadData", GUILayout.Width(200), GUILayout.Height(50)))
        {
            LoadData("SceneUVs");
        }
        EditorGUILayout.Space();
        if (GUILayout.Button("ReimportSelectedAll", GUILayout.Width(200), GUILayout.Height(50)))
        {
            DoReimport();
        }
        PostprocessOn = EditorGUILayout.Toggle("PostprocessOn", PostprocessOn);
    }

    void OnDestroy()
    {
        PostprocessOn = false;
    }

    // 手动导致Project选中的资源重新导入
    void DoReimport()
    {
        foreach (Object o in Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets))
        {
            if (!(o is GameObject))
                continue;

            Debug.Log(AssetDatabase.GetAssetPath(o));
            AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(o));
        }
    }

    // 载入数据
    public void LoadData(string strFilename)
    {
        XmlDocument xmlDoc = new XmlDocument();
        TextAsset textAsset = (TextAsset)Resources.Load("DB/" + strFilename, typeof(TextAsset));
        if (textAsset == null)
        {
            Debug.LogError("Load Monster DB Failed!");
            return;
        }
        xmlDoc.Load(new StringReader(textAsset.text));
        XmlElement xmlRoot = xmlDoc.DocumentElement;

        SceneUVs.Clear();

        XmlNodeList xmlGOList = xmlRoot.ChildNodes;
        foreach (XmlNode go in xmlGOList)
        {
            if ((go is XmlElement) == false)
                continue;

            // GameObject
            XmlElement xmlGO = go as XmlElement;
            List<List<Vector2>> lstGOData = new List<List<Vector2>>();

            foreach (XmlNode mesh in xmlGO.ChildNodes)
            {
                if ((mesh is XmlElement) == false)
                    continue;

                // MeshFilter
                XmlElement xmlMesh = mesh as XmlElement;
                List<Vector2> lstMeshUVs = new List<Vector2>();

                foreach (XmlNode uv in xmlMesh.ChildNodes)
                {
                    if ((uv is XmlElement) == false)
                        continue;

                    XmlElement xmlUV = uv as XmlElement;

                    // UV
                    lstMeshUVs.Add(new Vector2(XmlConvert.ToSingle(xmlUV.GetAttribute("U")), XmlConvert.ToSingle(xmlUV.GetAttribute("V"))));
                }

                lstGOData.Add(lstMeshUVs);
            }

            SceneUVs.Add(xmlGO.GetAttribute("name"), lstGOData);
        }
    }
}

  在3.5中加入上述代码,编译后,会多出一个菜单项【Editor/UpgradeUnity3D352】,打开此对话框,然后在Project视口中选中场景资源目录(也可选择要处理的单个对象),勾上PostprocessOn,然后LoadData,再ReimportSelectedAll。

  将下面的脚本代码也放入3.5的工程,其中的代码利用模型导入回调函数来对模型的UV2进行重写。AssetPostprocessor是U3D提供的一个资源处理类,其中的OnPostprocessModel为该类提供的一个回调,可以用来对导入完成的模型进行一个处理。

  而ReimportSelectedAll操作,会触发Reimport行为,从而运行OnPostprocessModel中的代码,其中的代码较为简单,就是重赋UV2。

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

public class FbxProcessor : AssetPostprocessor
{
    void OnPreprocessModel()
    {
        ModelImporter modelImporter = (ModelImporter)assetImporter;
        if (modelImporter.assetPath.ToLower().Contains(".fbx") && 
            (modelImporter.assetPath.Contains("Char")
            || modelImporter.assetPath.Contains("Parts")
            || modelImporter.assetPath.Contains("Weapon")))
        {
            modelImporter.globalScale = 1.0f;
			modelImporter.generateMaterials = ModelImporterGenerateMaterials.PerSourceMaterial;
        }
    }

    void OnPostprocessModel(GameObject go)
    {
        if (go.name == "FBmap1" || go.name == "tortoise_01")
            return;
        ModelImporter modelImporter = (ModelImporter)assetImporter;
        if (UpgradeUnity3D352.PostprocessOn == true)
        {
            // 如果勾选了生成UV
            if (modelImporter.generateSecondaryUV == true)
            {
                if (UpgradeUnity3D352.SceneUVs == null || UpgradeUnity3D352.SceneUVs.Count == 0)
                    return;

                if (UpgradeUnity3D352.SceneUVs.ContainsKey(go.name) == false)
                    return;

                int index = 0;
                foreach (MeshFilter meshFilter in go.GetComponentsInChildren(typeof(MeshFilter)))
                {
                    Vector2[] testUVs = new Vector2[meshFilter.sharedMesh.vertices.Length];
                    if (index >= UpgradeUnity3D352.SceneUVs[go.name].Count)
                        break;
                    for (int i = 0; i < testUVs.Length; ++i)
                    {
                        if (i < UpgradeUnity3D352.SceneUVs[go.name][index].Count)
                        {
                            testUVs[i].x = UpgradeUnity3D352.SceneUVs[go.name][index][i].x;
                            testUVs[i].y = UpgradeUnity3D352.SceneUVs[go.name][index][i].y;
                        }
                    }
                    meshFilter.sharedMesh.uv2 = testUVs;
                    index++;
                }
            }
        }
    }
}

 上述方法经测试有效,项目组正在使用。

目录
相关文章
|
安全 图形学
|
6月前
|
编解码 vr&ar 图形学
unity2018 软件
怎么用unity2018及以下版本打开工程和修改分辨率 qq_42591144的博客 4874 首先打开unity客户端,点击右上方的Open 找到对应的工程目录,点击右下方的选择文件夹 然后列表中就会多出一
28 0
|
搜索推荐 图形学
CorelDRAW2023更新了哪些新功能?
还更新了CorelDRAW Graphics Suite 2023引入了一项新工具、超过200款设计模板,以及最新的潘通颜色等等。CorelDRAW 全称“CorelDRAW Graphics Suite“,也就是众所周知的”CDR“,是一款智能高效的平面设计软件,广泛应用于排版印刷、矢量图形编辑及网页设计等领域,
216 0
|
芯片 异构计算
会声会影2023增强功能及旧版本区别对比
2023年会声会影带来了六大新功能,其中包含了瞩目的标题与转场的更新,另外也进行了多项功能的增强,包括GIF创作器、开始结束标记的功能增强等。大家是不是已经按耐不住,想赶紧解锁新功能?事不宜迟,现在就让我们一起来看看会声会影2023对比2022的变化,包括功能对比。
502 0
|
存储 编解码 定位技术
CorelDRAW2023全新版本功能最新介绍
使用 CorelDRAW2023,随时随都能进行设计创作。在 Windows或Mac上使用专为此平台设计的直观界面,以自己的风格尽情自由创作。同全球数百万信赖CorelDRAW Graphics Suite 的艺术家、设计者及小型企业主一样,大胆展现真我,创作出众的创意作品。上半年又出了CorelDRAW 2023(25.1.1.328)中文全功能 64-Bit直装版,再次跟大家分享下。由于本次安装的 CorelDRAW 2023简体中文安装包非常精简,直接安装就行,老规矩:在纯净的 Windows 10/11 操作系统下,断网安装CorelDRAW 2023安装包即可!
566 0
|
XML JSON JavaScript
鸿蒙 DevEco Studio升级更新、新特性(新设备、布局预览)
鸿蒙 DevEco Studio升级更新、新特性(新设备、布局预览)
416 0
鸿蒙 DevEco Studio升级更新、新特性(新设备、布局预览)
|
存储 编解码 安全
使用 Unity 引擎打造免安装游戏
自从免安装游戏(Google Play Instant)于2018年3月首次发布以来,游戏开发者已经能够在创造精彩的体验之上,让玩家得以即刻沉浸在游戏中而不需要漫长的完整安装过程。玩家们也可以通过多种方式发现和访问免安装游戏,从Google Play中的&quot;立即体验&quot;按钮,到一条简单的网络链接,开发者们现在可以更轻松地吸引新玩家,并立即向他们展示自己的游戏。在这篇文章中,我们将向您展示如何使用Unity从头开始构建生产环境级别的免安装游戏,并会列举出免安装游戏为您带来的一些优势。
|
搜索推荐 Linux C#
Unity基础
Unity是什么,Unity是一个游戏开发引擎,他功能强大,学习简单,炉石传说,王者荣耀等游戏就是利用Unity引擎开发出来的
259 0
Unity基础
|
图形学
Unity 之 高版本预制体转低版本
高版本预制体(Prefab)转低版本,亲测版本Unity2019 --> Unity2017/Unity5.6
700 0
Unity 之 高版本预制体转低版本
|
图形学
Unity等待写入数据
EasyTouch中QuickGesture的用法 本文提供全流程,中文翻译。Chinar坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例) ...
940 0