09世界,UI与屏幕空间
09世界,UI与屏幕空间
这里主要记录一下Unity下这三个概念所表示的含义。因为经常会在这些概念之间混淆,但同时我们又会经常会获得这些空间里面的数值。例如点击输入给出的坐标在屏幕空间,而UI的AnchoredPosition算是UI空间,通常的游戏对象算是在世界空间下。
所以这里通过文章记录并理清这些关系。
在RectTransform中有简单提到RectTransform与Transform的关系。从类型上来说是一个简单的继承,但是有着对Rect概念的封装表达。而其中也有部分涉及到UI空间与世界空间的关系。
这里先简单的阐述一下这几个概念:
- Transform本质表示一个变换。这个变换相当于将一个点从父节点所形成空间变换到当前节点的变换矩阵。所有的坐标,转向都是这么变换而来,也可以认为这些概念被这么一个变换表示。通俗的概念来讲s,此时这个Transform的坐标,都是该点在世界空间下连续变化而来,可以认为是对象的世界坐标的一个承载。
- RectTransform继承于Transform。也就是RectTransform本身也是一种变化,拥有Transform所有的概念语义。但它需要额外描述一个二维矩形概念。于是扩展出了Anchor等相关概念数据结构。所以RectTransform本身也有着一个世界坐标数据结构,其描述了Pivot点所在的世界坐标。而基于Anchor的变换结构,则单独给出了一个坐标描述结构。
- 而屏幕空间,则存粹的由游戏窗口所描述。即游戏窗口大小所形成的一个空间概念。没有相对关系,就是由简单二维窗口所形成的以左下角为原点的一个空间。
简单来说这三个概念,以及我想描述的对象:
- 世界空间:游戏场景中基于世界坐标原点所形成的空间。这个空间的下对象的坐标表示,称之为世界坐标。
- UI空间:由Canvas所形成的一个坐标空间。其中坐标数值通过Anchor变换而来,这个空间下由Rect所描述的坐标称之为,UI空间坐标。
- 屏幕空间:在屏幕空间所形成的一个纯粹空间。其中点的坐标,称之为屏幕空间左边。
这里简单备注几点。
- 实际上来说空间坐标并不准确,一是因为这个忽略了转动,二是因为实际上不论Transform还是RectTransform都实际是父节点的变换,也就是说,都是局部空间表征,不是完全基于原点。
- 这里主要是想区分几个坐标概念。实际上主要是他们所在的坐标系,不太一样。所以向记录各个坐标系之间的变换联系。
屏幕空间
下图大致表示屏幕空间和UI空间的一个区别:

屏幕空间实际上就是游戏视窗实际像素大小所构成的一个空间。
- 其坐标系如图,以左下角为原点,右上角为窗口像素的最大值构成坐标系。如果窗口像素大小为$(1920,1080)$那么最右上角的坐标值就是$(1920,1080)$。
- 对于一个点击输入,其实际上都是给出点击点在该坐标系下的位置。例如实例屏幕正中间位置则为$(649.5,397.5)$。
- 即便鼠标超出整个视窗,依然会基于该坐标系给出数值。
- Editor下Game窗口的分辨率设置会影响该数值。设置为Free Aspect将会是屏幕实际所占像素大小尺寸。如果指定了分辨率,会修改为目标值数值。
而要获取游戏屏幕空间的大小,则需要通过对应渲染的Camera来获取。
|
|
Canvas结构
而要搞清楚世界空间和UI空间坐标系的联系,就要从Canvas来入手。它决定了这两者之间的一个关系方式。 总所周知,Canvas有三种模式,一般讲到canvas都会涉及到:
- ScreenSpace-Overlay:直接叠加在屏幕空间上。
- ScreeSpace-Camera:放在相机前面,叠加在屏幕空间上。
- WorldSpace:直接按照世界坐标来渲染。
对应的实际计算操作是如下的:
ScreenSpace-Overlay
此时Canvas会使用屏幕空间的大小作为自己的Width和Height。然后以世界坐标系原点铺开。 如下图:

也就是根节点的RectTransform直接放置在如图的世界坐标中。这个时候会有以下特点。
- 因为直接以屏幕空间为Width和Height,一个RectTransform的Positon是其Pivot对应点的世界坐标。而这个世界坐标,因为canvas放在世界坐标系原点的缘故,恰好也就是其屏幕空间坐标点。
- RectTransform的GetWorldCorners方法,获得其Rect四个点的世界坐标。也对应其屏幕空间坐标。
ScreeSpace-Camera
此时Canvas会要求选择一个摄像机。然后Canvas会按照一个距离值放置在相机前方渲染。然后Canvas会撑满整个视窗范围。
如下图:

为了保持Canvas的Width与Height跟屏幕空间大小一致。canvas自动调整了一个缩放值。使得当前对应的Rect世界坐标大小被放大到目标屏幕空间大小。这个缩放值自然跟距离相机的距离有关系。
- 这使得世界坐标跟屏幕空间坐标不再一致,但是可以通过缩放系数算出对应的屏幕空间坐标。(不过有直接方法可以得出屏幕空间坐标)。
- 另一方面是放在相机前渲染,与Overlay不太一样。如果有世界物体出现在视窗内,可以跟Canvas的绘制穿插。
WorldSpace
直接当作世界空间中的一个物体来渲染绘制。此时x,y,z语义完全跟世界坐标一致。而Width和Height则表明Rect的宽度和高度。此时其Width和Height单位大小,跟世界空间的单位长度一致。所有Canvas中的物体也只是世界物体Rect中的一部分。
CanvasScaler
另一个需要注意的就是CanvasScaler。该组件会对应调整Canvas的Rect大小。也就是通常的整体UI画布的缩放自适应组件。该组件只在Canvas为ScreenSpace-Overlay或者ScreeSpace-Camera模式下生效。因为这个时候Canvas大小才会跟屏幕大小挂钩。如果Canvas为WorldSpace,该组件模式会被锁定为World。
https://docs.unity.cn/cn/2022.1/Manual/script-CanvasScaler.html
为什么要缩放,这是因为UI设计的分辨率往往跟设备分辨率有一定出入。例如设计时按照$(1920,1080)$。但是对于手机,或者带鱼屏,其有可能是$(3440,1440)$。这时候最主要的式宽高比不再一致,所以要指定一个处理策略。
CanvasScaler主要包含3种方式:
- Constant Pixel Size 固定像素大小
- Scaler With Screen Size 根据屏幕尺寸缩放
- Constant Physics Size 固定物理大小
Constant Pixel Size
此时控件如下图:

在这个模式下,基本不存在对分辨率的控制。整个Canvas的大小依旧保持屏幕分辨率大小。
其中:
- Scale Factor: 缩放因子,控制着Screen大小和Canvas大小的比值。即相当于Canvas放大来填满整个屏幕空间。这个时候,RectTransform中Width和Height实际也会被放大。
- Reference Pixels Per Unit: 该值主要跟Texture2D资产资源中的Pixels Per Unit对应。资产中的Pixels Per Unit表示的是,Unity中的1单位相当于该图片中多少像素。当图片放在Image组件上时,Set Native Size会根据图片像素值与该参数计算出RectTransform的width和Height。Reference Pixels Per Unit则表示Texture2D中的1单位相当于Canvas下多少像素。简单来说,如果这两个值相同,会发现Set Native Size设置的大小就是图片资源原始大小。即,假设Pixels Per Unit为$V_{PPU}$,Reference Pixels Per Unit为$V_{RPPU}$最后设置大小为$s=s_{Texture2D}*V_{PPU}/V_{RPPU}$
Scaler With Screen Size
此时控件如下图:

这个模式下,会对Canvas的分辨率进行缩放。调整Scale和Width,Heifht。使其最后的Width和Height保持比例的缩放到一定大小。
这里准确说有两个新的参数
- Reference Resolution: 参考分辨率,结合Screen Match Mode来使用。
- Screen Match Mode:
屏幕适配方式
- Match Width Or Height: 完全按照宽高比例来进行适配,即根据给定值,按照参考分辨率与原始大小来进行缩放。0表示完全按照Width,1表示完全按照Height。
- Expand: 扩展方式,即Width和Height都保证在参考分辨率之上。
- Shrink: 收缩方式,即Width和Heifth都缩放到参考分辨率之下。
首先这些都是调整缩放,但是依然保持屏幕的宽高比,即AspectRatio是不变的。假设原先Canvas的大小为$(O_x,O_y)$,给定的参考分辨率为$(T_x,T_y)$。最后设置的分辨率为$(F_x,F_y)$ 先看Match Width Or Height。其会给定一个比例使用值,相当于参考宽或者高的程度,设为$R$。则
$$ \begin{bmatrix} F_x\\ F_y \end{bmatrix}= \lbrack(1-R)\frac{T_x}{O_x} + R\frac{T_y}{O_y}\rbrack \begin{bmatrix} O_x\\ O_y \end{bmatrix} $$即如果$R$为0,可以看到最终宽度$F_x$即参考分辨率宽度$T_x$,然后按照对应的比例缩放高度。 即如果$R$为1,可以看到最终高度$F_y$即参考分辨率高度$T_y$,然后按照对应的比例缩放宽度。
再看Expand和Shrink,对于Expand来说即如下
$$ R=\max({\frac{T_x}{O_x}},\frac{T_y}{O_y})\quad \begin{bmatrix} F_x\\ F_y \end{bmatrix}= R \begin{bmatrix} O_x\\ O_y \end{bmatrix} $$可以认为其保证最终的宽高一定在参考分辨率之上,并且较小的那边恰好是参考分辨率。
上面可以看到两种计算方式还是有所不同的。
Constant Physical Size
改模式按照物理单位来控制画布分钟UI元素的整体缩放。
三个空间的划分
某些情况下,我们都主要关注屏幕空间和UI控件空间的转化。例如点击事件与UI,因为这两个空间都是2维空间,且跟屏幕交互息息相关。而这种情况下,世界空间,实际上更像是一个中介。
而另一种情况下,我们会关注世界空间坐标转换到UI空间。例如场景中一个三维物体头顶有个血条。这个时候需要计算世界空间位置,到屏幕空间的坐标,然后再转化到UI控件空间中去。
这里就涉及到两个重要的问题:
- 一个是说,对于一个点来说,其在三个空间下的坐标是多少?如何转化
- 另一个是说,在某些情况下,转换计算是有意义的,在另一些情况下只是方便计算的中介。例如UI有其世界空间坐标,但并不意味着任何一个物体的世界空间都可以逆向回去。
弄清楚上面Canvas,CanvasScaler的各种关系之后,也是可以手动计算出对应坐标的。但是依赖组件众多,环境复杂,参考麻烦。例如在ScreenSpace-Overlay模式下,RectTransform的Position就是Pivot点的世界坐标,也是其屏幕坐标。但是ScreenSpace-Camera模式下,就要转换一层。好在Unity提供了很多遍历的方式来计算各种坐标。这里主要说一下各种有意义情况下的计算操作。
uGUI到屏幕空间
- ScreenSpace-Camera模式下
可以先获取世界坐标然后通过RectTransformUtility.WorldToScreenPoint来计算屏幕空间坐标。 Pivot点可以直接取RectTransform的Postion坐标,对于Rect四个角落则提供了GetWorldCorners方法可以取得对应的世界坐标值。
|
|
当在ScreenSpace-Overlay模式下时,Camera部分应该传null,因为这个时候并不依赖相机设置Canvas位置。当传null的时候实际上直接返回的是传入坐标的$x,y$值。而另一点是ScreenSpace-Overlay模式下时这些点的世界坐标,也是屏幕空间下的坐标位置。
当在ScreenSpace-Camera模式下时,Camera部分应该传渲染Canvas的Camera。其会将世界坐标,通过相机的投影变化一次。所以Overlay模式下,传入了相机对象的话就会导致计算错误。
对于RectTransform空间下任意点的坐标,则可以通过获取localToWorldMatrix转换到世界坐标后计算。
|
|
屏幕空间到UI空间
RectTransformUtility提供了ScreenPointToLocalPointInRectangle方法可以把一个屏幕空间点转换成RectTransform下的一个局部坐标
|
|
相同类似的方法还有
|
|
当在ScreenSpace-Overlay模式下时,Camera部分应该传null。
当在ScreenSpace-Camera模式下时,Camera部分应该传渲染Canvas的Camera。
世界对象坐标到屏幕空间
这种情况下,一般提供的是一个三维空间下物体的坐标。正常流程来说是,把这个物体根据相机的视窗矩阵,投影矩阵变换到屏幕空间下。
而这个可以通过WorldToScreenPoint来直接转换,这个过程是带上了相机的透视。更确定的方式,是通过相机的以下接口来进行操作:
|
|