Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

Box / Triplanar mapping following object rotation

Discussion in 'Shaders' started by Azial, Oct 22, 2017.

  1. Azial

    Azial

    Joined:
    Mar 19, 2013
    Posts:
    17
    Hi,

    I need a very simple shader that projects a texture on all sides of the cube, but the texture should not get stretched by the cubes size. If the cube is not rotated, this is simple:

    Code (CSharp):
    1. Shader "Custom/BoxMap" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _TexScale ("Texture scale", Float) = 1
    6.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    7.         _Metallic ("Metallic", Range(0,1)) = 0.0
    8.     }
    9.     SubShader {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 200
    12.      
    13.         CGPROGRAM
    14.         #pragma surface surf Standard fullforwardshadows
    15.         #pragma target 3.0
    16.  
    17.         sampler2D _MainTex;
    18.         float _TexScale;
    19.  
    20.         struct Input {
    21.             float3 worldPos;
    22.             float3 worldNormal;
    23.         };
    24.  
    25.         half _Glossiness;
    26.         half _Metallic;
    27.         fixed4 _Color;
    28.  
    29.         void surf (Input IN, inout SurfaceOutputStandard o) {
    30.             // box mapping uv
    31.             float2 uv;
    32.             if (abs(IN.worldNormal.x) > 0.5) {
    33.                 uv = IN.worldPos.yz;
    34.             } else if (abs(IN.worldNormal.z) > 0.5) {
    35.                 uv = IN.worldPos.xy;
    36.             } else {
    37.                 uv = IN.worldPos.xz;
    38.             }
    39.             fixed4 c = tex2D(_MainTex, uv * (1/_TexScale));
    40.             o.Albedo = c.rgb * _Color;
    41.             // Metallic and smoothness come from slider variables
    42.             o.Metallic = _Metallic;
    43.             o.Smoothness = _Glossiness;
    44.             o.Alpha = c.a;
    45.         }
    46.         ENDCG
    47.     }
    48.     FallBack "Diffuse"
    49. }

    However, when the cube is rotated, the texture is still following the worldPos of course. Do I have to multiply the worldPos coords with the objects rotation matrix and if yes, how do I get this information in a shader? Or is there even a simpler way?
     
    Last edited: Oct 22, 2017
  2. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    339
    Pass local position (v.vertex.xyz) in from a custom vert program and use that instead.
     
  3. Azial

    Azial

    Joined:
    Mar 19, 2013
    Posts:
    17
    You mean like:

    #pragma surface surf Standard fullforwardshadows vertex:vert
    ...
    struct Input {
    ...
    float2 boxUV;
    };

    void vert (inout appdata_full v, out Input o) {
    UNITY_INITIALIZE_OUTPUT(Input, o);
    o.boxUV = v.vertex.xyz;
    }

    void surf (Input IN, inout SurfaceOutputStandard o) {
    fixed4 c = tex2D(_MainTex, IN.boxUV * (1/_TexScale));
    ...
    }

    But then the texture is stretched again, because the uv map is based on the mesh (which is affected by scale).
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    Basically you need to find a way to extract the scale from the object to world matrix and apply it to the local position with out the rotation.

    If you're using uniform scaling on your object, this is pretty easy. If you're using non-uniform scaling its still possible but a little more work. If you're using compound scaling, ie you have a parent game object that is also scaled and rotated such that it into introduces skewing, this gets a lot harder to get right, but don't the same thing as the non-uniform scale might be good enough.

    All that information is in the unity_ObjectToWorld matrix. You need to get the length of the first three columns to get the scale.
     
  5. Azial

    Azial

    Joined:
    Mar 19, 2013
    Posts:
    17
    Hm, is this matrix a TRS-matrix in homogeneous coordinates? If I know the matrix-layout I could try to get the rotation out of this instead of the scale. If I do it externally in a script, it looks good:

    Script:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PassRotationToShader : MonoBehaviour {
    6.  
    7.     // Use this for initialization
    8.     void Start() {
    9.         Material m = GetComponent<Renderer>().material;
    10.         Quaternion rotReversedQ = Quaternion.Inverse(transform.rotation);
    11.         Matrix4x4 rotReversedMat = QuaternionToMatrix(rotReversedQ);
    12.         m.SetMatrix("_WorldRotation", rotReversedMat);
    13.     }
    14.    
    15.     Matrix4x4 QuaternionToMatrix(Quaternion q) {
    16.         // Converts a quaternion rotation to rotation matrix
    17.         // algorithm taken from http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/
    18.  
    19.         Matrix4x4 mat = new Matrix4x4();
    20.         float sqw = q.w*q.w;
    21.         float sqx = q.x*q.x;
    22.         float sqy = q.y*q.y;
    23.         float sqz = q.z*q.z;
    24.  
    25.         // invs (inverse square length) is only required if quaternion is not already normalised
    26.         float invs = 1.0f / (sqx + sqy + sqz + sqw);
    27.         mat.m00 = ( sqx - sqy - sqz + sqw)*invs; // since sqw + sqx + sqy + sqz =1/invs*invs
    28.         mat.m11 = (-sqx + sqy - sqz + sqw)*invs;
    29.         mat.m22 = (-sqx - sqy + sqz + sqw)*invs;
    30.  
    31.         float tmp1 = q.x*q.y;
    32.         float tmp2 = q.z*q.w;
    33.         mat.m10 = 2.0f * (tmp1 + tmp2)*invs;
    34.         mat.m01 = 2.0f * (tmp1 - tmp2)*invs;
    35.  
    36.         tmp1 = q.x*q.z;
    37.         tmp2 = q.y*q.w;
    38.         mat.m20 = 2.0f * (tmp1 - tmp2)*invs;
    39.         mat.m02 = 2.0f * (tmp1 + tmp2)*invs;
    40.         tmp1 = q.y*q.z;
    41.         tmp2 = q.x*q.w;
    42.         mat.m21 = 2.0f * (tmp1 + tmp2)*invs;
    43.         mat.m12 = 2.0f * (tmp1 - tmp2)*invs;
    44.  
    45.         return mat;
    46.     }
    47. }

    Shader:
    Code (CSharp):
    1. Shader "Custom/BoxMap" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _TexScale ("Texture scale", Float) = 1
    6.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    7.         _Metallic ("Metallic", Range(0,1)) = 0.0
    8.     }
    9.     SubShader {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 200
    12.        
    13.         CGPROGRAM
    14.         // Physically based Standard lighting model, and enable shadows on all light types
    15.         #pragma surface surf Standard fullforwardshadows
    16.  
    17.         // Use shader model 3.0 target, to get nicer looking lighting
    18.         #pragma target 3.0
    19.  
    20.         sampler2D _MainTex;
    21.         float _TexScale;
    22.  
    23.         struct Input {
    24.             float3 worldPos;
    25.             float3 worldNormal;
    26.         };
    27.  
    28.         half _Glossiness;
    29.         half _Metallic;
    30.         fixed4 _Color;
    31.  
    32.         float4x4 _WorldRotation;
    33.  
    34.         void surf (Input IN, inout SurfaceOutputStandard o) {
    35.             // box mapping uv
    36.             float2 uv;
    37.             float3 pos = mul(_WorldRotation, float4(IN.worldPos, 0)).xyz;
    38.             if (abs(IN.worldNormal.x) > 0.5) {
    39.                 uv = pos.yz;
    40.             } else if (abs(IN.worldNormal.z) > 0.5) {
    41.                 uv = pos.xy;
    42.             } else {
    43.                 uv = pos.xz;
    44.             }
    45.             fixed4 c = tex2D(_MainTex, uv * (1/_TexScale));
    46.             o.Albedo = c.rgb * _Color;
    47.             // Metallic and smoothness come from slider variables
    48.             o.Metallic = _Metallic;
    49.             o.Smoothness = _Glossiness;
    50.             o.Alpha = c.a;
    51.         }
    52.         ENDCG
    53.     }
    54.     FallBack "Diffuse"
    55. }
    56.  
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    If you're going to calculate a matrix from a quaternion there's no need to calculate that yourself. There's a Matrix4x4.Rotate(quat) you can use instead. You could always just pass lossyScale to the shader and use that to scale the local vertex position too.
     
  7. Azial

    Azial

    Joined:
    Mar 19, 2013
    Posts:
    17
    Thanks for the hints so far. I think I have all the parts of the puzzle, but I'm not that much into shaders and the result is still not what I expect. I tried all kinds of combinations multiplying v.vertex or v.texcoord or worldPos with parts of the transformation matrix. The best I can get is this, but it works only for either scaling OR rotating the object. For both at the same time, the texture gets sheared:

    Code (CSharp):
    1. Shader "Custom/BoxMap" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _TexScale ("Texture scale", Float) = 1
    6.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    7.         _Metallic ("Metallic", Range(0,1)) = 0.0
    8.     }
    9.     SubShader {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 200
    12.        
    13.         CGPROGRAM
    14.         #pragma surface surf Standard fullforwardshadows
    15.         #pragma target 3.0
    16.  
    17.         sampler2D _MainTex;
    18.         float _TexScale;
    19.  
    20.         struct Input {
    21.             float3 worldPos;
    22.             float3 worldNormal;
    23.         };
    24.  
    25.         half _Glossiness;
    26.         half _Metallic;
    27.         fixed4 _Color;
    28.  
    29.         void surf (Input IN, inout SurfaceOutputStandard o) {
    30.             // get objects inverse transformation
    31.             float4x4 m = unity_WorldToObject;
    32.             // get translation from matrix
    33.             float3 pos = float3(m[0][3], m[1][3], m[2][3]);
    34.             // get scale from matrix
    35.             float3 scale;
    36.             scale.x = length(float4(m[0][0], m[1][0], m[2][0], m[3][0]));
    37.             scale.y = length(float4(m[0][1], m[1][1], m[2][1], m[3][1]));
    38.             scale.z = length(float4(m[0][2], m[1][2], m[2][2], m[3][2]));
    39.             // get rotation from matrix
    40.             float4x4 rot = float4x4(
    41.                 m[0][0]/scale.x, m[0][1]/scale.y, m[0][2]/scale.z, 0,
    42.                 m[1][0]/scale.x, m[1][1]/scale.y, m[1][2]/scale.z, 0,
    43.                 m[2][0]/scale.x, m[2][1]/scale.y, m[2][2]/scale.z, 0,
    44.                 0, 0, 0, 1);
    45.  
    46.             // make box mapping with rotation preserved
    47.             float3 map = mul(rot, float4(IN.worldPos+pos, 0)).xyz;
    48.             float3 norm = mul(rot, float4(IN.worldNormal, 0)).xyz;
    49.             float2 uv;
    50.             if (abs(norm.x) > 0.5) {
    51.                 uv = map.yz;
    52.             } else if (abs(norm.z) > 0.5) {
    53.                 uv = map.xy;
    54.             } else {
    55.                 uv = map.xz;
    56.             }
    57.  
    58.             fixed4 c = tex2D(_MainTex, uv * (1/_TexScale));
    59.             o.Albedo = c.rgb * _Color;
    60.             // Metallic and smoothness come from slider variables
    61.             o.Metallic = _Metallic;
    62.             o.Smoothness = _Glossiness;
    63.             o.Alpha = c.a;
    64.         }
    65.         ENDCG
    66.     }
    67.     FallBack "Diffuse"
    68. }
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    You were close. I simplified some of the code structures; using the _m## notation to access a matrix element inline and using a float3x3 rotation matrix instead of a float4x4.

    Biggest issue was switching from column to row. Also changing how the object position was factored in.

    Code (CSharp):
    1. Shader "Custom/BoxMap" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _TexScale ("Texture scale", Float) = 1
    6.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    7.         _Metallic ("Metallic", Range(0,1)) = 0.0
    8.     }
    9.     SubShader {
    10.         Tags { "RenderType"="Opaque" "DisableBatching"="True" }
    11.         LOD 200
    12.    
    13.         CGPROGRAM
    14.         #pragma surface surf Standard fullforwardshadows
    15.         #pragma target 3.0
    16.         sampler2D _MainTex;
    17.         float _TexScale;
    18.         struct Input {
    19.             float3 worldPos;
    20.             float3 worldNormal;
    21.         };
    22.         half _Glossiness;
    23.         half _Metallic;
    24.         fixed4 _Color;
    25.         void surf (Input IN, inout SurfaceOutputStandard o) {
    26.  
    27.             // get scale from matrix
    28.             float3 scale = float3(
    29.                 length(unity_WorldToObject._m00_m01_m02),
    30.                 length(unity_WorldToObject._m10_m11_m12),
    31.                 length(unity_WorldToObject._m20_m21_m22)
    32.                 );
    33.  
    34.             // get translation from matrix
    35.             float3 pos = unity_WorldToObject._m03_m13_m23 / scale;
    36.  
    37.             // get unscaled rotation from matrix
    38.             float3x3 rot = float3x3(
    39.                 normalize(unity_WorldToObject._m00_m01_m02),
    40.                 normalize(unity_WorldToObject._m10_m11_m12),
    41.                 normalize(unity_WorldToObject._m20_m21_m22)
    42.                 );
    43.             // make box mapping with rotation preserved
    44.             float3 map = mul(rot, IN.worldPos) + pos;
    45.             float3 norm = mul(rot, IN.worldNormal);
    46.  
    47.             float3 blend = abs(norm) / dot(abs(norm), float3(1,1,1));
    48.             float2 uv;
    49.             if (blend.x > max(blend.y, blend.z)) {
    50.                 uv = map.yz;
    51.             } else if (blend.z > blend.y) {
    52.                 uv = map.xy;
    53.             } else {
    54.                 uv = map.xz;
    55.             }
    56.             fixed4 c = tex2D(_MainTex, uv * (1/_TexScale));
    57.             o.Albedo = c.rgb * _Color;
    58.  
    59.             // Metallic and smoothness come from slider variables
    60.             o.Metallic = _Metallic;
    61.             o.Smoothness = _Glossiness;
    62.             o.Alpha = c.a;
    63.         }
    64.         ENDCG
    65.     }
    66.     FallBack "Diffuse"
    67. }
    necro edit: adding DisableBatching tag
     
    Last edited: Aug 22, 2018
  9. Azial

    Azial

    Joined:
    Mar 19, 2013
    Posts:
    17
    Ah, thanks! That's exactly what I was looking for.

    Unfortunately, in play mode and when built, the rotation on some object seems not applied. In edit mode everything looks fine.

    BoxMapping.jpg

    Btw, is there a particular reason for your changes on the face selecting (blend vs norm)? I tried both methods and didn't noticed any differences.
     
    Last edited: Oct 26, 2017
  10. mh114

    mh114

    Joined:
    Nov 17, 2013
    Posts:
    273
    Perhaps the cubes are batched together (statically or dynamically)?
     
    bgolus likes this.
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    This. Batching will cause all knowledge of local rotation to be lost as the actual mesh being rendered has no rotation in the object to world matrix. You can try adding "DisableBatching"="True" to the tags.

    For a box there won't be any noticable difference. Try it on a sphere though...
     
  12. Jick87

    Jick87

    Joined:
    Oct 21, 2015
    Posts:
    123
    @bgolus Sorry to necro this post but, I was just trying out your code and it works great! Not exactly as-is because I'm using it in a vert/frag shader rather than a surface shader, but your basic code is unchanged. However, I noticed one thing about it that I was wondering if you had any tips for...

    Basically, animated/modified vertexes... Earlier on in my shader, in the vertex function, I have some vertex modification going on for a special animation effect. It just moves the vertexes randomly over time. Then later, in the fragment function, I have basic texture/color stuff which is where I added your code. As I said, it all works fine, except for the animated vertexes. In those places the texture seems to stay static where it was rather than move with the vertexes, like it does for non-animated vertexes.

    I'm still new to the world of shaders, so I'm not sure how I could/would make changes to effect that. I figured maybe it was worth a shot to ask here. This isn't a total deal breaker so I can live with it if it's not possible, but I figured what's the harm in asking? :p

    Thanks!
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    If you're using a vertex fragment shader there's a lot of that shader you don't need as like a third of it is trying to get local vertex positions and normals.

    As for why your shader is having problems. No idea. There's no reason what you're describing can't work like you want, but I have no idea what the problem would be. The question I would ask is in what space are you modifying the vertices, and what version of the vertices are you passing into the triplanar code.
     
  14. Jick87

    Jick87

    Joined:
    Oct 21, 2015
    Posts:
    123
    Best as I can tell, my vertex modifying code is working in local(object?) space. It takes in a raw vertex at the very beginning of the vertex function and modifies it without changing the space it's working in at all (never calls unity_ObjectToWorld etc) and then returns it and assigns it directly to the same vertex value. Something like this:
    Code (CSharp):
    1. v2f vert( appdata_t v )
    2. {
    3.     v2f o;
    4.     UNITY_SETUP_INSTANCE_ID( v );
    5.     UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO( o );
    6.     UNITY_TRANSFER_INSTANCE_ID( v, o );
    7.  
    8.     v.vertex = FunctionToAnimateVertex( v.vertex );
    9.  
    10.     o.pos = UnityObjectToClipPos( v.vertex );
    11.     o.worldPos = mul( unity_ObjectToWorld, v.vertex ).xyz;
    12.     o.worldNormal = mul( unity_ObjectToWorld, v.normal ).xyz;
    13.  
    14.     return o;
    15. }
    This code is obviously stripped way down, but it should illustrate what I mean.

    As a matter of fact, speaking of stripped down code... I was trying this code before I came across your code in this thread:
    Code (CSharp):
    1. float3 bf = normalize( abs( i.localNormal ) );
    2. bf /= dot( bf, (float3)1 );
    3.  
    4. col = saturate( ( ( tex2D( _MainTex, ( i.localPos.yz * _TexScale ) ) * bf.x ) +
    5.       ( tex2D( _MainTex, ( i.localPos.zx * _TexScale ) ) * bf.y ) +
    6.       ( tex2D( _MainTex, ( i.localPos.xy * _TexScale ) ) * bf.z ) ) * _TintColor );
    It seems to work almost as good as your version, except it too has an issue with the animated vertexes, plus it also has an additional issue with stretching rather than tiling when dealing with scaled meshes. Your version doesn't seem to suffer from that issue, which is why I was excited when I found it.

    My overall shader code is a bit too bulky to post in its entirety, but perhaps I can post a stripped-down barebones version if it could help. But again, this isn't the hugest of deals. I can live with it if I have to. Just not sure where I'm going wrong with my shader code...

    Anyway, thanks for replying! I appreciate your help here and elsewhere. I've found a lot of useful things in many of your posts throughout this forum. A treasure trove of knowledge. :p
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    You’re moving the vertices, then using those positions to calculate the UVs. Try setting worldPos before you apply the noise so you’re using the original vertex positions.
     
  16. Jick87

    Jick87

    Joined:
    Oct 21, 2015
    Posts:
    123
    Oh... my... gosh... That was it! Now I feel like a real fool... :p


    As a last thing, you said that a good portion of your code wasn't necessary when using it in a vertex/fragment shader as opposed to a surface shader. I'm wondering, what parts exactly are not needed? I've been going over it but I'm not sure I understand. I'm learning... Just, slowly! :p


    Thanks again for your help! :)
     
  17. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    In a Surface Shader, the world position and world normal are both readily available in the surface function, so I used that. I could have passed the object space position and normal instead using a custom vertex function, but Surface Shaders already pass a ton of data from the vertex to the fragment shader, so I try to avoid that when possible.

    For a straight vertex fragment shader there's not really a point to calculating the world position and normal if you don't need to, as the object space triplanar code is just undoing that work. The only thing needed is extracting the scale and applying it to the object space vertex position and normal in the vertex function, then passing that on.

    Code (CSharp):
    1. Shader "Unlit/ObjectSpaceTriplanar"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.     }
    7.     SubShader
    8.     {
    9.         Tags { "RenderType"="Opaque" "DisableBatching"="True" }
    10.         LOD 100
    11.  
    12.         Pass
    13.         {
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.             #pragma multi_compile_fog
    18.          
    19.             #include "UnityCG.cginc"
    20.  
    21.             struct appdata
    22.             {
    23.                 float4 vertex : POSITION;
    24.                 float3 normal : NORMAL;
    25.             };
    26.  
    27.             struct v2f
    28.             {
    29.                 float4 pos : SV_POSITION;
    30.                 float3 uvPos : TEXCOORD0;
    31.                 float3 uvBlend : TEXCOORD1;
    32.                 UNITY_FOG_COORDS(2)
    33.             };
    34.  
    35.             sampler2D _MainTex;
    36.             float4 _MainTex_ST;
    37.          
    38.             v2f vert (appdata v)
    39.             {
    40.                 v2f o;
    41.                 o.pos = UnityObjectToClipPos(v.vertex);
    42.  
    43.                 float3 scale = float3(
    44.                     length(unity_ObjectToWorld._m00_m10_m20),
    45.                     length(unity_ObjectToWorld._m01_m11_m21),
    46.                     length(unity_ObjectToWorld._m02_m12_m22)
    47.                     );
    48.  
    49.                 o.uvPos = v.vertex.xyz * scale;
    50.                 o.uvBlend = v.normal.xyz / scale;
    51.                 o.uvBlend /= dot(abs(o.uvBlend), float3(1,1,1));
    52.  
    53.                 UNITY_TRANSFER_FOG(o,o.pos);
    54.                 return o;
    55.             }
    56.          
    57.             fixed4 frag (v2f i) : SV_Target
    58.             {
    59.                 float3 blend = abs(i.uvBlend);
    60.                 float2 uv;
    61.                 if (i.uvBlend.x > max(i.uvBlend.y, i.uvBlend.z)) {
    62.                     uv = i.uvPos.yz;
    63.                 } else if (i.uvBlend.z > i.uvBlend.y) {
    64.                     uv = i.uvPos.xy;
    65.                 } else {
    66.                     uv = i.uvPos.xz;
    67.                 }
    68.                 fixed4 col = tex2D(_MainTex, TRANSFORM_TEX(uv, _MainTex));
    69.  
    70.                 UNITY_APPLY_FOG(i.fogCoord, col);
    71.                 return col;
    72.             }
    73.             ENDCG
    74.         }
    75.  
    76.     }
    77.  
    78.     Fallback "VertexLit"
    79. }
    You have to extract the scale in a slightly different way as you're applying it to the object space position rather than removing it from the world space position, but there are no matrix multiplies needed.
     
  18. Jick87

    Jick87

    Joined:
    Oct 21, 2015
    Posts:
    123
    Oh, I understand now. Thanks for the code and the explanation. :)

    One issue I noticed with that code was some stretching on certain sides of my objects, but I think I solved it. In the code you defined a float3 called "blend" like this:
    Code (CSharp):
    1. float3 blend = abs( i.uvBlend );
    But then don't actually use it in the code that follows it. After I changed the instances of
    i.uvBlend.[XYZ]
    to
    blend.[XYZ]
    the stretching issues went away.

    So basically, I changed your code to this:
    Code (CSharp):
    1. Shader "Unlit/ObjectSpaceTriplanar"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.     }
    7.     SubShader
    8.     {
    9.         Tags { "RenderType"="Opaque" "DisableBatching"="True" }
    10.         LOD 100
    11.  
    12.         Pass
    13.         {
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.             #pragma multi_compile_fog
    18.  
    19.             #include "UnityCG.cginc"
    20.  
    21.             struct appdata
    22.             {
    23.                 float4 vertex : POSITION;
    24.                 float3 normal : NORMAL;
    25.             };
    26.  
    27.             struct v2f
    28.             {
    29.                 float4 pos : SV_POSITION;
    30.                 float3 uvPos : TEXCOORD0;
    31.                 float3 uvBlend : TEXCOORD1;
    32.                 UNITY_FOG_COORDS(2)
    33.             };
    34.  
    35.             sampler2D _MainTex;
    36.             float4 _MainTex_ST;
    37.  
    38.             v2f vert (appdata v)
    39.             {
    40.                 v2f o;
    41.                 o.pos = UnityObjectToClipPos(v.vertex);
    42.  
    43.                 float3 scale = float3(
    44.                     length(unity_ObjectToWorld._m00_m10_m20),
    45.                     length(unity_ObjectToWorld._m01_m11_m21),
    46.                     length(unity_ObjectToWorld._m02_m12_m22)
    47.                     );
    48.  
    49.                 o.uvPos = v.vertex.xyz * scale;
    50.                 o.uvBlend = v.normal.xyz / scale;
    51.                 o.uvBlend /= dot(abs(o.uvBlend), float3(1,1,1));
    52.  
    53.                 UNITY_TRANSFER_FOG(o,o.pos);
    54.                 return o;
    55.             }
    56.  
    57.             fixed4 frag (v2f i) : SV_Target
    58.             {
    59.                 float3 blend = abs(i.uvBlend);
    60.                 float2 uv;
    61.                 if (blend.x > max(blend.y, blend.z)) {
    62.                     uv = i.uvPos.yz;
    63.                 } else if (blend.z > blend.y) {
    64.                     uv = i.uvPos.xy;
    65.                 } else {
    66.                     uv = i.uvPos.xz;
    67.                 }
    68.                 fixed4 col = tex2D(_MainTex, TRANSFORM_TEX(uv, _MainTex));
    69.  
    70.                 UNITY_APPLY_FOG(i.fogCoord, col);
    71.                 return col;
    72.             }
    73.             ENDCG
    74.         }
    75.  
    76.     }
    77.  
    78.     Fallback "VertexLit"
    79. }
    Figured I should tell about the changes in case someone else comes along trying to use the code.

    But anyway, thanks again for the help. I really appreciate it. :)
     
    bgolus likes this.
  19. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    Yep, I originally was doing that abs in the vertex shader. I figured it would be a little safer to do it in the fragment shader, but I'm not positive that's true. Ideally both the sum normalization (divide by the dot product) and the abs should be done in the fragment shader and only the scaling of the normal should be done in the vertex, but for most use cases there won't be a noticeable difference.

    Honestly, for this type of shader, you could probably skip the dot product line entirely and not notice a difference. It's more important if you plan on doing blending.
     
    Last edited: Aug 30, 2018
    Jick87 likes this.
  20. Jick87

    Jick87

    Joined:
    Oct 21, 2015
    Posts:
    123
    Yeah, I was actually playing around trying to get a better understanding of the code and, I noticed that commenting out that line had no visual difference, at least as far as I can tell. But then the game I'm working on is a low poly one with no really round/smooth shapes (almost everything is made out of flat/cube shapes) so I guess that makes sense based on your info. :)

    Thanks again for the great help.
     
  21. CCTTINOVEM

    CCTTINOVEM

    Joined:
    Feb 11, 2019
    Posts:
    1
    Hi everyone! i know it's an old thread but I currently need a shader like the one mentionned in this discussion, with the addition of normal map and specular map.

    I'm a total newbie with shaders, I really want to understand how to add these features. I read the article form bgolus, it was really interesting, but every time I tried to add normal map to this shader, my object become either all white or pink...

    If someone is interested in helping me create this shader it would be really nice!
     
  22. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,680
    The example shader above is an unlit shader, normal mapping won't really be that useful to add since the shader doesn't get any lighting information to use with the normals.