找回密码
 立即注册
查看: 1101|回复: 10

[笔记] UE4中的材质也可以像Unity中的Shader一样可以直接通过代码写嘛?

[复制链接]
发表于 2020-12-30 09:50 | 显示全部楼层 |阅读模式
UE4中的材质也可以像Unity中的Shader一样可以直接通过代码写嘛?
发表于 2020-12-30 09:51 | 显示全部楼层
不光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真的恶心,好多暗坑。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2020-12-30 10:00 | 显示全部楼层
    custom节点再plugin中写.usf,再custom中直接include导入直接给.usf写配套的cpp类,但是写法比较鬼畜(每个版本都有点不一样)
发表于 2020-12-30 10:07 | 显示全部楼层
如果只是做普通效果开发,一般来说用CustomNode + return 1的trick,可以满足大部分的自定义复杂函数的需求。这个trick网上也比较多教程了。
如果你要自定义 Shading Model,或者自定义后处理的流程,则需要搞C++插件了,然后这个网上也有好多教程,UE4官网文档上也有比较详细的描述。
比如知乎大佬的这篇文章,这两种方案都有详细描述到:
窝窝头:UE4 HLSL 和 Shader 开发指南和技巧

总的来说,还是没有Unity的方便,Unity No1。 我自己平时测试啥或者想学习啥效果都是用Unity做的,实在是太方便了。U++写起来很难受,而且API文档很不详细,我太菜也是一方面原因。
发表于 2020-12-30 10:10 | 显示全部楼层
可以的,稍微修改一点引擎代码。主要要解决编译的入口函数和如何在shader中使用材质参数。
1 自定义入口函数

首先写一个自己的 Custom 节点类,我是继承 UE 原有的 Custom 节点。这个类主要用于设置shader文件并判断该材质是否使用自定义的 shader。
  1. UCLASS()
  2. class UMaterialExpressionMyCustom : public UMaterialExpressionCustom
  3. {
  4. GENERATED_UCLASS_BODY()
  5.    
  6.     // shader文件
  7. UPROPERTY(EditAnywhere, Category=MaterialExpressionCustom)
  8. FString ShaderFilePath;
  9. public:
  10. const FString &GetShaderFilePath() const;
  11. };
复制代码
然后在 FMeshMaterialShaderType::BeginCompileShader 和 FMaterialShaderType::BeginCompileShader 中修改入口函数名。
  1. const TCHAR *shaderEntryFuncName = GetFunctionName();
  2. const TCHAR *shaderFilename = GetShaderFilename();
  3. // 在 FMaterial 中定义的函数,判断是否有 MyCustom 节点
  4. if (Material->IsUseMyCustomShader())
  5. {
  6. UMaterialExpressionMYCustom *myCustomExrpession = Material->GetFirstUMaterialExpressionMyCustom();
  7.     // 这个地方要有一个机制判断当前的 Pass 是否使用自定义的 shader
  8.     // 我是通过文件名判断的,比如 /Engine/Private/BasePassPixelShader.usf
  9. if (IsCustomShaderSupport(shaderFilename))
  10. {
  11.   if (this->GetFrequency() == SF_Pixel)
  12.   {
  13.    shaderEntryFuncName = TEXT("MyMainPS");
  14.    ShaderEnvironment.SetDefine(TEXT("MY_PIXEL_SHADER"), TEXT("1"));
  15.   }
  16. }
  17. }
复制代码
之后要在合适的地方 include 到自己的 shader 文件。在 FMaterial::BeginCompileShaderMap 中,完成材质翻译之后,将自己的 shader 文件添加到 include path 中。
  1. if (this->IsUseMyCustomShader())
  2. {
  3. // #include "/Engine/Generated/MyCustomMaterial.ush" 被写在 .usf 最后,见 Engine\Shaders\Private\BasePassPixelShader.usf
  4. UMaterialExpressionMyCustom *myCustomExrpession = this->GetFirstUMaterialExpressionMyCustom();
  5. const FString &shaderFilename = *myCustomExrpession->GetShaderFilePath();
  6.     // 生成参数定义代码
  7. FString paramcode = MaterialTranslator.MyGetMaterialParametersCode();
  8. MaterialEnvironment->IncludeVirtualPathToContentsMap.Add(TEXT("/Engine/Generated/MyCustomMaterial.ush")
  9.   , FString::Printf(TEXT("%s \n #include \"%s\" "), *paramcode, *shaderFilename));
  10. }
复制代码
上面的代码把自己的 shader 文件在 /Engine/Generated/MyCustomMaterial.ush 中 include 了。之后需要在想要支持自定义 shader 的 pass 中 include MyCustomMaterial.ush。比如要支持 basepass 的 ps,则在 Engine\Shaders\Private\BasePassPixelShader.usf 文件最后 #include "/Engine/Generated/MyCustomMaterial.ush"。
上面做完后,就可以写自己的 shader 了。下面是一个 BasePass 的 PS,函数签名和 PixelShaderOutputCommon.ush 中一致。
  1. void MyMainPS(
  2. #if PIXELSHADEROUTPUT_INTERPOLANTS || PIXELSHADEROUTPUT_BASEPASS
  3.   FVertexFactoryInterpolantsVSToPS Interpolants,
  4. #endif
  5. #if PIXELSHADEROUTPUT_BASEPASS
  6.   FBasePassInterpolantsVSToPS BasePassInterpolants,
  7. #elif PIXELSHADEROUTPUT_MESHDECALPASS
  8.   FMeshDecalInterpolants MeshDecalInterpolants,
  9. #endif
  10.   in INPUT_POSITION_QUALIFIERS float4 SvPosition : SV_Position  // after all interpolators
  11.   OPTIONAL_IsFrontFace
  12.   , out float4 OutTarget0 : SV_Target0
  13. )
  14. {
  15.     OutTarget0 = float4(1, 0, 0, 1);
  16. }
复制代码
2 使用材质参数

在 FHLSLMaterialTranslator 定义一个函数 MyGetMaterialParametersCode,遍历其中的参数节点,将每个参数通过宏定义的方式构造一段代码。下面代码中获取了 Vector 参数,其他类型的参数也可以用类似方法取出。
  1. FString MyGetMaterialParametersCode() const
  2. {
  3. const FUniformExpressionSet &uniformSet = MaterialCompilationOutput.UniformExpressionSet;
  4. TArray<FString> paramCode;
  5.     for(int i=0; i<uniformSet.UniformVectorExpressions.Num(); ++i)
  6. {
  7.   const FMaterialUniformExpression *e = uniformSet.UniformVectorExpressions[i];
  8.   if (e == nullptr || e->GetType() != &FMaterialUniformExpressionVectorParameter::StaticType)
  9.   {
  10.    continue;
  11.   }
  12.   const FMaterialUniformExpressionVectorParameter *ee = (const FMaterialUniformExpressionVectorParameter*)e;
  13.   FString vname = ee->GetParameterInfo().Name.ToString();
  14.   FString code = FString::Printf(TEXT("#define MyParam_%s Material.VectorExpressions[%d]"), *vname, i);
  15.   paramCode.Add(code);
  16. }
  17. // result
  18. FString ret;
  19. for (const FString &code : paramCode)
  20. {
  21.   ret += code + "\n";
  22. }
  23. return ret;
  24. }
复制代码
在编译的时候,从材质中翻译出代码后,调用以上函数获得参数定义代码,写到 /Engine/Generated/MyCustomMaterial.ush 中,即可在自己的 shader 中使用这些参数了。
  1. void MyMainPS(
  2. #if PIXELSHADEROUTPUT_INTERPOLANTS || PIXELSHADEROUTPUT_BASEPASS
  3.   FVertexFactoryInterpolantsVSToPS Interpolants,
  4. #endif
  5. #if PIXELSHADEROUTPUT_BASEPASS
  6.   FBasePassInterpolantsVSToPS BasePassInterpolants,
  7. #elif PIXELSHADEROUTPUT_MESHDECALPASS
  8.   FMeshDecalInterpolants MeshDecalInterpolants,
  9. #endif
  10.   in INPUT_POSITION_QUALIFIERS float4 SvPosition : SV_Position  // after all interpolators
  11.   OPTIONAL_IsFrontFace
  12.   , out float4 OutTarget0 : SV_Target0
  13. )
  14. {
  15.     OutTarget0 = float4(MyParam_Color1.xyz, 1);
  16. }
复制代码
需要注意的是,参数必须要最终连到输出节点上才能被收入 UniformExpressionSet。
发表于 2020-12-30 10:19 | 显示全部楼层
当然可以了,用它的custom node写,或者直接改usf
发表于 2020-12-30 10:27 | 显示全部楼层
可以,ue,houdini,3dmax都支持。
发表于 2020-12-30 10:30 | 显示全部楼层
可以
usf ush了解一下
但一般动也是为了写库,少有直接用于特定材质的shader
发表于 2020-12-30 10:31 | 显示全部楼层
有个插件gpu reader, 可以写很方便
发表于 2020-12-30 10:32 | 显示全部楼层
可以,虽然我没做过
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-31 05:19 , Processed in 0.096130 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表