项目效果展示
基础材质
现在基本作为新场景的基础shader,涉及的功能有
- 光照
美术希望暗部的自发光会更暗一些,rim不受明暗影响。
col.rgb *= lerp(1, mainLight.color, adjustedNoL); // 平行光叠加
col.rgb = lerp(col.rgb * _DarkColor, col.rgb, adjustedNoL); // 颜色二分
col.rgb = lerp(col.rgb, reflect * _RefectionColor.rgb, _RefectionColor.a); // 反射部分
col.rgb += VertexSH * c.rgb * _SHIntensity; // 叠加sh, sh和二分放在前面正面完美背面完蛋,放后面,正面融合的地方会有一点点sh
col.rgb += EmissionTex * _EmissionColor.rgb * _EmissionColor.a; // Emission,美术要求支持RGB
*分层材质部分*
col.rgb = lerp(col.rgb * _ShadowColor, col.rgb, mainLight.shadowAttenuation); // 根据阴影范围来判断阴影颜色
col.rgb = lerp(col.rgb, _RimlightColor.rgb, fresnel * _RimlightColor.a); // rim最后叠加,现在是逐顶点的
二值化算法是白糖卡渲的简化版,用的兰伯特没用半兰伯特,暂时不需要区分亮暗面也不需要调整明暗交界线位置
// half adjustedNdotL = saturate(saturate((halfNdotL - cutLocation ) * _ToonCutSharpness ) + toonData.diffuseGrading);
half adjustedNoL = saturate(NoL * _ToonCutSharpness);
现在效果如下,中间jiuge是当前版本的,右边Toon是Character Body
右侧两个在GI上的区别就是 Toon有PBR数据,用 GlobalIllumination 函数算的,九歌没PBR数据直接拿 SH算的
- 分层材质
http://kms.sys.wanmei.net/pages/viewpage.action?pageId=46667925
分层材质+卡渲有好多坑,因为分层拿到的颜色信息是经过计算后的,所以很容易就会多算几遍。例如开了雾效后,需要在计算颜色的pass中把雾效的计算去掉;如果独立算了光照和阴影啥的,分层材质要放在光照计算后面,阴影颜色计算前面才能保证混合正确(左图为融合部分雾效混了两次)
//* wanghaoyu 由于雾效会计算两次,所以在这里面把雾效去掉就可以了
// #pragma shader_feature _ HEIGHT_FOG _ENABLE_BILINEAR_FOG //ATMOSPHERIC_FOG_DAY ATMOSPHERIC_FOG_NIGHT
- 阴影部分
Folige好像对阴影支持不是很好,所以在阴影部分加入了树的clip和shadowBias防止树在随风偏移的时候由于bias产生的摩尔纹。(下图为产生的摩尔纹)

bias部分直接用的Toon部分的trick,shader中引用并define一下 _Toon_Athena 就行
// normal bias is negative since we want to apply an inset normal offset
// pwrd * zhengyiye : 增加卡通影子偏移
#if _TOON_ATHENA
positionWS = lightDirection * (_ShadowBias.xxx - _ToonShadowLocalBias.xxx) + positionWS;
#else
positionWS = lightDirection * _ShadowBias.xxx + positionWS;
#endif
这个shader的阴影部分是由二分制作的伪阴影和自阴影同事作用的,所以一般正常制作的时候是将自阴影bias掉,使用二分的伪阴影,投影还用shadowCaster

还给阴影的vs部分加入了wind,现在影子可以正确随着风场运动了。右图为改后

#if defined(_WIND_ON) // 是否使用风场
worldPos = ApplyWind(input.color.r, worldPos, windSineOffset, worldRotUV, windSpeed, debug);
#endif
output.positionCS = TransformWorldToHClip(ApplyShadowBias(worldPos, worldN, _LightDirection)); // shadowbias
- 内描边
内描边用了一种很简单的方法,就是倒角单独给个颜色。效果还挺显著,依赖于屏幕像素数,不依赖贴图,也不用本村线哪种花很多时间调uv还有限制。虽然这个也要动uv,不过把uv缩到最小放到一起就可以。

blender中效果

- 贴图Lod
(郭昌写的)这个是朋叔说根据远近有不同的贴图细节,暂时做了个根据距离判断用那张图的shader。核心算法和Outline远近不同的方式大同小异。
这种方法太占贴图资源了,后续看效果再沟通研究一下有没有其他方案,实在不行就降低一些贴图分辨率。
- 反射
家园场景需要平面反射,就加了一个,主要是script,然后就是光照叠加顺序。一个很明显的缺点就是,由于没有粗糙度贴图,导致反射有点油。

- 双线性雾
http://kms.sys.wanmei.net/pages/viewpage.action?pageId=52210686
高度雾和深度雾叠算可以让远处的雾效更弄,更好的体现场景深度
接入就比较简单了,加上对应的关键字,vs部分做一个判断就可以
//* pwrd zhanglingyun:天气系统雾效
half fogFactor = 0;
#if HEIGHT_FOG | _ENABLE_BILINEAR_FOG | ATMOSPHERIC_FOG_DAY | ATMOSPHERIC_FOG_NIGHT
TRANSFER_FOG_FACTOR(fogFactor, o.positionWS)
#else
fogFactor = ComputeFogFactor(o.positionHCS.z);
#endif
增加功能
- 外描边
现在支持的功能是:世界空间y轴算了下不同高度的颜色;根据顶点色做偏移处理不同宽度,偏移部分的颜色有个叠加;叠了张屏幕空间的贴图;远近描边宽窄可调节
// 远近可调节宽度
float dist = length(_WorldSpaceCameraPos - o.positionWS.xyz); // 相机到物体的距离
float ratio = saturate(dist / _OutlineDistanceRange);
float width = lerp(_OutlineWidthNear, _OutlineWidthFar, ratio); // Near&Far实际上是远近的描边宽度
由于默认的shader可以用在树上,如果只想山体描边的话用feature调用pass的方式不是很好控制,所以就单独写成了shader,通过多材质控制

- 瀑布
(子浩写的)纯美术的track,后面微调了一下
弯折处高光部分这种黏连的感觉还不错,但实际上就是一个smoothstep
float foldrange=smoothstep(1-_FoldDensity,1-_FoldDensity+_FoldOffset,foldNoise); // 对弯曲处的噪声限定范围
剩下就是增加变化和层次让瀑布更丰富,基本都是noise的,有些地方用了mask,例如立面的白色高光和透明。参考元神的喷泉发现平面还有个透明度渐变,也做了,但稍微还是有点硬

基本都是用顶点色和mask图控制的(下图为max中顶点色)

- 光路
也是纯美术的track

- 字体模糊
用了屏幕流光。本来五次模糊,模糊的时候会很清楚,去掉中间内次采样模糊后识别性就低了一些

- GodRay
大佬们写的直接拿过来了,有些很精髓,例如背对阳光的时候就不算godray,也有些不懂的,例如
var sunWorldPos = cameraTrans.position - lightForward * 2000;
看起来像把阳光推远,但实际上去掉后没啥明显区别
问题修复
- 神灵界面反射
美术希望反射出来的颜色和原画更接近(能暗下去),原来shader只能整体调整,所以在shader上step了一下角色的范围,重新算颜色叠加。场景物体就直接反转180°
先在script里面加入可以去掉天空和的选项。
//* wanghaoyu 在shader里做mask用。只控制是否剔除天空盒
if (m_settings.RefCamBackGroundColor == true)
{ _reflectionCamera.backgroundColor = Color.black;
_reflectionCamera.clearFlags = CameraClearFlags.SolidColor;}
else
{ _reflectionCamera.clearFlags = CameraClearFlags.Skybox; }
//* wanghaoyu
然后shader里面控制反射部分颜色
//* wanghaoyu
// 反射部分颜色控制
half RefMask = 0; // 初始化
if(_RefMaskOn > 0.5)
{
// 区分角色的mask,需要同时控制 RefectionPlane.cs 的 Refect Layers 参数
RefMask = SAMPLE_TEXTURE2D(_PlanarReflectionTexture, sampler_PlanarReflectionTexture,reflectUV).rgb * 5 > 0.01;
// 分成两部分分别计算颜色,ps:角色反射的透明度由上面的“反射强度”决定
half3 TwoPartColor = lerp(_RefOneMinuColor.xyz,reflect*_RefColor.rgb,RefMask * _ReflectIntensity);
color.rgb = TwoPartColor;
}
//* wanghaoyu
然后美术又说有点飘,就加了个可以控制长度的阴影
Light mainLight = GetMainLight(TransformWorldToShadowCoord(input.WorldPosition));
if(_ShadowOn > 0.5)
{
// 用加法叠加颜色
half VdotL = smoothstep(0.175,_ShadowMask,dot(input.normalWS, viewDir));
half ShadowMask = 1-mainLight.shadowAttenuation;
half finalMask = 1 - saturate(VdotL*ShadowMask); //计算角色阴影范围
float3 shadowColor = finalMask + (ShadowMask * _ShadowColor);
color.rgb *= shadowColor;
}
//* wanghaoyu 添加阴影接收

最终结果是这样

- 草地偏移
看康康的文档可以看到,交互时候草的根部明显发生了偏移
所以直接在shader中计算偏移量部分 *顶点色 因为根部是黑色,所以锁住了根部

- 渲染顺序
开了深度写入

- 官方高斯dof黑边
采样精度问题

FragCoC 中
half coc = (depth - FarStart) / abs(FarEnd - FarStart) + 1e-4;
//half coc = (depth - FarStart) / abs(FarEnd - FarStart);
blur 函数中
float2 sampCoord = uv + kOffsets[i] * offset + 1e-4;
// float2 sampCoord = uv + kOffsets[i] * offset;
- 植被合批失败
合批后只有一个选中框,没有植被。矩阵w的问题,注释掉的部分是错的,TransformObjectToWorld 函数直接把pos的w转成1了,所以合批失败有可能unity_ObjcetToWorld的w不是1导致的


- 后处理规范
之前美术为了每个场景达到不同效果给每个场景单独一个调过曲线和颜色的volume。这样各场景表现效果不统一,例如高光范围,灰度范围什么的。现在统一成了标准volume的效果,场景颜色变化做贴图调整就能保证相对统一了。
弃用的
- 树
最开始美术希望树做好点,然后就开始好好研究了一下,后来朋叔说对标轩辕剑,只需要一个能控制阴影颜色的shader,有跟朋叔说明资源量会巨大,但朋叔表示这样才可以体现不同场景的风格化于是弃用。

当时研究了一下插片方式不同的影响

用九歌的树做了简单的迭代。最上面为最终效果,中间是插片方式,最下面是贴图修改

用houdini做了个给树插片的工具,来保证所有插片位置相同

尝试在原神树叶正面clip的基础上做了过度


原理就是 abs(NoV)与Dither做加法
但是这个有个问题就是,树基本上是要做地平线内种包围体法线映射的所以顶点法线值会被修改,而这里用到的是原始的顶点法线,所以需要找个地方单独存一套法线
- veg溶解
玩了两天新的粒子系统,最后做出来效果并不是特别好,就弃用了
- 仿古剑后处理