I'm trying to use Texture2DArray in place of a texture atlas. The problem is the Standard shader doesn't support Texture2DArray and I'm terrible at shaders. I want something that mimics all the same functionality as the Standard shader, but uses Vector3 in place of Vector2 for the UVs so the Z of the UV can be used to pick the texture from the Texture2DArray. I'm using mesh.SetUVs(0, List<Vector3>(...)) to pass in the 3D UVs. I tried following https://medium.com/@calebfaith/how-to-use-texture-arrays-in-unity-a830ae04c98b, but my simple test mesh is showing up plain white, no texture.
Here's the shader that I have so far; Code (CSharp): Shader "Custom/Voxel Standard Array" { Properties { _Color("Color", Color) = (1,1,1,1) _MainTex("Albedo (RGB)", 2DArray) = "white" {} _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 _ZOffset("Z Buffer Offset", Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 Offset[_ZOffset],[_ZOffset] CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 // texture arrays are not available everywhere, // only compile shader on platforms where they are #pragma require 2darray UNITY_DECLARE_TEX2DARRAY(_MainTex); //sampler2D _MainTex; struct Input { float4 color: COLOR; float3 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. // #pragma instancing_options assumeuniformscaling UNITY_INSTANCING_BUFFER_START(Props) // put more per-instance properties here UNITY_INSTANCING_BUFFER_END(Props) void surf(Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb * IN.color; // Combine normal color with the vertex color // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } But I get the following errors in Unity; Shader error in 'Custom/Voxel Standard Array': cannot implicitly convert from 'const float2' to 'float3' at line 93 (on d3d11) Compiling Vertex program with UNITY_PASS_DEFERRED LIGHTPROBE_SH UNITY_HDR_ON Platform defines: UNITY_ENABLE_REFLECTION_BUFFERS UNITY_USE_DITHER_MASK_FOR_ALPHABLENDED_SHADOWS UNITY_PBS_USE_BRDF1 UNITY_SPECCUBE_BOX_PROJECTION UNITY_SPECCUBE_BLENDING UNITY_ENABLE_DETAIL_NORMALMAP SHADER_API_DESKTOP UNITY_COLORSPACE_GAMMA UNITY_LIGHT_PROBE_PROXY_VOLUME UNITY_LIGHTMAP_FULL_HDR And; Shader error in 'Custom/Voxel Standard Array': cannot implicitly convert from 'const float2' to 'float3' at line 152 (on d3d11) Compiling Vertex program with UNITY_PASS_FORWARDBASE DIRECTIONAL Platform defines: UNITY_ENABLE_REFLECTION_BUFFERS UNITY_USE_DITHER_MASK_FOR_ALPHABLENDED_SHADOWS UNITY_PBS_USE_BRDF1 UNITY_SPECCUBE_BOX_PROJECTION UNITY_SPECCUBE_BLENDING UNITY_ENABLE_DETAIL_NORMALMAP SHADER_API_DESKTOP UNITY_COLORSPACE_GAMMA UNITY_LIGHT_PROBE_PROXY_VOLUME UNITY_LIGHTMAP_FULL_HDR But this confuses me as my shader is only 62 lines long, yet it's stating errors on lines 93 and 152. If I change line 34 to float2 uv_MainTex; and line 50 to fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MainTex, float3(IN.uv_MainTex, 0)) * _Color; it fixes the errors and then I can run my shader, but everything is stuck to only using the first texture in the texture array. So what I'm looking for is how do you use Vector3 for UVs in this shader instead of Vector2? Or is there a better way to pass the third UV value for specifying the texture index in the texture array?
The 0 you added is the array index of the element you want to fetch from the texture array. That's why you always use the first element. In texture arrays your "sampling" UV has a third component, and that component is the index of the image you want to sample from the array. Code (CSharp): fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MainTex, float3(IN.uv_MainTex, ARRAY_INDEX)) * _Color;
I know that the third component of the UV is the array index and that by using 0 it's always the first element. With your code example I assume ARRAY_INDEX is a property you can assign on the material, but that would mean the same array index is used for the entire mesh. What I'm asking is how do I make it so a different array index can be assigned to every vertex of the mesh, or in other words how can you use UVWs (Vector3s) in the shader. I can assign them to the mesh using mesh.SetUVs(0, List<Vector3>(...)) but I can't get them to work in the shader. Or is there a different better way to do this?
The problem is Surface Shaders predate the support for texture arrays by half a decade, and were never updated to add support for 3 component UVs. That’s not to say you can’t access the full float3 or even float4 of a UV set, just that the uv_TexName variables in the Input struct are always only a float2. The solution is to use a custom vertex function to get the z component and pass it to the Input struct as a custom variable. Code (csharp): #pragma surface surf ... vertex:vert struct Input { float2 uv_MainTex; float arrayIndex; // cannot start with “uv” }; void vert(inout appdata_full v, out Input o) { o.arrayIndex = v.texcoord.z; } // in the surf function fixed4 col = UNITY_SAMPLE_TEX2DARRAY(_MainTex, float3(IN.uv_MainTex, IN.arrayIndex));
That's perfect. Thank you very much. For anyone it may help, here's the finished shader. Code (CSharp): Shader "Custom/Voxel Standard Array" { Properties { _Color("Color", Color) = (1,1,1,1) _MainTex("Albedo (RGB)", 2DArray) = "white" {} _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 _ZOffset("Z Buffer Offset", Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 Offset[_ZOffset],[_ZOffset] CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows vertex:vert // Use shader model 3.5 target, to get nicer looking lighting and texture array support #pragma target 3.5 // texture arrays are not available everywhere, // only compile shader on platforms where they are #pragma require 2darray UNITY_DECLARE_TEX2DARRAY(_MainTex); struct Input { float2 uv_MainTex; float arrayIndex; // cannot start with “uv” float4 color: COLOR; // TODO could remove this if not using VertexColor and Texture2DArray together }; half _Glossiness; half _Metallic; fixed4 _Color; // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. // #pragma instancing_options assumeuniformscaling UNITY_INSTANCING_BUFFER_START(Props) // put more per-instance properties here UNITY_INSTANCING_BUFFER_END(Props) void vert(inout appdata_full v, out Input o) { o.uv_MainTex = v.texcoord.xy; o.arrayIndex = v.texcoord.z; o.color = v.color; } void surf(Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MainTex, float3(IN.uv_MainTex, IN.arrayIndex)) * _Color; o.Albedo = c.rgb * IN.color; // Combine normal color with the vertex color // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
I just implemented this like 3 hours ago too- So were probably both on the same track here. I'm not sure if you eventually will stumble upon the same problem, but I'm just gonna link this thread here: https://forum.unity.com/threads/materialpropertyblock-changes-in-editor.715901/ Basically what my problem with arrays is, that i can't preview them in edit mode (along with other values i set via code, but this probably doesn't affect you as much).
Does anyone know how to sample the tex2darray from the vertex function? I keep getting compile errors trying to use the macro "UNITY_SAMPLE_TEX2DARRAY" Error: cannot map expression to vs_4_0 instruction
IIRC you can do this via using the LOD version of the macro -- you'll need to calculate/supply the LOD yourself.
Cool. thanks, that did get rid of the compile error. I'm a bit new at this, but does the LOD level equate to the mip map to use? i.e. If I have an array with 0 mip maps I would just always sample LOD 0?
Hello, sorry for the late response. I want to implement something similar that you achieved here. I pass vertex attributes into a custom shader that allows per parameter a Texture2DArray. But I'm unsure how to achieve it. This is the shader code: Code (CSharp): Shader "MultiTriplanar" { Properties { _Top("Top Main Texture", 2DArray) = "" { } _Color("Color", Color) = (1,1,1,1) _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 _ZOffset("Z Buffer Offset", Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 Offset[_ZOffset],[_ZOffset] CGPROGRAM #pragma surface surf Standard fullforwardshadows vertex:vert #pragma vertex vert #pragma require 2darray struct Input { float2 uv_MainTex; float arrayIndex; }; UNITY_DECLARE_TEX2DARRAY(_Top); half _Glossiness; half _Metallic; fixed4 _Color; void vert(inout appdata_full v, out Input o) { o.uv_MainTex = v.texcoord.xy; o.arrayIndex = v.texcoord.z; } void surf(Input IN, inout SurfaceOutputStandard o) { fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_Top, float3(IN.uv_MainTex, IN.arrayIndex)) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } This is how I pass the data into the mesh buffer data, I need help with this part: Code (csharp): public class PlaneVerticesTest : MonoBehaviour { [StructLayout(LayoutKind.Sequential)] private struct BiomeVertexLayout { public Vector3 texcoord; } public Texture2DArray texArray; private void Start() { var textureCount = texArray.depth; var layout = new[] { new VertexAttributeDescriptor(VertexAttribute.TexCoord0) }; mesh.SetVertexBufferParams(mesh.vertexCount, layout); var data = Enumerable.Range(0, mesh.vertexCount).Select(i => new Vector3(mesh.uv[i].x, mesh.uv[i].y, i % textureCount)).ToArray(); mesh.SetVertexBufferData(data, 0, 0, mesh.vertexCount); } } This is the project: https://drive.google.com/file/d/1wJ9QyvkSA_rBAXfN1rFowDpWY5EhqEDV/view?usp=sharing