Android上的OpenGL绘图与Unity结合以通过帧缓冲区传输纹理无法正常工作

好吃

我目前正在为Unity制作一个Android播放器插件。基本想法是,我将MediaPlayer在Android上播放视频,该视频提供了一个setSurfaceAPI ,该API接收一个SurfaceTextureas构造函数参数,最后与OpenGL-ES纹理绑定。在大多数其他情况下,例如显示图像,我们可以仅以指针/ id的形式将此纹理发送给Unity,在其中调用Texture2D.CreateExternalTexture以生成一个Texture2D对象并将其设置为UIGameObject以呈现图片。但是,在显示视频帧时,这有点不同,因为在Android上播放视频需要一种纹理类型,GL_TEXTURE_EXTERNAL_OES而Unity仅支持通用类型GL_TEXTURE_2D

为了解决该问题,我已经搜索了一段时间,并且知道应该采用一种称为“渲染到纹理”的技术更明确地说,我应该产生2层的纹理,一个用于MediaPlayerSurfaceTextureAndroid中接收视频帧和另一团结也应具有图像数据内。第一个应该是类型GL_TEXTURE_EXTERNAL_OES(简称为OES纹理),第二个应该是类型GL_TEXTURE_2D(称为2D纹理)。这些生成的纹理在开始时都是空的。当与绑定时MediaPlayer,OES纹理将在视频播放期间更新,然后我们可以使用FrameBuffer在2D纹理上绘制OES纹理的内容。

我已经编写了此过程的纯Android版本,当我最终在屏幕上绘制2D纹理时,它工作得很好。但是,当我将其发布为Unity Android插件并在Unity上运行相同的代码时,将不会显示任何图片。相反,它仅显示来自的预设颜色glClearColor,这意味着两件事:

  1. OES纹理-> FrameBuffer-> 2D纹理的传输过程完成,Unity收到最终的2D纹理。因为glClearColor仅当我们将OES纹理的内容绘制到时才调用FrameBuffer
  2. 在绘制之后发生了一些错误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棒棒糖,目标API级别为9.0 Pie。

我真的需要一些帮助,在此先感谢!

好吃

现在,我找到了答案:如果要在插件中执行任何绘图工作,则应在本机层进行因此,如果您想制作一个Android插件,则应在JNI而不是Java端调用OpenGL-ES API 原因是Unity仅允许在其渲染线程上绘制图形。如果像问题描述那样像我在Java端那样简单地调用OpenGL-ES API,它们实际上将在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 ...我完全感到惊讶的是,没有博客/答案/维基可以告诉我们我们的C ++代码。尽管有一些开源实现,例如Google的gvr-unity-sdk,但它们提供了完整的参考,但是您仍然会怀疑也许无需编写任何C ++代码就可以完成任务。现在我们知道我们做不到。但是,老实说,我认为Unity有能力使这一进步变得更加容易。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

OpenGL:帧缓冲区-绘制纹理-glClearColor

OpenGL:帧缓冲区,不完整的纹理附件

附加到帧缓冲区的OpenGL多样本整数纹理无法正确解析

通过HTTP(通过FFmpeg)流OpenGL帧缓冲区

OpenGL ES2.0 glReadPixels()从渲染缓冲区通过帧缓冲区读取数据

使OpenGL-ES模板缓冲区正常工作

Opengl和Webgl:从附加到当前帧缓冲区的纹理采样

如何从OpenGL中的帧缓冲区纹理中采样像素?

从帧缓冲区对象读取时,OpenGL纹理坐标相反

OpenGL帧缓冲区渲染到纹理不起作用

OpenGL:使用实例化绘图来绘制我正在绘制的帧缓冲区

OpenGL帧缓冲区绑定目标

OpenGL sRGB帧缓冲区奇数

GTK3 GLArea 与 GLFW 的帧缓冲区问题(渲染到纹理):相同的 OpenGL 程序在 GLFW 中工作,但在 GTK3 的 GLArea 中无效

为什么从帧缓冲区创建的纹理无法正确映射

带有FLOAT纹理的帧缓冲区上的webGL 2 readPixels

OpenGL的缓冲区如何工作?

如何访问默认帧缓冲区的纹理

渲染到帧缓冲区/纹理显示为空

OpenGL纹理缓冲区对象的目的是什么?

(OpenGL)如何回读纹理缓冲区?

如何在 OpenGL 中重新定位绑定到帧缓冲区的 2D 纹理?

OpenGL-如何绘制到多样本帧缓冲区,然后将结果用作普通纹理?

OpenGL 3.0:无法在着色器中使用帧缓冲区对象(黑色)

OpenGL ES 2.0在帧缓冲区创建上的无效操作

如何在 OpenGL 中将多个纹理/缓冲区混合到一个纹理/缓冲区中?

无法使用lwjgl和顶点缓冲区渲染纹理

OpenGL:深度附件破坏了帧缓冲区

ARCore OpenGL 帧缓冲区错误 (1286)