第二章 2D 图形学简介——基于WPF
2.1 引言
选择具体的图形学平台:微软的 Window Presentation Foundation(WPF)
WPF 是同时支持 2D 和 3D应用的现代图形学平台
2.2 2D图形流水线概述
- 2D图形平台是应用程序和显示硬件的中介,提供的功能与输入和输出相关联。
- 应用通常是将某些数据(称为 应用模型 (Application Modle), AM) 转化为图像
- 应用程序开设客户区域有两个目的:
- 一部分用于应用程序的用户界面(UI)控制
- 其余部分为 视图,用来显示 场景 绘制的结果。显示内容由 应用程序的 场景生成器 模块从 AM 中提取或导出
2.3 2D图形平台的演变
- 从整数坐标到浮点数坐标
- 早期的2D光栅图形平台采用整数坐标
- 应用程序并非对单个像素进行着色,而是通过调用绘制 基元 的程序来绘制场景。基于可以是 几何形状 或 预先读入的矩形图像(通常叫做位图)。每一个几何基元的外观由具体的属性参数控制。
- 应用程序采用整数坐标,可一对一地直接映射屏幕像素。
- 原点 (0, 0) 置于画布的左上角,x 值按从左至右的方向,y值按从上到下的方向
- 应用程序通过函数对每个基元进行设置
- 采用整数坐标系,显示大小取决于输出设备的分辨率。同样的图形,如果输出设备的分辨率更大,则输出的图形会更小
- 之后图形平台采用 浮点数坐标系将详细的几何数据与具体设备的特性分离开。 整数坐标系对应 物理坐标系统 浮点数坐标系对应 抽象坐标系统
- 早期的2D光栅图形平台采用整数坐标
- 即时模式与保留模式
- 基于整数的数据 和 基于浮点数数据这两种表示 最终形成了 两种不同目标和功能的架构:即时模式(IM) 和 保留模式(RM)
- 即时模式当绘制函数被调用时,立即执行任务,将几何基元的坐标映射为设备坐标,并在显示缓冲器中对相关像素进行着色,然后将控制权返回给应用程序。
- 在即时模式下,程序员的工作就是:对绘制图像做任何修改时,让场景生成器遍历 AM,重新生成表示场景的基元集合
- 保留模式在一个专用数据库中保留了需绘制或观看的场景的表示,我们称之场景图。
- 应用程序的 UI 和 场景生成器 使用 RM平台的 API 构建场景图。可通过编辑场景图对场景进行增量式修改。任何增量式修改都会导致 RM平台的同步显示器自动更新客户区域的绘制结果。
- RM平台除了承担显示任务外,还可以承担许多与用户交互相关的任务
- 即时模式当绘制函数被调用时,立即执行任务,将几何基元的坐标映射为设备坐标,并在显示缓冲器中对相关像素进行着色,然后将控制权返回给应用程序。
-
过程语言 与 描述语言
-
为了给出对用户接口和场景的详细描述,图形平台提供以下两种技术:
- 面向过程的代码:采用命令式编程语言编写(通常是面向对象的,但并非必须如此),可通过大量的图形 API 与显示设备进行交互。例如,Java Swing、WPF、DirectX 等
- 描述性语言:通过标记语言表达,例如 SVG 或 XAML
-
最底层:面向对象的 API
- 核心层是一组提供所有 WPF 功能的类,程序员可以在这一层使用 .Net 语言来定义应用的对外接口和行为。仅通过这一层就可以创建一个 WPF 应用程序。但另外两层可以提高开发效率。
-
中间层:XAML
- 中间层 提供了定义 API 大部分功能的另一种途径,它使用描述语言 XAML,其语法与 HTML 和 XML 类似。可通过对描述性语言的解释性执行程序支持应用的快速原型实现。
-
最高层:工具
- WPF 的最高层集成了设计师和工程师用于生成 XAML 的实用程序包括 绘图工具、3D建模工具、创建复杂用户界面的工具
2.4 使用 WPF 定义 2D场景
在 2.4 节中,我们将构造一个简单的 XAML 应用程序,来显示一个模拟时钟
-
像 HTML 一样,XAML 也给出元素的层次化结构,但其元素类型不同,它包含:
- 布局面板(例如,负责对紧密安放在一起的控件或菜单进行空间布局的 Stack Panel)
- 用户界面控件(例如,按钮 和 文本输入框)
- 用于绘制场景的 Canvas(画布)
-
第一个例子,用 XAML 创建一个Canvas(画布):
<Canvas
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
ClipToBounds="True"
>
</Canvas>
- 通常都将 ClipToBoudns 设为 True,它将保证画布是有界的,即不会再指定的矩形区域之外显示任何数据
- 我们没有指定画布大小
- XAML 具有某些语法特性(例如上面提到的 Canvas 标签中奇特的 xmlns 性质),但它们并不会掩盖标签和属性的语义。
-
采用抽象坐标系定义场景
- 在图纸上建立 抽象坐标系,在抽象坐标系中我们不需要关心其一个单位的长度的具体物理单位(是 米 还是 厘米 还是 毫米)
- 原点为 (0, 0) x 从左 到 右 y 从上 到 下
- 绘制顺序:按从后(离观察者最远处) 到 近 的顺序绘制。即最开始绘制时钟的钟面
XAML:
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" ClipToBounds="True"> <Ellipse Canvas.Left="-10.0" Canvas.Top="-10.0" Width="20.0" Height="20.0" Fill="LightGray"/> </Canvas>
- 在图纸上建立 抽象坐标系,在抽象坐标系中我们不需要关心其一个单位的长度的具体物理单位(是 米 还是 厘米 还是 毫米)
* [ ] 此时会发现抽象坐标系带来的问题
* 抽象坐标映射到显示设备上后,显示出的灰色圆太小
* 只能看到 四分之一的圆
* [ ] 这是由于我们的抽象坐标系 并不 适应WPF画布坐标系统。我们希望钟面的半径为 1英寸,而对应 WPF坐标系统,96个单位 = 1英寸。因此,我们需要定义一个 抽象坐标系 到 WPF坐标系 的 显示变换
* [ ] 我们抽象坐标系 20个单位 映射到 WPF坐标系上 希望是其96个单位,那么就是x、y轴都乘上 96/20 即 4.8。可以通过 RenderTransform 实现比例变换
```xml
<Canvas
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
ClipToBounds="True">
<Canvas>
<Ellipse
Canvas.Left="-10.0" Canvas.Top="-10.0"
Width="20.0" Height="20.0"
Fill="LightGray" >
</Ellipse>
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="4.8" ScaleY="4.8" CenterX="0" CenterY="0"/>
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
```
* [ ] 坐标系/基原则:始终选择你工作作为方便的坐标系或基,并通过变换使它和不同的坐标系或基关联起来
* [ ] 此时,我们仍是只能看到四分之一圆,我们再添加一条 平移变换:
```xml
<Canvas
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
ClipToBounds="True">
<Canvas>
<Ellipse
Canvas.Left="-10.0" Canvas.Top="-10.0"
Width="20.0" Height="20.0"
Fill="LightGray" >
</Ellipse>
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="4.8" ScaleY="4.8" CenterX="0" CenterY="0"/>
<TranslateTransform X="48" Y="48"/>
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
```
* [ ] 比例变换 和 位移变换的执行顺序不同,结果也不同。即它们是相关联的
- 构造并使用模块化模板
- WPF 中 有重用模板(控制模板 Control),对于我们指针的时针和分针,它们的形状是相同的,只是缩放不同,因此我们可以先构造 模板,然后通过 模板 来实例化出具体的元素
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" ClipToBounds="True"> <Canvas> <Canvas.Resources> <ControlTemplate x:Key="ClockHandTemplate"> <Polygon Points="-0.3,-1 -0.2,8 0,9 0.2,8 0.3,-1 " Fill="Navy"/> </ControlTemplate> </Canvas.Resources> <Ellipse Canvas.Left="-10.0" Canvas.Top="-10.0" Width="20.0" Height="20.0" Fill="LightGray" /> <Ellipse Canvas.Left="-2" Canvas.Top="-10.0" Width="4" Height="4" Fill="Blue" /> <Control Name="MinuteHand" Template="{StaticResource ClockHandTemplate}" /> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform ScaleX="4.8" ScaleY="4.8