兩個引擎都用過一段時間,現在主要在使用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真的噁心,好多暗坑。
如果只是做普通效果開發,一般來說用CustomNode + return 1的trick,可以滿足大部分的自定義複雜函數的需求。這個trick網上也比較多教程了。
如果你要自定義 Shading Model,或者自定義後處理的流程,則需要搞C++插件了,然後這個網上也有好多教程,UE4官網文檔上也有比較詳細的描述。
比如知乎大佬的這篇文章,這兩種方案都有詳細描述到:
總的來說,還是沒有Unity的方便,Unity No1。 我自己平時測試啥或者想學習啥效果都是用Unity做的,實在是太方便了。U++寫起來很難受,而且API文檔很不詳細,我太菜也是一方面原因。
可以的,稍微修改一點引擎代碼。主要要解決編譯的入口函數和如何在shader中使用材質參數。
首先寫一個自己的 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::BeginCompileShader 和 FMaterialShaderType::BeginCompileShader 中修改入口函數名。
FMeshMaterialShaderType::BeginCompileShader
FMaterialShaderType::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 中。
FMaterial::BeginCompileShaderMap
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"。
/Engine/Generated/MyCustomMaterial.ush
include MyCustomMaterial.ush
#include "/Engine/Generated/MyCustomMaterial.ush"
上面做完後,就可以寫自己的 shader 了。下面是一個 BasePass 的 PS,函數簽名和 PixelShaderOutputCommon.ush 中一致。
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); }
在 FHLSLMaterialTranslator 定義一個函數 MyGetMaterialParametersCode,遍歷其中的參數節點,將每個參數通過宏定義的方式構造一段代碼。下面代碼中獲取了 Vector 參數,其他類型的參數也可以用類似方法取出。
FHLSLMaterialTranslator
MyGetMaterialParametersCode
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 中使用這些參數了。
, 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再走一遍材質系統。