如何在现代OpenGL中渲染多个纹理?

14

我目前正在为一个小游戏编写2D引擎。

我的想法是只需一次绘制调用就能呈现整个场景。我认为可以在四边形上渲染每个2D图像,这意味着我可以使用实例化技术。

我想象中的顶点着色器可能看起来像这样:

...
in vec2 pos;
in mat3 model;
in sampler2d tex;
in vec2 uv;
...

我曾以为我可以在GPU上加载纹理,并像处理VBO一样获取它的句柄,但事实证明这并不简单。

看起来我需要调用:

glActiveTexture(GL_TEXTURE0..N);

对于我想要加载的每个纹理。现在这似乎不像我想象的那么容易编程。现代游戏引擎如何呈现多个纹理?

我读到GL_TEXTURE的纹理限制取决于GPU,但至少为45。如果我想呈现由超过45个纹理组成的图像,例如90个纹理怎么办?

看来我必须呈现前45个纹理并从GPU中删除所有纹理,然后将另外45个纹理从硬盘加载到GPU中。这在每一帧都做起来似乎不是很合理。特别是当我想要动画化一个2D图像时。

我可以轻松想象一个简单的2D角色动画可能由10个不同的图像组成。这意味着我很容易超过纹理限制。

我的一个小想法是将多个图像合并成一个巨大的图像,然后通过UV坐标进行偏移。

我想知道我是否误解了OpenGL中纹理的工作方式。

您会如何在OpenGL中呈现多个纹理?


你不需要删除旧纹理,只需绑定新的纹理。同时也要考虑纹理集。 - ratchet freak
3
“小点子”实际上是一种非常常见的技术,在许多游戏中使用。GPU 在处理大型纹理方面表现得相当出色。 - Wander Nauta
我记得有很多类似的问题,但现在找不到重复的了。你的所有纹理都是相同的尺寸吗?你提到的末尾的想法大多称为“纹理集”,这是其中之一的选项。 - Reto Koradi
2
实际上,纹理图像单元的数量不是至少45个。在现代OpenGL中,它的最小值恰好等于16 * *<Num Shader Stages>*。这意味着在GL 3.3中,它是16 * 3(顶点、几何和片段)= 48,在GL 4.5中,它是16 * 5(顶点、镶嵌控制、镶嵌评估、几何和片段)= 80。这是为了让您可以绑定一组16个独特的纹理以供每个阶段使用,但实际上,在一个最小的实现中,您不能在单个着色器执行中采样超过16个纹理。 - Andon M. Coleman
我在这里发布了一个答案:http://stackoverflow.com/questions/16075791/drawing-multiple-objects-with-different-textures/34620543#34620543 - user2288580
user2288580,你是@NicolBolas吗?那个7/7飞行生物,没有人能承担它的维持费用?! - duhaime
2个回答

29
问题比较广泛,以下是使用同一绘制调用中多个纹理的一些选项的简要概述。
绑定到多个纹理单元
对于这种方法,您将每个纹理绑定到不同的纹理单元,使用典型的顺序:
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, tex[i]);

在着色器中,你可以有一堆单独的sampler2D uniforms,或者一个sampler2D uniforms数组。
这样做的主要缺点是你受到可用纹理单元数量的限制。
数组纹理
你可以使用数组纹理。这是通过使用GL_TEXTURE_2D_ARRAY纹理目标来实现的。在很多方面,2D纹理数组类似于3D纹理。它基本上是一堆2D纹理堆叠在一起,并存储在一个单一的纹理对象中。
缺点是所有纹理都需要具有相同的大小。如果它们不是,你必须使用最大的尺寸作为纹理数组的尺寸,并且会浪费较小纹理的内存。如果尺寸不都相同,你还需要对纹理坐标进行缩放。
纹理图集
这就是你已经提出的想法。你将所有纹理存储在一个大的纹理中,并使用纹理坐标来控制使用哪个纹理。
虽然这是一种流行的方法,但也存在一些技术挑战。在使用线性采样时,您必须小心处理纹理之间的接缝,以防止它们相互渗透。而且,与纹理数组不同,这种方法允许使用不同的纹理尺寸而不浪费内存,但在图集中分配区域变得更加棘手,因为尺寸是可变的。
无绑定纹理
目前,这仅作为扩展功能提供:ARB_bindless_texture

12

你需要了解纹理单元和纹理对象的区别。

纹理单元就像 OpenGL 光栅化器中的“纹理插槽”。光栅化器有一定数量的“插槽”(称为纹理单元)。要将纹理加载到纹理单元中,您首先需要使用 glActiveTexture 选择要使用的单元,然后使用 glBindTexture 加载纹理“插槽”(即纹理对象)。

您可以拥有的纹理对象数量仅受系统内存(和存储能力)的限制,但是在同一时间只能将有限量的纹理“插槽”到纹理单元中。

采样器就像对纹理单元的“取样器”。着色器内的不同采样器可以从相同的纹理单元中“取样”。通过将采样器 uniform 设置为一个纹理单元,您选择要从哪个单元中取样。

此外,您还可以将同一纹理同时“插槽”到多个纹理单元中。

更新(一些澄清)

我看到 GL_TEXTURE 的纹理限制取决于 GPU,但至少为 45。如果我想呈现由超过 45 个纹理组成的图像,比如 90 个呢?

通常您不会尝试使用单个绘制调用来渲染整个图像。捕捉所有情况下要使用哪些纹理实际上是不可能的。通常您会为特定外观的“材料”编写着色器。假设您有一个模拟金属上涂漆的着色器。您将拥有 3 种纹理:金属、油漆和控制金属和油漆可见性的调制纹理。然后该着色器将具有 3 个采样器 uniform,每个纹理一个。要呈现具有该外观的表面,您需要

  • 选择要使用的着色器程序(glUseProgram
  • 依次激活每个纹理单元中的纹理 (glActiveTexture(GL_TEXTURE_0+i)) 并绑定纹理('glBindTexture`)
  • 将采样器的uniform设置为要使用的纹理单元 (glUniform1i(…, i))。
  • 绘制几何图形。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接