Search Unity

Question Parallax Occlusion shader has distortion on curved surfaces

Discussion in 'Shaders' started by Gameslinx, Jul 20, 2020.

  1. Gameslinx

    Gameslinx

    Joined:
    Jun 24, 2017
    Posts:
    7
    Hello, I am having an issue with a parallax occlusion shader (with triplanar mapping) becoming distorted as surface curvature increases.

    I'm using triplanar mapping because this shader is for planets and I want the mountains to be textured without stretches. The triplanar mapping itself works fine, however the parallax effect becomes more and more distorted as the surface curves away. On a planet, the effect only works properly on the absolute X and absolute Z sides of the sphere, then gets progressively worse as you move around it.

    Here's a video of the problem:



    Here is the .cginc file code (which is where most of the parallax calculation is done):
    Code (CSharp):
    1. #pragma once
    2.  
    3. // Shamelessly derived from:
    4. // https://www.gamedev.net/resources/_/technical/graphics-programming-and-theory/a-closer-look-at-parallax-occlusion-mapping-r3262
    5. // License: https://www.gamedev.net/resources/_/gdnethelp/gamedevnet-open-license-r2956
    6.  
    7. void parallax_vert(    //For positive world X and Z
    8.     float4 vertex,
    9.     float3 normal,
    10.     float4 tangent,
    11.     out float3 eye,
    12.     out float sampleRatio
    13. ) {
    14.     float3 binormal = cross(normal, tangent.xyz) * tangent.w;
    15.     float3 EyePosition = _WorldSpaceCameraPos;
    16.  
    17.     //Corrected the calculation from the above website
    18.     float4 localCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));
    19.     float3 eyeLocal = vertex - localCameraPos;
    20.     float4 eyeGlobal = mul(float4(eyeLocal, 1), unity_ObjectToWorld);
    21.     float3 E = eyeGlobal.xyz;
    22.  
    23.     float3x3 objectToTangent = float3x3(
    24.         tangent.xyz,
    25.         cross(normal, tangent.xyz) * tangent.w,
    26.         normal
    27.         );
    28.  
    29.     eye = mul(objectToTangent, ObjSpaceViewDir(vertex));
    30.  
    31.     sampleRatio = 1 - dot(normalize(E), -normal);
    32. }
    33. void reverse_parallax_vert(    //For negative X and Z
    34.     float4 vertex,
    35.     float3 normal,
    36.     float4 negativeTangent,
    37.     out float3 reverseEye,
    38.     out float reverseSampleRatio
    39. ) {
    40.     float3 binormal = cross(normal, negativeTangent.xyz) * negativeTangent.w;
    41.     float3 EyePosition = _WorldSpaceCameraPos;
    42.  
    43.     float4 localCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));
    44.     float3 eyeLocal = vertex - localCameraPos;
    45.     float4 eyeGlobal = mul(float4(eyeLocal, 1), unity_ObjectToWorld);
    46.     float3 E = eyeGlobal.xyz;
    47.  
    48.     float3x3 objectToTangent = float3x3(
    49.         negativeTangent.xyz,
    50.         cross(normal, negativeTangent.xyz) * negativeTangent.w,
    51.         normal
    52.         );
    53.  
    54.     reverseEye = mul(objectToTangent, ObjSpaceViewDir(vertex));
    55.  
    56.     reverseSampleRatio = 1 - dot(normalize(E), -normal);
    57. }
    58.  
    59. float2 parallax_offset (    //For positive world X and Z
    60.     float fHeightMapScale,
    61.     float3 eye,
    62.     float sampleRatio,
    63.     float2 texcoord,
    64.     sampler2D heightMap,
    65.     int nMinSamples,
    66.     int nMaxSamples
    67. ) {
    68.  
    69.     float fParallaxLimit = -length( eye.xy ) / eye.z;
    70.     fParallaxLimit *= fHeightMapScale;
    71.    
    72.     float2 vOffsetDir = normalize( eye.xy );
    73.     float2 vMaxOffset = vOffsetDir * fParallaxLimit;
    74.    
    75.     int nNumSamples = (int)lerp( nMinSamples, nMaxSamples, saturate(sampleRatio) );
    76.    
    77.     float fStepSize = 1.0 / (float)nNumSamples;
    78.    
    79.     float2 dx = ddx( texcoord );
    80.     float2 dy = ddy( texcoord );
    81.    
    82.     float fCurrRayHeight = 1.0;
    83.     float2 vCurrOffset = float2( 0, 0 );
    84.     float2 vLastOffset = float2( 0, 0 );
    85.  
    86.     float fLastSampledHeight = 1;
    87.     float fCurrSampledHeight = 1;
    88.  
    89.     int nCurrSample = 0;
    90.    
    91.     while ( nCurrSample < nNumSamples )
    92.     {
    93.           fCurrSampledHeight = tex2Dgrad(heightMap, texcoord + vCurrOffset, dx, dy ).r;
    94.           if ( fCurrSampledHeight > fCurrRayHeight )
    95.           {
    96.                 float delta1 = fCurrSampledHeight - fCurrRayHeight;
    97.                 float delta2 = ( fCurrRayHeight + fStepSize ) - fLastSampledHeight;
    98.  
    99.                 float ratio = delta1/(delta1+delta2);
    100.  
    101.                 vCurrOffset = (ratio) * vLastOffset + (1.0-ratio) * vCurrOffset;
    102.  
    103.                 nCurrSample = nNumSamples + 1;
    104.           }
    105.           else
    106.           {
    107.                 nCurrSample++;
    108.  
    109.                 fCurrRayHeight -= fStepSize;
    110.  
    111.                 vLastOffset = vCurrOffset;
    112.                 vCurrOffset += fStepSize * vMaxOffset;
    113.  
    114.                 fLastSampledHeight = fCurrSampledHeight;
    115.           }
    116.     }
    117.    
    118.     return vCurrOffset;
    119. }
    120. float2 reverse_parallax_offset(    //For negative world X and Z
    121.     float fHeightMapScale,
    122.     float3 eye,
    123.     float sampleRatio,
    124.     float2 texcoord,
    125.     sampler2D heightMap,
    126.     int nMinSamples,
    127.     int nMaxSamples
    128. ) {
    129.  
    130.     float fParallaxLimit = -length(eye.xy) / eye.z;
    131.     fParallaxLimit *= fHeightMapScale;
    132.  
    133.     float2 vOffsetDir = normalize(eye.xy);
    134.     float2 vMaxOffset = vOffsetDir * fParallaxLimit;
    135.  
    136.     int nNumSamples = (int)lerp(nMinSamples, nMaxSamples, saturate(sampleRatio));
    137.  
    138.     float fStepSize = 1.0 / (float)nNumSamples;
    139.  
    140.     float2 dx = ddx(texcoord);
    141.     float2 dy = ddy(texcoord);
    142.  
    143.     float fCurrRayHeight = 1.0;
    144.     float2 vCurrOffset = float2(0, 0);
    145.     float2 vLastOffset = float2(0, 0);
    146.  
    147.     float fLastSampledHeight = 1;
    148.     float fCurrSampledHeight = 1;
    149.  
    150.     int nCurrSample = 0;
    151.  
    152.     while (nCurrSample < nNumSamples)
    153.     {
    154.         fCurrSampledHeight = tex2Dgrad(heightMap, texcoord + vCurrOffset, dx, dy).r;
    155.         if (fCurrSampledHeight > fCurrRayHeight)
    156.         {
    157.             float delta1 = fCurrSampledHeight - fCurrRayHeight;
    158.             float delta2 = (fCurrRayHeight + fStepSize) - fLastSampledHeight;
    159.  
    160.             float ratio = delta1 / (delta1 + delta2);
    161.  
    162.             vCurrOffset = (ratio)*vLastOffset + (1.0 - ratio) * vCurrOffset;
    163.  
    164.             nCurrSample = nNumSamples + 1;
    165.         }
    166.         else
    167.         {
    168.             nCurrSample++;
    169.  
    170.             fCurrRayHeight -= fStepSize;
    171.  
    172.             vLastOffset = vCurrOffset;
    173.             vCurrOffset += fStepSize * vMaxOffset;
    174.  
    175.             fLastSampledHeight = fCurrSampledHeight;
    176.         }
    177.     }
    178.  
    179.     return vCurrOffset;
    180. }
    Here is the shader code:
    Code (CSharp):
    1.  
    2.  
    3. Shader "Custom/ParallaxOcclusion"
    4. {
    5.     Properties
    6.     {
    7.         _SurfaceTexture("_SurfaceTexture", 2D) = "white" {}
    8.         _SurfaceVarianceTexture("_SurfaceVarianceTexture", 2D) = "white" {}
    9.         _SurfaceVarianceTexturePow("_SurfaceVarianceTexturePow", Range(0, 10)) = 1
    10.         _SurfaceVarianceTextureScale("_SurfaceVarianceTextureScale", Range(0, 2)) = 1
    11.         _SteepTex("_SteepTex", 2D) = "white" {}
    12.         _SteepPower("_SteepPower", Range(0.01, 10)) = 1
    13.         _Strength("_Strength", Range(0, 100)) = 1
    14.         [NoScaleOffset] _BumpMap("_BumpMap", 2D) = "bump" {}
    15.         [NoScaleOffset] _SurfaceVarianceBumpMap("_SurfaceVarianceBumpMap", 2D) = "bump" {}
    16.         _ParallaxMap("_ParallaxMap", 2D) = "white" {}
    17.         _ParallaxMapMulti("_ParallaxMapMulti", 2D) = "white" {}
    18.         _Parallax("_Parallax", Range(0, 1)) = 0.05
    19.         _ParallaxRange("_ParallaxRange", Range(0, 2000)) = 80
    20.         _ParallaxMinSamples("_ParallaxMinSamples", Range(1, 100)) = 1
    21.         _ParallaxMaxSamples("_ParallaxMaxSamples", Range(1, 400)) = 100
    22.         _PlanetOrigin("_PlanetOrigin", vector) = (0,0,0)
    23.         _NoiseTex("_NoiseTex", 2D) = "white" {}
    24.         _Metallic("_Metallic (Specular)", Range(0, 2)) = 0.308
    25.         _MetallicTint("_MetallicTint", COLOR) = (1,1,1)
    26.         _LightPos("_LightPos", vector) = (0, 0, 0)
    27.     }
    28.      SubShader
    29.     {
    30.         Tags { "LightMode" = "ForwardBase" "RenderType" = "Opaque" }
    31.  
    32.         Pass
    33.         {
    34.             CGPROGRAM
    35.  
    36.             #pragma vertex vert
    37.             #pragma fragment frag
    38.             #include "UnityCG.cginc"
    39.             #include "UnityStandardBRDF.cginc"
    40.            #include <ParallaxOcclusion.cginc>
    41.             struct appdata
    42.             {
    43.                 float4 vertex : POSITION;
    44.                 float3 normal : NORMAL;
    45.                 float4 tangent : TANGENT;
    46.                 float2 texcoord : TEXCOORD4;
    47.                 float sampleRatio : TEXCOORD7;
    48.                
    49.             };
    50.  
    51.             struct v2f
    52.             {
    53.                 float4 pos : SV_POSITION;
    54.                 float3 worldPos : TEXCOORD4;
    55.                 half3 tspace0 : TEXCOORD1;
    56.                 half3 tspace1 : TEXCOORD2;
    57.                 half3 tspace2 : TEXCOORD3;
    58.                 float cameraDist : TEXCOORD5;
    59.                 float3 eye : TEXCOORD6;
    60.                 float3 reverseEye : TEXCOORD9;
    61.                 float sampleRatio : TEXCOORD7;
    62.                 float reverseSampleRatio : TEXCOORD10;
    63.                 float2 texcoord : TEXCOORD0;
    64.                 float3 normal : TEXCOORD8;
    65.                 float4 tangent : TEXCOORD11;
    66.             };
    67.  
    68.             sampler2D _SurfaceTexture;
    69.             sampler2D _SteepTex;
    70.             sampler2D _SurfaceVarianceTexture;
    71.             float4 _SurfaceTexture_ST;
    72.             float4 _SteepTex_ST;
    73.             float _Strength;
    74.             float _SteepPower;
    75.             sampler2D _BumpMap;
    76.             sampler2D _SurfaceVarianceBumpMap;
    77.             float _Parallax;
    78.             float _SurfaceVarianceTexturePow;
    79.             float4 _SurfaceVarianceTexture_ST;
    80.             float3 _PlanetOrigin;
    81.             sampler2D _ParallaxMap;
    82.             sampler2D _ParallaxMapMulti;
    83.             sampler2D _NoiseTex;
    84.             float4 _NoiseTex_ST;
    85.             float _SurfaceVarianceTextureScale;
    86.             int _ParallaxMinSamples;
    87.             int _ParallaxMaxSamples;
    88.             float _Metallic;
    89.             float _ParallaxRange;
    90.             float3 _LightPos;
    91.             float3 _MetallicTint;
    92.             v2f vert(appdata v)
    93.             {
    94.                 v2f o;
    95.                 o.pos = UnityObjectToClipPos(v.vertex);
    96.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;    //Use this when calculating slope
    97.                 o.texcoord = v.texcoord;
    98.  
    99.  
    100.                 //PARALLAX VERT CALCULATION
    101.                 v.tangent = (v.tangent);
    102.                 parallax_vert(v.vertex, v.normal, v.tangent, o.eye, o.sampleRatio);                         //Positive X and Z
    103.                 reverse_parallax_vert(v.vertex, v.normal, -v.tangent, o.reverseEye, o.reverseSampleRatio);  //Negative X and Z have reversed tangents
    104.  
    105.                 half3 wNormal = UnityObjectToWorldNormal(v.normal);
    106.                
    107.  
    108.                 half3 wTangent = UnityObjectToWorldDir(v.tangent.xyz);
    109.                 half tangentSign = v.tangent.w * unity_WorldTransformParams.w;
    110.                 half3 wBitangent = cross(wNormal, wTangent) * tangentSign;
    111.                 o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x);        
    112.                 o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y);
    113.                 o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z);
    114.                 o.normal = mul(unity_ObjectToWorld, v.normal);
    115.  
    116.  
    117.                 o.cameraDist = distance(_WorldSpaceCameraPos, mul(unity_ObjectToWorld, v.vertex));
    118.                 o.tangent = v.tangent;
    119.                 return o;
    120.             }
    121.  
    122.             fixed4 frag(v2f i) : SV_Target
    123.             {
    124.  
    125.                 //Slope calculation relative to the planet origin (mountain slopes). This value determines the blending of textures
    126.                 float slope = abs(dot(normalize(i.worldPos - _PlanetOrigin), normalize(i.normal)));
    127.  
    128.  
    129.                 ///////////////////////////////////////////////
    130.                 //////////////Texture Zoom Levels//////////////
    131.                 ///////////////////////////////////////////////
    132.                 float2 surfaceTexcoord = (i.worldPos.xz * (_SurfaceTexture_ST.xy) + _SurfaceTexture_ST.zw);
    133.                 float ZoomLevel = ((0.8 * pow(i.cameraDist, 0.18) - 0.5));
    134.                 ZoomLevel = clamp(ZoomLevel, 1, 10);
    135.                 ZoomLevel = 1;  //set ZoomLevel to 1 while debugging parallax effect
    136.                 int ClampedZoomLevel = floor(ZoomLevel);
    137.                 if (ClampedZoomLevel >= 10)
    138.                 {
    139.                     ClampedZoomLevel = 10;
    140.                     ZoomLevel = 10;
    141.                 }
    142.                 float percentage = (ZoomLevel - ClampedZoomLevel);
    143.                 float uvDistortion = pow(ClampedZoomLevel, 3);
    144.                 float nextUVDist = pow((ClampedZoomLevel + 1), 3);
    145.  
    146.  
    147.                 //WORLD-SPACE NORMAL
    148.                 half3 vertexNormal = abs(normalize(half3(i.tspace0.z, i.tspace1.z, i.tspace2.z)));
    149.  
    150.                 //TRIPLANAR UV COORDS//
    151.                 float2 uvX = (i.worldPos.zy * _SurfaceTexture_ST.xy + _SurfaceTexture_ST.zw);
    152.                 float2 uvY = (i.worldPos.xz * (_SurfaceTexture_ST.xy) + _SurfaceTexture_ST.zw);
    153.                 float2 uvZ = (i.worldPos.xy * _SurfaceTexture_ST.xy + _SurfaceTexture_ST.zw);
    154.  
    155.                 /////////////////////////////////
    156.                 ///////PARALLAX CALCULATION//////
    157.                 /////////////////////////////////
    158.  
    159.                 float2 offsetSurfX = 0;
    160.                 float2 offsetSurfZ = 0;
    161.                 float2 offsetSurfY = 0;
    162.                 float2 offsetSurfX2 = 0;
    163.                 float2 offsetSurfZ2 = 0;
    164.                 float2 offsetSurfY2 = 0;
    165.                 float parallaxCameraRangeIntensity = (i.cameraDist / _ParallaxRange);
    166.  
    167.  
    168.                 if (parallaxCameraRangeIntensity > 1)
    169.                 {
    170.                     parallaxCameraRangeIntensity = 1;
    171.                 }
    172.                 if (parallaxCameraRangeIntensity < 0)
    173.                 {
    174.                     parallaxCameraRangeIntensity = 0;
    175.                 }
    176.                 parallaxCameraRangeIntensity = 1 - parallaxCameraRangeIntensity;
    177.                
    178.                 _Parallax = parallaxCameraRangeIntensity * _Parallax;
    179.                
    180.                 if (i.cameraDist < _ParallaxRange + 10)     //Shader etiquette 101 is to use minimal conditional statements but this massively improves performance
    181.                 {
    182.                     if (i.worldPos.x - _PlanetOrigin.x >= 0)
    183.                     {
    184.                         offsetSurfX = parallax_offset(_Parallax, i.eye, i.sampleRatio, uvX, _ParallaxMap, _ParallaxMinSamples, _ParallaxMaxSamples);  //POSITIVE X
    185.                     }
    186.                     else
    187.                     {
    188.                         offsetSurfX = parallax_offset(_Parallax, i.reverseEye, i.reverseSampleRatio, uvX, _ParallaxMap, _ParallaxMinSamples, _ParallaxMaxSamples);    //NEGATIVE X
    189.                     }
    190.                     if (i.worldPos.z <= 0)
    191.                     {
    192.                         offsetSurfZ = parallax_offset(_Parallax, i.eye, i.sampleRatio, uvZ, _ParallaxMap, _ParallaxMinSamples, _ParallaxMaxSamples);  //POSITIVE Z
    193.                     }
    194.                     else
    195.                     {
    196.                         offsetSurfZ = parallax_offset(_Parallax, i.reverseEye, i.reverseSampleRatio, uvZ, _ParallaxMap, _ParallaxMinSamples, _ParallaxMaxSamples);    //NEGATIVE Z
    197.                     }
    198.                     if (i.worldPos.y - _PlanetOrigin.y >= 0)
    199.                     {
    200.                         offsetSurfY = parallax_offset(-_Parallax, i.reverseEye, i.reverseSampleRatio, uvY, _ParallaxMap, _ParallaxMinSamples, _ParallaxMaxSamples);   //POSITIVE Y
    201.                     }
    202.                     else
    203.                     {
    204.                         offsetSurfY = parallax_offset(-_Parallax, i.eye, i.sampleRatio, uvY, _ParallaxMap, _ParallaxMinSamples, _ParallaxMaxSamples);     //NEGATIVE Y
    205.                     }
    206.                 }
    207.                
    208.  
    209.  
    210.  
    211.                 half3 triblend = pow(abs(vertexNormal), _Strength * 1.4); //Triblend determines blending of triplanar textures
    212.                 triblend /= max(dot(triblend, half3(1, 1, 1)), 0.000000);
    213.  
    214.  
    215.                 float currentuvX = uvX / uvDistortion;
    216.                 float currentuvY = uvY / uvDistortion;
    217.                 float currentuvZ = uvZ / uvDistortion;
    218.                 float nextuvX = uvX / nextUVDist;
    219.                 float nextuvY = uvY / nextUVDist;
    220.                 float nextuvZ = uvZ / nextUVDist;
    221.                
    222.                 slope = pow(slope, _SteepPower);
    223.                
    224.  
    225.                 //Noisemap tiling
    226.                 fixed4 multiTexBlendX = tex2D(_NoiseTex, uvX * _SurfaceVarianceTextureScale);
    227.                 fixed4 multiTexBlendY= tex2D(_NoiseTex, uvY * _SurfaceVarianceTextureScale);
    228.                 fixed4 multiTexBlendZ = tex2D(_NoiseTex, uvZ * _SurfaceVarianceTextureScale);
    229.                 fixed4 multiTexBlend = multiTexBlendX * triblend.x + multiTexBlendY * triblend.y + multiTexBlendZ * triblend.z;
    230.                 float multiTexBlendPow = pow(multiTexBlend, _SurfaceVarianceTexturePow);
    231.  
    232.                 fixed4 colX = tex2D(_SurfaceTexture, uvX / uvDistortion + offsetSurfX);
    233.                 fixed4 colY = tex2D(_SurfaceTexture, uvY  / uvDistortion + offsetSurfY);   //surface tex with parallax offset
    234.                 fixed4 colZ = tex2D(_SurfaceTexture, uvZ / uvDistortion + offsetSurfZ);    //UvDistortion refers to the texture zoom-out levels and does not affect parallax effect
    235.  
    236.                 fixed4 colX2 = tex2D(_SurfaceTexture, uvX / nextUVDist);
    237.                 fixed4 colY2 = tex2D(_SurfaceTexture, uvY / nextUVDist);    //surface tex zoomed out
    238.                 fixed4 colZ2 = tex2D(_SurfaceTexture, uvZ / nextUVDist);
    239.                
    240.                 fixed4 colXSteep = tex2D(_SteepTex, uvX / uvDistortion);
    241.                 fixed4 colYSteep = tex2D(_SteepTex, uvY / uvDistortion);    //steep tex
    242.                 fixed4 colZSteep = tex2D(_SteepTex, uvZ / uvDistortion);
    243.  
    244.                 fixed4 colXSteep2 = tex2D(_SteepTex, uvX / nextUVDist);
    245.                 fixed4 colYSteep2 = tex2D(_SteepTex, uvY / nextUVDist);    //steep tex
    246.                 fixed4 colZSteep2 = tex2D(_SteepTex, uvZ / nextUVDist);
    247.  
    248.              
    249.  
    250.  
    251.  
    252.                 fixed4 colXMulti = tex2D(_SurfaceVarianceTexture, (uvX * _SurfaceVarianceTexture_ST) / uvDistortion);
    253.                 fixed4 colYMulti = tex2D(_SurfaceVarianceTexture, (uvY * _SurfaceVarianceTexture_ST) / uvDistortion);   //surface variance tex
    254.                 fixed4 colZMulti = tex2D(_SurfaceVarianceTexture, (uvZ * _SurfaceVarianceTexture_ST) / uvDistortion);
    255.  
    256.                 fixed4 actualColXSteep = lerp(colXSteep, colXSteep2, percentage);
    257.                 fixed4 actualColYSteep = lerp(colYSteep, colYSteep2, percentage);   //Blend slope texture with the zoomed out version
    258.                 fixed4 actualColZSteep = lerp(colZSteep, colZSteep2, percentage);
    259.  
    260.                 fixed4 actualColX = lerp(colX, colX2, percentage);
    261.                 fixed4 actualColY = lerp(colY, colY2, percentage);  //UV distortion blending
    262.                 fixed4 actualColZ = lerp(colZ, colZ2, percentage);
    263.  
    264.                 fixed4 blendColX = lerp(actualColX, colXMulti, pow(multiTexBlend, _SurfaceVarianceTexturePow));
    265.                 fixed4 blendColY = lerp(actualColY, colYMulti, pow(multiTexBlend, _SurfaceVarianceTexturePow)); //multi-tex blending
    266.                 fixed4 blendColZ = lerp(actualColZ, colZMulti, pow(multiTexBlend, _SurfaceVarianceTexturePow));
    267.  
    268.                 fixed4 finalColX = lerp(blendColX, actualColXSteep, 1 - slope);
    269.                 fixed4 finalColY = lerp(blendColY, actualColYSteep, 1 - slope); //Final albedo color before lighting is applied
    270.                 fixed4 finalColZ = lerp(blendColZ, actualColZSteep, 1 - slope);
    271.                
    272.  
    273.                 finalColX.a = lerp(actualColX.a, colXMulti.a, pow(multiTexBlend, _SurfaceVarianceTexturePow));
    274.                 finalColY.a = lerp(actualColY.a, colYMulti.a, pow(multiTexBlend, _SurfaceVarianceTexturePow));  //Lerp the alpha channel for specular lighting
    275.                 finalColZ.a = lerp(actualColZ.a, colZMulti.a, pow(multiTexBlend, _SurfaceVarianceTexturePow));
    276.  
    277.                 fixed4 col = finalColX * triblend.x + finalColY * triblend.y + finalColZ * triblend.z;
    278.                 //return offset.x * 4;
    279.                 // tangent space normal map
    280.                 half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX + offsetSurfX));
    281.                 half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY + offsetSurfY));      //Triplanar normal mapping
    282.                 half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ + offsetSurfZ));
    283.  
    284.                 half3 tnormalSVX = UnpackNormal(tex2D(_SurfaceVarianceBumpMap, uvX + offsetSurfX));        //Second surface texture triplanar normal mapping
    285.                 half3 tnormalSVY = UnpackNormal(tex2D(_SurfaceVarianceBumpMap, uvY + offsetSurfY));
    286.                 half3 tnormalSVZ = UnpackNormal(tex2D(_SurfaceVarianceBumpMap, uvZ + offsetSurfZ));
    287.  
    288.                 half3 tnormal = tnormalX * triblend.x + tnormalY * triblend.y + tnormalZ * triblend.z;
    289.                 half3 tnormalSV = tnormalSVX * triblend.x + tnormalSVY * triblend.y + tnormalSVZ * triblend.z;
    290.                 tnormal = lerp(tnormal, tnormalSV, pow(multiTexBlend, _SurfaceVarianceTexturePow));
    291.  
    292.                 half3 worldNormal = normalize(half3(
    293.                     dot(i.tspace0, tnormal),
    294.                     dot(i.tspace1, tnormal),
    295.                     dot(i.tspace2, tnormal)
    296.                     ));
    297.                
    298.  
    299.                 //////////////////
    300.                 /////LIGHTING/////
    301.                 //////////////////
    302.                 half ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz));
    303.                 half3 ambient = ShadeSH9(half4(worldNormal, 1));
    304.                 half3 lighting = _LightColor0.rgb * ndotl + ambient;
    305.                 float3 lightDir = _WorldSpaceLightPos0.xyz;
    306.                 float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
    307.                 float3 halfVector = normalize(lightDir + viewDir);
    308.                 float3 specular = _LightColor0 * pow(DotClamped(halfVector, i.normal), _Metallic * 100);
    309.  
    310.  
    311.                 specular *= _MetallicTint;
    312.  
    313.  
    314.                 return fixed4(col.rgb + (specular.rgb * col.a), 1);
    315.                
    316.             }
    317.            
    318.             ENDCG
    319.         }
    320.     }
    321. }
    I understand that there is a lot of code to sort through, but I hope my comments have been helpful. You should be able to disregard a lot of the lighting / texture sampling and focus on where I've commented about the parallax effect.

    I've been struggling with this problem for a while, so any help would be much appreciated.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    You can't use any of this for triplanar mapped shaders. The tangent space is determined by the orientation of the texture, and since you're calculating the orientation dynamically from the world space position the mesh's existing tangents aren't valid anymore.

    This article is on doing correct normal mapping for triplanar shaders, but it also talks a little about calculating correct tangents for triplanar mapping.
    https://medium.com/@bgolus/normal-mapping-for-a-triplanar-shader-10bf39dca05a
    Most of the methods used work around needing correct tangents at all, but at the end I show two techniques that actually construct tangent to world matrices. See near Screen Space Partial Derivatives and Cross Product Reconstruction sections.
     
  3. Gameslinx

    Gameslinx

    Joined:
    Jun 24, 2017
    Posts:
    7
    I implemented the SSPD and CPR techniques and both yielded the same result:
    While that does correct my normal mapping (thank you!) it does not yield good results for the parallax occlusion effect.





    I'm unsure if I'm passing the correct variables to the Parallax_Vert method, but after experimenting with various values I wasn't able to produce any better results. Here is the code snippet I'm using in the Frag shader:

    Code (CSharp):
    1.  
    2.                    float3 blend = pow(abs(i.worldNormal.xyz), _Strength);
    3.                    blend /= blend.x + blend.y + blend.z;
    4.  
    5.                    //FROM https://medium.com/@bgolus/normal-mapping-for-a-triplanar-shader-10bf39dca05a#048e
    6.                    float2 uvX = i.worldPos.zy; // x facing plane
    7.                    float2 uvY = i.worldPos.xz; // y facing plane
    8.                    float2 uvZ = i.worldPos.xy; // z facing plane
    9.                    // Tangent space normal maps
    10.                    half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX * _SurfaceTexture_ST));
    11.                    half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY * _SurfaceTexture_ST));
    12.                    half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ * _SurfaceTexture_ST));
    13.                    // Get the sign (-1 or 1) of the surface normal
    14.                    half3 axisSign = sign(i.worldNormal);
    15.                    // Construct tangent to world matrices for each axis
    16.                    half3 tangentX = normalize(cross(i.worldNormal, half3(0, axisSign.x, 0)));
    17.                    half3 bitangentX = normalize(cross(tangentX, i.worldNormal)) * axisSign.x;
    18.                    half3x3 tbnX = half3x3(tangentX, bitangentX, i.worldNormal);
    19.                    half3 tangentY = normalize(cross(i.worldNormal, half3(0, 0, axisSign.y)));
    20.                    half3 bitangentY = normalize(cross(tangentY, i.worldNormal)) * axisSign.y;
    21.                    half3x3 tbnY = half3x3(tangentY, bitangentY, i.worldNormal);
    22.                    half3 tangentZ = normalize(cross(i.worldNormal, half3(0, -axisSign.z, 0)));
    23.                    half3 bitangentZ = normalize(-cross(tangentZ, i.worldNormal)) * axisSign.z;
    24.                    half3x3 tbnZ = half3x3(tangentZ, bitangentZ, i.worldNormal);
    25.                    // Apply tangent to world matrix and triblend
    26.                    // Using clamp() because the cross products may be NANs
    27.                    half3 worldNormal = normalize(
    28.                        clamp(mul(tnormalX, tbnX), -1, 1) * blend.x +
    29.                        clamp(mul(tnormalY, tbnY), -1, 1) * blend.y +
    30.                        clamp(mul(tnormalZ, tbnZ), -1, 1) * blend.z
    31.                    );
    32.                    float3 vertexNormal = normalize(i.worldNormal);
    33.                
    34.                    //Calculate new eye and sampleRatio from corrected tangents
    35.                    parallax_vert(float4(i.worldPos, 1), float4(vertexNormal, 1), float4(vertexNormal, 1), i.eye, i.sampleRatio);
     

    Attached Files:

    Last edited: Jul 21, 2020
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    The short answer is the
    parallax_vert
    function shouldn't be getting the vertex normal twice, it should be getting the vertex normal and the tangent with the bitangent sign for the w component. Mainly because it's expecting to have to calculate the bitangent itself. The bitangent is already being calculated by the "CPR" code above, but if you don't want to modify the parallax functions, the w component value is the
    axisSign
    seen for each bitangent. And that's the other big thing, the
    parallax_vert
    function needs to be run separately for all 3 axis as the tangent space and thus the tangent space eye vector (and sample ratio) that the function outputs is unique to each one!
     
  5. Gameslinx

    Gameslinx

    Joined:
    Jun 24, 2017
    Posts:
    7
    It appears as though progress is being made, but it's not quite there yet!


    This is the Z face, so I passed in the Z tangent and axisSign.z.
    Oddly, this works perfectly for Unity Terrain, but it doesn't work on a sphere.
    Here's the new line for the Parallax_Vert function:
    Code (CSharp):
    1. parallax_vert(float4(i.worldPos, 1), vertexNormal, float4(tangentZ, axisSign.z), i.eyeZ, i.sampleRatio.z);
    I've tried playing with the values and flipping them to no avail

    EDIT: After trying again within the game and applying the shader, it appears to somewhat work... Sort of!

    Edit 2: I've narrowed down the problem to the 'eye' value showing up incorrectly when returning it as the colour:
     
    Last edited: Jul 22, 2020
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    So, I don't think you can use that
    parallax_vert
    function as it's written at all on anything but terrain and expect it to work. At least not without a lot of extra work to prepare the data you send to it. It's expecting all the data you pass in to be in object space, and it converts it all from object space to world space. You're passing in already in world space data that's still going to get transformed from object space to world space. That means any kind of rotation, translation, or scale is going to complete screw things up. It kind of works on terrain because terrain can't be rotated or scaled, but it can be translated. However the weird patches ... yeah, I have no idea. Unity's terrain can do some weird things, so it's possible some of the data is "weird" on random patches, like the world space calculations are going off.

    Also, I should note that all of these kinds of tangent reconstruction techniques are approximate. Really, the whole concept of tangent space is itself something of an approximation. So there might still be some weirdness even after fixing the rest of the problems.
     
    hippocoder likes this.
  7. Gameslinx

    Gameslinx

    Joined:
    Jun 24, 2017
    Posts:
    7
    I believe I know what could be causing it:
    The parallax effect becomes increasingly distorted as the terrain moves away from 0, 0, 0 in the world.
    In the image below, the terrain has an offset of 8,0,0.
    upload_2020-7-23_18-10-38.png

    I also altered the Parallax_Vert function to accept the tbnX, tbnY and tbnZ variables so that it's not transforming data already in world space. This distortion in the effect occurred before and after the change to the method. It's as if moving the terrain around stretches the parallax effect. It isn't viable for me to pass the position of the terrain to the shader from the game (as the terrain is procedural, so I wonder what might be causing this?

    I believe it is the cause for the strange tiles in the terrain in my previous reply, since the effect observed in the bad tiles is identical to what happens when I offset the terrain in the editor.

    The new parallax_vert function reads
    Code (CSharp):
    1. void parallax_vert(
    2.                     float4 vertex,
    3.                     float3 normal,
    4.                     float3x3 objectToTangent,
    5.                     out float3 eye,
    6.                     out float sampleRatio
    7.                 ) {
    8.                     float3 EyePosition = _WorldSpaceCameraPos;
    9.  
    10.                     float4 localCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));
    11.                     float3 eyeLocal = localCameraPos;
    12.                     float4 eyeGlobal = mul(float4(eyeLocal, 1), unity_WorldToObject);
    13.                     float3 E = eyeGlobal.xyz;
    14.  
    15.                     eye = mul(objectToTangent, ObjSpaceViewDir(vertex));
    16.  
    17.                     sampleRatio = 1 - dot(normalize(E), -normal);
    18.                 }
    I am passing this data into it:
    Code (CSharp):
    1.  parallax_vert(float4(o.worldPos, 1), vertexNormal, tbnX, o.eyeX, o.sampleRatio.x);  //x
    2.                    parallax_vert(float4(o.worldPos, 1), vertexNormal, tbnY, o.eyeY, o.sampleRatio.y);  //y
    3.                    parallax_vert(float4(o.worldPos, 1), vertexNormal, tbnZ, o.eyeZ, o.sampleRatio.z);  //z
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    That function is doing a lot of needless work, and the transform matrices the CPR is generating are tangent to world space, not object space.

    Code (csharp):
    1. // world space view dir
    2. float3 worldSpaceViewDir = normalize(o.worldPos.xyz - _WorldSpaceCameraPos.xyz);
    3.  
    4. // just need to convert that one vector to each of the three tangent spaces
    5. eyeX = mul(tbnX, worldSpaceViewDir);
    6. eyeY = mul(tbnY, worldSpaceViewDir);
    7. eyeZ = mul(tbnZ, worldSpaceViewDir);
    8.  
    9. // this is the same for all 3 as it's not in tangent space
    10. sampleRatio = 1 - dot(-worldSpaceViewDir, vertexNormal);