Converting glsl shader to material nodes

Hi ernesernesto,

I don’t have anything amazing to say, really. Everything I did has been done before, the only problem is that there’s not enough information around on how to do it properly. So here are my 2 cents about what I’ve found:

  1. As stated on the official documentation and remembered by dsl is highly recommended to set r.ShaderDevelopmentMode to 1 before start working with shaders. By doing this you’ll get error messages when something goes wrong while compiling shaders and prevent the editor from crashing. Edit the “consolevariables.ini” file found in your (UEinstallationpath)/(version)/Engine/Config directory and uncomment “r.ShaderDevelopmentMode=1”

  2. Forget the idea of converting your complex shader into material nodes. The custom material expression node is the way to go.

  3. I suggest to test your vertex/fragment shader in a custom environment outside Unreal Engine first. You can write your own testbed in c++/DirectX/opengl or use Render Monkey or Fx Composer (they are discontinued but still working, more or less). This is because if you test your shader directly in Unreal Engine you’ll have to wait for a complete shader recompilation every change you make. This can take up to 5 minutes, depending on your hardware.

  4. My primary concern was losing cross platform support. As stated on the official documentation “Shader code written in a custom node is compiled ‘as is’ for the target platform. This means that if the shader is being compiled for PC, it is assumed to be valid HLSL. If compiling for PS3, it is assumed to be valid Cg.”. A workaround I’ve found is the following: write your shader in a .usf file in the unreal engine shaders directory. Usf files are HLSL shaders that are cross compiled correctly on different platforms. Then use the custom expression just to call your entry function defined in the .usf. The call syntax is the same between HLSL, GLSL and CG so every platform will compile the call correctly.

  5. From my experience custom expressions accept your custom code only if it is defined in MaterialTemplate.usf. I’ve tried to simply add my own .usf file to the shaders directory, it gets compiled but the functions contained in it aren’t accessible from custom nodes. I don’t like messing up too much with UE “system” files so to minimize the problem I’ve added a “MyShaders.usf” file to the shaders directory and then I’ve modified “materialtemplate.usf” to include my new file (#include “MyShaders.usf”). Using this method when a new version of UE is released you’ll just have to re-include your file in materialtemplate.usf.

  6. HLSL code view (under window menu in the material editor) is your friend. From there you can see how your function is called and where it is placed in the rendering pipeline.

  7. Unfortunately I haven’t found a method (I think there’s none) to pass data between shaders called in different stages in UE (you only have an output parameter in custom expression nodes, no way to send data to the stream output stage).

  8. Finally a practical example. Let’s say that you want to create a material that generates a simple plasma on the applied mesh (you can do this without problems in the material editor, it’s just an example).

  • create the file “plasma.usf” and put it in your engine/shaders dir. The file can have the following content:

float4 GeneratePlasma(float2 uv,float time,float phase)
{
float2 position = ( uv *phase );

float color = 0.0;
color += sin(position.x - position.y) ;
color += sin(time)* cos(sin(time)position.yposition.xsin(position.x))+.008;
color += sin(time)+position.x
sin(position.ysin(sin(tan(cos (time)))));
return float4( float3(sin(color
color)4.0, cos(colorcolor) , color )sin(time+position.x/(time3.14)),time/10.828 );
}

  • add "#include “plasma.usf” at the beginning of materialtemplate.usf
  • create a new material in your Unreal Engine project
  • connect the “base color” pin to a new custom expression node
  • add a total of three inputs to your custom expression node (uv -> type float2, time -> type float, phase -> type float)
  • define the output type as “CMOT Float 4”
  • in the “code” edit box write “return GeneratePlasma(uv,time,phase);”
  • connect the “uv” pin of your custom node to a TexCoord input expression
  • connect the “time” pin of your custom node to a Time input expression
  • connect the “phase” pin to a constant (ie. 2) or a scalar parameter.
  • compile the material, apply it to a mesh and that’s it

I know that there’s probably nothing new in my “guide” but I’ve lost several days on trial and error to collect all the information above. I hope that this post could help someone saving a bit of his/her precious time.