1.效果图及完整代码
如下图,是应用雾shader前后的效果图。
完整C#代码,需要挂在摄像机上:
using UnityEngine;
/*
* Author : SunnyDecember
*/
[ExecuteInEditMode]
public class FogCamera : MonoBehaviour
{
private Material material;
public float fogEnd = 15;
public float fogStart = 5;
private void OnEnable()
{
Camera.main.depthTextureMode |= DepthTextureMode.Depth;
material = new Material(Shader.Find("Fog"));
}
private void OnDisable()
{
Camera.main.depthTextureMode &= ~DepthTextureMode.Depth;
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
var inverseProjectionMatrix = Camera.main.projectionMatrix.inverse;
material.SetMatrix("_ClipToCameraMatrix", inverseProjectionMatrix);
material.SetFloat("_FogStart", fogStart);
material.SetFloat("_FogEnd", fogEnd);
Graphics.Blit(source, destination, material);
}
}
完整shader代码:
/*
* Author : SunnyDecember
*/
Shader "Fog"
{
Properties
{
_MainTex("MainTex", 2D) = "white" {}
}
SubShader
{
pass
{
ZWrite Off ZTest Off Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _CameraDepthTexture;
sampler2D _MainTex;
float4 _MainTex_TexelSize;
float4x4 _ClipToCameraMatrix;
half _FogEnd;
half _FogStart;
struct v2f
{
half2 uv : TEXCOORD0;
half2 depthUV : TEXCOORD0;
float4 pos : SV_POSITION;
};
v2f vert(appdata_base v)
{
v2f f;
f.uv = v.texcoord;
f.depthUV = v.texcoord;
f.pos = UnityObjectToClipPos(v.vertex);
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0)
{
f.depthUV.y = 1 - f.depthUV.y;
}
#endif
return f;
}
fixed4 frag(v2f f) : SV_Target
{
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, f.uv);
#if defined(UNITY_REVERSED_Z)
depth = 1 - depth;
#endif
float4 centerUV = float4(f.uv.x * 2 - 1, f.uv.y * 2 - 1, depth * 2 - 1, 1);
float4 positionInCameraSpace = mul(_ClipToCameraMatrix, centerUV);
positionInCameraSpace /= positionInCameraSpace.w;
fixed4 screenColor = tex2D(_MainTex, f.uv);
half fogDensity = smoothstep(_FogStart, _FogEnd, length(positionInCameraSpace));
fixed4 finalColor = lerp(screenColor, fixed4(1, 1, 1, 1), fogDensity);
return fixed4(finalColor.rgb, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
2.C#部分的代码
(1)添加[ExecuteInEditMode],能够不在点击运行情况下也能看到执行效果,调试更方便。
(2)让摄像机绘制深度图,并创建材质球。只有摄像机绘制了深度图,shader的_CameraDepthTexture变量才会有值。
private void OnEnable()
{
Camera.main.depthTextureMode |= DepthTextureMode.Depth;
material = new Material(Shader.Find("Fog"));
}
(3)在摄像机渲染完场景后,绘制RT时候,使用OnRenderImage得到源RT,我们使用shader对源RT处理。
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
var inverseProjectionMatrix = Camera.main.projectionMatrix.inverse;
material.SetMatrix("_ClipToCameraMatrix", inverseProjectionMatrix);
material.SetFloat("_FogStart", fogStart);
material.SetFloat("_FogEnd", fogEnd);
Graphics.Blit(source, destination, material);
}
首先我们得到摄像机投影矩阵的逆矩阵,此逆矩阵是从裁剪空间(clip space)转换到摄像机空间的(view space),我们把她赋值给shader的_ClipToCameraMatrix。同时并赋值_FogStart和_FogEnd,_FogStart表示距离摄像机多远距离开始产生雾,_FogStart和_FogEnd之间的距离会做一个雾颜色插值,在这距离内雾的颜色会越来越浓。距离大于_FogEnd,为雾颜色。使用blit,让源RT经过处理后,覆盖到目标RT中。如果我们没有使用自己的材质,而是像下面这样写,Unity自动帮我们调用一个隐藏的shader(Hidden/BlitCopy),并且Blend是直接覆盖.
Graphics.Blit(source, destination);
3.shader代码
(1)当我们使用Graphics.Blit处理源RT,源RT会传值给_MainTex,注意这里必须是_MainTex属性,片元着色器处理的结果会赋值给destination。
_MainTex("MainTex", 2D) = "white" {}
(2)对于后处理,我们通常都使用以下三个命令。事实上,屏幕后处理是在处理一张和屏幕等宽高的面片而已。关闭深度写入是为了,不影响在其后面还需要渲染物体的结果。
ZWrite Off ZTest Off Cull Off
(3)当我们允许摄像机绘制深度图:Camera.main.depthTextureMode |= DepthTextureMode.Depth; unity会赋值给_CameraDepthTexture,这里我们就能得到深度图了。_ClipToCameraMatrix是在C#部分计算好的从裁剪空间到摄像机空间的矩阵。
sampler2D _CameraDepthTexture;
sampler2D _MainTex;
float4 _MainTex_TexelSize;
float4x4 _ClipToCameraMatrix;
half _FogEnd;
half _FogStart;
(4)使用SAMPLE_DEPTH_TEXTURE对_CameraDepthTexture采样,得到在NDC下的深度值(因为深度图记录的值,就是NDC下的深度),然后通过UNITY_REVERSED_Z判断深度是否反向。在某些平台下深度会反向的,近平面为1,远平面为0。我们期望近平面为0,远平面为1。我们现在知道uv值和深度值,他们组合的xyz,便是NDC下的坐标。我们将NDC下的坐标乘2并且减去1,得到的新的坐标centerUV范围在(-1,1)之间,centerUV便是裁剪空间做齐次除法后的坐标。将centerUV乘上_ClipToCameraMatrix就可以逆向推出摄像机空间的坐标位置,也就是说,屏幕的每一个像素点,到这一步已经可以推出在摄像机空间下的坐标了。再使用tex2D(_MainTex, f.uv)获取这个像素点的颜色,根据距离摄像机的远近,像素颜色与雾颜色做平滑插值,从而得到距离越远雾越浓。smoothstep(min, max, currentValue)的意思是,当currentValue<=min返回0,currentValue>=max返回1,在min和max之间的话currentValue返回0-1的平滑插值。
fixed4 frag(v2f f) : SV_Target
{
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, f.uv);
#if defined(UNITY_REVERSED_Z)
depth = 1 - depth;
#endif
float4 centerUV = float4(f.uv.x * 2 - 1, f.uv.y * 2 - 1, depth * 2 - 1, 1);
float4 positionInCameraSpace = mul(_ClipToCameraMatrix, centerUV);
positionInCameraSpace /= positionInCameraSpace.w;
fixed4 screenColor = tex2D(_MainTex, f.uv);
half fogDensity = smoothstep(_FogStart, _FogEnd, length(positionInCameraSpace));
fixed4 finalColor = lerp(screenColor, fixed4(1, 1, 1, 1), fogDensity);
return fixed4(finalColor.rgb, 1);
}
Author : SunnyDecember
Date : 2020.3.17
原文