当前位置: 首页 > news >正文

Renderer 使用材质分析:materials、sharedMaterials 及 MaterialPropertyBlock

一、materials 与 sharedMaterials

1.1 使用上的区别与差异

Unity 开发时,在 C# 中通过 Renderer 取材质操作是非常常见的操作,Renderer 有两种常规获取材质的方式:

  1. sharedMaterials:可以理解这个就是原始材质,所有使用了同一个材质资源的模型 renderer, sharedMaterial 相同,修改了 sharedMaterials 相当于就是修改了资源

  2. materials:material 这个相当于 material Instance,比如同一个箱子模型实例化两个 renderer, sharedMaterial 相同,这时候你想让其中一个箱子是红色的,另一个箱子是绿色的,这时候就可以使用 material,clone 不同的材质实例来做表现的差异化

当然,除了 clone material instance 来做表现差异化,Unity 更希望你用 MaterialPropertyBlock,这个才是做材质表现差异化的正确路子

先不提 MaterialPropertyBlock,我们拉回到 sharedMaterial,material,有带 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_array<Material*> GetMaterialArray(Renderer* r);void GetMaterialArray(Renderer* r, dynamic_array<Material*>& mat);void GetSharedMaterialArray(Renderer* r, dynamic_array<PPtr<Material> >& mat);void SetMaterialArray(Renderer* r, const dynamic_array<Material*>& 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_array<Material*>& 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_array<PPtr<Material> >& mat)
{DebugAssert(r->GetMaterialCount() <= mat.size());for (int i = 0, in = r->GetMaterialCount(); i < in; ++i)mat[i] = r->GetMaterialArray()[i];
}dynamic_array<Material*> RendererScripting::GetMaterialArray(Renderer* r)
{dynamic_array<Material*> ret(r->GetMaterialCount(), kMemDynamicArray);RendererScripting::GetMaterialArray(r, ret);return ret;
}void RendererScripting::SetMaterialArray(Renderer* r, const dynamic_array<Material*>& 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 PPtr<Material> Renderer::GetMaterial(int i) const override 
{ return m_Materials[i]; 
}
void Renderer::SetMaterial(PPtr<Material> 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() != PPtr<Object> (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_array<PPtr<Material> > 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 == PPtr<Object>(&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 we're cloning, otherwise we'll end up using the properties of the default materialmaterial->EnsurePropertiesExist();Material* instance;instance = CreateObjectFromCode<Material>();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;}
}

从当中可以看出:

  1. Material 的 m_Owner 是否指向是 Renderer,决定此 Material 是否是 Renderer 的实例化材质(InstantiatedMaterial)
  2. 如果传入的 material 是①所述的实例化材质直接返回,否则就克隆一份 material 并都将归属 m_Owner 指向 renderer
  3. 克隆出来的实例材质名字规则是在原有 material 的名字后边追加字符串(Instance)

1.3 得出结论

  1. Renderer 仅有一份 material 数组,模型初始化后,其内容就为 sharedMaterial(以 mesh 类为例,可以理解 MeshRenderer 创建后,material 数组中的内容就是 prefab 上的材质资源列表
  2. 执行 renderer.material,无论左值还是右值都会触发 Renderer 的创建材质实例的函数,此时如果 material 数组中的材质已经是本 renderer 创造的材质实例,则直接返回,否则创建返回,它会覆盖 shareMaterial
  3. 在步骤②之后,如果再次调用 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.GetComponent<Renderer>();
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.GetComponentInChildren<MeshRenderer>();if (mr){var mpb = MCommonObjectPool<MaterialPropertyBlock>.Get();mr.GetPropertyBlock(mpb);mpb.SetVector("_Params", vec);mr.SetPropertyBlock(mpb);mpb.Clear();MCommonObjectPool<MaterialPropertyBlock>.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 的注意事项

  1. 没有必要在 shader 属性前面声明 [PerRendererData] 前缀:有教程将 [PerRendererData] 和 MaterialPropertyBlock 捆绑在了一起,其实它们没有直接的逻辑关系,[PerRendererData] 只影响 Editor 的显示行为,即当你通过 MaterialPropertyBlock 改变了某个 Material 的属性之后,只有加上了这个,才能在预览对应的 Material 面板上看到对应属性值的变更,很明显,这没有太大的意义
  2. MaterialPropertyBlock 会使得 SRP Batcher 不生效,这个上面刚提到过,毕竟这两种方法本质思路都是开辟一段新的内存用于数据的读取,很明显,在数据唯一的这一铁定条件下,它不可能存在于两块空间中,因此这两套方案可以说是平行/不相容

相关文章:

Renderer 使用材质分析:materials、sharedMaterials 及 MaterialPropertyBlock

一、materials 与 sharedMaterials 1.1 使用上的区别与差异 Unity 开发时&#xff0c;在 C# 中通过 Renderer 取材质操作是非常常见的操作&#xff0c;Renderer 有两种常规获取材质的方式&#xff1a; sharedMaterials&#xff1a;可以理解这个就是原始材质&#xff0c;所有使…...

java学习----网络编程

网络编程入门 网络编程概述 计算机网络 ​ 计算机网络是指地理位置不同的具有独立功能的计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件及网络通信协议的管理协调下&#xff0c;实现资源共享和信息传递的计算机系统…...

这些「误区」99%的研发都踩过

意识不到误区的存在最为离谱&#xff1b; 01生活中&#xff0c;职场上&#xff0c;游戏里&#xff0c;都少不了正面对喷过&#xff1a;意识太差&#xff1b; 在个人的认知中意识即思维&#xff0c;意识太差即思维中存在的误区比较多&#xff1b; 每个人或多或少都存在思维上的…...

Bi系统跟数据中台的区别是什么?

随着数据时代的发展&#xff0c;BI分析是当今数据时代必不可少的能力之一。BI系统通过系统化产品化的方法&#xff0c;能够大幅降低数据的获取成本、提升数据使用效率。同时借助可视化、交互式的操作&#xff0c;可以高效支持业务的分析及发展。 BI如此火热&#xff0c;随之而…...

微信小程序反编译方法分享

文章目录一、前言二、准备工作&#xff08;一&#xff09;安装Nodejs&#xff08;二&#xff09;解密和逆向工具三、小程序缓存文件解密&#xff08;一&#xff09;定位小程序缓存路径&#xff08;二&#xff09;源码解密&#xff08;三&#xff09;源码反编译四、小结一、前言…...

有了这些接口测试用例+工具,测试效率想不提升都难

写在前面&#xff1a;在日常开发过程中&#xff0c;有人做前端开发&#xff0c;有人负责后端开发。接口的主要作用就是连接前后台。但是&#xff0c;由于前端和后端开发的速度可能不一样&#xff0c;尤其是后端开发好了&#xff0c;但前端还未开发。这种时候我们需要做接口测试…...

麒麟 arm架构安装nginx

目录 1、下载nginx安装包并解压 在线安装&#xff1a; 离线安装&#xff1a; 上传nginx安装包&#xff08;下载地址&#xff1a;https://nginx.org/download/nginx-1.20.2.tar.gz&#xff09;到指定目录 2、安装系统相关依赖软件、组件包 1、上传或者下载对应的组件包 2、安…...

logrotate失效的排查---selinux开启状态拦截问题及解决方法

首先测试环境selinux 处于关闭状态 disable # getenforce disable重新开启selinux配置与生产环境一致 [rootlocal]# cat /etc/selinux/config # This file controls the state of SELinux on the system. # SELINUX can take one of these three values: # enforcing - S…...

Allegro使用总结-查看Layout基本操作:

好久没用CSDN写过笔记了&#xff0c;没想到无意间打开&#xff0c;编辑器更新啦&#xff01;以前巨难用的“富文本编辑器”终于改观了&#x1f62d;变的好像语雀&#xff0c;うれしい1. 视图/画面操作a. 画面缩放&#xff08;Zoom&#xff09;&#xff1a;F11/F12 或 鼠标滚轮b…...

cmd del命令笔记

使用 /s 删除文件夹下所有的 del /s sub # 删除目录下所有文件&#xff0c;这个目录不会删除 /p 确认提示 /q 静默模式&#xff0c;不会提示要不要删除 如过和/p同时使用&#xff0c;那么不提示 /a 根据属性删除&#xff0c;a是attribute的意思 del /a:r 01.jpg # 01.jp…...

apifox持续集成+java+企微机器人+xxljob定时推送

总览&#xff1a; apifox做接口测试后&#xff0c;把用例合并组装成测试套件&#xff0c;然后apifox-cli通过终端命令实现把套件执行后&#xff0c;输出本地文件的测试报告html或json。本地解析后拿到有用的解决通过定时执行推送到企微群里。 然后把html一起推到群里。 这个…...

盘点Linux内核网络知识100道题,这篇就够了

计算机网络模型 1、五层因特网协议栈和七层OSI&#xff08;Open System Interconnections&#xff09;参考模型分别是什么&#xff1f; 5层&#xff1a;应用层、传输层、网络层、数据链路层、物理层 7层&#xff1a;应用层、表示层、会话层、传输层、网络层、数据链路层、物理…...

数据库敏感字段脱敏

文章目录什么是脱敏脱敏后带来什么问题解决方案一解决方案二具体实施方案一方案二存量数据处理什么是脱敏 如果你有申请过一些软件资质&#xff0c;应该会被要求敏感数据进行加密&#xff0c;比如密码不能明文&#xff0c;用户的手机号&#xff0c;身份证信息&#xff0c;银行…...

skynet 游戏服务器探索(1)--熟悉skynet(网络)

因为做游戏服务器开发&#xff0c;大多数都跟脚本打交道&#xff0c;要么是lua,要么是python,要么是php,方便热更新的只有lua与php, php相关的游戏服务器开发&#xff0c;参考我另外的文章https://blog.csdn.net/guoyilongedu/article/details/121049511lua脚本相关的&#xff…...

select、poll、epoll

select、poll、epoll select int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); int nfds&#xff1a;被select管理的文件描述符的个数&#xff0c;最大描述符编号1fd_set *readfds&#xff1a;读文件描述符集合fd_se…...

rollup的基本使用 基本配置与处理各种文件

rollup rollup是一个javascript的模块化打包工具 可以帮助我们编译小的代码到一个大的负载的代码中 比如一个库或者一个应用 rollup与webpack的区别 rollup主要针对ES Module进行打包 另外webpack通常可以通过各种loader处理各种各样的文件 以及处理他们的依赖关系 rollup更多…...

ubuntu-debian系-redhat系

debian系 包类型&#xff1a;.deb包 本地安装包安装工具&#xff1a;dpkg 本地包管理命令&#xff1a;dpkg -i package 安装包 dpkg -r package 卸载包 dpkg -l package 查看已安装包 远程安装包安装工具&#xff1a;apt / apt-get 远程包管理命令&#xff1a;apt-get apt-cac…...

Altium Designer 18中原理图DRC编译和PCB DRC检查-AD DRC

一、原理图编译 原理图检查的主要内容有&#xff1a; 1、元件位号冲突。也即多个元件编号相同&#xff0c;例如两个电容在原理图中都被命名为C2&#xff0c;显然肯定是无法生成PCB的。 2、网络悬浮。也即网络标号没有附着在电气走线上&#xff0c;一般这种是人操作失误&…...

zipfile — 访问 ZIP 压缩文件

zipfile — 访问 ZIP 压缩文件 1.概述 zipfile 模块可用于操作 ZIP 存档文件&#xff0c;.zip 是 PC 程序 PKZIP 推广的格式 2.测试数据 为了演示用&#xff0c;你需要创建以下三个文件 1.README.txt 内容如下&#xff0c;注意最后一行为空行 The examples for the zipfil…...

检查nmos管是否损坏

NCEP85T14 功率mos管为例 以NMOS举例&#xff0c;只用万用表二极管档测量MOS管的好坏-电子发烧友网 NMOS的D极和S极之间有一个寄生二极管&#xff0c;方向为S到D&#xff0c;利用二极管单向导电性以及MOS管导通时寄生二极管截止的特性&#xff0c;可以快速测量MOS好坏。 1、测…...

第七章 - 聚合函数(count,avg,sum,max,min)和一些数学函数

第七章 - 聚合函数使用别名 ascount() 计数avg() 平均值sum() 求和max() 最大值min() 最小值一些数学计算函数Abs()Cos()Exp()Mod()Pi()radians()Sin()Sqrt()Power()Ceil()Floor()使用别名 as 在SQL中可以使用 as 来为一个字段或者一个值设置新的别名下面聚合函数的使用中就会…...

Typescript的原始据类型和Any类型

最新的ECMAScript标准定义了8中数据类型: 7种原始类型&#xff1a; BooleanNullUndefinedNumberBigintStringSymbol和 Object 除 Object 以外的所有类型都是不可变的 (值本身无法被改变》。例如&#xff0c;与C语言不同JavaScript 中字符串是不可变的 (译注: 如&#xff0c;Ja…...

[python入门㊼] - python类的高级函数

目录 ❤ 类的高级函数 ❤ __str__ ❤ __getattr__ ❤ __setattr__ ❤ __call__ ❤ 类的高级函数 今天来为大家介绍几个类中的高级函数&#xff0c;它们也是类中的内置函数。通过使用它们&#xff0c; 会让我们在进行类开发的时候更加的顺手&#xff0c;接下来我们就…...

【Windows】使用Fiddler 工具对手机进行接口监听

目录 工具下载 配置Fidder 手机端获取证书 过滤指定接口 工具下载 CSDN下载地址 其他下载地址 配置Fidder 安装后&#xff0c;打开进入如下界面 在fiddler菜单项选择Tools -> Options -> HTTPS 勾选【Decrypt HTTPS traffic 】 下拉框默认&#xff1a;【from al…...

SpringCloudAlibab-nacos

一、介绍注册中心配置中心的整合SpringCloudAlibaba中文地址&#xff1a;https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md下载地址&#xff1a;https://github.com/alibaba/nacos/访问&#xff1a;http://localhost:8848/nacos/二、使用1、添加依赖&…...

从一致性角度考虑推荐冷启动长尾推荐问题(二)

前言&#xff1a;在推荐系统中user&item emb往往是最重要的特征之一&#xff0c;在冷启动和长尾优化的工作中&#xff0c;往往也是优化的重点&#xff0c;相当一部分工作是围绕着emb优化展开&#xff0c;所以这里单独开了一章。4)emb分布一致性主要思路在于冷启内容emb和高…...

电脑c盘满了怎么清理,c盘空间清理

电脑c盘满了怎么清理&#xff1f;电脑C盘满了可能是因为您的操作系统、程序文件、下载文件、临时文件、垃圾文件等占用了太多的存储空间。所以&#xff0c;我们就需要进行一些操作和清理。 一.清理电脑C盘的方法 清理临时文件和垃圾文件。在Windows上&#xff0c;您可以使用系…...

vite的基本使用

vite 浏览器原生支持模块化 浏览器原生加载的缺点 1.必须明确的写上后缀名 2.如果某一个模块 加载跟多其他的js文件 那么这些js文件都需要被依次加载 浏览器需要将所有的js文件请求下来 发送跟多的http请求&#xff08;效率也是非常低的&#xff09; 3.如果代码中由typescrip…...

JavaScript 字符串(String) 对象

JavaScript 是一种流行的编程语言&#xff0c;可以用于开发各种 Web 应用程序和移动应用程序。在 JavaScript 中&#xff0c;字符串是一种非常常见的数据类型&#xff0c;可以使用 JavaScript 字符串&#xff08;String&#xff09;对象来处理。本文将详细介绍 JavaScript 字符…...

小知识点:Mac M1/M2 VMware Fusion 安装 Centos 7.9(ARM 64 版本)

最近换了 Mac M2 芯片的笔记本&#xff0c;用原来的 Centos 镜像安装虚拟机直接报错 “无法打开此虚拟机的电源&#xff0c;因为它需要使用 X86 计算机架构&#xff0c;而该架构与此 Arm 计算机架构主机不兼容。” 安装流程前置一、下载镜像二、安装虚拟机三、配置静态 IP四、安…...

app软件开发公司找用友yonmaker/平台优化是指什么

求递归算法时间复杂度&#xff1a;递归树1.参考资料2.内容1.参考资料 参考网页&#xff1a;求递归算法时间复杂度&#xff1a;递归树 2.内容 递归算法时间复杂度的计算方程式是一个递归方程: T(n){O(1)n1kT(n/m)f(n)n>1T(n) \left\{ \begin{array}{ll} O(1) & n1 \\…...

安远做网站/线下推广方案

本人&#xff0c;是属于那种心思比较重的人&#xff0c;当然不是那种坏心眼。就是比如本次工作哪里有问题&#xff0c;领导指出来&#xff0c;或是有些事情做的不是很到位&#xff0c;出现失误。在下次做这些事情的时候&#xff0c;我就会特别的紧张&#xff0c;甚至比较害怕会…...

网站后台登陆模板/网站广告制作

点击蓝字“角落的白板报”关注我哟加个“星标★”&#xff0c;好文必达&#xff01;推荐目录&#xff1a;https://t.cn/A6ApnczUWindows 的 Docker-desktop 是为在 Windows 10 上运行而设计的 Docker。适用于 Windows 的 Docker 桌面使用 Windows 原生的 Hyper-V 虚拟化和网络连…...

移动免费网站建设/今日国际新闻头条

王阳明&#xff1a;人生即修行 笔记 少说多听&#xff0c;必有收获 人的心灵成长地图要求我们&#xff0c;必须要随时更正自己的前进方向&#xff0c;不能更正&#xff0c;或是拒绝更正心灵成长路线图的人只能困在原地&#xff0c;永远绕弯。 心阳光&#xff0c;世界不会黑暗…...

wordpress 顶栏显示/百度注册页面

问题 问题1&#xff1a; 最近有不少用户反馈使用 php runtime的时候遇见如下报错Cannot modify header information - headers already sent by (output started at ... 问题2&#xff1a; 如果更改php 的session 目录&#xff1f;本文旨在梳理此类问题的原因&#xff0c;触发条…...

做网站的图片字虚/互联网平台推广怎么做

以下是我所知道的两种最简单的筑墙方法。这两种方法都适用于图结构和图搜索算法&#xff0c;因此如果您愿意&#xff0c;可以在将来实现“路径查找”。这都是我的头顶&#xff0c;所以我很抱歉&#xff0c;如果有任何不清楚&#xff0c;但我也提供了相关文件的链接&#xff0c;…...