CPT306 Principles of Computer Games Design 电脑游戏设计原理 Pt.3 实时图形

文章目录

  • [1. 什么是渲染?](#1. 什么是渲染?)
  • [2. Rasterization(光栅化)与 Ray Tracing(光线追踪)](#2. Rasterization(光栅化)与 Ray Tracing(光线追踪))
    • [2.1 Rasterization(光栅化)](#2.1 Rasterization(光栅化))
      • [2.1.1 3D Graphics Pipeline(3D 图形管线)](#2.1.1 3D Graphics Pipeline(3D 图形管线))
      • [2.1.2 3D 图元(3D Primitives)](#2.1.2 3D 图元(3D Primitives))
      • [2.1.3 渲染管线(Rendering Pipeline)](#2.1.3 渲染管线(Rendering Pipeline))
      • [2.1.4 图形管线(Graphics Pipeline)](#2.1.4 图形管线(Graphics Pipeline))
        • [2.1.4.1 Modeling Transformations(建模变换)](#2.1.4.1 Modeling Transformations(建模变换))
        • [2.1.4.2 Illumination(光照) / Shading(着色)](#2.1.4.2 Illumination(光照) / Shading(着色))
        • [2.1.4.3 Viewing Transformation(视图变换 / 相机变换)](#2.1.4.3 Viewing Transformation(视图变换 / 相机变换))
        • [2.1.4.4 Clipping(裁剪)](#2.1.4.4 Clipping(裁剪))
        • [2.1.4.5 Projection(投影)](#2.1.4.5 Projection(投影))
        • [2.1.4.6 Rasterization(光栅化 / 扫描转换)](#2.1.4.6 Rasterization(光栅化 / 扫描转换))
        • [2.1.4.7 Visibility / Display(可见性 / 显示)](#2.1.4.7 Visibility / Display(可见性 / 显示))
        • [2.1.4.8 Unity中的渲染](#2.1.4.8 Unity中的渲染)
        • [2.1.4.9 Common Coordinate Systems(常见坐标系)](#2.1.4.9 Common Coordinate Systems(常见坐标系))
        • [2.1.4.10 Common Space(常见空间)](#2.1.4.10 Common Space(常见空间))
    • [2.2 Ray Tracing(光线追踪)](#2.2 Ray Tracing(光线追踪))
  • [3. 变换(Transformation)](#3. 变换(Transformation))
    • [3.1 四种基本变换](#3.1 四种基本变换)
    • [3.2 Rigid-Body / Euclidean Transforms(刚体变换 / 欧几里得变换)](#3.2 Rigid-Body / Euclidean Transforms(刚体变换 / 欧几里得变换))
    • [3.3 相似变换(Similarity Transformations)](#3.3 相似变换(Similarity Transformations))
    • [3.4 线性变换(Linear Transformation)](#3.4 线性变换(Linear Transformation))
    • [3.5 仿射变换(Affine Transformation)](#3.5 仿射变换(Affine Transformation))
    • [3.6 投影变换(Projective Transformation)](#3.6 投影变换(Projective Transformation))
    • [3.7 透视投影(Perspective Projection)](#3.7 透视投影(Perspective Projection))
  • [4. 变换(Transformation)的表示](#4. 变换(Transformation)的表示)
    • [4.1 齐次坐标(Homogeneous Coordinates)](#4.1 齐次坐标(Homogeneous Coordinates))
      • [4.1.1 齐次坐标把平移变成矩阵乘法](#4.1.1 齐次坐标把平移变成矩阵乘法)
      • [4.1.2 w w w的意义](#4.1.2 w w w的意义)
    • [4.2 平移(Translation)](#4.2 平移(Translation))
    • [4.3 缩放(Scale)](#4.3 缩放(Scale))
    • [4.4 旋转(Rotation)](#4.4 旋转(Rotation))
      • [4.4.1 绕任意轴旋转(Rodrigues 公式)](#4.4.1 绕任意轴旋转(Rodrigues 公式))
    • [4.5 变换组合](#4.5 变换组合)
  • [5. 变换的意义](#5. 变换的意义)
  • [6. Unity中的Transformation(变换)](#6. Unity中的Transformation(变换))
  • [7. 世界、视图、投影变换](#7. 世界、视图、投影变换)
    • [7.1 世界、视图、投影矩阵](#7.1 世界、视图、投影矩阵)
    • [7.2 World Transformation(世界变换)](#7.2 World Transformation(世界变换))
      • [7.2.1 Unity中如何实现变换矩阵(Transformations)](#7.2.1 Unity中如何实现变换矩阵(Transformations))
    • [7.3 View Transformation(视图变换)](#7.3 View Transformation(视图变换))
    • [7.4 Projection Transformation(投影变换)](#7.4 Projection Transformation(投影变换))
      • [7.4.1 Orthographic Projection(正交投影)](#7.4.1 Orthographic Projection(正交投影))
      • [7.4.2 Viewing Volume Clipping(视体裁剪)](#7.4.2 Viewing Volume Clipping(视体裁剪))
      • [7.4.3 Unity里如何控制Projection Matrix](#7.4.3 Unity里如何控制Projection Matrix)
  • [8. Unity实践](#8. Unity实践)

1. 什么是渲染?

计算机图形学的目标是 把三维场景(3D scene)生成二维图像(2D image)。

例如在游戏或电影里,虚拟世界是三维的,但显示器只能显示二维画面,所以需要把三维信息"投影"成二维图像。

输入是场景描述。这包括物体的形状、材质、位置,光源的位置和强度,摄像机位置等。

输出结果就是一张二维图像,可以显示在屏幕上。

一种方法是用计算机模拟摄像机或人眼。

也就是说,把三维世界看作是一个真实世界,计算机像相机一样拍照,把 3D 场景"投影"到二维平面上。

一个场景中主要有三个元素:

  1. Objects(物体):三维模型,比如球、立方体、角色。
  2. Lights(光源):照亮物体的光,包括点光源、平行光等。
  3. Viewer(观察者):观察图像的人或虚拟摄像机的位置。

那我们回到渲染上。

渲染就是自动把模型生成图像的过程,可以是:

Photorealistic(逼真图像):尽量接近真实世界的效果。

Non-photorealistic(非逼真图像):卡通风格、漫画风格、技术图纸等。

用刚刚的表达那就是:

输入:2D 或 3D 模型

输出:图像(2D)

渲染方法一般有两类主要方法:

  1. Rasterization(光栅化)

    特点:把三维场景投影到二维屏幕上,然后按像素绘制。

    应用:主要用于 实时图形(Real-time Graphics),比如游戏、VR、交互式应用。

    优点:速度快,适合实时渲染。

    缺点:光影效果、反射、折射、全局光照等复杂效果难以完全逼真。

  2. Ray Tracing(光线追踪)

    特点:模拟光线从摄像机出发,与场景中的物体相交,计算光照、反射、折射。

    应用:传统上用于 离线渲染(Offline Rendering),比如电影特效。

    优点:图像逼真,可模拟复杂光照和反射。

    缺点:计算量大,渲染慢。

    趋势:随着 GPU 加速和实时光线追踪技术(RTX)发展,现在实时应用也可以部分使用 Ray Tracing

当然我们有可以把 Rasterization + Ray Tracing 结合使用:

  • Rasterization 处理大部分几何和像素
  • Ray Tracing 处理反射、阴影、折射等高质量效果

还有其他渲染方法这里不再详细叙述,比如Radiosity(辐射度)用于模拟全局光照(Global Illumination),尤其是光线在墙面、地面等表面多次反射后的光照效果。

2. Rasterization(光栅化)与 Ray Tracing(光线追踪)

2.1 Rasterization(光栅化)

步骤如下:

  1. 场景中的几何图元(如三角形、立方体、模型等)被投影到二维图像平面上。
  2. 光栅化器(Rasterizer)决定每个像素是否被某个图元覆盖。

特点:速度快,适合实时渲染(游戏、VR),但光照效果只是近似。

Rasterization(光栅化) 把三维场景投影到二维图像平面,然后决定每个像素的颜色。

左边的兔子模型是一个由三角形网格组成的三维对象。

三角形网格是光栅化的基本单位(primitive)。

虚拟摄像机或观察者看到模型时,光栅化器会把三角形投影到二维屏幕(image plane)。

图中用虚线表示从三维模型到屏幕的投影。

对于每个投影到屏幕上的三角形,光栅化器计算每个像素是否被覆盖(填充)。对每个像素计算深度(Depth),保留最靠近摄像机的像素值(z-buffer,确保前面的物体挡住后面的物体)。

伪代码如下。

复制代码
for (each triangle)
    for (each pixel)
        if (triangle covers pixel)
            keep closest hit

图片右上角显示了填充后的像素结果(灰度图或 RGB 通道分离示意)。

然后每个三角形被投影之前,会先计算照明(Compute Illumination),即判断颜色、阴影、反射等近似光照效果。

右侧三个彩色方框(红、绿、蓝)可能是示意光栅化对每个颜色通道的处理。

2.1.1 3D Graphics Pipeline(3D 图形管线)

GPU 渲染三维模型到二维图像的整个流程如下:

  1. Vertex Processing(顶点处理)
    输入:3D 模型的顶点信息(位置、法线、纹理坐标等)。
    操作:将顶点从模型空间变换到世界空间,再变换到摄像机视图空间。
    计算光照(可选)。
    输出顶点坐标,用于后续光栅化。
    图示:左边的兔子模型 → 投影到屏幕前的轮廓。
  2. Rasterization(光栅化)
    功能:把三角形投影到二维屏幕平面,生成 fragments(片元)。
    Fragments:类似像素,但尚未最终确定颜色。
    每个 fragment 有二维位置和颜色信息。
    图示:投影后的兔子轮廓 → 网格状片元。
  3. Fragment Processing(片元处理)
    功能:对每个 fragment 进行处理,最终生成屏幕上的像素。
    操作:隐藏面消除(Hidden Surface Removal):只保留最靠近摄像机的 fragment。
    颜色计算与合成(Compositing):考虑透明度、光照、纹理等,得到最终像素颜色。
    输出:最终二维图像,显示在屏幕上。

2.1.2 3D 图元(3D Primitives)

这一部分都可以参考 CPT205 知识点 CPT205 Pt.1

这里介绍6钟基本图片。

  1. Point Lists(点列表)
    只有点,没有线、没有面
    图中就是一堆独立的绿色点
    每个点互不连接
    用途:粒子效果(雪花、星星、火花)
  2. Line Lists(线段列表)
    每两个点组成一条线
    (A,B)、(C,D)、(E,F) 各自独立
    线与线之间不连接
    用途:绘制独立线段(比如辅助线)
  3. Line Strips(连续线)
    一条"连续折线"
    点按顺序连接:A → B → C → D
    不会断开
    用途:路径、曲线、轨迹
  4. Triangle Lists(三角形列表)
    每3个点组成一个独立三角形
    (A,B,C)、(D,E,F)
    每个三角形互不共享点
    用途:最常见!构建3D模型的基础
  5. Triangle Strips(三角形带)
    连续生成三角形(省点)
    前3个点 = 第一个三角形
    每增加一个点,就多一个三角形
    例如:A,B,C → 三角形1
    B,C,D → 三角形2
    C,D,E → 三角形3
    优点:更高效(减少重复顶点)
  6. Triangle Fans(三角形扇)
    所有三角形围绕一个中心点
    中心点固定(图中底部那个点)
    其他点围绕它形成"扇形"
    用途:圆形、扇形结构(比如雷达、光束)

因此3D图元是由一组"顶点(vertices)"组成的一个完整3D对象。

最简单的图元是3D坐标系中的一组点,这叫做"点列表(point list)"。

通常,3D图元是多边形。多边形是由至少三个顶点围成的一个封闭3D图形。最简单的多边形是三角形。

图形API通常使用三角形来构建所有多边形,因为三角形的三个顶点一定在同一个平面上。

那如何做个立方体(cube)呢?

其实还是用三角形拼出来。一个立方体有6个面。

顶部用三角形带制作两个三角形拼成正方形,侧面用三角形带一次制作四个正方形,底部与顶部同理。

那我们在Unity里怎么实现呢?

Unity里用Mesh(网格)来实现。

你看到的是一个普通的 Cube,但你能发现每个面都有对角线,是因为每个面其实被拆成了两个三角形。

所以Unity 也是用三角形来构建立方体的。

我们可以看右边 Inspector 面板信息。

在 Unity Inspector 里显示:Vertices: 24(顶点)、Triangles: 12(三角形)。

数学上的立方体确实只有 8 个角点,但:在图形学里是 24 个顶点,因为每个面有自己的法线(方向),每个面可能有不同纹理(UV)。所以一个角点会被拆成多个"独立顶点",我们可以简单理解为 一个角 = 3个面 也就是 3个顶点。

12 个三角形很好理解,因为每个面是 2 个三角形。

相关的代码如下。

csharp 复制代码
Mesh myMesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = myMesh.vertices;
int[] triangles = myMesh.triangles;

这里的代码可以拿到相关网格的所有顶点,但是这里的 triangles 是三角形的索引而不是坐标,它是顶点的编号。

2.1.3 渲染管线(Rendering Pipeline)

我们现在解释一下渲染管线(Rendering Pipeline)在 GPU 里是怎么一步步工作的。

  1. Input Assembly(输入组装)
    把来自内存(Memory)数据准备好:
    顶点数据(vertex buffer)
    索引数据(index buffer)
    组合成:
    点 / 线 / 三角形
  2. Vertex Shading(顶点着色器)
    处理每一个顶点
    计算位置(模型 → 屏幕)
    可以做动画、变形
  3. Rasterization(光栅化)
    把三角形变成像素
    判断哪些像素被三角形覆盖
    生成"片段(fragment)"
  4. Early Depth Test(提前深度测试)
    提前剔除看不见的像素
    被挡住的直接丢掉
    提高性能
  5. Pixel Shading(像素着色器)
    给每个像素上色
    计算颜色
    加光照、阴影、纹理
  6. Depth Test(深度测试)
    再次确认前后关系
    哪个像素在前面?
    哪个被遮挡?
  7. Render Target Output(输出)
    最终写入屏幕

2.1.4 图形管线(Graphics Pipeline)

这一部分在 CPT205 中也很详细 相关知识点

先是MC(Model Coordinate,模型坐标系)通过Modelling Transformation(模型变换)变换到WC(World Coordinate,世界坐标系),再通过Viewing Transformation(视图变换)变换到VC(Viewing Coordinate/Cammera Coordinate,相机坐标系),再通过Projection Transformation/Perspective Distortion(投影变换)变换到Perspective Coordinate(或者Clip Coordinates,裁剪坐标),再通过Clipping(裁剪)获得的是Still Clip Coordinate,再经过Homogeneous Divide(齐次除法)变换到NC(Normalization Coordinate,规范化坐标系,或者叫Window Coordinates),最后经过Window / Viewport Transform得到屏幕坐标(Screen Coordinates,或者叫Viewport Coordinates)。

下面介绍一个更加详细的理论版本:

这里输入是Geometric model(几何模型)、Lighting model(光照模型)、Camera(相机)、Raster viewport(屏幕)。

输出是每个像素的颜色值RGB(24-bit颜色)。

渲染管线会经过一系列阶段处理图元(Primitives),每个阶段都会把结果传递给下一个阶段。

我们需要注意的是管线可以用不同方式表示和实现,不同系统/引擎实现流程不完全一样,有些阶段在硬件中执行,有些在软件中执行。例如GPU:光栅化、像素着色。CPU:准备数据、逻辑控制。某些阶段可以进行优化,并支持编程扩展。

2.1.4.1 Modeling Transformations(建模变换)

3D模型是在它"自己的坐标系"中定义的(对象空间 / Object Space),建模变换把模型放到一个统一的坐标系中(世界空间)。

2.1.4.2 Illumination(光照) / Shading(着色)

这一步决定物体看起来是什么颜色、亮不亮、有没有高光。

顶点的光照(着色)取决于:

  1. 材质(material)
  2. 表面属性(法线 normal)
  3. 光源(light sources)

还可以使用局部光照模型(比如 Diffuse、Ambient、Phong 等),常见的模型有:

  1. Ambient(环境光)
    基础亮度
    不管有没有光,都有一点亮
    防止全黑
  2. Diffuse(漫反射)
    最真实的基础光
    光照角度越正 → 越亮
    常见的"自然光效果"
  3. Phong(高光)
    反光点
    金属、塑料那种"闪光点"
2.1.4.3 Viewing Transformation(视图变换 / 相机变换)

把世界空间(World Space)映射到眼睛空间(Eye Space).

具体操作是把相机位置移动到原点,并让视线方向对齐某个轴(通常是 z 轴)。

例如你相机在 (10, 0, 0),看向原点。系统会把所有物体往左移动 10,让相机变成在 (0,0,0)。

2.1.4.4 Clipping(裁剪)

这一步是将上一步结果转换到标准化设备坐标(NDC)

物体在视锥体(view frustum)之外的部分会被移除。

2.1.4.5 Projection(投影)

物体被投影到二维图像平面(屏幕空间)。

2.1.4.6 Rasterization(光栅化 / 扫描转换)

把物体光栅化为像素,在过程中进行插值(颜色、深度等)。

可以分成 3 步:

  1. 覆盖测试(Coverage)
    判断:
    哪个像素在三角形里面?
    在 → 生成 fragment(片段)
    不在 → 忽略
  2. 插值(Interpolation)
    从顶点"推算"每个像素的数据:
    比如:
    颜色(Color)
    深度(Depth)
    法线(Normal)
    纹理坐标(UV)
    例如:
    三角形三个顶点是红、绿、蓝。那么 中间的像素 = 混合颜色(渐变)
  3. 生成 Fragment(片元)
    每个像素候选点叫Fragment(片段)
    注意:Fragment ≠ 最终像素,还要经过后面的测试(深度测试等)。
2.1.4.7 Visibility / Display(可见性 / 显示)

每个像素会记录"离相机最近的物体",这里依靠的机制是 Z-buffer。

渲染时:新像素更近 → 覆盖

更远 → 丢弃

图形管线几乎每一步都在改变坐标系,变换是理解3D图形的核心。

你看到的一切操作:移动(Position)、旋转(Rotation)、缩放(Scale)、相机视角。本质都是矩阵变换(Matrix Transform)。

2.1.4.8 Unity中的渲染

Unity帮你封装了整个Graphics Pipeline,我们主要控制"模型 + 材质 + Shader"。

Mesh Renderer 负责提交 Draw Call(绘制调用)。

Mesh Renderer负责告诉GPU画哪个模型、用什么材质、在哪里画。

渲染状态(如裁剪、深度测试)和光照由材质和Shader控制。

2.1.4.9 Common Coordinate Systems(常见坐标系)

我们现在总结一下这里遇到的坐标系。

  1. Object Space(物体坐标)
    每个物体自己的坐标系。
  2. World Space(世界坐标)
    所有物体共享的坐标系。
  3. Eye Space / Camera Space(相机坐标)
    基于相机视锥体的坐标系。
  4. Clip Space / NDC(标准化设备坐标)
    坐标被压缩到 [-1,1] 范围。
  5. Screen Space(屏幕坐标)
    根据硬件(分辨率)定义的坐标。
2.1.4.10 Common Space(常见空间)

这个和前面近似。

  1. Object Space(物体空间)
    每个模型自己的坐标
    比如一个立方体以自己中心为原点
  2. World Space(世界空间)
    所有物体统一放在同一个场景里。
  3. Eye Space / Camera Space(观察空间)
    对应步骤:Viewing Transformation(视图变换)。把"世界"转换成"摄像机视角"。
  4. Clip Space(裁剪空间)
    对应步骤:Projection + Clipping。把3D视锥(camera frustum)变成一个标准盒子。
  5. Screen Space(屏幕空间)
    对应步骤:Rasterization(光栅化)。把标准坐标映射到屏幕像素。
  6. Visibility / Display(最终显示)
    使用Depth Buffer(深度缓冲)。每个像素只显示离摄像机最近"的物体。

2.2 Ray Tracing(光线追踪)

步骤如下:

  1. 从摄像机或观察者的每个像素出发,发射一条采样光线(View Ray)。
  2. 光线穿过像素平面,检测与场景中物体的交点。
  3. 计算光照、反射、折射、阴影等效果(包括 Shadow Ray)。

特点:更逼真,可以模拟真实光的物理传播,但计算量大,传统上用于离线渲染。

3. 变换(Transformation)

Transformation(变换)让你可以控制物体在哪、怎么看、怎么显示。

变换可以把模型旋转成任何方向,可以控制"相机在哪看",还可以把3D世界变成2D屏幕。

我们可以组合多个变换,模拟复杂运动。

这一部分也可以参考 CPT205知识

我们从最简单的二维开始理解。变换就是把一个点从一个坐标系"变换"到另一个坐标系。

原来的点 ( x , y ) (x, y) (x,y),变换之后变为 ( x ′ , y ′ ) (x', y') (x′,y′)。

我们可以用一个通用变换公式去概括。
x ′ = a x + b y + c x' = ax + by + c x′=ax+by+c
y ′ = d x + e y + f y' = dx + ey + f y′=dx+ey+f

a, b, d, e控制旋转(Rotate)、缩放(Scale)、拉伸(Shear)。

c, f控制平移(Translation)

3.1 四种基本变换

  1. Identity(恒等变换)
    什么都不做。
  2. Translation(平移)
    把物体移动位置。
  3. Rotation(旋转)
    围绕某个点旋转。
  4. Scaling(缩放)
    改变大小。

这些操作可以组合,而且都可以还原,但是除了把物体缩放成0以外。

3.2 Rigid-Body / Euclidean Transforms(刚体变换 / 欧几里得变换)

这是不改变形状和大小的变换。

具有两个关键性质:

  1. Preserves distances(保持距离):点与点之间的距离不变。
  2. Preserves angles(保持角度):所有角度都不变。

所以刚刚提到的四种中只有 Scaling 不包含,其余的都是刚体变换。

3.3 相似变换(Similarity Transformations)

相似变换指的是保持角度不变,长度可以改变(因为可以缩放)。所以就是形状不变,但大小可以变。

因此 相似变换 = 刚体变换 + 等比缩放。

3.4 线性变换(Linear Transformation)

线性变换 = 不包含平移的变换

也就是说原点 (0,0) 一定还是在原点。

常见的线性变换有:

  1. 缩放(Scaling),放大或缩小物体,可以是等比(uniform)也可以是不等比(non-uniform)。
  2. 反射(Reflection),镜像翻转(比如左右翻转)。
  3. 剪切(Shear),把物体"推斜"。

    线性变化符合下面两个性质。
  4. L ( p + q ) = L ( p ) + L ( q ) L(p+q)=L(p)+L(q) L(p+q)=L(p)+L(q),两个点一起变换,等于分别变换再合起来。
  5. L ( a p ) = a L ( p ) L(ap)=aL(p) L(ap)=aL(p),先放大再变换 = 先变换再放大。

这两个性质是判断是否是线性变换的参考依据,因此如果有平移,那就一定不是线性变换。

3.5 仿射变换(Affine Transformation)

仿射变换 = 线性变换 + 平移.

其的核心是preserves parallel lines(保持平行线)。

原来是平行的线 → 变换后还是平行。

因此仿射变换包含了刚刚提到的一切变换。

3.6 投影变换(Projective Transformation)

投影变换 = 模拟"透视效果"的变换.

也就是现实中的近大远小,或者平行线的尽头会相交(铁轨)。

它又包含前面提到的所有,所以这些变换的关系图如下。

3.7 透视投影(Perspective Projection)

透视投影就是用"人的眼睛"来看世界,把3D变成2D。

4. 变换(Transformation)的表示

这里也和CPT205知识点一致。

在图形学中用矩阵进行表示。

最基础的:
x ′ = a x + b y + c x' = ax + by + c x′=ax+by+c
y ′ = d x + e y + f y' = dx + ey + f y′=dx+ey+f

所以用矩阵表示就如下所示:

x ′ y ′ \] = \[ a b d e \] \[ x y \] + \[ c f \] \\begin{bmatrix} x' \\\\ y'\\end{bmatrix}= \\begin{bmatrix} a \& b \\\\ d \& e \\end{bmatrix} \\begin{bmatrix} x \\\\ y \\end{bmatrix} + \\begin{bmatrix} c \\\\ f \\end{bmatrix} \[x′y′\]=\[adbe\]\[xy\]+\[cf

简写为:
p ′ = M p + t p' = M p + t p′=Mp+t

这里 M M M是变换矩阵(改变形状), t t t是平移向量(改变位置)。

4.1 齐次坐标(Homogeneous Coordinates)

我们给坐标多加一维( w w w),让所有变换都可以用"矩阵乘法"统一表示。

正如前面所说,这里还有一个平移向量,因此无法用矩阵乘法统一表示。

所以我们现在加一个维度( w w w)。

所以现在矩阵表示为。

x ′ y ′ z ′ w ′ \] = \[ a b c d e f g h i j k l m n o p \] \[ x y z w \] \\begin{bmatrix} x' \\\\ y' \\\\ z' \\\\ w' \\end{bmatrix}= \\begin{bmatrix} a \& b \& c \& d \\\\ e \& f \& g \& h \\\\ i \& j \& k \& l \\\\ m \& n \& o \& p \\end{bmatrix} \\begin{bmatrix} x \\\\ y \\\\ z \\\\ w \\end{bmatrix} x′y′z′w′ = aeimbfjncgkodhlp xyzw 简写为 p ′ = M p p' = M p p′=Mp ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b654264568e94d1882d25c373089531d.png) #### 4.1.1 齐次坐标把平移变成矩阵乘法 x ′ = a x + b y + c x' = ax + by + c x′=ax+by+c y ′ = d x + e y + f y' = dx + ey + f y′=dx+ey+f 原来的写法如下: \[ x ′ y ′ \] = \[ a b d e \] \[ x y \] + \[ c f \] \\begin{bmatrix} x' \\\\ y' \\end{bmatrix}= \\begin{bmatrix} a \& b \\\\ d \& e \\end{bmatrix} \\begin{bmatrix} x \\\\ y \\end{bmatrix} + \\begin{bmatrix} c \\\\ f \\end{bmatrix} \[x′y′\]=\[adbe\]\[xy\]+\[cf

现在我们改成齐次坐标就可以转化为:

x ′ y ′ 1 \] = \[ a b c d e f 0 0 1 \] \[ x y 1 \] \\begin{bmatrix} x' \\\\ y' \\\\ 1 \\end{bmatrix}= \\begin{bmatrix} a \& b \& c \\\\ d \& e \& f \\\\ 0 \& 0 \& 1 \\end{bmatrix} \\begin{bmatrix} x \\\\ y \\\\ 1 \\end{bmatrix} x′y′1 = ad0be0cf1 xy1 这就完成了从 p ′ = M p + t p' = M p + t p′=Mp+t到 p ′ = M p p' = M p p′=Mp的转化。 #### 4.1.2 w w w的意义 由于通常 w = 1 w=1 w=1,所以我们可以忽视它。 \[ x ′ y ′ z ′ 1 \] = \[ a b c d e f g h i j k l 0 0 0 1 \] \[ x y z 1 \] \\begin{bmatrix} x' \\\\ y' \\\\ z' \\\\ 1 \\end{bmatrix}= \\begin{bmatrix} a \& b \& c \& d \\\\ e \& f \& g \& h \\\\ i \& j \& k \& l \\\\ 0 \& 0 \& 0 \& 1 \\end{bmatrix} \\begin{bmatrix} x \\\\ y \\\\ z \\\\ 1 \\end{bmatrix} x′y′z′1 = aei0bfj0cgk0dhl1 xyz1 如果我们使用仿射变换矩阵(Affine matrix)进行变换,那么就不会改变 w w w,也就是如果平移、旋转、缩放、剪切,都不会改变 w w w ,但是如果进行投射变换(Perspective),那么 w w w就会变。 我们可以将 w w w作为一个缩放因子。齐次坐标最终要除以 w w w才能得到真实坐标。这一步叫做归一化/齐次化(Homogenization)。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/eebb86fe4b0c4445a6bff041726ec3d8.png) 如图所示,这里 ( 8 , 10 , 2 ) → ( 8 / 2 , 10 / 2 , 2 / 2 ) = ( 4 , 5 , 1 ) (8,10,2)→(8/2,10/2,2/2)=(4,5,1) (8,10,2)→(8/2,10/2,2/2)=(4,5,1), ( x , y , w ) (x, y, w) (x,y,w)和 ( k x , k y , k w ) (kx, ky, kw) (kx,ky,kw)表示同一个点。 所以在在透视投影里远的点 → w 变大,然后除以 w → 变小,从而产生近大远小的效果。 这里的特殊情况是 w = 0 w=0 w=0,这里代表无穷远点(方向)。 ### 4.2 平移(Translation) 我们现在看在齐次坐标下怎么表示这些变换操作,我们从平移开始。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/32040b8a151e41a29f9ce7d6f295aad2.png) ![](https://i-blog.csdnimg.cn/direct/a702536e065c4c5ca8990787e5726893.png) t x t_x tx表示沿 x x x轴移动多少。 t y t_y ty表示沿 y y y轴移动多少。 t z t_z tz表示沿 z z z轴移动多少。 ### 4.3 缩放(Scale) 缩放与平移类似。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8d4df24561df4081bfea34ad6a71f03e.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a8809e16601249cdb53723c62add82c6.png) s x s_x sx表示沿 x x x轴移动多少。 s y s_y sy表示沿 y y y轴移动多少。 s z s_z sz表示沿 z z z轴移动多少。 ### 4.4 旋转(Rotation) 计算机图形学是右手坐标系,所以逆时针是正方向。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a521f4cf5c6b4637a7379bd4ec9d5265.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c2cb466b56ee47078b02e057752de6f8.png) 这里图片展示的是绕z轴,绕不同轴的结果如下。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1af46ade65ef447aa7cd1e600bf29fe2.png) #### 4.4.1 绕任意轴旋转(Rodrigues 公式) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a19a6f0d25904d659791a20eb70bd561.png) 我们现在定义一个方向向量(单位向量) ( k x , k y , k z ) (kx, ky, kz) (kx,ky,kz),它是旋转轴。 例如: k = ( 0 , 1 , 0 ) k = (0, 1, 0) k=(0,1,0)就是绕 y y y轴旋转。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a8b88ae69539437a867a04484af8545d.png) ### 4.5 变换组合 我们现在知道了如何进行单个变换操作,当然我们可以组合这些操作。 如下图所示。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/0283a75d5a0a4c86b48c646ca6dc2780.png) 我们先缩放 Scale(2,2),再平移 Translate(3,1)。 所以原始点 ( 1 , 1 ) (1,1) (1,1)就到了 ( 5 , 3 ) (5,3) (5,3)。 公式为 p ′ = T ( S p ) p' = T ( S p ) p′=T(Sp) 注意这里是矩阵,所以右边的先作用,因此 p ′ = T ( S ( p ) ) p' = T(S(p)) p′=T(S(p)),变为矩阵就是 T ⋅ S T · S T⋅S 平移矩阵: T = \[ 1 0 3 0 1 1 0 0 1 \] T = \\begin{bmatrix} 1 \& 0 \& 3 \\\\ 0 \& 1 \& 1 \\\\ 0 \& 0 \& 1 \\end{bmatrix} T= 100010311 缩放矩阵: S = \[ 2 0 0 0 2 0 0 0 1 \] S = \\begin{bmatrix} 2 \& 0 \& 0 \\\\ 0 \& 2 \& 0 \\\\ 0 \& 0 \& 1 \\end{bmatrix} S= 200020001 所以组合在一起那就是: T S = \[ 1 0 3 0 1 1 0 0 1 \] \[ 2 0 0 0 2 0 0 0 1 \] = \[ 2 0 3 0 2 1 0 0 1 \] TS =\\begin{bmatrix} 1 \& 0 \& 3 \\\\ 0 \& 1 \& 1 \\\\ 0 \& 0 \& 1 \\end{bmatrix}\\begin{bmatrix} 2 \& 0 \& 0 \\\\ 0 \& 2 \& 0 \\\\ 0 \& 0 \& 1 \\end{bmatrix}= \\begin{bmatrix} 2 \& 0 \& 3 \\\\ 0 \& 2 \& 1 \\\\ 0 \& 0 \& 1 \\end{bmatrix} TS= 100010311 200020001 = 200020311 既然是矩阵,所以下一个要注意的点是,矩阵乘法不满足交换律,也就是 T S ≠ S T TS ≠ ST TS=ST,这也是我们前一点强调的顺序问题。 比如我们前面的例子中,如果先平移再缩放的结果那就是 ( 1 , 1 ) → ( 4 , 2 ) → ( 8 , 4 ) (1,1) → (4,2) → (8,4) (1,1)→(4,2)→(8,4),和我们之前的得到的 ( 5 , 3 ) (5,3) (5,3)完全不一致。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/800174306b694802ac45ba733fa654c9.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cd477284729a4df0a851bfb4c28581a1.png) 我们在Unity里当然也符合这个规律, 模型矩阵 = T ⋅ R ⋅ S 模型矩阵 = T · R · S 模型矩阵=T⋅R⋅S,先缩放、再旋转、再平移。 ## 5. 变换的意义 变换有什么作用呢?变换在计算机图形学中无处不在。 把物体放到场景中的某个位置用的是平移(Translation)。 改变物体的形状用的是缩放(Scaling)。 复制多个物体用的是对同一个模型做不同变换(平移/旋转)。 我们将3D场景能变成屏幕画面用的是透视投影(Perspective Projection), 而我们的动画也是对模型里的人的身体的各部分不断做变换(旋转 + 平移)。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a56a2ea97f8d452eb65f2c91fb597836.png) ## 6. Unity中的Transformation(变换) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/ea132e71143b49ccb3bedbd0b75ba52f.png) Unity中有专门的Transform组件控制物体的变换中的平移、旋转、缩放。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f41eb93c787a4276bb01539ff76beed0.png) 也有专门的Camera组件修改Camera的各个属性。 Transform 组件本质上是一个 Model Matrix(模型矩阵),这个组件本质上维护了一个 4×4 变换矩阵(T · R · S)。 我们可以通过下面的代码获得其局部坐标。 ```csharp transform.localPosition ``` 通过下面的代码获得其在整个世界中的绝对位置。 ```csharp transform.position ``` 去获得真正的模型矩阵可以通过下面的代码。 ```csharp transform.localToWorldMatrix ``` 想获得视图矩阵可以通过下面的代码。 ```csharp Camera.main.worldToCameraMatrix ``` 想获得Projection Matrix(投影矩阵)可以通过下面的代码。 ```csharp Camera.main.projectionMatrix ``` 所以Unity的整个流程是local → world → camera → screen。对应的矩阵是Projection · View · Model · p。 因此Unity的渲染 = P ⋅ V ⋅ M ⋅ p = P · V · M · p =P⋅V⋅M⋅p。 ## 7. 世界、视图、投影变换 物体一开始是在自己的坐标系里。 我们可以使用世界变化从而实现移动、旋转和缩放操作,这时候物体就在世界坐标系里。 然后我们确定摄像机位置、摄像机方向从而通过 View Matrix(视图矩阵)来使用另一个变换来定位和旋转我们的视角。这样一来,物体就被转换到了 View Space(相机空间)。 最后一个变换是投影变换,它把 3D 场景投影到 2D 屏幕上。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/435f3b8b58a341939f532678ea59bb1d.png) ### 7.1 世界、视图、投影矩阵 不同矩阵分别在变换什么。 世界矩阵可以用来平移、旋转、缩放物体。 视图矩阵就是"相机"。 投影矩阵就是"相机镜头"。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/edbed7763088416da5af5a3be019859c.png) ### 7.2 World Transformation(世界变换) World Transformation(世界变换)把物体从"自己的坐标系"放到"世界坐标系"。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/291b81456c284936b23d0e35c5c966b7.png) 例如一个立方体中心在 (0,0,0),顶点在 (-1,1,-1) 这些坐标只和这个模型有关。 而世界坐标系就是将整个场景的统一坐标系(包括地面、玩家、敌人)都放进一个坐标系里。 再进行世界变换后,其就是在世界坐标系里,这里的所有点都是相对于"世界原点"的。 回到刚刚的例子中,这个模型没有任何修改,我们加上世界变换移动到 (10,0,5),那么它就进入了世界坐标系,如上图所示。 #### 7.2.1 Unity中如何实现变换矩阵(Transformations) 我们先回忆一下我们前面说的,Unity的标准顺序是先缩放 → 再旋转 → 再平移。 下面的代码进行了一个示范。 ```csharp Matrix4x4 T = Matrix4x4.Translate(new Vector3(3, 1, 0)); Matrix4x4 S = Matrix4x4.Scale(new Vector3(2, 2, 1)); Matrix4x4 TS = T * S; Matrix4x4 ST = S * T; Graphics.DrawMesh(mesh, TS, material, 0); ``` 这里前面创建了一个平移矩阵,将物体沿着x轴移动3,y轴移动1。 然后创建了一个缩放矩阵,x轴和y轴都放大2倍。 这里第三行是正确的操作,先进行缩放然后再平移(矩阵计算从右向左)。 而第四行的顺序就是先平移再缩放。 我们可以用最后一行去绘制模型。 这里依然强调一遍,矩阵乘法的交换律不成立,因此变换的顺序不同可能会得到完全不同的结果。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/da837acdd0a84b5985a08813e7c5f849.png) ### 7.3 View Transformation(视图变换) 视图变换在世界中定义相机的位置和方向,将所有的点从世界坐标系带到相机坐标系中。 在相机空间里相机永远在 (0,0,0),而且看向z轴方向。我们再强调一遍计算机图形学使用的是左手坐标系。 因此现在是世界在动而不是相机在动,因此现在不是移动相机而是将整个世界反向移动。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f9a06b46227843f89eed4975a314ff08.png) 这里图片黑色坐标系是世界空间(世界坐标系),而蓝色坐标系是相机空间(相机坐标系),目前是变换前的情况,当视图变换应用后,整个世界会移动旋转从而让相机在(0,0,0)。 ### 7.4 Projection Transformation(投影变换) 投影变换让原来的3D世界变为2D的照片。 所以这一步类似给相机选镜头。 影响投影的因素有: 1. 视野范围(FOV) 大 → 广角(wide-angle) 小 → 长焦(telephoto) 2. 近裁剪面(near plane) 离相机太近的东西会被裁掉。 3. 远裁剪面(far plane) 太远的东西也不显示。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d07c875cd0fc42499573e6bdf26fe1fc.png) DirectX 写法: ```cpp XMMatrixPerspectiveFovLH( XM_PI/4, // FOV 1.25f, // aspect 1.0f, // near 2000.0f // far ); ``` OpenGL 写法: ```cpp gluPerspective(fov, aspect, near, far); ``` 这些都是用代码实现投影变换的示例。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c1573576d318427eb7a87a48ebf764f1.png) #### 7.4.1 Orthographic Projection(正交投影) 这个与刚刚的透视投影相对立。 透视投影的效果是近大远小,正交投影的效果是远近一样大。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/849610ad18d348aca9bbe36620012de4.png) 如图所示,这里不是锥体,而是一个长方体,因此是平行的没有缩放变化。 实现的代码如下: DirectX版: ```cpp XMMatrixOrthographicLH(width, height, nearZ, farZ) ``` OpenGL版: ```cpp glOrtho(left, right, bottom, top, near, far) ``` #### 7.4.2 Viewing Volume Clipping(视体裁剪) 当我们确定了我们的视体,不在视体的部分就会被裁剪,无论我们的视体的形状,我们都可以用六个裁剪面去确定。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9836b1112d7c462b93bfb2fff81c79b1.png) #### 7.4.3 Unity里如何控制Projection Matrix ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/176f47ecb9394be4955078e18b75d2eb.png) 我们使用Unity里的Camera模块去控制Projection Matrix。 这里的参数都是前面所说的那些。 ## 8. Unity实践 我们现在如何在Unity中实践这些变换矩阵呢? 分四个步骤: 1. 定义物体。 2. 创建变换矩阵。 3. 矩阵相乘(把所有变换合成一个)。 4. 应用变换(将矩阵用在点上)。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/808ceefda18d406983fc02629043c869.png) 我们先创建一个c#脚本,然后在Update()函数里输入代码。 ```csharp // -------------------------------------------------------- // STEP 1: Create the individual transformation matrices // -------------------------------------------------------- // Translation Matrix (T) Matrix4x4 T = Matrix4x4.Translate(translation); // Rotation Matrix (R) - Unity uses Quaternions to avoid Gimbal Lock, // but it mathematically constructs the 4x4 rotation matrix under the hood. Quaternion rot = Quaternion.Euler(rotationAngles); Matrix4x4 R = Matrix4x4.Rotate(rot); // Scale Matrix (S) Matrix4x4 S = Matrix4x4.Scale(scale); // -------------------------------------------------------- // STEP 2: Combine matrices into a single Model Matrix (M) // -------------------------------------------------------- // Standard order: Scale -> Rotate -> Translate // Note: In column-major math (like Unity/OpenGL), matrix multiplication is read right-to-left. // Formula: v' = T * R * S * v modelMatrix = T * R * S; // -------------------------------------------------------- // STEP 3: Apply the Model Matrix to every vertex // (Simulating the Application Stage passing data to the Geometry Stage) // -------------------------------------------------------- for (int i = 0; i < originalVertices.Length; i++) { // Convert Vector3 (x, y, z) to a homogeneous coordinate Vector4 (x, y, z, 1) // as explained in PDF Page 40. The multiplyPoint3x4 handles the w=1 implicitly. transformedVertices[i] = modelMatrix.MultiplyPoint3x4(originalVertices[i]); } // -------------------------------------------------------- // STEP 4: Assign the transformed vertices back to the mesh // -------------------------------------------------------- mesh.vertices = transformedVertices; ``` 这样我们就可以获得一个可以控制平移、旋转、缩放的组件了。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b3f4b48160054c81975a045ce243c474.png) 然后我们像前面说的一样,第一步定义物体。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b96113d1f4014ed89f8e08b5726df254.png) 我们已经完成了第二步和第三步,然后我们选择这个刚刚添加的Cube,点击右边的Add Component以添加组件然后找到我们刚刚编辑的脚本,或者我们直接拖拽脚本到这个新添加的Cube上,这样我们就可以将这个组件加到这个Cube上,这样就完成了应用。

相关推荐
東雪木2 小时前
编程算法学习——栈与队列算法
学习·算法·排序算法
ADHD多动联盟2 小时前
什么是儿童ADHD的运动干预方案?主要有怎样的应对分心走神的疗法?
学习·学习方法·玩游戏
2501_918126912 小时前
学习所有6502写游戏地图的语句
汇编·嵌入式硬件·学习·游戏·个人开发
格林威3 小时前
工业相机图像高速存储(C++版):RAID 0 NVMe SSD 阵列暴力提速,附海康实战代码!
开发语言·c++·人工智能·数码相机·计算机视觉·工业相机·堡盟相机
sheji34163 小时前
【开题答辩全过程】以 基于微信小程序的少儿编程学习平台为例,包含答辩的问题和答案
学习·微信小程序·小程序
wincheshe4 小时前
AI Agent 辅助工具学习 --- SDD 开发与实践
人工智能·学习
C羊驼4 小时前
C/C++数据结构与算法:穷举法
c语言·c++·笔记·学习·算法
Struart_R4 小时前
Easi3R、VGGT4D、4D-VGGT论文解读
人工智能·计算机视觉·三维重建·4d·vggt
Willliam_william4 小时前
QEMU学习之路(11)— 使用VSCode调试qemu-system-riscv64
ide·vscode·学习