跳至主要內容

Unity3D中使用SkiaSharp处理Texture2D (中篇)

MonoLogueChiUnity大约 2 分钟

这一篇主要讲一种 Texture2D 和 SKBitmap 之间更快捷的转换方式。

看本片文章之前,请先阅读我的上一篇文章。

直接填充数据

我们再思考一个问题,能否不去填充像素点的颜色,而是直接填充数据,这样转换效率会更高。

当 SKBitmap 的 ColorType 与 Texture2D 的 TextureFormat,对应时,内存数据一致,调整数据后直接填充进去就可以了。

我给出下面的示例代码,可以看一下。

using System;
using System.Buffers;
using SkiaSharp;
using UnityEngine;
using UnityEngine.UI;

public class Example2 : MonoBehaviour
{
  [SerializeField] private Texture2D tex;
  private RawImage _rawImage;

  private void Start()
  {
    _rawImage = GetComponent<RawImage>();

    // 以下代码都是 已知 tex 的 TextureFormat 为 RGBA32 的情况下

    var bitmap = new SKBitmap(tex.width, tex.height, SKColorType.Rgba8888, SKAlphaType.Opaque);

    var data = tex.GetPixelData<byte>(0).AsReadOnlySpan();

    var writer = new ArrayBufferWriter<byte>(tex.width * tex.height * 4);

    for (var i = tex.height - 1; i >= 0; i--) writer.Write(data.Slice(i * tex.width * 4, tex.width * 4));

    var span = writer.WrittenSpan;

    unsafe
    {
      fixed (byte* ptr = span)
      {
        bitmap.SetPixels((IntPtr)ptr);
      }
    }

    var tex0 = new Texture2D(bitmap.Width, bitmap.Height, TextureFormat.RGBA32, false);

    var data0 = bitmap.GetPixelSpan();
    var writer0 = new ArrayBufferWriter<byte>(bitmap.Width * bitmap.Height * 4);

    for (var i = bitmap.Height - 1; i >= 0; i--) writer0.Write(data0.Slice(i * bitmap.Width * 4, bitmap.Width * 4));

    tex0.SetPixelData(writer0.WrittenSpan.ToArray(), 0);
    tex0.Apply();
    _rawImage.texture = tex0;
    _rawImage.GetComponent<RectTransform>().sizeDelta = new Vector2(tex0.width, tex0.height);
  }
}

从 GPU 读取数据

前面的所有代码,都是基于 Texture 数据在 CPU 内存中编写的,即 texture2d.isReadable = true,如果数据不在 CPU 内存中,则需要在显卡中读取数据。

/// <summary>
///   从GUP读取贴图
/// </summary>
/// <param name="texture"></param>
/// <returns></returns>
public static Texture2D GetTextureFromGpu(this Texture texture)
{
  var data = GetTextureDataFromGpu(texture);
  var tex2 = new Texture2D(texture.width, texture.height, texture.graphicsFormat, TextureCreationFlags.None);
  tex2.SetPixelData(data, 0);

  return tex2;
}

/// <summary>
///   从GPU读取数据
/// </summary>
/// <param name="texture"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static NativeArray<byte> GetTextureDataFromGpu(this Texture texture)
{
  var request = AsyncGPUReadback.Request(texture, 0, texture.graphicsFormat);
  request.WaitForCompletion();
  if (request.hasError) throw new Exception("");
  return request.GetData<byte>();
}

/// <summary>
///   从 GPU 获取数据
/// </summary>
/// <param name="texture2D"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private static async ValueTask<NativeArray<byte>> GetTextureDataFromGpuAsync(this Texture2D texture2D)
{
  var request = await AsyncGPUReadback.RequestAsync(texture2D, 0, texture2D.graphicsFormat);
  if (request.hasError) throw new Exception("");
  return request.GetData<byte>();
}

然后修改前面所写的代码,判断是否可读,如果可读就按前面所写的做,如果不可读,就改为从 GPU 获取数据。