1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-03

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
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?