繁体   English   中英

如何在 OpenGL ES 中高效加载纹理

[英]How to load textures in OpenGL ES efficiently

我对使用 OpenGL 有非常基本的了解,尤其是在 Android 上。 我正在开发一个使用 OpenGL 的应用程序,以便以快速的方式在全屏图像之间切换(因为使用普通的 Android 框架太慢了)。

我发现为了加载纹理,我需要执行以下操作:

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
textureBuffer = byteBuffer.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
_gl = gl;
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), _imageResourceId);
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();

现在,由于图像是全屏的(而且它们非常大 - 1024x1024),这需要一些时间,特别是因为我需要同时加载其中的 5 个。

所以我需要问一些关于改进代码的技巧的问题,特别是关于高效加载(但如果可能的话,也使用更少的内存):

  1. 如果我将位图解码为没有透明像素,通过使用 RGB_565 格式,它会提高将图像加载到 OpenGL(或解码阶段)的速度吗?

  2. 输入位图的宽度和高度是否必须是 2 的幂,或者我可以让 OpenGL 只获取它想要的部分,哪个会有这个规则? 我的意思是,也许我可以让texImage2D命令只获取位图的一部分?

  3. 也许我什至可以从一开始就只解码这部分(所以如果我有一个 10000x10000 的巨大图像,我只能解码它的 1024x1024 部分并将其提供给 OpenGL)?

  4. 是否可以只加载和显示第一张图像,并在后台加载其余图像? 我听说你不能在一个不是处理它的线程中将位图发送到 OpenGL。 将它们加载到GlRendereronDrawFrame方法上是否GlRenderer ,比如说,它第二次被调用?

  5. 我记得我听说过一个关于将多个图像合并为一个图像(一个接一个,从左到右)的提示,这样它可能会在解码阶段有一些速度提升。 不确定这种技术的名称是什么。 Android 上的 OpenGL ES 仍然可能吗?

  6. 是否可以避免创建 Bitmap 实例并让解码以更原生的方式完成(可能是 NDK )? 根据我的测试,在模拟器上解码 1024x1024 大小的 PNG 文件大约需要 400ms-700ms,而将其发送到 OpenGL 大约需要 50ms-70ms。

  7. 使用 Procrank,我发现 OpenGL 会占用大量内存。 有没有可能让它使用更少的内存? 也许使用更好的纹理类型?

  8. 由于Android可以在多种设备上运行,是否可以在清单中要求运行应用程序需要多少内存,以便内存太小的人无法安装它?


@马吉德马克斯:

  1. 所以以这种方式解码并将其发送到OpenGL就足够了,还是在发送到openGL时我还应该设置一些特殊的东西?

  2. 有没有这样的命令来获取位图的一部分?

  3. 我的意思是,我可以只解码要存储在位图中的文件的一部分,而不是所有位图吗?

  4. 所以我唯一能做的就是在开始时加载所有内容,然后全部使用? 怎么会这样? 游戏如何处理? 我的意思是,它们确实显示了一个又一个阶段,即使看起来像 OpenGL 生成的进度条。 这是非常有问题的。

  5. 我所描述的内容也可以在网络上找到。 例如,网页不是加载多个小图像,而是包含单个图像并映射它的哪个部分应该显示在哪里。 它的另一个名字是sprites 示例在这里

  6. 我知道了。 我想我也应该在看到不再使用时调用 glDeleteTextures ,对吗?

  7. 我怎么做? 我应该在代码中更改什么?

  8. 当我使用大量内存时会发生什么? 当没有可用 RAM 时,是否有虚拟 RAM(可能使用内部存储)之类的东西? 我在 Google IO 视频上听说过它,但似乎他们也不确定答案。 链接在这里 他们只是说当这种事情发生时会发生更多的崩溃。


@马吉德马克斯:

1+2+3。 ?

  1. 这可能有效。 你的意思是我创建了一个新线程来加载位图,然后将结果发送回 OpenGL 线程,它将纹理绑定到位图? 也许我可以对 asyncTask 使用类似的方法,并使用 publishprogress。

  2. 这也是一件好事。 你有任何我应该阅读的链接吗? 或者也许是在我的代码中更改的片段?

  3. 谢谢。

  4. 所以我认为使用 ETC1 是最简单的事情。 但是,它根本不支持透明度,因此根据此链接,我需要为此使用另一个纹理,但我找不到用于此的示例。

  5. 我可以假设我可以使用的可用内存是什么? 例如,我是否可以假设所有 Android 设备都可以为我提供 200MB 的视频内存,我可以将其用于 OpenGL?

  1. 如果我将位图解码为没有透明像素,通过使用 RGB_565 格式,它会提高将图像加载到 OpenGL(或解码阶段)的速度吗?

    是的,如果您不需要透明度 (alpha),请使用没有 alpha 的格式,而更小的格式 (RGB565) 意味着更小的纹理,这肯定会加快解码和加载到 opengl 的速度。

  2. 输入位图的宽度和高度是否必须是 2 的幂,或者我可以让 OpenGL 只获取它想要的部分,哪个会有这个规则? 我的意思是,也许我可以让 texImage2D 命令只获取位图的一部分?

    肯定是的,如果你加载一个宽度/高度非 2 次幂的纹理,opengl 将分配一个下一个 2 次幂的纹理(513/513 纹理将变成 1024/1024),这意味着你正在浪费 vram 内存。 并且您不能命令 opengl 获取图像的一部分,因为“teximage2d”需要加载图像的宽度/高度,并且指定任何其他值都会给您一个损坏的图像(如果不破坏应用程序)。

  3. 也许我什至可以从一开始就只解码这部分(所以如果我有一个 10000x10000 的巨大图像,我只能解码它的 1024x1024 部分并将其提供给 OpenGL)?

    没有意见。

  4. 是否可以只加载并显示第一张图像,而在后台加载其余图像? 我听说你不能在一个不是处理它的线程中将位图发送到 OpenGL。 将它们加载到 GlRenderer 的 onDrawFrame 方法上是否有意义,比如说,它第二次被调用?

    我不确定,已经有一个基于 opengl 的多线程渲染引擎。 并且您可以在发出渲染命令时加载纹理(从其他线程)(至少在本机 C/C++ 中,不确定 java)。 不,永远不要尝试在渲染循环中加载纹理,这是不好的做法。

  5. 我记得我听说过一个关于将多个图像合并为一个图像(一个接一个,从左到右)的提示,这样它可能会在解码阶段有一些速度提升。 不确定这种技术的名称是什么。 Android 上的 OpenGL ES 仍然可能吗?

    不知道你在这里是什么意思

  6. 是否可以避免创建 Bitmap 实例并让解码以更原生的方式完成(可能是 NDK )? 根据我的测试,在模拟器上解码 1024x1024 大小的 PNG 文件大约需要 400ms-700ms,而将其发送到 OpenGL 大约需要 50ms-70ms。

    这是最好的方法,本机代码(C/C++)总是更好

  7. 使用 Procrank,我发现 OpenGL 会占用大量内存。 有没有可能让它使用更少的内存? 也许使用更好的纹理类型?

    (如果你在这里指的是 vram)是的,使用像 ETC1、PVRTC、ATC 这样的纹理压缩格式总是一个好主意,它占用更少的 vram 内存并产生更好的性能。

  8. 由于Android可以在多种设备上运行,是否可以在清单中要求运行应用程序需要多少内存,以便内存太小的人无法安装它?

    考虑切换到后备解决方案(甚至更小的纹理),而不是砍掉低端设备。

编辑:

4..您在一个单独的线程中开始加载内容(图像),该线程向渲染线程指示加载过程的百分比,这可用于绘制加载过程的进度条。

5.. 这个技巧叫做“纹理图集”,它是为了加速渲染而不是加载。

6.. 将解码图像加载到 opengl 纹理后,您可以自由删除解码图​​像(从系统内存中),而不是加载下一个图像。 使用完 opengl 纹理后,您可以将其删除(从视频内存中)。

7.. 纹理压缩是特定于硬件的 opengl 扩展,因此您必须检查纹理压缩扩展是否存在,然后将其与“glCompressedTexImage2D”函数一起使用,而不是“texImage2D”。 PowerVR GPU 的 PVRTC; AMD GPU 的 ATC; Mali GPU 的 ASTC; ETC1 一种标准纹理格式(在 android 2.2+ 中支持)。

8.. 将图像加载到 opengl 纹理后,不再需要该图像。 所以你必须从不需要的内容(图像)中释放 ram。

编辑2:

5.. http://http.download.nvidia.com/developer/NVTextureSuite/Atlas_Tools/Texture_Atlas_Whitepaper.pdf

7.. 在你加载了两个 ETC 纹理后(例如:第一个纹理保存颜色,第二个纹理保存在红色通道中的 alpha)在 opengl 中使用“glCompressedTexImage2D”(比如 textureId1、textureId2),将两个纹理绑定在两个纹理单元:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureId1);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureId2);

然后使用以下片段着色器创建着色器程序:

uniform sampler2D     sTexture1;
uniform sampler2D     sTexture2;
varying mediump vec2  vTexCoord;

void main()
{
    gl_FragColor.rgb = texture2D(sTexture1, vTexCoord).rgb;
    gl_FragColor.a   = texture2D(sTexture2, vTexCoord).r;
}

最后,将两个着色器纹理采样器绑定到两个纹理单元(指向两个 ETC 纹理):

GLint texture1Sampler = glGetUniformLocation(programObject, "sTexture1");
GLint texture2Sampler = glGetUniformLocation(programObject, "sTexture2");

glUniform1i(texture1Sampler, GL_TEXTURE0);
glUniform1i(texture2Sampler, GL_TEXTURE1);

8.. 你不能假设任何事情,你一直在分配纹理和缓冲区(根据需要),直到你得到 GL_OUT_OF_MEMORY,而不是你必须释放其他未使用的资源(纹理,缓冲区,......)。

如果您仍在寻找使用 OpenGL 将位图渲染到 InputSurface 的示例。

我能够让这个工作。
看看我在这里的回答。

https://stackoverflow.com/a/49331192/7602598

https://stackoverflow.com/a/49331352/7602598

https://stackoverflow.com/a/49331295/7602598

我认为您应该查看http://www.andengine.org/它将为您节省大量时间和工作...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM