第三章 一个古老的绘制器

1525年,阿尔布雷·丢勒 制作了一幅木刻画,展示了一种可以绘制任一形体透视图的方法。
本章我们将开发一个软件来模拟丢勒展示的方法。

  • 丢勒视角绘制算法的伪代码
Input: a scene containing some objects, location of eye-point
Output: a drawing of the objectsinitialize drawing to be blank
foreach object oforeach visible point P of oOpen shutterPlace pointer at Pif string from P to eye-point touches boundary of frameDo nothingelseHold a pencil at point where string passes through frameHold string asideClose shutter to make pencil-mark on paperRelease string
  • 该算法有三个方面值得注意,这三个方面都体现在遍历所有采样点的循环种

    • 该循环面向的是 可见 采样点,因此,判定采样点的可见性很重要(对于人类,一个点能不能看到,我们的大脑有清除的认知,但对于计算机,这是需要进行判定的)
    • 可能存在无限数量的可见采样点
    • 当细线触碰画框而不是穿过画框内的空白区域该如何处理(即物体有一部分超出 显示界面时)
    • 对于 第二个问题,可用使用 逼近 绘制来解决,即选择有限数量的采样点,使得在纸上的这些标记能够较好地呈现出物体的外形。关于这一部分本书后面会有大量的讨论,具体讨论在这里先咱暂时搁置。
    • 对于 第三个问题,剔除视域(眼睛或相机能看见的那一部分世界)外的采样点,这是图形学中一个常见的操作,可以避免将绘制时间浪费在 视域 之外。该操作成为 裁剪。这里我们会使用一个非常简单的点裁剪版本。
    • 对于 第一个问题,即 可见性问题。 根据 丢勒 所示方法,要确定采样点 P 是否可见,用户只需要将指针定在 P 点,然后观察细线是沿着一条直线直达螺丝钉的孔眼,还是途中遇到鲁特琴的某处或其它物体而产生了弯折。 即,从 鲁特琴上一点出发,向 螺丝钉孔眼(观察者) 方向射出一道射线,如果射线能够直达观察者,途中没有接触其它点,则说明 该出发点对于观察者可见。 这里,我们暂时忽略可见性检测。

  • 实现

    • 设墙上螺丝钉孔眼作为坐标系原点,记为E(作为"视点")。

    • 令绘画的画框,位于 z = 1 平面上,即观察者到画框平面的距离为 一个单位长度

    • 记 画框平面上距离孔眼最近的点为 T;其坐标为 (0, 0, 1)

    • 令 y 轴竖直向上, x 轴沿水平方向

    • 平面画框的范围由角点 ( x m i n y m i n , 1 ) (x_{min} y_{min}, 1) (xmin​ymin​,1)、 ( x m a x , y m a x , 1 ) (x_{max}, y_{max}, 1) (xmax​,ymax​,1) 定义。这里我们做简化处理,设画框是一个正方形,即长宽相等 x m a x − x m i n = y m a x − y m i n x_{max} - x_{min} = y_{max} - y_{min} xmax​−xmin​=ymax​−ymin​

    • 画框中画纸的左下角为记为 ( x m i n , y m i n ) (x_{min}, y_{min}) (xmin​,ymin​), 右上角记为 ( x m a x , y m a x ) (x_{max}, y_{max}) (xmax​,ymax​)

    • 设我们正在观察物体上的点 P(x, y, z)

    • 连接 PE 即丢勒模型中的细线,会穿过画纸,设穿过的该点为 P ‘ = ( x ‘ , y ‘ , z ‘ ) P` = (x`, y`, z`) P‘=(x‘,y‘,z‘)

    • 可以得 z ‘ = 1 z` = 1 z‘=1 ,因此 P ‘ P` P‘ 在画纸上的坐标为 ( x ‘ , y ‘ ) (x`, y`) (x‘,y‘)

    • 接下来就是计算这个 x ‘ , y ‘ x`, y` x‘,y‘

    • 对于该问题,我们可以用相似三角形进行求解。因为 z ‘ = 1 z` = 1 z‘=1 这一条件,就很好求解了。

    • 得出:

    \frac{x`} {x} = \frac{z`} {z}
    
    \frac{y`} {y} = \frac{z`} {z}
    
    • 因 z` = 1 得:
    x` = \frac{x} {z}
    
    y` = \frac{y} {z}
    
    • 这样即可得到 P` 的坐标
    • 丢勒绘制算法的一个简单实现版本
    Input: a scene cotaining some objects
    Output: a drawing of the objectsinitialize drawing to be blank
    foreach object oforeach visible point P = (x, y ,z) of oif(x_min <= (x/z) <= x_max and y_min <= (y/z) <= y_max)make a point on the drawing at location (x/z, y/z)
    
    • 为了和我们后面将采取的更一般性的方法一致。这里,我们默认 x轴正向是朝左的,现在我们要让 x轴 反过来,即让 x轴正向朝右,那么代码中的结果 x 就要加个 负号
    if(x_min <= (x/z) <= x_max and y_min <= (y/z) <= y_max)make a point on the drawing at location (-x/z, y/z)
    

  • 绘图
  • 接下来绘制一个立方体,立方体有 8个 顶点,给出它们的模型坐标
索引 坐标
0 (-0.5, -0.5, -0.5)
1 (-0.5, 0.5, -0.5)
2 (0.5, 0.5, -0.5)
3 (0.5, -0.5, -0.5)
4 (-0.5, -0.5, 0.5)
5 (-0.5, 0.5, 0.51)
6 (0.5, 0.5, 0.5)
7 (0.5, -0.5, 0.5)
  • 需要注意的是,我们的视点是(0, 0, 0),那么这样,这个立方体就包裹了视点,因此,我们让立方体在 z 轴方向移动三个单位,即 立方体所有坐标的 z += 3

  • 接下来 根据 丢勒的绘制算法,我们需要在立方体表面采样大量的点来进行绘制。但实际上这是不必要的。

    • 若 A、B 是一条边的两个端点,我们将 A 和 B 映射到图纸上的 A ‘ A` A‘ 和 B ‘ B` B‘ ,可以发现 A 和 B 之间的点,也都映射到了 A ‘ A` A‘ 和 B ‘ B` B‘ 的连线上。这一点是可以几何证明出来的。
    • 而立方体属于 线框模型,即可以用几个顶点 和 顶点之间的连线得到的边 来进行描述。那么实际上,我们只需要绘制出 立方体的顶点,然后进行连线即可。
  • 需要注意的是,空间中的直线 其在平面上的投影 不一定为 直线,如果该直线穿过了 投影中心 (螺丝孔眼/观察者),则其在平面上的投影是一个点,我们认为其是无意义的。

  • 现在给立方体模型添加一个 边表,每条边用两端点的点的索引表示:

索引 端点
0 (0, 1)
1 (1, 2)
2 (2, 3)
3 (3, 0)
4 (0, 4)
5 (1, 5)
6 (2, 6)
7 (3, 7)
8 (4, 5)
9 (5, 6)
10 (6, 7)
11 (7, 4)
  • 绘制线段时会面临两个选择

    • 是逐条边进行迭代,对每一条边,分别计算它们端点的投影位置,再将这两个投影点连接在一起。
    • 还是先遍历每一个顶点,计算各顶点的投影点,然后再基于计算得到的投影点逐边进行迭代。
    • 由于每个顶点由三条边共享,对于第一个选择 每个顶点需要计算 三次
    • 而第二个选择则需要对数据进行重复访问
    • 这两种选择取决于任务是在 硬件上实现 还是 软件上实现,对此会在后面的章节进行讨论。 我们当前选择 第二个选择。
  • 之后我们还要思考裁剪问题,在投影后,一条边的一个端点可能会在图纸内,而另一个则可能跑到图纸外了。对于这种情况,这里我们暂时不作讨论,我们现在认为 画框外的部分不会被绘制(对于 WPF 而言 确实是这样)

  • 给出此时的伪代码:

Input: a scene containing one object ob
Output: a drawing of the objectsinitialize drawing to be blank;
for (int i = 0; i < number of vertices in ob; i++)
{Point3D P = vertices[i];pictureVertices[i] = Point(-P.x/P.z, P.y/P.z)
}
for(int i = 0; i < number of edges in ob; i++)
{int i0 = edges[i][0];int i1 = edges[i][1];Draw a line segment from pictureVertices[10] to pictureVertices[i1];
}
  • 最后还需要注意程序所绘图形显示在 ”矩形窗口“ 之内,而窗口的坐标从 ( x m i n , y m i n ) (x_{min}, y_{min}) (xmin​,ymin​) 到 ( x m a x , y m a x ) (x_{max}, y_{max}) (xmax​,ymax​)。

    • 我们可以去除这一坐标区间的限制。而采用在图形库中常用的、在 x 和 y 两个方向上均为 0~1 的区间。可按下面的方法 对 x 坐标进行转换

      • 首先将 x 坐标 减去 x m i n x_{min} xmin​,这样新的坐标将位于 0 ~ x m a x − x m i n x_{max} - x_{min} xmax​−xmin​ 的范围,再让它除以 x m a x − x m i n x_{max} - x_{min} xmax​−xmin​ 新的 x 坐标就映射到 0~1 范围了。
      x_{new} = \frac {x - x_{min}} {x_{max} - x_{min}}
      
      • y 坐标同理
      • 但之前为了让画面右侧方向对应场景 x 坐标增加方向,我们改变了 x 的符号。那么 x 重映射后其实是 -1 ~ 0 范围,因此我们还要让新的 x + 1
      • 这些位于 0~1 范围的坐标常称为 标准化的设备坐标:它们给出了显示设备从左到右、从上到下的取值范围。
        • 对一个典型的显示器而言,其竖直方向坐标的取值范围值常为 0~1,而水平方向坐标的取值范围则为 0~1.33
      • 这一标准化处理公式需要记住
  • 给出伪代码:

Input: a scene containing one object ob
Output: a drawing of the objectsinitialize drawing to be blank;
for (int i = 0; i < number of vertices in ob; i++)
{Point3D P = vertices[i];double x = P.x / P.z;double y = P.y / P,z;pictureVertices[i] = Point(1 - (x - x_min) / (x_max - x_min),(y - y_min) / (y_max - y_min));
}
for(int i = 0; i < number of edges in ob; i++)
{int i0 = edges[i][0];int i1 = edges[i][1];Draw a line segment from pictureVertices[10] to pictureVertices[i1];
}

  • 程序
    我们将使用一个简单的 WPF 程序来实现该算法。
    public partial class MainWindow : Window{public MainWindow(){InitializeComponent();Canvas gp = this.FindName("Paper") as Canvas;double[,] vtable ={{-0.5, -0.5, 2.5 },{-0.5, 0.5, 2.5 },{0.5, 0.5, 2.5 },{0.5, -0.5, 2.5 },{-0.5, -0.5, 3.5 },{-0.5, 0.5, 3.5 },{0.5, 0.5, 3.5 },{0.5, -0.5, 3.5 }};int[,] etable ={{0, 1 },{1, 2 },{2, 3 },{3, 0 },{0, 4 },{1, 5 },{2, 6 },{3, 7 },{4, 5 },{5, 6 },{6, 7 },{7, 4 }};Point[] pictureVertices = new Point[vtable.Length];double x_min = -0.5;double xSpace = 1;double y_min = -0.5;double ySpace = 1;double scale = 100;for(int i = 0; i < vtable.GetLength(0); ++i){double x = vtable[i, 0];double y = vtable[i, 1];double z = vtable[i, 2];x /= z;y /= z;x = scale * (1 - (x - x_min) / xSpace);y = scale * (y - y_min) / ySpace;pictureVertices[i] = new Point(x, y);gp.Children.Add(new Dot(pictureVertices[i]));}for(int i = 0; i < etable.GetLength(0); ++i){int i0 = etable[i, 0];int i1 = etable[i, 1];gp.Children.Add(new Segment(pictureVertices[i0],
pictureVertices[i1]));}}}
  • 书中给出的 C# 代码还是为了让读者理解这一章讨论的投射算法。里面的 Dot、Segament 需要自实现,可以在 cgpp.net 即本书官网下载。


  • 局限性

  • 显然我们这里的代码非常简单,且无法应用于更广泛 和 更高级的场景中。

    • 例如如果立方体每个面有不同的颜色,这里我们只是把边绘制了出来,无法绘制立方体面的颜色
    • 我们这里也没有对光进行模拟。我们能看到物体正是因为光从物体表面射入了我们的眼睛。
    • 我们对于模型数据的表示缺乏通用性。我们可以将建模数据存入一个可被程序读取的文件,该文件具有规范的格式。例如该文件中存储的 先是顶点的数目,跟着一个订点表,然后是边的数目 跟着一个边表。

练习

假设在丢勒木刻画中,不仅标记了点,还在点附近标记了细线另一端砝码距离地面的高度。该数字即为视点距离采样点的距离。如果 鲁特琴被带走了,而又想在画中画一盏灯,且灯在鲁特琴的前面,那么我们就可以根据之前标记的距离,来把灯合成到原本的画中。
这类似于 基于深度的画面合成,其是 z-buffer 的许多应用之一。
在每个采样点处记录的深度值类似于在 z-buufer 中存储的值,尽管并非同一值。

可以使用点的索引来表示面,称为 索引面集,我们用 ( P 0 , P 1 , P 2 , . . . ) (P_0, P_1, P_2, ...) (P0​,P1​,P2​,...) 来表示一个面。对于立方体而言,我们可以用 ( P 2 − P 1 ) × ( P 1 − P 0 ) (P_2- P_1) × (P_1 - P_0) (P2​−P1​)×(P1​−P0​) 来表示该面的法向量,请构建立方体的 索引面集,使得每个面的法向量都朝外。

画个三棱柱
我们只需要修改传入的 点集 和 边集 即可

《计算机图形学原理及实践》学习笔记之第三章相关推荐

  1. 计算机图形学经典教材《计算机图形学原理及实践》作者荣获2021年计算机历史博物馆Fellow奖...

    计算机历史博物馆 计算机历史博物馆Fellow奖项目(CHM Fellow Awards Program)致力于表彰杰出的技术先驱---从无名英雄到传奇人物,表彰他们在高级计算.照亮我们的世界.推动人 ...

  2. 机器学习理论《统计学习方法》学习笔记:第三章 k近邻法

    机器学习理论<统计学习方法>学习笔记:第三章 k近邻法 3 k近邻法 3.1 K近邻算法 3.2 K近邻模型 3.2.1 模型 3.2.2 距离度量 3.2.3 K值的选择 3.2.4 分 ...

  3. 计算机网络学习笔记:第三章

    文章目录 计算机网络学习笔记:第三章 前言 3.1.概述和运输层服务 3.1.1 运输层和网络层的关系 3.1.2 因特网运输层概述 3.2.多路复用与多路分解 前言 运输层位于应用层和网络层之间,是 ...

  4. 计算机图形学原理与实践 pdf,计算机图形学理论与实践.pdf

    高校计算机教学系列教材 计算机图形学理论与实践 主 编 李春雨 副主编 邱道尹 谭同德 王玉琨 内容简介 在多年教学科研和开发的基础上K从计算机图形学的理论高度和计算机绘图的实用角度来 研究K编写这本 ...

  5. 基于《高级计算机图形学原理与实践》(西安科技大学)的学习笔记(一、二)

    文章目录 前言 一.图形和图像的概念 二.基本二维图形绘制 1.直线的扫描转换 2.中点Bresenham直线扫描转换 3.圆的扫描转换 4.走样与反走样 5.有效边表填充算法 前言 提示:以下介绍计 ...

  6. 计算机安全原理与实践_《计算机图形学原理及实践》学习笔记之第三章

    第三章 一个古老的绘制器 1525年,阿尔布雷·丢勒 制作了一幅木刻画,展示了一种可以绘制任一形体透视图的方法. 本章我们将开发一个软件来模拟丢勒展示的方法. 丢勒视角绘制算法的伪代码 Input: ...

  7. Hbase原理与实践(学习笔记一:基本概念):

    相关笔记: <Hbase原理与实践>读书笔记--1.HBase概述_凯哥多帅哦的博客-CSDN博客_hbase的实现原理1.1 HBase前世今生Google当年风靡一时的"三篇 ...

  8. 【深入理解Java虚拟机学习笔记】第三章 垃圾收集器与内存分配策略

    最近想好好复习一下java虚拟机,我想通过深读 [理解Java虚拟机 jvm 高级特性与最佳实践] (作者 周志明) 并且通过写一些博客总结来将该书读薄读透,这里文章内容仅仅是个人阅读后简短总结,加强 ...

  9. 信息安全工程师学习笔记《第三章》

    第三章 密码学基本理论 本章讲述了密码学的基本概念以及常见的密码体制.密码算法,分析了杂凑函数.数字签名.国产密码算法.安全协议等的工作原理:本章还分析了密码在网络安全方面的应用场景类型. 3.1密码 ...

最新文章

  1. python工具使用笔记
  2. linux 编译器错误,linux – GHCi – Haskell编译器错误 – /home/user/.ghci归其他人所有,IGNORING...
  3. php微信墙开发,Node.js如何开发微信墙
  4. 浅谈对xmpp的理解及应用
  5. form、document.all[].value的数字处理
  6. 泛型技巧系列:类型字典和Type Traits
  7. mybatis 使用in 查询时报错_不会Mybatis?一文教你手写实现Mybatis(超详细),吊打面试官!...
  8. scala基础之类和对象
  9. Java对象锁和类锁全面解析(多线程synchronized关键字)
  10. 安装过程中又一个问题出现了。
  11. 知乎爬虫最新 x-zse-96参数解密教程
  12. 20171016课程随笔
  13. 2017百度之星复赛:1006. Valley Numer(数位DP)
  14. leaflet 的 marker 弹框 iframe 嵌套代码
  15. 北京极通EWebs与服务器蓝屏死机
  16. Tajima‘s D群体遗传
  17. 《创业36条军规》读书笔记
  18. 电动汽车换电池要多少钱?各品牌电池更换费用大起底!
  19. C++校招面试题合集
  20. Ps 如何制作网格背景

热门文章

  1. JavaScript 数组filter方法完整介绍
  2. Vue template 标签不可以使用 v-show/v-for
  3. Android sdk-29版本上使用 jni mkdir创建文件夹失败返回-1
  4. SpringBoot的四种异步处理,写这篇文章,我自己先学到了
  5. Linux系统日志存放位置/var/log
  6. php aria2离线下载器,下载工具系列——Aria2 (几乎全能的下载神器)
  7. Hi3559 mtcnn 例程分析
  8. 网页设计必备资源(ZT)
  9. visio studio
  10. DVWA通关攻略之命令注入