多层混合纹理—texture2darray创建说明

简要

  官方文档:texture2darray的使用
https://docs.unity.cn/Manual/class-Texture2DArray.html

兼容性:

Texture arrays need to be supported by the underlying graphics API and the GPU. They are available on:

Direct3D 11/12 (Windows)
OpenGL Core (Mac OS X, Linux)
Metal (iOS, Mac OS X)
OpenGL ES 3.0 (Android, WebGL 2.0)

为什么要使用texture2darray?

  splatmap对比texture2darray
  SplatMap由RGBA四个通道组成,这些通道能够存储四种贴图的分布信息,每增加一个Layer,就会相应占用一个通道。
  每张SplatMap通常只能对应四种纹理(Slice)。若需要超出4层的纹理混合,则需要使用多张SplatMap时,增加了纹理绑定数。

  而且设备api会限制单个shader最大的纹理张数。
  例如unity文档提到OpenGL ES 3.0 (Android platform)限制单个shader最多只能有16张纹理。

OpenGL ES 3.0 (Android platform)
Emulates mobile OpenGL ES 3.0 feature set.
Turns off support for compute Shaders and related features (compute buffers, random-write Textures), sparse Textures, tessellation Shaders and geometry Shaders. Enforces maximum of 4 simultaneous render targets, and maximum of 16 Textures used in a single Shader. Maximum allowed Texture size is set to 4096, and maximum cubemap

 size to 2048. Realtime soft shadows are disabled.

  例如volkan官方文档提到限制单个shader最多只能有16张纹理。

maxPerStageDescriptorSampledImages is the maximum number of sampled images that can be accessible to a single shader stage in a pipeline layout. Descriptors with a type of VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, or VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER count against this limit. Only descriptors in descriptor set layouts created without the VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT bit set count against this limit. A descriptor is accessible to a pipeline shader stage when the stageFlags member of the VkDescriptorSetLayoutBinding structure has the bit for that shader stage set. See Combined Image Sampler, Sampled Image, and Uniform Texel Buffer.

截图

  Texture2DArray比Texture2D减少纹理绑定的开销。
  texture2darray会将多张纹理视作一张来处理,减少纹理绑定,但是内含slice纹理多了,texture2darray占用内存也会变大。
  可额外用一张SplatMap作为index map,
  例如每个像素点仅受到两张纹理下,index map的R通道存储主纹理的贴图下标,G通道存储副纹理的贴图下标,而颜色信息则通过index/255.0获得;B通道用于存储混合的权重值。

  Texture3D 只能对相邻的两个 slice 进行插值。

  参考:
《Graphics hardware capabilities and emulation》
https://docs.unity3d.com/2018.4/Documentation/Manual/GraphicsEmulation.html
《地形渲染技术与游戏引擎探索:突破与比较》
https://baijiahao.baidu.com/s?id=1840328471667244255&wfr=spider&for=pc
《Unity3D: 使用Texture2DArray渲染地形与自定义笔刷》
https://zhuanlan.zhihu.com/p/405843561
《Unity技术分享(100)| Texture2DArray、粒子系统的性能开销……》
https://baijiahao.baidu.com/s?id=1593107166928627425&wfr=spider&for=pc
《Vulkan® 1.4.325 - A Specification (with all registered extensions)》
https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#limits-minmax

如何制作texture2darray

1.单图就是一张图集;

截图

  设置TextureType为default。
  同时设置Texture Shape为2d array。
  如果你的图集是2行2列,column、row则分别填对应的数值。
截图

  inspector通过下方slider进行切换纹理。
截图

2.多张散图合并为texture2darray;

截图

  需要代码进行合并(代码见附录)
  用于制作Texture2dArray的贴图需要开启可读写。

ArgumentException: Texture2D.GetPixels: texture data is either not readable, corrupted or does not exist. (Texture 'ground_03_d')
UnityEngine.Texture2D.GetPixels (System.Int32 miplevel) (at <55fbbbd17b724c15b6abe8c1a3e3289c>:0)
MyProject.Texture2dArrayCreator.CompileArray (System.Collections.Generic.List`1[T] textures, System.String path, System.String filename) (at Assets/Editor/project/terrain_generation/Texture2dArrayCreator.cs:48)
MyProject.Texture2dArrayCreator.OnWizardCreate () (at Assets/Editor/project/terrain_generation/Texture2dArrayCreator.cs:27)
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <b11ba2a8fbf24f219f7cc98532a11304>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <b11ba2a8fbf24f219f7cc98532a11304>:0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <b11ba2a8fbf24f219f7cc98532a11304>:0)
UnityEditor.ScriptableWizard.OnGUI () (at <a675c3b64da6474b9fa5007cb5424433>:0)
UnityEditor.HostView.OldOnGUI () (at <a675c3b64da6474b9fa5007cb5424433>:0)
UnityEngine.UIElements.IMGUIContainer.DoOnGUI (UnityEngine.Event evt, UnityEngine.Matrix4x4 parentTransform, UnityEngine.Rect clippingRect, System.Boolean isComputingLayout, UnityEngine.Rect layoutSize, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, UnityEngine.Matrix4x4 worldTransform, UnityEngine.Rect clippingRect, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, System.Boolean canAffectFocus) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.SendEventToIMGUIRaw (UnityEngine.UIElements.EventBase evt, System.Boolean canAffectFocus, System.Boolean verifyBounds) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.SendEventToIMGUI (UnityEngine.UIElements.EventBase evt, System.Boolean canAffectFocus, System.Boolean verifyBounds) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.ProcessEvent (UnityEngine.UIElements.EventBase evt) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.CallbackEventHandler.HandleEvent (UnityEngine.UIElements.EventBase evt) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.CallbackEventHandler.HandleEventAtCurrentTargetAndPhase (UnityEngine.UIElements.EventBase evt) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.CallbackEventHandler.HandleEventAtTargetPhase (UnityEngine.UIElements.EventBase evt) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.MouseCaptureDispatchingStrategy.DispatchEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.ApplyDispatchingStrategies (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel, System.Boolean imguiEventIsInitiallyUsed) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEventQueue () (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.OpenGate () (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcherGate.Dispose () (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.Dispatch (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel, UnityEngine.UIElements.DispatchMode dispatchMode) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.BaseVisualElementPanel.SendEvent (UnityEngine.UIElements.EventBase e, UnityEngine.UIElements.DispatchMode dispatchMode) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.UIElementsUtility.DoDispatch (UnityEngine.UIElements.BaseVisualElementPanel panel) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.UIElementsUtility.UnityEngine.UIElements.IUIElementsUtility.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr, System.Boolean& eventHandled) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.UIEventRegistration.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.UIEventRegistration+<>c.<.cctor>b__1_2 (System.Int32 i, System.IntPtr ptr) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.GUIUtility.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr, System.Boolean& result) (at <18a6dfdbe6dc40fe86acd7f8c3c21410>:0)

  打进texture2darray需要统一采用支持像素操作的纹理格式。
  确保纹理格式为RGBA32、ARGB32、RGB24或Alpha8等支持像素操作的格式。
  压缩格式如DXT5通常不支持直接像素操作。

ArgumentException: Texture2DArray.SetPixels: texture uses an unsupported format. (Texture '')
MyProject.Texture2dArrayCreator.CompileArray (System.Collections.Generic.List`1[T] textures, System.String path, System.String filename) (at Assets/Editor/project/terrain_generation/Texture2dArrayCreator.cs:48)
MyProject.Texture2dArrayCreator.OnWizardCreate () (at Assets/Editor/project/terrain_generation/Texture2dArrayCreator.cs:27)
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <b11ba2a8fbf24f219f7cc98532a11304>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <b11ba2a8fbf24f219f7cc98532a11304>:0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <b11ba2a8fbf24f219f7cc98532a11304>:0)
UnityEditor.ScriptableWizard.OnGUI () (at <a675c3b64da6474b9fa5007cb5424433>:0)
UnityEditor.HostView.OldOnGUI () (at <a675c3b64da6474b9fa5007cb5424433>:0)
UnityEngine.UIElements.IMGUIContainer.DoOnGUI (UnityEngine.Event evt, UnityEngine.Matrix4x4 parentTransform, UnityEngine.Rect clippingRect, System.Boolean isComputingLayout, UnityEngine.Rect layoutSize, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, UnityEngine.Matrix4x4 worldTransform, UnityEngine.Rect clippingRect, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, System.Boolean canAffectFocus) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.SendEventToIMGUIRaw (UnityEngine.UIElements.EventBase evt, System.Boolean canAffectFocus, System.Boolean verifyBounds) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.SendEventToIMGUI (UnityEngine.UIElements.EventBase evt, System.Boolean canAffectFocus, System.Boolean verifyBounds) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.IMGUIContainer.ProcessEvent (UnityEngine.UIElements.EventBase evt) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.CallbackEventHandler.HandleEvent (UnityEngine.UIElements.EventBase evt) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.CallbackEventHandler.HandleEventAtCurrentTargetAndPhase (UnityEngine.UIElements.EventBase evt) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.CallbackEventHandler.HandleEventAtTargetPhase (UnityEngine.UIElements.EventBase evt) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.MouseCaptureDispatchingStrategy.DispatchEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.ApplyDispatchingStrategies (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel, System.Boolean imguiEventIsInitiallyUsed) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEventQueue () (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.OpenGate () (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcherGate.Dispose () (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.EventDispatcher.Dispatch (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel, UnityEngine.UIElements.DispatchMode dispatchMode) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.BaseVisualElementPanel.SendEvent (UnityEngine.UIElements.EventBase e, UnityEngine.UIElements.DispatchMode dispatchMode) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.UIElementsUtility.DoDispatch (UnityEngine.UIElements.BaseVisualElementPanel panel) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.UIElementsUtility.UnityEngine.UIElements.IUIElementsUtility.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr, System.Boolean& eventHandled) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.UIEventRegistration.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.UIElements.UIEventRegistration+<>c.<.cctor>b__1_2 (System.Int32 i, System.IntPtr ptr) (at <ea801f3ed0fd4af29867174222859987>:0)
UnityEngine.GUIUtility.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr, System.Boolean& result) (at <18a6dfdbe6dc40fe86acd7f8c3c21410>:0)

  采用RBGA32格式,内存占用太大
截图

  将图标改成PC为BC7,移动改为astc6x6block,使用下方代码会提示“ArgumentException: Texture2DArray.SetPixels: texture uses an unsupported format. (Texture '')”,导出texture2darray会失败。

for (int i = 0; i < textures.Count; i++)
{
    Texture2D tex = textures[i];
    textureArray.SetPixels(tex.GetPixels(0), i, 0);
}
textureArray.Apply();

  将代码改成如下,再尝试打包texture2darray,能正常导出。

for (int i = 0; i < textures.Count; i++)
{
    Texture2D tex = textures[i];
    Graphics.CopyTexture(tex, 0, 0, textureArray, i, 0);
}
textureArray.Apply();

截图

打进texture2darray的纹理大小需要统一大小

  将1024x1024图放到512x512的texture2darray会出现条纹。说明里面的纹理大小需要统一大小。
截图

PC用bc7压缩,手机采用ASTC,混用可能会导致花屏。

  由于贴图都是设置好纹理格式再打进texture2darray里。
  如果Texture2dArray没压缩的(采用RGBA32),占用内存大,需要优化为可压缩。
image

  要想不同平台采用不同的压缩格式,需要对应平台加载对应的texture2darray。否则可能会导致纹理花掉。
  下图是pc采用了移动端的astc的texture2darray导致的显示异常。
image

  所以推荐PC用bc7压缩,手机采用ASTC。

附录:

#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;

namespace MyProject
{
    /// <summary>
    /// 散图合并成Texture2dArray
    /// </summary>
    public class ScatteredTexture2Texture2dArrayCombiner : ScriptableWizard
    {
        public const string WINDOW_NAME = "ScatteredTexture 2 Texture2dArray Combiner";
        [MenuItem("Window/" + ScatteredTexture2Texture2dArrayCombiner.WINDOW_NAME)]
        public static void ShowWindow()
        {
            ScriptableWizard.DisplayWizard<ScatteredTexture2Texture2dArrayCombiner>(ScatteredTexture2Texture2dArrayCombiner.WINDOW_NAME, "Build Asset");
        }

        public string path = "Assets/";
        public string filename = "MyTextureArray";

        public List<Texture2D> textures = new List<Texture2D>();

        private ReorderableList _list;

        private void OnWizardCreate()
        {
            CompileArray(textures, path, filename);
        }

        private void CompileArray(List<Texture2D> textures, string path, string filename)
        {
            if (textures == null || textures.Count == 0)
            {
                Debug.LogError("No textures assigned");
                return;
            }

            Texture2D sample = textures[0];
            Texture2DArray textureArray = new Texture2DArray(sample.width, sample.height, textures.Count, sample.format, false)
            {
                filterMode = FilterMode.Trilinear,
                wrapMode = TextureWrapMode.Repeat
            };

            for (int i = 0; i < textures.Count; i++)
            {
                Texture2D tex = textures[i];
                // 将图标改成PC为BC7,移动改为astc6x6block,使用下方代码(SetPixels)会提示“ArgumentException: Texture2DArray.SetPixels: texture uses an unsupported format. (Texture '')”,导出texture2darray会失败。
                // textureArray.SetPixels(tex.GetPixels(0), i, 0);
                Graphics.CopyTexture(tex, 0, 0, textureArray, i, 0);
            }
            textureArray.Apply();

            path = PathUtil.NormalizeDir(path);
            string uri = path + filename + ".asset";
            AssetDatabase.CreateAsset(textureArray, uri);
            Debug.Log("Saved asset to " + uri);
        }
    }
}
#endif
posted @ 2025-08-21 20:53  昂流  阅读(114)  评论(0)    收藏  举报
//替换成自己路径的js文件 hhttp(s)://static.tctip.com/tctip-1.0.4.min.js