UE4的材质编辑器的每个节点实际上是一个代码片段,通过连线拼接起来,然后放到一个shader模板里面,生成一个shaderTemplate文件。
shader模板文件是事先写好的,MaterialTemplate.ush。
HLSLTranslator文件里可以看到相关的代码,包含template文件,包含一些材质节点的函数。感觉hlsltranslator是用来把节点代码包括自带的节点代码转换成hlsl代码的一个翻译的东西。
我们写自己的ush文件,如下:
UE4渲染学习——1材质编辑器的本质及如何生成
添加到模板中,如下:
UE4渲染学习——1材质编辑器的本质及如何生成
重新生成工程,然后我们在材质编辑器中创建一个custom节点,在这个节点的code处输入如下代码:
UE4渲染学习——1材质编辑器的本质及如何生成
在custom节点里就能够引用我们自己写的函数了。

我们在hlslTranslator文件里添加这两行代码
UE4渲染学习——1材质编辑器的本质及如何生成
在生成的hlsl代码里就能够找到这句。

而ue4材质编辑器自带的表达式都是一个个类,
UE4渲染学习——1材质编辑器的本质及如何生成
Expression类会调用COmpiler,比如:
UE4渲染学习——1材质编辑器的本质及如何生成
UE4渲染学习——1材质编辑器的本质及如何生成
那FMaterialCompiler干了啥?
UE4渲染学习——1材质编辑器的本质及如何生成
比如texture函数是干啥的?

virtual int32 Texture(UTexture* InTexture,int32& TextureReferenceIndex,ESamplerSourceMode SamplerSource=SSM_FromTextureAsset, ETextureMipValueMode MipValueMode = TMVM_None) override
	{
		if (FeatureLevel == ERHIFeatureLevel::ES2 && ShaderFrequency == SF_Vertex)
		{
			if (MipValueMode != TMVM_MipLevel)
			{
				Errorf(TEXT("Sampling from vertex textures requires an absolute mip level on feature level ES2"));
				return INDEX_NONE;
			}
		}
		else if (ShaderFrequency != SF_Pixel
			&& ErrorUnlessFeatureLevelSupported(ERHIFeatureLevel::ES3_1) == INDEX_NONE)
		{
			return INDEX_NONE;
		}

	//材质类型
		EMaterialValueType ShaderType = InTexture->GetMaterialType();
		//贴图id
		TextureReferenceIndex = Material->GetReferencedTextures().Find(InTexture);

#if DO_CHECK
		// UE-3518: Additional pre-assert logging to help determine the cause of this failure.
		if (TextureReferenceIndex == INDEX_NONE)
		{
			const TArray<UTexture*>& ReferencedTextures = Material->GetReferencedTextures();
			UE_LOG(LogMaterial, Error, TEXT("Compiler->Texture() failed to find texture '%s' in referenced list of size '%i':"), *InTexture->GetName(), ReferencedTextures.Num());
			for (int32 i = 0; i < ReferencedTextures.Num(); ++i)
			{
				UE_LOG(LogMaterial, Error, TEXT("%i: '%s'"), i, ReferencedTextures[i] ? *ReferencedTextures[i]->GetName() : TEXT("nullptr"));
			}
		}
#endif
		checkf(TextureReferenceIndex != INDEX_NONE, TEXT("Material expression called Compiler->Texture() without implementing UMaterialExpression::GetReferencedTexture properly"));

		return AddUniformExpression(new FMaterialUniformExpressionTexture(TextureReferenceIndex, SamplerSource),ShaderType,TEXT(""));
	}

AddUniformExpression会创建一块shader代码块

// AddUniformExpression - Adds an input to the Code array and returns its index.
	int32 AddUniformExpression(FMaterialUniformExpression* UniformExpression,EMaterialValueType Type,const TCHAR* Format,...)
	{
		if (Type == MCT_Unknown)
		{
			return INDEX_NONE;
		}

		check(UniformExpression);

		// Only a texture uniform expression can have MCT_Texture type
		if ((Type & MCT_Texture) && !UniformExpression->GetTextureUniformExpression() && !UniformExpression->GetExternalTextureUniformExpression())
		{
			return Errorf(TEXT("Operation not supported on a Texture"));
		}

		// External textures must have an external texture uniform expression
		if ((Type & MCT_TextureExternal) && !UniformExpression->GetExternalTextureUniformExpression())
		{
			return Errorf(TEXT("Operation not supported on an external texture"));
		}

		if (Type == MCT_StaticBool)
		{
			return Errorf(TEXT("Operation not supported on a Static Bool"));
		}
		
		if (Type == MCT_MaterialAttributes)
		{
			return Errorf(TEXT("Operation not supported on a MaterialAttributes"));
		}

		bool bFoundExistingExpression = false;
		// Search for an existing code chunk with the same uniform expression in the array of all uniform expressions used by this material.
		for (int32 ExpressionIndex = 0; ExpressionIndex < UniformExpressions.Num() && !bFoundExistingExpression; ExpressionIndex++)
		{
			FMaterialUniformExpression* TestExpression = UniformExpressions[ExpressionIndex].UniformExpression;
			check(TestExpression);
			if(TestExpression->IsIdentical(UniformExpression))
			{
				bFoundExistingExpression = true;
				// This code chunk has an identical uniform expression to the new expression, reuse it.
				// This allows multiple material properties to share uniform expressions because AccessUniformExpression uses AddUniqueItem when adding uniform expressions.
				check(Type == UniformExpressions[ExpressionIndex].Type);
				// Search for an existing code chunk with the same uniform expression in the array of code chunks for this material property.
				for(int32 ChunkIndex = 0;ChunkIndex < CurrentScopeChunks->Num();ChunkIndex++)
				{
					FMaterialUniformExpression* OtherExpression = (*CurrentScopeChunks)[ChunkIndex].UniformExpression;
					if(OtherExpression && OtherExpression->IsIdentical(UniformExpression))
					{
						delete UniformExpression;
						// Reuse the entry in CurrentScopeChunks
						return ChunkIndex;
					}
				}
				delete UniformExpression;
				// Use the existing uniform expression from a different material property,
				// And continue so that a code chunk using the uniform expression will be generated for this material property.
				UniformExpression = TestExpression;
				break;
			}

#if 0
			// Test for the case where we have non-identical expressions of the same type and name.
			// This means they exist with separate values and the one retrieved for shading will
			// effectively be random, as we evaluate the first found during expression traversal
			if (TestExpression->GetType() == UniformExpression->GetType()) 
			{
				if (TestExpression->GetType() == &FMaterialUniformExpressionScalarParameter::StaticType)
				{
					FMaterialUniformExpressionScalarParameter* ScalarParameterA = (FMaterialUniformExpressionScalarParameter*)TestExpression;
					FMaterialUniformExpressionScalarParameter* ScalarParameterB = (FMaterialUniformExpressionScalarParameter*)UniformExpression;

					if (!ScalarParameterA->GetParameterInfo().Name.IsNone() && ScalarParameterA->GetParameterInfo() == ScalarParameterB->GetParameterInfo())
					{
						delete UniformExpression;
						return Errorf(TEXT("Invalid scalar parameter '%s' found. Identical parameters must have the same value."), *(ScalarParameterA->GetParameterInfo().Name.ToString()));
					}
				}
				else if (TestExpression->GetType() == &FMaterialUniformExpressionVectorParameter::StaticType) 
				{
					FMaterialUniformExpressionVectorParameter* VectorParameterA = (FMaterialUniformExpressionVectorParameter*)TestExpression;
					FMaterialUniformExpressionVectorParameter* VectorParameterB = (FMaterialUniformExpressionVectorParameter*)UniformExpression;

					// Note: Skipping NAME_SelectionColor here as this behavior is relied on for editor materials
					if (!VectorParameterA->GetParameterInfo().Name.IsNone() && VectorParameterA->GetParameterInfo() == VectorParameterB->GetParameterInfo()
						&& VectorParameterA->GetParameterInfo().Name != NAME_SelectionColor)
					{
						delete UniformExpression;
						return Errorf(TEXT("Invalid vector parameter '%s' found. Identical parameters must have the same value."), *(VectorParameterA->GetParameterInfo().Name.ToString()));
					}
				}
			}
#endif
		}

		int32	BufferSize		= 256;
		TCHAR*	FormattedCode	= NULL;
		int32	Result			= -1;

		while(Result == -1)
		{
			FormattedCode = (TCHAR*) FMemory::Realloc( FormattedCode, BufferSize * sizeof(TCHAR) );
			GET_VARARGS_RESULT( FormattedCode, BufferSize, BufferSize-1, Format, Format, Result );
			BufferSize *= 2;
		};
		FormattedCode[Result] = 0;

		const int32 ReturnIndex = CurrentScopeChunks->Num();
		// Create a new code chunk for the uniform expression
		new(*CurrentScopeChunks) FShaderCodeChunk(UniformExpression,FormattedCode,Type);

		if (!bFoundExistingExpression)
		{
			// Add an entry to the material-wide list of uniform expressions
			new(UniformExpressions) FShaderCodeChunk(UniformExpression,FormattedCode,Type);
		}

		FMemory::Free(FormattedCode);
		return ReturnIndex;
	}

好像是新建了一个FShaderCodeChunk。只有FormattedCode类型的才会创建。

而有的表达式,是添加了一块codeChunk,如下SceneColor:
UE4渲染学习——1材质编辑器的本质及如何生成

有的代码不会增加shader指令,比如组件的mask,所以,在HLSLMaterialTranslator.h里面,AddInlinedCodeChunk就因之而生了,比如获取actor的位置:
UE4渲染学习——1材质编辑器的本质及如何生成
UE4渲染学习——1材质编辑器的本质及如何生成
UE4渲染学习——1材质编辑器的本质及如何生成
而add指令,需要添加到shader
UE4渲染学习——1材质编辑器的本质及如何生成

shader中计算的:abs,add,AntialiasedTextureMask,Arccosine,ArccosineFast,Arcsine,ArcsineFast,Arctangent,ArctangentFast,Arctangent2,Arctangent2Fast,AtmosphericFogColor,AtmosphericLightColor,AtmosphericLightVector,
cpu中计算的:UMaterialExpressionActorPositionWS,Time,AppendVector
uniform变量的计算是在cpu进行的(感觉应该是静态编译完成的),在这个类中MaterialUniformExpressions.h。
比如HLSLMaterialTranslator.h里面的Add:

virtual int32 Add(int32 A,int32 B) override
	{
		if(A == INDEX_NONE || B == INDEX_NONE)
		{
			return INDEX_NONE;
		}

		if(GetParameterUniformExpression(A) && GetParameterUniformExpression(B))
		{
			return AddUniformExpression(new FMaterialUniformExpressionFoldedMath(GetParameterUniformExpression(A),GetParameterUniformExpression(B),FMO_Add),GetArithmeticResultType(A,B),TEXT("(%s + %s)"),*GetParameterCode(A),*GetParameterCode(B));
		}
		else
		{
			return AddCodeChunk(GetArithmeticResultType(A,B),TEXT("(%s + %s)"),*GetParameterCode(A),*GetParameterCode(B));
		}
	}

从上面代码里可以看出,如果参数A和参数B都是常量的话,会new一个FMaterialUniformExpressionFoldedMath类(该类继承了FMaterialUniformExpression类,),在MaterialUniformExpressions.h文件中。AddUniformExpression函数中,不处理贴图采样,staticBool等表达式,对其他的表达式会检查是否已经有相同的,如果有则不处理,如果没有则新建一个FShaderCodeChunk。

int32 AddUniformExpression(FMaterialUniformExpression* UniformExpression,EMaterialValueType Type,const TCHAR* Format,...)
	{
		if (Type == MCT_Unknown)
		{
			return INDEX_NONE;
		}

		check(UniformExpression);

		// Only a texture uniform expression can have MCT_Texture type
		if ((Type & MCT_Texture) && !UniformExpression->GetTextureUniformExpression() && !UniformExpression->GetExternalTextureUniformExpression())
		{
			return Errorf(TEXT("Operation not supported on a Texture"));
		}

		// External textures must have an external texture uniform expression
		if ((Type & MCT_TextureExternal) && !UniformExpression->GetExternalTextureUniformExpression())
		{
			return Errorf(TEXT("Operation not supported on an external texture"));
		}

		if (Type == MCT_StaticBool)
		{
			return Errorf(TEXT("Operation not supported on a Static Bool"));
		}
		
		if (Type == MCT_MaterialAttributes)
		{
			return Errorf(TEXT("Operation not supported on a MaterialAttributes"));
		}

		bool bFoundExistingExpression = false;
		// Search for an existing code chunk with the same uniform expression in the array of all uniform expressions used by this material.
		for (int32 ExpressionIndex = 0; ExpressionIndex < UniformExpressions.Num() && !bFoundExistingExpression; ExpressionIndex++)
		{
			FMaterialUniformExpression* TestExpression = UniformExpressions[ExpressionIndex].UniformExpression;
			check(TestExpression);
			if(TestExpression->IsIdentical(UniformExpression))
			{
				bFoundExistingExpression = true;
				// This code chunk has an identical uniform expression to the new expression, reuse it.
				// This allows multiple material properties to share uniform expressions because AccessUniformExpression uses AddUniqueItem when adding uniform expressions.
				check(Type == UniformExpressions[ExpressionIndex].Type);
				// Search for an existing code chunk with the same uniform expression in the array of code chunks for this material property.
				for(int32 ChunkIndex = 0;ChunkIndex < CurrentScopeChunks->Num();ChunkIndex++)
				{
					FMaterialUniformExpression* OtherExpression = (*CurrentScopeChunks)[ChunkIndex].UniformExpression;
					if(OtherExpression && OtherExpression->IsIdentical(UniformExpression))
					{
						delete UniformExpression;
						// Reuse the entry in CurrentScopeChunks
						return ChunkIndex;
					}
				}
				delete UniformExpression;
				// Use the existing uniform expression from a different material property,
				// And continue so that a code chunk using the uniform expression will be generated for this material property.
				UniformExpression = TestExpression;
				break;
			}

#if 0
			// Test for the case where we have non-identical expressions of the same type and name.
			// This means they exist with separate values and the one retrieved for shading will
			// effectively be random, as we evaluate the first found during expression traversal
			if (TestExpression->GetType() == UniformExpression->GetType()) 
			{
				if (TestExpression->GetType() == &FMaterialUniformExpressionScalarParameter::StaticType)
				{
					FMaterialUniformExpressionScalarParameter* ScalarParameterA = (FMaterialUniformExpressionScalarParameter*)TestExpression;
					FMaterialUniformExpressionScalarParameter* ScalarParameterB = (FMaterialUniformExpressionScalarParameter*)UniformExpression;

					if (!ScalarParameterA->GetParameterInfo().Name.IsNone() && ScalarParameterA->GetParameterInfo() == ScalarParameterB->GetParameterInfo())
					{
						delete UniformExpression;
						return Errorf(TEXT("Invalid scalar parameter '%s' found. Identical parameters must have the same value."), *(ScalarParameterA->GetParameterInfo().Name.ToString()));
					}
				}
				else if (TestExpression->GetType() == &FMaterialUniformExpressionVectorParameter::StaticType) 
				{
					FMaterialUniformExpressionVectorParameter* VectorParameterA = (FMaterialUniformExpressionVectorParameter*)TestExpression;
					FMaterialUniformExpressionVectorParameter* VectorParameterB = (FMaterialUniformExpressionVectorParameter*)UniformExpression;

					// Note: Skipping NAME_SelectionColor here as this behavior is relied on for editor materials
					if (!VectorParameterA->GetParameterInfo().Name.IsNone() && VectorParameterA->GetParameterInfo() == VectorParameterB->GetParameterInfo()
						&& VectorParameterA->GetParameterInfo().Name != NAME_SelectionColor)
					{
						delete UniformExpression;
						return Errorf(TEXT("Invalid vector parameter '%s' found. Identical parameters must have the same value."), *(VectorParameterA->GetParameterInfo().Name.ToString()));
					}
				}
			}
#endif
		}

		int32	BufferSize		= 256;
		TCHAR*	FormattedCode	= NULL;
		int32	Result			= -1;

		while(Result == -1)
		{
			FormattedCode = (TCHAR*) FMemory::Realloc( FormattedCode, BufferSize * sizeof(TCHAR) );
			GET_VARARGS_RESULT( FormattedCode, BufferSize, BufferSize-1, Format, Format, Result );
			BufferSize *= 2;
		};
		FormattedCode[Result] = 0;

		const int32 ReturnIndex = CurrentScopeChunks->Num();
		// Create a new code chunk for the uniform expression
		new(*CurrentScopeChunks) FShaderCodeChunk(UniformExpression,FormattedCode,Type);

		if (!bFoundExistingExpression)
		{
			// Add an entry to the material-wide list of uniform expressions
			new(UniformExpressions) FShaderCodeChunk(UniformExpression,FormattedCode,Type);
		}

		FMemory::Free(FormattedCode);
		return ReturnIndex;
	}

把uniform的东西已经生成了FShaderCodeChunk。在HLSLMaterialTranslator.h文件中,AccessUniformExpression函数中,返回Access一个uniform表达式AddInlinedCodeChunk,而AddInlinedCodeChunk就是前面说过的,不会增加shader指令的代码。AddInlinedCodeChunk又会调用AddCodeChunkInner

// AccessUniformExpression - Adds code to access the value of a uniform expression to the Code array and returns its index.
	int32 AccessUniformExpression(int32 Index)
	{
		check(Index >= 0 && Index < CurrentScopeChunks->Num());
		const FShaderCodeChunk&	CodeChunk = (*CurrentScopeChunks)[Index];
		check(CodeChunk.UniformExpression && !CodeChunk.UniformExpression->IsConstant());

		FMaterialUniformExpressionTexture* TextureUniformExpression = CodeChunk.UniformExpression->GetTextureUniformExpression();
		FMaterialUniformExpressionExternalTexture* ExternalTextureUniformExpression = CodeChunk.UniformExpression->GetExternalTextureUniformExpression();

		// Any code chunk can have a texture uniform expression (eg FMaterialUniformExpressionFlipBookTextureParameter),
		// But a texture code chunk must have a texture uniform expression
		check(!(CodeChunk.Type & MCT_Texture) || TextureUniformExpression || ExternalTextureUniformExpression);
		// External texture samples must have a corresponding uniform expression
		check(!(CodeChunk.Type & MCT_TextureExternal) || ExternalTextureUniformExpression);

		TCHAR FormattedCode[MAX_SPRINTF]=TEXT("");
		if(CodeChunk.Type == MCT_Float)
		{
			const static TCHAR IndexToMask[] = {'x', 'y', 'z', 'w'};
			const int32 ScalarInputIndex = MaterialCompilationOutput.UniformExpressionSet.UniformScalarExpressions.AddUnique(CodeChunk.UniformExpression);
			// Update the above FMemory::Malloc if this FCString::Sprintf grows in size, e.g. %s, ...
			FCString::Sprintf(FormattedCode, TEXT("Material.ScalarExpressions[%u].%c"), ScalarInputIndex / 4, IndexToMask[ScalarInputIndex % 4]);
		}
		else if(CodeChunk.Type & MCT_Float)
		{
			const TCHAR* Mask;
			switch(CodeChunk.Type)
			{
			case MCT_Float:
			case MCT_Float1: Mask = TEXT(".r"); break;
			case MCT_Float2: Mask = TEXT(".rg"); break;
			case MCT_Float3: Mask = TEXT(".rgb"); break;
			default: Mask = TEXT(""); break;
			};

			const int32 VectorInputIndex = MaterialCompilationOutput.UniformExpressionSet.UniformVectorExpressions.AddUnique(CodeChunk.UniformExpression);
			FCString::Sprintf(FormattedCode, TEXT("Material.VectorExpressions[%u]%s"), VectorInputIndex, Mask);
		}
		else if(CodeChunk.Type & MCT_Texture)
		{
			int32 TextureInputIndex = INDEX_NONE;
			const TCHAR* BaseName = TEXT("");
			switch(CodeChunk.Type)
			{
			case MCT_Texture2D:
				TextureInputIndex = MaterialCompilationOutput.UniformExpressionSet.Uniform2DTextureExpressions.AddUnique(TextureUniformExpression);
				BaseName = TEXT("Texture2D");
				break;
			case MCT_TextureCube:
				TextureInputIndex = MaterialCompilationOutput.UniformExpressionSet.UniformCubeTextureExpressions.AddUnique(TextureUniformExpression);
				BaseName = TEXT("TextureCube");
				break;
			case MCT_VolumeTexture:
				TextureInputIndex = MaterialCompilationOutput.UniformExpressionSet.UniformVolumeTextureExpressions.AddUnique(TextureUniformExpression);
				BaseName = TEXT("VolumeTexture");
				break;
			case MCT_TextureExternal:
				TextureInputIndex = MaterialCompilationOutput.UniformExpressionSet.UniformExternalTextureExpressions.AddUnique(ExternalTextureUniformExpression);
				BaseName = TEXT("ExternalTexture");
				break;
			default: UE_LOG(LogMaterial, Fatal,TEXT("Unrecognized texture material value type: %u"),(int32)CodeChunk.Type);
			};
			FCString::Sprintf(FormattedCode, TEXT("Material.%s_%u"), BaseName, TextureInputIndex);
		}
		else
		{
			UE_LOG(LogMaterial, Fatal,TEXT("User input of unknown type: %s"),DescribeType(CodeChunk.Type));
		}

		return AddInlinedCodeChunk((*CurrentScopeChunks)[Index].Type,FormattedCode);
	}

AddInlinedCodeChunk代码如下,会调用AddCodeChunkInner.

/** 
	 * Constructs the formatted code chunk and creates an inlined code chunk from it. 
	 * This should be used instead of AddCodeChunk when the code chunk does not add any actual shader instructions, for example a component mask.
	 */
	int32 AddInlinedCodeChunk(EMaterialValueType Type,const TCHAR* Format,...)
	{
		int32	BufferSize		= 256;
		TCHAR*	FormattedCode	= NULL;
		int32	Result			= -1;

		while(Result == -1)
		{
			FormattedCode = (TCHAR*) FMemory::Realloc( FormattedCode, BufferSize * sizeof(TCHAR) );
			GET_VARARGS_RESULT( FormattedCode, BufferSize, BufferSize-1, Format, Format, Result );
			BufferSize *= 2;
		};
		FormattedCode[Result] = 0;
		const int32 CodeIndex = AddCodeChunkInner(FormattedCode,Type,true);
		FMemory::Free(FormattedCode);

		return CodeIndex;
	}

HLSLMaterialTranslator.h里面,IsMaterialPropertyUsed函数里判断一个属性是否可用,就会判断是否输出为一个uniform,如果是,则该属性不可用。该类中,GetParameterCode函数,判断这个codeChunk如果是uniform表达式,则返回其定义(静态uniform表达式和inlined代码块通过定义访问)。
另外,FShaderCodeChunk是个啥呢?

/** Adds an already formatted inline or referenced code chunk */
	int32 AddCodeChunkInner(const TCHAR* FormattedCode,EMaterialValueType Type,bool bInlined)
	{
		if (Type == MCT_Unknown)
		{
			return INDEX_NONE;
		}

		if (bInlined)
		{
			const int32 CodeIndex = CurrentScopeChunks->Num();
			// Adding an inline code chunk, the definition will be the code to inline
			new(*CurrentScopeChunks) FShaderCodeChunk(FormattedCode,TEXT(""),Type,true);
			return CodeIndex;
		}
		// Can only create temporaries for float and material attribute types.
		else if (Type & (MCT_Float))
		{
			const int32 CodeIndex = CurrentScopeChunks->Num();
			// Allocate a local variable name
			const FString SymbolName = CreateSymbolName(TEXT("Local"));
			// Construct the definition string which stores the result in a temporary and adds a newline for readability
			const FString LocalVariableDefinition = FString("	") + HLSLTypeString(Type) + TEXT(" ") + SymbolName + TEXT(" = ") + FormattedCode + TEXT(";") + LINE_TERMINATOR;
			// Adding a code chunk that creates a local variable
			new(*CurrentScopeChunks) FShaderCodeChunk(*LocalVariableDefinition,SymbolName,Type,false);
			return CodeIndex;
		}
		else
		{
			……
			return INDEX_NONE;
		}
	}

new 了一个FShaderCodeChunk,得到他的局部变量定义,返回他的index。

/** 
	 * Constructs the formatted code chunk and creates a new local variable definition from it. 
	 * This should be used over AddInlinedCodeChunk when the code chunk adds actual instructions, and especially when calling a function.
	 * Creating local variables instead of inlining simplifies the generated code and reduces redundant expression chains,
	 * Making compiles faster and enabling the shader optimizer to do a better job.
	 */
	int32 AddCodeChunk(EMaterialValueType Type,const TCHAR* Format,...)
	{
	……
	}

FSHaderCOdeCHunk类也在HLSLMaterialTranslator.h文件中。

https://zhuanlan.zhihu.com/p/36663720

相关文章: