虚幻4材质系统【1】自定义材质节点

          虚幻4有一套强大的材质编辑器,我们可以用这套编辑器做很多事情,几乎90%的效果都可以通过它来实现,前段时间探索了一下材质系统。我将分以下几个部分来记录我的研究结果。如有错误还请指正。

【1】虚幻4材质系统概述,自定义材质节点。

【2】虚幻4材质和shader的关系,自定义上传shader。

【3】虚幻4渲染资源整理,使用自己的shader,模型。抛弃虚幻4的模型管理,材质编辑器。(纯粹是为了了解虚幻4的原理,实际项目不建议这么做)。



          最直观的,打开虚幻编辑器,我们随便连一个vector3到basecolor上,点编译按钮,我们就得到了一个材质。但是千万不要以为我们眼前看到东西就是shader。从本质上来说,我们眼前的材质节点只是包含一串HLSL代码的容器。

你可以打开引擎代码,找到如下目录(Source/Runtime/Engine/Classes/Material)就能看到引擎里所有材质节点的代码。找到HLSLMaterialTranslator,就会发现,这些节点里面保存的FString,里面加的就是HLSL代码。当我们在材质编辑器里面把线练好后,点下编译,材质节点就会依次执行,把里面的HLSl代码字符串抽出来,加到ShaderMap里,最后就得到了所谓的shader(简单粗暴)。下面就是Floor的HLSLTranslator里面的翻译代码,把节点翻译成HLSL代码

virtual int32 Floor(int32 X) override
	{
		if(X == INDEX_NONE)
		{
			return INDEX_NONE;
		}

		if(GetParameterUniformExpression(X))
		{
			return AddUniformExpression(new FMaterialUniformExpressionFloor(GetParameterUniformExpression(X)),GetParameterType(X),TEXT("floor(%s)"),*GetParameterCode(X));
		}
		else
		{
			return AddCodeChunk(GetParameterType(X),TEXT("floor(%s)"),*GetParameterCode(X));
		}
	}

整理一下执行顺序。首先我们在编辑器连好材质,这些材质节点都会有调用HLSLTranslator的与这个材质节点相称的HLSLTranslator的代码,比如floor节点就会调用HLSLTranslator里的“virtual int32 Floor(int32 X)”这个函数。HLSLTranslator再把这个函数里的HLSL代码字符串加到Shadermap里(加进去还有其他函数调用,并不是HLSLTRaslator直接做的这个操作,不过它是这个操作的发起者。)完成了shadermap的事情之后就是交给引擎的其他类去编译顶点着色器啦,像素着色器啦。。。后面的过程不作讨论了。大概的过程就是这样。

       下面我就实现了一个自己的材质节点。首先创建一个MyMaterialExpression类,派生自UMaterialExpression。下面是.h文件

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpression.h"
#include "MyMaterialExpression.generated.h"

/**
 * 
 */
UCLASS(MinimalAPI, collapsecategories, hidecategories = Object)
class UMyMaterialExpression : public UMaterialExpression
{
	
	GENERATED_UCLASS_BODY()

	//材质节点的输入
	UPROPERTY()
	FExpressionInput Input;


	
#if WITH_EDITOR
	//调用HLSLTranlator的函数来翻译HLSL代码
	virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
	//材质节点的名字
	virtual void GetCaption(TArray<FString>& OutCaptions) const override;
#endif
	
};

Compile函数会调用HLSL翻译代码,把FString翻译成HLSL代码并把它赛道shadermap里。getCaption代码是这个材质节点在材质编辑器里的名字。FExpressionInput是材质节点的输入接口,可以定义多个入口,数量随意。
下面是.cpp

#include "MyMaterialExpression.h"
#include "EditorSupportDelegates.h"
#include "MaterialCompiler.h"
#if WITH_EDITOR
#include "MaterialGraph/MaterialGraphNode_Comment.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#endif //WITH_EDITOR
#define LOCTEXT_NAMESPACE "MaterialExpression"
UMyMaterialExpression::UMyMaterialExpression(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// Structure to hold one-time initialization
	struct FConstructorStatics
	{
		FText NAME_Math;
		FConstructorStatics()
			: NAME_Math(LOCTEXT("MyMaterial", "MyMaterial"))
		{
		}
	};
	static FConstructorStatics ConstructorStatics;

#if WITH_EDITORONLY_DATA
	MenuCategories.Add(ConstructorStatics.NAME_Math);
#endif
}


#if WITH_EDITOR
int32 UMyMaterialExpression::Compile(FMaterialCompiler* Compiler, int32 OutputIndex)
{
	int32 Result = INDEX_NONE;

	if (!Input.GetTracedInput().Expression)
	{
		// 当输入口没有连线时报错,这个输出可以在材质编辑器里看到
		Result = Compiler->Errorf(TEXT("哇,报错了耶!!你的节点没连好吧!!!"));
	}
	else
	{
		//我只做了一个简单的返回一个颜色
		Result = Compiler->Constant3(0, 1, 1);
	}

	return Result;
}

void UMyMaterialExpression::GetCaption(TArray<FString>& OutCaptions) const
{
	OutCaptions.Add(TEXT("MyMaterialExpression"));
}
#endif // WITH_EDITOR


下面是效果图:报错信息是可以自己定义的



大功告成啦。

我们可以再把节点做复杂一点,不知道有没有人发现虚幻的材质编辑器没有很多个接口的switch,我们可以自己做一个


h文件:

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpression.h"
#include "MyMaterialExpression.generated.h"

/**
 * 
 */
UCLASS(MinimalAPI, collapsecategories, hidecategories = Object)
class UMyMaterialExpression : public UMaterialExpression
{
	
	GENERATED_UCLASS_BODY()

	//材质节点的输入
	UPROPERTY()
	FExpressionInput Input1;
	UPROPERTY()
	FExpressionInput Input2;
	UPROPERTY()
	FExpressionInput Input3;
	UPROPERTY()
	FExpressionInput Input4;
	UPROPERTY()
	FExpressionInput Input5;
	
	UPROPERTY(EditAnywhere, Category = "MyMaterial")
	float myIndex;

#if WITH_EDITOR
	//调用HLSLTranlator的函数来翻译HLSL代码
	virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
	//材质节点的名字
	virtual void GetCaption(TArray<FString>& OutCaptions) const override;
#endif
	
};

cpp

#include "MyMaterialExpression.h"
#include "EditorSupportDelegates.h"
#include "MaterialCompiler.h"
#if WITH_EDITOR
#include "MaterialGraph/MaterialGraphNode_Comment.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#endif //WITH_EDITOR
#define LOCTEXT_NAMESPACE "MaterialExpression"
UMyMaterialExpression::UMyMaterialExpression(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// Structure to hold one-time initialization
	struct FConstructorStatics
	{
		FText NAME_Math;
		FConstructorStatics()
			: NAME_Math(LOCTEXT("MyMaterial", "MyMaterial"))
		{
		}
	};
	static FConstructorStatics ConstructorStatics;

	myIndex = 0.0f;

#if WITH_EDITORONLY_DATA
	MenuCategories.Add(ConstructorStatics.NAME_Math);
#endif
}


#if WITH_EDITOR
int32 UMyMaterialExpression::Compile(FMaterialCompiler* Compiler, int32 OutputIndex)
{
	int32 Result = INDEX_NONE;

	if (!Input1.GetTracedInput().Expression)
	{
		// 当输入口没有连线时报错,这个输出可以在材质编辑器里看到
		return Compiler->Errorf(TEXT("哇,报错了耶!!你的节点第一根线没连好!!!"));
	}
	if (!Input2.GetTracedInput().Expression)
	{
		// 当输入口没有连线时报错,这个输出可以在材质编辑器里看到
		return Compiler->Errorf(TEXT("哇,报错了耶!!你的节点第二根线没连好!!!"));
	}
	if (!Input3.GetTracedInput().Expression)
	{
		// 当输入口没有连线时报错,这个输出可以在材质编辑器里看到
		return Compiler->Errorf(TEXT("哇,报错了耶!!你的节点第三根线没连好!!!"));
	}
	if (!Input4.GetTracedInput().Expression)
	{
		// 当输入口没有连线时报错,这个输出可以在材质编辑器里看到
		return Compiler->Errorf(TEXT("哇,报错了耶!!你的节点第四根线没连好!!!"));
	}
	if (!Input5.GetTracedInput().Expression)
	{
		// 当输入口没有连线时报错,这个输出可以在材质编辑器里看到
		return Compiler->Errorf(TEXT("哇,报错了耶!!你的节点第五根线没连好!!!"));
	}
	
	int32 newIndex = myIndex;

	if (newIndex>5||newIndex<0)
	{
		return Compiler->Errorf(TEXT("index指数不对,应该在0到5之间"));
	}

	switch (newIndex)
	{
	case 0:
		return Input1.Compile(Compiler);
	case 1:
		return Input2.Compile(Compiler);
	case 2:
		return Input3.Compile(Compiler);
	case 3:
		return Input4.Compile(Compiler);
	case 4:
		return Input5.Compile(Compiler);
	}
	
	return Result;
}

void UMyMaterialExpression::GetCaption(TArray<FString>& OutCaptions) const
{
	OutCaptions.Add(TEXT("MyMaterialExpression"));
}
#endif // WITH_EDITOR


最后的效果


  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值