Unity常用内置变换矩阵

Unity引擎提供了一系列内置的变换矩阵,这些矩阵在着色器中用于处理物体、摄像机和光照的坐标变换,是游戏开发中不可或缺的工具。它们帮助开发者在顶点着色器和片段着色器中实现坐标转换、光照计算等功能。


主要变换矩阵类型

模型矩阵 (Model Matrix)

cpp 复制代码
// 在着色器中访问
unity_ObjectToWorld  // float4x4
UNITY_MATRIX_M // 宏定义

作用:将顶点从模型空间转换到世界空间(World Space)。

用法:将物体的局部坐标转换为世界坐标,便于在世界空间中进行计算。

组成部分

  • 前3×3部分包含旋转和缩放
  • 第4列包含位移信息
  • 第4行通常为(0,0,0,1)

代码示例

cpp 复制代码
// 顶点着色器中的应用
float4 worldPos = mul(unity_ObjectToWorld, float4(vertexPosition, 1.0));
// 或
float4 worldPos = mul(UNITY_MATRIX_M, float4(vertexPosition, 1.0));

应用场景:适用于需要世界坐标的场景,例如光照计算、物理模拟或与其他物体交互。

视图矩阵 (View Matrix)

cpp 复制代码
// 在着色器中访问
unity_MatrixV  // float4x4
UNITY_MATRIX_V // 宏定义

作用:将顶点从世界坐标空间转换到相机的视图空间。

用法:将世界坐标转换到摄像机坐标系。

实际形式:

cpp 复制代码
[ Rx Ry Rz -dot(R, Eye) ]
[ Ux Uy Uz -dot(U, Eye) ]
[ Fx Fy Fz -dot(F, Eye) ]
[ 0  0  0        1      ]

其中R、U、F分别是相机的右、上、前方向向量,Eye是相机位置。

访问相机位置:

cpp 复制代码
float3 cameraPos = _WorldSpaceCameraPos;
// 或
float3 cameraPos = UNITY_MATRIX_V[3].xyz;

代码示例

cpp 复制代码
float4 viewPos = mul(UNITY_MATRIX_V, worldPos);

应用场景:用于需要摄像机视角坐标的场景,如计算摄像机空间的光照或实现自定义摄像机效果。

投影矩阵 (Projection Matrix)

cpp 复制代码
// 在着色器中访问
unity_MatrixP  // float4x4
UNITY_MATRIX_P // 宏定义

作用:将视图空间中的顶点转换到裁剪空间(也称为齐次裁剪空间)。

用法:在顶点着色器中执行最终的裁剪和透视变换。

类型:

  1. **透视投影矩阵:**用于模拟真实世界的视角,远处物体较小
  1. **正交投影矩阵:**用于2D渲染或工程视图,保持物体大小不变

透视投影矩阵形式:

cpp 复制代码
[ 2n/(r-l)   0        (r+l)/(r-l)   0         ]
[ 0          2n/(t-b) (t+b)/(t-b)   0         ]
[ 0          0        -(f+n)/(f-n)  -2fn/(f-n)]
[ 0          0        -1            0         ]

其中n、f为近平面和远平面距离,r、l、t、b为视锥体边界。

应用场景:常用于自定义投影模式或后处理效果。

模型-视图-投影矩阵 (MVP Matrix)

cpp 复制代码
// 在着色器中访问
UNITY_MATRIX_MVP // 宏定义

作用:将顶点从局部坐标空间直接转换到裁剪空间,是前三个矩阵的组合。

用法:在顶点着色器中,通过此矩阵将模型的顶点坐标转换为裁剪空间,用于裁剪和透视除法。

计算方式:

cpp 复制代码
MVP = P * V * M

代码示例

cpp 复制代码
// 顶点着色器中的应用 - 最常见的变换
float4 clipPos = mul(UNITY_MATRIX_MVP, float4(vertexPosition, 1.0));
// 或
float4 clipPos = UnityObjectToClipPos(vertexPosition);

应用场景:这是最常用的变换矩阵,用于将物体从局部坐标系转换到屏幕上的最终位置。

世界到对象矩阵 (World to Object Matrix)

cpp 复制代码
// 在着色器中访问
unity_WorldToObject  // float4x4

**功能:**将点从世界坐标空间转换回物体的局部坐标空间(Model矩阵的逆)。

应用:

cpp 复制代码
// 计算物体局部空间中的光照方向
float3 localLightDir = mul(unity_WorldToObject, float4(_WorldSpaceLightPos0.xyz, 0)).xyz;

特殊用途矩阵

法线变换矩阵

cpp 复制代码
// 计算方法
float3x3 normalMatrix = transpose(inverse(mat3(unity_ObjectToWorld)));

**功能:**将法线从局部空间变换到世界空间,考虑非均匀缩放的影响。

**为什么需要特殊处理:**法线需要使用模型矩阵的逆转置矩阵变换,以保持垂直性。

应用:

cpp 复制代码
float3 worldNormal = normalize(mul(normalMatrix, v.normal));
// 或使用Unity内置函数
float3 worldNormal = UnityObjectToWorldNormal(v.normal);

纹理变换矩阵

cpp 复制代码
// 访问方式
unity_MatrixVP   // 视图投影矩阵
_Object2World    // 对象到世界矩阵的旧名称
_World2Object    // 世界到对象矩阵的旧名称

**纹理投影矩阵:**用于投影纹理,如阴影贴图。

cpp 复制代码
// 阴影投影矩阵
unity_WorldToShadow[0] // 从世界空间到阴影贴图空间

矩阵操作和技巧

从矩阵提取信息

cpp 复制代码
// 从模型矩阵提取缩放
float3 objectScale = float3(
    length(unity_ObjectToWorld._m00_m10_m20),
    length(unity_ObjectToWorld._m01_m11_m21),
    length(unity_ObjectToWorld._m02_m12_m22)
);

// 从模型矩阵提取位置
float3 worldPos = unity_ObjectToWorld._m03_m13_m23;

// 从视图矩阵提取相机位置
float3 cameraPos = -mul(UNITY_MATRIX_V, float4(0, 0, 0, 1)).xyz;

矩阵分量访问

Unity使用行主序存储矩阵,但在HLSL中使用列主序数学。这导致了一些混淆:

cpp 复制代码
// 访问矩阵元素
float m11 = unity_ObjectToWorld[0][0]; // 第1行第1列
float m23 = unity_ObjectToWorld[1][2]; // 第2行第3列

// 使用特殊语法访问
float m11 = unity_ObjectToWorld._m00;
float m23 = unity_ObjectToWorld._m12;

// 按行访问
float4 firstRow = unity_ObjectToWorld[0];
// 按列访问需要额外处理
float4 firstColumn = float4(
    unity_ObjectToWorld._m00,
    unity_ObjectToWorld._m10,
    unity_ObjectToWorld._m20,
    unity_ObjectToWorld._m30
);

坐标空间转换示例

完整的渲染管线变换流程

cpp 复制代码
float4 TransformVertexToClip(float3 vertex)
{
    // 1. 从物体空间到世界空间
    float4 worldPos = mul(unity_ObjectToWorld, float4(vertex, 1.0));
    
    // 2. 从世界空间到视图空间
    float4 viewPos = mul(UNITY_MATRIX_V, worldPos);
    
    // 3. 从视图空间到裁剪空间
    float4 clipPos = mul(UNITY_MATRIX_P, viewPos);
    
    // 替代方案:直接从物体空间到裁剪空间
    // float4 clipPos = mul(UNITY_MATRIX_MVP, float4(vertex, 1.0));
    // 或
    // float4 clipPos = UnityObjectToClipPos(vertex);
    
    return clipPos;
}

屏幕空间计算

cpp 复制代码
float2 WorldToScreenPos(float3 worldPos)
{
    // 世界到裁剪空间
    float4 clipPos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0));
    
    // 透视除法
    float3 ndc = clipPos.xyz / clipPos.w;
    
    // NDC到屏幕空间 [0,1]
    float2 screenPos = float2(ndc.x * 0.5 + 0.5, ndc.y * 0.5 + 0.5);
    
    // Y轴翻转(DirectX到OpenGL)
    screenPos.y = 1.0 - screenPos.y;
    
    return screenPos;
}

Unity内置矩阵的脚本访问

cs 复制代码
// C#中访问变换矩阵
using UnityEngine;

public class MatrixExample : MonoBehaviour
{
    void Update()
    {
        // 局部到世界矩阵
        Matrix4x4 localToWorld = transform.localToWorldMatrix;
        
        // 世界到局部矩阵
        Matrix4x4 worldToLocal = transform.worldToLocalMatrix;
        
        // 视图矩阵
        Matrix4x4 viewMatrix = Camera.main.worldToCameraMatrix;
        
        // 投影矩阵
        Matrix4x4 projMatrix = Camera.main.projectionMatrix;
        
        // MVP矩阵
        Matrix4x4 mvp = projMatrix * viewMatrix * localToWorld;
        
        // 提取位置信息
        Vector3 position = localToWorld.GetColumn(3);
        
        // 提取旋转信息(不考虑缩放)
        Quaternion rotation = Quaternion.LookRotation(
            localToWorld.GetColumn(2),
            localToWorld.GetColumn(1)
        );
        
        // 提取缩放信息
        Vector3 scale = new Vector3(
            localToWorld.GetColumn(0).magnitude,
            localToWorld.GetColumn(1).magnitude,
            localToWorld.GetColumn(2).magnitude
        );
        
        Debug.Log($"Position: {position}, Scale: {scale}");
    }
}

注意事项

  • 坐标系:Unity的矩阵采用列主序(Column-Major),在Shader中矩阵乘法顺序为mul(matrix, vector)。
  • **向量乘法顺序:**矩阵乘法不满足交换律,M*v 和 v*M 有不同的结果。在Unity的HLSL中,使用 mul(M,v)。
  • **矩阵类型混淆:**将点变换和向量变换混淆。顶点变换时使用float4(position, 1.0),法线变换时使用float4(normal, 0.0),以避免平移影响。
  • **法线变换错误:**直接使用模型矩阵变换法线是错误的,应使用逆转置矩阵。
  • **透视除法遗漏:**从裁剪空间到NDC空间需要进行透视除法(除以w分量)。
  • **忘记归一化:**变换后的向量(如法线、切线)通常需要重新归一化。
  • 性能:矩阵乘法在Shader中较常见,但应尽量减少不必要的计算以优化性能。
cpp 复制代码
// 避免不必要的矩阵乘法
// 而不是:
float4 worldPos = mul(unity_ObjectToWorld, float4(vertex, 1.0));
float4 viewPos = mul(UNITY_MATRIX_V, worldPos);
float4 clipPos = mul(UNITY_MATRIX_P, viewPos);

// 使用组合矩阵:
float4 clipPos = mul(UNITY_MATRIX_MVP, float4(vertex, 1.0));
相关推荐
天人合一peng5 小时前
unity 生成标记根据背景色标记变色
unity·游戏引擎
天人合一peng9 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安10 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU210 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法10 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
迪捷软件11 小时前
显控系统虚拟仿真的工程化路径
游戏引擎·cocos2d
凡情15 小时前
android隐私合规检测
android·unity
小贺儿开发15 小时前
Unity3D 本地 Stable Diffusion 文生图效果演示
人工智能·unity·stable diffusion·文生图·ai绘画·本地化
Swift社区15 小时前
传统游戏引擎 vs 鸿蒙 System 架构
架构·游戏引擎·harmonyos
mxwin1 天前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader