(转) Unreal Engine 4 Custom Shaders Tutorial

说明:

1、这里的Custom Shaders 为且仅为 Custom Node的使用和USF的包含。并非全局Shader和Material Shader.

2、原文来源:https://www.raywenderlich.com/57-unreal-engine-4-custom-shaders-tutorial

The material editor is a great tool for artists to create shaders thanks to its node-based system. However, it does have its limitations. For example, you cannot create things such as loops and switch statements.

Luckily, you can get around these limitations by writing your own code. To do this, you can create a Custom node which will allow you to write HLSL code.

In this tutorial, you will learn how to:

  • Create a Custom node and set up its inputs
  • Convert material nodes to HLSL
  • Edit shader files using an external text editor
  • Create HLSL functions

    To demonstrate all of this, you will use HLSL to desaturate the scene image, output different scene textures and create a Gaussian blur.

    Note:?This tutorial assumes you already know the basics of using Unreal Engine. If you are new to Unreal Engine, check out our 10-part?Unreal Engine for Beginnerstutorial series.

    The tutorial also assumes you are familiar with a C-type language such as C++ or C#. If you know a syntactically similar language such as Java, you should still be able to follow along.

    Note:?This tutorial is part of a 4-part tutorial series on shaders in Unreal Engine:

  • Part 1: Cel Shading
  • Part 2: Toon Outline
  • Part 3: Custom Shaders Using HLSL (you are here!)
  • Part 4: Paint Filter

    Getting Started

    Start by downloading the materials for this tutorial (you can find a link at the top or bottom of this tutorial). Unzip it and navigate to?CustomShadersStarter?and open?CustomShaders.uproject. You will see the following scene:

    First, you will use HLSL to desaturate the scene image. To do this, you need to create and use a Custom node in a post process material.

    Creating a Custom Node

    Navigate to the?Materials?folder and open?PP_Desaturate. This is the material you will edit to create the desaturation effect.

    First, create a?Custom?node. Just like other nodes, it can have multiple inputs but is limited to one output.

    Next, make sure you have the Custom node selected and then go to the Details panel. You will see the following:

    Here is what each property does:

  • Code:?This is where you will put your HLSL code
  • Output Type:?The output can range from a single value (CMOT Float 1) up to a four channel vector (CMOT Float 4).
  • Description:?The text that will display on the node itself. This is a good way to name your Custom nodes. Set this to?Desaturate.
  • Inputs:?This is where you can add and name input pins. You can then reference the inputs in code using their names. Set the name for input?0?to?SceneTexture.

    To desaturate the image, replace the text inside?Code?with the following:

    return dot(SceneTexture, float3(0.3,0.59,0.11));

    Note:?dot()?is an?intrinsic function. These are functions built into HLSL. If you need a function such as?atan()?or?lerp(), check if there is already a function for it.

    Finally, connect everything like so:

    Summary:

  • SceneTexture:PostProcessInput0?will output the color of the current pixel
  • Desaturate?will take the color and desaturate it. It will then output the result to?Emissive Color

    Click?Apply?and then close?PP_Desaturate. The scene image is now desaturated.

    You might be wondering where the desaturation code came from. When you use a material node, it gets converted into HLSL. If you look through the generated code, you can find the appropriate section and copy-paste it. This is how I converted the Desaturation node into HLSL.

    In the next section, you will learn how to convert a material node into HLSL.

    Converting Material Nodes to HLSL

    For this tutorial, you will convert the?SceneTexture?node into HLSL. This will be useful later on when you create a Gaussian blur.

    First, navigate to the?Maps?folder and open?GaussianBlur. Afterwards, go back to?Materials?and open?PP_GaussianBlur.

    Unreal will generate HLSL for any nodes that contribute to the final output. In this case, Unreal will generate HLSL for the?SceneTexture?node.

    To view the HLSL code for the entire material, select?Window\HLSL Code. This will open a separate window with the generated code.

    Note:?If the HLSL Code window is blank, you need to enable?Live Preview?in the Toolbar.

    Since the generated code is a few thousand lines long, it‘s quite difficult to navigate. To make searching easier, click the?Copy?button and paste it into a text editor (I use?Notepad++). Afterwards, close the HLSL Code window.

    Now, you need to find where the?SceneTexture?code is. The easiest way to do this is to find the definition for?CalcPixelMaterialInputs(). This function is where the engine calculates all the material outputs. If you look at the bottom of the function, you will see the final values for each output:

    PixelMaterialInputs.EmissiveColor = Local1;
    PixelMaterialInputs.Opacity = 1.00000000;
    PixelMaterialInputs.OpacityMask = 1.00000000;
    PixelMaterialInputs.BaseColor = MaterialFloat3(0.00000000,0.00000000,0.00000000);
    PixelMaterialInputs.Metallic = 0.00000000;
    PixelMaterialInputs.Specular = 0.50000000;
    PixelMaterialInputs.Roughness = 0.50000000;
    PixelMaterialInputs.Subsurface = 0;
    PixelMaterialInputs.AmbientOcclusion = 1.00000000;
    PixelMaterialInputs.Refraction = 0;
    PixelMaterialInputs.PixelDepthOffset = 0.00000000;

    Since this is a post process material, you only need to worry about?EmissiveColor. As you can see, its value is the value of?Local1. The?LocalX?variables are local variables the function uses to store intermediate values. If you look right above the outputs, you will see how the engine calculates each local variable.

    MaterialFloat4 Local0 = SceneTextureLookup(GetDefaultSceneTextureUV(Parameters, 14), 14, false);
    MaterialFloat3 Local1 = (Local0.rgba.rgb + Material.VectorExpressions[1].rgb);

    The final local variable (Local1?in this case) is usually a "dummy" calculation so you can ignore it. This means?SceneTextureLookup()?is the function for the?SceneTexturenode.

    Now that you have the correct function, let‘s test it out.

    Using the SceneTextureLookup Function

    First, what do the parameters do? This is the signature for?SceneTextureLookup():

    float4 SceneTextureLookup(float2 UV, int SceneTextureIndex, bool Filtered)

    Here is what each parameter does:

  • UV:?The UV location to sample from. For example, a UV of?(0.5, 0.5)?will sample the middle pixel.
  • SceneTextureIndex:?This will determine which scene texture to sample from. You can find a table of each scene texture and their index below. For example, to sample?Post Process Input 0, you would use?14?as the index.
  • Filtered:?Whether the scene texture should use bilinear filtering. Usually set to?false.

    To test, you will output the World Normal. Go to the material editor and create a?Custom?node named?Gaussian Blur. Afterwards, put the following in the?Codefield:

    return SceneTextureLookup(GetDefaultSceneTextureUV(Parameters, 8), 8, false);

    This will output the World Normal for the current pixel.?GetDefaultSceneTextureUV()will get the UV for the current pixel.

    Note:?Before 4.19, you were able to get UVs by supplying a?TextureCoordinatenode as an input. In 4.19, the correct way is to use?GetDefaultSceneTextureUV()?and supply your desired index.

    This is an example of how custom HLSL can break between versions of Unreal.

    Next, disconnect the?SceneTexture?node. Afterwards, connect?Gaussian Blur?to?Emissive Color?and click?Apply.

    At this point, you will get the following error:

    [SM5] /Engine/Generated/Material.ush(1410,8-76): error X3004: undeclared identifier ‘SceneTextureLookup‘

    This is telling you that?SceneTextureLookup()?does not exist in your material. So why does it work when using a SceneTexture node but not in a Custom node? When you use a?SceneTexture, the compiler will include the definition for?SceneTextureLookup(). Since you are not using one, you cannot use the function.

    Luckily, the fix for this is easy. Set the?SceneTexture?node to the same texture as the one you are sampling. In this case, set it to?WorldNormal.

    Afterwards, connect it to the?Gaussian Blur. Finally, you need to set the input pin‘s name to anything besides?None. For this tutorial, set it to?SceneTexture.

    Note:?As of writing, there is an engine bug where the editor will crash if the scene textures are not the same. However, once it works, you can freely change the scene texture in the Custom node.

    Now the compiler will include the definition for?SceneTextureLookup().

    Click?Apply?and then go back to the main editor. You will now see the world normal for each pixel.

    Right now, editing code in the Custom node isn‘t too bad since you are working with little snippets. However, once your code starts getting longer, it becomes difficult to maintain.

    To improve the workflow, Unreal allows you to include external shader files. With this, you can write code in your own text editor and then switch back to Unreal to compile.

    Using External Shader Files

    First, you need to create a?Shaders?folder. Unreal will look in this folder when you use the?#include?directive in a Custom node.

    Open the project folder and create a new folder named?Shaders. The project folder should now look something like this:

    Next, go into the?Shaders?folder and create a new file. Name it?Gaussian.usf. This is your shader file.

    Note:?Shader files must have the?.usf?or?.ush?extension.

    Open?Gaussian.usf?in a text editor and insert the code below. Make sure to save the file after every change.

    return SceneTextureLookup(GetDefaultSceneTextureUV(Parameters, 2), 2, false);

    This is the same code as before but will output?Diffuse Color?instead.

    To make Unreal detect the new folder and shaders, you need to restart the editor. Once you have restarted, make sure you are in the?GaussianBlur?map. Afterwards, reopen?PP_GaussianBlur?and replace the code in?Gaussian Blur?with the following:

    #include "/Project/Gaussian.usf"
    return 1;

    Now when you compile, the compiler will replace the first line with the contents of?Gaussian.usf. Note that you do?not?need to replace?Project?with your project name.

    Click?Apply?and then go back to the main editor. You will now see the diffuse colors instead of world normals.

    Now that everything is set up for easy shader development, it‘s time to create a Gaussian blur.

    Note:?Since this is not a Gaussian blur tutorial, I won‘t spend too much time explaining it. If you‘d like to learn more, check out?Gaussian Smoothing?and?Calculating Gaussian Kernels.

    Creating a Gaussian Blur

    Just like in the toon outlines tutorial, this effect uses convolution. The final output is the average of all pixels in the kernel.

    In a typical box blur, each pixel has the same weight. This results in artifacts at wider blurs. A Gaussian blur avoids this by decreasing the pixel‘s weight as it gets further away from the center. This gives more importance to the center pixels.

    Convolution using material nodes is not ideal due to the number of samples required. For example, in a 5×5 kernel, you would need 25 samples. Double the dimensions to a 10×10 kernel and that increases to 100 samples! At that point, your node graph would look like a bowl of spaghetti.

    This is where the Custom node comes in. Using it, you can write a small?for?loop that samples each pixel in the kernel. The first step is to set up a parameter to control the sample radius.

    Creating the Radius Parameter

    First, go back to the material editor and create a new?ScalarParameter?named?Radius. Set its default value to?1.

    The radius determines how much to blur the image.

    Next, create a new input for?Gaussian Blur?and name it?Radius. Afterwards, create a?Round?node and connect everything like so:

    The?Round?is to ensure the kernel dimensions are always whole numbers.

    Now it‘s time to start coding! Since you need to calculate the Gaussian twice for each pixel (vertical and horizontal offsets), it‘s a good idea to turn it into a function.

    When using the Custom node, you cannot create functions in the standard way. This is because the compiler copy-pastes your code into a function. Since you cannot define functions within a function, you will receive an error.

    Luckily, you can take advantage of this copy-paste behavior to create global functions.

    Creating Global Functions

    As stated above, the compiler will literally copy-paste the text in a Custom node into a function. So if you have the following:

    return 1;

    The compiler will paste it into a?CustomExpressionX?function. It doesn‘t even indent it!

    MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters)
    {
    return 1;
    }

    Look what happens if you use this code instead:

    return 1;
    }

    float MyGlobalVariable;

    int MyGlobalFunction(int x)
    {
    return x;

    The generated HLSL now becomes this:

    MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters)
    {
    return 1;
    }

    float MyGlobalVariable;

    int MyGlobalFunction(int x)
    {
    return x;
    }

    As you can see,?MyGlobalVariable?and?MyGlobalFunction()?are not contained within a function. This makes them global and means you can use them anywhere.

    Note:?Notice that the final brace is missing in the input code. This is important since the compiler inserts a brace at the end. If you leave in the brace, you will end up with two braces and receive an error.

    Now let‘s use this behavior to create the Gaussian function.

    Creating the Gaussian Function

    The function for a?simplified Gaussian in one dimension?is:

    This results in a bell curve that accepts an input ranging from approximately -1 to 1. It will then output a value from 0 to 1.

    For this tutorial, you will put the Gaussian function into a separate Custom node. Create a new?Custom?node and name it?Global.

    Afterwards, replace the text in?Code?with the following:

    return 1;
    }

    float Calculate1DGaussian(float x)
    {
    return exp(-0.5 * pow(3.141 * (x), 2));

    Calculate1DGaussian()?is the simplified 1D Gaussian in code form.

    To make this function available, you need to use?Global?somewhere in the material graph. The easiest way to do this is to simply multiply?Global?with the first node in the graph. This ensures the global functions are defined before you use them in other Custom nodes.

    First, set the?Output Type?of?Global?to?CMOT Float 4. You need to do this because you will be multiplying with?SceneTexture?which is a?float4.

    Next, create a?Multiply?and connect everything like so:

    Click?Apply?to compile. Now, any subsequent Custom nodes can use the functions defined within?Global.

    The next step is to create a?for?loop to sample each pixel in the kernel.

    Sampling Multiple Pixels

    Open?Gaussian.usf?and replace the code with the following:

    static const int SceneTextureId = 14;
    float2 TexelSize = View.ViewSizeAndInvSize.zw;
    float2 UV = GetDefaultSceneTextureUV(Parameters, SceneTextureId);
    float3 PixelSum = float3(0, 0, 0);
    float WeightSum = 0;

    Here is what each variable is for:

  • SceneTextureId:?Holds the index of the scene texture you want to sample. This is so you don‘t have to hard code the index into the function calls. In this case, the index is for?Post Process Input 0.
  • TexelSize:?Holds the size of a texel. Used to convert offsets into UV space.
  • UV:?The UV for the current pixel
  • PixelSum:?Used to accumulate the color of each pixel in the kernel
  • WeightSum:?Used to accumulate the weight of each pixel in the kernel

    Next, you need to create two?for?loops. One for the vertical offsets and one for the horizontal. Add the following below the variable list:

    for (int x = -Radius; x <= Radius; x++)
    {
    for (int y = -Radius; y <= Radius; y++)
    {

    }
    }

    Conceptually, this will create a grid centered on the current pixel. The dimensions are given by?2r + 1. For example, if the radius is?2, the dimensions would be?(2 * 2 + 1) by (2 * 2 + 1)?or?5×5.

    Next, you need to accumulate the pixel colors and weights. To do this, add the following inside the inner?for?loop:

    float2 Offset = UV + float2(x, y) * TexelSize;
    float3 PixelColor = SceneTextureLookup(Offset, SceneTextureId, 0).rgb;
    float Weight = Calculate1DGaussian(x / Radius) * Calculate1DGaussian(y / Radius);
    PixelSum += PixelColor * Weight;
    WeightSum += Weight;

    Here is what each line does:

  • Calculate the relative offset of the sample pixel and convert it into UV space
  • Sample the scene texture (Post Process Input 0?in this case) using the offset
  • Calculate the weight for the sampled pixel. To calculate a 2D Gaussian, all you need to do is multiply two 1D Gaussians together. The reason you need to divide by?Radius?is because the simplified Gaussian expects a value from -1 to 1. This division will normalize?x?and?y?to this range.
  • Add the weighted color to?PixelSum
  • Add the weight to?WeightSum

    Finally, you need to calculate the result which is the?weighted?average. To do this, add the following at the end of the file (outside the?for?loops):

    return PixelSum / WeightSum;

    That‘s it for the Gaussian blur! Close?Gaussian.usf?and then go back to the material editor. Click?Apply?and then close?PP_GaussianBlur. Use?PPI_Blur?to test out different blur radiuses.

    Note:?Sometimes the?Apply?button will be disabled. Simply make a dummy change (such as moving a node) and it will reactivate.

    Limitations

    Although the Custom node is very powerful, it does come with its downsides. In this section, I will go over some of the limitations and caveats when using it.

    Rendering Access

    Custom nodes cannot access many parts of the rendering pipeline. This includes things such as lighting information and motion vectors. Note that this is slightly different when using forward rendering.

    Engine Version Compatibility

    HLSL code you write in one version of Unreal is not guaranteed to work in another. As noted in the tutorial, before 4.19, you were able to use a?TextureCoordinate?to get scene texture UVs. In 4.19, you need to use?GetDefaultSceneTextureUV().

    Optimization

    Here is an excerpt from Epic on optimization:

    Using the custom node prevents constant folding and may use significantly more instructions than an equivalent version done with built in nodes! Constant folding is an optimization that UE4 employs under the hood to reduce shader instruction count when necessary.

    For example, an expression chain of Time >Sin >Mul by parameter > Add to something can and will be collapsed by UE4 into a single instruction, the final add. This is possible because all of the inputs of that expression (Time, parameter) are constant for the whole draw call, they do not change per-pixel. UE4 cannot collapse anything in a custom node, which can produce less efficient shaders than an equivalent version made out of existing nodes.

    As a result, it is best to only use the custom node when it gives you access to functionality not possible with the existing nodes.

    Where to Go From Here?

    You can download the completed project using the link at the top or bottom of this tutorial.

    If you‘d like to get more out of the Custom node, I recommend you check out Ryan Bruck‘s?blog. He has posts detailing how to use the Custom node to create raymarchers and other effects.

    If there are any effects you‘d like to me cover, let me know in the comments below!

    ?
    ?

    来自 <https://www.raywenderlich.com/57-unreal-engine-4-custom-shaders-tutorial>

原文地址:https://www.cnblogs.com/BaiPao-XD/p/10527499.html

时间: 2024-10-14 10:13:35

(转) Unreal Engine 4 Custom Shaders Tutorial的相关文章

UNREAL ENGINE 4.12 正式发布!下载地址

UNREAL ENGINE 4.12 正式发布! 下载地址:https://www.unrealengine.com/ Alexander Paschall 在 June 1, 2016 |功能新闻社区 Share on Facebook Share on Twitter Share on Google+ Share on LinkedIn 此版本内含虚幻引擎 4 的数百个更新,以及 GitHub 虚幻引擎开发者社区提交的 106 项改良!特此对虚幻引擎 4.12 版本的贡献者们表达诚挚谢意:

Unreal Engine 4 Smear Frame效果的实现与分析

转自:http://www.52vr.com/article-868-1.html 这篇文章介绍了类似守望先锋中的帧转移模糊(Smear Frame)效果. 该效果由Jan Kaluza实现,本博客的介绍已获得原作者同意. Github地址:传送门 效果 效果图如下,两个模型实际上都是球: Tessellation 在本质上,这个效果是使用PN Tessellation来对模型进行曲面细分,然后使用world displacement通道来进行顶点的偏移操作.Tessellation能够给模型带

Unreal Engine 4 系列教程 Part 1:入门

.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > .katex-html { display: block; } .katex-display > .katex > .katex-html > .tag { position: absolute; right: 0px; } .katex { font: 1.21em/1.2 KaTeX_M

Unreal Engine 4 动态切割模型实现

Unreal Engine 4 动态切割模型实现 <合金装备:复仇>里面,有一个很有趣的设定,游戏里大部分的场景和物件都可以用主角的刀动态切割. UE4中的ProceduralMeshComponent这个组件可以很容易的就实现这种功能,下面介绍下实现的方法. 准备模型 首先我们准备一个模型. 我做了一个简单的圆柱体,用来测试.注意需要切割的地方,为了保证细节,多放点顶点. 为了能够动态切割,我们要勾选这个模型的Allow CPUAccess选项. 新建蓝图 新建一个Actor蓝图,为它添加两

游戏音频技术备忘 (四) Wwise Unreal Engine 集成代码浅析 (一)

在Engine\Plugins\Wwise\Source下为主要Wwise相关代码,AkAudio文件夹下为运行时相关代码,AudiokineticTools下为编辑器工具相关代码,AudiokineticTools.Build.cs为用于UnrealBuildTool的相应代码,与音频直接相关位于 AkAudio文件夹下. Unreal Engine提供若干宏来实现GC.序列化.网络等需求,譬如声明为UPROPERTY()的变量与UFUNCTION()的函数即可由引擎实现变量复制和远端调用 ,

游戏音频技术备忘 (三) 集成Wwise到Unreal Engine

当前受众较广的商业游戏引擎有 Unreal Engine.Unity.cocos2d-x等,在音频领域的第三方中间件则有Wwise.FMOD.Criware等,言多且烦,我们首先集成Wwise到 Unreal Engine 中去. 在https://github.com/EpicGames/UnrealEngine上可以抓取到Unreal Engine的源代码,前提条件是需要有一个 organization 加入EpicGames 的github账号,这就需要在https://www.unrea

Unreal Engine 4 RenderTarget制作Live Camera效果

Unreal Engine 4 RenderTarget制作Live Camera效果 先上效果: Live Camera我不知道怎么翻译.反正意思就是将一个摄影机的Image渲染到一个2D平面上. 以下介绍下详细的实现方法: 1.创建一个Scene Capture 2D对象 将这个对象拖动到合适的地方. 2.创建RendterTarget对象 材质&贴图->渲染目标 3.设置SceneCapture2D的对象的属性 将SceneCapture:Texture Target设置成上面创建的渲

从Unreal Engine 3到Unreal Engine 4

Unreal Engine 4发布好长好长时间了,直到最近才有时间仔细去看一下. TimSweeney老大一句话"IF YOU LOVE SOMETHING, SET IT FREE",原来需要几十万授权金才能拿到的东西,就从$19,变成免费了,而且开源.作为一个国际顶尖的引擎,能走出这一步,对我等普通开发者真是福音啊.如此的牛X,再加上开源,相信Unreal Engine 4会火起来的,你看看最近E3上的产品展示也能感觉到.不过,Unreal的定位毕竟是"国际顶尖"

Unreal Engine 4 —— 异步加载关卡的实现方法及思考

这篇文章介绍了在ue4中实现异步加载关卡的方法,以及我对应的思考. 背景 在很多游戏中都有用到关卡的异步加载,关卡的异步加载指的是在游戏的某个阶段,使用多线程的方法进行新关卡对应的内容加载.从而能够免去直接的Loading阶段,提升游戏体验的效果. 目前这种方法被广泛运用于各种游戏,例如在游戏战神Ⅲ中,主角有时会走过一段长长的山道,在此时实际上游戏就已经在开始加载下一个关卡了(当主角走出山道时如果还没有加载完,则会显示Loading界面):又例如在游戏暗黑血统Ⅱ中,主角解开谜题从一个房间进入下一