2D 计算机图形学基础速建------1
前言
计算机图形学对笔者而言是一个非常新鲜的内容,笔者主要是想研究chrominum如何在合成器的层次上加速对图像的光栅化(转向GPU的纹理Texture),但是发现一大堆概念就困住了我,所以笔者计划准备简单的整理一点2D 计算机图形学的基础备忘。
必须简单的说明,2D的计算机图形学就是研究如何在我们的显示屏幕上展现一个2D的图像
3D的笔者不计划研究,不过照常而言,3D是2D的一次拓展,不少概念仍然可以从中派生
笔者的博客有参考这个Youtube视频,但是不限于此:
Raster Image和Vector Image
从实用的角度上讲,Raster Image就是那一些可以直接被渲染的图像,因为它们的内容已经以像素网格的形式固定下来了,所以从这个角度上看,**栅格图像(Raster Image)**就是哪些已经存储了可以直接绘制到显示屏幕上的图像,比如说,他们已经存储了具体的RGB值(当然可能还会有各种更加详细的变种),我们常见的位图、照片(如JPEG, PNG)都属于这一类。
矢量图形则会略有不同,按照经典的说辞,那是用数学描述(点、线、曲线、路径、变换),可无限缩放的图像。不过这有点令人奇怪,怎么做到的呢?其实笔者认为,这类图像更加像是一种生成图像的手册指导。比如说SVG图像就是XML描述的,更加像是告诉驱动如何生成这些图像而不是存储图像可以直接渲染的信息本身,需要中间绕一层!自然,我们放缩的时候,只需要变换存储的方程信息,再送到渲染器中,这不就能拿到无失真无损的图像了!
自然,我们讨论的时候也就要区分,我们到底要准备处理什么图像?这两种图像的思路略有不同,矢量图形要多一步光栅化(Rasterization)。也就是实时的生成那些可以被上到屏幕上的Raster Image。
一般我们在这些时候用栅格图像
| 场景或内容 | 解释 | 常用格式 |
|---|---|---|
| 照片和写实图像 | 栅格图像(由数百万像素组成)是捕捉现实世界中复杂细节、光影和色彩渐变(如皮肤、云朵、风景)的唯一方式。 | JPEG, PNG, GIF, BMP, TIFF |
| 复杂的数字绘画 | 需要大量笔刷纹理、混色和细节的艺术作品,栅格格式能最好地保留这些效果。 | PNG, JPEG, TIFF |
| 网页或App中的图片 | 它们通常以固定大小显示,并且需要快速加载。栅格图像可以直接渲染,是网页 和移动应用中内容图像的标准。 | JPEG(用于照片), PNG(用于透明背景) |
| 需要进行像素级编辑 | 如果你需要使用"模糊"、"锐化"或"滤镜"等工具来操纵单个像素,你就需要栅格格式。 | Photoshop (.PSD) |
总结: 当你需要细节丰富、色彩复杂的图像 ,并且缩放需求有限时,选择栅格图像。
何时使用矢量图形 (Vector Graphic)
| 场景或内容 | 解释 | 常用格式 |
|---|---|---|
| Logo 和品牌标识 | Logo 经常需要用于名片(小)、网站(中)和广告牌(大)。矢量格式可以保证它在任何尺寸下都保持清晰锐利。 | SVG, AI (Adobe Illustrator), EPS, PDF |
| 图标 (Icons) 和 UI 元素 | 在响应式网页和不同分辨率的设备上,图标和按钮需要无损缩放。SVG (可缩放矢量图形) 是现代网页设计的首选。 | SVG, AI |
| 印刷品上的几何图形 | 如海报、传单、T恤设计、雕刻或丝网印刷中的线条和形状,矢量图形能够提供最精确的边缘。 | AI, EPS, PDF |
| 字体 (Typefaces) | 字体本质上就是矢量图形。它们的字符轮廓是用数学曲线定义的,因此无论放大多少倍,文字边缘都保持平滑。 | OTF, TTF |
总结: 当你需要简单、清晰、可无限缩放 ,并且性能要求高(如网页图标)时,选择矢量图形。
对于我们马上要研究的chrominum/ui/views而言,我们更多聚焦的是轻量级的UI控件,所以大部分是按照矢量图形的方式进行渲染的。这就是为什么传递到Skia上对之进行光栅化的处理。毕竟我们要争取无损的UI控件放缩。
像素、颜色与颜色空间
像素是屏幕的最小单元,当然,表达一个像素的内容,我们一般会采用RGB或者是更加丰富的RGBA的方式,或者是16-bit 或浮点(HDR)。
对于2D的图像学,我们完全可以把一张图像看作是一个经典的二维数组。(接触过OpenCV的朋友应该不会陌生,Mat不就是一个封装起来的2D图像嘛!)想要找到一个像素,我们需要提供一个坐标(x, y),做一个简单的二维数组向一维数组映射。很容易得到这个公式:
p i x e l _ i n d e x = ( y × w i d t h + x ) × c h a n n e l s pixel\_index = (y \times width + x) \times channels pixel_index=(y×width+x)×channels
channels不太好理解,这个表达的是通道数。比如说RGB这就有三个通道,因为前面的 ( y × w i d t h + x ) (y \times width + x) (y×width+x)只是在做最简单的映射处理,我们还要展开一个点(说像素也行)上存储了多少信息,比如说RGB这里就存储了三个信息,咱们默认RGB存储的bits数一致,故这里直接乘以三没毛病(值得一提的是每个像素占据 channels 个存储单元),为此,我们显然就可以拿到内容了
R = buffer[index + 0]; G = buffer[index + 1]; B = buffer[index + 2];
2D图形学比较常见的坐标系
这个比较重要,不管是UI编程还是图形编程。只有约定好具体的坐标系,我们才能有进一步讨论的价值。不论是绘制一个点、移动一个图标,还是渲染一段图形,你首先得搞清楚「坐标系」是什么、原点在哪、轴向哪边。
- 屏幕像素坐标(Screen Pixel Coordinate)
- 笛卡尔坐标系(Cartesian Coordinate System)
- GUI/Canvas/Web 坐标系(⭐)
屏幕像素坐标(Screen Pixel Coordinate)
这是最"直接"的坐标体系,也是底层图形设备最熟悉的。简单的说,我们的窗口也是采用类似的方式建模的。原点(0,0)在屏幕左上角 ,其中X轴向右,Y轴向下。每个坐标点对应屏幕上的一个物理像素点
比如说,笔者现在的显示器分辨率是 1920×1080:毫无悬念的,我们立马可以指出屏幕的左上角是(0,0),右下角点是 (1919, 1079),这种坐标体系和内存中帧缓冲区的布局是一致的------屏幕扫描线一行一行地从上到下绘制图像。因此,它的设计完全贴合硬件实现逻辑。
⚠️这种坐标方向(y 向下)与我们平时数学中的坐标系相反,所以在图形算法中(如矩阵变换、几何推导)需要特别留意。
笛卡尔坐标系(Cartesian Coordinate System)
笔者最为熟悉的,也是最喜欢起手建立的坐标系。这个内容我们就不再重复了,他将我们上面的Y轴翻转了一下,变成Y轴向上了。若你从屏幕像素坐标转为笛卡尔坐标:
y_cartesian = screen_height - y_screen
这就是我们常见的「上下翻转」操作。
笛卡尔坐标(数学):
▲
│
│
(0,0)────────────▶ X
Y 向上
Canvas / GUI / Web 坐标系
GUI 框架(如 Qt、Win32、UIKit、Android、HTML Canvas)普遍采用一种视觉直观、实现简单的坐标规则,这一点跟我们的屏幕坐标系是一致的,这样我们完全就能跟屏幕对应起来。
屏幕/Canvas 坐标:
(0,0)────────▶ X
│
│
▼ Y 向下
逻辑像素 vs 设备像素(Device vs Logical Pixels)
现代显示器分辨率越来越高,一个"像素"已经不再是单一含义了,我们的编程环节和显示环节正在逐步的分工化。所以,编程的时候我们可能只会关心逻辑像素,但是实际上做显示的时候还是会映射到设备像素。下面的表格就可以把事情说清楚了
| 类型 | 含义 |
|---|---|
| 设备像素(Device Pixel) | 显示屏上最小的物理发光单元 |
| 逻辑像素(Logical Pixel / CSS Pixel) | 程序或网页绘制时使用的抽象像素单位 |
高分屏设备(比如说我们的手机)通常有一个「缩放比(scale factor)」:
1 个逻辑像素 = N 个设备像素
比如说,我们的一个屏幕的缩放比是2,那么逻辑坐标 (100, 100) 实际绘制在 (200, 200) 的物理像素区域上。所以图像更清晰,但同样的逻辑布局不会变大。
光栅化(把矢量转像素)
光栅化(Rasterization)是计算机图形学中把矢量/几何描述 (点、线、三角形、曲线)转换为像素颜色的过程。它是实时渲染(例如游戏、UI、交互式可视化)中最常见的核心步骤之一:给定一个三角形和摄像机视角,**哪些屏幕像素该被绘成这个三角形的颜色?颜色是多少?**这些就是光栅化需要回答的问题。