Search Unity

Question What is the correct way to blend several layered materials? I get gaps.

Discussion in 'Shader Graph' started by Ben_at_Work, Jun 28, 2022.

  1. Ben_at_Work

    Ben_at_Work

    Joined:
    Mar 16, 2022
    Posts:
    83
    I built out a 4-layer material in Shader Graph that treats the fourth layer (A) as a base, consuming all remaining influence when the rest of the splatmap is absent. I used an already functioning splatmap which I assumed to be balanced, which allowed me to toss remaining influence into (A). I converted this to vertex colors in Maya which function identically.

    My layering is done with Lerps like every tutorial I was able to find, yet I always end up with a gap between the values. I have a prebuilt shader that came with the TerrainToMesh asset, and that apparently manages to use the same splatmap to create exactly the same blend as the Unity terrain I extracted it from. My shader is the only one not working, meaning that my algorithm isn't right.

    layeredMaterialLerps1.PNG layeredMaterialSplat.PNG

    I tried changing the order of the Lerps to see if that helped, but it just shifted the problem to a different layer instead. I also tried "normalizing" my vertex colors so if I were to paint white, it would result in a weight of (0.33, 0.33, 0.33, 0), but that was more of a fix for continued painting. I tried lerping from a vector4(0,0,0,0) towards the base layer as well, in case being explicit somehow paid off.

    The splatmap has sRGB unchecked and compression set to None.

    What causes these gaps when layering maps and how can I avoid it? What order does the default Unity terrain layer in?

    Chasing this has me thinking in circles. I spotted the same problem happening in a few of the basic tutorials I watched with no mention of fixing it. I have questions about enforcing a maximum weight between vertex painted channels, too, but I need help on the bold questions first.

    layeredMaterialGap.PNG layeredMaterialGap2.PNG layeredMaterialGap3.PNG
     
  2. Ben_at_Work

    Ben_at_Work

    Joined:
    Mar 16, 2022
    Posts:
    83
    Figured it out. Lerps don't properly respect weights because there's no channel or structure to represent unfilled space. In any given lerp, the total coverage by the end of the operation is always 100%. Lerp only allows me to define the weight of the second half of the operation, so if I have four channels, each with 25% weight, the first lerp will always give 75% to the unspecified base layer. Worse, if the first layer is empty (0%) and the second layer is 25%, you'll end up with something like 75% black blended into the result. That black can't be removed in any way, so it gets blended over and blended over.

    Instead of lerping layers, it's better to add from zero so that each layer isn't responsible for removing any part of the last layer. The only caveat is that the total weights painted need to add up to 1 which is a rule easily broken by painting white. I have a script handling that in the shader, but the best way to do it would be to bake it to the texture or vertex color source so it doesn't have to be recalculated endlessly. Enforcing it during painting somehow would also work.

    Screenshot shows a splatmap mutliplying each layer against its corresponding weight, then adding the results.

    layeredMaterialAdds.png
     
  3. thelazylamaGames

    thelazylamaGames

    Joined:
    May 13, 2020
    Posts:
    2
    Hey, what do you do with the textures after adding them?

    I've been trying to recreate unity's terrain layer blending but keep getting weird results.
    I'm new to Shader Graph and I've tried your method but with my limited knowledge I've got no clue what you do after the image cuts off.

    As you can see with my current solution I still get weird gaps especially when blending layers.
    SplatmapBlending.gif
    and here's my current blending setup
    upload_2022-8-2_13-55-39.png
    Any help would be much appreciated as this has been killing me for days now, Cheers
     
    Last edited: Aug 2, 2022
  4. Ben_at_Work

    Ben_at_Work

    Joined:
    Mar 16, 2022
    Posts:
    83
    I don't do anything beyond that point. Additive layering should ensure that a pixels full value comes from one of your layers, and all proportions are preserved regardless of the order of layering. Its main drawback is that there's no way to confirm that your RGBA values actually add up to a total of 1.0 influence. There's nothing stopping your splatmap from having more than 1.0 influence (resulting in excessively bright pixels) or less than 1.0 (resulting in dark pixels).

    If you exported your splatmap from a Unity terrain, the values should add up, or close enough as makes no difference. Check that your splatmap is imported as linear (uncheck sRGB) because the conversion there would distort the influences. I've had similar issues when trying to multiply HDR color tints against a base white texture, with the result not being the same as the HDR's appearance. There's some color space issues that need to be lined up when you do that.

    If you're painting the splatmap through vertex painting, you'll want to use pure red, green, blue, and alpha brushes. Unity is kind of bad with alpha, as it makes the swatch itself invisible on Polybrush. I had to create fake RGB swatches with their correct swatches beside them because they all appear invisible.

    If you want to be certain the values are balanced, you can temporarily normalize them in your shader. It's not the same as vector normalization (the built in node) because vector normalization creates a length of 1 unit, not a total of 1 influence across all channels. This type of operation should ideally be baked into the splatmap rather than recalculated every frame since it doesn't actually change during play.

    I assume it's also better to paint your layers after designing the blending algorithm, as each blending method will gravitate towards its own painting style. The order-of-operation limited methods are especially responsive to it.

    I think Unreal recommended having three additive layers and one full opacity layer, which I guess would require tracking the total influence at each point so the remainder can be allotted to the last layer. This allows you to ignore the alpha channel, too, and paint black in order to expose the base layer. With a permanent base layer, you'll never have gaps. I don't know how to implement that but the concept sounds approachable if you're digging around for options.

    I used a custom node connected to a .HLSL script with the below setup and code to test the blending without worrying about whether I'd balanced the splatmap correctly:
    forceInfluences.PNG

    Code (CSharp):
    1.  
    2. // Vector normalization adjusts the magnitude of a vector to 1.
    3. // Weighted normalization adjusts the total weight to 1.
    4.  
    5. void BalanceVector4Weights_float(
    6. float4 RawWeights, out float4 BalancedWeights)
    7. {
    8.     // Find the total influence across channels.
    9.     float totalInfluence = RawWeights.x + RawWeights.y + RawWeights.z + RawWeights.w;
    10.    
    11.     if(totalInfluence == 0)
    12.         // Edge case. If no weights are assigned, force them onto the first channel for safety.
    13.         BalancedWeights = float4(1, 0, 0, 0);
    14.     else
    15.         // If 0.4 weight is used, then multiply by: (1/totalWeights) = (1/0.4) = 2.5.
    16.         // If 1.5 weight is used, then multiply by: (1/totalWeights) = (1/1.5) = 0.66.
    17.         // If -1 weight is used, then multiply by: (1/totalWeights) = (1/-1) = -1.
    18.        
    19.         BalancedWeights = RawWeights*(1/totalInfluence);
    20. }
     
    zalogic likes this.
  5. thelazylamaGames

    thelazylamaGames

    Joined:
    May 13, 2020
    Posts:
    2
    You absolute legend, thanks for such a detailed response. With your help I've fixed it and now it's working perfectly.