Unity SRP学习笔记(二)

Unity SRP学习笔记(二)

主要参考:
https://catlikecoding.com/unity/tutorials/custom-srp/
https://docs.unity.cn/cn/2022.3/ScriptReference/index.html
中文教程部分参考(可选):
https://tuncle.blog/custom_render_pipeline/index.html
https://edu.uwa4d.com/lesson-detail/282/1308/0(依照Unity 2019版的内容翻译,不太适用于Unity 2022)
本文主要是,在参考以上内容学习的过程中,对一些基于已有内容但还是不太能理解的部分进行了一些额外的补充。

.Shader使用HLGL

当然也可以用GLSL在.Shader中写着色器,不过还是首推HLSL,毕竟绝大部分游戏都是在Windows平台下运行的。而且Shader中写的HLSL也不会直接执行,而是会根据目标平台编译成指定图形API。

SRP batcher

https://docs.unity.cn/cn/2019.4/Manual/SRPBatcher.html

初见SRPBatcher,了解到SPRBatcher可以通过合批优化渲染过程中CPU向GPU的数据发送环节。但也就仅限于此了,这种半懂不懂的感觉实在让人难受,于是便计划在实际项目中更深入的认识一下这到底是个什么玩意。
**主要有以下几个问题:**什么是通俗意义上的DrawCall?Unity渲染管线在最原始的情况下会如何进行渲染?经常出现在DrawCall优化中的方法——静态批处理/动态批处理/GPU Instancing都是什么?在Unity中实现后和原来相比到底什么区别?最后,再重新审视一下什么是SPR Batcher?

什么是通俗意义上的DrawCall?

你要写SRP batcher,就不能只写SRP batcher
参考了这篇文章DrawCall,Batches,SetPass calls是什么?,DrawCall就是一个CPU向GPU发出的渲染命令,同时告诉GPU我要渲染哪些数据。渲染管线进行一次渲染的步骤为:1)设置一个Shader为当前渲染状态(设置顶点着色器和片元着色器)。2)传递着色器参数,包括各种变换的矩阵(MVP),自定义的各类变量,以及纹理等。3)调用DrawCall,向GPU发出渲染命令。
文章中描述的一次渲染的流程和OpenGL完全对应的上🤗。
在OpenGL中,while (!glfwWindowShouldClose(window))所包围的部分就是完整的一帧渲染,而通常渲染一帧需要多次调用glDrawElements,glDrawElements就可以看成是DrawCall命令。进行一次glDrawElements,我们需要 1)用glUseProgram设置一个program为当前渲染环境(可选,如果不需要切换Shader就不需要再次设置)。2)设置着色器中Uniform的值,以及绑定VAO,设置layout,绑定纹理。3)调用画图函数(如glDrawElements)渲染目标,函数中指明了要渲染哪些数据。
了解了渲染管线基本步骤和DrawCall是什么,就该进到Unity里了。

为了进行后面的实验,需要能够控制渲染管线是否使用静态批处理/动态批处理/GPU Instancing/SPRBatcher。

Static Batching

我用的Unity版本是2022.3.45f1c1,平台为Windows10,静态批处理可以直接在ProjectSetting中设置。

在这里插入图片描述

Dynamic Batching/GPU Instancing

Dynamic Batching和GPU Instancing在SRP中,都需要在C#中手动设置,在URP中可以直接在内置的渲染管线中设置Dynamic Batching(该选项默认隐藏,需要开启全部可见),HDRP不支持Dynamic Batching。
通过对象初始化器{enableDynamicBatching = useDynamicBatching, enableInstancing = useGPUInstancing}设置m_Flags的值。

DrawingSettings drawingOpaqueSettings = new DrawingSettings(unlitShaderTagId, sortingOpaqueSettings) {enableDynamicBatching = useDynamicBatching, enableInstancing = useGPUInstancing };

这是因为Unity中通过变量m_Flags来控制Dynamic Batching/GPU Instancing的启用,m_Flags是一个枚举类型,其默认值为DrawRendererFlags.EnableInstancing。

internal enum DrawRendererFlags
{None = 0,EnableDynamicBatching = 1,EnableInstancing = 2
}

需要像URP一样在渲染管线中控制Dynamic Batching/GPU Instancing,需要在CustomRenderPipelineAsset中创建两个bool型变量并序列化,然后赋值到DrawingSettings中即可。具体实现在DrawCall后半章有。

疑问🤔:对已经通过某个变量完成实例化的类,在inspector中修改该变量为什么还能影响到已经完成实例化的类?
要么是检测如果有实例使用过该变量构造,则重新创建实例。
要么是Unity有什么特殊的办法能够修改readonly的变量。

public class CustomRenderPipelineAsset : RenderPipelineAsset
{[SerializeField]bool useDynamicBatching = true, useGPUInstancing = true, useSRPBatcher = true;protected override RenderPipeline CreatePipeline(){return new CustomRenderPipeline(useDynamicBatching, useGPUInstancing, useSRPBatcher);}
}
SRP Batcher

同样创建一个bool型变量并序列化,但不需要在DrawingSettings中设置,而只需要在CustomRenderPipeline.cs中设置Render函数的GraphicsSettings.useScriptableRenderPipelineBatching即可。

同时还需要提前将变量保存到常量缓冲区中:常量缓冲区介绍
常量缓冲区也需要字节对齐:cbuffer布局介绍,在Unity中如果不手动对齐的话,也能自动对齐。
在这里插入图片描述

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"可能会提示cannot open source file,说是历史遗留问题,除了写代码不太方便外不影响编译。
In Unity2018 and before, the editor feature ShaderIncludePath was used to configure the relative root of the shader, which is now obsolete.The current method is that #include “Packages//”,ShaderLab will parse and configure by itself when linking, but VS does not recognize this writing method and still links according to the default relative path of HLSL files, so an error will be reported, but compilation will not be affected.

以下代码会无法正常使用cbuffer,Shader文件会显示buildin property offset in cbuffer overlap other stages(UnityPerDraw)

CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
CBUFFER_END

UnityPerDraw必须定义特定的值组,cbuffer如下,这里的组名可以理解为更新方式,UnityPerDraw表示每次DrawCall更新,而unity_ObjectToWorld在不同DrawCall(即不同物体)时会改变,所以属于PerDraw。而相机的UnityPerFrame在每一帧内都是不变的,所以是PerFrame,不同材质对同一个Shader有不同的参数,即PerMaterial。在使用时Unity会自动检查所以不能混用,

CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
float4 unity_LODFade;
real4 unity_WorldTransformParams;
CBUFFER_ENDCBUFFER_START(UnityPerFrame)
float4x4 unity_MatrixVP;
CBUFFER_ENDCBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
CBUFFER_END

In this context, “update” refers to the process of initializing a constant buffer with data that will remain constant throughout the execution of the shader programs that access it. 基于上述内容理解,the execution of the shader programs可以理解为一次DrawCall的过程中cbuffer内的值会保持不变。

至此,在渲染管线中实现了对合批方法的控制。接下来用RenderDoc和Frame Debugger进行实验。

在这里插入图片描述

Unity渲染管线在最原始的情况下会如何进行渲染?

场景,40个物体(均使用默认网格),黄色为内置Shader Unlit/Color,绿色和红色为自定义Shader,仅材质参数BaseColor不同。
在这里插入图片描述
无合批优化情况下(RenderDoc能捕获到的内容)的Main Camera:

1)RSSetViewports 设置视口大小,ClearDepthStencilView 清除模板和深度缓冲。
2)绘制第一个物体,设置OM阶段混合状态,设置IA阶段顶点缓冲,布局及索引缓冲,设置OM阶段深度和模板缓冲状态,设置RS阶段光栅状态,设置VS和PS的着色器,UnityPerFrame,UnityPerDraw映射到CPU内存空间,修改后释放绑定到VS,UnityPerMaterial同样操作,绑定到PS,DrawIndexed调用绘制。
具体设置的情况在Pipeline State的OM阶段中可知。
在这里插入图片描述
cbuffer在Pipeline State的VS阶段和PS阶段的ConstantBuffers栏可见,需要HLSL中添加#pragma enable_d3d11_debug_symbols(提供有关着色器内部状态的附加信息),否则Slot只能看到cbuffer0,cbuffer1…
在这里插入图片描述
3)绘制下一个物体,结论为如果 a.同材质,无论是否使用同一个网格,都重新设置顶点,索引缓冲,修改UnityPerDraw。b.同Shader,不同材质,重新设置顶点,索引缓冲,修改UnityPerDraw和UnityPerMaterial。c.不同Shader,和第 2)步一样,所有内容重新设置。

上述是仅使用cbuffer情况下的渲染管线,如果把cbuffer去掉呢O.o。实际上如果去掉显式声明的cbuffer,Unity也会自动把所有的着色器参数放到为$Globals的常量缓冲区中。但是这样的话,因为每一次DrawCall都需要更新unity_ObjectToWorld的值,所以需要对整个Globals常量缓冲区进行映射,不好不好。
在这里插入图片描述

静态批处理/动态批处理/GPU Instancing

Static batching

静态批处理是一种绘制调用批处理方法,它将不会移动的网格组合在一起,以减少绘制调用。 它将组合网格转换到世界空间,并为它们建立一个共享顶点和索引缓冲区。 然后,对于可见的网格,Unity 会执行一系列简单的绘制调用,每次调用之间几乎不会改变状态。 静态批处理并不会减少绘制调用的次数,反而会减少它们之间渲染状态变化的次数。
Static batching会将不会移动的网格合并在一起(最主要的作用!),需要静态批处理的物体可在Inspector中设置为Stratic。
注意:Static Batching只有在Game窗口运行时才会生效!!!同时,因为是在运行模式下,默认的Clear flag:Skybox不会清除上一帧的颜色缓冲,虽然在运行时没有问题,但是会不便于观察调试,所以修改了一下让Skybox也清除颜色缓冲。
在这里插入图片描述
虽然文档说不会减少DrawCall,但是在RenderDoc中看DrawIndexed的次数是会减少的。Static batching只对同材质的物体渲染能有效提升(可不同Mesh)。如果材质不同,Mesh依然会合并且共用一个顶点缓冲区,但依然需要设置顶点缓冲,这样相对于不设置Static batching反而增加了每次DrawCall设置的顶点缓冲大小。
对于组合后的网格能否一次DrawCall绘制还得看是否在索引缓冲区上连续,如果不连续,即使是同材质的物体也需要多次DrawCall。但是不连续也只需要调用一次DrawIndexed,无需再次设置顶点缓冲和索引缓冲,渲染速度也是会更快的。

Dynamic batching

动态批处理是一种绘制调用批处理方法,可批处理移动的游戏对象以减少绘制调用。 动态批处理在网格和 Unity 在运行时动态生成的几何体(如粒子系统)之间的工作方式不同。 有关网格和动态几何体之间内部差异的信息,请参阅网格的动态批处理和动态生成几何体的动态批处理。 注:网格的动态批处理是为了优化老式低端设备的性能而设计的。 在现代消费级硬件上,动态批处理在 CPU 上的工作可能大于绘制调用的开销。 这会对性能产生负面影响。 更多信息,请参阅网格的动态批处理。
Dynamic batching会在每一帧动态计算哪些对象可以合批,哪些对象需要合批,针对需要合批的对象还需要在CPU中做模型变换(转到世界坐标下),这个是要CPU开销的。如果这个开销比不合批时多出来的DrawCall的开销还要大,就成负优化了(所以这种方法现在基本都不用了)。所以尽量还是用Static Batching吧,Static Batching虽然也要将合批对象在CPU中变换到世界坐标下组合,但是是在Build阶段实现的,而不是在运行时每一帧场景绘制之前。
在这里插入图片描述

GPU Instancing

GPU 实例化是一种绘制调用优化方法,可在一次绘制调用中渲染具有相同材质的多个网格副本。 每个网格副本称为一个实例。 这对于绘制场景中多次出现的物体非常有用,例如树木或灌木丛。 GPU 实例化可以在同一个绘制调用中渲染相同的网格。 为了增加变化和减少重复,每个实例可以有不同的属性,如颜色或比例。 渲染多个实例的绘制调用会在帧调试器中显示为 “渲染网格(实例化)”。
**用于优化相同材质,相同网格的物体渲染速度。**可以理解为,在所有相同材质,相同网格的物体中取其中一个实例,对于其他物体将会变化的部分以数组的方式存储在GPU常量缓冲区中,每个物体都有一个唯一InstanceID,通过这个ID就可以索引其数据在数组中的数据。
例如,假如每个实例在世界坐标下的位置(即模型变换矩阵)都不一样,那么GPU中就会有一个常量缓冲区(名为PerDraw0)用来存储其模型变换矩阵数组。启用GPU Instancing时,顶点着色器中传入的数据(即Attributes)带有UNITY_VERTEX_INPUT_INSTANCE_ID,通过UNITY_SETUP_INSTANCE_ID(input)设置InstanceID后GetObjectToWorldMatrix()就能获取对应实例的模型变换矩阵(这个部分在UnityInstancing.hlsl中实现,当然如果想变的麻烦的话也可以自己实现。比如这里的_BaseColor就是自己实现的,可以对不同实例实现不同颜色参数)。
PS:使用_BaseColor时,如果想改颜色需要用MaterialPropertyBlock(教程默认用这个,可能不会遇到这种问题),而不是直接改Material。因为直接改Material会创建新的材质,而MaterialPropertyBlock是重写材质,还是原来的材质所以符合使用相同材质的条件。

//.hlsl
//避免多次include导致重定义问题
#ifndef CUSTOM_UNLIT_SHADER#define CUSTOM_UNLIT_SHADER//包含了对cbuffer的宏定义替换
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"#define UNITY_MATRIX_M unity_ObjectToWorld;//包含了对GPU实例所需的很多内容,可以简单的使用GPU Instancing
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
float4 unity_LODFade;
real4 unity_WorldTransformParams;
CBUFFER_ENDfloat4x4 GetObjectToWorldMatrix()
{return UNITY_MATRIX_M;
}CBUFFER_START(UnityPerFrame)
float4x4 unity_MatrixVP;
CBUFFER_ENDUNITY_INSTANCING_BUFFER_START(BaseColorCB)  //可随意命名,用于在UNITY_ACCESS_INSTANCED_PROP中使用UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(BaseColorCB)struct Attributes
{UNITY_VERTEX_INPUT_INSTANCE_IDfloat3 positionOS : POSITION;
};struct Varyings
{UNITY_VERTEX_INPUT_INSTANCE_IDfloat4 positionCS : SV_POSITION;
};Varyings UnlitPassVertex(Attributes input)
{Varyings output;UNITY_SETUP_INSTANCE_ID(input);UNITY_TRANSFER_INSTANCE_ID(input, output); //因为这里的片元着色器中有根据InstanceID设置的变量所以需要传递InstanceID,如果没有可以去掉这部分output.positionCS = mul(unity_MatrixVP, mul(GetObjectToWorldMatrix(), float4(input.positionOS, 1.0)));return output;
}float4 UnlitPassFragment(Varyings input) : SV_TARGET
{UNITY_SETUP_INSTANCE_ID(input);return UNITY_ACCESS_INSTANCED_PROP(BaseColorCB, _BaseColor); //根据InstanceID在名为UnityPerMaterial的常量缓冲区中的_BaseColor数组内知道当前实例的_BaseColor
}#endif

综上所述,GPU Instancing其实就是把使用同一个Mesh和同一个材质的所有对象会变化的部分以数组的形式存储在常量缓冲区中,再通过InstanceID进行索引。同一个Mesh保证传入的顶点坐标是通用的(即在每个实例渲染中,Attributes的positionOS是通用的),同一个材质大概是确保无需切换渲染状态吧。所以GPU Instancing可以规避合并Mesh导致的内存与性能上升的问题。例如对于大量的树木或者杂草,使用GPU Instancing不会像Static Batching会导致生成一个巨大的顶点缓冲和索引缓冲,只需将模型变换矩阵以数组的形式加载到常量缓冲区中即可。
同样,常量缓冲区是有大小限制的(64kb),Unity中也限制了UNITY_MAX_INSTANCE_COUNT为500,最多一次DrawCall支持500个实例。
【Unity笔记】ShaderLab与其底层原理浅谈

SRP Batcher

Unity SRP Batcher的工作原理
关于静态批处理/动态批处理/GPU Instancing /SRP Batcher的详细剖析
内容可以参照这些文章👆,相比于GPU Instancing,SPR Batcher不会减少DrawCall,但条件更加宽松,只要是同一个Shader即可。
**数据提前加载到GPU:**从RenderDoc中看,所有需要进行渲染的对象的各种信息已经提前保存在GPU缓冲区中(例如顶点信息【不过就算不使用SRP Batcher,默认网格的顶点数据也会在初始化的时候加载到GPU】,模型变换矩阵,材质参数等)了,在DrawCall的准备阶段只需要进行绑定。即便DrawCall没有减少,但是由于每次DrawCall都只需要绑定资源,同时DrawCall本身也只是个绘制调用,所以每次DrawCall的时间大大降低了。
在这里插入图片描述
**数据不再每帧被重新创建:**这部分在RenderDoc中没能找到佐证的部分,可能是我用的方法有问题。
❗使用MaterialPropertyBlock重写的材质无法被SRP Bathcer合批处理。

unity/tutorials/custom-srp/draw-calls

简单看下结果,使用GPU Instancing绘制大量网格,只需要3个Batches即可绘制完毕。

在这里插入图片描述

不使用GPU Instancing,需要501次Batches(500次绘制球,一次绘制天空盒)。

在这里插入图片描述

cutoff / blend

在这里插入图片描述

❗记得在新创建的材质中勾选Enable GPU Instancing。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年10款超好用的企业防泄密软件|企业文件加密防泄密必备!

随着信息技术的迅速发展,企业面临的数据泄露风险越来越高。为了保护企业的敏感信息,防止数据泄露,企业防泄密软件应运而生。以下是2024年值得关注的10款企业防泄密软件,帮助企业有效保障数据安全。 1.安秉网盾 安秉网盾防泄密是一…

K8S flannel网络模式对比

K8S flannel网络模式对比 VXLAN 模式Host-GW 模式如何查看 Flannel 的网络模式?如何修改 Flannel 的网络模式?如何修改flannel vxlan端口?Flannel 是一个 Kubernetes 中常用的网络插件,用于在集群中的节点之间提供网络连接。Flannel 提供了多种后端实现方式,vxlan 和 host…

计算机网络:网络层 —— 移动 IP 技术

文章目录 IPv6IPv6 的诞生背景主要优势IPv6引进的主要变化 IPv6数据报的基本首部IPv6数据报首部与IPv4数据报首部的对比 IPv6数据报的拓展首部IPv6地址IPv6地址空间大小IPv6地址的表示方法 IPv6地址的分类从IPv4向IPv6过渡使用双协议栈使用隧道技术 网际控制报文协议 ICMPv6ICM…

大客户营销数字销售实战讲师培训讲师唐兴通专家人工智能大模型销售客户开发AI大数据挑战式销售顾问式销售专业销售向高层销售业绩增长创新

唐兴通 销售增长策略专家、数字销售实战导师 专注帮助企业构建面向AI数字时代新销售体系,擅长运用数字化工具重塑销售流程,提升销售业绩。作为《挑战式销售》译者,将全球顶尖销售理论大师马修狄克逊等理论导入中国销售业界。 核心专长&…

【dvwa靶场:XSS系列】XSS (Stored)低-中-高级别,通关啦

更改name的文本数量限制大小&#xff0c; 其他我们只在name中进行操作 【除了低级可以在message中进行操作】 一、低级low <script>alert("假客套")</script> 二、中级middle 过滤了小写&#xff0c;咱们可以大写 <Script>alert("假客套…

css中pointer-events:none属性对div里面元素的鼠标事件的影响

文章目录 前倾提要当没有设置属性pointer-events时候结果 当子元素设置了pointer-events: none修改后的代码结果如下所示 当父元素设置了pointer-events: none若两个div同级也就是兄弟级 前倾提要 在gis三维开发的地图组件上放一个背景图片&#xff0c;左右两侧的颜色渐变等&a…

Vue:计算属性

Vue&#xff1a;计算属性 计算属性getset 在模板中&#xff0c;有时候填入的值要依赖于多个属性计算得出。 例如使用姓和名拼出全名&#xff1a; 以上效果可以通过以下代码实现&#xff1a; <div id"root">姓&#xff1a;<input type"text" v-m…

就业市场变革:AI时代,我们将如何评估人才?

内容概要 在这个充满变革的时代&#xff0c;就业市场正被人工智能&#xff08;AI&#xff09;技术深刻改变。随着技术的进步&#xff0c;传统的人才评估方式逐渐显示出其局限性。例如&#xff0c;过去依赖于纸质简历和面试评估的方式在快速变化的环境中难以准确识别真实的人才…

网站504错误出现的原因以及如何修复

504网关超时错误意味着上游服务器未能在规定时间内完成请求&#xff0c;导致无法传递网站内容。当您访问某个网站时&#xff0c;浏览器会向该网站的服务器发出请求。如果请求处理成功&#xff0c;服务器会返回200 OK状态码&#xff1b;但如果服务器响应超时&#xff0c;浏览器可…

学习RocketMQ(记录了个人艰难学习RocketMQ的笔记)

目录 一、部署单点RocketMQ 二、原理篇 三、实操篇 1、引入依赖 2、启动自动装配 3、配置application.yml 4、启动类 5、编写一个统一格式的消息对象 6、生产者 ​编辑 7、定义一个constant 8、多/单个消费者订阅一个主题 1.实现消费者 2.编写接口发送消息 3.接口…

安全关键型嵌入式系统设计模式整理及应用实例

本文提供了对安全关键型嵌入式系统设计模式的全面概述&#xff0c;这些模式旨在提高系统在面临潜在故障时的安全性和可靠性。文中详细介绍了15种设计模式&#xff0c;包括同质冗余&#xff08;HmD&#xff09;、异质冗余&#xff08;HtD&#xff09;、三模冗余&#xff08;TMR&…

京东零售推荐系统可解释能力详解

作者&#xff1a;智能平台 张颖 本文导读 本文将介绍可解释能力在京东零售推荐系统中的应用实践。主要内容包括以下几大部分&#xff1a;推荐系统可解释定义、系统架构、排序可解释、模型可解释、流量可解释。 推荐系统可解释定义 推荐系统可解释的核心包括三部分&#xff0…

java项目之校园周边美食探索及分享平台(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的校园周边美食探索及分享平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 校园周边美食…

stack和queue --->容器适配器

不支持迭代器&#xff0c;迭代器无法满足他们的性质 边出边判断 实现 #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<stack> #include<queue> using namespace std; int main() {stack<int> st;st.push(1);st.push(2);st.push(3);…

vue3动态监听div高度案例

案例场景 场景描述&#xff1a;现在左边的线条长度需要根据右边盒子的高度进行动态变化 实践代码案例 HTML部分 <div v-for"(device, index) in devices" :key"index"><!-- 动态设置 .left-bar 的高度 --><div class"left-bar"…

华为OD机试真题(Python/JS/C/C++)- 考点 - 细节

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题 点这里。 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。

Spring源码学习(五):Spring AOP

免责声明 本人还处于学习阶段&#xff0c;如果内容有错误麻烦指出&#xff0c;敬请见谅&#xff01;&#xff01;&#xff01;Demo <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.8<…

vue 使用docx-preview 预览替换文档内的特定变量

在开发合同管理中&#xff0c;需要使用到此功能&#xff0c;就是替换合同模板内的一些字符串&#xff0c;如&#xff1a;甲乙方名称&#xff0c;金额日期等&#xff0c;合同内容不变。效果如下&#xff1a; 使用docx-preview 好处是只预览不可编辑内容。 前端vue import { re…

若依项目搭建

若依的大版本 基本环境搭建 搭建注意点

(11)(2.1.6) Hobbywing DroneCAN ESC(二)

文章目录 前言 2 配置ESC 3 测试 4 设置视频 5 参数说明 前言 具有 CAN 接口&#xff08;including these&#xff09;的业余 ESC 支持 DroneCAN&#xff0c;它允许自动驾驶仪通过 CAN 控制 ESC /电机&#xff0c;并检索单个转速、电压、电流和温度。 2 配置ESC 默认情…