【】NPR卡通风格渲染技术分析 - 互动创意
产品交互
【】NPR卡通风格渲染技术分析
- insen 发表于2022年8月12日

NPR是计算机图形学中的一类,即非真实感绘制(Non-photorealistic rendering),主要用于模拟艺术式的绘制风格,NPR在目前游戏领域应用广泛。从美术的角度来讲,NPR的渲染目的是追求原画效果的还原,而非真实环境模拟,用3D的技术去表现更加多样化的艺术风格,NPR流程中建模人员不需要考虑物理灯光环境的影响,着色器会保持纹理输入与实际渲染色彩一致,有效降低与原画对接的成本。

以当前比较热门的日式漫画风格渲染为例,使用Unity中的UPR渲染管线进行Shader的编写,要实现该风格的渲染,首页需要理解传统手绘漫画的上色方式,主要为基准色,暗部和亮部三部分构成。

1、基准色

基准色的实现方式非常简单,只需要以一张纹理去定义即可,直接对纹理进行解析即可:

half4 frag(Varyings i): SV_Target{
  return tex2D(_BaseTexture, i.uv);
}

aaa.jpg

这里导入了《原神》中的角色纳西妲纹理进行测试,可以看到,即便不做任何加工,仅使用基准纹理就已经有了不俗效果,说明在NPR流程中基准纹理的质量及其重要。

2、添加暗部

接下来就是暗部的添加,角色基准色纹理已经包含了一部分暗部信息,主要是为了减少模型的面数和计算量,也是为了更好的把控暗部细节,实际上暗部还是需要通过计算的方式实现动态的光影变化效果。这里使用经典的lambert光照模型,通过计算入射光与表面法线的点积得出模型表面的明暗关系:

half lambert = saturate(dot(normalDI, lightDirWS));

未标题-4.png

接下来就是最重要的一步,传统的日式卡通风格中,使用的是一种名为赛璐璐的日式平涂绘画风格,特点为明暗转折非常硬性,而通过lambert光照模型计算后得到是非常精准且平滑的明暗关系,所以这里经过一些处理来模拟出这种绘画风格。

Avatar_YoungGirl_Catalyst_Nahida_Tex_Body_Shadow_Ramp.png

Shadow_Ramp.png

在角色的纹理信息中,有一张名为Shadow_Ramp的纹理图,可以看到在图片的右侧有很明显的明暗分割,这是通过直接映射以X轴坐标的颜色直接得到处理后的暗部颜色。

未标题-5.jpg

用这种方式在明暗的处理上建模人员有很大的自由度,可以灵活控制不同灰阶下呈现出来的暗部颜色,但是也对建模人员的色感提出了很高的要求,在纹理绘制的过程中很难直观的观察到实际效果,需要大量时间去进行调试。

了解暗部的实现原理后,我们可以对其进行优化,使其能够更加符合团队的实际需求,既然通过映射灰阶的方式过于复杂,那么我们可以直接对灰阶进行风格化的处理,这里使用一个灰阶平滑度的处理函数:

float smoothBias (float input, float bias, float _smoothstep){
    return smoothstep(0.5 - _smoothstep * 0.5, 0.5 + _smoothstep * 0.5, max(input + bias - 0.5, 0));
}

通过输入两个参数,平滑范围(bias)和平滑度(_smoothstep),得到处理后的明暗灰阶:

未标题-6.jpg

接下来指定一种颜色对黑色部分进行映射即可,可以另外输入一张暗部的纹理图,或者直接以一个固定色与基准色进行相乘,类似与PS中的正片叠底,直接用lerp函数即可轻松实现。

lerp(BaseColor, BaseColor * ShadowColor, lambert);

未标题-1.gif

使用这种方式,只需要输入一个暗部颜色即可,不需要考虑到色阶的处理,进一步降低了使用门槛,适用于大部分卡通渲染中单阶暗部的需求。

3、添加高光

卡通风格中的亮部一般指的都是光在模型表面产生的直接反射产生的高光,这里参考了两种常见的光照模型中的高光计算方式,Phong和BlinnPhong:

未标题-2.jpg

左侧是Phong, 右侧是BlinnPhong. Phong模型会出现光照截断或者过度不自然的现象。这是由于反射光方向和视线方向夹角小于90,才会使镜面反射的值为非零。BlinnPhong使用视线与反射光线的中间向量与法线的点乘进行计算,这样做在任何情况下镜面反射都不会小于0。从而解决Phong模型面临的问题。从视觉效果上,BlinnPhong模型产生的高光范围更加圆润,能更匹配卡通渲染需要的高光效果。

half blinnphong (float3 lightDirWS, float3 normalSP, float3 viewDir,float Range){
    half3 halfDir = normalize(lightDirWS + viewDir);
    return pow(max(0,dot(normalSP, halfDir)), Range);
}

通过之前的平滑算法处理后,可以得到更加卡通化的高光效果。在卡通画渲染中,不一定所有物体都需要添加高光,但对于一些金属或光滑的材质,添加高光部分可以更加凸显质感

未标题-1.jpg

4、添加描边

目前最成熟且公认的方案是双Pass外扩描边方案,原理是复制一层网格,以表面法线的方向外扩一段距离,并剔除正面。这里直接抄作业,搬运了UnityURPToonLitShaderExample的描边相关代码

为了灵活控制描边的颜色和粗细,这里用上了很少会用到的顶点色,rgb通道控制颜色,alpha通道控制描边宽度:

未标题-4.jpg

手动控制描边线条的宽度,使角色嘴部和头发末端处的描边线条变细,可以更加拟真手绘出描线效果

5、扩展效果:自发光

对于本身具备发光功能的部分,如车灯,霓虹灯,宝石特效等,其特点为具备正常的光照处理以外,自身也具备额外的光照,其实只需要准备一张额外的自发光纹理,只包含发光的部分,与原模型光照模型相加即可。

未标题-2.jpg

这种处理手法比较简单,在低亮度环境下,自发光材质会保持较高的亮度,配合Bloom后处理会有更好的泛光发光效果

6、扩展效果:边缘光

在一些卡通插画中,通常在一些物体上打上一层轮廓光来突出其通透的材质属性,如宝石、角色皮肤、头发……在NPR流程中,因为缺少这种材质的光照处理模型,所以只能通过其他方式去模拟这种效果,可以参考菲涅尔的计算方式,计算表面法线与视线的点积,经过平滑处理后获取到轮廓线

float NdotL = dot(i.normalWS, lightDirWS);
float RimLine = (1 - smoothstep(_RimRadius,_RimRadius + 0.03,NdotV))

根据边缘光的特性,面向光源的面的亮度要比背向光源的亮度高,所以我们需要与之前lambert光照模型的结果进行相乘,得到我们需要的边缘光效果:

未标题-2.jpg

未标题-3.jpg

在角色皮肤上添加边缘光后,增添了一丝通透的感觉。

7、扩展效果:副光源照明

虽然NPR流程可以准确还原纹理上的色彩信息,但在实际游戏场景中,可能存在多个局部光源,路灯、火把等,我们需要模型在靠近这类局部光源时候,模型的光照表现能够产生变化,这时候,我们就需要给其添加副光源照明的功能。

URP中有个GetAdditionalLight()方法可以获取到场景中的所有副光源信息,通过同样的lambert和BlinnPhong光照模型得出亮部信息后,与原光照信息相加即可:

int pixelLightCount = GetAdditionalLightsCount();
for (int lightIndex = 0; lightIndex < pixelLightCount; lightIndex ++)
{
    Light additionalLight = GetAdditionalLight(lightIndex, i.positionWS);
    float3 lightDirWS = normalize(additionalLight.direction);

    half lambert = saturate(dot(normalDI, lightDirWS));
    lambert = smoothBias(lambert, _DiffuseBias, _DiffuseSmoothstep);
    color.rgb += additionalLight.color.rgb * additionalLight.distanceAttenuation * lambert;

    #if defined(_SPECULARTYPE_PHONG)
        half specular = phong(lightDirWS, normalSP, viewDir, _SpecularRange);
    #elif defined(_SPECULARTYPE_BLINNPHONG)
        half specular = blinnphong(lightDirWS, normalSP, viewDir, _SpecularRange);
    #endif
    color.rgb += additionalLight.color.rgb * additionalLight.distanceAttenuation * smoothBias(specular, 0.5, _SpecularSmoothstep) * _SpecularStrength;
}

处于性能优化的考虑,副光源都存在一定的生效距离,通过获取灯光的distanceAttenuation属性,对最终结果进行一个衰减运算;此外,UPR中的副光源数量也是限制的,最大不能超过URP管线中的灯光限制(管线的Inspector界面:Lighing->PerObjectLimit),超过部分不会纳入计算。

2022-08-12-17-18-59.gif

多个副光源经过角色时的动态光照效果


最终合成效果

2

评论