好多天没更新了,这几天换了台电脑,以前的电脑比较老,很难再满足我了,也是应为如此,所以才停更了几天,更让人高兴的是,我终于可以打开shadertoy这个网站了,这个网站是全球shader爱好者都会发布一些优秀的shader作品在上面,所以对电脑的配置有一定的要求,可能不久我会研究研究、然后发布有关上面作品学习的博客。
好了,废话不多说,今天我们来学习Unity里面全局雾效,要学习这个,我们必须要知道Unity内置了的内置宏,封装好的,也不用我们做太多的工作。这里我们用屏幕后处理来实现全局雾效,首先我们必须知道世界坐标,我们知道坐标系中坐标可以根据相对另一个顶点坐标偏移量来求得,所以我们只需知道摄像机在世界坐标中的位置以及该像素相对摄像机偏移量。
雾的计算实现中,我们需要计算一个雾效系数f,作为混合原始颜色和雾的颜色的混合系数。
计算f有很多种方法:
- Linear:
,其中Dmax和Dmin分别表示受雾影响的距离起点和终点。
- Exponential:
,其中d表示雾的浓度。
- Exp2:
,其中d表示雾的浓度。
这里我们实现基于高度的雾效,所以我们采用第一种方法。
下面开始实现,首先我们要创建一个c#脚本,依旧继承前几篇讲的c#脚本PostEffectsBase
using UnityEngine;
using System.Collections;
public class fogWithDepthTexture : PostEffectsBase {
public Shader fogShader;
private Material fogMaterial = null;
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.0f, 3.0f)]
public float fogDensity = 1.0f;
public Color fogColor = Color.white;
public float fogStart = 0.0f;
public float fogEnd = 2.0f;
void OnEnable()
{
camera.depthTextureMode |= DepthTextureMode.Depth;
}
void OnRenderImage(RenderTexture src,RenderTexture dest)
{
if (material != null)
{
Matrix4x4 frustumCorners = Matrix4x4.identity;
float fov = camera.fieldOfView;
float near = camera.nearClipPlane;
float far = camera.farClipPlane;
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 + toTop + toRight;
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.SetMatrix("_ViewProjectionInverseMatrix", (camera.projectionMatrix * camera.worldToCameraMatrix).inverse);
material.SetFloat("_FogDensity", fogDensity);
material.SetColor("_FogColor", fogColor);
material.SetFloat("_FogStart", fogStart);
material.SetFloat("_FogEnd", fogEnd);
Graphics.Blit(src, dest, material);
}else
{
Graphics.Blit(src, dest);
}
}
}
这里我就不一一讲解了,部分跟前面类似,这里求解了近裁剪平面的四个角对应的向量,并把它储存在一个矩阵类型的变量(frustumCorners)中,并按照一定顺序存储。
下面就开始讲解我们今天要学的shader:
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
}
这里我们只是添加了四个属性,雾的强度、雾的颜色、雾开始的高度、以及结束高度。
SubShader {
CGINCLUDE
#include "UnityCG.cginc"
float4x4 _FrustumCornersRay;
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;
这里声明各个变量。
struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, 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;
} else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5) {
index = 1;
} else 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 = _FrustumCornersRay[index];
return o;
}
这里定义了变量interpolatedRay存储差值后的像素向量。在顶点着色器中,我们处理了平台化处理,有关平台化处理如果想了解可以进一步阅读《Unity shader入门精要》。
fixed4 frag(v2f i) : SV_Target {
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepth
Texture, i.uv_depth));
float3 worldPos = _WorldSpaceCameraPos + linearDepth *
i.interpolatedRay.xyz;
float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
fogDensity = saturate(fogDensity * _FogDensity);
fixed4 finalColor = tex2D(_MainTex, i.uv);
finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);
return finalColor;
}
ENDCG
Pass {
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack Off
}
首先,我们重建该像素的世界坐标的位置,我们首先使用SAMPLE_DEPTH_TEXCOORD对纹理进行采样,再用LineEyeDepth来获取视角空间下的线性深度值,然后与interpolatedRay相乘后再和世界坐标空间下的摄像机位置相加渴求的该像素的世界坐标。
实现效果如下: