立即注册找回密码

QQ登录

只需一步,快速开始

微信登录

微信扫一扫,快速登录

手机动态码快速登录

手机号快速注册登录

搜索

图文播报

查看: 198|回复: 0

[讨论] 聊聊UI分辨率分离与线性空间下的GammaUI

[复制链接]
发表于 2025-5-30 14:54 | 显示全部楼层 |阅读模式

登陆有奖并可浏览互动!

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录 手机动态码快速登录

×
整理了下近期以及过去做过的这方面的一些功能, 挑出点有意思的东西或思路谈谈. 这篇没有什么代码之类的东西, 就纯是碎碎念.
UI分辨率分离

这年头在移动端做3D游戏, 分辨率基本是没可能拉满的, 最高也就八九百这样子, 低配下甚至六百多都有可能. 虽然场景怎么低分辨率怎么糊都好说, 但UI是万万不能糊的, 一糊这游戏也别玩了. 所以场景&UI分辨率分离就成了一个项目必需的feature.
最简单也是最直接的方案就是给UI单独来一张RT, 我们叫他UITarget. 场景在低分辨率的CameraAttachment上渲完后Blit到高分辨率的UITarget上, 再交给UI相机绘制UI.
听着很美好, 但实际并不能用. 因为这个方案对于带宽本就不富裕的移动端打击是致命的, 多一张1080p的RT简直是要了手机的老命.
于是就有了优化方案, 借用系统提供的Backbuffer作为UITarget. Backbuffer默认为手机硬件的原生分辨率, 无论任何情况下渲染管线的最终目标都会是它, 在URP正常的流程中, 管线会在最后一个相机渲染完后调用FinalBlit将当前的CameraAttechment Blit到Backbuffer上.
考虑一个常规情况, 即同时存在主相机(开后处理)与UI相机(不开后处理), 那么URP的默认流程是这样的:

  • MainCamera[Opaque/Transparent Pass]: 场景渲染到CameraColorAttachmentA
  • MainCamera[UberPost]: CameraColorAttachmentA渲染到CameraColorAttachmentB
  • UICamera[Transparent Pass]: UI渲染到CameraColorAttachmentB
  • UICamera[Final Blit]: CameraColorAttachmentB渲染到Backbuffer
我们只需要稍微改动一下管线, 让主相机做完后处理后直接Blit到Backbuffer:

  • MainCamera[Opaque/Transparent Pass]: 场景渲染到CameraColorAttachmentA
  • MainCamera[UberPost]: CameraColorAttachmentA渲染到Backbuffer
  • UICamera[Transparent Pass]: UI直接渲染到Backbuffer
这样一来, 不仅在没有性能开销的情况下实现了UI分辨率分离, 甚至还优化掉了FinalBlit的带宽, 可以说是可喜可贺. 后续可以通过Screen.SetResolution()调整Backbuffer也就是UI的分辨率, 然后在其基础上调整RenderScale也就是场景分辨率.
这个方案有一些注意事项和技术限制:
其一, 非UI相机的后处理RenderTarget, 只有最后一个非UI相机的后处理才应该且必需Blit到Backbuffer. 这个基本就是工程问题, 处理完善管线中相关的逻辑代码就好.
其二, UI相机必须是最后一个相机且不支持后处理.
前者没什么好说的, 但后者还是有些蛋疼.
一般来说Backbuffer是无法SetTexture到材质的, 也就无法做后处理Blit. 但如果是调用cmd.Blit()且当前的RenderTarget就是Backbuffer的话, 就可以神奇的拿到Backbuffer! 此时FrameDebug上显示是Grab RenderTexture, 原因是cmd.Blit()内部对此做了特殊处理, 将操作改为了曾经Builtin时代的GrabPass.(大概)
所以拿Backbuffer直接做后处理也并非不行...但因为要Blit, 之前省掉的RT和带宽又回来了...那这可能还不如上一个方案, GrabPass说不定比Blit还要更耗. 所以这一块就得和项目组好好对一下了, 如果说UI后处理只是在过剧情时出现、用完就关, 那开启UI后处理时动态关掉这个功能也不是不行.
此外, Linear空间下Backbuffer的格式应该是RGBA32_SRGB, 有些设备或模拟器并不支持SRGB格式的Backbuffer, 提供的是RGBA32_UNorm. URP原本的做法是在FinalBlit时判断一下CameraData.requireSrgbConversion, 如果为true则表示Backbuffer为RGBA32_UNorm需要手动做LinearToSRGB也就是常说的GammaCorrection(Pow0.45). 我个人测试下来的结果貌似目前只有在模拟器上SRGB Backbuffer是不受支持的, 问题不大, 干脆不支持就不开这功能得了.
还有就是Backbuffer不支持HDR, 导致UI材质(尤其是UI特效)上的HDR颜色输出到Backbuffer上会有严重错误, 这是因为Alpha Blend的执行时机是在LDR RT对Frag输出值进行Clamp(0,1)之后的. 解决方案是像内置UI Default Shader那样统一使用Alpha Permultiply, 把Alpha Blend的执行时机手动提前, 这样可以获得接近开启RenderToBackbuffer前的效果, 但依然存在微妙差异.
线性空间下的GammaUI

聊完UI分辨率分离, 再来聊聊UI的颜色空间.
进入PBR时代以后, 3D游戏的ColorSpace基本就都是Linear了. 这当然很好, 可以有正确的光照, 更接近物理世界的结果. 但美术们在PS中制作UI时, 却是在Gamma空间下工作的——这也合理, 即使在线性工作流下, 涉及视觉颜色的贴图也应该是sRGB的, 就比如BaseColor.
问题在于UI与BaseColor不同, 是要层与层之间做混合的. 美术在PS(Gamma空间)中得出了他们认为正确的结果, 丢到引擎中效果就不对了, 因为引擎是在Linear空间, 二者的混合算法不同.
在Gamma空间下, SrcAlpha-OneMinusSrcAlpha混合的公式是:
c = Src.rgb*Src.a+Dst.rgb*(1-Src.a)
而在Linear空间下, Unity会为勾选了sRGB的贴图做一次RemoveGammaCorrection(Pow2.2)将其转换到Linear空间, 并在Shader输出(到SRGB格式的RT)时再做一次GammaCorrection(Pow0.45)将其矫正回Gamma空间. 即:
c = (Src.rgb^{2.2}*Src.a+Dst.rgb^{2.2}*(1-Src.a))^{0.45}
解决方案大致分为两种.
第一种是修改美术工作流, 把美术PS改成线性空间, 让美术直接在线性空间下制作UI. 这个也是很多项目的做法, 美术虽然会不爽但时间长了也就接受了, 总之就是再苦一苦美术.
第二种也就是今天要着重谈的, 让UI相机在Gamma空间工作.
也就是给UI单独一张RT——没错, 又回到了上一节的论点, 这也是为什么我把这两块技术放在一起谈的原因.
思路也很简单, 既然RGBA32_SRGB的Backbuffer会自动做GammaCorrection(Pow0.45), 那么我只要申请一张RGBA32_UNrom格式的UITarget, 再把UI贴图取消勾选sRGB, 然后:

  • MainCamera[UberPost]: CameraAttachment渲染到UITarget, 手动LinearToSRGB(Pow0.45)
  • UICamera[Transparent Pass]: UI渲染到UITarget, 贴图取消勾选sRGB, 在Unorm贴图上正确的Gamma混合
  • UICamera[Final Blit]: UITarget渲染到Backbuffer并手动做一次SRGBToLinear(Pow2.2).
    对于UI, Pow2.2抵消了Backbuffer的自动GammaCorrection(Pow0.45), 而对于场景, 本就需要Backbuffer的自动GammaCorrection, 所以前后两次手动矫正正好互相抵消.
很好很完美, 但还是上一节的问题——带宽压力变大了. 能不能和做UI分辨率分离时一样不申请额外RT, 直接在Backbuffer做呢?
理论上当然可以, 我们知道, Unity会根据项目所选的颜色空间来选择Backbuffer格式, 若为Gamma则Backbuffer为RGBA32_UNorm, 若为Linear则Backbuffer为RGBA32_SRGB(前提是设备支持, 否则回退到RGBA32_UNorm).
而想要在SRGB格式的Backbuffer上直接绘制UI并做正确的Gamma空间混合是不可能的, 除非我们能把Linear颜色空间下的Backbuffer也改为RGBA32_UNorm, 这样一来我们只需要在场景渲染结束后, 对CameraAttechment手动做一次LinearToSRGB(Pow0.45)Blit到Backbuffer即可, 之后在Backbuffer上正常绘制UI就行.
那么可以改吗? 可以, 但得加钱, 改源码~

原文地址:https://zhuanlan.zhihu.com/p/720067122
楼主热帖
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册 微信登录 手机动态码快速登录

本版积分规则

关闭

官方推荐 上一条 /3 下一条

快速回复 返回列表 客服中心 搜索 官方QQ群 洽谈合作
快速回复返回顶部 返回列表