摄像机之间的管理、排序、3D坐标与平面坐标之间的转换等

3D坐标转换屏幕坐标

Unity的屏幕坐标系规定左下角是原点、X轴向右为正,Y轴向上为正。

Unity IMGUI坐标系规定左上角是原点,X轴向右为正,Y轴向下为正,如果我们想把转换的屏幕坐标用IMGUI在相同的位置显示出来,就需要用屏幕高度减去鼠标点击屏幕的Y轴坐标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using UnityEngine;

public class D3CoordinateToScreenCoordinate : MonoBehaviour
{
private Vector2 m_ScreenPotin = Vector3.zero;


// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray,out hit))
{
//获取屏幕坐标
m_ScreenPotin = Camera.main.WorldToScreenPoint(hit.point);
}
}
}
private void OnGUI()
{
GUI.color = Color.red;
GUI.Label(new Rect(m_ScreenPotin.x, Screen.height - m_ScreenPotin.y, 200, 40), string.Format("鼠标{0}", m_ScreenPotin));
}
}

首先从鼠标点击的位置发射一条射线,使用Physics.Raycast()得到射线碰到的模型表面的位置hit,然后使用Camera.main.WorldToScreenPoint()将hit碰撞点的3D世界坐标转换为相对当前摄像机的屏幕坐标。

3D坐标转换屏幕坐标

3D坐标转换UI坐标

UI也有一个3D正交摄像机(详见UIGI,Canvas那一节),3D坐标转UI坐标其实就是先转成屏幕坐标,再由屏幕坐标转成UI相机的3D坐标。

在3D世界中任意移动圆柱体的位置,UI血条会同步跟随移动。

3D坐标转屏幕坐标

这里UI屏幕坐标转换和Camera屏幕坐标转换是一样的,因为都是用的Camera.main(返回的是有main camera这个tag的摄像机)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using UnityEngine;

public class D3CoordinateToUICoordinate : MonoBehaviour
{
//角色头顶
public Transform characterTransform;
//血条
public RectTransform hpTransform;
//UI摄像机
public Camera UICamera;
//Canvas
public RectTransform canvasTransform;

private Vector2 rectScreenPoint, cameraScreenPoint;

private void Update()
{
//先将角色3D坐标转换为屏幕坐标
rectScreenPoint = RectTransformUtility.WorldToScreenPoint(Camera.main, characterTransform.position);
cameraScreenPoint = Camera.main.WorldToScreenPoint(characterTransform.position);
//再将屏幕坐标转换为UI坐标
Vector2 localPoint;
if(RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasTransform,rectScreenPoint,UICamera,out localPoint))
{
hpTransform.anchoredPosition = localPoint;
}
}
private void OnGUI()
{
if (GUILayout.Button("显示UI屏幕坐标"))
Debug.Log(rectScreenPoint.ToString());
if (GUILayout.Button("显示Camera屏幕坐标"))
Debug.Log(cameraScreenPoint.ToString());
}
}

主摄像机

主摄像机即MainCamera,是带有MainCamera这个Tag的摄像机。使用Camera.main()直接访问,不过这个tag如果被很多摄像机所使用,那么就并不安全。

在UI上显示模型

UI上是不能直接显示模型的,我们可以添加一个模型层,再创建一个新的摄像机来单独看这个模型层。将模型摄像机的深度设置到最高,那么它将显示在场景和UI的最上面。

Camera_UI的Culling Mask是UI图层,Camera_Model的Culling Mask是Model图层,Main Camera的Culling Mask是除去UI和Model层的Mixed

Model Camera

由于Canvas的ScreenMatchMode是Expand,那么模型需要在不同分辨率下进行缩放。

我们先将模型放在Camera_Model的子级,然后在Camera_Model挂载下面的脚本,在Canvas中准备一个背景Image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Camera))]
public class ModelCamera : MonoBehaviour
{
//ui
public RectTransform uiArea;
//模型
public Transform model;
//开发分辨率宽度
[Tooltip("开发分辨率宽度")] public float designWidth = 1366f;
//开发分辨率宽度
[Tooltip("开发分辨率高度")] public float designHeight = 768f;

private float designScale;
void Start()
{
designScale = designWidth / designHeight;
}

void Update()
{
//注意UI父级的世界坐标位置,一般是UI Camera的位置是否正常,这一步之后模型的z轴会变为-100,这个数值和Canvas的Plane Distance有关
model.transform.position = uiArea.transform.position;
float scaleRate = (float)Screen.width / (float)Screen.height;
float scaleFactor = scaleRate / designScale;
if (scaleRate < designScale)//屏幕长宽比变小了
model.localScale = Vector3.one * scaleFactor;
else
model.localScale = Vector3.one;
}
}

显示模型

显示模型结果

Render Texture

添加新摄像机来显示模型是有缺陷的。因为摄像机的深度(depth)决定了模型的渲染顺序,它要么显示在UI下面,要么显示在UI上面,如果想叠层显示在两个UI之间,就很麻烦了。我们可以使用Render Texture。它可以将某个摄像机看到的内容渲染到纹理上,最后再将这个纹理显示在UI的RawImage上。

Render Texture

开发中尽可能不要在Project中创建Render Texture文件,因为我们并不知道游戏中到底会用到多少个,Render Texture是可以在内存中创建的,这样本地就不需要保存这个文件了

在Render Texture上显示带特效的模型

粒子特效大都在使用Alpha Blending,它需要和后面的物体来做融合。在Render Texture上,如果粒子特效后面没有东西和他混合,显示就不正确。

我们可以把用在UI上的Sprite图片用在Sprite Render上,作为场景中的对象,就可以在粒子特效的后面当做背景了。特效与背景图混合后,再渲染到Render Texture上,最终在RawImage中也能正常显示

混合

背景图用Sprite Renderer的好处是,它可以和UI混合使用。在代码中,我们可以根据Camera来自动计算背景图的区域,保证它可以挡住特效。

给背景的Sprite Renderer挂这个脚本来自适应模型摄像机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SpriteFull : MonoBehaviour
{
//设置Sprite Renderer和摄像机的距离
private float distance = 3f;
//模型摄像机
public Camera modleCamera;
private SpriteRenderer spriteRenderer;
float worldScreenHeight, worldScreenWidth;

void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
spriteRenderer.material.renderQueue = 2980;//这段代码非常重要,否则透明的渲染层级就会出错
}

void Update()
{
Camera camera = modleCamera;
camera.transform.rotation = Quaternion.Euler(Vector3.zero);

float width = spriteRenderer.sprite.bounds.size.x;//一般情况下这个数值等于图片的宽度除以图片的pixel per unit
float height = spriteRenderer.sprite.bounds.size.y;//同理

//这里分别处理正交摄像机和非正交摄像机
if (camera.orthographic)
{
worldScreenHeight = camera.orthographicSize * 2.0f;
worldScreenWidth = worldScreenHeight / Screen.height * Screen.width;
}
else
{
worldScreenHeight = 2.0f * distance * Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad);
worldScreenWidth = worldScreenHeight * camera.aspect;
}
transform.localPosition = new Vector3(camera.transform.position.x, camera.transform.position.y, distance);
transform.localScale = new Vector3(worldScreenWidth / width, worldScreenHeight / height, 0f);
}
}

应用脚本

接着,将下列脚本挂载到模型摄像机中来自动创建Render Texture,并将其设置到RawImage上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Camera))]
public class ModelCameraCreateRendTex : MonoBehaviour
{
public RawImage rawImage;
void Start()
{
//为了避免Render Texture显示不清楚,所以乘以显示区域的1.5倍
float rate = 1.5f;
int width =(int)(rawImage.rectTransform.sizeDelta.x * rate);
int height =(int)(rawImage.rectTransform.sizeDelta.y * rate);
RenderTexture renderTexture = RenderTexture.GetTemporary(width, height, 16, RenderTextureFormat.ARGB32);
GetComponent<Camera>().targetTexture = renderTexture;
rawImage.texture = renderTexture;
}
}

LOD Group

Unity - Manual: LOD Group (unity3d.com)

Unity提供了LOD Group,同样的模型可以制作两份,一个是高精度的,一个是低精度的。最后,根据摄像机的远近来动态更换模型,或者剔除部分渲染。

在Edit——Project Settings——Quality中设置

  • LOD Bias:表示LOD的偏移值。LOD 级别是根据对象在屏幕上的尺寸选择的。当大小介于两个 LOD 级别之间时,选择可能会偏向于两个可用模型中较不详细或更详细的模型。这被设置为从 0 到 +infinity 的分数。当它设置在 0 和 1 之间时,它倾向于较少的细节。大于 1 的设置有利于更多细节。例如,将 LOD Bias 设置为 2 并使其在 50% 距离处发生变化,LOD 实际上仅在 25% 处发生变化。
  • Maximum LOD Level:表示LOD的最大等级,运行时修改它,可以优化低端机器。

设置参数

LOD的运行原理是根据物体在摄像机内的百分比来调节显示的级别,首先,会给物体添加平面的包围盒,摄像机发生移动后,即可计算与这个包围盒的百分比。

可以添加任意数量的LOD并且设置每个等级显示或不显示什么。

LOD Group

总体来说,LOD Group技术就是用内存来换时间,预先加载了模型的好几个状态,然后根据摄像机与它的距离来切换不同的状态以保证渲染效果。