MVP矩阵是图形学中常用的一种矩阵变换方式,用于将三维世界坐标系中的物体转换到二维屏幕坐标系中进行显示。 MVP矩阵是由三个矩阵相乘得到的,它们分别是模型矩阵(Model Matrix)、视图矩阵(View Matrix)和投影矩阵(Projection Matrix)。

  • 模型矩阵用于将物体从自身坐标系转换到世界坐标系。
  • 视图矩阵用于将世界坐标系中的物体转换到相机坐标系中。
  • 投影矩阵用于将相机坐标系中的物体投影到二维屏幕坐标系中。

MVP矩阵的计算方式为:MVP = ProjectionMatrix x ViewMatrix x ModelMatrix。 使用MVP矩阵的好处是可以将多个物体同时进行变换和投影,从而提高渲染速度和效率。 需要注意的是,不同的图形库和引擎可能对MVP矩阵的计算方式和顺序有所不同,因此在使用MVP矩阵时需要了解具体的实现方式和参数设置。

首先是MVP中的M

由模型空间经过M(模型变换)矩阵转为世界空间 将顶点坐标从模型转为世界的顺序为:先缩放,然后旋转,最后进行平移(顶点坐标变换顺序) 但书写顺序是从右向左 所以 矩阵平移矩阵旋转矩阵缩放矩阵�矩阵=平移矩阵⋅旋转矩阵⋅缩放矩阵

将模型空间下顶点坐标×M矩阵 即可得到世界空间下顶点坐标

然后是MVP中的V

由世界空间经过V(视图变换)矩阵转为观察空间(此步在右手坐标系进行) 将顶点坐标从世界转为观察的顺序为: 1.平移整个观察空间,将相机原点和世界坐标原点重合,坐标轴重合 2.相机在世界空间先旋转,后平移 3.进行逆变换,先平移,再旋转,最后取反 矩阵旋转矩阵平移矩阵M矩阵=旋转矩阵⋅平移矩阵

最后是MVP中的P

由观察空间经过P(投影变换)矩阵转为裁剪空间 目的:判断顶点是否在可见范围内 矩阵对进行缩放作为范围值如果在范围内则该点位于裁剪空间P矩阵:对X,Y,Z进行缩放,W作为范围值,如果XYZ在W范围内,则该点位于裁剪空间 这一步分为透视投影和正交投影两种 在Unity内置的ShaderLab中,提供了许多内置的变换矩阵

变量名

描述

UNITY_MATRIX_MVP

当前的模型观察投影矩阵,用于将顶点/方向矢量从模型空间转换到裁剪空间

UNITY_MATRIX_MV

当前的模型观察矩阵,用于将顶点/方向矢量从模型空间转换到观察空间

UNITY_MATRIX_V

当前的观察矩阵,用于将顶点/方向矢量从世界空间转换到观察空间

UNITY_MATRIX_P

当前的投影矩阵,用于将顶点/方向矢量从观察空间转换到裁剪空间

UNITY_MATRIX_VP

当前的观察投影矩阵,用于将顶点/方向矢量从世界空间转换到裁剪空间

UNITY_MATRIX_T_MV

UNITY_MATRIX_MV 的转置

UNITY_MATRIX_IT_MV

UNITY_MATRIX_MV 的逆转置矩阵,常用于将法线从模型空间转换到观察空间

_Object2World

当前的模型矩阵,用于将顶点/方向矢量从模型空间转换到世界空间

_World2Object

_Object2World 的逆矩阵,用于将顶点/方向矢量从世界空间转换到模型空间

有了这些内置矩阵,在顶点变换时候,就有很多的方式

v2f vert (appdata v)
{
 v2f o;

 // 方式一 
 o.vertex = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_MV, v.vertex));

 // 方式二
 o.vertex = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_V, mul(_Object2World, v.vertex)));

 // 方式三 
 o.vertex = mul(UNITY_MATRIX_VP, mul(_Object2World, v.vertex));

 // 方式四,通过构造一个模型观察投影矩阵,然后变换顶点坐标 
 float4x4 m = mul(UNITY_MATRIX_P, UNITY_MATRIX_MV);
 o.vertex = mul(m, v.vertex);

 // 方式五,最常见也是最高效的做法 
 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

 o.uv = v.uv;
 return o;
}

💡 在新版本的Unity当中 mul(UNITY_MATRIX_MVP, v.vertex);被替换为用函数UnityObjectToClipPos进行几何变换 UnityObjectToClipPos(v.vertex);

完整代码

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Learning/GeomertricTransformation/vf1"
{
 SubShader
 {
 pass{

 CGPROGRAM

 #pragma vertex vert

 #pragma fragment frag

 #include "UnityCG.cginc"

 struct v2f{
 float4 pos:POSITION;
 };


 v2f vert(appdata_base v)
 {
 v2f o;
 o.pos=UnityObjectToClipPos(v.vertex);
 return o;
 }

 fixed4 frag(v2f IN):COLOR
 {
 return fixed4(1,1,1,1);
 }

 ENDCG 
 }
 }
}

采用C#实现物体自身的几何变换(理解Unity矩阵变换的过程)

1.先尝试使用模型变换到世界的矩阵✖️世界变换到摄像机✖️透视矩阵

Matrix4x4 mvp=transform.localToWorldMatrix*Camera.main.worldToCameraMatrix* Camera.main.projectionMatrix;

2.将矩阵传入着色器,记得一定要在Update中不断更新mvp和更新数据

GetComponent().sharedMaterial.SetMatrix("_MVP",mvp);

3.在着色器的顶点程序当中用传入的矩阵与模型的顶点相乘

uniform float4x4 _MVP;

 struct v2f{
 float4 pos:POSITION;
 };


 v2f vert(appdata_base v)
 {
 v2f o;
 o.pos=mul(_MVP,v.vertex);
 return o;
 }

💡 运行时候会发现,模型并没有被正确渲染

Screenshot 2023-02-23 at 11.11.55 PM.png

💡 原因是,在Unity中矩阵乘法的顺序定义有一些区别 mvp矩阵改成下方相乘顺序即可

Matrix4x4 mvp=Camera.main.projectionMatrix*Camera.main.worldToCameraMatrix*transform.localToWorldMatrix;

最终在Game视图中可以被正确渲染, 但是在Editor中由于Scene存在另外的PreviewCamera,上面的矩阵只与游戏当中的MainCamera的矩阵相乘进行变换, 所以在Scene中的PreviewCamera并没有显示其在Scene场景中的视图变换 💡 所以,通常来说在编写Shader时,采用内置的UNITY_MATRIX_MVP即可,Unity内部已经对编辑器的PreviewCamera和运行时的Camera都做了对应的矩阵的相乘, (Unity新版本中,直接使用UnityObjectToClipPos就可以了)

Screenshot 2023-02-23 at 11.18.19 PM.png

完整代码

Shader:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Learning/GeomertricTransformation/vf1"
{
 SubShader
 {
 pass{

 CGPROGRAM

 #pragma vertex vert

 #pragma fragment frag

 #include "UnityCG.cginc"

 uniform float4x4 _MVP;

 struct v2f{
 float4 pos:POSITION;
 };


 v2f vert(appdata_base v)
 {
 v2f o;
 o.pos=mul(_MVP,v.vertex);
 return o;
 }

 fixed4 frag(v2f IN):COLOR
 {
 return fixed4(1,1,1,1);
 }

 ENDCG 
 }
 }
}

C#:

using UnityEngine;

public class MVP : MonoBehaviour
{

 void Update()
 {
 Matrix4x4 mvp=Camera.main.projectionMatrix*Camera.main.worldToCameraMatrix*transform.localToWorldMatrix;
 GetComponent().sharedMaterial.SetMatrix("_MVP",mvp);
 }
}

在C#中构建旋转矩阵使模型旋转

using UnityEngine;

public class MVP : MonoBehaviour
{

 void Update()
 {
 //构建绕Y轴旋转的矩阵
 Matrix4x4 rm=new Matrix4x4();
 rm[0,0]=Mathf.Cos(Time.realtimeSinceStartup);
 rm[0,2]=Mathf.Sin(Time.realtimeSinceStartup);
 rm[1,1]=1;
 rm[2,0]=-Mathf.Sin(Time.realtimeSinceStartup);
 rm[2,2]=Mathf.Cos(Time.realtimeSinceStartup);
 rm[3,3]=1;

 Matrix4x4 mvp=Camera.main.projectionMatrix*Camera.main.worldToCameraMatrix*transform.localToWorldMatrix;

 GetComponent().sharedMaterial.SetMatrix("_MVP",mvp);
 GetComponent().sharedMaterial.SetMatrix("_RM",rm);//传入着色器
 }
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Learning/GeomertricTransformation/vf1"
{
 SubShader
 {
 pass{

 CGPROGRAM

 #pragma vertex vert

 #pragma fragment frag

 #include "UnityCG.cginc"

 uniform float4x4 _MVP;
 uniform float4x4 _RM;

 struct v2f{
 float4 pos:POSITION;
 };

 v2f vert(appdata_base v)
 {
 v2f o;
 //将传入的旋转矩阵与顶点相乘 
 float4 p=mul(_RM,v.vertex);
 //将顶点变换到裁剪空间
 o.pos=UnityObjectToClipPos(p);

 return o;
 }

 fixed4 frag(v2f IN):COLOR
 {
 return fixed4(1,1,1,1);
 }

 ENDCG 
 }
 }
}

在C#中构建缩放矩阵使模型缩放

Matrix4x4 sm=new Matrix4x4();
 sm[0,0]=Mathf.Sin(Time.realtimeSinceStartup)/4+0.5f;
 sm[1,1]=Mathf.Cos(Time.realtimeSinceStartup)/8+0.5f;
 sm[2,2]=Mathf.Sin(Time.realtimeSinceStartup)/6+0.5f;
 sm[3,3]=1;

GetComponent().sharedMaterial.SetMatrix("_SM",sm);//传入着色器
v2f vert(appdata_base v)
 {
 v2f o;

 float4 p=mul(_RM,v.vertex);
 //将缩放矩阵与顶点相乘
 p=mul(_SM,p);

 o.pos=UnityObjectToClipPos(p);

 return o;
 }

在Shader内部实现模型旋转

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Learning/GeomertricTransformation/vf1"
{
 SubShader
 {
 pass{

 CGPROGRAM

 #pragma vertex vert

 #pragma fragment frag

 #include "UnityCG.cginc"

 struct v2f{
 float4 pos:POSITION;
 };

 //构建绕Y轴旋转的矩阵
 float4x4 RotateY(float _Angle)
 {
 float4x4 R_M={
 cos(_Angle),0,sin(_Angle),0,
 0,1,0,0,
 -sin(_Angle),0,cos(_Angle),0,
 0,0,0,1
 };

 return R_M;
 }

 v2f vert(appdata_base v)
 {
 v2f o;

 float4 p=mul(RotateY(_Time*10),v.vertex);

 o.pos=UnityObjectToClipPos(p);

 return o;
 }

 fixed4 frag(v2f IN):COLOR
 {
 return fixed4(1,1,1,1);
 }

 ENDCG 
 }
 }
}