Unity 图形 - 摄像机

1. Camera

摄像机定义了如何将场景显示到屏幕上。

摄像机位置定义了视口的位置,forward(Z)轴定义了视口方向,upward(Y)定义了上方。Camera component同时还定义了视口的尺寸和形状。

1.1 透视和正交

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CVj5Amrx-1581516550394)(https://docs.unity3d.com/uploads/Main/CameraPerspectiveAndOrtho.jpg)]

Unity的摄像机支持正交视图,实现跟人眼一样近大远小的效果。有时我们希望不要近大远小的效果,比如渲染战略地图的摄像机,这叫做正交视图。透视和正交视图模式可以在摄像机组件上选择。透视和正交其实是摄像机投影的两种模式(矩阵)。

1.2 视口区域形状

摄像机需要定义可视区域,由垂直于摄像机Z轴的平面构成,该平面有2个,分别是最近和最远可视距离,小于最近距离,或大于最远距离的对象将不被显然。由这两个距离确定的平面叫做近裁剪平面(near plan)和远裁剪平面(far plan)。

没有透视时,物体的大小跟到摄像机的距离没有关系,所以远/近裁剪平面也是2个一样大小的矩形。

有透视时,物体距离越远就越小,意味着距离越远,可以看到的场景范围就越大。可视区域就变成了从摄像机位置的点开始,到远裁剪面的锥体。当然因为可视区域并不是从摄像机位置开始的,而是还要靠前一些(near plan),所以是一个平顶的锥体。这个平顶锥体,学术上我们叫Frustum(视锥体)。视锥体由宽高比(aspect ratio),和视口范围(field of view FOV上下裁剪面的角度)来定义。根据FOV和Znear确定高度,根据高度和宽高比确定宽度。

1.3 背景

再室内场景,摄像机完全在场景的结构内部,摄像机显示没什么问题。相对的当在室外时,除了我们看到的景物,大部分区域(包括天空)都是单色的(Camera.background设置的颜色),着肯定不是我们要的效果。解决这个问题我们用SkyBox(天空盒)。天空盒就是一个立方体的盒子,永远跟随摄像机移动(或者说摄像机永远在盒子的中心点),在盒子向里的面,贴上天空的图案,这样,无论怎样移动,旋转摄像机,都能看到天空。天空盒距离摄像机假定是无穷远,所以它在所有对象的后面,也是在最后渲染。当然天空盒的贴图,不仅包含天空,还由地平线以下的部分,可以由远山等,用来表达部分场景内容。天空盒也可以用来实现在太空中,会水中这样的环境,相应的天球盒的图(是一张立方体贴图CubeMap)。

通过Window>Rendering>Lighting Settings打开光照设置窗口,来设置skybox属性。

2. 使用多个摄像机

除了使用一个摄像机来渲染场景,我们可以用多个摄像机对场景进行渲染,比如在多个机位之间切换,或渲染小地图。

2.1 多个摄像机切换

切换时,只需要将当前要渲染的摄像机激活,并关闭其它摄像机:Camera.enabled

using UnityEngine;

public class ExampleScript : MonoBehaviour {
    public Camera firstPersonCamera;
    public Camera overheadCamera;

    // Call this function to disable __FPS__ camera,
    // and enable overhead camera.
    public void ShowOverheadView() {
        firstPersonCamera.enabled = false;
        overheadCamera.enabled = true;
    }
    
    // Call this function to enable FPS camera,
    // and disable overhead camera.
    public void ShowFirstPersonView() {
        firstPersonCamera.enabled = true;
        overheadCamera.enabled = false;
    }
}

2.2 全屏渲染+部分屏幕区域渲染

该用法典型的是一个玩家视口摄像机,用来渲染玩家看到的东西,同时屏幕特定区域渲染小地图。

首先我们需要2个摄像机,通过修改参数Depth来确定渲染顺序,越小的先渲染,同时将第二个渲染的摄像机的ClearFlags 设置为 Don’t Clear。

因为我们希望第二个摄像机在屏幕的部分区域显示,所以我们设置要设置Viewport Rect参数。比如我们要在左下角显示,则x,y=0,0,w,h=0.1,0.1,表示在左下角10%屏幕区域显示。

Viewport:摄像机的屏幕区域Viewport rect 被归一化到0-1之间。左下角是0,0点,右上角是1,1点。

Depth:depth决定了摄像机的渲染顺序,越小的则先渲染。

ClearFlags:摄像机渲染时,可以将整个屏幕清理成一个设置的单色,或天空盒,也可以设置为Don’t Clear表示保留之前摄像机渲染的图像。

3. 使用物理摄像机 Using Physical camera

摄像机组件的Physical Camera选项,让我们可以用来模拟真实世界中的摄像机。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ev29yQkk-1581516550396)(https://docs.unity3d.com/uploads/Main/InspectorCameraPhysCam.png)]

有两个重要的属性分别是 Focal Length 和 Sensor Size:

  • Focal Length 焦距。感光器和镜头之间的距离。着决定了垂直的FOV。焦距越小,FOV越大。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-th7qAEAF-1581516550396)(https://docs.unity3d.com/uploads/Main/PhysCamAttributes.png)]

  • Sensor Size 感光元件尺寸。这决定了摄像机的宽高比。可以在Sensor Type下拉列表中选择根据显示世界摄像机类型对应而预定义的尺寸,也可以自己设置。如果感光器宽高比与渲染视口的宽高比不一致,则需要通过Gate Fit 下拉列表选择匹配方式。

Lens Shifts

Lens Shifts 控制将头在水平或垂直上进行相对于感光器的偏移。这样可以改变焦点,重新定位渲染目标在底片上的位置,可能会带来一点失真(可能也是我们想要的效果)。

该技术在摄影技术中很常见。例如,如果想要捕捉一个很高的建筑,则需要旋转摄像机。但那会导致失真,让平行线不再平行。如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oKCWdtsX-1581516550397)(https://docs.unity3d.com/uploads/Main/LensShift_VRot.png)]

如果该为提高焦点,也可以捕捉完整的建筑,但是平行线不会变形,如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YtKq9CnA-1581516550397)(https://docs.unity3d.com/uploads/Main/LensShift_VShift.png)]

同样的可以用水平偏移来在水平上捕捉目标而不失真。下面两张图,第一张通过旋转摄像机捕捉,有失真,第二张通过偏移焦点,没有失真

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-boKn310a-1581516550398)(https://docs.unity3d.com/uploads/Main/LensShift_HRot.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5P9Sx2Q-1581516550398)(https://docs.unity3d.com/uploads/Main/LensShift_HShift.png)]

Gate Fit

在物理摄像机中,有2个"gates":

  • 游戏视口(屏幕),由宽高比的分辨率决定,也叫“resolution gate” 屏幕视口
  • 摄像机视口,有Sensor Size决定,也叫“file gate” 胶片视口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p0Vn0LB5-1581516550399)(https://docs.unity3d.com/uploads/Main/GateFitGates.png)]

当这两个视口的比例不一致时,Unity会将屏幕视口匹配到胶片视口,并提供了多种适配模式,但是只有3种结果:

  • Cropping : 裁剪,当胶片尺寸在经过适配后,依然超过屏幕尺寸,游戏视图仅渲染摄像机视口内的部分,多余的将被裁剪掉。
  • Over scanning:当胶片尺寸在经过适配后,依然超过屏幕尺寸,游戏视图仍然会渲染在摄像机视口之外的部分。
  • Stretching:拉伸,游戏视口根据摄像机视口进行拉伸,并渲染整个摄像机视口内的场景。

选中摄像机,查看其视锥体。屏幕视口就是其远裁剪面,摄像机视口则是第二个矩形:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtbPS57v-1581516550399)(https://docs.unity3d.com/uploads/Main/GateFitUI.png)]

上图,矩形A是屏幕视口,B是摄像机视口。

Gate Fit Modes

Gate Fit Modes 决定了Unity如何改变屏幕视口的尺寸,同时也改变了视锥体。摄像机视口不会改变。

译者:这部分,理解不深刻,总之,都是按照游戏视口的尺寸渲染。这部分主要就是如何根据摄像机宽高比调整视口宽高比,底片多出来的裁剪掉,游戏视口大就都显示。

Vertical

当 Gate Fit Modes 设置为 Vertical 时,Unity根据Y轴(高度)来进行适配。所以改变感光器的宽度(Sensor Size.X)不会对渲染结果产生影响。

如果感光器的宽高比大于游戏视口的宽高比,Unity会将两边的裁剪掉:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nHAe48go-1581516550400)(https://docs.unity3d.com/uploads/Main/GateFitV_600x900_16mm.png)]

Gate Fit set to Vertical: Resolution gate aspect ratio is 0.66:1 (600 x 900 px). Film gate aspect ratio is 1.37:1 (16mm). The red areas indicate where Unity crops the image in the Game view.

如果感光器宽高比小于游戏视口宽高比,Unity会将感光器外的也渲染出来:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f54uG5pq-1581516550400)(https://docs.unity3d.com/uploads/Main/GateFitV_16-9_16mm.png)]

Gate Fit set to Vertical: Resolution gate aspect ratio is 16:9. Film gate aspect ratio is 1.37:1 (16mm). The green areas indicate where Unity overscans the image in the Game view.

所以渲染的内容,依然是以游戏视口为基准,但是根据摄像机视口调整游戏视口尺寸。

Horizontal

当 Gate Fit Modes 设置为 Horizontal 时,Unity根据X轴(宽度)来进行适配。所以改变感光器的高度(Sensor Size.Y)不会对渲染结果产生影响。

如果感光器宽高比大于游戏视口的,Unity依然会渲染垂直上超出感光器范围的部分:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ILu9jTU-1581516550401)(https://docs.unity3d.com/uploads/Main/GateFitH_600x900_16mm.png)]

Gate Fit is set to Horizontal: Resolution gate aspect ratio is 0.66:1 (600 x 900 px). Film gate aspect ratio is 1.37:1 (16mm). The green areas indicate where Unity overscans the image in the Game view.

如果感光器宽高比小于游戏视口的,Unity依然会在垂直上超出视口范围的部分裁剪掉:(Unity文档里的图贴错了,这里也不再引用了,免得造成误解。

例子:Gate Fit set to Horizontal: Resolution gate aspect ratio is 16:9. Film gate aspect ratio is 1.37:1 (16mm). The red areas indicate where Unity crops the image in the Game view.

None

当设置为None,Unity在X,Y轴上分别进行匹配,将渲染的图片适配(拉伸)在游戏视口显示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ij9RCy6H-1581516550401)(https://docs.unity3d.com/uploads/Main/GateFitF_16mm.png)]

摄像机使用的 film gate 宽高比=1.37:1 (16mm), 并水平拉伸图像适配游戏视口宽高比= 16:9 (左图),如果游戏视口宽高比= 0.66:1 则进行垂直拉伸(右图)

Fill and Overscan

当Fit Modes设置为Fill 或 Overscan,Unity会根据游戏视口和摄像机视口的宽高比,自动执行垂直或水平的匹配。

  • Fill 根据摄像机视口的较短的轴改变游戏视口。多余部分裁剪掉。
  • Overscan 根据摄像机视口较长的轴改变游戏视口,会渲染更多内容

4. 使用技巧 Camera Tricks

4.1 理解视锥体 Understanding the View Frustum

视锥体像一个被截掉顶部的锥体,只有该锥体范围内的对象才能被看到和渲染。

假设我们有一根直的木棒,一段朝向摄像机。如果木棒在摄像机的中间,垂直于摄像机镜头,则我们只能看到一个圆点,其它部分被自身挡住了。如果向上移动一点,则底下的部分能够看到,这时如果向上旋转,依然能隐藏掉其他部分,只剩一个圆点。这时在木棒的两个端点同时继续向上移动,则最终该圆点会到达屏幕顶端,在该点,所有在该木棒之上的对象都不可见。(上裁剪面)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0ojoInc-1581516550401)(https://docs.unity3d.com/uploads/Main/Rods.png)]

可以在水平或垂直,或同时水平垂直,上下左右移动并旋转木棒,隐藏木棒使之成为一个点的旋转角度,仅取决于在两个轴上,到屏幕中心点的距离。

该思维实验表达的意思是我们在摄像机影像中任意点定义为空间中的一条直线,而渲染为一个点。如果对这些直线,向摄像机位置延长,最终会交汇到一个点,这个点也是摄像机的位置。在该位置,屏幕顶部中心和底部中心两条直线的夹角,被称为事业(FOV)。

沿着屏幕外沿的对应的所有直线,在空间中围成一个锥体,在该锥体内的对象是可见的。同时还有两个限制,即两个平行于摄像机XY的近裁剪面和远裁剪面,在这两个平面之外的对象是不可见的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZlDWmOB-1581516550402)(https://docs.unity3d.com/uploads/Main/ViewFrustum.png)]

4.2 给定距离上的视锥体截面尺寸

给定距离上的视锥体截面是一个屏幕可见的矩形区域。有时需要根据距离求矩形区域,或根据矩形区域来计算距离。例如,我们希望无论怎么拉远或拉近,对象都在屏幕内。

计算指定距离上视锥体的高度公式:

float frustumHeight = 2.0f * distance * Mathf.Tan(camera.filedOfView*0.5f*Mathf.Deg2Rad);

根据给定视锥体高度求距离:

float distance = frustumHeight * 0.5f / Mathf.Tan(camera.fieldView * 0.5f * Mathf.Deg2Rad);

当知道高度和距离时,可以求出FOV:

camera.fieldOfView = 2.0f * Mathf.Atan(frustumHeight * 0.5f / distance) * Mathf.Rad2Deg;

知道宽或高时,可以求出另一个:

float frustumWidth = frustumHeight / camera.aspect;
float frustumHeight = frustumWidth / camera.aspect;

4.3 摄像机射线

Unity中,用 Ray 对象来定义射线。摄像机提供了2个得到设想的接口:ScreenPointToRayViewportPointToRay。它们的不同是ScreenPointToRay需要的是屏幕像素坐标为参数,ViewporPointToRay是以归一化(0-1)坐标(0,0为左下角,1,1为右上角)。需要注意的是,射线的源点在摄像机的近裁剪面上,而不是摄像机位置。

射线检测

摄像机射线最常用来向场景投射射线,检测与哪个碰撞体发生碰撞,如果发生碰撞,返回结RaycastHit对象,包含了碰撞对象,以及碰撞点。这种基于屏幕的射线检测非常有用,例如检测鼠标所在的位置的对象(鼠标拾取对象)的代码:

RaycastHit hit;
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out hit))
{
	Transform objectHit = hit.transform;
}

沿着射线移动摄像机

例如,可能允许用户用鼠标选择一个对象,然后摄像机向该对象移动,代码如下:

Ray ray = camera.ScreenPointToRay(Input.mousePosition);

float zoomDistance = zoomSpeed * Input.GetAxis("Vertical") * Time.deltaTime;

camera.transform.Translate(ray.direction * zoomDistance, Space.World);

4.4 使用倾斜的摄像机

默认视锥体是以摄像机中心线对称的,但那不是必须的。可以创建一个斜的视锥体,也就是说某一侧的裁剪面与中线的角度,比另一侧小(大)。可以获得一种在角度小的那一侧的对象距离更近的错觉。一个应用这个效果的例子是赛车游戏,压缩视锥体底部,让玩家看起来距离地面很近,有更强烈的速度感。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cHVTGxlg-1581516550402)(https://docs.unity3d.com/uploads/Main/ObliqueFrustum.png)]

实现该效果,需要使用物理摄像机,调整Lens Shifts的值来获取想要的效果,同时不会造成失真。

偏移镜头会改变对侧的视锥体角度。例如向上偏移,下面的视锥面与中线的角度会变小。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5z072qcP-1581516550403)(https://docs.unity3d.com/uploads/Main/ObliqueFrustum_LensShift.png)]

在脚本中进行控制

可以通过脚本修改投影矩阵来达到倾斜摄像机的目的:

void SetObliqueness(float horizObl, float vertObl)
{
	Matrix4x4 mat = Camera.main.projectionMatrix;
	mat[0,2] = horizObl;
	mat[1,2] = vertObl;
	Camera.main.projectionMatrix = mat;
}

horizObl和vertObl为正值,标识向上或向右偏移。偏移最大值为1或-1,表示对侧完全跟中心线重合了。

5. Occlusion Culling

遮挡剔除用于在渲染时,剔除对被其它对象遮挡,摄像机无法观察到的对象的渲染。由于很多时候,距离摄像机远的对象先被渲染,近的后渲染时就会在远的上渲染,这导致一个像素被绘制多次,而且之前的很多次,实际上都是不可见的,这就叫overdraw。

下图展示了没有frustum裁剪时,渲染了所有的对象:

第二幅图展示了视锥体裁剪后,被渲染的对象少了很多:

第三幅图,使用遮挡剔除后,只有被摄像机看到的对象渲染,少了很多

遮挡剔除的处理过程,是用一个虚拟的摄像机遍历场景,建立潜在可见对象集合的层级关系。运行时摄像机利用这些数据标识哪些对象可见,哪些不可见。利用这些数据,Unity可以确保仅将可见的对象送入渲染管线,降低DrawCall,提升效率。

遮挡数据由一些单元格组成,每个单元格是整个场景的包围体的细分,这些单元格构建成一颗二叉树。遮挡剔除用了2棵树,一棵是View Cells(静态对象),和一棵Target Cells(移动对象)。View Cells映射到定义可见静态对象的索引列表,从而为静态对象提供更精确的筛选结果。

创建对象时,需要在对象的尺寸和Cell的尺寸间取得一个好的平衡点。不能定义相对于对象太小的Cell,也不应该创建覆盖太多Cells的对象。有时,将一些大的对象拆分成小对象,可以提升裁剪效果。同样的,可以将一些小的在同一个Cell里的对象合并来降低DrawCall。

可以在Scene视图中选择"OverDraw"渲染模式来查看OverDraw,在Game视图的Stats信息面板中查看总的渲染三角形数量,以及渲染批次。下面2张图,对比了是否开启遮挡剔除的效果及数据

该图未开启遮挡剔除,注意Scene视图中的OverDraw,墙后面的大量的房间的渲染,造成了较高的OverDraw,这些房间在游戏视图中是看不到的。

应用遮挡剔除后,远处的房间不再渲染,OverDraw也低了很多。绘制的三角形数量和渲染批次也显著降低了。

5.1 建立遮挡剔除

首先,将关卡打散到合适的尺寸,这样有利于高效地被那些大的对象遮挡住,以剔除,如墙,大的建筑。例如如果一个房间里有很多家具,这些家具应该各自进行剔除,而不是合并到一起,虽然合并到一起降低了drawcall。

为了让对象参与遮挡剔除,需要将对象的tag设置为Occluder Static或者Occludee Static。可以选择多个对象一次进行设置。

Occludee Static:那些小的,会被其它对象挡住的对象,设置为 Occludee Static。

Occluder Static:大的物体,会遮挡其它对象的对象,设置为 Occluder Static 。

当使用了LOD group时,只有LOD0的对象会被当做遮挡对象。

5.2 遮挡剔除窗口

从 Window>Rendering>Occlusion Culling 打开遮挡剔除窗口。

在该窗口,可以处理遮挡Mesh和遮挡区域。

在Object面板,如果在场景里选择了Mesh,可以修改它们的遮挡类型:遮挡或者被遮挡,或者2者都设置

如果时选择了遮挡区域(Occlusion Area),则可以设置遮挡区域属性

注意:如果没有创建裁剪区域,那么裁剪将会被应用到整个场景。

注意:一旦摄像机不在裁剪区域内,遮挡剔除将会失效,所以设置遮挡区域完全覆盖摄像机的可达区域非常重要。但是过大的裁剪区域也需要更长的数据烘焙时间。

5.3 遮挡剔除烘焙 - bake

set default parameters 按钮,重置参数为Unity默认参数,以适应大多数情况。当然可以调整参数,适应我们的场景。

参数含义
Smallest Occluder执行遮挡剔除时,会遮挡其他对象的对象的最小尺寸。小于改尺寸将不会遮挡其它对象。选择一个合适的值来平衡裁剪精度和裁剪数据尺寸。
Smallest Hole该值表示摄像机视线能穿过的,2个几何体之间的最小距离。该值表示能通过该孔的对象的直径。
Backface ThresholdUnity通过背面测试来减少不必要的细节,来优化遮挡数据。默认值100非常简单地,不从数据集合中移除背面。将参数设置为5,可以根据可见背面的位置,大大降低数据量。例如,当摄像机在地形下面,或者实体对象内部,这些摄像机不会到达的区域。距离背面大于该参数的位置将不会生成遮挡数据。

Occlusion Culling - Visualization

窗口最下方,时Clear和Bake按钮。点击Bake按钮,开始生成遮挡数据。生成后,可以切到 Visualization面板,预览测试遮挡剔除。如果觉得结果不满意,点击Clear按钮,清除数据,然后调整参数,再重新烘焙。

场景中的所有对象都会影响包围体的大小,所以要尽量让它们都在场景的可见范围内。

烘焙完成后,可以看到蓝色的立方体,每个立方体是一个裁剪区域。

5.4 Occlusion Area 裁剪区域

为了对移动对象进行遮挡,需要创建 Occlusion Area ,并修改它的尺寸来适应移动对象能到达的区域。可以为一个空的对象添加 Occlusion Area 组件来创建。

创建完后,选择 Is View Volume 来遮挡移动对象。(移动对象与摄像机不在同一个遮挡区域内,不可见?)

创建完遮挡区域后,需要查看是如何将盒子拆分到Cells中的。要看遮挡区域是如何计算的,在 Occlusion Culling Preview Panel 中,选择 Edit,并选中View Volumes复选框。

测试效果

设置好遮挡后,可以在 Occlusion Culling Preview Panel 的 Visualize mode 中,选中 Occlusion Culling,在Scene窗口中移动摄像机来进行测试。

当移动摄像机时(无论是否在play模式),会看到一些对象被禁掉了。在这里,可以查找遮挡数据的错误。当你移动摄像机时,看到一个对象突然跳出来,说明有错误发生。当发生这种情况,可以改变分辨率,或者调整对象位置来解决。可以将摄像机移动到出问题的对象处,进一步检查问题。

数据生成后,视图中会有一些带颜色的立方体。蓝色的表示Target Volume(被遮挡对象)的Cell划分,白色的是View Volume(摄像机位置包围框)的Cell划分。如果参数设置正确,可以看到一些对象不被渲染,可能不在视锥体内,也可能是被遮挡剔除掉了。

如果你发现场景中没有任何对象被遮挡,那么打碎你的对象,让这些碎片完全包含在一个Cell内。

Occlusion Portals

为了创建可以在运行时打开和关闭的图元,使用 Occlusion Portals。

属性:

  • Open 阻挡的门是否是打开的
  • Center 遮挡区域的中心点。默认0,0,0在盒子的中心点
  • Size 遮挡区域的尺寸

6. Dynamic resolution

摄像机的动态分辨率参数允许在运行时动态改变分辨率,以改变render target,来降低GPU负载。如果游戏的帧率降低了,可以逐步降低分辨率来保持帧率。如果性能数据表明帧率的降低是由GPU瓶颈导致的,则会触发降低分辨率(自动的?这可高级了)。也可以手动的找到GPU负载比较高的部分,降低分辨率(比如某个后效)。逐步缩放分辨率,几乎无法发觉。

支持的平台

  • XBox One
  • PS4
  • Nintendo Switch
  • iOS
  • macOS and tvOS(metal only)
  • Android(Vulkan only)
  • Windows Standalone and UWP(DirectX 12 only)

对Render Target的影响 Impact on render targets

使用动态分辨率时,Unity不会重新分配render target。理论上Unity应该会缩放render target,但实际上,Unity是执行了一种扭曲,缩小render target仅使用原始render target的部分区域。Unity分配全尺寸的Render Target,然后再动态分辨率时,缩小或放大真正使用的区域。

缩放 Render Targets

开启动态分辨率后,render targets 会设置DynamicallyScalable 标识。可以设置该标识,告诉再动态分辨率时,是否对该render target进行处理。摄像机也有allowDynamicResolution 标识,当场景渲染比较简单,(仅摄像机用到RT),则可以用来设置摄像机的相关的RT的动态分辨率功能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8tN5SIth-1581516550409)(https://docs.unity3d.com/uploads/Main/DynamicResolution.png)]

MRT Buffers

当摄像机启用了Allow Dynamic Resolution,所有该摄像机相关的Render Targets都会被缩放。

控制缩放

可以通过ScalableBufferManager控制缩放,该类可以控制标识为DynamicallyScalable标识的RT进行宽高的动态缩放。

例如你的游戏运行再正常的帧率,但是在某些情况下,GPU效率降低了,比如例子增多,后效过于复杂。Unity的FrameTimingManager可以帮助发现CPU或GPU的效率降低。可以通过FrameTimingManager来计算出保持帧率需要的宽和高,然后应用该缩放来让帧率稳定(立即或逐步地)。当屏幕复杂度降低并且GPU效率稳定时,可以提升分辨率到GPU可以处理的程度。

例子

下面的例子演示了API的使用。这里需要将场景中摄像机的Allow Dynamic Resolution选项打开,还要把PlayerSettings>Player>Other Settings>Enable Frame Timing Stats 选项打开。

鼠标点击,或单手指点击屏幕,以 scaleWidthIncrement and scaleHeightIncrement来降低分辨率。2个手指触屏以同样方式增加分辨率。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.__UI__;

public class DynamicResolutionTest : MonoBehaviour
{
    public Text screenText;

    FrameTiming[] frameTimings = new FrameTiming[3];

    public float maxResolutionWidthScale = 1.0f;
    public float maxResolutionHeightScale = 1.0f;
    public float minResolutionWidthScale = 0.5f;
    public float minResolutionHeightScale = 0.5f;
    public float scaleWidthIncrement = 0.1f;
    public float scaleHeightIncrement = 0.1f;

    float m_widthScale = 1.0f;
    float m_heightScale = 1.0f;

    // Variables for dynamic resolution algorithm that persist across frames
    uint m_frameCount = 0;

    const uint kNumFrameTimings = 2;

    double m_gpuFrameTime;
    double m_cpuFrameTime;

    // Use this for initialization
    void Start()
    {
        int rezWidth = (int)Mathf.Ceil(ScalableBufferManager.widthScaleFactor * Screen.currentResolution.width);
        int rezHeight = (int)Mathf.Ceil(ScalableBufferManager.heightScaleFactor * Screen.currentResolution.height);
        screenText.text = string.Format("Scale: {0:F3}x{1:F3}\nResolution: {2}x{3}\n",
            m_widthScale,
            m_heightScale,
            rezWidth,
            rezHeight);
    }

    // Update is called once per frame
    void Update()
    {
        float oldWidthScale = m_widthScale;
        float oldHeightScale = m_heightScale;

        // One finger lowers the resolution
        if (Input.GetButtonDown("Fire1"))
        {
            m_heightScale = Mathf.Max(minResolutionHeightScale, m_heightScale - scaleHeightIncrement);
            m_widthScale = Mathf.Max(minResolutionWidthScale, m_widthScale - scaleWidthIncrement);
        }

        // Two fingers raises the resolution
        if (Input.GetButtonDown("Fire2"))
        {
            m_heightScale = Mathf.Min(maxResolutionHeightScale, m_heightScale + scaleHeightIncrement);
            m_widthScale = Mathf.Min(maxResolutionWidthScale, m_widthScale + scaleWidthIncrement);
        }

        if (m_widthScale != oldWidthScale || m_heightScale != oldHeightScale)
        {
            ScalableBufferManager.ResizeBuffers(m_widthScale, m_heightScale);
        }
        DetermineResolution();
        int rezWidth = (int)Mathf.Ceil(ScalableBufferManager.widthScaleFactor * Screen.currentResolution.width);
        int rezHeight = (int)Mathf.Ceil(ScalableBufferManager.heightScaleFactor * Screen.currentResolution.height);
        screenText.text = string.Format("Scale: {0:F3}x{1:F3}\nResolution: {2}x{3}\nScaleFactor: {4:F3}x{5:F3}\nGPU: {6:F3} CPU: {7:F3}",
            m_widthScale,
            m_heightScale,
            rezWidth,
            rezHeight,
            ScalableBufferManager.widthScaleFactor,
            ScalableBufferManager.heightScaleFactor,
            m_gpuFrameTime,
            m_cpuFrameTime);
    }

    // Estimate the next frame time and update the resolution scale if necessary.
    private void DetermineResolution()
    {
        ++m_frameCount;
        if (m_frameCount <= kNumFrameTimings)
        {
            return;
        }
        FrameTimingManager.CaptureFrameTimings();
        FrameTimingManager.GetLatestTimings(kNumFrameTimings, frameTimings);
        if (frameTimings.Length < kNumFrameTimings)
        {
            Debug.LogFormat("Skipping frame {0}, didn't get enough frame timings.",
                m_frameCount);

            return;
        }

        m_gpuFrameTime = (double)frameTimings[0].gpuFrameTime;
        m_cpuFrameTime = (double)frameTimings[0].cpuFrameTime;
    }
}

7. Cameras Reference

Camera

Camera负责将场景内容呈现给玩家。通过配置摄像机参数,可以让游戏画面独具特色。可以创建很多个摄像机,以特定的顺序进行渲染,渲染到屏幕任何地方。

不同的渲染管线,摄像机在Inspector窗口显示的属性也不一样:

属性:

  • Clear Flags 指示摄像机渲染完后,如何填充没被绘制过的区域
    • Skybox 用天空盒清理
    • Solid Color 以下面指定的 Background 颜色清理
    • Depth only 仅清理深度缓冲区
    • Don’t clear 不清理
  • Background 当摄像机渲染完成后,该颜色被填充到未被渲染的区域。
  • Culling Mask 指示该摄像机要渲染哪些层的对象。
  • Projection 投影方式
    • Perspective 透视投影
    • Orthographic 正交投影。延迟着色不支持,仅前向渲染支持
  • Size 当选择正交投影时,定义正交摄像机视口尺寸
  • FOV Axis 透视投影时,指定FOV是定义在哪个轴上
    • Horizontal 在水平轴上
    • Vertical 在垂直轴上
  • Field of view 透视投影时,设定摄像机在FOV Axis轴上的可视角度。
  • Physical Camera 选择该项,启用物理摄像机,并显示物理摄像机参数
    • Focal Length 焦距(毫米),感光元件与镜头之间的距离。该值越小,FOV越大。改变该值,Unity会自动更新FOV。
    • Sensor Type 指定预定义的真实世界中的摄像机类型,从下拉列表中选择。不同的类型,会自动设置预置的对应的Sensor Size参数。如果手动修改了Sensor Size,该值也会自动切换到Custom。
    • Sensor Size 设置感光元件的尺寸(毫米)。X:宽度;Y:高度。
    • Lens Shifts 设置镜片的偏移。该值需要乘以感光器尺寸。例如设置为x=0.5,则偏移尺寸的一半。可以用该参数修正当摄像机和对象之间有角度时造成的变形(例如保持平行线)。还可以通过该参数让视锥体倾斜。
      • X,Y 在水平,垂直方向上偏移。
    • Gate Fit 如何修改游戏视口宽高比来匹配感光器宽高比
    • Clipping Planes
      • near 近裁剪平面与摄像机位置的距离
      • far 远裁剪平面与摄像机位置的距离
    • Viewport Rect 指示该摄像机渲染到屏幕的哪个区域,用视口坐标表示(0,0左下角,1,1右上角)
      • X,Y 区域的左下角
      • W,H 区域的宽,高
    • Depth 摄像机绘制顺序。摄像机根据depth从小到大依次渲染
    • Rendering Path 配置使用什么方式渲染该摄像机
      • Use Graphics Settings 使用在Player Settings中的设置
      • Vertex Lit 顶点光照
      • Forward 前向渲染,每个对象每个材质渲染一次
      • Deferred Lighting 延迟着色。每个对象先不带光照渲染一次,最后再对对象进行光照渲染。正交摄像机该设置无效,会自动使用前向渲染。
    • Target Texture 可以指定渲染到贴图(Render Texture),而不再渲染到屏幕。
    • Occlusion Culling 开启遮挡剔除。参考遮挡剔除 Occlusion Culling
    • Allow HDR 开启高动态范围光照,参考 High Dynamic Range Rendering
    • Allow MSAA 开启多重采样抗锯齿
    • Allow Dynamic Resolution 开启动态分辨率,参考Dynamic Resolution
    • Target Disply 渲染到哪个显示器,1-8

细节

摄像机可以配置,脚本控制,或通过父子关系,实现各种效果。比如一个智力游戏,可能要保持摄像机固定来显示整个谜题场景。对第一人称射击类游戏,将摄像机挂到控制的角色身上,再角色眼睛位置。对竞速游戏,需要让摄像机正确的跟随车辆运动。

可以创建多个摄像机,并设置不同的Depth,这些摄像机从低Depth到高Depth依次渲染,也就是说,Depth2的摄像机,将覆盖到Depth为1的摄像机上面。可以调整 Viewport Rect来指定摄像机渲染再屏幕的特定区域,这可以用来实现小视口,例如导弹视口,小地图,后视镜,等。

Render Path

Unity支持不同的渲染路径。应该根据游戏类型和目标平台/硬件来选择合适的渲染路径。不同的渲染路径拥有不同的特性和效率特征,主要是光照和阴影。在Project Setting中设置渲染路径,之后可以在摄像机上覆盖选择新的。

参考rendering paths

Clear Flags

摄像机渲染的数据,包括颜色和深度,会保存下来。屏幕上没有被绘制过的地方是空的,默认会显示天空盒。当使用多个摄像机时,每个都会累加存储自己颜色和深度到缓冲区。任意摄像机渲染时,可以指定一个Clear Flags来清除缓冲区的不同区域。可以选择如下选项:

  • Skybox 默认选项,空白的部分将会显示该摄像机上挂的Skybox组件定义的天空盒,如果没有,则使用Window>Rendering>Lighting Settings中指定的天空盒。如果最终没有天空盒可以使用,则使用Background Color。创建天空盒,you can use this guide.
  • Solid color 屏幕空白区域将用该摄像机的Background Color 填充。
  • Depth only 仅清除深度数据。如果想绘制玩家的枪,但是不会插到环境中被遮挡,用Depth=0的摄像机渲染环境,Depth=1的摄像机单独渲染武器,并设置武器摄像机的Clear Flags = depth only。这会保留环境图形在屏幕上,但是丢弃了已有图形的3D深度。当绘制武器时,不透明的部分将完全覆盖在最上面,而无论武器与墙有多近,甚至事实上已经查到墙里了。

  • Don’t clear 该模式不进行清理,不同摄像机渲染的对象被涂抹到一起,通常不在游戏中使用,可能只在某些特殊shader效果使用。

在多数GPU上(尤其是移动端GPU),不清理屏幕可能会导致下一帧图像不确定。在一些系统上,屏幕可能包含上一帧的图像,纯黑色,或随机颜色。

Clip Planes

Near和Far Clip Plane决定了摄像机视口的开始和结束范围。平面垂直于摄像机方向向量,从摄像机位置开始计算。Near Plane 和 Far Plane 之间的被渲染。

远近裁剪面还定义了深度缓冲区的精度如何对应到场景。通常为了获得较好的深度精度,尽量远近裁剪面不要太远。

由远近裁剪面,以及由FOV定义的平面,共同组合成摄像机的视锥体。完全在视锥体外的对象不会被渲染,这叫视锥体裁剪,该裁剪与遮挡剔除无关。

为了提升执行效率,可能想要提前裁剪掉那些小物体。例如小石头,碎片,一定距离后太小可能就看不到了。可以将这些小对象放到特定的层,然后在脚本中,通过 Camera.layerCullDistances 设置该层的裁剪距离。

Culling Mask

Culling Mask用来选择该摄像机渲染哪些层下的对象。

归一化的视口矩形 Normalized Viewport Rectangles

Normalized Viewport Rectangles 用来指定当前摄像机渲染到屏幕的哪个区域。可以在屏幕右下角创建地图视口,或者在左上角显示导弹跟踪视口。可以设计实现某些特定的效果。

可以很容易的创建两个玩家分屏的游戏。创建好2个摄像机后,修改它们的H=0.5,玩家1的的Y=0,玩家2的Y=0.5:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yIdbwa2C-1581516550411)(https://docs.unity3d.com/uploads/Main/Camera-Viewport.jpg)]

正交视图

正交视图去掉了透视效果,尤其是对创建等距的2D游戏。

需要注意的是,雾效是用统一的方式渲染的,所以在正交视图下的渲染可能会不正确。因为雾效使用透视后的Z坐标作为深度进行计算。但是处于效率(该雾效的效率相对较高)考虑,还是会使用。

透视摄像机:

正交摄像机:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRa3w3bE-1581516550412)(https://docs.unity3d.com/uploads/Main/Camera-Ortho-FPS.jpg)]

Render Texture

指定该参数,摄像机将会最终渲染到一张特殊的纹理(Render Texture)上,可以被作为一张普通贴图在渲染时使用。例如创建竞技场的 大屏幕 ,或创建反射。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZB1vGojs-1581516550412)(https://docs.unity3d.com/uploads/Main/Camera-RenderTexture.jpg)]

Target Display

摄像机最多有8个显示目标选项来设置,控制渲染到8个显示器中的一个。这仅在PC,Mac,Linux下支持。当前选择的显示器在Game视窗显示。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值