学习进度
0/9 已勾只保存在这台浏览器;勾满 9 项即视为本题完成。
先把题目说成人话
手机里的石墙既要有凹凸感,又不能让下载、内存和画面读取一起爆掉。用颜色记录表面细小朝向的图片叫法线贴图(Normal Map)。近处英雄道具值得保留细小起伏,远处墙面可能只需要稳定的大明暗;先分清占硬盘和内存的量、每帧搬运的数据以及压缩后的可见损伤,才能按距离和用途分档,而不是简单删掉它。
这是虚构教学故事,不是个人项目经历。
场景:先让问题变得看得见
角色护甲近看有细密拉丝,侧光扫过时,高光会沿凹凸连续滑动;镜头拉远后,玩家只能看见较大的起伏。现在的问题不是“方向图越清楚越好”,而是哪些距离真的能看见细节,图片装进手机要占多少空间,以及每帧读取这些细节是否挤占了别的工作。
先把三本账分开:行李箱里装了多少,是容量;每秒从箱子取出多少,是带宽;一帧是否按时完成,是时间。它们会互相影响,却不能用同一个数字直接替换。
(右脑)画面: 护甲表面像铺着无数根细小方向箭头,光从侧面扫过时,箭头朝向的轻微误差会被高光放大成跳动;镜头远离后,细箭头已经小到一个屏幕格里,只剩大片起伏仍能改变轮廓和明暗。图片压得更紧或缩得更小,像把许多箭头塞进同一个包装盒,既可能抹掉细节,也可能让相邻方向互相污染。质量档应该把精确箭头留给脸、武器和近景湿润表面,把远景换成更粗的方向或模型自身的表面方向;是否真的省时间,还要看每帧覆盖、重复读取和缓存,而不是只看文件变小。
(左脑)对表: 先把微小箭头映射为切线空间法线,并按线性数据采样。进入 Shader 后不能先验地把原始 r、g 当成 x、y:Unity 的平台 NormalMapEncoding 可能走 XYZ 或 DXT5nm 等不同打包,必须使用当前渲染管线匹配的法线 unpack helper;只有明确规定布局的自定义双通道纹理,才手动把存储值重映射到有符号 x、y。一个 2048² 基础层若用 RGBA32,容量为 2048×2048×4=16 MiB,理想完整 mip 链约 21.3 MiB;若最终为 ASTC 6×6,128÷36≈3.56 bpp,基础层约 1.78 MiB,完整链约 2.37 MiB。公式只估算容量,运行带宽还要经过当前 mip、屏幕覆盖、采样次数、过滤、块读取与缓存命中;因此驻留 MiB、带宽 GB/s 和图形时间 ms 必须分别测量。
小明:先形成一个完整猜测
小明认为法线有 XYZ 三个分量,只存 XY、在 Shader 里重建 Z,就能固定节省三分之一带宽。这个模型在数学上有一半是对的:先按 Unity 当前平台的实际打包方式正确 unpack,或对自定义已知双通道布局把纹理值解码到有符号范围后,单位切线空间法线才满足 x²+y²+z²=1;若项目约定法线只在正半球,才可以用 z=sqrt(max(0,1-x²-y²)) 重建。未经 unpack 的原始 0–1 纹理值不能直接代入,负 Z 的符号也已经无法从 XY 推回。
但“少写一个通道”不自动等于“少读三分之一”。如果导入后仍占同一种 ASTC 块或 RGBA 格式,物理块大小没有改变,容量与带宽未必下降;还会增加解码 ALU,并放大低精度 XY 对 Z 的误差。
小红:把猜测放回现实检查
小红把四种对象分别检查:
- 容量:用实际平台导入格式计算 bpp、尺寸和 mip,而不是看源 PNG 大小。
- 精度:在掠射光、镜面高光和压缩后的目标设备上看块伪影;法线贴图按线性数据采样。
- 带宽:用设备 GPU 计数器看纹理读取与缓存压力,并结合 GPU 时间;不从容量公式反推时间。
- 视觉距离:近景脸部、武器与湿润材质对法线更敏感,远景或粗糙材质通常能接受更小尺寸、更粗 ASTC 块或只用顶点法线。
她逐项替换后发现:单纯改通道但不改物理格式,内存没有变化;真正改变分辨率、mip 策略或压缩块后,容量才按公式下降。至于帧时间是否改善,仍以同一目标设备捕获为准。
我:用检查结果修正模型
我把替代方案做成材质质量档,而不是全局开关。高档保留关键资产的高分辨率法线;中档使用经过画面验收的 ASTC 块与较小尺寸;低档让远景只用顶点法线或低频主法线,Detail Normal 只在近距离启用。普通 Unity Normal Map 先走当前管线匹配的 unpack helper;XY 重建 Z 只用于打包约定明确、确实对应更合适存储格式的候选,并在目标 GPU 上让 ALU 与质量都过关。
同时保证切线基、绿色通道方向和法线强度在各档一致,避免把坐标约定错误误判为压缩误差。最终分别验收驻留容量、硬件带宽计数、GPU 时间和镜头画质;四者是有关联的账,但不是同一个单位。
(右脑)画面修正: 小明依据单位向量只存 XY、现场重建 Z,以为包裹会固定缩小三分之一;数学上可以重建,但导入后若仍占同一种 ASTC 块或 RGBA 格式,物理包裹没有变小,实测内存也不会下降,还会增加解码 ALU 并放大低精度误差。小红再用近景高光、远景距离和目标设备计数器核对,才把单轴的“精度换带宽”改成多项取舍:最终存储格式、解码计算、缓存行为、采样距离和视觉敏感度必须一起过关。
(左脑)严格对表: 对每档资产先确认切线约定、平台 NormalMapEncoding、实际导入格式与管线 unpack 路径,并关闭数据贴图 sRGB;按最终格式用 width×height×bpp/8 加 mip 与块取整估算容量;制作分辨率、ASTC 块、Detail Normal 距离开关和自定义 XY 重建候选;在同一设备与镜头分别记录驻留 MiB、纹理带宽计数、GPU ms 和画质。自定义 XY 重建前先按已知布局把存储值解码到有符号范围,并仅在正半球约定成立、目标格式确实少占物理存储且 ALU/误差通过时使用 z=sqrt(max(0,1-x²-y²));所有档位还须保持法线方向、接缝与镜面响应一致。
老师解惑
- 理想基础层容量约为宽×高×bpp÷8;完整 mip 链约为基础层的 4/3,块压缩边缘需按整块取整。
- 带宽描述单位时间传输的数据量,帧时间描述完成一帧的时间;二者不能直接相减。
- 两通道法线可重建第三分量,但只有物理存储格式随之改变时,才一定减少容量。
- Unity 法线贴图的通道位置取决于平台编码与导入路径;先用当前管线的 unpack helper 得到法线,再讨论自定义 XY 重建,不能把原始 r、g 一律当作 x、y。
- 更粗压缩、较小尺寸、Detail Normal 距离开关、顶点法线和几何细节烘焙,都是不同边界下的替代路线。
工具分工
- Texture Importer:确认 Android/iOS 最终格式、尺寸、mip 和 sRGB 设置。
- Memory Profiler:看 Unity 报告的纹理内存与已加载资源,而不是源文件大小;物理 GPU 驻留仍要由 Texture Streaming 统计或平台工具补证。
- 平台 GPU Profiler:读取纹理带宽、缓存与 GPU 时间等硬件证据。
- texture-sampling Lab:拆开通道重建、采样、压缩误差和距离质量档。
记忆钩子
先用格式算容量,再用硬件看带宽;少一个通道只有改变物理存储时,才真的少一份行李。
这是教学场景,不是目标项目的真实捕获或个人经历。
下一步:验证这条因果
法线 Mip 与压缩误差如何改变斜视高光。
教学边界:格式支持和最终质量仍要在 Unity 目标设备实测。