Unity Shader - 描边效果
原文链接:http://blog.csdn.net/puppet_master/article/details/54000951
简介
![](/assets/blank.gif)
![](/assets/blank.gif)
简单描边效果的原理
![](/assets/blank.gif)
![](/assets/blank.gif)
开启深度写入,剔除正面的描边效果
![](https://code.csdn.net/assets/CODE_ico.png)
- //描边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"
- }
![](/assets/blank.gif)
原始模型渲染采用了 半兰伯特Diffuse进行渲染,主要是前面多了一个描边的Pass。这个Pass里,我们没有关闭深度写入,主要是开启了模型的正面剔除,这样,在这个Pass渲染的时候,就只会渲染模型的背面,让背面向外拓展一下,既不会影响什么,并且背面一般都在正面的后面,一般情况下不会遮挡住正面,正好符合我们后面的部分外拓的需求。这个的主要优点是没有关闭深度写入,因为关闭深度写入,引入的其他问题实在是太多了。
![](/assets/blank.gif)
![](/assets/blank.gif)
![](/assets/blank.gif)
![](https://code.csdn.net/assets/CODE_ico.png)
- //描边使用两个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
- }
这样,我们的描边效果也可以支持不能背面剔除的模型了:
![](/assets/blank.gif)
Offset指令
![](/assets/blank.gif)
![](/assets/blank.gif)
![](https://code.csdn.net/assets/CODE_ico.png)
- //描边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"
- }
效果如下:
![](/assets/blank.gif)
关闭深度写入的描边效果实现
![](https://code.csdn.net/assets/CODE_ico.png)
- //描边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"
- }
结果如下:
![](/assets/blank.gif)
![](/assets/blank.gif)
![](/assets/blank.gif)
![](/assets/blank.gif)
![](https://code.csdn.net/assets/CODE_ico.png)
- //描边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了。效果如下:
![](/assets/blank.gif)
基于后处理的描边效果
![](/assets/blank.gif)
![](/assets/blank.gif)
![](/assets/blank.gif)
![](/assets/blank.gif)
![](https://code.csdn.net/assets/CODE_ico.png)
- /********************************************************************
- 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);
- }
- }
- }
![](https://code.csdn.net/assets/CODE_ico.png)
- //描边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,模糊处理,抠出轮廓,最终混合):
![](https://code.csdn.net/assets/CODE_ico.png)
- //后处理描边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层中):
![](/assets/blank.gif)
![](/assets/blank.gif)
C#部分:
![](https://code.csdn.net/assets/CODE_ico.png)
- /********************************************************************
- 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);
- }
- }
- }
![](https://code.csdn.net/assets/CODE_ico.png)
- //后处理描边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
- }
- }
- }
结果如下:
![](/assets/blank.gif)
总结
![](/assets/blank.gif)
![](/assets/blank.gif)
Unity Shader - 描边效果相关推荐
- 【Unity Shader 描边效果_案例分享】
1.实现逻辑 描边效果Shader有多种实现方式,可以通过后处理和MatCap实现. 这次主要想展示的是通过两个Pass实现. 当Shader中有多个Pass时,渲染流程会安装顺序依次执行,于是后面的 ...
- [Unity Shader]凌波微步效果
[Unity Shader]凌波微步效果 相信很多人都看过天龙八部,里面的段誉有一个技能就是凌波微步:移动的时候人先到,衣角跟随其后.说白了就是移动时有一个残影跟着他.下面先看下最终效果 下面我们看如 ...
- Unity Shader -描边(后期处理)
之前的文章中我们通过两种方式分别实现了描边效果,他们各有优缺点,也比较简单,今天我们来通过后期处理这种方式来实现描边效果,相对于之前两种实现方式要稍微复杂一点. 后期处理的描边最终效果图如下: 实现大 ...
- Unity Shader 动画效果出现残影
问题 复现<Unity Shader入门精要>的动画效果时,我发现我的动画都出现了残影. 一开始认为是程序写错了,采样出现了问题,但是复查对比其他资料很多遍也没发现错误. 解决方法 后来反 ...
- Unity Shader放大镜效果
小菜最近看到了一篇关于Shader实现的放大镜效果,酷炫的效果让小菜倍生好奇,冲动之下还是搬来练练手,刚好巩固下自己的顶点片元着色器编码. 有没有想学习的冲动!!! 文章的开始先来介绍一款好用的vs编 ...
- Unity Shader 新手引导效果
这两天实现了下新手引导需要的遮罩镂空shader效果,记录一下. 1.圆形镂空shader代码: //计算片元世界坐标和目标中心位置的距离 float dis = distance(IN.worldP ...
- unity Shader 扭曲效果
原理 所谓扭曲就是扰动一个物体的uv坐标,表现出来就是一个扰动效果,如火焰扭曲空气,和水波对水底的影响. 1. 在shader 中我们需要一张当前渲染的纹理 2. 给一个物体渲染并使用当前纹理,用屏幕 ...
- Unity Shader透明效果
在实时渲染中要实现透明效果,通常会在渲染模型时控制他的透明通道.当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值之外,它还有另一个属性--透明度.当透明度为1时表示该像素是完全 ...
- Unity Shader 贴花效果(一)
本文实现的是一个Mesh Decal方法的贴花方案,参考了本篇博文链接: unity的贴花方案.链接的文章是转载的,我并没有找到原文地址.本篇文章主要是学习和自己的理解为主. 先看未贴花之前的效果 这 ...
最新文章
- Setting Gdb on Windows
- 前端 html border-right: 1px solid red;
- AMD EPYC——CPU命名规则
- 【C语言重点难点精讲】C语言内存管理
- 随想录(用python预测未来中国的GDP)
- Nsrp实现juniper防火墙的高可用性【HA】!
- vs2005下,回发或回调参数无效的解决方法
- 零基础学cad要多久_完整版的CAD技巧!3天轻松玩转CAD,零基础也能学会
- 怎样用计算机制作思维导图,Win电脑快速制作思维导图的方案一
- 已解决:Word加载MathType时出现MathPage.wll或MathType.dll文件找不到
- 让你的专属博客更加漂亮
- .vdat文件怎么打开
- 团队作业第六次——团队Github实战训练
- 怎样使用JS代码代码跳转的方法
- 高清视频相关知识和、KMPlayer 硬解码(DXVA)设置、Z520+US15W+GMA500硬解码测试
- 腾讯校招课堂|程序员如何在腾讯完成自己的“游戏梦”
- euclidea教程_euclidea星 图文攻略 | 手游网游页游攻略大全
- Could not get JDBC Connection; nested exception is java.sql.SQLException: Cannot get a connection, p
- 【2019年07月22日】A股最便宜的股票
- 基于java的贪吃蛇游戏