【UnityShader入门精要学习笔记】第十六章 Unity中的渲染优化技术 (上)
本系列为作者学习UnityShader入门精要而作的笔记,内容将包括:
- 书本中句子照抄 + 个人批注
- 项目源码
- 一堆新手会犯的错误
- 潜在的太监断更,有始无终
我的GitHub仓库
总之适用于同样开始学习Shader的同学们进行有取舍的参考。
文章目录
- 移动平台上的优化
- 影响性能的因素
- 渲染统计窗口
- 性能分析器
- 帧调试器
- 减少DrawCall的数量
- 关于渲染相关数据结构的说明
- 批处理
- 动态批处理
- 静态合批
- 共享材质
- 批处理的注意事项
- GPU实例化
- 享元模式
移动平台上的优化
对游戏的优化,一开始就应当视为游戏设计的一部分,特别是当游戏可能在一些低配设备上运行的时候,例如移动设备,在移动设备上的GPU与PC的GPU设计完全不同,它能使用的带宽,功能和其他资源特别有限。这要求我们时刻把优化谨记在心。才可以避免等到项目完成时才发现游戏根本无法在移动设备上运行。
在本章中,我们将会学习到一些关于渲染的优化技术:
影响性能的因素
一个游戏主要使用两种计算资源:CPU和GPU。其中CPU主要负责保证帧率,GPU主要负责分辨率渲染等相关的一些处理
我们可以把造成性能瓶颈的主要原因分成以下几个方面:
(1) CPU
- 过多的DrawCall
- 复杂的脚本或者物理模拟
(2) GPU
- 顶点处理
-
- 过多的顶点
-
- 过多的逐顶点计算
- 片元处理
-
- 过多的片元(可能是由于分辨率造成的,也可能是由于DrawCall造成的)
-
- 过多的逐片元计算
(3) 带宽
- 使用了尺寸很大且未压缩的纹理
- 分辨率过高的帧缓存
对于CPU来说,限制他的主要是每一帧中DrawCall的数目。我们曾介绍过DrawCall的相关概念和原理,简单来说,就是CPU在每次通知GPU进行渲染之前,都需要提前准备好顶点数据(如位置、法线、颜色、坐标纹理等),然后调用一系列API把他们放到GPU可以访问到的指定位置。
最后调用一个DrawCall绘制指令通知GPU将准备好的数据取走并进行计算。
但是过多的DrawCall会导致CPU性能瓶颈,因为每次调用DrawCall时CPU往往都需要改变很多的渲染状态的设置,这些操作都是很耗时的。如果一帧中需要的DrawCall数目过多的话,就会导致CPU大部分时间都花在提交DrawCall上了。
其他的一些因素,例如物理、布料模拟、蒙皮、粒子模拟等,这些都是计算量很大的操作,也会导致CPU效率低下。
而对于GPU来说,它负责整个渲染流水线,从处理CPU传递过来的模型数据开始,进行顶点着色器、片元着色器等一系列的工作,最后输出到屏幕上的每个像素。因此,GPU的性能瓶颈和需要处理的顶点数目、屏幕分辨率、显存等因素有关。而相关的优化策略可以从减少处理的数据规模(包括绘制的顶点数量和片元数量,避免overDraw)、减少运算复杂度等方面入手
后续本章会涉及的优化技术有:
CPU优化:
- 使用批处理技术减少DrawCall数量
GPU优化
-
减少需要处理的顶点数量
-
- 优化几何体
-
- 使用模型的LOD技术
-
- 使用遮挡剔除(Occlusion Culling)技术
-
减少需要处理的片元数量
-
- 控制绘制顺序
-
- 警惕透明物体
-
- 减少实时光照
-
减少计算复杂度
-
- 使用Shader的LOD技术
-
- 代码方面的优化
(3) 节省内存带宽
-
- 减少纹理大小
-
- 利用分辨率缩放
渲染统计窗口
在游戏画面的右上角,我们可以通过states来查看渲染统计窗口,该窗口显示了3个方面的信息:音频、图像和网络(似乎后面的版本不显示网络了)。
渲染统计窗口显示了很多重要的渲染数据,例如FPS、批处理数目、顶点和三角形网格的数目等。
这些较为基础的数据指示了我们该从哪些方面进行优化,但是需要更多分析的话则需要性能分析器:
性能分析器
性能分析器指示了程序运行时的大部分信息。例如在Rendering一栏中,绿线代表批处理数量,蓝线代表了PassCall的数量,还有一些其他的信息,例如顶点和三角形面的信息等。
然而性能分析器给出的DrawCall数量和批处理数量、Pass数量等等不一定准确,往往会大于我们估算的数量。这是由于Unity需要进行很多其他的工作,例如初始化各个缓存,为阴影更新深度纹理和阴影映射纹理等,因此需要花费比预期更多的DrawCall。
帧调试器
使用帧调试器,我们可以清楚的看到每一个DrawCall的工作结果,看到渲染该帧时发生的所有的DrawCall渲染事件以及当前渲染事件使用的Pass,每一步实现了什么样的效果。
在移动平台上进行优化时,由于上述的内置分析器往往是基于PC的分析结果,有时我们还需要使用移动平台专用的性能分析工具来进行分析。
减少DrawCall的数量
为了将一个物体渲染到屏幕上,CPU需要检查哪些光源影响了该物体,绑定Shader并设置它的参数(包括材质,网格,各类贴图等等,这些都由drawcall传递),再把渲染命令发送到GPU,当场景中包含了大量对象时,这些操作就会非常耗时。
例如我们想要渲染一千个三角形,如果按照一千个单独的网格进行渲染,所花费的时间要远远大于渲染一个带有一千个三角形的网格。因为为一千个物体准备DrawCall和为一个物体准备DrawCall,显然前者的耗时更多,而在GPU上二者的计算却基本没有区别。
因此CPU的DrawCall会成为优化瓶颈,一个优化思想就是尽可能的减少DrawCall的数量。
关于渲染相关数据结构的说明
顶点缓冲区对象(VBO):主要用于存储顶点以及顶点附带的各种属性信息,比如顶点位置、法线、颜色、UV等
顶点数组对象(VAO):规定VBO中数据的格式。比如多少空间存储顶点坐标,多少空间存储顶点法线等等
索引缓冲区对象(EBO):负责缓存VBO中顶点的索引,用来解决顶点数据重复使用的问题,避免顶点数据被重复存储。举个例子,绘制一个长方形需要四个顶点、两个三角形,在没有EBO的情况下需要在VBO中存储6个顶点的数据(其中两个是重复的)。存在EBO时,VBO中存储四个顶点的数据,通过EBO中的索引顺序重复调用VBO中相应顶点数据绘制三角形
批处理
什么样的物体可以一起处理呢?答案是使用了同一个材质的物体,对于使用了同一材质的物体,他们的区别仅仅在于使用的顶点数据的差别,我们可以将他们的顶点数据在一次DrawCall中合并,再一起发给GPU,从而完成一次批处理
Unity中支持两种批处理方式,一种是动态批处理,另一种是静态批处理,对于动态批处理来说,优点是一切处理都是Unity自动完成的,不需要我们自己完成任何操作,且物体是可以移动的。但缺点是限制很多,可能一不小心就劈坏了这种机制,导致Unity无法动态批处理使用了相同材质的物体
静态批处理的优点是自由度很高,限制很少;但缺点是可能会占用更多的内存,并且经过静态批处理后的所有物体都不可以再移动了。(即使在脚本中尝试改变物体的位置也是无效的)
动态批处理
如果一些模型共享了同一个材质并满足一些条件,则Unity会自动为其进行动态批处理,将这些网格合并一个DrawCall。
动态批处理的原理是对可批处理的模型网格进行一次合并,在把合并后的模型数据传递给GPU,并用同一个材质进行渲染。且进行了批处理之后的模型仍然可以移动,这是由于处理每帧时Unity都会重新合并一次网格。
虽然动态批处理不需要我们进行任何操作,但是注意只有满足条件的模型和材质才会被动态批处理:
转自Unity性能优化之动态合批
动态合批条件:
- 使用相同的材质球
- 正在屏幕视野中
动态合批的适用范围:
- 未勾选Static的网格模型
- 粒子系统、线条或轨迹渲染器
勾选动态批处理前,三个方块需要4个DrawCall
在Project Setting中勾选动态批处理后同样材质的物体将节省两个DrawCall。如果勾选静态批处理也是节省两个DrawCall,但是静态批处理后物体位置不可移动。
动态合批的缺点:
- 由于模型顶点变换的操作,计算的模型顶点数量不宜太多,否则CPU串行计算耗费的时间太长会造成场景渲染卡顿,所以动态合批只能处理一些小的模型
动态合批失败的情况:
-
物体Mesh大于等于900个面
-
改变Renderer.material将会造成一份材质的拷贝,导致不满足"使用相同材质球"的合批条件
-
如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体,否则都无法参与合批
随后我们试试不同模型的动态合批:
可以看到即使是不同的模型,也成功进行了动态合批,这是由于这些模型的顶点数小于300
在增加了一个顶点数大于300的球体后,drawcall也随之增加了,说明该网格并没有被动态合批。
动态合批中断的情况:
- 位置不相邻且中间夹杂着不同材质的其他物体
在中间穿插了一个不同材质的物体,此时是有合批的
将左边物体移出合批范围,尽管左边网格仍然在屏幕内,但此时两个相同材质的网格不合批了
在隐藏中间的物体之后,两个相同材质的网格又合批了
-
物体如果都符合条件会优先参与静态合批,然后是GPU Instancing,最后才是动态合批
-
物体之间如果有镜像变换则不能进行合批
将其中一个物体的一个轴进行翻转,则不合批
若两个轴进行翻转,则还是合批
若三个轴进行翻转,则不合批
-
拥有lightmap的物体和没有lightmap的物体
-
使用Multi-pass Shader的物体会禁用动态合批
-
Unity的Forward Rendering Path(前向渲染路径)中如果一个物体接受多个光照会为每一个per_pixel light产生多余的模型提交和绘制,从而附加了多个Pass导致无法合批
在场景中新增了一个点光源后,动态合批失效了,这是由于渲染了多个Pass的Shader在应用多个光照下破坏了动态合批的机制。需要处理的pass由2个变为了2*2=4个。当然不在点光源内的物体们依旧会被合批。
静态合批
静态批处理是另一种合批方式,它可以适用于任何大小的几何模型。其原理是:在程序开始运行的阶段,把需要进行静态批处理的模型合并到一个新的网格结构中,这意味着这些模型不可以在运行时刻被移动。但由于它只需要进行一次合并操作,因此比动态批处理更高效。
静态批处理算是一个典型的用内存换时间的策略。要做优化无非就是从两个资源下手:内存优化和计算时间优化。而除了减少内存消耗和计算时间消耗外,我们也可以使用内存换时间,或是用时间换内存。
静态批处理的缺点在于:往往需要占用更多的内存来存储合并后的几何结构。如果合并前的物体共享了相同的网格,那么合批时内存中的每一个该物体都会复制一个该共享网格。例如有1000棵树模型共享了一个树的网格模型,当我们对这1000棵树进行静态批处理,那么每棵树都会在内存中复制一个树的网格模型,那么所消耗的内存就是原来的1000倍!
在上述情况下虽然合批后性能是提高了,但是内存消耗太大了,反而得不偿失。那么如果这些树的网格恰好超过了动态合批限制的顶点数量,那么动态合批也用不了了。这种情况下,要么自行编写合批代码,要么我们可以使用GPU实例化来解决。
在静态合批下,可以看到节省了2个DrawCall
在面板中查看此时的模型网格,会发现所有静态合批的模型都合并为了同一种VBO网格:
可以看到包含了4个submeshes,对于合并后的网格,Unity会判断其中使用同一个材质的子网格,然后对它们进行批处理。
在内部实现上,unity会将这些静态物体变换到世界空间下,然后为它们构建一个更大的顶点和索引缓存。而对于使用了同一材质的物体,Unity只需要调用一个DrawCall就可以绘制全部的物体。而对于使用不同材质的物体,静态批处理则同样可以提升渲染性能——尽管使用材质不同,但是静态批处理减少了这些DrawCall之间的状态切换(上下文切换)。
同时我们发现,尽管有三个茶壶,但是子物体依旧是四个,因此尽管每个茶壶使用的网格是相同的,但是在内存中会缓存一个该网格的复制,因此是三个网格,而非三个茶壶直接共用一个网格。
现在我们在场景中增加一个点光源,会发现DrawCall数量显然增加了。但是由于处理平行光的BasePass部分仍然会被静态批处理,因此依然为我们节省了两个DrawCall。
共享材质
无论是动态批处理还是静态批处理,都要求模型使用同一种材质(同一材质,而非同一Shader)。但有时我们希望使用同一种材质,但材质中的部分数据有变化,例如颜色,某些属性等,例如我们想要给茶壶换换色,使得场景中同时存在两种颜色的茶壶,那么在编辑器中就需要创建两种材质,即使它们是同一个shader。
为了使用一个材质实现不同模型的微调效果,一种常用的方法就是使用网格的顶点数据(最常见的就是顶点颜色数据)来存储这些参数。
经过合批的物体会合成一个更大的VBO发送给GPU,VBO中的数据作为输入传递给顶点着色器,因此,我们可以巧妙地对VBO中的数据进行控制,例如森林场景中所有的树使用了同一种材质,我们希望它们可以通过批处理减少DrawCall,又希望不同树使用不同颜色,此时我们可以使用网格的顶点颜色来调整。
如果我们需要访问合批后的共享材质,应当使用Renderer.shadedMaterial
来保证修改的是和其他物体共享的材质,但这意味着材质修改会应用到所有使用该材质的物体上。另一个类似的API是Renderer.material
,如果使用Renderer.material
来修改材质,Unity会创建一个该材质的复制品,从而破坏批处理在该物体上的应用。
批处理的注意事项
在选择使用动态批处理还是静态批处理时,有一些小小的建议:
- 尽可能使用静态批处理,但得时刻小心对内存的消耗,并且记住被静态批处理的物体不可以再移动
- 如果无法使用静态批处理,那么使用动态批处理时要小心上述的条件,尽量为顶点小于300的小物体使用动态批处理
- 对于一些重复的小道具,尽可能使用动态批处理
- 对于包含动画的部分,我们无法对其进行静态批处理,但是可以将不动的部分设置为静态
在使用批处理时还需要注意,由于批处理需要把模型变换到世界空间下再合并,因此,如果Shader中存在一些基于模型空间下的坐标的运算,那么往往会得到错误的结果。要么把坐标运算变换到世界空间下进行,要么再Shader中使用DisableBatching标签来强制使用该shader的材质不会被批处理。
另一个注意事项是,使用半透明材质的物体通常需要使用严格的从后往前的绘制顺序来保证透明混合的正确性。对于这些物体,unity会先保证绘制顺序,再应用批处理,若合批的绘制顺序不能满足则无法应用批处理。
GPU实例化
这里要拓展一些优化DrawCall的小技巧,一个是GPU实例化。假设我们要生成很多士兵,这些士兵使用相同的模型网格和相同的材质,使用相同的动画和骨骼。那么我们能不能用一个DrawCall来生成所有的士兵?如果使用之前讲的静态合批的话,未免也太耗内存了,有一个更好的方法——GPU Instancing
通过在材质面板上勾选该选项开启GPU实例化,Unity的表面着色器自带GPU实例化选项,而顶点片元着色器则需要在代码中使用开启GPU实例化的宏。
GPU Instancing(GPU实例化)有许多优点,首先,我们可以通过GPU示例化用一个DrawCall就能完成一批相同材质相同模型物体的渲染——因为一次DrawCall能把材质和模型缓存保存在GPU中,接着GPU直接调用缓存即可。
除此之外,我们可以用GPU Instancing对每个重复生成的物体单独进行材质修改,只需要在shader中设置属性块变量,并在C#中使用MaterialPropertyBlock
类赋值该变量即可,例如下面代码:
Shader:
UNITY_INSTANCING_BUFFER_START(prop)
UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
UNITY_INSTANCING_BUFFER_END(prop)
C#:
GameObject chair = Instantiate(Prefab,new Vector3(pos.x,0,pos.y),Quaternion.identity);
MaterialPropertyBlock prop = new MaterialPropertyBlock();
prop.SetColor("_Color",color);
chair.GetComponentInChildren<MeshRenderer>().SetPropertyBlock(prop);
开启GPU实例化后,所有相同模型网格和材质的实例渲染时只调用一个DrawCall,我们可以Instantiate直接生成实例物体(当然生成实例时需要消耗性能),生成实例方便我们直接去访问它们并对其进行各类操作。
或者有时我们并不需要操作或者访问这些实例,此时我们可以直接将其渲染到屏幕上,如下述代码所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;public class GrassInstance : MonoBehaviour
{public GameObject prefab;public int InstanceCount = 10;public float Scale = 4.0f;private Mesh mesh;private Material material;private List<Matrix4x4[]> matrixList;private Matrix4x4[] matrix;private MeshFilter[] meshFilters;private int matrixCount;private Renderer[] renders;private MaterialPropertyBlock materialPropertyBlock;void Awake() {if(prefab == null)return;matrixList = new List<Matrix4x4[]>();var meshFilter = prefab.GetComponent<MeshFilter>();if(meshFilter) {mesh = prefab.GetComponent<MeshFilter>().sharedMesh;material = prefab.GetComponent<Renderer>().sharedMaterial;}materialPropertyBlock = new MaterialPropertyBlock();// 每组GPUInstance指令最多生成1023个物体matrixCount = InstanceCount / 1024 + 1;int RemainCount = InstanceCount;for (int i = 0; i < matrixCount; i++){int LoopTime = RemainCount - (matrixCount - i - 1) * 1023;matrix = new Matrix4x4[LoopTime];for (int j = 0; j < LoopTime; j++){float x = Random.Range(2.0f, 6.0f);float y = 0;float z = Random.Range(-10.0f, 10.0f);matrix[j] = Matrix4x4.identity; //设置位置matrix[j].SetColumn(3, new Vector4(x, y, z, 1)); //设置缩放,矩阵缩放matrix[j].m00 = Scale;matrix[j].m11 = Scale;matrix[j].m22 = Scale;}matrixList.Add((matrix));RemainCount -= LoopTime;}}void Update() {for (int i = 0; i < matrixList.Count; i++){Graphics.DrawMeshInstanced(mesh, 0, material, matrixList[i], matrixList[i].Length,(MaterialPropertyBlock) null, ShadowCastingMode.On, true, 5);}}
}
我们可以使用DrawMeshInstanced
直接将物体渲染到屏幕上,这样就不用生成实例了,更节省性能。当然这些直接绘制的物体由于没有实例,就无法访问预制体上的一些组件了,不过一些shader中的交互还是可以实现的。
(使用GPU实例化生成的4096棵草)
使用GPU实例化,我们就可以生成许多相同模型相同材质的士兵,它们使用相同的骨骼动画,并且我们可以分别设置它们的属性块。而且竟然只使用一个DrawCall,对于性能是巨大的提升。
享元模式
参考:Unity设计模式:享元模式,享元模式(附代码)
享元模式严格来说并不属于渲染领域的内容,而是一种设计模式。还是以上述的士兵为例,每个士兵单位的基础属性应该都是相同的,而通常士兵的血量、身高这些属性是独立的。假如每个士兵预制体类用一个脚本SoliderManager
来管理士兵的属性的话。那么每个Manager上都带有重复的基础属性,1000个士兵的属性就要在内存中重复保存1000次,且不同预制体上的相同属性的地址各不相同,显然浪费了内存和CPU资源。
因此如果对于同个士兵,我们能够让所有士兵都引用同一个属性的话,就不需要再创建一个属性了。即使有一千个,一万个士兵,它们引用的基础属性也始终只有一个。
public class FlyweightAttr
{public int maxHp { get; set; }public float moveSpeed { get; set; }public string name { get; set; }public FlyweightAttr(string name, int maxHp, float moveSpeed){this.name = name;this.maxHp = maxHp;this.moveSpeed = moveSpeed;}
}
public class SoldierAttr
{public int hp { get; set; }public float height { get; set; }public FlyweightAttr flyweightAttr { get; }// 构造函数public SoldierAttr(FlyweightAttr flyweightAttr, int hp, float height){this.flyweightAttr = flyweightAttr;this.hp = hp;this.height = height;}
}
可以看到士兵属性类的构造函数中定义了一个共享属性类,只需在实例化士兵属性类的时候为构造函数传入共享属性类的引用就可以使所有的士兵都引用同一个共享属性类。
之所以提到享元模式,是因为在模型情况下,例如我们要使用相同材质网格模型时,材质,网格实际上也可以看作共享属性。利用享元模式,我们可以将共享的属性只发给GPU一次,不就是相当于将多个DrawCall节省为了一个DrawCall?
使用享元模式使用相同材质:
using UnityEngine;
using System.Collections;
using System;public class flyweightTerrain : MonoBehaviour {public Material redMat;public Material greenMat;flyweightTile redTile;flyweightTile greenTile;flyweightTile[,] tiles;int width = 5;int height = 5;int[,] terrain = {{ 0,1,0,0,0},{ 0,0,0,1,0},{ 1,0,0,1,0},{ 1,0,0,0,0},{ 0,0,1,0,0}};void Start () {redTile = new flyweightTile(redMat, true);greenTile = new flyweightTile(greenMat, false);drawTerrain();}void drawTerrain() {tiles = new flyweightTile[width, height];for (int i = 0; i < width; i++)for (int j = 0; j < height; j++){if (terrain[i, j] == 0)tiles[i, j] = greenTile;elsetiles[i, j] = redTile;}for (int i = 0; i < width; i++)for (int j = 0; j < height; j++){GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);obj.transform.position = new Vector3(i - 2, 0, j);obj.GetComponent<MeshRenderer>().material = tiles[i, j].mat;}}
}class flyweightTile {public flyweightTile(Material mat, bool isHard=false){this.mat = mat;_ishard = ishard;}public Material mat;bool _ishard = false;public bool ishard {get { return _ishard; }}}
使用享元模式,我们生成了两种不同的立方体。其中相同材质的立方体共享了性能消耗。
假设这是个生成地形的代码,两种材质代表了两种地形,我们就可以用享元模式共享性能消耗,生成两种不同地形。
相关文章:

【UnityShader入门精要学习笔记】第十六章 Unity中的渲染优化技术 (上)
本系列为作者学习UnityShader入门精要而作的笔记,内容将包括: 书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更,有始无终 我的GitHub仓库 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 移动平台上…...

GPT-4o:免费且更快的模型
OpenAI GPT-4o 公告 OpenAI 推出了增强版 GPT-4 模型——OpenAI GPT-4o,用于支持 ChatGPT。首席技术官 Mira Murati 表示,更新后的模型速度更快,并在文本、视觉和音频处理方面有了显著提升。GPT-4o 将免费向所有用户开放,付费用户…...

docker部署fastdfs
我的镜像包地址 链接:https://pan.baidu.com/s/1j5E5O1xdyQVfJhsOevXvYg?pwdhcav 提取码:hcav docker load -i gofast.tar.gz拉取gofast docker pull sjqzhang/go-fastdfs启动gofast docker run -d --name fastdfs -p 8080:8080 -v /opt/lijia/lijia…...
【劲舞团game】
编写《劲舞团》这样的游戏代码是一个复杂的过程,涉及到游戏引擎的使用、图形渲染、物理模拟、音频处理、网络通信等多个方面。以下是一个非常简化的步骤,用于说明如何开始编写一个基于Unity引擎的简单舞蹈游戏: 1. 准备开发环境 下载并安装…...
Day15—图像爬虫与简单处理
图像爬虫是一种专门用于从互联网上下载图像的网络爬虫。除了文本内容,图像也是网站中的重要组成部分,它们可以用于多种目的,如图像识别、内容分析、数据备份等。 环境准备 首先,确保你的环境中已安装Python和必要的库。如果没有安装Pillow库,可以通过以下命令安装:pip in…...

Rust基础学习-Rust中的文件操作
文件结构 在Rust中,std::fs::File 结构体代表一个文件。它允许我们对文件执行读/写操作。文件 I/O 是通过提供与文件系统交互的功能的 std::fs 模块执行的。 File 结构体中的所有方法都返回std::io::Result的变体,或者简单地是 Result 枚举。这里会涉及…...

Activator.CreateInstance 与 Type.InvokeMember的区别
文章目录 一、使用 Activator.CreateInstance 创建实例1、使用 Activator.CreateInstance 的优点和缺点2、使用 Activator.CreateInstance 的代码示例 二、使用 Type.InvokeMember 创建实例1、使用 Type.InvokeMember 的优点和缺点2、使用 Type.InvokeMember 的代码示例 三、Ac…...

Java18+App端采用uniapp+开发工具 idea hbuilder智能上门家政系统源码,一站式家政服务平台开发家政服务
Java18App端采用uniapp开发工具 idea hbuilder智能上门家政系统源码,一站式家政服务平台开发 家政服务 家政服务是一个专为家政服务人员设计的平台,该平台旨在提供便捷、高效的工作机会,同时确保服务质量和客户体验。 以下是关于家政服务师…...
【MySQL】探索 MySQL 的 GROUP_CONCAT 函数
缘分让我们相遇乱世以外 命运却要我们危难中相爱 也许未来遥远在光年之外 我愿守候未知里为你等待 我没想到为了你我能疯狂到 山崩海啸没有你根本不想逃 我的大脑为了你已经疯狂到 脉搏心跳没有你根本不重要 🎵 邓紫棋《光年之外》 什么是 GRO…...

SpringBoot整合RabbitMQ (持续更新中)
RabbitMQ 官网地址:RabbitMQ: One broker to queue them all | RabbitMQ RabbitMQ 与 Erlang 版本兼容关系 3.13.0 26.0 26.2.x The 3.13 release series is compatible with Erlang 26. OpenSSL 3 support in Erlang is considered to be mature and ready for…...

瑞鑫RK3588 画中画 OSD 效果展示
这些功能本来在1126平台都实现过 但是迁移到3588平台之后 发现 API接口变化较大 主要开始的时候会比较费时间 需要找到变动接口对应的新接口 之后 就比较好操作了 经过几天的操作 已实现 效果如下...

【全开源】防伪溯源一体化管理系统源码(FastAdmin+ThinkPHP+Uniapp)
🔍防伪溯源一体化管理系统:守护品质,追溯无忧 一款基于FastAdminThinkPHP和Uniapp进行开发的多平台(微信小程序、H5网页)溯源、防伪、管理一体化独立系统,拥有强大的防伪码和溯源码双码生成功能࿰…...

自然语言处理:第三十三章FILCO:过滤内容的RAG
文章链接: [2311.08377] Learning to Filter Context for Retrieval-Augmented Generation (arxiv.org) 项目地址: zorazrw/filco: [Preprint] Learning to Filter Context for Retrieval-Augmented Generaton (github.com) 在人工智能领域,尤其是在开放域问答和事…...

js:flex弹性布局
目录 代码: 1、 flex-direction 2、flex-wrap 3、justify-content 4、align-items 5、align-content 代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewp…...
Pytorch常用函数用法归纳:创建tensor张量
1.torch.arange() (1)函数原型 torch.arange(start,end,step,*,out,dtype,layout,device,requires_grad) (2)参数说明: 参数名称参数类型参数说明startNumber起始值,默认值为0endNumber结束值,取不到,为开区间stepNumber步长值࿰…...

WPF前端:一个纯Xaml的水平导航栏
效果图: 代码: 1、样式代码,可以写在窗体资源处或者样式资源文件中 <Style x:Key"MenuRadioButtonStyle" TargetType"{x:Type RadioButton}"><Setter Property"FontSize" Value"16" />…...

谷粒商城实战(033 业务-秒杀功能4-高并发问题解决方案sentinel 1)
Java项目《谷粒商城》架构师级Java项目实战,对标阿里P6-P7,全网最强 总时长 104:45:00 共408P 此文章包含第326p-第p331的内容 关注的问题 sentinel(哨兵) sentinel来实现熔断、降级、限流等操作 腾讯开源的tendis,…...

STM32项目分享:智能家居(机智云)系统
目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 2.PCB板及元器件图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片: 哔哩哔哩视频链接: https://www.bilibili.c…...

游戏盾之应用加速,何为应用加速
在数字化时代,用户对于应用程序的防护要求以及速度和性能要求越来越高。为了满足用户的期望并提高业务效率,应用加速成为了不可忽视的关键。 应用加速是新一代的智能分布式云接入系统,采用创新级SD-WAN跨域技术,针对高防机房痛点进…...
Java 基础面试题
文章目录 重载与重写抽象类与接口面向对象a a b 与 a b 的区别final、finalize、finallyString、StringBuild、StringBuffer位运算反射 重载与重写 重载:是在同一个类中,方法名相同,方法参数类型,个数不同,返回类型…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...