Calculating the surface normal in the pixel shader

Hi all,

I’m playing around with the shader system and I feel like I’m making some progress. What I’m aiming for is cubic-style voxel terrain. I have written a .usf file which is converting the vertex factory type into parameters which are compatible with the BasePass shader. Since the cube corners are sharing vertices I can’t (and in my previous OpenGL tests haven’t needed to) use vertex normals. Instead, I would like to calculate the normal in the pixel shader. I OpenGL, I can do this in the fragment shader with some dFdY calls as seen in example.frag.

Unreal Engine provides the chance to define a [FONT=Courier New]GetMaterialPixelParameters() function in the .usf file which allows me to define a [FONT=Courier New]FMaterialPixelParameters and set the world normal (or tangent normal) with


Result.WorldNormal = TransformLocalToTranslatedWorld(normalize(cross(ddx(localPosition), ddy(localPosition))));

However, if I do that, the value is overridden by the next call in BasePassPixelShader.usf which is to [FONT=Courier New]CalcMaterialParameters(). This is defined in MaterialTemplate.usf which, I am assuming, gets indirectly populated by whatever I make in the click-and-drag material editor and is returning whatever is output to the ‘normal’ output pin.

Is my only option to implement the code snippet above using the click-and-drag material editor or can I calculate a world-space surface normal in the pixel shader and use that as input to the material editor (to be combined with normal maps to give the final result)?

I feel that the lack of vertex normals in my mesh and the need to calculate them in the pixel shader is a feature of my mesh type and so it should be possible to cover this in the vertex factory shader file (this is the point of vertex factories I believe).

Does anyone have any thoughts?

Cheers,
Matt

Take a look at my Metaballs snippet code:

Here I calculate the normals (very basic) entirely in the Pixel shader based on results from the geometry shader.

Thank you . That looks useful. There’s not a big difference between what you’re doing and what I have so far so at elast I know I’m on the right track.

I don’t see any geometery shader code in there, only the definition of [FONT=Courier New]FVertexFactoryInterpolantsGSToPS saying that it will pass a normal. I don’t see that normal being written or read anywhere?

Apart from that, the main difference is that you are setting [FONT=Courier New]Result.TangentToWorld in [FONT=Courier New]GetMaterialPixelParameters(). What exactly is this parameter defining and is it used as an input itno the material editor or something?

The code I uploaded doesn’t actually contain the Geometry Shader code, as I was using it as an example to help someone work on custom shaders. The Geometry shader aspects require additions to the engine to allow Vertex Factories to opt in for a Geometry Shader. The normal itself gets generated in the Geometry Shader when I create the tri’s. The variable it stores it in is called WorldField I believe, thats just a legacy thing, forgot to update the name when I changed the purpose of the variable. The TangentToWorld matrix is the actual TBN matrix used to transform your tangent space normals from a normalmap to worldspace. So thats the one you want to ensure is correct. As thats what is used in the Material editor.

Fantastic, thank you for your help. I’ll try this out when I get home this evening.

I’m still having trouble with this. In order to project the texture to each face of the cubic mesh, I need access to the world-space normal of each pixel in the GUI material editor in order to pass it as the “World Space Normal” input into my WorldAlignedTexture node. Exactly which node in the editor represents the computed normal (presumably what I’ve put into [FONT=Courier New]TangentToWorld[2])? Is it VertexNormalWS (I assume not since in my case I don’t have a ‘vertex normal’) or PixelNormalWS (I don’t seem to be able to use this as an input into the ‘Normal’ output pin in the editor (“Invalid node PixelNormalWS used for Normal input.”)) or the “Z W” component of “TangentBasis”?

To debug this, I’ve tried inputting each of these into the ‘Base Color’ output pin in the material editor. All three of those options work on the preview cube in the material editor (give one green side, one blue side and one red side) but they all seem to output pure green when I apply the material to my custom mesh.

Maybe my code to generate the pixel normals isn’t being run? Do I have to do something special to make my mesh/material use my custom [FONT=Courier New]GetMaterialPixelParameters() function?

It turns out that I had a bug in my code which was causing my custom vertex factory (and scene proxy) not to be used. The custom shader is now working very well. Thank you very much for your help.

Thats good news, apologies, I would have responded earlier, but I actually didn’t realise you had updated the thread.