01 · 渲染与着色器
01 · 渲染与着色器
目标不是"写最牛的 shader",是用最少的 shader 覆盖最多的资产,并留下一个能豪赌的招牌。
1. 渲染路径选型(第一步,决定后面所有事)
| 路径 | 优点 | 缺点 | 独立游戏建议 |
|---|---|---|---|
| Forward | 便宜、透明处理好、MSAA 可用 | 多光源贵 | 2D / 低多边形 / 移动端首选 |
| Forward+ | 多光源便宜(光剔除) | 复杂度中 | 有较多动态光时的首选(Unity URP) |
| Deferred | 多光源极便宜,易做屏幕空间效果 | 透明难处理、内存高、MSAA 不可用 | 场景光多且不需要太多透明时 |
| Deferred + Forward(透明) | 折中 | 两套代码 | 3A 常用,独立游戏一般不必 |
决策表
| 你的情况 | 选什么 |
|---|---|
| 2D / 像素 | Forward |
| 3D 卡通,光源 ≤ 4 | Forward |
| 3D 场景暗、需要很多火把/灯光 | Forward+ |
| 开放世界 + 大量点光 | Deferred |
| 移动端 | Forward(禁用延迟) |
| 不确定 | Forward+(最稳) |
2. 光照策略(独立游戏的性价比之选)
三层光照模型
L0 天光/环境光 → 烘焙(Ambient / Sky Occlusion / 环境 cubemap)
L1 主光(太阳/月亮) → 1 盏实时方向光 + 阴影
L2 补光(火把/灯/特效光) → 烘焙 + Light Probe + 少量实时(无阴影)
预算
| 项 | Low | Mid | High |
|---|---|---|---|
| 实时投影光源 | 1 | 1–2 | 4 |
| 无阴影实时光 | 2 | 4 | 8 |
| 阴影贴图分辨率 | 1024 | 2048 | 4096 |
| 阴影距离 | 30m | 60m | 120m |
| 级联数(CSM) | 2 | 3 | 4 |
烘焙 vs 实时
| 烘焙 | 实时 | |
|---|---|---|
| 成本 | 构建时间 + 内存 | 每帧成本 |
| 动态物体 | 需要 Light Probe | 天然支持 |
| 昼夜变化 | 需多套切换 | 免费 |
| 独立游戏建议 | 静态环境全烘焙 | 只留主光实时 + 动态物体用 Probe |
技巧:
- 烘焙 + 探针 是独立游戏的最优解,90% 场景适用。
- 昼夜循环:烘焙 3–4 套(晨/午/昏/夜),运行时按时间混合(内存翻倍,但每帧成本为零)。
- Light Probe 密度:每 2–5m 一个,关键路径更密。少了会看到动态物体"跳变"。
- Reflection Probe:每 10–20m 一个,室内必须单独放。
光照模板(量产关键)
建一套"全局光照模板":一个配置好的场景(天光、主光、雾、后处理、探针密度),所有新场景从这个模板复制。 禁止每个场景重新调光照——那是量产杀手。
3. 母材质体系(独立游戏 TA 的第一生产力)
为什么必须有
- 风格统一(所有资产共享同一套材质逻辑)
- 性能好(同一 shader → 可合批)
- 迭代快(改母材质 = 改所有资产)
- 新人/外包不会跑偏
建议的母材质清单(3–5 个,覆盖 90%)
| # | 母材质 | 覆盖 | 关键参数 |
|---|---|---|---|
| M1 | 环境 Standard | 地面/墙/建筑/道具 | Albedo + ORM + Normal + 三平面选项 + 顶点色(湿滑/苔藓/磨损) |
| M2 | 角色 Character | 主角/敌人/NPC | Albedo + ORM + Normal + Rim + 描边 + 受击闪白 + 溶解 |
| M3 | 透明/特效 | 粒子/能量/水 | 加性/Alpha 混合 + 软粒子 + UV 流动 + 溶解遮罩 |
| M4 | 植被 | 树/草 | 双面 + 风摆动 + Alpha Test + 半透光(次表面近似) |
| M5 | 天空/背景 | 天空盒/远景 | 渐变 + 云流动 + 视差层 |
超过 5 个母材质 = 你在制造维护债。 新增母材质需评审。
母材质的参数化设计(关键技巧)
把"看起来不同"变成"参数不同":
| 参数 | 作用 | 一个资产变 N 个 |
|---|---|---|
_Tint |
改色 | 石材 → 木材 → 冰 |
_Wear / _Era |
磨损程度 | 新 → 旧 → 废墟 |
_Wetness |
湿度 | 干 → 湿 → 积水 |
_Moss |
苔藓覆盖 | 干净 → 长苔 |
_Snow |
积雪 | 无雪 → 厚雪 |
_Dissolve |
溶解(死亡/消失/出现) | 死亡动画 |
_EmissionPulse |
呼吸发光 | 可交互提示 |
_HitFlash |
受击闪白 | 打击反馈 |
这是独立游戏省钱的核心手段:1 套资产 × 10 组参数 = 10 个视觉变体,成本接近零。
4. Shader 技法库(独立游戏高频,按性价比排序)
A 级(必会,成本极低)
| 技法 | 用途 | 成本 |
|---|---|---|
| Rim / Fresnel 边缘光 | 主体浮出、风格化 | 极低 |
| 溶解 Dissolve(噪声 + clip) | 死亡、出现、消散 | 极低 |
| UV 流动 | 水、能量、岩浆 | 极低 |
| 顶点色控制(湿/苔/磨损/描边宽) | 免贴图变体 | 极低 |
| 三平面映射 Triplanar | 免 UV 的地表/岩壁 | 低 |
| 风摆动(顶点位移噪声) | 植被、布料、旗帜 | 低 |
| 受击闪白 | 打击感 | 极低 |
| 深度淡出软粒子 | 粒子不穿模 | 低 |
| 卡通分层(step/ramp) | 风格化 | 低 |
| Dithering 抖动 | 复古/半透明替代 | 极低 |
B 级(招牌候选)
| 技法 | 用途 | 成本 |
|---|---|---|
| 描边(背面外扩/后处理) | 卡通风格 | 中 |
| 屏幕空间水/折射 | 水面 | 中 |
| 体积光/光轴 | 氛围 | 中 |
| 视差映射 Parallax | 深度感 | 中 |
| 高度混合 Height Blend | 自然地表过渡 | 中 |
| GPU 粒子 | 大量粒子 | 中高 |
| 次表面近似(SSS) | 皮肤、叶子 | 中 |
| 程序化天空 | 昼夜 | 中 |
| 高度雾/体积雾 | 氛围 | 中 |
C 级(谨慎,除非它是你的招牌)
屏幕空间反射 SSR · SSGI · 实时 GI · 复杂流体 · 光线追踪 · 毛发 · 高级布料 · 全场景曲面细分
原则:C 级技术一次只允许上一个,且必须有降级方案。
5. Shader 性能优化(硬技能)
检查清单
| 项 | 规则 |
|---|---|
| 纹理采样 | 每个 shader ≤ 5 次采样(移动端 ≤ 3) |
| 精度 | 能用 half 就用 half(位置/时间除外) |
| 分支 | 避免动态分支;分支两边复杂度要对等 |
| 数学 | 避免 pow/sqrt/sin/cos/log/exp,用近似或 LUT |
| 计算位置 | 能放顶点着色器的就别放片元 |
| 能放 CPU 的 | 每帧算一次的放脚本里传参 |
discard/clip |
禁用或可关闭(破坏 early-Z) |
| 透明 | 能不用就不用(破坏 early-Z + overdraw) |
| 通道打包 | Rough/AO/Metal 打包成 ORM,一次采样 |
| 依赖纹理读取 | 避免在片元里用计算出的 UV 采样(无法预取) |
变体治理(独立游戏最容易翻车的一项)
变体来源:keyword(multi_compile / shader_feature)、光照模式、雾模式、阴影、实例、质量档。
爆炸原因
- 用了
multi_compile而非shader_feature(前者无条件编译所有组合) - 材质上开了很多 keyword 但只有 1 个在用
- 每个母材质都带全套光照/雾变体
治理三步
- 用
shader_feature替代multi_compile(只编译被材质实际用到的) - 剥离:Player Settings 中剥离不需要的光照/雾/阴影变体
- 预热:Shader Variant Collection(Unity)/ PSO Caching(UE)/
RenderingDevice预热(Godot);或首屏隐藏相机渲染所有材质
预算:总变体 ≤ 5000,单 shader ≤ 200,首屏编译 ≤ 3s。
6. 合批与 Draw Call
| 技术 | 适用 | 说明 |
|---|---|---|
| Static Batching | 静态环境 | 内存换 DrawCall,注意别把大场景全合了 |
| Dynamic Batching | 小网格 | Unity 老方案,限制多 |
| GPU Instancing | 同网格同材质多实例 | 植被/碎石/人群首选 |
| SRP Batcher | URP | 按 shader 合批,母材质策略的直接收益 |
| Impostor / Billboard | 远景 | 远景植被用面片 |
| 遮挡剔除 | 室内/密集场景 | 必须开 |
| LOD | 一切 | 见下 |
目标:DrawCall < 800(Low)/ 1500(Mid)
7. LOD 策略
| 距离 | LOD | 减面比 | 额外 |
|---|---|---|---|
| 0–10m | LOD0 | 100% | 全材质 |
| 10–30m | LOD1 | 50% | 简化材质 |
| 30–80m | LOD2 | 20% | 单材质 |
| 80m+ | Billboard/Impostor | 2 tri | |
| 超出 | Cull | — | 用雾遮住切换 |
独立游戏技巧:
- 自动生成 LOD(引擎自带或脚本),不要手做
- 雾是 LOD 的朋友:雾浓时可以更早切换 LOD 而不被察觉
- 角色 LOD 同时降骨骼数(见
04-character-and-animation.md)
8. 渲染调试方法(TA 的日常)
| 症状 | 第一步 | 工具 |
|---|---|---|
| 掉帧 | 先分 CPU/GPU | Profiler(Unity)/ Unreal Insights / Godot Profiler |
| GPU 慢但不知道哪 | 逐 pass 关闭 | RenderDoc / Frame Debugger / GPU Visualizer |
| 透明很贵 | 看 overdraw | Overdraw 视图 |
| DrawCall 多 | 看 Frame Debugger | |
| 内存爆 | Memory Profiler / Stat | 按类别查(纹理通常占 50%+) |
| 闪烁 | 查深度精度/z-fighting | 调整 near/far plane |
| 加载慢 | 查 shader 编译 + 纹理加载 | 预热 + Addressables/流式 |
铁律:只在真机、Development Build、真实玩法下 Profile。 编辑器和空场景的数据全是假的。
9. Shader 规格单模板
见 templates/04-shader-spec.md
10. 自检清单
- 渲染路径定了吗?理由是什么?
- 母材质 ≤ 5 个吗?覆盖了 90% 资产吗?
- 光照模板建了吗?新场景是从模板复制的吗?
- 纹理通道打包了吗?
- Shader 变体总数有上限吗?有剥离和预热吗?
- 每个 shader 有降级/关闭开关吗?
- 真机 Profile 过吗?数据是多少?
- 招牌技术是哪一个?其余的技术投入有没有被质问过?
- DrawCall / 面数 / 透明层 在预算内吗?
- 材质参数化了吗?(1 个资产能变出几个变体?)
11. 一句话总结
独立游戏的渲染能力不来自"会写多少 shader", 来自**"3 个母材质 + 1 个招牌技术 + 一张被严格执行的预算表"**。 你的 shader 越通用,你的团队越快;你的招牌越集中,你的游戏越容易被记住。