Custom HLSL tips

I’m not that experienced with shader development but needed to get an external shader working in UE4, without using a custom build of the engine. After banging my head against the wall for a few days, I got it all working. I’m posting a few things that helped me in case they will help someone else out - feel free to add more! The idea is that, using a pre-built version of the Engine, you can use a Custom node in the Material Editor and have it run custom and non-trivial HLSL code (normally a Custom node is useful only for single functions and short expressions). There are probably cases in which you still need to do a build of the engine, and I definitely recommend using the Material Editor and Material Functions when possible, but in cases where you just really need to run some shader code, this can be the way to go.

  1. First, a big shout out to Büke for this post. That post explains how to have multiple functions inside a single Custom node in the material editor, as well as how to load code from an external text file instead of having it all in the Custom node - both of these together make it possible to iterate on shader development. Everything else in this post is minor by comparison, so thank you Büke!

  2. In newer UE4 (I’m using 4.18 but I think it was added in 4.17) you can have per-plugin and per-project directories. For example, in my project I have:


myprj\myprj.uproject
myprj\Content
...
myprj\Shaders\myshader.ush

and then in my material I have a custom node with this:


#include "/Project/myshader.ush"
return 0;

If you create the directories while UE4 is running, you need to restart the editor before they get picked up - on editor startup you’ll see some log messages about the mapping of virtual shader directories (e.g. from ‘/Project/’ to the full path on disk).

  1. Editing the shader file outside of UE4 is of course a much better dev experience. Also, if you’re just tweaking the shader code iteratively, your cycle can go like this:
  • edit code in external editor and save
  • in the material editor, make a dummy change (such as moving a node’s position) and click the Apply button

At this point the changes are in effect - i.e. you can see them in the Level Editor w/o even starting PIE (assuming of course that the camera is looking at an object that uses the material, etc. - the point being that this ends up being a relatively fast way to iterate).

In the event that your edits cause a shader compilation error, you can’t get by with a dummy change in the graph but instead you need to add a space at the end of the text in the custom node box as described in Büke’s post. Still, that doesn’t slow you down too much.

  1. See the generated code by looking in Window → HLSL Code in the Material Editor. This is probably obvious to a lot of people, but it was awhile before I ever even saw it, so I figured I’d mention it. It’s super helpful in troubleshooting because you can also see all of the auto-generated boilerplate code that gets included in the final shader.

  2. Even if you are not building from source, you can look at the code for built in shaders (assuming you checked the ‘engine source’ box in the engine options in the Epic Games Launcher) by going to e.g. c:\Program Files\Epic Games\UE_4.18\Engine\Shaders - lots of good stuff in there.

  3. Some of the boilerplate code (mentioned in #4, above) uses conditional logic to determine what to make available to your Custom shader node. For example, I was working on a postprocessing material and by default I couldn’t get access to PostProcessingInput0 in my shader code, so I just added a ShaderTexture:PostProcessInput0 node as an input to my Custom node. Not only did that make that value available to use in the shader (obviously), it triggered the boilerplate code to include some additional helper functions.

  4. In addition to whatever input parameters you explicitly pass to your Custom node, it will also receive a ‘parameters’ parameter that often has a lot of the inputs you might need. Look in the generated HLSL code for ‘customexpression’ to find your custom node and you’ll see something like:


MaterialFloat4 CustomExpression0(FMaterialPixelParameters Parameters,...)
{
#include "/Project/myshader.ush"
return 0;
}


And then look in Engine/Source/Shaders/Private/MaterialTemplate.ush to see the definition of the struct (FMaterialPixelParameters in this case) - you may find what you need is already provided there (e.g. Parameters.SvPosition.xy).

Anyway, I don’t for a moment pretend to know a great deal about any of this stuff, but thought I’d share in the hopes that it potentially saves other people some frustration!

2 Likes

Great stuff! I hope to see more tips in this thread in the future! :slight_smile:

wow, thanks so much for those tips. Very helpful, definitely not enough around on the topic.

Remember that you can also modify shaders inside the engine without need for source code access. I store copies of modified shaders on source control and we have script that copy all modified shaders back to the engine directory. When you package game those modified shaders are used.
This way you can fix bugs, optimize for your usecase or customize look pretty heavily.

Very helpful indeed, thank you!
At first I was confused about the “/Project/myshader.ush” part because I was replacing “Project” with my project name, but it’s actually supposed to be “Project” and nothing else :slight_smile:

Very helpful indeed, thank you!

not working ,i put the ush file under the /myproject/Shaders, then i still get an error: cant map virtual shader source path"/Project/myshader.ush

There is now an “Include Path” section of the Custom Node in the material editor but I can’t figure out what the path should look like as it either crashes or gives an error for a bad path.
I’m on 4.26.