正所谓:3D坐标学的好,天天梦游不迷路。
-
欢迎关注公众号:雷潮课堂
一、课程内容
1-1什么是3D坐标系?
1-2Unity中的坐标系统
1-3Unity中坐标系统分类
二、Unity3D坐标系
2-1世界坐标系
2-2局部坐标系
2-3屏幕坐标系
2-4GUI坐标系
2-5视口坐标系
三、坐标系的转换
3-1 世界坐标与局部坐标的转换
3-2 屏幕坐标与世界坐标的转换
3-4 视口坐标与屏幕、世界坐标的转换
3-4 视口坐标绘制图像代码
1-1 什么是3D坐标系?
3D坐标系是3D游戏开发与VR开发中的基础概念。一般而言3D坐标系都是使用的
笛卡尔坐标系来描述物体的坐标信息,笛卡尔坐标系:分为左手坐标系与右手坐标系
1-2 Unity中的坐标系统
Unity中使用的是左手坐标系,X轴代表水平方向 horizontal, Y轴代表垂直方向vertical ,Z轴代表深度。
使用三维向量表示:Vector3(x,y,z)
1-3 Unity中坐标系分类:
- word Space (世界坐标系)
- local Space (局部坐标系)
- Screen Space(屏幕坐标系)
- GUI界面的坐标系
- ViewPort (视口坐标)
2-1 世界坐标系
世界坐标系:用来描述游戏场景内所有物体位置和方向的基准,Unity场景中创建的物体都是以全局坐标系中的坐标原点(0,0,0)来确定各自的位置。
-
别名:全局坐标系
- 怎么获取游戏对象的世界坐标?
transform.position可以获得该坐标
2-2 局部坐标系
局部坐标系:是相对于父物体来说也就是当两个游戏对象互为父子关系,那么子物体会以父物体的坐标点为自身的坐标原点。如果游戏对象没有父物体,那么会以这个世界的0,0为自身的坐标原点.
-
别名:物体坐标系,模型坐标系?(有的模型软件并不是如此)
怎么获取游戏对象的局部坐标?
transform.LocalPosition可以获得该局部坐标
2-3 屏幕坐标系
屏幕坐标系:以像素为单位,左下角是(0,0),右上角为(screen.width, screen.height),Z的位置是以相机的世界单位来衡量及物体离相机远近。这个坐标坑最多,首先要知道是相对屏幕。举个例子,现实的投影仪大家很清楚,我们在天花板顶上放一个投影仪,然后投影的影像映射到墙面上或幕布上,这墙面或幕布的影像区域就是屏幕,当我们移动幕布的时候,屏幕的大小也在变化,这就跟我们幕布到投影仪的垂直距离有关,这个垂直距离就是我们的Z轴,后面的屏幕坐标转世界坐标会讲到。值得注意的一点屏幕的坐标跟分辨率的关系,当我们的分辨率是600800的时候,但我们截取其中的200200作为屏幕的话,那么屏幕的左下角坐标为(0,0),屏幕右上角坐标为(200,200)。屏幕坐标常用于鼠标交互,而获取的鼠标坐标就属于屏幕坐标(input.mouseposition)。
- 怎么获取屏幕坐标系
Input.mousePosition;
Input.GetTouch(0).position;
public Vector3 screenPos;
void Update () {
screenPos = Input.mousePosition;
}
2-4 GUI界面坐标系
GUI界面坐标系:这个坐标系与屏幕坐标系相似,不同的是该坐标系以屏幕的左上角为(0,0)点,右下角为(Screen.width,Screen.height)。
private void OnGUI()
{
GUILayout.TextArea("顶部框" );
GUILayout.TextArea("第二个框" );
GUILayout.TextArea("第三个框:" );
GUILayout.TextArea("第四个框:" );
GUI.TextArea(new Rect(0, 400, Screen.width, Screen.height / 5), "整个屏幕宽度与整个屏幕高度为5分之一的框,");
}
2-5 viewport (视口坐标)
视口坐标:视口坐标是标准的和相对于相机的。相机的左下角为(0,0)点,右上角为(1,1)点,Z的位置是以相机的世界单位来衡量的,它的Z轴即是摄像机的Z轴,上面的示例中投影仪屏幕上建立的坐标就是视口坐标,这个坐标大多用来过渡转化坐标作用,例如世界坐标转屏幕坐标,世界坐标转视口坐标,视口坐标转屏幕坐标。
v.x = camera.transform.position.x + K1;
v.y = camera.transform.position.y + K2;
v.z = camera.transform.position.z + ps.z;
其中K1 = ps.z * asp * ((ps.x -0.5)/0.5)*tan(e/2);
K2 = ps.z * ((ps.y -0.5)/0.5)*tan(e/2);
e位摄像机的视口夹角fieldOfView的值,asp为摄像机视口的宽高比aspect。
摄像机分为两种,一种是正交摄像机还有一种是透视摄像机。正交摄像机无论远近它的视口范围永远是固定的,但是透视摄像机是由原点向外扩散性发射,也就是距离越远它的视口区域也就越大。那么我们如何获取距离摄像机任意距离的视口区域呢?
如下图所示,分别用红色和黄色两种颜色将计算出来的视口区域标记了出来。
- 注意点
相机如何渲染物体
摄像机对游戏世界的渲染范围是一个平截头体,渲染边界是一个矩形,用与near clippingplane或者far clippingplane平行的平面截取这个平截头体,可以获得无数个平行的矩形面,也就是我们看到的屏幕矩形。离摄像机越远,矩形越大,离摄像机越近,矩形越小。所以,同样大小的物体,随着离摄像机越来越远,相对于对应屏幕矩形就越来越小,所看起来就越来越小。
ScreenToWorldPoint:
首先截取一个垂直于摄像机Z轴的,距离为Z的平面P,这样不管X,Y怎么变化,返回的点都只能在这个平面上,参数是一个三维坐标,而实际上,屏幕坐标只能是二维坐标。参数中的z坐标的作用就是:用来表示上述平面离摄像机的距离。X,Y表示像素坐标,根据(X,Y)相对于屏幕的位置,得到游戏世界中的点相对于截面P的位置,也就将屏幕坐标转换为了世界坐标。
- 视口坐标的转换
// 视口坐标到屏幕坐标
screenPos = Camera.main.ViewportToScreenPoint(cube.transform.position);
// 屏幕坐标到视口坐标
viewPos1 = Camera.main.ScreenToViewportPoint(screenPos);
// 视口坐标到世界坐标
worldPos = Camera.main.ViewportToWorldPoint(cube.transform.position);
// 世界坐标到视口坐标
viewPos =Camera.main.WorldToViewportPoint(cube.transform.position);
3-1 世界坐标与局部坐标的转换
局部坐标系转换到全局坐标系:
Transform.TransformPoint()
全局坐标系转换到局部坐标系:
Transform.InVerseTransformPoint()
Vector3 worldPos = GameObject.Find("Cube").transform.position;
Vector3 localPos = GameObject.Find("Cube").transform.localPosition;
Debug.Log("物体的世界坐标" + worldPos);
Debug.Log("物体的局部坐标" + localPos);
Vector3 SpWorldPos = GameObject.Find("Cube").transform.Find("Sphere").transform.position;
Vector3 SpLocalPos = GameObject.Find("Cube").transform.Find("Sphere").transform.localPosition;
Debug.Log("子物体的世界坐标" + SpWorldPos);
Debug.Log("子物体的局部坐标" + SpLocalPos);
// 世界坐标与局部坐标的转换 注意点:关于坐标的转换,使用父类对象Transform进行
// 从局部坐标转世界坐标
Vector3 SpWorldPos1 = GameObject.Find("Cube").transform.TransformPoint(SpLocalPos);
// 从世界坐标转局部坐标
Vector3 SpLocalPos1 = GameObject.Find("Cube").transform.InverseTransformPoint(SpWorldPos);
Debug.Log("子物体的世界坐标转换" + SpWorldPos1);
Debug.Log("子物体的局部坐标转换" + SpLocalPos1);
// 局部向量转换到全局向量
Transform.TransformDirection
// 全局向量转换到局部向量
Transform. InVerseTransformDirection
3-2 屏幕坐标与世界坐标的转换
using UnityEngine;
public class MyCube : MonoBehaviour
{
public GameObject cube;
private Vector3 v;
void Start()
{
cube = GameObject.Find("Cube");
}
void OnGUI()
{
GUILayout.Label("方块转屏幕坐标是:" + v);
}
void Update()
{
v = ScreenToWorld(Input.mousePosition, cube.transform);
if (Input.GetMouseButton(0))
{
cube.transform.position = v;
}
if (Input.GetMouseButtonUp(0))
{
cube.transform.position = v;
}
}
public Vector3 ScreenToWorld(Vector3 mousePos, Transform targetTransform)
{
//先计算相机到目标的向量
Vector3 dis = targetTransform.position - Camera.main.transform.position;
//计算投影
Vector3 normardir = Vector3.Project(dis, Camera.main.transform.forward);
//获取矩形面的距离 获取的不是物体到相机的距离,而是物体所在的平面到相机的距离。这个平面是和相机x,y平面平行
Vector3 worldPos = Camera.main.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, normardir.magnitude));
return worldPos;
}
}
using UnityEngine;
public class MyCube : MonoBehaviour
{
public GameObject cube;
private Vector3 v;
float asp;
private float e;
void Start()
{
cube = GameObject.Find("Cube");
asp = Camera.main.aspect;
e = Camera.main.fieldOfView;
}
void OnGUI()
{
GUILayout.Label("方块转屏幕坐标是:" + v);
}
void Update()
{
v = ScreenToWorld(Input.mousePosition, cube.transform);
if (Input.GetMouseButton(0))
{
Vector3 v = Camera.main.ScreenToWorldPoint(cube.transform.position);//ps为参考点
//v的各个分量值为
v.x = Camera.main.transform.position.x + cube.transform.position.z * asp * Mathf.Tan(e / 2);
v.y = Camera.main.transform.position.y + cube.transform.position.z * Mathf.Tan(e / 2);
v.z = Camera.main.transform.position.z + cube.transform.position.z;
cube.transform.position = v;
}
if (Input.GetMouseButtonUp(0))
{
cube.transform.position = v;
}
}
}
- 注意:首先截取一个垂直于摄像机Z轴的,距离为Z的平面P,这样不管X,Y怎么变化,返回的点都只能在这个平面上,参数是一个三维坐标,而实际上,屏幕坐标只能是二维坐标。参数中的z坐标的作用就是:用来表示上述平面离摄像机的距离。X,Y表示像素坐标,根据(X,Y)相对于屏幕的位置,得到游戏世界中的点相对于截面P的位置,也就将屏幕坐标转换为了世界坐标。
3-3 视口坐标与屏幕、世界坐标的转换
[世界->视口] *[视口->屏幕]
public Vector3 screenPos;
public Vector3 worldPos;
public Vector3 viewPos;
public Vector3 viewPos1;
private GameObject cube;
void Start() {
cube = GameObject.Find("Cube");
// 视口坐标到屏幕坐标
screenPos = Camera.main.ViewportToScreenPoint(cube.transform.position);
// 屏幕坐标到视口坐标
viewPos1 = Camera.main.ScreenToViewportPoint(screenPos);
// 视口坐标到世界坐标
worldPos = Camera.main.ViewportToWorldPoint(cube.transform.position);
// 世界坐标到视口坐标
viewPos =Camera.main.WorldToViewportPoint(cube.transform.position);
}
private void OnGUI()
{
GUILayout.TextArea("屏幕坐标:" + screenPos);
GUILayout.TextArea("世界坐标:" + worldPos);
GUILayout.TextArea("视口坐标:" + viewPos);
GUILayout.TextArea("视口坐标:" + viewPos1);
}
3-4 视口坐标绘制图像代码
拷贝代码做成脚本挂载到相机就可以。
using UnityEngine;
using System.Collections;
public class CameraView : MonoBehaviour
{
private Camera theCamera;
//距离摄像机8.5米 用黄色表示
public float upperDistance = 8.5f;
//距离摄像机12米 用红色表示
public float lowerDistance = 12.0f;
private Transform tx;
void Start()
{
if (!theCamera)
{
theCamera = Camera.main;
}
tx = theCamera.transform;
}
void Update()
{
FindUpperCorners();
FindLowerCorners();
}
void FindUpperCorners()
{
Vector3[] corners = GetCorners(upperDistance);
// for debugging
Debug.DrawLine(corners[0], corners[1], Color.yellow); // UpperLeft -> UpperRight
Debug.DrawLine(corners[1], corners[3], Color.yellow); // UpperRight -> LowerRight
Debug.DrawLine(corners[3], corners[2], Color.yellow); // LowerRight -> LowerLeft
Debug.DrawLine(corners[2], corners[0], Color.yellow); // LowerLeft -> UpperLeft
}
void FindLowerCorners()
{
Vector3[] corners = GetCorners(lowerDistance);
// for debugging
Debug.DrawLine(corners[0], corners[1], Color.red);
Debug.DrawLine(corners[1], corners[3], Color.red);
Debug.DrawLine(corners[3], corners[2], Color.red);
Debug.DrawLine(corners[2], corners[0], Color.red);
}
Vector3[] GetCorners(float distance)
{
Vector3[] corners = new Vector3[4];
float halfFOV = (theCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
float aspect = theCamera.aspect;
float height = distance * Mathf.Tan(halfFOV);
float width = height * aspect;
// UpperLeft
corners[0] = tx.position - (tx.right * width);
corners[0] += tx.up * height;
corners[0] += tx.forward * distance;
// UpperRight
corners[1] = tx.position + (tx.right * width);
corners[1] += tx.up * height;
corners[1] += tx.forward * distance;
// LowerLeft
corners[2] = tx.position - (tx.right * width);
corners[2] -= tx.up * height;
corners[2] += tx.forward * distance;
// LowerRight
corners[3] = tx.position + (tx.right * width);
corners[3] -= tx.up * height;
corners[3] += tx.forward * distance;
return corners;
}
}
3-5 坐标转换练习
//(x= 左下角= 0 右上角 = 屏幕的宽度,y左下角 = 0右上角 = 屏幕的高度 z = 0)
pos = Input.mousePosition;
// 鼠标的屏幕坐标转成世界坐标
GPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
// 鼠标的屏幕转视口
VPos = Camera.main.ScreenToViewportPoint(SPos);
// 鼠标的视口转屏幕
SPos1 = Camera.main.ViewportToScreenPoint(VPos);
// 鼠标的视口转世界
GPos1 = Camera.main.ViewportToWorldPoint(VPos);
// 鼠标的世界坐标转成屏幕坐标
SPos = Camera.main.WorldToScreenPoint(GPos);
// 鼠标的世界坐标转成视口坐标
SPos = Camera.main.WorldToViewportPoint(GPos);
- 欢迎购买本课程:
腾讯课堂
网易云课堂