《拓扑图画书》的渲染版
文章摘要
这是一个浏览器内的交互式工具,目标非常明确:用算法复现 George K. Francis 1987 年那本《A Topological Picture Book》(Springer)里的手绘风格曲面插图,并让任意曲面都能被实时渲染成那种钢笔排线的样子。页面自称风格取法 Francis、Apéry、以及 Hilbert–Cohn-Vossen 一脉中世纪中叶的画法。
操作方式是直接的:拖动旋转模型,按住 Shift 拖动做滚转,滚轮拉近;触屏上单指旋转、双指捏合缩放并旋转。
曲面的两种定义方式
- 参数式:直接输入 x(u,v)、y(u,v)、z(u,v) 三个表达式,设定 u 和 v 的取值区间与细分段数,还可以选择追踪自相交产生的双重曲线(double curve)。
- 隐式:输入一个等于零的三元函数,设定包围盒半宽和网格分辨率。零集通过行进四面体(marching tetrahedra,即 Marching Cubes 的四面体变体)三角化,法向量取自梯度。
公式直接写成 JavaScript 表达式,^ 被接受为乘方;可用的函数包括常见三角、反三角、双曲、指数对数、开方、立方根、绝对值、符号、幂、极值、取整、斜边长以及圆周率等常量。取值区间框里也可以写表达式。
内置了若干便于探索经典例子的辅助函数:boy(u,v) 返回实射影平面的 Bryant–Kusner 浸入;torusknot(u,v,p,q,R,r,a) 返回环面上 (p,q) 环面纽结周围的管状面;apery(u,v,n,k) 是 Apéry 的笛卡尔族,参数取 n=2、k=1 是 Morin 曲面,n=3、k=1 是 Boy 曲面;kusner(u,v,p,d) 是 Kusner–Bryant 族,p=2 是 Morin 曲面,奇数 p 时覆盖实射影平面一次(p=3 即 Boy 曲面),d 用于平移反演中心。作者还特地说明,他对 Boy 曲面所用参数化做了数值验证——对映边界粘合与三重对称性在机器精度下成立;Kusner 族一般 p 形式中的常数也是通过数值检验反演前曲面确实是极小曲面而确定的(流传的某个 p=2 版本用了错误的常数,并非极小)。
渲染管线:画的是笔触,不是像素
这是整个项目技术上最讲究的部分,作者在「来源与方法」一节里交代得很清楚:
- 轮廓线:光滑轮廓取自网格上法向量与视线方向点积的零集,使用插值法向量求取,因而能串接成光滑曲线;边界线和浸入产生的双重曲线作为额外的曲线链加入。所有这些都被绘制成带锥度的宽头钢笔笔触——笔宽在阴影一侧和靠近观察者的一侧加重,并带有连贯的「手抖」。
- 可见性:在 GPU 上解决。有一个专门的隐藏线通道,把被遮挡部分画成虚线;每条近处轮廓线下方还有一圈单侧的「纸张光晕」,用来切断它后方的线条。隐藏线可以选择虚线、淡化或直接省略,线条交叉处的光晕也可开关。
- 排线(hatching):这是复现手绘质感的关键。排线是主曲率线场的流线,在构建时以三层嵌套密度追踪(第二层沿另一个主方向,用于交叉排线);明暗决定哪一族流线被落墨、以及每条笔触在何处淡出,高光处则保持纸面空白。作者特别指出一个容易出错的细节:主方向按带符号的曲率排序而非按大小排序,这样两族排线在两个主曲率互为相反数的轨迹上仍能保持连续。除了主曲率方向,还提供沿参数线、沿空间平面截线、以及沿画面平面(屏幕空间)三种条纹模式。
- 墨水与纸张:笔触末端略微出头(取自素描式线条渲染的做法),起笔处有墨水积聚,边缘向纸张纹理里渗化。作者诚实地标注,后两者是本页手工调出的效果(一条被类纤维噪声控制边缘的加宽色带),并非取自论文。
- 标注:标签是手写体,用引线钉在曲面上的具体点位,可拖动移动、双击删除,当所钉的点被遮挡时自动变暗。
- 另有光照方位与高度、对比度、环境光、轮廓明暗、墨色与纸色等参数,以及可选的 Sobel 边缘滤镜和 2 倍超采样的精细渲染模式。成果可导出为 PNG。
参考文献
页面列出了一份相当扎实的非真实感渲染(NPR)文献表,几乎是一份该领域的小型导读:Bénard 与 Hertzmann 的线条画教程(轮廓管线)、Hertzmann 的 SIGGRAPH 99 课程笔记(由插值顶点法向量求轮廓)、Hertzmann 与 Zorin 的《Illustrating Smooth Surfaces》(沿主曲率方向排线、仅在暗部交叉排线、高光留白——作者自陈这是本项目的主要依据)、Jobard 与 Lefer 的等距流线算法(排线的间距规则)、Appel 等人的光晕线消隐、Northrup 与 Markosian 的艺术化轮廓、Strothotte 等人关于出头与抖动笔触的工作、Salisbury 等人的交互式钢笔墨水插画、Praun 等人的实时排线(此处用对象空间笔触实现,因此排线不会「游移」)、Winkenbach 与 Salesin 的计算机生成钢笔画,以及 Elber 的等参曲线线条画。此外还有 Lorensen–Cline 的 Marching Cubes 与 Doi–Koide 的四面体变体、Möller–Trumbore 的射线三角形求交(用于双重曲线计算)、Saito–Takahashi 的法向与深度缓冲边缘提取。
HN 评论精华
145 分,评论不算多但质量很高,而且作者本人到场并当场改进了产品。
作者现身
e-infinityspace 确认这是自己的项目并感谢关注,同时透露了技术核心:应用主要基于 Hertzmann 与 Zorin 的那篇论文的思路,使用主曲率方向来决定排线方向。他还表示已经根据反馈让页面对移动端和小屏设备更友好了——这发生在讨论进行期间。
来自 Francis 学生的证词
讨论中最动人的两条来自曾师从 George Francis 的人。
sevensor 说自己多年前是 George 的本科生,看到这些渲染「绝对是一种喜悦」。他回忆道:George 会在任何可用的表面上涂画这些图形——黑板、笔记本纸、餐巾纸,什么都行,而且常常用钢笔。他还补充了一个很多人不知道的细节:Francis 本人也非常热衷于用计算机做可视化,写过若干在 CAVE 沉浸式环境中运行的程序。
jjtheblunt 接上:他是 Francis 的研究生,「你描述的一切都能引起共鸣,将近四十年后我仍与他保持联系。那是研究生阶段最快乐最有趣的一些时光,有着无穷无尽的极客乐趣。我很庆幸曾在他门下学习。看他作画是件不可思议的事。」
技术讨论
ekzhang 提出了最切中要害的技术问题:手绘线条的间距和风格实在完美,即便用了主曲率场,要得到这样干净的画面难不难? 作者的回复(即上文那条)正是对此的回应。
fogleman 的留言代表了一类共同的渴望:他受同样的来源启发,一直想要一种能为任意 3D 模型(比如 STL 文件)渲染出此类图画的方法,为此折腾了不少,最近还借助 LLM 试过,但仍未达到自己想要的水准。「总有一天会的!」
tobytylam 分享了自己做过的一个更原始的绘图器:可以在 UV 平面上作画并把曲线投影到曲面上(对理解基本群很有帮助),还包含一个时间参数用于表现同伦。他建议这两点可以作为功能想法。
mif 贡献了一条自嘲式的数学小插曲:他本以为自己能想出立方体的隐式表达式,结果想不出来(「多尴尬」),答案是 x、y、z 三者绝对值的最大值减去一个常数。
可用性反馈(并被采纳)
ks2048 询问在移动端有没有办法隐藏菜单——他只能看到几个像素的画面,但「看起来确实挺酷」。boguscoder 建议横屏会好一些。
sheept 给出了最专业的一条 bug 报告:除了让侧边栏在小屏上可收起之外,建议给画布加上禁用默认触摸手势的属性,否则旋转模型的拖动会被解释成页面滚动;他指出至少在 Android Chrome 上,拖动旋转只能短暂生效就会被打断。
throwaway81523 报告说自己只看到一张图(首页的克莱因瓶),问是不是还有更多,怀疑是浏览器设置问题。随后他自己找到了答案并回复:原来要用「预设」下拉菜单来切换不同曲面。他顺带提出了一个好问题:让其他人添加新曲面的可行性如何?(这其实指向了页面本身的一个发现性问题——参数化输入框就在那里,但新用户未必意识到。)
纯粹的赞叹
mathgenius(发帖人)、gotfork(「太棒了,多么不可思议的项目」)、medwards666(「美极了」)、aanet(「出色的项目,喜欢这些精巧的功能,我能在这儿泡一整天」)、nxpnsv(「很漂亮,调参数玩起来很有意思」)等多人留下了简短的正面评价。aanet 还提了个请求:很想读一读实现细节,用了什么技术栈、什么技法——值得一提的是,页面底部的「来源与方法」一节其实已经相当详尽地回答了这个问题,这或许说明该节的位置不够显眼。