一 项目起因
2019年的时候我正在为我的功夫题材游戏寻找到一个合适的美术风格。当时在知乎看到了一个仿 吴冠中绘画风格的unity 3D场景实现(搜了下找不到了,有知道的可以联系我,我会补上连接),于是就开始对中国传统绘画的风格进行了一些研究和尝试。 之后又结合了 知乎作者 无聊 发布的文章 《Unity-一个简单的水墨渲染方法》 的启发,然后有了现在的这个美术风格。 同时还要感谢中国独立游戏的先行者之一 彭必涛 老彭的帮助。 老彭在十多年前就在研究3D国画场景的相关实现,在艺术上给了我很多建议。
《Unity-一个简单的水墨渲染方法》https://zhuanlan.zhihu.com/p/98948117
彭毕涛B站 https://space.bilibili.com/277100/video
目前项目实现效果
游戏《无极道人》实机截图
二 山水画简介
《富春山居图》(局部)-[元]黄公望 (图片来源网络)
山水画是中国传统绘画的一个分支。很多人只知道国画,水墨, 但和西方绘画一样,中国传统绘画有很多不同的分类, 比如按技法可以分为工笔,写意,水墨,白描等, 按内容可以分为花鸟,山水,人物,宫室等。每种画法都有自己的特点和习惯。
山水画从隋唐开始出现,逐渐演变成一个单独的题材,并从宋代开始盛行,技法也趋于完善。
山水画主要描写山川自然景色为主, 可以分为青绿山水,水墨山水,浅绛山水,小青绿山水和没骨山水等种类。常用技法有勾,皴,擦,染,点五法。
在研究了众多中国传统绘画风格后, 我选择了宋代水墨山水画作为基础进行风格的模拟仿制,因为这是一个相对写实的风格,比较有规律,在计算机上模拟器来比较容易。
三 宋代水墨山水画简介
宋代水墨山水画在国画中属于比较写实的一个派别。 跟素描的绘画技法有些相似。
国画讲究线为骨。书画技巧在表现结构是要有“骨力”,重视线条的运用,并将书法笔画的技巧融入其中。很多国画大家也是书法大家。类似于素描线条一样,不同的轻重快慢可以画出不同感觉的线条。
《十八描》,一本传统国画关于线条技法的书。(图片来源于网络)
颜色以墨色为主,讲究墨分五色, 焦,浓,重,淡,清, 配合干湿一共有十种主要变化。物体的远近和遮挡关系,质地坚硬还是柔软,视觉重点还是点缀,都要靠墨色的变化来实现。
(图片来源于网络)
技法为勾,皴,擦,染,点。
勾又称勾勒,为轮廓沟边。皴是画出主要结构。擦和染负责明暗关系和质感。点又称点苔, 画出附着在山石上的植物。绘画时通常先用墨线勾出山石的轮廓,再用各种皴法画出山石明暗向背,然后用淡墨渲染,进一步加强山石的立体感,最后用浓墨或鲜明的颜色点出石上青苔或远山的树木。
(图片来源于网络)
在塑造立体感方面讲究石分三面, 受光面,侧光面,背光面一起构成物体的立体感。印象中国画不怎么讲究光影和立体关系,比较平面,但实际上不是这样。
(图片来源于网络)
内容和构图
宋代山水画倾于自然,侧重“真”和“实”,提倡山水画的写实风格,但不拘泥于物体本身的真实性,而重在其背后的主观真实,“山川使我为山川言”等,借物抒情,以物喻人。有些类似于后印象派和当代美术的理念。绘画内容是自我的表达,抒发艺术家的自我感受和主观感情,而非对景物的客观还原。
在本项目中主要参考了各种古代名画和《芥子园画谱》等书。
《溪山清远图》(局部)- [南宋]夏圭,本项目的主要美术参考风格。(图片来源于网络)
《芥子园画谱》-[清] (图片来源网络)
四 在Unity 引擎 中进行实现
1. 渲染使用的相关技术说明
这里给出相关技术的中英文名称和简介方便查找。 有些技术会有多个不同的中文翻译名称。
Non-Photorealistic Rendering(NPR) 非真实感绘制
本项目的美术风格属于 NPR的一种。
非真实感绘制(NPR)是计算及图形学的一类,主要模拟艺术式的绘制风格,也用于法阵新的绘制风格。和传统的追求真实感的计算机图形学不同,NPR受到油画,素描,技术图纸和动画卡通的影响。—— Wikipedia
Borderlands (无主之地)一款NPR美术风格非常出名的游戏 (图片来源网络)
Vertex Extrusion Outlines 顶点外扩描边
顶点外扩描边为游戏渲染中常用的几种实现描边(Outlines)的方法之一。这里主要是用沿法线方向对顶点进行外扩。想要得到比较好的效果,模型的顶点数量最好不要太低,同时整个模型需要全部软边(Maya 里称为软边, 3ds max 里称为同一光滑组,在不同软件里可能有不同叫法)。 如果描边效果不好,可以尝试增加边缘的顶点数量并检查是否都是软边。如果遇到一定要硬边的情况,则可以使用沿顶点色方向扩张,即将顶点扩张的方向记录在顶点色里。这可能需要额外的制作流程关系和相关工具。
立方体的布线模式分别为硬边 软边 加辅助线 和 倒角
Rim Light边缘光
边缘光是一个常见的游戏渲染效果,根据摄像机视角方向和物体法线的夹角来计算一个值以进行照亮,描边等后续操作。
MatCap (Material Capture)
MatCap 是 Material Capture的缩写,是一种用作图像纹理的图像,可以在 3D 应用程序中伪造包括照明和反射在内的整个材质。允许通过简单的预渲染一个3D球体对象的2D图片来创建表面材质和照明环境,然后在Shader中将原始3D球形法线映射到渲染表面的法线,从而将预渲染的球形图像的光影和材质应用于目标表面,并使其看起来像其原始渲染环境中希望表面出现的样子。
2D MatCap 素材纹理
Lambert 兰伯特光照模型
兰伯特是渲染中常见的一个光照模型。在这个项目中主要是用来和其他技术一起组合出纹理的渲染区域。
Tri-planar projection 三方向纹理投影
Tri-planar projection 是将多个平面纹理组合在一起并混合以覆盖整个 3D 网格的 纹理投影,可以创建无缝的噪音和纹理贴图。通常使用模型的法线方向作为投影和混合依据。可以是世界坐标也可以是局部坐标。
Tilemap 四方连续贴图
四方连续贴图也称瓦片贴图,特点是相邻贴图上下边和左右边的纹理是连续的没有接缝。通常用来绘制大片相同区域。
(图片来源网络)
Noise map 噪音纹理
Noise map 一种用于在计算机生成的表面上产生有自然外观的纹理的技术,用于游戏电影等视觉效果。常用的有Perlin Noise 等生成算法。Noise map用来让计算机图形渲染时能够更好地表现视觉效果中自然现象的复杂性。
3D Noise map 3D噪音纹理
3D Noise map 为三维空间下的噪音纹理, 通常使用模型的顶点作为3D uv 使用, 不受模型本身uv的影响。
2 山石渲染实现
具体代码请在 Github 下载项目示例查看。链接在文末。
2.1 勾
在 CIPR_0_Gou_step_0 中通过 外层 中层 和内层三个Pass的控制完成勾线的笔触效果模拟。
毛笔的勾线笔触效果, 笔刷中间白色的部分称为飞白。
为了模拟毛笔勾线的勾边效果,一共使用了两个 Vertex Extrusion 的pass 和一个 基础的着色 pass。在之前提到的知乎作者 无聊 发布的文章中使用了两个 Vertex Extrustion pass, 一个提供基础描边, 一个提供外轮廓的笔触效果。 在此基础上,可以使用 rimlight + matcap 或 rimlight + noise 的方式 再额外对内轮廓的笔触效果进行模拟。当然也可以增加第三个 Vertex Extrusion pass 向内收缩或通过offset 改变渲染顺序来模拟内轮廓, 但是会额外增加不少三角形的渲染数量。 综合性能考虑还是在着色pass 中进行模拟比较好。但是要想追求更好效果的话也不是不能再增加一个pass。通过一张 __OutlineNoiseFB贴图 的控制来模拟笔触中 飞白 的效果。利用公式将2D Noise map计算成伪3D Noise map。 这里可以根据实际需要选择用3D Noise 还是 2D Noise, 是world space 还是 local space。
第一个顶点外扩Pass 基础勾边和飞白效果
第二个顶点外扩Pass 外层飞白效果
rimLight 实现内层勾边和飞白效果
RimColor 用来Debug 显示 RimLight 的范围
RimRate 调整 RimLight 的范围
Inline Ctrl 调整 内勾边的范围,显示区间, Noise 纹理大小 和 擦除强度
ON Ctrl(Outline Noise Control)调整主要勾边 和 外层勾边的 Noise 纹理大小和擦除强度。
在 CIPR_0_Gou_step_1中,引进根据距离控制笔触效果的参数。增加 _DistanceCtrl距离控制来处理不同距离物体的渲染效果。根据距离,修改勾边的粗细和颜色。 这个值包括最大距离,描边粗细衰减,近距离物体边缘光和远距离物体边缘光。增加一个 _ColorRemap材质 来映射不同距离下的墨色使用。 边缘光根据距离 变化公式里 pow(clamp(dist/_DistanceCtrl.x,0,1), 0.05), 这个0.05次方是项目中实验下来比较好的值,实际根据自己项目场景大小可以自行调整。
Color Remap 根据模型距离重定向 渲染颜色。
Distance 距离控制, 最远计算距离, 描边粗细衰减, 近距离 RimLight 强度, 远距离 RimLight 强度
在CIPR_0_Gou_step_2中,引进随机函数来模拟笔触的粗细和浓淡变化。这里简单的利用顶点位置作为随机种子,简单的用正弦函数来计算随机粗细变化。使用一张Noise贴图配合顶点相对位置作为做种子来模拟浓淡。实际可以根据项目的具体需求选择不同的随机方式。
Outline Ctrl 描边控制 主描边粗细, 外描边粗细, 随机函数周期, 随机大小百分比
OC Rand 描边颜色随机 Noise 贴图大小, 随机范围
2.2 皴 擦
这里我们主要用笔触的Tilemap贴图 配合 Noise来模拟 皴擦的笔触效果。通过一些shader基础对需要绘制的位置进行定位。
首先加入一张 _Stroke笔触贴图 和 _Noise 笔触噪音贴图。利用伪3D Noise 技术将这两张图变为 3D 纹理。利用rimLight 和 Lambert 光照来控制笔触贴图的投影范围。 这里对笔触贴图进行两次采样,一个作为皴的笔触效果, 一个作为擦的笔触效果。在_Stroke贴图中我们可以在不同通道储存不同的笔触纹理。之后跟Noise 纹理混合来消除重复感。
Lambert <=> RimLight 调整控制区域受灯光还是 边缘光照影响。
增加了 Stroke 笔触贴图 和 Noise 噪音贴图。
Size 贴图大小
Area Begin 根据RimLight 或 光照 强度决定渲染开始的区域
Area End 根据RimLight 或 光照 强度决定渲染开始过度的位置
Dark 加强纹理的颜色深度。
想要更好的纹理效果可以用 Tri-planar 代替伪3D Noise, 但是会增加纹理采样数。 取决于项目需求是否采用。
如果希望更精确的控制皴擦的显示区域, 可以用MatCap贴图 和uv贴图 绘制控制区域来代替或混合 rimLight 和 Lambert 光照 的控制区域效果。 一样会增加纹理采样次数,取决于项目需求和美术效果是否要采用。
加入了一个MatCap 贴图来控制笔触的渲染位置。
2.3 染 和其他
一般来讲直接用 rimLight + Lambert 光照控制 或 MatCap 控制都可以。 如果需要再叠加一层 Noise 噪音来消除重复感。
另外也可以直接用 MatCap 或 绘制到模型的 Diffuse 纹理贴图上。
MatCap
Diffuse
对于整个场景,可以考虑像烘UV 一样,直接绘制到 lightmap上。 本项目由于比较简单,就直接使用 projector投影绘制了一个。 用编辑器指令 CaptureScreenShot 在投影位置拍摄场景, 然后到PS里绘制笔触, 最后在用投影投回去。项目中可以在编辑器顶部的插件 SDTK -> Common 里找到这个指令。
投影前
投影后
PS里绘制的投影贴图
对于宣纸等底层纹理, 实现方法也很多。项目里考虑到希望减少透视关系,采用的是从摄像机观察相似角度用投影仪把一张宣纸纹理投影到游戏会用到的区域。
屋顶瓦片使用法线求出朝向后以世界坐标进行贴图平铺和Noise 混合,消除重复感。
五 其他注意事项
本文讨论的主要是制作思路,不用完全按照这篇文章的步骤制作。
部分渲染技术消耗资源比较大,比如两次勾边等在远处的物体上并不明显。 所以推荐做 LOD 的时候shader就可以用的简单一点,只勾一次边,减少皴擦的贴图采样次数等。
本项目代码主要用来说明各种制作过程,如果在项目中使用需要自行优化。 比如几个控制函数的控制公式是可以直接一起计算的,但是为了说明方便被分开来写了。 控制贴图和笔触贴图都是黑白的,可以合并到一张图的不同通道里。 这里为了展示也没有合并。
如果想要做出不错的美术场景,还是需要对中国传统绘画进行深入的研究和了解。比如之前我一直觉得水墨画应该是高饱和高对比的, 冲击力很强。 后来分析了很多国画的直方图之类的后发现,传统国画一般都是中低饱和,整体颜色对比并不强烈,过度平缓。对比感来自于技巧的合理运用和构图画面安排。
六 示例项目下载地址
https://github.com/sacshadow/3D_ChineseInkPaintingStyleShader
2023 年 2月2日
Shadow
暂无关于此日志的评论。