1.6.0
为网格自动计算平滑法线并烘焙到顶点色 / 切线 / TEXCOORD,用于背面外扩描边,解决硬边处描边断裂。工具本体是纯编辑器 C#,与渲染管线无关。
⚠️ 升级必读
本版移动了一个包内文件,已导入过 Sample 的项目升级后会编译失败(材质变洋红 + Console 报错)。原因:Shader/OutlineNPR.hlsl 移到了 Shader/Demo/OutlineNPR.hlsl,而升级 UPM 包不会更新你 Assets/Samples/ 下那份旧 Shader,它仍 include 旧路径。
第 1 步:重新导入 Sample(所有升级者必做)
- 删除
Assets/Samples/Outline Smooth Normals Generator/<旧版本号>/整个目录
(Sample 按版本号分目录,旧目录不删的话新旧两份 Shader 的.metaGUID 相同、会撞车) - 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。 |
把
OUTLINEPass 复制进自有 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 的
OUTLINEPass 改用共享模板 —— 该 Pass 由 URP 85 行 → 34 行、Built-in 74 行 → 21 行,手写的Attributes/Varyings/vert/frag全部移入模板。 -
URP Demo Shader 的
FORWARDPass 也改用同一个宏: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(
TEXCOORD0–TEXCOORD7,共 8 个通道)。 - 两种存储空间 —— 对象空间 / 切线空间,与存储方式正交;切线空间让蒙皮模型的描边正确跟随骨骼动画。
- 描边 Pass 模板 —— 两步接进自己的 Shader,URP / Built-in 各一份,描边逻辑随包升级。
- 导入时自动烘焙 —— 命中文件名后缀的模型(重)导入即烘焙,非破坏性、零手动操作;配置持久化到
ProjectSettings/,另有自定义规则 / 自定义存储两个扩展委托。 - 广泛的目标来源 —— 场景对象、Mesh 资产、模型(
.fbx等)、预制体,自动遍历层级、复选框多选批量处理。 - 实时描边预览 —— 内嵌视口,同屏显示所有勾选网格,法线可视化对比、通道状态检查、屏幕 / 世界空间描边对照。
- 网格健康检查 —— 生成 / 烘焙前扫描非法数据(缺法线、退化三角、NaN、退化切线、重合顶点过多等),手动二次确认、自动烘焙跳过。
- 数据安全 —— 阻止对只读资产的假保存、批量另存为独立 Mesh、会话快照还原。
- 描边 Shader(Sample) —— URP / Built-in 两版,两 Pass(描边 + 基础 NPR),描边来源支持 8 个 UV 通道,基础色调试档位,自定义材质面板。
⚠️ 已知限制
- HDRP 未提供描边 Shader,可参照文档自行移植(解码逻辑通用,仅渲染 Pass 需适配)。
- 存储方式与存储空间无法从数据反推 —— 两种空间存的都只是一条单位方向。工具与材质选得不一致时不会报错,只是描边偏斜或撕开。排查描边异常请先核对这两项。
- 切线空间要求网格有合法切线(导入设置
Tangents≠None)。UV 退化处的切线构不成正交基,这些顶点的描边会退化为沿原始顶点法线外扩,健康检查会按比例报出。 - 重新导入模型可能让已烘数据失配 —— 烘焙用的是当时的切线,若之后改用不同的切线生成方式重新导入,需重新烘焙。因此切线空间推荐配合「导入时自动烘焙」使用。
- Pass 模板声明了全部 8 个 TEXCOORD,因为存储来源是材质上运行时切换的。对顶点带宽敏感、且确定只用一个通道的项目,请按文档「极简写法」手写 vert。
- 不支持 Undo ——
Undo.RecordObject不可靠地跟踪网格顶点数据;工具提供自己的会话快照还原。 - 顶点合并采用格点取整,恰好跨越格边界的两点仍会被分开;容差需远小于模型最小特征尺寸。
- 通道状态最高只说「可能是平滑法线」—— 编码后与普通顶点色 / UV 在数据上不可区分。
- 世界空间宽度模式下
_OutlineWidth是世界单位,对很大 / 很小的模型需相应调整。
📖 文档
总览 / 快速上手:
简体中文 ·
English ·
日本語