Skip to content

kids.kapish.textures

unity

Texture helpers for Unity. Readability conversion, GPU blit utilities, texture array slice extraction (CPU and GPU paths), and PNG export.

Architecture

The package provides static utility classes for common texture operations that Unity's API makes surprisingly awkward. No MonoBehaviours or ScriptableObjects -- everything is static extension methods and helpers.

TextureExtensions

Single extension method:

  • MakeReadable(this Texture2D) -- if the texture is already readable (isReadable == true), returns it as-is. Otherwise, blits to a temporary RenderTexture, reads back pixels into a new Texture2D, and returns the readable copy.

TextureBlitUtility

Shared infrastructure for GPU blit operations:

MethodPurpose
LoadShaderMaterial(shaderName)Finds a shader by name, wraps in a HideAndDontSave material. Logs error and returns null if not found.
CreateTempRT(width, height, linear)Gets a pooled temporary RenderTexture (ARGB32, point filtering). Linear or sRGB.
CreateTempRTHdr(width, height)Gets a pooled temporary HDR RenderTexture (DefaultHDR format, linear).
ReleaseTempRT(rt)Returns a temporary RenderTexture to Unity's pool.
Readback(rt, linear)Reads a RenderTexture into a new Texture2D (RGBA32). Saves and restores RenderTexture.active.
WritePng(tex, outputPath)Encodes to PNG, creates the output directory if needed, writes to disk.

Texture Slice Extraction

Two parallel APIs for extracting slices from Texture2DArray (or similar array textures):

CPU path (TextureSlicingCpu):

  • Returns Texture2D instances with CPU-readable pixel data.
  • Single slice: texture.ExtractSliceCpu(sliceIndex).
  • All slices: texture.ExtractSlicesCpu(sliceCount) -- yields lazily, reusing one RenderTexture and one material.
  • Falls through to MakeReadable() for plain Texture2D inputs.

GPU path (TextureSlicingGpu):

  • Returns RenderTexture instances (no CPU readback overhead).
  • Single slice: texture.ExtractSliceGpu(sliceIndex).
  • All slices: texture.ExtractSlicesGpu(sliceCount) -- yields RenderTexture results.
  • Output textures have enableRandomWrite = true for compute shader compatibility.

Both paths use the Hidden/Textures/CopySlice shader, which samples a Texture2DArray at a configurable _Slice index.

CopySlice Shader

A minimal utility shader:

  • Input: _MainTex as 2DArray, _Slice as float index.
  • Samples UNITY_SAMPLE_TEX2DARRAY(_MainTex, float3(uv, _Slice)).
  • Full-screen blit pass: ZTest Always, Cull Off, ZWrite Off.
  • No lighting, no render pipeline dependency (uses UnityCG.cginc).

Assembly

  • Runtime assembly: Carrot.Textures (references Carrot)
  • Editor assembly: Carrot.Textures.Editor (references Carrot)

Key Design Decisions

  • Extension method pattern. MakeReadable() and slice extraction are extensions on Texture2D/Texture for discoverability and fluent usage.
  • Lazy enumeration for slices. ExtractSlicesCpu uses yield return to reuse a single temporary RenderTexture and material across all slices, minimising memory pressure.
  • Separate CPU and GPU paths. CPU readback is expensive; the GPU path avoids it entirely when you only need the result in a shader or as a RenderTexture.
  • HDR render targets. Slice extraction uses DefaultHDR format to preserve precision for textures that may contain HDR data.
  • No runtime shader compilation. The CopySlice shader is pre-compiled as part of the package. Shader.Find is used at static init time.

Usage Guide

Texture helpers for Unity. Readability conversion, slice extraction from texture arrays, GPU blit utilities, and PNG export.

Setup

Add kids.kapish.textures to your Unity project's package manifest.

Common Patterns

1. Make a texture CPU-readable

csharp
using Carrot.Textures;
using UnityEngine;

Texture2D source = /* loaded from asset, render target, etc. */;
Texture2D readable = source.MakeReadable();

// Now safe to call GetPixels, GetPixel, etc.
Color[] pixels = readable.GetPixels();

2. Extract a single slice from a Texture2DArray (CPU)

csharp
using Carrot.Textures;
using UnityEngine;

Texture2DArray textureArray = /* ... */;
Texture2D slice = textureArray.ExtractSliceCpu(sliceIndex: 2);

// slice is a CPU-readable Texture2D
Color pixel = slice.GetPixel(0, 0);

3. Extract all slices from a Texture2DArray (CPU)

csharp
using Carrot.Textures;
using UnityEngine;

Texture2DArray textureArray = /* ... */;

foreach (Texture2D slice in textureArray.ExtractSlicesCpu(sliceCount: textureArray.depth))
{
    // Process each slice
    byte[] png = slice.EncodeToPNG();
}

4. Extract a slice as a RenderTexture (GPU-only)

csharp
using Carrot.Textures;
using UnityEngine;

Texture2DArray textureArray = /* ... */;
RenderTexture gpuSlice = textureArray.ExtractSliceGpu(sliceIndex: 0);

// Use directly in shaders or as a render target -- no CPU readback cost
someMaterial.SetTexture("_MainTex", gpuSlice);

5. Extract all slices as RenderTextures (GPU-only)

csharp
using Carrot.Textures;
using UnityEngine;

Texture2DArray textureArray = /* ... */;

foreach (RenderTexture rt in textureArray.ExtractSlicesGpu(sliceCount: textureArray.depth))
{
    // Each rt is a GPU-resident RenderTexture
}

6. Blit and readback a RenderTexture

csharp
using Carrot.Textures;
using UnityEngine;

RenderTexture rt = TextureBlitUtility.CreateTempRT(256, 256);
Graphics.Blit(sourceTexture, rt);

Texture2D result = TextureBlitUtility.Readback(rt);
TextureBlitUtility.ReleaseTempRT(rt);

7. Save a texture to PNG

csharp
using Carrot.Textures;
using UnityEngine;

Texture2D tex = /* ... */;
TextureBlitUtility.WritePng(tex, "Assets/Output/slice.png");

8. Load a shader as a temporary material

csharp
using Carrot.Textures;
using UnityEngine;

Material mat = TextureBlitUtility.LoadShaderMaterial("Hidden/MyCustomShader");

if (mat != null)
{
    Graphics.Blit(source, destination, mat);
    Object.DestroyImmediate(mat);
}

Tips

  • CPU path is lazy. ExtractSlicesCpu reuses a single RenderTexture and material across all slices via yield return. Process slices one at a time for minimal memory use.
  • GPU path avoids readback. Use ExtractSliceGpu when you only need the slice in a shader or as a render target. CPU readback is the expensive part.
  • Temporary RTs are pooled. CreateTempRT / CreateTempRTHdr use Unity's RenderTexture.GetTemporary pool. Always call ReleaseTempRT when done.
  • MakeReadable is a no-op for readable textures. If the texture is already readable, it returns the same instance with no copy.

Carrot