SurfaceTexture(外部图像)和普通纹理的Android OpenGL组合

Posted

技术标签:

【中文标题】SurfaceTexture(外部图像)和普通纹理的Android OpenGL组合【英文标题】:Android OpenGL combination of SurfaceTexture (external image) and ordinary texture 【发布时间】:2012-11-02 19:08:23 【问题描述】:

我想将相机预览SurfaceTexture 与一些叠加纹理混合。我正在使用这些着色器进行处理:

private final String vss = "attribute vec2 vPosition;\n"
        + "attribute vec2 vTexCoord;\n"
        + "varying vec2 texCoord;\n"
        + "void main() \n" 
        + "  texCoord = vTexCoord;\n"
        + "  gl_Position = vec4 ( vPosition.x, vPosition.y, 0.0, 1.0 );\n"
        + "";

private final String fss = "#extension GL_OES_EGL_image_external : require\n"
        + "precision mediump float;\n"
        + "uniform samplerExternalOES sTexture;\n"
        + "uniform sampler2D filterTexture;\n"
        + "varying vec2 texCoord;\n"
        + "void main() \n"
        +"  vec4 t_camera = texture2D(sTexture,texCoord);\n"
        //+"  vec4 t_overlayer = texture2D(filterTexture, texCoord);\n" 
        //+ "  gl_FragColor = t_overlayer;\n" + "";
        + "  gl_FragColor = t_camera;\n" + "";

我的目标是混合t_camerat_overlayer。当我分别显示t_camerat_overlayer 时,它可以工作(显示相机预览或纹理)。但是当我取消注释t_overlayer 时,t_camera 会变成黑色(不知何故采样不好)。我的覆盖层纹理是 512x512 和 CLAMPT_TO_EDGE。 此问题仅在以下情况下发生:android Emulator、HTC Evo 3D。 但在 SGS3、HTC One X 上,它运行良好。

怎么了?是 Evo 3D 缺少一些扩展还是什么?

【问题讨论】:

您提到它不适用于Android Emulator。您尝试了哪个操作系统版本?你试过 x86 吗? 我在 MacOSX 10.8.2 上使用 x64 模拟器。 【参考方案1】:

我想你有这个问题是因为你没有在你的代码上设置正确的纹理 ID。这是一个看似合乎逻辑的假设的典型错误,但实际上并未在文档中如此定义。如果您查看此扩展程序的文档,您会看到以下(已编辑)文本:

每个 TEXTURE_EXTERNAL_OES 纹理对象最多可能需要 3 个纹理 它绑定到的每个纹理单元的图像单元。什么时候 设置为 TEXTURE_EXTERNAL_OES 此值将介于 1 和 3 之间 (包括的)。对于其他有效的纹理目标,此值将始终为 1.注意,当绑定一个TEXTURE_EXTERNAL_OES纹理对象时,单个纹理单元所需的纹理图像单元数可能为 1、2 或 3,而对于其他纹理对象,每个纹理单元都需要 正好 1 个纹理图像单元。

这意味着,如果您使用 id 0,至少还有一个可以工作。在你的情况下:

GLES20.glUniform1i(sTextureHandle, 1);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
        sTextureId);

对于您的 2D 纹理:

GLES20.glUniform1i(filterTextureHandle, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, filterTextureID);

我相信这会为你锻炼。

【讨论】:

好吧,我遇到了完全相同的问题,并且 GL_TEXTURE_EXTERNAL_OES 和纹理 ID 排序的东西根本不起作用。我想和其他人一样,这是一个与硬件相关的问题,不同的手机对此的支持不同。 目前尚不清楚“您使用 id 0”中的“id”是什么意思。我在制服中看到 0,它不是 id 而是一个单位。一个纹理id在sTextureId和filterTextureId处,我们不能随意设置。 嗨 Leon,我的意思是: sTextureId = GL_TEXTURE1 这又需要 glUniform1i(sTextureHandle, 1);对于着色器。 0 也一样。不确定“我们不能任意设置”是什么意思。我只是通过使用 0 和 1 告诉着色器谁是 GL_TEXTURE0 和 GL_TEXTURE1。对我有用。【参考方案2】:

我在 Nexus 7 上遇到了同样的问题,这让我发疯了。访问 samplerExternalOES 或 sampler2D 工作得很好,但是在同一个着色器中访问它们会产生意想不到的结果。有时输出会是黑色的。有时,其中一个查找的输出会出现不良的量化伪影。行为也会因采样器绑定到的纹理单元而异。我确实检查了每个 opengl 错误并验证程序结果。

最终,起作用的是使用单独的着色器来简单地访问相机输出并将其渲染到纹理中。然后可以通过常规 sampler2D 访问生成的纹理,并且一切都按预期工作。我怀疑某处存在与 samplerExternalOES 相关的错误。

【讨论】:

那么如何将相机缓冲区传递给 OpenGL?我只能通过 SurfaceTexture 找到,这迫使我使用 samplerExternalOES。 想法:我正在使用 onPreviewFrame 捕获相机缓冲区,缓冲区是 YUV21 格式。然后我将缓冲区分成缓冲区 - Y、U、V - YUV21 格式很简单。然后我使用纹理将这些缓冲区上传到 GPU - GL_ALPHA。在片段着色器中,我有 3 个统一的 sample2d(y,u,v)。然后在片段着色器中,我从 YUV->RGB 进行了以下转换: 浮动 y = texture2D(yTexture, texCoord).a;浮动 u = texture2D(uTexture, texCoord).a;浮动 v = texture2D(vTexture, texCoord).a; y=1.1643*(y-0.0625); u=u-0.5; v=v-0.5;浮动 r=y+1.5958*v;浮动 g=y-0.39173*u-0.81290*v;浮动 b=y+2.017*u; 它运行得非常快。将 YUV21 分离到缓冲区只是 memcpy(在 CPU 中完成)。 YUV->RGB 转换在 GPU=fast 我猜想在使用这种方法时与设备特定的专有编解码器存在兼容性问题,因为不同的芯片会产生不同的 YUV 格式。【参考方案3】:

上面的方法节省了我很多时间。谢谢大师:

GLES20.glUniform1i(sTextureHandle, 1);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
    sTextureId);

对于您的 2D 纹理:

GLES20.glUniform1i(filterTextureHandle, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, filterTextureID);

改变纹理索引是解决这个问题的好方法。

【讨论】:

这是关于使用 OES 纹理的最高 INT 吗? “从上面”是什么意思?哦,对不起帽子。它在阅读“从上面”时自动激活。 :)【参考方案4】:

这似乎是 OpenGL 实现中的一个错误。相同的代码在三星 Note 上运行良好,而不是在 nexus 4 上运行良好。对于位于 samplerExternalOES 之后的所有变量,getUniformLocation 似乎在某些设备上中断。

编译器似乎也按字母顺序对统一变量进行排序,因此使其在两种设备上都能正常工作的解决方案是将您的 samplerExternalEoz 重命名为 zzzTexture 或其他东西。

【讨论】:

解决方法是在某些设备(例如:Motorola Razr)上设置为aaaTexture,在其他设备(例如:Xperia)上设置为zzzTexture【参考方案5】:

这不是一个答案,而是对问题的详细说明 - 也许它会帮助 OpenGL ES 专家了解问题。


我有 3 个用于叠加的纹理和一个用于捕获媒体播放器输出的外部纹理。如果我只使用外部纹理,输出是预期的,来自 MPlayer 的帧。相同的完整代码在 Nexus4、Samsung Galaxy S3、S4 等设备上运行良好(所有设备都使用 adreno gpus 或 Arm 的 Mali400) 硬件差异在于 Nexus 7 使用 Nvidia Tegra 3 板。


编辑 (我这边是怎么解决的)

Nvidia Tegra 3 要求外部纹理采样器以采样器中字母顺序最低的名称调用,而 Adreno 220 似乎需要相反的名称。此外,T3 要求最后对外部纹理进行采样。对于使用 Android 4.3 及更高版本的设备,这些错误可能会得到解决。在 Nvidia 方面,这是一个错误,很久以前就解决了,但后来才更新 Nexus 驱动程序。所以我必须检查哪个 gpu 存在,并相应地调整代码。

【讨论】:

我认为4.3没有解决它。太疯狂了。这是 Nvidia Tegra 3 与 - 任何其他 - 吗?【参考方案6】:

参考 user1924406 的帖子 (https://***.com/a/14050597/3250829) 关于拆分访问 sampler2D 纹理和 samplerExternalOES 纹理,这是我必须做的,因为我正在开发的应用程序是从文件读取或从服务器流式传输而不是使用设备上的相机。在同一个着色器中使用这两种纹理会导致非常奇怪的着色伪影(Galaxy S3 上的情况)或饱和度和对比度问题(Nexus 4 上的情况)。

因此,解决 samplerExternalOES 纹理错误的唯一方法(据我目前所见)是执行两个着色器程序:一个将 samplerExternalOES 纹理中包含的内容写入 FBO,另一个它从 FBO 获取内容并将其直接写入表面。

您需要检查的一件事是,有时当您写入 FBO 时,纹理坐标会翻转。在我的例子中,V(或 T 或 Y)坐标被翻转,从而在水平轴上形成镜像。在第二阶段编写片段着色器时,我必须考虑到这一点。

这是一个我想分享的战争故事,以防有些人可能需要从服务器读取文件或流,而不是直接从相机中读取。

【讨论】:

在大约一百万年的时间里搜索了您如何实现这一目标,请允许我删除此链接。 github.com/harism/android_instacam/blob/master/src/fi/harism/… @LéonPelletier - 你本可以给我留言并询问我的代码 :) 顺便说一句,谢谢你的链接。很高兴在那里看到它。 我会在家里试试。我只是想知道如果应用于视频流,它在 FPS 方面会是什么样子。我计划让 Android MediaPlayer 和 ios AVFoundation 将 YUV 帧发送到一个常见的 OpenTK 便携式应用程序,并使用上面提到的流程(fbo 到纹理)来应用着色器。这将使跨平台视频效果变得非常简单。 @LéonPelletier 链接已失效。这是基于 harism 的另一个很好的例子:github.com/lynntech/Wide-Angle-Mobile-Basics 看起来只是帐户名称发生了变化。 github.com/KhalidElSayed/android_instacam【参考方案7】:

为了将外部纹理(非 GPU)转换为常规内部纹理,您必须

首先创建一个外部纹理(与创建常规纹理相同,但将所有GLES20.GL_TEXTURE_2D 替换为GLES11Ext.GL_TEXTURE_EXTERNAL_OES)。 创建要写入的内部/常规纹理 用外部纹理包裹SurfaceTexture。 将内容读入外部纹理(如果来自相机、文件等) 覆盖该 SurfaceTexture 的 onFrameAvailable 并在此处进行转换,提供要读取的外部纹理和要写入的内部纹理。 您可能需要调用getTransformMatrix 进行坐标校正(通常是翻转y 轴)并提供它。有时不...

这里有一些示例着色器:

顶点-shdaer -

uniform mat4 transform; // might be needed, and might not
uniform mat4 modelview;
uniform mat4 projection;
attribute vec2 position;

varying vec2 vTexcoord;

void main() 
    gl_Position = projection * modelview * vec4(position.xy, 0.0, 1.0);
    // texture takes points in [0,1], while position is [-1,1];
    vec4 newpos = (gl_Position + 1.0) * 0.5;
    vTexcoord = (transform * newpos).xy ;

片段着色器 -

#extension GL_OES_EGL_image_external : require

precision mediump float;

uniform samplerExternalOES sTexture;
varying vec2 vTexcoord;

void main() 
    gl_FragColor = texture2D(sTexture, vTexcoord);

【讨论】:

使用“getTransformMatrix”解决了非常奇怪的显示问题。我觉得一定要用! 我不记得了……但这可能取决于其他因素(可能是设备)【参考方案8】:

我可能也有同样的问题。经过几天的尝试,我在这里提出了我的解决方案。希望这对其他人有所帮助。

首先,问题陈述。就像 Lukáš Jezný 一样,我有一个预览纹理和一个叠加纹理。它适用于 nexus 4/5 和大多数其他类型,但在 OPPO find 5、Lenovo A820、Lenovo A720 上没有显示任何内容。

解决方案:

(1)就像 Lukáš Jezný 一样,使用 YUV 数据并在着色器中将它们转换为 RGB。

(2)多遍绘制,将预览纹理绘制到帧缓冲区一次,并读取它,然后将其再次绘制到屏幕上。

(3)在使用自己的程序之前先使用另一个程序,

    GLES20.glUseProgram(another one);
    GLES20.glUseProgram(your "real" program);

它只适用于 OPPO find 5、联想 A820、联想 A720 等。没有人知道为什么......

【讨论】:

以上是关于SurfaceTexture(外部图像)和普通纹理的Android OpenGL组合的主要内容,如果未能解决你的问题,请参考以下文章

SurfaceTexture详解

Android OpenGL camera2纹理分辨率

Android OpenGL camera2 纹理分辨率

Android:将 SurfaceTexture 附加到 FrameBuffer

android OpenGLES 1.x CameraPreview 到 Surfacetexture

在具有目标 GL_TEXTURE_2D 的纹理上渲染相机预览