UE4的材质编辑器的每个节点实际上是一个代码片段,通过连线拼接起来,然后放到一个shader模板里面,生成一个shaderTemplate文件。
shader模板文件是事先写好的,MaterialTemplate.ush。
HLSLTranslator文件里可以看到相关的代码,包含template文件,包含一些材质节点的函数。感觉hlsltranslator是用来把节点代码包括自带的节点代码转换成hlsl代码的一个翻译的东西。
我们写自己的ush文件,如下:
添加到模板中,如下:
重新生成工程,然后我们在材质编辑器中创建一个custom节点,在这个节点的code处输入如下代码:
在custom节点里就能够引用我们自己写的函数了。
我们在hlslTranslator文件里添加这两行代码
在生成的hlsl代码里就能够找到这句。
而ue4材质编辑器自带的表达式都是一个个类,
Expression类会调用COmpiler,比如:
那FMaterialCompiler干了啥?
比如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:
有的代码不会增加shader指令,比如组件的mask,所以,在HLSLMaterialTranslator.h里面,AddInlinedCodeChunk就因之而生了,比如获取actor的位置:
而add指令,需要添加到shader
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