再谈全局雾效
在前面我们讲到了如何使用深度纹理来实现一种基于屏幕后处理的全局特效。我们由深度纹理重建每个像素在世界空间下的位置,再使用一个基于高度的公式来计算雾效的混合系数,最后使用该系数来混合雾的颜色和原屏幕的颜色。这里实现的效果是一个基于高度的均匀雾效,即在同一个高度上,雾的浓度是相同的。如下图的左图所示:
然而,一些时候我们希望可以模拟一种不均匀的雾效,同时让雾不断飘动,使雾看起来更加缥缈,如上图右图所示。这样就可以通过使用一张噪声纹理来实现。
本节的实现非常简单,绝大多数的代码和前面一样,我们只是添加了噪声相关的参数和属性,并在Shader的片元着色器中对高度的计算添加了噪声的影响。
脚本:
(1)首先,继承基类:
public class FogWithNoise : PostEffectsBase {
(2)声明该效果需要的Shader,并据此创建相应的材质:
public Shader fogShader;
private Material fogMaterial = null;
public Material material {
get {
fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);
return fogMaterial;
}
}
(3)在本节中,我们需要获取摄像机的相关参数,如近裁剪平面的距离、FOV等,同时还需要获取摄像机在世界空间下的前方、上方和右方等方向,因此我们用两个变量存储摄像机的Camera组件和Transform组件:
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;
}
}
(4)定义模拟雾效时使用的各个参数:
[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;
fogDensity用于控制雾的浓度,fogColor用于控制雾的颜色。我们使用的雾效模拟函数是基于高度的,因此参数fogStart用于控制雾效的起始高度,fogEnd用于控制雾的终止高度。noiseTexture是我们使用的噪声纹理,fogXSpeed和fogYSpeed分别对应了噪声纹理在X和Y方向上的移动速度,以此来模拟雾的飘动效果。最后,noiseAmount用于控制噪声程度,当noiseAmount为0时,表示不应用任何噪声,即得到一个均匀的基于高度的全局雾效。
(5)由于本例中需要获取摄像机的深度纹理,我们在脚本的OnEnable函数中设置摄像机的相应状态:
void OnEnable() {
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
}
(6)最后,我们实现了OnRenderImage函数:
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
Matrix4x4 frustumCorners = 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 = cameraTransform.forward * near + toTop - toRight;
float scale = topLeft.magnitude / near;
topLeft.Normalize();
topLeft *= scale;
Vector3 topRight = cameraTransform.forward * near + toRight + toTop;
topRight.Normalize();
topRight *= scale;
Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
bottomLeft.Normalize();
bottomLeft *= scale;
Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop;
bottomRight.Normalize();
bottomRight *= scale;
frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);
material.SetMatrix("_FrustumCornersRay", frustumCorners);
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);
}
}
我们首先利用前面讲到的计算近裁剪平面的4个角对应的向量,并把它们存储在一个矩阵类型的变量(frustumCorners)中。随后,我们把结果和其他参数传递给材质,并调用Graphics.Blit (src, dest, material)把渲染结果显示在屏幕上。
Shader:
(1)我们首先声明本例使用的各个属性:
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
}
(2)在本节中,我们使用CGINCLUDE来组织代码。我们在SubShader块中利用CGINCLUDE和ENDCG语义来定义一系列代码:
SubShader{
CGINCLUDE
...
ENDCG
...
}
(3)声明代码中需要使用的各个变量:
float4x4 _FrustumCornersRay;
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;
sampler2D _NoiseTex;
half _FogXSpeed;
half _FogYSpeed;
half _NoiseAmount;
_FrustumCornersRay虽然没有在Properties中声明,但仍可以由脚本传递给Shader。除了Properties中声明的各个属性,我们还声明了深度纹理_CameraDepthTexture,Unity会在背后把得到的深度纹理传递给该值。
(4)定义顶点着色器,和之前一样
(5)定义片元着色器
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;
}
我们首先根据深度纹理来重建该像素在世界空间中的位置。然后,我们利用内置的_Time.y变量和_FogXSpeed、 _FogYSpeed属性计算出当前噪声纹理的偏移量,并据此对噪声纹理进行采样,得到噪声值。我们把该值减去0.5,再乘以控制噪声程度的属性_NoiseAmount,得到最终的噪声值。随后,我们把该噪声值添加到雾效浓度的计算中,得到应用噪声后的雾效混合系数fogDensity。最后,我们使用该系数将雾的颜色和原始颜色进行混合后返回。
(6)随后,我们定义了雾效渲染所需的Pass:
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
(7)最后,我们关闭了Shader的Fallback
FallBack Off
本节使用的噪声纹理如下图所示: