[SOLVED]Sparse Texture Clarification Please

Firstly hello, I just been playing around with the sparse texture demo and am trying to wrap my head around it. I am nearly there, but I just need to understand a few things if anyone can help it would be appreciated very much!

ok, I have loaded in a few textures of my own to the mega texture with these textures original size being 1024x1024.

now it seems that they are assigned in the mega texture as 32x32, and there is no way to alter this tile size.

So am I correct in assuming that to get the same quality I should break my original texture (1024x1024)into tiles of 32x32 and load them to the megatexture, individually to preserve the quality?

if so that is good at least I understand that if correct…

with that in mind, I assume that this mega texture is referenced in the shader, by scaling the uv coords to read the correct pixels of the SparseTexture?

And lastly how big can the mega texture be? 16384x16384 is that the largest?

Thanks very much for any clarity with this, I think I understand but not %100 yet.

Well it seems I have to create tiles of 256 x 256, so I was able to just fill with bytes extracted fro the original texture and assign to each tile, so that is all good.

I am just using the megatexture in a shader and scaling the meshes uv to fit the desired part of the megatexture so hope that is all I have to do there.

so that just leaves the largest size available, but I guess that is platform specific not unity specific, if going by the documentation. Well all things aside, sparse textures are actually really cool, and unity has made them easy to use, if I have done it all right :). Unity is amazing, keeps impressing me everyday now, just felt like vocalizing that:).

@winning11123 Hey sorry for bumping an old thread, do you have any example code for this? the documentation and provided example are confusing for me :s

1 Like

Hi Reanimate_L, Sorry for late reply but better late than never)) here is some really messy code))

stage one export the texture to binary…(you will change file path accordingly - and this setup is different for each texture type but ARGB32 seems most common). smallest is 128x128 if I remember correctly - just did a test with 1024x1024 and was fine.

//******************************************************
    //EXPORT AS ARGB32 FORMAT
    //******************************************************
    void ExportAsARGB32(Texture2D _tex2D)
    {
        //--------------------------------------------------
        //change format to ARGB32
        Texture2D tex_new = new Texture2D(_tex2D.width, _tex2D.height, TextureFormat.ARGB32, false);
        tex_new.hideFlags = HideFlags.HideAndDontSave;
        //tex_128.filterMode = FilterMode.Point;
        tex_new.Apply(false);

        Color[] _t_color_orig = _tex2D.GetPixels();

        tex_new.SetPixels(_t_color_orig);
        tex_new.Apply(false);
        //--------------------------------------------------
        int TileCount = 0;
        int TileCount_Total = 0;
        int w = tex_new.width;
        int h = tex_new.height;
        int DesiredPixelSize = 128;

        if (w >= DesiredPixelSize && h >= DesiredPixelSize && w == h)
        {
            //----------------------------------------------
            TileCount = w / DesiredPixelSize;
            TileCount_Total = TileCount * TileCount;

            byte[] _Full_bytes = new byte[(((128 * 128) * 4) * 4) * TileCount_Total];

            int tc = 0;
            for (int y = 0; y < TileCount; y++)
            {
                for (int x = 0; x < TileCount; x++)
                {
                    Texture2D tex_128 = new Texture2D(DesiredPixelSize, DesiredPixelSize, TextureFormat.ARGB32, false);
                    tex_128.hideFlags = HideFlags.HideAndDontSave;
                    //tex_128.filterMode = FilterMode.Point;
                    tex_128.Apply(false);

                    Graphics.CopyTexture(tex_new, 0, 0, x * DesiredPixelSize, y * DesiredPixelSize, DesiredPixelSize, DesiredPixelSize, tex_128, 0, 0, 0, 0);
                    //----------------------------------------------
                    byte[] _t_bytes = tex_128.GetRawTextureData();

                    System.Buffer.BlockCopy(_t_bytes, 0, _Full_bytes, tc * _t_bytes.Length, _t_bytes.Length);
                    tc++;
                    //destroy sub tile
                    DestroyImmediate(tex_128);
                    tex_128 = null;
                }
            }
            string FinalFilePath_Export = ExportFilePath;
            FinalFilePath_Export += SaveName;
            FinalFilePath_Export += "_MT2.bytes";

            System.IO.File.WriteAllBytes(FinalFilePath_Export, _Full_bytes);
            //----------------------------------------------
            UnityEngine.Debug.Log("Success - All Done!");
        }
        else
        {
            UnityEngine.Debug.Log("Could not export  - Texture should be power of 2 and greater or equal to 128 * 128 - All Done!");
        }
        //destroy new texture
        DestroyImmediate(tex_new);
        tex_new = null;
        //--------------------------------------------------
    }

Then a Mega texture Manager to create the mega texture and load int the tile (NOTE: Mega Textures have to allow some frames to go by before you can fill them - as I found when trying to load in Start in a build that nothing would show up - so load tiles in Update)

This really messy but didn’t have time to clean up…as each tile is uploaded, it will auto fill the whole mt and return starting uv coords for each tile as uploaded. did not implement what happens when reaching the end of the mt, should add code to deal with multiples but have not had time or gone over one yet). texture size for this setup is 1024x1024.

//**********************************************************
//copyright 2018
//Author: David Gallagher
//Publisher: Oddity Interactive
//http://oddityinteractive.com
//v.5.0.0.1
//**********************************************************
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using UnityEngine;
//**********************************************************
//
//**********************************************************
public class MT_Manager : MonoBehaviour
{
    //******************************************************
    //classes
    //******************************************************
    //declarations
    public string ImportFilePath = "C:/Users/User_Back_Up/Desktop/TriangleMeshToPointCloud/";
    public string ImportFileName = "";

    public int width = 16384;
    public int height = 16384;

    private int tileWidth_C;
    private int tileHeight_C;
    private int tileCountX_C;
    private int tileCountY_C;

    public int MT_Count = 1;
    public SparseTexture[] texture_C;//argb32

    //color
    private byte[] imageData_C;

    //tile loading
    private int TileBegin_X = 0;
    private int TileBegin_Y = 0;
    int Tile_StartIndex = 0;
    int Tile_CurrentIndex = 0;

    //full load to mega texture
    public bool bFullLoad = true;
    private bool bRefreshStartY_ForTiles = true;
    private int px = 0;
    private int py = 0;

    //debug
    public bool bDebug = true;
    public GameObject _Debug_GO;
    public Texture2D _Debug_Tex;
    //******************************************************
    //START
    //******************************************************
    void Start()
    {
        //--------------------------------------------------
        //init
        InitializeThings();
        //--------------------------------------------------
    }
    //******************************************************
    //UPDATE
    //******************************************************
    int x = 0;
    int y = 0;
    //int Count = 0;
    void Update()
    {
        //--------------------------------------------------
        if (bFullLoad)
        {
            string ReadPath_MV = ImportFilePath;
            ReadPath_MV += ImportFileName;
            ReadPath_MV += ".bytes";
            byte[] _bytes = File.ReadAllBytes(ReadPath_MV);
            print(ReadPath_MV);
            print(_bytes.Length);

           Vector3 v1 = iMT_Fill_Tile_256(0,_bytes);
//Starting UV coords for this Tile
                                MT_UV_Pos_X= (int)v1.x;
                                MT_UV_Pos_Y = (int)v1.y;

            bFullLoad = false;
        }
        //--------------------------------------------------
        //debug
        iDebugUpdate();
        //--------------------------------------------------
    }
    //******************************************************
    //initialize things
    //******************************************************
    void InitializeThings()
    {
        //--------------------------------------------------
        if (MT_Count < 0) { MT_Count = 1; }
        texture_C = new SparseTexture[MT_Count];

        for (int i = 0; i < MT_Count; i++)
        {

            DestroyImmediate(texture_C[i]);
            texture_C[i] = new SparseTexture(width, height, TextureFormat.ARGB32, 1);
            texture_C[i].hideFlags = HideFlags.HideAndDontSave;
            texture_C[i].wrapMode = TextureWrapMode.Clamp;

            if (texture_C[i] != null) { print("C Success"); }
        }

        tileWidth_C = texture_C[0].tileWidth;
        tileHeight_C = texture_C[0].tileHeight;
        tileCountX_C = (width + tileWidth_C - 1) / tileWidth_C;
        tileCountY_C = (height + tileHeight_C - 1) / tileHeight_C;

        if (bDebug)
        {

            print("color");
            print(tileWidth_C);
            print(tileHeight_C);
            print(tileCountX_C);
            print(tileCountY_C);
        }
        //--------------------------------------------------
    }
    //******************************************************
    //FULL LOAD
    //******************************************************
    public Vector3 iMT_Fill_Tile_256(int MT_Index,byte[] _bytes)//SparseTexture st, byte[] _bytes,int _TileBegin_X,int _TileBegin_Y)
    {
        //--------------------------------------------------
        //load in bytes - File.ReadAllBytes()
        imageData_C = _bytes;// _MTTL[0].GetTile_Color_Grouped_AnySize_C();
        //get the number of pixels
        int id_size = imageData_C.Length;//length of byte buffer
        int _id_size = (id_size / 4) / 4;//byte length / 4 singles / 4 bytes per single - i.e for a 256*256 texture -> 1048576 / 4 / 4 = 65536
        int DesiredTileSize = 128;//<-----128 for uploading to mega texture @ 16384*16384 - ARGB32,RFloat
                                 //<-----64 for uploading to mega texture @ 16384*16384 - RGBAFloat

        //how many 128*128 tiles make up the source data - or image
        int TileCountPerBuffer = 1;
        int TileCountPerBuffer_Sqrt = 1;
        if (_id_size > (DesiredTileSize * DesiredTileSize))
        {
            TileCountPerBuffer = ((_id_size) / (DesiredTileSize * DesiredTileSize));
        }
        //layout texture in power of 2 fashion
        TileCountPerBuffer_Sqrt = (int)Mathf.Sqrt((float)TileCountPerBuffer);
        //TileCountPerBuffer = TileCountPerBuffer * TileCountPerBuffer;
        //print("_id_size" + _id_size.ToString());
        //print("DesiredTileSize" + DesiredTileSize.ToString());
        //print("TileCountPerBuffer" + TileCountPerBuffer.ToString());
        //print("TileCountPerBuffer_Orig" + TileCountPerBuffer_Sqrt.ToString());
        //minimum size allowed for upload to mega texture
        int Totalpixels = (256 * 256);

        //byte array of 128*128 tile to hold our main input byte array - break it down to fit onto the megatexture tiles
        byte[][] TempData_C = new byte[TileCountPerBuffer][];//128 - 1024

        //work to break up to smaller 128 * 128 tiles
        Tile_StartIndex = 0;
        Tile_CurrentIndex = 0;

        for (int j = 0; j < TileCountPerBuffer; j++)//64 - 1024 texture
        {
            TempData_C[j] = new byte[Totalpixels];

            Tile_CurrentIndex = 0;

            for (int k = Tile_StartIndex; k < (Tile_StartIndex + Totalpixels); k++)
            {
                TempData_C[j][Tile_CurrentIndex] = imageData_C[k];

                Tile_CurrentIndex++;
            }
            Tile_StartIndex = Tile_StartIndex + Totalpixels;
        }
        //actually upload the tiles onto the mega texture
        FillMegaTextureFullLoad_AnySize(texture_C[MT_Index], TileBegin_X * 4, TileBegin_Y * 4, TempData_C, TileCountPerBuffer, TileCountPerBuffer_Sqrt);

        Vector3 v1 = new Vector3((TileBegin_X * 256), (TileBegin_Y * 256), 0.0f);

       //1024x1024 setup
TileBegin_X = TileBegin_X + 2;
        if (TileBegin_X >= 32)
        {
            TileBegin_X = 0;
            TileBegin_Y = TileBegin_Y + 2;
            if (TileBegin_Y >= 32)
            {
                TileBegin_Y = 0;
            }
        }
        return v1;
        return v1;
        //--------------------------------------------------
    }
    //******************************************************
    //FILL MEGA TEXTURE - NO STREAMING
    //******************************************************
    private void FillMegaTextureFullLoad_AnySize(SparseTexture st, int spx, int spy, byte[][] byteData, int _TileCountPerBuffer, int _TileCountPerBuffer_Sqrt)
    {
        //--------------------------------------------------
        int StartPosX = spx;
        px = spx;
        if (bRefreshStartY_ForTiles) { py = spy; bRefreshStartY_ForTiles = false; }
        //--------------------------------------------------
        for (int j = 0; j < _TileCountPerBuffer; j++)
        {
            st.UnloadTile(px, py, 0);
            st.UpdateTileRaw(px, py, 0, byteData[j]);

            px = px + 1;
            if (px > (StartPosX + ((_TileCountPerBuffer_Sqrt) - 1)))
            {
                px = StartPosX;
                py = py + 1;
            }
            if (j >= (_TileCountPerBuffer - 1))
            {
                bRefreshStartY_ForTiles = true;
                px = 0;
                py = 0;

                break;
            }
        }
        //--------------------------------------------------
    }
    //******************************************************
    //GET HANDLE TO MEGA TEXTURE
    //******************************************************
    public SparseTexture iMTGet(int Index)
    {
        //--------------------------------------------------
        SparseTexture SP = texture_C[Index];
        return SP;
        //--------------------------------------------------
    }
    //******************************************************
    //DEBUG
    //******************************************************
    void iDebugUpdate()
    {
        //--------------------------------------------------
        if (bDebug)
        {
            if (texture_C != null)
            {
                for (int i = 0; i < 1; i++)
                {
                    _Debug_GO.GetComponent<Renderer>().sharedMaterial.mainTexture = texture_C[i];
                }
            }
        }
        //--------------------------------------------------
    }
    //******************************************************
    //DESTROY
    //******************************************************
    void OnDestroy()
    {
        //--------------------------------------------------
        //--------------------------------------------------
        if (_Debug_GO != null) { _Debug_GO.GetComponent<Renderer>().sharedMaterial.mainTexture = _Debug_Tex; }
        for (int i = 0; i < MT_Count; i++)
        {
            DestroyImmediate(texture_C[i]);
        }
        //--------------------------------------------------
    }
}

And Finally a Shader to access the coords on the MT - Mega texture set like ordinary texture…

Shader "DX11/Test_Mega_Texture_UV"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #pragma target 5.0
       
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float Texel_X = 1.0f;
            float Texel_Y = 1.0f;
            float UV_X_Pos = 1.0f;
            float UV_Y_Pos = 1.0f;
       
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                float2 UV = v.uv.xy;
                UV = UV * float2(Texel_X, Texel_Y);
                UV = UV + float2(UV_X_Pos, UV_Y_Pos);

                o.uv = TRANSFORM_TEX(UV, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
       
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

Hope That Helps…Texel_X and Texel_Y I did not need in the compute shader version of this so cannot remember or find use in code, but I believe it was to scale the UVs and then use UV_X_Pos and UV_Y_Pos to set position. _Debug_GO was just a quad with 0-1 coords to view whole MT.

1 Like