Mac下在Unity中调用C++库

Unity: 2021.2.10f1c1 silicon OS: macOS Monterey 12.1 Compiler: Clang 13.0.0 arm64-apple-darwin21.1.0

一个简单的Demo

新建一个c++文件unity_api.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#if defined(__CYGWIN32__)
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport) __stdcall
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY)
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport) __stdcall
#elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(__QNX__)
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#else
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#endif

extern "C" {
UNITY_CPP_INTEROP_DLL_BRIDGE float Add(const float a,
const float b) {
return a + b;
}
}

CMakeLists.txt:

1
2
3
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O3 -D__ARM_NEON__ -DENABLE_NEON -Wno-unused-result -march=armv8-a+fp+simd+crypto -DCMAKE_APPLE_SILICON_PROCESSOR=arm64")

add_library(unity_api SHARED unity_api.cpp)

编译生成libunity_api.dylib,并且放到UnityProject/Assets/Plugins/macOS文件夹下,并且在Unity中对库进行设置:

unity_inspector

编写Unity的C#脚本PluginImport.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class PluginImport : MonoBehaviour
{
[DllImport("unity_api", CallingConvention = CallingConvention.Cdecl)]
public static extern float Add(float a, float b);

// Start is called before the first frame update
void Start()
{
Debug.Log(Add(1, 2));
}

}

并且将脚本PluginImport.cs挂靠到Unity的Main Camera下,并且运行,可以在Console下看到结果:

unity_simple_test_output

使用OpenCV库

修改c++文件unity_api.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <opencv2/opencv.hpp>

#if defined(__CYGWIN32__)
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport) __stdcall
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY)
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport) __stdcall
#elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(__QNX__)
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#else
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#endif



extern "C" {
UNITY_CPP_INTEROP_DLL_BRIDGE float Add(const float a,
const float b) {
return a + b;
}

UNITY_CPP_INTEROP_DLL_BRIDGE bool ToGray(const char *color_img_file,
const char *gray_img_file) {

cv::Mat img = cv::imread(std::string(color_img_file));
if (img.empty()) {
return false;
}

cv::Mat img_gray;
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
cv::imwrite(std::string(gray_img_file), img_gray);
return true;
}
}

CMakeLists.txt:

1
2
3
4
5
6
7
8
9
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O3 -D__ARM_NEON__ -DENABLE_NEON -Wno-unused-result -march=armv8-a+fp+simd+crypto -DCMAKE_APPLE_SILICON_PROCESSOR=arm64")

find_package(OpenCV REQUIRED)

include_directories(${OpenCV_INCLUDE_DIRS})
link_directories(${OpenCV_LIBRARY_DIRS})

add_library(unity_api SHARED unity_api.cpp)
target_link_libraries(unity_api ${OpenCV_LIBS})

编译生成libunity_api.dylib,并且放到UnityProject/Assets/Plugins/macOS文件夹下。修改Unity的C#脚本PluginImport.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class PluginImport : MonoBehaviour
{
[DllImport("unity_api", CallingConvention = CallingConvention.Cdecl)]
public static extern float Add(float a, float b);

[DllImport("unity_api", CallingConvention = CallingConvention.Cdecl)]
public static extern bool ToGray(string color_img_file, string gray_img_file);

// Start is called before the first frame update
void Start()
{
Debug.Log(ToGray("/Users/luohanjie/Downloads/lenna_color.png", "/Users/luohanjie/Downloads/lenna_gray.png"));
}

}

并且将脚本PluginImport.cs挂靠到Unity的Main Camera下,并且运行,可以得到的灰度图lenna_gray.png

lenna_gray

通过Unity调用网络摄像头

在Unity的Main Camera下添加一个Raw Image,命名为Background并且设置TagbackgroundCanvas节点中中将Render Mode设置为Screen Space - Camera,并且将Main Camera节点拖拽到Render Camera上。

background1

Background节点(Raw Image)中,将LeftTopPos ZRightBottom都设置为0。为了使得显示的图片比例正确,添加Aspect Ratio Fitter并且Aspect Mode设置为Fit In Parent

background2

Main Camera下添加脚本WebCamera.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;


public class WebCamera : MonoBehaviour
{
public string cam_name = "FaceTime HD Camera";
public int cam_width = 1280;
public int cam_heidht = 720;
public int cam_fps = 30;
public AspectRatioFitter aspect_fitter;
private WebCamTexture web_cam_tex = null;
private bool is_init = false;

// Start is called before the first frame update
void Start()
{
WebCamDevice[] devices = WebCamTexture.devices;
for (int i = 0; i < devices.Length; i++)
{
if (devices[i].name == cam_name)
{
is_init = true;
Debug.Log("Connect camera to " + cam_name);
break;
}
}

if (is_init == false) {
Debug.Log("Can not find " + cam_name);
return;
}

web_cam_tex = new WebCamTexture(cam_name, cam_width, cam_heidht, cam_fps);

if (web_cam_tex == null) {
is_init = false;
Debug.Log ("Can not new WebCamTexture");
return;
}

GameObject.FindWithTag("background").GetComponent<RawImage>().texture = web_cam_tex;

web_cam_tex.Play();
}

void OnStart (Texture preview) {
aspect_fitter.aspectRatio = preview.width / (float)preview.height;
}

// Update is called once per frame
void Update()
{

}
}

然后将Background节点拖拽到Main Camera节点中,Web Camera (Script)项目下的Aspect_fitter项目上:

background1

点击运行可以看到结果:

webcam_unity

将c++ OpenCV的Mat传输到Unity并且显示

c++文件unity_api.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#if defined(__CYGWIN32__)
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport) __stdcall
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY)
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport) __stdcall
#elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(__QNX__)
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#else
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#endif

#include <opencv2/opencv.hpp>

cv::Mat img_;

extern "C" {
UNITY_CPP_INTEROP_DLL_BRIDGE bool ReadImage(const char *color_img_file,
unsigned char*& rgb24_pixels,
int& width,
int& height,
int& size) {

img_ = cv::imread(std::string(color_img_file));
if (img_.empty()) {
return false;
}

// Unity中图片坐标系原点在图片的坐下角,与OpenCV中的定义不一样。
// 为了显示正确,需要flip操作。
cv::flip(img_, img_, 0);
cv::cvtColor(img_, img_, cv::COLOR_BGR2RGB);

rgb24_pixels = img_.data;
width = img_.cols;
height = img_.cols;
size = img_.cols * img_.rows * img_.channels();
return true;
}
}

Unity的C#脚本PluginImport.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

public class PluginImport : MonoBehaviour
{
[DllImport("unity_api", CallingConvention = CallingConvention.Cdecl)]
public static extern bool ReadImage(string color_img_file, ref IntPtr data, ref int width, ref int height, ref int size);

// Start is called before the first frame update
void Start()
{
IntPtr data = IntPtr.Zero;
int width = 0;
int height = 0;
int size = 0;

bool flag = ReadImage("/Users/luohanjie/Downloads/lenna_color.png", ref data, ref width, ref height, ref size);

if (!flag || size <= 0) return;

byte[] buffer = new byte[size];

Texture2D tex = new Texture2D(width, height, TextureFormat.RGB24, false, false);

System.Runtime.InteropServices.Marshal.Copy(data, buffer, 0, size);
tex.LoadRawTextureData(buffer);
tex.Apply();

RawImage background = GameObject.FindWithTag("background").GetComponent<RawImage>();
background.texture = tex;
}
}

在Unity的Main Camera下添加一个Raw Image,命名为Background并且设置Tagbackground

将脚本PluginImport.cs挂靠到Unity的Main Camera下,并且运行:

mat2unity

从c++读取Unity物体的Pose

c++文件unity_api.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#if defined(__CYGWIN32__)
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport) __stdcall
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY)
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport) __stdcall
#elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(__QNX__)
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#else
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#endif

extern "C" {
UNITY_CPP_INTEROP_DLL_BRIDGE void GetPose(float *pose_out) {
// 假定我们有一个右手系的pose = [x, y, z, qw, qx, qy, qz],表示物体在摄像头坐标系下(opencv定义)的位姿。
std::array<float, 7> pose;

// 从OpenCV的右手系转到Unity中使用的左手系
pose[1] = -pose[1]; //y
pose[4] = -pose[4]; //qx
pose[6] = -pose[6]; //qz

memcpy(pose_out, pose.data(), 7 * sizeof(float));
}
}

Unity的C#脚本PluginImport.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

public class PluginImport : MonoBehaviour
{
[DllImport("unity_api", CallingConvention = CallingConvention.Cdecl)]
public static extern void GetPose([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] float[] pose);

// Start is called before the first frame update
void Start()
{
float[] obj_pose = new float[7]; // x, y, z, qw, qx, qy, qz
GetPose(obj_pose);

//方法1,根据读取的Pose设置Unity中物体的姿态
GameObject obj = GameObject.FindWithTag("obj_tag_name");
obj.transform.position = new Vector3((float)obj_pose[0], (float)obj_pose[1], (float)obj_pose[2]);
obj.transform.rotation = new Quaternion((float)obj_pose[4], (float)obj_pose[5], (float)obj_pose[6], (float)obj_pose[3]);

//方法2,根据读取的Pose设置Unity中摄像头的姿态
Camera cam_obj = GameObject.FindWithTag("MainCamera").GetComponent<Camera>();
cam_obj.transform.position = new Vector3((float)obj_pose[0], (float)obj_pose[1], (float)obj_pose[2]);
cam_obj.transform.rotation = new Quaternion((float)obj_pose[4], (float)obj_pose[5], (float)obj_pose[6], (float)obj_pose[3]);
}
}