幸福341 发表于 2020-12-21 20:28

Unreal材质面板添加全局属性节点

Unreal添加全局的材质变量一般通过材质收集器来实现,这篇博客提供另一种内嵌到材质蓝图节点上的方法,在实现的过程中,也能理解一下Unreal的材质蓝图是怎么工作的。
Unreal材质蓝图相关的引擎层代码在这个路径下:\UnrealEngine\Engine\Source\Runtime\Engine\Classes\Materials。在材质蓝图的节点基本都继承自UMaterialExpression,然后实现自己的Compile方法,通过调用HLSLMaterialTranslator来翻译出HLSL代码块 。每个节点有输入和输出,通过拓扑排序的方式去处理整个节点树,直到输出到材质的Result节点上。然后就会生成一个void CalcPixelMaterialInputs(in out FMaterialPixelParameters Parameters, in out FPixelMaterialInputs PixelMaterialInputs)这样的方法。其中FMaterialPixelParameters里存放了一些基本常量,如:
struct FMaterialPixelParameters
{
#if NUM_TEX_COORD_INTERPOLATORS
float2 TexCoords;
#endif

/** Interpolated vertex color, in linear color space. */
half4 VertexColor;

/** Normalized world space normal. */
half3 WorldNormal;

/** Normalized world space reflected camera vector. */
half3 ReflectionVector;

/** Normalized world space camera vector, which is the vector from the point being shaded to the camera position. */
half3 CameraVector;

/** World space light vector, only valid when rendering a light function. */
half3 LightVector;

/**
* Like SV_Position (.xy is pixel position at pixel center, z:DeviceZ, .w:SceneDepth)
* using shader generated value SV_POSITION
* Note: this is not relative to the current viewport. RelativePixelPosition = MaterialParameters.SvPosition.xy - View.ViewRectMin.xy;
*/
float4 SvPosition;

/** Post projection position reconstructed from SvPosition, before the divide by W. left..top -1..1, bottom..top -1..1 within the viewport, W is the SceneDepth */
float4 ScreenPosition;

half UnMirrored;

half TwoSidedSign;

/**
* Orthonormal rotation-only transform from tangent space to world space
* The transpose(TangentToWorld) is WorldToTangent, and TangentToWorld is WorldVertexNormal
*/
half3x3 TangentToWorld;

#if USE_WORLDVERTEXNORMAL_CENTER_INTERPOLATION
/** World vertex normal interpolated at the pixel center that is safe to use for derivatives. */
half3 WorldVertexNormal_Center;
#endif

/**
* Interpolated worldspace position of this pixel
* todo: Make this TranslatedWorldPosition and also rename the VS/DS/HS WorldPosition to be TranslatedWorldPosition
*/
float3 AbsoluteWorldPosition;

/**
* Interpolated worldspace position of this pixel, centered around the camera
*/
float3 WorldPosition_CamRelative;

/**
* Interpolated worldspace position of this pixel, not including any world position offset or displacement.
* Only valid if shader is compiled with NEEDS_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS, otherwise just contains 0
*/
float3 WorldPosition_NoOffsets;

/**
* Interpolated worldspace position of this pixel, not including any world position offset or displacement.
* Only valid if shader is compiled with NEEDS_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS, otherwise just contains 0
*/
float3 WorldPosition_NoOffsets_CamRelative;

/** Offset applied to the lighting position for translucency, used to break up aliasing artifacts. */
half3 LightingPositionOffset;

float AOMaterialMask;

#if LIGHTMAP_UV_ACCESS
float2 LightmapUVs;
#endif

#if USE_INSTANCING
half4 PerInstanceParams;
#endif

// Index into View.PrimitiveSceneData
uint PrimitiveId;

/** Per-particle properties. Only valid for particle vertex factories. */
FMaterialParticleParameters Particle;

#if (ES2_PROFILE || ES3_1_PROFILE)
float4 LayerWeights;
#endif

#if TEX_COORD_SCALE_ANALYSIS
/** Parameters used by the MaterialTexCoordScales shader. */
FTexCoordScalesParams TexCoordScalesParams;
#endif

#if POST_PROCESS_MATERIAL && (FEATURE_LEVEL <= FEATURE_LEVEL_ES3_1)
/** Used in mobile custom pp material to preserve original SceneColor Alpha */
half BackupSceneColorAlpha;
#endif

#if COMPILER_HLSL
// Workaround for "error X3067: 'GetObjectWorldPosition': ambiguous function call"
// Which happens when FMaterialPixelParameters and FMaterialVertexParameters have the same number of floats with the HLSL compiler ver 9.29.952.3111
// Function overload resolution appears to identify types based on how many floats / ints / etc they contain
uint Dummy;
#endif

#if NUM_VIRTUALTEXTURE_SAMPLES || LIGHTMAP_VT_ENABLED
FVirtualTextureFeedbackParams VirtualTextureFeedback;
#endif
};FPixelMaterialInputs则是材质蓝图的Result节点相关的属性,这个方法样例贴一下。
void CalcPixelMaterialInputs(in out FMaterialPixelParameters Parameters, in out FPixelMaterialInputs PixelMaterialInputs)
{
// Initial calculations (required for Normal)

// The Normal is a special case as it might have its own expressions and also be used to calculate other inputs, so perform the assignment here
PixelMaterialInputs.Normal = MaterialFloat3(0.00000000,0.00000000,1.00000000);


// Note that here MaterialNormal can be in world space or tangent space
float3 MaterialNormal = GetMaterialNormal(Parameters, PixelMaterialInputs);

#if MATERIAL_TANGENTSPACENORMAL
#if SIMPLE_FORWARD_SHADING
Parameters.WorldNormal = float3(0, 0, 1);
#endif

#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4
// ES2 will rely on only the final normalize for performance
MaterialNormal = normalize(MaterialNormal);
#endif

// normalizing after the tangent space to world space conversion improves quality with sheared bases (UV layout to WS causes shrearing)
// use full precision normalize to avoid overflows
Parameters.WorldNormal = TransformTangentNormalToWorld(Parameters.TangentToWorld, MaterialNormal);

#else //MATERIAL_TANGENTSPACENORMAL

Parameters.WorldNormal = normalize(MaterialNormal);

#endif //MATERIAL_TANGENTSPACENORMAL

#if MATERIAL_TANGENTSPACENORMAL
// flip the normal for backfaces being rendered with a two-sided material
Parameters.WorldNormal *= Parameters.TwoSidedSign;
#endif

Parameters.ReflectionVector = ReflectionAboutCustomWorldNormal(Parameters, Parameters.WorldNormal, false);

#if !PARTICLE_SPRITE_FACTORY
Parameters.Particle.MotionBlurFade = 1.0f;
#endif // !PARTICLE_SPRITE_FACTORY

// Now the rest of the inputs
MaterialFloat4 Local0 = ProcessMaterialColorTextureLookup(Texture2DSampleBias(Material.Texture2D_0, Material.Texture2D_0Sampler,Parameters.TexCoords.xy,View.MaterialTextureMipBias));

PixelMaterialInputs.EmissiveColor = Material.VectorExpressions.rgb;
PixelMaterialInputs.Opacty = 1.00000000;
PixelMaterialInputs.OpacityMask = 1.00000000;
PixelMaterialInputs.BaseColor = Local0.rgb;
PixelMaterialInputs.Metallic = 0.00000000;
PixelMaterialInputs.Specular = 0.50000000;
PixelMaterialInputs.Roughness = 0.50000000;
PixelMaterialInputs.Subsurface = 0;
PixelMaterialInputs.AmbientOcclusion = 1.00000000;
PixelMaterialInputs.Refraction = 0;
PixelMaterialInputs.PixelDepthOffset = 0.00000000;
PixelMaterialInputs.ShadingModel = 1;

}到这一步基本就把材质蓝图面板翻译并计算到FPixelMaterialInputs里边去了,然后就是Unreal会有一些shader模板,以移动平台为例为:MobileBasePassVertexShader.usf和MobileBasePassPixelShader.usf,这个里边就直接使用上面计算出来的变量了。这样一个完整的HLSL代码就生成出来了。
好了,大致流程清楚了,现在关键的是材质蓝图的节点以及翻译问题。这里以在材质蓝图实现一个Shader全局变量节点为例来掠一掠这个流程。
在UnrealEngine\Engine\Source\Runtime\Engine\Classes\Materials路径下新建MaterialExpressionMyGlobalVector.h,然后填写以下代码。
#pragma once

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

UCLASS(collapsecategories, hidecategories = Object)
class UMaterialExpressionMyGlobalVector : public UMaterialExpression
{
GENERATED_UCLASS_BODY()

//~ Begin UMaterialExpression Interface
#if WITH_EDITOR
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
virtual void GetCaption(TArray<FString>& OutCaptions) const override;
virtual uint32 GetOutputType(int32 OutputIndex) override;
#endif
//~ End UMaterialExpression Interface
};这里最好编译一下,因为要生成generated.h,Unreal有个不太好的地方,编译错误就不能生成generated.h,而.h类又必须include这个generated.h,总之保证生成了MaterialExpressionMyGlobalVector.generated.h。
打开MaterialCompile.h,在FMaterialCompiler我们申明一下Complie这个节点的方法。




在FProxyMaterialCompiler这个类实现




在HLSLMaterialTranslator.h中实现这个方法




这里我们还是按照上一章的做法,放在View上,所以我们只需要添加一段这样的代码就可以了,如果对其他的翻译方法感兴趣,可以查阅这个类的其他方法。
打开MaterialExpressions.cpp,这个里边有所有的蓝图节点的实现方法,把我们的类include添加。




实现重载方法。


UMaterialExpressionMyGlobalVector::UMaterialExpressionMyGlobalVector(const FObjectInitializer& ObjectInitializer)
        : Super(ObjectInitializer)
{
#if WITH_EDITORONLY_DATA
        // Structure to hold one-time initialization
        struct FConstructorStatics
        {
                FText NAME_Constants;
                FConstructorStatics()
                        : NAME_Constants(LOCTEXT("Constants", "Constants"))
                {
                }
        };
        static FConstructorStatics ConstructorStatics;

        MenuCategories.Add(ConstructorStatics.NAME_Constants);

        bShaderInputData = true;
#endif
}

#if WITH_EDITOR
int32 UMaterialExpressionMyGlobalVector::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
        return Compiler->MyGlobalVector();
}

void UMaterialExpressionMyGlobalVector::GetCaption(TArray<FString>& OutCaptions) const
{
        OutCaptions.Add(TEXT("MyGlobalVector"));
}
uint32 UMaterialExpressionMyGlobalVector::GetOutputType(int32 OutputIndex)
{
        return MCT_Float4;
}
#endif // WITH_EDITOR
然后在像上一章添加全局贴图的方式添加全局变量
SceneView.h




SkyLightComponent.h




SceneManagement.h


SkyLightComponent.cpp




SceneRendering.cpp




编译运行,在材质界面右键添加GlobalVector








保存材质挂到Mesh上。
在SkyLight面板,修改MyGLobalVector值,材质上的值就会实时变化。






页: [1]
查看完整版本: Unreal材质面板添加全局属性节点