【Unity3D】法线贴图和凹凸映射
1 法线贴图原理
表面着色器中介绍了使用表面着色器进行法线贴图,实现简单快捷。本文将介绍使用顶点和片元着色器实现法线贴图和凹凸映射,实现更灵活。
本文完整代码资源见→法线贴图和凹凸映射。
1)光照原理
Phong 光照模型和 Blinn Phong 光照模型是应用比较广泛的光照模型,两者区别在与镜面反射光的计算,Phong 光照模型根据反向量和观察向量计算镜面反射光,Blinn Phong 光照模型根据半向量和法向量计算镜面反射光。

光照计算如下:
// 模型自身颜色
fixed4 albedo = tex2D(_MainTex, i.uv) * _ModelColor;
// 环境光
fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT * albedo;
// 漫反射光
fixed4 diffuse = _LightColor0 * albedo * max(0, dot(normal, lightDir));
// 镜面反射光(Phong光照模型)
// fixed4 specular = _LightColor0 * _Specular * pow(max(0, dot(reflectDir, viewDir)), _Gloss);
// 镜面反射光(Blinn Phong光照模型)
fixed4 specular = _LightColor0 * _Specular * pow(max(0, dot(normal, halfDir)), _Gloss);
// 合成颜色
fixed4 finalColor = fixed4(ambient + diffuse + specular, 1.0);
其中,_ModelColor、_LightColor0 分别表示模型颜色、灯光颜色,UNITY_LIGHTMODEL_AMBIENT 表示环境光强度,normal、lightDir、viewDir、halfDir 分别表示法向量、灯光向量、观察向量、半向量(见上图,它们都已归一化)。
2)法线纹理
如下,左侧是纹理图,右侧是其对应的法线纹理图。

法线向量归一化后,每个分量的值域是 [-1, 1],为了使用 RGB 颜色显示法线向量,需要将法线向量映射到区间 [0, 1],映射函数是:y = (x + 1) / 2;由法线纹理还原到法线向量,映射函数是:y = x * 2 - 1。由于法线向量都是归一化的,并且方向始终是由内侧指向外侧,因此可以省去 z 维的存储空间,通过 z = sqrt(x * x + y * y) 推出 z 值。
由法线纹理获取法线向量的方法如下:
fixed4 packedNormal = tex2D(_NormalTex, i.uv);
fixed3 tangentNormal = UnpackNormal(packedNormal); // 切线空间法线向量
// 如果法线纹理未被标记为"Normal map", 可以使用以下方式求出法线纹理中片元的法线值
// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
说明:法线纹理需要设置 Texture Type 为 Normal map,如下,否则 UnpackNormal 函数会失效,需要使用注释中的代码还原法线向量。其中,_BumpScale 反映了物体表面的凹凸程度。

3)切线空间
从法线纹理图中采样并通过 UnpackNormal 还原后得到的法线向量,是切线空间中的向量,切线空间坐标系的定义如下。注意:切线空间坐标系是右手坐标系。

切线空间坐标轴正方向单位向量对应的世界坐标系中的向量如下:
struct a2v {float4 vertex : POSITION; // 模型空间顶点坐标float3 normal : NORMAL; // 模型空间法线坐标(几何法线, 非纹理法线)float4 tangent : TANGENT; // 模型空间切线坐标(几何切线, 非纹理切线)
};v2f vert(a2v v) {v2f o;fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); // 法线(z轴)fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); // 切线(x轴)fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; // 副切线(y轴)return o;
}
说明: worldTangent、worldBinormal、worldNormal 分别为切向空间坐标系 x、y、z 轴正方向单位向量(世界坐标系下的坐标)。
4)切线空间与世界空间的变换
漫反射和镜面反射光照计算使用了法线向量、灯光向量、观察向量,但是它们所处的坐标系不相同,为方便计算光照,需要统一坐标系,即将纹理法线向量由切线坐标系转换到世界坐标系,或者将灯光向量和观察向量由世界坐标系转换到切线坐标系。注意,之所以不在模型空间计算光照(即将法线向量、灯光向量、观察向量转换到模型空间,再进行光照计算),因为如果模型变换中存在非统一缩放,会导致世界空间下的法线与切线不垂直,详见空间和变换 中 2.5 节法线变换。
假设切线空间 x、y、z 坐标轴正方向单位向量对应的世界坐标系中的向量分别为 r、u、f(即上文中的 worldTangent、worldBinormal、worldNormal),世界坐标系下 x、y、z 轴正方向对应的方向向量分别为 e1、e2、e3,因此存在以下关系:

由于 r、u、f 两两正交,并且都是单位向量,因此它们组成的的矩阵是正交矩阵,即 A-1 = A',因此 e1、e2、e3 在一组基向量 r、u、f 下的表示如下:

切线坐标系下的任意向量 v 对应的世界坐标系下的向量如下:

因此,切线空间→世界空间的变换矩阵如下:

世界坐标系下的任意向量 v 对应的切线坐标系下的向量如下:

因此,世界空间→切线空间的变换矩阵如下:

5)切线坐标系下法线向量对应的世界坐标系坐标
// 1. 计算[切线空间->世界空间]的变换矩阵
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); // 法线(z轴)
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); // 切线(x轴)
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; // 副切线(y轴)
float3x3 tangentToWorld = transpose(float3x3(worldTangent, worldBinormal, worldNormal)); // 切线空间->世界空间
// 2. 计算切线空间的法线向量
fixed4 packedNormal = tex2D(_NormalTex, i.uv.zw);
fixed3 tangentNormal = UnpackNormal(packedNormal); // 切线空间法线向量
// 3. 计算世界空间的法线向量
float3 normal = mul(tangentToWorld, tangentNormal);
6)世界坐标系下灯光向量、观察向量对应的切线坐标系坐标
// 1. 计算[世界空间->切线空间]的变换矩阵
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); // 法线(z轴)
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); // 切线(x轴)
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; // 副切线(y轴)
float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal); // 世界空间->切线空间
// 2. 计算切线空间的灯光向量、观察向量
float3 lightDir = mul(worldToTangent, UnityWorldSpaceLightDir(v.vertex));
float3 viewDir = mul(worldToTangent, UnityWorldSpaceViewDir(v.vertex));
7)凹凸映射
为了使物体表面呈现动态凹凸变化,可以通过以下方式动态调整法线。
fixed4 packedNormal = tex2D(_NormalTex, i.uv); // 法线纹理采样
fixed3 tangentNormal = UnpackNormal(packedNormal); // 切线空间法线向量
tangentNormal.xy *= _BumpScale; // 法线凹凸映射
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); // 重新调整法线z值
2 切线空间中计算光照
NormalMapInTangentSpace.shader
Shader "MyShader/NormalMapInTangentSpace" {Properties {_Color ("Color", Color) = (1, 1, 1, 1) // 贴图颜色_MainTex ("MainTex", 2D) = "white" {} // 主纹理_NormalTex ("NormalTex", 2D) = "bump" {} // 法线纹理_BumpScale ("Bump Scale", Range(-2, 2)) = -2 // 法线纹理凹凸比例_Specular ("Specular", Color) = (1, 1, 1, 1) // 镜面反射颜色_Gloss ("Gloss", Range(8.0, 256)) = 20 // 镜面反射光泽度}SubShader {Pass { Tags { "LightMode"="ForwardBase" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"fixed4 _Color; // 贴图颜色sampler2D _MainTex; // 主纹理float4 _MainTex_ST; // 主纹理缩放和偏移sampler2D _NormalTex; // 法线纹理float4 _NormalTex_ST; // 法线纹理缩放和偏移float _BumpScale; // 法线纹理凹凸比例fixed4 _Specular; // 镜面反射光颜色float _Gloss; // 镜面反射光泽度struct a2v {float4 vertex : POSITION; // 模型空间顶点坐标float3 normal : NORMAL; // 模型空间法线坐标(仅用于构造世界空间到切线空间的变换矩阵)float4 tangent : TANGENT; // 模型空间切线坐标(仅用于构造世界空间到切线空间的变换矩阵)float2 texcoord : TEXCOORD0; // 纹理坐标};struct v2f {float4 pos : SV_POSITION; // 裁剪空间顶点坐标float4 uv : TEXCOORD0; // xy存储主纹理坐标, zw存储法线纹理坐标float3 lightDir: TEXCOORD1; // 切线空间光线向量(顶点指向光源)float3 viewDir : TEXCOORD2; // 切线空间观察向量(顶点指向相机)};v2f vert(a2v v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间顶点坐标, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; // 主纹理缩放和偏移o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw; // 法线纹理缩放和偏移// 世界空间法线、切线、副切线fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; // 世界空间坐标变换到切线空间坐标的变换矩阵float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);// 将光线向量、观察向量由世界空间变换到切线空间o.lightDir = mul(worldToTangent, UnityWorldSpaceLightDir(v.vertex));o.viewDir = mul(worldToTangent, UnityWorldSpaceViewDir(v.vertex));// 如果模型变换中不包含非统一缩放, 可以将光线向量和观察向量直接由模型空间变换到切线空间, 而不必先转换到世界空间// float3 objectBinormal = cross(normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;// float3x3 objectToTangent = float3x3(v.tangent.xyz, objectBinormal, v.normal);// o.lightDir = mul(objectToTangent, ObjSpaceLightDir(v.vertex));// o.viewDir = mul(objectToTangent, ObjSpaceViewDir(v.vertex));return o;}fixed4 frag(v2f i) : SV_Target { fixed3 tangentLightDir = normalize(i.lightDir); // 切线空间光线向量归一化fixed3 tangentViewDir = normalize(i.viewDir); // 切线空间观察向量归一化fixed4 packedNormal = tex2D(_NormalTex, i.uv.zw); // 法线纹理采样fixed3 tangentNormal = UnpackNormal(packedNormal); // 切线空间法线向量// 如果法线纹理未被标记为"Normal map", 可以使用以下方式求出法线纹理中片元的法线值//tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;//tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));tangentNormal.xy *= _BumpScale; // 法线凹凸映射tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); // 重新调整法线z值fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb; // 物体自身颜色fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; // 环境光fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir)); // 漫反射光fixed3 halfDir = normalize(tangentLightDir + tangentViewDir); // 半向量fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss); // 镜面反射光return fixed4(ambient + diffuse + specular, 1.0);}ENDCG}}FallBack "Specular"
}
通过调节 _BumpScale 值在区间 [-2, 2] 之间变化,使得胶囊体呈现动态凹凸变化,效果如下:

3 世界空间中计算光照
NormalMapInWorldSpace.shader
Shader "MyShader/NormalMapInWorldSpace" {Properties {_Color ("Color", Color) = (1, 1, 1, 1) // 贴图颜色_MainTex ("MainTex", 2D) = "white" {} // 主纹理_NormalTex ("NormalTex", 2D) = "bump" {} // 法线纹理_BumpScale ("Bump Scale", Range(-2, 2)) = -2 // 法线纹理凹凸比例_Specular ("Specular", Color) = (1, 1, 1, 1) // 镜面反射颜色_Gloss ("Gloss", Range(8.0, 256)) = 20 // 镜面反射光泽度}SubShader {Pass { Tags { "LightMode"="ForwardBase" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"fixed4 _Color; // 贴图颜色sampler2D _MainTex; // 主纹理float4 _MainTex_ST; // 主纹理缩放和偏移sampler2D _NormalTex; // 法线纹理float4 _NormalTex_ST; // 法线纹理缩放和偏移float _BumpScale; // 法线纹理凹凸比例fixed4 _Specular; // 镜面反射光颜色float _Gloss; // 镜面反射光泽度struct a2v {float4 vertex : POSITION; // 模型空间顶点坐标float3 normal : NORMAL; // 模型空间法线坐标(仅用于构造世界空间到切线空间的变换矩阵)float4 tangent : TANGENT; // 模型空间切线坐标(仅用于构造世界空间到切线空间的变换矩阵)float2 texcoord : TEXCOORD0; // 纹理坐标};struct v2f {float4 pos : SV_POSITION; // 裁剪空间顶点坐标float4 uv : TEXCOORD0; // xy存储主纹理坐标, zw存储法线纹理坐标float4 T2W0 : TEXCOORD1; // 切线空间到世界空间的变换矩阵的第一行float4 T2W1 : TEXCOORD2; // 切线空间到世界空间的变换矩阵的第二行float4 T2W2 : TEXCOORD3; // 切线空间到世界空间的变换矩阵的第三行};v2f vert(a2v v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间顶点坐标, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; // 主纹理缩放和偏移o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw; // 法线纹理缩放和偏移float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 世界空间顶点坐标// 世界空间法线、切线、副切线fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; // 切线空间到世界空间的变换矩阵(为充分利用GPU插值寄存器, 将顶点坐标保存到w维中)o.T2W0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);o.T2W1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);o.T2W2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);return o;}fixed4 frag(v2f i) : SV_Target { float3 worldPos = float3(i.T2W0.w, i.T2W1.w, i.T2W2.w); // 世界空间顶点坐标fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos)); // 世界空间光线向量fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); // 世界空间观察向量fixed4 packedNormal = tex2D(_NormalTex, i.uv.zw); // 法线纹理采样fixed3 tangentNormal = UnpackNormal(packedNormal); // 切线空间法线向量// 如果法线纹理未被标记为"Normal map", 可以使用以下方式求出法线纹理中片元的法线值//tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;//tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));tangentNormal.xy *= _BumpScale; // 法线凹凸映射tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); // 重新调整法线z值// 世界空间法线坐标fixed3 worldNormal = normalize(half3(dot(i.T2W0.xyz, tangentNormal), dot(i.T2W1.xyz, tangentNormal), dot(i.T2W2.xyz, tangentNormal)));fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb; // 物体自身颜色fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; // 环境光fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir)); // 漫反射光fixed3 halfDir = normalize(worldLightDir + worldViewDir); // 半向量fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); // 镜面反射光return fixed4(ambient + diffuse + specular, 1.0);}ENDCG}}FallBack "Specular"
}
运行效果同第 2 节。
相关文章:
【Unity3D】法线贴图和凹凸映射
1 法线贴图原理 表面着色器中介绍了使用表面着色器进行法线贴图,实现简单快捷。本文将介绍使用顶点和片元着色器实现法线贴图和凹凸映射,实现更灵活。 本文完整代码资源见→法线贴图和凹凸映射。 1)光照原理 Phong 光照模型和 Blinn Phong 光…...
代码误写到master分支(或其他分支),此时代码还未提交,如何转移到新建分支?
问题背景 有时候,我们拿到需求,没仔细看当前分支是什么,就开始撸代码了。完成了需求或者写到一半发现开发错分支了。 比如此时新需求代码都在master分支上,提交必然是不可能的,所有修改还是要在新建分支上进行&#x…...
java多线程之线程安全(重点,难点)
线程安全1. 线程不安全的原因:1.1 抢占式执行1.2 多个线程修改同一个变量1.3 修改操作不是原子的锁(synchronized)1.一个锁对应一个锁对象.2.多个锁对应一个锁对象.2.多个锁对应多个锁对象.4. 找出代码错误5. 锁的另一种用法1.4 内存可见性解决内存可见性引发的线程安全问题(vo…...
如何免费使用chatGPT4?无需注册!
Poe体验真滴爽首先提大家问一个大家最关心的问题如何在一年内赚到一百万?用个插件给他翻译一下体验地址效果是非常炸裂的,那么我就将网址分分享给大家https://poe.com/前提:要有魔法,能够科学shangwangChatGPT-3 随便问GPT-4 模型…...
Android Flutter在点击事件上添加动画效果
在Android App的开发项目中,我们需要在点击事件上实现一个动画效果来提高用户的体验度。比如闲鱼底部中间按钮的那种。该怎么实现呢? 一起来看看吧 实现效果如图: 实现思路 根据UI的设计图,对每个模块设计好动画效果࿰…...
VSCode嵌入式开发环境搭建
Vscode开发环境搭建 看这个链接就可以了,后面下载调试有点问题看下3.3。 在VSCode上部署STM32F1的开发环境 1. MXCube配置工程生成Makefile文件 借助正确的编译工具链进行编译, 2. 编译工具链搭建 编译工具链使用GCC的ARM版本 arm-none-eabi-gcc &am…...
数据结构之栈的使用
栈是计算机科学中一个重要的数据结构。它是一种特殊的线性表,只允许在一端进行进出操作。这一端被称为栈顶,另外一端被称为栈底。栈的特点是后进先出,即最后进入栈的元素会先被弹出栈。栈的应用广泛,例如在编译器中,栈…...
QMessageBox手动添加按钮并绑定按钮的信号
视频展示效果(结合代码看效果更佳哦,代码在最下面): QMessageBox手动添加有重试效果的按钮效果图: 点击详细文本之后展开如下图: 图标可选: QMessageBox::Critical错误图标QMessageBox::NoIco…...
【C++进阶】位图和布隆过滤器
文章目录位图位图概念位图使用场景位图的结构构造setresettest完整代码布隆过滤器布隆过滤器概念布隆过滤器结构构造setresettest完整版代码位图 位图概念 所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用…...
Android开发-Android UI与布局
01 Android UI 1.1 UI 用户界面(User Interface,简称 UI,亦称使用者界面)是系统和用户之间进行交互和信息交换的媒介,它实现信息的内部形式与人类可以接受形式之间的转换。软件设计可分为两个部分:编码设计与UI设计。 1.2 Andr…...
在不丢失数据的情况下解锁锁定的 Android 手机的 4 种方法
尽管您可以使用指纹解锁手机,但大多数智能手机都需要 PIN 码、图案或字母数字代码作为主密码。如果您有一段时间没有输入手机密码,很容易忘记。正是由于这个原因,即使您打开了指纹解锁,大多数智能手机也会让您每天至少输入一次 PI…...
【11】核心易中期刊推荐——人工智能 | 图形图像处理
🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…...
Spring 中的事件发布与监听
主要代码在org.springframework.context,org.springframework.context.event包中 事件发布与监听主要包含以下角色: 事件:ApplicationEvent事件监听器:ApplicationListener SmartApplicationListener GenericApplicationListene…...
c++ 一些常识 2
前言 今天主要讲类相关概念。 构造和析构函数是否可以抛出异常 在构造函数中抛出异常,控制权会转出构造函数之外,对象的析构函数不会被调用,造成内存泄漏。 如果析构函数中抛出异常,而且没有在当地捕捉,析构函数便执…...
用嘴写代码?继ChatGPT和NewBing之后,微软又开始整活了,Github Copilot X!
用嘴写代码?继ChatGPT和NewBing之后,微软又开始整活了,Github Copilot X! AI盛行的时代来临了,在这段时间,除了爆火的GPT3.5后,OpenAI发布了GPT4版本,同时微软也在Bing上开始加入了A…...
3分钟阐述这些年我的 接口自动化测试 职业生涯经验分享
接口自动化测试学习教程地址:https://www.bilibili.com/video/BV1914y1F7Bv/ 你好,我是凡哥。 很高兴能够分享我的接口自动化测试经验和心得体会。在我目前的职业生涯中,接口自动化测试是我经常进行的一项任务。通过不断地学习和实践…...
十大Python可视化工具,太强了
今天介绍Python当中十大可视化工具,每一个都独具特色,惊艳一方。 Matplotlib Matplotlib 是 Python 的一个绘图库,可以绘制出高质量的折线图、散点图、柱状图、条形图等等。它也是许多其他可视化库的基础。 import matplotlib.pyplot as p…...
五.ElasticSearch的基础+实战
五.ElasticSearch的基础+实战 1.Elasticsearch的是什么? 2.Elasticsearch的作用是什么? 3.Elasticsearch的核心思想? 4.Elasticsearch启动与简单使用 5.kibana结合elasticsearch实现简单的增删改查 6.elasticsearch安装中文分词器 7.elasticsearch结合springboot开发…...
Oracle的学习心得和知识总结(十三)|Oracle数据库Real Application Testing之Database Reply实操(一)
目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《Oracle Database SQL Language Reference》 2、参考书籍:《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Guid…...
CAD外部参照如何重新定位?CAD外部参照重定位步骤
CAD外部参照如何重新定位?这个问题并不算是一个常见的问题,但偶尔也会遇到,今天小编就来给大家简单介绍一下浩辰CAD软件中CAD外部参照重定位的操作步骤,一起来看看吧! CAD外部参照重定位步骤: 浩辰CAD软件…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
Copilot for Xcode (iOS的 AI辅助编程)
Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot,它能根据上下文补全代码,快速生成常用…...
java 局域网 rtsp 取流 WebSocket 推送到前端显示 低延迟
众所周知 摄像头取流推流显示前端延迟大 传统方法是服务器取摄像头的rtsp流 然后客户端连服务器 中转多了,延迟一定不小。 假设相机没有专网 公网 1相机自带推流 直接推送到云服务器 然后客户端拉去 2相机只有rtsp ,边缘服务器拉流推送到云服务器 …...
