19.噪声-消融效果-具体实现
19.1 知识点
知识回顾:消融效果 基本原理
消融效果的基本原理是通过对比噪声纹理的采样值与消融进度参数,剔除低于阈值的像素,同时在边缘添加渐变颜色,实现动态溶解效果。
- 剔除像素:利用
clip(噪声纹理值 – 消融进度参数)
来剔除不需要的像素。 - 处理边缘:
- 使用
smoothstep
函数结合消融阈值计算平滑过渡系数。 - 利用
lerp
在原始颜色和边缘渐变颜色之间进行插值,从而确定最终使用的颜色。 - 可通过自定义参数来控制边缘效果。
- 使用
准备工作
导入纹理
导入噪声纹理和渐变纹理。
新建 Shader
新建 Shader 名称为 Lesson19_Dissolve
,复制 Shader 开发入门中用于计算切线空间下法线纹理的ShaderLesson29_Bump_Texture_Tangent
。
Shader "Unlit/Lesson19_Dissolve"
{
Properties
{
_MainColor("MainColor", Color) = (1,1,1,1)// 漫反射颜色
_MainTex("MainTex", 2D) = ""{}// 单张纹理
_BumpMap("BumpMap", 2D) = ""{}// 法线纹理
_BumpScale("BumpScale", Range(0,1)) = 1// 凹凸程度
_SpecularColor("SpecularColor", Color) = (1,1,1,1)// 高光反射颜色
_SpecularNum("SpecularNum", Range(0,256)) = 18// 光泽度
}
SubShader
{
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度
struct v2f
{
//裁剪空间下坐标
float4 pos:SV_POSITION;
//纹理uv
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//float2 uvTex:TEXCOORD0;//颜色纹理
//float2 uvBump:TEXCOORD1;//法线纹理
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0; //纹理变量
//光的方向 相对于切线空间下的
float3 lightDir:TEXCOORD1;
//视角的方向 相对于切线空间下的
float3 viewDir:TEXCOORD2;
};
// 顶点着色器中传入:
// 可以使用 UnityCG.cginc 中的 appdata_full
// 其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//单张纹理和法线纹理 UV坐标缩放偏移计算
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
//切线、副切线、法线
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条(确定副切线方向)
//cross是叉乘 appdata_full.tangent是切线 appdata_full.normal是法线 appdata_full.tangent.w代表叉乘结果方向
float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
tangent.w;
//构建模型空间到切线空间的变换矩阵
float3x3 rotation = float3x3(appdata_full.tangent.xyz,
binormal,
appdata_full.normal);
//模型空间下的光的方向
//v2f.lightDir = ObjSpaceLightDir(appdata_full.vertex);
//乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));
//模型空间下的视角的方向
//v2f.viewDir = ObjSpaceViewDir(appdata_full.vertex);
//乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 视角的方向了
v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));
return v2f;
}
// 片元着色器中传入:
// 自定义一个结构体
// 其中包含 裁剪空间下坐标、uv坐标、光的方向、视角的方向
fixed4 frag(v2f v2f) : SV_Target
{
//纹理采样函数tex2D
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度
//注意 tangentNormal不要进行单位化 直接用即可 因为乘了BumpScale就不是单位向量了
tangentNormal *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(切线坐标系下的法线, 光的方向))
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(
0, dot(tangentNormal, normalize(v2f.lightDir)));
//半角向量 = 视角方向 + 光的方向
float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));
//高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(切线坐标系下的法线, 半角向量)), 光泽度)
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
max(0, dot(tangentNormal, halfA)), _SpecularNum);
//布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
return fixed4(color.rgb, 1);
}
ENDCG
}
}
}
创建材质,将新建的 Shader 材质赋值给新建的机器人模型。
实现消融效果
主要步骤
添加属性:
_Noise
噪声纹理_Gradient
渐变纹理_Dissolve
消融进度_EdgeRange
边缘范围
属性映射:在 Shader 的 Properties 中添加上述参数,并在材质属性声明中定义变量。
结构体扩展:在顶点到片元传递的结构体中加入噪声纹理的 UV 坐标。
顶点着色器:计算噪声纹理的缩放偏移。
片元着色器:
- 采样噪声纹理,通过
clip
函数实现溶解区域的剔除。 - 使用
smoothstep
计算边缘过渡值,并通过lerp
实现模型原始颜色与边缘渐变颜色之间的插值。
- 采样噪声纹理,通过
添加属性,包括噪声纹理,渐变纹理,消融进度,边缘范围四个参数。并且添加属性映射
Properties
{
// 基础材质属性
_MainColor("MainColor", Color) = (1,1,1,1) // 主颜色,用于混合纹理颜色
_MainTex("MainTex", 2D) = "white" {} // 主纹理贴图
_BumpMap("BumpMap", 2D) = "bump" {} // 法线贴图
_BumpScale("BumpScale", Range(0,1)) = 1 // 法线强度调节系数
_SpecularColor("SpecularColor", Color) = (1,1,1,1) // 高光反射颜色
_SpecularNum("SpecularNum", Range(8,256)) = 18 // 高光反射指数(光泽度)
// 溶解效果相关属性
_Noise("Noise", 2D) = "white" {} // 噪声纹理(决定溶解区域)
_Gradient("Gradient", 2D) = "white" {} // 渐变纹理(控制溶解边缘颜色)
_Dissolve("Dissolve", Range(0,1)) = 0 // 溶解进度(0:完整,1:完全溶解)
_EdgeRange("EdgeRange", Range(0,1)) = 0 // 溶解边缘过渡宽度
}
// 材质属性声明
float4 _MainColor; // 漫反射颜色
sampler2D _MainTex; // 颜色纹理
float4 _MainTex_ST; // 颜色纹理的缩放和平移
sampler2D _BumpMap; // 法线纹理
float4 _BumpMap_ST; // 法线纹理的缩放和平移
float _BumpScale; // 凹凸程度
float4 _SpecularColor; // 高光颜色
fixed _SpecularNum; // 光泽度
// 溶解效果属性
sampler2D _Noise; // 噪声纹理
float4 _Noise_ST; // 噪声纹理的缩放和平移
sampler2D _Gradient; // 渐变纹理
fixed _Dissolve; // 消融进度
fixed _EdgeRange; // 消融边界宽窄范围
结构体中加入噪声纹理 UV
struct v2f
{
float4 pos : SV_POSITION; // 裁剪空间顶点位置
float4 uv : TEXCOORD0; // xy:主纹理 UV,zw:法线纹理 UV
float2 uvNoise : TEXCOORD1; // 噪声纹理 UV
float3 lightDir : TEXCOORD2; // 切线空间光照方向
float3 viewDir : TEXCOORD3; // 切线空间观察方向
};
顶点着色器计算噪声纹理的缩放偏移
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//单张纹理和法线纹理 UV坐标缩放偏移计算
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//计算噪声纹理的缩放偏移
v2f.uvNoise = appdata_full.texcoord.xy * _Noise_ST.xy + _Noise_ST.zw;
//...
}
片元着色器从噪声纹理中采样,利用clip函数进行剔除
fixed4 frag(v2f v2f) : SV_Target
{
//剔除——消融 溶解区域剔除(当_Dissolve为1时强制剔除)
//从噪声纹理中采样
fixed3 noiseColor = tex2D(_Noise, v2f.uvNoise.xy).rgb;
//剔除判断
clip(noiseColor.r - _Dissolve);
//...
}
质赋值噪声图和渐变图,可以看到溶解效果了,但是溶解程度为1时没有完全消失,等一下再解决
片元着色器利用smoothstep函数计算出采样系数,从渐变纹理中获取溶解边缘的颜色,利用lerp函数决定是用哪个颜色,把颜色返回出去
fixed4 frag(v2f v2f) : SV_Target
{
//剔除——消融 溶解区域剔除(当_Dissolve为1时强制剔除)
//从噪声纹理中采样
fixed3 noiseColor = tex2D(_Noise, v2f.uvNoise.xy).rgb;
//剔除判断
clip(noiseColor.r - _Dissolve);
//...
// return fixed4(color.rgb, 1);
//需要在模型本来的颜色 和 消融边缘的颜色之间去进行选择
//渐变颜色的采样
// 计算溶解边缘的过渡值
// smoothstep函数用于在指定范围内生成平滑的过渡值,避免硬边缘的出现。
// 其定义为:smoothstep(edge0, edge1, x),当x在[edge0, edge1]范围内时,返回0到1之间的平滑插值值;
// 当x小于edge0时,返回0;当x大于edge1时,返回1。
// 这里,noiseColor.r - _Dissolve表示当前噪声值与溶解进度的差值,
// _EdgeRange控制过渡的宽度,value表示当前片段的过渡值。
fixed value = 1 - smoothstep(0, _EdgeRange, noiseColor.r - _Dissolve);
// 当 _Dissolve 变化时:
// - noiseColor.r - _Dissolve 增大:
// - smoothstep 函数的输入值增大,导致其输出值增大。
// - 由于 value 是 1 - smoothstep 的结果,value 会减小。
// - noiseColor.r - _Dissolve 减小:
// - smoothstep 函数的输入值减小,导致其输出值减小。
// - 因此,value 会增大。
// 当 _EdgeRange 变化时:
// - _EdgeRange 增大:
// - smoothstep 函数的范围增大,使得输入值在更宽的范围内变化。
// - 这会导致 smoothstep 的输出值变化更平缓,从而使 value 的变化也更平缓。
// - _EdgeRange 减小:
// - smoothstep 函数的范围减小,使得输入值在更窄的范围内变化。
// - 这会导致 smoothstep 的输出值变化更剧烈,从而使 value 的变化也更剧烈。
// 从渐变纹理中获取溶解边缘的颜色
// tex2D函数用于从纹理中获取指定UV坐标的颜色值。
// 这里,value作为U坐标,0.5作为V坐标,从渐变纹理中获取对应的颜色。
// 由于渐变纹理通常是水平渐变的,V坐标固定为0.5可以获取中间的颜色。
fixed3 gradientColor = tex2D(_Gradient, fixed2(value, 0.5)).rgb;
//最终颜色
fixed3 finalColor = lerp(color, gradientColor, value);
return fixed4(finalColor.rgb, 1);
}
修改参数,可以看到溶解效果以及渐变纹理,但是还没消融时就存在红点了,同时消融为1时没有消融干净
没开始消融存在红点的原因是,当_Dissolve为0但是noiseColor.r不为0(比如是很小的数时)时,计算出来的value仍然不为0,finalColor仍然会进行插值运算。解决方法是使用step函数,作用是当_Dissolve小于一个很小的数时,让 value * step(0.000001, _Dissolve)这一块变成0。这样完全使用模型颜色了。
//最终颜色
//当消融进度为0时 一定要让颜色使用我们的模型本来的颜色
//step函数保证_Dissolve为0是完全从模型颜色中采样
fixed3 finalColor = lerp(color, gradientColor, value * step(0.000001, _Dissolve));
// fixed3 finalColor = lerp(color, gradientColor, value);
消融程度为1还存在的远呀是因为clip函数只会剔除小于0,等于0是不会剔除的。所以可以设置_Dissolve为1时就设置一个负数强行剔除。
//剔除判断
//clip函数只会剔除小于0 等于0是不会剔除的 所以可以设置_Dissolve为1时就设置一个负数 强行剔除
clip(_Dissolve == 1 ? -1 : noiseColor.r - _Dissolve);
// clip(noiseColor.r - _Dissolve);
阴影相关添加
添加预编译指令和自动光照库
#pragma multi_compile_fwdbase// 启用前向渲染多编译变体
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"//自动光照库
结构体添加世界空间位置与阴影坐标
struct v2f
{
float4 pos : SV_POSITION; // 裁剪空间顶点位置
float4 uv : TEXCOORD0; // xy:主纹理 UV,zw:法线纹理 UV
float2 uvNoise : TEXCOORD1; // 噪声纹理 UV
float3 lightDir : TEXCOORD2; // 切线空间光照方向
float3 viewDir : TEXCOORD3; // 切线空间观察方向
float3 worldPos : TEXCOORD4; // 世界空间位置(用于阴影计算)
SHADOW_COORDS(5) // 阴影坐标(自动生成)
};
顶点着色器添加世界空间位置和阴影坐标计算
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//...
// 世界空间位置和阴影坐标
v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex).xyz;
TRANSFER_SHADOW(v2f);
return v2f;
}
片元着色器添加强度计算,并且让兰伯特乘上强度
fixed4 frag(v2f v2f) : SV_Target
{
//...
//强度计算
UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);
//布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
// fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten + specularColor;
//...
return fixed4(finalColor.rgb, 1);
}
添加后备着色器
Fallback "Diffuse" // 后备着色器(当不支持时使用漫反射)
阴影问题说明
目前实现中支持投射和接收阴影,但阴影部分尚未实现消融效果。
阴影消融效果处理
主要步骤
- 复制 Shader 开发基础中 Lesson15_ForwardLighting 的自定义投射阴影代码。
- 加入噪声纹理和消融进度的属性映射。
- 在结构体中加入 UV 坐标。
- 在顶点着色器中计算 UV 的缩放偏移。
- 在片元着色器中加入剔除操作。
复制投射阴影代码
//阴影投影 Pass 主要是用来计算阴影映射纹理
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
// 比如有些低端移动设备不支持
#pragma multi_compile_shadowcaster
// 其中包含了关键的阴影计算相关的宏
#include "UnityCG.cginc"
struct v2f
{
//顶点到片元着色器阴影投射结构体数据宏
//这个宏定义了一些标准的成员变量
//这些变量用于在阴影投射路径中传递顶点数据到片元着色器
//我们主要在结构体中使用
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f v2f;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
//我们主要在顶点着色器中使用
//这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
return v2f;
}
float4 frag(v2f v2f):SV_Target
{
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(v2f);
}
ENDCG
}
加入噪声纹理和消融进度属性映射
// 溶解效果属性
sampler2D _Noise; // 噪声纹理
float4 _Noise_ST; // 噪声纹理的缩放和平移
fixed _Dissolve; // 消融进度
结构体中添加噪声纹理 UV
struct v2f
{
//顶点到片元着色器阴影投射结构体数据宏
//这个宏定义了一些标准的成员变量
//这些变量用于在阴影投射路径中传递顶点数据到片元着色器
//我们主要在结构体中使用
V2F_SHADOW_CASTER;// 阴影投射内置结构
float2 uvNoise : TEXCOORD0; // 噪声纹理UV
};
顶点着色器中计算uv缩放偏移
v2f vert(appdata_base v)
{
v2f v2f;
//计算噪声纹理的缩放偏移
v2f.uvNoise = v.texcoord.xy * _Noise_ST.xy + _Noise_ST.zw;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
//我们主要在顶点着色器中使用
//这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
return v2f;
}
片元着色器中剔除处理(与主 Pass 同步)
float4 frag(v2f v2f):SV_Target
{
// 阴影投射时的溶解剔除(与主Pass同步)
fixed3 noise = tex2D(_Noise, v2f.uvNoise).rgb;
clip(_Dissolve == 1 ? -1 : noise.r - _Dissolve);
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(v2f);
}
最终效果
通过上述步骤,阴影部分也将呈现消融效果。
19.2 知识点代码
Lesson19_Dissolve.shader
Shader "Unlit/Lesson19_Dissolve"
{
Properties
{
// 基础材质属性
_MainColor("MainColor", Color) = (1,1,1,1) // 主颜色,用于混合纹理颜色
_MainTex("MainTex", 2D) = "white" {} // 主纹理贴图
_BumpMap("BumpMap", 2D) = "bump" {} // 法线贴图
_BumpScale("BumpScale", Range(0,1)) = 1 // 法线强度调节系数
_SpecularColor("SpecularColor", Color) = (1,1,1,1) // 高光反射颜色
_SpecularNum("SpecularNum", Range(8,256)) = 18 // 高光反射指数(光泽度)
// 溶解效果相关属性
_Noise("Noise", 2D) = "white" {} // 噪声纹理(决定溶解区域)
_Gradient("Gradient", 2D) = "white" {} // 渐变纹理(控制溶解边缘颜色)
_Dissolve("Dissolve", Range(0,1)) = 0 // 溶解进度(0:完整 1:完全溶解)
_EdgeRange("EdgeRange", Range(0,1)) = 0 // 溶解边缘过渡宽度
}
SubShader
{
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma multi_compile_fwdbase // 启用前向渲染多编译变体
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
// 材质属性声明
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度
// 溶解效果属性
sampler2D _Noise; //噪声纹理
float4 _Noise_ST; //法线纹理的缩放和平移
sampler2D _Gradient; //噪声纹理
fixed _Dissolve; //消融进度
fixed _EdgeRange; //消融边界宽窄范围
struct v2f
{
float4 pos : SV_POSITION; // 裁剪空间顶点位置
float4 uv : TEXCOORD0; // xy:主纹理UV zw:法线纹理UV
float2 uvNoise : TEXCOORD1; // 噪声纹理UV
float3 lightDir : TEXCOORD2; // 切线空间光照方向
float3 viewDir : TEXCOORD3; // 切线空间观察方向
float3 worldPos : TEXCOORD4; // 世界空间位置(用于阴影计算)
SHADOW_COORDS(5) // 阴影坐标(自动生成)
};
// 顶点着色器中传入:
// 可以使用 UnityCG.cginc 中的 appdata_full
// 其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//单张纹理和法线纹理 UV坐标缩放偏移计算
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//计算噪声纹理的缩放偏移
v2f.uvNoise = appdata_full.texcoord.xy * _Noise_ST.xy + _Noise_ST.zw;
//在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
//切线、副切线、法线
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条(确定副切线方向)
//cross是叉乘 appdata_full.tangent是切线 appdata_full.normal是法线 appdata_full.tangent.w代表叉乘结果方向
float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
tangent.w;
//构建模型空间到切线空间的变换矩阵
float3x3 rotation = float3x3(appdata_full.tangent.xyz,
binormal,
appdata_full.normal);
//模型空间下的光的方向
//v2f.lightDir = ObjSpaceLightDir(appdata_full.vertex);
//乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));
//模型空间下的视角的方向
//v2f.viewDir = ObjSpaceViewDir(appdata_full.vertex);
//乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 视角的方向了
v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));
// 世界空间位置和阴影坐标
v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex).xyz;
TRANSFER_SHADOW(v2f);
return v2f;
}
// 片元着色器中传入:
// 自定义一个结构体
// 其中包含 裁剪空间下坐标、uv坐标、光的方向、视角的方向
fixed4 frag(v2f v2f) : SV_Target
{
//剔除——消融 溶解区域剔除(当_Dissolve为1时强制剔除)
//从噪声纹理中采样
fixed3 noiseColor = tex2D(_Noise, v2f.uvNoise.xy).rgb;
//剔除判断
//clip函数只会剔除小于0 等于0是不会剔除的 所以可以设置_Dissolve为1时就设置一个负数 强行剔除
clip(_Dissolve == 1 ? -1 : noiseColor.r - _Dissolve);
// clip(noiseColor.r - _Dissolve);
//纹理采样函数tex2D
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度
//注意 tangentNormal不要进行单位化 直接用即可 因为乘了BumpScale就不是单位向量了
tangentNormal *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(切线坐标系下的法线, 光的方向))
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(
0, dot(tangentNormal, normalize(v2f.lightDir)));
//半角向量 = 视角方向 + 光的方向
float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));
//高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(切线坐标系下的法线, 半角向量)), 光泽度)
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
max(0, dot(tangentNormal, halfA)), _SpecularNum);
//强度计算
UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);
//布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
// fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten + specularColor;
// return fixed4(color.rgb, 1);
//需要在模型本来的颜色 和 消融边缘的颜色之间去进行选择
//渐变颜色的采样
// 计算溶解边缘的过渡值
// smoothstep函数用于在指定范围内生成平滑的过渡值,避免硬边缘的出现。
// 其定义为:smoothstep(edge0, edge1, x),当x在[edge0, edge1]范围内时,返回0到1之间的平滑插值值;
// 当x小于edge0时,返回0;当x大于edge1时,返回1。
// 这里,noiseColor.r - _Dissolve表示当前噪声值与溶解进度的差值,
// _EdgeRange控制过渡的宽度,value表示当前片段的过渡值。
fixed value = 1 - smoothstep(0, _EdgeRange, noiseColor.r - _Dissolve);
// 当 _Dissolve 变化时:
// - noiseColor.r - _Dissolve 增大:
// - smoothstep 函数的输入值增大,导致其输出值增大。
// - 由于 value 是 1 - smoothstep 的结果,value 会减小。
// - noiseColor.r - _Dissolve 减小:
// - smoothstep 函数的输入值减小,导致其输出值减小。
// - 因此,value 会增大。
// 当 _EdgeRange 变化时:
// - _EdgeRange 增大:
// - smoothstep 函数的范围增大,使得输入值在更宽的范围内变化。
// - 这会导致 smoothstep 的输出值变化更平缓,从而使 value 的变化也更平缓。
// - _EdgeRange 减小:
// - smoothstep 函数的范围减小,使得输入值在更窄的范围内变化。
// - 这会导致 smoothstep 的输出值变化更剧烈,从而使 value 的变化也更剧烈。
// 从渐变纹理中获取溶解边缘的颜色
// tex2D函数用于从纹理中获取指定UV坐标的颜色值。
// 这里,value作为U坐标,0.5作为V坐标,从渐变纹理中获取对应的颜色。
// 由于渐变纹理通常是水平渐变的,V坐标固定为0.5可以获取中间的颜色。
fixed3 gradientColor = tex2D(_Gradient, fixed2(value, 0.5)).rgb;
//最终颜色
//当消融进度为0时 一定要让颜色使用我们的模型本来的颜色
//step函数保证_Dissolve为0是完全从模型颜色中采样
fixed3 finalColor = lerp(color, gradientColor, value * step(0.000001, _Dissolve));
// fixed3 finalColor = lerp(color, gradientColor, value);
return fixed4(finalColor.rgb, 1);
}
ENDCG
}
//阴影投影 Pass 主要是用来计算阴影映射纹理
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
// 比如有些低端移动设备不支持
#pragma multi_compile_shadowcaster
// 其中包含了关键的阴影计算相关的宏
#include "UnityCG.cginc"
struct v2f
{
//顶点到片元着色器阴影投射结构体数据宏
//这个宏定义了一些标准的成员变量
//这些变量用于在阴影投射路径中传递顶点数据到片元着色器
//我们主要在结构体中使用
V2F_SHADOW_CASTER;// 阴影投射内置结构
float2 uvNoise : TEXCOORD0; // 噪声纹理UV
};
// 溶解效果属性
sampler2D _Noise; //噪声纹理
float4 _Noise_ST; //法线纹理的缩放和平移
fixed _Dissolve; //消融进度
v2f vert(appdata_base v)
{
v2f v2f;
//计算噪声纹理的缩放偏移
v2f.uvNoise = v.texcoord.xy * _Noise_ST.xy + _Noise_ST.zw;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
//我们主要在顶点着色器中使用
//这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
return v2f;
}
float4 frag(v2f v2f):SV_Target
{
// 阴影投射时的溶解剔除(与主Pass同步)
fixed3 noise = tex2D(_Noise, v2f.uvNoise).rgb;
clip(_Dissolve == 1 ? -1 : noise.r - _Dissolve);
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(v2f);
}
ENDCG
}
}
Fallback "Diffuse" // 后备着色器(当不支持时使用漫反射)
}
Lesson19_噪声_消融效果_具体实现.cs
using UnityEngine;
public class Lesson19_噪声_消融效果_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾 消融效果 基本原理
//消融效果 基本原理
//通过对比噪声纹理值与消融进度参数,剔除低于阈值的像素,
//同时在边缘添加渐变颜色实现动态溶解效果。
//1.如何剔除像素
//clip(噪声纹理值 – 消融进度参数)
//2.如何处理边缘
//利用smoothstep结合消融阈值来决定在渐变纹理中采用的渐变颜色
//利用lerp来决定在原始颜色和边缘渐变颜色中使用哪个颜色
//利用自定义参数决定边缘
#endregion
#region 知识点一 准备工作
//1.导入噪声纹理和渐变纹理
//2.新建Shader Lesson19_Dissolve,复制Shader开发入门中计算切线空间下法线纹理Shader Lesson29_Bump_Texture_Tangent
#endregion
#region 知识点二 实现消融效果
//1.添加属性
// _Noise 噪声纹理
// _Gradient 渐变纹理
// _Dissolve 消融进度
// _Range 边缘范围
// 添加属性映射
//2.结构体 加入一个噪声纹理uv
//3.片元着色器
// 3-1:剔除效果制作
// 从噪声纹理中采样,利用clip函数进行剔除
// 3-2:边缘渐变采样,范围控制,颜色插值
// 利用smoothstep函数计算出采样系数
// 利用lerp函数决定是用哪个颜色
#endregion
#region 知识点三 阴影相关添加
#endregion
#region 知识点四 阴影消融效果处理
//1.复制Shader开发基础中Lesson15_ForwardLighting中自定义投射阴影相关代码
//2.加入噪声纹理和消融进度属性映射
//3.结构体中加入uv
//4.顶点着色器中计算uv缩放偏移
//5.片元着色器中剔除
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com