UE4风格化场景全流程——③树叶的制作
本文为up学习笔记及踩坑记录,会结合我知道的知识点扩展
项目源文件请到上面视频链接的评论区下载
欢迎大佬指出我的错误,若图片太小请放大观看(网页端缩放浏览器)
之前的文章:UE4风格化场景全流程——①项目设置及地形创建
制作不透明度遮罩
打开Ai,当然用Ps或其他熟练的作图软件都行,我们只是要绘制一个树叶的不透明度遮罩,大家也可以发挥自己的创意做出各种不同的形状。创建一个512×512像素的文件,命名为SM_TreeOpacity。

不透明度遮罩一般是黑色打底的白色图案,黑色部分在引擎中会被识别为透明度为0,所以只显示白色的图案。(如果颜色是夹在黑白之间的灰色,应该会以半透明显示,但具体效果要看引擎是否开启了透明度测试、透明度混合和深度写入。)我们新建一个图层命名为background,选中该图层使用矩形工具,更改为黑色(记得去掉描边),覆盖整个画板。需要注意的是,因为Ai是矢量图,能在画板之外的地方作画,所以一定要对齐画板。最后为了避免之后误操作,我们点击图层前留空的地方锁定图层。

右键点击矩形工具切换到椭圆工具(也可以快捷键L切换),按住Shift可以绘制一个正圆。按住Alt可以以鼠标为圆心绘制,我们先换成白色(记得去掉描边),按自己的喜好拖出一个椭圆。接着我们可以用钢笔工具(P键切换),按住Ctrl不放(不按会消除此锚点),点击椭圆两端的锚点,再拖动手柄使其与锚点重合,或直接使用锚点工具(Shift+C),什么都不按点击椭圆两端锚点,使其两端便尖(或是一段变尖,做成什么形状的叶片大家自行发挥)。

调整下叶片的大小(按住Shift等比缩放,按住Ctrl以中心点缩放),鼠标在边框的四个角可以旋转。我们按不同的大小复制排列出一个基础图形,按Ctrl+G编组(视频中是错误的,Ctrl+C只是复制,Ctrl+F是复制后相同位置创建副本,Ctrl+V是不同位置粘贴复制的对象,真正编组后右侧的图层下会有编组显示)。

我们将这个组合随意复制粘贴,尽量使整体的轮廓呈现圆形(可能是为了避免Billboard过于规整),大叶片和小叶片交替使用,使中间的黑色缝隙错落有致的分布,也可以把叶片组合拆分开来单独排布。up的思路是先用大叶片做一个包围,然后中间用大小不等的叶片补齐空隙,尽量去遮挡叶片组合的尾部,同时减少不同组合之间的覆盖,使得叶片看起来有清楚的轮廓,补齐空隙时尽量使用一个叶片切分成较多的小空隙。

如果你做的叶片重心看起来不在画板中间,选中所有组合调整下位置,最后将其导出为PNG格式,分辨率为屏幕(72ppi),视频中消除锯齿为优化文字(提示),up原本觉得用优化图稿(超像素采样)更好,但两种都导出对比了一下,没有什么差别。背景色设置为透明,虽然预览图上看起来我们的黑色矩形没布满,但实际导出后是正确显示的(这样我岂不是可以直接画带透明通道的贴图不用遮罩?)。

我们再回去选中所有组合,效果-模糊-高斯模糊,给一点模糊,不要太多,不然会失去细节。视频中说如果边缘非常清晰,引擎中效果看起来就不会很好(盲猜是上面说的锯齿问题?因为位图导出为PNG,是转像素图,像素图因为其像素排列必定有边缘锯齿的问题,通常解决办法是按图案占像素的比例计算不同权重的灰度填充),完成后命名为SM_TreeOpacity_blurred导出,以便与上面导出的做对照。

打开UE项目将上面两张贴图导入Textures文件夹,然后点击Materials文件夹的M_TreeLeavesMaterial材质球,准备制作材质。我们先右键添加一个runtime virtual texture sample节点,将其命名为colorRVT,再右键添加一个StaticSwitchParameter节点,命名为useRVT?因为我们需要知道我们是否需要使用RVT,将colorRVT的BaseColor连到useRVT?的True上。这个节点的作用是给我们材质实例一个复选框,如果选中了,材质就使用True的RVT,如果没选中就用False。

当不使用RVT(实时虚拟纹理)的时候,我们需要创建一个颜色参数传递给useRVT?的False,按住3或4点击鼠标左键创建一个三维或四维常量,再右键转换为参数,三维常量和四维常量都会被转换成四维的参数,但区别是三维常量转换后的第四维度默认为1,四维常量转换后第四维度默认为0,命名为Color后连接至False(可以先随意给Color一个颜色,colorRVT用的是之前刷地表的LandscapeColor)。

将useRVT?输出至Lerp节点的A,L+鼠标左键可以添加Lerp节点(linear interpolation),这个节点的作用是基于Alpha在两个值之间进行线性插值,Alpha是一张灰度图,当Alpha为黑色时表现为A的值,为白色时表现为B的值。

所以我们需要再右键添加一个HueShift输出到B,这是一个用来做色调变换的节点,使输入颜色的当前色调值按给定的百分比偏移,我们还需要创建一个参数输出到Hue Shift Percentage(色调改变百分比),S+鼠标左键创建一个一维参数命名为HueShift%(理论上这个值不能超过1,因为这个函数是围绕色环进行变换的,HueShift%为0.5时变换为互补色调,为1时色环整正好旋转一周,实际应用时超过一应该会取小数继续偏移,有兴趣的朋友可以搜下HueShift的应用,有非常多有趣的效果)。

这一步的目的是我们要给树叶的顶部和底部不同的颜色,使其有更好的层次感,所以可以将useRVT?输出的颜色直接连接到HueShift的Texture,但为了底部颜色更可控,我们将其先乘上一个参数,S+鼠标左键创建一个一维参数命名为undersidecolor(底部颜色,实测是控制明暗的),再M+鼠标左键添加乘法节点,连接后如下图。

接着我们要给Lerp节点的Alpha创建一个蒙版用来控制顶部和底部之间的过渡,为此我们要得到顶点法向,右键搜索VertexNormalWS添加节点,WS表示worldspace(世界空间),将其输出到BreakOutFloat3Components。前者传入了XYZ轴坐标,后者将其分到对应的RGB通道分别输出,我们只需要Z轴坐标,所以将Z轴对应的B通道输出到Add节点(A+鼠标左键添加或拖出连接线时搜索add),再S+鼠标左键创建一个名为normalAdd的一维参数输出到Add的另一个值。

再将Add的输出乘上一个默认值为0.5的一维常量(1/2/3/4+鼠标左键创建1/2/3/4维常量),再将这个结果输出到Power节点(乘方,up还没有理解这样计算的意义,通常来说法线坐标在-1~1之间,需要将其变到0~1,所以加1乘0.5,normalAdd可以是常量。根据材质最终效果up已理解这里计算的意义,VertexNormalWS是世界空间下的顶点法线坐标,只取z轴输出时,朝上的法线会在0~1,朝下的法线会在-1~0,那么作为蒙版输出时就会呈从上到下从白到黑的渐变,这样就区分树叶了上层和下层,加normalAdd是为了控制其分界线,乘上0.5其实不是很必要,但可以增加过渡的范围,和normalPower效果类似),新建一个名为normalPower的一维参数连接到Power的Exp(理论上来说除0外的0次方都为1,那么无论normalAdd值为多少,只要normalPower为0其结果都是一样的,但实测normalAdd为1时下半部分材质呈渐变变化,增大normalPower可以增加渐变的范围),这样我们就得到了这个模型自顶到底的高度,我们希望得到相反的结果,所以将其连到OneMinus节点(O+鼠标左键,up认为这里是为了直观控制下部分蒙版,不然色调变换后的颜色会从上半部分开始显示,反美术逻辑)。

最终将1-x输出到Lerp节点的Alpha,再将Lerp输出到基础颜色,选中所有参数(不包括常量),将他们在左侧细节-材质表达式面板分组到baseColor。框选这部分节点按C进行注释,注释为Color。我们还需要给粗糙度(Roughness)一个为1的常量,因为我们不需要经常控制粗糙度。可以看到下半部分材质显示黑色,因为我们undersidecolor为0,所以下半部分明暗度为0,当将其改为1时上下颜色一致,因为HueShift%为0,色调没有偏移。

接着我们来制作边缘光效果,先右键添加一个Fresnel(菲涅尔)节点,再S+鼠标左键新建一个名为FresnelExponent的一维参数,输出到Fresnel的Exponentln(衰减指数,默认值为5),再将Fresnel输出到saturate节点(视频有误,这里不翻译成饱和度,应该翻译成使充满),其作用是将值限制在0~1之间,但原理并不是映射,而是超过1的值算1,小于0的值算0(up觉得这里不用加saturate,因为理论上Fresnel输出的值就在0~1之间,实测证明有无saturate效果一样,但需要记住部分ue节点输入限定在0~1之间)。

再将结果与命名为Highlight的颜色参数相乘(up默认为0.4,0.4,0.1,1),我们就得到了边缘光颜色,这颜色只会出现在有菲尼尔效果的地方。菲涅尔来自于基于物理的渲染技术(PBR),它通常用来表现物体的反射和光泽,但它也可以用在我们的卡通效果上。最后S+鼠标左键创建一个命为emissiveStrength的一维参数与之相乘,用于控制颜色明暗(也可以用乘方)。

在实时渲染中,我们经常会使用菲涅耳反射(Fresnel reflection)来根据视角方向控制反射程度。通俗的讲,菲涅耳反射描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅耳等式进行计算。使用上面的菲涅耳近似等式,我们可以在边界处模拟反射光强和折射光强/漫反射光强之间的变化。——《unity shader入门精要》-冯乐乐 10.1.5菲涅耳反射
UE中的Fresnel节点的运算逻辑是将摄像机的方向向量与物体表面的法线向量做点积,当方向相同时为1,方向垂直时为0,方向相反时为-1,需要注意的是相机属于观察空间,使用右手坐标系,+z轴指向相机的后方,所以手动计算相机向量的话需要对z分量取反。因为颜色的值在0~1之间,所以需要对点积结果进行约束,再对结果进行线性插值(Lerp),使其为1的时候显示黑色,为0的时候显示白色(对约束后的结果1-x或是直接对点积结果取反再saturate,也能得到一样的效果,如果直对点积直接输出,1会显示白色,0会显示黑色),这样就得到了我们的菲涅尔效果。有兴趣的朋友可以看下UE5材质宝典---Fresnel菲涅尔节点的底层原理这个视频(更多应用请看UE4材质中使用菲涅尔效果),原理一致但中间的指数与基础反射部分计算仅供参考,实测同样的参数并不能得到同样的效果,同时即便不进行约束,颜色也能得到合理范围内的效果,猜测是UE自动剔除了小于0大于1的值(更正:超出范围的数值较小时确实看不出区别,但当超出范围过大时结果就有了明显差别,比如输出至颜色的值超出1会更白,但似乎是有上限的,而输出至自发光的值超出1过大会表现出辉光,过于小于0会出现侵蚀的效果)。

我们将最终结果输出至自发光颜色,再选中这部分节点按C备注为HighLights,并将FresnelExponent、Highlight、emissiveStrength三个参数在左侧材质表达式分组中归组至HighLights组。接着我们要在左侧材质面板将混合模式从不透明改为已遮罩,这样才能开启材质的不透明蒙版,让我们制作的树叶蒙版能够使用。

接着我们导入之前制作好的树叶蒙版,先试试没有模糊的版本,右键转换为参数并命名为OpcityMask(不透明度蒙版),将其归组至材质实例下的OpcityMask组,并将RBG通道输出至不透明蒙版(视频中说可以添加乘法节点控制其不透明度,理论上来说相乘的数值应该控制在0~1之间,因为白色为1黑色为0,透明度应该在0~1之间变化,但我实操结果显示透明度不会随着相乘的数值变化,反而0.3333以下的数值图形就会消失,且边缘会随着数值减小出现锯齿效果)。

我们希望树叶有风吹的效果,右键创建SimpleGrassWind节点并连接至世界场景位置偏移(World Position Offset),然后按住S+鼠标左键创建3个一维参数,第一个命名为WindIntensity(风力强度),第二个命名为WindWeight(风重),第三个命名为WindSpeed(风速),默认值都为0,即无风,和SimpleGrassWind节点的输入一一对应连上。

然后我们要让之前的树叶模型每个面都成为一个billboard,即每个面都单独面向摄像机。我们右键创建一个TextureCoordinate(纹理坐标)节点,将其连接至oneMinus节点(O+鼠标左键,为了将其颠倒),我们可以简单示范一下,右键创建LinearGradient(线性渐变)节点,右键开启预览节点,可以看到从左到右从黑到白的渐变,此时预览的是U向渐变,我们将其连接至oneMinus节点并预览,可以看到变成从左到右从白到黑的渐变,所以oneMinus有颠倒的效果(好奇视频里为什么不直接U轴输出,实际原理就是纹理坐标是0~1范围内的,当执行1-操作的时候就反转了)。

我们再将之前反转后的纹理坐标结果乘上一个常量2再减一(M+鼠标左键创建乘法节点,1+鼠标左键创建1维常量,右键创建subtract节点),这样做的目的是将billboard的中心移动到面的中央而不是左下角,就如我上面说的纹理坐标的值在0~1,当执行上面操作的时候值就变换到了-1~1。可以参考【中字】四边面到Billboard方法制作毛茸茸的风格化树这个视频,虽然用的是unity但是对每个步骤都讲解了详细的数学原理。

之后将结果乘上一个值为(-1,1)的vector2类型数据(二维矢量),up不理解这一步是为了什么,本质上每个点的纹理坐标也可以看作是二维矢量,两个向量相乘只会得到一个新的垂直于这两个向量的向量(包括前面的oneMinus反转操作,实测没有这两步结果无影响)。billboard原理实际上是让每个点在渲染时候根据其纹理坐标进行偏移,为了其能同时根据相机视角进行旋转,我们需要将其与相机的空间矩阵相乘,ue中有十分方便的转换节点(右键TransformVector,左侧面板源选摄像机空间,目标本地空间),但在此之前我们需要通过append节点将纹理坐标转换为三维矢量,因为摄像机空间矩阵是三维矩阵。

我们还需要对结果进行归一化(Normalize,N+鼠标左键),为了避免缩放模型时由于向量长度不一致导致的缩放异常(使其成为单位向量)。再将其乘上一个名为BillboardSize的参数(默认值为1),用于控制billboard的大小。此时如果你应用材质就会发现,虽然正面看起来是正常的移动相机也是正常的,但当你旋转树木,树叶就变成了始终侧面朝向你,这是因为此时的偏移是模型空间下的偏移,是相对于树的方向应用的,我们需要将其转换为世界空间, 右键TransformVector,源选本地空间,目标选世界场景空间。(up觉得ue里给了方便操作的节点,可以不像视频里完全模仿unity的流程操作,直接第一次转换就从相机空间转到世界场景空间,归一化操作也应该在全部转化后,实测结果一致)。

我们再右键添加一个VertexNormalWS(世界空间下的顶点法线),将其乘上一个名为Inflate的一维参数(S+鼠标左键,默认值为1),用于扩张或缩小billboard(up觉得这描述不准确,实测会在法线方向缩放billboard,类似于模型整体放大,会让树叶有蓬蓬的感觉),再将结果与之前矩阵空间转换后的结果相加,再乘上一个名为BillboardScale的一维参数,用于调整整体的比例(默认值为0,这个是总控参数,为0时候之前两个参数怎么调整都不会变化),最后输出至SimpleGrassWind节点的AdditionalWPO。将这部分节点按C注释为BillboardWPO,再将BillboardSize、Inflate、BillboardScale、WindIntensity、WindWeight、WindSpeed这几个参数归组到WPO-Billboard。

保存后我们为这个材质右键创建一个材质实例,并将其拖至树叶上。可以看到此时虽然有了纹理但依旧贴附在模型上,且底部的颜色不能正确显示。这是因为我们的undersidecolor为0,在材质节点内乘算时会显示黑色,我们勾选材质实例里需要的参数,将undersidecolor调为1,并将BillboardScale也调为1(为了使调整BillboardSize与Inflate有效)。

然后我们调整BillboardSize,可以看到树叶变得毛茸茸起来了,我们尽量不留空隙,如果在调整参数后美观和空隙之间不能平衡,需要考虑修改网格体。此时还没有菲涅尔效果的边缘光,这是因为emissiveStrength为0,在材质节点内乘算时不显示,我们将其调为1,可以看到树叶全部被高光效果覆盖,调整FresnelExponent控制其范围。调整HueShift%可以改变底部树叶的颜色偏移,normalAdd控制上下分界线的位置,normalPower控制渐变的过渡,大伙可以按自己的喜好调整。

