Unity将场景中所有的模型对应的材质存到本地

本文介绍如何在Unity中利用ScriptableObject来存储和管理材质数据。通过创建自定义类和ScriptableObject,可以有效地在项目中保存和读取材质数组。文章提供了详细的代码示例,包括如何生成和使用这些自定义资产。

利用Unity ScriptableObject储存,先生成一个asset文件

代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//为什么创建一个类?
//因为List<xx[]>不能序列化,创建一个类管理,类加上[System.Serializable]标签
[System.Serializable]
public class MyMaterial
{
    public Material[] materials;

    public MyMaterial(Material[] materials)
    {
        this.materials = materials;
    }
}

[CreateAssetMenu(menuName = "CreatEditOrigialMats")]
public class EditOriginalMaterial : ScriptableObject
{
    public static EditOriginalMaterial Get()
    {
        return Resources.Load<EditOriginalMaterial>("EditOriginalMaterial");
    }

    public List<string> nameArr = new List<string>();
    public List<MyMaterial> materials = new List<MyMaterial>();
}

使用方法:在Poject面板右键Creat->CreatEditOrigialMats,新生成的文件放到Resources文件夹下,名字为EditOriginalMaterial

测试代码:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using UnityEditor;
using UnityEngine;

public class Use : MonoBehaviour
{
    public static EditOriginalMaterial EditOriginalMaterialInfo;

    private void Awake()
    {
        EditOriginalMaterialInfo = EditOriginalMaterial.Get();
    }
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.R))
        {
            foreach (var item in EditOriginalMaterialInfo.materials)
            {
                Debug.Log(item);
            }
        }
    }
    [MenuItem("test/on")]
    public static void On()
    {
        EditOriginalMaterialInfo = EditOriginalMaterial.Get();
        EditOriginalMaterialInfo.nameArr.Clear();
        EditOriginalMaterialInfo.materials.Clear();

        Renderer[] renderers = FindObjectsOfType<Renderer>();
        foreach (var item in renderers)
        {
            EditOriginalMaterialInfo.nameArr.Add(item.gameObject.name);
            EditOriginalMaterialInfo.materials.Add(new MyMaterial(item.sharedMaterials));
            Debug.Log(item.material.name);
        }
    }
}
<think>我们面对的问题是如何在Unity中加载AssetBundle(AB包)中的模型后,将模型材质替换为本地对应材质。根据引用内容,我们知道如果不加载材质的AB包,可能会导致材质丢失(显示紫色)。但是,用户的需求是替换为本地材质,这意味着我们可能不希望使用AB包中的材质,而是使用项目中原有的材质。 解决方案思路: 1. 加载模型AB包,实例化模型。 2. 在实例化后,获取模型上的材质引用。 3. 根据某种规则(比如材质名称)在本地资源中查找对应材质。 4. 将模型上的材质替换为找到的本地材质。 注意:本地材质指的是项目Resources文件夹中的材质,或者通过Addressables加载的材质,或者直接放在项目Assets目录下可以通过Resources.Load加载的材质。这里我们假设用户已经把材质放在Resources文件夹中,或者知道材质的路径。 具体步骤: 步骤1:加载模型AB包实例化模型 步骤2:在场景中找到模型上的所有材质 步骤3:遍历每个材质,根据材质名称(或其他标识)加载本地材质资源 步骤4:替换材质 代码示例: 假设我们的本地材质都放在Resources/Materials文件夹下,材质名称和AB包中的材质名称一致。 ```csharp using UnityEngine; using System.Collections.Generic; public class LoadAssetBundleAndReplaceMaterial : MonoBehaviour { void Start() { // 加载模型的AB包 AssetBundle modelBundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/modelbundle"); if (modelBundle == null) { Debug.LogError("Failed to load model asset bundle"); return; } // 实例化模型(假设模型预制体的名称是"MyModel") GameObject modelPrefab = modelBundle.LoadAsset<GameObject>("MyModel"); GameObject modelInstance = Instantiate(modelPrefab); // 替换材质 ReplaceMaterials(modelInstance); // 卸载AB包(注意:只卸载AB包,不卸载已经实例化的资源) modelBundle.Unload(false); } void ReplaceMaterials(GameObject model) { // 获取模型上所有Renderer组件 Renderer[] renderers = model.GetComponentsInChildren<Renderer>(); foreach (Renderer renderer in renderers) { // 获取每个Renderer上的材质数组 Material[] materials = renderer.sharedMaterials; for (int i = 0; i < materials.Length; i++) { // 获取当前材质 Material mat = materials[i]; // 根据材质名称加载本地材质 Material localMat = LoadLocalMaterial(mat.name); if (localMat != null) { materials[i] = localMat; } else { Debug.LogWarning("Failed to load local material: " + mat.name); } } // 将修改后的材质数组设置回Renderer renderer.sharedMaterials = materials; } } Material LoadLocalMaterial(string matName) { // 从Resources文件夹加载材质 // 注意:材质在Resources文件夹中的路径,假设材质放在Resources/Materials目录下,则路径为"Materials/材质名" // 这里我们假设材质名称就是文件名,且不需要后缀 Material mat = Resources.Load<Material>("Materials/" + matName); return mat; } } ``` 注意事项: 1. 材质名称必须唯一,否则可能会加载错误的材质。 2. 如果材质不在Resources文件夹,我们可以使用其他方式加载,比如AssetDatabase(仅编辑器)或Addressables(推荐,支持热更)。 3. 如果模型中使用了多个子材质,确保每个材质都能正确找到对应本地材质。 4. 如果本地材质没有和AB包中的材质名称一致,我们需要建立映射关系(比如通过一个映射表)。 另外,引用[1]中提到,如果不加载材质的AB包,模型可能会因为没有材质而显示紫色。但是,我们现在主动替换材质,所以不需要加载材质AB包。注意,如果模型预制体上直接引用了材质,那么即使我们不加载材质AB包,Unity也会尝试去加载材质(如果材质在同一个AB包中,那么材质也会被打包进去)。为了避免加载材质AB包,我们应该将模型材质引用设置为null,或者将材质单独打包,然后不加载材质包。但是,我们这里直接替换材质,所以不需要加载材质包,但是要注意模型实例化时可能会临时使用AB包中的材质(然后被替换掉)。 如果使用Addressables系统,则加载材质的方式可以改为: ```csharp using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; ... Material LoadLocalMaterial(string matName) { // 使用Addressables加载材质 AsyncOperationHandle<Material> handle = Addressables.LoadAssetAsync<Material>(matName); handle.WaitForCompletion(); // 同步等待,实际中建议使用异步 if (handle.Status == AsyncOperationStatus.Succeeded) { return handle.Result; } return null; } ``` 但是,使用Addressables需要提前将材质标记为Addressable,设置好key。 总结:替换材质的关键在于通过材质名称(或唯一标识)从本地加载材质资源,然后替换模型上的材质。 §§相关问题§§ 1. 如何避免替换材质时造成的内存泄漏? 2. 使用Addressables替换材质有哪些优势? 3. 如何管理AB包材质本地材质的映射关系? 4. 替换材质后如何恢复原始材质? 5. 材质替换对GPU Instancing有什么影响? </think>### Unity中加载AB包模型后替换为本地材质的实现方法 在Unity中加载AssetBundle模型后替换为本地材质的关键在于**材质引用替换**和**资源依赖管理**。以下是系统解决方案: #### 核心实现原理 1. **材质引用机制** Unity预制体存储的是材质的引用路径($ \text{GUID} \rightarrow \text{Path} $) 2. **运行时替换** 在实例化模型后动态替换材质引用 3. **依赖解耦** 分离模型网格数据和材质资源依赖[^1] #### 分步实现方法 ##### 步骤1:准备本地材质资源 ```csharp // 将本地材质放入Resources文件夹 // Assets/Resources/Materials/LocalMaterial.mat ``` ##### 步骤2:加载模型AB包替换材质 ```csharp IEnumerator LoadModelWithLocalMaterial() { // 加载模型AB包 AssetBundle modelBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "model_bundle")); GameObject modelPrefab = modelBundle.LoadAsset<GameObject>("ModelPrefab"); // 实例化模型 GameObject modelInstance = Instantiate(modelPrefab); // 加载本地材质 Material localMat = Resources.Load<Material>("Materials/LocalMaterial"); // 替换所有Renderer的材质 Renderer[] renderers = modelInstance.GetComponentsInChildren<Renderer>(); foreach (Renderer rend in renderers) { Material[] newMats = new Material[rend.sharedMaterials.Length]; for (int i = 0; i < newMats.Length; i++) { newMats[i] = localMat; // 替换为本地材质 } rend.sharedMaterials = newMats; } modelBundle.Unload(false); } ``` #### 替代方案:材质重映射(无需代码修改) 1. **同名材质覆盖** 在项目中创建同名材质(与AB包中原材质名相同),放在`Resources`文件夹下 ```plaintext Assets/ └── Resources/ └── Materials/ └── OriginalMat.mat // 与AB包中材质同名 ``` 2. **Unity自动引用机制** 实例化时会自动使用项目中的材质而非AB包内材质 #### 注意事项 1. **材质属性兼容性** $$ \text{Shader}_{AB} \equiv \text{Shader}_{Local} $$ 确保本地材质使用相同Shader,否则需动态赋值: ```csharp localMat.CopyPropertiesFromMaterial(originalMat); ``` 2. **资源卸载策略** ```csharp // 正确卸载避免内存泄漏 Resources.UnloadAsset(localMat); Resources.UnloadUnusedAssets(); ``` 3. **性能优化** ```csharp // 使用材质属性块避免材质实例化 MaterialPropertyBlock props = new MaterialPropertyBlock(); rend.GetPropertyBlock(props); props.SetColor("_Color", Color.red); rend.SetPropertyBlock(props); ``` #### 调试技巧 1. **材质引用检测** ```csharp Debug.Log($"Material source: {rend.sharedMaterial?.ToString()}"); ``` 2. **依赖分析工具** 使用`AssetStudio`[^4]检查AB包内容,确认材质名称和路径 > ✅ **最佳实践** > 推荐使用材质重映射方案,减少运行时计算量简化工作流程
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值