在本文中使用的是基于空间的3D模型的描边,着手于1条边的2个邻接面,即退化四边形进行边缘检测和绘制。本文读者默认为有图形学基础和编写Shader基础,若没有请先去把这些基础学习一下,再来阅读本文,不然可能会有阅读障碍。git
3D模型描边有两种方式,一种是基于图像,即在全部3D模型渲染完成一张图片后,对这张图片进行边缘检测,最后得出描边效果。一种是基于空间,即针对3D模型的三角面三个顶点构成的线条作边缘检测(注:和基于图像的边缘检测的检测方式是不一样的,但都叫边缘检测)。在本文中使用的是基于空间的3D模型的描边。github
空间的3D模型的描边的边是有类型的,大体总结有4种:轮廓边、边界边、折缝边、材质边。其中前3种是本文认为卡通渲染所必须有的。算法
图1 空间中的边分类 来自文献[1]编程
轮廓边的检测根据定义就是,1条边的2个邻接面的法线分别和视线方向的点乘结果再相乘,结果小于0就是轮廓边。数学上表达就是:(N1·V)*(N2·V) < 0,N1和N2是2个邻接面的法向量,V是视线方向。学习
边界边的检测比较简单,若是1条边只有1个面邻接,那么这条边就是边界边。优化
折缝边的检测须要使用1个角度参数α,1条边的2个邻接面的夹角和这个参数作大小比较。通常在0~180度,但本文选择在0~90度,并规定小于参数α的就是折缝边。spa
总结:要在3D模型上实现这3类边的绘制,只须要知道构成1条边的2个邻接面便可完成3类边的检测。在学术上有人把这2个邻接面当作1个点输入到着色器中,并做为1条边做为输出,并称这2个邻接面为退化四边形。.net
图2 退化四边形orm
本文使用目前最主流的Uinty3D游戏引擎实现,因为这是脱离正常渲染流程的算法,所以若是不能自定义这部分渲染流程,那么实现起来就无比困难,庆幸的是Unity3D支持自定义渲染流程,其中使用到的关键类为CommandBuffer、ComputeBuffer。blog
因为不一样的图形引擎,对图形接口的高级用法支持程度并不一致,例如Unity3D的几何着色器就没办法获取邻接三角形。所以必须进行预处理。
实现步骤:
步骤1:预处理阶段,在这个阶段找出模型全部的边,以及边所对应的2个邻接面(默认全部边都是由2个邻接面相交构成,不规范的请自行处理)。
步骤2:把预处理阶段的数据保存下来,Uinty3D提供了很好接口ScriptableObject 。(注:默认保存退化四边形4个顶点的索引,若是保存的不是顶点索引,请自行处理)
步骤3:经过CommandBuffer、ComputeBuffer把预处理据输入到显卡(GPU)中,每帧都全部退化四边形进行边的检测和绘制。
shader中边缘检测步骤:
步骤1:经过退化四边形4个顶点数据,计算出2个邻接面的法向量。若是顶点数不足4个则代表该边只有1个邻接面,所以它是边界边。
步骤2:计算出2个邻接面的法向量和视线方向的点乘结果dot(N1, V) * dot(N2, V),若是结果为负则是轮廓边。
步骤3:pow(dot(N1, N2) / cos(α), 2)在[0, PI/2]上单调递增, 可避免开方的特色,完成和dot(N1, N1) * dot(N2, N2)比较,若是小于则为折缝边。
因为Uinty3D的帧数实在看不太懂,运算量低的时候帧数基本保持在90帧左右,运算量高一点点的时候就跑到120帧,估计是内部作了优化,所以运行结果仅供参考,并不许确,运行环境I5-4210M,GTX765M。
这个描边算法在很早就被提出,这种基于空间的描边极大程度的获得模型丰富的描边细节,惋惜在市面上貌似并不为人所知。某崩公司貌似是使用了这种算法,结合其余卡通渲染算法,画面效果惊人,把同行远远甩在后面。所以若是想让本身的做品更有竞争力,开发人员应该静下心来学习一下这些教科书上没有的论文想法。本文的后续工做是控制线条的大小,理论上应该是能够在裁剪空间实现近粗远细的线条算法,但因为刚刚踏足这一块,还有不少其余工须要去研究,本文到此结束,谢谢。
附:源码
[1]基于着色器技术实时卡通渲染的的研究
[2]基于GPU实时非真实感渲染的研究与实现
[3]3D日式卡通人物渲染的经验分享
[4]Unity网格编程篇(二) 很是详细的Mesh编程入门文章
[5]Unity Shader入门精要
[6]Geometry Shader Input: GL_TRIANGLES_ADJACENCY
[7]Adjacency information in geometry shaders
[8]Direct X 12 – Geometry Shader 几何着色器
[9]OpenGL 图元处理
从csdn转移过来,顺便把写过的文章改写一下转过来。原文在这,比较凌乱,再也不建议阅读。