Surface shaders don’t necessarily provide all of the data you need in an easy to access form. Plus, if you’re modifying an appdata struct someplace for a Surface Shader, unless you’re hand modifying the generated code, you’re probably not going to get the results you want. The only place you have direct access to the appdata struct is with a custom vertex function, but you can’t do derivatives in a vertex function. You also should only ever be using appdata_full for custom vertex functions as the actual vertex shader is using that as the input. Understand the custom vertex function is just a function the actual generated vertex shader is calling, and the appdata struct for that function does not define the vertex stage’s input.
First, let’s talk about that TangentMatrix[2].xyz. That is the world normal, as you correctly surmised already. In a Surface Shader it would be accessed by putting float3 worldNormal; in the Input struct. Or at least it would be if that hasn’t been broken for years for any shader that sets o.Normal. Instead along with including worldNormal and INTERNAL_DATA in the Input struct you need to use this bit of code in the surf function to get the world normal:
IN.worldNormal = WorldNormalVector(IN, float3(0,0,1));
Next is world to tangent transformation. Technically the world to tangent matrix is passed to the surf function, that’s what the INTERNAL_DATA macro is adding to the struct. But due to some peculiarities with how Unity handles Surface Shader generation, if you try to actually directly use that data you’ll get shader compiler errors. So instead you’ll need to use the same WorldNormalVector() function to extract the other two elements of the matrix. My triplanar Surface Shader has an example implementation:
https://github.com/bgolus/Normal-Mapping-for-a-Triplanar-Shader/blob/master/TriplanarSurfaceShader.shader
float3 WorldToTangentNormalVector(Input IN, float3 normal) {
float3 t2w0 = WorldNormalVector(IN, float3(1,0,0));
float3 t2w1 = WorldNormalVector(IN, float3(0,1,0));
float3 t2w2 = WorldNormalVector(IN, float3(0,0,1));
float3x3 t2w = float3x3(t2w0, t2w1, t2w2);
return normalize(mul(t2w, normal));
}
Those WorldNormalVector() functions are dot products against the actual matrix, which feels like it should be way less performant than if you directly access the matrix, but shader compilers are smart. Since the input values to the dot product is a hard coded value, in the actual compiled shader the above code becomes identical to accessing the matrix directly!
I would normally agree with this, and it can also usually produce higher quality results than using derivatives, it depends on how expensive your noise function is. For a texture, I’d absolutely say use @Namey5 's example code, or something similar. For a noise function it may end up being significantly more expensive to have to calculate the noise multiple times. Also, ideally you want to use 4 offset samples instead of the center and 2 offset to derive the normals, otherwise you get a slight bias in your normals to the side and up, but it all depends on what level of quality you need / can find acceptable. I’d even maybe try both derivatives and calculating the noise multiple times and seeing which you prefer, and if either is significantly worse for performance in your use case.