兩個引擎都用過一段時間,現在主要在使用Unity做遊戲。感覺在材質部分已經戒掉了 用節點式的製作方式。個人最大的感觸是在代碼量,優化演算法,以及數學運算符控制方面,unity的材質製作模式更加省性能,特別是多Pass的問題上 我在UE4上還不知道怎麼樣製作。希望各位大佬指點一下 在UE4上怎麼去寫材質Shader


不光Shader,連整個渲染層都可以,甚至還能和UE自帶的兼容,出現一邊跑DX一邊跑OpenGL的奇景,最近剛剛測試結果可行。

本人的測試平臺是Win10,UE使用4.25.3版本,無奈於極度噁心的RHI封裝和材質結構,決定嘗試一下外置DLL的方法掛載渲染組件。

DXGIFactory和Device這些東西其實並不會有衝突問題的,而且也並沒有很大的overhead,一整套系統包含各種初始化,也就僅僅佔用了20M內存。

API部分初始化沒什麼特殊,直接用龍書案例搞,也沒用到啥高端特性,普通光柵化和Compute Shader夠用了,就用了個最普通的12.0的Feature Level:

至於析構函數?全都不用自己寫,ComPtr智能指針包辦了。由於暫時只用來做GPGPU,所以也沒設置窗口狀態和Swapchain之類的,這些按需配置。

隨便做幾個操作,載入幾個Shader和ID3D12Resource,輸出一下Device狀態,S_OK,沒什麼問題。

這樣Shader Compiling + Configuring + Loading自己搞都沒什麼問題。我個人是Shaderlab死忠粉,全都封裝成一個Shader有多個Pass,每個Pass包含幾個二進位函數的引用和Render State(Compute Shader貌似並沒有Render State,直接裸Kernel),Configure文件也仿著ShaderLab來的,該有的都沾點:

UBT支持熱編,這個還挺舒服的(只改個介面類頭文件一般不會崩,一般吧。。),所以動態Load可能舒服一些,這個可以封裝個純虛類和extern "C"的工廠函數,這個自己開心就好。

想走靜態的懟Plugins,但是Plugins真的噁心,好多暗坑。


  1. custom節點
  2. 再plugin中寫.usf,再custom中直接include導入
  3. 直接給.usf寫配套的cpp類,但是寫法比較鬼畜(每個版本都有點不一樣)


如果只是做普通效果開發,一般來說用CustomNode + return 1的trick,可以滿足大部分的自定義複雜函數的需求。這個trick網上也比較多教程了。

如果你要自定義 Shading Model,或者自定義後處理的流程,則需要搞C++插件了,然後這個網上也有好多教程,UE4官網文檔上也有比較詳細的描述。

比如知乎大佬的這篇文章,這兩種方案都有詳細描述到:

窩窩頭:UE4 HLSL 和 Shader 開發指南和技巧?

zhuanlan.zhihu.com圖標

總的來說,還是沒有Unity的方便,Unity No1。 我自己平時測試啥或者想學習啥效果都是用Unity做的,實在是太方便了。U++寫起來很難受,而且API文檔很不詳細,我太菜也是一方面原因。


可以的,稍微修改一點引擎代碼。主要要解決編譯的入口函數和如何在shader中使用材質參數。

1 自定義入口函數

首先寫一個自己的 Custom 節點類,我是繼承 UE 原有的 Custom 節點。這個類主要用於設置shader文件並判斷該材質是否使用自定義的 shader。

UCLASS()
class UMaterialExpressionMyCustom : public UMaterialExpressionCustom
{
GENERATED_UCLASS_BODY()

// shader文件
UPROPERTY(EditAnywhere, Category=MaterialExpressionCustom)
FString ShaderFilePath;

public:
const FString GetShaderFilePath() const;
};

然後在 FMeshMaterialShaderType::BeginCompileShaderFMaterialShaderType::BeginCompileShader 中修改入口函數名。

const TCHAR *shaderEntryFuncName = GetFunctionName();
const TCHAR *shaderFilename = GetShaderFilename();

// 在 FMaterial 中定義的函數,判斷是否有 MyCustom 節點
if (Material-&>IsUseMyCustomShader())
{
UMaterialExpressionMYCustom *myCustomExrpession = Material-&>GetFirstUMaterialExpressionMyCustom();

// 這個地方要有一個機制判斷當前的 Pass 是否使用自定義的 shader
// 我是通過文件名判斷的,比如 /Engine/Private/BasePassPixelShader.usf
if (IsCustomShaderSupport(shaderFilename))
{
if (this-&>GetFrequency() == SF_Pixel)
{
shaderEntryFuncName = TEXT("MyMainPS");
ShaderEnvironment.SetDefine(TEXT("MY_PIXEL_SHADER"), TEXT("1"));
}
}

}

之後要在合適的地方 include 到自己的 shader 文件。在 FMaterial::BeginCompileShaderMap 中,完成材質翻譯之後,將自己的 shader 文件添加到 include path 中。

if (this-&>IsUseMyCustomShader())
{
// #include "/Engine/Generated/MyCustomMaterial.ush" 被寫在 .usf 最後,見 EngineShadersPrivateBasePassPixelShader.usf
UMaterialExpressionMyCustom *myCustomExrpession = this-&>GetFirstUMaterialExpressionMyCustom();
const FString shaderFilename = *myCustomExrpession-&>GetShaderFilePath();

// 生成參數定義代碼
FString paramcode = MaterialTranslator.MyGetMaterialParametersCode();

MaterialEnvironment-&>IncludeVirtualPathToContentsMap.Add(TEXT("/Engine/Generated/MyCustomMaterial.ush")
, FString::Printf(TEXT("%s
#include "%s" "), *paramcode, *shaderFilename));
}

上面的代碼把自己的 shader 文件在 /Engine/Generated/MyCustomMaterial.ush 中 include 了。之後需要在想要支持自定義 shader 的 pass 中 include MyCustomMaterial.ush。比如要支持 basepass 的 ps,則在 EngineShadersPrivateBasePassPixelShader.usf 文件最後 #include "/Engine/Generated/MyCustomMaterial.ush"

上面做完後,就可以寫自己的 shader 了。下面是一個 BasePass 的 PS,函數簽名和 PixelShaderOutputCommon.ush 中一致。

void MyMainPS(
#if PIXELSHADEROUTPUT_INTERPOLANTS || PIXELSHADEROUTPUT_BASEPASS
FVertexFactoryInterpolantsVSToPS Interpolants,
#endif
#if PIXELSHADEROUTPUT_BASEPASS
FBasePassInterpolantsVSToPS BasePassInterpolants,
#elif PIXELSHADEROUTPUT_MESHDECALPASS
FMeshDecalInterpolants MeshDecalInterpolants,
#endif

in INPUT_POSITION_QUALIFIERS float4 SvPosition : SV_Position // after all interpolators

OPTIONAL_IsFrontFace

, out float4 OutTarget0 : SV_Target0
)
{
OutTarget0 = float4(1, 0, 0, 1);
}

2 使用材質參數

FHLSLMaterialTranslator 定義一個函數 MyGetMaterialParametersCode,遍歷其中的參數節點,將每個參數通過宏定義的方式構造一段代碼。下面代碼中獲取了 Vector 參數,其他類型的參數也可以用類似方法取出。

FString MyGetMaterialParametersCode() const
{
const FUniformExpressionSet uniformSet = MaterialCompilationOutput.UniformExpressionSet;

TArray& paramCode;

for(int i=0; i&GetType() != FMaterialUniformExpressionVectorParameter::StaticType)
{
continue;
}
const FMaterialUniformExpressionVectorParameter *ee = (const FMaterialUniformExpressionVectorParameter*)e;
FString vname = ee-&>GetParameterInfo().Name.ToString();

FString code = FString::Printf(TEXT("#define MyParam_%s Material.VectorExpressions[%d]"), *vname, i);
paramCode.Add(code);
}

// result
FString ret;
for (const FString code : paramCode)
{
ret += code + "
";
}
return ret;
}

在編譯的時候,從材質中翻譯出代碼後,調用以上函數獲得參數定義代碼,寫到 /Engine/Generated/MyCustomMaterial.ush 中,即可在自己的 shader 中使用這些參數了。

void MyMainPS(
#if PIXELSHADEROUTPUT_INTERPOLANTS || PIXELSHADEROUTPUT_BASEPASS
FVertexFactoryInterpolantsVSToPS Interpolants,
#endif
#if PIXELSHADEROUTPUT_BASEPASS
FBasePassInterpolantsVSToPS BasePassInterpolants,
#elif PIXELSHADEROUTPUT_MESHDECALPASS
FMeshDecalInterpolants MeshDecalInterpolants,
#endif

in INPUT_POSITION_QUALIFIERS float4 SvPosition : SV_Position // after all interpolators

OPTIONAL_IsFrontFace

, out float4 OutTarget0 : SV_Target0
)
{
OutTarget0 = float4(MyParam_Color1.xyz, 1);
}

需要注意的是,參數必須要最終連到輸出節點上才能被收入 UniformExpressionSet。


當然可以了,用它的custom node寫,或者直接改usf


可以,ue,houdini,3dmax都支持。


可以

usf ush瞭解一下

但一般動也是為了寫庫,少有直接用於特定材質的shader


有個插件gpu reader, 可以寫很方便


可以,雖然我沒做過


材質編輯器

可以使用cutom 節點來寫HLSL。4.25終於增加了在custom節點中包含ush的功能,可喜可賀。你可以在把usf放在插件中,記得在插件中添加Shader虛擬目錄映射。(可以在我的文章找到寫法,也可以直接查看源碼)

缺點是沒有多Pass功能,你可以手動通過藍圖調用Draw Material To Render Target,將結果繪製到Rt上從而實現多Pass方式。

純代碼

你可以先使用GlobalShader嘗嘗鮮。既然你已經有經驗了,那肯定要使用正確的方式。Ue4中使用的是MeshDraw與RDG框架。這些 @YivanLee 已經寫了相關的文章,主要是直接修改管線。但對於沒有3900X的人,的確有點難。

所以本人也會在後續寫在插件中使用RDG的文章(MeshDraw也可以在插件中使用)。但插件化的一個缺點就是你需要自己實現光照,無法使用Ue4自己的光照系統,除非你渲染出Rt再走一遍材質系統。


推薦閱讀:
相關文章