Unity景深模拟的实现大都是,
1、先获取屏幕后期纹理,然后复制出来进行高斯等方式模糊。
2、 再根据场景物体的深度纹理, 在着色器中对景深需要清晰的目标为切割获取0-1的深度插值。
3、将屏幕后期模糊过的纹理和原本后期纹理根据插值进行混合模拟出景深效果。
当然还有其他更偏于基于物理的形式实现,但这种实现更真实,但也更吃性能。
然而模拟景深的效果,网上也有不少例子。但他们的实现,因为是两张纹理混合差值,混合像素, 多数导致清晰物体轮廓会污染背景模糊的颜色,导致聚焦的清晰物体和身后的背景模糊色有一层混合色,产生一种边界模糊的效果。
本文实现的景深虽然也是模拟景深,但优化了聚焦物体和模糊背景的边界色,有效得到一个更清爽的效果:
实现原理主要是模糊纹理的片元操作里,指定一个聚焦深度位置微小的阀值范围,标明在这阀值范围内,在屏幕后处理纹理中,背景模糊运算获取目标像素的周围邻居像素时,不进入清晰聚焦物体目标区域像素内进行模糊采样颜色值,也就是聚焦物体背后的颜色模糊时不能入侵聚焦物体颜色区域采样颜色,聚焦物体颜色不污染背景色,那么就可以给聚焦物体保留一个清晰的轮廓。
原场景
添加景深,得到边界清晰的效果。
用法,
1、将DepthOfField.cs挂载Main Camera节点,
2、初始先设置Clipping Planes 0.3–100。
3、在DepthOfField节点上,将要聚焦的物体拖到Fcous On槽位。
记住,深度纹理和相机Clipping Planes有关,所以开始时一定要先将Camera的值设置在0.3-100等较小的区域范围,否则范围太大,效果不明显。
源码如下:
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class DepthOfField : MonoBehaviour
{
public bool ShowGizmosWire = true;
public Transform FocusOn;
//归一化后的焦距
[Tooltip("景深相关:聚焦点的距离,值0-1对应相机远近裁剪平面")]
[Range(-0.2f, 1.2f)]
public float FocusDistance = 0.5f;
[Tooltip("景深相关:聚焦范围")]
[Range(0, 1f)]
public float FocusRange = 0.5f;
[Tooltip("模糊相关:模糊扩散程度")]
[Range(0.2f, 3.0f)]
public float BlurSpread = 0.6f;
[Tooltip("模糊相关:纹理采样遍历次数,值越大性能消耗越大")]
[Range(0, 4)]
public int BlurIterations = 3;
[Tooltip("模糊相关:纹理采样分辨率,值越小性能消耗越大")]
[Range(1, 8)]
public int BlurDownSample = 2;
private Material dMaterial;
private Camera camera;
private void Awake()
{
camera = GetComponent<Camera>();
}
private void OnEnable()
{
GetComponent<Camera>().depthTextureMode = DepthTextureMode.Depth;
dMaterial = new Material(Shader.Find("Custom/DepthOfFiled"));
}
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
dMaterial.SetFloat("_FocusDistance", FocusDistance);
dMaterial.SetFloat("_FocusRange", 10f-10f*FocusRange);
if (dMaterial != null)
{
int rtW = src.width / BlurDownSample;
int rtH = src.height / BlurDownSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer0.filterMode = FilterMode.Bilinear;
Graphics.Blit(src, buffer0);
for (int i = 0; i < BlurIterations; i++)
{
dMaterial.SetFloat("_BlurSize", 1.0f + i * BlurSpread);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(buffer0, buffer1, dMaterial, 0);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(buffer0, buffer1, dMaterial, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
dMaterial.SetTexture("_BlurTex", buffer0);
Graphics.Blit(src, dest, dMaterial, 2);
RenderTexture.ReleaseTemporary(buffer0);
}
else
{
Graphics.Blit(src, dest);
}
}
private void Update()
{
if(this.FocusOn != null) {
this.FocusDistance = (this.FocusOn.position.z - this.camera.transform.position.z - camera.nearClipPlane) / (camera.farClipPlane - camera.nearClipPlane);
}
}
private void OnDrawGizmosSelected()
{
if (camera == null) {
camera = GetComponent<Camera>();
}
if (enabled && ShowGizmosWire) {
float interpZ = camera.nearClipPlane + FocusDistance * camera.farClipPlane;
float focusZ = transform.position.z + interpZ;
Vector3 fpos = new Vector3(transform.position.x, transform.position.y, focusZ);
Gizmos.color = new Color(0,1,0,0.2f);
float fov = camera.fieldOfView / 2 * Mathf.Deg2Rad;
float h = interpZ * Mathf.Tan(fov) * 2f;
float w = h * camera.aspect;
Gizmos.DrawCube(fpos, new Vector3(w,h,0.01f));
Gizmos.color = Color.green;
Gizmos.DrawWireCube(fpos, new Vector3(w, h, 0.01f));
}
}
}
DepthOfField.shader
Shader "Custom/DepthOfFiled"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {
}
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _CameraDepthTexture;
half4 _MainTex_TexelSize;
fixed _FocusDistance;
float _FocusRange;
sampler2D _BlurTex;
float _BlurSize;
struct v2f_blur {
float4 pos : SV_POSITION;
half2 uv[5]: TEXCOORD0;
};
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
half4 uv:TEXCOORD0;
half2 uv_depth:TEXCOORD1;
float4 vertex:SV_POSITION;
};
//https://www.desmos.com/calculator
fixed4 getRange(float x, float n){
x = clamp(abs(x), 0, 1/n);
float y = -(cos(n*x*3.1415926)-1);
return clamp(y, 0.0, 1.0);
}
float getDepthInterpolation(float2 uv){
float depth=SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv);
depth=Linear01Depth(depth);
return depth-_FocusDistance;
}
//模糊相关
v2f_blur vertBlurVertical(appdata_img v) {
v2f_blur o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
v2f_blur vertBlurHorizontal(appdata_img v) {
v2f_blur o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
return o;
}
fixed4 fragBlur(v2f_blur i) : SV_Target {
float weight[3] = {
0.4026, 0.2442, 0.0545};
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
float d = getDepthInterpolation(i.uv[0]);
for (int it = 1; it < 3; it++) {
float2 uv1 = i.uv[it*2-1];
float2 uv2 = i.uv[it*2];
float iter1 = getDepthInterpolation(uv1);
float iter2 = getDepthInterpolation(uv2);
//对于在焦点后面的,不进入焦点区域取样颜色,保持聚焦区域轮廓清晰。
iter1 = getRange(iter1, _FocusRange);
iter2 = getRange(iter2, _FocusRange);
//纯粹是为了替代if(d>0)逻辑运算
iter1 -= clamp(d*100, -1, 0);
iter2 -= clamp(d*100, -1, 0);
uv1 = lerp(i.uv[0], uv1, clamp(iter1, 0, 1));
uv2 = lerp(i.uv[0], uv2, clamp(iter2, 0, 1));
//}
sum += tex2D(_MainTex, uv1).rgb * weight[it];
sum += tex2D(_MainTex, uv2).rgb * weight[it];
}
return fixed4(sum, 1.0);
}
v2f vert(appdata v)
{
v2f o;
o.vertex=UnityObjectToClipPos(v.vertex);
o.uv.xy=v.uv;
o.uv.zw=v.uv;
o.uv_depth=v.uv;
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y<0){
o.uv.w=1.0-o.uv.w;
o.uv_depth.y=1.0-o.uv_depth.y;
}
#endif
return o;
}
fixed4 frag(v2f i):SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv.xy);
fixed4 blur=tex2D(_BlurTex,i.uv.zw);
fixed iter= getDepthInterpolation(i.uv_depth);
fixed cositer = getRange(iter, _FocusRange);
//return fixed4(bVa, bVa, bVa, 1);
return lerp(color,blur,cositer);
}
ENDCG
Pass {
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDCG
}
Pass {
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack Off
}