Gordon520 发表于 2020-12-30 09:50

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

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

ffycxyw2274436 发表于 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类,但是写法比较鬼畜(每个版本都有点不一样)

123456833 发表于 2020-12-30 10:07

如果只是做普通效果开发,一般来说用CustomNode + return 1的trick,可以满足大部分的自定义复杂函数的需求。这个trick网上也比较多教程了。
如果你要自定义 Shading Model,或者自定义后处理的流程,则需要搞C++插件了,然后这个网上也有好多教程,UE4官网文档上也有比较详细的描述。
比如知乎大佬的这篇文章,这两种方案都有详细描述到:
窝窝头:UE4 HLSL 和 Shader 开发指南和技巧

总的来说,还是没有Unity的方便,Unity No1。 我自己平时测试啥或者想学习啥效果都是用Unity做的,实在是太方便了。U++写起来很难受,而且API文档很不详细,我太菜也是一方面原因。

ffycxyw2274436 发表于 2020-12-30 10:10

可以的,稍微修改一点引擎代码。主要要解决编译的入口函数和如何在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::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 中。
if (this->IsUseMyCustomShader())
{
// #include "/Engine/Generated/MyCustomMaterial.ush" 被写在 .usf 最后,见 Engine\Shaders\Private\BasePassPixelShader.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 \n #include \"%s\" "), *paramcode, *shaderFilename));
}上面的代码把自己的 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 中一致。
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<FString> paramCode;

    for(int i=0; i<uniformSet.UniformVectorExpressions.Num(); ++i)
{
const FMaterialUniformExpression *e = uniformSet.UniformVectorExpressions;
if (e == nullptr || e->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 + "\n";
}
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。

白刃玄衣及 发表于 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

可以,虽然我没做过
页: [1] 2
查看完整版本: UE4中的材质也可以像Unity中的Shader一样可以直接通过代码写嘛?