Triplanar mapping with TBN matrix in surface shader

So I’m writing a surface shader to texture a procedural mesh and to solve textures from stretching I thought of applying triplanar mapping.

The way I am doing it is something like this:

        void vert(inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input, o);

            o.wN = UnityObjectToWorldNormal(v.normal);
            o.worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
            // compute bitangent from cross product of normal and tangent
            half tangentSign = v.tangent.w * unity_WorldTransformParams.w;
            o.worldBitangent = cross(o.wN, o.worldTangent) * tangentSign;
        }

I have a vertex shader that computes all the basis vectors of the TBN matrix that are later passed to compute the correct normals for the lighting.

            float3x3 TBN = float3x3(normalize(IN.worldTangent), normalize(IN.worldBitangent), normalize(IN.wN)); // TANGENT TO WORLD
            
            // calculate triplanar blend
            half3 triblend = saturate(pow(IN.wN, 4));
            triblend /= max(dot(triblend, half3(1, 1, 1)), 0.0001);

            // calculate triplanar uvs
            float2 uvX = IN.worldPos.zy;
            float2 uvY = IN.worldPos.xz;
            float2 uvZ = IN.worldPos.xy;

After creating the TBN matrix in the surface shader and computing the UVs of each axis projection i did the following:

//Triplanar sampling
float3 xProjection = UNITY_SAMPLE_TEX2DARRAY(_TexturesArray, float3(uvX, j)).rgb * triblend.x;
float3 yProjection = UNITY_SAMPLE_TEX2DARRAY(_TexturesArray, float3(uvY, j)).rgb * triblend.y;
float3 zProjection = UNITY_SAMPLE_TEX2DARRAY(_TexturesArray, float3(uvZ, j)).rgb * triblend.z;

return xProjection + yProjection + zProjection; //This is assigned to a variable called finalColor


float3 tnormalX = UnpackNormal(UNITY_SAMPLE_TEX2DARRAY(_NormalMaps, float3(uvX, j)));
float3 tnormalY = UnpackNormal(UNITY_SAMPLE_TEX2DARRAY(_NormalMaps, float3(uvY, j)));
float3 tnormalZ = UnpackNormal(UNITY_SAMPLE_TEX2DARRAY(_NormalMaps, float3(uvZ, j)));

float3 worldNormalX = mul(TBN, tnormalX);
float3 worldNormalY = mul(TBN, tnormalY);
float3 worldNormalZ = mul(TBN, tnormalZ);

// we apply triblending and contribution
return (worldNormalX * triblend.x + worldNormalY * triblend.y + worldNormalZ * triblend.z); //This is assigned to a variable called finalNormal

So basically I sample the normals which are in tangent space and convert them to world space in order to apply the blending.

Finally when i write to o.Normal i do the following to pass from world to tangent space back again:

o.Normal = mul(transpose(TBN), finalNormal);

but the results are weird looking, I guess light isn’t supposed to be there:


I would be grateful if someone could help me

I could be wrong as I’m guessing based the picture. The normal appears to be rendering like a normal map would when you set it to black/white in the texture settings. Other than that it looks to me that the world space aspect is displaying correctly from what I can see.

Aside from that I’m a fan of using worldspace triplaner shaders in this way. It can be really useful beyond just procedural generation.