具有纹理对象和采样器的多纹理理论

Multitexturing theory with texture objects and samplers

我找不到关于如何仅使用纹理对象或纹理对象加采样器来编写多纹理代码的任何好的理论文章。我只是不知道如何管理 glActiveTexture 函数以及它到底做了什么。

glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0 + 0); // Number between 0 and GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img.getSize().x, img.getSize().y, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.getPixelsPtr()); // Not in sampler
glGenerateMipmap(GL_TEXTURE_2D); // Not in sampler

/* Values associated with the texture and not with sampler (sampler has priority over texture).
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);*/

glGenSamplers(1, &textureSampler);
glBindSampler(0, textureSampler);
glSamplerParameteri(textureSampler, GL_TEXTURE_WRAP_S, GL_REPEAT);
glSamplerParameteri(textureSampler, GL_TEXTURE_WRAP_T, GL_REPEAT);
glSamplerParameteri(textureSampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(textureSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

glUniform1i(glGetUniformLocation(colorShader->program, "textureSampler"), 0); // 0 pour GL_TEXTURE0

我有点困惑,多纹理是关于在片段代码中有多个采样器链接到多个纹理,还是可能只有一个采样器有多个纹理?

之前肯定已经解释过其中的大部分内容,但让我试着给出一个概述,希望能更清楚地说明所有不同部分是如何组合在一起的。我将首先分别解释每一部分,然后解释它们是如何连接的。

纹理目标

这是指不同类型的纹理(2D、3D 等)。你可以有多个纹理,每个纹理类型一个,同时绑定到同一个纹理单元。例如,之后:

glBindTexture(GL_TEXTURE_2D, texId1);
glBindTexture(GL_TEXTURE_3D, texId2);

texId1texId2都将绑定到同一个纹理单元,这是可能的,因为它们绑定到不同的目标。

这方面的细节有些复杂和混乱,我不会在这个答案的其余部分考虑它。我建议您始终将不同的纹理绑定到不同的纹理单元。它会让您免于头痛和意外。

纹理对象

纹理对象的名称是用 glGenTextures() 创建的,它们是用 glBindTexture() 绑定的,等等。纹理对象拥有:

  • 纹理数据。
  • 定义纹理数据采样方式的状态,例如使用 glTexParameteri() 设置的过滤属性。

它们还包含有关与数据一起指定的纹理 format/type 的信息。

纹理单元

作为当前 OpenGL 状态的一部分,您可以绘制 table 当前绑定的纹理。我们需要同时绑定多个纹理以支持多纹理。一个纹理单元可以看作是这种状态下的一个条目table.

您使用 glActiveTexture() 指定当前活动的纹理单元。需要对特定纹理单元进行操作的调用将对活动纹理单元进行操作。例如:

glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, texId);

texId 绑定到纹理单元 3。再次描绘绑定纹理的 table,第 4 个条目(编号从 0 开始)现在指向纹理 texId

采样器对象

这是 OpenGL 3.3 及更高版本中可用的一种新型对象。对于大多数用例,您将不需要,即使它们涉及从多个纹理采样。为了完整起见,我将它们包括在这里,但在您牢牢掌握纹理对象和纹理单元之前,无需担心采样器。

还记得我在上面解释过纹理对象拥有纹理数据,以及定义数据采样方式的状态吗?采样器本质上所做的是将这两个方面分离。采样器对象包含可以覆盖纹理对象中采样相关状态的状态。

这允许您在同一个着色器中使用 不同的采样参数 采样 单个纹理 。假设您想在单个着色器中对同一纹理进行线性和最近采样。没有采样器对象,如果没有相同纹理的多个副本(数据的多个副本),您将无法做到这一点。采样器对象启用了这种功能。

纹理视图

这是 OpenGL 4.3 中引入的功能。甚至不仅仅是纹理采样器,我只是为了完整性才提到它。

在采样器将纹理数据(及其相关格式)与采样参数分离的情况下,纹理视图将原始纹理数据与格式分离。它们使得使用不同格式的相同原始纹理数据成为可能。我怀疑您可以在不使用此功能的情况下走很长一段路。

拼凑

您最终想要做的是指定着色器应该从哪些纹理中采样。纹理单元是在着色器和纹理之间建立联系的关键部分。

从着色器的侧面看,着色器知道它从哪些纹理单元采样。这是由采样器统一变量的值给出的。例如,如果 "MyFirstTexture" 是着色器代码中采样器变量的名称,则以下指定该变量与纹理单元 3 关联:

GLint loc = glGetUniformLocation(prog, "MyFirstTexture");
glUniform1i(loc, 3);

纹理单元和纹理对象之间的关联是使用上面已经显示的代码片段建立的:

glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, texId);

这两个部分是在着色器代码中将纹理连接到采样器变量的关键部分。请注意,统一变量的值是纹理单元的索引(3),而glActiveTexture() 的参数是相应的枚举(GL_TEXTURE3)。我认为这是不幸的 API 设计,但你必须习惯它。

一旦你理解了这一点,你就会很清楚如何在着色器中使用多个纹理(又名 "multi-texturing"):

  • 您的着色器代码中有多个采样器变量。
  • 您进行 glUniform1i() 调用以将采样器变量的值设置为不同纹理单元的索引。
  • 您将纹理绑定到每个匹配的纹理单元。

显示这两个纹理,使用纹理单元 0 和 1:

glUseProgram(prog);

GLint loc = glGetUniformLocation(prog, "MyFirstTexture");
glUniform1i(loc, 0);
loc = glGetUniformLocation(prog, "MySecondTexture");
glUniform1i(loc, 1);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texId0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texId1);

另一种看待这个问题的方式是,着色器中的采样器变量和纹理对象之间存在一定程度的间接关系。着色器与纹理对象没有直接联系。相反,它有一个 table 纹理对象的索引(其中该索引是统一变量的值),而这个 table 又包含 "pointers" 纹理对象(其中table 条目填充有 glActiveTexture()/glBindTexture()`。

或者同一件事的最后一个类比,使用通信术语:您可以将纹理单元视为端口。您告诉着色器从哪些端口读取数据(统一变量的值)。然后将纹理插入端口(通过将其绑定到纹理单元)。着色器现在将从您插入端口的纹理中读取数据。

每个纹理对象中都包含一个默认的采样器对象,当没有采样器对象绑定到相应的采样器单元时,将用于从纹理中读取。为了修改这个对象的参数,提供了类似的glTexParameter函数。