iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >详解Unity安卓共享纹理
  • 280
分享到

详解Unity安卓共享纹理

2024-04-02 19:04:59 280人浏览 独家记忆
摘要

目录概述简单通信获取和创建Context共享纹理视频流RTT纹理取回概述 本文的目的是实现以下的流程: Android/iOS native app 操作摄像头 -> 获取视频

概述

本文的目的是实现以下的流程:

Android/iOS native app 操作摄像头 -> 获取视频流数据 -> 人脸检测或美颜 -> 传输给 Unity 渲染 -> Unity做出更多的效果(滤镜/粒子)

简单通信

在之前的博客里已经说到,Unity 和安卓通信最简单的方法是用 UnitySendMessage 等 api 实现。

Android调用Unity:


//向unity发消息
UnityPlayer.UnitySendMessage("Main Camera", //gameobject的名字
                             "ChangeColor", //调用方法的名字
                             "");			//参数智能传字符串,没有参数则传空字符串

Unity调用Android:


//通过该API来实例化java代码中对应的类
AndroidJavaObject jc = new AndroidJavaObject("com.xxx.xxx.UnityPlayer");
jo.Call("Test");//调用void Test()方法
jo.Call("Text1", msg);//调用string Test1(string str)方法
jo.Call("Text2", 1, 2);//调用int Test1(int x, int y)方法

所以按理来说我们可以通过 UnitySendMessage 将每一帧的数据传给 Unity,只要在 onPreviewFrame 这个回调里执行就能跑通。


@Override public void onPreviewFrame(byte[] data, Camera camera){
    // function trans data[] to Unity
}

但是,且不说 UnitySendMessage 只能传递字符串数据(必然带来的格式转换的开销), onPreviewFrame() 回调方法也涉及到从GPU拷贝到CPU的操作,总的流程相当于下图所示,用屁股想都知道性能太低了。既然我们的最终目的都是传到GPU上让Unity渲染线程渲染,那何不直接在GPU层传递纹理数据到Unity。

获取和创建Context

于是我们开始尝试从 Unity 线程中拿到 EGLContext 和 EGLConfig ,将其作为参数传递给 Java线程 的 eglCreateContext() 方法创建 Java 线程的 EGLContext ,两个线程就相当于共享 EGLContext 了

先在安卓端写好获取上下文的方法 setupOpenGL() ,供 Unity 调用(代码太长,if 里的 check 的代码已省略)


// 创建单线程池,用于处理OpenGL纹理
private final ExecutorService mRenderThread = Executors.newSingleThreadExecutor();

private volatile EGLContext mSharedEglContext;
private volatile EGLConfig mSharedEglConfig;

// 被unity调用获取EGLContext,在Unity线程执行
public void setupOpenGL {
    Log.d(TAG, "setupOpenGL called by Unity ");

    // 获取Unity线程的EGLContext,EGLDisplay
    mSharedEglContext = EGL14.eglGetCurrentContext();
    if (mSharedEglContext == EGL14.EGL_NO_CONTEXT) {...}
    EGLDisplay sharedEglDisplay = EGL14.eglGetCurrentDisplay();
    if (sharedEglDisplay == EGL14.EGL_NO_DISPLAY) {...}
    
    // 获取Unity绘制线程的EGLConfig
    int[] numEglConfigs = new int[1];
    EGLConfig[] eglConfigs = new EGLConfig[1];
    if (!EGL14.eglGetConfigs(sharedEglDisplay, eglConfigs, 0, 
                             eglConfigs.length,numEglConfigs, 0)) {...}

    mSharedEglConfig = eglConfigs[0];
    mRenderThread.execute(new Runnable() {	// Java线程内
        @Override
        public void run() {
            // Java线程初始化OpenGL环境
            initOpenGL();
            // 生成OpenGL纹理ID
            int textures[] = new int[1];
            GLES20.glGenTextures(1, textures, 0);
            if (textures[0] == 0) {...}
            mTextureID = textures[0];
            mTextureWidth = 670;
            mTextureHeight = 670;
        }
    });
}

在 Java 线程内初始化 OpenGL 环境


private void initOpenGL() {
    mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
    if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {...}

    int[] version = new int[2];
    if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {...}

    int[] eglContextAttribList = new int[]{
        EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, // 版本需要与Unity使用的一致
        EGL14.EGL_NONE
    };

    // 将Unity线程的EGLContext和EGLConfig作为参数,传递给eglCreateContext,
    // 创建Java线程的EGLContext,从而实现两个线程共享EGLContext
    mEglContext = EGL14.eglCreateContext(
        mEGLDisplay, mSharedEglConfig, mSharedEglContext,
        eglContextAttribList, 0);
    if (mEglContext == EGL14.EGL_NO_CONTEXT) {...}

    int[] surfaceAttribList = {
        EGL14.EGL_WIDTH, 64,
        EGL14.EGL_HEIGHT, 64,
        EGL14.EGL_NONE
    };

    // Java线程不进行实际绘制,因此创建PbufferSurface而非windowsurface
    // 将Unity线程的EGLConfig作为参数传递给eglCreatePbufferSurface
    // 创建Java线程的EGLSurface
    mEglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mSharedEglConfig, surfaceAttribList, 0);
    if (mEglSurface == EGL14.EGL_NO_SURFACE) {...}
    if (!EGL14.eglMakeCurrent(
        mEGLDisplay, mEglSurface, mEglSurface, mEglContext)) {...}

    GLES20.glFlush();
}

共享纹理

共享context完成后,两个线程就可以共享纹理了。只要让 Unity 线程拿到将 Java 线程生成的纹理 id ,再用 CreateExternalTexture() 创建纹理渲染出即可,C#代码如下:


public class GLTexture : MonoBehaviour
{
    private AndroidJavaObject mGLTexCtrl;
    private int mTextureId;
    private int mWidth;
    private int mHeight;

    private void Awake(){
        // 实例化com.xxx.nativeandroidapp.GLTexture类的对象
        mGLTexCtrl = new AndroidJavaObject("com.xxx.nativeandroidapp.GLTexture");
        // 初始化OpenGL
        mGLTexCtrl.Call("setupOpenGL");
    }

    void Start(){
        BindTexture();
    }
    
    void BindTexture(){
        // 获取 Java 线程生成的纹理ID
        mTextureId = mGLTexCtrl.Call<int>("getStreamTextureID");
        if (mTextureId == 0) {...}
        mWidth = mGLTexCtrl.Call<int>("getStreamTextureWidth");
        mHeight = mGLTexCtrl.Call<int>("getStreamTextureHeight");
        // 创建纹理并绑定到当前GameObject上
        material.mainTexture = 
            Texture2D.CreateExternalTexture(
            	mWidth, mHeight, 
            	TextureFORMat.ARGB32, 
            	false, false, 
            	(IntPtr)mTextureId);
        // 更新纹理数据
        mGLTexCtrl.Call("updateTexture");
    }
}

unity需要调用updateTexture方法更新纹理


public void updateTexture() {
    //Log.d(TAG,"updateTexture called by unity");
    mRenderThread.execute(new Runnable() { //java线程内
        @Override
        public void run() {
            String imageFilePath = "your own picture path"; //图片路径
            final Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);
            GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
            GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
            bitmap.recycle();//回收内存
        }
    });
}

同时注意必须关闭unity的多线程渲染,否则无法获得Unity渲染线程的EGLContext(应该有办法,小弟还没摸索出来),还要选择对应的图形 API,我们之前写的是 GLES3,如果我们写的是 GLES2,就要换成 2 。

然后就可以将 Unity 工程打包到安卓项目,如果没意外是可以显示纹理出来的。

如果没有成功可以用 glGetError() 一步步检查报错,按上面的流程应该是没有问题的

视频流RTT

那么如果把图片换成 camera 视频流的话呢?上述的方案假定 Java 层更新纹理时使用的是 RGB 或 RBGA 格式的数据,但是播放视频或者 camera 预览这种应用场景下,解码器解码出来的数据是 YUV 格式,Unity 读不懂这个格式的数据,但是问题不大,我们可以编写 Unity Shader 来解释这个数据流(也就是用 GPU 进行格式转换了)

另一个更简单的做法是通过一个 FBO 进行转换:先让 camera 视频流渲染到 SurfaceTexture 里(SurfaceTexture 使用的是 GL_TEXTURE_EXTERNAL_OES ,Unity不支持),再创建一份 Unity 支持的 GL_Texture2D 。待 SurfaceTexture 有新的帧后,创建 FBO,调用 glFramebufferTexture2D 将 GL_Texture2D 纹理与 FBO 关联起来,这样在 FBO 上进行的绘制,就会被写入到该纹理中。之后和上面一样,再把 Texutrid 返回给 unity ,就可以使用这个纹理了。这就是 RTT Render To Texture。


private SurfaceTexture mSurfaceTexture; //camera preview
private GLTextureOES mTextureOES;       //GL_TEXTURE_EXTERNAL_OES
private GLTexture2D mUnityTexture;      //GL_TEXTURE_2D 用于在Unity里显示的贴图
private FBO mFBO;						//具体代码在GitHub仓库

public void openCamera() {
	......
    
    // 利用OpenGL生成OES纹理并绑定到mSurfaceTexture
    // 再把camera的预览数据设置显示到mSurfaceTexture,OpenGL就能拿到摄像头数据。
    mTextureOES = new GLTextureOES(UnityPlayer.currentActivity, 0,0);
    mSurfaceTexture = new SurfaceTexture(mTextureOES.getTextureID());
    mSurfaceTexture.setOnFrameAvailableListener(this);
    try {
        mCamera.setPreviewTexture(mSurfaceTexture);
    } catch (IOException e) {
        e.printStackTrace();
    }
    mCamera.startPreview();
}

SurfaceTexture 更新后(可以在 onFrameAvailable 回调内设置 bool mFrameUpdated = true; )让 Unity 调用这个 updateTexture() 获取纹理 id 。


public int updateTexture() {
    synchronized (this) {
        if (mFrameUpdated) { mFrameUpdated = false; }
        mSurfaceTexture.updateTexImage();
        int width = mCamera.getParameters().getPreviewSize().width;
        int height = mCamera.getParameters().getPreviewSize().height;

        // 根据宽高创建Unity使用的GL_TEXTURE_2D纹理
        if (mUnityTexture == null) {
            Log.d(TAG, "width = " + width + ", height = " + height);
            mUnityTexture = new GLTexture2D(UnityPlayer.currentActivity, width, height);
            mFBO = new FBO(mUnityTexture);
        }
        Matrix.setIdentityM(mMVPMatrix, 0);
        mFBO.FBOBegin();
        GLES20.glViewport(0, 0, width, height);
        mTextureOES.draw(mMVPMatrix);
        mFBO.FBOEnd();

        Point size = new Point();
        if (Build.VERSION.SDK_INT >= 17) {
            UnityPlayer.currentActivity.getWindowManager().getDefaultDisplay().getRealSize(size);
        } else {
            UnityPlayer.currentActivity.getWindowManager().getDefaultDisplay().getSize(size);
        }
        GLES20.glViewport(0, 0, size.x, size.y);

        return mUnityTexture.getTextureID();
    }
}

详细的代码可以看这个 demo,简单封装了下。

跑通流程之后就很好办了,Unity 场景可以直接显示camera预览

这时候你想做什么效果都很简单了,比如用 Unity Shader 写一个赛博朋克风格的滤镜:

shader代码


Shader "Unlit/CyberpunkShader"
{
	Properties
	{
		_MainTex("Base (RGB)", 2D) = "white" {}
		_Power("Power", Range(0,1)) = 1
	}
	SubShader
	{
		Tags { "RenderType" = "Opaque" }
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			struct a2v 
			{
				float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
			};

			struct v2f 
			{
				float4 vertex : SV_POSITION;
				half2 texcoord : TEXCOORD0;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _Power;

			v2f vert(a2v v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				fixed4 baseTex = tex2D(_MainTex, i.texcoord);
				float3 xyz = baseTex.rgb;
				float oldx = xyz.x;
				float oldy = xyz.y;
				float add = abs(oldx - oldy)*0.5;
				float stepxy = step(xyz.y, xyz.x);
				float stepyx = 1 - stepxy;
				xyz.x = stepxy * (oldx + add) + stepyx * (oldx - add);
				xyz.y = stepyx * (oldy + add) + stepxy * (oldy - add);
				xyz.z = sqrt(xyz.z);
				baseTex.rgb = lerp(baseTex.rgb, xyz, _Power);
				return baseTex;
			}
			ENDCG
		}
	}
	Fallback off
}

还有其他粒子效果也可以加入,比如Unity音量可视化——粒子随声浪跳动

纹理取回

在安卓端取回纹理也是可行的,我没有写太多,这里做了一个示例,在 updateTexture() 加入这几行


// 创建读出的GL_TEXTURE_2D纹理
if (mUnityTextureCopy == null) {
    Log.d(TAG, "width = " + width + ", height = " + height);
    mUnityTextureCopy = new GLTexture2D(UnityPlayer.currentActivity, size.x, size.y);
    mFBOCopy = new FBO(mUnityTextureCopy);
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mUnityTextureCopy.mTextureID);
GLES20.glCopyTexSubImage2D(GLES20.GL_TEXTURE_2D, 0,0,0,0,0,size.x, size.y);
mFBOCopy.FBOBegin();
// //test是否是当前FBO
// GLES20.glClearColor(1,0,0,1);
// GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// GLES20.glFinish();
int mImageWidth = size.x;
int mImageHeight = size.y;
Bitmap dest = Bitmap.createBitmap(mImageWidth, mImageHeight, Bitmap.Config.ARGB_8888);
final ByteBuffer buffer = ByteBuffer.allocateDirect(mImageWidth * mImageHeight * 4);
GLES20.glReadPixels(0, 0, mImageWidth, mImageHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer);
dest.copyPixelsFromBuffer(buffer);
dest = null;//断点
mFBOCopy.FBOEnd();

在 dest = null; 打个断点,就能在 android studio 查看当前捕捉下来的 Bitmap,是 Unity 做完效果之后的。

以上就是详解Unity安卓共享纹理的详细内容,更多关于Unity安卓共享纹理的资料请关注编程网其它相关文章!

--结束END--

本文标题: 详解Unity安卓共享纹理

本文链接: https://www.lsjlt.com/news/127247.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 详解Unity安卓共享纹理
    目录概述简单通信获取和创建Context共享纹理视频流RTT纹理取回概述 本文的目的是实现以下的流程: Android/iOS native app 操作摄像头 -> 获取视频...
    99+
    2024-04-02
  • Unity配置安卓环境(详细、简单)
    为unity安装和配置安卓打包环境,这里我是以2019版本的unity为例子,其他版本也一样的方式。 因为步骤详细,所以内容稍稍有点长,实际只要按照步骤操作是很简单的。 安卓环境安装配置有很多办法,大...
    99+
    2023-09-11
    unity android 游戏引擎
  • nginx共享内存的机制详解
    目录1 共享内存申请2 共享内存实现原理2.1 共享内存组织2.2 slab共享内存管理机制2.3 slab与ngx_shm_zone_t 关系3 共享内存应用1 共享内存申请 共享...
    99+
    2024-04-02
  • 详解Android Ashmem匿名共享内存
    目录1. 简述2. 创建 MemoryFile 和 数据写入3. 将文件描述符传递到其他进程4. 在其他进程接收 FileDescriptor 并读取数据1. 简述 Android...
    99+
    2024-04-02
  • Unity 之 安卓平台上架隐私问题解决方案
    Unity 之 助力游戏增长 -- 解决隐私问题 一,平台测试隐私问题二,解决方式一2.1 勾选自定义Mainifest2.2 修改自定义Mainifest2.3 隐私协议弹窗逻辑 三,...
    99+
    2023-10-05
    unity 游戏引擎 隐私协议
  • 微信小程序如何实现数据共享与方法共享详解
    目录全局数据共享 Moboxnpm安装及其注意事项小程序对 npm 的支持与限制npm 依赖包的安装与使用Mobox组件方法共享 behaviors1. 什么是 behaviors2...
    99+
    2024-04-02
  • Ubuntu和windows文件共享问题详解
    Ubuntu和Windows文件共享问题 ubuntu访问windows共享文件夹(ubuntu桌面系统): 最简单的方法,随便打开一个文件 www.jb51.net sudo apt-get install samba...
    99+
    2023-05-26
    Ubuntu和windows文件共享问题 文件共享 问题 windows Ubuntu
  • 详解win10文件共享设置方法
    有小伙伴想要给自己的win10系统设置文件共享,这样可以获取到公司电脑的一些共享文件,但是自己之前没有操作过,不知道win10如何设置文件共享怎么办。下面小编教下大家win10文件共享设置方法。步骤一:启用网络发现1、打开桌面上的“文件资源...
    99+
    2023-07-10
  • 【体系结构】共享SQL的理解
    1.  共享 SQL 作用 SQL 在共享之后,就可以减少硬解析,硬解析会消耗很多资源。 2.  共享 SQL 概念 共...
    99+
    2024-04-02
  • 怎么 理解ORACLE的游标共享
    这篇文章主要讲解了“怎么 理解ORACLE的游标共享”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么 理解ORACLE的游标共享”吧!游标共享(curso...
    99+
    2024-04-02
  • SpringMVC域对象共享数据示例详解
    目录SpringMVC域对象共享数据一、域对象1. 域对象的作用2. 域对象生命周期3. 使用原则二、向域对象共享数据1. 向 request 域对象共享数据2. 向 session...
    99+
    2024-04-02
  • Vue组件之间的数据共享详解
    目录一、在项目开发中,组件之间的最常见的关系分为如下两种:1.1父子组件之间的数据共享1. 父->子共享数据2.子->父共享数据1.2兄弟组件之间的数据共享总结一、在项目...
    99+
    2024-04-02
  • Vue 生命周期和数据共享详解
    目录1.组件的生命周期1.1生命周期与生命周期函数 1.2组件生命周期函数的分类1.3生命周期图示以及详解2.组件之间的数据共享2.1组件之间的关系2.2父向子传值2.3子...
    99+
    2024-04-02
  • win7共享文件夹设置方法详解
    在同一局域网内设置共享文件夹可以增加文件的传输和共享的便利性,提高办公效率。不同系统设置共享文件夹的方法各不相同,有些网友对如何在win7上设置共享文件夹表示不清楚。下面小编教下大家win7共享文件夹设置方法。具体步骤如下:1、首先开启gu...
    99+
    2023-07-16
  • 详解win7win10共享文件夹设置方法
    在同一公司内都是公用一个局域网,有时候经常需要共享些文件夹等,这个时候就需要给电脑设置局域网共享文件夹操作了。有网友想了解win7win10如何设置共享文件夹,下面小编就教下大家win7win10共享文件夹设置方法。1、准备win7或者wi...
    99+
    2023-07-13
  • java Semaphore共享锁实现原理解析
    目录正文Semaphore内部类及继承关系Semaphore.acquire流程分析(以非公平锁为例)tryAcquireShareddoAcquireSharedInterrupt...
    99+
    2023-01-09
    Semaphore共享锁 java 锁
  • Win11 安卓子系统 2305今日更新: 邀请测试文件共享功能
    微软今天邀请所有频道的 Windows Insider 项目成员,在 Win11 系统上测试 Windows Subsystem for android™ 更新,最新版本号为 2305.40000.4.0。 ...
    99+
    2023-06-13
    Win11 安卓 子系统
  • 详解React 代码共享最佳实践方式
    任何一个项目发展到一定复杂性的时候,必然会面临逻辑复用的问题。在React中实现逻辑复用通常有以下几种方式:Mixin、高阶组件(HOC)、修饰器(decorator)、Render...
    99+
    2024-04-02
  • Windows 7与vista的网络共享方法详解
    一般大部分人在Windows7/Vista共享文件为了方便都去掉了密码保护的共享。这样在共享文件夹时就需要添加Everyone或Guest权限。一般共享文件夹时是以向导的形式,添加用户设置权限都很简单,不会有问题。但看到...
    99+
    2023-05-25
    Windows7 vista 网络共享 详解 Windows 7与vista 方法
  • 详解Angular项目中共享模块的实现
    目录一、共享CommonModule二、共享MaterialModule三、共享ConfirmDialog一、共享CommonModule 创建share Modele:ng g m...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作