0
\$\begingroup\$

Scenario

For the sake of example I have four textured planes that overlap each other. Theoretically this produces one large image but the textures come from a third party at run time and often are misaligned due to the cropping procedure which I have no control over.

Question

While keeping the four planes separate is there a way to blend the textures into one another so that my 4x4 looks like a seamless image ?

Related Question

Extra

My app is a 2d x,y free scroller so I need to add & take away planes often as a user scrolls.

\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

Here's a simple approach you can try, just drawing each texture a little larger so it overlaps its neighbours, and feathering the edges so it blends smoothly:

Blending tiles

From left to right, I'm increasing the amount of overlap. At the far left, there's no overlap, and so hard seams are very noticeable. At the far right, I'm overlapping objects by about 60% of their width, so you get a very smooth transition, though it can start to muddy some of the details within each tile. In the middle I'm using about 16% overlap as a middle ground.

The shader that does this just renders each tile additively, with Z-writing turned off, so you don't get Z-fighting between tiles. It darkens the edges of each tile so that when they meet they add up to full brightness again. Set your camera's clear colour to black for this to look right.

Shader "Unlit/TileBlend"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        LOD 100

        Blend One One
        ZWrite Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _BlendFactor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            float2 smoothstep2(float2 t) {
                return t * t * (3 - 2 * t);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                float2 fromEdge = 1.0f - abs(i.uv - 0.5f) * 2.0f;
                float2 margin = saturate(fromEdge * _BlendFactor);
                float2 falloff = smoothstep2(margin);

                float opacity = falloff.x * falloff.y;

                return col * opacity;
            }
            ENDCG
        }
    }
}

Here's a script to set up a grid of textured quads with a desired amount of overlap, and adjust their scales and the _BlendFactor shader parameter to match:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TileBlender : MonoBehaviour
{
    // Cache material property ID for fast lookups.
    static readonly int BLEND_FACTOR_ID = Shader.PropertyToID("_BlendFactor");

    [Tooltip("Textures for each tile, ordered left-to-right, top-to-bottom")]
    public Texture2D[] tileTextures;

    [Tooltip("Size of the tile grid, x: columns, y: rows")]
    public Vector2Int layout = new Vector2Int(3, 3);

    [Tooltip("How much of the tile should blend with its neighbours?")]
    [Range(0, 1)]
    public float overlap;

    [Tooltip("Use TileBlend shader")]
    public Material blendMaterial;

    Renderer[] _tiles;

    static float ComputeBlendFactor(float overlap) {
        // Blend area in UV space is one tile overlap in each direction,
        // expressed as a ratio to the tile's full size.
        // Return the reciprocal so in the shader it's just a multiply, no divide.
        return (1.0f + overlap) / (2.0f * overlap);
    }

    // Adjust blending when overlap slider is changed in Inspector.
    void OnValidate() {
        if (_tiles == null) return;

        Shader.SetGlobalFloat(BLEND_FACTOR_ID, ComputeBlendFactor(overlap));
        foreach(var tile in _tiles) {
            tile.transform.localScale = Vector3.one * (1f + overlap);
        }
    }

    void Start() {
        _tiles = new Renderer[layout.x * layout.y];
        Shader.SetGlobalFloat(BLEND_FACTOR_ID, ComputeBlendFactor(overlap));
        int i = 0;
        for (int y = 0; y < layout.y; y++) {
            for (int x = 0; x < layout.x; x++) {
                _tiles[i] = MakeTile(x, y, tileTextures[i % tileTextures.Length]);
                i++;
            }
        }
    }

    Renderer MakeTile(int x, int y, Texture2D texture) {
        var quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
        
        var mat = Instantiate(blendMaterial);
        mat.mainTexture = texture;
        
        quad.TryGetComponent(out Renderer renderer);
        renderer.sharedMaterial = mat;

        quad.transform.SetParent(transform, false);
        quad.transform.localPosition = new Vector3(x, y, 0);
        quad.transform.localScale = Vector3.one * (1f + overlap);

        return renderer;
    }

    void OnDestroy() {
        foreach(var tile in _tiles) {
            Destroy(tile.sharedMaterial);
        }
    }

}

Here's what it looks like as we adjust the overlap slider up and down:

Animated gif of tiles zooming and feathering

\$\endgroup\$
1
  • \$\begingroup\$ I'm just seeing this, it's a pretty slick "basic" implementation so thanks for that. I have to integrate this in with my existing vert/frag shader work but I think it's going to work. Before accepting this as the answer I'm going to make sure with it scales nicely with my existing setup. Cheers~ \$\endgroup\$
    – Jacksonkr
    Mar 9, 2023 at 0:09
0
\$\begingroup\$

I don't have enough information to give you a direct specific solution but I would suggest that you can stitch the planes together and texture it all as one plane and you could also try to change the texturing options. Maybe this will help https://learnopengl.com/Getting-started/Textures

\$\endgroup\$
1
  • \$\begingroup\$ As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center. \$\endgroup\$
    – liggiorgio
    Feb 23, 2023 at 10:05
0
\$\begingroup\$

It sounds like what you want is what a photographer might call "image stitching", which is often used for creating panoramic images from multiple photographs. You might find that software that's designed for stitching together photos automatically works for your images.

Note that stitching the images together like that can take a while - there's a big search space to work out which bits of which images match each other, especially when the overlapping parts of the images may not be exactly identical. With photographs, those images could all have different brightness, rotation, lens distortion, etc.

\$\endgroup\$
3
  • \$\begingroup\$ Note "the textures come from a third party at run time" - that means the game creator can't manually take these images and filter them in an external piece of software as a pre-process. The running game needs to be able to do this blending on the fly. \$\endgroup\$
    – DMGregory
    Feb 23, 2023 at 1:24
  • \$\begingroup\$ You could technically invoke a tool like Hugin at runtime, and without knowing more about the images it's tricky to suggest something better - anything simple will probably need to make assumptions about the content of the images. \$\endgroup\$
    – Adam
    Feb 23, 2023 at 2:06
  • \$\begingroup\$ @Adam Thanks for the answer. I like the idea of what you're saying but I think that for the limitations of my app it may be too much overhead. My app is a 2d x,y free scroller and at the speed which users can swipe past tiles (planes) the mobile app will hardly have time to compute, compile, and re-crop images. Thoughts ? \$\endgroup\$
    – Jacksonkr
    Feb 23, 2023 at 16:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .