Shadergraph triplanar object space normal mapping help

Discussion in 'Shaders' started by mattbirkettsmith, Aug 21, 2018.

1. mattbirkettsmith

Joined:
Mar 19, 2018
Posts:
8
Has anyone got any experience doing Triplanar normal mapping with object space projection rather than world in Shadergraph? I've spent quite a bit of time trying to get this to work properly but I'm finding the maths to be an absolute pig. The built in node doesn't seem easily adaptable to convert to object space so I'm trying to do it from scratch.

I've tried converting some of the techniques listed here into ShadeGraph but haven't been able to get any real success in adapting them to a node setup. A swizzle technique has got me the closest but I know I haven't done it correctly.

As well as making things hard on myself by using object spaced projection I'm also making it more complex by flipping the U axis of the projection on the backface so that any texture details aren't flipped, however this makes the normal mapping a bit more fiddly!

Any tips would be appreciated!

2. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
The Shader Graph already has a triplanar node with a normal map option, and is based roughly off of my article. The code for the node even has a comment referencing the article. However in normal map mode it only works properly when using world space normals and positions, which my article is also written explicitly for. For Shader Graph makes things harder in part because the Triplanar node assumes the normals it calculates are always in world space, which if the inputs are not the world space position and normal isn't true.

Doing object space triplanar with working normals is a slightly more difficult problem than world space triplanar. World space triplanar normal mapping ends up working out relatively simply in the end due to the final normals and the triplanar uvs aligning along axial directions nicely, which is why swizzling works at all. For surface shaders and Shader Graph, the o.Normal or master node Normal connection both assume mesh tangent space normals, so the Triplanar node for Shader Graph transforms the triplanar normal from world space to tangent space. As I mentioned above this is wrong if you're not using world space inputs. For arbitrary object space triplanar normal mapping, the output of the triplanar blending is still in object space, so you have to transform the triplanar normals from object space to mesh tangent space, and not world space to mesh tangent space.

Unity doesn't offer any transform matrices for doing object to tangent space, but you can access the individual vectors that make up the matrix on your own, but it also means you have to recreate the entirety of the Triplanar node in the graph itself.

KiddUniverse likes this.
3. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
Here's an object space triplanar subgraph with inputs and outputs for albedo and normals. This uses the same basic setup as the Triplanar node that ships with Unity's ShaderGraph, but without the incorrect application of the world space to tangent transform, but instead an object space to tangent transform.

This doesn't have all the features that I implemented in my article, neither does the built in node, but it should serve as a good example. Maybe someday I'll update or make another article with object space triplanar stuff.

jakesee, Lex4art, Ne0mega and 12 others like this.
4. mattbirkettsmith

Joined:
Mar 19, 2018
Posts:
8
Wow, thanks so much bgolus, I literally couldn't have asked for a better reply! I really appreciate you taking the time to explain that so clearly, I'll be looking forward to reconstructing your example subgraph shortly! There's some completely new techniques to me here such as the Matrix Construction which there's no way I would have worked out without assistance.

I assume this is using the 'Whiteout' technique?

5. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
Yep. I prefer RNM, but I'm mimicking the existing Triplanar node, and that uses Whiteout.

6. Beauque

Joined:
Mar 7, 2017
Posts:
61
Hi bgolus, first thank you for your very instructive article on normal mapping for triplanar shaders. I am an artist very bad at maths and shader writing but I did get some more understanding of normal maps.

I wish to make a triplanar shader with normal maps for rotating asteroids (which are procedurally generated meshes).
I assumed the example graph you gave here was what I needed and would work for my case, however I re-did it within Amplify Shader Editor and I still get what looks like flipped normals on some faces when the object rotation is not 0,0,0.

Here is the graph and some screenshots, is there something I could adjust to correct the issue?

Also, as I say I am very bad at shader code, and I failed to reproduce in my graph the Height map triplanar blend you described in your article. Could you help me with it? that would be appreciated!

7. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
Doesn't ASE already have a built in triplanar node? I think it's even based off of my blog post.

P_Jong likes this.
8. Beauque

Joined:
Mar 7, 2017
Posts:
61
It does have a built in triplanar node but the result I got with it isn't much better... this is why I am trying to create a custom one.

9. bgolus

Joined:
Dec 7, 2012
Posts:
12,318

That matrix is probably wrong. I did it wrong in my example above too. (Still trying to think through how to do it right...)

Nope, that's not it.

Last edited: Jan 15, 2019
10. Beauque

Joined:
Mar 7, 2017
Posts:
61
With this graph, the normal map reacts fine with my directionnal light on every side of the object, as long as this one is not rotated.
With ASE triplanar node, there are always normal details facing the wrong direction on some sides whatever the rotation values of the object.

11. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
Checking locally, using these settings, I didn't see any problems with the normals on a default Unity sphere. I'm testing with a pretty old version of ASE though (1.5.3), so maybe it got broken at some point?

Just to double check, your asteroid mesh has vertex tangents, yes?

Also I still can't see anything wrong with your node based version that would cause the issues you're seeing, so the only thing I can think is the mesh's tangents are bad.

12. Beauque

Joined:
Mar 7, 2017
Posts:
61
My bad! Sorry. The problems was coming from my normal texture, exported in the wrong format from substance.
Its looks much better now! It works well with both ASE triplanar sampler and your graph. I will continue with this one, it gives more controle. Thank you for this.

Just one more thing, concerning the blending between each face, I tried to make a height map triplanar blend in ASE based on you method but I could'nt make it work using the nodes I am struggling with the maths. Could you help me with it?

Last edited: Jan 17, 2019
13. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
You need a height map from each face using the triplanar UVs, but not blended together. So you can't use the existing triplanar node here. I would suggest packing these in the alpha of the main albedo texture.

If you're trying to replicate the version I have in my blog post, then take the height values directly from each texture and pipe them into the append node you currently have that triplanar node piped into. The other bug you have in that node graph is you're using the original textures' values for the inputs into the max node chain. That should be the max of the components from the add.

There's also a number of single component floats that should be vector3 values to work properly. Some platforms won't behave the same with floats (either outright error, or produce different results).

Here's a horrible hacked together sketch:

P_Jong likes this.
14. Beauque

Joined:
Mar 7, 2017
Posts:
61
Wow nice! This is working perfectly.
Thank you very much!

Joined:
Jan 21, 2015
Posts:
60
16. emanuelbjorsell

Joined:
May 4, 2015
Posts:
8
Hi!

Thank you @bgolus for sharing your subgraph for object space triplanar. I've been trying to replicate it but I seem to have missed something. I've gone through the graph several times now but I can't find the issue.

I would really appreciate the help.

File size:
120.7 KB
Views:
540
File size:
390.2 KB
Views:
746
17. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
You missed the swizzle on the combine.

Attached Files:

File size:
37.5 KB
Views:
513
emanuelbjorsell likes this.
18. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
@playentertainment Oh, and I'm not intentionally ignoring you. I can't actually find that subgraph anymore. I've replaced my computer since I posted that and I don't think it got copied over to my new machine.

19. emanuelbjorsell

Joined:
May 4, 2015
Posts:
8
Oh, thank you! Can't believe I missed that.

It fixed most of the issue, but now I'm getting totally black faces on some meshes. It might be an issue with the mesh it self, though I don't get it when using world space triplanar normal. That do you think?

File size:
138 KB
Views:
711
File size:
140.2 KB
Views:
520
20. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
Hmm. Super common issue to see with triplanar mapping. I've seen this bug on stuff in the asset store, and was a bug that popped up in some of the shaders I was writing for my article on it ... it's usually something small, but I can never remember exactly what it is that causes it. Probably some solitary value plugged in wrong some place. Sorry. Happens when the input normal is perfectly (0,1,0) (and always seems to be broken for that specific normal every time I see it too...).

21. emanuelbjorsell

Joined:
May 4, 2015
Posts:
8
Ah, I see. I guess I will need to do some kind of work around.

It's weird because if I'm using a branch to decide whether to use world space or object space triplanar the issue is still there, when the bool is true so it uses world space. I need to completely disconnect the object space triplanar sub graph for the world space to work.

File size:
196.6 KB
Views:
492
22. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
Because the bug is actually producing a NaN someplace, and Shader Graph's "Branch" node isn't actually doing a branch so NaNs can leak through it.

A NaN is short for "Not a Number", and is what happens when the shader does some math that has no real number answer (divide by zero, square root of -1, etc). The ugly thing with a NaN is any math operations that includes a NaN results in a NaN, so since the "Branch" node is actually a lerp, if there's a NaN in one input the output is guaranteed to be NaN.

I recreated the node graph I did above and I'm not getting any errors, so hopefully I did it right.

As noted, this is just directly recreating the Triplanar node's normal map code as shown here:

I don't include the albedo / basic texture sampling in this sub graph just because I like the cleanness of the output actually being the normal map. Also, unlike the built in Triplanar and general texture sampling nodes, it can't use the texture's own sampler state, which means no anisotropic filtering is possible (except on OpenGLES!). That's a feature that's been missing for a long time though. Really you generally don't want anisotropic filtering on normal maps anyway as it can lead to unwanted aliasing, but you do often want it for Albedo textures, so use the built-in node with Object space inputs.

File size:
11 KB
Views:
794
23. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
On the topic of NaNs in Shader Graph, here's an example where I purposefully create a NaN and try to use a boolean material property to switch it on or off. No matter what UseNaN is set to (defaulted to off in these examples), the preview will always show pink, and the in-game material will always be black. And by always be black I mean it's unaffected by the alpha value (this Unlit Master node is set to Transparent), fog, bloom, nothing. Those pixels will always be black unless there's an opaque object in front of it.

Alternatively, this custom function implements an inline conditional instead of using a lerp. Notice, this one actually shows green in the preview! That's because an inline conditional can actually survive a NaN.

Note, the Branch node also uses an implicit inline conditional to convert the bool into a 0.0 or 1.0 used for the lerp. It's not in the code itself, but that's what the shader compiler is doing when you use a bool as a float. I've argued that this is how it should be implemented to begin with. I suspect it's not due to some old mobile GPUs having bugs with inline conditionals?

Ruchir and emanuelbjorsell like this.
24. emanuelbjorsell

Joined:
May 4, 2015
Posts:
8
Wow! Thank you for this in-depth explanation. It helps a lot for understanding why this is happening.

Joined:
Jun 25, 2013
Posts:
584

Joined:
Dec 7, 2012
Posts:
12,318
27. Beauque

Joined:
Mar 7, 2017
Posts:
61
Hi there, I bring that thread up again after converting my project to URP. Something beyond my comprehension has happened to the shading of my planets with the normal maps, and the uvs, it seams.

I remember to have struggled to get correct normals with my object-space triplanar shader but thanks to @bgolus I worked this out.
Only now after switching to URP, all the URP compatible triplanar shaders I have tried messed up the normal maps on my generated planets. I have tried a simple triplanar shadergraph, as well as my original Amplify planet shader copy-paste as is into an ASE URP template.
My planet mesh generation code have changed slightly, but I tested the shading in built-in RP and URP with the same code. See the difference (switched off noise dispacement for clarity):

Unity 2019.1.12f1

Unity 2019.4.1f1 - URP 7.3.1

You can clealy see the strange normal invert between top/bottom and right/left parts of the planet face. Also the UVs seem to match incorrectly between faces. I can't figure out where this originates from. For now I suspect the URP rendering is working differently, since the RP in use is the only difference between the two copies of my project.

Here is the URP version of the planet shader:

Any clue?

EDIT: Got similar issue in HDRP

EDIT 2: It seems there was an issue with ASE Universal template, Amplify team fixed it in a recent update, normals are good now !

Last edited: Jan 6, 2021
28. Korindian

Joined:
Jun 25, 2013
Posts:
584
This is the only place I can find that shows how to implement object space triplanar normal mapping using Shader Graph on the internet. The graph that @bgolus posted works great as long as the object's scale stays at (1,1,1). If I increase the object scale to let's say 100, 100, 100), the normals no longer look correct.

I tried modifying quite a lot of things without success, then came upon this thread where he possibly indicated how to account for object scale, if my understanding is correct. I'm referring to this part:

I'm not sure how to modify the subgraph to achieve this. Has anyone figured this out?

29. Korindian

Joined:
Jun 25, 2013
Posts:
584
I found that Amplify Shader Editor's Triplanar node is able to handle object space triplanar normals at any object scale, but after spending many hours trying to achieve the same thing in Shader Graph, I'm still at a loss. I'd really appreciate any help on how to modify the above graph in post #22 to achieve the same thing, thanks.

30. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
What do you mean by "handle object space triplanar normals at any object scale"? The above node graph does that already.

Here are two spheres with the same material using the above shader. One is scaled up to 600x (and far away), the other is 1x (and close to the camera), and which one is which doesn't matter because they look exactly the same.

If you want object aligned world space triplanar normals such that the textures follow the rotation of the object, but the one scaled up 600x has it's texture repeated 600 times more than the one on the right, that's a relatively minor modification to the original node graph. Multiply the object space position by the object scale, and you're done.

This snippet also has a minor improvement for how it handles normal blending on non-uniformly scaled meshes. Original on the left, updated on the right.

And you can get the same improvements for other textures that use Unity's built in Triplanar node by applying the same multiply and divide of the object scale to the input object space position and normal.

Attached Files:

• ObjectSpaceTriplanarNormal.improved.zip
File size:
13.2 KB
Views:
369
MichaelEGA, Alverik, fuzzy3d and 2 others like this.
31. Korindian

Joined:
Jun 25, 2013
Posts:
584
Hi, thank you so much for replying, taking the time to post screenshots, and including an updated version.

For some reason I don't understand, I'm not getting the same results as you at different scales, using either the older or updated versions you posted.

I downloaded your files into a test HDRP 11 project using Unity 2021.1.12f1. I added the subgraphs into a basic shader, and created a material from it:

I applied the material on a default Unity sphere.

Here's the result as expected at a scale of 1, 1, 1:

I enter 100,100,100 into the transform scale, and zoom out the scene camera:

As you can see, the results are not the same (the above pic is with the updated version). The results are similar between the older and updated subgraphs.

If I add a Normal Strength node between the subgraphs and the graph's Tangent Space Normal input and increase the normal strength to 100 just to get something to appear clearly, for the older subgraph at scale 100,100,100, I get something strange:

and now with your updated subgraph at scale 100,100,100, with normal strength to 100:

The updated subgraph, but with a normal strength of 100, gives a matching result. Is this correct, or am I missing something somewhere?

Edit: Also I noticed in the updated subgraph, you have a divide node after the normal vector node whose output is not connected to anything. Is it supposed to be connected to something?

Last edited: Jul 1, 2021
32. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
Might be a difference between URP and HDRP, or something changed in more recent versions of Shader Graph. I've not tested out the graph in HDRP 11.

33. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
I narrowed it down to this:

The matrix that's constructed at the end needs to have the normal vector normalized. I don't know why, because looking at the generated code it is supposed to already be getting normalized, but somehow it is not. Seems to be a bug with the HDRP as it is not need when using the URP.

I also realized since originally posting the triplanar node graph Unity also added support for object space and world space normals for both the URP and HDRP Shader Graphs instead of requiring tangent space normals on the Master node. So the graph below also outputs the correct normals for use with those.

(note, that comment in the graph is now a lie, since the object and world space normals don't exist in the built in node, but *shrug*, meh.)

Attached Files:

• ObjectSpaceTriplanarNormal.improved2.zip
File size:
21.7 KB
Views:
454
Alverik and Korindian like this.
34. Korindian

Joined:
Jun 25, 2013
Posts:
584
Thank you for solving the HDRP problem. I just tested it and it works well. You're the best!

35. bitinn

Joined:
Aug 20, 2016
Posts:
961
Hi Ben, just for my own curiosity:

- what are we actually doing when *we divide the object space normal by object scale*? And I saw in your full graph, the result is divided by scale again before space transform.

- does this mean if position and normal input are in the same space (object space, instead of world space), the built-in triplanar node still work for non-normal blending?

(EDIT: I tried, the same node works, though I need to customize its behaviour so I rolled my own based on it.)

- based on discussion here, the normal-blending triplanar is also correct for object space input, right up to the point where it calls TransformWorldToTangent(), correct?

(EDIT: I tried, it works also, just need to remember what space our blended normal is by the end.)

Last edited: Sep 1, 2021
36. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
It's to account for non-uniform scaling, and the difference between object space triplanar and object aligned triplanar, which is what most people actually want.

Think about a sphere that's been scaled down on one axis. If you just use the object space normals as is, the regions of the sphere that each plane the blend calculates doesn't change, but when you have a flattened sphere you really want more of the axis that's been flattened to get "more" of that axis's texture.

Conceivably you might still want the blend on the right if you're not using world space object aligned UVs and really do just want object space triplanar mapping. But I suspect most people really want world space object aligned UVs.

Yes, absolutely, it always did. That's why it had those inputs to begin with because it handles that case just fine. It only fails in the case of tangent space normals as it explicitly assumes the "UVs" (input Position) are world space aligned in how it reconstructs the output normal vector. When you pass in an object space position and normal it is no longer internally producing a world space normal so that assumption is wrong! And that you want a tangent space normal vector, which might not even be true anymore with the more recent version of Shader Graph.

Answered above, but yes. It's "correct" in that it's producing a normal in the space the input data is was in.

Alverik and bitinn like this.
37. bitinn

Joined:
Aug 20, 2016
Posts:
961
Hi ben, here I am, back to this thread:

I recently notice shading of my custom triplanar node isn't correct on only a few triangles (black flickering artifact), and I eventually track it down to that Transform node World to Tangent isn't quite right.

When I switch to World Normal mode in my master node, I no longer see any artifact (and Tangent to World doesn't suffer from the same issue it seems).

I wonder if it had anything to do with your discovery here?

Joined:
Dec 7, 2012
Posts:
12,318
Yes.

39. caseyfarina

Joined:
Dec 22, 2016
Posts:
8
Is this thread still valid? This is the only way to do object space triplanar mapping in URP?
Thanks Everyone

Alverik likes this.
40. Korindian

Joined:
Jun 25, 2013
Posts:
584
I was told by QA after making a bug report and pointing them to this thread:

"It's now fixed in Unity 2022.1.0a16 and above"

I haven't tried it out yet though.

41. ryanslikesocool

Joined:
Jul 31, 2016
Posts:
49
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.

Alverik likes this.
42. andyz

Joined:
Jan 5, 2010
Posts:
2,237
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!

43. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
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!)

andyz likes this.
44. unfa

Joined:
Oct 10, 2019
Posts:
12
@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?

45. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
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.

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.

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.

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.

Last edited: Aug 6, 2022
46. Rokis533

Joined:
Sep 15, 2019
Posts:
6
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?

47. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
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.

48. Rokis533

Joined:
Sep 15, 2019
Posts:
6
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

49. Rokis533

Joined:
Sep 15, 2019
Posts:
6
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?

50. bgolus

Joined:
Dec 7, 2012
Posts:
12,318
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.