由于方体的法线是垂直关系的 利用视角法线点积的方式做边缘光效果非常不理想
所以只能在屏幕空间处理边缘效果
此方法是先用commandbuff将这个物体用白色shader绘制出来作为RenderTarget,并且申请两个图片缓存空间,利用两个缓存空间,再将图片用模糊shader进行多次处理,输出给模型材质,最后在模型shader的屏幕空间输出边缘光效果
三个脚本+两个shader
以下是代码分享
//此代码是处理图片
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
public class CommandBufferOutline : MonoBehaviour
{
private Material outlinePrePassMaterial = null;
[HideInInspector]
public int downSample = 3;
[HideInInspector]
public float samplerScale = 1.5f;
[HideInInspector]
public int iteration = 2;
[HideInInspector]
public List<GameObject> ob = new List<GameObject>();
List<Renderer> renderers = new List<Renderer>();
private CommandBuffer commandBuffer = null;
#if UNITY_EDITOR
float lastsamplerScale;
int lastiteration;
int lastWidth;
int lastHeight;
#endif
void Start()
{
if (outlinePrePassMaterial == null)
outlinePrePassMaterial = new Material(Shader.Find("Hide/OutlinePrePassCB"));
SetCommandBuff();
}
#if UNITY_EDITOR
void Update()
{
if ( (lastWidth != Screen.width >> downSample || lastHeight != Screen.height >> downSample || lastiteration != iteration || lastsamplerScale != samplerScale))
{
SetCommandBuff();
}
}
#endif
void SetCommandBuff() {
if (commandBuffer!=null) {
Camera.main.RemoveCommandBuffer(CameraEvent.BeforeForwardOpaque, commandBuffer);
}
commandBuffer = new CommandBuffer();
#if UNITY_EDITOR
lastWidth = Screen.width >> downSample;
lastHeight = Screen.height >> downSample;
lastiteration = iteration;
lastsamplerScale = samplerScale;
#endif
foreach (GameObject i in ob)
{
renderers.Add(i.GetComponentInChildren<Renderer>());
}
int temp1 = Shader.PropertyToID("_Temp1");
int temp2 = Shader.PropertyToID("_Temp2");
commandBuffer.ReleaseTemporaryRT(temp1);
commandBuffer.ReleaseTemporaryRT(temp2);
commandBuffer.GetTemporaryRT(temp1, Screen.width >> downSample, Screen.height >> downSample, 0, FilterMode.Bilinear);
commandBuffer.GetTemporaryRT(temp2, Screen.width >> downSample, Screen.height >> downSample, 0, FilterMode.Bilinear);
commandBuffer.SetRenderTarget(temp1);
commandBuffer.ClearRenderTarget(true, true, Color.black);
foreach (Renderer r in renderers)
commandBuffer.DrawRenderer(r, outlinePrePassMaterial, 0, 0);
for (int i = 0; i<iteration; i++)
{
commandBuffer.SetGlobalVector("_offsets", new Vector4(0, samplerScale, 0, 0));
commandBuffer.Blit(temp1, temp2, outlinePrePassMaterial, 1);
commandBuffer.SetGlobalVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
commandBuffer.Blit(temp2, temp1, outlinePrePassMaterial, 1);
}
commandBuffer.SetGlobalTexture("_PrePassTexture", temp1);
Camera.main.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, commandBuffer);
}
}
//此代码是动态添加脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OutlineBlurRender : MonoBehaviour {
GameObject mygameobject;
GameObject gameobjectcb;
GameObject gameobject;
CommandBufferOutline cbo ;
bool isCreat = false;
void Awake() {
mygameobject = gameObject;
GameObject gameobjectCB = GameObject.Find("CommandBuffRender");
if (gameobjectCB)
{
isCreat = true;
}
if (!isCreat)
{
gameobject = new GameObject("CommandBuffRender");
}
if (gameobject && !gameobject.GetComponent<CommandBufferOutline>() )
{
cbo = gameobject.AddComponent<CommandBufferOutline>();
}
gameobjectcb = GameObject.Find("CommandBuffRender");
if (gameobjectcb)
{
cbo = gameobjectcb.GetComponent<CommandBufferOutline>();
cbo.ob.Add(mygameobject);
}
}
}
//此代码用来动态调节参数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OutlineBlurRenderParameter : MonoBehaviour{
[Range(0, 4)]
public int downSample = 3;
[Range(0, 2)]
public float samplerScale = 1.5f;
[Range(0, 3)]
public int iteration = 2;
CommandBufferOutline cbo;
void Start () {
GameObject ob = GameObject.Find("CommandBuffRender");
if (ob) {
cbo = ob.GetComponent<CommandBufferOutline>();
}
}
void Update () {
if (cbo || cbo.downSample != downSample || cbo.iteration != iteration || cbo.samplerScale != samplerScale)
{
cbo.downSample = downSample;
cbo.samplerScale = samplerScale;
cbo.iteration = iteration;
}
}
}
//此shader挂在模型上显示效果
Shader "Artist/Scene/OutLinePostEffectCB"
{
Properties
{
_Diffuse("Diffuse (RGB)", 2D) = "white" {}
_RimParameter("RimParameter", Vector) = (0,0,0,0)
_RimStrength ("RimStrength", Range(0.0, 5)) = 2.0
_RimColor ("RimColor",Color) = (0.6, 0.7, 1.0, 1.0)
[Toggle] _USERIM("USERIM",float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma shader_feature _USERIM_ON
#pragma multi_compile_fwdbase //nodynlightmap //noshadow
// #pragma skip_variants DIRLIGHTMAP_SEPARATE VERTEXLIGHT_ON
#define USING_FOG (defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2))
#pragma multi_compile_fog
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float2 texcoord1 : TEXCOORD1;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float2 uv0 : TEXCOORD1;
float2 uv1 : TEXCOORD2;
#if USING_FOG
fixed fog : TEXCOORD3;
#endif
UNITY_VERTEX_OUTPUT_STEREO
};
float4 _RimParameter;
sampler2D _PrePassTexture;
sampler2D _Diffuse;
float2 uv0;
float4 _RimColor;
float _RimStrength;
v2f vert (appdata v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
float3 eyePos = UnityObjectToViewPos(v.vertex);
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos);
o.uv0 = v.texcoord;
o.uv1 = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
#if USING_FOG
float fogCoord = length(eyePos.xyz);
UNITY_CALC_FOG_FACTOR_RAW(fogCoord);
o.fog = saturate(unityFogFactor);
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = i.scrPos.xy / i.scrPos.w;
uv=half2(uv.x+_RimParameter.x,uv.y+_RimParameter.y);
fixed3 col = tex2D(_PrePassTexture, uv).rgb;
fixed3 diffuse = tex2D(_Diffuse, i.uv0).rgb;
half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv1);
fixed4 color ;
#if !defined(LIGHTMAP_OFF)
color = half4(DecodeLightmap(bakedColorTex),1);
#else
color = fixed4(1,1,1,1);
#endif
#if USING_FOG
diffuse = lerp(unity_FogColor.rgb, diffuse, i.fog);
#endif
#if _USERIM_ON
return fixed4(diffuse * color.rgb + (1-col)*_RimStrength*_RimColor, 1);
#else
return fixed4(diffuse * color.rgb , 1);
//return fixed4(color.rgb , 1);
#endif
}
ENDCG
}
}
}
//此shader第一个pass用来做模糊处理
//第二个pass做渲染白色物体
Shader "Hide/OutlinePrePassCB"
{
Properties{
_MainTex("Base (RGB)", 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;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
float4 _offsets;
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;
}
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;
}
ENDCG
SubShader
{
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//这个Pass直接输出描边颜色
return fixed4(1,1,1,1);
}
ENDCG
}
Pass
{
ZTest Off
Cull Off
ZWrite Off
Fog{ Mode Off }
CGPROGRAM
#pragma vertex vert_blur
#pragma fragment frag_blur
ENDCG
}
}
}
2018/7/23 把老版本的方法也发出来记录一下
原理都是差不多的,不过没有用commandbuff,有点费
//liujiuqiang for JUBIAN in 2018/7/18
using UnityEngine;
using System.Collections.Generic;
public class OutlineBlur : MonoBehaviour
{
Shader BlurShader;
public LayerMask outlineBlurLayer;
private Material BlurMaterial = null;
Camera outlineBlurCam;
Camera mainCamera;
Renderer render;
GameObject outlineBlurObj;
[Range(0, 10)]
public int downSample = 8;
[Range(0, 5)]
public int iteration = 3;
[Range(0, 3)]
public float samplerScale = 1.5f;
BlurRender pre;
public GameObject gameobject;
void Awake()
{
mainCamera = GameObject.FindWithTag("MainCamera").GetComponentInChildren<Camera>();
if (outlineBlurObj == null)
{
outlineBlurObj = new GameObject("outlineBlurCam");
}
else
{
return;
}
outlineBlurCam = outlineBlurObj.AddComponent<Camera>();
outlineBlurCam.transform.parent = mainCamera.transform;
outlineBlurCam.transform.localPosition = Vector3.zero;
outlineBlurCam.transform.localRotation = Quaternion.identity;
outlineBlurCam.farClipPlane = mainCamera.farClipPlane;
outlineBlurCam.nearClipPlane = mainCamera.nearClipPlane;
outlineBlurCam.backgroundColor = Color.black;
outlineBlurCam.clearFlags = CameraClearFlags.Color;
outlineBlurCam.cullingMask = outlineBlurLayer;
outlineBlurObj.AddComponent<BlurRender>();
pre = mainCamera.GetComponentInChildren<BlurRender>();
}
bool isChange = false;
void Update()
{
if (pre && !isChange)
{
pre.gameobject = gameobject;
pre.SetValue();
isChange = true;
}
else if (pre){
pre.downSample = downSample;
pre.iteration = iteration;
pre.samplerScale = samplerScale;
}
}
void OnEnable()
{
if (outlineBlurCam)
outlineBlurCam.enabled = true;
}
void OnDisable()
{
if (outlineBlurCam)
{
outlineBlurCam.enabled = false;
pre.enabled = false;
}
}
void OnDestroy()
{
if (pre.rt!=null || pre.rta != null || pre.rtb != null)
{
RenderTexture.ReleaseTemporary(pre.rt);
RenderTexture.ReleaseTemporary(pre.rta);
RenderTexture.ReleaseTemporary(pre.rtb);
}
if (outlineBlurCam) {
DestroyImmediate(outlineBlurCam.gameObject);
}
}
}
//liujiuqiang for JUBIAN in 2018/7/18
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BlurRender : MonoBehaviour {
public RenderTexture rt;
public GameObject gameobject;
List<Material> mBlur = new List<Material>();
Renderer rd;
Camera outlineBlurCam;
public RenderTexture rta;
public RenderTexture rtb;
public int downSample = 8;
Shader outshader;
Shader outshaderpass;
Material mt;
public float samplerScale = 1.5f;
public int iteration = 3;
OutlineBlur ur;
public void SetValue()
{
outlineBlurCam = GetComponent<Camera>();
//ob = GameObject.Find("guangchangbufen");
if (gameobject)
{
for (int i = 0; i < gameobject.transform.childCount; i++)
{
if (gameobject.transform.GetChild(i).gameObject.layer == LayerMask.NameToLayer("AdditionalRim"))
{
rd = gameobject.transform.GetChild(i).gameObject.GetComponent<Renderer>();
mBlur.Add(rd.sharedMaterial);
}
}
}
outshader = Shader.Find("Hide/OutlinePrePass");
outshaderpass = Shader.Find("Hide/OutLinePostPass");
mt = new Material(outshaderpass);
if (rt == null)
rt = RenderTexture.GetTemporary(outlineBlurCam.pixelWidth / downSample, outlineBlurCam.pixelHeight / downSample, 0);
if (rta == null)
rta = RenderTexture.GetTemporary(outlineBlurCam.pixelWidth / downSample, outlineBlurCam.pixelHeight / downSample, 0);
if (rtb == null)
rtb = RenderTexture.GetTemporary(outlineBlurCam.pixelWidth / downSample, outlineBlurCam.pixelHeight / downSample, 0);
outlineBlurCam.targetTexture = rt;
}
void Update () {
if (outlineBlurCam.enabled)
{
if (outlineBlurCam != null && (rt.width != Screen.width / downSample || rt.height != Screen.height / downSample ))
{
RenderTexture.ReleaseTemporary(rt);
rt = RenderTexture.GetTemporary(Screen.width / downSample, Screen.height / downSample, 0);
RenderTexture.ReleaseTemporary(rta);
rta = RenderTexture.GetTemporary(Screen.width / downSample, Screen.height / downSample, 0);
RenderTexture.ReleaseTemporary(rtb);
rtb = RenderTexture.GetTemporary(Screen.width / downSample, Screen.height / downSample, 0);
}
outlineBlurCam.targetTexture = rt;
//outlineBlurCam.RenderWithShader(outshader, "");
}
if (mBlur!=null)
{
for (int i = 0; i < mBlur.Count; i++)
{
mBlur[i].SetTexture("_MainDiffuse", rt);
}
}
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, rta);
for (int i = 0; i < iteration; i++)
{
mt.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
Graphics.Blit(rta, rtb, mt, 0);
mt.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
Graphics.Blit(rtb, rta, mt, 0);
}
Graphics.Blit(rta, destination);
}
}
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
//liujiuqiang for JUBIAN in 2018/7/18
Shader "Artist/Scene/OutLinePostEffect"
{
Properties
{
_Diffuse("Diffuse (RGB)", 2D) = "white" {}
_RimParameter("RimParameter", Vector) = (0,0,0,0)
_RimStrength ("RimStrength", Range(0.0, 5)) = 2.0
_RimColor ("RimColor",Color) = (0.6, 0.7, 1.0, 1.0)
[Toggle] _USERIM("USERIM",float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma shader_feature _USERIM_ON
#pragma multi_compile_fwdbase //nodynlightmap //noshadow
// #pragma skip_variants DIRLIGHTMAP_SEPARATE VERTEXLIGHT_ON
#define USING_FOG (defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2))
#pragma multi_compile_fog
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float2 texcoord1 : TEXCOORD1;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float2 uv0 : TEXCOORD1;
float2 uv1 : TEXCOORD2;
#if USING_FOG
fixed fog : TEXCOORD3;
#endif
UNITY_VERTEX_OUTPUT_STEREO
};
float4 _RimParameter;
sampler2D _MainDiffuse;
sampler2D _Diffuse;
float2 uv0;
float4 _RimColor;
float _RimStrength;
v2f vert (appdata v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
float3 eyePos = UnityObjectToViewPos(v.vertex);
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos);
o.uv0 = v.texcoord;
o.uv1 = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
#if USING_FOG
float fogCoord = length(eyePos.xyz);
UNITY_CALC_FOG_FACTOR_RAW(fogCoord);
o.fog = saturate(unityFogFactor);
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = i.scrPos.xy / i.scrPos.w;
uv=half2(uv.x+_RimParameter.x,(uv.y)+_RimParameter.y);
fixed3 col = tex2D(_MainDiffuse, uv).rgb;
fixed3 diffuse = tex2D(_Diffuse, i.uv0).rgb;
half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv1);
fixed4 color ;
#if !defined(LIGHTMAP_OFF)
color = half4(DecodeLightmap(bakedColorTex),1);
#else
color = fixed4(1,1,1,1);
#endif
#if USING_FOG
diffuse = lerp(unity_FogColor.rgb, diffuse, i.fog);
#endif
#if _USERIM_ON
return fixed4(diffuse * color.rgb + (1-col)*_RimStrength*_RimColor, 1);
#else
return fixed4(diffuse * color.rgb , 1);
//return fixed4(color.rgb , 1);
#endif
}
ENDCG
}
}
}
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
//liujiuqiang for JUBIAN in 2018/7/18
Shader "Hide/OutLinePostPass"
{
Properties
{
_MainTex("MainTex (RGB)", 2D) = "white" {}
_offsets("offsets", Vector) = (0,0,0,0)
}
SubShader
{
ZTest Off
Cull Off
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert_blur
#pragma fragment frag_blur
#include "UnityCG.cginc"
struct v2f_blur
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 uv01 : TEXCOORD1;
float4 uv23 : TEXCOORD2;
float4 uv45 : TEXCOORD3;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
float4 _offsets;
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;
}
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;
}
ENDCG
}
}
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
//liujiuqiang for JUBIAN in 2018/7/18
Shader "Hide/OutlinePrePass"
{
SubShader
{
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
fixed4 _OutlineCol;
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return fixed4(1,1,1,1);
}
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}