效果
原理
使用后处理,
- 在后处理阶段先渲染产生一张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
}
}
}