本文简述了 Unity 中 CanvasScaler 的一点知识

制作 UI 时,一般都需要进行多分辨率适配,基本的方法大概有以下几种:

  • UI 参照单一的分辨率(参考分辨率)进行制作,实际显示时按照某种方式调整到实际的设备分辨率
  • UI 按照所有可能的分辨率分别进行制作,实际显示时选择对应的设备分辨率显示
  • 上述两种方法(间)的某种平衡方式(譬如根据占比较高的几种分辨率来制作UI)

UGUI 中的多分辨率适配支持第一种方法,类型 CanvasScaler 包含了相关的分辨率调整逻辑.

CanvasScaler 在 Scale With Screen Size 的 UI 缩放模式下支持 3 种屏幕适配模式:

  • Match Width Or Height
  • Expand
  • Shrink

后两种模式比较容易理解(不了解的朋友可以直接参看文档),只是第一种适配模式(Match Width Or Height)让人觉得有些生疏,相关文档是这么说的:

Scale the canvas area with the width as reference, the height as reference, or something in between

解释的有些含糊,我们直接看下代码:

// We take the log of the relative width and height before taking the average.
// Then we transform it back in the original space.
// the reason to transform in and out of logarithmic space is to have better behavior.
// If one axis has twice resolution and the other has half, it should even out if widthOrHeight value is at 0.5.
// In normal space the average would be (0.5 + 2) / 2 = 1.25
// In logarithmic space the average is (-1 + 1) / 2 = 0
float logWidth = Mathf.Log(screenSize.x / m_ReferenceResolution.x, kLogBase);
float logHeight = Mathf.Log(screenSize.y / m_ReferenceResolution.y, kLogBase);
float logWeightedAverage = Mathf.Lerp(logWidth, logHeight, m_MatchWidthOrHeight);
scaleFactor = Mathf.Pow(kLogBase, logWeightedAverage);

可以看到代码中首先将宽高的缩放比例都进行了取对数的操作(转换到了对数空间),然后在对数空间进行线性插值,接着再进行了指数操作(转换回了原始空间),注释里举了一个例子:

假设参考分辨率的宽是实际分辨率的宽的2倍(此时 screenSize.x / m_ReferenceResolution.x 等于 0.5, 我们将其记为 a),参考分辨率的高则是实际分辨率的高的0.5倍(此时 screenSize.y / m_ReferenceResolution.y 等于 2, 我们将其记为 b),并且设插值比例(m_MatchWidthOrHeight, 我们将其记为 t)为 0.5,那么如果直接进行线性插值(设要求解的缩放值为 s),则有:

s = ( 1 − t ) ∗ a + t ∗ b    ⟹    s = ( 1 − 0.5 ) ∗ 0.5 + 0.5 ∗ 2 = 1.25 \begin{aligned} & s = (1 - t) * a + t * b \implies \\ & s = (1 - 0.5) * 0.5 + 0.5 * 2 = 1.25 \end{aligned} ​s=(1−t)∗a+t∗b⟹s=(1−0.5)∗0.5+0.5∗2=1.25​

如果进行对数空间插值的话(对数基底设为 2),则有:

l o g 2 a = l o g 2 0.5 = − 1 l o g 2 b = l o g 2 2 = 1 l o g 2 s = ( 1 − t ) ∗ l o g 2 a + t ∗ l o g 2 b    ⟹    l o g 2 s = ( 1 − 0.5 ) ∗ ( − 1 ) + 0.5 ∗ 1 = 0    ⟹    s = 2 l o g 2 s = 2 0 = 1 \begin{aligned} & log_2{a} = log_2{0.5} = -1 \\ & log_2{b} = log_2{2} = 1 \\ & log_2{s} = (1 - t) * log_2{a} + t * log_2{b} \implies \\ & log_2{s} = (1 - 0.5) * (-1) + 0.5 * 1 = 0 \implies \\ & s = 2 ^ {log_2{s}} = 2 ^ 0 = 1 \end{aligned} ​log2​a=log2​0.5=−1log2​b=log2​2=1log2​s=(1−t)∗log2​a+t∗log2​b⟹log2​s=(1−0.5)∗(−1)+0.5∗1=0⟹s=2log2​s=20=1​

关于对数空间插值的原理,我是这么理解的:

实际上而言,对于具体给定的 a 和 b, 我们要插值的并不是 a, b 本身,而是他们所代表的"缩放程度",当 a = 0.5 时,其代表的是缩小一倍,即 a = 2 − 1 a = 2 ^ {-1} a=2−1,而 b = 2 时,其代表的是放大一倍,即 b = 2 1 b = 2 ^ {1} b=21,一般的有:

a = 2 a ′ b = 2 b ′ s = 2 ( 1 − t ) ∗ a ′ + t ∗ b ′ \begin{aligned} & a = 2 ^ {a'} \\ & b = 2 ^ {b'} \\ & s = 2 ^ {(1 - t) * a' + t * b'} \end{aligned} ​a=2a′b=2b′s=2(1−t)∗a′+t∗b′​

将上式翻译一下便是之前的示例代码了.

实际上,上述的计算过程是可以简化的,延续上面的等式,我们有:

a = 2 a ′ b = 2 b ′ s = 2 ( 1 − t ) ∗ a ′ + t ∗ b ′    ⟹    a ′ = l o g 2 a b ′ = l o g 2 b s = 2 ( 1 − t ) ∗ l o g 2 a + t ∗ l o g 2 b = 2 ( 1 − t ) ∗ l o g 2 a ∗ 2 t ∗ l o g 2 b = ( 2 l o g 2 a ) 1 − t ∗ ( 2 l o g 2 b ) t = a 1 − t ∗ b t \begin{aligned} & a = 2 ^ {a'} \\ & b = 2 ^ {b'} \\ & s = 2 ^ {(1 - t) * a' + t * b'} \\ & \implies \\ & a' = log_2{a} \\ & b' = log_2{b} \\ s & = 2 ^ {(1 - t) * log_2{a} + t * log_2{b}} \\ & = 2 ^ {(1 - t) * log_2{a} } * 2 ^ {t * log_2{b}} \\ & = (2 ^ {log_2{a}})^{1 - t} * (2 ^ {log_2{b}}) ^ {t} \\ & = a ^ {1 - t} * b ^ {t} \end{aligned} s​a=2a′b=2b′s=2(1−t)∗a′+t∗b′⟹a′=log2​ab′=log2​b=2(1−t)∗log2​a+t∗log2​b=2(1−t)∗log2​a∗2t∗log2​b=(2log2​a)1−t∗(2log2​b)t=a1−t∗bt​

相关代码大概是这个样子:

scaleFactor = Mathf.Pow(screenSize.x / m_ReferenceResolution.x, 1 - m_MatchWidthOrHeight) * Mathf.Pow(screenSize.y / m_ReferenceResolution.y, m_MatchWidthOrHeight);

简单的 profile 显示,简化过的代码比原始代码快 35% 左右,当然,可读性上也更差了一些~

番外

如果需要在 Lua 脚本中进行 profile,很多朋友可能就直接选择就地编码了,但实际上,我们可以进一步封装相关操作,下面是一个简单的实现:

-- simple profile implementationlocal profile_infos = {}local function on_profile_end_default(profile_info)print("[Profile]Profile elapsed time : " .. profile_info.elapsed .. "s(" .. profile_info.elapsed * 1000 .. "ms)")
endfunction _G.ProfileStart(start_callback)local profile_info = { start = os.clock() }table.insert(profile_infos, profile_info)if start_callback thenstart_callback(profile_info)end
endfunction _G.ProfileEnd(end_callback)end_callback = end_callback or on_profile_end_defaultlocal profile_info = profile_infos[#profile_infos]if profile_info thenprofile_info.elapsed = os.clock() - profile_info.startif end_callback thenend_callback(profile_info)endtable.remove(profile_infos)elseprint("[Profile]Incorrect profile info, seems profile start and profile end do not match ...")end
end

使用时直接在相关代码块中添加 ProfileStart 和 ProfileEnd 即可(假设代码可以访问到 _G):

ProfileStart()// logic to profile here ...ProfileEnd()

编程小知识之 CanvasScaler 的一点知识相关推荐

  1. c语言代码游戏跳一跳,微信小程序《跳一跳》游戏里的编程小知识,你知道吗?...

    微信小程序<跳一跳>游戏里的编程小知识,你知道吗? 今日你跳了吗? 玩过的朋友都知道,跳一跳里的游戏操作非常简单,就用手指按住屏幕按住施放进行跳跃,整个游戏是个人都可以很轻松玩起来! 游戏 ...

  2. C 编程语言多少数学知识,编程,需要多少英语,数学知识?初中毕业直接修编程,会不会太快。学不进?...

    编程,需要多少英语,数学知识?初中毕业直接修编程,会不会太快.学不进?以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 编 ...

  3. python编程中常用的12种基础知识总结

    python编程中常用的12种基础知识总结:正则表达式替换,遍历目录方法,列表按列排序.去重,字典排序,字典.列表.字符串互转,时间对象操作,命令行参数解析(getopt),print 格式化输出,进 ...

  4. linux网络编程(一)网络基础传输知识

    linux网络编程(一)网络传输基础知识 一.什么是协议? 二.使用步骤 典型协议 2.网络应用程序设计模式 C/S模式 B/S模式 优缺点 3.分层模型 4.TCP/IP四层模型 通信过程 5.协议 ...

  5. python编程基础知识点总结_【转载】Python编程中常用的12种基础知识总结

    Python编程中常用的12种基础知识总结:正则表达式替换,遍历目录方法,列表按列排序.去重,字典排序,字典.列表.字符串互转,时间对象操作,命令行参数解析(getopt),print 格式化输出,进 ...

  6. Python 编程中常用的12种基础知识总结

    Python 编程中常用的12 种基础知识总结:正则表达式替换,遍历目录方法,列表按列排序.去重,字典排序,字典.列表.字符串互转,时间对象操作,命令行参数解析(getopt),print 格式化输出 ...

  7. 数控机床手动编程能否用计算机验证,数控技术基础知识

    数控技术基础知识 数控技术和数控装备是制造工业现代化的重要基础.下面是YJBYS小编整理的数控技术基础知识,希望对你有帮助! 一.数控机床编程的方法 数控机床程序编制的方法有三种:即手工编程.自动编程 ...

  8. jQuery小测试系列之jQuery基础知识

    日期:2012-4-17  来源:GBin1.com 这是jQuery小测试系列第一部分:基础知识. 来源:jQuery小测试系列之jQuery基础知识

  9. 你只管打开清华小姐姐总结的Python学习知识手册 ,剩下的交给「卧槽」

    这是之前入门学习Python时候的学习资料,非常全面,从Python基础.到web开发.数据分析.机器学习.深度学习.金融量化统统都有,该手册是HTML版本,左侧是目录,可以点击,右侧是对目录知识点的 ...

最新文章

  1. c++重载(以运算符重载为主)
  2. TSC打印机编程C#
  3. semantic computing Cognitive linguistics Distributed intelligence
  4. 10、HTML的基本结构
  5. 台积电5nm来了!谁会是第一个吃螃蟹的人
  6. libvirt-virsh命令
  7. JavaScript(二)数据类型
  8. C++程序设计方法3:数组下标运算符重载
  9. 黑客编程学习_1.黑客编程入门
  10. 浪涌保护器ant120_菲尼克斯浪涌保护器
  11. acm:C语言程序设计:求圆柱的体积等,去除小数点后两位最后一位的四舍五入
  12. 大作家超级写作软件_3种对作家有用的Atom文本编辑器软件包
  13. 推荐凸优化经典书籍,来自boyd
  14. php直播平台原理,php直播平台源码的直播带货平台有何技巧,别说没告诉你
  15. 解决了sql server 用户 sa 登录失败的问题
  16. 用scratch实现网上“超人训练”游戏
  17. ckeditor5-vue自定义图片上传函数
  18. 内存数据库-H2简介与实践
  19. uniapp ios原生插件开发之插件包格式(package.json)
  20. 阿德莱德计算机专业offer,2020年阿德莱德大学录取offer介绍及offer多久到

热门文章

  1. 你还在淘宝购物吗???那你就OUT了!!
  2. 提权,远控生成器,查看浏览器缓存,xp密码破解
  3. 游戏中自动攻击最近目标、二次连击和群体攻击的大体代码思路
  4. enhance io参数配置方法及其含义
  5. mysql随机生成中文地址_MySQL随机生成电话号码,邮箱和中文姓名sql
  6. android 图形旋转
  7. openfile存储服务器及oracle体系架构简介
  8. redis Autocomplete 做简单的“按键精灵”
  9. 转:2010 Web前端技术趋势及总结
  10. 港股将迎“最牛十元店“,名创优品能借IPO突围?