Download

Material Custom Expressions - HLSL, why you so fidgety?

So I’ve probably wasted 20 hours now trying to wrap my head around custom expressions, material functions and some other advanced material topics. What I have been trying to do, is port or emulate some basic shaders. Particularly various noise generators, atmospheric scattering, whatever has my eye really… Problem is, not one lick of HLSL code has worked that didn’t require a fine-toothed comb. I can only assume I am missing something that will blow the lid off of this feature of UE for me, or it just really isn’t worth my time. For example, this code won’t work as is:


float4 grad4(float j, float4 ip)
{
    const float4 ones = float4(1.0, 1.0, 1.0, -1.0);
    float4 p, s;
    p.xyz = floor( frac(j * ip.xyz) * 7.0) * ip.z - 1.0;
    p.w = 1.5 - dot( abs(p.xyz), ones.xyz );
    p.xyz -= sign(p.xyz) * (1 - step(p.y, p.x));

    return p;
}

I haven’t a clue how to modify it to work. Engine says (like a broken record the last day): Error [SM5] error X3000: syntax error: unexpected token ‘(’. I ran into this previously and it appears we can only do a single simple function per custom expression. I have yet to figure out how to reference custom expressions from other custom expressions. I have managed to “convert” a couple of simple HLSL snippets, such as this:


#define NOISE_SIMPLEX_1_DIV_289 0.00346020761245674740484429065744f

float mod289(float x) {
    return x - floor(x * NOISE_SIMPLEX_1_DIV_289) * 289.0;
}

into this:


#define NOISE_SIMPLEX_1_DIV_289 0.00346020761245674740484429065744f

float mod289 = float(x);
return x - floor(x * NOISE_SIMPLEX_1_DIV_289) * 289.0;

Now, that snippet was simple enough to make into a material function:


Begin Object Class=MaterialGraphNode Name="MaterialGraphNode_415"
   Begin Object Class=EdGraphPin Name="EdGraphPin_3559"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3558"
   End Object
   Begin Object Class=MaterialExpressionFunctionOutput Name="MaterialExpressionFunctionOutput_6"
   End Object
   Begin Object Name="EdGraphPin_3559"
      PinName="Output"
      PinFriendlyName=" "
      Direction=EGPD_Output
   End Object
   Begin Object Name="EdGraphPin_3558"
      PinName="Input"
      PinFriendlyName=" "
      PinType=(PinCategory="required")
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_417.EdGraphPin_3564'
   End Object
   Begin Object Name="MaterialExpressionFunctionOutput_6"
      A=(Expression=MaterialExpressionSubtract'MaterialGraphNode_417.MaterialExpressionSubtract_19')
      bLastPreviewed=True
      Id=F780BEED47319A28037711B52F5E5428
      MaterialExpressionEditorX=384
      MaterialExpressionEditorY=336
      MaterialExpressionGuid=7F267A844D68C7902E3A75AE97E7B630
      Material=Material'/Engine/Transient.Material_94'
   End Object
   MaterialExpression=MaterialExpressionFunctionOutput'MaterialExpressionFunctionOutput_6'
   Pins(0)=EdGraphPin'EdGraphPin_3558'
   Pins(1)=EdGraphPin'EdGraphPin_3559'
   NodePosX=384
   NodePosY=336
   NodeGuid=4A0432B843E1F17F54F8CD857021D567
End Object
Begin Object Class=MaterialGraphNode Name="MaterialGraphNode_416"
   Begin Object Class=EdGraphPin Name="EdGraphPin_3561"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3560"
   End Object
   Begin Object Class=MaterialExpressionFunctionInput Name="MaterialExpressionFunctionInput_17"
   End Object
   Begin Object Name="EdGraphPin_3561"
      PinName="Output"
      PinFriendlyName=" "
      Direction=EGPD_Output
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_417.EdGraphPin_3562'
      LinkedTo(1)=EdGraphPin'MaterialGraphNode_419.EdGraphPin_3567'
   End Object
   Begin Object Name="EdGraphPin_3560"
      PinName="Preview"
      PinType=(PinCategory="optional")
   End Object
   Begin Object Name="MaterialExpressionFunctionInput_17"
      InputName="x"
      Id=05726F994A29548D41FA9FB878F75DFE
      InputType=FunctionInput_Scalar
      MaterialExpressionEditorX=-384
      MaterialExpressionEditorY=368
      MaterialExpressionGuid=0FAADE414142C61E743EE7AF1BD34B04
      Material=Material'/Engine/Transient.Material_94'
   End Object
   MaterialExpression=MaterialExpressionFunctionInput'MaterialExpressionFunctionInput_17'
   Pins(0)=EdGraphPin'EdGraphPin_3560'
   Pins(1)=EdGraphPin'EdGraphPin_3561'
   NodePosX=-384
   NodePosY=368
   NodeGuid=D7E637C94B662747F01E409882EBC22C
End Object
Begin Object Class=MaterialGraphNode Name="MaterialGraphNode_417"
   Begin Object Class=EdGraphPin Name="EdGraphPin_3564"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3563"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3562"
   End Object
   Begin Object Class=MaterialExpressionSubtract Name="MaterialExpressionSubtract_19"
   End Object
   Begin Object Name="EdGraphPin_3564"
      PinName="Output"
      PinFriendlyName=" "
      Direction=EGPD_Output
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_415.EdGraphPin_3558'
   End Object
   Begin Object Name="EdGraphPin_3563"
      PinName="B"
      PinType=(PinCategory="optional")
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_418.EdGraphPin_3566'
   End Object
   Begin Object Name="EdGraphPin_3562"
      PinName="A"
      PinType=(PinCategory="optional")
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_416.EdGraphPin_3561'
   End Object
   Begin Object Name="MaterialExpressionSubtract_19"
      A=(Expression=MaterialExpressionFunctionInput'MaterialGraphNode_416.MaterialExpressionFunctionInput_17')
      B=(Expression=MaterialExpressionFloor'MaterialGraphNode_418.MaterialExpressionFloor_5')
      MaterialExpressionEditorX=256
      MaterialExpressionEditorY=368
      MaterialExpressionGuid=7D08791648538EC2EE466B81C93BC530
      Material=Material'/Engine/Transient.Material_94'
   End Object
   MaterialExpression=MaterialExpressionSubtract'MaterialExpressionSubtract_19'
   Pins(0)=EdGraphPin'EdGraphPin_3562'
   Pins(1)=EdGraphPin'EdGraphPin_3563'
   Pins(2)=EdGraphPin'EdGraphPin_3564'
   NodePosX=256
   NodePosY=368
   NodeGuid=31E0DF3B4B35E8C5626DFFB63E228934
End Object
Begin Object Class=MaterialGraphNode Name="MaterialGraphNode_418"
   Begin Object Class=EdGraphPin Name="EdGraphPin_3566"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3565"
   End Object
   Begin Object Class=MaterialExpressionFloor Name="MaterialExpressionFloor_5"
   End Object
   Begin Object Name="EdGraphPin_3566"
      PinName="Output"
      PinFriendlyName=" "
      Direction=EGPD_Output
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_417.EdGraphPin_3563'
   End Object
   Begin Object Name="EdGraphPin_3565"
      PinName="Input"
      PinFriendlyName=" "
      PinType=(PinCategory="required")
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_420.EdGraphPin_3572'
   End Object
   Begin Object Name="MaterialExpressionFloor_5"
      Input=(Expression=MaterialExpressionMultiply'MaterialGraphNode_420.MaterialExpressionMultiply_46')
      MaterialExpressionEditorX=144
      MaterialExpressionEditorY=448
      MaterialExpressionGuid=350A5DE5473382AEF9412682E9C765CC
      Material=Material'/Engine/Transient.Material_94'
   End Object
   MaterialExpression=MaterialExpressionFloor'MaterialExpressionFloor_5'
   Pins(0)=EdGraphPin'EdGraphPin_3565'
   Pins(1)=EdGraphPin'EdGraphPin_3566'
   NodePosX=144
   NodePosY=448
   NodeGuid=2A8808D04B8CDDB3EE473BA73F8B0359
End Object
Begin Object Class=MaterialGraphNode Name="MaterialGraphNode_419"
   Begin Object Class=EdGraphPin Name="EdGraphPin_3569"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3568"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3567"
   End Object
   Begin Object Class=MaterialExpressionMultiply Name="MaterialExpressionMultiply_45"
   End Object
   Begin Object Name="EdGraphPin_3569"
      PinName="Output"
      PinFriendlyName=" "
      Direction=EGPD_Output
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_420.EdGraphPin_3570'
   End Object
   Begin Object Name="EdGraphPin_3568"
      PinName="B"
      PinType=(PinCategory="optional")
   End Object
   Begin Object Name="EdGraphPin_3567"
      PinName="A"
      PinType=(PinCategory="optional")
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_416.EdGraphPin_3561'
   End Object
   Begin Object Name="MaterialExpressionMultiply_45"
      A=(Expression=MaterialExpressionFunctionInput'MaterialGraphNode_416.MaterialExpressionFunctionInput_17')
      ConstB=0.003460
      MaterialExpressionEditorX=-192
      MaterialExpressionEditorY=448
      MaterialExpressionGuid=0A3F0EAE4DB5830EB7B5CBB7E9369E8E
      Material=Material'/Engine/Transient.Material_94'
   End Object
   MaterialExpression=MaterialExpressionMultiply'MaterialExpressionMultiply_45'
   Pins(0)=EdGraphPin'EdGraphPin_3567'
   Pins(1)=EdGraphPin'EdGraphPin_3568'
   Pins(2)=EdGraphPin'EdGraphPin_3569'
   NodePosX=-192
   NodePosY=448
   NodeGuid=D7B0C6394A887811A6DEF585E3ABC3A1
End Object
Begin Object Class=MaterialGraphNode Name="MaterialGraphNode_420"
   Begin Object Class=EdGraphPin Name="EdGraphPin_3572"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3571"
   End Object
   Begin Object Class=EdGraphPin Name="EdGraphPin_3570"
   End Object
   Begin Object Class=MaterialExpressionMultiply Name="MaterialExpressionMultiply_46"
   End Object
   Begin Object Name="EdGraphPin_3572"
      PinName="Output"
      PinFriendlyName=" "
      Direction=EGPD_Output
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_418.EdGraphPin_3565'
   End Object
   Begin Object Name="EdGraphPin_3571"
      PinName="B"
      PinType=(PinCategory="optional")
   End Object
   Begin Object Name="EdGraphPin_3570"
      PinName="A"
      PinType=(PinCategory="optional")
      LinkedTo(0)=EdGraphPin'MaterialGraphNode_419.EdGraphPin_3569'
   End Object
   Begin Object Name="MaterialExpressionMultiply_46"
      A=(Expression=MaterialExpressionMultiply'MaterialGraphNode_419.MaterialExpressionMultiply_45')
      ConstB=289.000000
      MaterialExpressionEditorX=-16
      MaterialExpressionEditorY=448
      MaterialExpressionGuid=B92882FA48784E774FC2689A8EB2387E
      Material=Material'/Engine/Transient.Material_94'
   End Object
   MaterialExpression=MaterialExpressionMultiply'MaterialExpressionMultiply_46'
   Pins(0)=EdGraphPin'EdGraphPin_3570'
   Pins(1)=EdGraphPin'EdGraphPin_3571'
   Pins(2)=EdGraphPin'EdGraphPin_3572'
   NodePosX=-16
   NodePosY=448
   NodeGuid=BDF588FA492C16D04BFA818B4AEDA138
End Object

My inquery is thus: what is the state of HLSL custom expressions? What am I missing here? I have a nice library of snippets I’d like to get working and a resource I’d be more than happy to share with the community once I get it working well enough. I’ve read over several documents, searched and searched, watched an outdated YouTube video and wasted time on an outdated plugin(which I’d love to see updated for 4.10!). Any help is much appreciated!

Think of a Custom Expression node as a function, anything you place into the text block exists in its own function, so thats why your mod289 function example did not work, because you basically were trying to embed a function within a function. Your second mod289 example works, because now its just code inside a function.

The second thing is, a custom expression has its own function name (CustomExpression_#). So you cannot call one custom expression from another, unless you call it by its actual function name, but this order is able to change, so not reliable. If you need to feed the results of one function into another, just create a new pin (this will only work if you only need a single set of results, looping will not be able to call a function multiple times).

For anything more complex, you will need to look into adding the function directly to the USF files. ie. You could add the functions to MaterialTemplate.usf and then using a custom expression, call those functions directly, if you want your own nodes for your functions then thats where it gets more complicated, as you’ll need to add a new MaterialExpression UObject, and update a bunch of different engine files to support it.

Thanks for the reply and all your work on the GameWorks stuff. I really dug into that for a week or so, then realized I wasn’t working on the “mechanics” of my project, ha! I do know that I’ll be coming back to that though, for FleX and… Yeah, all of it. Seriously, thanks for all the time you put into that.

I was afraid I’d have to get dirtier than I already was. While there are tons of “things” to pull in with custom HLSL, I was really after various fast and sweet noise functions, from 1D on up to 4D, as well as a few other utility functions. I’ve been following Inigo Quilez’s work for a bit (a shader guru, well know around ShaderToy and elsewhere) and some of his little snippets would be lovely to have around. I wanted to clean up and make a little user friendly package for the community… I had also come across a couple fantastic papers on fast and good looking cloud rendering techniques and wanted to take a stab at those. Granted, a lot of the processes involved require some specific noise setups that I could possibly circumvent with dirty tricks, though I want to stay as dynamic and easily customizable as possible.

Now, when you said about creating a new pin to feed from one function to another, do you mean in a custom expression? Like making a variable in one, that can take in the output of another custom expression? If so, I struggled with that to no avail. At this point, the only way I can think of getting some of these HLSL bits in without modifying core code, is to break them down into simpler chunks. The only wall I hit here, is looping. Several of these bad-boys require some loops, so I dunno. I’ll look into tinkering with USF files, I briefly read something about that and moved on. Thanks for the nudge in that direction.

I’m gonna keep at this, and will surely share my successes here. I keep dreaming of running loops in a straight-up material, but alas…

Well I have had a small, but very welcome success. I managed to port over Inigo Quilez’s color palette shader! Pretty simple, but fast (I think?) and useful for various things. This little 'feller generates a 1D gradient ramp from 4 colors. The cool thing is, those colors can be altered dynamically, which paves the way for a myriad of effects I want to achieve. I had first replicated the snippet in a material function, then a custom expression. Both yielded the same instruction counts in my tests, so I went with the expression to keep things tidy. I built a master material using that expression, so it’s quick and easy to make instances with new palettes. I also added in an optional 8-bit dither, as well as an optional SCurve boost parameter. I’ll be making it more useful by wrapping it up in a material function for easy calling from other materials. Here are some screenshots:

In-game test:
palette_test_shot_01.png

Instance with basic settings:
c165d77e66de0f77694f4bbdb4d6613067166229.png

Instance with dithering:
5e4cc6c498e7dcea6369ae924c2d867f3c65cd6f.png

Instance with SCurve boost:
e2f605ae4cd8b1827a8bd1c165596345909735c5.png

I noticed some banding in the screenshots, likely from compression. There is some slight banding in-game, espcially (and obviously) with dithering enabled. It’s slight, and only really noticable when viewed up close and at large scale. I love how the dithering only adds 2 more instructions, though it utilizes a technique involving a 256x256px noise texture. I will work out an alternative dither that doesn’t take up a texture sampler. The trade-off there is a higher instruction count. The SCurve boost was thrown in, because I noticed colors were not what I expected them to be. This is due to the tonemapping and gamma correction in the engine, which I am still getting familiar with. I have the meat and potatoes of it all down, but working around it efficiently has me scratching my head at times. Thus, I threw that SCurve boost in, which adds (I think) 18 instructions, but allows you to tweak the vibrance of the gradient. I was able to get closer to my desired colors with a value of around 1.5-2.2. A side effect emerged with certain values, that being banding removal.

All-in-all, I’m happy with how this turned out and will be marching forward on my endeavor. I plan to use this for effects such as fire, water, smoke, clouds, atmospherics, etc. Doing some fancy math with this at hand, I can imagine generating a sky system with ground color, Rayleigh and Mie scattering, sun color, all tweaking and reading from these palettes and contributing to other elements in the scene, such as shadows, cloud volume colors, water depth colors, etc.

Success story chapter 2: The Voronoi’ing. I’ve managed to get Inigo’s Voronois 3D shader working! I ended up having to add a couple functions to Common.usf (should I be using this or another USF file?) and it took a bit of massaging (read: massaging with a chainsaw), but it’s running and running well. Obligatory screenshots:

voronoi_shot1.png
voronoi_shot2.png

Material options allow for tweaking the contrast and scale of things, so you can achieve different effects as needed. The instruction count with an added animated panner over time for preview/testing purposes, was 100. I’m thinking I can trim this down a bit, but I’m still learning what’s an acceptable count. I want these utility bits as low as possible of course, since in realistic usage scenarios, they will be a part of a wider chain of material functions building up a desired effect such as clouds, or vapors coming from Toby’s flaming zombie grandma.

You might notice I have this applie to a boring surface. It is in fact 3D though. At least I think it is. I don’t actually know what I’m doing anymore. I shall sally forth and see what I can grind up and shove down the engine’s throat next.

[edit]
I’m thinking the title of the thread might need to be adjusted, to imply that some success is being had here? I still want some input from others who might have tinkered around here… I hope I’ve shown I’m not just a leech asking for someone to fix my problems with the easy button!
[/edit]

Slight update here:

  • Implemented several more noise functions: simplex, value, gradient, and 3D versions of value and gradient.
  • Updated voronois 3D
  • Added a better dithering routine, with linear to sRGB and vice versa
  • Updated 1D gradient function, also utilizing new dithering (much better looking now)

Noise (notice the difference between normal/3D versions):
HighresScreenshot00000.png
HighresScreenshot00001.png
HighresScreenshot00002.png
HighresScreenshot00003.png
HighresScreenshot00004.png
HighresScreenshot00005.png
HighresScreenshot00006.png

Might not be able to notice the improvement due to image resizing, but the improved 1D gradient using new dither:
HighresScreenshot00007.png

Finally, an example of dithering with a low-step count (8 on bottom, 65536 on top):
HighresScreenshot00008.png

I’ve taken GalaxyMan’s suggestion of working with USF files and am now including my own shader file in MaterialTemplate.usf. This makes shader development a little clunky, but it’s not so bad. Getting some of these core functions working makes it much easier to develop custom expressions in materials. I’ve made master materials for each of the noise functions, but really only to show off and test the shader functions. I’m not really ready to dive into making this more accessible, but eventually would like to package this all up somehow to share. I’m guessing the only way to do this, would be to make a plugin? I’ve yet to tinker with that, but if it enabled me to create straight up material nodes using the functions I’m working on, I’d be willing to aim for that.

Small update today. I’ve added fractal variants of each of the noise functions. I also added in a fast bias function for the simplex and value noise functions that allows for finer tuning. I’m going to start work on optimizing these puppies, but for now here is the instruction counts:


Gradient            : 106
GradientFractal     : 252
Gradient3D          : 167
Gradient3D Fractal  : 509
Simplex             : 113
Simplex Fractal     : 258
Value               : 100
Value Fractal       : 208
Value3D             : 87
Value3D Fractal     : 200
Voronoi3D           : 99
Voronoi3D Fractal   : 241

I’m certain I can get these lower! I tested out the noise functions in a material for a particle effect, which looked great and things were zippy. I created some pretty convincing smoke and by throwing in a panner node that was perturbed by the particles x/y velocity, it added even more variation. Basically there was no reptition in the look of the particles, so I’m quite happy with the progress here.

Really nice progress and interesting area to experiment with :smiley: