Search Unity

Textured Diffuse with a Triplanar repeating detail.

Discussion in 'Shaders' started by Kurt-Dekker, Oct 17, 2021.

  1. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    I'm trying to make standard diffuse shader that also multiplies by a speckle detail that uses local coordinates for UV.

    The thinking is to use local coordinates to triplanar-map speckle texture even on top of hyper-low poly objects where all the UVs of a single face are crushed to a single color in the source diffuse texture.

    I have it working as long as I specify
    worldPos
    and
    worldNormal
    . That works fine for stationary objects since their world won't change.

    But I want to use this on moving objects so I switched those two inputs to
    localPos
    and
    localNormal
    , based on this:

    https://github.com/keijiro/StandardTriplanar

    However, in my shader code below when I use localPos/worldPos I am not getting textures that change across the faces. It just seems to blend with a constant gray rather than the expected speckle mapped to triplanar local space.

    My work so far... works perfectly if you use worldPos and worldNormal:

    Code (csharp):
    1. // Triplanar portion cribbed from and simplified to single texture:
    2. // http://www.blog.radiator.debacle.us/2013/02/a-smoother-triplanar-shader-for-unity.html
    3.  
    4. Shader "Tri-Planar Simplify" {
    5.   Properties {
    6.         _Color ("Main Color", Color) = (1,1,1,1)
    7.         _MainTex ("Base (RGB)", 2D) = "white" {}
    8.  
    9.         _Detail("Detail", 2D) = "white" {}
    10.  
    11.         _SideScale("Side Scale", Float) = 2
    12.         _TopScale("Top Scale", Float) = 2
    13.         _BottomScale ("Bottom Scale", Float) = 2
    14.     }
    15.    
    16.     SubShader {
    17.         Tags {
    18. //            "Queue"="Geometry"
    19. //            "IgnoreProjector"="False"
    20.             "RenderType"="Opaque"
    21.         }
    22.  
    23.         Cull Back
    24.         ZWrite On
    25.        
    26.         CGPROGRAM
    27.         #pragma surface surf Lambert
    28.         #pragma exclude_renderers flash
    29.  
    30.         sampler2D _Detail;
    31.         float _SideScale, _TopScale, _BottomScale;
    32.  
    33.         sampler2D _MainTex;
    34.         fixed4 _Color;
    35.        
    36.         struct Input {
    37.             float2 uv_MainTex;
    38.             float3 localPos;        // used to be worldPos
    39.             float3 localNormal;        // used to be worldNormal
    40.         };
    41.            
    42.         void surf (Input IN, inout SurfaceOutput o) {
    43.             float3 projNormal = saturate(pow(IN.localNormal * 1.4, 4));
    44.            
    45.             // SIDE X
    46.             float3 x = tex2D(_Detail, frac(IN.localPos.zy * _SideScale)) * abs(IN.localNormal.x);
    47.            
    48.             // TOP / BOTTOM
    49.             float3 y = 0;
    50.             if (IN.localNormal.y > 0) {
    51.                 y = tex2D(_Detail, frac(IN.localPos.zx * _TopScale)) * abs(IN.localNormal.y);
    52.             } else {
    53.                 y = tex2D(_Detail, frac(IN.localPos.zx * _BottomScale)) * abs(IN.localNormal.y);
    54.             }
    55.            
    56.             // SIDE Z    
    57.             float3 z = tex2D(_Detail, frac(IN.localPos.xy * _SideScale)) * abs(IN.localNormal.z);
    58.  
    59.  
    60.             // multiply by mapped texture
    61.             float3 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    62.  
    63.             // mix color channel into each sampled axis
    64.             x = x * c;
    65.             y = y * c;
    66.             z = z * c;
    67.  
    68.             // the core triplanar blend
    69.             o.Albedo = z;
    70.             o.Albedo = lerp(o.Albedo, x, projNormal.x);
    71.             o.Albedo = lerp(o.Albedo, y, projNormal.y);
    72.         }
    73.         ENDCG
    74.     }
    75.     Fallback "Diffuse"
    76. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    This is what it looks like with world coordinates (the above code changed to 'world').

    I want this effect with local coordinates, but the detail does not appear with the above code, eg., it's mapped all from the same input texel.

    Screen Shot 2021-10-16 at 6.59.00 PM.png
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    You might want to search this page for the word "local".
    https://docs.unity3d.com/Manual/SL-SurfaceShaders.html

    Not finding anything? It's because they don't exist. Those aren't Input variable names that Surface Shaders automatically handle. And any variable in the input struct that isn't automatically handled (and it really is just the 6 listed at the bottom of the page) will be left uninitialized as zeros unless you write code to fill in the data yourself.

    See the example on this page titled "Custom data computed per-vertex":
    https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

    Alternatively you could keep the world position and normal values, and reconstruct the local position and normal using the
    unity_WorldToObject
    matrix.

    Code (csharp):
    1. float3 localPos = mul(unity_WorldToObject, float3(IN.worldPos, 1.0)).xyz;
    2. float3 localNormal = normalize(mul(IN.worldNormal, (float3x3)unity_ObjectToWorld));


    One word of warning though. If you want to use this on skinned meshes this won't get you the position or normals you want. The mesh Unity gives to the shader has already been deformed by the skinning, so you'll get the "local position" moving around. The only solution is to bake the original local space position and normals into extra UV channels on the mesh. This can be done externally or in the editor with a custom asset processor or manually run script. Alternatively the data can be passed to the shader as some kind of buffer, but there is no other option but to get and pass this information to the shader yourself.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    Hey @bgolus thanks a lot for the insight. I have a lot to learn in shader land still. My late-night googling turned up both localPos and localCoord, so that should have been the clue that they were only locally-provided in each of the response contexts where I found it.

    There's a lot more going on here than I presently understand, with apparently the overall batch driving parameters related to the projection.

    By this I mean if I put one cube in and spin it, I get exactly what I want (after the changes above): textures nailed perfectly to local cube space.

    But if I add a a second cube in the scene, works fine as long as it's a different material. As soon as it shares the same material as the first cube, the materials all jump and are now pinned to world coordinates.

    That stops the instant I go back to a single mesh (or move one cube out of the view frustum so it gets culled), so there's a lot more going on here than I presently understand.

    I also ran up against the whole "too many interpolators," which I solved with going to 3.5, but I don't know what implication that has for who in my target audience (mobile) would use it vs the Diffuse fallback.

    Thanks for your input, I need to do some more ramping up before using this. I guess I could use the worldPos variant directly for non-moving stuff, like terrain / environment / props, as that works as I would need for static items.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Dynamic and static batching work by taking several meshes and combining them into one mesh pre-transformed into world space. So local space is now world space for a batched mesh. The only way to avoid that is to A) bake the local position & normal into an extra uv channel (like with skinned meshes) or B) disable dynamic batching. That can be disabled for the whole project, or this shader with
    “DisableBatching”=“true”
    added to the
    Tags {}
    .

    If you’re trying to support OpenGLES 2.0, yes, it’ll drop to the fallback shader. If you’re trying to support OpenGLES 3.0 and newer, 3.5 = GLES3.0