AirsoftGoGo 发表于 2017-8-3 23:36:30

ue4自定义Shader教程(一)

我们可以在ue4材质编辑器实现各种材质效果,不过这些操作最终都是转换成Shader代码运行的。那如果我们要想在ue4中使用自己的Shader要怎么做呢?国外有一个自定义Shader教程,不过那个是用第一人称射击工程来示范的,对于学习和参考来说太复杂, 我这里给出一个简化版的,仅使用完全新建的空白项目来实现,供大家参考。只使用了一个pixel shader, 最终效果如图:


首先,我们这里使用到的一个shader文件,名为ShaderExample.usf,就是实现以上一个简单颜色不断变化的效果,这里大家需要先把这个文件放入引擎的Engine\Shaders文件夹下面。ShaderExample.usf文件内容:

/*
把这些内容拷贝出来,新建一个ShaderExample.usf文件拷进去即可。或者在这里使用你自定义的shader代码。
*/

//PIXEL SHADER
//////////////

void MainVertexShader(
      float4 InPosition : ATTRIBUTE0,
      float2 InUV : ATTRIBUTE1,
      out float2 OutUV : TEXCOORD0,
      out float4 OutPosition : SV_POSITION
      )
{
      OutPosition = InPosition;
      OutUV = InUV;
}

Texture2D<uint> TextureParameter;

void MainPixelShader(
      in float2 uv : TEXCOORD0,
      out float4 OutColor : SV_Target0
      )
{
      //First we need to unpack the uint material and retrieve the underlying R8G8B8A8_UINT values.
      float sizeX, sizeY;
      TextureParameter.GetDimensions(sizeX, sizeY);
      uint packedValue = TextureParameter.Load(int3(sizeX * uv.x, sizeY * uv.y, 0));
      uint r = (packedValue & 0x000000FF);
      uint g = (packedValue & 0x0000FF00) >> 8;
      uint b = (packedValue & 0x00FF0000) >> 16;
      uint a = (packedValue & 0xFF000000) >> 24;
         
      //Here we will just blend using the TextureParameterBlendFactor between our simple color change shader and the input from the compute shader
      float alpha = length(uv) / length(float2(1, 1));
      OutColor = lerp(PSConstant.StartColor, PSVariable.EndColor, alpha) * (1.0 - PSVariable.TextureParameterBlendFactor)
                           + float4(r, g, b, a) / 255.0 * PSVariable.TextureParameterBlendFactor;
}

然后大家新建一个空白的蓝图项目,假设名叫ShaderDemo,然后用ue4打开项目,在编辑器里新建一个C++类,选择父类为Actor,因为待会我们要让这个类能拖入场景中。名字随便起,我这里为默认的MyActor。以上步骤编辑器会自动在项目中创建一个名为MyActor的c++类。

然后,需要创建一个自定义Shader的插件模块,而这个插件里,就是封装shader功能的一些代码。 为什么叫"插件"呢,大家可以简单理解为ue4里的插件就是一个功能模块,我们这里使用插件是为了让我们的自定义shader在项目启动之前就加载到引擎里面。

这里我已经把插件的完整文件夹打包,

如何使用很简单,大家下载这个插件包,解压后是一个Plugins的文件夹,把它放在你刚才新建项目的根目录下面,然后来到你的项目目录右键项目文件(*.uproject),点击"Generate visual studio project files":

这样就为我们的插件模块生成了工程文件。

这时候再双击.sln解决方案文件打开项目(会启动vs),可以看到插件模块已经自动被加载到我们的项目文件夹下面,

大家也可以对比一下你的插件目录文件是否正确。

好了,现在名叫ShaderActor的插件加载好了。下面我们就动手写一下测试项目,这就很简单了,因为刚才我们添加了一个类,现在项目里应该有一个MyActor.h和一个MyActor.cpp文件,

这两个文件里面添加一些内容以实现我们的测试功能,这里给出MyActor.h的代码:
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Actor.h"
#include "ShaderActor.h"
#include "MyActor.generated.h"

UCLASS()
class DEMO_API AMyActor : public AActor
{
      GENERATED_BODY()
         
public:      
      // Sets default values for this actor's properties
      AMyActor();

      // Called when the game starts or when spawned
      virtual void BeginPlay() override;
         
      // Called every frame
      virtual void Tick( float DeltaSeconds ) override;

      UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ShaderDemo)
                FColor PixelShaderTopLeftColor;

      UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ShaderDemo)
                UMaterialInterface* MaterialToApply; //我们要给场景中的Actor设置的材质

      UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ShaderDemo)
                UTextureRenderTarget2D* RenderTarget; //用以显示的渲染目标

private:
      FShaderActor* PixelShading;
      float EndColorBuildup;
      float EndColorBuildupDirection;
};

MyActor.cpp代码:
// Fill out your copyright notice in the Description page of Project Settings.

#include "demo.h"
#include "MyActor.h"
#include "ShaderActor.h"
using namespace EMaterialQualityLevel;
// Sets default values
AMyActor::AMyActor()
{
         // Set this actor to call Tick() every frame.You can turn this off to improve performance if you don't need it.
      PrimaryActorTick.bCanEverTick = true;
      EndColorBuildup = 0;
      EndColorBuildupDirection = 1;
      PixelShaderTopLeftColor = FColor::Green;
}

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
      Super::BeginPlay();
      PixelShading = new FShaderActor(PixelShaderTopLeftColor,
                GetWorld()->Scene->GetFeatureLevel());
      PixelShading->SetTextureRenderTarget(RenderTarget);
      TArray<UStaticMeshComponent*> StaticMeshComponents =
                TArray<UStaticMeshComponent*>();
      GetComponents<UStaticMeshComponent>(StaticMeshComponents);
      UStaticMeshComponent* CurrentStaticMeshPtr = StaticMeshComponents;
      CurrentStaticMeshPtr->SetMaterial(0, MaterialToApply);      //设置当前Actor的材质为我们给的材质

      UMaterialInstanceDynamic* MID =
                CurrentStaticMeshPtr->CreateAndSetMaterialInstanceDynamic(0); //根据mesh创建一个动态材质实例
      UTexture* CastedRenderTarget = Cast<UTexture>(RenderTarget);      //将我们的渲染目标
      MID->SetTextureParameterValue("InputTexture", CastedRenderTarget); //设置给刚才创建的动态材质(以在场景中显示shader效果)
         
}

// Called every frame
void AMyActor::Tick( float DeltaTime )
{
      Super::Tick( DeltaTime );

      if (PixelShading) {
                EndColorBuildup = FMath::Clamp(EndColorBuildup + DeltaTime *
                        EndColorBuildupDirection, 0.0f, 1.0f);

                if (EndColorBuildup >= 1.0 || EndColorBuildup <= 0) {
                        EndColorBuildupDirection *= -1;
                }
               
                PixelShading->ExecutePixelShader(FColor(EndColorBuildup * 255, 0, 0, 255), DeltaTime);
      }

}

仅有一些部分修改,大部分都是初始代码。然后生成一下项目(vs中右键工程,生成),生成成功之后回到ue4编辑器中,创建一个蓝图类testShaderActor继承刚才的c++类MyActor,再创建一个渲染目标newTextureRender,一个材质newMat,


打开材质newMat,给其添加一个纹理输入参数节点,改名为"InputTexture"(代码中用到);

双击蓝图类testShaderActor,会出现两个在c++代码中定义的属性,将这两个属性分别设置为我们刚才创建的材质和渲染目标,


OK,设置完成,现在把这个材质应用到刚才创建的蓝图类,注意要先给蓝图类添加一个staticmesh,你可以随便拉一个立方体或者球体进来,只要是mesh就可以,主要是应用我们的材质。我这里是给了一个平面网格,比较方便观察。

那么把这个蓝图类随便拖几个到场景中,


点击运行,就可以看到shader运行结果了!


本项目仅做一个示范,核心的功能,也就是ue4如何加载我们自己的shader文件这部分内容都在插件的代码中,大家需要仔细阅读一下代码。理解其流程(用源码版的同学可以直接调试跟踪一下流程)之后,可以抛开插件,直接在引擎里创建包装shader的类,完全仿照插件中的写法来实现相同的功能。下期教程我就讲一下如何在引擎中直接创建和运行我们的自定义shader。



当然,做了这么多,大家可能会问有什么用?用材质不行吗,当然,材质编辑器是ue4方便普通用户而苦心创造的一个庞大复杂的系统,它的思想是将编写shader这层操作改变为在材质编辑器中拖动各种节点来实现,其最终还是要经过引擎编译成shader代码,这其中又有一层模板编译机制,ue4引擎根据一些参数对每一份材质会生成多个shader,最终绑定到渲染管线进行渲染。如果你不懂这其中的流程,那么就会受限于材质编辑器提供的功能,若想实现一些其他的效果如材质编辑器做不到的功能就无能为力了。所以,理解其机制是我们的第一个目的,其次,便于我们脱离材质系统,来实现一些自己想要的功能。


时间关系,先到这里。给大家一个思考的空间。

cg_bull 发表于 2017-8-4 00:51:37

材质编辑器唯一的弱点就是不支持函数了。。。受楼主指导准备写一个SSS的透射试试(滑稽)

bueez 发表于 2017-8-4 01:37:27

学习学习

HOGWARTS 发表于 2017-8-4 16:40:04

学习学习,感谢分享。

cg_bull 发表于 2017-8-4 20:03:48

什么时候出续集滑稽

蜗牛 发表于 2017-8-6 00:46:54

谢谢分享!

道道 发表于 2017-8-7 13:22:51

这就很叼了

shitouge 发表于 2017-8-7 16:35:37

初学,有点点不太明白,用函数做吗

zz_nirvana 发表于 2017-8-7 18:08:07

多谢分享~~~

chengchunqian 发表于 2017-8-9 08:42:43

THK!!!!!!!!!
页: [1] 2 3
查看完整版本: ue4自定义Shader教程(一)