Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Mip map bleeding at the edges on tile atlas

Discussion in 'Shaders' started by aXu_AP, Sep 7, 2015.

  1. aXu_AP

    aXu_AP

    Joined:
    Sep 7, 2015
    Posts:
    5
    I'm making a Minecraft-type voxel engine (ok no surprises here, I guess), and have textures packed in an atlas. I've made a shader which then chooces the right tile according color alpha channel. When there are no mip maps it works all too well, but with mip maps there's strange bleeding at the edges of blocks. For test purposes I've colored each mip map level differently. At the image, the lines you see are from level 5. Any ideas what would cause this? Problem.png
    Here's the same view with actual textures (problem isn't as striking, but still persists). For testing, textures are from Minecraft texture pack "Faithful" by Vattic.
    Problem textured.png
    Here's the shader code. I have currently set the safe margin to 0, as it doesn't affect this problem (it's for filtered images).
    Code (CSharp):
    1. Shader "Custom/Textured" {
    2.     Properties {
    3.         _MainTex ("Atlas", 2D) = "gray" {}
    4.     }
    5.     SubShader {
    6.         Tags { "RenderType"="Opaque" }
    7.         LOD 200
    8.      
    9.         CGPROGRAM
    10.         #pragma surface surf Standard fullforwardshadows
    11.         #pragma target 3.0
    12.  
    13.         sampler2D _MainTex;
    14.  
    15.         struct Input {
    16.             float4 color : COLOR;
    17.             float3 worldNormal;
    18.             float3 worldPos;
    19.         };
    20.  
    21.         void surf (Input i, inout SurfaceOutputStandard o) {
    22.             const float gridSize = 16; //How many cells are in a row
    23.             const float margin = 0; //(1 / gridSize) / 16; //Margin for preventing bleeding
    24.             const float cellSize = (1 / gridSize) - (margin * 2); //Size of one cell
    25.             float blockId = int(i.color.a * gridSize * gridSize) / gridSize; //Get tile id from alpha channel, and transform it so that fraction is horizontal position [0, 1] and integer part points vertical position [0, gridSize]
    26.             fixed4 c = tex2D (_MainTex, frac(float2(
    27.                 i.worldPos.x * (1 - i.worldNormal.x) + i.worldPos.z * i.worldNormal.x, //Figure out texel location inside a cell.
    28.                 i.worldPos.y * (1 - i.worldNormal.y) + i.worldPos.z * i.worldNormal.y)) * cellSize  //Take in account face normal
    29.             + float2(blockId + margin, floor(blockId) / gridSize + margin)); //Choose the actual tile
    30.          
    31.             o.Albedo = c.rgb;
    32.         }
    33.         ENDCG
    34.     }
    35.     FallBack "Diffuse"
    36. }
    37.  
    ps. Why code block doesn't have option for shader code? Forum newbie here...
     
    Last edited: Sep 7, 2015
  2. emilmeiton

    emilmeiton

    Joined:
    Apr 8, 2014
    Posts:
    15
  3. aXu_AP

    aXu_AP

    Joined:
    Sep 7, 2015
    Posts:
    5
    Thanks for reply. Having read that, and just about every other article about voxel engines I've found I am already using greedy meshing, figured per tile mip mapping all right and tried to add the huge bleeding area (quadrupling texture size) - it kind of solves the problem, as no other tile shows up, but strange seams of higher mip map persist and they're pretty ugly. Here's a zoomed up image with the seam highlighted.
    Seam.png
    As you can see, the seam isn't continuous - so it isn't bleeding problem. Somehow for these pixels wrong mip map is chosen :/
     
  4. emilmeiton

    emilmeiton

    Joined:
    Apr 8, 2014
    Posts:
    15
    Ah I know what that is!!
    I think atleast.., since the uvs are "jumping" from one part of the texture to another place (instead of normal wraping) it thinks it needs to use a lower mip-mapping level.., got the same problem myself in a custom shader..,

    you can use your own values for the uv-delta (I think it's called)

    I'll check real quick...
     
  5. emilmeiton

    emilmeiton

    Joined:
    Apr 8, 2014
    Posts:
    15
  6. aXu_AP

    aXu_AP

    Joined:
    Sep 7, 2015
    Posts:
    5
    Thank you, that seems to be the solution. I gave the article a quick read, but so far I didn't find the equivalent function for texture2DGrad in cg. I'll look more to it after work.
    EDIT: Ah, it is simply tex2Dgrad, but editor just didn't highlight it properly. Still gotta figure those delta values somehow :)
     
  7. emilmeiton

    emilmeiton

    Joined:
    Apr 8, 2014
    Posts:
    15
    I think you can just use tex2D or Texture2D directly without the grad.., I'm not sure what the differnce is exactly...
    I definitely used tex2D in my shaders...
     
  8. aXu_AP

    aXu_AP

    Joined:
    Sep 7, 2015
    Posts:
    5
    How exactly did you solve the problem in your shader? Tex2Dgrad lets you use your own delta values, which ensures correct mip level is chosen. Normal tex2D makes this calculation for you, but in this case, chooses the wrong mip map at the seams.
     
  9. aXu_AP

    aXu_AP

    Joined:
    Sep 7, 2015
    Posts:
    5
    It worked! Thank you :) . Here's the fixed shader function:
    Code (CSharp):
    1.         void surf (Input i, inout SurfaceOutputStandard o) {
    2.             const float gridSize = 16;
    3.             const float margin = (1 / gridSize) / 4;//For 16x16 textures, margin of 1px, 32x32 => 2px etc.
    4.             const float cellSize = (1 / gridSize) - (margin * 2);
    5.             float blockId = int(i.color.a * gridSize * gridSize) / gridSize;
    6.             float2 uv = float2(
    7.                 i.worldPos.x * (1 - i.worldNormal.x) + i.worldPos.z * i.worldNormal.x,
    8.                 i.worldPos.y * (1 - i.worldNormal.y) + i.worldPos.z * i.worldNormal.y);
    9.             fixed4 c = tex2Dgrad (_MainTex, frac(uv) * cellSize + float2(blockId + margin, floor(blockId) / gridSize + margin), ddx(uv * cellSize), ddy(uv * cellSize));
    10.            
    11.             o.Albedo = c.rgb;
    12.         }
    13.  
    There's still some seams between chunks (altough these chunks consist one mesh)... I'll see if I can work it out myself. Other problem, which is still harder to fix is when mip maps get too small to contain tiles :/
    Here's solution, but it's been there since 2013: http://feedback.unity3d.com/suggestions/maximum-mipmap-level
     
    PhD-wannabe and LaireonGames like this.
  10. emilmeiton

    emilmeiton

    Joined:
    Apr 8, 2014
    Posts:
    15
    regarding tex2D vs tex2Dgrad.., For me it seems tex2D can take two more values and thus behaving exactly like tex2Dgrad.., I'm not sure if there's any difference then..., maybe it's good to use tex2Dgrad for claritys sake