Unity UGUI 原理篇(一):Canvas 渲染模式
目标
了解各种不同UI Render Mode
使用环境与版本
Window 7 Unity 5.2.5
Render Mode
UI渲染的方式,有以下三种
- Screen Space – Overlay:萤幕空间 – 覆盖
- Screen Space – Camera:萤幕空间 – 摄影机
- World Space:世界座标空间
Screen Space - Overlay
在此模式下不会参照到Camera,UI直接显示在任何图形之上
- 1.Pixel Perfect:可以使图像更清晰,但是有额外的性能开销,如果在有大量UI动画时,动画可能会不平顺
- 2.Sort Order:深度值,该值越高显示越前面
Screen Space - Camera
使用一个Camera作为参照,将UI平面放置在Camera前的一定距离,因为是参照Camera,如果萤幕大小、分辨率、Camera视锥改变时UI平面会自动调整大小。如果Scene中的物件(GameObject)比UI平面更靠近摄影机,就会遮挡到UI平面。
- 1.Render Camera:用于渲染的摄影机
- 2.Plane Distance:与Camera的距离
- 3.Sorting Layer:Canvas属于的排序层,在 Edit->Project Setting->Tags and Layers->Sorting Layers 进行新增,越下方的层显示越前面
- 4.Order in Layer:Canvas属于的排序层下的顺序,该值越高显示越前面
World Space
把物体当作世界座标中的平面(GameObject),也就是当作3D物件,显示3D UI
- 1.Event Camera:处理UI事件(Click、Drag)的Camera,所设定的Camera才能触发事件
参考资料 Unity – Manual: Canvas
Unity UGUI 原理篇(二):Canvas Scaler 缩放核心
目标
- 1.了解各种不同 UI Scale Mode
- 2.Pixels Per Unit 每单位像素
- 3.Canvas Scale Factor 缩放因子
- 4.Reference Resolution(预设屏幕大小)
- 5.Screen Size丶Canvas Size 之间的关系与算法
使用环境 与 版本
Window 7
Unity 5.2.4
Canvas Scaler
Canvas Scaler是Unity UI系统中,控制UI元素的总体大小和像素密度的Compoent,Canvas Scaler的缩放比例影响著Canvas下的元素,包含字体大小和图像边界。
Size
- Reference Resolution:预设萤幕大小
- Screen Size:目前萤幕大小
Canvas Size:Canvas Rect Transform 宽高
Scale Factor
用于缩放整个Canvas,而且调整Canvas Size与Screen Size一样
先来看一段官方代码
CanvasScaler.cs
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 (800600)、Canvas Size(800600),图片大小1倍
当Scale Factor为2时,Screen Size (800600)、Canvas Size(400300),图片大小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元素
- Scale Factor:透过此Factor缩放所有在此Canvas下的元素
- Reference Pixels Per Unit:
先介绍图片档设定中的Pixels Per Unit,意思是在这张Sprite中,世界座标中的一单位由几个Pixel组成
这边使用的测试图片为原始大小100*100 的图档,这边统称测试图
举例来说,场景中有一个11 Cube ,与一个Sprite图片指定为测试图,两者的Transform Scale 都为 1 当 Pixels Per Unit=100,每单位由 100 Pixel组成,Sprite 是100100 Pixels,那 Sprite 在世界座标中大小就会变成 100/100 * 100/100 = 1*1 Unit
(左:Cube ,右:Sprite)
当 Pixels Per Unit=10,每单位由 10 Pixel组成,Sprite 是100100 Pixels,那 Sprite 在世界座标中大小就会变成 100/10 * 100/10 = 1010 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.cs
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.cs
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设定为测试图,参数如下
■ 上表可以看出当数值改变时,图片预设大小也会改变
■ 由此可以推导出公式
UI大小 = 原图大小(Pixels) / (Pixels Per Unit / Reference Pixels Per Unit)
Scale With Screen Size: 透过设定的Reference Resolution(预设萤幕大小)来缩放
- Reference Resolution:预设萤幕大小
- Screen Match Mode:缩放模式
先来看官方的算法
CanvasScaler.cs
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为1280720,Screen Size为800600
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为1280720,Screen Size为800600
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为400300,Screen Size为200600 大小关系是
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 每英吋点数),进行缩放
- 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