Background Flare

Linear Algebra Review

线性变换与矩阵向量乘积

线性变换 挤压拓展空间
网格线分布仍然是平行且等距分布的 且原点不变

从本质上来讲 这种变换只会对空间的基向量做改变

Linear transformation diagram

如图 (a,c) 和(b,d)代表 i j 两基向量变换后的坐标表示 (x,y)代表向量坐标

因为网格线分布仍然是平行且等距分布的

所以变换以后的该向量 仍等于 x 倍的 i 向量和 y 倍的 j 向量之和

结果如上
我们把这个计算得出来的过程与结果称之为矩阵向量乘积

即:对坐标系(两基向量)改变后得到的矩阵(变换矩阵)与 原向量 的乘积 == 变换后该向量的坐标表示
它的数学形式如图 但几何意义即上 代表一个矩阵对向量的作用(一个线性变换对向量的作用)

复合变换

顾名思义 即多个变换的叠加作用后形成的变换

Composite transformation 1

做 M1 和 M2 代表的两个变换

Composite transformation 2 Composite transformation 3 Composite transformation 4

上述运算代表这个过程
对于 M1 矩阵中的基向量 (e , g) 和 (f , h) 我们对它们采用都 M2 代表的线性变换

最终得出的矩阵 M3

即代表这两个矩阵对于基向量的复合作用 数学形式如上

性质:

M1 M2 ≠ M2 M1

(M1 M2)M3 = M1(M2 M3)

更图形学化的理论方面

对于法线变换至世界空间

Normal transformation to world space diagram 1 Normal transformation to world space diagram 2

关于 HLSL

首先 HLSL(High-Level Shader Language)默认使用 列主序(Column-Major Order) 来存储矩阵,这意味着:

  • 矩阵中的索引 A[i] 是矩阵的第 i

例如以下:

(_u_xlat0.xyz = (_u_xlat0.xyz * _u_xlat1.yyy));

(_u_xlat0.xyz = ((_in_TANGENT0.xyz * _u_xlat1.xxx) + _u_xlat0.xyz));
(_u_xlat0.xyz = ((_in_NORMAL0.xyz * _u_xlat1.zzz) + _u_xlat0.xyz));

// 按照某个向量的xyz三个分量标量 对法线 副法线和切线向量进行缩放并加和
// _u_xlat0.xyz = 副法线向量 * _in_TEXCOORD3的y标量 + _in_TANGENT0 * _in_TEXCOORD3的x标量 + _in_NORMAL0* _in_TEXCOORD3的z标量

(_u_xlat1 = (_hlslcc_mtx4x4unity_ObjectToWorld[1].yyyy * _hlslcc_mtx4x4unity_MatrixV[1]));
(_u_xlat1 = ((_hlslcc_mtx4x4unity_MatrixV[0] * _hlslcc_mtx4x4unity_ObjectToWorld[1].xxxx) + _u_xlat1));
(_u_xlat1 = ((_hlslcc_mtx4x4unity_MatrixV[2] * _hlslcc_mtx4x4unity_ObjectToWorld[1].zzzz) + _u_xlat1));
(_u_xlat1 = ((_hlslcc_mtx4x4unity_MatrixV[3] * _hlslcc_mtx4x4unity_ObjectToWorld[1].wwww) + _u_xlat1));

(_u_xlat3.xz = (_u_xlat0.yy * _u_xlat1.xy));
(_u_xlat1 = (_u_xlat1 * _in_POSITION0.yyyy));

(_u_xlat2 = (_hlslcc_mtx4x4unity_ObjectToWorld[0].yyyy * _hlslcc_mtx4x4unity_MatrixV[1]));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[0] * _hlslcc_mtx4x4unity_ObjectToWorld[0].xxxx) + _u_xlat2));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[2] * _hlslcc_mtx4x4unity_ObjectToWorld[0].zzzz) + _u_xlat2));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[3] * _hlslcc_mtx4x4unity_ObjectToWorld[0].wwww) + _u_xlat2));

(_u_xlat0.xy = ((_u_xlat2.xy * _u_xlat0.xx) + _u_xlat3.xz));
(_u_xlat1 = ((_u_xlat2 * _in_POSITION0.xxxx) + _u_xlat1));

(_u_xlat2 = (_hlslcc_mtx4x4unity_ObjectToWorld[2].yyyy * _hlslcc_mtx4x4unity_MatrixV[1]));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[0] * _hlslcc_mtx4x4unity_ObjectToWorld[2].xxxx) + _u_xlat2));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[2] * _hlslcc_mtx4x4unity_ObjectToWorld[2].zzzz) + _u_xlat2));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[3] * _hlslcc_mtx4x4unity_ObjectToWorld[2].wwww) + _u_xlat2));

(_u_xlat0.xy = ((_u_xlat2.xy * _u_xlat0.zz) + _u_xlat0.xy));
(_u_xlat1 = ((_u_xlat2 * _in_POSITION0.zzzz) + _u_xlat1));

// _u_xlat1顶点坐标从世界空间到相机空间(非裁剪)

(_u_xlat0.z = 0.001);
(_u_xlat6 = dot(_u_xlat0.xyz, _u_xlat0.xyz));
(_u_xlat6 = rsqrt(_u_xlat6));
(_u_xlat0.xy = (vec2_ctor(_u_xlat6) * _u_xlat0.xy));

(_u_xlat2 = (_hlslcc_mtx4x4unity_ObjectToWorld[3].yyyy * _hlslcc_mtx4x4unity_MatrixV[1]));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[0] * _hlslcc_mtx4x4unity_ObjectToWorld[3].xxxx) + _u_xlat2));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[2] * _hlslcc_mtx4x4unity_ObjectToWorld[3].zzzz) + _u_xlat2));
(_u_xlat2 = ((_hlslcc_mtx4x4unity_MatrixV[3] * _hlslcc_mtx4x4unity_ObjectToWorld[3].wwww) + _u_xlat2));

(_u_xlat1 = ((_u_xlat2 * _in_POSITION0.wwww) + _u_xlat1));

视图矩阵只改变顶点的坐标表示,但顶点之间的相对位置和形状保持不变:

View matrix transformation 1 View matrix transformation 2 View matrix transformation 3 View matrix transformation 4

对象空间(Object Space / Model Space)

坐标含义:

  • 顶点坐标相对于模型的本地坐标系。
  • 通常,模型的原点 (0, 0, 0) 是模型的几何中心或一个定义的基准点。
  • 没有经过任何变换,是模型在未移动、旋转或缩放时的原始坐标。

(x, y, z, w) 的意义:

  • (x, y, z):顶点的本地位置。
  • w = 1:表示这是一个位置(非方向)。

世界空间(World Space)

坐标含义:

  • 顶点坐标相对于场景的全局坐标系。
  • 通过 模型矩阵(Model Matrix) 将对象空间的坐标变换为世界空间坐标。
  • 世界空间定义了所有对象的统一坐标系,通常场景的原点 (0, 0, 0) 是世界的基准点。

(x, y, z, w) 的意义:

  • (x, y, z):顶点在世界坐标系中的位置。
  • w = 1:表示这是一个位置。
  • 如果是法线等方向性数据,w = 0(方向向量没有平移分量)。

视图空间(View Space / Camera Space / Eye Space)

坐标含义:

  • 顶点坐标相对于摄像机的位置和方向。
  • 通过 视图矩阵(View Matrix) 将世界空间的坐标变换到视图空间。
  • 在视图空间中,摄像机位于原点 (0, 0, 0),朝向 -Z 轴。

(x, y, z, w) 的意义:

  • (x, y, z):顶点相对于摄像机的方向和距离。
    • z 的值表示顶点与摄像机的距离,通常用于深度测试。
  • w = 1:表示这是一个位置。
  • 视图空间的坐标以相机(观察者)为中心,表示物体相对于相机的位置。
  • z 的值表示顶点离相机的距离:
    • 如果 z<0,说明物体在相机的前方(可见)。
    • 如果 z>0,则物体在相机的后方(不可见)。
World and view space

裁剪空间(Clip Space)

坐标含义:

  • 顶点经过 投影矩阵(Projection Matrix) 变换后,从视图空间转换到裁剪空间。
  • 裁剪空间是一个 4D 空间,坐标范围是 [-w, w],在透视投影中保留深度信息。
  • 坐标的范围将在光栅化阶段被裁剪到视锥体内部。
Clip space

(x, y, z, w) 的意义:

  • (x, y, z):经过投影变换后的坐标。 x y z 都是顶点在经过矩阵空间变换后的坐标
  • w:用于透视除法(将裁剪空间坐标转换为归一化设备坐标)。

对于正交投影

Orthographic projection diagram

正交投影矩阵

Orthographic projection matrix

这是一个复合矩阵 C = AB B 代表缩放矩阵 A 代表转移矩阵

视图空间坐标经过正交投影矩阵变换后 w 值仍然不变 = 1 而 xyz 直接表示它在规则观察体下的坐标

也就是说可以 直接按照此时的 (x, y, z) 是否位于 -1 到 1 进行裁剪

对于透视投影

透视投影矩阵

Perspective projection matrix

这之后的坐标 视图空间坐标经过正交投影矩阵变换后 (x,y,z,w) 仍然是齐次坐标

P=(x,y,z,w)P' = (x,y,z,w)

孩子们 投影矩阵这个说法好像是不准确的
实际上 投影矩阵处理后的坐标 P’ 只是转换空间后的坐标 而不是投影操作后的坐标
此时的 w 代表相机空间下的 z 坐标 表示其深度
而 xyz 三个坐标表示分别被缩放后的坐标值

  • 如果一个顶点在视锥体内,那么它变换后的坐标必须满足: 
    -w ≤ x ≤ w -w ≤ y ≤ w -w ≤ z ≤ w
  • 也就是除以 w 后 -1 ≤ x ≤ 1 -1 ≤ y ≤ 1 -1 ≤ z ≤ 1 这样的点才位于规则观察体的内部

综上所示 投影矩阵有两个目的:

  • 首先是为投影做准备。这是个迷惑点,虽然投影矩阵的名称包含了投影二字,但是它并没有进行真正的投影工作,而是在为投影做准备。真正的投影发生在后面的齐次除法(homogeneous division)过程中。而经过投影矩阵的变换后,顶点的 w 分量将会具有特殊的意义。
  • 其次是对 x、 y、 z 分量行进缩放。我们上面讲过直接使用视锥体的6个裁剪平面来进行裁剪会比较麻烦。而经过投影矩阵的缩放后,我们可以直接使用 w 分量作为一个范围值,如果 x、 y、 z 分量都位于这个范围内,就说明该顶点位于裁剪空间内。

所以 实际上的投影操作在下面这步 NDC 中完成 真正实现把三维物体转变为二维图像

归一化设备坐标(Normalized Device Coordinates, NDC)

坐标含义:

  • 通过 透视除法(将裁剪空间坐标除以 w)将坐标转换到一个标准化的范围。
  • 归一化设备坐标范围为 [-1, 1]

(x, y, z, w) 的意义:

  • (x, y):表示屏幕上的横纵坐标范围,[-1, 1] 映射到屏幕像素的整个范围。
  • z:表示深度值,通常在 [0, 1](有时为 [-1, 1] 取决于图形 API)。
  • w 不再被使用。

为什么在顶点着色器中 position 对应的 buffer 数要比实际的几何顶点数多很多?

(Reference to external note: [[为什么在顶点着色器中 position对应的buffer 数要比实际的几何顶点数多很多?]] - Content pending)

片元着色器如何依赖顶点着色器

片元着色器依赖顶点着色器的输出,但它并不直接运行在顶点上,而是运行在光栅化后生成的片元上。

插值过程:

  • 顶点着色器输出的每个顶点属性(如颜色、纹理坐标)会经过光栅化阶段进行插值。
  • 插值使用三角形内片元的 重心坐标(Barycentric Coordinates)

属性值 = w1 * 属性1 + w2 * 属性2 + w3 * 属性3

其中:

  • w1, w2, w3 是片元在三角形内的重心坐标权重。
  • 属性1, 属性2, 属性3 是三角形顶点的属性值(来自顶点着色器)。

片元着色器的输入:

  • 片元着色器接收到插值后的属性值,并使用它们计算片元的颜色。
阶段运行对象运行次数
顶点着色器每个顶点等于索引缓冲区的长度(顶点数)。
片元着色器每个片元(像素)等于屏幕上被三角形覆盖的像素数(片元数)。

OpenGL vs DirectX 转换与兼容性

VS_OUTPUT generateOutput(VS_INPUT input)
{
    VS_OUTPUT output;
    output.gl_Position = gl_Position;
    output.dx_Position.x = gl_Position.x;
    output.dx_Position.y = clipControlOrigin * gl_Position.y;

    if (clipControlZeroToOne)
    {
        output.dx_Position.z = gl_Position.z;
    } else {
        output.dx_Position.z = (gl_Position.z + gl_Position.w) * 0.5;
    }

    output.dx_Position.w = gl_Position.w;
    return output;
}

一些奇怪的矩阵

hlslcc_mtx4x4_XShadowWorldToProj

将顶点坐标从世界空间转换到光源投影空间,通常用于阴影映射技术中计算阴影坐标。