Skip to content

1.6.0

Choose a tag to compare

@AleFeng AleFeng released this 20 Jul 09:22

为网格自动计算平滑法线并烘焙到顶点色 / 切线 / TEXCOORD,用于背面外扩描边,解决硬边处描边断裂。工具本体是纯编辑器 C#,与渲染管线无关。

⚠️ 升级必读

本版移动了一个包内文件,已导入过 Sample 的项目升级后会编译失败(材质变洋红 + Console 报错)。原因:Shader/OutlineNPR.hlsl 移到了 Shader/Demo/OutlineNPR.hlsl,而升级 UPM 包不会更新你 Assets/Samples/ 下那份旧 Shader,它仍 include 旧路径。

第 1 步:重新导入 Sample(所有升级者必做)

  1. 删除 Assets/Samples/Outline Smooth Normals Generator/<旧版本号>/ 整个目录
    (Sample 按版本号分目录,旧目录不删的话新旧两份 Shader 的 .meta GUID 相同、会撞车)
  2. Package Manager → 选中本包 → Samples → 重新导入你用的那一个

GUID 保持不变,材质会自动接回新 Shader。

第 2 步:只有从 1.4.x 或更早升级才需要

你现在的版本 还需要做什么
1.5.x 没有了。本版不改任何数据格式,重新导入 Sample 即可。
1.4.x 及更早 还要补做 1.5.0 的存储空间迁移:旧数据都是对象空间烘的,而材质默认已是 Tangent Space —— 要么重新烘焙一次(推荐,顺带获得蒙皮支持),要么把材质的 Smooth Normal Space 改回 Object Space。详见 1.5.0 Release notes

OUTLINE Pass 复制进自有 Shader 的用户不受影响 —— 本版没有移除或改名任何共享库函数,你现有的代码照常工作。不过现在有更省事的接入方式了,见下。

📦 安装

Window > Package Manager → 左上角 +Install package from git URL... → 粘贴:

https://github.com/AleFeng/OutlineSmoothNormalsGenerator.git?path=/Packages/com.alefeng.outlinesmoothnormalsgenerator#1.6.0

⚠️ 装完还必须导入一个 Sample,否则没有描边 Shader。
核心包刻意不含 Shader —— 这是「不引入任何渲染管线依赖」的代价。
Package Manager 里选中本包 → Samples按你的管线导入其中一个

Sample Shader 名
Outline Shader (URP) & Demo OutlineSmoothNormalsGenerator/Outline URP
Outline Shader (Built-in RP) & Demo OutlineSmoothNormalsGenerator/Outline Built-in

安装成功后菜单栏出现 Tools → Smooth Normal Generator

🆕 1.6.0 更新内容

描边 Pass 模板:接入自己的 Shader 从「整段照抄」变成「两步」

这是本版的主题。以前文档教你「把 OUTLINE Pass 整段复制进自己的 Shader」—— 那是七八十行的 Attributes / Varyings / vert / frag,能用,但复制出去的代码不随包升级1.5.0 新增存储空间时就吃过这个亏:所有手抄过 Pass 的项目都得手动补三处,漏了不报错、只是描边整体偏斜。

现在包内提供现成的 Pass 模板:

第 1 步,把 6 个描边属性加进 Properties(ShaderLab 不支持宏,这段只能复制):

[Header(Outline)]
_OutlineColor   ("Outline Color", Color) = (0,0,0,1)
[PowerSlider(3.0)]
_OutlineWidth   ("Outline Width", Range(0, 0.1)) = 0.015
[Enum(Screen Space, 0, World Space, 1)]
_OutlineWidthMode ("Outline Width Mode", Float) = 0
_SmoothNormalSrc ("Smooth Normal Source", Float) = 0
[Enum(RG, 0, GB, 1, BA, 2)]
_VCChannel      ("Vertex Color Channel", Float) = 2
[Enum(Object Space, 0, Tangent Space, 1)]
_SmoothNormalSpace ("Smooth Normal Space", Float) = 1

第 2 步,加一个 Pass(URP 版;Built-in 版见文档):

Pass
{
    Name "OUTLINE"
    Tags { "LightMode" = "SRPDefaultUnlit" }

    Cull Front
    ZWrite On
    ZTest LEqual

    HLSLPROGRAM
    #pragma vertex   OSN_OutlineVert
    #pragma fragment OSN_OutlineFrag

    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    #include "Packages/com.alefeng.outlinesmoothnormalsgenerator/Shader/OutlineSmoothNormals.hlsl"

    CBUFFER_START(UnityPerMaterial)
        float4 _BaseColor;      // ← 你自己的属性
        float4 _BaseMap_ST;
        OSN_OUTLINE_MATERIAL_FIELDS
    CBUFFER_END

    #include "Packages/com.alefeng.outlinesmoothnormalsgenerator/Shader/OutlinePassURP.hlsl"
    ENDHLSL
}

完了。不必抄任何解码代码,后续库升级时你的 Shader 跟着一起更新。

Demo 的两个描边 Shader 已经改用同一套模板 —— 模板出问题,Demo 会第一时间暴露。

其他新增

  • OSN_OUTLINE_MATERIAL_FIELDS —— 展开为描边所需的 6 个 uniform 声明,供拼进你自己的 UnityPerMaterial。之所以不由本库另开一个 CBUFFER:SRP Batcher 要求同一 Shader 各 Pass 的 UnityPerMaterial 布局完全一致,另开一个就是两份布局,batcher 会静默失效 —— 不报错、只掉性能,最难查。
  • OSN_GetSmoothNormalOS(...) —— 「解码 + 存储空间还原」的合并调用,写给需要完全掌控顶点着色器的人。这两步必须成对出现,而漏掉后者不产生任何报错、只是描边整体偏斜。合成一个函数后,这个坑在结构上不再存在。

变更

  • Shader/ 目录按用途分层:根目录现在只放生产用户可直接 include 的公开接口,Demo 专用的 NPR 数学(卡通明暗 / 边缘光 / 调试色)移入 Shader/Demo/。此前两者同级,容易让人以为它也是接入描边的必需品。

    Shader/
    ├── OutlineSmoothNormals.hlsl    ← 解码 + 空间还原 + 外扩的唯一真源
    ├── OutlinePassCommon.hlsl       ← Pass 模板主体(不直接 include)
    ├── OutlinePassURP.hlsl          ← Pass 模板 · URP 适配层
    ├── OutlinePassBuiltIn.hlsl      ← Pass 模板 · Built-in 适配层
    └── Demo/
        └── OutlineNPR.hlsl          ← Demo 专用,生产用不到
    
  • 两个 Demo 描边 Shader 的 OUTLINE Pass 改用共享模板 —— 该 Pass 由 URP 85 行 → 34 行、Built-in 74 行 → 21 行,手写的 Attributes / Varyings / vert / frag 全部移入模板。

  • URP Demo Shader 的 FORWARD Pass 也改用同一个宏:OUTLINE Pass 用宏后字段顺序改变,两个 Pass 的 UnityPerMaterial 若不再逐字一致,SRP Batcher 就会静默失效。两处共用一个宏,从结构上杜绝漂移。

文档

  • 重写「在游戏中使用描边」与「Shader 中读取平滑法线」两节。前者是上面那套两步接入的完整版(URP / Built-in 各一份可整段复制的 Pass{})外加四个必须注意的点:include 顺序、SRP Batcher 的 CBUFFER 一致性、描边 Pass 要排在基础 Pass 之前、LightMode 两管线各自的取值。
  • 后者重构为三档:通用写法(材质上可运行时切换存储方式)、极简写法(存储方式写死在 Shader 里,零分支、UV 一个都不用声明)、以及明确劝退的完全不 include
  • 三语(中 / 英 / 日)包内与根 README 同步更新。

✨ 主要功能

  • 平滑法线生成 —— 角度加权平均、可调合并容差,跨硬边得到连续外扩方向。
  • 三种存储方式 —— 顶点色(八面体编码,8-bit 下误差约 1°)/ 切线通道 / TEXCOORD(TEXCOORD0TEXCOORD7,共 8 个通道)。
  • 两种存储空间 —— 对象空间 / 切线空间,与存储方式正交;切线空间让蒙皮模型的描边正确跟随骨骼动画。
  • 描边 Pass 模板 —— 两步接进自己的 Shader,URP / Built-in 各一份,描边逻辑随包升级。
  • 导入时自动烘焙 —— 命中文件名后缀的模型(重)导入即烘焙,非破坏性、零手动操作;配置持久化到 ProjectSettings/,另有自定义规则 / 自定义存储两个扩展委托。
  • 广泛的目标来源 —— 场景对象、Mesh 资产、模型(.fbx 等)、预制体,自动遍历层级、复选框多选批量处理
  • 实时描边预览 —— 内嵌视口,同屏显示所有勾选网格,法线可视化对比、通道状态检查、屏幕 / 世界空间描边对照。
  • 网格健康检查 —— 生成 / 烘焙前扫描非法数据(缺法线、退化三角、NaN、退化切线、重合顶点过多等),手动二次确认、自动烘焙跳过。
  • 数据安全 —— 阻止对只读资产的假保存、批量另存为独立 Mesh、会话快照还原。
  • 描边 Shader(Sample) —— URP / Built-in 两版,两 Pass(描边 + 基础 NPR),描边来源支持 8 个 UV 通道,基础色调试档位,自定义材质面板。

⚠️ 已知限制

  • HDRP 未提供描边 Shader,可参照文档自行移植(解码逻辑通用,仅渲染 Pass 需适配)。
  • 存储方式与存储空间无法从数据反推 —— 两种空间存的都只是一条单位方向。工具与材质选得不一致时不会报错,只是描边偏斜或撕开。排查描边异常请先核对这两项。
  • 切线空间要求网格有合法切线(导入设置 TangentsNone)。UV 退化处的切线构不成正交基,这些顶点的描边会退化为沿原始顶点法线外扩,健康检查会按比例报出。
  • 重新导入模型可能让已烘数据失配 —— 烘焙用的是当时的切线,若之后改用不同的切线生成方式重新导入,需重新烘焙。因此切线空间推荐配合「导入时自动烘焙」使用
  • Pass 模板声明了全部 8 个 TEXCOORD,因为存储来源是材质上运行时切换的。对顶点带宽敏感、且确定只用一个通道的项目,请按文档「极简写法」手写 vert。
  • 不支持 Undo —— Undo.RecordObject 不可靠地跟踪网格顶点数据;工具提供自己的会话快照还原。
  • 顶点合并采用格点取整,恰好跨越格边界的两点仍会被分开;容差需远小于模型最小特征尺寸。
  • 通道状态最高只说「可能是平滑法线」—— 编码后与普通顶点色 / UV 在数据上不可区分。
  • 世界空间宽度模式下 _OutlineWidth世界单位,对很大 / 很小的模型需相应调整。

📖 文档

总览 / 快速上手:
简体中文 ·
English ·
日本語

详细使用文档(含界面详解、存储空间原理、Shader 接入说明):
简体中文 ·
English ·
日本語

完整 CHANGELOG