Unity随记(八) shader实现立方体Cube显示边框,描边
效果如下:
或者只显示线框,如下:
注意此效果仅仅针对部分模型生效,比如unity内置的立方体Cube,因为采用的实现方式比较依赖顶点UV值。
实现思路:
通过UV值判断,处在立方体边的边缘附近(接近0和接近1的部分)的部分可见,而其他部分透明度设为0,作为不可见部分,而具体要怎么判断是否在边附近。要从内置的cube顶点参数来找线索了,如果仅仅是为了显示一个立方体模型的话,用八个顶点就够了,也就是说立方体的每个角就一个顶点,一个面两个三角形就总共十二个三角形就搞定,的确,如果自己手动尝试构造一个立方体Mesh八个顶点是足够的,但是如果需要贴图,并且要求每个面都能正常且完整的显示这张贴图的话,就会发现八个顶点是基本上达不到要求的(就是立方体的每个面都像一个Quad一样的显示贴图,反正我是没有摸索出这样的妙招),可以观察到unity内置的Cube构造的立方体Mesh用了24个顶点,才能达到立方体的每个面都像一个Quad一样的显示贴图,当然多出来的顶点不仅仅是解决UV问题,还有normal等,这里用不到所以只关注UV即可。
额,说了这么多其实就是为了引出一个结论,Cube的Mesh的每个面的UV情况其实就是和一个Quad是基本一致的,也和普通的UI控件的Image一样,均匀的沿着U和V方向从0->1,如果不是很确定的话可以用代码获取到Mesh对象,并打印他的顶点中的UV即可验证。
红色范围则是我们要找的边缘附近的部分,比如这里的边长是0.1,则边缘范围就是:
uv.x < 0.1 || uv.x > 1 - 0.1 || uv.y < 0.1 || uv.y > 1 - 0.1
撸代码
到此就可以开始着手撸shader代码了。Vertex Shader中就中规中矩的一个MVP转换,传递一个UV即可,主要来看下Fragment Shader中,其实就上面找范围的一个判断,代码如下:
float4 frag(v2f i) : SV_Target
{
fixed4 col = fixed4(0,0,0,0);
if (i.uv.x < _Width || i.uv.x > 1 - _Width || i.uv.y < _Width || i.uv.y > 1 - _Width)
{
col = _Color;
}
return col;
}
不过有点不太满意的是,用到了一个if分支,如果能避免分*就更优雅了。还是Step大法好呀,上面的if分支语句可以这样,去掉分支语句,舒服多了~
col += saturate(step(i.uv.x, _Width) + step(1 - _Width, i.uv.x) + step(i.uv.y, _Width) + step(1 - _Width, i.uv.y)) * _Color;
完整代码如下:
Shader "Vitens/CubeOutline"
{
Properties
{
_Color("Color", color) = (1,1,1,1)
_Width("Width", range(0,0.5)) = 0.1
}
SubShader
{
Tags { "Queue"="Transparent" }
Pass {
//如果要显示背面的线框,取消下面两个注释即可
//cull off
//ZWrite off
blend srcalpha oneminussrcalpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
fixed4 _Color;
fixed _Width;
struct a2v {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
float4 frag(v2f i) : SV_Target {
fixed4 col = fixed4(0,0,0,0);//Cube的基础颜色
col += saturate(step(i.uv.x, _Width) + step(1 - _Width, i.uv.x) + step(i.uv.y, _Width) + step(1 - _Width, i.uv.y)) * _Color;
//if (i.uv.x < _Width || i.uv.x > 1 - _Width || i.uv.y < _Width || i.uv.y > 1 - _Width)
//{
// col = _Color;
//}
return col;
}
ENDCG
}
}
}