在Shader编写过程中,常常需要处理不同坐标系下的顶点位置信息,因此必须借助MVP矩阵来实现相应的坐标变换操作。在Unity引擎中,不仅为用户提供了完备的各类MVP矩阵及其逆矩阵,还给出了历史帧中的矩阵数据。
坐标系转换函数
坐标转换函数
原理:补齐第四维度w为1.0,应用对应矩阵,返回XYZ。
局部空间 ⇔ 世界空间
float3 TransformObjectToWorld(float3 positionOS) // 局部空间 ⇒ 世界空间
float3 TransformWorldToObject(float3 positionWS) // 世界空间 ⇒ 局部空间观察空间 ⇔ 世界空间
float3 TransformViewToWorld(float3 positionVS) // 观察空间 ⇒ 世界空间
float3 TransformWorldToView(float3 positionWS) // 世界空间 ⇒ 观察空间任意空间 ⇔ 世界空间
float4 TransformObjectToHClip(float3 positionOS) // 局部空间 ⇒ 裁剪空间
float4 TransformWorldToHClip(float3 positionWS) // 世界空间 ⇒ 裁剪空间
float4 TransformWViewToHClip(float3 positionVS) // 观察空间 ⇒ 裁剪空间其他函数
- 方向向量转换函数
- 原理:将变换矩阵的第四维度丢弃,转为3x3矩阵,再将其应用。
- 定义:
real3 Transform###To###Dir(real3 dir, bool doNormalize) - 参数:局部空间 与 世界空间互转的函数中
doNormalize参数默认值为true,且参数和返回值的类型均为float3
- 法线转换函数
- 原理:本质上就是 方向向量转换函数,但特殊处理Model变换 -> 模型法线的Model变换
- 定义:
real3 Transform###To###Normal(real3 normal, bool doNormalize) - 参数:同上
- 更多请查看:库文件
Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl
矩阵变量
模型矩阵 Model Matrix
- 原矩阵:
UNITY_MATRIX_M、GetObjectToWorldMatrix()、unity_ObjectToWorld(旧) - 逆矩阵:
UNITY_MATRIX_I_M、GetWorldToObjectMatrix()、unity_WorldToObject(旧) - 历史帧数据:
- 原矩阵:
UNITY_PREV_MATRIX_M、GetPrevObjectToWorldMatrix() - 逆矩阵:
UNITY_PREV_MATRIX_I_M、GetPrevWorldToObjectMatrix()
- 原矩阵:
历史帧数据表示上一帧的模型空间到世界空间的变换矩阵。它存储了前一帧的变换状态,主要用于处理与时间相关的效果(如运动模糊、顶点动画插值等)。
视图矩阵 View Matrix
- 原矩阵:
UNITY_MATRIX_V、GetWorldToViewMatrix()、unity_WorldToCamera(旧) - 逆矩阵:
UNITY_MATRIX_I_V、GetViewToWorldMatrix()、unity_CameraToWorld(旧)
有坑! UNITY_MATRIX_V/UNITY_MATRIX_I_V 与 unity_WorldToCamera/unity_CameraToWorld 并不等价!-> 坑:新旧两版视图矩阵的区别
投影矩阵 Project Matrix
- 矩阵:
UNITY_MATRIX_P、GetViewToHClipMatrix() - 逆矩阵:
UNITY_MATRIX_I_P
视图矩阵 ✕ 投影矩阵
- 原矩阵:
UNITY_MATRIX_VP、GetWorldToHClipMatrix() - 逆矩阵:
UNITY_MATRIX_I_VP
坑:新旧两版视图矩阵的区别
在视图矩阵中,UNITY_MATRIX_V/UNITY_MATRIX_I_V 与 unity_WorldToCamera/unity_CameraToWorld 的值是不一样的!
直接看Unity源码中unity_WorldToCamera/unity_CameraToWorld的赋值代码:
Matrix4x4 worldToCameraMatrix = Matrix4x4.Scale(new Vector3(x: 1.0f, y: 1.0f, z: -1.0f)) * viewMatrix;
Matrix4x4 cameraToWorldMatrix = worldToCameraMatrix.inverse;
cmd.SetGlobalMatrix(ShaderPropertyId.worldToCameraMatrix, worldToCameraMatrix);
cmd.SetGlobalMatrix(ShaderPropertyId.cameraToWorldMatrix, cameraToWorldMatrix);我们可以看到unity_WorldToCamera/unity_CameraToWorld实际上是在视图矩阵viewMatrix的基础上将Z轴反向,而UNITY_MATRIX_V/UNITY_MATRIX_I_V并不会进行任何修改。
在Unity中,相机空间通常是一个左手坐标系,由此我们便可知,UNITY_MATRIX_V/UNITY_MATRIX_I_V的相机空间保留了原来的左手系,unity_WorldToCamera/unity_CameraToWorld的相机空间被转成了右手坐标系,而Unity的世界空间是一个右手坐标系,这意味着如果你直接使用UNITY_MATRIX_V/UNITY_MATRIX_I_V在某些情况下(如转换深度图重建的世界坐标)可能会出现问题。如果出现问题可以将欲转换的坐标的Z轴反向后再转换:
pixelPosVS.z *= -1.0;
float3 pixelPosWS = TransformViewToWorld(pixelPosVS);警告
函数TransformViewToWorld/TransformWorldToView使用的是UNITY_MATRIX_V/UNITY_MATRIX_I_V