Unity Shader入门精要学习笔记:非真实感渲染
前言
尽管游戏渲染一般是以照相写实主义作为主要目标,但也有一些游戏使用了非真实感渲染(Non-Photorealistic Rendering,NPR)
,例如卡通,水彩风格等。
卡通风格的渲染
要实现卡通渲染有很多方法,其中之一就是使用基于色调的着色技术
。
在卡通风格中,模型高光往往是一块块分界明显的纯色区域。
渲染轮廓线
在《Real Time Rendering, third edition》一书中,作者把这些方法分成5种类型。
- 基于观察角度和表面法线的轮廓线渲染,这种方法使用视角方向和表面法线的点乘结果来得到轮廓线的信息,可以在一个Pass中就得到渲染结果,但局限性很大,效果也不尽如人意。
- 过程式集合轮廓线渲染,使用两个Pass渲染,第一个Pass渲染背面的面片,并使用某些技术让它轮廓可见,第二个Pass再正常渲染正面的面片,这种方法快速有效,并且适用于绝大多数表面平滑的模型,缺点是不适合类似于立方体这样平整的模型。
- 基于图像处理的轮廓线渲染,适应面较广,缺点是一些深度和法线变换很小的轮廓无法被检测出来,例如桌子上的纸张。
- 基于轮廓边检测的轮廓线渲染,前几个方法最大问题就是,无法控制轮廓线的风格渲染,但有时我们希望刻意渲染出独特风格的轮廓线,例如水墨风格,所以我们希望可以检测出精确的轮廓线,然后直接渲染他们。检测一条边是否是轮廓边的公式很简单,我们只需要检查和这条边相邻的两个三角面片是否满足下面的条件:,其中和分别表示两个相邻三角面片的法向,v是从视角到该边上任意顶点的方向,上述公式的本质是检查两个相邻的三角面片是否一个朝正面,一个朝背面。缺点是在帧和帧之间会出现跳跃性。
- 最后一个种类就是混合了上述的几种渲染方法,例如,先找到精确的轮廓边,把模型和轮廓边渲染到纹理中,再使用图像处理的方法识别出轮廓线,并在图像空间下进行风格化渲染。
我们将使用两个Pass渲染模型,第一个Pass中,我们使用轮廓线颜色渲染整个背面的面片,并在视角空间下把模型顶点沿着法线方向向外扩张一段距离,以此让背部轮廓线可见。
viewPos = view + viewNormal * _Outline
但是,对于一些内凹的模型,就有可能发生背面面片遮挡正面面片情况,所以我们在扩张背面顶点之前,我们首先对顶点法线的z分量进行处理,让他们等于一个定值,然后把法线归一化后再对顶点进行扩张。这样扩展后的背面更加扁平化,从而降低了遮挡正面面片的可能性。
viewNormal.z = -0.5; viewNormal = normalize(viewNormal); viewPos = viewPos + viewNormal * _OutLine;
添加高光
卡通风格中的高光往往是模型上一块块分界明显的纯色区域,所以我们计算normal和halfDir点乘结果后需要与一个阈值进行比较,如果小于该阈值,高光反射系数为0,否则返回1。
float spec = dot(worldNormal, worldHalfDir); //平滑处理,对高光边缘抗锯齿,还可以使用fwidth函数得到邻域像素之间的近似导数值 spec = lerp(0, 1, smoothstep(-w, w, spec - threshold));
实现
1 | // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' |
素描风格的渲染
Praum等人发表的一篇著名的论文,他们使用提前生成的素描纹理来实现实时的素描风格渲染,这些纹理组成了一个色调艺术映射。
从左到右的笔触逐渐增多,用于模拟不同光照下的漫反射效果,从上到下则对应每张纹理的多级渐远纹理(并不是简单地对上一层纹理进行降采样,而是需要保持笔触之间的间隔,以便更加真实的模拟素描效果
)
我们首先在顶点着色器阶段计算逐顶点的光照,然后在片元着色器中根据权重来混合6张纹理的采样结果。
1 | // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' |