Pixel-Perfect Outline Shaders for Unity

Table of Contents

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

原文地址

以下是基于你提供的内容的总结和解读,保留示例代码和图片链接:


轮廓着色器的工作原理与优化

1. 经典轮廓技术

  • 起源:在 2003 年游戏《XIII》中首次看到 3D 对象的轮廓效果,使用固定功能管线实现。
  • 实现方式:通过网格的外壳(Outer Hull)和反转法线绘制轮廓,覆盖黑色纹理。
  • 现代改进:使用顶点着色器在单独 Pass 中渲染略微膨胀的网格,剔除正面。

2. 经典轮廓技术的局限性

局限性 描述
轮廓宽度不均匀 轮廓宽度随物体形状和视角变化。
透视缩短 轮廓宽度随物体远离相机而变窄(透视投影)。
对象空间单位 轮廓宽度以对象空间单位指定,而非像素。

3. 经典轮廓着色器代码

Shader "Tutorial/Outline" {
    Properties {
        _Color ("Color", Color) = (1, 1, 1, 1)
        _Glossiness ("Smoothness", Range(0, 1)) = 0.5
        _Metallic ("Metallic", Range(0, 1)) = 0
        _OutlineColor ("Outline Color", Color) = (0, 0, 0, 1)
        _OutlineWidth ("Outline Width", Range(0, 0.1)) = 0.03
    }
    Subshader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        Input { float4 color : COLOR; }
        half4 _Color; half _Glossiness; half _Metallic;
        void surf(Input IN, inout SufaceStandardOutput o) {
            o.Albedo = _Color.rgb * IN.color.rgb;
            o.Smoothness = _Glossiness;
            o.Metallic = _Metallic;
            o.Alpha = _Color.a * IN.color.a;
        }
        ENDCG
        Pass {
            Cull Front
            CGPROGRAM
            #pragma vertex VertexProgram
            #pragma fragment FragmentProgram
            half _OutlineWidth;
            float4 VertexProgram(float4 position : POSITION, float3 normal : NORMAL) : SV_POSITION {
                position.xyz += normal * _OutlineWidth;
                return UnityObjectToClipPos(position);
            }
            half4 _OutlineColor;
            half4 FragmentProgram() : SV_TARGET { return _OutlineColor; }
            ENDCG
        }
    }
}
  • 轮廓 Pass:通过 Cull Front 剔除正面,沿法线偏移顶点位置生成轮廓。

4. 示例效果

  • 球体轮廓轮廓效果良好,但球体法线平滑,无法展示尖锐边缘问题
  • 圆环轮廓轮廓延伸到圆环中心,展示法线偏移的效果

5.优化技术

1. 在裁剪空间工作

为了消除对象缩放对轮廓宽度的影响,可以将顶点位置和法线转换到裁剪空间。这样,轮廓宽度不会随对象缩放而变化。以下是实现代码:

float4 clipPosition = UnityObjectToClipPos(position);
float3 clipNormal = mul((float3x3) UNITY_MATRIX_VP, mul((float3x3) UNITY_MATRIX_M, normal));
clipPosition.xyz += normalize(clipNormal) * _OutlineWidth;

2. 二维偏移

在裁剪空间中,仅对 x 和 y 方向进行偏移,可以进一步稳定轮廓宽度,减少视角变化的影响。以下是实现代码:

float2 offset = normalize(clipNormal.xy) * _OutlineWidth;
clipPosition.xy += offset;

3. 消除透视缩短

透视缩短会导致轮廓宽度随物体远离相机而变窄。为了消除这种影响,可以在偏移量中乘以裁剪空间位置的 w 分量,以抵消透视除法的影响。以下是实现代码:

float2 offset = normalize(clipNormal.xy) * _OutlineWidth * clipPosition.w;
clipPosition.xy += offset;

4. 像素级精度

为了实现精确的像素级轮廓宽度,可以根据屏幕宽高比调整偏移量,并将轮廓宽度直接指定为像素值。以下是实现代码:

float2 offset = normalize(clipNormal.xy) / _ScreenParams.xy * _OutlineWidth * clipPosition.w * 2;
clipPosition.xy += offset;

5. 处理尖锐边缘

尖锐边缘会导致轮廓效果不佳。可以通过以下方法解决:

  • 平滑边缘:通过平滑法线或倒角(grazing angle)处理尖锐边缘。
  • 单独网格:使用具有平滑法线的单独网格绘制轮廓。
  • 顶点颜色存储法线:将平滑法线数据存储在顶点颜色通道中。

6. 最终效果

  • 像素级轮廓轮廓宽度精确到像素,适合 UI 和像素艺术风格
  • 抗锯齿效果开启抗锯齿后,轮廓更加平滑,适合矢量艺术风格

总结

  • 经典技术:适合手绘风格,但轮廓宽度不稳定,受透视影响。
  • 优化技术:通过裁剪空间、二维偏移、消除透视缩短和像素级精度,实现更稳定的轮廓效果。
  • 适用场景:经典技术适合手绘风格,优化技术适合 UI 和像素艺术风格。
kumakoko avatar
kumakoko
pure coder
comments powered by Disqus