詳解Unity安卓共享紋理_第1頁
詳解Unity安卓共享紋理_第2頁
詳解Unity安卓共享紋理_第3頁
詳解Unity安卓共享紋理_第4頁
詳解Unity安卓共享紋理_第5頁
已閱讀5頁,還剩8頁未讀 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第詳解Unity安卓共享紋理目錄概述簡單通信獲取和創(chuàng)建Context共享紋理視頻流RTT紋理取回

概述

本文的目的是實現以下的流程:

Android/iOSnativeapp操作攝像頭-獲取視頻流數據-人臉檢測或美顏-傳輸給Unity渲染-Unity做出更多的效果(濾鏡/粒子)

簡單通信

在之前的博客里已經說到,Unity和安卓通信最簡單的方法是用UnitySendMessage等API實現。

Android調用Unity:

//向unity發(fā)消息

UnityPlayer.UnitySendMessage("MainCamera",//gameobject的名字

"ChangeColor",//調用方法的名字

"");//參數智能傳字符串,沒有參數則傳空字符串

Unity調用Android:

//通過該API來實例化java代碼中對應的類

AndroidJavaObjectjc=newAndroidJavaObject("com.xxx.xxx.UnityPlayer");

jo.Call("Test");//調用voidTest()方法

jo.Call("Text1",msg);//調用stringTest1(stringstr)方法

jo.Call("Text2",1,2);//調用intTest1(intx,inty)方法

所以按理來說我們可以通過UnitySendMessage將每一幀的數據傳給Unity,只要在onPreviewFrame這個回調里執(zhí)行就能跑通。

@OverridepublicvoidonPreviewFrame(byte[]data,Cameracamera){

//functiontransdata[]toUnity

}

但是,且不說UnitySendMessage只能傳遞字符串數據(必然帶來的格式轉換的開銷),onPreviewFrame()回調方法也涉及到從GPU拷貝到CPU的操作,總的流程相當于下圖所示,用屁股想都知道性能太低了。既然我們的最終目的都是傳到GPU上讓Unity渲染線程渲染,那何不直接在GPU層傳遞紋理數據到Unity。

獲取和創(chuàng)建Context

于是我們開始嘗試從Unity線程中拿到EGLContext和EGLConfig,將其作為參數傳遞給Java線程的eglCreateContext()方法創(chuàng)建Java線程的EGLContext,兩個線程就相當于共享EGLContext了

先在安卓端寫好獲取上下文的方法setupOpenGL(),供Unity調用(代碼太長,if里的check的代碼已省略)

//創(chuàng)建單線程池,用于處理OpenGL紋理

privatefinalExecutorServicemRenderThread=Executors.newSingleThreadExecutor();

privatevolatileEGLContextmSharedEglContext;

privatevolatileEGLConfigmSharedEglConfig;

//被unity調用獲取EGLContext,在Unity線程執(zhí)行

publicvoidsetupOpenGL{

Log.d(TAG,"setupOpenGLcalledbyUnity");

//獲取Unity線程的EGLContext,EGLDisplay

mSharedEglContext=EGL14.eglGetCurrentContext();

if(mSharedEglContext==EGL14.EGL_NO_CONTEXT){...}

EGLDisplaysharedEglDisplay=EGL14.eglGetCurrentDisplay();

if(sharedEglDisplay==EGL14.EGL_NO_DISPLAY){...}

//獲取Unity繪制線程的EGLConfig

int[]numEglConfigs=newint[1];

EGLConfig[]eglConfigs=newEGLConfig[1];

if(!EGL14.eglGetConfigs(sharedEglDisplay,eglConfigs,0,

eglConfigs.length,numEglConfigs,0)){...}

mSharedEglConfig=eglConfigs[0];

mRenderThread.execute(newRunnable(){//Java線程內

@Override

publicvoidrun(){

//Java線程初始化OpenGL環(huán)境

initOpenGL();

//生成OpenGL紋理ID

inttextures[]=newint[1];

GLES20.glGenTextures(1,textures,0);

if(textures[0]==0){...}

mTextureID=textures[0];

mTextureWidth=670;

mTextureHeight=670;

}

在Java線程內初始化OpenGL環(huán)境

privatevoidinitOpenGL(){

mEGLDisplay=EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);

if(mEGLDisplay==EGL14.EGL_NO_DISPLAY){...}

int[]version=newint[2];

if(!EGL14.eglInitialize(mEGLDisplay,version,0,version,1)){...}

int[]eglContextAttribList=newint[]{

EGL14.EGL_CONTEXT_CLIENT_VERSION,3,//版本需要與Unity使用的一致

EGL14.EGL_NONE

//將Unity線程的EGLContext和EGLConfig作為參數,傳遞給eglCreateContext,

//創(chuàng)建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線程不進行實際繪制,因此創(chuàng)建PbufferSurface而非WindowSurface

//將Unity線程的EGLConfig作為參數傳遞給eglCreatePbufferSurface

//創(chuàng)建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()創(chuàng)建紋理渲染出即可,C#代碼如下:

publicclassGLTexture:MonoBehaviour

privateAndroidJavaObjectmGLTexCtrl;

privateintmTextureId;

privateintmWidth;

privateintmHeight;

privatevoidAwake(){

//實例化com.xxx.nativeandroidapp.GLTexture類的對象

mGLTexCtrl=newAndroidJavaObject("com.xxx.nativeandroidapp.GLTexture");

//初始化OpenGL

mGLTexCtrl.Call("setupOpenGL");

voidStart(){

BindTexture();

voidBindTexture(){

//獲取Java線程生成的紋理ID

mTextureId=mGLTexCtrl.Callint("getStreamTextureID");

if(mTextureId==0){...}

mWidth=mGLTexCtrl.Callint("getStreamTextureWidth");

mHeight=mGLTexCtrl.Callint("getStreamTextureHeight");

//創(chuàng)建紋理并綁定到當前GameObject上

material.mainTexture=

Texture2D.CreateExternalTexture(

mWidth,mHeight,

TextureFormat.ARGB32,

false,false,

(IntPtr)mTextureId);

//更新紋理數據

mGLTexCtrl.Call("updateTexture");

}

unity需要調用updateTexture方法更新紋理

publicvoidupdateTexture(){

//Log.d(TAG,"updateTexturecalledbyunity");

mRenderThread.execute(newRunnable(){//java線程內

@Override

publicvoidrun(){

StringimageFilePath="yourownpicturepath";//圖片路徑

finalBitmapbitmap=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讀不懂這個格式的數據,但是問題不大,我們可以編寫UnityShader來解釋這個數據流(也就是用GPU進行格式轉換了)

另一個更簡單的做法是通過一個FBO進行轉換:先讓camera視頻流渲染到SurfaceTexture里(SurfaceTexture使用的是GL_TEXTURE_EXTERNAL_OES,Unity不支持),再創(chuàng)建一份Unity支持的GL_Texture2D。待SurfaceTexture有新的幀后,創(chuàng)建FBO,調用glFramebufferTexture2D將GL_Texture2D紋理與FBO關聯起來,這樣在FBO上進行的繪制,就會被寫入到該紋理中。之后和上面一樣,再把Texutrid返回給unity,就可以使用這個紋理了。這就是RTTRenderToTexture。

privateSurfaceTexturemSurfaceTexture;//camerapreview

privateGLTextureOESmTextureOES;//GL_TEXTURE_EXTERNAL_OES

privateGLTexture2DmUnityTexture;//GL_TEXTURE_2D用于在Unity里顯示的貼圖

privateFBOmFBO;//具體代碼在github倉庫

publicvoidopenCamera(){

......

//利用OpenGL生成OES紋理并綁定到mSurfaceTexture

//再把camera的預覽數據設置顯示到mSurfaceTexture,OpenGL就能拿到攝像頭數據。

mTextureOES=newGLTextureOES(UnityPlayer.currentActivity,0,0);

mSurfaceTexture=newSurfaceTexture(mTextureOES.getTextureID());

mSurfaceTexture.setOnFrameAvailableListener(this);

try{

mCamera.setPreviewTexture(mSurfaceTexture);

}catch(IOExceptione){

e.printStackTrace();

mCamera.startPreview();

}

SurfaceTexture更新后(可以在onFrameAvailable回調內設置boolmFrameUpdated=true;)讓Unity調用這個updateTexture()獲取紋理id。

publicintupdateTexture(){

synchronized(this){

if(mFrameUpdated){mFrameUpdated=false;}

mSurfaceTexture.updateTexImage();

intwidth=mCamera.getParameters().getPreviewSize().width;

intheight=mCamera.getParameters().getPreviewSize().height;

//根據寬高創(chuàng)建Unity使用的GL_TEXTURE_2D紋理

if(mUnityTexture==null){

Log.d(TAG,"width="+width+",height="+height);

mUnityTexture=newGLTexture2D(UnityPlayer.currentActivity,width,height);

mFBO=newFBO(mUnityTexture);

Matrix.setIdentityM(mMVPMatrix,0);

mFBO.FBOBegin();

GLES20.glViewport(0,0,width,height);

mTextureOES.draw(mMVPMatrix);

mFBO.FBOEnd();

Pointsize=newPoint();

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);

returnmUnityTexture.getTextureID();

}

詳細的代碼可以看這個demo,簡單封裝了下。

跑通流程之后就很好辦了,Unity場景可以直接顯示camera預覽

這時候你想做什么效果都很簡單了,比如用UnityShader寫一個賽博朋克風格的濾鏡:

shader代碼

Shader"Unlit/CyberpunkShader"

Properties

_MainTex("Base(RGB)",2D)="white"{}

_Power("Power",Range(0,1))=1

SubShader

Tags{"RenderType"="Opaque"}

Pass

CGPROGRAM

#pragmavertexvert

#pragmafragmentfrag

#include"UnityCG.cginc"

structa2v

float4vertex:POSITION;

float2texcoord:TEXCOORD0;

structv2f

float4vertex:SV_POSITION;

half2texcoord:TEXCOORD0;

sampler2D_MainTex;

float4_MainTex_ST;

float_Power;

v2fvert(a2vv)

v2fo;

o.vertex=UnityObjectToClipPos(v.vertex);

o.texcoord=TRANSFORM_TEX(v.texcoord,_MainTex);

returno;

fixed4frag(v2fi):SV_Target

fixed4baseTex=tex2D(_MainTex,i.texcoord);

float3xyz=baseTex.rgb;

floatoldx=xyz.x;

floatoldy=xyz.y;

floatadd=abs(oldx-oldy)*0.5;

floatstepxy=step(xyz.y,xyz.x);

floatstepyx=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);

returnbaseTex;

ENDCG

Fallbackoff

}

還有其他粒子效果也可以加入,比如Unity音量可視化——粒子隨聲浪跳動

紋理取回

在安卓端取回紋理也是可行的,我沒有寫太多,這里做了一個示例,在updateTexture()加入這幾行

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論