gpt4 book ai didi

android - Android上的OpenGL绘图结合Unity通过帧缓冲区传输纹理无法工作

转载 作者:塔克拉玛干 更新时间:2023-11-02 08:42:49 29 4
gpt4 key购买 nike

我目前正在为 Unity 制作一个 Android 播放器插件。基本思路是我将播放 MediaPlayer 的视频。在 Android 上,它提供了 setSurface API 接收 SurfaceTexture作为构造函数参数并最终与 OpenGL-ES 纹理绑定(bind)。在大多数其他情况下,例如显示图像,我们可以将这个纹理以指针/id 的形式发送给 Unity,调用 Texture2D.CreateExternalTexture在那里生成一个Texture2D对象并将其设置为 UI GameObject渲染图片。但是,在显示视频帧时,它与 略有不同。在 Android 上播放视频需要 GL_TEXTURE_EXTERNAL_OES 类型的纹理而 Unity 只支持通用类型 GL_TEXTURE_2D .

为了解决这个问题,我google了一段时间,知道应该采用一种叫做“渲染到纹理”的技术。更清楚地说,我应该生成 2 个纹理,一个用于 MediaPlayerSurfaceTexture在 Android 中接收视频帧,另一个用于 Unity,其中也应该包含图片数据。第一个应该是 GL_TEXTURE_EXTERNAL_OES 的类型(我们简称为 OES 纹理)和 GL_TEXTURE_2D 类型的第二个纹理(我们称之为 2D 纹理)。这两个生成的纹理一开始都是空的。当与 MediaPlayer 绑定(bind)时, OES 纹理将在视频播放期间更新,然后 我们可以使用 FrameBuffer在 2D 纹理上绘制 OES 纹理的内容。

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

  • OES纹理的转移过程-> FrameBuffer -> 2D 纹理已完成,Unity 确实收到了最终的 2D 纹理。因为glClearColor仅当我们将 OES 纹理的内容绘制到 FrameBuffer 时才调用.
  • glClearColor 之后发生的绘图过程中出现了一些错误,因为我们看不到视频帧图片。其实我也打glReadPixels绘制之后和解除绑定(bind)之前 FrameBuffer ,它将从 FrameBuffer 中读取数据我们绑定(bind)。它返回与我们在 glClearColor 中设置的颜色相同的单色值.

  • 为了简化我应该在此处提供的代码,我将通过 FrameBuffer 将三角形绘制为 2D 纹理。 .如果我们能弄清楚哪一部分是错误的,那么我们就可以很容易地解决类似的问题来绘制视频帧。

    该函数将在 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 的 fragment 着色器:
    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插件,你应该在JNI调用OpenGL-ES APIs。而不是Java端。原因是 Unity 只允许在其渲染线程上绘制图形。如果您像问题描述中那样简单地调用 OpenGL-ES API,就像我在 Java 端所做的那样,它们实际上将在 Unity 主线程上运行,而不是在渲染线程上运行。 Unity提供了一个方法,GL.IssuePluginEvent , 在渲染线程上调用您自己的函数,但它需要 native 编码,因为此函数需要函数指针作为其回调。这是一个使用它的简单示例:

    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 不同,并且有很多关于这个过程的 Material ,所以你肯定可以做到。

    最后让我再次解决这个问题的关键: 在原生层做你的原生工作,不要沉迷于纯 Java...我很惊讶没有博客/答案/wiki 告诉我们只需用 C++ 编写代码。虽然有一些像 Google 的 gvr-unity-sdk 这样的开源实现,它们提供了一个完整的引用,但你仍然会怀疑你是否可以在不编写任何 C++ 代码的情况下完成任务。现在我们知道我们做不到。但是,老实说,我认为 Unity 有能力使这一进展变得更加容易。

    关于android - Android上的OpenGL绘图结合Unity通过帧缓冲区传输纹理无法工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52088859/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com