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));
相关推荐
qq_431331352 小时前
Unity ML-Agents + VScode 环境搭建 Windows
windows·vscode·unity·强化学习
林枫依依3 小时前
Unity 创建、读取、改写Excel表格数据
unity·excel
EQ-雪梨蛋花汤3 小时前
【Unity笔记】Unity 编辑器扩展:一键查找场景中组件引用关系(含完整源码)(组件引用查找工具实现笔记)
笔记·unity·编辑器
李詹4 小时前
游戏开发核心技术解析——从引擎架构到攻防体系的完整技能树
架构·ue5·游戏引擎·游戏程序·3dsmax·虚幻
太妃糖耶6 小时前
URP-UGUI交互功能实现
unity·游戏引擎·shader
小浪学编程6 小时前
C#学习1_认识项目/程序结构
开发语言·前端·学习·c#·游戏引擎
萌萌的提莫队长7 小时前
Unity 跳转资源商店,并打开特定应用
unity·游戏引擎
wodownload212 小时前
srp batch
unity
虾球xz18 小时前
游戏引擎学习第236天:GPU 概念概述
c++·学习·游戏引擎