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;
}
💡 运行时候会发现,模型并没有被正确渲染
💡 原因是,在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就可以了)
完整代码
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
}
}
}


...