前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编程小知识之 CanvasScaler 的一点知识

编程小知识之 CanvasScaler 的一点知识

作者头像
用户2615200
发布2019-06-14 20:45:21
5170
发布2019-06-14 20:45:21
举报

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1446298

本文简述了 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

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

代码语言:javascript
复制
// 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),则有:

log2a=log20.5=−1log2b=log22=1log2s=(1−t)∗log2a+t∗log2b  ⟹  log2s=(1−0.5)∗(−1)+0.5∗1=0  ⟹  s=2log2s=20=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−1a = 2 ^ {-1}a=2−1,而 b = 2 时,其代表的是放大一倍,即 b=21b = 2 ^ {1}b=21,一般的有:

a=2a′b=2b′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=2a′b=2b′s=2(1−t)∗a′+t∗b′  ⟹  a′=log2ab′=log2bs=2(1−t)∗log2a+t∗log2b=2(1−t)∗log2a∗2t∗log2b=(2log2a)1−t∗(2log2b)t=a1−t∗bt \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​

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

代码语言:javascript
复制
scaleFactor = Mathf.Pow(screenSize.x / m_ReferenceResolution.x, 1 - m_MatchWidthOrHeight) * Mathf.Pow(screenSize.y / m_ReferenceResolution.y, m_MatchWidthOrHeight);

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

番外

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

代码语言:javascript
复制
-- simple profile implementation

local profile_infos = {}

local function on_profile_end_default(profile_info)
    print("[Profile]Profile elapsed time : " .. profile_info.elapsed .. "s(" .. profile_info.elapsed * 1000 .. "ms)")
end

function _G.ProfileStart(start_callback)
    local profile_info = { start = os.clock() }
    table.insert(profile_infos, profile_info)
    if start_callback then
        start_callback(profile_info)
    end
end

function _G.ProfileEnd(end_callback)
    end_callback = end_callback or on_profile_end_default
    local profile_info = profile_infos[#profile_infos]
    if profile_info then
        profile_info.elapsed = os.clock() - profile_info.start
        if end_callback then
            end_callback(profile_info)
        end
        table.remove(profile_infos)
    else
        print("[Profile]Incorrect profile info, seems profile start and profile end do not match ...")
    end
end

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

代码语言:javascript
复制
ProfileStart()

// logic to profile here ...

ProfileEnd()
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年05月16日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 番外
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档