Shadergraph triplanar object space normal mapping help

As of 2022.1.0b16 (Shader Graph 13.1.7) it still seems to be an issue. I think the fix in this thread is the best option right now.

1 Like

I am surprised there is no object space Tri Planar Node - I have used custom surface shaders to do object space tri-planar mapping for years yet I only see a huge graph above to do it with shader graph!!
Surely you can do a Custom Function Node or something? - anything to avoid a huge graph ideally!

You can absolutely have a custom function node that does all of that work instead of the massive node graph. But I uploaded it as a sub graph so you don’t ever have to look at it. Plus a custom function version could be broken by updates to the SRP (which it absolutely would have been at least once, possibly twice, since I posted the graph!)

1 Like

@bgolus thank you for your articles and sharing your node graph!

I used it in a project where I need to add dirt to textured car models, and I think Unity must have broken something (the project I work on uses 2021.3) as your triplanar object-space normal mapping subgraph has 2 issues for me:

  1. the coordinates are not consistent with the built-in node, so I extended yours with Color (Vec4) input based on your graph screenshot to have consistent texture mapping across BaseMap, MaskMap and Normals.

  2. The normals are still not applied properly in triplanar, causing strange shading. Viewing from some angles the normals are almost entirely flat, from others they are reversed etc. It’s hard for me to believe Unity doesn’t have a working tool to do this.

I guess I probably should find a way to convert tangent-space normals to world-space normals so I cna use the built-in node instead?

Their new version is better, but still probably not what people want.

On the left side, the built in Triplanar node set to Object Space.
On the right side, the custom Triplanar subgraph I posted above.

Outer spheres are scaled uniformly by 10, and both look correct and match what an unscaled sphere would look like in terms of normal map “strength”.

The center spheres are scaled to 10, 10, 1.
The updated built in triplanar node causes the normals to become flattened. Technically this is “correct” behavior in terms of what should happen to the surface normals of an object that’s been flattened like this, but I suspect not what most people want.
The custom subgraph the normals retain the same “strength” as the uniformly scaled sphere. Certainly this is what I would want from a triplanar normal map.
8341431--1096998--upload_2022-8-5_22-33-48.jpg

The coordinates should match exactly, as long as the WorldTiling option isn’t checked and they’re using the same Tiling setting. And you can make the built in node match the custom subgraph by tweaking the input object space coordinates.

This is what the WorldTiling option enables.
8341431--1097004--upload_2022-8-5_22-47-4.png

Though you are correct the blending does not perfectly match the built in node. You can make it match by modifying the normals passed into the built in, or by removing this divide in the subgraph.
8341431--1097001--upload_2022-8-5_22-44-29.png

Using the subgraph I posted (specifically the “improved2” version), or the built in one? If you’re talking about the built in one, yeah (see above). If you’re talking about the custom subgraph, the latest version should have all of those issues fixed. Though it depends on what you mean by “reverse”. The normal map texture will be mirrored on half the sides (this is the same behavior as the built in node), but the resulting “bumps” shouldn’t be inside out. Though if you’re using a normal map with -Y it can sometimes look like that.

I mean, sure … and that’s easy to do. The built in Transform node can do that for you. And then you can transform the results of that from object space to world or tangent space, or keep them in object space, depending on how you’ve setup your graph’s normals.
8341431--1097013--upload_2022-8-5_23-11-56.png

Hello @bgolus , thanks for explaining triplanar shader. I tried following this thread and got a small problem. When I rotate my object shadow rotates with an object. In my scene, I use directional light. Maybe there is a fix for that?

8605101--1154658--upload_2022-11-22_12-33-1.png

8605101--1154655--upload_2022-11-22_12-32-41.png

The Triplanar node needs to have its Type set properly.

And then look at the last image in my previous post. When you pass in object space position & normals to the built in Tripanar node it’ll calculate normals in object space, and then transform that object space normal with a world to tangent space transform that you have to undo.

For some reason, I couldn’t make it work, but I found a way by using your other triplanar shader modification. Now it works fine. Thanks

Sorry to bother you @bgolus , but I just noticed that it’s not fully correct because a shadow is still moving a little(I am using the shader graph above). I tried another method with undo, but it didn’t work. Any idea why is it happening and how to fix that?
8609094--1155360--shadow.gif

Are the normal maps you’re using being imported as normal maps?

Select the texture asset and make sure the type is set to Normal Map.
8609259--1155411--upload_2022-11-23_10-59-15.png

Yes it is a normal map.

8609280--1155417--upload_2022-11-23_21-3-15.png

The bump texture in that github is not a normal map texture. It’s a grey scale texture like a displacement map. (Its not even a proper height or displacement map, it’s just a greyscale version of the diffuse texture with increased contrast. But that’s a different conversation.)

Using the texture you provided with the settings above does produce the same issue you were complaining about, as it is not actually a normal map. Clicking on “create from greyscale”, which converts the greyscale texture provided into a proper normal map no longer has any issues.

Thanks, this was one of the problems, but still did not work with my older shader. I did a few adjustments and used the build in triplanar and added a Normal Unpack node and that fixed everything. Once again thank you, @bgolus you are a true hero with your knowledge of shaders. :slight_smile:

oh no … My entire article on triplanar normal mapping is an attempt to stop people from doing that exact setup.

Again, you need to set the built in triplanar node to Type Normal, and then you need to use the
8609676--1155531--upload_2022-11-23_13-38-5.png

And then you need to add the Tangent to World and Object to Tangent Transform nodes to get the correct object space normal.

Or use the provided object space triplanar normals subgraph you were using before, just with an actual normal map as the input.

1 Like

Thank you, now it works perfectly.

Hello bgolus,
I have been trying to port your subgraph and the triplanar node in HLSL for my Surface Shader.
In ShaderGraph you need a normal in tangent space in the fragment shader, and this is true also for SurfaceOutput.normal (tell me if I’m wrong).
So I was thinking about implementing the Unity default triplanar node first to see if it was working, and then your triplanar subgraph.
I took the node code from here: https://docs.unity3d.com/Packages/com.unity.shadergraph@10.3/manual/Triplanar-Node.html

Unluckily this is not working properly, here I am trying to output the triplanar node as albedo (btw, I am using the built-in render pipeline in ShaderGraph).
The bottom cube and sphere shader is in ShaderGraph, the other two with magenta artifacts are from my SurfaceShader.
8717847--1178526--triplanarNotWorking.png

Here is the shader graph:
8717847--1178532--triplanarWorkingGraph.png

Here is the porting of the triplanar nodes:

#ifndef TOOLKIT_TRIPLANAR
#define TOOLKIT_TRIPLANAR

float3 TransformWorldToTangent(float3 dirWS, float3x3 worldToTangent)
{
    return mul(worldToTangent, dirWS);
}

//triplanar node: https://docs.unity3d.com/Packages/com.unity.shadergraph@10.3/manual/Triplanar-Node.html
//tex2D vs SAMPLE_TEXTURE2D: https://discussions.unity.com/t/737245
//
//Works with both WS (world space) and OS (object space)
float3 Triplanar(sampler2D Texture, float3 Position, float3 Normal, float Tile, float Blend)
{
    float3 Node_UV = Position * Tile;
    float3 Node_Blend = pow(abs(Normal), Blend);
    Node_Blend /= dot(Node_Blend, 1.0);
    float4 Node_X = tex2D(Texture, Node_UV.zy);
    float4 Node_Y = tex2D(Texture, Node_UV.xz);
    float4 Node_Z = tex2D(Texture, Node_UV.xy);

    return Node_X * Node_Blend.x + Node_Y * Node_Blend.y + Node_Z * Node_Blend.z;
}

//triplanar node: https://docs.unity3d.com/Packages/com.unity.shadergraph@10.3/manual/Triplanar-Node.html
//only for WS triplanar
float3 TriplanarNormal_WS(sampler2D Texture, float3 PositionWS, float3 NormalWS, float3 TangentWS, float Tile, float Blend)
{
    float3 Node_UV = PositionWS * Tile;
    float3 Node_Blend = max(pow(abs(NormalWS), Blend), 0);
    Node_Blend /= (Node_Blend.x + Node_Blend.y + Node_Blend.z).xxx;

    float3 Node_X = UnpackNormal(tex2D(Texture, Node_UV.zy));
    float3 Node_Y = UnpackNormal(tex2D(Texture, Node_UV.xz));
    float3 Node_Z = UnpackNormal(tex2D(Texture, Node_UV.xy));

    Node_X = float3(Node_X.xy + NormalWS.zy, abs(Node_X.z) * NormalWS.x);
    Node_Y = float3(Node_Y.xy + NormalWS.xz, abs(Node_Y.z) * NormalWS.y);
    Node_Z = float3(Node_Z.xy + NormalWS.xy, abs(Node_Z.z) * NormalWS.z);

    float3 Out =
        float4(normalize(Node_X.zyx * Node_Blend.x + Node_Y.xzy * Node_Blend.y + Node_Z.xyz * Node_Blend.z), 1);
   
    //original shader graph code, I calculate WorldSpaceBiTangent from normal and tanget in WS
    //float3x3 Node_Transform = float3x3(IN.WorldSpaceTangent, IN.WorldSpaceBiTangent, IN.WorldSpaceNormal);
    float3 biTangentWS = normalize(cross(NormalWS, TangentWS));
    float3x3 Node_Transform = float3x3(TangentWS, biTangentWS, NormalWS);

    Out.rgb = TransformWorldToTangent(Out.rgb, Node_Transform);
    return Out;
}

//source: https://discussions.unity.com/t/712256
//only for OS triplanar
float3 TriplanarNormal_OS(sampler2D Texture, float3 PositionOS, float3 NormalOS, float Tile, float Blend)
{
    //TODO:
    return float4(0, 0, 0, 0);
}

#endif

I am attaching my surface shader down below, maybe you can help me understand what I’m not understanding.
Thanks for your time!

8717847–1178538–SurfaceTriplanar.shader (2.14 KB)

If you’re looking to get triplanar mapping working in a Surface Shader, I wouldn’t try to convert the Shader Graph setup, but instead look at the Surface Shader implementation I included with my article on this topic.

Thanks you bgolus your subgraph is nice!
I just added an offset (i.e. moving wave) and the possibility to add the actual normal (being able to make multipass into the subgraph i.e. for two different wave normal map texture).


8829436–1202200–ObjectSpaceTriplanarNormal.zip (25.8 KB)

This is an old post, but the Triplanar Node in Shader Graph is still broken so I figured it was worth bumping this solution up as it does correct the issue, and also to thank @bgolus because this was a very informative thread due to his contributions.