除了本地的脚本代码,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;
//返回渲染的接口函数
}