效果
原理
使用後處理,
- 在後處理階段先渲染產生一張RenderTexture,包含要被描邊的物體,使用描邊色渲染。
- 高斯模糊RenderTexture,會產生邊緣
- 用高斯模糊的圖片反向剔除未模糊的圖,這樣只保留模糊多出的部分。
- 此時RenderTexture上爲只有邊緣的圖,將此邊緣圖與渲染結果圖進行混合並輸出屏幕,即得到結果。
實現
OutLineCameraComponent.cs 掛載在攝像機下,賦值兩個shader。
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Rendering;
[ExecuteInEditMode]
public class OutLineCameraComponent
{
private RenderTexture renderTexture = null;
private CommandBuffer commandBuffer = null;
[Header("Outline/OutLineEffect.shader")]
public Shader preoutlineShader = null;
[Header("Outline/OutlinePrePass.shader")]
public Shader shader = null;
private Material _material = null;
public Material mMaterial
{
get
{
if (_material == null)
_material = GenerateMaterial(shader);
return _material;
}
}
[Header("採樣範圍")]
public float samplerArea = 1;
[Header("降分辨率")]
public int downSample = 1;
[Header("迭代次數")]
public int iteration = 2;
[Header("描邊強度")]
[Range(0.0f, 10.0f)]
public float outLineStrength = 3.0f;
//根據shader創建用於屏幕特效的材質
protected Material GenerateMaterial(Shader shader)
{
if (shader == null)
return null;
if (shader.isSupported == false)
return null;
Material material = new Material(shader);
material.hideFlags = HideFlags.DontSave;
if (material)
return material;
return null;
}
//目標對象
private List<OutLineTargetComponent> targetObjects = new List<OutLineTargetComponent>();
public void AddTarget(OutLineTargetComponent target)
{
if(target.material==null)
target.material = new Material(preoutlineShader);
targetObjects.Add(target);
RefreshCommandBuff();
}
public void RemoveTarget(OutLineTargetComponent target)
{
bool found = false;
for (int i = 0; i < targetObjects.Count; i++)
{
if (targetObjects[i] == target)
{
targetObjects.Remove(target);
DestroyImmediate(target.material);
target.material = null;
found = true;
break;
}
}
if(found)
RefreshCommandBuff();
}
public void RefreshCommandBuff()
{
if (renderTexture)
RenderTexture.ReleaseTemporary(renderTexture);
renderTexture = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample, 0);
commandBuffer = new CommandBuffer();
commandBuffer.SetRenderTarget(renderTexture);
commandBuffer.ClearRenderTarget(true, true, Color.black);
for (int i = 0; i < targetObjects.Count; i++)
{
Renderer[] renderers = targetObjects[i].GetComponentsInChildren<Renderer>();
foreach (Renderer r in renderers)
commandBuffer.DrawRenderer(r, targetObjects[i].material);
}
}
void OnEnable()
{
if (preoutlineShader == null)
return;
RefreshCommandBuff();
}
void OnDisable()
{
if (renderTexture)
{
RenderTexture.ReleaseTemporary(renderTexture);
renderTexture = null;
}
if (commandBuffer != null)
{
commandBuffer.Release();
commandBuffer = null;
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (mMaterial && renderTexture && commandBuffer != null)
{
Graphics.ExecuteCommandBuffer(commandBuffer);
//對RT進行Blur處理
RenderTexture temp1 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0);
RenderTexture temp2 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0);
//高斯模糊,兩次模糊,橫向縱向,使用pass0進行高斯模糊
mMaterial.SetVector("_offsets", new Vector4(0, samplerArea, 0, 0));
Graphics.Blit(renderTexture, temp1, mMaterial, 0);
mMaterial.SetVector("_offsets", new Vector4(samplerArea, 0, 0, 0));
Graphics.Blit(temp1, temp2, mMaterial, 0);
//如果有疊加再進行迭代模糊處理
for(int i = 0; i < iteration; i++)
{
mMaterial.SetVector("_offsets", new Vector4(0, samplerArea, 0, 0));
Graphics.Blit(temp2, temp1, mMaterial, 0);
mMaterial.SetVector("_offsets", new Vector4(samplerArea, 0, 0, 0));
Graphics.Blit(temp1, temp2, mMaterial, 0);
}
//用模糊圖和原始圖計算出輪廓圖
mMaterial.SetTexture("_BlurTex", temp2);
Graphics.Blit(renderTexture, temp1, mMaterial, 1);
//輪廓圖和場景圖疊加
mMaterial.SetTexture("_BlurTex", temp1);
mMaterial.SetFloat("_OutlineStrength", outLineStrength);
Graphics.Blit(source, destination, mMaterial, 2);
RenderTexture.ReleaseTemporary(temp1);
RenderTexture.ReleaseTemporary(temp2);
}
else
{
Graphics.Blit(source, destination);
}
}
}
OutLineTargetComponent.cs 掛載在要使用描邊的物體上
using UnityEngine;
[ExecuteInEditMode]
public class OutLineTargetComponent : MonoBehaviour
{
public Color color = Color.green;
public Material material { set; get; }
void OnEnable()
{
Camera[] allCameras = Camera.allCameras;
for (int i = 0; i < allCameras.Length; i++)
{
if (allCameras[i].GetComponent<OutLineCameraComponent>()!=null)
{
allCameras[i].GetComponent<OutLineCameraComponent>().AddTarget(this);
}
}
}
private void Update()
{
if(material!=null)
material.SetColor("_OutLineColor", color);
}
void OnDisable()
{
Camera[] allCameras = Camera.allCameras;
for (int i = 0; i < allCameras.Length; i++)
{
if (allCameras[i].GetComponent<OutLineCameraComponent>()!=null)
{
allCameras[i].GetComponent<OutLineCameraComponent>().RemoveTarget(this);
}
}
}
}
OutlinePrePass.shader
Shader "OutLine/OutlinePrePass"
{
SubShader
{
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
fixed4 _OutLineColor;
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutLineColor;
}
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
OutLineEffect.shader
Shader "OutLine/OutLineEffect" {
Properties{
_MainTex("Base (RGB)", 2D) = "white" {}
_BlurTex("Blur", 2D) = "white"{}
}
CGINCLUDE
#include "UnityCG.cginc"
//用於剔除中心留下輪廓
struct v2f_cull
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
//用於模糊
struct v2f_blur
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 uv01 : TEXCOORD1;
float4 uv23 : TEXCOORD2;
float4 uv45 : TEXCOORD3;
};
//用於最後疊加
struct v2f_add
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float2 uv1 : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
sampler2D _BlurTex;
float4 _BlurTex_TexelSize;
float4 _offsets;
float _OutlineStrength;
//獲得輪廓 pass 1
v2f_cull vert_cull(appdata_img v)
{
v2f_cull o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord.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_cull(v2f_cull i) : SV_Target
{
fixed4 colorMain = tex2D(_MainTex, i.uv);
fixed4 colorBlur = tex2D(_BlurTex, i.uv);
return colorBlur - colorMain;
}
//高斯模糊 pass 0
v2f_blur vert_blur(appdata_img v)
{
v2f_blur o;
_offsets *= _MainTex_TexelSize.xyxy;
o.pos = mul(UNITY_MATRIX_MVP, 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;
}
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;
}
//最終疊加 pass 2
v2f_add vert_final(appdata_img v)
{
v2f_add o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.texcoord.xy;
o.uv1.xy = o.uv.xy;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y;
#endif
return o;
}
fixed4 frag_final(v2f_add i) : SV_Target
{
fixed4 ori = tex2D(_MainTex, i.uv1);
fixed4 blur = tex2D(_BlurTex, i.uv);
fixed4 final = ori + blur * _OutlineStrength;
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 }
CGPROGRAM
#pragma vertex vert_cull
#pragma fragment frag_cull
ENDCG
}
//pass 2: 最終疊加
Pass
{
ZTest Off
Cull Off
ZWrite Off
Fog{ Mode Off }
CGPROGRAM
#pragma vertex vert_final
#pragma fragment frag_final
ENDCG
}
}
}