Unity Shader能否将8个偏移绘制Pass合并为单通道实现2D角色描边?
实现Spine 2D角色描边:合并8个偏移Pass为单个Pass
问题描述
我是Shader新手,正在为tk2d Spine 2D角色制作描边效果。我知道有其他描边方法,但对当前思路感兴趣(请勿推荐其他描边方式)。
当前思路是通过获取对象顶点,设置8个方向(上下左右及4个对角线)的偏移,绘制同纹理来实现描边。效果已实现,但Shader代码需9个Pass:8个仅偏移方向不同的重复Pass,最后1个绘制角色本体。重复代码冗余且可能影响性能,因此想询问:能否将这8个Pass压缩为1个?即保存各方向顶点位置信息传递给片元着色器。
我曾尝试放大顶点,但因角色边缘不光滑导致描边失真。
解决方案
可以通过在片元着色器中采样8个偏移方向的纹理像素来合并这8个Pass,不需要重复绘制8次顶点。核心逻辑是:在顶点着色器中计算纹理空间的8个方向偏移量,传递给片元着色器后,检查当前像素周围8个方向是否存在不透明的本体像素,若存在则输出描边颜色,最后叠加原角色纹理。
以下是修改后的完整Shader代码:
Shader "Custom/Shader_Spine_Battle_Red" { Properties { _Color ("描边颜色", color) = (1, 0, 0, 1) _MainTex ("基础纹理 (RGB) 透明通道 (A)", 2D) = "white" {} _OutlineWidth ("描边宽度", float) = 1 } SubShader { Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} ZWrite Off Lighting Off Cull Off Fog { Mode Off } Blend SrcAlpha OneMinusSrcAlpha LOD 110 Stencil{ Comp Equal } // 合并描边与本体的单个Pass Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" sampler2D _MainTex; fixed4 _Color; float _OutlineWidth; // 纹理像素大小,由Unity自动传入 float4 _MainTex_TexelSize; struct vin_vct { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f_vct { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; // 传递8个方向的纹理偏移坐标 float2 offset[8] : TEXCOORD1; }; v2f_vct vert(vin_vct v) { v2f_vct o; o.vertex = UnityObjectToClipPos(v.vertex); o.color = v.color; o.texcoord = v.texcoord; // 计算纹理空间的8个方向偏移量 float2 baseOffset = _OutlineWidth * _MainTex_TexelSize.xy; o.offset[0] = float2(baseOffset.x, baseOffset.y); // 右上 o.offset[1] = float2(baseOffset.x, -baseOffset.y); // 右下 o.offset[2] = float2(-baseOffset.x, baseOffset.y); // 左上 o.offset[3] = float2(-baseOffset.x, -baseOffset.y); // 左下 o.offset[4] = float2(baseOffset.x, 0); // 右 o.offset[5] = float2(-baseOffset.x, 0); // 左 o.offset[6] = float2(0, baseOffset.y); // 上 o.offset[7] = float2(0, -baseOffset.y); // 下 return o; } fixed4 frag(v2f_vct i) : SV_Target { // 采样原纹理像素 fixed4 originalCol = tex2D(_MainTex, i.texcoord); // 如果原像素不透明,直接返回本体颜色 if (originalCol.a > 0.01) { return originalCol * i.color; } // 检查8个方向的采样结果 fixed outlineAlpha = 0; for (int j = 0; j < 8; j++) { fixed4 offsetCol = tex2D(_MainTex, i.texcoord + i.offset[j]); outlineAlpha = max(outlineAlpha, offsetCol.a); } // 如果有方向采样到不透明像素,输出描边颜色 if (outlineAlpha > 0.01) { fixed4 outlineCol = _Color; outlineCol.a *= outlineAlpha; return outlineCol; } // 否则返回透明 return fixed4(0,0,0,0); } ENDCG } } }
关键改动说明
- 利用
_MainTex_TexelSize获取纹理像素尺寸,将描边宽度转换为纹理空间偏移量,适配不同分辨率的纹理 - 顶点着色器预计算8个方向的纹理偏移坐标,一次性传递给片元着色器
- 片元着色器优先判断当前像素是否为本体像素,避免重复计算;仅在透明区域检查周围8个方向的像素,生成描边
- 整个逻辑合并在单个Pass中,消除了重复Pass的冗余代码,同时降低了绘制调用开销
内容的提问来源于stack exchange,提问作者will do
相关产品推荐
相关产品推荐

