A shader can use up to 16 samplers per pass. Unity's surface shaders or lighting functions will use up some of those. However you can sample a single texture as many times as you want as it's reusing the same sampler. This is one of the reasons using a texture atlas is useful. Texture arrays also use a single sampler, so you can sample any layers from the array as many times as you want. Each sampler is actually a physical bit of the GPU designed to read and decode image data. The basic OpenGL and D3D9 shader code using sample2D and tex2D() is defining and using the sampler state (clamping, filtering, etc.) and the texture data at the same time, so they're linked together and each texture uses up a sampler unit. Using D3D11 style texture2D, samplerstate, and tex.Load(sampler, uv) breaks up the two and let's you reuse one sampler on multiple textures, or multiple samplers on a single texture.
Thanks for the info so I guess I have a maximum of 16 textures per pass. About texture arrays you mean that sampling any layer costs 1 sampler right?
Sampling 1 layer costs 1 sampler. Sampling 100 layers 100 times still costs 1 sampler. Not each, total. Think about it this way. You can sample (call tex2D()) on any one texture as many times as you want and it'll only cost one sampler total be it 1 or 100 times. A Texture2DArray is considered "one texture" in this context, and can be sampled as many times as you want at the cost of only one sampler. Sampler limits are more about the number of texture samplers defined in the shader outside the vertex or fragment functions, not how many times the texture is sampled. In other words how many "sampler2D" or "UNITY_DECLARE_TEX2DARRAY" lines you have in your shader file or in the includes that are also used by the shader. Those that are defined, but never sampled, are removed at compile time.
This is great news so I guess there is no need for a texture atlas. I mean suppose I use 6 textures for a mesh, I can store the layer index in the uv0.z and then sample using only 1 sampler ?? Is this the usual workflow?
Yep. Nope, an atlas is the usual workflow, or just using multiple materials. The most common place texture arrays are used are for terrain, or other situations where many textures need to be blended between (and thus sampled at once). Generally the additional friction of dealing with texture arrays isn't worth the effort. Some games do go this route though, often moving everything into texture arrays to reduce draw calls, or more specifically reduce render state changes. It's just usually not necessary unless you're really pushing how much stuff you want to put on screen.
Well I'm trying to render 1600 units with attachments(weapons/shields/helmets) so to reduce draw calls I combined all the meshes and use 1 material. Because I'm CPU bound. So texture arrays come in pretty handy, the only issue I've encountered though was size for some reason array layers are double size the original texture where is this attributed?
Hey, would the same principle apply to the Shader Graph shaders? In other words, does this picture below mean I'm using a single Sampler regardless of how many Sample Texture 2D nodes are present there provided that I feed to them the same atlas texture? Spoiler Additional question. Supposing that I'm storing my normal maps alongside albedo maps in the same atlas and, as such, I have to change the Type of the Sample Texture 2D node from Default to Normal, would it still be considered as using the same Sampler, or would it break it up in two different ones? Spoiler Thanks in advance! P.s. Also, just to clarify, am I right to assume that if the above mentioned is true then adding different Sampler States nodes (i.e. one to the Normal Texture and a different one to the Albedo one) will not split the whole thing in two Samplers as long as Sample Texture 2D nodes use the same atlas texture?
As long as you're using a single texture asset and not overriding the Sampler(SS) with a unique Sampler State node per Sample Texture node, then yes, it'll reuse the texture sampler for that one texture for all of them. If you plug in a single Sampler State node to all Sample Texture nodes you can even reuse a single sampler with multiple textures, though this doesn't work on OpenGLES platforms (Android).
Hello @bgolus , here in the documentation the max limit of texture is 8 for OpenGL ES 2. Is this wrong?
If you are targeting support for OpenGLES 2.0 devices, you will be limited to 8 textures per shader stage. And ideally you want to use less because sampling 8 textures on a GLES 2.0 device will be quite expensive. Some very late era GLES 2.0 class devices did support more than 8 textures, but they’re extremely uncommon, and you would not be able to make use of those extra textures using Unity. If you need more textures, you’ll need to use multiple passes, or target GLES 3.0 as your min spec.
Hello @bgolus , I'm doing the same thing for my shader, which has too many textures. Is there any workaround for Android? I can not use an atlas, as my textures are dynamic and updating via APIs. I'm building for Quest 2 with OpenGLES 3 , the shader works great on editor, but on Quest 2 it's messed up. I am getting this error: Also, how can I make my shadergraph use multiple passes? I'm using the shadergraph with URP on Unity 2021.3.25f1
If they're dynamic, then you can pack them dynamically. Otherwise the solution is... don't do that. Use multiple materials. Neither URP* or Shader Graph supports multi pass materials. You'd have to use multiple separate shader graph materials and apply all of them to the mesh. As long as the mesh only has one material index, it will render the mesh with each material one after the other. Though, really, the solution is probably to find some way to not have so many textures in one material and split it up into multiple separate materials and meshes that don't overlap. (* Technically URP can do multi pass materials, but it's very limited, and you have to hand write the shaders. Even then, it's only a single lit and single unlit pass per material.)
@bgolus Thank you for the quick response. It's a complex shader that I'm using, it has two layers for the base and coat. And the coating layer will be visible according to the user's raycast. So I don't know if I can pack the textures dynamically for this one. But yes, I managed to reduce 4 textures from the shader and now it's working on the Android build.