从C++更新Texture2D像素

Shi*_*ing 5 c# c++ pinvoke unity-game-engine

在该项目中,我通过 DllImports 使用 Unity3D 的 C# 脚本和 C++。我的目标是游戏场景有 2 个立方体(Cube & Cube2),其中一个立方体纹理显示通过我的笔记本电脑摄像头和 Unity 的实时视频webCamTexture.Play(),另一个立方体纹理显示外部 C++ 函数处理的视频ProcessImage()

代码上下文:

在c++中,我定义了

struct Color32
{
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
};
Run Code Online (Sandbox Code Playgroud)

函数是

extern "C"
{
    Color32* ProcessImage(Color32* raw, int width, int height);
}
...

Color32* ProcessImage(Color32* raw, int width, int height)
{
    for(int i=0; i<width*height ;i++)
    {
       raw[i].r = raw[i].r-2;
       raw[i].g = raw[i].g-2;
       raw[i].b = raw[i].b-2;
       raw[i].a = raw[i].a-2;
    }
    return raw;
}
Run Code Online (Sandbox Code Playgroud)

C#:

声明和导入

public GameObject cube; 
public GameObject cube2;
private Texture2D tx2D;
private WebCamTexture webCamTexture;

[DllImport("test22")]   /*the name of Plugin is test22*/
private static extern Color32[] ProcessImage(Color32[] rawImg, 
                                                 int width, int height);
Run Code Online (Sandbox Code Playgroud)

获取相机情况并设置cube1、cube2纹理

void Start()
{
    WebCamDevice[] wcd = WebCamTexture.devices;

    if(wcd.Length==0)
    {
        print("Cannot find a camera");
        Application.Quit();
    }
    else
    {
        webCamTexture = new WebCamTexture(wcd[0].name);
        cube.GetComponent<Renderer>().material.mainTexture = webCamTexture;

        tx2D = new Texture2D(webCamTexture.width, webCamTexture.height);
        cube2.GetComponent<Renderer>().material.mainTexture = tx2D;

        webCamTexture.Play();
    }
}
Run Code Online (Sandbox Code Playgroud)

通过将数据发送到外部 C++ 函数DllImports并使用 接收处理后的数据Color32[] a。最后,我使用 UnitySetPixels32设置tx2D(Cube2) 纹理:

void Update()
{
    Color32[] rawImg = webCamTexture.GetPixels32();
    System.Array.Reverse(rawImg);

    Debug.Log("Test1");
    Color32[] a = ProcessImage(rawImg, webCamTexture.width, webCamTexture.height);
    Debug.Log("Test2");

    tx2D.SetPixels32(a);
    tx2D.Apply();
}
Run Code Online (Sandbox Code Playgroud)

结果:

结果是立方体 1 的纹理仅显示实时视频,而无法显示使用立方体 2 的纹理处理的数据。

错误:

调用 SetPixels32 时,数组中的像素数量无效 UnityEngine.Texture2D:SetPixels32(Color32[]) Webcam:Update() (位于 Assets/Scripts/Webcam.cs:45)

我不明白为什么当我将数组 a 输入到 SetPixels32 时数组中的像素数无效

有任何想法吗?


更新(2018 年 10 月 10 日)

感谢@Programmer,现在它可以通过引脚内存工作。

顺便说一句,我发现一些关于 Unity 引擎的小问题。当Unity相机运行0到1秒之间时,webCamTexture.width或者webCamTexture.height总是返回16x16尺寸,甚至请求更大的图像,例如1280x720,然后它将在1秒后返回正确的尺寸。(可能是几帧)所以,我参考这篇文章Process()并延迟2秒在函数中运行Update()并在函数中重置Texture2D大小Process()。它会工作得很好:

delaytime = 0;
void Update()
{
    delaytime = delaytime + Time.deltaTime;
    Debug.Log(webCamTexture.width);
    Debug.Log(webCamTexture.height);

    if (delaytime >= 2f)
        Process();
}
unsafe void Process()
{
    ...

    if ((Test.width != webCamTexture.width) || Test.height != webCamTexture.height)
    {
       Test = new Texture2D(webCamTexture.width, webCamTexture.height, TextureFormat.ARGB32, false, false);
       cube2.GetComponent<Renderer>().material.mainTexture = Test;
       Debug.Log("Fixed Texture dimension");
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

Pro*_*mer 4

C# 不返回理解Color32从 C++ 返回的对象。如果您将返回类型设置为然后在 C# 端unsigned char*使用将返回的数据复制到字节数组中,然后使用该数组将数组加载到您的. 下面是一个从 C# 返回字节数组的示例。Marshal.CopyTexture2D.LoadRawTextureDataTexture2D


我不建议您返回数据,因为这样做的成本很高。填充您传递给函数的数组,然后只需将该数组重新分配给您的Texture2D.

C++:

extern "C"
{
    void ProcessImage(unsigned char* raw, int width, int height);
}
...

void ProcessImage(unsigned char* raw, int width, int height)
{
    for(int y=0; y < height; y++)
    {
        for(int x=0; x < width; x++)
        {
            unsigned char* pixel = raw + (y * width * 4 + x * 4);
            pixel[0] = pixel[0]-(unsigned char)2; //R
            pixel[1] = pixel[1]-(unsigned char)2; //G
            pixel[2] = pixel[2]-(unsigned char)2; //B
            pixel[3] = pixel[3]-(unsigned char)2; //Alpha
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

C#:

[DllImport("test22")]
private static extern void ProcessImage(IntPtr texData, int width, int height);

unsafe void ProcessImage(Texture2D texData)
{
    Color32[] texDataColor = texData.GetPixels32();
    System.Array.Reverse(texDataColor);

    //Pin Memory
    fixed (Color32* p = texDataColor)
    {
        ProcessImage((IntPtr)p, texData.width, texData.height);
    }
    //Update the Texture2D with array updated in C++
    texData.SetPixels32(texDataColor);
    texData.Apply();
}
Run Code Online (Sandbox Code Playgroud)

使用方法:

public Texture2D tex;

void Update()
{
    ProcessImage(tex);
}
Run Code Online (Sandbox Code Playgroud)

如果您不想使用unsafefixed关键字,您还可以使用GCHandle.Alloc来固定数组,然后再将其发送到 C++ 端GCHandle.AddrOfPinnedObject。请参阅这篇文章了解如何执行此操作。


如果您遇到以下异常:

调用 SetPixels32 时,数组中的像素数量无效 UnityEngine.Texture2D:SetPixels32(Color32[]) Webcam:Update() (位于 Assets/Scripts/Webcam.cs:45)

这意味着Texture2D您正在调用的纹理SetPixels32具有不同的大小或纹理尺寸WebCamTexture。要解决此问题,请在调用 之前SetPixels32,检查纹理尺寸是否更改,然后调整目标纹理的大小以与WebCamTexture.

代替

tx2D.SetPixels32(rawImg);
tx2D.Apply();
Run Code Online (Sandbox Code Playgroud)

//Fix texture dimension if it doesn't match with the web cam texture size
if ((tx2D.width != webCamTexture.width) || tx2D.height != webCamTexture.height)
{
    tx2D = new Texture2D(webCamTexture.width, webCamTexture.height, TextureFormat.RGBA32, false, false);
    Debug.Log("Fixed Texture dimension");
}

tx2D.SetPixels32(rawImg);
tx2D.Apply();
Run Code Online (Sandbox Code Playgroud)