How to make terrain triplanar shader always paint horizontally?

Hello forum. I’ve been experimenting with the terrain triplanar shaders online lately and I’ve been able to implement it into my custom terrain shader. This shader automatically paints a cliff texture for the terrain depending on the dot product of the normal.

However, both the bottom and top parts of a cliff are textured incorrectly and I haven’t been able to figure out the cause of this (see screenshot). Instead of being textured horizontally, the y and z blends are being texture slightly vertically.

7448963--913706--s1.PNG

How can I achieve a horizontal texturing straight across the terrain walls? I’ve pasted my terrain surface shader below to show how the triplanar mapping is done. Thanks!

            void surf(Input IN, inout SurfaceOutput o)
            {
                // Splat map controls.
                half4 splat_control;
                half weight;
                fixed4 mixedDiffuse;
                // Splat map mixer called in cginc.
                SplatmapMix(IN, splat_control, weight, mixedDiffuse, o.Normal);

                // texture scape for cliff texture
                float3 scaledWorldPos = IN.localCoord / _MapScale;

                // Get UVs for each axis based on local position of the fragment.
                half2 yUV = IN.localCoord.xz / _MapScale;
                half2 xUV = IN.localCoord.zy / _MapScale;
                half2 zUV = IN.localCoord.xy / _MapScale;

                // Texture samples from diffuse map with each of the 3 UV sets.
                half3 yDiff = tex2D(_VerticalTex, yUV);
                half3 xDiff = tex2D(_VerticalTex, xUV);
                half3 zDiff = tex2D(_VerticalTex, zUV);

                // Get the absolute value of the world normal.
                // Put the blend weights to the power of BlendSharpness to sharpen transition
                half3 blendWeights = pow(abs(IN.localNormal), _TriplanarBlendSharpness);

                // Divide blend mask by the sum of it's components.
                blendWeights = blendWeights / (blendWeights.x + blendWeights.y + blendWeights.z);

                // Sharpen blend weight completely by rounding (currently causing black spots between blends).
                blendWeights = round(blendWeights);

                // Blend together all three samples based on the blend mask.
                half3 color = xDiff * blendWeights.x + yDiff * blendWeights.y + zDiff * blendWeights.z;

                // Automatically texture terrain with cliff texture if dot product is greater than 0.8.
                if (dot(IN.localNormal, fixed3(0, 1, 0)) >= 0.8)
                {
                    o.Albedo = mixedDiffuse.rgb;
                }
                else {
                    o.Albedo = color.rgb;
                }
                o.Alpha = 1;
            }

If you are only ever going to be using this for doing textures on cliffs or the like, then you don’t need or even want to do a full triplanar shader. Just skip anything to with the Y axis, since you don’t want it at all.

// max prevents a divide by 0 on floors where the x and z values would both be 0.0
blendWeights = blendWeights / max(0.0001, blendWeights.x + blendWeights.z);
half3 color = xDiff * blendWeights.x + zDiff * blendWeights.z;
1 Like

Thanks so much for the reply bgolus! That makes total sense, can’t believe I didn’t try that before. I made a small tweak to your code block which I’ve pasted below for anyone curious:

blendWeights = blendWeights / (blendWeights.x + blendWeights.z);
blendWeights = round(blendWeights);

This helped fix some issues where the texture would overlap it’s repeated self (on the Y axis I believe). Then I round it to make the repeated texturing blending pixel perfect and sharp. Thanks again for the help!