Help us understand the problem. What is going on with this article?

[Unity] AsyncGPUReadback を利用して、非 Color 形式の Texture からデータを取り出す

More than 1 year has passed since last update.

Unity の Tetxture2D クラスには Int 要素などを持つ Generic な Texture からデータを読むインターフェイスがない。AsyncGPUReadback クラスを利用すると、任意フォーマットの Texture (Texture2D / RenderTexture) から NativeArray 形式でデータを取得できる。

Texture ソースから非同期でデータ取得を行う AsyncCPUTexture クラスを以下のように定義する。Source に読みたい Texture を設定し、Update() を繰り返すことで常に最新のデータを取り出す。

NativeArray として取得したデータは、直後に破棄されるので ”T[]” 配列にコピーしておく。ここで UnsafeCopyTo() 関数は、「Unity - "NativeArray<T> where T:struct" の要素を "T[] where T:struct" へ高速にコピーする」 で定義したものを流用する。

また、Bilinear などの補間や uv 座標でのアクセスもしたいので、TextureData というクラスでデータをラップする。

"AsyncCPUTexture<T> where T:struct" クラス

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class AsyncCPUTexture<T> where T:struct {

    protected bool active = false;
    protected AsyncGPUReadbackRequest req;

    protected Vector2Int size;
    protected T defaultValue;
    protected T[] data;
    protected TextureData<T> output;

    public AsyncCPUTexture(T defaultValue = default(T)) {
        this.defaultValue = defaultValue;
    }

    #region interface
    public virtual Texture Source { get; set; }
    public virtual void Update() {

        if (active) {
            if (req.hasError) {
                Debug.Log($"Failed to read back from GPU async");
                active = false;
            } else if (req.done) {
                Release();
                var nativeData = req.GetData<T>();
                System.Array.Resize(ref data, nativeData.Length);
                nativeData.UnsafeCopyTo(data);
                output = GenerateCPUTexture(data, size);
            }
        } else {
            if (Source != null) {
                active = true;
                req = AsyncGPUReadback.Request(Source);
                size = new Vector2Int(Source.width, Source.height);
            }
        }
    }

    public virtual Vector2Int Size => size;
    public virtual System.Func<float, float, T> Interpolation { get; set; }
    public virtual T this[Vector2 uv] => (output != null ? output[uv] : defaultValue);
    public virtual T this[float nx, float ny] => (output != null ? output[nx, ny] : defaultValue);
    public virtual T this[int x, int y] => (output != null ? output[x, y] : defaultValue);
    #endregion

    #region member
    protected virtual TextureData<T> GenerateCPUTexture(IList<T> data, Vector2Int size) {
        var tex = new TextureData<T>(data, size);
        tex.Interpolation = Interpolation;
        return tex;
    }
    protected virtual void Release() {
        if (output != null) {
            output.Dispose();
            output = null;
        }
        active = false;
    }
    #endregion
}

"TextureData<T> where T:struct" クラス

using System.Collections.Generic;
using UnityEngine;

public class TextureData<T> : System.IDisposable where T:struct {
    public delegate T BilinearFunc(T v00, T v01, T v10, T v11, float s, float t);
    public event System.Action<TextureData<T>> OnLoad;

    protected Vector2Int size;
    protected IList<T> pixels;
    protected Vector2 uvToIndex;
    protected System.Func<float, float, T> interpolation;

    public TextureData(IList<T> pixels, Vector2Int size,
        System.Func<float, float, T> interpolation = null) {
        this.Size = size;
        this.Interpolation = interpolation;
        Load(pixels);
    }

    #region interface
    public System.Func<float, float, T> Interpolation {
        get { return interpolation; }
        set {
            interpolation = (value ?? PointInterpolation);
        }
    }
    public virtual void Load(IList<T> pixels) {
        lock (this) {
            this.pixels = pixels;
        }
        NotifyOnLoad();
    }
    public T PointInterpolation(float nx, float ny) {
        var x = Mathf.RoundToInt(nx * uvToIndex.x);
        var y = Mathf.RoundToInt(ny * uvToIndex.y);
        ClampPixelPos(ref x, ref y);
        return GetPixelDirect(x, y);
    }
    public System.Func<float, float, T> GenerateInterpolation(BilinearFunc bilinear) {
        return (float nx, float ny) => {
            int x0, y0, x1, y1;
            float t, s;
            Bridge(nx, ny, out x0, out y0, out x1, out y1, out t, out s);
            ClampPixelPos(ref x0, ref y0);
            ClampPixelPos(ref x1, ref y1);
            return bilinear(
                GetPixelDirect(x0, y0),
                GetPixelDirect(x0, y1),
                GetPixelDirect(x1, y0),
                GetPixelDirect(x1, y1),
                s, t);
        };
    }

    public virtual T this[int x, int y] {
        get {
            lock (this) {
                ClampPixelPos(ref x, ref y);
                return GetPixelDirect(x, y);
            }
        }
        set {
            lock (this) {
                ClampPixelPos(ref x, ref y);
                SetPixelDirect(x, y, value);
            }
        }
    }

    public virtual T this[float nx, float ny] {
        get {
            lock (this) {
                return interpolation(nx, ny);
            }
        }
    }

    public virtual T this[Vector2 uv] {
        get { return this[uv.x, uv.y]; }
    }

    public virtual Vector2Int Size {
        get { return size; }
        set {
            size = value;
            uvToIndex = new Vector2(value.x - 1, value.y - 1);
        }
    }
    #endregion

    #region static
    public static Color Bilinear(Color v00, Color v01, Color v10, Color v11, float s, float t) {
        return t * (s * v00 + (1f - s) * v01)
            + (1f - t) * (s * v10 + (1f - s) * v11);
    }
    public static float Bilinear(float v00, float v01, float v10, float v11, float s, float t) {
        return t * (s * v00 + (1f - s) * v01)
            + (1f - t) * (s * v10 + (1f - s) * v11);
    }
    #endregion

    #region member
    protected virtual void NotifyOnLoad() {
        OnLoad?.Invoke(this);
    }

    protected virtual void Bridge(float nx, float ny, out int x0, out int y0, out int x1, out int y1, out float t, out float s) {
        var x = uvToIndex.x * nx;
        var y = uvToIndex.y * ny;
        x0 = Mathf.FloorToInt(x);
        y0 = Mathf.FloorToInt(y);
        x1 = x0 + 1;
        y1 = y0 + 1;
        t = x1 - x;
        s = y1 - y;
    }
    protected virtual void ClampPixelPos(ref int x, ref int y) {
        x = (x < 0 ? 0 : (x < size.x ? x : size.x - 1));
        y = (y < 0 ? 0 : (y < size.y ? y : size.y - 1));
    }

    protected virtual int GetLinearIndex(int x, int y) {
        return x + y * size.x;
    }
    protected virtual int GetLinearIndex(Vector2Int index) {
        return GetLinearIndex(index.x, index.y);
    }
    protected virtual T GetPixelDirect(int x, int y) {
        return pixels[GetLinearIndex(x, y)];
    }
    protected virtual void SetPixelDirect(int x, int y, T c) {
        pixels[GetLinearIndex(x, y)] = c;
    }
    #endregion

    #region IDisposable
    public void Dispose() {
        if (pixels != null) {
            pixels = null;
        }
    }
    #endregion
}
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away