UE4 用C++构建自定义材质 完成视频抠像

Posted wyk023

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE4 用C++构建自定义材质 完成视频抠像相关的知识,希望对你有一定的参考价值。

众所周知,UE4中的材质可以保存成资产,可以在材质编辑器中打开,通过设置材质表达式以及调用材质函数进行编辑。编辑完成后,可以设置在组件上,或者在蓝图中调用。

现在老板告诉你:我不想管理那么多材质文件,不想调用蓝图,能否用C++代码直接生成我想要的材质?
看完这篇文章你可以自信地说:OK。

一、准备知识

  • 在材质编辑器中,通过材质表达式构建的材质

    最右侧是待编辑的材质,可以看到它有各种属性节点(不是所有节点都同时有效,选择不同的材质域、混合模式、着色模型等,会开启或关闭相应的节点),每种属性的具体性质由左侧连接的材质表达式和材质函数来决定。
  • 材质编辑器中的材质函数

    带有 Input 前缀的节点表示函数输入,带有 Output 前缀的节点表示函数输出。
    其实双击点开材质函数会发现,函数内依然是一堆材质表达式,所以,构建一个材质的基本单位就是材质表达式,无非就是要确定材质表达式的类型,以及它们之间如何是连接的。 材质函数是以资产的形式加载和保存的,在蓝图中可以直接访问,在C++中需要用LoadObject的方式主动加载访问。本篇文章中没有使用UMaterialFunction(主要是没搞懂怎么用,请大神指教),而是把所有节点都展开成UMaterialExpression来实现的。
  • 材质表达式在C++中对应的类
    你会用的这个目录下的类:
    Engine/Source/Runtime/Engine/Classes/Materials

    这个目录下包含了各种材质表达式,老多了,他们都是UMaterialExpression的子孙。蓝图中的每一种材质表达式都能在这里找到相应的C++类。
  • 在C++中材质表达式的连接
    输入节点:UMaterialExpression的子类可能拥有不同数量和名字的输入节点,用FExpressionInput保存,如下:

    输出节点:没有输出节点,材质表达式(UMaterialExpression)本身就是下一个节点的输入,可以类比一个链表,链表的每个节点保存有指向上一个节点的指针。你可以看到FExpressionInput类中保存了UMaterialExpression指针,指向输入的材质表达式。
    连接示例:所以如果要连接两个材质表达式A和B,你应该把B的某个FExpressionInput中的UmaterialExpression指针置为A。即 B->LightMass.Expression = A;

二、大致思路

  1. 先用材质编辑器构建出想要的材质,或者已经有现成的材质。
  2. 将其转换为C++实现。
    注意:蓝图是基于C++封装的,所有蓝图一定能在C++中找到相应实现。在C++中实现自定义材质,其实就是用C++生成(New出对象)各种材质表达式,然后连接各节点。虽然材质球从直观的蓝图变成了不那么直观的C++代码,但它们表示的拓扑结构是一致的。
  3. 在C++中大致分为两步:(1)New出我们需要的表达式对象 (2)按编辑器中的结构去连接它们

三、代码示例

C++动态生成抠像材质

  1. 语境介绍
    UE在播放视频时,会将视频帧Buffer转换成媒体纹理,然后根据媒体纹理构建材质并应用在Mesh上,这样就可以在物体表面播放视频。而我现在想集成视频抠像功能,一个办法就是在纹理转换成材质之前,加入抠像的算法。由于整个视频组件(包括生成Mesh、创建播放器、创建媒体源、播放视频画面…)都是C++实现,所以现在需要在代码中集成自定义抠像材质。(参考文章:在 UE4 中设置色键材质
  2. 将材质在编辑器中打开,查看我需要在C++中New哪些类?

    这是一个现成的解决方案,用蓝图实现。可以看到,最左侧输入是纹理采样(对应UMaterialExpressionTextureSample),它来自于视频媒体纹理,左下角Param指示了抠像键值(对应UMaterialExpressionVectorParameter),绿幕抠像RGB值则是(0.0, 1.0, 0.0)。点开材质函数可以看到:

    VectorLength也是材质函数,点开可以看到里面有一个常量和两个Distance:

    这里有相当多的节点需要我们去实现。依次查看节点类型,可以发现有材质表达式常量(对应UMaterialExpressionConstant),有材质表达式相减(对应UMaterialExpressionSubtract),有材质表达式自定义ExtractColor(对应UMaterialExpressionCustom),等等。
  3. 转换成C++代码
    在我的组件中,创建媒体纹理和通过纹理创建动态材质实例主要通过 CreateMediaMaterialCreateMaterialFromTexture(创建抠像材质的实现在此函数中,重点关注此函数) 这两个函数实现。
  • CreateMediaMaterial
void UPXVideoComponent::CreateMediaMaterial()

	FString MediaTextureAssetName = GetOwner()->GetFName().ToString() + TEXT("_VideoTexture");
	EObjectFlags theFlags = (RF_Public | RF_Transactional);
	
	MediaTexture = NewObject<UMediaTexture>(this, UMediaTexture::StaticClass(), *MediaTextureAssetName, theFlags);
	MediaTexture->UpdateResource();
	MediaTexture->SetMediaPlayer(MediaPlayer);

	UTexture* theTexture = Cast<UTexture>(MediaTexture);
	// 通过以下函数由媒体纹理生成媒体材质,在此函数中实现材质的自定义
	UObject* theMatObj = CreateMaterialFromTexture(theTexture);
	UMaterialInterface* MediaTextureMat = Cast<UMaterialInterface>(theMatObj);

	MediaDynamicMaterial = VideoMeshComponent->CreateAndSetMaterialInstanceDynamicFromMaterial(0, MediaTextureMat);
	MediaDynamicMaterial->SetScalarParameterValue("Enable Video Alpha", 0.0f);
	MediaDynamicMaterial->SetScalarParameterValue("Enable Video Texture", 1.0f);
	MediaDynamicMaterial->SetTextureParameterValue(FName(TEXT("VideoTexture")), theTexture);

  • CreateMaterialFromTexture(重点
UObject* UPXVideoComponent::CreateMaterialFromTexture(UTexture* UnrealTexture)

	FString MediaMaterialAssetName = UnrealTexture->GetFName().ToString() + TEXT("_Mat");
	EObjectFlags theFlags = (RF_Public | RF_Transactional);
	UMaterial* UnrealMaterial = nullptr;

	UPackage* Package = nullptr;
	UnrealMaterial = NewObject<UMaterial>(this, UMaterial::StaticClass(), *MediaMaterialAssetName, theFlags);

	const int X = -340;
	const int Y = 0;

	// Create a new texture sample expression, 
	// this is our texture input node into the material output.
	// [ 对应材质表达式纹理采样 ]
	UMaterialExpressionTextureSample* UnrealTextureExpression = NewObject<UMaterialExpressionTextureSample>(UnrealMaterial);

	UnrealTextureExpression->Texture = UnrealTexture;
	UnrealTextureExpression->AutoSetSampleType();
	UnrealTextureExpression->MaterialExpressionEditorX += X;
	UnrealTextureExpression->MaterialExpressionEditorY += Y;

	UnrealMaterial->Expressions.Add(UnrealTextureExpression);
	UnrealMaterial->EmissiveColor.Expression = UnrealTextureExpression;
	UnrealMaterial->SetShadingModel(EMaterialShadingModel::MSM_Unlit);

	if (bEnableKeying)
	
		// Chroma_Key_Alpha (Simplified)
		// - Prepare for connection.
		
		// [ 对应材质表达式向量参数 ]
		UMaterialExpressionVectorParameter* ChromaParam = NewObject<UMaterialExpressionVectorParameter>(UnrealMaterial);
		ChromaParam->DefaultValue.R = float(ChromaColor.R * 1.0f / 255.0f);
		ChromaParam->DefaultValue.G = float(ChromaColor.G * 1.0f / 255.0f);
		ChromaParam->DefaultValue.B = float(ChromaColor.B * 1.0f / 255.0f);

		// [ 对应材质表达式常量 ]
		UMaterialExpressionConstant* ConstantLumaMask = NewObject<UMaterialExpressionConstant>(UnrealMaterial);
		ConstantLumaMask->R = 2.0f;

		FString ShaderCode = TEXT("float Luma = dot(Color, 1); \\
		float ColorMask = exp(-Luma * 2 * PI / LumaMask); \\
		Color = lerp(Color, Luma, ColorMask); \\
		return Color / (dot(Color, 2));");

		FCustomInput Color;
		Color.InputName = FName(TEXT("Color"));
		FCustomInput LumaMask;
		LumaMask.InputName = FName(TEXT("LumaMask"));

		// [ 对应材质表达式自定义 ]
		UMaterialExpressionCustom* ExtractColor_Chroma = NewObject<UMaterialExpressionCustom>(UnrealMaterial);
		ExtractColor_Chroma->Code = ShaderCode;
		ExtractColor_Chroma->Inputs.Empty();
		ExtractColor_Chroma->Inputs.Add(Color);
		ExtractColor_Chroma->Inputs.Add(LumaMask);
		ExtractColor_Chroma->OutputType = ECustomMaterialOutputType::CMOT_Float3;
		ExtractColor_Chroma->Desc = TEXT("ExtractColor");

		// [ 对应材质表达式自定义 ]
		UMaterialExpressionCustom* ExtractColor_Image = NewObject<UMaterialExpressionCustom>(UnrealMaterial);
		ExtractColor_Image->Code = ShaderCode;
		ExtractColor_Image->Inputs.Empty();
		ExtractColor_Image->Inputs.Add(Color);
		ExtractColor_Image->Inputs.Add(LumaMask);
		ExtractColor_Image->OutputType = ECustomMaterialOutputType::CMOT_Float3;
		ExtractColor_Image->Desc = TEXT("ExtractColor");

		// [ 对应材质表达式相减 ]
		UMaterialExpressionSubtract* Subtract = NewObject<UMaterialExpressionSubtract>(UnrealMaterial);

		// [ 对应材质表达式常量 ]
		UMaterialExpressionConstant* ConstantZero = NewObject<UMaterialExpressionConstant>(UnrealMaterial);
		ConstantZero->R = 0.0f;

		// [ 对应材质表达式距离 ]
		UMaterialExpressionDistance* Distance = NewObject<UMaterialExpressionDistance>(UnrealMaterial);

		// - Graph node connection.
		// 开始连接各个材质表达式,拓扑结构和材质编辑器中展现的结构保持一致
		Distance->A.Expression = ConstantZero;
		Distance->B.Expression = Subtract;

		Subtract->A.Expression = ExtractColor_Chroma;
		Subtract->B.Expression = ExtractColor_Image;

		ExtractColor_Chroma->Inputs[0].Input.Expression = ChromaParam;
		ExtractColor_Chroma->Inputs[1].Input.Expression = ConstantLumaMask;

		ExtractColor_Image->Inputs[0].Input.Expression = UnrealTextureExpression;
		ExtractColor_Image->Inputs[1].Input.Expression = ConstantLumaMask;

		UnrealMaterial->Expressions.Add(Distance);
		UnrealMaterial->OpacityMask.Expression = Distance;
		UnrealMaterial->BlendMode = EBlendMode::BLEND_Masked;
	

	UnrealMaterial->PostLoad();

	return UnrealMaterial;

  • CreateMaterialFromTextureV2(升级版实现 自动抠像 抠像效果更佳
UObject* UPXVideoComponent::CreateMaterialFromTextureV2(UTexture* UnrealTexture)

	FString MediaMaterialAssetName = UnrealTexture->GetFName().ToString() + TEXT("_Mat");
	EObjectFlags theFlags = (RF_Public | RF_Transactional);
	UMaterial* UnrealMaterial = nullptr;

	UnrealMaterial = NewObject<UMaterial>(this, UMaterial::StaticClass(), *MediaMaterialAssetName, theFlags);

	const int X = -340;
	const int Y = 0;

	// Create a new texture sample expression, 
	UMaterialExpressionTextureCoordinate* UVs = NewObject<UMaterialExpressionTextureCoordinate>(UnrealMaterial);
	UVs->CoordinateIndex = 0;
	UVs->UTiling = 1.0f;
	UVs->VTiling = 1.0f;

	UMaterialExpressionTextureSampleParameter2D* Video = NewObject<UMaterialExpressionTextureSampleParameter2D>(UnrealMaterial);
	Video->SamplerType = EMaterialSamplerType::SAMPLERTYPE_External;
	Video->Texture = UnrealTexture;
	Video->Coordinates.Expression = UVs;
	Video->MaterialExpressionEditorX += X;
	Video->MaterialExpressionEditorY += Y;

	UnrealMaterial->Expressions.Add(Video);
	UnrealMaterial->EmissiveColor.Expression = Video;
	UnrealMaterial->SetShadingModel(EMaterialShadingModel::MSM_Unlit);

	if (bEnableKeying)
	
		// M_CPCutout
		// - Prepare for connection
		// -- Emissive Color
		UMaterialExpressionComponentMask* Mask_R = NewObject<UMaterialExpressionComponentMask>(UnrealMaterial);
		Mask_R->R = 1;
		Mask_R->G = 0;
		Mask_R->B = 0;
		Mask_R->A = 0;
		UMaterialExpressionComponentMask* Mask_G = NewObject<UMaterialExpressionComponentMask>(UnrealMaterial);
		Mask_G->R = 0;
		Mask_G->G = 1;
		Mask_G->B = 0;
		Mask_G->A = 0;
		UMaterialExpressionComponentMask* Mask_B = NewObject<UMaterialExpressionComponentMask>(UnrealMaterial);
		Mask_B->R = 0;
		Mask_B->G = 0;
		Mask_B->B = 1;
		Mask_B->A = 0;

		UMaterialExpressionAdd* Add = NewObject<UMaterialExpressionAdd>(UnrealMaterial);
		UMaterialExpressionDivide* Divide = NewObject<UMaterialExpressionDivide>(UnrealMaterial);
		Divide->ConstB = 2.0f;

		UMaterialExpressionIf* If = NewObject<UMaterialExpressionIf>(UnrealMaterial);

		UMaterialExpressionAppendVector* Append_0 = NewObject<UMaterialExpressionAppendVector>(UnrealMaterial);
		UMaterialExpressionAppendVector* Append_1 = NewObject<UMaterialExpressionAppendVector>(UnrealMaterial);

		// -- Opacity Mask
		UMaterialExpressionTextureObject* TextureObject = NewObject<UMaterialExpressionTextureObject>(UnrealMaterial);
		TextureObject->SamplerType = EMaterialSamplerType::SAMPLERTYPE_External;
		TextureObject->Texture = UnrealTexture;

		UMaterialExpressionTextureCoordinate* TextureCoordinate = NewObject<UMaterialExpressionTextureCoordinate>(UnrealMaterial);
		TextureCoordinate->CoordinateIndex = 0;
		TextureCoordinate->UTiling = 1.0f;
		TextureCoordinate->VTiling = 1.0f;

		UMaterialExpressionConstant2Vector* Constant2Vector = NewObject<UMaterialExpressionConstant2Vector>(UnrealMaterial);
		Constant2Vector->R = 0.0f;
		Constant2Vector->G = 0.0f;

		FString CustomCode = TEXT("Tex.Load(int3(Coordinate,0))");
		FCustomInput Tex;
		Tex.InputName = FName(TEXT("Tex"));
		FCustomInput UV;
		UV.InputName = FName(TEXT("UV"));
		FCustomInput Coordinate;
		Coordinate.InputName = FName(TEXT("Coordinate"));
		UMaterialExpressionCustom* Custom = NewObject<UMaterialExpressionCustom>(UnrealMaterial);
		Custom->Code = CustomCode;
		Custom->Inputs.Empty();
		Custom->Inputs.Add(Tex);
		Custom->Inputs.Add(UV);
		Custom->Inputs.Add(Coordinate);
		Custom->OutputType = ECustomMaterialOutputType::CMOT_Float3;
		Custom->Desc = TEXT("Custom");

		UMaterialExpressionVectorParameter* AlphaThresOffset = NewObject<UMaterialExpressionVectorParameter>(UnrealMaterial);
		AlphaThresOffset->ParameterName = "AlphaThresOffset";
		AlphaThresOffset->DefaultValue.R = 1.0f;
		AlphaThresOffset->DefaultValue.G = 0.0f;
		AlphaThresOffset->DefaultValue.B = 0.0f;
		AlphaThresOffset->DefaultValue.A = 1.0f;
		UMaterialExpressionComponentMask* AlphaThresOffset_Mask = NewObject<UMaterialExpressionComponentMask>(UnrealMaterial);
		AlphaThresOffset_Mask->R = 1;
		AlphaThresOffset_Mask->G = 1;
		AlphaThresOffset_Mask->B = 0;
		AlphaThresOffset_Mask->A = 0;

		UMaterialExpressionVectorParameter* WeightsRB = NewObject<UMaterialExpressionVectorParameter>(UnrealMaterial);
		WeightsRB->ParameterName = "WeightsRB";
		WeightsRB->DefaultValue.R = 0.5f;
		WeightsRB->DefaultValue.G = 0.5f;
		WeightsRB->DefaultValue.B = 0.0f;
		WeightsRB->DefaultValue.A = 1.0f;
		UMaterialExpressionComponentMask* WeightsRB_Mask = NewObject<UMaterialExpressionComponentMask>(UnrealMaterial);
		WeightsRB_Mask->R = 1;
		WeightsRB_Mask->G = 1;
		WeightsRB_Mask->B = 0;
		WeightsRB_Mask->A = 0;

		UMaterialExpressionVectorParameter* ClipBW = NewObject<UMaterialExpressionVectorParameter>(UnrealMaterial);
		ClipBW->ParameterName = "ClipBW";
		ClipBW->DefaultValue.R = 0.0f;
		ClipBW->DefaultValue.G = 1.0f;
		ClipBW->DefaultValue.B = 0.0f;
		ClipBW->DefaultValue.A = 1.0f;
		UMaterialExpressionComponentMask* ClipBW_Mask = NewObject<UMaterialExpressionComponentMask>(UnrealMaterial);
		ClipBW_Mask->R = 1;
		ClipBW_Mask->G = 1;
		ClipBW_Mask->B = 0;
		ClipBW_Mask->A = 0;

		UMaterialExpressionScalarParameter* Unpremult = NewObject<UMaterialExpressionScalarParameter>(UnrealMaterial);
		Unpremult->DefaultValue = 0.0f;

		FString ColorDiffKeyerCode = TEXT("const float epsilon = 0.001; \\
			float diffGR = max(KeyColor.g - KeyColor.r, epsilon); \\
			float diffGB = max(KeyColor.g - KeyColor.b, epsilon); \\
			float diff = min(diffGR, diffGB); \\
			float weightedRB = GreenScreen.g - (GreenScreen.r * WeightsRB.x + GreenScreen.b * WeightsRB.y); \\
			float alpha = weightedRB / diff; \\
			alpha /= AlphaThresOffset.x; \\
			alpha = (alpha - ClipBW.x) / (ClipBW.y - ClipBW.x); \\
			alpha = saturate((alpha - AlphaThresOffset.y) / (1.0 - AlphaThresOffset.y)); \\
			alpha = 1.0 - alpha; \\
			float3 rgb = lerp(GreenScreen, GreenScreen * alpha, Unpremult); \\
			return float4(rgb, alpha);");
		FCustomInput GreenScreenInput;
		GreenScreenInput.InputName = FName(TEXT("GreenScreen"));
		FCustomInput KeyColorInput;
		KeyColorInput.InputName = FName(TEXT("KeyColor"));
		FCustomInput AlphaThresOffsetInput;
		AlphaThresOffsetInput.InputName = FName(TEXT("AlphaThresOffset"));
		FCustomInput WeightsRBInput;
		WeightsRBInput.InputName = FName(TEXT("WeightsRB"));
		FCustomInput ClipBWInput;
		ClipBWInput.InputName = FName(TEXT("ClipBW"));
		FCustomInput UnpremultInput;

UE4-材质法线强度调整法线贴图混合自定义材质函数材质边缘过渡植被动态效果

一、法线强度

想控制一张法线贴图的呈现强度,不能直接对整个贴图的UV进行运算,需要对法线贴图的RG通道进行运算,并将每个通道的计算结果通过追加节点Append组成新的数值,改变强度参数,从而改变物体表现的强度。

1.各通道使用乘法与强度参数NormalIntensify相乘

2.使用追加节点,组合新的向量。并将值与Normal链接。

二、法线贴图融合 BlendAngleCorrectedNormals

1.使用 BlendAngleCorrectedNormals 节点,完成两张法线贴图的叠加。

2.蓝图连线

3.如果想要控制第二张贴图的法线强度,按照一图中的方法复制强度控制及追加逻辑,要注意的是强度参数命名避免使用同一名称,如果使用相同的命名,系统会自动将两个参数识别成一个。

三、使用普通贴图制作法线 NormalFromHeightMap

1.使用 NormalFromHeightMap节点

2.注意:需要将贴图转换为纹理对象

四、材质函数的编辑

以NormalFromHeightMap节点为例,对材质的重复率进行控制。

注意:一定要在新建的材质函数中操作,不要再源蓝图函数中操作。

1.创建材质函数,在材质与纹理的分类中找到,打开创建的材质函数,双击 NormalFromHeightMap节点,进入到函数中,将逻辑复制到新蓝图中,并将输出值连接好。 

2.在原节点中的 InputHeightMapUVOffset传入值后面有(S)标识,说明此值是参数,在方法体内找到此参数对应的节点,进行复制,并重命名,就可以增加节点参数的输入。

2.复制上图节点,重命名,并完善UV的重复率逻辑

 3.保存,应用。将我们新建的材质函数拖拽放入到材质蓝图中

4.在节点中出现我们自己增加的参数。将此参数提升为变量,通过材质实例可以进行控制。

 5.完整连线,创建材质实例,可以在实例中调整参数,查看效果。

五、修复UV拉伸 WorldAlignedTexture

我们拉伸简单物体的时候,贴图也会跟随拉伸。使用此节点可以设置UV进行平铺,物体拉伸时,贴图会自动进行平铺。

注意:不要对特别复杂的物体进行此操作,因为此操作没有作用。也不要对球体进行此操作,球体顶部也会存在拉伸。

 将贴图转换为纹理对象与节点进行连接。

六、材质边缘过渡 DitherTemporalAA

使用乘法调整变异的融合度。此节点基本不使用。

作用:能够让模型与模型,模型与地面接触的边缘进行颜色过渡,不会过于生硬。 

 

六、植被动态效果

使用SimpleGrassWind节点设置风的大小、权重、速度、断裂处拉伸贴图

此节点在材质中与世界场景位置偏移链接,通过对网格体顶点的移动来造成偏移,所以当随风晃动的时候会出现中间区域撕裂的情况,就需要链接一张断裂处拉伸贴图用来避免出现断裂的情况。

直接节点使用会出现整个植被都会随风飘摇,需要处理一张黑白贴图,将植被贴图根部为黑色,与原结果相乘,就会实现黑白贴图中黑色区域部分不动,其余部分动的效果。

如果觉得制作黑白贴图太麻烦,可以使用VertexColor节点,使用网格体绘制笔刷,模式选择绿色通道,选择绘制模式。选择要绘制顶点着色的植被。

绘制时注意绘制的颜色,如果选择绘制颜色黑色,当绘制时,变黑的地方,使用风效果时,是不会动的。

备注:植被在VertexColor节点中一般使用绿色通道,置换时使用红色通道。

 

 

以上是关于UE4 用C++构建自定义材质 完成视频抠像的主要内容,如果未能解决你的问题,请参考以下文章

虚幻引擎 4,用 c++ 导入?

虚幻UE4中移动端水材质的设置

UE4/UE5 虚幻引擎,材质HueShift色相(色调改变)

虚幻UE4中如何采集360度全景图片和VR视频

UE4-Loading时材质Shader序列化流程

UE4-Loading时材质Shader序列化流程