图形学实验框架 Dandelion 始末(七):未始与未了
这一篇我想讲讲 Dandelion 开始之前与 1.0 版本结束之后的故事,“未始”代表参与图形学课程改革之前所想,“未了”代表这段经历落幕后的尾声——也是我的一点心声。由于大多是主观的经历和感受,我并不强求各位读者的赞同。如有幼稚、片面之处,请作诸君之笑料。
从接受到传递
从 2022 年春季第一次担任程序设计助教到 2024 年秋季最后一次担任图形学助教,将近四年的时间过去,我的助教生涯也终于走到了尾声。大四时接触的大一同学,现在都已经临近毕业,即将开始自己的研究生旅程。自己大一大二那些忙碌又茫然的学习生活,现在回想起来都已经蒙上了时间的滤镜,如同施加了不可逆的高斯模糊。我为什么选择担任助教,又为什么坚持了四个学年,如果不仔细回忆,其实自己也有些说不上来了。
在我的求学路上,给过我帮助的师长很多。尤为幸运的是:从小学到高中,总是有老师启发并且鼓励我的学习兴趣,冲淡应试教育的刻板压力,这些经历无疑构成了接受并传递善意的底色。不过我并不打算用这篇文章回忆童年,因为我成为助教的直接推动力主要还是出现在大学本科。
我是转专业到计算机的,这源于一些高考和志愿的问题,也导致我在大二完成转入后需要补修一些大一的课程。作为退役的 OI 选手,应对《程序设计基础》(2020 年春)这样的课当然易如反掌。在联系老师开放所有题目之后,我用两天时间做完了 OJ 上一学期的作业,以及六道附加题中的四道。对我来说,程设的编程题目实在味同嚼蜡,少半是因为简单,多半是因为陈旧无聊。毕竟,从“给五个同学的语文成绩排序”到“给五个同学的语数英成绩排序”实在不是什么有趣的进展,而老旧的题目大多如此。两相对比,内核是 JSON 解析的附加题显得有趣太多了。当然,多数大一的同学还不了解 JSON,所以出题者写了一段类似 RPG 游戏的简短背景故事,把 JSON 称作“魔物语”,要求作答者将其转换为 token 表示的“人类语言”。故事一般,但这个任务让我感觉相当新奇——后来我才知道,这是编译原理的一点点应用。
六道题目主题相同,但难度依次递增。到了最后两题,我需要实现任意结构的 JSON 与 token 互转。彼时我虽然不懂编译原理,但状态机还是会写的。于是我兴冲冲设计好状态转移,发现自己无论如何都无法通过第五道题的测试。熬了三个小时之后我终于忍不住跳过它,结果竟然轻松通过了最后一道题目。由于最后两题的任务互为逆变换,我开始怀疑第五题的测试数据,并通过额外的检查代码定位了参考答案中的错误字符。跟随题目页面上的 Gitee 链接,我提交了自己的第一个 PR (Pull Request)。在和出题者——也就是 16 级 jm233333 学长——聊天的过程中,我第一次感到:如果对某些一潭死水的教学安排不够满意,或许我也能做些什么。
而谈到“不够满意”,就不得不说起上一个学期(2019 年秋)的学习经历。我作为课改小白鼠之一,在电气学院的《电路》课程实验中尝试完全自己编程求解动态电路的瞬态过程,并提交了一份求解任意阶线性动态电路的程序。整个系仅有两份作业作了这种尝试,我是其中之一。当时的我并不知道,自己实现的那个粗糙思路就是前向欧拉法。其实还有若干同学完全有能力做到这件事情,并且有几位应该可以比我做得更好。毕竟这是我第一个规模超过 2000 行代码的项目,而我当时甚至不会使用 GNU Make 或者 CMake 这样的构建工具,靠手写的 bat 脚本完成编译和链接,现在看来简直是个灾难。据说我们超额完成的任务在第二年变成了标准要求,有时我也会不厚道地偷笑几声。但看过电气学院的所作所为,再去修本院堪称灾难的《面向对象程序设计》,我不禁对计算机学院里剩下的两年多时间有了不好的预感。讲《数据结构》的张老师很有意思,授课独具一格。尽管课上的内容都是已经学过的知识,我还是愿意去听听他怎么讲——顺便,为我的家教工作备课,写些算法和数据结构的讲义。遗憾的是,数据结构的实验仍然因循守旧,我做了一些小小的、突破老旧题目的尝试,也受到了张老师的鼓励,心中隐隐的遗憾却越发浮上水面了。
回到 2020 年春季学期,罗老师讲的《优化方法基础》无疑是我在计算机学院听的第一门好课。它当然称不上完美,有限的课时导致凸优化的内容被严重压缩,罗老师的讲课速度飞快;没有足够深入的授课当然也没有足够深入的实验,总有些戛然而止的味道。但是这门课目标清晰,罗老师讲得快却并非读稿,可以随时中断、展开;实验比起探究更像是习题,但也不是套公式和填模板的问题了。总而言之,就是没有明显的短板。作为助教的芦学长已经连续五年参与课程,而恰好给分不够高的《优化方法》选课人数也比较少,让芦学长能够充分地和我们互动。他引导着我从私聊到群聊,第一次开始和同在一门课程的同学交流这门课的内容。到学期结束时,我已经和芦学长聊过专业方向的选择、聊过研究生生活,以及一些别的已经记不清的话题。我惊讶于一个学生可以如此影响一门课,以至于成为这门课底色的一部分。可天下无不散之筵席,我也为将来芦学长离开后的空白而略感担忧。
如果站得远些回望 2019-2020 学年,我看到的多半是被繁多的课程吞没的忙碌和茫然。数理、电子、硬件、编程四个方面的课一起压过来,还要加上因疫情而打乱的金工、电工、测控实习,在我还没有学会应付差事的时候,只能度过一段疲于奔命的生活。2020 年秋天,大三课程数量骤减,时间马上宽裕起来。此时我终于有些余裕将头脑从一大堆作业中抽出来,并在尝试中发掘了自己对图形学的一点点兴趣。在图形学课程上,我遇到了后来的导师。当时老师只负责讲中间的几节课,讲得其实也不算很精彩,但足够认真、思路足够连贯、不流于表面。后来的邮件沟通中,我了解到 GAMES(图形学与混合现实研讨会)和他们的公开课,打开了自学的大门。
然而在 2020 年秋天并不能简单概括为我图形学道路的开端。相较于听过几节 GAMES 101、读过几章 Learn OpenGL 的匆匆一瞥,其他方向上的尝试反而占据了我更多的时间和精力。我对贴近底层的 OS 和 CPU 同样有些兴趣。除了读过著名的 CSAPP,也尝试过基于 RISC-V 平台的 OS kernel 教学项目太素 OS (Tisu OS),还一度想要多玩玩 FPGA 和 Verilog。在此之前,我对 GitHub 和开源世界还十分陌生,心态上还是一个刚刚脱离算法题目的竞赛选手。除了管中窥豹地感受过 Linux Kernel 和 OpenStack 的规模震撼,对开源项目的印象也仅限于“特别厉害的开发者们做的一些影响很大的事情”罢了。阅读和模仿太素 OS 是我对开源祛魅的过程,而太素以及随后了解到的 xv6 和 rCore(分别是 MIT 和清华的 OS 教学项目)这样“大规模”的教学项目也向我展示了一种不同于听课写作业的全新教学形式。在此之前,我的想象力并不能弥合那些算法题目与平日接触的庞大软件系统之间的沟壑,自媒体上偶尔看到的“大学生学完体系结构自己设计 CPU”也不过是远在天边的故事。在此之后,我才明白有些具体而微的项目正是为了填补这些沟壑而存在的。
到了 2021 年春季,身心俱疲的我沉默了三个月。尽管没有休学,在多半个学期里我还是只能勉强维持被动听课的状态。讲数据库的何老师是个非常友善宽容的人,又善于活跃气氛,这是大家所公认的。所以无论对课程感不感兴趣,大家在他的课上情绪都不错。在临近期末的一次答疑中,我怀揣着一些问题去了何老师的办公室,随即惊讶于把桌面和书架占得满满的中外数据库教材。于是解决了几个问题之后,我与何老师聊起这门课的教学来。他是个知足且好脾气的人,谈起《数据库原理》年年缩减的课时却也显出不加掩饰的批评。再说到内容删减之下实验设置的缩水乃至残疾,便有些保持不住平静了。曾经的实验是关于 JDBC 和 ODBC 的,虽然与 DBMS 关联甚浅,多少还涉及些协议和理论;当下已经退化到在数据库终端上写写 SQL 语句,实在与“原理”二字毫无关联。再听何老师曾经设计的、关于修改 DBMS 的实验思路,我也只好一起惋惜。毕竟在学期中,我曾经认真考虑过参考 SQLite 去实现一个更小的 DBMS 作为自己的课程实验,而当时何老师除了答应我不必做那些无味的实验,还给我推荐过关于 SQLite 的资料。惭愧的是,我后来放弃了这个想法,做完“无味的实验”了事。
春天另外两门课程的意气风发,恰好与何老师的些许委屈形成对比。《软件定义网络》和《计算机视觉与模式识别》都是完全由一位老师掌握的选修课,也已经建成了自己的实验方案,且都与授课内容配合得相当不错。SDN 的实验文档的“手写味道”很重,行文不像教材一样严肃,细节虽然不太多但是重点的思路很突出。实验在模拟的多节点网络环境中执行,输出和调试比单机程序麻烦一些,课后我为此总结了几篇博客并同步放在知乎上,还引来过一些私信。CV 的实验则是和传统课后题一样的小单元风格,直接用 Jupyter Notebook 提供文档和代码一体的环境,让我感叹 CV/ML 领域的基础工具确实做到了非常方便顺手的程度。
在 2021 年夏天,我的本科课程告一段落,而暑期则远程参加了 NUS 的 Summer Workshop,做了一些实时渲染的基础练习。回看两年的专业学习经历,我依然是一个比较传统的学生——听课、写作业、做做课内实验,虽然会有一些自己的想法和学习的动力,但还没有学会抛开学校的课程体系向外学习。直到考研前夕,我才接触并了解到诸如 CS61A/B/C、MIT 6.S081 等等优秀的公开基础课。所以我还是很感谢校内校内那些用心讲课和设计实验的老师与助教,尽管他们的课程并不能与国际名校的顶尖课程相媲美,却也能够让我发现计算机领域存在许多有趣的学习方向。哪怕我最终没有选择这些方向,至少与那些切成十段放在 PPT 上的陈年代码、语焉不详难以理解的实验要求或是无条件把历史当经典的课程相比,这些不算一流却已经不错的课程让我看到了教学的优劣之别。而与学长的交流让我相信我也能略尽绵薄之力,在教学这杆这认真与敷衍的天平上,多加一点点善意的砝码。
文档还是代码
我是一个爱写点文字的人,讲话喜欢说尽,甚至有时啰嗦,所以爱写文档。不过教学的观点不是只看个人好恶的,我最终整理了上百页的实验手册也不只是因为我喜欢写作。
图形学的课程改革并不是从我读研才开始,而是在前一年由老师和 siyuanluo 学弟开启的。第一年采用了三个不同的单元实验,但图形学的工程属性又注定无法像 CV 实验那样打开 Jupyter Notebook 就能开始做实验。后来回看那一年知乎上尖锐的批评和倾泻的不满,老师和学弟一度有些心灰意冷——我读研之后才知道。已经记不清 2022 年暑期是谁提出的想法,可以先参考国外的实验框架看看,最终学弟帮忙选中了 CMU 的 Scotty3D。我提了加入之后的第一个建议:咱们先从全盘引入开始吧!没有自己的实验框架听起来不够高调,但是借助 CMU 成熟的方案可以保证实验的质量和连贯性。我们先不要管名声是否响亮,把完整的实验方案做出来再说。感谢当年学弟、师兄的支持,我们一起翻译 Scotty3D 的英文文档、一起做 Scotty3D 的实验,从 CMU 的实验方案中选择了五个作为 2022 年的课内实验。
相比 2021 年,2022 年课程的反响要好得多。虽然由于实验方案有分歧而不得不仓促加入了一个来自深圳大学的实验,但统一的实验手册还是得到了很不错的评价。这个学期我几乎每天泡在图形学的 QQ 群里,每天当群里的“龙王”🐲。课间、吃饭、晚上回宿舍,都在回着回不完的私聊消息。不过整个学期里我发得最多的消息,就是“看群消息”、“看公告”、“看实验手册”——因为这些东西确实帮同学(也帮我)解决了大多数问题。
2023 年夏天,学弟毕业离校,我接手了图形学的实验工作。有了一学期与同学交流的经验,再加上对 Scotty3D 与 Blender 这样优秀开源项目的一点点了解,我开始不可遏制地渴望自己的实验框架。事实上,在 2022 年秋季学期尚未结束的时候,我就已经开始向老师表达这种造轮子的渴望了。好在渴望还不至于冲昏我的头脑,2023 年初的我至少明白两件事情:我们可用的开发力量是多么有限,以及看到成品的最后期限是七月。
对前几篇文章还有印象的读者可能记得,1.0 版本的开发者只有我、JoTaiLang 和 siyuanluo 三人。而由于我并不是一个出色的架构师或者团队主管,在主体架构成型之前,我只能独自尝试。于是我从三月到六月,笔记本几乎不离身,Vim 也用出了肌肉记忆。虽然最后只得到一个三千行代码的框架,但三个多月里被删掉、重构的部分,也许还有这么多。到六月底,我终于首次从 debug 和重构的死结中挣脱出来,隐隐看到了 1.0 版本的样子。即使如此,我还是超期了——原定七月之前要完成所有功能的,而此时我还只有一个空架子 v0.2.2,一个没有对外发布过的版本。一部分原因是,我实在遇到了太多未曾预料的困难。另一部分原因在于,我需要一边学习现代 C++ 的特性和 Doxygen 的使用方法,一边尝试新的架构或者优化,进度比较缓慢。好消息是我的期末考试结束了,我总算拥有了时间上完全的自由。于是我开启了读研以来最疯狂也是最疲惫的一段工作,后来在 GitHub 上我数出了连续 42 天的 commit 记录,才明白自己疲惫到喘不上气的原因是无休工作了四十多天。同时,我也要帮助 JoTaiLang 熟悉我编写的架构以及 Doxygen 注释。所幸我们一起上了一学期的课,他已经在课上课下看过许多了。
而后来编写实验手册的进度证明,我原先以七月为开发最后期限、后来在七月初开始同时撰写实验手册的计划甚至只是堪堪及时。尽管已经有了 2022 年的实验手册,还有 GAMES 101、CMU 15/462 和 NUS Real Time Rendering 的课程资料可供参考,新的实验手册还是迟迟没有写完。因为我们惊讶地发现:即使一个实验一章的单元剧式写法很少需要直接的交叉引用,我们还是需要频繁地修改已经写好的章节,有时为了后面的章节提供基础知识,更多时候只是为了让整本实验手册“读起来感觉一样”。一直到九月开学前两天,实验手册还在不停地修订。而最终,负责先行测试实验的师弟师妹其实是拿着没有完全修改好的手册动手尝试的。
2023 年秋季开学之后我大概估计了前几个月的工作量,发现我全天投入的时候,每天大约让 Dandelion 新增 100 到 150 行代码。据此将春天那些零碎的时间折合到一起,我用在编程上的时间大概相当于两个半月。而从七月初开始撰写实验手册,到九月十几号发布 2023 版,同样也用了两个半月。我相信如果再挪一个月的时间用来编程,v1.0 应该足以达到今年 v1.1 的水平,甚至有望补上一两个之前所说的架构缺陷。然而我却不敢想如果少了一个月撰写手册的时间,我如何拿着一份缺失三分之一章节的材料开展教学。
课程文档到底是详尽好还是简略好,这样的讨论甚至争论一直存在。反对长篇大论者通常认为,太多的文字、太长的篇幅会让同学望而生畏,进而感到无从入手。我并不反对这种观点。事实上,尽管我从个人偏好与主观意愿出发站在详尽一边,面对一百页的手册也会产生相同的担忧。尤其近年来视频媒体大行其道,大家阅读长篇图文的意愿似乎大大降低,我的担心无疑更重了。最终让我反对大幅删减的不是什么“985 高校的学生应该锻炼阅读能力”的理念,而是 2022 年同学对旧实验手册的正面反馈。2023 年和 2024 年的同学们对实验手册和开发者文档普遍持正面看法,相当一部分同学甚至认为文档可以更详细些。这虽然不足以证明“长文档”总是好的,但我认为足以佐证当前大多数计算机课程的文档建设水平仍然不能满足同学的学习要求。换言之,“缺乏更充分、更详尽的文档”仍然是许多课程实验教学的一大弊病,文档建设对编程教学的重要性至少不逊于任务设计与代码实现。像发论文那样留下一份“让读者自行钻研”的“可复现代码”,在教学领域很可能造就孤芳自赏式的悲剧。
选修课:教给谁与教什么
聊过为什么开始与做过的选择,我想用如今形成的课程观点作个结尾。常言数学界有明显的精英主义倾向,其实理工学科、尤其是研究领域莫不如是。我们敬仰乃至崇拜欧拉、图灵、冯·诺依曼,作为竞赛选手仰望楼教主,作为科研狗仰望自己学科的前沿大佬,已经形成习惯。
工作并生活在一种“一个高手胜过一百个庸人”的氛围中,产生精英化教育的倾向是不足为奇的。高校申报教学规划和改革项目时,往往要以培养国家、世界一流人才为目标;作为讲授者建设课程时,我们也常常期待在自己的课堂上涌现才华横溢、出类拔萃的学生,能以非凡的速度踏入研究前沿、成为我们的同路人乃至领路人。一旦如愿以偿,自己身边便多出一位或几位志趣相类、足以切磋的朋友,他(她)能理解自己辛苦备课的理由、交流对学习工作的思考。这种期待诚然美好,有时却过于诱人了。倘若我们要寻找的只是已经具备赤诚热爱乃至明确目标的同道者,那么面向绝大多数——九成以上——同学的教学工作,就容易成为一种需要应付的任务和负累。继而,我们的课程便无形中带上了甄别或选拔的意味,向更多的同学展示出冷淡的一面。
回想自己的本科生涯,虽然上文讲了不少与老师助教交流的经历,但我并没有在很多课上表现出色,并且在很多课程里全程沉默。自始至终,我从来不是一个“看起来出类拔萃”的本科生,不是那个在课上和老师侃侃而谈的人,没有一大把满绩的课程,也没有亮眼的科研经历和文章。在沉默的大多数中,我至多是有时举手的一个,与超群依然无缘。不过,我还是喜欢计算机,并且已经从中找到了很多乐趣。“大学教育已不再是精英教育”,这句话对一所位居全国前列的“研究型高校”而言,就意味着她的多数学生并不会成为前沿的学者。已有很多人批评大学不能教会学生谋生的技能,那么如果连学习与尝试的意趣都不能传递,岂不是更加空洞了吗?我无意将精神追求置于物质生活之前,而是确实认为:对于当下的计算机学院来说,培养学生的学习意趣是远比传授实用技术更容易的事情。
计算机是一个年轻的学科,很难说它快速演进变化的过程何时结束。如不能彻底改变高校教师的工作和生活方式,想要让教师跟上技术潮流从而具备技术教学的能力,实在绝无可能。举些较陈旧的例子:譬如 JVM 引入 JIT 甚至 AOT 已久,JIT 早已广泛应用,而多少教师能通过高强度编程实践掌握新一代 JVM 的特性呢?再看 C++ 的构建系统从 Makefile 到 CMake 再到 Modern CMake,又有多少教师能使用过这些构建方式呢?更遑论 Ninja 作为 CMake 后端对 Makefile 的挑战,或者 XMake 这样的新一代跨平台构建工具了。不过,我认为这倒并不值得苛责,毕竟所谓“企业级”技术本身,正是一批从业者每天(至少)八小时工作所创造出来的。教师的一天也只有 24 小时,又如何追得上技术前沿?
我们可以用学术前沿作个类比:最新的知识在论文中,来源不一、表述各异,掺杂造假也不稀奇,只有全心投入才能理解。等到某个领域有了若干篇不错的综述,基础较好的同行便可以迅速理解这些知识了。再到各种课程、白皮书或专著出现,大家才开始“学习”而不是“探索”这些内容。如果将技术知识也划分为“论文”、“综述”、“专著”这样三个级别,那么根据“教师不可能每天写四小时代码“的事实,我估计教师所能掌握的技术内容往往局限于“综述”的级别,而更多情况下只达到“专著”的级别。在技术没有停滞的时代里,教师用落后一代甚至两代的技术授课才是常态。所以对高校而言,培养探究兴趣要比传授前沿技术更现实。
所以我想要建设的选修课,是为普通的、多数的甚至对学习有些茫然的同学开设的,是为展示一些有趣的设计和应用、介绍一些独有的方法和技术开设的。我在做的事情,也正是用十几年前的技术和知识,提供一些尚可算作新奇的实践,并尽量用有限的几个命题带动大家的兴致。于我而言,参与课程的同学往后不再涉足图形学领域才是常态,忘记图形学的知识也很平常。所有实验设置的终极意义在于:未来投身互联网大潮,或者走上考公考编等等道路之后,除了将技术作为谋生的手段以外,还能在工作或业余时保留一些尝试的乐趣。如果还能存留些许对图形领域乃至图形技术的关注,那我无疑是为一个同学培养了一些积极的爱好,可以算作对一人的育成有所贡献了。
说空话是很干瘪的,总还是举个例子更好些。渲染从来是大多数人对图形的第一印象,而现在 Dandelion 以及整个实验方案中,渲染部分占到的比重也的确最大。在 2024 学年的 v1.1(我离校前的最后一个版本)中,我们最大的重点是光栅化渲染管线以及相关的优化,而在光线追踪方面只做了老旧的 Whitted-Style 渲染,完全没有引入作为全局光照事实标准的路径追踪,以及各种近似全局光照的方案。相比之下,CMU 15/462 对光栅化的介绍比较少,它的实验方案中甚至没有将光栅化渲染整合进 Scotty3D。类似的,ETH Zurich 的图形学实验方案 Nori 也是围绕全局光照和路径追踪。
我们的选择当然有很多取舍方面的原因,比如 1.0 版本移植 GAMES 101 的光栅化渲染管线更能控制进度,再比如我们可用的课时更少、XJTU 大一大二的教学方案对编程基础的训练也不如这些顶级高校等等,这些在之前都有所提及。不过无关取舍的选择同样重要,这也是 v1.1 着重重构光栅化相关的实验,而不是全力引入路径追踪的首要原因。图形应用的一大魅力在于实时,而实时性则很大程度上来自软硬件的协同发展。所以,现代 GPU 以及软硬结合的可编程渲染管线无疑是图形技术发展的重要脉络之一,流水化而非朴素指令式的渲染则是现代渲染管线与早期图形显示的分界点。光栅化渲染管线是一个经典、成熟且至今实用的例子,而它的演进过程则为教学设计提供了很好的参考。今天光栅化渲染管线已经高度成熟,但“复现和优化流水化的渲染管线”仍然可以作为理解优化、初窥图形并行计算的一次良好实践,于是有了 v1.1 从流水化与并行化两个方面讨论光栅化渲染管线实现的设计。再看现代渲染的另一个大变革全局光照技术,它本应包含离线渲染(路径追踪)与实时渲染(各种近似全局光技术)两个相互参考和印证的视角,若只设计路径追踪的实验则不免遗憾,实时全局光技术又相当复杂,本科课程实难承载。相比之下,我更想先引入一些大多数同学都能做、做完也许还有余力和意愿再向前走一步的内容。
最后用一段 2023 年的课程结语收尾,写意地表达一下 Dandelion 未了的目标和情怀:
在计算机领域里,图形学向来是一个不太大众也不太小众的分支。如今每个人的电脑都已经离不开图形化界面,而在硬件渲染技术的革命性变化影响下,界面上所有 2D 或 3D 的图形都是调用硬件渲染接口绘制的。因此,我们可以说图形学伴随在每个人身边。然而,绝大多数人——哪怕是对计算机技术有所了解的人——谈到图形学时,留下的印象往往是“画画的技术”,或是“做游戏的技术”,进而将图形学工作者理解为“画画的人”或者“做游戏的人”。就此而言,大家对图形学还是比较陌生的。
作为一个经常使用计算机的人,相信你也曾经产生过与大家类似的印象和疑问,例如:图形学到底是做什么的?图形工作者是游戏开发者吗?计算机的算力那么强,绘制一些画面应该不难吧?在对计算机直观印象的基础上,产生这样的疑问完全正常。我们相信,正是这些好奇的疑问推动一个人自发地学习与探索。
经过一学期的学习,想必你已经初窥屏幕上那些精美画面背后复杂的技术机制,也体会到渲染、建模、制作动画这些任务是多么严格地受制于算力。这一切共同构成了图形学领域的两大追求:更美妙的场景与更极致的效率。
一些知乎网友会说,“图形学是计算机的三大浪漫之一”。但我们更愿意说:不必在乎图形学是否属于三大、四大或者五大浪漫,因为它的浪漫始终存在、无需比较、值得追求。
始末系列至此结束,也许我未来还会参与到 Dandelion 的维护中,继续这些未了的故事。