Unity Shader 学习笔记(28) 噪声纹理、消融效果、水波效果、噪声雾效
参考书籍:《Unity Shader 入门精要》
噪声纹理
通过纹理深浅可以作为一些变换的阈值,或者透明度等。
消融效果(Dissolve)
常用与角色死亡、地图烧毁时效果。
Burn Amount从0变化到1。
Properties {
_BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0 // 消融程度 0为正常效果
_LineWidth("Burn Line Width", Range(0.0, 0.2)) = 0.1 // 消融效果时的线宽
_MainTex ("Base (RGB)", 2D) = "white" {} // 漫反射纹理
_BumpMap ("Normal Map", 2D) = "bump" {} // 法线纹理
_BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1) // 火焰边缘颜色值
_BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1)
_BurnMap("Burn Map", 2D) = "white"{} // 噪声纹理
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
Cull Off // 关掉剔除,正反面都渲染
...
fixed4 frag(v2f i) : SV_Target {
...
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
clip(burn.r - _BurnAmount); // 如果小于0,该像素会被剔除
//_LineWidth宽度范围模拟渐变。t为0是正常颜色,为1时位于消融的边界
fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t); //两个烧焦颜色做插值
burnColor = pow(burnColor, 5); // 加强颜色模拟烧焦痕迹
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));
return fixed4(finalColor, 1);
}
ENDCG
}
// 阴影投射,直接剔除掉阈值内的像素即可
Pass {
Tags { "LightMode" = "ShadowCaster" }
...
fixed4 frag(v2f i) : SV_Target {
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
clip(burn.r - _BurnAmount); // 剔除掉透明的
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
水波效果
噪声作为高度图,修改水面法线。使用Schilick菲涅尔反射实现:fresnel = pow(1 - max(0, v·n),4 )
。
菲涅尔动态水面效果:
Properties {
_Color ("Main Color", Color) = (0, 0.15, 0.115, 1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_WaveMap ("Wave Map", 2D) = "bump" {} // 噪声法线纹理
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {} // 立方体纹理
_WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01 // 法线平移速度
_WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01
_Distortion ("Distortion", Range(0, 100)) = 10 // 折射扭曲度
}
SubShader {
// 所有不透明物体渲染完后
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
// 抓取屏幕Pass 存入_RefractionTex
GrabPass { "_RefractionTex" }
Pass {
...
#pragma multi_compile_fwdbase
...
sampler2D _RefractionTex; // 对应GrabPass
float4 _RefractionTex_TexelSize;
...
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos); // 抓取屏幕图像的采样坐标
...// 计算世界空间下切线法线等,构造转换矩阵
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
// 对法线两次采样(模拟两层交叉水面波动),相加得到切线空间下的法线方向。
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
fixed3 bump = normalize(bump1 + bump2);
// 计算偏移量,切线空间
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy; // offset * i.scrPos.z 模拟水越深,折射越大
fixed3 refrCol = tex2D( _RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
// 计算法线到世界空间
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed);
fixed3 reflDir = reflect(-viewDir, bump);
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb;
// Schilick菲涅尔近似等式
fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4);
fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel);
return fixed4(finalColor, 1);
}
ENDCG
}
}
噪声雾效
前面实现过雾效在同一高度的浓度是一致的,这里使用噪声模拟雾的浓度,实现非均匀雾效。
在之前实现的雾效添加一些属性。
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_FogDensity ("Fog Density", Float) = 1.0
_FogColor ("Fog Color", Color) = (1, 1, 1, 1)
_FogStart ("Fog Start", Float) = 0.0
_FogEnd ("Fog End", Float) = 1.0
_NoiseTex ("Noise Texture", 2D) = "white" {} // 噪声纹理
_FogXSpeed ("Fog Horizontal Speed", Float) = 0.1
_FogYSpeed ("Fog Vertical Speed", Float) = 0.1
_NoiseAmount ("Noise Amount", Float) = 1 // 噪声数量,0为不使用噪声
}
fixed4 frag(v2f i) : SV_Target {
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;
float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5) * _NoiseAmount; // 随时间偏移雾效,-0.5是把范围控制在[-0.5,0.5]
float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
fogDensity = saturate(fogDensity * _FogDensity * (1 + noise)); // (1 + noise):浓度倍数范围在[0.5, 1.5]内。
fixed4 finalColor = tex2D(_MainTex, i.uv);
finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);
return finalColor;
}