Implementation of Unity3D TriPlanar three plane mapping Shader

Posted by Paulus Magnus on Tue, 05 Oct 2021 04:58:15 +0200

TriPlanar is a texture mapping technology without uv. There seems to be no standard translation in China. It is temporarily called TriPlanar mapping.
So why doesn't the model have uv.. This is very simple. Some may be that the simple modeler forgot to do it, such as you go online and offline. However, the most important use of TriPlanar is related to the programmed generation of terrain, which is difficult to set uv coordinates for vertices.
Some people may think that the terrain is a quadrilateral. Wouldn't it be good to directly cover a texture from diagonal setting [0,0] to [1,1]. Some ancient games do this, and even some terrain editing tools of modern engines do this by default (such as unity), However, this method will cause serious distortion in some convex places such as mountains, and a section of texture will be pulled very long.

As shown in the figure, the texture of the 'peak' is obviously elongated compared with the ground texture. The default terrain editing system of unity is such a problem.

The method of TriPlanar three plane mapping is to pass in the points and normals of a three-dimensional space, and use the points of this three-dimensional space to sample three maps respectively.

The input parameters are three coordinates, namely the XY, YZ and XZ axes of the three-dimensional coordinates and the normal of the point in world space. We use world space here, and we can also use other spaces.

fixed4 color = tex3D(IN.worldPos.xy, IN.worldPos.yz, IN.worldPos.xz, IN.worldNormal);

The implementation of this mapping function is also relatively simple. Roughly, three maps are sampled respectively according to these three coordinates, and then mixed with the three components of the normal of the world hair space.
Note that the abs function is used here to ensure the positive value of the normal component. This is mainly because we only have three maps, so the sampling maps used up, down, left, right, front and back are exactly the same, so it's good to only deal with positive numbers. However, if you want to implement a 'six plane mapping', you can also sample negative values.
abs should be normalized later. We don't use normalize here, because we want to ensure that the sum of the three components is equal to 1, not the sum of the squares of the three components is equal to 1

fixed4 tex3D(float2 xy, float2 yz, float2 xz, fixed3 worldNormal)
        {
            fixed4 colorForward = tex2D(_TexForward, yz);
            fixed4 colorUp = tex2D(_TexUp, xz);
            fixed4 colorLeft = tex2D(_TexLeft, xy);

            worldNormal = abs(worldNormal);
            worldNormal = worldNormal / (worldNormal.x + worldNormal.y + worldNormal.z);
            fixed4 finalColor = colorForward * worldNormal.x + colorUp * worldNormal.y + colorLeft * worldNormal.z;
           

            return finalColor;
        }

Finally, set three maps, the effect is as follows.

It may look a little strange, but it's mainly because I don't set the map well.. The most important thing is that there is no map stretching. There may still be some at some 45 degree angles, but it is not so obvious.

Three maps can also be set to the same map. It can be seen that the distortion problem can not be seen even when it is magnified.

Parameter panel

The complete code is as follows

Shader "LX/triplaneProj"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _TexUp ("TexUp", 2D) = "white" {}
        _TexForward ("TexForward", 2D) = "white" {}
        _TexLeft ("TexLeft", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _TexUp;
        sampler2D _TexForward;
        sampler2D _TexLeft;

        struct Input
        {
            float3 worldPos;
            float3 worldNormal;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;


        fixed4 tex3D(float2 xy, float2 yz, float2 xz, fixed3 worldNormal)
        {
            fixed4 colorForward = tex2D(_TexForward, yz);
            fixed4 colorUp = tex2D(_TexUp, xz);
            fixed4 colorLeft = tex2D(_TexLeft, xy);

            worldNormal = abs(worldNormal);
            worldNormal = worldNormal / (worldNormal.x + worldNormal.y + worldNormal.z);
            fixed4 finalColor = colorForward * worldNormal.x + colorUp * worldNormal.y + colorLeft * worldNormal.z;
            
            return finalColor;
        }


        void surf(Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 color = tex3D(IN.worldPos.xy, IN.worldPos.yz, IN.worldPos.xz, IN.worldNormal);
            o.Albedo = color.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = color.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

Topics: Game Development Computer Graphics Unity3d Shader