Jump to content
  • Advertisement
Sign in to follow this  

3D Handling seams in lightmap atlas creation

Recommended Posts

Hello! I'm in the middle of a procedural texture system (for things like lightmaps, splatoon-style decal painting etc.) and I have an annoying artefact I don't seem to be able to get rid of.

I've been working through this ( https://www.flipcode.com/archives/Lightmap_Storage.shtml ) method of generating atlas uvs, which mostly works pretty well (here on a capsule):


But under closer inspection has artefacts around the places where the charts meet:


Since this method of uv generation splits tris into charts based on  primary axis, this is where the charts join up. I've drawn out roughly the edges of the three visible charts and you can see how it lines up:


I've got the texture filtering set to 'point' for debugging.

So from what I can see the uvs are correct and the texel edges are all aligned correctly, it's just that the fringe texels from one atlas don't match their counterpart texels in the other charts. I'm calling these 'twinned' texels - they exist twice (or more) in the atlas texture, but to eliminate these artifacts they should be identical colours.


To populate the texture I've got an unwrapped version of the mesh (which basically swaps vertex positions with uvs) which can be rendered into the texture directly. My thinking was that if I divide the world into a 3d grid and have the shader's output colour *only* dependant on the grid position (not the actual fragment world position) then these 'twinned' texels would evaluate to the same colour in all instances, even though it was being generated by different  geometry. Although clearly something's not working.

Atlas rendering shader code:

Shader "Unlit/Atlas"
        _MainTex ("Texture", 2D) = "white" {}

		_VoxelSize("Voxel Size", Float) = 1.0

		_ColourScale("Colour Scale", Float) = 0.1
		_ColourOffset("Colour Offset", Vector) = (0,0,0,0)
        Tags { "RenderType"="Opaque" }
		Cull Off
		ZWrite Off
		ZTest Off
        LOD 100

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
				float4 vertex : POSITION;
				float2 uv0 : TEXCOORD0;
				float2 uv1 : TEXCOORD1;

            struct v2f
                float2 uv0 : TEXCOORD0;
				float2 uv1 : TEXCOORD1;
				float2 atlasCoord : TEXCOORD2;
                float4 vertex : SV_POSITION;

            sampler2D _MainTex;
            float4 _MainTex_ST;

			float _VoxelSize;

			float _ColourScale;
			float4 _ColourOffset;

            v2f vert (appdata v)
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                o.uv0 = v.uv0;
				o.uv1 = v.uv1;
				o.atlasCoord = v.vertex.xy;

                return o;

            fixed4 frag (v2f i) : SV_Target
				fixed4 col = fixed4(0.0, 0.0, 0.0, 1.0);

				// Extract model's world space coord from two uv channels
				float3 worldCoord = float3(i.uv0.xy, i.uv1.x);	// World coords (in meters)

				// Snap from world space to containing voxel coord
				float3 voxelCoord = floor(floor(worldCoord / _VoxelSize));	// Voxel coords (integer values)

				// Convert voxel xyz into colour range
				col.xyz = frac((voxelCoord * _ColourScale) + _ColourOffset.xyz);

				return col;

One thing I'm not sure of the proper solution to is making sure all the atlas  texels actually get rendered to. If I just draw the unwrapped tris directly then they don't always touch all the texels they need to around the edges:


So I'm manually extending the edges of the charts with additional fins to make sure these are filled in:


This seems more accurate than just running a 2d dilate operation (since it should go through the deterministic grid method) but maybe I'm missing something. Has anyone any ideas where these artefacts might be coming from and how to make sure these 'twinned' texels are rendered out correctly? Even if I wanted to fudge it in a post-process, I'm drawing a blank as to how to actually figure out which texels would need to be processed this way. 


Share this post

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!