模块化网站建设系统,网络宣传网站建设定制,凡科网官网登录入口,什么二手车网站做最好一、materials 与 sharedMaterials
1.1 使用上的区别与差异
Unity 开发时#xff0c;在 C# 中通过 Renderer 取材质操作是非常常见的操作#xff0c;Renderer 有两种常规获取材质的方式#xff1a; sharedMaterials#xff1a;可以理解这个就是原始材质#xff0c;所有使…
一、materials 与 sharedMaterials
1.1 使用上的区别与差异
Unity 开发时在 C# 中通过 Renderer 取材质操作是非常常见的操作Renderer 有两种常规获取材质的方式 sharedMaterials可以理解这个就是原始材质所有使用了同一个材质资源的模型 renderer sharedMaterial 相同修改了 sharedMaterials 相当于就是修改了资源 materialsmaterial 这个相当于 material Instance比如同一个箱子模型实例化两个 renderer sharedMaterial 相同这时候你想让其中一个箱子是红色的另一个箱子是绿色的这时候就可以使用 materialclone 不同的材质实例来做表现的差异化
当然除了 clone material instance 来做表现差异化Unity 更希望你用 MaterialPropertyBlock这个才是做材质表现差异化的正确路子
先不提 MaterialPropertyBlock我们拉回到 sharedMaterialmaterial有带 s 的和不带 s 的这个对应美术同学制作过程中的多维子材质的概念也就是一个完整几何体是可以有多个材质虽然渲染次数还是多次但完整的几何体天然的解决缝合问题并且如果相邻渲染几何体可以不用重复传入性能好一些所以不管 sharedMaterials 还是 materials都是数组的形式存储对于没有 s 的其实就是取数组的第一个元素因为大多数情况数组里还是仅仅有一个元素
综上所述我们脑海中的 Renderer 内部是什么样子的一定有个 sharedMaterial Array 和 material Array当 material Array 存在的时候就用其来渲染否则用 sharedMaterial Array 来渲染这样我使用了 material后边的逻辑想舍弃它继续用 sharedMaterial只需置空 material Array 即可真实的 Unity 是这样的规则吗直接上 Unity 源码 1.2 Unity 源码分析
[NativeHeader(Runtime/Graphics/Renderer.h)]
public partial class Renderer : Component
{[FreeFunction(Name RendererScripting::GetMaterial, HasExplicitThis true)] extern private Material GetMaterial();[FreeFunction(Name RendererScripting::GetSharedMaterial, HasExplicitThis true)] extern private Material GetSharedMaterial();[FreeFunction(Name RendererScripting::SetMaterial, HasExplicitThis true)] extern private void SetMaterial(Material m);[FreeFunction(Name RendererScripting::GetMaterialArray, HasExplicitThis true)] extern private Material[] GetMaterialArray();[FreeFunction(Name RendererScripting::GetMaterialArray, HasExplicitThis true)] extern private void CopyMaterialArray([Out] Material[] m);[FreeFunction(Name RendererScripting::GetSharedMaterialArray, HasExplicitThis true)] extern private void CopySharedMaterialArray([Out] Material[] m);[FreeFunction(Name RendererScripting::SetMaterialArray, HasExplicitThis true)] extern private void SetMaterialArray([NotNull] Material[] m);public Material[] materials{get{#if UNITY_EDITORif (IsPersistent()){Debug.LogError(Not allowed to access Renderer.materials on prefab object. Use Renderer.sharedMaterials instead, this);return null;}#endifreturn GetMaterialArray();}set { SetMaterialArray(value); }}public Material material{get{#if UNITY_EDITORif (IsPersistent()){Debug.LogError(Not allowed to access Renderer.material on prefab object. Use Renderer.sharedMaterial instead, this);return null;}#endifreturn GetMaterial();}set { SetMaterial(value); }}public Material sharedMaterial { get { return GetSharedMaterial(); } set { SetMaterial(value); } }public Material[] sharedMaterials { get { return GetSharedMaterialArray(); } set { SetMaterialArray(value); } }}
namespace RendererScripting
{Material* GetMaterial(Renderer* r);Material* GetSharedMaterial(Renderer* r);void SetMaterial(Renderer* r, Material* m);dynamic_arrayMaterial* GetMaterialArray(Renderer* r);void GetMaterialArray(Renderer* r, dynamic_arrayMaterial* mat);void GetSharedMaterialArray(Renderer* r, dynamic_arrayPPtrMaterial mat);void SetMaterialArray(Renderer* r, const dynamic_arrayMaterial* ma);} Material* RendererScripting::GetMaterial(Renderer* r)
{
#if UNITY_EDITORDebugAssert(!r-IsPersistent());
#endifreturn r-GetAndAssignInstantiatedMaterial(0, false);
}Material* RendererScripting::GetSharedMaterial(Renderer* r)
{return r-GetMaterialCount() ? r-GetMaterial(0) : 0;
}void RendererScripting::SetMaterial(Renderer* r, Material* m)
{r-SetMaterialCount(std::max(1, r-GetMaterialCount()));r-SetMaterial(m, 0);
}void RendererScripting::GetMaterialArray(Renderer* r, dynamic_arrayMaterial* mat)
{
#if UNITY_EDITORDebugAssert(!r-IsPersistent());
#endifDebugAssert(r-GetMaterialCount() mat.size());for (int i 0, in r-GetMaterialCount(); i in; i)mat[i] r-GetAndAssignInstantiatedMaterial(i, false);
}void RendererScripting::GetSharedMaterialArray(Renderer* r, dynamic_arrayPPtrMaterial mat)
{DebugAssert(r-GetMaterialCount() mat.size());for (int i 0, in r-GetMaterialCount(); i in; i)mat[i] r-GetMaterialArray()[i];
}dynamic_arrayMaterial* RendererScripting::GetMaterialArray(Renderer* r)
{dynamic_arrayMaterial* ret(r-GetMaterialCount(), kMemDynamicArray);RendererScripting::GetMaterialArray(r, ret);return ret;
}void RendererScripting::SetMaterialArray(Renderer* r, const dynamic_arrayMaterial* ma)
{r-SetMaterialCount(ma.size());for (int i 0, in ma.size(); i in; i)r-SetMaterial(ma[i], i);
}上述代码仅仅截取了 Renderer 中 sharedMaterial 和 material 的源代码调用部分其他部分暂时省去先从源代码的 get 部分分析可以发现 material 的 get 最终调用来自于 Renderer::GetAndAssignInstantiatedMaterial 函数 sharedMaterial 的 get 最终调用来自于 Renderer::GetMaterial 函数
virtual PPtrMaterial Renderer::GetMaterial(int i) const override
{ return m_Materials[i];
}
void Renderer::SetMaterial(PPtrMaterial material, int index)
{Assert(index (int)m_Materials.size());m_Materials[index] material;/*#if !DEPLOY_OPTIMIZEDMaterial* materialPtr material;if (materialPtr materialPtr-GetOwner ().GetInstanceID () ! 0 materialPtr-GetOwner() ! PPtrObject (this)){ErrorString(Assigning an instantiated material is not a good idea. Since the material is owned by another game object, it will be destroyed when the game object is destroyed.\nYou probably want to explicitly instantiate the material.);}#endif*/SetDirty();
}
Material* Renderer::GetAndAssignInstantiatedMaterial(int i, bool allowFromEditMode)
{// Grab shared materialMaterial* material NULL;if (GetMaterialCount() i)material GetMaterial(i);// instantiate material if necessaryMaterial* instantiated Material::GetInstantiatedMaterial(material, *this, allowFromEditMode);// Assign materialif (material ! instantiated){SetMaterialCount(std::max(GetMaterialCount(), i 1));SetMaterial(instantiated, i);}return instantiated;
}
从 Renderer 的源码分析内部仅仅有一个 material 数组而不是两个这个和我们上述脑海中浮现的数据结构已经不一样了很有意思倒是要看看他到底怎么做的因此到这继续深度分析抛开诸多假象来看实际的本质
class EXPORT_COREMODULE Renderer : public Unity::Component, public BaseRenderer
{ ...........typedef dynamic_arrayPPtrMaterial MaterialArray;MaterialArray m_Materials; /// List of materials to use when rendering.........
}
看上边 Renderer::GetAndAssignInstantiatedMaterial 的实现发现其主要调用了 Material::GetInstantiatedMaterial 来实现的材质克隆再粘一下代码
Material Material::GetInstantiatedMaterial(Material* material, Object renderer, bool allowInEditMode)
{if (material NULL)material GetDefaultMaterial();if (material-m_Owner PPtrObject(renderer))return *material;else{if (!allowInEditMode !IsWorldPlaying())ErrorStringObject(Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead., renderer);// Make sure the properties are initialized before were cloning, otherwise well end up using the properties of the default materialmaterial-EnsurePropertiesExist();Material* instance;instance CreateObjectFromCodeMaterial();instance-SetNameCpp(Append(material-GetName(), (Instance)));instance-m_Shader material-m_Shader;instance-m_Owner renderer;// Creating the material above already creates the shared material data, so release and create a new one using copy constructor.// Would be nice to avoid this extra work (but then, the default create material already does a bunch of extra other work// that we are discarding here; an optimization for some future day).SAFE_RELEASE(instance-m_SharedMaterialData);instance-m_SharedMaterialData UNITY_NEW(SharedMaterialData, kMemMaterial)(*material-m_SharedMaterialData);instance-m_SharedMaterialData-smallMaterialIndex instance-GetInstanceID();instance-CopySettingsFromOther(*material);instance-m_SavedProperties material-m_SavedProperties;return *instance;}
}
从当中可以看出
Material 的 m_Owner 是否指向是 Renderer决定此 Material 是否是 Renderer 的实例化材质InstantiatedMaterial如果传入的 material 是①所述的实例化材质直接返回否则就克隆一份 material 并都将归属 m_Owner 指向 renderer克隆出来的实例材质名字规则是在原有 material 的名字后边追加字符串(Instance)1.3 得出结论
Renderer 仅有一份 material 数组模型初始化后其内容就为 sharedMaterial以 mesh 类为例可以理解 MeshRenderer 创建后material 数组中的内容就是 prefab 上的材质资源列表执行 renderer.material无论左值还是右值都会触发 Renderer 的创建材质实例的函数此时如果 material 数组中的材质已经是本 renderer 创造的材质实例则直接返回否则创建返回它会覆盖 shareMaterial在步骤②之后如果再次调用 renderer.shareMaterial右值则直接返回当前 material 数组中的材质当然它已经不再是最早的那个 material 资产了也就是说此 renderer.shareMaterial 非比原 renderer.shareMaterial左值则会再次实例化创建新的 material 并赋值material 数组中的材质会再次被覆盖
因此不存在最早我们分析的sharedMaterial 和 material 是泾渭分明的两套数组存储他们最终 cache 在同一个数组里就是说你调用了 renderer.material 后renderer.sharedMaterial 也就变成了你最新克隆的 renderer.material如果你需要找回初始的 renderer.sharedMaterial就只能自己提前 cache 源码中 m_Owner 就是材质克隆归属的依据如果不满足克隆规则Unity 会重新克隆这样很多时候都会和我们预想的事与愿违 觉得绕可以直接看下面的例子
Renderer r go.GetComponentRenderer();
Material ma r.material;
r.sharedMaterial ms1;
Material mb r.material;
上述代码假设 r.sharedMaterial 为 ms。
执行完第2句r.material 和 r.sharedMaterial 皆为 ms(Instance)ms 在此处的引用已经丢失执行完第3句r.sharedMaterial 为 ms1执行完第4句r.material 和 r.sharedMaterial 皆为 ms1(Instance)ms1 在此处的引用已经丢失
所以替换 sharedMaterial 要谨慎如果你直接修改了它r.sharedMaterial可能就会动到资源文件但如果你在实例化了 material 之后再访问 renderer.shareMaterial难以避免的会再次实例化一个新的材质很明显这个时候就会出现资源的浪费一个 GO 实例化了两个 material因此尽量还是采用 MaterialPropertyBlock 来做材质个性化的事 二、MaterialPropertyBlock https://www.jianshu.com/p/eff18c57fa42 使用MaterialPropertyBlock来替换Material属性操作 - UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 接上文其实从应用层考虑的话其实我们只是想实现一个简单的需求那就是修改当前 GameObject(Renderer) 的材质属性
前面提到过如果你通过 renderer.material 修改材质属性那么其实底层相当于是给你实例化了一个新的 material并且这个 material 专属于当前的 GO其实这样也没有问题只要你能管理好这个实例化后的 material 也不是不行
当然还有一个更快更省的方法就是使用 MaterialPropertyBlock
//一个使用 MaterialPropertyBlock 及 Renderer.SetPropertyBlock 修改材质的例子
private void onFxCircleLoaded(GameObject obj, int fxId, object userData)
{if (!obj || userData null){return;}Vector4 vec (Vector4)userData;var mr obj.GetComponentInChildrenMeshRenderer();if (mr){var mpb MCommonObjectPoolMaterialPropertyBlock.Get();mr.GetPropertyBlock(mpb);mpb.SetVector(_Params, vec);mr.SetPropertyBlock(mpb);mpb.Clear();MCommonObjectPoolMaterialPropertyBlock.Release(mpb);}
}
网上很多文章都会将 MaterialPropertyBlock 和 GPU Instancing 绑定讲解但其实 MaterialPropertyBlock 本质上只是一种优化的手段其还可以被用于 Graphics.DrawMesh 和 Renderer.SetPropertyBlock 两个 API当我们想要绘制许多相同材质但不同属性的对象时都可以使用它无论是否 GPU Instancing
它和直接赋值 renderer.material 不同完全不会产生额外的材质实例使用 MaterialPropertyBlock 会直接覆盖某个渲染器上对应的属性开辟一片新的存储空间存储当前变量而并非在原先的 cbuffer 此 cbuffer 非比 DX 里的 constant buffer更准确的说法应该是指 Unity 材质属性区里面后面也不再从 cbuffer 中拿数据了也因此它会打破 SRP Batcher 2.1 使用 MaterialPropertyBlock 的注意事项
没有必要在 shader 属性前面声明 [PerRendererData] 前缀有教程将 [PerRendererData] 和 MaterialPropertyBlock 捆绑在了一起其实它们没有直接的逻辑关系[PerRendererData] 只影响 Editor 的显示行为即当你通过 MaterialPropertyBlock 改变了某个 Material 的属性之后只有加上了这个才能在预览对应的 Material 面板上看到对应属性值的变更很明显这没有太大的意义MaterialPropertyBlock 会使得 SRP Batcher 不生效这个上面刚提到过毕竟这两种方法本质思路都是开辟一段新的内存用于数据的读取很明显在数据唯一的这一铁定条件下它不可能存在于两块空间中因此这两套方案可以说是平行/不相容