发布于

计算机图形学中的变换与光栅化

AI 辅助翻译自英文阅读英文原文

作者

@Author: Garfield Zhu

作为计算机图形学中最基础的概念,变换光栅化是将图形内容显示为图像格式时的基本任务。

让我们先了解这些基础知识,迈入图形学的大门。

变换

基本二维变换

  • 缩放

    沿 x 轴缩放图像

    缩放后每个点 (x0, y0) 变为 (x1, y1),有:

    "y1 = y0"

    "x1 = x0 * 0.5 "

    于是定义缩放矩阵 S(x,y):

    本例中 Sx = 0.5,Sy = 1。

  • 旋转

    旋转图像(以原点为中心)

    设旋转矩阵 R(θ) 满足:

    证明:

    (1, 0) becomes (cosθ, sinθ)

    (0, 1) becomes (-sinθ, cosθ)

    因此得到 R 的定义:

  • 线性变换

    缩放和旋转都可以表示为矩阵的线性形式。

    线性变换
  • 平移

    平移就是在坐标系中移动对象。

齐次坐标

平移不同于线性变换。现在需要两个矩阵才能将它们组合起来:

同时包含线性变换和平移的矩阵。

但我们希望用一个矩阵同时表示它们。为此引入新工具:齐次坐标

定义

简单来说,齐次坐标通过为矩阵增加一个维度,描述投影等会产生高维结果的变换。换句话说,n 维空间使用 n+1 维矩阵描述。

这里讨论二维变换,因此用三元形式表示:

  • 2D point: (x, y, 1)
  • 2D vector: (x, y, 0)

相应地,变换矩阵也是三维矩阵:

齐次坐标中的变换矩阵:

齐次变换具有以下特性:

  1. 对向量而言,w 的值不会改变。
平移向量:
  1. Vector + Vector = Vector
  • [x1, y1, 0] + [x2, y2, 0] = [x1 + x2, y1 + y2, 0]
  1. Point - Point = Vector
  • [x1, y1, 1] - [x2, y2, 1] = [x1 - x2, y1 - y2, 0]
  1. Point + Vector = Point
  • [x1, y1, 1] + [x2, y2, 0] = [x1 - x2, y1 - y2, 1]
仿射变换

应用齐次矩阵后得到的变换称为“仿射变换”。

仿射变换:
借助该工具,可以用齐次形式表示上述三种变换:

当然,三种变换也可以合并为一个。

变换链

矩阵乘法按左结合计算,例如 Matrix_1 x vector

计算顺序是从右到左:

M4 x M3 x M2 x M1 x vector

顺序为 M1、M2、M3、M4……也可以依据结合律计算。

(M4 x M3 x M2 x M1) x vector

-> M5 x vector

实践

二维仿射变换矩阵非常实用。在 UI 绘制元素时,通常传入该矩阵来应用变换。

在三维变换矩阵中,第三行始终是 [0, 0, 1],无需作为参数传入。

因此二维绘图函数中的变换矩阵通常写作:

[A, B, C, D, Tx, Ty]

  • PDF 变换:
        Rect.transform(new Matrix(A, B, C, D, Tx, Ty))
    
  • SVG 变换
      <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
        <rect x="10" y="10" width="30" height="20" fill="red"
              transform="matrix(3 1 -1 3 30 40)" />
      </svg>
    

三维变换

三维仿射变换与二维类似,只需将变换矩阵扩展为四维。

观察变换

视口变换

投影变换

透视变换

待定


光栅化

分辨率

  • 图像分辨率

    以不同分辨率渲染的图像
  • 显示分辨率

    显示分辨率标准:
  • 原生分辨率 显示设备的原生分辨率由固定的物理像素决定。在固定的原生分辨率下,可以设置相同或更低的显示分辨率,也可以显示更低分辨率的图像;这本质上是缩放。

    显卡将 800×600 图像发送到 1366×768 LCD 时,显示器必须插值(缩放)图像以填满屏幕。此例中宽高比(800×600 为 4:3,1366×768 为 16:9)不同,因此图像不仅会放大,还会变形。

    • 位图受影响很大,矢量图表现更好。
    • 原生分辨率越高,缩放的副作用越不明显。
    • 建议将原生分辨率作为显示分辨率。
    发送到 1024768 原生分辨率的 800600 内容。
  • 屏幕空间

    数字屏幕空间用二维矩阵描述,一个单位表示一个像素。
  • 子像素布局

    一个像素由 RGB 子像素组成,不同显示设备的条纹布局不同。

    P30 Pro

    Google Nexus One

光栅(位图)与矢量图

放大光栅图与矢量图

光栅图(位图)

位图结构示例:
  • 光栅图形或位图是最常见的图形表示形式,采用点阵数据结构。

  • 典型数据结构:固定大小的二维矩阵

  • 显示算法:缩放

  • 典型格式:Jpeg、Png、WebP、Bmp、PPM 等

  • 用途:照片、图像、画布。

    位图示例:原始文本 PPM 格式图像:
    对应的 PNG:

矢量图

  • 矢量图形通过笛卡尔平面上的点定义。

  • 典型数据结构:可绘制对象,如点、线、矩形、圆、曲线等。

  • 显示算法:变换、光栅化

  • 典型格式:SVG、EPS、PDF 等

  • 用途:几何图形、印刷、徽标、图标、插画。

    SVG 示例:W3C SVG 文件示例:

对比

何时使用?

  • 对于高分辨率显示,矢量图形更高效;光栅图在高分辨率下可能很大。
  • 对于大量对象,光栅图形更高效。矢量图需要将每个对象存储为模型,而光栅图只存储整个画布。
SVG 与 canvas

SVG,即 Scalable Vector Graphics(可缩放矢量图形)。

  • 它是 W3C 自 1999 年制定的基于 XML 的矢量图像格式。
  • 基于 XML,同时属于 DOM,可以直接操作。

Canvas 元素。

  • 它是 HTML5 标准的一部分,支持动态、可脚本化地渲染二维图形和位图(Apple 于 2004 年在 WebKit 中初始化)。
  • 基于脚本,绘制完成后浏览器不会持续关注它。
  • 操作元素后需要重新绘制。
性能对比:

基础光栅化

什么是光栅化?它是一种采样。

举个简单例子:想象彩色墨水漂浮在水面上形成画面。将屏幕窗口沉入水中再慢慢取出,就得到一幅画,这与光栅化类似。

Bresenham 直线算法 是把线(基本图元、矢量)绘制成位图的最基础光栅化算法。

Bresenham 直线算法结果示意。
  • 第 1 步:

    将几何图形(如三角形)放入屏幕空间。
  • 第 2 步:

    采样:判断每个像素(像素空间中心)位于三角形内还是外。
  • 第 3 步:

    使用包围盒缩小采样区域。
  • 第 4 步:

    取得三角形内的像素。
  • 第 5 步:

    显示这些像素的颜色以呈现三角形。
  • 现在得到了一幅光栅化图形,但锯齿明显。提高分辨率可以改善效果(参见 MSPaint)。

抗锯齿

原理

  • 糟糕的“采样”:

    抗锯齿本质上是采样;对锐利图像进行低频采样会产生锯齿或摩尔纹
  • 采样频率如何丢失原始信号:

    分辨率不够高时,会看到“模糊”的图像。
  • 采样前先模糊图像:

    如果分辨率不够高,就会看到“模糊”的图像。

抗锯齿方法

  • 使用低通滤波器滤除高频信号:

    使用卷积(或对单元内颜色取平均)。
  • SSAA/MSAA(超采样/多重采样抗锯齿):

    在单个像素中使用更高的采样比率。
    比较不同采样效果。

    在像素中使用单次采样:

    在像素中使用多重采样:

    通过卷积/平均降至低分辨率。

字体光栅化

参见 Wiki 上的字体光栅化

如今字体是矢量图,可以从极小缩放到极大。字体光栅化就是将矢量路径映射到像素矩阵。

rasterization

未经过抗锯齿的原始光栅字体:

示例

字体抗锯齿

  • 不同的抗锯齿级别

    • 不带 hint 的基础抗锯齿

      示例
    • 带 hint 的抗锯齿

      示例
    • RGB 平板上的子像素渲染

      示例
  • 字体 Hinting

    在屏幕文字显示中,字体 hinting 指定需要插值的基础像素,从而更清晰地渲染字体。

    -> 字体编辑器可以自动执行 hinting。

    -> 高质量商业字体通常会手动 hinting,以获得最清晰的外观。

    示例
  • 使白色显示的子像素渲染

    示例
  • 现代字体光栅化

如今同时支持 Windows 和 macOS 的主流字体光栅化库是 FreeType

  • FreeType 中采用子像素渲染的小字号字符“e”。

    子像素渲染的“e”