Unity Shader 基于 RGB 插值的 Wireframe 描边着色器

先睹为快

 

引入

  这周学习了一种对模型的 Wireframe 做描边的着色器,其核心思想是:在几何着色器中,先将三维空间中每个顶点的坐标转换为屏幕坐标系下的平面坐标,然后求这个平面三角形的面积,继而求出各顶点到对边的高,并塞到一个另外两维用零补足的三维数组中。如此一来,经过几何着色器到片元着色器的插值后,这个三维数组中存的就变成了各像素点到临近三条边的距离。接下来只需让这三维中的任意一维小于某个值时画出 wireframe 的颜色,就完成了 wireframe 的描边。

  它的效果如下:

  它的几何着色器与片元着色器代码如下:

[maxvertexcount(3)]
void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
{

	float2 WIN_SCALE = float2(_ScreenParams.x / 2.0, _ScreenParams.y / 2.0);

	//frag position
	float2 p0 = WIN_SCALE * IN[0].pos.xy / IN[0].pos.w;
	float2 p1 = WIN_SCALE * IN[1].pos.xy / IN[1].pos.w;
	float2 p2 = WIN_SCALE * IN[2].pos.xy / IN[2].pos.w;

	//barycentric position
	float2 v0 = p2 - p1;
	float2 v1 = p2 - p0;
	float2 v2 = p1 - p0;
	//triangles area
	float area = abs(v1.x * v2.y - v1.y * v2.x);

	g2f OUT;
	OUT.pos = IN[0].pos;
	OUT.uv = IN[0].uv;
	OUT.dist = float3(area / length(v0),0,0);
	triStream.Append(OUT);

	OUT.pos = IN[1].pos;
	OUT.uv = IN[1].uv;
	OUT.dist = float3(0,area / length(v1),0);
	triStream.Append(OUT);

	OUT.pos = IN[2].pos;
	OUT.uv = IN[2].uv;
	OUT.dist = float3(0,0,area / length(v2));
	triStream.Append(OUT);

}

half4 frag(g2f IN) : COLOR
{
	//distance of frag from triangles center
	float d = min(IN.dist.x, min(IN.dist.y, IN.dist.z));
	//fade based on dist from center
	float I = exp2(-4.0 * d * d);

	return lerp(_Color, _WireColor, I);
}

进入正题

  对于 wireframe 描边这个命题,咱首先想到的是另一种解法。现在想来这其实算上面这种解法的青春版。

  既然从几何到片元的过程中会做插值,又正好要用到三个维度,那么直接把每个像素需要的信息存到 rgb 里岂不美哉?把每个面片的三个顶点设为红、绿、蓝,之后的插值就丢给着色器处理了。当然啦,本质也还是把 COLOR 当成三维数组来用,不过可视化的中间步骤更容易理解不是嘛。

  代码如下:

Properties
{
    _Color ("Color", Color) = (1,1,1,1)
    _FrameColor ("Wireframe Color", Color) = (1,0,0,1)
    _Epsilon ("Wireframe Width", Range(0, 0.34)) = 0.005
}
SubShader
{
    Tags { "RenderType"="Opaque" }

    Pass
    {
        CGPROGRAM
        #include "UnityCG.cginc"
        #pragma target 4.0
        #pragma vertex vert
        #pragma geometry geom
        #pragma fragment frag

        static const fixed4 COLOR[3] = 
        {
            fixed4(1,0,0,1),
            fixed4(0,1,0,1),
            fixed4(0,0,1,1),
        };

        fixed4 _Color, _FrameColor;
        float _Epsilon;

        struct v2g
        {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
            float3 normal : NORMAL;
        };

        struct g2f
        {
            float4 vertex : SV_POSITION;
            float2 uv : TEXCOORD0;
            float3 normal : NORMAL;
            fixed4 color : COLOR;
        };

        v2g vert(appdata_base v)
        {
            v2g o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord;
            o.normal = v.normal;
            return o;
        }

        [maxvertexcount(3)]
        void geom(triangle v2g v[3], inout TriangleStream<g2f> triStream)
        {
            g2f o;

            for (uint i = 0; i < 3; i++)
            {
                o.vertex = v[i].vertex;
                o.uv = v[i].uv;
                o.normal = v[i].normal;
                o.color = COLOR[i];
                triStream.Append(o);
            }

            triStream.RestartStrip();
        }

        fixed4 frag(g2f i) : COLOR
        {
            //Raw RBG Color
            return i.color;
        }

        ENDCG
    }
}

  效果就是这样:

  显然地,这还没描边呢。所以我们再把片元着色器稍加修改,让它不要再显示闪瞎眼的 RGB 了:

fixed4 frag(g2f i) : COLOR
{
    //return i.color;

    float minColor = min(i.color.r, min(i.color.g, i.color.b));

    if (minColor < _Epsilon)
        return _FrameColor;
    else
        return _Color;
}

   这几行的意思是,只要 rgb 中有一个分量小于设定好的 ε,就当描边线,否则就还是原本的颜色。效果如下:

  你或许发现了,虽然也描出了边,但该效果与开头提到的人家的效果有重要的差别。

  再放两张远景对比一下(均无光照)。这是我们的效果:

  这是人家的效果:

  出现这种立体感的差别,是因为我们的代码将描边画在了模型上,而人家的代码将描边画在了屏幕上。这意味着后者的线条在屏幕上永远是等粗的,所以在面片越密集的地方,线条占的份量也会越大。

  看起来好像是我们的效果更廉价喔?其实我们的效率是更高的,比人家少了好几次除法、幂运算啥的,或者说实际上我们除了做了几次比较,其他什么运算也没有。而且这种算法可以更自由地调整线条宽度,所以还是看个人的需求来选择啦。

 

扩展应用

  该算法不止能描 wireframe 边,还可以有以下多种变体效果。

  在上色时用最小的颜色分量作 weight 进行插值:

  在上色时用最大的颜色分量作 weight 进行插值:

  再加上描边:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值