├── README.md ├── LICENSE ├── TriplanarNaive.shader ├── TriplanarSwizzle.shader ├── TriplanarGPUGems3.shader ├── TriplanarWhiteout.shader ├── TriplanarUDN.shader ├── TriplanarRNM.shader ├── TriplanarReconstructedTangents.shader ├── TriplanarSurfaceShader.shader ├── TriplanarDerivativeCotangents.shader └── TriplanarReconstructedBitangents.shader /README.md: -------------------------------------------------------------------------------- 1 | # Normal-Mapping-for-a-Triplanar-Shader 2 | 3 | Example Unity shaders for my article on triplanar normal mapping. 4 | 5 | https://medium.com/@bgolus/normal-mapping-for-a-triplanar-shader-10bf39dca05a 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /TriplanarNaive.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // Naive Method example shader 3 | 4 | // This is the example of what not to do, don't use this for anything! 5 | 6 | Shader "Triplanar/Naive Method" 7 | { 8 | Properties 9 | { 10 | _MainTex ("Texture", 2D) = "white" {} 11 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 12 | } 13 | SubShader 14 | { 15 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 16 | LOD 100 17 | 18 | Pass 19 | { 20 | CGPROGRAM 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | 24 | #include "UnityCG.cginc" 25 | 26 | struct appdata 27 | { 28 | float4 vertex : POSITION; 29 | float3 normal : NORMAL; 30 | float4 tangent : TANGENT; 31 | }; 32 | 33 | struct v2f 34 | { 35 | float4 pos : SV_POSITION; 36 | float3 worldPos : TEXCOORD0; 37 | half3 tspace0 : TEXCOORD1; 38 | half3 tspace1 : TEXCOORD2; 39 | half3 tspace2 : TEXCOORD3; 40 | }; 41 | 42 | sampler2D _MainTex; 43 | float4 _MainTex_ST; 44 | 45 | sampler2D _BumpMap; 46 | 47 | fixed4 _LightColor0; 48 | 49 | v2f vert (appdata v) 50 | { 51 | v2f o; 52 | o.pos = UnityObjectToClipPos(v.vertex); 53 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 54 | 55 | half3 wNormal = UnityObjectToWorldNormal(v.normal); 56 | half3 wTangent = UnityObjectToWorldDir(v.tangent.xyz); 57 | // compute bitangent from cross product of normal and tangent 58 | half tangentSign = v.tangent.w * unity_WorldTransformParams.w; 59 | half3 wBitangent = cross(wNormal, wTangent) * tangentSign; 60 | // output the tangent space matrix 61 | o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x); 62 | o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y); 63 | o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z); 64 | 65 | return o; 66 | } 67 | 68 | fixed4 frag (v2f i) : SV_Target 69 | { 70 | half3 vertexNormal = abs(normalize(half3(i.tspace0.z, i.tspace1.z, i.tspace2.z))); 71 | half3 triblend = pow(vertexNormal, 4); 72 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 73 | 74 | // preview blend 75 | // return fixed4(triblend.xyz, 1); 76 | 77 | // calculate triplanar uvs 78 | // applying texture scale and offset values ala TRANSFORM_TEX macro 79 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 80 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 81 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 82 | 83 | // sample the texture 84 | fixed4 colX = tex2D(_MainTex, uvX); 85 | fixed4 colY = tex2D(_MainTex, uvY); 86 | fixed4 colZ = tex2D(_MainTex, uvZ); 87 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 88 | 89 | // tangent space normal map 90 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 91 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 92 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 93 | half3 tnormal = tnormalX * triblend.x + tnormalY * triblend.y + tnormalZ * triblend.z; 94 | 95 | half3 worldNormal = normalize(half3( 96 | dot(i.tspace0, tnormal), 97 | dot(i.tspace1, tnormal), 98 | dot(i.tspace2, tnormal) 99 | )); 100 | 101 | // preview world normals 102 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 103 | 104 | // calculate lighting 105 | half ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 106 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 107 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 108 | 109 | // preview directional lighting 110 | // return fixed4(ndotl.xxx, 1); 111 | 112 | return fixed4(col.rgb * lighting, 1); 113 | } 114 | ENDCG 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /TriplanarSwizzle.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // Swizzle example shader 3 | 4 | // The basic swizzle method of remapping tangent space normal maps into the world space. Use GPU Gems 3 or Whiteout instead. 5 | 6 | Shader "Triplanar/Swizzle" 7 | { 8 | Properties 9 | { 10 | _MainTex ("Texture", 2D) = "white" {} 11 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 12 | } 13 | SubShader 14 | { 15 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 16 | LOD 100 17 | 18 | Pass 19 | { 20 | CGPROGRAM 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | 24 | #include "UnityCG.cginc" 25 | 26 | // flip UVs horizontally to correct for back side projection 27 | #define TRIPLANAR_CORRECT_PROJECTED_U 28 | 29 | // offset UVs to prevent obvious mirroring 30 | // #define TRIPLANAR_UV_OFFSET 31 | 32 | struct appdata 33 | { 34 | float4 vertex : POSITION; 35 | float3 normal : NORMAL; 36 | }; 37 | 38 | struct v2f 39 | { 40 | float4 pos : SV_POSITION; 41 | float3 worldPos : TEXCOORD0; 42 | half3 worldNormal : TEXCOORD1; 43 | }; 44 | 45 | sampler2D _MainTex; 46 | float4 _MainTex_ST; 47 | 48 | sampler2D _BumpMap; 49 | 50 | fixed4 _LightColor0; 51 | 52 | v2f vert (appdata v) 53 | { 54 | v2f o; 55 | o.pos = UnityObjectToClipPos(v.vertex); 56 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 57 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 58 | 59 | return o; 60 | } 61 | 62 | fixed4 frag (v2f i) : SV_Target 63 | { 64 | // calculate triplanar blend 65 | half3 triblend = saturate(pow(i.worldNormal, 4)); 66 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 67 | 68 | // preview blend 69 | // return fixed4(triblend.xyz, 1); 70 | 71 | // calculate triplanar uvs 72 | // applying texture scale and offset values ala TRANSFORM_TEX macro 73 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 74 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 75 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 76 | 77 | // offset UVs to prevent obvious mirroring 78 | #if defined(TRIPLANAR_UV_OFFSET) 79 | uvY += 0.33; 80 | uvZ += 0.67; 81 | #endif 82 | 83 | // minor optimization of sign(). prevents return value of 0 84 | half3 axisSign = i.worldNormal < 0 ? -1 : 1; 85 | 86 | // flip UVs horizontally to correct for back side projection 87 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 88 | uvX.x *= axisSign.x; 89 | uvY.x *= axisSign.y; 90 | uvZ.x *= -axisSign.z; 91 | #endif 92 | 93 | // albedo textures 94 | fixed4 colX = tex2D(_MainTex, uvX); 95 | fixed4 colY = tex2D(_MainTex, uvY); 96 | fixed4 colZ = tex2D(_MainTex, uvZ); 97 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 98 | 99 | // tangent space normal maps 100 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 101 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 102 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 103 | 104 | // flip normal maps' x axis to account for flipped UVs 105 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 106 | tnormalX.x *= axisSign.x; 107 | tnormalY.x *= axisSign.y; 108 | tnormalZ.x *= -axisSign.z; 109 | #endif 110 | 111 | // flip normal maps' z axis to account for world surface normal facing 112 | tnormalX.z *= axisSign.x; 113 | tnormalY.z *= axisSign.y; 114 | tnormalZ.z *= axisSign.z; 115 | 116 | // swizzle tangent normals to match world orientation and blend together 117 | half3 worldNormal = normalize( 118 | tnormalX.zyx * triblend.x + 119 | tnormalY.xzy * triblend.y + 120 | tnormalZ.xyz * triblend.z 121 | ); 122 | 123 | // preview world normals 124 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 125 | 126 | // calculate lighting 127 | half ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 128 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 129 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 130 | 131 | // preview directional lighting 132 | // return fixed4(ndotl.xxx, 1); 133 | 134 | return fixed4(col.rgb * lighting, 1); 135 | } 136 | ENDCG 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /TriplanarGPUGems3.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // GPU Gems 3 example shader 3 | 4 | // Uses the GPU Gems 3 style normal map blend. Fastest method giving plausible results. 5 | 6 | Shader "Triplanar/GPU Gems 3" 7 | { 8 | Properties 9 | { 10 | _MainTex ("Texture", 2D) = "white" {} 11 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 12 | } 13 | SubShader 14 | { 15 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 16 | LOD 100 17 | 18 | Pass 19 | { 20 | CGPROGRAM 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | 24 | #include "UnityCG.cginc" 25 | 26 | // flip UVs horizontally to correct for back side projection 27 | #define TRIPLANAR_CORRECT_PROJECTED_U 28 | 29 | // offset UVs to prevent obvious mirroring 30 | // #define TRIPLANAR_UV_OFFSET 31 | 32 | struct appdata 33 | { 34 | float4 vertex : POSITION; 35 | float3 normal : NORMAL; 36 | }; 37 | 38 | struct v2f 39 | { 40 | float4 pos : SV_POSITION; 41 | float3 worldPos : TEXCOORD0; 42 | half3 worldNormal : TEXCOORD1; 43 | }; 44 | 45 | sampler2D _MainTex; 46 | float4 _MainTex_ST; 47 | 48 | sampler2D _BumpMap; 49 | 50 | fixed4 _LightColor0; 51 | 52 | v2f vert (appdata v) 53 | { 54 | v2f o; 55 | o.pos = UnityObjectToClipPos(v.vertex); 56 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 57 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 58 | 59 | return o; 60 | } 61 | 62 | fixed4 frag (v2f i) : SV_Target 63 | { 64 | // calculate triplanar blend 65 | half3 triblend = saturate(pow(i.worldNormal, 4)); 66 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 67 | 68 | // preview blend 69 | // return fixed4(triblend.xyz, 1); 70 | 71 | // calculate triplanar uvs 72 | // applying texture scale and offset values ala TRANSFORM_TEX macro 73 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 74 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 75 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 76 | 77 | // offset UVs to prevent obvious mirroring 78 | #if defined(TRIPLANAR_UV_OFFSET) 79 | uvY += 0.33; 80 | uvZ += 0.67; 81 | #endif 82 | 83 | // minor optimization of sign(). prevents return value of 0 84 | half3 axisSign = i.worldNormal < 0 ? -1 : 1; 85 | 86 | // flip UVs horizontally to correct for back side projection 87 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 88 | uvX.x *= axisSign.x; 89 | uvY.x *= axisSign.y; 90 | uvZ.x *= -axisSign.z; 91 | #endif 92 | 93 | // albedo textures 94 | fixed4 colX = tex2D(_MainTex, uvX); 95 | fixed4 colY = tex2D(_MainTex, uvY); 96 | fixed4 colZ = tex2D(_MainTex, uvZ); 97 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 98 | 99 | // tangent space normal maps 100 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 101 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 102 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 103 | 104 | // flip normal maps' x axis to account for flipped UVs 105 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 106 | tnormalX.x *= axisSign.x; 107 | tnormalY.x *= axisSign.y; 108 | tnormalZ.x *= -axisSign.z; 109 | #endif 110 | 111 | // swizzle tangent normal map to match world normals 112 | half3 normalX = half3(0.0, tnormalX.yx); 113 | half3 normalY = half3(tnormalY.x, 0.0, tnormalY.y); 114 | half3 normalZ = half3(tnormalZ.xy, 0.0); 115 | 116 | // blend normals and add to world normal 117 | half3 worldNormal = normalize( 118 | normalX.xyz * triblend.x + 119 | normalY.xyz * triblend.y + 120 | normalZ.xyz * triblend.z + 121 | i.worldNormal 122 | ); 123 | 124 | // preview world normals 125 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 126 | 127 | // calculate lighting 128 | half ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 129 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 130 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 131 | 132 | // preview directional lighting 133 | // return fixed4(ndotl.xxx, 1); 134 | 135 | return fixed4(col.rgb * lighting, 1); 136 | } 137 | ENDCG 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /TriplanarWhiteout.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // Whiteout Blend example shader 3 | 4 | // Uses a straight forward Whiteout style normal map blend. Good balance of performance and appearance. 5 | 6 | Shader "Triplanar/Whiteout Blend" 7 | { 8 | Properties 9 | { 10 | _MainTex ("Texture", 2D) = "white" {} 11 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 12 | } 13 | SubShader 14 | { 15 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 16 | LOD 100 17 | 18 | Pass 19 | { 20 | CGPROGRAM 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | 24 | #include "UnityCG.cginc" 25 | 26 | // flip UVs horizontally to correct for back side projection 27 | #define TRIPLANAR_CORRECT_PROJECTED_U 28 | 29 | // offset UVs to prevent obvious mirroring 30 | // #define TRIPLANAR_UV_OFFSET 31 | 32 | struct appdata 33 | { 34 | float4 vertex : POSITION; 35 | float3 normal : NORMAL; 36 | }; 37 | 38 | struct v2f 39 | { 40 | float4 pos : SV_POSITION; 41 | float3 worldPos : TEXCOORD0; 42 | half3 worldNormal : TEXCOORD1; 43 | }; 44 | 45 | sampler2D _MainTex; 46 | float4 _MainTex_ST; 47 | 48 | sampler2D _BumpMap; 49 | 50 | fixed4 _LightColor0; 51 | 52 | v2f vert (appdata v) 53 | { 54 | v2f o; 55 | o.pos = UnityObjectToClipPos(v.vertex); 56 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 57 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 58 | 59 | return o; 60 | } 61 | 62 | fixed4 frag (v2f i) : SV_Target 63 | { 64 | // calculate triplanar blend 65 | half3 triblend = pow(abs(i.worldNormal), 4); 66 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 67 | 68 | // preview blend 69 | // return fixed4(triblend.xyz, 1); 70 | 71 | // calculate triplanar uvs 72 | // applying texture scale and offset values ala TRANSFORM_TEX macro 73 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 74 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 75 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 76 | 77 | // offset UVs to prevent obvious mirroring 78 | #if defined(TRIPLANAR_UV_OFFSET) 79 | uvY += 0.33; 80 | uvZ += 0.67; 81 | #endif 82 | 83 | // minor optimization of sign(), prevents return value of 0 84 | half3 axisSign = i.worldNormal < 0 ? -1 : 1; 85 | 86 | // flip UVs horizontally to correct for back side projection 87 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 88 | uvX.x *= axisSign.x; 89 | uvY.x *= axisSign.y; 90 | uvZ.x *= -axisSign.z; 91 | #endif 92 | 93 | // albedo textures 94 | fixed4 colX = tex2D(_MainTex, uvX); 95 | fixed4 colY = tex2D(_MainTex, uvY); 96 | fixed4 colZ = tex2D(_MainTex, uvZ); 97 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 98 | 99 | // tangent space normal maps 100 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 101 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 102 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 103 | 104 | // flip normal maps' x axis to account for flipped UVs 105 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 106 | tnormalX.x *= axisSign.x; 107 | tnormalY.x *= axisSign.y; 108 | tnormalZ.x *= -axisSign.z; 109 | #endif 110 | 111 | // swizzle world normals to match tangent space and apply Whiteout normal blend 112 | tnormalX = half3(tnormalX.xy + i.worldNormal.zy, tnormalX.z * i.worldNormal.x); 113 | tnormalY = half3(tnormalY.xy + i.worldNormal.xz, tnormalY.z * i.worldNormal.y); 114 | tnormalZ = half3(tnormalZ.xy + i.worldNormal.xy, tnormalZ.z * i.worldNormal.z); 115 | 116 | // swizzle tangent normals to match world normal and blend together 117 | half3 worldNormal = normalize( 118 | tnormalX.zyx * triblend.x + 119 | tnormalY.xzy * triblend.y + 120 | tnormalZ.xyz * triblend.z 121 | ); 122 | 123 | // preview world normals 124 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 125 | 126 | // calculate lighting 127 | half ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 128 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 129 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 130 | 131 | // preview directional lighting 132 | // return fixed4(ndotl.xxx, 1); 133 | 134 | return fixed4(col.rgb * lighting, 1); 135 | } 136 | ENDCG 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /TriplanarUDN.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // UDN Blend example shader 3 | 4 | // Uses a straight forward UDN style normal map blend. Use the GPU Gems 3 shader instead. 5 | 6 | Shader "Triplanar/UDN Blend" 7 | { 8 | Properties 9 | { 10 | _MainTex ("Texture", 2D) = "white" {} 11 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 12 | } 13 | SubShader 14 | { 15 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 16 | LOD 100 17 | 18 | Pass 19 | { 20 | CGPROGRAM 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | 24 | #include "UnityCG.cginc" 25 | 26 | // flip UVs horizontally to correct for back side projection 27 | #define TRIPLANAR_CORRECT_PROJECTED_U 28 | 29 | // offset UVs to prevent obvious mirroring 30 | // #define TRIPLANAR_UV_OFFSET 31 | 32 | struct appdata 33 | { 34 | float4 vertex : POSITION; 35 | float3 normal : NORMAL; 36 | }; 37 | 38 | struct v2f 39 | { 40 | float4 pos : SV_POSITION; 41 | float3 worldPos : TEXCOORD0; 42 | half3 worldNormal : TEXCOORD1; 43 | }; 44 | 45 | sampler2D _MainTex; 46 | float4 _MainTex_ST; 47 | 48 | sampler2D _BumpMap; 49 | 50 | fixed4 _LightColor0; 51 | 52 | v2f vert (appdata v) 53 | { 54 | v2f o; 55 | o.pos = UnityObjectToClipPos(v.vertex); 56 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 57 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 58 | 59 | return o; 60 | } 61 | 62 | fixed4 frag (v2f i) : SV_Target 63 | { 64 | // calculate triplanar blend 65 | half3 triblend = saturate(pow(i.worldNormal, 4)); 66 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 67 | 68 | // preview blend 69 | // return fixed4(triblend.xyz, 1); 70 | 71 | // calculate triplanar uvs 72 | // applying texture scale and offset values ala TRANSFORM_TEX macro 73 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 74 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 75 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 76 | 77 | // offset UVs to prevent obvious mirroring 78 | #if defined(TRIPLANAR_UV_OFFSET) 79 | uvY += 0.33; 80 | uvZ += 0.67; 81 | #endif 82 | 83 | // minor optimization of sign(). prevents return value of 0 84 | half3 axisSign = i.worldNormal < 0 ? -1 : 1; 85 | 86 | // flip UVs horizontally to correct for back side projection 87 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 88 | uvX.x *= axisSign.x; 89 | uvY.x *= axisSign.y; 90 | uvZ.x *= -axisSign.z; 91 | #endif 92 | 93 | // albedo textures 94 | fixed4 colX = tex2D(_MainTex, uvX); 95 | fixed4 colY = tex2D(_MainTex, uvY); 96 | fixed4 colZ = tex2D(_MainTex, uvZ); 97 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 98 | 99 | // tangent space normal maps 100 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 101 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 102 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 103 | 104 | // flip normal maps' x axis to account for flipped UVs 105 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 106 | tnormalX.x *= axisSign.x; 107 | tnormalY.x *= axisSign.y; 108 | tnormalZ.x *= -axisSign.z; 109 | #endif 110 | 111 | // swizzle world normals to match tangent space and apply ala UDN normal blending 112 | // these should get normalized, but it's very a minor visual difference to skip it 113 | tnormalX = half3(tnormalX.xy + i.worldNormal.zy, i.worldNormal.x); 114 | tnormalY = half3(tnormalY.xy + i.worldNormal.xz, i.worldNormal.y); 115 | tnormalZ = half3(tnormalZ.xy + i.worldNormal.xy, i.worldNormal.z); 116 | 117 | // swizzle tangent normals to match world normal and blend together 118 | half3 worldNormal = normalize( 119 | tnormalX.zyx * triblend.x + 120 | tnormalY.xzy * triblend.y + 121 | tnormalZ.xyz * triblend.z 122 | ); 123 | 124 | // preview world normals 125 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 126 | 127 | // calculate lighting 128 | half ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 129 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 130 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 131 | 132 | // preview directional lighting 133 | // return fixed4(ndotl.xxx, 1); 134 | 135 | return fixed4(col.rgb * lighting, 1); 136 | } 137 | ENDCG 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /TriplanarRNM.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // Reoriented Normal Mapping example shader 3 | 4 | // Uses Reoriented Normal Mapping normal map blend. More expensive than GPU Gems 3 or Whiteout, but looks great! 5 | 6 | Shader "Triplanar/Reoriented Normal Mapping" 7 | { 8 | Properties 9 | { 10 | _MainTex ("Texture", 2D) = "white" {} 11 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 12 | } 13 | SubShader 14 | { 15 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 16 | LOD 100 17 | 18 | Pass 19 | { 20 | CGPROGRAM 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | 24 | #include "UnityCG.cginc" 25 | 26 | // flip UVs horizontally to correct for back side projection 27 | #define TRIPLANAR_CORRECT_PROJECTED_U 28 | 29 | // offset UVs to prevent obvious mirroring 30 | // #define TRIPLANAR_UV_OFFSET 31 | 32 | struct appdata 33 | { 34 | float4 vertex : POSITION; 35 | float3 normal : NORMAL; 36 | }; 37 | 38 | struct v2f 39 | { 40 | float4 pos : SV_POSITION; 41 | float3 worldPos : TEXCOORD0; 42 | half3 worldNormal : TEXCOORD1; 43 | }; 44 | 45 | sampler2D _MainTex; 46 | float4 _MainTex_ST; 47 | 48 | sampler2D _BumpMap; 49 | 50 | fixed4 _LightColor0; 51 | 52 | v2f vert (appdata v) 53 | { 54 | v2f o; 55 | o.pos = UnityObjectToClipPos(v.vertex); 56 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 57 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 58 | 59 | return o; 60 | } 61 | 62 | // Reoriented Normal Mapping 63 | // http://blog.selfshadow.com/publications/blending-in-detail/ 64 | // Altered to take normals (-1 to 1 ranges) rather than unsigned normal maps (0 to 1 ranges) 65 | half3 blend_rnm(half3 n1, half3 n2) 66 | { 67 | n1.z += 1; 68 | n2.xy = -n2.xy; 69 | 70 | return n1 * dot(n1, n2) / n1.z - n2; 71 | } 72 | 73 | fixed4 frag (v2f i) : SV_Target 74 | { 75 | // calculate triplanar blend 76 | half3 triblend = saturate(pow(i.worldNormal, 4)); 77 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 78 | 79 | // preview blend 80 | // return fixed4(triblend.xyz, 1); 81 | 82 | // calculate triplanar uvs 83 | // applying texture scale and offset values ala TRANSFORM_TEX macro 84 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 85 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 86 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 87 | 88 | // offset UVs to prevent obvious mirroring 89 | #if defined(TRIPLANAR_UV_OFFSET) 90 | uvY += 0.33; 91 | uvZ += 0.67; 92 | #endif 93 | 94 | // minor optimization of sign(). prevents return value of 0 95 | half3 axisSign = i.worldNormal < 0 ? -1 : 1; 96 | 97 | // flip UVs horizontally to correct for back side projection 98 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 99 | uvX.x *= axisSign.x; 100 | uvY.x *= axisSign.y; 101 | uvZ.x *= -axisSign.z; 102 | #endif 103 | 104 | // albedo textures 105 | fixed4 colX = tex2D(_MainTex, uvX); 106 | fixed4 colY = tex2D(_MainTex, uvY); 107 | fixed4 colZ = tex2D(_MainTex, uvZ); 108 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 109 | 110 | // tangent space normal maps 111 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 112 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 113 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 114 | 115 | // flip normal maps' x axis to account for flipped UVs 116 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 117 | tnormalX.x *= axisSign.x; 118 | tnormalY.x *= axisSign.y; 119 | tnormalZ.x *= -axisSign.z; 120 | #endif 121 | 122 | half3 absVertNormal = abs(i.worldNormal); 123 | 124 | // swizzle world normals to match tangent space and apply reoriented normal mapping blend 125 | tnormalX = blend_rnm(half3(i.worldNormal.zy, absVertNormal.x), tnormalX); 126 | tnormalY = blend_rnm(half3(i.worldNormal.xz, absVertNormal.y), tnormalY); 127 | tnormalZ = blend_rnm(half3(i.worldNormal.xy, absVertNormal.z), tnormalZ); 128 | 129 | // apply world space sign to tangent space Z 130 | tnormalX.z *= axisSign.x; 131 | tnormalY.z *= axisSign.y; 132 | tnormalZ.z *= axisSign.z; 133 | 134 | // sizzle tangent normals to match world normal and blend together 135 | half3 worldNormal = normalize( 136 | tnormalX.zyx * triblend.x + 137 | tnormalY.xzy * triblend.y + 138 | tnormalZ.xyz * triblend.z 139 | ); 140 | 141 | // preview world normals 142 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 143 | 144 | // calculate lighting 145 | fixed ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 146 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 147 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 148 | 149 | // preview directional lighting 150 | // return fixed4(ndotl.xxx, 1); 151 | 152 | return fixed4(col.rgb * lighting, 1); 153 | } 154 | ENDCG 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /TriplanarReconstructedTangents.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // Reconstructed Tangents example shader 3 | 4 | // Calculate plausible tangents and binormals in the fragment shader. Similar to how mesh tangents would be calculated. 5 | // Slightly faster than the screen space partial derivatives method. 6 | 7 | Shader "Triplanar/Reconstructed Tangents" 8 | { 9 | Properties 10 | { 11 | _MainTex ("Texture", 2D) = "white" {} 12 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 13 | } 14 | SubShader 15 | { 16 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 17 | LOD 100 18 | 19 | Pass 20 | { 21 | CGPROGRAM 22 | #pragma vertex vert 23 | #pragma fragment frag 24 | 25 | #include "UnityCG.cginc" 26 | 27 | // flip UVs horizontally to correct for back side projection 28 | #define TRIPLANAR_CORRECT_PROJECTED_U 29 | 30 | // offset UVs to prevent obvious mirroring 31 | // #define TRIPLANAR_UV_OFFSET 32 | 33 | struct appdata 34 | { 35 | float4 vertex : POSITION; 36 | float3 normal : NORMAL; 37 | }; 38 | 39 | struct v2f 40 | { 41 | float4 pos : SV_POSITION; 42 | float3 worldPos : TEXCOORD0; 43 | half3 worldNormal : TEXCOORD1; 44 | }; 45 | 46 | sampler2D _MainTex; 47 | float4 _MainTex_ST; 48 | 49 | sampler2D _BumpMap; 50 | 51 | fixed4 _LightColor0; 52 | 53 | v2f vert (appdata v) 54 | { 55 | v2f o; 56 | o.pos = UnityObjectToClipPos(v.vertex); 57 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 58 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 59 | 60 | return o; 61 | } 62 | 63 | fixed4 frag (v2f i) : SV_Target 64 | { 65 | // calculate triplanar blend 66 | half3 triblend = saturate(pow(i.worldNormal, 4)); 67 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 68 | 69 | // preview blend 70 | // return fixed4(triblend.xyz, 1); 71 | 72 | // calculate triplanar uvs 73 | // applying texture scale and offset values ala TRANSFORM_TEX macro 74 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 75 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 76 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 77 | 78 | // offset UVs to prevent obvious mirroring 79 | #if defined(TRIPLANAR_UV_OFFSET) 80 | uvY += 0.33; 81 | uvZ += 0.67; 82 | #endif 83 | 84 | // minor optimization of sign(). prevents return value of 0 85 | half3 axisSign = i.worldNormal < 0 ? -1 : 1; 86 | 87 | // flip UVs horizontally to correct for back side projection 88 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 89 | uvX.x *= axisSign.x; 90 | uvY.x *= axisSign.y; 91 | uvZ.x *= -axisSign.z; 92 | #endif 93 | 94 | // albedo textures 95 | fixed4 colX = tex2D(_MainTex, uvX); 96 | fixed4 colY = tex2D(_MainTex, uvY); 97 | fixed4 colZ = tex2D(_MainTex, uvZ); 98 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 99 | 100 | // tangent space normal maps 101 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 102 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 103 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 104 | 105 | // flip normal maps' x axis to account for flipped UVs 106 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 107 | tnormalX.x *= axisSign.x; 108 | tnormalY.x *= axisSign.y; 109 | tnormalZ.x *= -axisSign.z; 110 | #endif 111 | 112 | // construct tangent to world matrices for each axis 113 | half3 tangentX = normalize(cross(i.worldNormal, half3(0, axisSign.x, 0))); 114 | half3 bitangentX = normalize(cross(tangentX, i.worldNormal)) * axisSign.x; 115 | half3x3 tbnX = half3x3(tangentX, bitangentX, i.worldNormal); 116 | 117 | half3 tangentY = normalize(cross(i.worldNormal, half3(0, 0, axisSign.y))); 118 | half3 bitangentY = normalize(cross(tangentY, i.worldNormal)) * axisSign.y; 119 | half3x3 tbnY = half3x3(tangentY, bitangentY, i.worldNormal); 120 | 121 | half3 tangentZ = normalize(cross(i.worldNormal, half3(0, -axisSign.z, 0))); 122 | half3 bitangentZ = normalize(-cross(tangentZ, i.worldNormal)) * axisSign.z; 123 | half3x3 tbnZ = half3x3(tangentZ, bitangentZ, i.worldNormal); 124 | 125 | // Apply tangent to world matricies and blend 126 | half3 worldNormal = normalize( 127 | clamp(mul(tnormalX, tbnX), -1, 1) * triblend.x + 128 | clamp(mul(tnormalY, tbnY), -1, 1) * triblend.y + 129 | clamp(mul(tnormalZ, tbnZ), -1, 1) * triblend.z 130 | ); 131 | 132 | // preview world normals 133 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 134 | 135 | // calculate lighting 136 | fixed ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 137 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 138 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 139 | 140 | // preview directional lighting 141 | // return fixed4(ndotl.xxx, 1); 142 | 143 | return fixed4(col.rgb * lighting, 1); 144 | } 145 | ENDCG 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /TriplanarSurfaceShader.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // Unity Surface Shader example shader 3 | 4 | // Implements correct triplanar normals in a Surface Shader with out computing or passing additional information from the 5 | // vertex shader. 6 | 7 | Shader "Triplanar/Surface Shader (RNM)" { 8 | Properties { 9 | _MainTex ("Albedo (RGB)", 2D) = "white" {} 10 | [NoScaleOffset] _BumpMap("Normal Map", 2D) = "bump" {} 11 | _Glossiness("Smoothness", Range(0, 1)) = 0.5 12 | [Gamma] _Metallic("Metallic", Range(0, 1)) = 0 13 | [NoScaleOffset] _OcclusionMap("Occlusion", 2D) = "white" {} 14 | _OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0 15 | } 16 | SubShader { 17 | Tags { "RenderType"="Opaque" } 18 | LOD 200 19 | 20 | CGPROGRAM 21 | // Physically based Standard lighting model, and enable shadows on all light types 22 | #pragma surface surf Standard fullforwardshadows 23 | 24 | // Use shader model 3.0 target, to get nicer looking lighting 25 | #pragma target 3.0 26 | 27 | #include "UnityStandardUtils.cginc" 28 | 29 | // flip UVs horizontally to correct for back side projection 30 | #define TRIPLANAR_CORRECT_PROJECTED_U 31 | 32 | // offset UVs to prevent obvious mirroring 33 | // #define TRIPLANAR_UV_OFFSET 34 | 35 | // Reoriented Normal Mapping 36 | // http://blog.selfshadow.com/publications/blending-in-detail/ 37 | // Altered to take normals (-1 to 1 ranges) rather than unsigned normal maps (0 to 1 ranges) 38 | half3 blend_rnm(half3 n1, half3 n2) 39 | { 40 | n1.z += 1; 41 | n2.xy = -n2.xy; 42 | 43 | return n1 * dot(n1, n2) / n1.z - n2; 44 | } 45 | 46 | sampler2D _MainTex; 47 | float4 _MainTex_ST; 48 | 49 | sampler2D _BumpMap; 50 | sampler2D _OcclusionMap; 51 | 52 | half _Glossiness; 53 | half _Metallic; 54 | 55 | half _OcclusionStrength; 56 | 57 | struct Input { 58 | float3 worldPos; 59 | float3 worldNormal; 60 | INTERNAL_DATA 61 | }; 62 | 63 | float3 WorldToTangentNormalVector(Input IN, float3 normal) { 64 | float3 t2w0 = WorldNormalVector(IN, float3(1,0,0)); 65 | float3 t2w1 = WorldNormalVector(IN, float3(0,1,0)); 66 | float3 t2w2 = WorldNormalVector(IN, float3(0,0,1)); 67 | float3x3 t2w = float3x3(t2w0, t2w1, t2w2); 68 | return normalize(mul(t2w, normal)); 69 | } 70 | 71 | void surf (Input IN, inout SurfaceOutputStandard o) { 72 | // work around bug where IN.worldNormal is always (0,0,0)! 73 | IN.worldNormal = WorldNormalVector(IN, float3(0,0,1)); 74 | 75 | // calculate triplanar blend 76 | half3 triblend = saturate(pow(IN.worldNormal, 4)); 77 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 78 | 79 | // calculate triplanar uvs 80 | // applying texture scale and offset values ala TRANSFORM_TEX macro 81 | float2 uvX = IN.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zy; 82 | float2 uvY = IN.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zy; 83 | float2 uvZ = IN.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zy; 84 | 85 | // offset UVs to prevent obvious mirroring 86 | #if defined(TRIPLANAR_UV_OFFSET) 87 | uvY += 0.33; 88 | uvZ += 0.67; 89 | #endif 90 | 91 | // minor optimization of sign(). prevents return value of 0 92 | half3 axisSign = IN.worldNormal < 0 ? -1 : 1; 93 | 94 | // flip UVs horizontally to correct for back side projection 95 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 96 | uvX.x *= axisSign.x; 97 | uvY.x *= axisSign.y; 98 | uvZ.x *= -axisSign.z; 99 | #endif 100 | 101 | // albedo textures 102 | fixed4 colX = tex2D(_MainTex, uvX); 103 | fixed4 colY = tex2D(_MainTex, uvY); 104 | fixed4 colZ = tex2D(_MainTex, uvZ); 105 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 106 | 107 | // occlusion textures 108 | half occX = tex2D(_OcclusionMap, uvX).g; 109 | half occY = tex2D(_OcclusionMap, uvY).g; 110 | half occZ = tex2D(_OcclusionMap, uvZ).g; 111 | half occ = LerpOneTo(occX * triblend.x + occY * triblend.y + occZ * triblend.z, _OcclusionStrength); 112 | 113 | // tangent space normal maps 114 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 115 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 116 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 117 | 118 | // flip normal maps' x axis to account for flipped UVs 119 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 120 | tnormalX.x *= axisSign.x; 121 | tnormalY.x *= axisSign.y; 122 | tnormalZ.x *= -axisSign.z; 123 | #endif 124 | 125 | half3 absVertNormal = abs(IN.worldNormal); 126 | 127 | // swizzle world normals to match tangent space and apply reoriented normal mapping blend 128 | tnormalX = blend_rnm(half3(IN.worldNormal.zy, absVertNormal.x), tnormalX); 129 | tnormalY = blend_rnm(half3(IN.worldNormal.xz, absVertNormal.y), tnormalY); 130 | tnormalZ = blend_rnm(half3(IN.worldNormal.xy, absVertNormal.z), tnormalZ); 131 | 132 | // apply world space sign to tangent space Z 133 | tnormalX.z *= axisSign.x; 134 | tnormalY.z *= axisSign.y; 135 | tnormalZ.z *= axisSign.z; 136 | 137 | // sizzle tangent normals to match world normal and blend together 138 | half3 worldNormal = normalize( 139 | tnormalX.zyx * triblend.x + 140 | tnormalY.xzy * triblend.y + 141 | tnormalZ.xyz * triblend.z 142 | ); 143 | 144 | // set surface ouput properties 145 | o.Albedo = col.rgb; 146 | o.Metallic = _Metallic; 147 | o.Smoothness = _Glossiness; 148 | o.Occlusion = occ; 149 | 150 | // convert world space normals into tangent normals 151 | o.Normal = WorldToTangentNormalVector(IN, worldNormal); 152 | } 153 | ENDCG 154 | } 155 | FallBack "Diffuse" 156 | } 157 | -------------------------------------------------------------------------------- /TriplanarDerivativeCotangents.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // Screen Space Partial Derivative Cotangent Frame example shader 3 | 4 | // Calculates a tangent to world transform in the fragment shader using screen space partial derivatives. 5 | // Based on the ideas in this article by Christian Schüler http://www.thetenthplanet.de/archives/1180 6 | // Expensive, but lets you get tangent space vectors if needed. 7 | 8 | Shader "Triplanar/Derivative Cotangents" 9 | { 10 | Properties 11 | { 12 | _MainTex ("Texture", 2D) = "white" {} 13 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 14 | } 15 | SubShader 16 | { 17 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 18 | LOD 100 19 | 20 | Pass 21 | { 22 | CGPROGRAM 23 | #pragma vertex vert 24 | #pragma fragment frag 25 | 26 | #include "UnityCG.cginc" 27 | 28 | // flip UVs horizontally to correct for back side projection 29 | #define TRIPLANAR_CORRECT_PROJECTED_U 30 | 31 | // offset UVs to prevent obvious mirroring 32 | // #define TRIPLANAR_UV_OFFSET 33 | 34 | struct appdata 35 | { 36 | float4 vertex : POSITION; 37 | float3 normal : NORMAL; 38 | }; 39 | 40 | struct v2f 41 | { 42 | float4 pos : SV_POSITION; 43 | float3 worldPos : TEXCOORD0; 44 | half3 worldNormal : TEXCOORD1; 45 | }; 46 | 47 | sampler2D _MainTex; 48 | float4 _MainTex_ST; 49 | 50 | sampler2D _BumpMap; 51 | 52 | fixed4 _LightColor0; 53 | 54 | // Unity version of http://www.thetenthplanet.de/archives/1180 55 | float3x3 cotangent_frame( float3 normal, float3 position, float2 uv ) 56 | { 57 | // get edge vectors of the pixel triangle 58 | float3 dp1 = ddx( position ); 59 | float3 dp2 = ddy( position ) * _ProjectionParams.x; 60 | float2 duv1 = ddx( uv ); 61 | float2 duv2 = ddy( uv ) * _ProjectionParams.x; 62 | // solve the linear system 63 | float3 dp2perp = cross( dp2, normal ); 64 | float3 dp1perp = cross( normal, dp1 ); 65 | float3 T = dp2perp * duv1.x + dp1perp * duv2.x; 66 | float3 B = dp2perp * duv1.y + dp1perp * duv2.y; 67 | // construct a scale-invariant frame 68 | float invmax = rsqrt( max( dot(T,T), dot(B,B) ) ); 69 | 70 | // matrix is transposed, use mul(VECTOR, MATRIX) order 71 | return float3x3( T * invmax, B * invmax, normal ); 72 | } 73 | 74 | v2f vert (appdata v) 75 | { 76 | v2f o; 77 | o.pos = UnityObjectToClipPos(v.vertex); 78 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 79 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 80 | 81 | return o; 82 | } 83 | 84 | fixed4 frag (v2f i) : SV_Target 85 | { 86 | // calculate triplanar blend 87 | half3 triblend = saturate(pow(i.worldNormal, 4)); 88 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 89 | 90 | // preview blend 91 | // return fixed4(triblend.xyz, 1); 92 | 93 | // calculate triplanar uvs 94 | // applying texture scale and offset values ala TRANSFORM_TEX macro 95 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 96 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 97 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 98 | 99 | // offset UVs to prevent obvious mirroring 100 | #if defined(TRIPLANAR_UV_OFFSET) 101 | uvY += 0.33; 102 | uvZ += 0.67; 103 | #endif 104 | 105 | // minor optimization of sign(). prevents return value of 0 106 | half3 axisSign = i.worldNormal < 0 ? -1 : 1; 107 | 108 | // flip UVs horizontally to correct for back side projection 109 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 110 | uvX.x *= axisSign.x; 111 | uvY.x *= axisSign.y; 112 | uvZ.x *= -axisSign.z; 113 | #endif 114 | 115 | // albedo textures 116 | fixed4 colX = tex2D(_MainTex, uvX); 117 | fixed4 colY = tex2D(_MainTex, uvY); 118 | fixed4 colZ = tex2D(_MainTex, uvZ); 119 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 120 | 121 | // tangent space normal maps 122 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 123 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 124 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 125 | 126 | half3 vertexNormal = normalize(i.worldNormal); 127 | 128 | // calculate tangent to world transform matrices for each projection plane 129 | half3x3 tbnX = cotangent_frame(vertexNormal, i.worldPos, uvX); 130 | half3x3 tbnY = cotangent_frame(vertexNormal, i.worldPos, uvY); 131 | half3x3 tbnZ = cotangent_frame(vertexNormal, i.worldPos, uvZ); 132 | 133 | // apply transform and blend together 134 | half3 worldNormal = normalize( 135 | mul(tnormalX, tbnX) * triblend.x + 136 | mul(tnormalY, tbnY) * triblend.y + 137 | mul(tnormalZ, tbnZ) * triblend.z 138 | ); 139 | 140 | // preview world normals 141 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 142 | 143 | // calculate lighting 144 | half ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 145 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 146 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 147 | 148 | // preview directional lighting 149 | // return fixed4(ndotl.xxx, 1); 150 | 151 | return fixed4(col.rgb * lighting, 1); 152 | } 153 | ENDCG 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /TriplanarReconstructedBitangents.shader: -------------------------------------------------------------------------------- 1 | // Normal Mapping for a Triplanar Shader - Ben Golus 2017 2 | // Reconstructed "Bitangents"(?) example shader 3 | 4 | // Unlike the Reconstructed Tangents example, the tangent and binormal are not perpendicular to eachother. 5 | // Possibly more accurate? This method is not presented in the original article as I honestly don't know 6 | // what to call it. It is however cheaper than either the derivative cotangents or reconstructed tangents 7 | // shaders. Gets closer to Reoriented Normal Mapping in cost while still providing transform matrices. 8 | 9 | Shader "Triplanar/Reconstructed Bitangents" 10 | { 11 | Properties 12 | { 13 | _MainTex ("Texture", 2D) = "white" {} 14 | [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {} 15 | } 16 | SubShader 17 | { 18 | Tags { "LightMode"="ForwardBase" "RenderType"="Opaque" } 19 | LOD 100 20 | 21 | Pass 22 | { 23 | CGPROGRAM 24 | #pragma vertex vert 25 | #pragma fragment frag 26 | 27 | #include "UnityCG.cginc" 28 | 29 | // flip UVs horizontally to correct for back side projection 30 | #define TRIPLANAR_CORRECT_PROJECTED_U 31 | 32 | // offset UVs to prevent obvious mirroring 33 | // #define TRIPLANAR_UV_OFFSET 34 | 35 | struct appdata 36 | { 37 | float4 vertex : POSITION; 38 | float3 normal : NORMAL; 39 | }; 40 | 41 | struct v2f 42 | { 43 | float4 pos : SV_POSITION; 44 | float3 worldPos : TEXCOORD0; 45 | half3 worldNormal : TEXCOORD1; 46 | }; 47 | 48 | sampler2D _MainTex; 49 | float4 _MainTex_ST; 50 | 51 | sampler2D _BumpMap; 52 | 53 | fixed4 _LightColor0; 54 | 55 | v2f vert (appdata v) 56 | { 57 | v2f o; 58 | o.pos = UnityObjectToClipPos(v.vertex); 59 | o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz; 60 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 61 | 62 | return o; 63 | } 64 | 65 | fixed4 frag (v2f i) : SV_Target 66 | { 67 | // calculate triplanar blend 68 | half3 triblend = saturate(pow(i.worldNormal, 4)); 69 | triblend /= max(dot(triblend, half3(1,1,1)), 0.0001); 70 | 71 | // preview blend 72 | // return fixed4(triblend.xyz, 1); 73 | 74 | // calculate triplanar uvs 75 | // applying texture scale and offset values ala TRANSFORM_TEX macro 76 | float2 uvX = i.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zw; 77 | float2 uvY = i.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zw; 78 | float2 uvZ = i.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zw; 79 | 80 | // offset UVs to prevent obvious mirroring 81 | #if defined(TRIPLANAR_UV_OFFSET) 82 | uvY += 0.33; 83 | uvZ += 0.67; 84 | #endif 85 | 86 | // minor optimization of sign(). prevents return value of 0 87 | half3 axisSign = i.worldNormal < 0 ? -1 : 1; 88 | 89 | // flip UVs horizontally to correct for back side projection 90 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 91 | uvX.x *= axisSign.x; 92 | uvY.x *= axisSign.y; 93 | uvZ.x *= -axisSign.z; 94 | #endif 95 | 96 | // albedo textures 97 | fixed4 colX = tex2D(_MainTex, uvX); 98 | fixed4 colY = tex2D(_MainTex, uvY); 99 | fixed4 colZ = tex2D(_MainTex, uvZ); 100 | fixed4 col = colX * triblend.x + colY * triblend.y + colZ * triblend.z; 101 | 102 | // tangent space normal maps 103 | half3 tnormalX = UnpackNormal(tex2D(_BumpMap, uvX)); 104 | half3 tnormalY = UnpackNormal(tex2D(_BumpMap, uvY)); 105 | half3 tnormalZ = UnpackNormal(tex2D(_BumpMap, uvZ)); 106 | 107 | // flip normal maps' x axis to account for flipped UVs 108 | #if defined(TRIPLANAR_CORRECT_PROJECTED_U) 109 | tnormalX.x *= axisSign.x; 110 | tnormalY.x *= axisSign.y; 111 | tnormalZ.x *= -axisSign.z; 112 | #endif 113 | 114 | // construct tangent to world matrices for each axis 115 | // half3 tangentX = normalize(cross(i.worldNormal, half3(0, axisSign.x, 0))); 116 | // half3 bitangentX = normalize(cross(i.worldNormal, half3(0, 0, -axisSign.x))); 117 | half3 tangentX = normalize(half3(-i.worldNormal.z * axisSign.x, 0.0, i.worldNormal.x * axisSign.x)); 118 | half3 bitangentX = normalize(half3(-i.worldNormal.y * axisSign.x, i.worldNormal.x * axisSign.x, 0.0)); 119 | half3x3 tbnX = half3x3(tangentX, bitangentX, i.worldNormal); 120 | 121 | // half3 tangentY = normalize(cross(i.worldNormal, half3(0, 0, axisSign.y))); 122 | // half3 bitangentY = normalize(cross(i.worldNormal, half3(-axisSign.y, 0, 0))); 123 | half3 tangentY = normalize(half3(i.worldNormal.y * axisSign.y, -i.worldNormal.x * axisSign.y, 0.0)); 124 | half3 bitangentY = normalize(half3(0.0, -i.worldNormal.z * axisSign.y, i.worldNormal.y * axisSign.y)); 125 | half3x3 tbnY = half3x3(tangentY, bitangentY, i.worldNormal); 126 | 127 | // half3 tangentZ = normalize(cross(i.worldNormal, half3(0, -axisSign.z, 0))); 128 | // half3 bitangentZ = normalize(cross(i.worldNormal, half3(axisSign.z, 0, 0))); 129 | half3 tangentZ = normalize(half3(i.worldNormal.z * axisSign.z, 0.0, -i.worldNormal.x * axisSign.z)); 130 | half3 bitangentZ = normalize(half3(0.0, i.worldNormal.z * axisSign.z, -i.worldNormal.y * axisSign.z)); 131 | half3x3 tbnZ = half3x3(tangentZ, bitangentZ, i.worldNormal); 132 | 133 | // Apply tangent to world matricies and blend 134 | half3 worldNormal = normalize( 135 | clamp(mul(tnormalX, tbnX), -1, 1) * triblend.x + 136 | clamp(mul(tnormalY, tbnY), -1, 1) * triblend.y + 137 | clamp(mul(tnormalZ, tbnZ), -1, 1) * triblend.z 138 | ); 139 | 140 | // preview world normals 141 | // return fixed4(worldNormal * 0.5 + 0.5, 1); 142 | 143 | // calculate lighting 144 | fixed ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); 145 | half3 ambient = ShadeSH9(half4(worldNormal, 1)); 146 | half3 lighting = _LightColor0.rgb * ndotl + ambient; 147 | 148 | // preview directional lighting 149 | // return fixed4(ndotl.xxx, 1); 150 | 151 | return fixed4(col.rgb * lighting, 1); 152 | } 153 | ENDCG 154 | } 155 | } 156 | } 157 | --------------------------------------------------------------------------------