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

Array Textures in Unity

Discussion in 'Shaders' started by Plutoman, Apr 22, 2015.

  1. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    Previously I'd inquired about using 3d textures, and I had that working, however, mipmaps and filtering are completely broken for my use case.

    To explain the problem - I need more than 16 textures in a shader. In 90% of the cases I will be using no more than 6 to 12, but in about 10% of edge cases, my terrain will be blending many more textures, 24, 30, for certain meshes.

    I've been trying to use 3d textures to store sets of textures (diffuse, normal, height, etc) in a 3d texture, and then read in the shader. This is fine, except it requires point filtering and I cannot use mipmaps.

    Is Unity going to have any support for Array Textures?

    Alternatively, is there any way to adequately use cubemaps as texture arrays, and fetch from one singular face of a cubemap for each call? I explored converting UV's to a directional coordinate, but I've either done it wrong or it is not possible (taking the positive X facing as say, (1, x, y), x,y being the UV coordinates).

    Essentially, I want to use more than 16 textures, but texture atlases have mipmap, filtering, and wrapping issues, 3d textures have mipmap and filtering problems, and cubemaps take a direction, not a UV coordinate, and I cannot seem to get a pixel value from them that is actually correct.

    The use-case is a voxel-based terrain that uses vertex/uv's to blend textures, blending multiple texture sets such as grass flooring, rock walls, dirt overlays onto stone, alpha blending vines onto walls with tessellation, and more. Most meshes will use only a few sets, but some cases will involve blending between themed areas that could collide.

    My last solution would be to create texture atlases with copied pixels from the opposite side of the texture, so that wrapping is seamless even on mips 1, 2, and 3, and past that any minor bleeding on the padding would not be noticeable. And handling wrapping in the shader between the valid sections (not including the copied pixels), so filtering is all correct. I'd like something cleaner than this, however. Is there any ideas?
     
  2. petersvp

    petersvp

    Joined:
    Dec 20, 2013
    Posts:
    63
    Bump & Upvote:

    The use-case is a voxel-based terrain that uses vertex/uv's to blend textures, blending multiple texture sets such as grass flooring, rock walls, dirt overlays onto stone, alpha blending vines onto walls with tessellation, and more. Most meshes will use only a few sets, but some cases will involve blending between themed areas that could collide.

    We really need Texture Arrays! This will make it possible to not just blend, but also height-tesselate!

    (I only care about PC & consoles!)
     
  3. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    I've created a solution that generates the atlas textures on the fly with the GPU, and built the shader for it, but I would really prefer to use texture arrays.

    However, I do have a shader that can now represent 14 textures at once with various options. Texture arrays would dramatically increase the flexibility though, because I have to downsample and add bordering to prevent issues with mipmaps, and bilinear/trilinear filtering!
     
    olavrv likes this.
  4. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    From http://docs.unity3d.com/Manual/SL-BuiltinMacros.html where they also explain how to avoid this limit!
     
  5. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    Interesting! Thanks! That was not there pre-Unity 5, I don't believe (I could be wrong, but I've been working on this issue for quite a while and I don't recall it in any shape or form).

    That might solve my issues right on it's own, in every way. Still, array textures would be useful in terms of cleanliness.

    I'm not entirely sure whether to be annoyed or happy, because I've collectively spent several weeks dealing with sampler limits. I've learned a lot, but that's several weeks I could've used elsewhere. Ah well.
     
    Last edited: Apr 26, 2015
  6. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Yeah, I think Unity 5.0 exposed a LOT more to the documentation in regards to stuff like this, and thanks to the Standard shader they have shown us, at long last, how to set stencil values and blend modes per material and / or renderer!
    But as you say, it's hard to know if this functionality was exposed pre-5.0 or not.
     
  7. antislash

    antislash

    Joined:
    Apr 23, 2015
    Posts:
    646
    Hi plutoman (and zicandar too of course)
    i'm interested in the way you coded your texture atlas and rendered it into a texture.
    did you create a custom editor window ?
    i also want to create 2 textures diffuse/alpha and normals/heightmap from 2 texture atlases...
    Thanks in advance
    Victor
     
    Last edited: Apr 27, 2015
  8. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    The point of this discussion was how to NOT have to atlas textures!
    I think that unity has built in ways of creating atlases, but generally this is avoided or done by hand. (Our artists do it in photoshop and/or when they create the UV-layouts to fit a compact atlas'd texture for much reused assets such as the ground.)
     
  9. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    I wrote a quick script to set them into an atlas. Before I did this, I did a downsampling by 16 pixels, and built the opposite edges into the texture. I wrote a compute shader for that, too, but it was not perfect, as it had a few edge borders (I don't think the bilinear filtering properly handled the wrap mode of repeat).

    This is a compute shader.

    Code (csharp):
    1.  
    2. #pragma kernel CSMain
    3.  
    4. RWTexture2D<float4> Result;
    5. Texture2D<float4> Texture;
    6. uint2 Offsets;
    7.  
    8. [numthreads(8,8,1)]
    9. void CSMain (uint3 id : SV_DispatchThreadID)
    10. {
    11.     uint2 tex = uint2(id.x, id.y);
    12.     uint2 res = uint2(id.x + Offsets.x, id.y + Offsets.y);
    13.  
    14.     Result[res] = Texture[tex];
    15. }
    16.  
    And called it with this at runtime, before assigning the texture to a material (most of the time to do this was simply memory allocation, and don't forget to release any unused render textures);

    Code (csharp):
    1.  
    2. public static RenderTexture CreateAtlasTexture(ComputeShader cs, params Texture[] textures)
    3.     {
    4.         int width = textures[0].width; //assume all textures of equal sizing
    5.         int height = textures[0].height;
    6.  
    7.         RenderTexture rt = new RenderTexture(width * 2, height * 2, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
    8.         rt.useMipMap = false;
    9.         rt.enableRandomWrite = true;
    10.         rt.filterMode = FilterMode.Bilinear;
    11.         rt.wrapMode = TextureWrapMode.Repeat;
    12.         rt.Create();
    13.  
    14.         if (!SystemInfo.supportsComputeShaders)
    15.             return rt;
    16.  
    17.         cs.SetTexture(0, "Texture", textures[0]);
    18.         cs.SetTexture(0, "Result", rt);
    19.         cs.SetInts("Offsets", 0, 0);
    20.         cs.Dispatch(0, width, height, 1);
    21.  
    22.         cs.SetTexture(0, "Texture", textures[1]);
    23.         cs.SetTexture(0, "Result", rt);
    24.         cs.SetInts("Offsets", width, 0);
    25.         cs.Dispatch(0, width, height, 1);
    26.  
    27.         cs.SetTexture(0, "Texture", textures[2]);
    28.         cs.SetTexture(0, "Result", rt);
    29.         cs.SetInts("Offsets", 0, height);
    30.         cs.Dispatch(0, width, height, 1);
    31.  
    32.         return rt;
    33.     }
    34.  
     
  10. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    I did quite a lot of research into this, since it actually impacts quite a lot - I wanted to blend textures in for dynamic effects that follow terrain, rather than flat particle effects.

    If anyone is interested in how my code is set up now, I simply assign textures in to the material, normally, but my shader code works like this;

    Properties
    Code (csharp):
    1.  
    2. Properties {
    3.         // etc
    4.         _Diffuse0 ("Diffuse0", 2D) = "white" {}
    5.         _Diffuse1 ("Diffuse1", 2D) = "white" {}
    6.         _Diffuse2 ("Diffuse2", 2D) = "white" {}
    7.         // .... etc w/ more Diffuse
    8.         _Normal0 ("Normal0", 2D) = "white" {}
    9.         // .. etc
    10.         _Height0 ("Height0", 2D) = "white" {}
    11.         // .. etc
    12.     }
    13.  
    Inside the program block;
    Code (csharp):
    1.  
    2. Texture2D<float4> _Diffuse0; uniform float4 _Diffuse0_ST;
    3. Texture2D<float4> _Diffuse1; uniform float4 _Diffuse1_ST;
    4. Texture2D<float4> _Diffuse2; uniform float4 _Diffuse2_ST;
    5. // .. etc
    6. Texture2D<float4> _Normal0; uniform float4 _Normal0_ST;
    7. // .. etc
    8. Texture2D<float4> _Height0; uniform float4 _Height0_ST;
    9. // .. etc
    10. SamplerState _LinearRepeat;
    11. //Also valid, for the various combinations
    12. //SamplerState _LinearClamp;
    13. //SamplerState _PointRepeat;
    14. //SamplerState _PointClamp;
    15.  
    And to actually get values;
    Code (csharp):
    1.  
    2. if (vertexColor.r > .01)
    3. {
    4.     tex += _Diffuse0.Sample(_LinearRepeat, TRANSFORM_TEX(UV, _Diffuse0)) * vertexColor.r;
    5. }
    6. if (vertexColor.g > .01)
    7. {
    8.     tex += _Diffuse1.Sample(_LinearRepeat, TRANSFORM_TEX(UV, _Diffuse1)) * vertexColor.g;
    9. }
    10. // .. etc
    11.  
    Since I'm using this for blending, triplanar as it stands, I'm adding based on vertex colors/uv values. But there's no need to complicate with their macros, and in fact, using their macros prevents you from using SampleLevel for specific mips (maybe most don't need that, but it's used in the domain portion of the shader for tessellation).

    If you want the specific definitions of Unity's macros, they are in HLSLSupprt.cginc, in the Editor/Data/CGIncludes folder of the unity installation. But essentially, the declare simply declares the Texture2D as separate. I prefer handling it myself if I know what is going on, but really, I had no idea this code was valid and could compile on Unity's platform.

    This gives me an almost infinite set, limited primarily by rendering power. Tex2D calls are expensive. 12 channels directly, between vertex colors and 4 uv's, and more if I want to compress channels, or to alter textures based on normals.
     
    dudester likes this.
  11. antislash

    antislash

    Joined:
    Apr 23, 2015
    Posts:
    646
    thanks for the hints
     
  12. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    My suggestion for blending stuff in on say: Terrain, use deferred and deferred decals. The unity example is kinda a start, but really not complete. For our project I use a modified G-buffer layout, but I'm considering making a Deferred decal system that doesn't need that change in exchange for 2 passes.
    It should really allow you limitless amounts of variation "painted" onto the terrain/whatever :D
     
  13. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    I'm not entirely sure what you mean by that, but I'll take a look at it. Here's an example of my shader.

    https://www.dropbox.com/s/ruklhq5wk968fp9/Screenshot 2015-04-26 18.08.12.png?dl=0

    We generate themed sections, and it'll blend between sections, between walls, and we blend lake beds into the terrain, dirt sections in, etc etc. I also use it on walls to blend in vines. The whole section of rocks is actually tessellation, that rock facing is maybe 20 tris (another 10 or so at the very base where it angles in). The vines pop out as I use the alpha channel of diffuse textures to blend in textures on a conditional basis, and Bitmap2Material to generate varied textures to be applied and blended in.

    I haven't decided how to apply burnt sections though from a fireball explosion, or other alternatives, so I'll take a look at what you're mentioning.

    Oh, that's nifty. I see they are also Unity 5. I had made all my plans originally involving Unity 4. I'm not sure whether to be sad or happy at the new features.. mainly wish I had known earlier! I'll take a look and see if it can really apply the blending I'd like, but it'd be much quicker for a burn patch from an explosion to be applied that way than iterating through vertexes. My work isn't wasted, though, as the blending of vines, dirt, bricks, etc is still valid.
     
  14. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Perhaps enough could be moved out to deferred decals that your base terrain layers (visible on screen) never exceed 16?
    Remember that you could always swap what 16 textures are active at any one time.
    Btw, are you on unity 5.x or 4.x ?

    Or did it already work on OpenGL as it is?
     
    Last edited: Apr 29, 2015
  15. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    Oh, everything works fine at this point, so there's no concerns there anymore. The sampler issue was the primary fix required.

    But I will take into some consideration of using decals for more interesting features like explosions and burn effects, acid, etc. It's easier to wrap my mind around the terrain blending, but I'm also more familiar, so it's a bias. The concept of scrolling UV's on specific textures to let blended sections scroll is also easier for me to understand.

    I'm not altogether too concerned with a mac release. I'm mainly concerned about Windows, Xbox One, and Playstation 4. Compiling to mac is not my biggest concern.. it's never been a good platform for gaming, imo.
     
  16. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    Hi Plutoman, I'm in the same case in my terrain system now.
    I'm trying my best to get texture2DArray to work at least on Dx11 device.
    What I got now is using Dx11 native code to create a Texture2DArray SRV
    Code (C++):
    1. extern "C" EXPORT_API void* ApplyTextureArray()
    2. {
    3.     if (!g_D3D11Device)
    4.     {
    5.         OutputDebugStringA("D3D11Device not set!\n");
    6.         return NULL;
    7.     }
    8.     if (g_ArrayCount<=0)
    9.     SAFE_RELEASE(g_pTextureArray);
    10.     D3D11_TEXTURE2D_DESC descTexture;
    11.     g_ppTextures[0]->GetDesc(&descTexture);
    12.     descTexture.ArraySize = g_ArrayCount;
    13.     descTexture.Usage = D3D11_USAGE_DEFAULT;
    14.     descTexture.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    15.     g_D3D11Device->CreateTexture2D(&descTexture, NULL, &g_pTextureArray);
    16.     ID3D11DeviceContext *ctx;
    17.     g_D3D11Device->GetImmediateContext(&ctx);
    18.     for (int _arrayIndex = 0; _arrayIndex < g_ArrayCount; _arrayIndex++)
    19.     {
    20.         for (int _mipIndex = 0; _mipIndex < descTexture.MipLevels; _mipIndex++)
    21.         {
    22.             ctx->CopySubresourceRegion(g_pTextureArray, D3D11CalcSubresource(_mipIndex, _arrayIndex, descTexture.MipLevels), 0, 0, 0,
    23.                 g_ppTextures[_arrayIndex], D3D11CalcSubresource(_mipIndex, 0, descTexture.MipLevels), NULL);
    24.         }
    25.     }
    26.     g_D3D11Device->CreateShaderResourceView(g_pTextureArray, NULL, &g_SRV);
    27.     return g_SRV;
    28. }
    and apply this srv to unity Texture2D object by:
    Code (CSharp):
    1.             textureArray = Texture2D.CreateExternalTexture(2048,2048,TextureFormat.DXT1,true,false,ApplyTextureArray());
    2.             GetComponent<Renderer>().material.mainTexture = textureArray;
    But It is still a dead end as unity shader compiler won't go with :
    Code (CSharp):
    1.  
    2. Texture2DArray _MainTex;
    3. SamplerState sampler_MainTex;
    If i change it to Texture2D the texture can be sampled in shader but only the first layer.
    This syntax is good as I tried to render with this Texture2DArray in native code with shader like:
    Code (C++):
    1. static const char* kD3D11ShaderText =
    2.     "Texture2DArray mTexture: register(t100);"
    3.     "SamplerState mTextureSampler:register(s15);\n"
    4.     "cbuffer MyCB : register(b0) {\n"
    5.     "    float4x4 worldMatrix;\n"
    6.     "    float4 arrayIdx;\n"
    7.     "}\n"
    8.     "void VS (float3 pos : POSITION, float4 color : COLOR, out float4 ocolor : COLOR, out float4 opos : SV_Position) {\n"
    9.     "    opos = mul (worldMatrix, float4(pos,1));\n"
    10.     "    ocolor = float4(pos.xy,arrayIdx.x,1);\n"
    11.     "}\n"
    12.     "float4 PS (float4 color : COLOR,float4 pos : SV_Position) : SV_TARGET {\n"
    13.     "    return mTexture.Sample(mTextureSampler,float3(color.xyz));\n"
    14.     "}\n";
    And all layer can be sampled correctly.

    It was so close! SamplerState to Texture2D/Texture3DArray are the same. It is only the compiler that is stopingit to happen.
     
  17. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
  18. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    I wonder if there's a way to access/use/reference a pre-compiled shader, compiled under DX?

    I'm not sure if that's possible with Unity, or at least, in any simple form, but since the rendering pipeline is calling shaders that run on the GPU, it makes sense that it would be possible.
     
  19. petersvp

    petersvp

    Joined:
    Dec 20, 2013
    Posts:
    63
    Guys, Did you checked the 5.4 changelog?

    Looks like we, the Minecraft cloners, finally can have vistas done in a single draw call! Hardware Texture Arrays are coming on March, 16!
     
  20. ASteer

    ASteer

    Joined:
    May 13, 2016
    Posts:
    14
    @petersvp The 5.4 has been out there since then. Did you have a chance to play with it? I was wondering whether the texture arrays are working faster or better than the texture atlases solutions.
     
  21. AdamGoodrich

    AdamGoodrich

    Joined:
    Feb 12, 2013
    Posts:
    3,780
    Has anyone managed to get mipmaps working with compressed texture arrays ?
     
  22. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    I have..

    ;)
     
  23. AdamGoodrich

    AdamGoodrich

    Joined:
    Feb 12, 2013
    Posts:
    3,780
    Actually so did I as of last night. The unity doco is terrible.