Unity脚本更改材质文件遇到的坑

本文记录了在Unity中批量修改材质参数时遇到的问题,包括如何查找同shader材质,处理材质属性和shader宏命令,以及Unity的材质文件更新规则。在处理宏命令时要注意Toggle属性对宏定义的影响,而在更新材质文件时,Unity仅在首次修改并保存时才会添加新属性。

这两天接到美术同事的一个需求,想要批量修改材质的参数。

本来以为是很简单的需求,但是碰上了点坑,这里纪录一下。

 

首先需要查找同shader材质,这不是难题。Unity的文件引用关系都存在meta文件中,可以自行根据meta文件查找。

当然全局查找会特别慢,这里使用了项目中用到的一个插件ReferenceFinder 的引用缓存,把缓存暴露出一个可以调用的接口。

https://www.cnblogs.com/blueberryzzz/p/10674581.html

 

之后要记得要分别处理普通的材质属性和shader宏命令。

这里也还简单,Unity提供了修改两种值的接口,分别是material.SetXXX()和material.EnableKeyword()material.DisableKeyword()

这里有5点需要注意:

1.如果输入只有需要批量修改的参数名,你需要自己查找对应的参数类型以及它是否是宏命令。因此需要你自己检查输入属性名是否是宏命令。

材质的参数组可以通过material.shader.GetPropertyXXX()系列获取。遍历material.shader.GetPropertyCount(),可以获取到每个参数的名称,描述,类型信息等等。临时存储后可以在之后调用对应类型的material.SetXXX()修改。

 

2.如果Shader对宏命令使用了[Toggle(XXX)]这样的属性,material.shader.GetPropertyXXX()也会获取到同名的属性。也就是说宏命令既需要调用EnableKeyword()也需要调用material.SetFloat()让面板对应的切换按钮更改。

在编辑器面板debug模式下可以看到各自对应的值。

 

3.shader宏定义参数部分如果用的是[Toggle]的写法,实际上的宏是XXXXX_ON,同样如果要使用这个宏也应该是#ifdef XXXXX_ON。如果使用[Toggle(XXXXXX)]实际上的宏才是XXXXX。使用这个宏应该是#ifdef XXXXX。

这部分和是否使用双下滑线没有关系,只和定义Toggle属性的写法有关。

4.注意EnableKeyword()和DisableKeyword()不会检查是否真的存在这个宏命令,只是简单的把字符串写进ShaderKeyWords这一栏中。也就是说EnableKeyword("随便填的")也真的会把“随便填的”加进材质的Keywords

5.不能通过material.shaderKeywords()获取所有可能的宏命令,这条只能获取到上图shaderKeywords一栏的值。不会把Shader中定义的所有宏命令列出来。

 

 

有关宏命令和材质属性的部分终于有惊无险的处理完了!但是接下来才是重头戏——Unity的材质文件更新规则。

众所周知,Unity shader提供方便的Properties给对应的材质生成属性面板,而材质的属性具体参数存储在对应的材质文件中,使用Text方式存储的Unity文件可以简单的看到对应值。

但是如果在shader中增加和减少属性,Unity是不会立刻更新所有引用这个shader的材质文件内容的!这也好理解,毕竟如果改一次shader几百个材质文件都要修改一遍,那来回切换也太慢了。

那具体的更改时机是什么时候呢?答案是你第一次修改这个材质的时候。

没错,只有当你选中这个shader属性被修改过的材质,并作出任何修改的时候,按下ctrl+s,Unity才会更新材质文件。

这里还要分为两种情况,分别是面板的情况和文件的情况。

当你触发Unity的刷新的时候,面板会根据shader的属性变化而变化。

但是如果你不做任何修改,只是单纯的按下ctrl+s,不会把面板上的属性默认值写入材质文件!只有你做出任何修改操作,按下ctrl+s,unity才会触发文件写入操作!

 

复现流程

我们在该条属性被注释的情况下创建材质。

注意看不管是材质文件还是Debug面板都不存在这条宏。

我们将这一条注释取消。

返回Unity,此时不管刷新还是保存都不会改变材质文件。

选中材质发现已经有对应属性,但是这时候按下ctrl+s还是不会有任何东西保存。

没用的!不做修改就不会保存!

修改该材质任意一个属性之后按下ctrl+s才能将这条新属性保存到文件!

之后即使注释掉这个属性,虽然面板上这条属性消失了,但是debug模式下还是看得到,材质文件中也仍然保存这一条属性和参数值。

 

 

了解上述事实后,我们可以总结下Unity对材质文件的更新流程了!

首先要知道,Unity的转菊花对应函数AssetDatabase.Refresh()的调用。选中资源对应Selection.activeObject的Set。ctrl+s对应AssetDatabase.SaveAssets()的调用。

材质的面板显示内容存在于内存之中,只要调用AssetDatabase.Refresh(),就可以显示shader文件的修改。一般情况下auto Refresh都是默认打开状态,此时切换窗口回到Unity就会触发AssetDatabase.Refresh()。

也就是说调用AssetDatabase.Refresh()可以刷新面板的显示。

当手动对材质做出任何修改后,调用AssetDatabase.SaveAssets()才可以把新的属性值保存到材质文件中。

 

于是我在做这个需求的时候就遇上了这么一个坑——我调用自己写的函数,成功修改了所有材质并且确认修改已经被保存到文件。

但是当美术点击材质时,触发了Unity的材质更新机制,它发现这个材质和shader的属性不符合,就覆盖了我的修改,从面板上看,就变成了没有修改过的样子。仿佛修改失败。

这个bug只有在第一次修改时才会出现,因为这时候材质缺少shader对应的属性,只要被Unity的材质更新机制更新过一次,之后的修改就是正常的,因为这时候新的材质属性已经被更新,不会再次触发Unity的材质更新机制覆盖我的修改了。

(这点非常坑!导致我一直没有发现这个bug,因为我的本地材质文件都已经有这条属性)

 

最后的解决办法也很简单粗暴,就是想办法先触发Unity的材质更新机制。

但是Unity并没有提供让材质根据shader更新的接口。于是只能在修改每一个材质之前按顺序调用这些函数。

    void ForceRefreshMaterial()
    {
        Selection.activeObject = material;
        AssetDatabase.Refresh();
        AssetDatabase.SaveAssets();
    }

(这点最坑!还不能一次性全修改最后保存!- = 一定要修改一个材质调用一次,搞得改完之后慢了无数倍!)

 

如果有更好的办法欢迎评论区分享,这个bug搞得我颜面扫地,好恨啊啊啊啊~~

评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值