Unity3D-UGUI原理篇Canvas Scaler 缩放原理

Posted 恬静的小魔龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D-UGUI原理篇Canvas Scaler 缩放原理相关的知识,希望对你有一定的参考价值。

推荐阅读

大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。

一、前言

这个系列文章,是为了记录分析UGUI的各种原理:

  • UGUI的渲染模式。
  • UGUI的缩放计算。
  • UGUI的锚点定位。
  • UGUI的层级渲染流程。
  • UGUI的事件触发原理。
  • UGUI的布局自动布局方式。

这是本系列文章的第X篇:

二、Canvas Scaler 缩放原理

我们接着来看UGUI的缩放核心组件Canvas Scaler的详细属性:

Canvas 缩放模式有三种:Constant Pixer Size、Scale With Screen Size、Constant Physical Size

下面就分别进行进行解析:

2-1、Constant Pixer Size —— 恒定像素

属性功能
Scale Factor缩放因子
Reference Pixels Per Uit单位面积像素数量,默认100.

这种模式下 UI以像素为大小,同样的像素在不同的分辨率下进行尺寸的自适应。

Scale Factor参数

首先,来看官方代码对于这个参数的设置:

protected void SetScaleFactor(float scaleFactor)
{
    if (scaleFactor == m_PrevScaleFactor)
        return;
 
    m_Canvas.scaleFactor = scaleFactor;
    m_PrevScaleFactor = scaleFactor;
}

用代码可以看出来,Canvas Scaler 透过设定Canvas下的Scale Factor参数来缩放所有在此Canvas下的UI元素的大小,下面就举个例子说明一下。

例子:
将Scale Factor设为1:

Canvas的长宽等于整成的屏幕的长宽(1960 X 1080),缩放是1倍。


图片也是正常大小。

将Scale Factor设为2:

Canvas的长宽是原来长宽的一半(980 X 540),然后缩放是2倍。

UI也是两倍大小:

当Scale Factor为2时,Scale Factor 会调整整个Canvas 的大小,并让他的大小跟屏幕大小一样,运算后Canvas Size放大2倍,刚好等于屏幕大小,而Canvas往下的子对象ImageI会放大2倍。

Reference Pixels Per Unit参数

这里,我们详细的介绍一下Constant Pixer Size模式下的Reference Pixels Per Unit属性跟图片的Pixels Per Unit的关系。

举个例子
导入项目一张图片,将图片的Pixels Per Unit设置为100:

场景中有一个标准大小的Cube和一个标准大小的Sprite,两者的缩放比例都为1,大小一致:

当图片的Pixels Per Unit为100,每单位由100Pixels组成,那么这个Sprite在世界坐标就是

图片大小 = (100/100) * (100/100)=1 Unit

然后,将图片的Pixels Per Unit设置为10:

图片大小 = (100/10) * (100/10)=10 Unit

结论:

  • Unity中一单位等于 100 Pixels
  • 公式:Sprite的大小 = 原图大小(Pixels)/ Pixels Per Unit

让我们回到 Reference Pixels Per Unit,官方解释是,如果图片档有设定Pixels Per Unit,则会将Sprite 的 1 pixel 转换成 UI 中的 1 pixel,让我们来看一下官方代码:

public float pixelsPerUnit
{
    get
    {
        float spritePixelsPerUnit = 100;
        if (sprite)
            spritePixelsPerUnit = sprite.pixelsPerUnit;
 
        float referencePixelsPerUnit = 100;
        if (canvas)
            referencePixelsPerUnit = canvas.referencePixelsPerUnit;
 
        return spritePixelsPerUnit / referencePixelsPerUnit;
    }
}
public override void SetNativeSize()
{
    if (overrideSprite != null)
    {
        float w = overrideSprite.rect.width / pixelsPerUnit;
        float h = overrideSprite.rect.height / pixelsPerUnit;
        rectTransform.anchorMax = rectTransform.anchorMin;
        rectTransform.sizeDelta = new Vector2(w, h);
        SetAllDirty();
    }
}

上面的代码,可以看出 Image 是通过 spritePixelsPerUnit / referencePixelsPerUnit 方式算出新的 pixelsPerUnit大小。

在设定 Image 图片大小时,是把 宽高 / Pixels Per Unit。

实测一下,建立一个Canvas参数如下:

Canvas下面新建一个Image,参数如下:

通过修改Canvas Scaler的Reference Pixels Per Unit参数 与 图片的Pixels Per Unit参数,来做4组测试,然后来看图片的不同变化:

Reference Pixels Per UnitPixels Per UnitImage Rect Transform(w*h)
100100100*100
200100200*200
100101000*1000
200102000*2000

■ 上表可以看出当数值改变时,图片预设大小也会改变

■ 由此可以推导出公式

UI大小 = 原图大小(Pixels) / (Pixels Per Unit / Reference Pixels Per Unit)

2-2、Scale With Screen Size —— 屏幕尺寸比例

属性功能
Referencee Resolution预设屏幕大小
Screen Match Mode缩放模式
Match宽高比

这种缩放模式下的UI位置是根据屏幕的分辨率和设置的宽高比来调整UI的位置的,通常做屏幕UI自适应的时候都需要调整到这个缩放模式下。

先来看官方的算法:

Vector2 screenSize = new Vector2(Screen.width, Screen.height);
 
float scaleFactor = 0;
switch (m_ScreenMatchMode)
{
    case ScreenMatchMode.MatchWidthOrHeight:
    {
        // 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);
        break;
    }
    case ScreenMatchMode.Expand:
    {
        scaleFactor = Mathf.Min(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
        break;
    }
    case ScreenMatchMode.Shrink:
    {
        scaleFactor = Mathf.Max(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
        break;
    }
}

下面,就详细的说一下Screen Match Mode缩放模式:

Expand(扩大):将Canvas Size进行宽或高扩大

根据官方代码,计算方式如下:

scaleFactor = Mathf.Min(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);

意思是分别算出长宽 ,也就是Screen Size在Reference Resolution的比例。

举例来说,Reference Resolution为1280 X 720,Screen Size为800 X 600

ScaleFactor Width: 800/1280=0.625
ScaleFactor Height:600/720=0.83333

套用ScaleFactor公式:

Canvas Size = Screen Size / Scale Factor
Canvas Width:800 / 0.625 = 1280
Canvas Height:600 / 0.625 = 960

Canvas Size 为 1280*960,高度从720变成了960,最大程度的放大(显示所有元素)

Shrink(收缩):将Canvas Size进行宽或高收缩

根据官方代码,计算方式如下:

scaleFactor = Mathf.Max(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);

意思是分别算出长宽 ,也就是Screen Size在Reference Resolution的比例。

举例来说,Reference Resolution为1280 X 720,Screen Size为800 X 600

ScaleFactor Width: 800/1280=0.625
ScaleFactor Height:600/720=0.83333

套用ScaleFactor公式:

Canvas Size = Screen Size / Scale Factor
Canvas Width:800 / 0.625 = 960
Canvas Height:600 / 0.83333 = 720

Canvas Size 为 960*720,宽度从1280变成了960,最大程度的缩小

Match Width or Height:根据Width或Height进行混合缩放

根据官方代码,计算方式如下:

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);

分别对ScaleFactor Width、Height取对数后,再进行平均混合,那为什麽不直接使用March对Width、Height进行混合呢??,让我们来比较一下

假设Reference Resolution为400 X 300,Screen Size为200 X 600 大小关系是

Reference Resolution Width 是 Screen Size Width的2倍
Reference Resolution Height 是 Screen Size 的0.5倍

看起来会像下图:

当March为0.5时,ScaleFactor应该要是 1 (拉平):

ScaleFactor Width: 200/400=0.5
ScaleFactor Height:600/300=2

一般混合:

ScaleFactor = March * ScaleFactor Width + March * ScaleFactorHeight
ScaleFactor = 0.5 * 0.5 + 0.5 * 2 = 1.25

对数混合:

logWidth:log2(0.5) = -1
logHeight:log2(2) = 1
logWeightedAverage:0
ScaleFactor:20 = 1

scaleFactor一般混合为1.25,对数混合为1,结果很明显,使用对数混合能更完美的修正大小。

2-3、Constant Physical Size —— 恒定尺寸


属性说明:

1、Physical Unit单位

单位类型单位与1英寸对比
Centimeters公分(cm、厘米)2.54
Millimeters公厘(mm,毫米)25.4
Inches英寸1
Points72
Picas皮卡6

2、Fallback Screen DPI:备用Dpi,当找不到设备Dpi时,使用此值
3、Default Sprite DPI:预设的图片Dpi

官方代码:

float currentDpi = Screen.dpi;
float dpi = (currentDpi == 0 ? m_FallbackScreenDPI : currentDpi);
float targetDPI = 1;
switch (m_PhysicalUnit)
{
    case Unit.Centimeters: targetDPI = 2.54f; break;
    case Unit.Millimeters: targetDPI = 25.4f; break;
    case Unit.Inches:      targetDPI =     1; break;
    case Unit.Points:      targetDPI =    72; break;
    case Unit.Picas:       targetDPI =     6; break;
}
 
SetScaleFactor(dpi / targetDPI);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit * targetDPI / m_DefaultSpriteDPI);

结论

■ ScaleFactor 为 “目前硬体dpi” 佔了 “目标单位” 的比例

■ ReferencePixelsPerUnit 要与目前的Dpi在运算求出新的值,再传入Canvas中求出大小,公式如下:

新的 Reference Pixels Per Unit = Reference Pixels Per Unit * Physical Unit / Default Sprite DPI

UI大小 = 原图大小(Pixels) / (Pixels Per Unit / 新的 Reference Pixels Per Unit)

以上是关于Unity3D-UGUI原理篇Canvas Scaler 缩放原理的主要内容,如果未能解决你的问题,请参考以下文章

Unity3D-UGUI原理篇RectTransform 组件详解

Unity3D-UGUI原理篇Auto Layout 自动布局

Unity3D-UGUI原理篇Event System Manager 事件与触发

Unity3D-UGUI原理篇使用 UnityEngine.Events 让程序更灵活稳定

Unity3D-UGUI系列Canvas 画布组件详解

Unity3D-UGUI应用篇使用UGUI实现层级菜单