大家好,又见面了,我是你们的朋友全栈君。
简介
简单描边效果的原理
边缘光效果。边缘光的效果如下图所示:
开启深度写入,剔除正面的描边效果
//描边Shader
//by:puppet_master
//2017.1.5
Shader "ApcShader/Outline"
{
//属性
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_OutlineCol("OutlineCol", Color) = (1,0,0,1)
_OutlineFactor("OutlineFactor", Range(0,1)) = 0.1
_MainTex("Base 2D", 2D) = "white"{}
}
//子着色器
SubShader
{
//描边使用两个Pass,第一个pass沿法线挤出一点,只输出描边的颜色
Pass
{
//剔除正面,只渲染背面,对于大多数模型适用,不过如果需要背面的,就有问题了
Cull Front
CGPROGRAM
#include "UnityCG.cginc"
fixed4 _OutlineCol;
float _OutlineFactor;
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
//在vertex阶段,每个顶点按照法线的方向偏移一部分,不过这种会造成近大远小的透视问题
//v.vertex.xyz += v.normal * _OutlineFactor;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//将法线方向转换到视空间
float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
//将视空间法线xy坐标转化到投影空间,只有xy需要,z深度不需要了
float2 offset = TransformViewToProjection(vnormal.xy);
//在最终投影阶段输出进行偏移操作
o.pos.xy += offset * _OutlineFactor;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//这个Pass直接输出描边颜色
return _OutlineCol;
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
//正常着色的Pass
Pass
{
CGPROGRAM
//引入头文件
#include "Lighting.cginc"
//定义Properties中的变量
fixed4 _Diffuse;
sampler2D _MainTex;
//使用了TRANSFROM_TEX宏就需要定义XXX_ST
float4 _MainTex_ST;
//定义结构体:vertex shader阶段输出的内容
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float2 uv : TEXCOORD1;
};
//定义顶点shader,参数直接使用appdata_base(包含position, noramal, texcoord)
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//通过TRANSFORM_TEX宏转化纹理坐标,主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
return o;
}
//定义片元shader
fixed4 frag(v2f i) : SV_Target
{
//unity自身的diffuse也是带了环境光,这里我们也增加一下环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.xyz;
//归一化法线,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出的
fixed3 worldNormal = normalize(i.worldNormal);
//把光照方向归一化
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//根据半兰伯特模型计算像素的光照信息
fixed3 lambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
//最终输出颜色为lambert光强*材质diffuse颜色*光颜色
fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz + ambient;
//进行纹理采样
fixed4 color = tex2D(_MainTex, i.uv);
color.rgb = color.rgb* diffuse;
return fixed4(color);
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
//前面的Shader失效的话,使用默认的Diffuse
FallBack "Diffuse"
}
原始模型渲染采用了
半兰伯特Diffuse进行渲染,主要是前面多了一个描边的Pass。这个Pass里,我们没有关闭深度写入,主要是开启了模型的正面剔除,这样,在这个Pass渲染的时候,就只会渲染模型的背面,让背面向外拓展一下,既不会影响什么,并且背面一般都在正面的后面,一般情况下不会遮挡住正面,正好符合我们后面的部分外拓的需求。这个的主要优点是没有关闭深度写入,因为关闭深度写入,引入的其他问题实在是太多了。
//描边使用两个Pass,第一个pass沿法线挤出一点,只输出描边的颜色 Pass { //剔除正面,只渲染背面,对于大多数模型适用,不过如果需要背面的,就有问题了 Cull Front //控制深度偏移,描边pass远离相机一些,防止与正常pass穿插 Offset 1,1 CGPROGRAM #include "UnityCG.cginc" fixed4 _OutlineCol; float _OutlineFactor; struct v2f { float4 pos : SV_POSITION; }; v2f vert(appdata_full v) { v2f o; //在vertex阶段,每个顶点按照法线的方向偏移一部分,不过这种会造成近大远小的透视问题 //v.vertex.xyz += v.normal * _OutlineFactor; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); //将法线方向转换到视空间 float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal); //将视空间法线xy坐标转化到投影空间 float2 offset = TransformViewToProjection(vnormal.xy); //在最终投影阶段输出进行偏移操作 o.pos.xy += offset * _OutlineFactor; return o; } fixed4 frag(v2f i) : SV_Target { //这个Pass直接输出描边颜色 return _OutlineCol; } //使用vert函数和frag函数 #pragma vertex vert #pragma fragment frag ENDCG }
这样,我们的描边效果也可以支持不能背面剔除的模型了:
Offset指令
这篇文章:
//描边Shader
//by:puppet_master
//2017.1.10
Shader "ApcShader/OutlineZOffset"
{
//属性
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_OutlineCol("OutlineCol", Color) = (1,0,0,1)
_MainTex("Base 2D", 2D) = "white"{}
}
//子着色器
SubShader
{
//描边使用两个Pass,第一个Pass渲染背面,但是拉近一点
Pass
{
//剔除正面,只渲染背面
Cull Front
//拉近一点,为了与后面的Pass重叠
Offset -1,-1
CGPROGRAM
#include "UnityCG.cginc"
fixed4 _OutlineCol;
float _OutlineFactor;
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
{
//这个Pass直接输出描边颜色
return _OutlineCol;
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
//正常着色的Pass,拉远一点
Pass
{
//拉远一点,强行导致上一个Pass渲染的背面与此处发生Z-Fighting
Offset 3,-1
CGPROGRAM
//引入头文件
#include "Lighting.cginc"
//定义Properties中的变量
fixed4 _Diffuse;
sampler2D _MainTex;
//使用了TRANSFROM_TEX宏就需要定义XXX_ST
float4 _MainTex_ST;
//定义结构体:vertex shader阶段输出的内容
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float2 uv : TEXCOORD1;
};
//定义顶点shader,参数直接使用appdata_base(包含position, noramal, texcoord)
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//通过TRANSFORM_TEX宏转化纹理坐标,主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
return o;
}
//定义片元shader
fixed4 frag(v2f i) : SV_Target
{
//unity自身的diffuse也是带了环境光,这里我们也增加一下环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.xyz;
//归一化法线,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出的
fixed3 worldNormal = normalize(i.worldNormal);
//把光照方向归一化
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//根据半兰伯特模型计算像素的光照信息
fixed3 lambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
//最终输出颜色为lambert光强*材质diffuse颜色*光颜色
fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz + ambient;
//进行纹理采样
fixed4 color = tex2D(_MainTex, i.uv);
color.rgb = color.rgb* diffuse;
return fixed4(color);
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
//前面的Shader失效的话,使用默认的Diffuse
FallBack "Diffuse"
}
效果如下:
参考文章。
关闭深度写入的描边效果实现
//描边Shader
//by:puppet_master
//2017.1.9
Shader "ApcShader/OutlineZWriteOff"
{
//属性
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_OutlineCol("OutlineCol", Color) = (1,0,0,1)
_OutlineFactor("OutlineFactor", Range(0,1)) = 0.1
_MainTex("Base 2D", 2D) = "white"{}
}
//子着色器
SubShader
{
//描边使用两个Pass,第一个pass沿法线挤出一点,只输出描边的颜色
Pass
{
//剔除正面,只渲染背面
Cull Front
//关闭深度写入
ZWrite Off
CGPROGRAM
#include "UnityCG.cginc"
fixed4 _OutlineCol;
float _OutlineFactor;
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//将法线方向转换到视空间
float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
//将视空间法线xy坐标转化到投影空间
float2 offset = TransformViewToProjection(vnormal.xy);
//在最终投影阶段输出进行偏移操作
o.pos.xy += offset * _OutlineFactor;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//这个Pass直接输出描边颜色
return _OutlineCol;
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
//正常着色的Pass
Pass
{
CGPROGRAM
//引入头文件
#include "Lighting.cginc"
//定义Properties中的变量
fixed4 _Diffuse;
sampler2D _MainTex;
//使用了TRANSFROM_TEX宏就需要定义XXX_ST
float4 _MainTex_ST;
//定义结构体:vertex shader阶段输出的内容
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float2 uv : TEXCOORD1;
};
//定义顶点shader,参数直接使用appdata_base(包含position, noramal, texcoord)
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//通过TRANSFORM_TEX宏转化纹理坐标,主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
return o;
}
//定义片元shader
fixed4 frag(v2f i) : SV_Target
{
//unity自身的diffuse也是带了环境光,这里我们也增加一下环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.xyz;
//归一化法线,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出的
fixed3 worldNormal = normalize(i.worldNormal);
//把光照方向归一化
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//根据半兰伯特模型计算像素的光照信息
fixed3 lambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
//最终输出颜色为lambert光强*材质diffuse颜色*光颜色
fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz + ambient;
//进行纹理采样
fixed4 color = tex2D(_MainTex, i.uv);
color.rgb = color.rgb* diffuse;
return fixed4(color);
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
//前面的Shader失效的话,使用默认的Diffuse
FallBack "Diffuse"
}
结果如下:
//描边Shader
//by:puppet_master
//2017.1.9
Shader "ApcShader/OutlineZWriteOff"
{
//属性
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_OutlineCol("OutlineCol", Color) = (1,0,0,1)
_OutlineFactor("OutlineFactor", Range(0,1)) = 0.1
_MainTex("Base 2D", 2D) = "white"{}
}
//子着色器
SubShader
{
//让渲染队列靠后,并且渲染顺序为从后向前,保证描边效果不被其他对象遮挡。
Tags{"Queue" = "Transparent"}
//描边使用两个Pass,第一个pass沿法线挤出一点,只输出描边的颜色
Pass
{
//剔除正面,只渲染背面
Cull Front
//关闭深度写入
ZWrite Off
CGPROGRAM
#include "UnityCG.cginc"
fixed4 _OutlineCol;
float _OutlineFactor;
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//将法线方向转换到视空间
float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
//将视空间法线xy坐标转化到投影空间
float2 offset = TransformViewToProjection(vnormal.xy);
//在最终投影阶段输出进行偏移操作
o.pos.xy += offset * _OutlineFactor;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//这个Pass直接输出描边颜色
return _OutlineCol;
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
//正常着色的Pass
Pass
{
CGPROGRAM
//引入头文件
#include "Lighting.cginc"
//定义Properties中的变量
fixed4 _Diffuse;
sampler2D _MainTex;
//使用了TRANSFROM_TEX宏就需要定义XXX_ST
float4 _MainTex_ST;
//定义结构体:vertex shader阶段输出的内容
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float2 uv : TEXCOORD1;
};
//定义顶点shader,参数直接使用appdata_base(包含position, noramal, texcoord)
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//通过TRANSFORM_TEX宏转化纹理坐标,主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
return o;
}
//定义片元shader
fixed4 frag(v2f i) : SV_Target
{
//unity自身的diffuse也是带了环境光,这里我们也增加一下环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.xyz;
//归一化法线,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出的
fixed3 worldNormal = normalize(i.worldNormal);
//把光照方向归一化
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//根据半兰伯特模型计算像素的光照信息
fixed3 lambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
//最终输出颜色为lambert光强*材质diffuse颜色*光颜色
fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz + ambient;
//进行纹理采样
fixed4 color = tex2D(_MainTex, i.uv);
color.rgb = color.rgb* diffuse;
return fixed4(color);
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
//前面的Shader失效的话,使用默认的Diffuse
FallBack "Diffuse"
}
这样,我们的ZWrite Off版本的描边效果也OK了。效果如下:
基于后处理的描边效果
这篇文章)。到了这里,我们就能够得到渲染到RT上的纯色的渲染RT了,如下图:
简单均值模糊和
高斯模糊,这里就不多做解释了,经过模糊后的结果如下:
简单屏幕较色):
/********************************************************************
FileName: OutlinePostEffect.cs
Description: 后处理描边效果
Created: 2017/01/12
history: 12:1:2017 0:42 by puppet_master
*********************************************************************/
using UnityEngine;
using System.Collections;
public class OutlinePostEffect : PostEffectBase
{
private Camera mainCam = null;
private Camera additionalCam = null;
private RenderTexture renderTexture = null;
public Shader outlineShader = null;
//采样率
public float samplerScale = 1;
public int downSample = 1;
public int iteration = 2;
void Awake()
{
//创建一个和当前相机一致的相机
InitAdditionalCam();
}
private void InitAdditionalCam()
{
mainCam = GetComponent<Camera>();
if (mainCam == null)
return;
Transform addCamTransform = transform.FindChild("additionalCam");
if (addCamTransform != null)
DestroyImmediate(addCamTransform.gameObject);
GameObject additionalCamObj = new GameObject("additionalCam");
additionalCam = additionalCamObj.AddComponent<Camera>();
SetAdditionalCam();
}
private void SetAdditionalCam()
{
if (additionalCam)
{
additionalCam.transform.parent = mainCam.transform;
additionalCam.transform.localPosition = Vector3.zero;
additionalCam.transform.localRotation = Quaternion.identity;
additionalCam.transform.localScale = Vector3.one;
additionalCam.farClipPlane = mainCam.farClipPlane;
additionalCam.nearClipPlane = mainCam.nearClipPlane;
additionalCam.fieldOfView = mainCam.fieldOfView;
additionalCam.backgroundColor = Color.clear;
additionalCam.clearFlags = CameraClearFlags.Color;
additionalCam.cullingMask = 1 << LayerMask.NameToLayer("Additional");
additionalCam.depth = -999;
if (renderTexture == null)
renderTexture = RenderTexture.GetTemporary(additionalCam.pixelWidth >> downSample, additionalCam.pixelHeight >> downSample, 0);
}
}
void OnEnable()
{
SetAdditionalCam();
additionalCam.enabled = true;
}
void OnDisable()
{
additionalCam.enabled = false;
}
void OnDestroy()
{
if (renderTexture)
{
RenderTexture.ReleaseTemporary(renderTexture);
}
DestroyImmediate(additionalCam.gameObject);
}
//unity提供的在渲染之前的接口,在这一步渲染描边到RT
void OnPreRender()
{
//使用OutlinePrepass进行渲染,得到RT
if(additionalCam.enabled)
{
//渲染到RT上
//首先检查是否需要重设RT,比如屏幕分辨率变化了
if (renderTexture != null && (renderTexture.width != Screen.width >> downSample || renderTexture.height != Screen.height >> downSample))
{
RenderTexture.ReleaseTemporary(renderTexture);
renderTexture = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample, 0);
}
additionalCam.targetTexture = renderTexture;
additionalCam.RenderWithShader(outlineShader, "");
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (_Material && renderTexture)
{
//renderTexture.width = 111;
//对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进行高斯模糊
_Material.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
Graphics.Blit(renderTexture, temp1, _Material, 0);
_Material.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
Graphics.Blit(temp1, temp2, _Material, 0);
//如果有叠加再进行迭代模糊处理
for(int i = 0; i < iteration; i++)
{
_Material.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
Graphics.Blit(temp2, temp1, _Material, 0);
_Material.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
Graphics.Blit(temp1, temp2, _Material, 0);
}
//用模糊图和原始图计算出轮廓图
_Material.SetTexture("_BlurTex", temp2);
Graphics.Blit(renderTexture, temp1, _Material, 1);
//轮廓图和场景图叠加
_Material.SetTexture("_BlurTex", temp1);
Graphics.Blit(source, destination, _Material, 2);
RenderTexture.ReleaseTemporary(temp1);
RenderTexture.ReleaseTemporary(temp2);
}
else
{
Graphics.Blit(source, destination);
}
}
}
//描边Shader(输出纯色)
//by:puppet_master
//2017.1.12
Shader "ApcShader/OutlinePrePass"
{
//子着色器
SubShader
{
//描边使用两个Pass,第一个pass沿法线挤出一点,只输出描边的颜色
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
fixed4 _OutlineCol;
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
{
//这个Pass直接输出描边颜色
return fixed4(1,0,0,1);
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
后处理shader(三个Pass,模糊处理,抠出轮廓,最终混合):
//后处理描边Shader
//by:puppet_master
//2017.1.12
Shader "Custom/OutLinePostEffect" {
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;
//Blur图和原图进行相减获得轮廓
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);
//最后的颜色是_BlurTex - _MainTex,周围0-0=0,黑色;边框部分为描边颜色-0=描边颜色;中间部分为描边颜色-描边颜色=0。最终输出只有边框
//return fixed4((colorBlur - colorMain).rgb, 1);
return colorBlur - colorMain;
}
//高斯模糊 vert shader(之前的文章有详细注释,此处也可以用BoxBlur,更省一点)
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;
}
//高斯模糊 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_add vert_add(appdata_img v)
{
v2f_add o;
//mvp矩阵变换
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//uv坐标传递
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_add(v2f_add i) : SV_Target
{
//取原始场景图片进行采样
fixed4 ori = tex2D(_MainTex, i.uv1);
//取得到的轮廓图片进行采样
fixed4 blur = tex2D(_BlurTex, i.uv);
//输出:直接叠加
fixed4 final = ori + blur;
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_add
#pragma fragment frag_add
ENDCG
}
}
}
描边结果(把要描边的对象放到Additional层中):
C#部分:
/********************************************************************
FileName: OutlinePostEffect.cs
Description: 后处理描边效果
Created: 2017/01/12
history: 12:1:2017 0:42 by puppet_master
*********************************************************************/
using UnityEngine;
using System.Collections;
public class OutlinePostEffectX : PostEffectBase
{
private Camera mainCam = null;
private Camera additionalCam = null;
private RenderTexture renderTexture = null;
public Shader outlineShader = null;
//采样率
public float samplerScale = 0.01f;
public int downSample = 0;
public int iteration = 0;
public Color outlineColor = Color.green;
void Awake()
{
//创建一个和当前相机一致的相机
InitAdditionalCam();
}
private void InitAdditionalCam()
{
mainCam = GetComponent<Camera>();
if (mainCam == null)
return;
Transform addCamTransform = transform.FindChild("additionalCam");
if (addCamTransform != null)
DestroyImmediate(addCamTransform.gameObject);
GameObject additionalCamObj = new GameObject("additionalCam");
additionalCam = additionalCamObj.AddComponent<Camera>();
SetAdditionalCam();
}
private void SetAdditionalCam()
{
if (additionalCam)
{
additionalCam.transform.parent = mainCam.transform;
additionalCam.transform.localPosition = Vector3.zero;
additionalCam.transform.localRotation = Quaternion.identity;
additionalCam.transform.localScale = Vector3.one;
additionalCam.farClipPlane = mainCam.farClipPlane;
additionalCam.nearClipPlane = mainCam.nearClipPlane;
additionalCam.fieldOfView = mainCam.fieldOfView;
additionalCam.backgroundColor = Color.clear;
additionalCam.clearFlags = CameraClearFlags.Color;
additionalCam.cullingMask = 1 << LayerMask.NameToLayer("Additional");
additionalCam.depth = -999;
if (renderTexture == null)
renderTexture = RenderTexture.GetTemporary(additionalCam.pixelWidth >> downSample, additionalCam.pixelHeight >> downSample, 0);
}
}
void OnEnable()
{
SetAdditionalCam();
additionalCam.enabled = true;
}
void OnDisable()
{
additionalCam.enabled = false;
}
void OnDestroy()
{
if (renderTexture)
{
RenderTexture.ReleaseTemporary(renderTexture);
}
DestroyImmediate(additionalCam.gameObject);
}
//unity提供的在渲染之前的接口,在这一步渲染描边到RT
void OnPreRender()
{
//使用OutlinePrepass进行渲染,得到RT
if (additionalCam.enabled)
{
//渲染到RT上
//首先检查是否需要重设RT,比如屏幕分辨率变化了
if (renderTexture != null && (renderTexture.width != Screen.width >> downSample || renderTexture.height != Screen.height >> downSample))
{
RenderTexture.ReleaseTemporary(renderTexture);
renderTexture = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample, 0);
}
additionalCam.targetTexture = renderTexture;
additionalCam.RenderWithShader(outlineShader, "");
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (_Material && renderTexture)
{
//renderTexture.width = 111;
//对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进行高斯模糊
_Material.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
Graphics.Blit(renderTexture, temp1, _Material, 0);
_Material.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
Graphics.Blit(temp1, temp2, _Material, 0);
//如果有叠加再进行迭代模糊处理
for (int i = 0; i < iteration; i++)
{
_Material.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
Graphics.Blit(temp2, temp1, _Material, 0);
_Material.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
Graphics.Blit(temp1, temp2, _Material, 0);
}
//用模糊图和原始图计算出轮廓图,并和场景图叠加,节省一个Pass
_Material.SetTexture("_OriTex", renderTexture);
_Material.SetTexture("_BlurTex", temp2);
_Material.SetColor("_OutlineColor", outlineColor);
Graphics.Blit(source, destination, _Material, 1);
RenderTexture.ReleaseTemporary(temp1);
RenderTexture.ReleaseTemporary(temp2);
}
else
{
Graphics.Blit(source, destination);
}
}
}
//后处理描边Shader
//by:puppet_master
//2017.1.12
Shader "Custom/OutLinePostEffectX" {
Properties{
_MainTex("Base (RGB)", 2D) = "white" {}
_BlurTex("Blur", 2D) = "white"{}
_OriTex("Ori", 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_add
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float2 uv2 : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
sampler2D _BlurTex;
float4 _BlurTex_TexelSize;
sampler2D _OriTex;
float4 _OriTex_TexelSize;
float4 _offsets;
fixed4 _OutlineColor;
//高斯模糊 vert shader(之前的文章有详细注释,此处也可以用BoxBlur,更省一点)
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;
}
//高斯模糊 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_add vert_add(appdata_img v)
{
v2f_add o;
//mvp矩阵变换
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//uv坐标传递
o.uv.xy = v.texcoord.xy;
o.uv1.xy = o.uv.xy;
o.uv2.xy = o.uv.xy;
#if UNITY_UV_STARTS_AT_TOP
//if (_OriTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y;
o.uv2.y = 1 - o.uv2.y;
#endif
return o;
}
fixed4 frag_add(v2f_add i) : SV_Target
{
//取原始场景纹理进行采样
fixed4 scene = tex2D(_MainTex, i.uv1);
//return scene;
//对blur后的纹理进行采样
fixed4 blur = tex2D(_BlurTex, i.uv);
//对blur之前的rt进行采样
fixed4 ori = tex2D(_OriTex, i.uv);
//轮廓是_BlurTex - _OriTex,周围0-0=0,黑色;边框部分为描边颜色-0=描边颜色;中间部分为描边颜色-描边颜色=0。最终输出只有边框
fixed4 outline = blur - ori;
//输出:blur部分为0的地方返回原始图像,否则为0,然后叠加描边
fixed4 final = scene * (1 - all(outline.rgb)) + _OutlineColor * any(outline.rgb);//0.01,1,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 }
CGPROGRAM
#pragma vertex vert_add
#pragma fragment frag_add
ENDCG
}
}
}
结果如下:
总结
Command Buffer的使用这篇文章
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/128055.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...