Unity酱~ 卡通渲染技术分析(一)

前面的话

unitychan是日本unity官方团队提供的一个Demo,里面有很好的卡通渲染效果,值得参考学习

Shader分类

上图是我整理出来的shader结构,可以看到Unity娘被拆分成了很多个小的部件,我想主要是为了挂动态骨骼吧。因为有很多部件的材质,shader其实都是一样的可以合并成少数几个

我打算分3个部分来学习

  1. CharaMain.cginc 主要用于衣服等材质
  2. CharaSkin.cginc 皮肤效果
  3. Hair 头发、眼睛、睫毛等部位的渲染

CharaMain.cginc

本篇先写第一部分body材质。CharaMain.cginc中包含漫反射、高光、反射、边缘光、阴影等效果的实现,接下来我们详细拆解

基础着色效果(模拟漫反射)

这里是用视角向量跟法线点积(但这样的做法就不会受光照角度变化),然后用结果采样一张类似这样的衰减纹理

	// Falloff. Convert the angle between the normal and the camera direction into a lookup for the gradient
	float_t normalDotEye = dot( normalVec, i.eyeDir.xyz );
	float_t falloffU = clamp( 1.0 - abs( normalDotEye ), 0.02, 0.98 );
	float4_t falloffSamplerColor = FALLOFF_POWER * tex2D( _FalloffSampler, float2( falloffU, 0.25f ) );
	float3_t shadowColor = diffSamplerColor.rgb * diffSamplerColor.rgb;
	float3_t combinedColor = lerp( diffSamplerColor.rgb, shadowColor, falloffSamplerColor.r );
	combinedColor *= ( 1.0 + falloffSamplerColor.rgb * falloffSamplerColor.a );

FalloffSampler
效果如下,边缘较白
漫反射

但我感觉加上采样颜色后效果不是很明显,边缘颜色会深一些。
加上采样颜色

高光效果

	// Use the eye vector as the light vector
	float4_t reflectionMaskColor = tex2D( _SpecularReflectionSampler, i.uv.xy );
	float_t specularDot = dot( normalVec, i.eyeDir.xyz );
	float4_t lighting = lit( normalDotEye, specularDot, _SpecularPower );
	float3_t specularColor = saturate( lighting.z ) * reflectionMaskColor.rgb * diffSamplerColor.rgb;
	combinedColor += specularColor;

这个高光的计算很奇葩,也是用法线跟视角向量的点积,最后的效果就是有一点淡淡的高光。也是跟真正的灯光角度无关的。

这里还用到了一张贴图,高光会用到这张图的rgb通道,后面要写的反射,会用到这张图的a通道。
大概就是描出来那些地方高光强一些

RGB通道

高光效果

反射

// Reflection
float3_t reflectVector = reflect( -i.eyeDir.xyz, normalVec ).xzy;
float2_t sphereMapCoords = 0.5 * ( float2_t( 1.0, 1.0 ) + reflectVector.xy );
float3_t reflectColor = tex2D( _EnvMapSampler, sphereMapCoords ).rgb;
reflectColor = GetOverlayColor( reflectColor, combinedColor );

combinedColor = lerp( combinedColor, reflectColor, reflectionMaskColor.a );
combinedColor *= _Color.rgb * _LightColor0.rgb;
float opacity = diffSamplerColor.a * _Color.a * _LightColor0.a;

这里是采样一张环境贴图,为啥用张这样的图呢?我觉得他主要是为了能反射出这种银色的色调罢了。你想要什么色调就换啥样的环境图。

GetOverlayColor这个函数是用来融合自身贴图颜色,跟反射环境贴图颜色的。里面用了很多小技巧

先来看看直接输出反射贴图是什么样

反射贴图

通过GetOverlayColor融合后的反射颜色
融合后

然后这里会用到高光反射贴图的A通道,来表示某些区域的反射的强度。
Alpha通道

用alpha通道做差值后会发现,大部分区域的反射颜色都不见了,因为大部分是黑色。只有少数白色区域,能看到一些反射效果
最终反射效果

接收阴影处理

这里是计算其他物体投射在身上的阴影,这里插入了一个自定义的阴影颜色,为了明显一点我直接调成纯黑,然后弄了一个物体挡住了unity酱~ 效果如图

使用LIGHT_ATTENUATION指令对阴影贴图采样并且返回数据供你使用。如果你想知道LIGHT_ATTENUATION指令具体做了些什么,检查 AutoLight.cginc文件

#ifdef ENABLE_CAST_SHADOWS
	// Cast shadows
	shadowColor = _ShadowColor.rgb * combinedColor;
	float_t attenuation = saturate( 2.0 * LIGHT_ATTENUATION( i ) - 1.0 );
	combinedColor = lerp( shadowColor, combinedColor, attenuation );
#endif

边缘高光

终于这里有一个跟着光照角度变换的效果了,哈哈

一般我们会使用菲尼尔效应来实现边缘光,而unitychan里面则是采用了一张边缘光贴图

RimLight

用N.L计算出来的值域在[-1,1]之间,*0.5+1后,转成[0,1]区间,然后对这张1维的rim图采样。

我们从右边打一盏平行灯,来看看采样的效果。跟光向量夹角越小的值越接近1,也就越白

边缘光贴图采样

但是边缘光效果要收到漫反射影响,所以他这里乘上了之前采样出来的漫反射渐变,可以看到高光被控制在了边缘

falloffU = saturate( rimlightDot * falloffU );

rim * 漫反射渐变

完整效果

完整效果

总结

CharaMain.cginc中用了很多的小技巧实现了漫反射、高光、反射、边缘光。她没有传统卡通渲染赛璐璐的阴影渐变,也没有油腻的高光。她的效果都有种欲出还收的感觉。

我还是觉得效果太淡了一些

posted @ 2020-02-19 17:35  李嘉的博客  阅读(2534)  评论(5编辑  收藏  举报