Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Multiple Textures tiled on a single mesh.

Discussion in 'Shaders' started by Jahmelon, Apr 26, 2019.

  1. Jahmelon

    Jahmelon

    Joined:
    Mar 29, 2014
    Posts:
    2
    Hello, I'm trying to figure out if it is possible to use a shader to display multiple textures tiled in a grid fashion on a single mesh. I currently achieve this effect by building a small mesh and adding one material and texture to the mesh. I'm doing this 100's of times and it is wildly inefficient.

    Another requirement is that all textures originate as downloaded PNGs. I do not have the option of creating textures from PNGs already stored on the device.

    Thanks for any information you can provide
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Is it possible to have a single shader draw a grid of multiple textures? Yes, that's what essentially all terrain shaders do, just usually with extra code to blend between the textures and not just a straight grid, though they're certainly capable of that.

    Is that going to be more efficient than having multiple individual meshes each with a single texture material? Depends, but probably not, especially if you have 100s of individual textures. Really, shaders are limited to 16 unique textures, or more specifically 16 unique texture samplers, which are physical parts of the GPU hardware that read texture data and supply it to the shader. With some work you can reuse one or a handful of samplers to sample multiple textures, but this gets inefficient and slow quickly. Many terrain shaders are limited to 16 textures for similar reasons. Unity's built in terrain shader only really supports 4 texture layers, and supports more than that by rendering the entire terrain again for each additional group of 4 textures.

    Some terrain shaders do support up to 256 (or even more) textures using texture arrays. These are a special texture type that the shader thinks of as a single texture, but for which you supply both a UV and an index to to sample from. This is a much more efficient way of handling multiple textures in a single shader, but it does require all of the textures are exactly the same size and format. It's also not necessarily that much faster (if at all) than the original multiple mesh & material option.

    The last option that I can think of would be to use some kind of simple virtual texture system, or construct a texture atlas on the fly. Create one big texture and use Graphics.CopyTexture() to copy your individual textures into the atlas. This also requires all of the textures to be the same size and format, but it's one of the few options that might actually be faster than the method you're doing.


    Just stepping back a moment to the multiple meshes and materials. You could optimize it a bit by not creating new materials, but reuse a single quad mesh & basic material in multiple renderer components (or DrawMesh calls) and override the texture using a MaterialPropertyBlock.
     
    BikramKumar, mannyhams and Jahmelon like this.
  3. Jahmelon

    Jahmelon

    Joined:
    Mar 29, 2014
    Posts:
    2
    This is great info thanks for your time!
    I like the idea of creating a texture atlas on the fly. I will experiment with that amd look into terrain shaders.

    I'm creating a quad tile map where, 4 smaller tiles fit into one larger tile. The larger tile being a higher zoom level, much like google maps. As the user zooms in, I'm loading the new zoom levels into the larger tile space. As I mentioned before, I'm using new meshes to do this so I want to try and reuse the mesh. One of the reasons being, that elevation also comes into play. I'm generating a mesh from real world height information. so , its pretty in efficient to rebuild a new mesh with new heiht information for the new zoom level, when I can acutally try and use the larger tiles height info. So that is why I'm trying to minimize some of the work with mesh generation.

    One other idea I was thinking of but don't know how well it will perform, is to create a giant texture for the larger tile, then using setPixels(0,256,256,256) on the texture, as I load each subtile. The problem then seems to be when I want to unload the subtiles as the user pans away.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Yeah, no. Never use SetPixels. If you come to a point where you’re using SetPixels at runtime, you’ve done something horribly wrong 99% of the time. That’s why I mentioned CopyTexture.
    https://docs.unity3d.com/ScriptReference/Graphics.CopyTexture.html

    The use case you’re describing sounds like the exact situation that Virtual Texturing was designed to deal with.
    https://silverspaceship.com/src/svt/

    There were two virtual texturing assets for Unity, but they’re very expensive (hundreds of dollars) and neither appear to be available anymore. The closest I could find is this project which is a slightly different application of the technology (though a much more common use for today’s games, as it’s how the recent Battlefield and Battlefront games do their terrain).
    https://github.com/ACskyline/PVTUT

    However for your case you might be able to simplify the situation a bit and stick with using an atlas or a texture array. The idea would be to have a multiple meshes pre-UVed to various parts of the atlas or array. Load in individual PNGs, upload them to the GPU (using tex.Apply(true, true)), then use CopyTexture to add a new texture into the atlas or texture array layer, potentially replacing an old one, and turn back on the already existing mesh mapped to that UV area. Since you’re doing terrain, if you’re just concerned with visualization, have an atlas that is also the height map and have the mesh read the height map atlas rather than generating a new mesh every time. Just make sure you modify your mesh’s bounds to be large enough to cover the plausible height range. Really you could have a single mesh and use an instanced UV offset property and now you can draw everything in a single draw call!


    Using SetPixels on an atlas means the entire atlas texture has to be reuploaded to the GPU. CopyTexture is performed on the GPU, so no CPU side copy is needed.