噪声的使用会使结果充满随机性,从而表现效果更加自然。
消融效果
消融效果常在游戏中角色死亡,物品烧毁中被使用。消融效果往往从不同区域开始,并向看似随机的方向进行扩张,最后整个物体都消失不见。消融效果可以通过噪声纹理+透明度测试去实现。使用对噪声纹理进行采样的结果与某个控制消融效果的阈值进行比较,小于阈值,使用clip函数将该像素点裁剪掉,对应被烧毁的部分,而边缘部分的烧焦效果则将两种颜色进行混合,在使用pow函数处理,得到最终边缘烧毁颜色。
实例代码:
Shader "Custom/Chapter15_Dissolve" {
Properties{
_BurnAmount("Burn Amount",Range(0.0,1.0))=0.0
_LineWidth("Burn Line Width",Range(0.0,0.2))=0.1
_MainTex("MainTex",2D)="white"{}
_BumpMap("Bump 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"{}
//_BurnAmount 用于控制消融的程度,值为0时为正常效果,值为1时物体完全消融
//_LineWidth 控制模拟烧焦效果的线宽
//_BurnFirstColor 和 _BurnSecondColor 对应火焰边缘两种颜色
//_BurnMap 对应消融效果的噪声纹理
}
SubShader{
Pass{
Tags{"LightMode"="ForwardBase"}
Cull Off
//关闭剔除功能,也就是说模型的正面和背面对会被渲染出来
//这是由于消融效果会显示出物体内部的结构,因此背面的剔除需要关闭
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdbase
fixed _BurnAmount;
fixed _LineWidth;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
fixed4 _BurnFirstColor;
fixed4 _BurnSecondColor;
sampler2D _BurnMap;
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);
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(tangentLightDir,tangentNormal));
fixed t=1-smoothstep(0.0,_LineWidth,burn.r-_BurnAmount);
fixed3 burnColor=lerp(_BurnFirstColor,_BurnSecondColor,t);
burnColor=pow(burnColor,5);
//当t的值为1时,表明该像素点在消融边缘,t为0时,则为正常颜色
//中间部分的插值模拟烧焦的痕迹,并使用pow函数
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
fixed3 finalColor=lerp(ambient+diffuse*atten,burnColor,t*step(0.0001,_BurnAmount));
return fixed4(finalColor,1.0);
}
ENDCG
}
//这里需要定义一个投射阴影的Shader,避免被剔除的区域投射阴影
Pass{
Tags{"LightMode"="ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
//用于投射阴影的Pass的LightMode要设置为ShadowCaster
//同时需要使用 #pragma multi_compile_shadowcaster 编译指令
#include "UnityCG.cginc"
fixed _BurnAmount;
sampler2D _BurnMap;
float4 _BurnMap_ST;
struct v2f{
V2F_SHADOW_CASTER;
//利用V2F_SHADOW_CASTER定义阴影投射需要定义的变量
float2 uvBurnMap:TEXCOORD1;
};
v2f vert(appdata_base v){
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
//使用TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)填充
//V2F_SHADOW_CASTER在背后声明的部分变量,由Unity去完成
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)
//根据噪声纹理将消融的像素剔除,剩下的通过内置宏由Unity去完成对应的阴影投射计算
}
ENDCG
}
}
FallBack "DIFFUSE"
}
实例效果:
通过设置_BurnAmount的值随时间变化可以得到动态消融效果
使用噪声实现扰动的动态效果
扰动效果类似于动漫中人物不动,身后的背景流动,比如说
王也小哥手中的两团气团来回流动的效果 扰动效果的实现关键点在于:
使用遮罩纹理区别流动和非流动区域,
使用噪声纹理来控制流动过程的随机性,
这里使用的源纹理:
遮罩纹理一般是对源纹理做处理:
黑色区域即为不受扰动影响的区域,而红色部分为需要应用扰动效果的区域,因为是通过采样遮罩纹理的值去控制,所以黑色部分采样值乘以处理结果的像素颜色后得0,这样就不受扰动效果的影响,而其他部分为红色是由于采样的通道定为R,当然也可以使用其他通道。
噪声纹理可以通用,WarpMode设为Repeat就可以
主要思想是:
通过噪声纹理和时间变量得到对源纹理UV采样的动态偏移值,
通过对遮罩纹理的采样结果*动态偏移值来控制受扰动的区域
实例代码:
Shader "Custom/Disturbance" {
Properties{
_MainTex("MainTex",2D)="white"{}
_Mask("Mask",2D)="white"{}
_Noise("Noise",2D)="white"{}
_NoiseSpeedX("NoiseSpeedX",Range(0.0,3.0))=1.0
_NoiseSpeedY("NoiseSpeedY",Range(0.0,3.0))=1.0
_NoiseIntensity("NoiseIntensity",Range(0.0,3.0))=1.0
}
SubShader{
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _Mask;
float4 _Mask_ST;
sampler2D _Noise;
float4 _Noise_ST;
half _NoiseSpeedX;
half _NoiseSpeedY;
half _NoiseIntensity;
struct a2v{
float4 vertex:POSITION;
float2 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float2 uv1:TEXCOORD0;
float2 uv2:TEXCOORD1;
};
v2f vert(a2v v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.uv1=TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv2=TRANSFORM_TEX(v.texcoord,_Noise);
return o;
}
//噪声纹理采样得到随机值并映射
fixed2 SamplerFromNoise(float2 uv){
float2 newUv=uv*_Noise_ST.xy+_Noise_ST.zw;
fixed4 noiseColor=tex2D(_Noise,newUv);
noiseColor=(noiseColor*2-1)*0.05;
return noiseColor;
}
fixed4 frag(v2f i):SV_Target{
//遮罩纹理采样
fixed4 mask=tex2D(_Mask,i.uv1);
//时间变量(t/20,t,2t,3t)
float2 time=float2(_Time.x,_Time.x);
//计算噪声偏移
fixed2 noiseOffset=fixed2(0,0);
noiseOffset=SamplerFromNoise(i.uv2+time*float2(_NoiseSpeedX,_NoiseSpeedY));
//主纹理采样,使用噪声纹理控制扰动区域
fixed4 mainColor=tex2D(_MainTex,i.uv1+noiseOffset*_NoiseIntensity*mask.r);
return mainColor;
}
ENDCG
}
}
FallBack "Diffuse"
}
实例效果:
扰动效果前:
扰动效果:
这个效果时动态的,因为使用了时间变量,当然可以使用粒子效果和多层次扰动实现更加精细的效果。
水波效果
噪声纹理也可以应用在实时水面的模拟中。通常将噪声纹理用作高度图,不断修改水面的法线方向。通过使用时间变量来对噪声纹理进行采样以得到不断流动的效果,得到法线信息后,再进行正常的反射+折射的计算,得到动态水面效果。在之前实现玻璃效果时,通过使用一个Cubemap纹理作为环境纹理以采样得到反射效果,折射效果的模拟是使用GrabPass抓取当前的渲染纹理,使用切线空间下的法线方向对采样坐标进行偏移,并使用该坐标对屏幕图像进行采样,近似模拟折射效果。在实现水波的过程中,将使用噪声纹理得到法线,并结合时间变量实现动态效果。得到折射与反射值后,其混合值的混合系数通过公式得到:
fresnel=pow(1-max(0,v*n),4)
观察方向与法线方向夹角越小,fresnel值越小,反射越弱,折射越强,在水面非常清澈的情况下。
实例代码:
Shader "Custom/Chapter15_WaterWave" {
Properties{
_Color("MainColor",Color)=(1,1,1,1) //水面颜色
_MainTex("MainTex",2D)="white"{} //水面纹理
_WaveMap("WaveMap",2D)="bump"{}
_CubeMap("CubeMap",Cube)="_Skybox"{}
_WaveXSpeed("WaveXSpeed",Range(-0.1,0.1))=0.01
_WaveYSpeed("WaveYSpeed",Range(-0.1,0.1))=0.01
_Distortion("Distortion",Range(0,100))=10 //控制图像扭曲程度
}
SubShader{
Tags{"Queue"="Transparent" "RenderType"="Opaque"}
//这里设置渲染队列为Transparent,是为了保证渲染该物体前,其他所有不透明物体已经被渲染
//而设置渲染类型则是为了使用摄像机的深度和法线纹理时,物体被正确渲染(会使用着色器替换技术)
GrabPass{"_RefractionTex"}
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
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 srcPos: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.srcPos=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);
float3 worldNormal=UnityObjectToWorldNormal(v.normal);
float3 worldTangent=UnityObjectToWorldDir(v.tangent.xyz);
float3 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));
float2 speed=_Time.y*float2(_WaveXSpeed,_WaveYSpeed); //_Time(t/20,t,2t,3t)
//切线空间下的法线采样并反解
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;
i.srcPos.xy=offset*i.srcPos.z+i.srcPos.xy; //使用i.srcPos.z与偏移相乘,模拟深度越大,折射越强的效果
fixed3 refrCol=tex2D(_RefractionTex,i.srcPos.xy/i.srcPos.w).rgb;
//进行矩阵变换,得到世界空间下的法线方向
bump=normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
fixed3 texColor=tex2D(_MainTex,i.uv.xy+speed);
fixed3 reflDir=reflect(-viewDir,bump);
fixed3 reflCol=texCUBE(_CubeMap,reflDir).rgb*texColor*_Color;
fixed3 fresnel=pow(1-max(0,dot(viewDir,bump)),4);
fixed3 finalColor=refrCol*(1-fresnel)+reflCol*fresnel;
return fixed4(finalColor,1);
}
ENDCG
}
}
FallBack "Transparent"
}
通过噪声纹理实现不均匀雾效
之前通过深度纹理实现基于屏幕后处理的雾效,由深度纹理重建像素的世界空间下的位置,使用基于高度的公式计算全局雾效系数,然后使用该系数混合雾的颜色和原屏幕颜色。这种雾效是一种均匀的雾效,通过噪声纹理可以实现不均匀雾效。
实例代码:
public class Chapter15_FogWithNoise : PostEffectsBase
{
public Shader fogShader;
private Material fogMaterial;
public Material material
{
get
{
fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);
return fogMaterial;
}
}
private Camera myCamera;
public Camera camera
{
get
{
if (myCamera == null)
{
myCamera = GetComponent<Camera>();
}
return myCamera;
}
}
private Transform myCameraTransform;
public Transform cameraTransform
{
get
{
if (myCameraTransform == null)
{
myCameraTransform = camera.transform;
}
return myCameraTransform;
}
}
[Range(0.1f, 3.0f)]
public float fogDensity = 1.0f;
public Color fogColor = Color.white;
public float fogStart = 0.0f;
public float fogEnd = 2.0f;
public Texture noiseTexture;
[Range(-0.5f, 0.5f)]
public float fogXSpeed = 0.1f;
[Range(-0.5f, 0.5f)]
public float fogYSpeed = 0.1f;
[Range(0.0f, 3.0f)]
public float noiseAmount = 1.0f;
void OnEnable()
{
camera.depthTextureMode |=DepthTextureMode.Depth;
}
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
Matrix4x4 frustumCornors = Matrix4x4.identity;
float fov = camera.fieldOfView;
float near = camera.nearClipPlane;
float aspect = camera.aspect;
float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cameraTransform.right*halfHeight*aspect;
Vector3 toTop = cameraTransform.up*halfHeight;
Vector3 topLeft = camera.transform.forward*near + toTop - toRight;
float scale = topLeft.magnitude/near;
Vector3 topRight = camera.transform.forward*near + toTop + toRight;
topRight.Normalize();
topRight *= scale;
Vector3 bottomLeft = camera.transform.forward*near - toTop - toRight;
bottomLeft.Normalize();
bottomLeft *= scale;
Vector3 bottomRight = camera.transform.forward*near - toTop + toRight;
bottomRight.Normalize();
bottomRight *= scale;
frustumCornors.SetRow(0,bottomLeft);
frustumCornors.SetRow(1,bottomRight);
frustumCornors.SetRow(2,topRight);
frustumCornors.SetRow(3,topLeft);
material.SetMatrix("_FrustumCornorsRay",frustumCornors);
material.SetFloat("_FogDensity",fogDensity);
material.SetColor("_FogColor",fogColor);
material.SetFloat("_FogStart",fogStart);
material.SetFloat("_FogEnd",fogEnd);
material.SetTexture("_NoiseTex",noiseTexture);
material.SetFloat("_FogXSpeed",fogXSpeed);
material.SetFloat("_FogYSpeed",fogYSpeed);
material.SetFloat("_NoiseAmount",noiseAmount);
Graphics.Blit(src,dest,material);
}
else
{
Graphics.Blit(src,dest);
}
}
}
Shader代码:
Shader "Custom/Chapter15_FogWithNoise" {
Properties{
_MainTex("MainTex",2D)="white"{}
_FogDensity("FogDensity",Float)=1.0
_FogColor("FogColor",Color)=(1,1,1,1)
_FogStart("FogStart",Float)=0.0
_FogEnd("FogEnd",Float)=1.0
_NoiseTex("NoiseTex",2D)="white"{}
_FogXSpeed("FogXPeed",Float)=0.1
_FogYSpeed("FogYSpeed",Float)=0.1
_NoiseAmount("NoiseAmount",Float)=1
}
SubShader{
CGINCLUDE
#include "UnityCG.cginc"
float4x4 _FrustumCornorsRay;
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;
sampler2D _NoiseTex;
float _FogXSpeed;
float _FogYSpeed;
float _NoiseAmount;
struct v2f{
float4 pos:POSITION;
half2 uv:TEXCOORD0;
half2 uv_depth:TEXCOORD1;
float4 interpolatedRay:TEXCOORD2;
};
v2f vert(appdata_img v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.uv=v.texcoord;
o.uv_depth=v.texcoord;
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y<0)
o.uv_depth.y=1-o.uv_depth.y;
#endif
int index=0;
if(v.texcoord.x<0.5&&v.texcoord.y<0.5){
index=0;
}
if(v.texcoord.x>0.5&&v.texcoord.y<0.5){
index=1;
}
if(v.texcoord.x>0.5&&v.texcoord.y>0.5){
index=2;
}
else{
index=3;
}
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y<0)
index=3-index;
#endif
o.interpolatedRay=_FrustumCornorsRay[index];
return o;
}
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;
float fogDensity=(_FogEnd-worldPos.y)/(_FogEnd-_FogStart);
fogDensity=saturate(fogDensity*_FogDensity*(1+noise));
fixed4 finalColor=tex2D(_MainTex,i.uv);
finalColor.rgb=lerp(finalColor.rgb,_FogColor.rgb,fogDensity);
return finalColor;
}
ENDCG
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack Off
}