在中我们介绍了多线程OpenGL绘制方案,但是如果需要在Java线程不断修改纹理数据,会由于并发访问导致Unity线程出现访问非法内存而崩溃。因此,考虑在Java线程加载数据,然后在unity线程调用OpenGL操作更新纹理。这样所有的OpenGL操作都在Unity绘制线程完成,从而避免了多线程OpenGL引入的各种问题。为了能够从Java线程切换到Unity线程执行,我们获取到Unity线程的Looper,然后使用该Looper实例化一个Handler,这样就可以通过往上发送消息或者Runnable在Unity线程执行任务了。Java代码如下:
1 package com.thornbirds.unity; 2 3 import android.graphics.Bitmap; 4 import android.graphics.BitmapFactory; 5 import android.opengl.GLES10; 6 import android.opengl.GLES11Ext; 7 import android.opengl.GLES20; 8 import android.opengl.GLUtils; 9 import android.os.Handler;10 import android.os.Looper;11 import android.util.Log;12 13 import java.util.concurrent.ExecutorService;14 import java.util.concurrent.Executors;15 16 public class PluginTexture {17 private static final String TAG = "PluginTexture";18 19 private int mTextureID = 0;20 private int mTextureWidth = 0;21 private int mTextureHeight = 0;22 23 // 创建单线程池,用于加载图片资源24 private final ExecutorService mJavaThread = Executors.newSingleThreadExecutor();25 // 使用Unity线程Looper的Handler,用于执行Java层的OpenGL操作26 private Handler mUnityRenderHandler;27 28 public int getStreamTextureWidth() {29 return mTextureWidth;30 }31 32 public int getStreamTextureHeight() {33 return mTextureHeight;34 }35 36 public int getStreamTextureID() {37 return mTextureID;38 }39 40 public PluginTexture() {41 }42 43 private void glLogE(String msg) {44 Log.e(TAG, msg + ", err=" + GLES10.glGetError());45 }46 47 public void setupOpenGL() {48 // 注意:该调用一定是从Unity绘制线程发起49 if (Looper.myLooper() == null) {50 Looper.prepare();51 }52 mUnityRenderHandler = new Handler(Looper.myLooper());53 // 生成OpenGL纹理ID54 int textures[] = new int[1];55 GLES20.glGenTextures(1, textures, 0);56 if (textures[0] == 0) {57 glLogE("glGenTextures failed");58 return;59 }60 mTextureID = textures[0];61 mTextureWidth = 640;62 mTextureHeight = 360;63 }64 65 public void updateTexture() {66 mJavaThread.execute(new Runnable() {67 @Override68 public void run() {69 // 加载图片资源70 String imageFilePath = "/sdcard/test/image.png";71 final Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);72 // 切换到Unity绘制线程更新纹理73 mUnityRenderHandler.post(new Runnable() {74 @Override75 public void run() {76 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);77 GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);78 GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);79 GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);80 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);81 bitmap.recycle();82 }83 });84 }85 });86 }87 88 public void destroy() {89 mJavaThread.shutdownNow();90 mUnityRenderHandler.removeCallbacksAndMessages(null);91 }92 }
至此,我们完整介绍了Unity3D开发中在Android Java Plugin中进行纹理更新的方案和实现方法。基于相同的原理,我们可以很方便地给出iOS Object-C Plugin的实现,此处不在赘述。