三维引擎中Camera类带有一系列几何函数,这里我们看一下unity中这两个Camera提供的几何函数的意义和实现:
1.ScreenToViewportPoint
顾名思义就是屏幕坐标转视口坐标,在渲染流程中,建模->世界->视口->裁剪->视图得到屏幕坐标系中坐标数值,那么阶段性反过来从屏幕到视口的坐标变换也好理解,屏幕的左下角(0,0)到右上角(width,height)(ps:如果为1080p屏幕的话,width=1920,height=1080)对应的视口空间左下角(0,0)到右上角(1,1),那这个就很好理解了,鼠标坐标x/width&y/height就得到屏幕中,函数设计如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class StoV : MonoBehaviour { void Start() { } void Update() { if (Input.GetMouseButtonDown(0)) { Vector3 apiVPos = Camera.main.ScreenToViewportPoint(Input.mousePosition); Vector3 vPos = StoVPoint(Input.mousePosition); Debug.LogFormat("apivpos = {0} vpos = {1}", apiVPos, vPos); } } public Vector3 StoVPoint(Vector3 smousePos) { return new Vector3(smousePos.x / Screen.width, smousePos.y / Screen.height, 0); } }
测试结果:
2.ScreenToWorldPoint
顾名思义了,就是屏幕坐标转世界坐标,从屏幕空间到视口空间到世界坐标,这里有个重要的问题我要解释一下:
屏幕坐标明明是二维坐标,为什么可以转到三维呢?如果看了之前写的图形学原理,就会知道,三维空间中z轴经过空间转换就成了最后的深度depth信息,所以说我们要理解一个屏幕坐标对应的像素其实是包含深度depth信息的,那么我们脑海中就可以模拟出来,在世界空间到视口空间的z值转换成depth,z值处于camera的near/far裁剪面,深度depth则处于0-255,这就是对应的转换关系,下面来一个示意图:
函数设计如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class StoW : MonoBehaviour { [Range(1,100)] [SerializeField] public float depth = 1; void Start() { } void Update() { DrawViewport(); if (Input.GetMouseButtonDown(0)) { Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, depth); Vector3 apiwpos = Camera.main.ScreenToWorldPoint(mousePos); Vector3 wpos = StoWWorldPos(mousePos); Debug.LogFormat("mousePos = {0} apiwpos = {1} wpos = {2}", mousePos, apiwpos, wpos); } } //画出far裁剪面的视口线段 private void DrawViewport() { float far = Camera.main.farClipPlane; float hfov = Camera.main.fieldOfView / 2; float hhei = Mathf.Tan(hfov * Mathf.Deg2Rad) * far; float hwid = hhei * Camera.main.aspect; Vector3 world00 = CameraLocalToWorldPos(new Vector3(-hwid, -hhei, far)); Vector3 world01 = CameraLocalToWorldPos(new Vector3(-hwid, hhei, far)); Vector3 world11 = CameraLocalToWorldPos(new Vector3(hwid, hhei, far)); Vector3 world10 = CameraLocalToWorldPos(new Vector3(hwid, -hhei, far)); Debug.DrawLine(world00, world01, Color.red); Debug.DrawLine(world01, world11, Color.red); Debug.DrawLine(world11, world10, Color.red); Debug.DrawLine(world10, world00, Color.red); } //mainCamera处理local到world的TRS变换 private Vector3 CameraLocalToWorldPos(Vector3 localpos) { return Camera.main.transform.TransformPoint(localpos); } //将视口空间相对坐标转到世界坐标 //处理TRS矩阵变换 private Vector3 StoWWorldPos(Vector3 mousePos) { return CameraLocalToWorldPos(StoWLocalPos(mousePos)); } //计算视口空间中相对坐标 private Vector3 StoWLocalPos(Vector3 mousePos) { Vector3 eyePos = Camera.main.transform.position; float halffov = Camera.main.fieldOfView / 2; float halfhei = Mathf.Tan(halffov * Mathf.Deg2Rad) * depth; float halfwid = halfhei * Camera.main.aspect; float ratiox = (mousePos.x - Screen.width / 2) / (Screen.width / 2); float ratioy = (mousePos.y - Screen.height / 2) / (Screen.height / 2); float localx = halfwid * ratiox; float localy = halfhei * ratioy; Vector3 localxyz = new Vector3(localx, localy, depth); return localxyz; } }
测试结果如下:
顺便再次解释一下,首先使用camera的属性算出裁剪面的相对坐标范围,我是用红色line画出的区域就是,然后使用屏幕空间像素坐标映射到视口红色区域,得到相对xy坐标,最后得到localXYZ坐标,然后使用TRS矩阵变换到世界空间即可。
这里解析实现Camera的函数,可以为我以后发展一下图形库开发做准备。
————————————————
版权声明:本文为CSDN博主「羊羊2035」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yinhun2012/article/details/93343513

