在游戏过程中更改资产的材质属性,是向玩家提供视觉反馈、传达玩法信息或增强沉浸感的有效方式。 例如:
玩家皮肤可以改变外观,用于表示不同的状态效果。
拾取物可以发光,以便吸引附近玩家的注意。
下雨时,地面从干燥变为湿润可以增加游戏内的沉浸感。
在本教程中,你将使用蓝图在运行时更改材质属性,创建一个交互式技术演示。 在此演示中,你将创建一个水球,当玩家在关卡中推动它时,它会浸湿地板。
你还将尝试使用自发光材质、简单的遮罩和静态开关,创建能在湿地板上产生反射效果的布景。
创建可立即使用的地板资产
你将为技术演示构建的第一个资产是地板。 在Lvl_Adventure的Room 3中,你将创建一个可用于游戏的成品地板资产,以替换占位地板网格体。
在开始之前,让我们暂停一下,思考你的实现方法。 在上一篇教程中,你了解到平铺纹理是一种为大型网格体(如地板)添加表面的低成本方法。 你也已经了解网格的UV会影响纹理的显示效果。
Room 3中的占位地板由三个网格体组成,每个网格体都有不同的UV。
在下面的演示中,请注意,由于不同网格之间UV不一致,平铺纹理看起来大小不同。 这给地板的整体表面处理带来了一个挑战。
你将通过使用三平面投影来解决这个问题。 三平面投影是一种表面处理技术,它忽略UV并使纹理实现世界对齐。 这种方法在无法修正UV,或者处理难以展开UV的几何体时非常有用。
基于这一思路,你将使用表达式让M_Surfaces实现世界对齐。 由于材质实例会继承父材质的属性,MI_Surfaces_Floor也将采用世界对齐方式。 然后,你将把MI_Surfaces_Floor应用到这个蓝图上,以替换场景中的占位网格体。
为了提前避免UV问题,我们建议在建模软件中创建模块化资产时,适当地缩放其UV。
创建世界对齐材质
要使你的父材质世界对齐,请执行以下操作:
在内容浏览器中,导航至文件路径All > Content > AdventureGame > Artist > Materials,并打开
M_Surfaces。选择UV 平铺(UV Tiling)注释框中的所有节点、漫反射贴图和法线贴图。 将它们删除。
右键单击图表并搜索纹理对象(Texture Object)。 创建两个这样的节点。
选择第一个纹理对象(Texture Object)。 在细节面板中,将其纹理(Texture)设置为DefaultDiffuse。
右键单击纹理对象(Texture Object)并选择转换为参数(Convert to Parameter)。 将新参数命名为
Diffuse。选择第二个纹理对象(Texture Object)。 在细节面板中,将其纹理(Texture)设置为DefaultNormal。
右键单击纹理对象(Texture Object)并选择转换为参数(Convert to Parameter)。 将新参数命名为
Normal。在Diffuse节点上,从输出端口拖出并添加一个World Aligned Texture节点。
从XYZ Texture输出端口,将其连接到Multiply节点的A输入端口(在漫反射色调(Diffuse Hue)注释框内)。
在Normal节点上,从输出端口拖出并添加一个World Aligned Normal节点。
从XYZ纹理(XYZ Texture)输出端口,将其连接到M_Surfaces材质根节点的法线(Normal)输入。
在图表中右键单击并搜索Scalar Parameter。 将其命名为
Texture Scaling。将其默认值(Default Value)设置为
214。将纹理缩放(Texture Scaling)的输出连接到两个WorldAligned节点的TextureSizeV3输入端。
保存你的材质。
你的材质图表应如下所示:
接下来,你将创建一个蓝图,用于替换占位地板。
创建地板蓝图
要创建该蓝图,请执行以下操作:
在内容浏览器中,导航至路径All > Content > AdventureGame > Artist,右键单击并选择新建文件夹(New Folder)。
将文件夹命名为
Blueprints。在Blueprints文件夹中右键单击,选择蓝图类(Blueprint Class),然后选择Actor作为父类(Parent Class)。
将蓝图命名为
BP_Floor并双击,以便在蓝图编辑器中将其打开。在内容浏览器中,选择All文件夹,并搜索
SM_Cube。将SM_Cube拖入蓝图编辑器(Blueprint Editor)的组件(Components)面板,作为BP_Floor的子项。
将SM_Cube命名为
Floor。在细节面板中,确保其缩放(Scale)为
1.0, 1.0, 1.0。在Materials > Element 0旁边,将其材质设置为
MI_Surfaces_Floor。在组件(Components)选项卡中,选择DefaultSceneRoot,点击添加(Add),然后添加一个
盒体碰撞(Box Collision)。将盒体碰撞命名为
Trigger。在细节面板中,将触发器(Trigger)的缩放(Scale)设置为
1.5, 1.5, 1.5。将触发器(Trigger)的位置设置为
50, 50, 55。保存并编译蓝图。
将一个BP_Floor实例拖入你的关卡进行移动。 请注意,当你移动网格体时,纹理仍然保持在原来的位置。 如果你尝试缩放网格体,会发现网格体本身发生了变化,但纹理并不会随之缩放。
接下来,你将用BP_Floor的实例替换Room 3中的占位符地板。
替换占位网格体
现在,你可以按照自己的喜好,用BP_Floor替换Room 3中的占位网格并体进行排列。 我们创建了一个与占位符匹配的地板:
如果你希望使用上面展示的内容,可以按照以下步骤复制该关卡:
确认你已根据上述教程更新了
M_Surfaces并创建了BP_Floor。在大纲视图中,选择Room 3文件夹内的所有内容,然后按删除(Delete)。
复制以下代码段。
Console OutputBegin Map Begin Level Begin Actor Class=/Script/Engine.StaticMeshActor Name=StaticMeshActor_2 Archetype="/Script/Engine.StaticMeshActor'/Script/Engine.Default__StaticMeshActor'" ExportPath="/Script/Engine.StaticMeshActor'/Game/SFEFWFWEEEWEF.SFEFWFWEEEWEF:PersistentLevel.StaticMeshActor_2'" Begin Object Class=/Script/Engine.StaticMeshComponent Name="StaticMeshComponent0" Archetype="/Script/Engine.StaticMeshComponent'/Script/Engine.Default__StaticMeshActor:StaticMeshComponent0'" ExportPath="/Script/Engine.StaticMeshComponent'/Game/SFEFWFWEEEWEF.SFEFWFWEEEWEF:PersistentLevel.StaticMeshActor_2.StaticMeshComponent0'" End Object Begin Object Name="StaticMeshComponent0" ExportPath="/Script/Engine.StaticMeshComponent'/Game/SFEFWFWEEEWEF.SFEFWFWEEEWEF:PersistentLevel.StaticMeshActor_2.StaticMeshComponent0'" StaticMesh="/Script/Engine.StaticMesh'/Game/LevelPrototyping/Meshes/SM_Cylinder.SM_Cylinder'" StaticMeshImportVersion=1 bUseDefaultCollision=False StaticMeshDerivedDataKey="STATICMESH_FD1BFC73B5510AD60DFC65F62C1E933E_228332BAE0224DD294E232B87D83948FQuadricMeshReduction_V2$2e1_6D3AF6A2$2d5FD0$2d469B$2dB0D8$2dB6D9979EE5D2_CONSTRAINED0_100100000000000000000000000100000000000080FFFFFFFFFFFFFFFFFFFFFFFF000000000000803F00000000000000803F0000803F00000000000000003D19FC1626C9B2485E57DB4B8EC731318B8215AE8D46FAD400000000010000000100000000000000010000000100000000000000000000000100000001000000400000000000000001000000000000000000F03F000000000000F03F000000000000F03F0000803F00000000050000004E6F6E65000C00000030000000803FFFFFFFFF0000803FFFFFFFFF0000000000000041000000000000A0420303030000000000000000_RT00_0"在视口中按下Ctrl + V。
接下来,你将创建水球并使用动态材质实例(Dynamic Material Instances)将地板从干燥变为湿润。
通过玩家交互更改材质
水球将是一个启用了物理效果的对象,能够触发地板属性变化,使其呈现湿润外观。
创建水球
要创建并展示水球,请执行以下操作:
在内容浏览器的Blueprints文件夹中,右键单击以创建一个新的蓝图类(Blueprint Class)。
选择Actor作为父类(Parent Class)。
将新蓝图命名为
BP_WaterBall,并在蓝图编辑器(Blueprint Editor)中将其打开。在组件(Component)选项卡中,点击添加(Add)并搜索
Sphere。在细节面板中,将Materials > Element 0设置为
M_Water。还是在细节面板中,在物理(Physics)下,勾选模拟物理(Simulate Physics)。
保存并编译蓝图。
将一个
BP_WaterBall实例拖入你的关卡中。
在视口中右键单击并选择从此处运行(Play From Here),在你的关卡中测试水球。 当你撞到该球体时,它应当沿着地面弹跳。
如果你的关卡中有敌人,将它们的最大速度(Max Speed)设置为0,以避免它们追逐你,或者直接将其移除。
接下来,你将在BP_Floor中使用逻辑创建一个动态材质实例(Dynamic Material Instance)。
创建动态材质实例
动态材质实例(Dynamic Material Instance)是通过脚本(例如蓝图)在运行时生成的材质实例,可以在游戏运行时动态更改其属性。
在本节中,你将创建蓝图逻辑,该逻辑引用指定给地板网格体的材质(MI_Surfaces_Floor),从中生成一个动态材质实例,并将新实例指定给地板。 然后,动态材质实例会更改你在上一个教程中公开的属性,即粗糙度(Roughness)和漫反射色调(Diffuse Hue),以模拟湿地板。
在运行时,这次替换会让地板看起来仿佛被一层薄薄的水浸湿。
要创建动态材质实例,请执行以下操作:
在内容浏览器中,双击
BP_Floor,在蓝图编辑器中将其打开。在事件图表中,删除Event BeginPlay之外的所有节点。
在组件(Components)选项卡中,将一个Floor实例拖入事件图表。
从Floor的输出引脚拖出并搜索Get Material。
从Get Material节点的返回值(Return Value)输出引脚拖出连线,并添加一个Create Dynamic MaterialInstance节点,目标为Kismet Material Library。
将Event BeginPlay的输出连接到创建动态材质(Create Dynamic Material Instance)实例的输入执行引脚。
从动态材质实例(Dynamic Material Instance)的返回值(Return Value)拖出并选择提升为变量(Promote to Variable)。
在细节面板中,将此变量重命名为
Dynamic Mat Ref。将另一个地板(Floor)实例拖入事件图表。
从Floor的输出端口拖出并搜索Set Material。
将设置(Set)节点上的白色执行输出引脚连接到设置材质(Set Material)节点的执行输入引脚。
将设置节点上的蓝色输出引脚连接到设置材质节点的材质输入引脚。
为了保持图表整洁,选中所有节点并按C键。 将注释框命名为
Create Dynamic Material。保存并编译。
你的事件图表现在应该如下所示:
调用事件
要触发地板的湿润效果,你需要一个事件来检测BP_Floor是否与BP_WaterBall重叠,并根据结果调用相应的外观效果。
让我们来设计这段逻辑:
当某个Actor与
BP_Floor发生重叠时:检查另一个Actor是否(等于)
BP_WaterBall。 如果条件为(true),则:调用
BP_Floor的湿润外观。
否则,如果结果为false,则:
不执行任何操作。
要创建此逻辑,请执行以下操作:
在
BP_Floor的EventGraph中右键单击,并搜索Add Custom Event。 将此节点命名为Call Wet Look。在组件(Components)选项卡中,右键单击触发器(Trigger)并创建Add Event > Add OnComponentBeginOverlap。
OnComponentBeginOverlap用于检测碰撞。
从OnComponentBeginOverlap中的Other Actor输出端口拖出并创建Get Class。
从返回值(Return Value)输出引脚拖出并创建Equal节点。
右键单击选择类(Select Class)输入并选择提升为变量(Promote to Variable)。
将变量命名为
WaterBall,然后编译你的蓝图。在细节面板中,将WaterBall的默认值(Default Value)设置为
BP_WaterBall。从Equal节点的输出端口拖出并创建一个分支(Branch)。
从分支(Branch)的True输出端口拖出并搜索Call Wet Look。
将OnComponentBeginOverlap的执行输出引脚连接到分支(Branch)节点的执行输入引脚。
你的事件图表现在应该如下所示:
控制材质属性
现在,你将构建地板的湿润变体。 由于地板是石质且多孔的,它在吸水时的颜色应变暗(通过漫反射色调(Diffuse Hue)实现)。 由于表面覆盖着一层薄薄的水,地面应当呈现出光滑效果(通过调整Roughness实现)。
要为湿润外观构建逻辑,请执行以下操作:
将Dynamic Mat Ref变量拖到EventGraph中,然后从列表中选择Get。
从输出引脚拖出并创建设置标量参数值(Set Scalar Parameter Value)。
从同一个输出引脚拖出连线,创建设置向量参数值(Set Vector Parameter Value)节点。
还记得你在
M_Surfaces中公开的参数吗? 粗糙度(Roughness)是一个标量(单个值),而漫反射色调(Diffuse Hue)是一个向量(三个值,即RGB)。在Set Scalar Parameter Value节点上,右键单击Parameter Name并选择提升为变量(Promote to Variable)。 将变量命名为
Roughness。在Set Vector Parameter Value节点上,右键单击Parameter Name 并选择提升为变量(Promote to Variable)。 将变量命名为
Diffuse Hue。编译蓝图并为每个参数名称输入值:
选择粗糙度(Roughness)变量并输入
Roughness作为其默认值(Default Value)。选择漫反射色调(Diffuse Hue)变量,并输入
Diffuse Hue作为其默认值(Default Value)。默认值必须与
M_Surfaces中的相应参数匹配。
将Set Vector Parameter Value节点的输出执行引脚连接到Set Scalar Parameter Value节点的输入执行引脚。
除了让地板从干燥变为湿润外,你还可以使用逻辑来控制这种变化发生的速度。 由于材质被“浸湿”需要一定时间,你将逐步在不同材质属性之间进行过渡,以实现渐变效果。 你可以使用线性插值(Lerp)节点来实现这一点。
Lerp节点用于在两个数值之间进行平滑混合或插值。 它非常适合在不同颜色、纹理或效果之间进行渐变过渡。
要创建线性插值(Lerp),请执行以下操作:
从标量参数(Scalar Parameter)节点的值(Value)输入引脚拖出连线,创建一个Lerp节点。 这将控制粗糙度(Roughness)。
在Lerp节点上,将A值设置为
1.0。 这将是干燥外观的粗糙度值。 将B值保持为0.0。 这将是湿润外观的值。你已经通过上一篇教程了解到:粗糙度(Roughness)为1表示哑光,为0表示光滑。(glossy)。 你希望水看起来有光泽。
从Set Vector Parameter Value节点的Value输入端口拖出并创建一个Lerp节点。 这将控制漫反射色调(Diffuse Hue)。
从新Lerp的A值拖出并选择提升为变量(Promote to Variable)(线性颜色)。 将变量命名为Unsaturated。
从Lerp的B值拖出并选择提升为变量(Promote to Variable)。 将变量命名为
Saturated。编译你的蓝图。 在细节面板中,将不饱和(Unsaturated)的默认值设置为
CDDAFFFF。在细节面板中,将饱和度(Saturated)的默认值设置为较深的颜色,例如
656C7FFF。
你的事件图表现在应该如下所示:
现在,你已经通过蓝图逻辑为地板创建了一个全新的外观。 然而,你还缺少一个用于驱动这套逻辑的核心组件:时间轴(Timeline)。
使用时间轴驱动逻辑
与动画软件中的时间轴非常相似,时间轴(Timeline)节点会在关键帧之间随时间驱动数值变化。 你将使用该节点,在指定时间内实现干湿效果之间的插值过渡。
要创建时间线,请按照以下步骤操作:
在事件图表中右键单击,搜索并创建添加时间轴(Add Timeline)。
双击Timeline节点以打开Timeline_Template选项卡。
单击轨道(Track),然后从列表中选择添加浮点轨道(Add Float Track)。
将轨道命名为
Alpha。 此轨道名称将作为输出引脚出现在时间轴(Timeline)节点上。将时间轴长度(Length)设置为
3.0秒。在时间轴(Timeline)中右键单击,选择添加关键帧(Add Key)。
选中关键帧(显示为蓝色高亮)后,将该关键帧的时间(Time)和值(Value)设置为
0.0。创建第二个关键帧,并将其时间(Time)和值(Value)设置为
1.0。右键单击第一个关键帧并选择用户(User)。
保存并编译。
此时间轴现在会输出一个在三秒内从0到1进行混合的数值。 现在,你可以将剩余的节点连接到时间轴(Timeline):
回到事件图表,将Call Wet Look的输出连接到Timeline上的Play输入。
将时间轴(Timeline)上的更新(Update)输出连接到设置标量参数值(Set Scalar Parameter Value)的执行输入(Exec input)。
将时间轴的Alpha输出连接到两个Lerps的Alpha输入。
选中这些节点并按下C键添加注释。 将注释框命名为
Lerp Dynamic Materials。保存并编译。
你完整的事件图表现在应该如下所示:
要测试你的效果,在关卡中右键单击并选择从此处运行(Play From Here)。 当你在房间内推动水球时,地板应呈现出“被淹没”的效果。
在白天环境下,湿润表面的反射效果并不是那么清晰。 为了展示地板的反射效果,你将调暗关卡环境,并使用自发光材质( Emissive Material)添加光照。
自发光材质
自发光材质(Emissive Materials)是创建自发光效果的一种低成本方式。 它们可以用于创建发光效果,并整合进更复杂的材质中,例如角色的科幻护甲上的LED灯,或汽车的刹车灯。
自发光材质还能够与Lumen全局光照和反射(Lumen Global Illumination and Reflections)系统交互,这意味着它们会与周围环境相互作用。
自发光材质的亮度可以通过浮点值控制,范围从0(无光)到1(自发光),或大于1(产生泛光的自发光)。
自发光材质和反射效果在较暗的关卡中会更加明显。 要更改关卡中的整体光照,请执行以下操作:
在大纲视图中,选择定向光源(Directional Light)。
在视口中,按下Ctrl + L,然后移动鼠标以调整关卡中定向光源的位置和相对时间。
通常不建议将自发光材质用于环境光照。 使用自发光材质作为光源可能会产生非预期的结果。 相反,我们建议在环境中使用真实的光源。
创建自发光材质
在本教程中,你将创建霓虹灯标牌,并将它们摆放在关卡中,使其倒映在湿漉漉的石质地面上。 为实现这一点,你将创建一个灵活的父材质,以便将以下参数传播到其子实例中:
发光颜色
自发光亮度
纹理遮罩
要创建自发光材质,请执行以下操作:
在内容浏览器中,导航至路径All > Content > AdventureGame > Artist > Materials,创建一个新的材质(Material)。
将材质命名为
M_EmissiveSign,然后双击它,以便在材质编辑器中将其打开。在材质图表中,从材质根节点上的自发光颜色(Emissive Color)输入拖出,并从选择列表中添加一个乘法(Multiply)节点。
从Multipl)节点的A输入端拖出,并添加一个Constant3Vector。
右键单击Constant3Vector,并选择转换为参数(Convert to Parameter)。
将该参数重命名为
Color。双击色条,为你的自发光效果选择一个颜色。
从Multiply节点的B输入端口拖出,并从选择列表中添加一个常量(Constant)节点。
将常量转换为参数,命名为
Brightness,并将其值设置为25。
你的材质图表应如下所示:
使用钳制(Clamps)来限制参数
虽然自发光亮度理论上没有上限,但你可以在父材质中使用钳制来自定义最小和最大亮度范围。 钳制可以让你通过滑块更轻松地调整数值,尤其是在处理较小数值或对数值变化较为敏感的情况下。
与其他参数一样,钳制会传播到材质实例。
要钳制亮度常量,请执行以下操作:
在材质图表中,选择亮度(Brightness)参数。
在细节面板中,将滑块最大值(Slider Max)设置为
50。
接下来,你将使用纹理遮罩为你的标牌添加内容。
创建简单遮罩
纹理遮罩(Texture Mask)是一种灰度(Alpha)或单通道纹理贴图,用于显示或隐藏材质的某些区域。 你可以将Alpha遮罩想象成图层;遮罩的白色区域显示下层的信息,黑色区域则将其隐藏。
在你的自发光材质中,你将使用Alpha遮罩来显示或隐藏发光区域,以形成霓虹灯牌的内容。
要在M_Emissive父材质内创建遮罩,请执行以下操作:
在未选中任何节点的情况下,导航到细节面板。 在混合模式(Blend Mode)旁边,点击下拉菜单并选择已遮罩(Masked)。
在事件图表中,右键单击并创建一个纹理采样(Texture Sample)。
在细节面板中,在纹理(Texture)旁边,搜索
T_UE_Logo_M。该纹理随虚幻引擎提供,无需下载或自行创建。
将纹理采样(Texture Sample)上的RGB输出连接到材质根节点上的不透明蒙版(Opacity Mask)输入。
右键单击纹理采样(Texture Sample)并选择转换为参数(Convert to Parameter)。 将参数命名为
LED Sign。
你的材质图表应如下所示:
你现在已经创建了一个模拟霓虹灯牌效果的材质。 接下来,你将通过反转遮罩进一步提高父材质的灵活性,并增加你可以创建的独特资产数量:
要实现这一点,你也可以为反转和未反转的遮罩分别创建独立的材质实例。 相反,你将使用静态开关(Static Switch)从任何由M_EmissiveSign创建的材质实例内部切换反转。
使用静态开关切换参数
在上一个教程中,你学习了材质传播层次结构,子实例继承父材质的属性。
实例可以自定义继承的参数,但不能彻底弃用——除非借助 静态开关(Static Switch)。 在父材质中设置的静态开关(Static Switch),可以让子实例切换参数的开启或关闭状态。
由于被关闭的参数不会被编译,静态开关可以在运行时提高性能。 不过,由于每个布尔值都会创建一个新的着色器排列组合,这可能会大幅增加编译时间(取决于材质的复杂性)。 开关的取值取决于你的使用方式以及项目的开发需求。
运行时(Runtime)指的是游戏运行期间。 编译时(Complie time)指的是游戏正在编译、尚未运行的阶段。
要创建用于控制遮罩反转的静态开关,请执行以下操作:
在材质图表中,选中LED Sign节点,按下Ctrl + D进行复制。
将新节点重命名为
Screen。从Screen节点的RGB输出引脚拖出并搜索One Minus。
在图表中右键单击并搜索Static Switch Parameter。 将开关命名为
Flip Mask?。将One Minus的输出连接到开关的False输入端。
将LED Sign的RGB输出连接到开关的True输入端。
将开关的输出端口连接到材质根节点的Opacity Mask输入。
保存你的材质。
你的材质图表应如下所示:
现在你可以从M_EmissiveSign创建材质实例,从而分别控制亮度、颜色、纹理贴图或遮罩反转。
将M_EmissiveSign的实例应用到关卡中新建或现有的网格体上,以创建你选择的场景。 当你在关卡中滚动水球时,你的招牌将使用Lumen的全局光照和反射系统(Global Illumination and Reflections System)(默认开启)反射在湿地板上。 你将在下一篇教程中了解更多关于Lumen以及其他反射系统的内容。
下一步
在下一篇教程中,你将学习更多关于后期处理体积(Post-Process Volumes)中的反射、光照系统,以及如何为关卡应用不同的镜头内效果。