前面的话
unitychan 是日本 unity 官方团队提供的一个 Demo, 里面有很好的卡通渲染效果, 值得参考学习
上图是我整理出来的 shader 结构, 可以看到 Unity 娘被拆分成了很多个小的部件, 我想主要是为了挂动态骨骼吧. 因为有很多部件的材质, shader 其实都是一样的可以合并成少数几个
我打算分 3 个部分来学习
CharaMain.cginc 主要用于衣服等材质
CharaSkin.cginc 皮肤效果
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 );
效果如下, 边缘较白
但我感觉加上采样颜色后效果不是很明显, 边缘颜色会深一些.
高光效果
- // 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 通道.
大概就是描出来那些地方高光强一些
反射
- // 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 通道做差值后会发现, 大部分区域的反射颜色都不见了, 因为大部分是黑色. 只有少数白色区域, 能看到一些反射效果
接收阴影处理
这里是计算其他物体投射在身上的阴影, 这里插入了一个自定义的阴影颜色, 为了明显一点我直接调成纯黑, 然后弄了一个物体挡住了 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 里面则是采用了一张边缘光贴图
用 N.L 计算出来的值域在 [-1,1] 之间,*0.5+1 后, 转成 [0,1] 区间, 然后对这张 1 维的 rim 图采样.
我们从右边打一盏平行灯, 来看看采样的效果. 跟光向量夹角越小的值越接近 1, 也就越白
但是边缘光效果要收到漫反射影响, 所以他这里乘上了之前采样出来的漫反射渐变, 可以看到高光被控制在了边缘
falloffU = saturate( rimlightDot * falloffU );
完整效果
总结
CharaMain.cginc 中用了很多的小技巧实现了漫反射, 高光, 反射, 边缘光. 她没有传统卡通渲染赛璐璐的阴影渐变, 也没有油腻的高光. 她的效果都有种欲出还收的感觉.
我还是觉得效果太淡了一些
来源: https://www.cnblogs.com/lijiajia/p/12332232.html