Unity-Shader详解-其五
关于Unity的Shader部分的基础知识其实已经讲解得差不多了,今天我们来一些实例分享:
溶解
效果如下:
代码如下:
Shader "Chapter8/chapter8_1"
{Properties{// 定义属性[NoScaleOffset]_Albedo("Albedo", 2D) = "white" {} // 基础颜色纹理,默认白色_Noise("Dissolve Noise", 2D) = "white" {} // 溶解噪声纹理,默认白色_Dissolve("Dissolve", Range(0, 1)) = 0 // 溶解程度,范围0到1,默认0[NoScaleOffset]_Gradient("Edge Gradient", 2D) = "black" {} // 边缘渐变纹理,默认黑色_Range("Edge Range", Range(2, 100)) = 6 // 边缘范围,范围2到100,默认6_Brightness("Brightness", Range(0, 10)) = 1 // 亮度,范围0到10,默认1}SubShader{Tags{ "RenderType"="TransparentCutout" "Queue" = "AlphaTest" // 渲染类型为透明剪切,队列为Alpha测试}CGPROGRAM#pragma surface surf StandardSpecular addshadow fullforwardshadows // 使用StandardSpecular表面着色器,并添加阴影和全向阴影struct Input{float2 uv_Albedo; // Albedo纹理的UV坐标float2 uv_Noise; // 噪声纹理的UV坐标};sampler2D _Albedo; // Albedo纹理sampler2D _Noise; // 噪声纹理fixed _Dissolve; // 溶解程度sampler2D _Gradient; // 边缘渐变纹理float _Range; // 边缘范围float _Brightness; // 亮度void surf (Input IN, inout SurfaceOutputStandardSpecular o){// 溶解遮罩fixed noise = tex2D(_Noise, IN.uv_Noise).r; // 从噪声纹理中获取红色通道值fixed dissolve = _Dissolve * 2 - 1; // 将溶解程度从0-1映射到-1到1fixed mask = saturate(noise - dissolve); // 计算遮罩值,限制在0到1之间clip(mask - 0.5); // 根据遮罩值进行剪切,小于0.5的部分将被剔除// 燃烧效果fixed texcoord = saturate(mask * _Range - 0.5 * _Range); // 计算纹理坐标,用于边缘渐变o.Emission = tex2D(_Gradient, fixed2(texcoord, 0.5)) * _Brightness; // 根据渐变纹理和亮度计算自发光fixed4 c = tex2D (_Albedo, IN.uv_Albedo); // 从Albedo纹理中获取颜色o.Albedo = c.rgb; // 设置表面颜色}ENDCG}
}
属性中有一个[NoScaleOffset]:
一言以蔽之, [NoScaleOffset]修饰的纹理的尺寸不可修改(至少在Inspector界面不可修改)。
Tags{ "RenderType"="TransparentCutout" "Queue" = "AlphaTest" // 渲染类型为透明剪切,队列为Alpha测试}
Tags中设置渲染类型为TransparentCutout而渲染队列为AlphaTest。
#pragma surface surf StandardSpecular addshadow fullforwardshadows // 使用StandardSpecular表面着色器,并添加阴影和全向阴影
正如注释所写,使用表面着色器,相关函数为surf,采用StandardSpecular光照模型,添加阴影以及全向阴影。
如果我们需要去查询Unity内置的光照模型和阴影模型可以:
struct Input{float2 uv_Albedo; // Albedo纹理的UV坐标float2 uv_Noise; // 噪声纹理的UV坐标};
这是作为输入的结构体,内部包含的是基础的纹理的UV坐标和噪声纹理的UV纹理。
void surf (Input IN, inout SurfaceOutputStandardSpecular o){// 溶解遮罩fixed noise = tex2D(_Noise, IN.uv_Noise).r; // 从噪声纹理中获取红色通道值fixed dissolve = _Dissolve * 2 - 1; // 将溶解程度从0-1映射到-1到1fixed mask = saturate(noise - dissolve); // 计算遮罩值,限制在0到1之间clip(mask - 0.5); // 根据遮罩值进行剪切,小于0.5的部分将被剔除// 燃烧效果fixed texcoord = saturate(mask * _Range - 0.5 * _Range); // 计算纹理坐标,用于边缘渐变o.Emission = tex2D(_Gradient, fixed2(texcoord, 0.5)) * _Brightness; // 根据渐变纹理和亮度计算自发光fixed4 c = tex2D (_Albedo, IN.uv_Albedo); // 从Albedo纹理中获取颜色o.Albedo = c.rgb; // 设置表面颜色}
我们实现了两个效果:溶解的遮罩,我们获取噪声纹理的红色通道值之后减去溶解的程度值来作为遮罩值,遮罩值小于0.5的部分将会被剔除。
然后是边缘的燃烧效果,我们利用之前生成的遮罩值来计算纹理坐标之后再根据纹理坐标来乘以自发光强度实现渐变纹理。最后我们从基础纹理中获取颜色后再添加到输出的模型中。
透视
效果如图:
能够看到我们可以透过岩石看到人物的轮廓。
代码如下:
Shader "Chapter8/chapter8_2"
{Properties{// 定义属性[Header(The Blocked Part)] // 标题,表示以下属性是用于被遮挡部分的设置[Space(10)] // 在Inspector中留出10像素的空白_Color ("X-Ray Color", Color) = (0,1,1,1) // X射线颜色,默认青色_Width ("X-Ray Width", Range(1, 2)) = 1 // X射线宽度,范围1到2,默认1_Brightness ("X-Ray Brightness",Range(0, 2)) = 1 // X射线亮度,范围0到2,默认1}SubShader{Tags{"RenderType" = "Opaque" "Queue" = "Geometry"} // 渲染类型为不透明,队列为几何体//---------- 被遮挡部分的效果 ----------Pass{ZTest Greater // 深度测试设置为大于当前深度值时才渲染(即渲染被遮挡的部分)ZWrite Off // 关闭深度写入,避免影响后续渲染Blend SrcAlpha OneMinusSrcAlpha // 设置混合模式,实现透明效果CGPROGRAM#pragma vertex vert // 顶点着色器#pragma fragment frag // 片段着色器#include "UnityCG.cginc" // 引入Unity的CG库// 定义顶点着色器的输出结构struct v2f{float4 vertexPos : SV_POSITION; // 顶点在裁剪空间中的位置float3 viewDir : TEXCOORD0; // 视线方向float3 worldNor : TEXCOORD1; // 世界空间中的法线方向};// 顶点着色器v2f vert(appdata_base v){v2f o;o.vertexPos = UnityObjectToClipPos(v.vertex); // 将顶点从对象空间转换到裁剪空间o.viewDir = normalize(WorldSpaceViewDir(v.vertex)); // 计算视线方向o.worldNor = UnityObjectToWorldNormal(v.normal); // 将法线从对象空间转换到世界空间return o;}// 声明属性变量fixed4 _Color; // X射线颜色fixed _Width; // X射线宽度half _Brightness; // X射线亮度// 片段着色器float4 frag(v2f i) : SV_Target{//计算边缘光强度half NDotV = saturate(dot(i.worldNor, i.viewDir)); // 计算法线与视线方向的点积NDotV = pow(1 - NDotV, _Width) * _Brightness; // 根据宽度和亮度调整边缘光强度fixed4 color;color.rgb = _Color.rgb; // 设置颜色color.a = NDotV; // 设置透明度(基于Fresnel值)return color; // 返回最终颜色}ENDCG}}
}
// 定义属性[Header(The Blocked Part)] // 标题,表示以下属性是用于被遮挡部分的设置[Space(10)] // 在Inspector中留出10像素的空白_Color ("X-Ray Color", Color) = (0,1,1,1) // X射线颜色,默认青色_Width ("X-Ray Width", Range(1, 2)) = 1 // X射线宽度,范围1到2,默认1_Brightness ("X-Ray Brightness",Range(0, 2)) = 1 // X射线亮度,范围0到2,默认1
首先是属性中,[Space(10)]在Inspector中留出10像素的空白,效果如图。
Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}
Tags中渲染类型为不透明,队列为几何体。
ZTest Greater // 深度测试设置为大于当前深度值时才渲染(即渲染被遮挡的部分)ZWrite Off // 关闭深度写入,避免影响后续渲染
开启深度测试的同时关闭深度写入。
// 定义顶点着色器的输出结构struct v2f{float4 vertexPos : SV_POSITION; // 顶点在裁剪空间中的位置float3 viewDir : TEXCOORD0; // 视线方向float3 worldNor : TEXCOORD1; // 世界空间中的法线方向};
顶点着色器的输出中多了一个视线方向。
// 顶点着色器v2f vert(appdata_base v){v2f o;o.vertexPos = UnityObjectToClipPos(v.vertex); // 将顶点从对象空间转换到裁剪空间o.viewDir = normalize(WorldSpaceViewDir(v.vertex)); // 计算视线方向o.worldNor = UnityObjectToWorldNormal(v.normal); // 将法线从对象空间转换到世界空间return o;}
这里的视线方向计算方法:
最后是片元着色器的内容:
// 片段着色器float4 frag(v2f i) : SV_Target{//计算边缘光强度half NDotV = saturate(dot(i.worldNor, i.viewDir)); // 计算法线与视线方向的点积NDotV = pow(1 - NDotV, _Width) * _Brightness; // 根据宽度和亮度调整边缘光强度fixed4 color;color.rgb = _Color.rgb; // 设置颜色color.a = NDotV; // 设置透明度(基于Fresnel值)return color; // 返回最终颜色}
这里的边缘光强度计算的内容可能比较难以理解,我们先用法线和视线方向进行一个点积之后调整该值到[0,1]之间,然后根据宽度和亮度来调整光强,这里我们采用了幂次计算,宽度作为幂,那么宽度值越小则光强越小,同时注意我们的base是一减去点积,意思就是法线和视线的夹角越大则光强越强(夹角越大则越边缘),我们通过这些函数实现了边缘光越边缘强度越大的效果。
切割
效果如图:
非常直接的切割效果,代码如下:
Shader "Chapter8/chapter8_3"
{Properties{// 纹理部分[Header(Textures)] [Space(10)] // 标题和空白[NoScaleOffset] _Albedo ("Albedo", 2D) = "white" {} // 基础颜色纹理,默认白色,无缩放偏移// 切割部分[Header(Cutting)] [Space(10)] // 标题和空白[KeywordEnum(X, Y, Z)] _Direction ("Cutting Direction", Float) = 1 // 切割方向枚举(X、Y、Z),默认Y轴[Toggle] _Invert ("Invert Direction", Float) = 0 // 是否反转切割方向,默认关闭}SubShader{Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" } // 渲染类型为透明剪切,队列为Alpha测试Cull Off // 关闭背面剔除,渲染双面CGPROGRAM#pragma surface surf StandardSpecular addshadow fullforwardshadows // 使用StandardSpecular表面着色器,添加阴影和全向阴影#pragma target 3.0 // 目标着色器模型3.0#pragma multi_compile _DIRECTION_X _DIRECTION_Y _DIRECTION_Z // 多编译选项,支持X、Y、Z三个方向的切割sampler2D _Albedo; // 基础颜色纹理float3 _Position; // 切割位置fixed _Invert; // 是否反转切割方向struct Input{float2 uv_Albedo; // 基础颜色纹理的UV坐标float3 worldPos; // 世界空间中的顶点位置fixed face : VFACE; // 判断当前渲染的是正面还是背面};void surf (Input i, inout SurfaceOutputStandardSpecular o){// 获取基础颜色fixed4 col = tex2D(_Albedo, i.uv_Albedo);// 如果是正面,使用纹理颜色;如果是背面,使用黑色o.Albedo = i.face > 0 ? col.rgb : fixed3(0,0,0);// 判断切割方向#if _DIRECTION_X// 如果选择X轴方向,根据世界坐标的X值与切割位置比较col.a = step(_Position.x, i.worldPos.x);#elif _DIRECTION_Y// 如果选择Y轴方向,根据世界坐标的Y值与切割位置比较col.a = step(_Position.y, i.worldPos.y);#else // 如果选择Z轴方向,根据世界坐标的Z值与切割位置比较col.a = step(_Position.z, i.worldPos.z);#endif// 判断是否反转切割方向col.a = _Invert? 1 - col.a : col.a;// 根据透明度进行剪切,小于0.001的部分将被剔除clip(col.a - 0.001);}ENDCG}
}
{// 纹理部分[Header(Textures)] [Space(10)] // 标题和空白[NoScaleOffset] _Albedo ("Albedo", 2D) = "white" {} // 基础颜色纹理,默认白色,无缩放偏移// 切割部分[Header(Cutting)] [Space(10)] // 标题和空白[KeywordEnum(X, Y, Z)] _Direction ("Cutting Direction", Float) = 1 // 切割方向枚举(X、Y、Z),默认Y轴[Toggle] _Invert ("Invert Direction", Float) = 0 // 是否反转切割方向,默认关闭}
这里有两个新东西:KeywordEnum和一个Toggle。
效果如下:
struct Input{float2 uv_Albedo; // 基础颜色纹理的UV坐标float3 worldPos; // 世界空间中的顶点位置fixed face : VFACE; // 判断当前渲染的是正面还是背面};
作为输入的结构体里除了基本的纹理UV坐标和顶点坐标以外还有一个声明为VFACE的face变量,用来表明具体渲染的是正面还是背面。
void surf (Input i, inout SurfaceOutputStandardSpecular o){// 获取基础颜色fixed4 col = tex2D(_Albedo, i.uv_Albedo);// 如果是正面,使用纹理颜色;如果是背面,使用黑色o.Albedo = i.face > 0 ? col.rgb : fixed3(0,0,0);// 判断切割方向#if _DIRECTION_X// 如果选择X轴方向,根据世界坐标的X值与切割位置比较col.a = step(_Position.x, i.worldPos.x);#elif _DIRECTION_Y// 如果选择Y轴方向,根据世界坐标的Y值与切割位置比较col.a = step(_Position.y, i.worldPos.y);#else // 如果选择Z轴方向,根据世界坐标的Z值与切割位置比较col.a = step(_Position.z, i.worldPos.z);#endif// 判断是否反转切割方向col.a = _Invert? 1 - col.a : col.a;// 根据透明度进行剪切,小于0.001的部分将被剔除clip(col.a - 0.001);}
表面着色器里,我们首先获取纹理的颜色,然后先判断是正面还是背面,接着从X,Y,Z轴选择切割的方向,根据选择的轴来确定世界坐标和切割位置的比较。这里我们使用了一个函数step来进行比较,step的具体用法如下:
最后把小于阈值的部分直接剔除掉即可。
切割轴为Y轴时效果如图:
广告
效果如下:
就是实现无论哪个位置看到的图片效果都一样。
代码如下:
Shader "Chapter8/chapter8_4"
{Properties{[NoScaleOffset] _Tex ("Texture", 2D) = "white" {}[KeywordEnum(Spherical, Cylindrical)] _Type ("Type", float) = 0}SubShader{Tags{"RenderType" = "Transparent""Queue" = "Transparent""DisableBatching" = "True"}//Blend OneMinusDstColor OneZWrite OffPass{CGPROGRAM#pragma vertex vert#pragma fragment frag// 声明枚举的关键词#pragma shader_feature _TYPE_SPHERICAL _TYPE_CYLINDRICALstruct appdata{float4 vertex : POSITION;float2 texcoord : TEXCOORD0;};struct v2f{float4 vertex : SV_POSITION;float2 texcoord : TEXCOORD0;};sampler2D _Tex;v2f vert (appdata v){v2f o;// 计算面片朝向摄像机的前方向量float3 forward = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1)).xyz;// 判断Billboard的类型#if _TYPE_CYLINDRICALforward.y = 0;#endifforward = normalize(forward);// 当摄像机完全在面片正上方或者正下方的时候,旋转临时的上方向量float3 up = abs(forward.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);float3 right = normalize(cross(forward, up));up = normalize(cross(right, forward));// 将顶点在新的坐标系上移动位置float3 vertex = v.vertex.x * right + v.vertex.y * up;o.vertex = UnityObjectToClipPos(vertex);o.texcoord = v.texcoord;return o;}float4 frag (v2f i) : SV_Target{return tex2D(_Tex, i.texcoord);}ENDCG}}
}
Tags{"RenderType" = "Transparent""Queue" = "Transparent""DisableBatching" = "True"}
这次的Tags里有新东西DisableBatching:
总结来说就是,批处理可以减少draw call但是会导致顶点坐标从模型空间转换为世界空间,所以如果在后续的代码中我们要使用模型空间的坐标的话就无法使用,所以我们需要显式地禁止使用批处理。
// 声明枚举的关键词#pragma shader_feature _TYPE_SPHERICAL _TYPE_CYLINDRICAL
比起往常的shader多了一个shader_feature。
shader_feature本质上更像一个shader代码里的宏定义,我们可以根据不同的宏定义替换不同的shader变体。
v2f vert (appdata v){v2f o;// 计算面片朝向摄像机的前方向量float3 forward = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1)).xyz;// 判断Billboard的类型#if _TYPE_CYLINDRICALforward.y = 0;#endifforward = normalize(forward);// 当摄像机完全在面片正上方或者正下方的时候,旋转临时的上方向量float3 up = abs(forward.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);float3 right = normalize(cross(forward, up));up = normalize(cross(right, forward));// 将顶点在新的坐标系上移动位置float3 vertex = v.vertex.x * right + v.vertex.y * up;o.vertex = UnityObjectToClipPos(vertex);o.texcoord = v.texcoord;return o;}
我们将摄像机的世界空间坐标从世界坐标系转换到模型空间坐标系,获得该面片朝向摄像机的方向向量。如果是圆柱形的广告牌,我们将这个方向向量的y轴修改为0。否则如果该方向向量的y轴分量大于0.999(基本就是在纯上方)我们把向上的向量直接设置为z轴方向向量(此时forward向量和向上向量高度重合,叉乘大概率为0,后续计算无法展开),否则就设置为y轴方向向量,然后用前向向量和上方向量叉乘得到右侧向量,之后再用右向向量和前方向量叉乘得到上方向量。
最后我们把v的顶点坐标和纹理坐标都根据这个右向向量和上方向量更新之后即可,这样我们就实现了一个面片永远朝向摄像机的效果。
扭曲
效果如下:
可以看到这些形形色色的颜色球在的位置视线被扭曲了。
代码如下:
Shader "Chapter8/chapter8_5"
{Properties{_StrengthColor("Color strength", Float) = 1 // 颜色强度,默认1_DistortionStrength ("Distortion strength", Range(-2,2)) = 0.1 // 扭曲强度,范围-2到2,默认0.1_DistortionCircle ("Distortion circle", Range(0,1)) = 0 // 扭曲圆形范围,范围0到1,默认0_NormalTexture("Normal", 2D) = "blue" { } // 法线纹理,默认蓝色_NormalTexStrength("Normal strength", Range(0,1)) = 0.5 // 法线强度,范围0到1,默认0.5_NormalTexFrameless("Normal circle", Range(0,1)) = 0.5 // 法线圆形范围,范围0到1,默认0.5_UVOffset("UVOffset XY, ignore ZW", Vector) = (0,0.01,0,0) // UV偏移,默认(0, 0.01, 0, 0)}Category{Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" } // 渲染队列为透明,渲染类型为透明,忽略投影器Blend SrcAlpha OneMinusSrcAlpha // 混合模式:SrcAlpha, OneMinusSrcAlphaZWrite Off // 关闭深度写入SubShader{GrabPass // 抓取屏幕内容{Name "BASE"Tags { "LightMode" = "Always" }}Pass{Name "BASE"Tags { "LightMode" = "Always" }CGPROGRAM#pragma vertex vert // 顶点着色器#pragma fragment frag // 片段着色器#include "UnityCG.cginc" // 引入Unity的CG库sampler2D _GrabTexture; // 抓取的屏幕纹理float _DistortionStrength; // 扭曲强度float _DistortionCircle; // 扭曲圆形范围float _StrengthColor; // 颜色强度sampler2D _NormalTexture; // 法线纹理float4 _NormalTexture_ST; // 法线纹理的缩放和偏移float _NormalTexStrength; // 法线强度float _NormalTexFrameless; // 法线圆形范围float4 _UVOffset; // UV偏移// 顶点着色器输入结构struct VertexInput{float4 vertex : POSITION; // 顶点位置float2 texcoord0 : TEXCOORD0; // 纹理坐标float4 color : COLOR; // 顶点颜色};// 顶点着色器输出结构struct Vert2Frag{float4 position : SV_POSITION; // 裁剪空间中的顶点位置float4 uv_grab : TEXCOORD0; // 抓取纹理的UV坐标float2 uv : TEXCOORD1; // 纹理坐标float2 uv_normal : TEXCOORD2; // 法线纹理的UV坐标float2 movement: TEXCOORD3; // UV偏移运动float4 color : TEXCOORD4; // 顶点颜色};// 顶点着色器Vert2Frag vert (VertexInput vertIn){Vert2Frag output;output.position = UnityObjectToClipPos(vertIn.vertex); // 将顶点从对象空间转换到裁剪空间output.uv_grab = ComputeGrabScreenPos(output.position); // 计算抓取纹理的UV坐标output.uv = vertIn.texcoord0; // 传递纹理坐标output.uv_normal = vertIn.texcoord0.xy * _NormalTexture_ST.xy + _NormalTexture_ST.zw; // 计算法线纹理的UV坐标output.movement = _UVOffset.xy*_Time.y; // 计算UV偏移运动output.color = vertIn.color; // 传递顶点颜色return output;}// 获取从中心到当前UV的向量float2 getVectorFromCenter(float2 uv){float factor = _ScreenParams.y / _ScreenParams.x; // 计算屏幕宽高比float2 direction = float2((uv.x-0.5), (uv.y-0.5)) * factor; // 计算从中心到当前UV的向量return (direction);}// 获取扭曲强度float getDistortionStrength(float2 uv){float2 diff = float2(distance(0.5, uv.x), distance(0.5, uv.y)) * 2.0; // 计算UV到中心的距离float dist = saturate(length(diff)); // 计算距离并限制在0到1之间return 1.0-dist; // 返回扭曲强度}// 获取法线float2 getNormal(sampler2D _NormalTexture, float2 normalUv, float2 uv, float2 uvOffset, float frameless, float strength){float2 normal = tex2D( _NormalTexture, normalUv+uvOffset ).zy; // 从法线纹理中获取法线值float length = getDistortionStrength(uv); // 获取扭曲强度float normalTexStrength = ((1-frameless) + frameless*length) * strength; // 计算法线强度normal.x = ((normal.x-.5)*2) * normalTexStrength; // 调整法线X分量normal.y = ((normal.y-.5)*2) * normalTexStrength; // 调整法线Y分量return normal; // 返回法线}// 片段着色器half4 frag (Vert2Frag fragIn) : SV_Target{float4 uvScreen = UNITY_PROJ_COORD(fragIn.uv_grab); // 获取抓取纹理的UV坐标float2 direction = getVectorFromCenter(fragIn.uv); // 获取从中心到当前UV的向量float strength = getDistortionStrength(fragIn.uv); // 获取扭曲强度strength = (_DistortionCircle*strength + (1-_DistortionCircle)) * _DistortionStrength; // 计算最终扭曲强度direction *= strength; // 调整方向向量uvScreen += float4(direction.x, direction.y, 0, 0); // 调整抓取纹理的UV坐标float2 influence = normalize(direction) * strength; // 计算影响向量float2 offset = fragIn.movement; // 获取UV偏移float2 normal = getNormal(_NormalTexture, fragIn.uv_normal, fragIn.uv, offset, _NormalTexFrameless, _NormalTexStrength); // 获取法线uvScreen += float4(normal.x, normal.y, 0, 0); // 调整抓取纹理的UV坐标influence += normal.xy; // 调整影响向量float4 final = tex2Dproj(_GrabTexture, uvScreen); // 从抓取纹理中获取颜色float alpha = 1; // 设置透明度final = float4(final.xyz, alpha); // 设置最终颜色strength = saturate(sqrt(pow(abs(influence.x), 2.0) + pow(abs(influence.y), 2.0)) * _StrengthColor); // 计算最终强度final = final + (fragIn.color*strength); // 调整最终颜色final.w = saturate(final.w*fragIn.color.w); // 调整最终透明度return final; // 返回最终颜色}ENDCG}}}
}
我们应该首先能发现这一次的shader中没有Pass而是Category:
GrabPass // 抓取屏幕内容{Name "BASE"Tags { "LightMode" = "Always" }}
如果还记得我们透明章节的话,我们在那里介绍过GrabPass:从屏幕中抓取缓冲来使用。
// 顶点着色器输入结构struct VertexInput{float4 vertex : POSITION; // 顶点位置float2 texcoord0 : TEXCOORD0; // 纹理坐标float4 color : COLOR; // 顶点颜色};// 顶点着色器输出结构struct Vert2Frag{float4 position : SV_POSITION; // 裁剪空间中的顶点位置float4 uv_grab : TEXCOORD0; // 抓取纹理的UV坐标float2 uv : TEXCOORD1; // 纹理坐标float2 uv_normal : TEXCOORD2; // 法线纹理的UV坐标float2 movement: TEXCOORD3; // UV偏移运动float4 color : TEXCOORD4; // 顶点颜色};
顶点着色器的输入和输出都有很多东西,把顶点位置、纹理坐标和顶点颜色作为输入而输出裁剪空间的顶点位置、抓取的纹理坐标、纹理坐标、法线纹理坐标、UV的偏移和颜色。
// 顶点着色器Vert2Frag vert (VertexInput vertIn){Vert2Frag output;output.position = UnityObjectToClipPos(vertIn.vertex); // 将顶点从对象空间转换到裁剪空间output.uv_grab = ComputeGrabScreenPos(output.position); // 计算抓取纹理的UV坐标output.uv = vertIn.texcoord0; // 传递纹理坐标output.uv_normal = vertIn.texcoord0.xy * _NormalTexture_ST.xy + _NormalTexture_ST.zw; // 计算法线纹理的UV坐标output.movement = _UVOffset.xy*_Time.y; // 计算UV偏移运动output.color = vertIn.color; // 传递顶点颜色return output;}
这是顶点着色器的函数代码内容。
// 获取从中心到当前UV的向量float2 getVectorFromCenter(float2 uv){float factor = _ScreenParams.y / _ScreenParams.x; // 计算屏幕宽高比float2 direction = float2((uv.x-0.5), (uv.y-0.5)) * factor; // 计算从中心到当前UV的向量return (direction);}// 获取扭曲强度float getDistortionStrength(float2 uv){float2 diff = float2(distance(0.5, uv.x), distance(0.5, uv.y)) * 2.0; // 计算UV到中心的距离float dist = saturate(length(diff)); // 计算距离并限制在0到1之间return 1.0-dist; // 返回扭曲强度}// 获取法线float2 getNormal(sampler2D _NormalTexture, float2 normalUv, float2 uv, float2 uvOffset, float frameless, float strength){float2 normal = tex2D( _NormalTexture, normalUv+uvOffset ).zy; // 从法线纹理中获取法线值float length = getDistortionStrength(uv); // 获取扭曲强度float normalTexStrength = ((1-frameless) + frameless*length) * strength; // 计算法线强度normal.x = ((normal.x-.5)*2) * normalTexStrength; // 调整法线X分量normal.y = ((normal.y-.5)*2) * normalTexStrength; // 调整法线Y分量return normal; // 返回法线}
这里有三个函数,分别用于计算从中心到当前UV的向量,计算扭曲强度以及获取法线。
第一个函数,我们首先计算一个屏幕的高宽比,然后把uv的x轴和y轴各减去0.5之后乘以这个比值,因为我们知道uv坐标是一个从0到1的坐标系,所以这样能得到正确的向量。
第二个函数中,我们先计算uv坐标的xy坐标到屏幕的中心的距离,然后把这个距离限制在[0,1]之间,最后返回一减去这个距离即可。
第三个函数中,我们首先从法线纹理中获取到法线,然后使用第二个函数获取扭曲强度,然后我们根据扭曲强度和参数中的边缘衰减(frameless)来动态调整法线强度。
// 片段着色器half4 frag (Vert2Frag fragIn) : SV_Target{float4 uvScreen = UNITY_PROJ_COORD(fragIn.uv_grab); // 获取抓取纹理的UV坐标float2 direction = getVectorFromCenter(fragIn.uv); // 获取从中心到当前UV的向量float strength = getDistortionStrength(fragIn.uv); // 获取扭曲强度strength = (_DistortionCircle*strength + (1-_DistortionCircle)) * _DistortionStrength; // 计算最终扭曲强度direction *= strength; // 调整方向向量uvScreen += float4(direction.x, direction.y, 0, 0); // 调整抓取纹理的UV坐标float2 influence = normalize(direction) * strength; // 计算影响向量float2 offset = fragIn.movement; // 获取UV偏移float2 normal = getNormal(_NormalTexture, fragIn.uv_normal, fragIn.uv, offset, _NormalTexFrameless, _NormalTexStrength); // 获取法线uvScreen += float4(normal.x, normal.y, 0, 0); // 调整抓取纹理的UV坐标influence += normal.xy; // 调整影响向量float4 final = tex2Dproj(_GrabTexture, uvScreen); // 从抓取纹理中获取颜色float alpha = 1; // 设置透明度final = float4(final.xyz, alpha); // 设置最终颜色strength = saturate(sqrt(pow(abs(influence.x), 2.0) + pow(abs(influence.y), 2.0)) * _StrengthColor); // 计算最终强度final = final + (fragIn.color*strength); // 调整最终颜色final.w = saturate(final.w*fragIn.color.w); // 调整最终透明度return final; // 返回最终颜色}
我们的片元着色器就是重点了,可以看到很多内容啊,我们来看看AI怎么说吧:
扫描
这是一个更为复杂的项目,效果如下:
我们实现的效果是鼠标点击某个地点之后会发射这样一个扫描的波形。
这里就不只是一个shader可以实现的效果了,我们还需要一个C#脚本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 该脚本实现了一个扫描效果,当用户点击物体时,扫描效果会从该位置开始,并随时间扩展
// 这个脚本会在编辑模式下也生效([ExecuteInEditMode]属性)
[ExecuteInEditMode]
public class PosScanEffect : MonoBehaviour
{// 用于存储扫描效果的材质(shader),此材质会控制实际的视觉效果public Material ScanMat;// 控制扫描速度,扫描的范围会根据这个值逐渐增大public float ScanSpeed = 20;// 扫描计时器,决定扫描的进度,随着时间的推移,扫描的范围会增大public float scanTimer = 0;// 存储相机组件的引用private Camera scanCam;// 记录扫描的中心点,即鼠标点击时的物体位置private Vector3 ScanPoint = Vector3.zero;// 初始化方法,这里没有初始化操作void Awake(){}// 每一帧调用,主要用于计算扫描参数和更新扫描效果private void Update(){// 获取当前物体上的相机组件scanCam = GetComponent<Camera>();// 启用深度纹理(Depth)和法线深度纹理(DepthNormals),这些纹理对后续的渲染处理至关重要scanCam.depthTextureMode |= DepthTextureMode.Depth;scanCam.depthTextureMode |= DepthTextureMode.DepthNormals;// 获取相机的长宽比float aspect = scanCam.aspect;// 获取相机的远裁剪平面(远离相机的最大距离)float farPlaneDistance = scanCam.farClipPlane;// 根据相机的视野(field of view)计算出上方向量,用于定位视锥体的边界Vector3 midup = Mathf.Tan(scanCam.fieldOfView / 2 * Mathf.Deg2Rad) * farPlaneDistance * scanCam.transform.up;// 计算右方向量,同样用于确定视锥体的边界Vector3 midright = Mathf.Tan(scanCam.fieldOfView / 2 * Mathf.Deg2Rad) * farPlaneDistance * scanCam.transform.right * aspect;// 计算远裁剪平面的中心点位置Vector3 farPlaneMid = scanCam.transform.forward * farPlaneDistance;// 根据计算出的参数确定视锥体的四个角的世界坐标Vector3 bottomLeft = farPlaneMid - midup - midright;Vector3 bottomRight = farPlaneMid - midup + midright;Vector3 upLeft = farPlaneMid + midup - midright;Vector3 upRight = farPlaneMid + midup + midright;// 创建一个矩阵来表示视锥体的四个角Matrix4x4 frustumCorner = new Matrix4x4();frustumCorner.SetRow(0, bottomLeft); // 设置底左角frustumCorner.SetRow(1, bottomRight); // 设置底右角frustumCorner.SetRow(2, upRight); // 设置上右角frustumCorner.SetRow(3, upLeft); // 设置上左角// 将视锥体的矩阵传递给ShaderScanMat.SetMatrix("_FrustumCorner", frustumCorner);// 进行射线检测,获取鼠标点击的世界坐标RaycastHit hit;Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // 将鼠标屏幕坐标转换为射线if (Input.GetMouseButton(0) && Physics.Raycast(ray, out hit)) // 如果按下左键并且射线与物体碰撞{scanTimer = 0; // 重置扫描计时器ScanPoint = hit.point; // 获取碰撞点的位置作为扫描的起始点}// 增加扫描计时器,控制扫描的进度scanTimer += Time.deltaTime;// 将扫描的起始位置和扫描进度传递给材质(Shader)ScanMat.SetVector("_ScanCenter", ScanPoint);ScanMat.SetFloat("_ScanRange", scanTimer * ScanSpeed);// 将相机的世界坐标变换矩阵传递给ShaderScanMat.SetMatrix("_CamToWorld", scanCam.cameraToWorldMatrix);}// 在渲染图像时调用,进行后处理操作,应用扫描效果private void OnRenderImage(RenderTexture source, RenderTexture destination){// 将相机的远裁剪平面距离传递给材质(Shader),用于控制扫描的范围ScanMat.SetFloat("_CamFar", GetComponent<Camera>().farClipPlane);// 使用Graphics.Blit方法将源纹理渲染到目标纹理,并应用扫描效果的材质Graphics.Blit(source, destination, ScanMat);}
}
具体的代码作用注释里已经写明了。
我们重点还是来看看着色器的写法:
Shader "Chapter2/PointScanShader"
{Properties{// 主要纹理,用于物体表面的纹理_MainTex ("Texture", 2D) = "white" {}// 扫描纹理,用于显示扫描的图像或效果_ScanTex("ScanTexure", 2D) = "white" {}// 扫描的范围,控制扫描从中心开始的距离_ScanRange("ScanRange", float) = 0// 扫描宽度,控制扫描的宽度,影响扫描的可见区域_ScanWidth("ScanWidth", float) = 0// 扫描的背景颜色_ScanBgColor("ScanBgColor", color) = (1, 1, 1, 1)// 扫描时网格的颜色_ScanMeshColor("ScanMeshColor", color) = (1, 1, 1, 1)// 网格线的宽度_MeshLineWidth("MeshLineWidth", float) = 0.3// 网格的宽度,用于控制网格分割的尺寸_MeshWidth("MeshWidth", float) = 1// 缝隙的平滑度,控制缝隙的过渡效果_Smoothness("SeamBlending", Range(0, 0.5)) = 0.25}SubShader{// 不使用背面剔除(Cull Off),不写入深度缓存(ZWrite Off),且始终通过深度测试(ZTest Always)//Cull Off ZWrite Off ZTest AlwaysPass{CGPROGRAM#pragma vertex vert#pragma fragment frag// 引用Unity的内置CG代码库#include "UnityCG.cginc"// 定义顶点着色器的数据结构struct appdata{float4 vertex : POSITION; // 顶点位置float2 uv : TEXCOORD0; // 顶点纹理坐标};// 定义片段着色器的数据结构struct v2f{float2 uv : TEXCOORD0; // 传递给片段着色器的纹理坐标float2 uv_depth : TEXCOORD1; // 用于传递深度信息的纹理坐标float4 interpolatedRay : TEXCOORD2; // 传递与扫描效果相关的视锥体数据float4 vertex : SV_POSITION; // 顶点最终位置};// 定义一个矩阵,用于描述视锥体四个角的坐标float4x4 _FrustumCorner;// 顶点着色器:计算顶点的最终位置,并计算视锥体四个角的插值数据v2f vert(appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex); // 计算顶点的最终位置o.uv = v.uv; // 将纹理坐标传递给片段着色器o.uv_depth = v.uv; // 将纹理坐标传递给深度计算// 根据UV坐标的不同区域选择不同的视锥体角int rayIndex;if (v.uv.x < 0.5 && v.uv.y < 0.5){rayIndex = 0;}else if (v.uv.x > 0.5 && v.uv.y < 0.5){rayIndex = 1;}else if (v.uv.x > 0.5 && v.uv.y > 0.5){rayIndex = 2;}else{rayIndex = 3;}// 从视锥体四个角中选择一个,传递给片段着色器o.interpolatedRay = _FrustumCorner[rayIndex];return o;}// 声明材质参数,允许外部设置sampler2D _MainTex;sampler2D _ScanTex;float _ScanRange;float _ScanWidth;float3 _ScanCenter;fixed4 _ScanBgColor;fixed4 _ScanMeshColor;float _MeshLineWidth;float _MeshWidth;float4x4 _CamToWorld;fixed _Smoothness;// 声明用于获取深度信息的纹理sampler2D_float _CameraDepthTexture;sampler2D _CameraDepthNormalsTexture;// 片段着色器:计算像素的最终颜色fixed4 frag(v2f i) : SV_Target{float tempDepth;half3 normal; // 获取该像素的法线和深度值DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), tempDepth, normal);// 将法线从相机空间转换到世界空间normal = mul((float3x3)_CamToWorld, normal);normal = normalize(max(0, (abs(normal) - _Smoothness))); // 对法线进行平滑处理// 获取该像素的颜色fixed4 col = tex2D(_MainTex, i.uv);// 通过深度纹理获取该像素的深度值,并转换为线性深度float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);float linearDepth = Linear01Depth(depth);// 计算该像素在世界空间中的位置float3 pixelWorldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay;// 计算像素到扫描中心的距离float pixelDistance = distance(pixelWorldPos, _ScanCenter);// 计算该像素的方向向量float3 pixelDir = pixelWorldPos - _ScanCenter;// 使用网格宽度将像素位置进行取模操作,用于创建网格效果float3 modulo = pixelWorldPos - _MeshWidth * floor(pixelWorldPos / _MeshWidth);modulo = modulo / _MeshWidth;// 使用平滑插值创建网格线效果float3 meshCol = smoothstep(_MeshLineWidth, 0, modulo) + smoothstep(1 - _MeshLineWidth, 1, modulo);// 将扫描背景颜色和网格颜色进行插值fixed4 scanMeshCol = lerp(_ScanBgColor, _ScanMeshColor, saturate(dot(meshCol, 1 - normal)));// 如果像素距离扫描中心在指定范围内,执行扫描效果if (_ScanRange - pixelDistance > 0 && _ScanRange - pixelDistance < _ScanWidth && linearDepth < 1){// 根据像素距离计算扫描百分比fixed scanPercent = 1 - (_ScanRange - pixelDistance) / _ScanWidth;// 通过插值将当前颜色与网格颜色混合,生成扫描效果col = lerp(col, scanMeshCol, scanPercent);}// 返回最终颜色return col;}ENDCG}}
}
Properties{// 主要纹理,用于物体表面的纹理_MainTex ("Texture", 2D) = "white" {}// 扫描纹理,用于显示扫描的图像或效果_ScanTex("ScanTexure", 2D) = "white" {}// 扫描的范围,控制扫描从中心开始的距离_ScanRange("ScanRange", float) = 0// 扫描宽度,控制扫描的宽度,影响扫描的可见区域_ScanWidth("ScanWidth", float) = 0// 扫描的背景颜色_ScanBgColor("ScanBgColor", color) = (1, 1, 1, 1)// 扫描时网格的颜色_ScanMeshColor("ScanMeshColor", color) = (1, 1, 1, 1)// 网格线的宽度_MeshLineWidth("MeshLineWidth", float) = 0.3// 网格的宽度,用于控制网格分割的尺寸_MeshWidth("MeshWidth", float) = 1// 缝隙的平滑度,控制缝隙的过渡效果_Smoothness("SeamBlending", Range(0, 0.5)) = 0.25
一些主要的属性,含义已在注释中写明。
// 定义片段着色器的数据结构struct v2f{float2 uv : TEXCOORD0; // 传递给片段着色器的纹理坐标float2 uv_depth : TEXCOORD1; // 用于传递深度信息的纹理坐标float4 interpolatedRay : TEXCOORD2; // 传递与扫描效果相关的视锥体数据float4 vertex : SV_POSITION; // 顶点最终位置};
顶点着色器的输出以及片元着色器的输入中除了uv坐标和顶点坐标以外还有一个传递深度信息的uv坐标以及一个与扫描效果相关的变量。
// 定义一个矩阵,用于描述视锥体四个角的坐标float4x4 _FrustumCorner;
注意这里定义矩阵的方式:float4*4,这个矩阵用来描述视锥体的四个角。
// 顶点着色器:计算顶点的最终位置,并计算视锥体四个角的插值数据v2f vert(appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex); // 计算顶点的最终位置o.uv = v.uv; // 将纹理坐标传递给片段着色器o.uv_depth = v.uv; // 将纹理坐标传递给深度计算// 根据UV坐标的不同区域选择不同的视锥体角int rayIndex;if (v.uv.x < 0.5 && v.uv.y < 0.5){rayIndex = 0;}else if (v.uv.x > 0.5 && v.uv.y < 0.5){rayIndex = 1;}else if (v.uv.x > 0.5 && v.uv.y > 0.5){rayIndex = 2;}else{rayIndex = 3;}// 从视锥体四个角中选择一个,传递给片段着色器o.interpolatedRay = _FrustumCorner[rayIndex];return o;}
我们根据不同的视锥体的位置来给定一个序号,从这四个序号中选择一个传给片元着色器。
// 片段着色器:计算像素的最终颜色
fixed4 frag(v2f i) : SV_Target
{float tempDepth;half3 normal; // 获取该像素的法线和深度值DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), tempDepth, normal);// 将法线从相机空间转换到世界空间normal = mul((float3x3)_CamToWorld, normal);normal = normalize(max(0, (abs(normal) - _Smoothness))); // 对法线进行平滑处理// 获取该像素的颜色fixed4 col = tex2D(_MainTex, i.uv);// 通过深度纹理获取该像素的深度值,并转换为线性深度float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);float linearDepth = Linear01Depth(depth);// 计算该像素在世界空间中的位置float3 pixelWorldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay;// 计算像素到扫描中心的距离float pixelDistance = distance(pixelWorldPos, _ScanCenter);// 计算该像素的方向向量float3 pixelDir = pixelWorldPos - _ScanCenter;// 使用网格宽度将像素位置进行取模操作,用于创建网格效果float3 modulo = pixelWorldPos - _MeshWidth * floor(pixelWorldPos / _MeshWidth);modulo = modulo / _MeshWidth;// 使用平滑插值创建网格线效果float3 meshCol = smoothstep(_MeshLineWidth, 0, modulo) + smoothstep(1 - _MeshLineWidth, 1, modulo);// 将扫描背景颜色和网格颜色进行插值fixed4 scanMeshCol = lerp(_ScanBgColor, _ScanMeshColor, saturate(dot(meshCol, 1 - normal)));// 如果像素距离扫描中心在指定范围内,执行扫描效果if (_ScanRange - pixelDistance > 0 && _ScanRange - pixelDistance < _ScanWidth && linearDepth < 1){// 根据像素距离计算扫描百分比fixed scanPercent = 1 - (_ScanRange - pixelDistance) / _ScanWidth;// 通过插值将当前颜色与网格颜色混合,生成扫描效果col = lerp(col, scanMeshCol, scanPercent);}// 返回最终颜色return col;
}
首先我们使用DecodeDepthNormal函数从_CameraDepthNormalsTexture中获取到法线和深度值,然后将法线经过一系列处理转换到世界空间。我们将深度值转换成线性深度之后用来计算该像素在世界空间的位置与扫描中心的距离,同时计算这个像素到扫描中心的向量。因为我们要实现网格效果,网格效果是一格一格实现的,所以我们需要将像素的位置取模来确定具体在第几个网格,然后将背景的颜色和网格颜色进行插值实现混合的效果,最终返回颜色。
关于线性深度:
相关文章:
Unity-Shader详解-其五
关于Unity的Shader部分的基础知识其实已经讲解得差不多了,今天我们来一些实例分享: 溶解 效果如下: 代码如下: Shader "Chapter8/chapter8_1" {Properties{// 定义属性[NoScaleOffset]_Albedo("Albedo", 2…...
从零打造个人博客静态页面与TodoList应用:前端开发实战指南
前言 在当今数字时代,拥有个人博客和高效的任务管理工具已成为开发者展示自我和提升生产力的标配。本文将带你从零开始,通过纯前端技术实现一个兼具个人博客静态页面和TodoList任务管理功能的综合应用。无论你是前端新手还是希望巩固基础的中级开发者&a…...
开发者如何优雅应对HTTPS抓包难题
开发者如何优雅应对HTTPS抓包难题:工具实战 深度解析 调试HTTPS接口这件事,真是程序员永远的痛。特别是在移动端、或者遇到客户端集成了第三方安全SDK的项目时,网络调试的门槛几乎成倍提升。你可能也遇到过:Charles不识别证书、…...
Ubuntu 安装远程桌面连接RDP方式
1. 安装 XFCE4 桌面环境 如果你的 Ubuntu 系统默认使用 GNOME 或其它桌面环境,可以安装轻量级的 XFCE4: sudo apt update sudo apt install xfce4 xfce4-goodies 说明:xfce4-goodies 包含额外的插件和工具(如面板插件、终端等&a…...
Ubuntu 22.04 出现 ‘Temporary failure resolving‘ 解决方案
a、使用apt 安装 resolvconf sudo apt-get install resolvconf b、使用 cd /etc/resolvconf/resolv.conf.d/ 进入文件夹,使用 ls 查看目录,会显示 base head tail c、使用 sudo vim base 编辑base文件, 进入时为空,添加 name…...
ubuntu 22.04 换源
参考:清华大学开源软件镜像站 ubuntu | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror...
Java并发编程几个问题的解答
目录 1、以前你编写的Java程序同时能做几件事情?有几个执行流程?main方法执行完,整个程序一定会退出吗?2、早期的电脑一般是单核CPU,但那时我们就可以在编写程序的同时听歌,你觉得其CPU可以同时执行两个程序…...
JavaScript中数组和对象不同遍历方法的顺序规则
在JavaScript中,不同遍历方法的顺序规则和适用场景存在显著差异。以下是主要方法的遍历顺序总结: 一、数组遍历方法 for循环 • 严格按数组索引顺序遍历(0 → length-1) • 支持break和continue中断循环 • 性能最优,…...
C++ STL入门:set 集合容器
C STL入门:set 集合容器 一、核心特性与适用场景 set 是 C STL 提供的关联式容器,基于红黑树实现,具有两大核心特性: 特性表现形式底层原理元素唯一性重复值自动去重插入时进行二叉树键比对自动排序元素默认升序排列红黑树中序遍…...
[论文笔记] 超详细解读DeepSeek v3全论文技术报告
DeepSeek-V3是一个强大的专家混合(Mixture-of-Experts,MoE)语言模型,总共671B参数,每个token激活37B参数(可以理解为有多个专家,但每个token只会选择一部分专家进行推理,所以一个token的预测,只会用到37B参数),DeepSeek-V3 使用了 多头潜在注意力(...
JS 问号(?)运算符避免中间报错
一、场景 在前端开发过程中,有一些情况比如某些属性可能由于渲染数据的时机不同,一开始是null 或者undifine, 这样访问下面的属性的时候就会报错,我们可以给每个层级后面加个? 就可以避免这个错误。 let data {user: {profile: {name: &q…...
4:点云处理—去噪、剪切、调平
1.点云去噪 dev_clear_window ()dev_open_window(0, 0, 560, 560, black, WindowHandle)GenParamNames : [lut,intensity,light_position,disp_pose,alpha]GenParamValues : [color1,coord_z,0.0 0.0 -0.3 1.0,true,1]DispPose : [0,-0.0005,717.04,280,0,20,0]Instructions[0]…...
机器学习实操 第二部分 神经网路和深度学习 第17章 编码器、生成对抗网络和扩散模型
机器学习实操 第二部分 神经网路和深度学习 第17章 编码器、生成对抗网络和扩散模型 内容概要 第17章深入探讨了自编码器(Autoencoders)、生成对抗网络(GANs)和扩散模型(Diffusion Models)。这些模型能够…...
【今日三题】ISBN号码(模拟) / kotori和迷宫(BFS最短路) / 矩阵最长递增路径(dfs)
⭐️个人主页:小羊 ⭐️所属专栏:每日两三题 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 ISBN号码(模拟)kotori和迷宫(BFS最短路)矩阵最长递增路径(dfs) ISBN号码(模拟) ISBN号码 #include <iostream> #incl…...
【记录】HunyuanVideo 文生视频工作流
HunyuanVideo 文生视频工作流指南 概述 本指南详细介绍如何在ComfyUI中使用腾讯混元HunyuanVideo模型进行文本到视频生成的全流程操作,包含环境配置、模型安装和工作流使用说明。 参考:https://comfyui-wiki.com/zh/install/install-comfyui/install-c…...
DevExpressWinForms-布局之TablePanel
布局之TablePanel 在 DevExpress 的控件库中,TablePanel 是一个功能强大且灵活的布局控件,它能够以表格形式组织和排列其他控件,让界面布局更加规整、有序。无论是开发复杂的企业级应用程序,还是设计简洁美观的用户界面ÿ…...
MySQL 数据库初体验
目录 1.1 数据库简介 1.1.1 使用数据库的必要性 1.1.2 数据库的基本概念 1.数据 2.数据库和数据库表 3.数据库管理系统和数据库系统 1.1.3 数据库发展史 1.数据库系统发展史 (1)初级阶段——第一代数据库 (2)中级阶段—…...
flink超时未揽收单量统计
应用场景: 双十一大屏统计 - - 订单超时汇总 项目指标概况: 应用背景:晚点超时指标,例如:出库超6小时未揽收订单量 难点:flink消息触发式计算,没有消息到达则无法计算,而这类指标…...
【造包工具】【Xcap】精讲Xcap构造分片包(IPv4、ipv6、4G\5G等pcap均可),图解超赞超详细!!!
目录 前言 1. XCap工具概念介绍 2. Xcap环境说明 2.1 新建报文组 2.2 导入数据包 2.3 查看报文组 2.4 复制删除报文组 3. 构造分片包 3.1 造普通/外层分片步骤: 3.2 造内层分片步骤 3.2.1 建立一个新报文 3.2.2 将组装的新报文分片 3.2.3 替换原始包内层…...
RabbitMQ学习(第二天)
文章目录 1、生产者可靠性①、生产者重连②、生产者确认 2、MQ可靠性①、数据持久化②、LazyQueue(惰性队列) 3、消费者可靠性①、消费者确认②、失败重试机制③、保证业务幂等性 总结 之前的学习中,熟悉了java中搭建和操作RabbitMQ发送接收消息,熟悉使用…...
旧版 Flutter 写的项目, 想要在新的环境上运行?
DeepSeek 给出的最佳实践 以下是针对拷贝 Flutter 项目到新环境运行的 完整检查清单和最佳实践,覆盖了环境配置、版本兼容性、依赖管理等多个关键点: 📋 完整检查清单 检查项操作方式/命令重要性1. Flutter SDK 版本flutter --version 对比…...
Flutter接入ProtoBuff和原生Android通信【性能最优】
Protocol Buffers(简称Protobuf)是由 Google 开发的一种结构化数据序列化框架,旨在实现高效的数据交换与存储。其核心特性及优势如下: 一、核心特性 跨语言与跨平台 支持多种编程语言(如 C、Java、Python、Dart …...
【MySQL】(10)用户和权限管理
一、应用场景 通常一个应用对应一个数据库,我们希望某个数据库只能被相关人员操纵,就需要创建用户并指定权限。只有登录该用户,才能在权限范围内操纵数据库。root 是权限最高的用户,它拥有所有的权限。 二、查询用户 在 mysql 数…...
学成在线之缓存
一:缓存 把白名单可以看到的信息和学生用户下的我的学习,我的选课等这些信息,存到缓存中,因为这些查询量比较大。 当查询时,先去检查缓存中是否有这个数据,如果有,就直接返回 如果没有&#…...
视频编解码学习8之视频历史
视频技术的发展历史可以追溯到19世纪,至今已跨越近200年。以下是视频技术发展的主要阶段和里程碑: 1. 早期探索阶段(19世纪-1920年代) 1832年:约瑟夫普拉托(Joseph Plateau)发明"费纳奇镜&…...
从装饰器出发,优雅处理 UI 自动化中的异常
二、装饰器核心原理:函数式编程的“语法糖” 在 UI 自动化测试的实际场景中,页面加载超时、元素定位失败、网络波动等异常问题频繁出现。传统的try-except嵌套方式虽然能捕获异常,但会导致业务代码与处理逻辑高度耦合,造成代码冗…...
华为首款鸿蒙电脑正式亮相
在2025年5月8日的鸿蒙电脑技术与生态沟通会上,华为终端BG平板与PC产品线总裁朱懂东发表演讲。鸿蒙办公将支持无缝协同(统一账户 / 多端同步)、智慧体验(鸿蒙 AI / 智慧交互)、融合生态(桌面生态 / 移动生态…...
森林生态学研究深度解析:R语言入门、生物多样性分析、机器学习建模与群落稳定性评估
在生态学研究中,森林生态系统的结构、功能与稳定性是核心研究内容之一。这些方面不仅关系到森林动态变化和物种多样性,还直接影响森林提供的生态服务功能及其应对环境变化的能力。森林生态系统的结构主要包括物种组成、树种多样性、树木的空间分布与密度…...
需求分析阶段测试工程师主要做哪些事情
在软件测试需求分析阶段,主要围绕确定测试范围、明确测试目标、细化测试内容等方面开展工作,为后续测试计划的制定、测试用例的设计以及测试执行提供清晰、准确的依据。以下是该阶段具体要做的事情: 1. 需求收集与整理 收集需求文档&#x…...
IBM BAW(原BPM升级版)使用教程第五讲
结前篇! 一、服务:外部服务 在 IBM Business Automation Workflow (BAW) 中,外部服务(External Services)是指在流程中调用和集成外部系统或服务的组件。外部服务允许IBM BAW与其他业务系统、应用程序或第三方服务进行…...
一个用C#开发的记事本Notepads开源编辑器
从零学习构建一个完整的系统 推荐一个用C#开发的,模仿Nodepad的记事本编辑器。 项目简介 这是一个国人开发的,采用Fluent Design风格,支持Markdown语法及其预览,可以用于替代Windows自带记事本功能。 虽然它没有Nodepad、VS C…...
Office宏病毒钓鱼-打点突破
Office宏钓鱼 [攻击路径] / [利用流程] - 详细步骤:制作一个简单的Word宏钓鱼文档 步骤一:规划恶意宏功能 (Payload Design) 目的: 决定宏执行后要达成的目标。为教学演示,我们设定一个简单的目标:弹出一个消息框&…...
神经网络—感知器、多层感知器
文章目录 前言一、生物神经元与感知器的类比二、感知器1、简单感知器2、多层感知器(1)多层感知机结构 3、神经网络结构 总结1、感知器的局限性如何突破感知器的局限性? 2、感知器的应用 前言 感知器(Perceptron)是神经…...
美化IDEA注释:Idea 中快捷键 Ctrl + / 自动注释的缩进(避免添加注释自动到行首)以及 Ctrl + Alt + l 全局格式化代码的注释缩进
打开 Settings 界面,依次选择 Editor -> Code Style -> Java,选择 Code Generation, 取消 Line comment at first column 和 Block comment at first column 的勾选即可, 1、Line comment at first column (行注释在第一列…...
基于大型语言模型的高效时间序列预测模型选择
时间序列预测在金融、医疗、能源等多个领域具有重要意义,其结果对于决策和规划起到关键作用。然而,选择合适的预测模型往往需要丰富的领域知识和大量计算资源。Abdallah等(2022)的研究表明,没有单一算法能在所有预测任…...
2025 EAU UTUC指南学习笔记③:诊断策略精读——从症状到活检,如何科学判断治疗路径?
👋 欢迎关注我的学习专栏~ 如果觉得文章有帮助,别忘了点赞、关注、评论,一起学习 本文聚焦UTUC的诊断策略,全面梳理影像学检查、尿液学检测、内镜评估与活检手段,并结合指南推荐等级,提出科研视角的思考。 …...
【Ubuntu】Netplan静态网络配置
1.配置文件 # 默认配置文件:/etc/netplan/*.yaml# 本机 rootubuntu-server:~# vim /etc/netplan/50-cloud-init.yaml 2.DHCP network: ethernets:ens33:dhcp4: trueversion: 2 3.静态IP设置 注意:netplan 说明文件格式存储在下列路径下…...
【SQLSERVER】Ubuntu 连接远程 SQL Server(MSSQL)
Ubuntu 连接远程 SQL Server(MSSQL) 引言 在企业级开发环境中,跨平台的数据库连接是常见需求。虽然 SQL Server 通常在 Windows 服务器上运行,但在 Ubuntu 上连接远程 SQL Server 也非常重要。本文将详细介绍如何在 Ubuntu 上通…...
GuPPy-v1.2.0安装与使用-生信工具52
GuPPy:Python中用于光纤光度数据分析的免费开源工具 01 背景 Basecalling 是将原始测序信号转换为碱基序列的过程,通俗地说,就是“把碱基识别出来”。这一过程在不同代测序技术中各不相同: 一代测序是通过解析峰图实现࿱…...
Mysql-OCP PPT课程讲解并翻译
#跳过介绍,直接从干货开始记录 第一章 安装mysql...
maven 依赖冲突异常分析
异常如下 Description:An attempt was made to call a method that does not exist. The attempt was made from the following location:com.google.common.cache.CacheBuilder.initialCapacity(CacheBuilder.java:353)The following method did not exist:com.google.common…...
【软件设计师:软件工程】9.软件开发模型与方法
一、软件危机与软件工程 软件危机与软件工程是计算机科学发展中密切相关的两个概念。 1.软件危机(Software Crisis) 背景:20世纪60年代至80年代,随着计算机硬件性能提升,软件规模与复杂度剧增,传统开发方法难以应对,导致大量项目失败。 表现: 成本失控:开发周期长、…...
用uniapp在微信小程序实现画板(电子签名)功能,使用canvas实现功能
效果: 功能:实现重签 退出 保存 等功能 解决的问题: 电子签名画布抖动问题解 注意: 保存的时候上传到自己的服务器地址,后端返回图片地址 代码: <template><view><view class"signature&qu…...
旅游设备生产企业的痛点 质检系统在旅游设备生产企业的应用
在旅游设备制造行业,产品质量直接关系到用户体验与企业口碑。从景区缆车、观光车到水上娱乐设施,每一件设备的安全性与可靠性都需经过严苛检测。然而,传统质检模式常面临数据分散、流程不透明、合规风险高等痛点,难以满足旅游设备…...
为什么使用Less替代原始CSS?
What is Less? Less 是一种 CSS 预处理器,它在 CSS 的基础上添加了许多有用的特性,提供了更高效、更灵活的方式来编写样式代码。 特性: 1、变量(Variables) 优势: 可以定义一个值并在整个样…...
C++ - 类和对象 #日期类的实现
文章目录 前言 一、导言 二、构造 三、比较大小 1、实现大于 2、等于 3、大于等于 4、小于 5、小于等于 6、不等于 二、加减 1、加与加等 2、减与减等 3、、-- 4、日期-日期 三、流提取、流插入 1、流插入 2、流提取 四、日期类所有代码汇总 总结 前言 路…...
6:点云处理—QT三维窗口显示
1.编写halcon显示程序 导出C格式 *读取三维点云 dev_get_window(WindowHandle)dev_open_window(0, 0, 512, 512, black, WindowHandle1)*512, 512 CamParam : [0.01,0,7e-6,7e-6,512/2, 512/2,512, 512] DisPose:[30.427, -1.05274, 97.8798, 8.29326, 356.416, 99.016, 0]*彩…...
css3伸缩盒模型第三章(伸缩相关)
伸缩盒模型第三章(伸缩) 介绍了伸缩性的伸缩提现到哪些方面,以及flex 居中的小技巧,视觉差异等 flex 水平垂直居中伸缩性flex 复合式属性项目排序侧轴单独对齐 flex 水平居中 我们可以使用flex 使元素居中对齐,当然也可以用 margin 等进行…...
【Ubuntu】安裝向日葵远程控制
前言 在Ubuntu 24.04.2下安装向日葵远程控制出错,少了一些依赖,需要安装一些依赖。 1.安装gconf2-common wget http://mirrors.kernel.org/ubuntu/pool/universe/g/gconf/gconf2-common_3.2.6-6ubuntu1_all.deb sudo dpkg -i gconf2-common_3.2.6-6ub…...
Screeps Arena基础入门
本文主要内容 JavaSsript语法使用VScode编译环境Screeps Arena游戏规则 JavaSsript语法使用 基本数据类型 // String, Numker,Boolean,null, undefined const username "John"; const age 30; const rate 4.5; const iscool true; const x null; #表示值为…...