Unity 屏幕特效 之 簡單地使用 Shader 獲取深度,實現景深效果
目錄
Unity 屏幕特效 之 簡單地使用 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<Camera>().depthTextureMode |= DepthTextureMode.Depth;
}
void OnDisable()
{
GetComponent<Camera>().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、掛載到場景中
五、根據獲得的深度實現景深效果
通過以上的演示,我們就可根據獲得深度,獲得距離camera的大概距離遠近。
景深效果是一個複合效果,其中的模糊效果前面的文章也有介紹,這篇文章的重點也就是通過DepthTexture來混合清晰和模糊的圖像,來達到我們想要的“重點”清晰,“陪襯”模糊的效果。
實現原理:大部分的景深效果是前景清晰,遠景模糊,這也是景深的標準用法,不過有時候也有需要近景模糊,遠景清晰的效果,或者前後都模糊,中間焦點位置清晰,在實現上我們通過像素點深度到達焦點的距離作爲參數,在清晰和模糊圖像之間插值,先計算遠景的,結果與模糊圖片再進行插值,得到最終的效果。
1、景深效果圖,合理調整幾個參數即可獲得不同聚焦的景深效果
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<Camera>();
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