本系列为作者学习UnityShader入门精要而作的笔记,内容将包括:
- 书本中句子照抄 + 个人批注
- 项目源码
- 一堆新手会犯的错误
- 潜在的太监断更,有始无终
总之适用于同样开始学习Shader的同学们进行有取舍的参考。
使用噪声
在有些时候,向规则的事物里面添加一些杂乱无章的效果,往往会有奇效。而这些杂乱无章的效果的来源就是噪声。在本章中我们将学习如何使用噪声来模拟一些特效。
上节补充:smoothstep
这篇文章很好的描述了smoothstep实现了什么样的效果。实际实现了对一个圆形范围的边缘模糊Shader实验室: smoothstep函数
消融效果
消融效果往往使用在角色死亡,地图烧毁等现象上。消融的效果往往是从不同区域开始,然后往看似随机的方向扩散,最后整个物体消失不见。
Shader
Shader "Custom/Dissolve_Copy"
{
Properties
{
_BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0
// _LineWidth代表了周边延申的效果线
_LineWidth("Burn Line Width", Range(0.0, 10)) = 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
CGPROGRAM
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
fixed _BurnAmount;
fixed _LineWidth;
sampler2D _MainTex;
sampler2D _BumpMap;
fixed4 _BurnFirstColor;
fixed4 _BurnSecondColor;
sampler2D _BurnMap;
float4 _MainTex_ST;
float4 _BumpMap_ST;
float4 _BurnMap_ST;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvBumpMap : TEXCOORD1;
float2 uvBurnMap : TEXCOORD2;
float3 lightDir : TEXCOORD3;
float3 worldPos : TEXCOORD4;
SHADOW_COORDS(5)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap);
o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// 对噪声纹理进行采样
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
// 根据_BurnAmount来clip像素,保留白色去除黑色
clip(burn.r - _BurnAmount);
float3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap));
fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
// 对裁剪掉的部分进行混合颜色渲染
fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
// 这一部分渲染的颜色是根据smoothstep获取的形状渲染的,
// 被裁剪部分=0,因此经过smoothstep后周边模糊区域会保留,中心区域裁剪,渲染范围是裁剪部分区域 + _LineWidth
// 而越靠近裁剪部分的像素渲染越接近firstColor
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 to render object as a shadow caster
Pass {
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
fixed _BurnAmount;
sampler2D _BurnMap;
float4 _BurnMap_ST;
struct v2f {
V2F_SHADOW_CASTER;
float2 uvBurnMap : TEXCOORD1;
};
v2f vert(appdata_base v) {
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
// 对阴影也需要剔除
clip(burn.r - _BurnAmount);
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
FallBack "Diffuse"
}
例如火焰焚毁效果
发现配合不同的噪声贴图可以实现很多有意思的效果,例如扫描线重建
或者这样的沙化效果,总而言之只要是类似的随时间渐变效果应当都是能够使用噪声贴图实现的,只是需要充分发挥想象力
水波效果
在模拟实时水面的时候,我们往往也会使用噪声纹理作为高度图,不断修改水面的法线方向,以模拟水不断流动的效果。我们会使用和时间相关的变量来对噪声纹理进行采样,当得到法线信息后再进行正常的反射 + 折射运算,得到最终的水面波动效果
在之前我们写过一个实现了菲涅尔反射的玻璃效果,现在我们想要实现水波,自然也要使用菲涅尔反射,此外为了实现水面的波动效果,我们可以用一张噪声贴图,并不断进行偏移采样来模拟水面的随机波动。以实现波光粼粼的效果。
Shader "Custom/WaterWave_Copy"
{
Properties
{
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Base Tex", 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" }
// 抓取不透明物体渲染后的缓存,并保存到_RefractionTex纹理中
// 此处GrabPass在渲染水面纹理之前,因此抓取的是未渲染水面时的场景画面
GrabPass { "_RefractionTex" }
Pass
{
Tags{ "LightMode"="ForwardBase" }
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _WaveMap;
float4 _WaveMap_ST;
samplerCUBE _Cubemap;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _Distortion;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap);
// 用于计算光照模型(菲涅尔反射)。需要用到世界空间下的切线方向
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;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
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));
// 计算水波纹理在uv上的采样速度
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
// 对法线纹理采样并将法线纹理转换回切线空间
// 此处为了模拟水波的不规则运动,bump1边对speed正方向采样,bump2对负方向采样,并将两种法线纹理采样都应用上去
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);
// 将场景的像素与法线进行混合以实现水面波动和场景画面的颜色值的混合
// _Distortion越大,水体背后的物体看起来变形程度越大
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
// 对z坐标进行相乘,以模拟深度越大,折射程度越大的效果
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
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;
fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4);
fixed3 finalColor = lerp(refrCol,reflCol,fresnel);
//fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel);
return fixed4(finalColor, 1);
}
ENDCG
}
}
// 不投射阴影
FallBack Off
}
全局雾效
从代码上看来其实就是对原来的雾效后处理进行了一个噪声纹理贴图的采样。
此处就不展开了