赞
踩
最近在学习untiy游戏引擎的知识,在学习过程中突发奇想,unity和flutter都是可以通过opengl和vulkan绘制界面,那有没有一种方法可以使得二者界面互相融合,即将flutter的界面渲染到unity的物体中,或者将unity的界面渲染到flutter的widget上。由于这两种渲染方式大体相同,下面我们就着重讲下如何将flutter界面渲染到unity中。
首先我们想到的是将flutter界面截屏成bitmap,然后通过交互将bitmap传递给unity,并在unity中使用该bitmap,并且我们也可以立马发现flutter自带截屏函数。但是我们会立刻发现此方案的弊端,此方案首先需要将flutter界面从gpu中下载到内存,然后再通过unity与java通信将bitmap传递到unity中,最后再在unity中将bitmap上传至gpu中作为纹理使用。如此一番折腾,需要在内存中倒腾很多遍,更何况bitmap通常很大,来回传递严重影响效率。那有没有一种更好的方案来解决这个问题呢,当然有,那就是–
我们知道opengl上下文通常是和线程绑定的,不同上下文之间环境比较独立,无法共享内容,但是为了更好的在多线程环境下工作,opengl提供了纹理共享的方式来弥补上诉问题,提高多线程下工作效率。使用方法也比较简单,即在新建opengl环境的时候传入一个已有opengl上下文和configs,这样就可以在两个opengl环境下共享使用同一份纹理。
至于如何在安卓上实现与unity的纹理共享,大家可以参考该文章https://blog.csdn.net/jnlws/article/details/113176253
该文章讲解的比较详细,并且代码比较完善,大家可以仔细阅读并自己实现一遍即可,这边我大概讲解下相机纹理共享的实现流程(因为flutter共享过程与之类似)。
纹理共享是opengl的方法,对于vulkan和metal这样天生就支持多线程的渲染管线,不需要这种方式,不过目前对于vulkan不是很熟悉,所以这里就先不进行讲解。
上面我们已经分析了如何将相机数据通过纹理共享到unity中,我们知道可以通过surfacetexture和fbo离屏渲染将纹理共享给unity,因此我们只需要找到如何将flutter界面渲染到surfacetexture中,即可实现flutter和unity界面的融合。接下来我们来分析flutter源码,接下来的源码都是基于flutter1.16来分析的。
首先flutter在新的版本中,为了方便进行混合接入,抽离出了flutter engine,而且我们不需要将flutter界面直接渲染出来,所以我们只需要创建一个没有view的flutter fragment放入unity activity中就可以了。我们将flutter fragment源码拷贝出来并进行改造,与之对应的还需要将FlutterActivityAndFragmentDelegate,接下来我们顺着代码分析flutter界面是如何渲染到安卓上的,首先flutter是通过FlutterView加载到安卓界面的
而FlutterView分为两种模式,我们只需要分析surface模式,我们发现有个FlutterSurfaceView用来实现和flutter关联,我们发现在surfaceview surface创建的时候通过FlutterRender将surface传入flutter中。
这下分析下来就比较明了了,我们只需要创建一个surface,通过FlutterRender将其传给flutter,同时通过将其数据输出到surfacetexture,并通过fbo离屏渲染将其输出到unity可以使用的纹理id中即可。代码如下
public void attachToFlutterEngine(FlutterEngine flutterEngine) {
this.flutterEngine = flutterEngine;
FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
flutterRenderer.startRenderingToSurface(GLTexture.instance.surface);
flutterRenderer.surfaceChanged(GLTexture.instance.getStreamTextureWidth(), GLTexture.instance.getStreamTextureHeight());
FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics();
viewportMetrics.width = GLTexture.instance.getStreamTextureWidth();
viewportMetrics.height = GLTexture.instance.getStreamTextureHeight();
viewportMetrics.devicePixelRatio = GLTexture.instance.context.getResources().getDisplayMetrics().density;
flutterRenderer.setViewportMetrics(viewportMetrics);
flutterRenderer.addIsDisplayingFlutterUiListener(new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
GLTexture.instance.setNeedUpdate(true);
GLTexture.instance.updateTexture();
}
@Override
public void onFlutterUiNoLongerDisplayed() {
}
});
GLTexture.instance.attachFlutterSurface(this);
}
这里需要注意的是需要给flutter传递宽和高,不然flutter界面可能显示不出来
接下来我们只需要和相机一样,在unity中接收纹理并渲染到gameobject就可以了。
最终实现效果如下,我们可以看到flutter界面完美的渲染到unity中了
整体流程如下,其中重绘时候数据都在gpu内部,不涉及来回拷贝,这就是纹理共享的好处,可以实现更高的刷新率。
我们接下来还需要处理flutter点击事件,我们需要在unity中获取点击事件,并将其传递给安卓,然后传递给flutter即可。
unity点击处理代码如下
void Update()
{
#if UNITY_ANDROID
if(mGLTexCtrl.Call<bool>("isNeedUpdate"))
mGLTexCtrl.Call("updateTexture");
if (Input.touches.Length > 0){
if(haveStartFlutter == 1){
//传递touch信息给java,代码省略,具体可以参考unity点击处理和源码
}
}else{
mFlutterApp.Call("startFlutter");
haveStartFlutter = 1;
}
}
#endif
if(Input.GetMouseButtonDown(1)){
Debug.Log(Input.mousePosition);
}
}
安卓传递给flutter代码如下。这里我们可以在flutter源码中发现如何传递点击事件,这里也借助于flutter源码来实现
public void onTouchEvent(int type, double x, double y) {
ByteBuffer packet =
ByteBuffer.allocateDirect(1 * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
packet.order(ByteOrder.LITTLE_ENDIAN);
double x1, y1;
x1 = GLTexture.instance.getStreamTextureWidth() * x;
y1 = GLTexture.instance.getStreamTextureHeight() * y;
Log.i("myyf", "x:" + x1 + "&y:" + y1 + "&type:" + type);
addPointerForIndex(x1, y1, type + 4, 0, packet);
if (packet.position() % (POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD) != 0) {
throw new AssertionError("Packet position is not on field boundary");
}
flutterEngine.getRenderer().dispatchPointerDataPacket(packet,packet.position());
}
//传递点击信息给flutter,代码省略,具体参考flutter内部代码
// TODO(mattcarroll): consider creating a PointerPacket class instead of using a procedure that
// mutates inputs.
private void addPointerForIndex(
double x, double y, int pointerChange, int pointerData, ByteBuffer packet) {
}
这样我们就可以实现在unity中点击flutter界面了最终实现效果如下
上面我们分析了如和将flutter界面渲染到unity中,通过opengl的纹理共享和安卓的surface即可实现,同理如何将unity界面渲染到flutter也是一样,我们只需要自定义UnityPlayer将其输出到纹理中,并在flutter中使用即可,可以更广泛的推广,我们可以通过这种方法将安卓界面渲染到flutter和unity中。本文代码已上传至github,此处奉上链接,有需要的同学可以自取(https://github.com/feiyin0719/unity_flutter)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。