yww*_*ynm 9 android opengl-es unity-game-engine
我目前正在为Unity制作Android播放器插件.基本的想法是我将MediaPlayer在Android上播放视频,它提供了一个setSurface接收SurfaceTexture构造函数参数的API ,并最终与OpenGL-ES纹理绑定.在大多数其他情况下,例如显示图像,我们可以将这个纹理以指针/ id的形式发送到Unity,调用Texture2D.CreateExternalTexture它来生成一个Texture2D对象并将其设置为UI GameObject来渲染图片.然而,当涉及到显示视频帧时,它有点不同,因为Android上的视频播放需要类型的纹理,GL_TEXTURE_EXTERNAL_OES而Unity仅支持通用类型GL_TEXTURE_2D.
为了解决这个问题,我已经google了一段时间,并且知道我应该采用一种名为"Render to texture"的技术.更明确地说,我应该产生2层的纹理,一个用于MediaPlayer和SurfaceTextureAndroid中获得的团结也应该有内部的图像数据的视频帧和另一个.第一个应该是GL_TEXTURE_EXTERNAL_OES(我们简称为OES纹理)和第二个类型GL_TEXTURE_2D(让我们称之为2D纹理).这两个生成的纹理在开始时都是空的.当绑定时MediaPlayer,OES纹理将在视频播放期间更新,然后我们可以使用a FrameBuffer在2D纹理上绘制OES纹理的内容.
我写了这个过程的纯Android版本,当我最终在屏幕上绘制2D纹理时,它的效果非常好.但是,当我将其作为Unity Android插件发布并在Unity上运行相同的代码时,将不会显示任何图片.相反,它只显示预设颜色glClearColor,这意味着两件事:
FrameBuffer- > 2D纹理的转移过程完成,Unity确实接收到最终的2D纹理.因为glClearColor只有在我们绘制OES纹理的内容时才会调用它FrameBuffer.glClearColor,因为我们没有看到视频帧图片.事实上,我也在glReadPixels绘图之后和之前调用之前调用FrameBuffer,它将从FrameBuffer我们绑定的数据中读取数据.它返回单色的值,与我们设置的颜色相同glClearColor.为了简化我应该在这里提供的代码,我将通过绘制三维到2D纹理FrameBuffer.如果我们能够找出哪个部分是错误的,那么我们就可以轻松地解决类似的问题来绘制视频帧.
该函数将在Unity上调用:
  public int displayTriangle() {
    Texture2D texture = new Texture2D(UnityPlayer.currentActivity);
    texture.init();
    Triangle triangle = new Triangle(UnityPlayer.currentActivity);
    triangle.init();
    TextureTransfer textureTransfer = new TextureTransfer();
    textureTransfer.tryToCreateFBO();
    mTextureWidth = 960;
    mTextureHeight = 960;
    textureTransfer.tryToInitTempTexture2D(texture.getTextureID(), mTextureWidth, mTextureHeight);
    textureTransfer.fboStart();
    triangle.draw();
    textureTransfer.fboEnd();
    // Unity needs a native texture id to create its own Texture2D object
    return texture.getTextureID();
  }
2D纹理的初始化:
  protected void initTexture() {
    int[] idContainer = new int[1];
    GLES30.glGenTextures(1, idContainer, 0);
    textureId = idContainer[0];
    Log.i(TAG, "texture2D generated: " + textureId);
    // texture.getTextureID() will return this textureId
    bindTexture();
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
        GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
        GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
    GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D,
        GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
    GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D,
        GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
    unbindTexture();
  }
  public void bindTexture() {
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
  }
  public void unbindTexture() {
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
  }
draw()的Triangle:
  public void draw() {
    float[] vertexData = new float[] {
        0.0f,  0.0f, 0.0f,
        1.0f, -1.0f, 0.0f,
        1.0f,  1.0f, 0.0f
    };
    vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(vertexData);
    vertexBuffer.position(0);
    GLES30.glClearColor(0.0f, 0.0f, 0.9f, 1.0f);
    GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
    GLES30.glUseProgram(mProgramId);
    vertexBuffer.position(0);
    GLES30.glEnableVertexAttribArray(aPosHandle);
    GLES30.glVertexAttribPointer(
        aPosHandle, 3, GLES30.GL_FLOAT, false, 12, vertexBuffer);
    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 3);
  }
顶点着色器Triangle:
attribute vec4 aPosition;
void main() {
  gl_Position = aPosition;
}
片段着色器Triangle:
precision mediump float;
void main() {
  gl_FragColor = vec4(0.9, 0.0, 0.0, 1.0);
}
关键代码TextureTransfer:
  public void tryToInitTempTexture2D(int texture2DId, int textureWidth, int textureHeight) {
    if (mTexture2DId != -1) {
      return;
    }
    mTexture2DId = texture2DId;
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTexture2DId);
    Log.i(TAG, "glBindTexture " + mTexture2DId + " to init for FBO");
    // make 2D texture empty
    GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, textureWidth, textureHeight, 0,
        GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
    Log.i(TAG, "glTexImage2D, textureWidth: " + textureWidth + ", textureHeight: " + textureHeight);
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
    fboStart();
    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0,
        GLES30.GL_TEXTURE_2D, mTexture2DId, 0);
    Log.i(TAG, "glFramebufferTexture2D");
    int fboStatus = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER);
    Log.i(TAG, "fbo status: " + fboStatus);
    if (fboStatus != GLES30.GL_FRAMEBUFFER_COMPLETE) {
      throw new RuntimeException("framebuffer " + mFBOId + " incomplete!");
    }
    fboEnd();
  }
  public void fboStart() {
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOId);
  }
  public void fboEnd() {
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
  }
最后在Unity端的一些代码:
int textureId = plugin.Call<int>("displayTriangle");
Debug.Log("native textureId: " + textureId);
Texture2D triangleTexture = Texture2D.CreateExternalTexture(
  960, 960, TextureFormat.RGBA32, false, true, (IntPtr) textureId);
triangleTexture.UpdateExternalTexture(triangleTexture.GetNativeTexturePtr());
rawImage.texture = triangleTexture;
rawImage.color = Color.white;
好吧,上面的代码不会显示预期的三角形,只会显示蓝色背景.glGetError几乎每个OpenGL函数调用后都会添加,而不会抛出任何错误.
我的Unity版本是2017.2.1.对于Android版本,我关闭实验性多线程渲染,其他设置都是默认设置(没有纹理压缩,不使用开发构建,等等).我的应用程序的最低API级别为5.0 Lollipop,目标API级别为9.0 Pie.
我真的需要一些帮助,提前谢谢!
现在我找到了答案:如果你想在插件中进行任何绘图工作,你应该在本机层进行。因此,如果你想制作一个 Android 插件,你应该调用 OpenGL-ES API,而JNI不是 Java 端。原因是Unity只允许在其渲染线程上绘制图形。如果您像问题描述中那样简单地调用 OpenGL-ES API,就像我在 Java 端所做的那样,它们实际上将在 Unity 主线程而不是渲染线程上运行。Unity 提供了一种方法,GL.IssuePluginEvent可以在渲染线程上调用您自己的函数,但它需要本机编码,因为该函数需要函数指针作为其回调。这是一个使用它的简单示例:
侧面JNI:
// you can copy these headers from https://github.com/googlevr/gvr-unity-sdk/tree/master/native_libs/video_plugin/src/main/jni/Unity
#include "IUnityInterface.h"
#include "UnityGraphics.h"
static void on_render_event(int event_type) {
  // do all of your jobs related to rendering, including initializing the context,
  // linking shaders, creating program, finding handles, drawing and so on
}
// UnityRenderingEvent is an alias of void(*)(int) defined in UnityGraphics.h
UnityRenderingEvent get_render_event_function() {
  UnityRenderingEvent ptr = on_render_event;
  return ptr;
}
// notice you should return a long value to Java side
extern "C" JNIEXPORT jlong JNICALL
Java_com_abc_xyz_YourPluginClass_getNativeRenderFunctionPointer(JNIEnv *env, jobject instance) {
  UnityRenderingEvent ptr = get_render_event_function();
  return (long) ptr;
}
在 Android Java 端:
class YourPluginClass {
  ...
  public native long getNativeRenderFunctionPointer();
  ...
}
在 Unity 方面:
private void IssuePluginEvent(int pluginEventType) {
  long nativeRenderFuncPtr = Call_getNativeRenderFunctionPointer(); // call through plugin class
  IntPtr ptr = (IntPtr) nativeRenderFuncPtr;
  GL.IssuePluginEvent(ptr, pluginEventType); // pluginEventType is related to native function parameter event_type
}
void Start() {
  IssuePluginEvent(1); // let's assume 1 stands for initializing everything
  // get your texture2D id from plugin, create Texture2D object from it,
  // attach that to a GameObject, and start playing for the first time
}
void Update() {
  // call SurfaceTexture.updateTexImage in plugin
  IssuePluginEvent(2); // let's assume 2 stands for transferring TEXTURE_EXTERNAL_OES to TEXTURE_2D through FrameBuffer
  // call Texture2D.UpdateExternalTexture to update GameObject's appearance
}
您仍然需要传输纹理,并且有关它的所有内容都应该在JNI图层上进行。但不用担心,它们与我在问题描述中所做的几乎相同,但只是使用与 Java 不同的语言,并且有很多关于此过程的材料,因此您一定可以做到。
最后让我再次解决解决这个问题的关键:在本机层做你本机的事情,不要沉迷于纯 Java...我很惊讶没有博客/答案/wiki 告诉我们只写我们的 C++ 代码。尽管有一些开源实现,例如 Google 的 gvr-unity-sdk,它们提供了完整的参考,但您仍然会怀疑是否可以在不编写任何 C++ 代码的情况下完成任务。现在我们知道我们不能。不过,说实话,我认为 Unity 有能力让这一进展变得更加容易。
| 归档时间: | 
 | 
| 查看次数: | 1077 次 | 
| 最近记录: |