Appearance
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 temporaryRenderTexture, reads back pixels into a newTexture2D, and returns the readable copy.
TextureBlitUtility
Shared infrastructure for GPU blit operations:
| Method | Purpose |
|---|---|
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
Texture2Dinstances with CPU-readable pixel data. - Single slice:
texture.ExtractSliceCpu(sliceIndex). - All slices:
texture.ExtractSlicesCpu(sliceCount)-- yields lazily, reusing oneRenderTextureand one material. - Falls through to
MakeReadable()for plainTexture2Dinputs.
GPU path (TextureSlicingGpu):
- Returns
RenderTextureinstances (no CPU readback overhead). - Single slice:
texture.ExtractSliceGpu(sliceIndex). - All slices:
texture.ExtractSlicesGpu(sliceCount)-- yieldsRenderTextureresults. - Output textures have
enableRandomWrite = truefor 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:
_MainTexas2DArray,_Sliceas 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(referencesCarrot) - Editor assembly:
Carrot.Textures.Editor(referencesCarrot)
Key Design Decisions
- Extension method pattern.
MakeReadable()and slice extraction are extensions onTexture2D/Texturefor discoverability and fluent usage. - Lazy enumeration for slices.
ExtractSlicesCpuusesyield returnto reuse a single temporaryRenderTextureand 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
DefaultHDRformat to preserve precision for textures that may contain HDR data. - No runtime shader compilation. The
CopySliceshader is pre-compiled as part of the package.Shader.Findis 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.
ExtractSlicesCpureuses a singleRenderTextureand material across all slices viayield return. Process slices one at a time for minimal memory use. - GPU path avoids readback. Use
ExtractSliceGpuwhen you only need the slice in a shader or as a render target. CPU readback is the expensive part. - Temporary RTs are pooled.
CreateTempRT/CreateTempRTHdruse Unity'sRenderTexture.GetTemporarypool. Always callReleaseTempRTwhen done. - MakeReadable is a no-op for readable textures. If the texture is already readable, it returns the same instance with no copy.