2
\$\begingroup\$

I'm making a top-down style game in Unity 5 (2D). The floor is made up of many tiles (which are each game objects) of different types, eg sand and dirt.

I want to render a single texture over all of a single type of tile. So if I had a large area of sand, a single texture would be displayed for all of it. However, if there was a small area of dirt within that sand, that would need its own texture as well. These areas wouldn't necessarily be square either.

I've seen this used in games such as RimWorld, but I have no idea how to accomplish it. Any ideas?

Edit: Added picture of what I am attempting. This is a screenshot from RimWorld. As you can see, the sand is made up of hundreds of tiles, but has only one texture over it. enter image description here

This is all I have managed so far: enter image description here

\$\endgroup\$
  • 1
    \$\begingroup\$ I describe worldspace texturing in this answer — is something like this what you're looking for? \$\endgroup\$ – DMGregory Aug 11 '16 at 6:40
  • \$\begingroup\$ @DMGregory - Thanks, this is very helpful. I've almost got it working, but when I try to change the size it says its incompatible with the SpriteRenderer? This is my first time using shaders, so any help would be appreciated. \$\endgroup\$ – Infergnome Aug 11 '16 at 13:46
  • \$\begingroup\$ Update your question to show what you have so far, and I'll answer after work this evening (or someone else will in the meantime) \$\endgroup\$ – DMGregory Aug 11 '16 at 13:51
  • \$\begingroup\$ Now that I can see your use case more fully, a SpriteRenderer might not be the best way to handle this. SpriteRenderers are good for when you want lots of different 2D sprites (often with alpha) combined into one atlas. In this case you want almost the inverse - lots of objects sharing one tiling opaque texture. Have you considered using quads with a MeshRenderer instead of SpriteRenderer? We can apply the scaling manually in the shader if you need to use SpriteRenderers, but it should work out of the box with MeshRenderers. \$\endgroup\$ – DMGregory Aug 11 '16 at 17:13
  • \$\begingroup\$ Thank you, that worked perfectly! Now I need only work out how to mark this question as answered... \$\endgroup\$ – Infergnome Aug 11 '16 at 17:38
0
\$\begingroup\$

Apparently the recommended StackExchange approach when two different questions can be solved by the same answer is to reproduce the relevant parts of the solution, along with a link. So:


I proposed worldspace texturing to solve an earlier question about adjusting the tiling of a texture when scaling geometry. As a helpful side effect, this also makes adjacent object tile seamlessly together (assuming an appropriate texture), so it can be used in this case too.

Worldspace tiling (right) solves stretching & mismatch artifacts with conventional UV-mapping

In this example you can see conventional texture mapping on the left, using four standard quads at different scales with one material. On the right are the same four quads textured in worldspace using a single material. (The brick texture is a free asset from Kenney)

To do this, create a new shader something like the following:

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

        Pass
        {           
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag               
            #include "UnityCG.cginc"

            // Structure for input coming from the game to the vertex shader.
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            // Structure to pass data from vertex to fragment shader.
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            // Variables to refer to the texture & transform parameters assigned in the Inspector.
            sampler2D _MainTex;
            float4 _MainTex_ST;

            // Vertex shader transforms each vertex & preps data for interpolation.
            v2f vert (appdata v)
            {
                // Prepare a structure to hold the output.
                v2f o;

                // Transform the vertex into screen space.
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

                // Get the xy position of the vertex in worldspace.
                // We'll use these instead of the object's own texture coordinates.
                float2 worldXY = mul(_Object2World, v.vertex).xy;

                // Apply the texture offset & scale parameters set in the material Inspector.
                o.uv = TRANSFORM_TEX(worldXY, _MainTex);

                return o;
            }

            // Fragment shader renders each pixel.
            fixed4 frag (v2f i) : SV_Target
            {
                // Look up the right colour from the texture and output it.                   
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

(Here I've used an unlit opaque shader without fog as a base, but other effects can be incorporated in a straightforward way if you need.)

Assign this shader to a new material, drop in your texture (make sure its wrap mode is set to repeat), and apply that material to your tiles. You can use the regular texture tiling & offset fields to fine-tune how many repeats you want per world unit and the alignment of the tiling.


As you've noted in the comments above, using this on a SpriteRenderer prevents us from changing the scale & offset of the tiling using the material's texture transform parameters. Switching to quads with MeshRenderers solves this issue. If you want the atlasing & layering features of a SpriteRenderer we can make an alternate version of the shader, but it's likely to be more complicated than what you need.

For additional optimizations, we could even get rid of the individual tile objects entirely, and just draw one big quad for your level, using a texture to mark which tile texture should be used for each square of the grid.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.