【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;
                else
                    tiles[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; }
    }

}

在这里插入图片描述
使用享元模式,我们生成了两种不同的立方体。其中相同材质的立方体共享了性能消耗。

假设这是个生成地形的代码,两种材质代表了两种地形,我们就可以用享元模式共享性能消耗,生成两种不同地形。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/664447.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一站式链路追踪:阿里云的端到端解决方案

作者&#xff1a;涯海 炎炎夏日&#xff0c;当你打开外卖 APP 购买奶茶却发现下单失败&#xff1b;五一佳节&#xff0c;当你自驾游途中发现导航响应缓慢&#xff0c;频繁错过路口&#xff1b;深更半夜&#xff0c;当你辅导孩子功课&#xff0c;却发现 GPT 应用迟迟无法应答。…

使用画图工具修改图片文字

方法思路&#xff1a; 使用背景色将需要修改的文字覆盖&#xff0c;然后在原来的地方加入修改后的字。 第一步&#xff1a; 选中图片后右键&#xff0c;选择“编辑”&#xff08;默认会使用画图工具打开&#xff09; 第二步&#xff1a; 选取颜色选取器&#xff0c;如下图 使…

探索Lora:微调大型语言模型和扩散模型的低秩适配方法【原理解析,清晰简洁易懂!附代码】

探索Lora&#xff1a;微调大型语言模型和扩散模型的低秩适配方法 随着深度学习技术的快速发展&#xff0c;大型语言模型&#xff08;LLMs&#xff09;和扩散模型&#xff08;Diffusion Models&#xff09;在自然语言处理和计算机视觉领域取得了显著的成果。然而&#xff0c;这…

【tomcat 源码分析总结】

文章目录 tomcat官网路径目录结构介绍&#xff1a;Tomcat 系统架构 和 原理剖析http 的请求的处理过程 Tomcat 请求处理大致过程 tomcat官网路径 目录结构介绍&#xff1a; confserver.xml 端口的指定tomcat-users.xml 角色web.xml : tomcat 全局的xmllogging.properties 日志…

项目管理主要文档介绍

1、商业论证&#xff1a;一般由项目发起人创建&#xff0c;用于论证项目是否对组织有财务方面的收益。商业论证创建于项日开始之前&#xff0c;用于判断项目是否需要被开展。 2、项目章程&#xff1a;一般由项日经理创建,并由发起入和关键相关力提供输人&#xff0c;最后经项目…

长难句打卡5.31

In a workplace that’s fundamentally indifferent to your life and its meaning, office speak can help you figure out how you relate to your work—and how your work defines who you are. 在一个对你的生活和生活意义漠不关心的工作场所中&#xff0c;办公室语言可以…

ArcGIS空间数据处理、空间分析与制图;PLUS模型和InVEST模型的原理,参量提取与模型运行及结果分析;土地利用时空变化以及对生态系统服务的影响分析

工业革命以来&#xff0c;社会生产力迅速提高&#xff0c;人类活动频繁&#xff0c;此外人口与日俱增对土地的需求与改造更加强烈&#xff0c;人-地关系日益紧张。此外&#xff0c;土地资源的不合理开发利用更是造成了水土流失、植被退化、水资源短缺、区域气候变化、生物多样性…

禁用USB端口的办法,哪一种禁用USB端口的方法好

禁用USB端口的办法&#xff0c;哪一种禁用USB端口的方法好 禁用USB端口是保护公司数据安全的一种常见做法&#xff0c;旨在防止未经授权的数据传输和潜在的恶意软件传播。以下是几种常见的禁用USB端口方法及其效果评价。 1、硬件方法&#xff1a; BIOS设置&#xff1a;通过BIO…

Android下HWC以及drm_hwcomposer普法(下)

Android下HWC以及drm_hwcomposer普法(下) 引言 不容易啊&#xff0c;写到这里。经过前面的普法(上)&#xff0c;我相信童鞋们对HWC和drm_hwcomposer已经有了一定的认知了。谷歌出品&#xff0c;必须精品。我们前面的篇章见分析到啥来了&#xff0c;对了分析到了HwcDisplay::in…

2024年,抖音小店618十大爆款预测!商家抓紧时间上架!

哈喽~我是电商月月 做电商的玩家都知道&#xff0c;一但到了换季或者是节日大促的时候&#xff0c;销量高&#xff0c;是最容易爆单的阶段 而提前上架一些热卖产品&#xff0c;爆单的几率在自己的店铺机会就越大 而最近的一个大型活动&#xff0c;就是618了&#xff0c;抖音…

【C++ ——— 继承】

文章目录 继承的概念即定义继承概念继承定义定义格式继承关系和访问限定符继承基类成员访问方式的变化 基类对象和派生类对象的赋值转换继承中的作用域派生类中的默认成员函数继承与友元继承与静态成员菱形继承虚继承解决数据冗余和二义性的原理继承的总结继承常见笔试面试题 继…

【Text2SQL 论文】T5-SR:使用 T5 生成中间表示来得到 SQL

论文&#xff1a;T5-SR: A Unified Seq-to-Seq Decoding Strategy for Semantic Parsing ⭐⭐⭐ 北大 & 中科大&#xff0c;arXiv:2306.08368 文章目录 一、论文速读二、中间表示&#xff1a;SSQL三、Score Re-estimator四、总结 一、论文速读 本文设计了一个 NL 和 SQL 的…

DVWA靶场搭建:Apache、MySQL、PHP、DVWA

最近为了能够较为真实地学习Web渗透的各种技术&#xff0c;就想着自己搭建一个专门用于学习的Web演练平台--DVWA“靶场”。 DVWA可以进行暴力&#xff08;破解&#xff09;、命令行注入、跨站请求伪造、文件包含、文件上传、不安全的验证码、SQL注入、SQL盲注、弱会话ID、XSS漏…

Open3D(C++) OTSU点云二值化

目录 一、算法原理二、代码实现三、结果展示1、原始点云2、二值化本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理 最大类间方差法(Between-class scatter method)是一种用于分割的方法,它通过计算图…

3225mm晶振SG3225CAN专用于蓝牙模块应用

在无线通信技术迅猛发展的今天&#xff0c;蓝牙技术因其低功耗、高传输速率和广泛的应用范围&#xff0c;成为物联网和智能设备的重要组成部分。晶振在蓝牙模块中无处不在&#xff0c;大部分的智能手机&#xff0c;打开设置工具里面就会有一个是蓝牙功能&#xff0c;蓝牙技术引…

52-QSplitter类QDockWidget类

一 QSplitter类 Qt提供QSplitter(QSplitter)类来进行分裂布局&#xff0c;QSplitter派生于QFrame。 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow>class MainWindow : public QMainWindow {Q_OBJECTpublic:MainWindow(QWidget *parent nullptr);~…

Nginx企业级负载均衡:技术详解系列(15)—— 一篇文章教你如何自定义错误日志

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 在今天的文章中&#xff0c;我将带你深入了解Nginx的一个强大功能——自定义错误日志。无论是对于运维人员还是开发者&#xff0c;掌握这一技能都是提升工作效率、优化系统监控的关键。主要是能装13。图片 自定义错误…

WEB攻防-JAVAWEB项目常见漏洞

知识点 1.JavaWeb常见安全及代码逻辑 2.目录遍历&身份验证&逻辑&JWT 3.访问控制&安全组件&越权&三方组件 本篇主要了解以上问题在javaweb中的呈现&#xff0c; 第一个重点理解URL与javaweb代码框架的对应方式&#xff0c;java在没有代码的情况下是很难…

Echarts 让柱状图在图表中展示,离开X轴

文章目录 需求分析需求 分析 话不多说,直接源码展示 option = {title: {text: Waterfall Chart,subtext: Li

PDF高效编辑器革新:一键智能转换PDF至HTML,轻松开启文件处理全新时代!

信息爆炸的时代&#xff0c;PDF文件因其跨平台、不易修改的特性&#xff0c;成为了商务、教育、出版等领域不可或缺的文件格式。然而&#xff0c;PDF文件的固定性也带来了诸多不便&#xff0c;特别是在需要对其内容进行编辑或格式转换时。这时&#xff0c;一款高效、易用的PDF编…