背景
《玩具帝国》是一款Windows 安卓平台的双端游戏,使用Unity URP进行开发。画面属于写实风格。
纸片渲染
纸片渲染的核心要点是“透光性”,体现在游戏里,有以下几点:
- 透光性好,受光面与背光面亮度接近。
- 双面显示阴影。如果有一个面受到投影,那这个面的反面也会显示出阴影。
- 投出的阴影上仍然有透光,不是完全的阴影
第一个问题很好解决,首先开启双面渲染,然后直接指定面的法线,让它满足与光线点乘为正。由于游戏场景的光源始终是固定的,纸片也的走向也是固定的,所以只要给每个场景的所有纸片指定一个特定的法线值即可。
代码语言:javascript复制Light mainLight = GetMainLight(inputData,inputData.shadowMask,aoFactor);
half nDotL=dot(mainLight.direction,_LightingNormal);
进一步,还需要考虑纸片对光线透射与吸收,所以正反两面的颜色还是略有区别的。所以我在这里直接以albedo的rgb加权求和得到一个光线吸收率,对ndotl进行缩放。
第二个问题不需要专门的处理,只要走的是双面渲染,正常采Shadow Map,就可以让两个面获得相同的阴影。不过这里做了模拟纸片发生散射阴影处被提亮的trick:
代码语言:javascript复制half shadow = mainLight.shadowAttenuation;
half3 sssShadow=saturate(1-mainLight.distanceAttenuation*shadow)*_SubsurfaceStrength*nDotL*surfaceData.albedo;
至于第三个问题,直接用半透明阴影的做法,在Shadow Caster中加了个Dither,用高度做控制:
代码语言:javascript复制half p=GenerateHashedRandomFloat(input.positionCS);
half height=input.positionOS.y;
half k=pow(smoothstep(_Min,_Max,height),_ShadowContrast);
half a=SampleAlbedoAlpha(input.uv,TEXTURE2D_ARGS(_BaseMap,sampler_BaseMap)).a;
a=(a .2*(1-k))*k;
p=lerp(p,1-p,step(0.5,a));
clip(a-p);
区别还是很明显的:
之前 之后
实物投影和纸片投影的区别
由于游戏中的士兵很多,还必须进行合批:
士兵在受到攻击时会闪红,所以需要为它准备Per Instance信息:
代码语言:javascript复制UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float,_HitTime)
UNITY_INSTANCING_BUFFER_END(Props)
原本想传递闪红瞬间时间值,由材质计算具体的闪红。但发现由于精度原因,在游戏进行时间变长后闪红的计算误差会越来越大,最后改成通过程序传值,不知道有没有规避的方法=。=。
还没有做图集,所以目前只对同种士兵合批
茸毛灌木丛
游戏里的灌木丛按设定上是茸毛球
核心思路是让球面Mesh上的每一个四边面都变成Billboard,但法线信息依然照旧,用来计算光影、散射,做出假的体积感
只需要将模型拓扑成四边面,让四个点分别在UV的四个角即可。
代码语言:javascript复制float3 FurVertex(float2 uv,float3 positionOS)
{
half3 localOffset=half3(remap(uv,0,1,-1,1),0);
half3x3 rotateMat=AngleAxis3x3(_WindStrength*(Wind(positionOS,_TimeScale)),normalize(GetCameraPositionWS()-TransformObjectToWorld(half3(0,0,0))));
localOffset=.1*normalize(mul(UNITY_MATRIX_I_M,mul(rotateMat,mul(UNITY_MATRIX_I_V,localOffset))));
localOffset=lerp(0,localOffset,_Shape);
return localOffset;
}
//…Vertex Shader
half3 localOffset=FurVertex(output.uv,input.positionOS);
VertexPositionInputs vertexInput=GetVertexPositionInputs(input.positionOS.xyz localOffset);
最后加上风动
代码语言:javascript复制 float Wind(float3 posOS,half timeScale)
{
half3 posWS=TransformObjectToWorld(posOS);
half random=sin(dot(posWS,half3(125,3651.52456,38512.352)));
return 2.36*sin(2.35*random*timeScale*_Time 10.52) 3.255*cos(6.325*timeScale*random*_Time 491.463);
}
类似的操作在我的新书《Unity Shader入门与实战》中有详细的阐述,这是我面向对Shader一无所知的菜鸟人群撰写的Unity Shader入门读物(〃∀〃),都是以最简单最浅显的语言对Unity Shader开发中基本技术进行讲解,欢迎各位老爷支持。
当当 《Unity Shader入门与实战》《Unity Shader入门与实战》【摘要 书评 试读】- 京东图书
高亮与描边
鼠标移动到建筑上会产生高亮效果,同时建筑上带有外描边效果。这也是相当经典的案例了,在Unity URP刚发布的时候,官方案例就有类似的例子,在此也是用Render Feature处理的。逻辑里将需要描边或高亮的物体设置到对应的Rendering Layer中即可。
描边用了三个Pass,分别处理:
- Stencil Mask,用来指示建筑内部范围,用于剔除内描边
- Outline Pass 1,但关闭深度测试,显示被遮挡时的轮廓
- Outline Pass 2,但开启深度测试,显示未被遮挡时的轮廓
Pass
{
Ztest Off
// ...
// 高亮效果
}
// 被遮挡时的描边
Pass
{
Cull Front
Ztest LEqual
ZWrite On
Stencil
{
Ref 1
Comp NotEqual
Pass keep
}
// ...
}
// 未被遮挡时的描边
Pass
{
Cull Front
Ztest Greater
ZWrite On
Stencil
{
Ref 1
Comp NotEqual
Pass keep
}
// ...
}
/// 剔除内描边
Pass
{
Blend Zero One
Ztest Off
Zwrite Off
Stencil
{
Ref 1
Comp Always
Pass Replace
}
}
描边用的软法线存在顶点色里,用houdini可以简单实现,Blender的几何节点也能很容易拉出来
Houdini
Blender
这个经典例子的操作在我的新书《Unity Shader入门与实战》中也有详细的阐述(〃∀〃),再次拉出来。
当当 《Unity Shader入门与实战》《Unity Shader入门与实战》【摘要 书评 试读】- 京东图书
特效
VAT
城楼上抖动的旗帜是用Houdini中物理解算布料后,导出VAT实现的。根据需要可以在材质里调节风动的强度,这样在与风场结合时可以非常方便。
至于顶点数量比较多的模型,可以烘焙成骨骼动画导入
破碎特效
( ˇωˇ)众所周知Unity里没有Chaos,所以建筑破碎也是在Houdini里切的,效果还凑合。
爆炸/烟雾特效
Embergeni出Flipbook真的好用,向各位老爷们推荐,六点光照、Motion Vector都可以直出,不过游戏里的光照都是固定死的,所以我只需要一张最基础的图就够用了。
在Unity里用的是VFX Graph,( ˇωˇ)用起来没有Niagara那么爽,不过比原来的particle system还是强上不少的。游戏里的火焰效果与逻辑有一部分重合,这部分可以直接移动到VFX Graph里去算。