URP 渲染实战 / 第 29 章 性能优化 / 案例 29.4
案例 29.4远离原点的精度事故入门
目标:复现“用 half 保存世界坐标再算 UV”在远离原点时的问题。由于桌面平台可能把 half 当 float,这里按 half 的格式手动舍入来模拟:
Assets/Cookbook/Ch29/HalfPrecisionDemo.shader(节选:frag)
// half:1 位符号 + 5 位指数 + 10 位尾数。数值落在 [2^e, 2^(e+1)) 时,相邻两个可表示值相差 2^(e-10) float2 ToHalfAndBack(float2 v) { float2 e = floor(log2(max(abs(v), 6.1e-5))); // 6.1e-5 ≈ half 最小正规数 float2 ulp = exp2(e - 10); return round(v / ulp) * ulp; } half4 frag(Varyings i) : SV_Target { float2 p = i.positionWS.xz; if (_EmulateHalf > 0.5) p = ToHalfAndBack(p); // 相当于 half2 p = positionWS.xz; float2 uv = p * _Tiling; half3 c = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv).rgb; return half4(c, 1); }
// half:1 位符号 + 5 位指数 + 10 位尾数。数值落在 [2^e, 2^(e+1)) 时,相邻两个可表示值相差 2^(e-10) vec2 ToHalfAndBack(vec2 v) { vec2 e = floor(log2(max(abs(v), vec2(6.1e-5)))); // 6.1e-5 ≈ half 最小正规数 vec2 ulp = exp2(e - 10.0); return round(v / ulp) * ulp; } void main() { vec2 p = positionWS.xz; if (_EmulateHalf > 0.5) p = ToHalfAndBack(p); // 相当于 half2 p = positionWS.xz; vec2 uv = p * _Tiling; vec3 c = texture(_BaseMap, uv).rgb; fragColor = vec4(c, 1.0); }

精度选择的经验规则:
✓ 可以用 half
颜色、HDR 亮度(不超过几万)、单位向量(法线、光照方向、视线方向)、0~1 的系数(金属度、粗糙度、AO)、小范围内的 UV 偏移。✗ 必须用 float
位置(世界、观察、裁剪空间)、深度及其重建、大范围的 UV(世界空间 UV、大地形 UV)、时间(_Time.y 随运行时长增大)、需要做差的大数、矩阵运算的中间结果。URP/core 库里还有一个 real 类型:在支持原生 half 的平台上(定义了 PREFER_HALF 一类的条件)是 half,否则是 float,用于“精度要求不高、但希望在移动端省一点”的库函数。自己写 Shader 时,明确写 half 或 float 更清楚。
“时间越久越抖”:
_Time.y 是从启动开始的秒数,运行几个小时后达到数千,任何用 half 存储它或 sin(_Time.y * 很大的系数) 的动画都会开始卡顿、跳帧。长时间运行的应用(展厅、挂机游戏)要用 frac 或在 C# 里自己维护一个循环的时间参数。