卡通风格渲染-初级
卡通风格渲染,或者叫非真实感渲染(NPR),是游戏中常用的渲染方法,本文主要参考Unity入门精要和一些网上资料对这块内容进行初步了解和学习
卡通风格着色
相较于传统的渲染方式,卡通风格的渲染的色差会更为明显,在Shader中这一改变主要是通过diffuse的结果进行处理,将NDotL的结果进行离散化并通过Smoothstep将边缘进行柔化
通过控制SmoothStep的阈值和区间大小,我们可以控制光影的过渡效果
此外,我们常会用到一张RampTexture实现漫反射的颜色梯度变化
1 | fixed halfLambert = 0.5* dot(lightDir, worldNormal) + 0.5; |
卡通风格高光
对于卡通风格的高光,我们需要和漫反射进行相似的处理将NDotH进行离散化处理
fixed spec = dot(worldNormal, halfDir);
fixed w = fwidth(spec) * 2.0;
//这边Smoothstep的渐变区域大小由邻近域像素的近似导数值决定,让高光边缘部分更平滑
fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1))
* step(0.0001, _SpecularScale);
可以看到高光边缘部分是比较平滑的
边缘光
在卡通渲染中,许多元素都需要加上边缘光的效果,需要基于视线和物体表面法线来实现
边缘光的表现为视线与物体表面垂直时,边缘光强度高,平行时强度低,并且在暗面不会产生,此外我们也进行边缘的平滑我的可以得到如下代码
1 | float NDL = saturate(dot(worldNormal, worldLightDir)); |
渲染轮廓线
在卡通渲染中,我们需要在物体边缘部分绘制轮廓,绘制轮廓的方法的大致可以分为三种
基于视角方向的描边:思路主要是将视角方向和表面法线点乘来判断是否是边缘,然后对边缘进行轮廓线绘制,该方法只需要一个Pass,效果如下
可以看到效果不佳,并不能体现完整的轮廓线效果
基于过程几何方法的描边:思路主要是渲染两层Pass,一层是本体,一层进行处理过的本体的背面,而这层渲染背面的Pass用于描边。这部分处理可以分为两种,一种是在调节物体观察空间的Z值,让物体更接近相机,然后渲染背面效果如下
可以看到效果还可以,但是不同位置的物体会有不一样的轮廓线表现
第二种是让物体沿着法线向外扩张来描边,效果如下
该方法有相机拉近后描边会变粗的问题,要解决这一问题需要将扩张转移到NDC空间进行解决,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13float4 pos = UnityObjectToClipPos(v.vertex);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal.xyz);
//这边乘以pos.w是为了抵消光栅化后偏移量会除以pos.w,使得计算结果保持不变
float3 ndcNormal = normalize(TransformViewToProjection(viewNormal.xyz)) * pos.w;
//为了让描边均匀,需要按照屏幕宽高比进行调整,这里通过近裁面右上坐标(PS:在Reverse-Z模式下,近裁面z为1,坐标为1,1,1)相机空间的位置来获得宽高比
float4 nearUpperRight = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));
float aspect = abs(nearUpperRight.y / nearUpperRight.x);
float2 offset = float2(ndcNormal.x * aspect, ndcNormal.y);
o.pos = pos;
//可以通过顶点颜色R通道来控制轮廓线粗细
o.pos.xy += offset * v.color.r *_Outline * _OUTLINE_WIDTH_BASE;
//在实际项目中会通过顶点颜色G通道来把轮廓线往远离相机方向推,以此解决背面面片遮挡正面面片的问题
//ShoveOutlineDepth(o.pos.z, v.color.g);通过边缘检测实现描边:思路是屏幕后处理中计算得到的边缘来作为轮廓线,这边也有两种方式一种是卷积,一种是通过相机深度图