




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第詳解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. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 礦井測風工崗位合規(guī)化技術規(guī)程
- 2025年甘肅省民航機場集團校園招聘45人考前自測高頻考點模擬試題及答案詳解1套
- 壓電石英晶體配料裝釜工標準化技術規(guī)程
- 鋼鐵產品質檢工崗位工藝技術規(guī)程
- 船舶涂裝工大數據看板解讀考核試卷及答案
- 鑄管熔煉工設備操作認證考核試卷及答案
- 防銹處理工職業(yè)道德與行為規(guī)范考核試卷及答案
- 自行車與電動自行車裝配工服務標準化考核試卷及答案
- 2025嘉興市秀拓燃氣有限公司招聘2人(二)模擬試卷及參考答案詳解一套
- “百萬英才匯南粵”2025年佛山市高明區(qū)公開招聘中小學教師(第四場)模擬試卷及答案詳解(有一套)
- 《宣講有道:晚清宣講小說的倫理敘事》隨筆
- 信創(chuàng)的基礎知識培訓課件
- 2024年江蘇省常州市武進區(qū)中考三模道德與法治試題
- 臨時工工傷私了協(xié)議書
- 人工造林項目投標方案(技術方案)
- 微生物與單細胞蛋白
- 江蘇開放大學2024年春《公文寫作與處理 050008》第一次作業(yè)(占平時成績的20%)參考答案
- 冠心病PCI術后康復策略
- 通勤車租賃投標方案(技術標)
- 脲的合成方法總結
- 《田螺姑娘》兒童故事ppt課件(圖文演講)
評論
0/150
提交評論