unity坐标系

左右手坐标系

unity 采用左手坐标系: 大拇指向x,其它指从y->z,y轴向上,z轴向前,x轴向右
opencv opengl WebGL 3dMax 采用右手坐标系 ( 参考 OpenCVForUnity\org\opencv\unity\ARUtils.cs)
左右手坐标系的互换规则可以是 x,y,z 任一坐标取反,当你定下来后,后续就要统一,比如你采用 x 取反,那后续就不要用 y ,z 取反
Matrix4x4 invertYMatrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(-1, 1, 1));
绕轴旋转就是伸出左手,大拇指跟轴向一致,握拳方向就是正方向
点P坐标从A坐标系到B坐标系的转换
Pb = MatrixABPa
矩阵M从A坐标系到B坐标系的转换
点P经过Ma变换和经过Mb变换后空间位置保持不变
则有 Ma
P = MatrixBAMbMatrixABP
因此 Mb = MatrixAB
MaMatrixBA
如果是取点P,保证在A,B坐标系下相机 C 的画面相同,则相机C 在A 坐标系下的矩阵为
CA = invertYMatrix * CB * invertYMatrix;
推导如下:
要保证画面相同,则点P 在2个坐标系下的相机坐标系中的位置存在关系 PA = invertYMatrix
PB
而 PA = CA^-1invertYMatrixCBPB
因此 invertYMatrix
PB = CA^-1invertYMatrixCBPB
整理得 CA = invertYMatrix
CBinvertYMatrix^-1
而 invertYMatrix == invertYMatrix^-1
最终 CA = invertYMatrix
CB*invertYMatrix
这个就是相机矩阵在2个坐标系的映射关系,保证2个相机的输出画面一致

相机方向

  • 相机方向是跟采用的左右手坐标系绑定的

    • 在右手坐标系下,相机方向就是标准的opengl处理方式,即相机方向沿z轴负方向
    • 在左手坐标系下,相机方向就是unity处理方式,沿z轴正方向
  • worldToCameraMatrix
    我们外部计算unity的投影矩阵时远近平面距离都为正值,但底层shader使用的是opengl投影矩阵,即远近平面距离都为负值
    在shader中 UNITY_MATRIX_MV 已经包含了转成OpenGL的矩阵的过程,用unity代码实现是这样

        Matrix4x4 unityToOpenGL = Matrix4x4.identify;
        unityToOpenGL.m22 = -1;
        projectionOpenGL = unityToOpenGL * projectionUnity;
    

    那么外部传入的顶点坐标转换 camera.worldToCameraMatrix 也需要转成相对OpenGL的坐标系
    camera.worldToCameraMatrix = unityToOpenGL * camera.worldToLocalMatrix;

  • 同一个模型如果想在2个坐标系下看到一致的图像,且可以调整相机缩放
    则相机在2个坐标系下的位置一样,但必须旋转才能看到模型

    • 如果右手坐标系下相机绕x轴旋转180度,则看到的图像与左手坐标系的上下颠倒,也就是相机的y轴缩放必须设为-1才能看到正常图像
    • 如果右手坐标系下相机绕y轴旋转180度,则看到的图像与左手坐标系的左右颠倒,也就是相机的x轴缩放必须设为-1才能看到正常图像
  • 同一个模型如果想在2个坐标系下看到一致的图像,而且不能调整相机缩放
    则相机在2个坐标系下的旋转一样,但必须调整相机位置 z 为 -z
    右手系保存的模型,左手系读取时要调整顶点 z 为 -z,不调整的话无论怎么调整相机看到的都是上下颠倒或左右颠倒的模型
    如果模型在右手系摆放在(x,y,z), 则左手系下要摆放在 (x,y,-z)
    比如用3dsmax保存的模型,unity读取时将顶点 z 转为 -z,才能保证模型正确显示而不用调整相机的缩放

相机矩阵

相机矩阵是相机在2个坐标系下的映射关系,保证2个相机的输出画面一致
相机矩阵的推导过程如下:
相机矩阵的推导过程如下:
要保证画面相同,则点P 在2个坐标系下的相机坐标系中的位置存在关系 PA = invertYMatrixPB
而 PA = CA^-1
invertYMatrixCBPB

Camera坐标系

  • 模型坐标系
    就是模型自己的坐标系
    比如顶点经常记录为相对原点的位置

  • 世界坐标系
    就是以世界为原点的坐标系

  • 视图坐标系
    就是以相机为原点的坐标系,这里遵守 OpenGL 约定:摄像机前方为负 Z 轴。
    这与 Unity 的约定不同,在 Unity 中,摄像机前方为 正 Z 轴,因此
    Matrix4x4 openGLToUnity = Matrix4x4.identity;
    openGLToUnity.m22 = -1;
    openGLToUnity * camera.worldToCameraMatrix == camera.transform.worldToLocalMatrix

  • 剪裁空间
    视图空间点经过投影后的坐标系
    标准化为 [(-1,-1,-1), (1,1,1)]

  • 视口坐标系
    视口空间是标准化的、相对于摄像机的空间。
    摄像机左下角为 (0,0),右上角为 (1,1)。z 位置为与摄像机的距离在摄像相方向上的投影,采用世界单位。
    Camera.rect 设置并不会影响该坐标系,也就是坐标是相对相机在屏幕上的视口范围的
    比如 相机在屏幕的视口范围是 (0.25,0.25,0.5,0.5),则视口坐标系的 (0,0)对应屏幕的(0.25,0.25)

  • 屏幕坐标系
    相机最终输出映射到屏幕的位置
    屏幕左下是(0,0) , 右上是(Screen.width, Screen.height)
    比如 相机在屏幕的视口范围是 (0.25,0.25,0.5,0.5),则视口坐标系的 (0,0)对应屏幕的(0.25Screen.width,0.25Screen.height)

  • fieldofview
    指的是y方向上的张角,所以当窗口x方向变化时,物体显示的大小不变,y方向变化时,物体会缩放

ugui坐标系

[0,0,width,height]
左下角为0,0

imgui坐标系

参考 imgui坐标系

坐标系之间的转换

    //  模型坐标系到世界坐标系的转换矩阵
    camera.transform.localToWorldMatrix
    //  世界坐标系到模型坐标系的转换矩阵
    camera.transform.worldToLocalMatrix
    
    // 世界坐标系到视图坐标系的转换矩阵
    camera.worldToCameraMatrix
    // 视图坐标系到世界坐标系的转换矩阵
    camera.worldToCameraMatrix.inverse
    //  由于 OpenGL 约定与 Unity 约定的相机空间不一致,有
    Matrix4x4 openGLToUnity = Matrix4x4.identity;
    openGLToUnity.m22 = -1;
    openGLToUnity * camera.worldToCameraMatrix == camera.transform.worldToLocalMatrix
    
    // 视图坐标系到剪裁空间的转换矩阵
    camera.projectionMatrix
    // 剪裁空间到视图坐标系的转换矩阵
    camera.projectionMatrix.inverse

    // 剪裁空间到视口坐标系的转换
    Vector2 viewprotPoint = new Vector2((clipPos.x+1)/2, (clipPos.y+1)/2);
    // 视口坐标系到剪裁空间的转换
    Vector2 clipPos = new Vector2(viewprotPoint.x * 2 - 1, viewprotPoint.y * 2-1);

    // 视口坐标到屏幕坐标的转换(可以用camera.ViewportToScreenPoint)
    Rect pixelRect = camera.pixelRect;
    Vector2 screenPoint = new Vector2(pixelRect.x+viewprotPoint.x * pixelRect.width, pixelRect.y+viewprotPoint.y * pixelRect.height);
    
    // 屏幕坐标到视口坐标的转换(可以用camera.ScreenToViewportPoint)
    Vector2 viewprotPoint = new Vector2((screenPoint.x -pixelRect.x)/ pixelRect.width, (screenPoint.y-pixelRect.y) / camera.pixelHeight);

内置函数在坐标系之间转换

    // 世界坐标系到视口坐标系的转换,z值是物体到相机距离在相机方向上的投影
    Vector3 viewprotPoint = camera.WorldToViewportPoint(worldPos)
    // 视口坐标系到世界坐标系的转换
    Vector3 worldPos = camera.ViewportToWorldPoint(viewprotPoint)

    // 世界到屏幕坐标的转换
    Vector3 screenPoint = camera.WorldToScreenPoint(worldPos)
    // 批量转换时可用 CameraProjectionCache 缓存中间值提高效率
    Vector3 screenPoint = new CameraProjectionCache(camera).WorldToScreenPoint(worldPos)
    // 屏幕坐标到世界坐标的转换
    Vector3 worldPos = camera.ScreenToWorldPoint(screenPoint)

    // 视口到屏幕坐标的转换
    Vector3 screenPoint = camera.ViewportToScreenPoint(viewprotPoint)
    // 屏幕坐标到视口坐标的转换
    Vector3 viewprotPoint = camera.ScreenToViewportPoint(screenPoint)

    // 屏幕坐标转成射线(screenPoint.z 会被忽略,起点在近平面上)
    Ray ray = camera.ScreenPointToRay(screenPoint)

更多推荐