Unity 屏幕特效 之 简单地使用 Shader 获取深度,实现景深效果

 

Unity 屏幕特效 之 简单地使用 Shader 获取深度,实现景深效果_第1张图片

 

Unity 屏幕特效 之 简单地使用 Shader 获取深度,实现景深效果

 

目录

Unity 屏幕特效 之 简单地使用 Shader 获取深度,实现景深效果

一、简单介绍

二、Unity shander 几个关键宏或者函数

三、注意事项

四、这里看一下shader获得深度的效果

五、根据获得的深度实现景深效果

六、参考文章:


 

一、简单介绍

所谓屏幕后处理,简单来说就是渲染流水线的最后阶段,对由整个场景生成的一张图片进行处理,比如HDR,运动模糊等等效果,通过屏幕空间的后处理,可以整体改变整个游戏的风格或者效果。所以,要制作屏幕后处理,我们需要两样东西,一个是用于渲染后处理效果的shader,而另一个是我们需要调用这个渲染的脚本。

  • 什么是景深?

景深(DOF),是指在摄影机镜头或其他成像器前沿能够取得清晰图像的成像所测定的被摄物体前后距离范围。光圈、镜头、及焦平面到拍摄物的距离是影响景深的重要因素。

在聚焦完成后,焦点前后的范围内所呈现的清晰图像的距离,这一前一后的范围,便叫做景深。

在镜头前方(焦点的前、后)有一段一定长度的空间,当被摄物体位于这段空间内时,其在底片上的成像恰位于同一个弥散圆之间。被摄体所在的这段空间的长度,就叫景深。换言之,在这段空间内的被摄体,其呈现在底片面的影象模糊度,都在容许弥散圆的限定范围内,这段空间的长度就是景深。

 

  • 问题是,我们再 Unity Shader怎么确定物体离摄像机远近呢?

 

二、Unity shander 几个关键宏或者函数

1)_CameraDepthTexture//Unity3d提供给我们的深度图
2)float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,uv);//按uv坐标获取_CameraDepthTexture中的深度
3)depth = Linear01Depth(depth);//深度线性到01范围
 

三、注意事项

1)修改 Camer Clipping Planes 中的 Near 和 Far 可以更改效果

 

四、这里看一下shader获得深度的效果

1、下面黑白即是shader 或者到的深度效果

 

2、关键代码

1)DepthTest.shader

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/DepthTest" {

	CGINCLUDE
	#include "UnityCG.cginc"

	//仍然要声明一下_CameraDepthTexture这个变量,虽然Unity这个变量是unity内部赋值
	sampler2D _CameraDepthTexture;
	sampler2D _MainTex;
	float4	  _MainTex_TexelSize;

	struct v2f
	{
		float4 pos : SV_POSITION;
		float2 uv  : TEXCOORD0;
	};

	v2f vert(appdata_img v)
	{
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		o.uv.xy = v.texcoord.xy;

		return o;
	}

	fixed4 frag(v2f i) : SV_Target
	{
		//直接根据UV坐标取该点的深度值(1- i.uv 可以取反(上下,左右)画面)
		float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);

		//将深度值变为线性01空间
		depth = Linear01Depth(depth);

		// 这样使得,越近越黑,越远月亮
		return float4(depth, depth, depth, 1);
	}

		ENDCG

		SubShader
	{
		Pass
		{

			ZTest Off
			Cull Off
			ZWrite Off
			Fog{ Mode Off }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			ENDCG
		}

	}
}

 

2)DepthTextureTest.cs

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class DepthTextureTest : ScreenPostEffectBase
{
    void OnEnable()
    {
        GetComponent().depthTextureMode |= DepthTextureMode.Depth;
    }

    void OnDisable()
    {
        GetComponent().depthTextureMode &= ~DepthTextureMode.Depth;
    }

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            Graphics.Blit(source, destination, _Material);
        }
    }
}

 

3)ScreenPostEffectBase.cs

using UnityEngine;

//非运行时也触发效果
[ExecuteInEditMode]
//屏幕后处理特效一般都需要绑定在摄像机上
[RequireComponent(typeof(Camera))]
//提供一个后处理的基类,主要功能在于直接通过Inspector面板拖入shader,生成shader对应的材质
public class ScreenPostEffectBase : MonoBehaviour
{

    //Inspector面板上直接拖入
    public Shader shader = null;
    private Material _material = null;
    public Material _Material
    {
        get
        {
            if (_material == null)
                _material = GenerateMaterial(shader);
            return _material;
        }
    }

    //根据shader创建用于屏幕特效的材质
    protected Material GenerateMaterial(Shader shader)
    {
        // 系统是否支持
        if (!SystemInfo.supportsImageEffects)
        {
            return null;
        }

        if (shader == null)
            return null;
        //需要判断shader是否支持
        if (shader.isSupported == false)
            return null;
        Material material = new Material(shader);
        material.hideFlags = HideFlags.DontSave;
        if (material)
            return material;
        return null;
    }

}

 

3、挂载到场景中

Unity 屏幕特效 之 简单地使用 Shader 获取深度,实现景深效果_第2张图片

 

五、根据获得的深度实现景深效果

通过以上的演示,我们就可根据获得深度,获得距离camera的大概距离远近。

 

景深效果是一个复合效果,其中的模糊效果前面的文章也有介绍,这篇文章的重点也就是通过DepthTexture来混合清晰和模糊的图像,来达到我们想要的“重点”清晰,“陪衬”模糊的效果。

实现原理:大部分的景深效果是前景清晰,远景模糊,这也是景深的标准用法,不过有时候也有需要近景模糊,远景清晰的效果,或者前后都模糊,中间焦点位置清晰,在实现上我们通过像素点深度到达焦点的距离作为参数,在清晰和模糊图像之间插值,先计算远景的,结果与模糊图片再进行插值,得到最终的效果。
 

1、景深效果图,合理调整几个参数即可获得不同聚焦的景深效果

Unity 屏幕特效 之 简单地使用 Shader 获取深度,实现景深效果_第3张图片

 

2、景深效果图

 

3、关键代码

1)DepthOfField.shader

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/DepthOfField" {

	Properties{
		_MainTex("Base (RGB)", 2D) = "white" {}
		_BlurTex("Blur", 2D) = "white"{}
	}

		CGINCLUDE
#include "UnityCG.cginc"

		struct v2f_blur
	{
		float4 pos : SV_POSITION;
		float2 uv  : TEXCOORD0;
		float4 uv01 : TEXCOORD1;
		float4 uv23 : TEXCOORD2;
		float4 uv45 : TEXCOORD3;
	};

	struct v2f_dof
	{
		float4 pos : SV_POSITION;
		float2 uv  : TEXCOORD0;
		float2 uv1 : TEXCOORD1;
	};

	sampler2D _MainTex;
	float4 _MainTex_TexelSize;
	sampler2D _BlurTex;
	sampler2D_float _CameraDepthTexture;
	float4 _offsets;
	float _focalDistance;
	float _nearBlurScale;
	float _farBlurScale;

	//高斯模糊 vert shader
	v2f_blur vert_blur(appdata_img v)
	{
		v2f_blur o;
		_offsets *= _MainTex_TexelSize.xyxy;
		o.pos = UnityObjectToClipPos(v.vertex);
		o.uv = v.texcoord.xy;

		o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);
		o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;
		o.uv45 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 3.0;

		return o;
	}

	//高斯模糊 pixel shader
	fixed4 frag_blur(v2f_blur i) : SV_Target
	{
		fixed4 color = fixed4(0,0,0,0);
		color += 0.40 * tex2D(_MainTex, i.uv);
		color += 0.15 * tex2D(_MainTex, i.uv01.xy);
		color += 0.15 * tex2D(_MainTex, i.uv01.zw);
		color += 0.10 * tex2D(_MainTex, i.uv23.xy);
		color += 0.10 * tex2D(_MainTex, i.uv23.zw);
		color += 0.05 * tex2D(_MainTex, i.uv45.xy);
		color += 0.05 * tex2D(_MainTex, i.uv45.zw);
		return color;
	}

		//景深效果 vertex shader
		v2f_dof vert_dof(appdata_img v)
	{
		v2f_dof o;
		//mvp矩阵变换
		o.pos = UnityObjectToClipPos(v.vertex);
		//uv坐标传递
		o.uv.xy = v.texcoord.xy;
		o.uv1.xy = o.uv.xy;
		//dx中纹理从左上角为初始坐标,需要反向
#if UNITY_UV_STARTS_AT_TOP
		if (_MainTex_TexelSize.y < 0)
			o.uv.y = 1 - o.uv.y;
#endif	
		return o;
	}

	fixed4 frag_dof(v2f_dof i) : SV_Target
	{
		//取原始清晰图片进行uv采样
		fixed4 ori = tex2D(_MainTex, i.uv1);
	//取模糊普片进行uv采样
	fixed4 blur = tex2D(_BlurTex, i.uv);
	//取当位置对应的深度值
	float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
	//将深度值转化到01线性空间
	depth = Linear01Depth(depth);

	//如果depth小于焦点的物体,那么使用原始清晰图像,否则使用模糊的图像与清晰图像的差值,通过差值避免模糊和清晰之间明显的边界,结果为远景模糊效果
	fixed4 final = (depth <= _focalDistance) ? ori : lerp(ori, blur, clamp((depth - _focalDistance) * _farBlurScale, 0, 1));
	//上面的结果,再进行一次计算,如果depth大于焦点的物体,使用上面的结果和模糊图像差值,得到近景模糊效果
	final = (depth > _focalDistance) ? final : lerp(ori, blur, clamp((_focalDistance - depth) * _nearBlurScale, 0, 1));
	//焦点位置是清晰的图像,两边分别用当前像素深度距离焦点的距离进行差值,这样就达到原理焦点位置模糊的效果

	//上面的?在编译时会被编译成if语句,GPU并不擅长分支计算,而且如果有分支,两个分支都要跑。这里给了一个更优化一些的计算方式,不过语法比较晦涩
	//float focalTest = clamp(sign(depth - _focalDistance),0,1);
	//fixed4 final = (1 - focalTest) * ori + focalTest * lerp(ori, blur, clamp((depth - _focalDistance) * _farBlurScale, 0, 1));
	//final = (focalTest)* final + (1 - focalTest) * lerp(ori, blur, clamp((_focalDistance - depth) * _nearBlurScale, 0, 1));
	return final;
	}

		ENDCG

		SubShader
	{
		//pass 0: 高斯模糊
		Pass
		{
			ZTest Off
			Cull Off
			ZWrite Off
			Fog{ Mode Off }

			CGPROGRAM
			#pragma vertex vert_blur
			#pragma fragment frag_blur
			ENDCG
		}

			//pass 1: 景深效果
			Pass
		{

			ZTest Off
			Cull Off
			ZWrite Off
			Fog{ Mode Off }
			ColorMask RGBA

			CGPROGRAM
			#pragma vertex vert_dof
			#pragma fragment frag_dof
			ENDCG
		}

	}
}

 

2)DepthOfFiled.cs

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class DepthOfFiled : ScreenPostEffectBase
{

    [Range(0.0f, 100.0f)]
    public float focalDistance = 10.0f;
    [Range(0.0f, 100.0f)]
    public float nearBlurScale = 0.0f;
    [Range(0.0f, 1000.0f)]
    public float farBlurScale = 50.0f;
    //分辨率
    public int downSample = 1;
    //采样率
    public int samplerScale = 1;

    private Camera _mainCam = null;
    public Camera MainCam
    {
        get
        {
            if (_mainCam == null)
                _mainCam = GetComponent();
            return _mainCam;
        }
    }

    void OnEnable()
    {
        //maincam的depthTextureMode是通过位运算开启与关闭的
        MainCam.depthTextureMode |= DepthTextureMode.Depth;
    }

    void OnDisable()
    {
        MainCam.depthTextureMode &= ~DepthTextureMode.Depth;
    }

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            //首先将我们设置的焦点限制在远近裁剪面之间
            Mathf.Clamp(focalDistance, MainCam.nearClipPlane, MainCam.farClipPlane);

            //申请两块RT,并且分辨率按照downSameple降低
            RenderTexture temp1 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format);
            RenderTexture temp2 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format);

            //直接将场景图拷贝到低分辨率的RT上达到降分辨率的效果
            Graphics.Blit(source, temp1);

            //高斯模糊,两次模糊,横向纵向,使用pass0进行高斯模糊
            _Material.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
            Graphics.Blit(temp1, temp2, _Material, 0);
            _Material.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
            Graphics.Blit(temp2, temp1, _Material, 0);

            //景深操作,景深需要两的模糊效果图我们通过_BlurTex变量传入shader
            _Material.SetTexture("_BlurTex", temp1);
            //设置shader的参数,主要是焦点和远近模糊的权重,权重可以控制插值时使用模糊图片的权重
            _Material.SetFloat("_focalDistance", FocalDistance01(focalDistance));
            _Material.SetFloat("_nearBlurScale", nearBlurScale);
            _Material.SetFloat("_farBlurScale", farBlurScale);

            //使用pass1进行景深效果计算,清晰场景图直接从source输入到shader的_MainTex中
            Graphics.Blit(source, destination, _Material, 1);

            //释放申请的RT
            RenderTexture.ReleaseTemporary(temp1);
            RenderTexture.ReleaseTemporary(temp2);
        }
    }

    //计算设置的焦点被转换到01空间中的距离,以便shader中通过这个01空间的焦点距离与depth比较
    private float FocalDistance01(float distance)
    {
        return MainCam.WorldToViewportPoint((distance - MainCam.nearClipPlane) * MainCam.transform.forward + MainCam.transform.position).z / (MainCam.farClipPlane - MainCam.nearClipPlane);
    }


}

 

六、参考文章:

https://blog.csdn.net/puppet_master/article/details/52819874

 

Unity 屏幕特效 之 简单地使用 Shader 获取深度,实现景深效果_第4张图片

 

 

你可能感兴趣的:(Shader,Unity,屏幕特效)