描边
1、基于观察角度和表面法线的轮廓线渲染
- 基本思路:以归一化的顶点到摄像机的向量和归一化的顶点法线向量的点乘为参考值,该值越小(越接近于0),该像素就越解决于边缘。
- 优点:简单快速,只需要一个Pass。
- 缺点:局限性大,很多模型渲染出的效果不尽人意。
Shader "Unlit/RimLight"
{
Properties
{
_RimColor("RimColor",Color) = (1,1,1,1)
_RimPower("Power",Range(0,10)) = 0.1
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _RimColor;
float _RimPower;
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float2 uv : TEXCOORD1;
float3 worldViewDir : TEXCOORD2;
};
v2f vert (appdata_base v)
{
v2f o;
//顶点坐标从模型空间转换到世界空间
o.pos = UnityObjectToClipPos(v.vertex);
//调整uv坐标,必须定义_MainTex_ST变量
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
//将法线向量从模型空间变换到世界空间
o.worldNormal = UnityObjectToWorldNormal(v.normal);
//获取世界空间,顶点到摄像机的方向向量
o.worldViewDir = WorldSpaceViewDir(v.vertex );
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//获取归一化的法线向量
fixed3 worldNormal = normalize(i.worldNormal);
//根据贴图,环境光,和漫反射,计算颜色
fixed4 color = tex2D(_MainTex,i.uv);
//获取归一化的顶点到摄像机的方向向量
float3 worldViewDir = normalize(i.worldViewDir);
//根据摄像机方向和法线,计算边缘光权重
float rim = 1-saturate(dot(worldViewDir,worldNormal));
//使用_RimPower控制强度
fixed3 rimColor = _RimColor * pow(rim,_RimPower);
//将边缘光叠加到最终颜色
color.rgb = color.rgb+rimColor;
return color;
}
ENDCG
}
}
}
2、过程式几何轮廓线
背面顶点偏移
-
基本思路:用两个Pass渲染,一个Pass正常渲染模型正面,另一个Pass将背面顶点延法线方向向外偏移。
- 优点:快速有效,适用于大多数边缘平滑的模型。
- 缺点:不适用于棱角分明的模型,边缘会出现断裂。
-
基本思路:在模型空间下将背面顶点延原点到顶点的向量偏移。
- 优点:不会出现断裂,适用于简单的几何体
- 切点:对于复杂的模型,效果不佳
Shader "Unlit/OutlineModel"
{
Properties
{
_OutlineCol("OutlineCol",Color) = (1,0,0,1)
_OutlineFactor("OutlineFactor",Range(0,1)) = 0.1
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
//裁剪模型的正面
Cull Front
Offset 1,1
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _OutlineCol;
float _OutlineFactor;
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//方法一:将法线转换到投影空间,在投影阶段进行偏移
//有断裂
float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
float3 offset = TransformViewToProjection(viewNormal);
//调整法线z坐标,防止遮挡正面渲染
offset.z = -0.5f;
o.pos.xyz+=offset * _OutlineFactor;
//方法二:将顶点坐标作为方向矢量,转换到投影空间,以此为基础偏移
//没有断裂
float3 dir = normalize(v.vertex.xyz);
float4 newPos = v.vertex;
newPos.xyz += dir * _OutlineFactor;
o.pos = UnityObjectToClipPos(newPos);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _OutlineCol;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD1;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
return tex2D(_MainTex,i.uv);
}
ENDCG
}
}
}
添加新三角面+模板测试
详细内容博客
-
基于几何着色器和模板检测的描边
-
思路:将所有三角面的顶点延法线方向向外一段距离添加一个顶点,每个三角面添加6个三角形作为描边。使用模板测试丢弃和正常渲染的模型重合的像素。
-
优点:描边没有断裂,不需要模型具有背面模型,只会描画出模型真正的边缘。
-
缺点:耗费性能
Shader "Unlit/几何着色器+模板" { Properties { _MainTex ("Texture", 2D) = "white" {} _OutlineColor("LineColor", Color) = (1,1,1,1) _OutlineWidth("LineWidth",Range(0.001,0.01)) = 0.001 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { Stencil{ Ref 1 Comp Always Pass Replace } 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 { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } Pass{ Stencil{ Ref 1 Comp NotEqual } CGPROGRAM #pragma vertex vert #pragma geometry geom #pragma fragment frag #include "UnityCG.cginc" struct v2gf { float4 vertex : TEXCOORD0; float3 normal : NORMAL; float4 pos : SV_POSITION; }; fixed4 _OutlineColor; fixed _OutlineWidth; v2gf vert(appdata_base i) { v2gf o; o.vertex = i.vertex; o.normal = i.normal; o.pos = UnityObjectToClipPos(i.vertex); return o; } //获取外扩后的顶点 v2gf Offset( v2gf i) { v2gf o = i; //将法线从模型空间转换到观察空间 float3 vNormal = mul((float3x3)UNITY_MATRIX_IT_MV,o.normal); //投影空间版本 //将法线的xy分量,转换到投影空间 float2 vOffset = TransformViewToProjection(vNormal.xy); //归一化 vOffset = normalize(vOffset); o.pos = UnityObjectToClipPos(o.vertex); o.pos.xy += vOffset * _OutlineWidth; //观察空间版本 //float4 vPos = mul(UNITY_MATRIX_MV,o.vertex); //vPos = vPos + float4(normalize(vNormal),0)* _OutlineWidth; //o.pos = mul(UNITY_MATRIX_P,vPos); return o; } [maxvertexcount(18)] void geom(triangle v2gf inputTriangle[3], inout TriangleStream<v2gf> outStream) { //获取拓展的三个顶点 v2gf outTriangle0 = Offset(inputTriangle[0]); v2gf outTriangle1 = Offset(inputTriangle[1]); v2gf outTriangle2 = Offset(inputTriangle[2]); //添加扩展的三角形 outStream.Append(inputTriangle[0]); outStream.Append(inputTriangle[1]); outStream.Append(outTriangle1); //outStream.RestartStrip(); outStream.Append(outTriangle0); outStream.Append(outTriangle1); outStream.Append(inputTriangle[0]); //outStream.RestartStrip(); outStream.Append(outTriangle1); outStream.Append(outTriangle2); outStream.Append(inputTriangle[2]); //outStream.RestartStrip(); outStream.Append(inputTriangle[1]); outStream.Append(inputTriangle[2]); outStream.Append(outTriangle1); //outStream.RestartStrip(); outStream.Append(inputTriangle[0]); outStream.Append(inputTriangle[2]); outStream.Append(outTriangle2); //outStream.RestartStrip(); outStream.Append(outTriangle0); outStream.Append(outTriangle2); outStream.Append(inputTriangle[0]); //outStream.RestartStrip(); } fixed4 frag(v2gf i) : SV_Target{ return _OutlineColor; } ENDCG } } }
3、基于图像处理
来自UnityShader入门精要
基于颜色
-
原理:使用屏幕后处理,基于颜色,法线,深度进行轮廓线检测。
-
使用卷积操作:使用一个卷积核对每个像素进行操作。卷积核是一个正方形的结构,例如2X2,3X3,每一格有一个权重值,将中心放在像素上,将每一格权重和其覆盖的像素值相乘,将所有结果相加作为该像素的权值。
-
轮廓识别常用的卷积核:
$$
\left[ \begin{matrix}
-1 &0
&\0 &1
\end{matrix} \right]\left[ \begin{matrix}
-1 &-1 &-1
&\0 &0 &0
&\1 &1 &1
\end{matrix} \right]\left[ \begin{matrix}
-1 &-2 &-1
&\0 &0 &0
&\1 &2 &1
\end{matrix} \right]
$$ -
基于颜色的轮廓检测
public class OutLines : PostEffectBase
{
[Range(0f, 1f)]
//是否只保留轮廓
public float edgesOnly = 0.0f;
//轮廓和背景的颜色
public Color edgeColor = Color.black;
public Color backgroundColor = Color.white;
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if(_Material != null)
{
_Material.SetFloat("_EdgeOnly", edgesOnly);