Canvas Scaler
Size
UI Scale Mode
Sprite 在世界座标中大小 = 原图大小(Pixels) / Pixels Per Unit
scaleFactor = Mathf.Min(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
scaleFactor = Mathf.Max(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
Canvas Scaler是Unity UI系统中,控制UI元素的总体大小和像素密度的Compoent,Canvas Scaler的缩放比例影响著Canvas下的元素,包含字体大小和图像边界。
Size
Reference Resolution:预设萤幕大小
Screen Size:目前萤幕大小

Canvas Size:Canvas Rect Transform 宽高

Scale Factor
http://docs.unity3d.com/ScriptReference/Canvas-scaleFactor.html
用于缩放整个Canvas,而且调整Canvas Size与Screen Size一样
先来看一段官方程式码
CanvasScaler.csC#
protected void SetScaleFactor(float scaleFactor)
{
if (scaleFactor == m_PrevScaleFactor)
return;
m_Canvas.scaleFactor = scaleFactor;
m_PrevScaleFactor = scaleFactor;
}
程式码可以看出,Canvas Scaler 透过设定Canvas下的Scale Factor,缩放所有在此Canvas下的元素
当Scale Factor为1时,Screen Size (800*600)、Canvas Size(800*600),图片大小1倍


当Scale Factor为2时,Screen Size (800*600)、Canvas Size(400*300),图片大小2倍


在当Scale Factor为2时,Scale Factor 会调整整个Canvas 的大小,并让他的大小跟Screen Size一样,运算后Canvas Size放大2倍,刚好等于Screen Size,而底下的图片会放大2倍
UI Scale Mode
Constant Pixel Size
Canvas Size 始终等于 Screen Size,透过Scale Factor直接缩放所有UI元素

1. Scale Factor:透过此Factor缩放所有在此Canvas下的元素
2. Reference Pixels Per Unit:
先介绍图片档设定中的Pixels Per Unit,意思是在这张Sprite中,世界座标中的一单位由几个Pixel组成

这边使用的测试图片为原始大小100*100 的图档,这边统称测试图

举例来说,场景中有一个1*1 Cube ,与一个Sprite图片指定为测试图,两者的Transform Scale 都为 1
当 Pixels Per Unit=100,每单位由 100 Pixel组成,Sprite 是100*100 Pixels,那 Sprite 在世界座标中大小就会变成 100/100 * 100/100 = 1*1 Unit

(左:Cube ,右:Sprite)
当 Pixels Per Unit=10,每单位由 10 Pixel组成,Sprite 是100*100 Pixels,那 Sprite 在世界座标中大小就会变成 100/10 * 100/10 = 10*10 Unit

(左:Cube,右:Sprite)
结论:
■ Unity中一单位等于 100 Pixels
■ 由此可以推导出公式:
Sprite 在世界座标中大小 = 原图大小(Pixels) / Pixels Per Unit
让我们回到 Reference Pixels Per Unit,官方解释是,如果图片档有设定Pixels Per Unit,则会将Sprite 的 1 pixel 转换成 UI 中的 1 pixel
Image.csC#
public float pixelsPerUnit
{
get
{
float spritePixelsPerUnit = 100;
if (sprite)
spritePixelsPerUnit = sprite.pixelsPerUnit;
float referencePixelsPerUnit = 100;
if (canvas)
referencePixelsPerUnit = canvas.referencePixelsPerUnit;
return spritePixelsPerUnit / referencePixelsPerUnit;
}
}
上面官方程式码,可以看出 Image 透过 spritePixelsPerUnit / referencePixelsPerUnit 方式算出新的 pixelsPerUnit
Image.csC#
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 图片大小时,是把 宽高 / pixelsPerUnit
实作一下,建立一个Canvas参数如下

Canvas底下建立一个Image,Sprite设定为测试图,参数如下

这边做4种不同的测试:测试方式是修改 Reference Pixels Per Unit 与 Pixels Per Unit 后,点下 Image Compoent 的 Set Native Size来设定图片原始大小,藉此看到图片变化

■ 上表可以看出当数值改变时,图片预设大小也会改变
■ 由此可以推导出公式
UI大小 = 原图大小(Pixels) / (Pixels Per Unit / Reference Pixels Per Unit)
Scale With Screen Size:
透过设定的Reference Resolution(预设萤幕大小)来缩放

1. Reference Resolution:预设萤幕大小
2. Screen Match Mode:缩放模式
先来看官方的算法
CanvasScaler.csC#
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;
}
}
a. Expand(扩大):将Canvas Size进行宽或高扩大,让他高于Reference Resolution,计算如下
scaleFactor = Mathf.Min(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
意思是分别算出长宽 ,”Screen Size” 佔了 “Reference Resolution” 的比例,在求小的
举例来说,Reference Resolution为1280*720,Screen Size为800*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,最大程度的放大(显示所有元素)

b. Shrink(收缩):将Canvas Size进行宽或高收缩,让他低于Reference Resolution,计算如下
scaleFactor = Mathf.Max(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
意思是分别算出长宽 ,”Screen Size” 佔了 “Reference Resolution” 的比例,在求大的
举例来说,Reference Resolution为1280*720,Screen Size为800*600
ScaleFactor Width: 800/1280=0.625
ScaleFactor Height:600/720=0.83333
套用ScaleFactor公式:Canvas Size = Screen Size / Scale Factor
Canvas Width:800 / 0.83333 = 960
Canvas Height:600 / 0.83333 = 720
Canvas Size 为 960*720,宽度从1280变成了960,最大程度的缩小

c. 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*300,Screen Size为200*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,结果很明显,使用对数混合能更完美的修正大小
Constant Physical Size
透过硬体设备的Dpi(Dots Per Inch 每英吋点数),进行缩放

1. Physical Unit:使用的单位种类

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)
参考资料
■ Unity – Manual: Canvas
http://docs.unity3d.com/Manual/class-Canvas.html
原文作者: k79k06k02k
原文链接:
http://k79k06k02k.com/blog/24/unity/unity-ugui-%E5%8E%9F%E7%90%86%E7%AF%87%E4%BA%8C%EF%BC%9Acanvas-scaler-%E7%B8%AE%E6%94%BE%E6%A0%B8%E5%BF%83