之前在网上看到物体遮挡描边的功能,自己也拿来实现了一番。算作第一篇博客的开篇。

  先贴出几张效果图,也是个人思路和方案的改进路线吧。

 //////////////////////////////////////////////////////////////////方案实现////////////////////////////////////////////////////////////////////////////////////////

  看到描边的功能,最先想到的就是用stencil的方法实现。功能最主要的部分就是如何判断边界像素,之后在FragmentShader中将该像素颜色设置成需要描边的颜色。

方案一:

  一个简单的VF Shader:

 Shader "Unlit/Shape"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_ShapeLineWidth("ShapeWidth",float) = 0.1
_ShapeColor("ShapeColor",COLOR) = (,,,)
}
SubShader
{
Tags { "Queue"="Geometry" }
LOD
//output origin color
Pass
{ CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
}; struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
}; sampler2D _MainTex;
float4 _MainTex_ST; v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
} fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
} //output stencil to define occlued area
Pass
{
ColorMask
ZTest Off
Stencil
{
Ref
Comp Always
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
}; struct v2f
{
float4 vertex : SV_POSITION;
}; v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
} fixed4 frag (v2f i) : SV_Target
{
return fixed4(,,,);
}
ENDCG
} //output outlinecolor
Pass
{
Stencil
{
Ref
Comp Equal
Pass Keep
}
ZWrite Off
ZTest Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
}; float _ShapeLineWidth;
fixed4 _ShapeColor; v2f vert (appdata_base v)
{
v2f o;
v.vertex.xyz += v.normal * _ShapeLineWidth;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
} [earlyDepthStencil]
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = _ShapeColor;
return col;
}
ENDCG
}
}
}

  主要功能在第三个pass中:将所有像素沿法线方向延伸(相当于将物体略微放大一些),再通过第二个pass写入的stencil剔除中间区域,剩下就是边缘的像素了。虽然成功实现了描边的功能,但是未遮挡部位也被描边了。

方案二:

  利用后期渲染的技术,将所有描边的物体轮廓先输出到一张图上,在最后与原图叠加在一起。这种方案的优点更加灵活了。在Unity中,我们可以定义不同的相机来渲染出各种想要的图像包含着丰富的信息。例如本例中在后期渲染中先后得到shadowmap,颜色缓存等图像信息。

  由于该方案比较复杂,先贴出思路:

  1.获得场景除去要描边物体的depthmap

  2.通过比较depthmap判定被遮挡区域,并将该区域放大

  3.将放大区域剔除遮挡区域就是描边的像素区域了,将该像素区域叠加到原图像中。

  工程代码如下:

 using UnityEngine;
using System.Collections; public class ShapeOutline : MonoBehaviour { public Camera objectCamera = null;
public Color outlineColor = Color.green;
Camera mainCamera;
RenderTexture depthTexture;
RenderTexture occlusionTexture;
RenderTexture strechTexture; // Use this for initialization
void Start()
{
mainCamera = Camera.main;
mainCamera.depthTextureMode = DepthTextureMode.Depth;
objectCamera.depthTextureMode = DepthTextureMode.None;
objectCamera.cullingMask = << LayerMask.NameToLayer("Outline");
objectCamera.fieldOfView = mainCamera.fieldOfView;
objectCamera.clearFlags = CameraClearFlags.Color;
objectCamera.projectionMatrix = mainCamera.projectionMatrix;
objectCamera.nearClipPlane = mainCamera.nearClipPlane;
objectCamera.farClipPlane = mainCamera.farClipPlane;
objectCamera.aspect = mainCamera.aspect;
objectCamera.orthographic = false;
objectCamera.enabled = false;
} void OnRenderImage(RenderTexture srcTex, RenderTexture dstTex)
{
depthTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, , RenderTextureFormat.Depth);
occlusionTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, );
strechTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, ); objectCamera.targetTexture = depthTexture;
objectCamera.RenderWithShader(Shader.Find("ShapeOutline/Depth"), string.Empty); Material mat = new Material(Shader.Find("ShapeOutline/Occlusion"));
mat.SetColor("_OutlineColor", outlineColor);
Graphics.Blit(depthTexture, occlusionTexture, mat); mat = new Material(Shader.Find("ShapeOutline/StrechOcclusion"));
mat.SetColor("_OutlineColor", outlineColor);
Graphics.Blit(occlusionTexture, strechTexture, mat); mat = new Material(Shader.Find("ShapeOutline/Mix"));
mat.SetColor("_OutlineColor", outlineColor);
mat.SetTexture("_occlusionTex", occlusionTexture);
mat.SetTexture("_strechTex", strechTexture);
Graphics.Blit(srcTex, dstTex, mat); RenderTexture.ReleaseTemporary(depthTexture);
RenderTexture.ReleaseTemporary(occlusionTexture);
RenderTexture.ReleaseTemporary(strechTexture); }
}

  16-27行:创建一个专门用来渲染描边的相机。该相机渲染出一个剔除了待描边物体的depthmap用于判断遮挡的区域。17将相机渲染模式设置为depth,这样在之后的shader中就可以调用Unity的内置变量_CameraDepthTexture来获取深度图了。

  30行:OnRenderImage()是Unity引擎内置的函数,在相机最终输出图像时会调用该函数,很多后期渲染处理都放在该函数中。具体的可以搜一下“Unity流程图”,直观的了解在一帧中Unity是如何调用各种内置的函数的。注意将该.cs脚本挂在对应相机对象上才启用。

  32-34行:调用RenderTexture.GetTemporary()来分配一个texture内存。为什么不用New呢?Unity的官方文档解释说这个比new快很多,也确实是。学习了。但使用后记得马上ReleaseTemporary。另外特别注意的是在创建一个RenderTexture(不论是用new还是gettemporary)时对depthBuffer的设置,如果将33,34行的depthBuffer设置为16/24/32,Blit输出的图像始终就都是相机渲染的图像?关于RenderTexture中depthBuffer这块还不是很理解,之后还需要查下资料,这里暂标记下。有兄弟了解的可以在评论区交流。

  接下来是几个shader的源码  

 Shader "ShapeOutline/Depth"
{
Properties
{
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 depth : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.depth = o.vertex.zw;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float depth = i.vertex.z/i.vertex.w; return fixed4(depth,depth,depth,);
}
ENDCG
}
}
}

  这段代码没什么好说的了,就是输出outline层物体的depthmap,注意下输出的格式。(DepthMap的要求?这里也做个标记)

 Shader "ShapeOutline/Occlusion"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0; };
struct v2f
{
float4 ScreenPos : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
uniform sampler2D _CameraDepthTexture;
fixed4 _OutlineColor; v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.ScreenPos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
i.ScreenPos.xy = i.ScreenPos.xy/i.ScreenPos.w;
float2 uv = float2(i.ScreenPos.x,i.ScreenPos.y);
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, uv));
float depthTex = tex2D(_MainTex,i.ScreenPos.xy);
if((depthTex > depth) && depthTex!= )
return fixed4(_OutlineColor.rgb,i.vertex.z);
else
return fixed4(,,,);
}
ENDCG
}
}
}

  输出Outline层物体被其他物体遮挡的部分。注意_CameraTexture变量的来源,上文已经提到了。另外就是在对输入的Texture进行采样时,不再是直接根据模型UV坐标来采样了,而是应该用屏幕坐标来采样。模型顶点坐标如何转换为屏幕坐标请看37,38,43。这里贴一张OcclusionTexture方便理解

 Shader "ShapeOutline/StrechOcclusion"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
}; struct v2f
{
float4 screenPos : TEXCOORD0;
float4 vertex : SV_POSITION;
}; sampler2D _MainTex;
float4 _MainTex_ST;
uniform fixed4 _OutlineColor; v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
i.screenPos.xy = i.screenPos.xy/i.screenPos.w;
fixed4 col1 = tex2D(_MainTex,i.screenPos.xy);
fixed4 col2 = tex2D(_MainTex,float2(i.screenPos.x + /_ScreenParams.x,i.screenPos.y));
fixed4 col3 = tex2D(_MainTex,float2(i.screenPos.x - /_ScreenParams.x,i.screenPos.y));
fixed4 col4 = tex2D(_MainTex,i.screenPos.xy);
fixed4 col5 = tex2D(_MainTex,float2(i.screenPos.x ,i.screenPos.y+ /_ScreenParams.y));
fixed4 col6 = tex2D(_MainTex,float2(i.screenPos.x ,i.screenPos.y- /_ScreenParams.y));
if((col1.x + col1.y + col1.z
+ col2.x + col2.y + col2.z
+ col3.x + col3.y + col3.z
+ col4.x + col4.y + col4.z
+ col5.x + col5.y + col5.z
+ col6.x + col6.y + col6.z
)>0.01)
return fixed4(_OutlineColor.rgb,i.vertex.z);
else
return fixed4(,,,);
}
ENDCG
}
}
}

  这段Shader功能是对原来遮挡输出的图像进行上下左右放大一个像素,之后将这张图的图像剔除遮挡部分就是描边的线条了。

 Shader "ShapeOutline/Mix"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
}; struct v2f
{
float4 screenPos : TEXCOORD0;
float4 vertex : SV_POSITION;
}; sampler2D _MainTex;
float4 _MainTex_ST;
uniform sampler2D _occlusionTex;
uniform sampler2D _strechTex;
uniform fixed4 _OutlineColor; v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.vertex);
return o;
} fixed4 frag (v2f i) : SV_Target
{
i.screenPos.xy /= i.screenPos.w;
fixed4 srcCol = tex2D(_MainTex,float2(i.screenPos.x,-i.screenPos.y));
fixed4 occlusionCol = tex2D(_occlusionTex,fixed2(i.screenPos.x,i.screenPos.y));
fixed4 strechCol = tex2D(_strechTex,fixed2(i.screenPos.x,i.screenPos.y));
float isOcclusion = occlusionCol.x + occlusionCol.y + occlusionCol.z;
float isStrech = strechCol.x + strechCol.y + strechCol.z;
if(isStrech > 0.5 && isOcclusion<0.1)
return _OutlineColor;
else
return srcCol;
}
ENDCG
}
}
}

  最终混合的Shader,即将拉伸的图像剔除遮挡部分,并与原相机的图像进行叠加。

  该方案实现了遮挡描边的效果,但是问题又来了。图2中如果蓝方块在红方块后面,则无法描绘出边框,如果全部位于红色方块后,则描边的效果就消失了。

  这部分代码参考了EsFog前辈的博客,原文地址:http://www.cnblogs.com/Esfog/p/CoverOutline_Shader.html

方案三:

  基于方案二的优化,在渲染depthmap时仅剔除自身。

  改进的思路:记录所有待描边的物体,描绘当前物体时仅将当前物体设为“Outline”层,讲所有描边的物体绘制出边框轮廓后再与原相机渲染的图像叠加。

  工程代码如下

 using UnityEngine;
using System.Collections;
using System.Collections.Generic; public class MultiShapeOutline : MonoBehaviour { public Camera objectCamera = null;
public Color outlineColor = Color.green;
Camera mainCamera;
RenderTexture depthTexture;
RenderTexture occlusionTexture;
RenderTexture strechTexture;
RenderTexture outputTexture;
RenderTexture inputTexture;
Material m;
[SerializeField]
List<GameObject> renderObjects = new List<GameObject>();
// Use this for initialization
void Start () {
mainCamera = Camera.main;
mainCamera.depthTextureMode = DepthTextureMode.Depth;
objectCamera.depthTextureMode = DepthTextureMode.None;
objectCamera.cullingMask = << LayerMask.NameToLayer("Outline");
objectCamera.fieldOfView = mainCamera.fieldOfView;
objectCamera.clearFlags = CameraClearFlags.Color;
objectCamera.projectionMatrix = mainCamera.projectionMatrix;
objectCamera.nearClipPlane = mainCamera.nearClipPlane;
objectCamera.farClipPlane = mainCamera.farClipPlane;
objectCamera.targetTexture = depthTexture;
objectCamera.aspect = mainCamera.aspect;
objectCamera.orthographic = false;
objectCamera.enabled = false; m = new Material(Shader.Find("ShapeOutline/DoNothing"));
} // Update is called once per frame
void Update () { } void OnRenderImage(RenderTexture srcTex, RenderTexture dstTex)
{
outputTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, );
inputTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, );
Graphics.Blit(srcTex, inputTexture, m);
for (int i = ; i < renderObjects.Count; i++)
{
depthTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, , RenderTextureFormat.Depth);
occlusionTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, );
strechTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, ); int orgLayer = renderObjects[i].layer;
renderObjects[i].layer = LayerMask.NameToLayer("Outline"); objectCamera.targetTexture = depthTexture;
objectCamera.RenderWithShader(Shader.Find("ShapeOutline/Depth"), string.Empty); Material mat = new Material(Shader.Find("ShapeOutline/Occlusion"));
mat.SetColor("_OutlineColor", outlineColor);
Graphics.Blit(depthTexture, occlusionTexture, mat); mat = new Material(Shader.Find("ShapeOutline/StrechOcclusion"));
mat.SetColor("_OutlineColor", outlineColor);
Graphics.Blit(occlusionTexture, strechTexture, mat); mat = new Material(Shader.Find("ShapeOutline/MultiMix"));
mat.SetColor("_OutlineColor", outlineColor);
mat.SetTexture("_occlusionTex", occlusionTexture);
mat.SetTexture("_strechTex", strechTexture);
Graphics.Blit(inputTexture, outputTexture, mat); RenderTexture.ReleaseTemporary(depthTexture);
RenderTexture.ReleaseTemporary(occlusionTexture);
RenderTexture.ReleaseTemporary(strechTexture);
renderObjects[i].layer = orgLayer; Graphics.Blit(outputTexture, inputTexture, m);
}
Graphics.Blit(outputTexture, dstTex, m); RenderTexture.ReleaseTemporary(inputTexture);
RenderTexture.ReleaseTemporary(outputTexture);
}
}

  17行:记录所有待描边的物体

  44、45行:定义了两张临时的RenderTexture,其实主要目的就是为了将上一次的图像输出用作下一次的图像输入。当然,实现这种功能第一想法就是Graphics.Blit(renderTexture,renderTexture,material),但实际Blit函数并不允许这样的参数操作,各位兄弟可以自己实际测试下。

  53、54、77行:将描边的物体单独设置为描边渲染的层。

  68行:shader改了名字而已,和上面贴出的代码一致。

  功能写到这就结束了,当然根据各种不同的需求可以对功能进行修改。这是自己第一篇博文,不足之处请大家指出,也欢迎大家一起评论交流。

  

  

UnityShader实现物体被遮挡描边的更多相关文章

  1. Esfog_UnityShader教程_遮挡描边(实现篇)

     在上一篇中,我们基本上说明了遮挡描边实现的一种基本原理.这一篇中我们将了解一下基于这种原理的具体实现代码.本篇中的内容和前几篇教程相比,相对比较难一些,建议先有一些基本的Unity的C#脚本编程经验 ...

  2. Esfog_UnityShader教程_遮挡描边(原理篇)

    咳咳,有段时间没有更新了,最近有点懒!把不少精力都放在C++身上了.闲言少叙,今天要讲的可和之前的几篇有所不同了,这次是一个次综合应用.这篇内容中与之前不同主要体现在下面几点上. 1.之前我们写的都是 ...

  3. Cesium源码剖析---Post Processing之物体描边(Silhouette)

    Cesium在1.46版本中新增了对整个场景的后期处理(Post Processing)功能,包括模型描边.黑白图.明亮度调整.夜视效果.环境光遮蔽等.对于这么炫酷的功能,我们绝不犹豫,先去翻一翻它的 ...

  4. UnityShader 表面着色器简单例程集合

    0.前言 这些简单的shader程序都是写于2015年的暑假.当时实验室空调坏了,30多个人在实验室中挥汗如雨,闷热中学习shader的日子还历历在目.这些文章闲置在我个人博客中,一年将过,师弟也到了 ...

  5. [UnityShader]点染队列、ZWrite和ZTest

    转载自:http://www.myexception.cn/mobile/1902628.html [UnityShader]渲染队列.ZWrite和ZTest 参考链接:http://blog.cs ...

  6. UnityShader快速上手指南(三)

    简介 这一篇还是一些基本的shader操作:裁剪.透明和法向量的应用 (纠结了很久写不写这些,因为代码很简单,主要是些概念上的东西) 先来看下大概的效果图:(从左到右依次是裁剪,透明,加了法向量的透明 ...

  7. [UnityShader基础]02.深度测试 & 深度写入

    参考链接: https://blog.csdn.net/v_xchen_v/article/details/79380222 前面说到了渲染队列,对于两个不透明的物体A和B,它们处于同一个渲染队列中. ...

  8. OpenGL 遮挡查询

    原文地址:http://www.linuxidc.com/Linux/2015-02/114036.htm 在一个场景中,如果有有些物体被其他物体遮住了不可见.那么我们就不需要绘制它.在复杂的场景中, ...

  9. 遮挡剔除 Occlusion Culling(转)

    一.首先介绍下draw call(这个东西越少你的游戏跑的越快): 在游戏中每一个被展示的独立的部分都被放在了一个特别的包中,我们称之为“描绘指令”(draw call),然后这个包传递到3D部分在屏 ...

随机推荐

  1. 2018年—2019年第二学期第四周C#学习个人总结

    第五章面向对象高级包括:5.1类的继承中有5.1.1继承的概念:一是在现实生活中,继承一般指的是子女继承父辈的财产;在C#,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类, ...

  2. linux服务基础(二)之httpd2.2基础配置

    一.安装httpd服务 CentOS6 默认安装httpd2.2版本 CentOS7 默认安装httpd2.4版本 # yum install httpd 二.安装后相关文件说明 配置文件: /etc ...

  3. MySQL中select、insert、update批量操作语句

    项目中经常的需要使用批量操作语句对数据进行操作,使用批量语句的时候需要注意MySQL数据库的条件限制,这里主要是数据的大小限制,这里对批量插入.批量查找及批量更新的基础操作进行下简单的汇总. 1.批量 ...

  4. Jenkins去GitLab拉取Java代码自动打包

    jenkins的部署 一.部署git 1)先检查系统是否已经自带了git,如果有,就卸载 $ rpm -qa | grep git && rpm -e git --nodeps 2)开 ...

  5. DataFrame 重新设置索引: reindex 和 reset_index 的区别

    将两个 DataFrame 拼接后,想要对拼接后的 DataFrame 重新设置索引要用 reset_index 方法,要想让之前的索引消失,传入参数:drop=True.具体事例: data2017 ...

  6. 《Professional JavaScript for Web Developers》day02

    <Professional JavaScript for Web Developers>day02 1.在HTML中使用JavaScript 1.1 <script>元素 HT ...

  7. 如何解锁亚马逊A9的新算法?

    亚马逊每一次变动总能在跨境圈里掀起一场场风波,最近A9算法的更新更是牵动着不少卖家的心. A9算法是亚马逊运行的内核算法,只要消费者在亚马逊上面搜索了商品,那么他就已经开始使用了A9算法,通过分析每一 ...

  8. Java核心技术卷一 · 笔记(2)

    目录 1.多态.动态绑定 2.覆盖 3.阻止继承:final 类和方法 4.抽象类(abstract修饰) 5. 4 个访问修饰符: 6.toString() 7.对设计继承关系很有帮助的建议 8.接 ...

  9. 使用js实现放大镜效果

    点击预览放大镜效果,图片比较大,因为需要精细的图片去凸显放大的效果,请耐心 可以通过滑轮控制放大倍数,由于图片太大 如果放大镜没有出现 可刷新一下网页

  10. sql server 将两列数据合并到一列 拼接

    create table a( s nvarchar null, ss nvarchar null, f decimal(18,1) null, ff decimal(18,1) null,)INSE ...