Unity低级本地插件接口--Low-level Native Plugin Interface

1 篇文章 0 订阅
1 篇文章 0 订阅

除了本地的脚本代码,Unity的本地插件能够接收特定时间的回调。这个特性最常用被来实施低层次的插件渲染工作,使之能够和Unity的多线程渲染一起工作。

定义这些接口的头文件由编辑器提供。(什么意思,没看明白)

接口注册

一个插件应该导出UnityPluginLoad和UnityPluginUnload函数来处理unity事件。参考IUnityInterface.h来获取正确的签名(声明)。IUnityInterfaces被提供给插件继而进一步获取Unity APIs。

#include "IUnityInterface.h"
#include "IUnityGraphics.h"
// Unity 插件加载事件
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
    UnityPluginLoad(IUnityInterfaces* unityInterfaces)
{
    IUnityGraphics* graphics = unityInterfaces->Get<IUnityGraphics>();
}

访问图形设备

插件通过获取IUnityGraphics接口能够功能性地访问通常的图形设备。早期Unity版本中,为了接收图形设备上事件的通知,UnitySetGraphicsDevice函数必须被导出。从5.2版本以后新的IUnityGraphics接口提供了一种方式来注册一个回调。

#include "IUnityInterface.h"
#include "IUnityGraphics.h"

static IUnityInterfaces* s_UnityInterfaces = NULL;
static IUnityGraphics* s_Graphics = NULL;
static UnityGfxRenderer s_RendererType = kUnityGfxRendererNull;

// Unity插件加载事件
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UnityPluginLoad(IUnityInterfaces* unityInterfaces)
{
s_UnityInterfaces = unityInterfaces;
s_Graphics = unityInterfaces->Get<IUnityGraphics>();
s_Graphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent);

// 插件加载时手动运行OnGraphicsDeviceEvent(initialize) 
// 为了不丢失/错过这个事件,以防图形设备已经被初始化过了
OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize);
}

// Unity 插件卸载事件
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UnityPluginUnload()
{
s_Graphics->UnregisterDeviceEventCallback(OnGraphicsDeviceEvent);
}

static void UNITY_INTERFACE_API
OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType)
{
switch (eventType)
{
case kUnityGfxDeviceEventInitialize:
{
s_RendererType = s_Graphics->GetRenderer();
//TODO: 用户初始化代码
break;
	}
case kUnityGfxDeviceEventShutdown:
{
s_RendererType = kUnityGfxRendererNull;
//TODO: 用户关闭代码
break;
}
case kUnityGfxDeviceEventBeforeReset:
{
//TODO: user Direct3D 9 code
break;
}
case kUnityGfxDeviceEventAfterReset:
{
//TODO: user Direct3D 9 code
break;
}
};
}


渲染线程上的插件回调

如果平台或者CPU数量支持则Unity能够进行多线程渲染。当多线程渲染被使用时,一个线程上的渲染API接口命令完全独立于运行MonoBehaviours脚本的线程。所以,通常你的插件没法立即开始执行渲染,因为很可能跟主渲染线程正在做的事发生冲突。为了从插件做任何的渲染工作,你需要从你的代码中调用GL.IssuePluginEvent。这将会导致提供的本地函数从渲染线程调用。例如,如果你从camera的OnPostRender函数调用GL.IssuePluginEvent,你将会在摄像机完成渲染后马上得到一个插件回调。

UnityRenderingEvent回调的声明被提供在IUnityGraphics.h中。一个本地插件的示例:

// Plugin function to handle a specific rendering event    处理特定渲染事件的插件函数
static void UNITY_INTERFACE_API OnRenderEvent(int eventID)
{
    //TODO: 用户渲染代码
}
    
// Freely defined function to pass a callback to plugin-specific scripts   自由定义的函数,用来传递给插件相关代码的回调
extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
    GetRenderEventFunc()
{
    return OnRenderEvent;
}
托管的插件代码示例:

#if UNITY_IPHONE && !UNITY_EDITOR
[DllImport ("__Internal")]
#else
[DllImport("RenderingPlugin")]
#endif
private static extern IntPtr GetRenderEventFunc();
    
// Queue a specific callback to be called on the render thread
GL.IssuePluginEvent(GetRenderEventFunc(), 1);
这种回调也可以被加在CommandBUffers通过CommandBuffer.IssuePluginEvent。


使用openGL图形接口的插件

有两种openGL的对象:

openGL Context之间共享的对象(texture, buffer, renderbuffer, samplers, query, shader, program objects);

per-OpenGL context对象(vertex array, framebuffer, program pipeline, transform feedback, sync objects);

Unity使用多个OpenGL context。当初始化、关闭编辑器和player的时候,我们依赖一个Master context,但是渲染的时候我们用专门的context。

因此,你不能创建per-context 对象在 kUnityGfxDeviceEventInitialize 和 kUnityGfxDeviceEventShutdown事件时候。


例如,一个本地的插件不能在kUnityGfxDeviceEventInitialize事件中创建vertex array 然后在UnityRenderingEvent 中使用它,因为激活的context不是vertex array对象创建时被使用的context。

案例

https://bitbucket.org/Unity-Technologies/graphicsdemos

这个案例干了两个事:

1 所有通常的渲染完成后又用c++渲染了一个三角形。

2 填充了一个C++代码的 procedural texture,使用Texture.GetNativetexturePtr 来获取它。

实施

官方的文档,说白了,已经把需要的所有东西都告诉你了,然而,然而,对于没有接触过底层的人来说,就跟雾里看花一样。

首先,我们来解释下官方文档的例子。

官方文档做了很少的底层渲染的一部分,而且只做了通过Native Plugin Interface获取unity渲染线程的opengl(或者其他绘图api)资源和相应的textureID(也就是指针,新的unityapi教NativeTexturePtr),一是在本地通过cpp插件改变这个texture的值,官方例子是通过实时的time值来改变纹理数值,写到内存区,然后绑定textureID写入到texture中,所以我们能看到Unity中plane的texture实时变化。二是,获取texture的指针,改变它的顶点值之类的,这样就是我们看到的波浪的效果。三是,在unity 的绘制线程内,在协程中EndOfFrame,或者Lateupdate中,也就是每一帧渲染完成之后,再绘制自己的三角型,由于Unity本身接口就在一个循环里,所以每一次都是在接口函数OnrenderEvent里面绘制一次。

所以,综合的看,这个接口给了一个你直接进入unity渲染线程的接口,可以获取到某些可以线程共享的资源。所以你可以在本地插件代码中进行底层的opengl绘制操作。具体操作流程可以查看项目源码,由于是支持多平台的,所以定义了很多的接口,官方例子还写成了类。

由于工作中的问题,我需要的并不是通过本地代码改变或者做插件渲染,而是要获取unity的texture来进行自己的渲染。这块的知识一个就是本地插件的接口,另外一块就是opengl多线程的绘制,资源共享等。具体就不详细说了,感兴趣的留方式我们再交流,目前已经成功实现unity内部任意texture、rendertexture等的绘制输出,采用Win32-openGL外部窗口,并且添加了跟外部进程交互的内容。

相关代码:

C# Code

[DllImport("user32.dll")]
    static extern System.IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    static extern System.IntPtr GetCurrentHinstance();

    [DllImport("unitydll")]
    static extern bool ReturnShareTrue();

    [DllImport("unitydll")]
    static extern void TellUnityNull();

    [DllImport("unitydll")]
    private static extern void StartDllThread();

    [DllImport("unitydll")]
    private static extern void PassTextureFromUnity(System.IntPtr texture, int w, int h);

    [DllImport("unitydll")]
    private static extern System.IntPtr GetRenderEventFunc();

    [DllImport("unitydll")]
    private static extern void PassUnityHwnd(System.IntPtr unityhwnd);

    bool flag2 = false;
    private StreamWriter DebugToFile;

    void Awake()
    {
    }

    IEnumerator Start()
    {
        _ARcamera = this.gameObject.AddComponent<Camera>();
        _ARcamera.enabled = false;
        StartCoroutine(WaitAndMakeCurrentNull());
        StartCoroutine(startDLL());
        StartCoroutine(CreateTextureAndPassToPlugin());
        yield return StartCoroutine("CallPluginAtEndOfFrames");
        Initial();
    }
    private void Update()
    {
        Debug.Log("--------update");
    }
    private void LateUpdate()
    {
        Debug.Log("--------Late update");
        if (flag)
        {
            Render();
        }

        if (!flag2)
        {
            GL.IssuePluginEvent(GetRenderEventFunc(), 1);
            flag2 = true;
        }
    }

    IEnumerator startDLL()
    {
        yield return new WaitForSeconds(0.2f);
        StartDllThread();
    }
        IEnumerator WaitAndMakeCurrentNull()
    {
        yield return new WaitForSeconds(0.2f);
        Debug.Log("makeNULL");
        GL.IssuePluginEvent(GetRenderEventFunc(), 2);//make null
        TellUnityNull();
    }

    private IEnumerator CallPluginAtEndOfFrames()
    {
        while (true)
        {
//你的代码,在lateupdate中也可以做同样的调用,机制相同
        }
    }

    IEnumerator CreateTextureAndPassToPlugin()
    {
        yield return new WaitForSeconds(0.2f);
        Debug.Log("Create Texture!");

        flag = true;
        tempRT = new RenderTexture(ARcamera.pixelWidth, ARcamera.pixelHeight, 24, RenderTextureFormat.ARGB32);
        tempRT.filterMode = FilterMode.Point;
        tempRT.Create();
        PassTextureFromUnity(tempRT.GetNativeTexturePtr(), tempRT.width, tempRT.height);
        Debug.Log(tempRT.GetNativeTexturePtr());
    }

    public void Render()
    {
        _ARcamera.CopyFrom(ARcamera);
        _ARcamera.targetTexture = tempRT;
        _ARcamera.Render();
    }

DLL Cpp

extern "C"	void DLL_API StartDllThread()
{
	g_hInstance = GetModuleHandle(NULL);
	_beginthread(LoadDllThread, NULL, NULL);//
}

extern "C"	void DLL_API PassTextureFromUnity(void* textureHandle, int w, int h)
{
	g_TextureHandle = textureHandle;
	g_texture_Width = w;
	g_texture_Height = h;
}

static void UNITY_INTERFACE_API OnRenderEvent(int eventID)
{
        //渲染程序入口,可以参考官方例子
	switch (eventID)
	{
        //你的时间处理代码,基于eventID
	}
}

extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API GetRenderEventFunc()
{
	return OnRenderEvent;
       //返回渲染的接口函数
}




  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值